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