/* * Copyright (c) 1997, 2008, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package javax.swing.tree; import java.util.*; import java.beans.ConstructorProperties; import java.io.*; import javax.swing.event.*; /** * A simple tree data model that uses TreeNodes. * For further information and examples that use DefaultTreeModel, * see How to Use Trees * in The Java Tutorial. *

* Warning: * Serialized objects of this class will not be compatible with * future Swing releases. The current serialization support is * appropriate for short term storage or RMI between applications running * the same version of Swing. As of 1.4, support for long term storage * of all JavaBeansTM * has been added to the java.beans package. * Please see {@link java.beans.XMLEncoder}. * * @author Rob Davis * @author Ray Ryan * @author Scott Violet */ public class DefaultTreeModel implements Serializable, TreeModel { /** 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. * * @see TreeNode#getAllowsChildren * @see TreeModel#isLeaf * @see #setAsksAllowsChildren */ protected boolean asksAllowsChildren; /** * Creates a tree in which any node can have children. * * @param root a TreeNode object that is the root of the tree * @see #DefaultTreeModel(TreeNode, boolean) */ @ConstructorProperties({"root"}) 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 * @see #asksAllowsChildren */ public DefaultTreeModel(TreeNode root, boolean asksAllowsChildren) { super(); this.root = root; this.asksAllowsChildren = asksAllowsChildren; } /** * 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; } /** * 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 * @see #asksAllowsChildren */ public boolean asksAllowsChildren() { return asksAllowsChildren; } /** * Sets the root to root. A null root implies * the tree is to display nothing, and is legal. */ public void setRoot(TreeNode root) { Object oldRoot = this.root; this.root = root; if (root == null && oldRoot != null) { fireTreeStructureChanged(this, null); } else { nodeStructureChanged(root); } } /** * 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; } /** * Returns the index of child in parent. * If either the parent or child is null, returns -1. * @param parent a note in the tree, obtained from this data source * @param child the node we are interested in * @return the index of the child in the parent, or -1 * if either the parent or the child is null */ public int getIndexOfChild(Object parent, Object child) { if(parent == null || child == null) return -1; return ((TreeNode)parent).getIndex((TreeNode)child); } /** * 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 whether the specified node is a leaf node. * The way the test is performed depends on the * askAllowsChildren setting. * * @param node the node to check * @return true if the node is a leaf node * * @see #asksAllowsChildren * @see TreeModel#isLeaf */ public boolean isLeaf(Object node) { if(asksAllowsChildren) return !((TreeNode)node).getAllowsChildren(); return ((TreeNode)node).isLeaf(); } /** * Invoke this method if you've modified the {@code TreeNode}s upon which * this model depends. The model will notify all of its listeners that the * model has changed. */ public void reload() { reload(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); } /** * 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){ parent.insert(newChild, index); int[] newIndexs = new int[1]; newIndexs[0] = index; nodesWereInserted(parent, newIndexs); } /** * 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); } /** * 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 if you've modified the {@code TreeNode}s upon which * this model depends. The model will notify all of its listeners that the * model has changed below the given node. * * @param node the node below which the model has changed */ public void reload(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 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); } } /** * 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 */ public TreeNode[] getPathToRoot(TreeNode aNode) { return getPathToRoot(aNode, 0); } /** * 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; } // // Events // /** * Adds a listener for the TreeModelEvent posted after the tree changes. * * @see #removeTreeModelListener * @param l the listener to add */ public void addTreeModelListener(TreeModelListener l) { listenerList.add(TreeModelListener.class, l); } /** * Removes a listener previously added with addTreeModelListener(). * * @see #addTreeModelListener * @param l the listener to remove */ public void removeTreeModelListener(TreeModelListener l) { listenerList.remove(TreeModelListener.class, l); } /** * Returns an array of all the tree model listeners * registered on this model. * * @return all of this model's TreeModelListeners * or an empty * array if no tree model listeners are currently registered * * @see #addTreeModelListener * @see #removeTreeModelListener * * @since 1.4 */ public TreeModelListener[] getTreeModelListeners() { return listenerList.getListeners(TreeModelListener.class); } /** * Notifies 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. * * @param source the source of the {@code TreeModelEvent}; * typically {@code this} * @param path the path to the parent of the nodes that changed; use * {@code null} to identify the root has changed * @param childIndices the indices of the changed elements * @param children the changed elements */ protected void fireTreeNodesChanged(Object source, Object[] path, int[] childIndices, Object[] children) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); TreeModelEvent e = null; // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==TreeModelListener.class) { // Lazily create the event: if (e == null) e = new TreeModelEvent(source, path, childIndices, children); ((TreeModelListener)listeners[i+1]).treeNodesChanged(e); } } } /** * Notifies 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. * * @param source the source of the {@code TreeModelEvent}; * typically {@code this} * @param path the path to the parent the nodes were added to * @param childIndices the indices of the new elements * @param children the new elements */ protected void fireTreeNodesInserted(Object source, Object[] path, int[] childIndices, Object[] children) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); TreeModelEvent e = null; // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==TreeModelListener.class) { // Lazily create the event: if (e == null) e = new TreeModelEvent(source, path, childIndices, children); ((TreeModelListener)listeners[i+1]).treeNodesInserted(e); } } } /** * Notifies 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. * * @param source the source of the {@code TreeModelEvent}; * typically {@code this} * @param path the path to the parent the nodes were removed from * @param childIndices the indices of the removed elements * @param children the removed elements */ protected void fireTreeNodesRemoved(Object source, Object[] path, int[] childIndices, Object[] children) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); TreeModelEvent e = null; // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==TreeModelListener.class) { // Lazily create the event: if (e == null) e = new TreeModelEvent(source, path, childIndices, children); ((TreeModelListener)listeners[i+1]).treeNodesRemoved(e); } } } /** * Notifies 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. * * @param source the source of the {@code TreeModelEvent}; * typically {@code this} * @param path the path to the parent of the structure that has changed; * use {@code null} to identify the root has changed * @param childIndices the indices of the affected elements * @param children the affected elements */ protected void fireTreeStructureChanged(Object source, Object[] path, int[] childIndices, Object[] children) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); TreeModelEvent e = null; // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==TreeModelListener.class) { // Lazily create the event: if (e == null) e = new TreeModelEvent(source, path, childIndices, children); ((TreeModelListener)listeners[i+1]).treeStructureChanged(e); } } } /** * Notifies 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. * * @param source the source of the {@code TreeModelEvent}; * typically {@code this} * @param path the path to the parent of the structure that has changed; * use {@code null} to identify the root has changed */ private void fireTreeStructureChanged(Object source, TreePath path) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); TreeModelEvent e = null; // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==TreeModelListener.class) { // Lazily create the event: if (e == null) e = new TreeModelEvent(source, path); ((TreeModelListener)listeners[i+1]).treeStructureChanged(e); } } } /** * Returns an array of all the objects currently registered * as FooListeners * upon this model. * FooListeners are registered using the * addFooListener method. * *

* * You can specify the listenerType argument * with a class literal, * such as * FooListener.class. * For example, you can query a * DefaultTreeModel m * for its tree model listeners with the following code: * *

TreeModelListener[] tmls = (TreeModelListener[])(m.getListeners(TreeModelListener.class));
* * If no such listeners exist, this method returns an empty array. * * @param listenerType the type of listeners requested; this parameter * should specify an interface that descends from * java.util.EventListener * @return an array of all objects registered as * FooListeners on this component, * or an empty array if no such * listeners have been added * @exception ClassCastException if listenerType * doesn't specify a class or interface that implements * java.util.EventListener * * @see #getTreeModelListeners * * @since 1.3 */ public T[] getListeners(Class listenerType) { return listenerList.getListeners(listenerType); } // Serialization support. private void writeObject(ObjectOutputStream s) throws IOException { Vector values = new Vector(); s.defaultWriteObject(); // Save the root, if its Serializable. if(root != null && root instanceof Serializable) { values.addElement("root"); values.addElement(root); } s.writeObject(values); } private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); Vector values = (Vector)s.readObject(); int indexCounter = 0; int maxCounter = values.size(); if(indexCounter < maxCounter && values.elementAt(indexCounter). equals("root")) { root = (TreeNode)values.elementAt(++indexCounter); indexCounter++; } } } // End of class DefaultTreeModel