How do I "decorate" nodes that come from another module (i.e. add icons, actions)?
Say you have a reference to the root of a tree of Node
instances, and you want to add icons or actions to those nodes. First, what you do not do is call setDisplayName
or any other setter on that Node (unless you created it - the point here is that it is rude and can have bad side effects to call setters on random Nodes somebody else created - setters in APIs are bugs - the fact that Node has them is a historical artifact, not proper design).
If you own the component that will display the Nodes, this sort of thing is very easily done by subclassing FilterNode
and overriding the appropriate methods (e.g. getActions()
, getIcon()
, etc.), wrapping the original node inside your FilterNode
. Now let’s say that the Node you want to decorate builds out its children in a lazy fashion, that is, only when the user expands the tree in some tree view. How would you decorate that node and all of its children, without traversing the entire tree and effectively undoing the benefits of the lazy population of the tree?
Fortunately, while this sounds rather challenging, it turns out to be surprisingly easy and simple to achieve. The trick is to subclass the FilterNode.Children
class and override the copyNode()
method. Below is a short example:
class NodeProxy extends FilterNode {
public NodeProxy(Node original) {
super(original, new ProxyChildren(original));
}
// add your specialized behavior here...
}
class ProxyChildren extends FilterNode.Children {
public ProxyChildren(Node owner) {
super(owner);
}
protected Node copyNode(Node original) {
return new NodeProxy(original);
}
}
As you can see, NodeProxy
is intended to wrap around another Node
and provide some additional appearance or behavioral changes (e.g. different icons or actions). The fun part is the ProxyChildren
class. While very short and simple, it provides that critical ability for our NodeProxy
to act as a decorator for not only the root node, but all of its children, and their children, and so on, without having to traverse the entire tree at once.
While FilterNode
should NOT be used to insert additional nodes at the beginning or end of the list (see its JavaDoc), it can be easily used to filter out some of the children nodes. For instance, this refinement of ProxyChildren
overrides the createNodes()
method and conditionally selects the children nodes by submitting them to a custom accept()
method:
class ProxyChildren extends FilterNode.Children {
public ProxyChildren (Node owner) {
super(owner);
}
@Override
protected Node copyNode (Node original){
return new NodeProxy(original);
}
@Override
protected Node[] createNodes (Object object) {
List<Node> result = new ArrayList<Node>();
for (Node node : super.createNodes(object)) {
if (accept(node)) {
result.add(node);
}
}
return result.toArray(new Node[0]);
}
private boolean accept (Node node) {
// ...
}
}
Below a complete example of a FileFilteredNode
that can be used to show a file hierarchy where only a subset of files is shown, selected by means of the standard java.io.FileFilter
class:
class FileFilteredNode extends FilterNode {
static class FileFilteredChildren extends FilterNode.Children {
private final FileFilter fileFilter;
public FileFilteredChildren (Node owner, FileFilter fileFilter) {
super(owner);
this.fileFilter = fileFilter;
}
@Override
protected Node copyNode (Node original) {
return new FileFilteredNode(original, fileFilter);
}
@Override
protected Node[] createNodes (Object object) {
List<Node> result = new ArrayList<Node>();
for (Node node : super.createNodes(object)) {
DataObject dataObject = (DataObject)node.getLookup().lookup(DataObject.class);
if (dataObject != null) {
FileObject fileObject = dataObject.getPrimaryFile();
File file = FileUtil.toFile(fileObject);
if (fileFilter.accept(file)) {
result.add(node);
}
}
}
return result.toArray(new Node[result.size()]);
}
}
public FileFilteredNode (Node original, FileFilter fileFilter) {
super(original, new FileFilteredChildren(original, fileFilter));
}
}
Note that if you’re showing the filtered nodes in a tree view according to the code above, you might find expansion handles on leaf nodes. This thread from the dev@openide list discusses some solutions to this problem.