View Javadoc
1   package wjhk.jupload2.gui.filepanel.treeview;
2   
3   import java.lang.reflect.Array;
4   import java.util.ArrayList;
5   import java.util.List;
6   
7   import javax.swing.JTree;
8   import javax.swing.event.EventListenerList;
9   import javax.swing.event.TreeModelEvent;
10  import javax.swing.event.TreeModelListener;
11  import javax.swing.tree.TreeModel;
12  import javax.swing.tree.TreePath;
13  
14  import wjhk.jupload2.policies.UploadPolicy;
15  
16  /**
17   * This code is taken from the tutorial written by Jörn Hameister, <A
18   * HREF="http://www.hameister.org/JavaSwingTreeTable.html">available here</A>.<BR/>
19   * <BR/>
20   * From Jörn sample, the main modifications are:
21   * <UL>
22   * <LI>Use of the uploadPolicy</LI>
23   * <LI>Add of the getRoot and setRoot methods</LI>
24   * <LI>Capability to remove an item</LI>
25   * <LI>Transformation in a generic type</LI>
26   * <UL>
27   * <BR/>
28   * In the next step, the interface is MyTreeTableModel to the abstract class MyAbstractTreeTableModel expanded. In this
29   * class, the Wuzelknoten (root) is stored, there is a method for checking whether child nodes are available and manages
30   * all event listener verwerden. The EventListener ensure that structural changes in the data model will be forwarded to
31   * the tree and displayed.<BR/>
32   * <B>Note:</B> We can not use the DefaultTreeModel for various model. One is that we need multiple columns. Another one
33   * is that we must delegate the content to the FilePanel, as the view may be flat or treeview. So we stay with the
34   * implementation which existed before the tree view, there would otherwise be too much changes in the applet code.
35   * 
36   * @author Jörn Hameister
37   */
38  public abstract class MyAbstractTreeTableModel<T extends MyTreeNode> implements MyTreeTableModel<T> {
39  
40      UploadPolicy uploadPolicy = null;
41  
42      /** The {@link JTree} which contains the data for this model */
43      MyTreeTableCellRenderer tree = null;
44  
45      /**
46       * The absolute root of the hierarchical structure. It can't be delegated to the tree table, as the displayed root
47       * can change, when adding other files to upload.<BR/>
48       * Note: on Windows, there can be several roots. So the JUpload's absolute root is a non existing folder, which
49       * contain one folder under Unix ('/') and may contain several folders under Windows ('C:\', 'D:\'...).
50       */
51      protected T absoluteRoot = null;
52  
53      protected T visibleRoot;
54  
55      protected EventListenerList listenerList = new EventListenerList();
56  
57      private static final int CHANGED = 0;
58  
59      private static final int INSERTED = 1;
60  
61      private static final int REMOVED = 2;
62  
63      private static final int STRUCTURE_CHANGED = 3;
64  
65      public MyAbstractTreeTableModel(UploadPolicy uploadPolicy, T root) {
66          this.uploadPolicy = uploadPolicy;
67          this.absoluteRoot = root;
68          this.visibleRoot = root;
69          this.absoluteRoot.setTreeModel(this);
70          this.visibleRoot.setTreeModel(this);
71      }
72  
73      /**
74       * @return the tree
75       */
76      public MyTreeTableCellRenderer getTree() {
77          return tree;
78      }
79  
80      /**
81       * @param tree the tree to set
82       */
83      public void setTree(MyTreeTableCellRenderer tree) {
84          this.tree = tree;
85      }
86  
87      public T getAbsoluteRoot() {
88          return this.absoluteRoot;
89      }
90  
91      /** @see MyTreeTableModel#getRoot() */
92      public T getRoot() {
93          return visibleRoot;
94      }
95  
96      /**
97       * @see MyTreeTableModel#setRoot(MyTreeNode)
98       * @throws IllegalArgumentException If this root is not valid.
99       */
100     public synchronized void setRoot(T root) {
101         this.uploadPolicy.displayInfo("Setting visible Root to file " + root.toString());
102         this.visibleRoot = root;
103         reload();
104     }
105 
106     /** @see MyTreeTableModel#remove(MyTreeNode) */
107     public synchronized void remove(T item) {
108         remove(item, true);
109     }
110 
111     /** @see MyTreeTableModel#remove(MyTreeNode) */
112     public synchronized void removeAndClean(T item) {
113         removeAndClean(item, true);
114     }
115 
116     /**
117      * Actual implementation of {@link #remove(MyTreeNode)}
118      * 
119      * @param item The item to remove
120      * @param callReloadAfterRemoval true if the {@link #reload()} should be called after removal, false otherwise.
121      * @see MyTreeTableModel#remove(MyTreeNode)
122      */
123     @SuppressWarnings("unchecked")
124     synchronized void remove(T item, boolean callReloadAfterRemoval) {
125         if (item == absoluteRoot) {
126             throw new IllegalArgumentException("The absoluteRoot may not be removed");
127         }
128         if (item == visibleRoot) {
129             setRoot((T) visibleRoot.getParent());
130         }
131 
132         FolderNode parent = (FolderNode) item.getParent();
133         if (parent != null) {
134             // Actually executes the removal. This removes the node, and detach all of its descendants.
135             parent.removeChild(item);
136             // If asked for, we indicates that the structure has changed.
137             if (callReloadAfterRemoval) {
138                 reload();
139             }
140         }
141     }
142 
143     /**
144      * Actual implementation of {@link #removeAndClean(MyTreeNode)}
145      * 
146      * @param item The item to remove
147      * @param callReloadAfterRemoval true if the {@link #reload()} should be called after removal, false otherwise.
148      * @see wjhk.jupload2.gui.filepanel.treeview.MyTreeTableModel#removeAndClean(wjhk.jupload2.gui.filepanel.treeview.MyTreeNode)
149      */
150     @SuppressWarnings("unchecked")
151     synchronized void removeAndClean(T item, boolean callReloadAfterRemoval) {
152         if (item == absoluteRoot) {
153             throw new IllegalArgumentException("The absoluteRoot may not be removed");
154         }
155         if (item == visibleRoot) {
156             setRoot((T) visibleRoot.getParent());
157         }
158 
159         T parentNode = (T) item.getParent();
160 
161         // First step: actually remove the node
162         remove(item, false);
163 
164         // Second step: remove the parent node, if it had no other child.
165         if (parentNode.getChildCount() == 0) {
166             // No reload during recursion
167             removeAndClean(parentNode, false);
168         } else if (callReloadAfterRemoval) {
169             reload();
170         }
171     }
172 
173     /** @see MyTreeTableModel#getTreePath(MyTreeNode) */
174     @SuppressWarnings("unchecked")
175     public TreePath getTreePath(T item) {
176         if (item == null) {
177             // This method may be called with a null Node. In this case, we return .. an empty TreePath
178             return null;
179         } else if (item == this.absoluteRoot) {
180             return new TreePath(this.absoluteRoot);
181         } else if (item.getParent() == null) {
182             throw new IllegalArgumentException("Root not found for node " + item);
183         } else {
184             return getTreePath((T) item.getParent()).pathByAddingChild(item);
185         }
186     }
187 
188     public void addTreeModelListener(TreeModelListener l) {
189         listenerList.add(TreeModelListener.class, l);
190     }
191 
192     public void removeTreeModelListener(TreeModelListener l) {
193         listenerList.remove(TreeModelListener.class, l);
194     }
195 
196     private void fireTreeNode(int changeType, Object source, TreePath path, int[] childIndices, T[] children) {
197         Object[] listeners = listenerList.getListenerList();
198         TreeModelEvent e = new TreeModelEvent(source, path, childIndices, children);
199         for (int i = listeners.length - 2; i >= 0; i -= 2) {
200             if (listeners[i] == TreeModelListener.class) {
201                 switch (changeType) {
202                     case CHANGED:
203                         ((TreeModelListener) listeners[i + 1]).treeNodesChanged(e);
204                         break;
205                     case INSERTED:
206                         ((TreeModelListener) listeners[i + 1]).treeNodesInserted(e);
207                         break;
208                     case REMOVED:
209                         ((TreeModelListener) listeners[i + 1]).treeNodesRemoved(e);
210                         break;
211                     case STRUCTURE_CHANGED:
212                         ((TreeModelListener) listeners[i + 1]).treeStructureChanged(e);
213                         break;
214                     default:
215                         break;
216                 }
217             }
218         }
219     }
220 
221     /** @see wjhk.jupload2.gui.filepanel.treeview.MyTreeTableModel#cleanHierarchy() */
222     synchronized public boolean cleanHierarchy() {
223         return cleanHierarchy(absoluteRoot, true);
224     }
225 
226     /**
227      * Cleans the hierarchy behind a given node. It's actually the recursive implementation for
228      * {@link wjhk.jupload2.gui.filepanel.treeview.MyTreeTableModel#cleanHierarchy()}
229      * 
230      * @return true if at least one node was removed, due to cleaning. false if no cleaning was needed.
231      */
232     synchronized public boolean cleanHierarchy(T node) {
233         return cleanHierarchy(node, true);
234     }
235 
236     /**
237      * Cleans the hierarchy behind a given node. It's actually the recursive implementation for
238      * {@link wjhk.jupload2.gui.filepanel.treeview.MyTreeTableModel#cleanHierarchy()}
239      * 
240      * @param node The node where cleaning starts. This node will not be removed.
241      * @param callReloadAfterRemoval true if the {@link #reload()} should be called after removal, false otherwise.
242      * @return true if at least one node was removed, due to cleaning. false if no cleaning was needed.
243      */
244     @SuppressWarnings("unchecked")
245     synchronized private boolean cleanHierarchy(T node, boolean callReloadAfterRemoval) {
246         boolean ret = false;
247         if (node != null) {
248             if (node.getChildren().size() > 0) {
249                 // To avoid that a ConcurrentModificationException is thrown, we must not use the list. Here is another
250                 // way to loop into it, while the list size can diminish (due to node removal during recursion)
251                 List<T> children = new ArrayList<T>();
252                 for (MyTreeNode child : node.getChildren()) {
253                     children.add((T) child);
254                 }
255 
256                 for (T child : children) {
257                     if (cleanHierarchy(child, false)) {
258                         ret = true;
259                     }
260                     // If we've a non leaf folder (typically a FolderNode), with no children, we remove it.
261                     if (!child.isLeaf() && child.getChildCount() == 0) {
262                         node.removeChild(child);
263                         ret = true;
264                     }
265                 }// for
266             }// if (node.getChildren().size() > 0)
267         }// if (node != null)
268 
269         if (ret && callReloadAfterRemoval) {
270             reload();
271         }
272         return ret;
273     }
274 
275     /** @see MyTreeTableModel#reload() */
276     public synchronized void reload() {
277         reload(visibleRoot);
278     }
279 
280     /** @see MyTreeTableModel#reload(MyTreeNode) */
281     @SuppressWarnings("unchecked")
282     public synchronized void reload(T node) {
283         //
284         int n = getChildCount(node);
285         int[] childIdx = new int[n];
286         T[] children = (T[]) (T[]) Array.newInstance(MyTreeNode.class, n);
287 
288         for (int i = 0; i < n; i++) {
289             childIdx[i] = i;
290             children[i] = getChild(node, i);
291         }
292 
293         fireTreeStructureChanged(this, getPathToRoot(node), childIdx, (T[]) children);
294     }
295 
296     /**
297      * Builds the parents of node up to and including the root node, where the original node is the last element in the
298      * returned array. The length of the returned array gives the node's depth in the tree.
299      * 
300      * @param node - the TreeNode to get the path for
301      * @return TreeNode[] - the path from node to the root
302      * @throws IllegalArgumentException If the node has not visibleRoot as a ancestror.
303      */
304     private TreePath getPathToRoot(MyTreeNode node) {
305         if (node == visibleRoot) {
306             return new TreePath(visibleRoot);
307         } else if (node == absoluteRoot) {
308             throw new IllegalArgumentException("The visibleRoot is not an ancestror of the given node");
309         } else {
310             MyTreeNode parent = (MyTreeNode) node.getParent();
311             if (parent == null) {
312                 return null;
313             } else {
314                 return getPathToRoot(parent).pathByAddingChild(node);
315             }
316         }
317     }
318 
319     /** @see MyTreeTableModel#fireTreeNodesChanged(MyTreeNode, MyTreeNode[], int[], MyTreeNode[]) */
320     public void fireTreeNodesChanged(Object source, TreePath path, int[] childIndices, T[] children) {
321         fireTreeNode(CHANGED, source, path, childIndices, children);
322     }
323 
324     /** @see MyTreeTableModel#fireTreeNodesInserted(MyTreeNode, MyTreeNode[], int[], MyTreeNode[]) */
325     public void fireTreeNodesInserted(Object source, TreePath path, int[] childIndices, T[] children) {
326         fireTreeNode(INSERTED, source, path, childIndices, children);
327     }
328 
329     /** @see MyTreeTableModel#fireTreeNodesRemoved(MyTreeNode, MyTreeNode[], int[], MyTreeNode[]) */
330     public void fireTreeNodesRemoved(Object source, TreePath path, int[] childIndices, T[] children) {
331         fireTreeNode(REMOVED, source, path, childIndices, children);
332     }
333 
334     /** @see MyTreeTableModel#fireTreeStructureChanged(MyTreeNode, MyTreeNode[], int[], MyTreeNode[]) */
335     public void fireTreeStructureChanged(Object source, TreePath path, int[] childIndices, T[] children) {
336         fireTreeNode(STRUCTURE_CHANGED, source, path, childIndices, children);
337     }
338 
339     // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
340     // Implementation of the TreeModel interface: can't override these methods by narrowing Object to
341     // MyHierarchicalFileData. So we must 'manually' manage the cast from T to MyHierarchicalFileData.
342     // //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
343 
344     /** @see TreeModel#getChild(Object, int) */
345     @SuppressWarnings("unchecked")
346     public final T getChild(Object parent, int index) {
347         return (T) ((T) parent).getChild(index);
348     }
349 
350     /** @see TreeModel#getChildCount(Object) */
351     @SuppressWarnings("unchecked")
352     public final int getChildCount(Object child) {
353         return ((T) child).getChildCount();
354     }
355 
356     /** @see TreeModel#isLeaf(Object) */
357     @SuppressWarnings("unchecked")
358     public final boolean isLeaf(Object node) {
359         return ((T) node).isLeaf();
360     }
361 
362     /**
363      * This is normally not called
364      * 
365      * @see TreeModel#valueForPathChanged(TreePath, Object)
366      */
367     public final void valueForPathChanged(TreePath path, Object newValue) {
368         uploadPolicy.displayErr("The method MyAbstractTreeTableModel.valueForPathChanged should not be called");
369     }
370 
371     /**
372      * This is normally not called
373      * 
374      * @see TreeModel#getIndexOfChild(Object, Object)
375      */
376     @SuppressWarnings("unchecked")
377     public final int getIndexOfChild(Object parent, Object child) {
378         return ((T) parent).getChildren().indexOf((T) child);
379     }
380 
381     public abstract Object getValueAt(T nodeForRow, int column);
382 
383 }