001 /*
002 * Copyright 1997-2007 Sun Microsystems, Inc. All Rights Reserved.
003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004 *
005 * This code is free software; you can redistribute it and/or modify it
006 * under the terms of the GNU General Public License version 2 only, as
007 * published by the Free Software Foundation. Sun designates this
008 * particular file as subject to the "Classpath" exception as provided
009 * by Sun in the LICENSE file that accompanied this code.
010 *
011 * This code is distributed in the hope that it will be useful, but WITHOUT
012 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014 * version 2 for more details (a copy is included in the LICENSE file that
015 * accompanied this code).
016 *
017 * You should have received a copy of the GNU General Public License version
018 * 2 along with this work; if not, write to the Free Software Foundation,
019 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020 *
021 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022 * CA 95054 USA or visit www.sun.com if you need additional information or
023 * have any questions.
024 */
025
026 package javax.swing.tree;
027
028 import java.util.*;
029 import java.beans.ConstructorProperties;
030 import java.io.*;
031 import javax.swing.event.*;
032
033 /**
034 * A simple tree data model that uses TreeNodes.
035 * For further information and examples that use DefaultTreeModel,
036 * see <a href="http://java.sun.com/docs/books/tutorial/uiswing/components/tree.html">How to Use Trees</a>
037 * in <em>The Java Tutorial.</em>
038 * <p>
039 * <strong>Warning:</strong>
040 * Serialized objects of this class will not be compatible with
041 * future Swing releases. The current serialization support is
042 * appropriate for short term storage or RMI between applications running
043 * the same version of Swing. As of 1.4, support for long term storage
044 * of all JavaBeans<sup><font size="-2">TM</font></sup>
045 * has been added to the <code>java.beans</code> package.
046 * Please see {@link java.beans.XMLEncoder}.
047 *
048 * @version 1.63 05/05/07
049 * @author Rob Davis
050 * @author Ray Ryan
051 * @author Scott Violet
052 */
053 public class DefaultTreeModel implements Serializable, TreeModel {
054 /** Root of the tree. */
055 protected TreeNode root;
056 /** Listeners. */
057 protected EventListenerList listenerList = new EventListenerList();
058 /**
059 * Determines how the <code>isLeaf</code> method figures
060 * out if a node is a leaf node. If true, a node is a leaf
061 * node if it does not allow children. (If it allows
062 * children, it is not a leaf node, even if no children
063 * are present.) That lets you distinguish between <i>folder</i>
064 * nodes and <i>file</i> nodes in a file system, for example.
065 * <p>
066 * If this value is false, then any node which has no
067 * children is a leaf node, and any node may acquire
068 * children.
069 *
070 * @see TreeNode#getAllowsChildren
071 * @see TreeModel#isLeaf
072 * @see #setAsksAllowsChildren
073 */
074 protected boolean asksAllowsChildren;
075
076 /**
077 * Creates a tree in which any node can have children.
078 *
079 * @param root a TreeNode object that is the root of the tree
080 * @see #DefaultTreeModel(TreeNode, boolean)
081 */
082 @ConstructorProperties({"root"})
083 public DefaultTreeModel(TreeNode root) {
084 this (root, false);
085 }
086
087 /**
088 * Creates a tree specifying whether any node can have children,
089 * or whether only certain nodes can have children.
090 *
091 * @param root a TreeNode object that is the root of the tree
092 * @param asksAllowsChildren a boolean, false if any node can
093 * have children, true if each node is asked to see if
094 * it can have children
095 * @see #asksAllowsChildren
096 */
097 public DefaultTreeModel(TreeNode root, boolean asksAllowsChildren) {
098 super ();
099 this .root = root;
100 this .asksAllowsChildren = asksAllowsChildren;
101 }
102
103 /**
104 * Sets whether or not to test leafness by asking getAllowsChildren()
105 * or isLeaf() to the TreeNodes. If newvalue is true, getAllowsChildren()
106 * is messaged, otherwise isLeaf() is messaged.
107 */
108 public void setAsksAllowsChildren(boolean newValue) {
109 asksAllowsChildren = newValue;
110 }
111
112 /**
113 * Tells how leaf nodes are determined.
114 *
115 * @return true if only nodes which do not allow children are
116 * leaf nodes, false if nodes which have no children
117 * (even if allowed) are leaf nodes
118 * @see #asksAllowsChildren
119 */
120 public boolean asksAllowsChildren() {
121 return asksAllowsChildren;
122 }
123
124 /**
125 * Sets the root to <code>root</code>. A null <code>root</code> implies
126 * the tree is to display nothing, and is legal.
127 */
128 public void setRoot(TreeNode root) {
129 Object oldRoot = this .root;
130 this .root = root;
131 if (root == null && oldRoot != null) {
132 fireTreeStructureChanged(this , null);
133 } else {
134 nodeStructureChanged(root);
135 }
136 }
137
138 /**
139 * Returns the root of the tree. Returns null only if the tree has
140 * no nodes.
141 *
142 * @return the root of the tree
143 */
144 public Object getRoot() {
145 return root;
146 }
147
148 /**
149 * Returns the index of child in parent.
150 * If either the parent or child is <code>null</code>, returns -1.
151 * @param parent a note in the tree, obtained from this data source
152 * @param child the node we are interested in
153 * @return the index of the child in the parent, or -1
154 * if either the parent or the child is <code>null</code>
155 */
156 public int getIndexOfChild(Object parent, Object child) {
157 if (parent == null || child == null)
158 return -1;
159 return ((TreeNode) parent).getIndex((TreeNode) child);
160 }
161
162 /**
163 * Returns the child of <I>parent</I> at index <I>index</I> in the parent's
164 * child array. <I>parent</I> must be a node previously obtained from
165 * this data source. This should not return null if <i>index</i>
166 * is a valid index for <i>parent</i> (that is <i>index</i> >= 0 &&
167 * <i>index</i> < getChildCount(<i>parent</i>)).
168 *
169 * @param parent a node in the tree, obtained from this data source
170 * @return the child of <I>parent</I> at index <I>index</I>
171 */
172 public Object getChild(Object parent, int index) {
173 return ((TreeNode) parent).getChildAt(index);
174 }
175
176 /**
177 * Returns the number of children of <I>parent</I>. Returns 0 if the node
178 * is a leaf or if it has no children. <I>parent</I> must be a node
179 * previously obtained from this data source.
180 *
181 * @param parent a node in the tree, obtained from this data source
182 * @return the number of children of the node <I>parent</I>
183 */
184 public int getChildCount(Object parent) {
185 return ((TreeNode) parent).getChildCount();
186 }
187
188 /**
189 * Returns whether the specified node is a leaf node.
190 * The way the test is performed depends on the
191 * <code>askAllowsChildren</code> setting.
192 *
193 * @param node the node to check
194 * @return true if the node is a leaf node
195 *
196 * @see #asksAllowsChildren
197 * @see TreeModel#isLeaf
198 */
199 public boolean isLeaf(Object node) {
200 if (asksAllowsChildren)
201 return !((TreeNode) node).getAllowsChildren();
202 return ((TreeNode) node).isLeaf();
203 }
204
205 /**
206 * Invoke this method if you've modified the {@code TreeNode}s upon which
207 * this model depends. The model will notify all of its listeners that the
208 * model has changed.
209 */
210 public void reload() {
211 reload(root);
212 }
213
214 /**
215 * This sets the user object of the TreeNode identified by path
216 * and posts a node changed. If you use custom user objects in
217 * the TreeModel you're going to need to subclass this and
218 * set the user object of the changed node to something meaningful.
219 */
220 public void valueForPathChanged(TreePath path, Object newValue) {
221 MutableTreeNode aNode = (MutableTreeNode) path
222 .getLastPathComponent();
223
224 aNode.setUserObject(newValue);
225 nodeChanged(aNode);
226 }
227
228 /**
229 * Invoked this to insert newChild at location index in parents children.
230 * This will then message nodesWereInserted to create the appropriate
231 * event. This is the preferred way to add children as it will create
232 * the appropriate event.
233 */
234 public void insertNodeInto(MutableTreeNode newChild,
235 MutableTreeNode parent, int index) {
236 parent.insert(newChild, index);
237
238 int[] newIndexs = new int[1];
239
240 newIndexs[0] = index;
241 nodesWereInserted(parent, newIndexs);
242 }
243
244 /**
245 * Message this to remove node from its parent. This will message
246 * nodesWereRemoved to create the appropriate event. This is the
247 * preferred way to remove a node as it handles the event creation
248 * for you.
249 */
250 public void removeNodeFromParent(MutableTreeNode node) {
251 MutableTreeNode parent = (MutableTreeNode) node.getParent();
252
253 if (parent == null)
254 throw new IllegalArgumentException(
255 "node does not have a parent.");
256
257 int[] childIndex = new int[1];
258 Object[] removedArray = new Object[1];
259
260 childIndex[0] = parent.getIndex(node);
261 parent.remove(childIndex[0]);
262 removedArray[0] = node;
263 nodesWereRemoved(parent, childIndex, removedArray);
264 }
265
266 /**
267 * Invoke this method after you've changed how node is to be
268 * represented in the tree.
269 */
270 public void nodeChanged(TreeNode node) {
271 if (listenerList != null && node != null) {
272 TreeNode parent = node.getParent();
273
274 if (parent != null) {
275 int anIndex = parent.getIndex(node);
276 if (anIndex != -1) {
277 int[] cIndexs = new int[1];
278
279 cIndexs[0] = anIndex;
280 nodesChanged(parent, cIndexs);
281 }
282 } else if (node == getRoot()) {
283 nodesChanged(node, null);
284 }
285 }
286 }
287
288 /**
289 * Invoke this method if you've modified the {@code TreeNode}s upon which
290 * this model depends. The model will notify all of its listeners that the
291 * model has changed below the given node.
292 *
293 * @param node the node below which the model has changed
294 */
295 public void reload(TreeNode node) {
296 if (node != null) {
297 fireTreeStructureChanged(this , getPathToRoot(node), null,
298 null);
299 }
300 }
301
302 /**
303 * Invoke this method after you've inserted some TreeNodes into
304 * node. childIndices should be the index of the new elements and
305 * must be sorted in ascending order.
306 */
307 public void nodesWereInserted(TreeNode node, int[] childIndices) {
308 if (listenerList != null && node != null
309 && childIndices != null && childIndices.length > 0) {
310 int cCount = childIndices.length;
311 Object[] newChildren = new Object[cCount];
312
313 for (int counter = 0; counter < cCount; counter++)
314 newChildren[counter] = node
315 .getChildAt(childIndices[counter]);
316 fireTreeNodesInserted(this , getPathToRoot(node),
317 childIndices, newChildren);
318 }
319 }
320
321 /**
322 * Invoke this method after you've removed some TreeNodes from
323 * node. childIndices should be the index of the removed elements and
324 * must be sorted in ascending order. And removedChildren should be
325 * the array of the children objects that were removed.
326 */
327 public void nodesWereRemoved(TreeNode node, int[] childIndices,
328 Object[] removedChildren) {
329 if (node != null && childIndices != null) {
330 fireTreeNodesRemoved(this , getPathToRoot(node),
331 childIndices, removedChildren);
332 }
333 }
334
335 /**
336 * Invoke this method after you've changed how the children identified by
337 * childIndicies are to be represented in the tree.
338 */
339 public void nodesChanged(TreeNode node, int[] childIndices) {
340 if (node != null) {
341 if (childIndices != null) {
342 int cCount = childIndices.length;
343
344 if (cCount > 0) {
345 Object[] cChildren = new Object[cCount];
346
347 for (int counter = 0; counter < cCount; counter++)
348 cChildren[counter] = node
349 .getChildAt(childIndices[counter]);
350 fireTreeNodesChanged(this , getPathToRoot(node),
351 childIndices, cChildren);
352 }
353 } else if (node == getRoot()) {
354 fireTreeNodesChanged(this , getPathToRoot(node), null,
355 null);
356 }
357 }
358 }
359
360 /**
361 * Invoke this method if you've totally changed the children of
362 * node and its childrens children... This will post a
363 * treeStructureChanged event.
364 */
365 public void nodeStructureChanged(TreeNode node) {
366 if (node != null) {
367 fireTreeStructureChanged(this , getPathToRoot(node), null,
368 null);
369 }
370 }
371
372 /**
373 * Builds the parents of node up to and including the root node,
374 * where the original node is the last element in the returned array.
375 * The length of the returned array gives the node's depth in the
376 * tree.
377 *
378 * @param aNode the TreeNode to get the path for
379 */
380 public TreeNode[] getPathToRoot(TreeNode aNode) {
381 return getPathToRoot(aNode, 0);
382 }
383
384 /**
385 * Builds the parents of node up to and including the root node,
386 * where the original node is the last element in the returned array.
387 * The length of the returned array gives the node's depth in the
388 * tree.
389 *
390 * @param aNode the TreeNode to get the path for
391 * @param depth an int giving the number of steps already taken towards
392 * the root (on recursive calls), used to size the returned array
393 * @return an array of TreeNodes giving the path from the root to the
394 * specified node
395 */
396 protected TreeNode[] getPathToRoot(TreeNode aNode, int depth) {
397 TreeNode[] retNodes;
398 // This method recurses, traversing towards the root in order
399 // size the array. On the way back, it fills in the nodes,
400 // starting from the root and working back to the original node.
401
402 /* Check for null, in case someone passed in a null node, or
403 they passed in an element that isn't rooted at root. */
404 if (aNode == null) {
405 if (depth == 0)
406 return null;
407 else
408 retNodes = new TreeNode[depth];
409 } else {
410 depth++;
411 if (aNode == root)
412 retNodes = new TreeNode[depth];
413 else
414 retNodes = getPathToRoot(aNode.getParent(), depth);
415 retNodes[retNodes.length - depth] = aNode;
416 }
417 return retNodes;
418 }
419
420 //
421 // Events
422 //
423
424 /**
425 * Adds a listener for the TreeModelEvent posted after the tree changes.
426 *
427 * @see #removeTreeModelListener
428 * @param l the listener to add
429 */
430 public void addTreeModelListener(TreeModelListener l) {
431 listenerList.add(TreeModelListener.class, l);
432 }
433
434 /**
435 * Removes a listener previously added with <B>addTreeModelListener()</B>.
436 *
437 * @see #addTreeModelListener
438 * @param l the listener to remove
439 */
440 public void removeTreeModelListener(TreeModelListener l) {
441 listenerList.remove(TreeModelListener.class, l);
442 }
443
444 /**
445 * Returns an array of all the tree model listeners
446 * registered on this model.
447 *
448 * @return all of this model's <code>TreeModelListener</code>s
449 * or an empty
450 * array if no tree model listeners are currently registered
451 *
452 * @see #addTreeModelListener
453 * @see #removeTreeModelListener
454 *
455 * @since 1.4
456 */
457 public TreeModelListener[] getTreeModelListeners() {
458 return (TreeModelListener[]) listenerList
459 .getListeners(TreeModelListener.class);
460 }
461
462 /**
463 * Notifies all listeners that have registered interest for
464 * notification on this event type. The event instance
465 * is lazily created using the parameters passed into
466 * the fire method.
467 *
468 * @param source the source of the {@code TreeModelEvent};
469 * typically {@code this}
470 * @param path the path to the parent of the nodes that changed; use
471 * {@code null} to identify the root has changed
472 * @param childIndices the indices of the changed elements
473 * @param children the changed elements
474 */
475 protected void fireTreeNodesChanged(Object source, Object[] path,
476 int[] childIndices, Object[] children) {
477 // Guaranteed to return a non-null array
478 Object[] listeners = listenerList.getListenerList();
479 TreeModelEvent e = null;
480 // Process the listeners last to first, notifying
481 // those that are interested in this event
482 for (int i = listeners.length - 2; i >= 0; i -= 2) {
483 if (listeners[i] == TreeModelListener.class) {
484 // Lazily create the event:
485 if (e == null)
486 e = new TreeModelEvent(source, path, childIndices,
487 children);
488 ((TreeModelListener) listeners[i + 1])
489 .treeNodesChanged(e);
490 }
491 }
492 }
493
494 /**
495 * Notifies all listeners that have registered interest for
496 * notification on this event type. The event instance
497 * is lazily created using the parameters passed into
498 * the fire method.
499 *
500 * @param source the source of the {@code TreeModelEvent};
501 * typically {@code this}
502 * @param path the path to the parent the nodes were added to
503 * @param childIndices the indices of the new elements
504 * @param children the new elements
505 */
506 protected void fireTreeNodesInserted(Object source, Object[] path,
507 int[] childIndices, Object[] children) {
508 // Guaranteed to return a non-null array
509 Object[] listeners = listenerList.getListenerList();
510 TreeModelEvent e = null;
511 // Process the listeners last to first, notifying
512 // those that are interested in this event
513 for (int i = listeners.length - 2; i >= 0; i -= 2) {
514 if (listeners[i] == TreeModelListener.class) {
515 // Lazily create the event:
516 if (e == null)
517 e = new TreeModelEvent(source, path, childIndices,
518 children);
519 ((TreeModelListener) listeners[i + 1])
520 .treeNodesInserted(e);
521 }
522 }
523 }
524
525 /**
526 * Notifies all listeners that have registered interest for
527 * notification on this event type. The event instance
528 * is lazily created using the parameters passed into
529 * the fire method.
530 *
531 * @param source the source of the {@code TreeModelEvent};
532 * typically {@code this}
533 * @param path the path to the parent the nodes were removed from
534 * @param childIndices the indices of the removed elements
535 * @param children the removed elements
536 */
537 protected void fireTreeNodesRemoved(Object source, Object[] path,
538 int[] childIndices, Object[] children) {
539 // Guaranteed to return a non-null array
540 Object[] listeners = listenerList.getListenerList();
541 TreeModelEvent e = null;
542 // Process the listeners last to first, notifying
543 // those that are interested in this event
544 for (int i = listeners.length - 2; i >= 0; i -= 2) {
545 if (listeners[i] == TreeModelListener.class) {
546 // Lazily create the event:
547 if (e == null)
548 e = new TreeModelEvent(source, path, childIndices,
549 children);
550 ((TreeModelListener) listeners[i + 1])
551 .treeNodesRemoved(e);
552 }
553 }
554 }
555
556 /**
557 * Notifies all listeners that have registered interest for
558 * notification on this event type. The event instance
559 * is lazily created using the parameters passed into
560 * the fire method.
561 *
562 * @param source the source of the {@code TreeModelEvent};
563 * typically {@code this}
564 * @param path the path to the parent of the structure that has changed;
565 * use {@code null} to identify the root has changed
566 * @param childIndices the indices of the affected elements
567 * @param children the affected elements
568 */
569 protected void fireTreeStructureChanged(Object source,
570 Object[] path, int[] childIndices, Object[] children) {
571 // Guaranteed to return a non-null array
572 Object[] listeners = listenerList.getListenerList();
573 TreeModelEvent e = null;
574 // Process the listeners last to first, notifying
575 // those that are interested in this event
576 for (int i = listeners.length - 2; i >= 0; i -= 2) {
577 if (listeners[i] == TreeModelListener.class) {
578 // Lazily create the event:
579 if (e == null)
580 e = new TreeModelEvent(source, path, childIndices,
581 children);
582 ((TreeModelListener) listeners[i + 1])
583 .treeStructureChanged(e);
584 }
585 }
586 }
587
588 /**
589 * Notifies all listeners that have registered interest for
590 * notification on this event type. The event instance
591 * is lazily created using the parameters passed into
592 * the fire method.
593 *
594 * @param source the source of the {@code TreeModelEvent};
595 * typically {@code this}
596 * @param path the path to the parent of the structure that has changed;
597 * use {@code null} to identify the root has changed
598 */
599 private void fireTreeStructureChanged(Object source, TreePath path) {
600 // Guaranteed to return a non-null array
601 Object[] listeners = listenerList.getListenerList();
602 TreeModelEvent e = null;
603 // Process the listeners last to first, notifying
604 // those that are interested in this event
605 for (int i = listeners.length - 2; i >= 0; i -= 2) {
606 if (listeners[i] == TreeModelListener.class) {
607 // Lazily create the event:
608 if (e == null)
609 e = new TreeModelEvent(source, path);
610 ((TreeModelListener) listeners[i + 1])
611 .treeStructureChanged(e);
612 }
613 }
614 }
615
616 /**
617 * Returns an array of all the objects currently registered
618 * as <code><em>Foo</em>Listener</code>s
619 * upon this model.
620 * <code><em>Foo</em>Listener</code>s are registered using the
621 * <code>add<em>Foo</em>Listener</code> method.
622 *
623 * <p>
624 *
625 * You can specify the <code>listenerType</code> argument
626 * with a class literal,
627 * such as
628 * <code><em>Foo</em>Listener.class</code>.
629 * For example, you can query a
630 * <code>DefaultTreeModel</code> <code>m</code>
631 * for its tree model listeners with the following code:
632 *
633 * <pre>TreeModelListener[] tmls = (TreeModelListener[])(m.getListeners(TreeModelListener.class));</pre>
634 *
635 * If no such listeners exist, this method returns an empty array.
636 *
637 * @param listenerType the type of listeners requested; this parameter
638 * should specify an interface that descends from
639 * <code>java.util.EventListener</code>
640 * @return an array of all objects registered as
641 * <code><em>Foo</em>Listener</code>s on this component,
642 * or an empty array if no such
643 * listeners have been added
644 * @exception ClassCastException if <code>listenerType</code>
645 * doesn't specify a class or interface that implements
646 * <code>java.util.EventListener</code>
647 *
648 * @see #getTreeModelListeners
649 *
650 * @since 1.3
651 */
652 public <T extends EventListener> T[] getListeners(
653 Class<T> listenerType) {
654 return listenerList.getListeners(listenerType);
655 }
656
657 // Serialization support.
658 private void writeObject(ObjectOutputStream s) throws IOException {
659 Vector values = new Vector();
660
661 s.defaultWriteObject();
662 // Save the root, if its Serializable.
663 if (root != null && root instanceof Serializable) {
664 values.addElement("root");
665 values.addElement(root);
666 }
667 s.writeObject(values);
668 }
669
670 private void readObject(ObjectInputStream s) throws IOException,
671 ClassNotFoundException {
672 s.defaultReadObject();
673
674 Vector values = (Vector) s.readObject();
675 int indexCounter = 0;
676 int maxCounter = values.size();
677
678 if (indexCounter < maxCounter
679 && values.elementAt(indexCounter).equals("root")) {
680 root = (TreeNode) values.elementAt(++indexCounter);
681 indexCounter++;
682 }
683 }
684
685 } // End of class DefaultTreeModel
|