Java: Swing: JTree and TreeModel: DefaultTreeModel
As they are usually contained in a TreeModel, tree nodes must be immutable and unique (i.e. just take Object's implementation of equals() and hashCode()).
One could define other equivalence relations on tree nodes (or the subtrees they represent), for example by recursively comparing the children (and maybe their user objects if they exist and are accessible), or by comparing the whole tree structure, including the ancestors. These may exist in addition, but cannot be used for equals() if the tree nodes should be in a tree model. Besides, both hashCode() and equals() then would have a lot of overhead.
DefaultMutableTreeNode doesn't override equals() or hashCode(), as is sensible.
MutableTreeNodes have a "user object" (whatever that is supposed to mean). Often, this is the really interesting thing about the node, the TreeNodes themselves only forming the tree structure. There are some problems with that:
While there is setUserObject() in TreeNode, getUserObject() is missing. That means algorithms dealing with user objects cannot be implemented for generic MutableTreeNodes, but need a subinterface or a class (like DefaultMutableTreeNode) that has a method to access the user object. Without the accessor counterpart, the existance of setUserObject() in MutableTreeNode is questionable, as you can never get the object you have set back. In fact getUserObject() without setUserObject() would be much more useful, since inspection of the tree structure with the user objects could then be done, while modifications without knowing the specifics are rare (only in DefaultTreeModel.valueForPathChanged() is it done at all, that possibly is the reason why setUserObject() exists at all and getUserObject() doesn't - DefaultTreeModel otherwise isn't interested in the user objects, and the default renderer also only calls toString() on the TreeModel value, which is the TreeNode.)
See bug reports: third second first
The user object itself doesn't carry information about where it is contained in the tree structure, and there are no lookup mapping or search methods) in DefaultTreeModel; so if you only know the user object, you have to search possibly the whole tree structure, which has a lot of overhead. Furthermore, the user object need not be unique, causing problems about which node is meant.
It may not make sense to set any user object on a specific node, but how is not accepting managed (exception, ignoring)? Probably ignoring.
For "free" tree nodes (without a TreeModel), there are almost no requirements about when it changes (It is sensible to assume that it doesn't do when only accessor methods are called), with a TreeModel, either the TreeModel must be notified (that requires setting up a reference to a TreeModel somewhere), or the changes must have been caused by the TreeModel (so that it knows to fire events afterwards).
A TreeNode may create its children only on demand. If the node is really part of a TreeModel, it may either be done in the TreeModel or in the node. Putting it in the node has the advantage that code that inspects the tree structure (if that ever happens) directly will cause the creation.
DefaultMutableTreeNode adds so many methods that would have to be overridden to first create the children that it is almost impossible to do.
When adding children to a DefaultMutableTreeNode, they are implicitly removed from the old parent. This "feature" (I would rather have an Exception instead of implicit magic) cannot be used when the concerned nodes are contained in the TreeModel. Even if insertNodeInto() is used to automatically fire an event of the insertion, DefaultTreeModel doesn't (and can't in general, since the old parent may be part in a completely different tree structure or TreeModel) fire an event for the removal. Always first make sure the nodes you add don't have a parent.
DefaultTreeModel sets the new value given to valueForPathChanged() when editing has finished as the user object of the path's node, so the old user object is replaced by (typically) a String. If you only want to change a property of your user object or react in still a different way, do something similar to this:
public void valueForPathChanged(TreePath path, Object newValue)
{
DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
((Whatever)node.getUserObject()).setProperty(newValue);
nodeChanged(node);
}
If the user object should be replaced by a new one, but not by a String, one could also replace the TreeCellEditor not to return a String, but already the new Object from getCellEditorValue().
If you already have a reference to a node and want to find (unique) path in the TreeModel for it, use:
new TreePath(data.getPathToRoot(node));
Care must be exercised that the node really is in the tree structure: the implementation of DefaultTreeModel doesn't notice that an invalid node has been given (although it could without effort).
Note: If the root of the DefaultTreeModel and the root of the tree structure aren't the same (i.e. the TreeModel only displays a subtree of the whole structure), then new TreePath(node.getPath()) (in DefaultMutableTreeNode) would be wrong: it returns the path to the tree structure root, which isn't part of the TreeModel anymore. So better always use getPathToRoot().
If you only now the user object of a node, you have to search for it in the tree structure.
There may be cases where the user object isn't unique, then in general you can only search until you find one occurence, or collect all.
If you use DefaultMutableTreeNode, you can use its tree walk methods, for example::
DefaultTreeModel data = ...;
DefaultMutableTreeNode root = (DefaultMutableTreeNode)data.getRoot();
DefaultMutableTreeNode node = null;
if (root != null)
for (Enumeration e = root.getBreadthFirstEnumeration(); e.hasMoreElements();)
{
DefaultTreeModeNode current = (DefaultMutableTreeNode)e.nextElement();
if (target.equals(current.getUserObject()))
{
node = current;
break;
}
}
if (node != null)
{
// found
}
Without DefaultMutableTreeNode, it cannot be done in a generic way, since there is no way to access the user object at all.
If you really need a path, you can then use new TreePath(data.getPathToRoot(node)); as above.
If you know the user object is contained in a child node of a certain parent node, you can search only that node's children.
public int indexOfUserObject(DefaultMutableTreeNode parent, Object userObject)
{
for (Enumeration e = parent.children(); e.hasMoreElements();)
{
if (userObject.equals(((DefaultMutableTreeNode)e.nextElement()).getUserObject()))
return index;
index++;
}
return -1;
}
(C) 2001-2009 Christian Kaufhold (swing@chka.de)