View Javadoc

1   package org.lsst.ccs.utilities.structs;
2   
3   import javax.swing.tree.TreeNode;
4   import java.beans.ConstructorProperties;
5   import java.io.Serializable;
6   import java.util.*;
7   
8   /**
9    * Immutable simple tree that can be used by swing Trees.
10   *
11   * @author bamade
12   */
13  // Date: 07/01/2014
14  
15  public class TreeBranch<T extends Serializable> implements Serializable, TreeNode, Iterable<T> {
16  
17      private static final long serialVersionUID = -8299651867210784530L;
18      /**
19       * the parent node. null if node is root.
20       */
21      private TreeBranch<T> parent;
22      /**
23       * list of children
24       */
25      private ArrayList<TreeBranch<T>> children;
26      /**
27       * the value attached to this node
28       */
29      private T content;
30      /**
31       * @ImplNote : there is a problem here: what is the currentIndex of the rootNode?
32       * if 0 this could lead to bugs, if -1 other problems arise
33       */
34      private int currentIndex = -1;
35  
36      /**
37       * to be used initially to build the root (avoid otherwise)
38       *
39       * @param content
40       */
41      public TreeBranch(T content) {
42          this.content = content;
43      }
44  
45      /**
46       * Avoid using this constructor: for beans purposes use
47       * the constructor annotated with <TT>ConstructorProperties</TT>
48       */
49      protected TreeBranch() {
50      }
51  
52      /**
53       * prefer the use of this constructor when building a tree
54       * (instead of using sequence one-arg constructor + addChild)
55       *
56       * @param parent  may be null if node is intended as a root Node.
57       * @param content
58       */
59      @ConstructorProperties({"realParent", "content"})
60      public TreeBranch(TreeBranch<T> parent, T content) {
61          //this.parent = parent;
62          this.content = content;
63          if (parent != null) {
64              parent.addChild(this);
65          }
66      }
67  
68      /**
69       * adds a child to the current node.
70       * Prefer the use of the 2-args constructor if possible;
71       *
72       * @param child
73       */
74      public synchronized void addChild(TreeBranch<T> child) {
75          if (child == null) {
76              throw new IllegalArgumentException("null child");
77          }
78          if (child.parent != null) {
79              throw new IllegalArgumentException(this + " node " + child + " already part of another tree " + child.parent);
80          }
81          if (children == null) {
82              children = new ArrayList<>();
83          }
84          int index = children.size();
85          children.add(child);
86          child.currentIndex = index;
87          child.parent = this;
88      }
89  
90      /**
91       * @return the data stored in this node
92       */
93      public T getContent() {
94          return content;
95      }
96  
97      /**
98       * @return the current index of this node in the parent's
99       * child list. For the rootNode the index is -1 ;
100      */
101     public int getCurrentIndex() {
102         return currentIndex;
103     }
104 
105     /**
106      * @return an iterator of contents where tree is explored in preOrder.
107      */
108     @Override
109     public Iterator<T> iterator() {
110         return new PreOrderIterator<T>(this);
111     }
112 
113     /**
114      * @return an iterator of nodes where tree is explored in preOrder.
115      */
116     public Iterator<TreeBranch<T>> nodeIterator() {
117         return new PreOrderNodeIterator<T>(this);
118     }
119 
120     /**
121      * @return the list of immediate children of this node or an empty list if none
122      */
123     public List<TreeBranch<T>> getChildren() {
124         if (children != null) {
125             return children;
126         }
127         return Collections.emptyList();
128     }
129 
130     /**
131      * @return an iterator over immediate children or an empty Iterator if none
132      */
133     public Iterator<TreeBranch<T>> getChildIterator() {
134         if (children != null) {
135             return children.iterator();
136         }
137         return Collections.emptyIterator();
138     }
139 
140 
141     /**
142      * @return the list of parent nodes starting from the rootNode
143      */
144     public List<TreeBranch<T>> getPath() {
145         ArrayList<TreeBranch<T>> res = new ArrayList<>();
146         TreeBranch<T> curNode = this;
147         while (null != (curNode = curNode.getRealParent())) {
148             res.add(curNode);
149         }
150         Collections.reverse(res);
151         return res;
152     }
153 
154     /**
155      * @param childIndex
156      * @return the child at the requested index or <TT>null</TT>
157      * if out of range.
158      * @ImplNote TODO: specify what happens if childIndex out of range
159      */
160     @Override
161     public TreeNode getChildAt(int childIndex) {
162         if (children != null) {
163             if (childIndex >= children.size() || childIndex < 0) {
164                 return null;
165             }
166             TreeBranch<T> res = children.get(childIndex);
167             assert childIndex == res.currentIndex;
168             return res;
169         }
170         return null;
171     }
172 
173     /**
174      * @return the number of children of this node
175      */
176     @Override
177     public int getChildCount() {
178         if (children == null) return 0;
179         return children.size();
180     }
181 
182     /**
183      * @return the parent of this node with type
184      * <TT>TreeNode</TT> (TreeNode interface contract)
185      */
186     @Override
187     public TreeNode getParent() {
188         return parent;
189     }
190 
191     /**
192      * same as <TT>getParent</TT> but with proper return type
193      *
194      * @return a <TT>TreeBranch</TT> node or null when at the top
195      * of the tree.
196      */
197     public TreeBranch<T> getRealParent() {
198         return parent;
199     }
200 
201     /**
202      * @param node should be a child node of the current node
203      * @return the index in the child list or -1 if the argument
204      * is not found in this list
205      */
206     @Override
207     public int getIndex(TreeNode node) {
208         if (node.getParent() != this) {
209             return -1;
210         }
211         if (children != null) {
212             TreeBranch<T> branch = (TreeBranch<T>) node;
213             return branch.currentIndex;
214         }
215         return -1;
216     }
217 
218     /**
219      * since the tree is immutable this method is not intended to be called during building operations.
220      * It is here for the <TT>TreeNode</TT> contract.
221      *
222      * @return
223      */
224     @Override
225     public boolean getAllowsChildren() {
226         return children != null;
227     }
228 
229     /**
230      * @return true if this node has no children
231      */
232     @Override
233     public boolean isLeaf() {
234         return children == null;
235     }
236 
237     /**
238      * @return true of this node is the top of the tree.
239      */
240     public boolean isRootNode() {
241         return parent == null;
242     }
243 
244     /**
245      * @return an enumeration over children of this node
246      * (TreeNode interface contract)
247      */
248     @Override
249     public Enumeration children() {
250         //was return children.elements(); when Vector was used
251         return new Enumeration() {
252             final Iterator iter = children.iterator();
253 
254             @Override
255             public boolean hasMoreElements() {
256                 return iter.hasNext();
257             }
258 
259             @Override
260             public Object nextElement() {
261                 return iter.next();
262             }
263         };
264     }
265     //utilities
266 
267     /**
268      * @return a String representing the value attached to this node.
269      */
270     @Override
271     public String toString() {
272         return String.valueOf(content);
273     }
274 
275     /**
276      * inner class that delegates to <TT>PreOrderNodeIterator</TT>
277      * to fetch content in pre-order.
278      *
279      * @param <X>
280      */
281     protected static class PreOrderIterator<X extends Serializable> implements Iterator<X> {
282         private final PreOrderNodeIterator<X> preOrderNodeIterator;
283 
284         public PreOrderIterator(TreeBranch<X> rootNode) {
285             preOrderNodeIterator = new PreOrderNodeIterator<>(rootNode);
286         }
287 
288 
289         @Override
290         public X next() {
291             TreeBranch<X> node = preOrderNodeIterator.next();
292             return node.getContent();
293         }
294 
295 
296         @Override
297         public boolean hasNext() {
298             return preOrderNodeIterator.hasNext();
299         }
300 
301 
302         @Override
303         public void remove() {
304             throw new UnsupportedOperationException("remove");
305         }
306     }
307 
308     /**
309      * Inner class to fetch nodes in pre-order.
310      *
311      * @param <X>
312      */
313     protected static class PreOrderNodeIterator<X extends Serializable> implements Iterator<TreeBranch<X>> {
314         private final Stack<Iterator<TreeBranch<X>>> stack = new Stack<>();
315 
316         public PreOrderNodeIterator(TreeBranch<X> rootNode) {
317             //hack to generalize the algorithm: the rootNode is seen as a virtual member of a child list
318             ArrayList<TreeBranch<X>> edenChildList = new ArrayList<>(1);
319             edenChildList.add(rootNode);
320             stack.push(edenChildList.iterator());
321         }
322 
323 
324         @Override
325         public TreeBranch<X> next() {
326             // get two aspirins before reading code! this is an idiom
327             // gets Iterator on the stack
328             Iterator<TreeBranch<X>> iter = stack.peek();
329             // gets the next element
330             TreeBranch<X> node = iter.next();
331             // gets an Iterator over children
332             Iterator<TreeBranch<X>> itChildren = node.getChildIterator();
333             // if nothing else to read at next iteration -> pop
334             if (!iter.hasNext()) {
335                 stack.pop();
336             }
337             // if child Iterator not empty push child Iterator
338             if (itChildren.hasNext()) {
339                 stack.push(itChildren);
340             }
341             return node;
342         }
343 
344 
345         /**
346          * is there an iterator on the stack and
347          * is there something to read ?
348          *
349          * @return
350          */
351         @Override
352         public boolean hasNext() {
353             return (!stack.empty() && stack.peek().hasNext());
354         }
355 
356 
357         @Override
358         public void remove() {
359             throw new UnsupportedOperationException("remove");
360         }
361     }
362 }