echopointng.tree.DefaultTreeModel Maven / Gradle / Ivy
Show all versions of ibis-echo2 Show documentation
package echopointng.tree;
/*
* This file is part of the Echo Point Project. This project is a collection
* of Components that have extended the Echo Web Application Framework.
*
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*/
/*
* The design paradigm and class name used within have been taken directly from
* the java.swing package has been retro-fitted to work with the NextApp Echo web framework.
*
* This file was made part of the EchoPoint project on the 25/07/2002.
*
*/
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map.Entry;
import nextapp.echo2.app.event.EventListenerList;
/**
* A simple tree data model that uses TreeNodes.
*/
public class DefaultTreeModel implements TreeModel, java.io.Serializable {
/** Root of the tree. */
protected TreeNode root;
/** Listeners. */
protected EventListenerList listenerList = new EventListenerList();
/**
* Determines how the isLeaf
method figures out if a node is
* a leaf node. If true, a node is a leaf node if it does not allow
* children. (If it allows children, it is not a leaf node, even if no
* children are present.) That lets you distinguish between folder
* nodes and file nodes in a file system, for example.
*
* If this value is false, then any node which has no children is a leaf
* node, and any node may acquire children.
*
*/
protected boolean asksAllowsChildren;
/** Map of Node identifiers keyed by the Nodes themselves */
protected HashMap nodeIds = new HashMap();
private int nextNodeId = 0;
/**
* Creates a tree in which any node can have children.
*
* @param root
* a TreeNode object that is the root of the tree
*/
public DefaultTreeModel(TreeNode root) {
this(root, false);
}
/**
* Creates a tree specifying whether any node can have children, or whether
* only certain nodes can have children.
*
* @param root
* a TreeNode object that is the root of the tree
* @param asksAllowsChildren
* a boolean, false if any node can have children, true if each
* node is asked to see if it can have children
*/
public DefaultTreeModel(TreeNode root, boolean asksAllowsChildren) {
super();
if (root == null) {
throw new IllegalArgumentException("root is null");
}
this.root = root;
this.asksAllowsChildren = asksAllowsChildren;
}
/**
* Adds a TreeModelListener to the model
*/
public void addTreeModelListener(TreeModelListener l) {
listenerList.addListener(TreeModelListener.class, l);
}
/**
* Tells how leaf nodes are determined.
*
* @return true if only nodes which do not allow children are leaf nodes,
* false if nodes which have no children (even if allowed) are leaf
* nodes
*/
public boolean asksAllowsChildren() {
return asksAllowsChildren;
}
/**
* Notify all listeners that have registered interest for notification on
* this event type. The event instance is lazily created using the
* parameters passed into the fire method.
*/
protected void fireTreeNodesChanged(Object source, Object[] path, int[] childIndices, Object[] children) {
Object[] listeners = listenerList.getListeners(TreeModelListener.class);
TreeModelEvent e = null;
for (int index = 0; index < listeners.length; ++index) {
// Lazily create the event:
if (e == null)
e = new TreeModelEvent(source, path, childIndices, children);
((TreeModelListener) listeners[index]).treeNodesChanged(e);
}
}
/**
* Notify all listeners that have registered interest for notification on
* this event type. The event instance is lazily created using the
* parameters passed into the fire method.
*/
protected void fireTreeNodesInserted(Object source, Object[] path, int[] childIndices, Object[] children) {
Object[] listeners = listenerList.getListeners(TreeModelListener.class);
TreeModelEvent e = null;
for (int index = 0; index < listeners.length; ++index) {
// Lazily create the event:
if (e == null)
e = new TreeModelEvent(source, path, childIndices, children);
((TreeModelListener) listeners[index]).treeNodesInserted(e);
}
}
/**
* Notify all listeners that have registered interest for notification on
* this event type. The event instance is lazily created using the
* parameters passed into the fire method.
*/
protected void fireTreeNodesRemoved(Object source, Object[] path, int[] childIndices, Object[] children) {
Object[] listeners = listenerList.getListeners(TreeModelListener.class);
TreeModelEvent e = null;
for (int index = 0; index < listeners.length; ++index) {
// Lazily create the event:
if (e == null)
e = new TreeModelEvent(source, path, childIndices, children);
((TreeModelListener) listeners[index]).treeNodesRemoved(e);
}
}
/**
* Notify all listeners that have registered interest for notification on
* this event type. The event instance is lazily created using the
* parameters passed into the fire method.
*/
protected void fireTreeStructureChanged(Object source, Object[] path, int[] childIndices, Object[] children) {
Object[] listeners = listenerList.getListeners(TreeModelListener.class);
TreeModelEvent e = null;
for (int index = 0; index < listeners.length; ++index) {
// Lazily create the event:
if (e == null)
e = new TreeModelEvent(source, path, childIndices, children);
((TreeModelListener) listeners[index]).treeStructureChanged(e);
}
}
/**
* Returns the child of parent at index index in the
* parent's child array. parent must be a node previously obtained
* from this data source. This should not return null if index is a
* valid index for parent (that is index >= 0 && index
* < getChildCount( parent )).
*
* @param parent
* a node in the tree, obtained from this data source
* @return the child of parent at index index
*/
public Object getChild(Object parent, int index) {
return ((TreeNode) parent).getChildAt(index);
}
/**
* Returns the number of children of parent . Returns 0 if the node
* is a leaf or if it has no children. parent must be a node
* previously obtained from this data source.
*
* @param parent
* a node in the tree, obtained from this data source
* @return the number of children of the node parent
*/
public int getChildCount(Object parent) {
return ((TreeNode) parent).getChildCount();
}
/**
* Returns the index of child in parent.
*/
public int getIndexOfChild(Object parent, Object child) {
if (parent == null || child == null)
return 0;
return ((TreeNode) parent).getIndex((TreeNode) child);
}
/**
* Returns the parent of child . Returns null if the node has no
* parent or the tree model does not support traversing from a child to a
* parent node.
*
* @param child
* a node in the tree
* @return the parent of the node child
*/
public Object getParent(Object child) {
return null;
}
/**
* Builds the parents of node up to and including the root node, where the
* original node is the last element in the returned array. The length of
* the returned array gives the node's depth in the tree.
*
* @param aNode
* the TreeNode to get the path for
* @param depth
* an int giving the number of steps already taken towards the
* root (on recursive calls), used to size the returned array
* @return an array of TreeNodes giving the path from the root to the
* specified node
*/
protected TreeNode[] getPathToRoot(TreeNode aNode, int depth) {
TreeNode[] retNodes;
// This method recurses, traversing towards the root in order
// size the array. On the way back, it fills in the nodes,
// starting from the root and working back to the original node.
/*
* Check for null, in case someone passed in a null node, or they passed
* in an element that isn't rooted at root.
*/
if (aNode == null) {
if (depth == 0)
return null;
else
retNodes = new TreeNode[depth];
} else {
depth++;
if (aNode == root)
retNodes = new TreeNode[depth];
else
retNodes = getPathToRoot(aNode.getParent(), depth);
retNodes[retNodes.length - depth] = aNode;
}
return retNodes;
}
/**
* Builds the parents of node up to and including the root node, where the
* original node is the last element in the returned array. The length of
* the returned array gives the node's depth in the tree.
*
* @param node
* the TreeNode to get the path for
*/
public Object[] getPathToRoot(Object node) {
return getPathToRoot((TreeNode) node, 0);
}
/**
* Returns the root of the tree. Returns null only if the tree has no nodes.
*
* @return the root of the tree
*/
public Object getRoot() {
return root;
}
/**
* Invoked this to insert newChild at location index in parents children.
* This will then message nodesWereInserted to create the appropriate event.
* This is the preferred way to add children as it will create the
* appropriate event.
*/
public void insertNodeInto(MutableTreeNode newChild, MutableTreeNode parent, int index) {
// Remove new node from parent first
if (newChild.getParent() != null) {
removeNodeFromParent(newChild);
}
parent.insert(newChild, index);
int[] newIndexs = new int[1];
newIndexs[0] = index;
nodesWereInserted(parent, newIndexs);
}
/**
* Returns whether the specified node is a leaf node. The way the test is
* performed depends on the askAllowsChildren
setting.
*/
public boolean isLeaf(Object node) {
if (asksAllowsChildren)
return !((TreeNode) node).getAllowsChildren();
return ((TreeNode) node).isLeaf();
}
/**
* Invoke this method after you've changed how node is to be represented in
* the tree.
*/
public void nodeChanged(TreeNode node) {
if (listenerList != null && node != null) {
TreeNode parent = node.getParent();
if (parent != null) {
int anIndex = parent.getIndex(node);
if (anIndex != -1) {
int[] cIndexs = new int[1];
cIndexs[0] = anIndex;
nodesChanged(parent, cIndexs);
}
} else if (node == getRoot()) {
nodesChanged(node, null);
}
}
}
/**
* Invoke this method after you've changed how the children identified by
* childIndicies are to be represented in the tree.
*/
public void nodesChanged(TreeNode node, int[] childIndices) {
if (node != null) {
if (childIndices != null) {
int cCount = childIndices.length;
if (cCount > 0) {
Object[] cChildren = new Object[cCount];
for (int counter = 0; counter < cCount; counter++) {
cChildren[counter] = node.getChildAt(childIndices[counter]);
}
fireTreeNodesChanged(this, getPathToRoot(node), childIndices, cChildren);
}
} else if (node == getRoot()) {
fireTreeNodesChanged(this, getPathToRoot(node), null, null);
}
}
}
/**
* Invoke this method if you've totally changed the children of node and its
* childrens children... This will post a treeStructureChanged event.
*/
public void nodeStructureChanged(TreeNode node) {
if (node != null) {
fireTreeStructureChanged(this, getPathToRoot(node), null, null);
}
}
/**
* Invoke this method after you've inserted some TreeNodes into node.
* childIndices should be the index of the new elements and must be sorted
* in ascending order.
*/
public void nodesWereInserted(TreeNode node, int[] childIndices) {
if (listenerList != null && node != null && childIndices != null && childIndices.length > 0) {
int cCount = childIndices.length;
Object[] newChildren = new Object[cCount];
for (int counter = 0; counter < cCount; counter++) {
newChildren[counter] = node.getChildAt(childIndices[counter]);
}
fireTreeNodesInserted(this, getPathToRoot(node), childIndices, newChildren);
}
}
/**
* Invoke this method after you've removed some TreeNodes from node.
* childIndices should be the index of the removed elements and must be
* sorted in ascending order. And removedChildren should be the array of the
* children objects that were removed.
*/
public void nodesWereRemoved(TreeNode node, int[] childIndices, Object[] removedChildren) {
if (node != null && childIndices != null) {
fireTreeNodesRemoved(this, getPathToRoot(node), childIndices, removedChildren);
}
}
/**
* Invoke this method if you've modified the TreeNodes upon which this model
* depends. The model will notify all of its listeners that the model has
* changed.
*/
public void reload() {
reload(root);
}
/**
* Invoke this method if you've modified the TreeNodes upon which this model
* depends. The model will notify all of its listeners that the model has
* changed below the node
.
*/
public void reload(TreeNode node) {
nodeStructureChanged(node);
}
/**
* Message this to remove node from its parent. This will message
* nodesWereRemoved to create the appropriate event. This is the preferred
* way to remove a node as it handles the event creation for you.
*/
public void removeNodeFromParent(MutableTreeNode node) {
MutableTreeNode parent = (MutableTreeNode) node.getParent();
if (parent == null) {
throw new IllegalArgumentException("node does not have a parent.");
}
int[] childIndex = new int[1];
Object[] removedArray = new Object[1];
childIndex[0] = parent.getIndex(node);
parent.remove(childIndex[0]);
removedArray[0] = node;
nodesWereRemoved(parent, childIndex, removedArray);
}
/**
* Removes a TreeModelListener from the model
*/
public void removeTreeModelListener(TreeModelListener l) {
listenerList.removeListener(TreeModelListener.class, l);
}
/**
* Sets whether or not to test leafness by asking getAllowsChildren() or
* isLeaf() to the TreeNodes. If newvalue is true, getAllowsChildren() is
* messaged, otherwise isLeaf() is messaged.
*/
public void setAsksAllowsChildren(boolean newValue) {
asksAllowsChildren = newValue;
}
/**
* Sets the root to root
. This will throw an
* IllegalArgumentException if root
is null.
*/
public void setRoot(TreeNode root) {
if (root == null)
throw new IllegalArgumentException("Root of tree is not allowed to be null");
this.root = root;
nodeStructureChanged(root);
}
/**
* This sets the user object of the TreeNode identified by path and posts a
* node changed. If you use custom user objects in the TreeModel you're
* going to need to subclass this and set the user object of the changed
* node to something meaningful.
*/
public void valueForPathChanged(TreePath path, Object newValue) {
MutableTreeNode aNode = (MutableTreeNode) path.getLastPathComponent();
aNode.setUserObject(newValue);
nodeChanged(aNode);
}
/**
* @see echopointng.tree.TreeModel#getNodeId(java.lang.Object)
*/
public String getNodeId(Object node) {
Integer nodeId = (Integer) nodeIds.get(node);
if (nodeId == null) {
nodeId = new Integer(nextNodeId++);
nodeIds.put(node, nodeId);
}
return String.valueOf(nodeId);
}
/**
* @see echopointng.tree.TreeModel#getNodeById(java.lang.String)
*/
public Object getNodeById(String id) {
Iterator iter = nodeIds.entrySet().iterator();
while (iter.hasNext()) {
Entry entry = (Entry) iter.next();
if (String.valueOf(entry.getValue()).equals(id)) {
return entry.getKey();
}
}
return null;
}
}