|
What this is
This file is included in the DevDaily.com
"Java Source Code
Warehouse" project. The intent of this project is to help you "Learn
Java by Example" TM.
Other links
The source code
/*
* Sun Public License Notice
*
* The contents of this file are subject to the Sun Public License
* Version 1.0 (the "License"). You may not use this file except in
* compliance with the License. A copy of the License is available at
* http://www.sun.com/
*
* The Original Code is NetBeans. The Initial Developer of the Original
* Code is Sun Microsystems, Inc. Portions Copyright 1997-2004 Sun
* Microsystems, Inc. All Rights Reserved.
*/
package org.netbeans.modules.search;
import java.awt.Image;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.CharConversionException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import java.util.Set;
import java.util.TreeSet;
import javax.swing.UIManager;
import org.openide.actions.DeleteAction;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataObject;
import org.openide.nodes.AbstractNode;
import org.openide.nodes.Children;
import org.openide.nodes.FilterNode;
import org.openide.nodes.Node;
import org.openide.util.RequestProcessor;
import org.openide.util.Utilities;
import org.openide.util.actions.SystemAction;
import org.openide.xml.XMLUtil;
import org.openidex.search.SearchGroup;
import org.openidex.search.SearchType;
/**
* Children for the result root node.
*
* @author Petr Kuzel
* @author Marian Petras
*/
final class ResultTreeChildren extends Children.Keys
implements Runnable, Observer {
/**
* Keys as found objects for the search.
* Must us esyncronized access to avoid connurent modifications.
*/
private Set keys;
/** Comparator used for sorting children nodes. */
private final Comparator comparator;
/** */
private final ResultModel resultModel;
/** Whether this node has sorted children. */
private boolean sorted = false;
/** */
private boolean hasDetails = false;
/*
* once number of keys gets greater than BATCH_LEVEL,
* start batching expensive setKeys call
*/
private final int BATCH_LEVEL = 61;
private final int BATCH_INTERVAL_MS = 759;
private volatile RequestProcessor.Task batchSetKeys;
private volatile boolean active = false;
private int size = 0;
/** Constructor. */
public ResultTreeChildren(final ResultModel resultModel) {
this.resultModel = resultModel;
this.resultModel.addObserver(this);
keys = resultModel.getSearchGroup().getResultObjects();
this.comparator = new ResultItemsComparator();
}
/** Overrides superclass method. */
protected void addNotify() {
setKeys(Collections.EMPTY_SET);
active = true;
RequestProcessor.getDefault().post(new Runnable() {
public void run() {
synchronized (keys) {
setKeys(keys);
}
}
});
}
/** Overrrides superclass method. */
protected void removeNotify() {
active = false;
setKeys(Collections.EMPTY_SET);
}
/**
* Clear children in fast batch manner, does not touch model.
* Model should be cleaned by client. This approach eliminates costly
* event driven cleanup.
*/
void clear() {
Enumeration en = nodes();
while (en.hasMoreElements()) {
FoundNode node = (FoundNode) en.nextElement();
node.originalDataObject.removePropertyChangeListener(node);
}
dispose();
}
/** Explicit garbage collect request. */
void dispose() {
synchronized (keys) {
keys = Collections.EMPTY_SET;
}
removeNotify();
resultModel.deleteObserver(this);
}
/** Creates nodes. */
protected Node[] createNodes(Object key) {
return new Node[] { createFoundNode(key)};
}
/** Creates result node with carefully cafted children */
private FoundNode createFoundNode(Object foundObject) {
final SearchGroup searchGroup = resultModel.getSearchGroup();
Node node = searchGroup.getNodeForFoundObject(foundObject);
SearchType[] types = searchGroup.getSearchTypes();
// TODO need faster hasDetails check, without creating (and discarding) actual detail nodes
boolean hasDetails = false;
for (int i = 0; i < types.length; i++) {
SearchType searchType = types[i];
Node[] details = searchType.getDetails(node);
if ((details != null) && details.length > 0) {
hasDetails = true;
this.hasDetails = true;
break;
}
}
if (hasDetails) {
return new FoundNode(node, new DetailChildren(node), foundObject);
} else {
return new FoundNode(node, Children.LEAF, foundObject);
}
}
/**
*/
boolean isEmpty() {
return size == 0;
}
/**
* Does any of the nodes have at least one detail node.
*
* @return true if so, otherwise
*/
boolean hasDetails() {
return hasDetails;
}
/**
*/
public void update(Observable o, Object foundObject) {
synchronized (keys) {
keys.add(foundObject);
size++;
}
if (size < BATCH_LEVEL) {
synchronized (keys) {
setKeys(keys); //??? -> sort (sorted);
}
} else {
batchSetKeys(); //much faster
}
}
// do not update keys too often it's rather heavyweight operation
// batch all request that come in BATCH_INTERVAL_MS into one real update
private void batchSetKeys() {
if (batchSetKeys == null) {
batchSetKeys = RequestProcessor.getDefault()
.post(this, BATCH_INTERVAL_MS);
}
}
public void removeFoundObject(Object foundObject) {
boolean removed = false;
synchronized (keys) {
removed = keys.remove(foundObject);
}
if (removed) {
sort(sorted);
}
}
/** Sorts/unsorts the children nodes. */
public void sort(boolean sort) {
Set newKeys;
if (sort) {
newKeys = new TreeSet(comparator);
} else {
newKeys = new HashSet();
}
synchronized (keys) {
newKeys.addAll(keys);
}
setKeys(newKeys);
sorted = sort;
}
/** Getter for sorted property. */
public boolean isSorted() {
return sorted;
}
// called from random request processor thread
public void run() {
batchSetKeys = null;
synchronized (keys) {
if (active) {
setKeys(keys);
}
}
}
/** Node to show in result window. */
final class FoundNode extends FilterNode implements PropertyChangeListener {
/** Original data object if there is any. */
private DataObject originalDataObject;
/** Original found object. */
private Object foundObject;
/**
*/
FoundNode() {
super(Node.EMPTY);
}
/** Use {@link ResultModel#createFoundNode} instead. */
FoundNode(Node node, org.openide.nodes.Children kids, Object foundObject) {
// cut off children, we do not need to show them and they eat memory
super(node, kids);
this.foundObject = foundObject;
this.originalDataObject = (DataObject)
getOriginal().getCookie(DataObject.class);
if (originalDataObject == null) {
return;
}
this.originalDataObject.addPropertyChangeListener(this);
FileObject fileFolder = originalDataObject.getPrimaryFile().getParent();
if (fileFolder != null) {
disableDelegation(DELEGATE_SET_SHORT_DESCRIPTION |
DELEGATE_GET_DISPLAY_NAME | DELEGATE_GET_SHORT_DESCRIPTION);
setShortDescription(""); // NOI18N
}
}
public String getDisplayName() {
FileObject fileFolder = originalDataObject.getPrimaryFile().getParent();
if (fileFolder != null) {
String hint = fileFolder.getPath();
String orig = getOriginal().getDisplayName();
return orig + " " + hint;
} else {
return getOriginal().getDisplayName();
}
}
public String getHtmlDisplayName() {
FileObject fileFolder = originalDataObject.getPrimaryFile().getParent();
if (fileFolder != null) {
String hint = FileUtil.getFileDisplayName(fileFolder);
String orig = getOriginal().getDisplayName();
try {
String color;
if (UIManager.getDefaults().getColor("Tree.selectionBackground").equals( // NOI18N
UIManager.getDefaults().getColor("controlShadow"))) { // NOI18N
color = "Tree.selectionBorderColor"; // NOI18N
} else {
color = "controlShadow"; // NOI18N
}
return "" + orig + " " + XMLUtil.toElementContent(hint); // NOI18N
} catch (CharConversionException e) {
return null;
}
} else {
return getOriginal().getHtmlDisplayName();
}
}
/** Gets system actions for this node. Overrides superclass method.
* Adds RemoveFromSearchAction. */
public SystemAction[] getActions() {
List originalActions = new ArrayList(Arrays.asList(super.getActions()));
int deleteIndex = originalActions.indexOf(SystemAction.get(DeleteAction.class));
SystemAction removeFromSearch = SystemAction.get(RemoveFromSearchAction.class);
if (deleteIndex != -1) {
originalActions.add(deleteIndex, removeFromSearch);
} else {
originalActions.add(null);
originalActions.add(removeFromSearch);
}
return (SystemAction[])originalActions.toArray(new SystemAction[originalActions.size()]);
}
/** Action performer. Removes node from search result window (and model). Note: it doesn't delete the original. */
public void removeFromSearch() {
if (originalDataObject != null) {
originalDataObject.removePropertyChangeListener(this);
}
ResultTreeChildren.this.removeFoundObject(foundObject);
}
/** Destroys node and it's file. Overrides superclass method. */
public void destroy() throws IOException {
super.destroy();
if (originalDataObject != null) {
originalDataObject.removePropertyChangeListener(this);
}
ResultTreeChildren.this.removeFoundObject(foundObject);
}
/** Implements PropertyChangeListener litening or originalDataObject. */
public void propertyChange(PropertyChangeEvent evt) {
if (DataObject.PROP_VALID.equals(evt.getPropertyName())) {
// data object might be deleted
if (!originalDataObject.isValid()) { //link becomes invalid
if (originalDataObject != null) {
originalDataObject.removePropertyChangeListener(this);
}
ResultTreeChildren.this.removeFoundObject(foundObject);
}
}
}
/** Optimalized JavaNode.getIcon that starts parser! */
public Image getIcon(int type) {
if (originalDataObject.getPrimaryFile().hasExt("java")) { // NOI18N
// take icon from loader, it should be similar to dataobject's one
try {
BeanInfo info = Utilities.getBeanInfo(originalDataObject.getLoader().getClass());
return info.getIcon(type);
} catch (IntrospectionException e) {
return AbstractNode.EMPTY.getIcon(type);
}
} else {
return super.getIcon(type);
}
}
/** Optimalized JavaNode.getIcon that starts parser! */
public Image getOpenedIcon(int type) {
if (originalDataObject.getPrimaryFile().hasExt("java") ) { // NOI18N
// take icon from loader, it should be similar to dataobject's one
try {
BeanInfo info = Utilities.getBeanInfo(originalDataObject.getLoader().getClass());
return info.getIcon(type);
} catch (IntrospectionException e) {
return AbstractNode.EMPTY.getIcon(type);
}
} else {
return super.getOpenedIcon(type);
}
}
} // End of FoundNode class.
/** Details for found top level (file) nodes. */
final class DetailChildren extends Children.Array {
private final Node parent;
DetailChildren(Node parent) {
this.parent = parent;
}
// TODO why I must subclass Children.Array? I tried to subclass Children directly
// and returned createNodes result from getNodes() but it did not work.
// Why I complain, *Children.Array* is very memory expensive structure
// other implementation (Keys, ...) are even worse :-(
protected void addNotify() {
add(createNodes(parent));
}
protected void removeNotify() {
remove(getNodes());
}
protected Node[] createNodes(Object key) {
Node node = (Node) key; // the parent
SearchType[] types = resultModel.getSearchGroup().getSearchTypes();
ArrayList nodes = new ArrayList(5);
for (int i = 0; i < types.length; i++) {
SearchType searchType = types[i];
Node[] details = searchType.getDetails(node);
if ((details != null) && details.length>0) {
for (int j = 0; j < details.length; j++) {
Node detail = details[j];
nodes.add(detail);
}
}
}
return (Node[]) nodes.toArray(new Node[nodes.size()]);
}
}
/**
*/
final class ResultItemsComparator implements Comparator {
/** */
private final SearchGroup searchGroup;
/**
*/
ResultItemsComparator() {
searchGroup = ResultTreeChildren.this.resultModel.getSearchGroup();
}
/* overridden */
public int compare(Object o1, Object o2) {
if (o1 == o2) {
return 0;
}
if (o1 == null) {
return 1;
}
if (o2 == null) {
return -1;
}
Node node1 = searchGroup.getNodeForFoundObject(o1);
Node node2 = searchGroup.getNodeForFoundObject(o2);
if (node1 == node2) {
return 0;
}
if (node1 == null) {
return 1;
}
if (node2 == null) {
return -1;
}
int result = node1.getDisplayName()
.compareTo(node2.getDisplayName());
/*
* Must not return that two different nodes are equal,
* even their names are same.
*/
return result == 0 ? -1 : result;
}
}
}
|