1. /*
  2. * @(#)DefaultTreeModel.java 1.41 00/02/02
  3. *
  4. * Copyright 1997-2000 Sun Microsystems, Inc. All Rights Reserved.
  5. *
  6. * This software is the proprietary information of Sun Microsystems, Inc.
  7. * Use is subject to license terms.
  8. *
  9. */
  10. package javax.swing.tree;
  11. import java.util.*;
  12. import java.awt.*;
  13. import java.io.*;
  14. import javax.swing.event.*;
  15. /**
  16. * A simple tree data model that uses TreeNodes.
  17. * For further information and examples that use DefaultTreeModel,
  18. * see <a href="http://java.sun.com/docs/books/tutorial/uiswing/components/tree.html">How to Use Trees</a>
  19. * in <em>The Java Tutorial.</em>
  20. * <p>
  21. * <strong>Warning:</strong>
  22. * Serialized objects of this class will not be compatible with
  23. * future Swing releases. The current serialization support is appropriate
  24. * for short term storage or RMI between applications running the same
  25. * version of Swing. A future release of Swing will provide support for
  26. * long term persistence.
  27. *
  28. * @version 1.41 02/02/00
  29. * @author Rob Davis
  30. * @author Ray Ryan
  31. * @author Scott Violet
  32. */
  33. public class DefaultTreeModel implements Serializable, TreeModel {
  34. /** Root of the tree. */
  35. protected TreeNode root;
  36. /** Listeners. */
  37. protected EventListenerList listenerList = new EventListenerList();
  38. /**
  39. * Determines how the <code>isLeaf</code> method figures
  40. * out if a node is a leaf node. If true, a node is a leaf
  41. * node if it does not allow children. (If it allows
  42. * children, it is not a leaf node, even if no children
  43. * are present.) That lets you distinguish between <i>folder</i>
  44. * nodes and <i>file</i> nodes in a file system, for example.
  45. * <p>
  46. * If this value is false, then any node which has no
  47. * children is a leaf node, and any node may acquire
  48. * children.
  49. *
  50. * @see TreeNode#getAllowsChildren
  51. * @see TreeModel#isLeaf
  52. * @see #setAsksAllowsChildren
  53. */
  54. protected boolean asksAllowsChildren;
  55. /**
  56. * Creates a tree in which any node can have children.
  57. *
  58. * @param root a TreeNode object that is the root of the tree
  59. * @see #DefaultTreeModel(TreeNode, boolean)
  60. */
  61. public DefaultTreeModel(TreeNode root) {
  62. this(root, false);
  63. }
  64. /**
  65. * Creates a tree specifying whether any node can have children,
  66. * or whether only certain nodes can have children.
  67. *
  68. * @param root a TreeNode object that is the root of the tree
  69. * @param askAllowsChildren a boolean, false if any node can
  70. * have children, true if each node is asked to see if
  71. * it can have children
  72. * @see #asksAllowsChildren
  73. */
  74. public DefaultTreeModel(TreeNode root, boolean asksAllowsChildren) {
  75. super();
  76. if (root == null) {
  77. throw new IllegalArgumentException("root is null");
  78. }
  79. this.root = root;
  80. this.asksAllowsChildren = asksAllowsChildren;
  81. }
  82. /**
  83. * Sets whether or not to test leafness by asking getAllowsChildren()
  84. * or isLeaf() to the TreeNodes. If newvalue is true, getAllowsChildren()
  85. * is messaged, otherwise isLeaf() is messaged.
  86. */
  87. public void setAsksAllowsChildren(boolean newValue) {
  88. asksAllowsChildren = newValue;
  89. }
  90. /**
  91. * Tells how leaf nodes are determined.
  92. *
  93. * @return true if only nodes which do not allow children are
  94. * leaf nodes, false if nodes which have no children
  95. * (even if allowed) are leaf nodes
  96. * @see #asksAllowsChildren
  97. */
  98. public boolean asksAllowsChildren() {
  99. return asksAllowsChildren;
  100. }
  101. /**
  102. * Sets the root to <code>root</code>. This will throw an
  103. * IllegalArgumentException if <code>root</code> is null.
  104. */
  105. public void setRoot(TreeNode root) {
  106. if(root == null)
  107. throw new IllegalArgumentException("Root of tree is not allowed to be null");
  108. this.root = root;
  109. nodeStructureChanged(root);
  110. }
  111. /**
  112. * Returns the root of the tree. Returns null only if the tree has
  113. * no nodes.
  114. *
  115. * @return the root of the tree
  116. */
  117. public Object getRoot() {
  118. return root;
  119. }
  120. /**
  121. * Returns the index of child in parent.
  122. */
  123. public int getIndexOfChild(Object parent, Object child) {
  124. if(parent == null || child == null)
  125. return -1;
  126. return ((TreeNode)parent).getIndex((TreeNode)child);
  127. }
  128. /**
  129. * Returns the child of <I>parent</I> at index <I>index</I> in the parent's
  130. * child array. <I>parent</I> must be a node previously obtained from
  131. * this data source. This should not return null if <i>index</i>
  132. * is a valid index for <i>parent</i> (that is <i>index</i> >= 0 &&
  133. * <i>index</i> < getChildCount(<i>parent</i>)).
  134. *
  135. * @param parent a node in the tree, obtained from this data source
  136. * @return the child of <I>parent</I> at index <I>index</I>
  137. */
  138. public Object getChild(Object parent, int index) {
  139. return ((TreeNode)parent).getChildAt(index);
  140. }
  141. /**
  142. * Returns the number of children of <I>parent</I>. Returns 0 if the node
  143. * is a leaf or if it has no children. <I>parent</I> must be a node
  144. * previously obtained from this data source.
  145. *
  146. * @param parent a node in the tree, obtained from this data source
  147. * @return the number of children of the node <I>parent</I>
  148. */
  149. public int getChildCount(Object parent) {
  150. return ((TreeNode)parent).getChildCount();
  151. }
  152. /**
  153. * Returns whether the specified node is a leaf node.
  154. * The way the test is performed depends on the
  155. * <code>askAllowsChildren</code> setting.
  156. *
  157. * @param node the node to check
  158. * @return true if the node is a leaf node
  159. *
  160. * @see #asksAllowsChildren
  161. * @see TreeModel#isLeaf
  162. */
  163. public boolean isLeaf(Object node) {
  164. if(asksAllowsChildren)
  165. return !((TreeNode)node).getAllowsChildren();
  166. return ((TreeNode)node).isLeaf();
  167. }
  168. /**
  169. * Invoke this method if you've modified the TreeNodes upon which this
  170. * model depends. The model will notify all of its listeners that the
  171. * model has changed.
  172. */
  173. public void reload() {
  174. reload(root);
  175. }
  176. /**
  177. * This sets the user object of the TreeNode identified by path
  178. * and posts a node changed. If you use custom user objects in
  179. * the TreeModel you're going to need to subclass this and
  180. * set the user object of the changed node to something meaningful.
  181. */
  182. public void valueForPathChanged(TreePath path, Object newValue) {
  183. MutableTreeNode aNode = (MutableTreeNode)path.getLastPathComponent();
  184. aNode.setUserObject(newValue);
  185. nodeChanged(aNode);
  186. }
  187. /**
  188. * Invoked this to insert newChild at location index in parents children.
  189. * This will then message nodesWereInserted to create the appropriate
  190. * event. This is the preferred way to add children as it will create
  191. * the appropriate event.
  192. */
  193. public void insertNodeInto(MutableTreeNode newChild,
  194. MutableTreeNode parent, int index){
  195. parent.insert(newChild, index);
  196. int[] newIndexs = new int[1];
  197. newIndexs[0] = index;
  198. nodesWereInserted(parent, newIndexs);
  199. }
  200. /**
  201. * Message this to remove node from its parent. This will message
  202. * nodesWereRemoved to create the appropriate event. This is the
  203. * preferred way to remove a node as it handles the event creation
  204. * for you.
  205. */
  206. public void removeNodeFromParent(MutableTreeNode node) {
  207. MutableTreeNode parent = (MutableTreeNode)node.getParent();
  208. if(parent == null)
  209. throw new IllegalArgumentException("node does not have a parent.");
  210. int[] childIndex = new int[1];
  211. Object[] removedArray = new Object[1];
  212. childIndex[0] = parent.getIndex(node);
  213. parent.remove(childIndex[0]);
  214. removedArray[0] = node;
  215. nodesWereRemoved(parent, childIndex, removedArray);
  216. }
  217. /**
  218. * Invoke this method after you've changed how node is to be
  219. * represented in the tree.
  220. */
  221. public void nodeChanged(TreeNode node) {
  222. if(listenerList != null && node != null) {
  223. TreeNode parent = node.getParent();
  224. if(parent != null) {
  225. int anIndex = parent.getIndex(node);
  226. if(anIndex != -1) {
  227. int[] cIndexs = new int[1];
  228. cIndexs[0] = anIndex;
  229. nodesChanged(parent, cIndexs);
  230. }
  231. }
  232. else if (node == getRoot()) {
  233. nodesChanged(node, null);
  234. }
  235. }
  236. }
  237. /**
  238. * Invoke this method if you've modified the TreeNodes upon which this
  239. * model depends. The model will notify all of its listeners that the
  240. * model has changed below the node <code>node</code> (PENDING).
  241. */
  242. public void reload(TreeNode node) {
  243. if(node != null) {
  244. fireTreeStructureChanged(this, getPathToRoot(node), null, null);
  245. }
  246. }
  247. /**
  248. * Invoke this method after you've inserted some TreeNodes into
  249. * node. childIndices should be the index of the new elements and
  250. * must be sorted in ascending order.
  251. */
  252. public void nodesWereInserted(TreeNode node, int[] childIndices) {
  253. if(listenerList != null && node != null && childIndices != null
  254. && childIndices.length > 0) {
  255. int cCount = childIndices.length;
  256. Object[] newChildren = new Object[cCount];
  257. for(int counter = 0; counter < cCount; counter++)
  258. newChildren[counter] = node.getChildAt(childIndices[counter]);
  259. fireTreeNodesInserted(this, getPathToRoot(node), childIndices,
  260. newChildren);
  261. }
  262. }
  263. /**
  264. * Invoke this method after you've removed some TreeNodes from
  265. * node. childIndices should be the index of the removed elements and
  266. * must be sorted in ascending order. And removedChildren should be
  267. * the array of the children objects that were removed.
  268. */
  269. public void nodesWereRemoved(TreeNode node, int[] childIndices,
  270. Object[] removedChildren) {
  271. if(node != null && childIndices != null) {
  272. fireTreeNodesRemoved(this, getPathToRoot(node), childIndices,
  273. removedChildren);
  274. }
  275. }
  276. /**
  277. * Invoke this method after you've changed how the children identified by
  278. * childIndicies are to be represented in the tree.
  279. */
  280. public void nodesChanged(TreeNode node, int[] childIndices) {
  281. if(node != null) {
  282. if (childIndices != null) {
  283. int cCount = childIndices.length;
  284. if(cCount > 0) {
  285. Object[] cChildren = new Object[cCount];
  286. for(int counter = 0; counter < cCount; counter++)
  287. cChildren[counter] = node.getChildAt
  288. (childIndices[counter]);
  289. fireTreeNodesChanged(this, getPathToRoot(node),
  290. childIndices, cChildren);
  291. }
  292. }
  293. else if (node == getRoot()) {
  294. fireTreeNodesChanged(this, getPathToRoot(node), null, null);
  295. }
  296. }
  297. }
  298. /**
  299. * Invoke this method if you've totally changed the children of
  300. * node and its childrens children... This will post a
  301. * treeStructureChanged event.
  302. */
  303. public void nodeStructureChanged(TreeNode node) {
  304. if(node != null) {
  305. fireTreeStructureChanged(this, getPathToRoot(node), null, null);
  306. }
  307. }
  308. /**
  309. * Builds the parents of node up to and including the root node,
  310. * where the original node is the last element in the returned array.
  311. * The length of the returned array gives the node's depth in the
  312. * tree.
  313. *
  314. * @param aNode the TreeNode to get the path for
  315. * @param an array of TreeNodes giving the path from the root to the
  316. * specified node.
  317. */
  318. public TreeNode[] getPathToRoot(TreeNode aNode) {
  319. return getPathToRoot(aNode, 0);
  320. }
  321. /**
  322. * Builds the parents of node up to and including the root node,
  323. * where the original node is the last element in the returned array.
  324. * The length of the returned array gives the node's depth in the
  325. * tree.
  326. *
  327. * @param aNode the TreeNode to get the path for
  328. * @param depth an int giving the number of steps already taken towards
  329. * the root (on recursive calls), used to size the returned array
  330. * @return an array of TreeNodes giving the path from the root to the
  331. * specified node
  332. */
  333. protected TreeNode[] getPathToRoot(TreeNode aNode, int depth) {
  334. TreeNode[] retNodes;
  335. // This method recurses, traversing towards the root in order
  336. // size the array. On the way back, it fills in the nodes,
  337. // starting from the root and working back to the original node.
  338. /* Check for null, in case someone passed in a null node, or
  339. they passed in an element that isn't rooted at root. */
  340. if(aNode == null) {
  341. if(depth == 0)
  342. return null;
  343. else
  344. retNodes = new TreeNode[depth];
  345. }
  346. else {
  347. depth++;
  348. if(aNode == root)
  349. retNodes = new TreeNode[depth];
  350. else
  351. retNodes = getPathToRoot(aNode.getParent(), depth);
  352. retNodes[retNodes.length - depth] = aNode;
  353. }
  354. return retNodes;
  355. }
  356. //
  357. // Events
  358. //
  359. /**
  360. * Adds a listener for the TreeModelEvent posted after the tree changes.
  361. *
  362. * @see #removeTreeModelListener
  363. * @param l the listener to add
  364. */
  365. public void addTreeModelListener(TreeModelListener l) {
  366. listenerList.add(TreeModelListener.class, l);
  367. }
  368. /**
  369. * Removes a listener previously added with <B>addTreeModelListener()</B>.
  370. *
  371. * @see #addTreeModelListener
  372. * @param l the listener to remove
  373. */
  374. public void removeTreeModelListener(TreeModelListener l) {
  375. listenerList.remove(TreeModelListener.class, l);
  376. }
  377. /*
  378. * Notify all listeners that have registered interest for
  379. * notification on this event type. The event instance
  380. * is lazily created using the parameters passed into
  381. * the fire method.
  382. * @see EventListenerList
  383. */
  384. protected void fireTreeNodesChanged(Object source, Object[] path,
  385. int[] childIndices,
  386. Object[] children) {
  387. // Guaranteed to return a non-null array
  388. Object[] listeners = listenerList.getListenerList();
  389. TreeModelEvent e = null;
  390. // Process the listeners last to first, notifying
  391. // those that are interested in this event
  392. for (int i = listeners.length-2; i>=0; i-=2) {
  393. if (listeners[i]==TreeModelListener.class) {
  394. // Lazily create the event:
  395. if (e == null)
  396. e = new TreeModelEvent(source, path,
  397. childIndices, children);
  398. ((TreeModelListener)listeners[i+1]).treeNodesChanged(e);
  399. }
  400. }
  401. }
  402. /*
  403. * Notify all listeners that have registered interest for
  404. * notification on this event type. The event instance
  405. * is lazily created using the parameters passed into
  406. * the fire method.
  407. * @see EventListenerList
  408. */
  409. protected void fireTreeNodesInserted(Object source, Object[] path,
  410. int[] childIndices,
  411. Object[] children) {
  412. // Guaranteed to return a non-null array
  413. Object[] listeners = listenerList.getListenerList();
  414. TreeModelEvent e = null;
  415. // Process the listeners last to first, notifying
  416. // those that are interested in this event
  417. for (int i = listeners.length-2; i>=0; i-=2) {
  418. if (listeners[i]==TreeModelListener.class) {
  419. // Lazily create the event:
  420. if (e == null)
  421. e = new TreeModelEvent(source, path,
  422. childIndices, children);
  423. ((TreeModelListener)listeners[i+1]).treeNodesInserted(e);
  424. }
  425. }
  426. }
  427. /*
  428. * Notify all listeners that have registered interest for
  429. * notification on this event type. The event instance
  430. * is lazily created using the parameters passed into
  431. * the fire method.
  432. * @see EventListenerList
  433. */
  434. protected void fireTreeNodesRemoved(Object source, Object[] path,
  435. int[] childIndices,
  436. Object[] children) {
  437. // Guaranteed to return a non-null array
  438. Object[] listeners = listenerList.getListenerList();
  439. TreeModelEvent e = null;
  440. // Process the listeners last to first, notifying
  441. // those that are interested in this event
  442. for (int i = listeners.length-2; i>=0; i-=2) {
  443. if (listeners[i]==TreeModelListener.class) {
  444. // Lazily create the event:
  445. if (e == null)
  446. e = new TreeModelEvent(source, path,
  447. childIndices, children);
  448. ((TreeModelListener)listeners[i+1]).treeNodesRemoved(e);
  449. }
  450. }
  451. }
  452. /*
  453. * Notify all listeners that have registered interest for
  454. * notification on this event type. The event instance
  455. * is lazily created using the parameters passed into
  456. * the fire method.
  457. * @see EventListenerList
  458. */
  459. protected void fireTreeStructureChanged(Object source, Object[] path,
  460. int[] childIndices,
  461. Object[] children) {
  462. // Guaranteed to return a non-null array
  463. Object[] listeners = listenerList.getListenerList();
  464. TreeModelEvent e = null;
  465. // Process the listeners last to first, notifying
  466. // those that are interested in this event
  467. for (int i = listeners.length-2; i>=0; i-=2) {
  468. if (listeners[i]==TreeModelListener.class) {
  469. // Lazily create the event:
  470. if (e == null)
  471. e = new TreeModelEvent(source, path,
  472. childIndices, children);
  473. ((TreeModelListener)listeners[i+1]).treeStructureChanged(e);
  474. }
  475. }
  476. }
  477. /**
  478. * Return an array of all the listeners of the given type that
  479. * were added to this model.
  480. *
  481. * @returns all of the objects recieving <em>listenerType</em> notifications
  482. * from this model
  483. *
  484. * @since 1.3
  485. */
  486. public EventListener[] getListeners(Class listenerType) {
  487. return listenerList.getListeners(listenerType);
  488. }
  489. // Serialization support.
  490. private void writeObject(ObjectOutputStream s) throws IOException {
  491. Vector values = new Vector();
  492. s.defaultWriteObject();
  493. // Save the root, if its Serializable.
  494. if(root != null && root instanceof Serializable) {
  495. values.addElement("root");
  496. values.addElement(root);
  497. }
  498. s.writeObject(values);
  499. }
  500. private void readObject(ObjectInputStream s)
  501. throws IOException, ClassNotFoundException {
  502. s.defaultReadObject();
  503. Vector values = (Vector)s.readObject();
  504. int indexCounter = 0;
  505. int maxCounter = values.size();
  506. if(indexCounter < maxCounter && values.elementAt(indexCounter).
  507. equals("root")) {
  508. root = (TreeNode)values.elementAt(++indexCounter);
  509. indexCounter++;
  510. }
  511. }
  512. } // End of class DefaultTreeModel