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      * returns a String that represents the tree
277      * @return
278      */
279     public String deepString() {
280        return deepString(0)  ;
281     }
282 
283     protected String deepString(int level) {
284         StringBuilder stb  = new StringBuilder() ;
285         for(int ix = 0 ; ix < level ; ix++) {
286             stb.append('\t') ;
287         }
288         stb.append(toString()).append('\n') ;
289         int levelChilds = level+1 ;
290         if(children != null) {
291             for (TreeBranch child : children) {
292                 stb.append(child.deepString(levelChilds));
293             }
294         }
295        return stb.toString() ;
296     }
297 
298     /**
299      * inner class that delegates to <TT>PreOrderNodeIterator</TT>
300      * to fetch content in pre-order.
301      *
302      * @param <X>
303      */
304     protected static class PreOrderIterator<X extends Serializable> implements Iterator<X> {
305         private final PreOrderNodeIterator<X> preOrderNodeIterator;
306 
307         public PreOrderIterator(TreeBranch<X> rootNode) {
308             preOrderNodeIterator = new PreOrderNodeIterator<>(rootNode);
309         }
310 
311 
312         @Override
313         public X next() {
314             TreeBranch<X> node = preOrderNodeIterator.next();
315             return node.getContent();
316         }
317 
318 
319         @Override
320         public boolean hasNext() {
321             return preOrderNodeIterator.hasNext();
322         }
323 
324 
325         @Override
326         public void remove() {
327             throw new UnsupportedOperationException("remove");
328         }
329     }
330 
331     /**
332      * Inner class to fetch nodes in pre-order.
333      *
334      * @param <X>
335      */
336     protected static class PreOrderNodeIterator<X extends Serializable> implements Iterator<TreeBranch<X>> {
337         private final Stack<Iterator<TreeBranch<X>>> stack = new Stack<>();
338 
339         public PreOrderNodeIterator(TreeBranch<X> rootNode) {
340             //hack to generalize the algorithm: the rootNode is seen as a virtual member of a child list
341             ArrayList<TreeBranch<X>> edenChildList = new ArrayList<>(1);
342             edenChildList.add(rootNode);
343             stack.push(edenChildList.iterator());
344         }
345 
346 
347         @Override
348         public TreeBranch<X> next() {
349             // get two aspirins before reading code! this is an idiom
350             // gets Iterator on the stack
351             Iterator<TreeBranch<X>> iter = stack.peek();
352             // gets the next element
353             TreeBranch<X> node = iter.next();
354             // gets an Iterator over children
355             Iterator<TreeBranch<X>> itChildren = node.getChildIterator();
356             // if nothing else to read at next iteration -> pop
357             if (!iter.hasNext()) {
358                 stack.pop();
359             }
360             // if child Iterator not empty push child Iterator
361             if (itChildren.hasNext()) {
362                 stack.push(itChildren);
363             }
364             return node;
365         }
366 
367 
368         /**
369          * is there an iterator on the stack and
370          * is there something to read ?
371          *
372          * @return
373          */
374         @Override
375         public boolean hasNext() {
376             return (!stack.empty() && stack.peek().hasNext());
377         }
378 
379 
380         @Override
381         public void remove() {
382             throw new UnsupportedOperationException("remove");
383         }
384     }
385 }