Coverage Report - wjhk.jupload2.gui.filepanel.treeview.MyAbstractTreeTableModel
 
Classes in this File Line Coverage Branch Coverage Complexity
MyAbstractTreeTableModel
72 %
89/122
57 %
33/57
2,267
 
 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  419
 public abstract class MyAbstractTreeTableModel<T extends MyTreeNode> implements MyTreeTableModel<T> {
 39  
 
 40  266
     UploadPolicy uploadPolicy = null;
 41  
 
 42  
     /** The {@link JTree} which contains the data for this model */
 43  266
     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  266
     protected T absoluteRoot = null;
 52  
 
 53  
     protected T visibleRoot;
 54  
 
 55  266
     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  266
     public MyAbstractTreeTableModel(UploadPolicy uploadPolicy, T root) {
 66  266
         this.uploadPolicy = uploadPolicy;
 67  266
         this.absoluteRoot = root;
 68  266
         this.visibleRoot = root;
 69  266
         this.absoluteRoot.setTreeModel(this);
 70  266
         this.visibleRoot.setTreeModel(this);
 71  266
     }
 72  
 
 73  
     /**
 74  
      * @return the tree
 75  
      */
 76  
     public MyTreeTableCellRenderer getTree() {
 77  2
         return tree;
 78  
     }
 79  
 
 80  
     /**
 81  
      * @param tree the tree to set
 82  
      */
 83  
     public void setTree(MyTreeTableCellRenderer tree) {
 84  41
         this.tree = tree;
 85  41
     }
 86  
 
 87  
     public T getAbsoluteRoot() {
 88  34
         return this.absoluteRoot;
 89  
     }
 90  
 
 91  
     /** @see MyTreeTableModel#getRoot() */
 92  
     public T getRoot() {
 93  386
         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  25
         this.uploadPolicy.displayInfo("Setting visible Root to file " + root.toString());
 102  25
         this.visibleRoot = root;
 103  25
         reload();
 104  25
     }
 105  
 
 106  
     /** @see MyTreeTableModel#remove(MyTreeNode) */
 107  
     public synchronized void remove(T item) {
 108  18
         remove(item, true);
 109  17
     }
 110  
 
 111  
     /** @see MyTreeTableModel#remove(MyTreeNode) */
 112  
     public synchronized void removeAndClean(T item) {
 113  0
         removeAndClean(item, true);
 114  0
     }
 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  18
         if (item == absoluteRoot) {
 126  0
             throw new IllegalArgumentException("The absoluteRoot may not be removed");
 127  
         }
 128  18
         if (item == visibleRoot) {
 129  0
             setRoot((T) visibleRoot.getParent());
 130  
         }
 131  
 
 132  18
         FolderNode parent = (FolderNode) item.getParent();
 133  17
         if (parent != null) {
 134  
             // Actually executes the removal. This removes the node, and detach all of its descendants.
 135  17
             parent.removeChild(item);
 136  
             // If asked for, we indicates that the structure has changed.
 137  17
             if (callReloadAfterRemoval) {
 138  17
                 reload();
 139  
             }
 140  
         }
 141  17
     }
 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  0
         if (item == absoluteRoot) {
 153  0
             throw new IllegalArgumentException("The absoluteRoot may not be removed");
 154  
         }
 155  0
         if (item == visibleRoot) {
 156  0
             setRoot((T) visibleRoot.getParent());
 157  
         }
 158  
 
 159  0
         T parentNode = (T) item.getParent();
 160  
 
 161  
         // First step: actually remove the node
 162  0
         remove(item, false);
 163  
 
 164  
         // Second step: remove the parent node, if it had no other child.
 165  0
         if (parentNode.getChildCount() == 0) {
 166  
             // No reload during recursion
 167  0
             removeAndClean(parentNode, false);
 168  0
         } else if (callReloadAfterRemoval) {
 169  0
             reload();
 170  
         }
 171  0
     }
 172  
 
 173  
     /** @see MyTreeTableModel#getTreePath(MyTreeNode) */
 174  
     @SuppressWarnings("unchecked")
 175  
     public TreePath getTreePath(T item) {
 176  5725
         if (item == null) {
 177  
             // This method may be called with a null Node. In this case, we return .. an empty TreePath
 178  0
             return null;
 179  5725
         } else if (item == this.absoluteRoot) {
 180  699
             return new TreePath(this.absoluteRoot);
 181  5026
         } else if (item.getParent() == null) {
 182  0
             throw new IllegalArgumentException("Root not found for node " + item);
 183  
         } else {
 184  5026
             return getTreePath((T) item.getParent()).pathByAddingChild(item);
 185  
         }
 186  
     }
 187  
 
 188  
     public void addTreeModelListener(TreeModelListener l) {
 189  120
         listenerList.add(TreeModelListener.class, l);
 190  120
     }
 191  
 
 192  
     public void removeTreeModelListener(TreeModelListener l) {
 193  0
         listenerList.remove(TreeModelListener.class, l);
 194  0
     }
 195  
 
 196  
     private void fireTreeNode(int changeType, Object source, TreePath path, int[] childIndices, T[] children) {
 197  742
         Object[] listeners = listenerList.getListenerList();
 198  742
         TreeModelEvent e = new TreeModelEvent(source, path, childIndices, children);
 199  1198
         for (int i = listeners.length - 2; i >= 0; i -= 2) {
 200  456
             if (listeners[i] == TreeModelListener.class) {
 201  456
                 switch (changeType) {
 202  
                     case CHANGED:
 203  0
                         ((TreeModelListener) listeners[i + 1]).treeNodesChanged(e);
 204  0
                         break;
 205  
                     case INSERTED:
 206  408
                         ((TreeModelListener) listeners[i + 1]).treeNodesInserted(e);
 207  408
                         break;
 208  
                     case REMOVED:
 209  0
                         ((TreeModelListener) listeners[i + 1]).treeNodesRemoved(e);
 210  0
                         break;
 211  
                     case STRUCTURE_CHANGED:
 212  48
                         ((TreeModelListener) listeners[i + 1]).treeStructureChanged(e);
 213  48
                         break;
 214  
                     default:
 215  
                         break;
 216  
                 }
 217  
             }
 218  
         }
 219  742
     }
 220  
 
 221  
     /** @see wjhk.jupload2.gui.filepanel.treeview.MyTreeTableModel#cleanHierarchy() */
 222  
     synchronized public boolean cleanHierarchy() {
 223  1
         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  1
         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  35
         boolean ret = false;
 247  35
         if (node != null) {
 248  35
             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  15
                 List<T> children = new ArrayList<T>();
 252  15
                 for (MyTreeNode child : node.getChildren()) {
 253  33
                     children.add((T) child);
 254  33
                 }
 255  
 
 256  15
                 for (T child : children) {
 257  33
                     if (cleanHierarchy(child, false)) {
 258  11
                         ret = true;
 259  
                     }
 260  
                     // If we've a non leaf folder (typically a FolderNode), with no children, we remove it.
 261  33
                     if (!child.isLeaf() && child.getChildCount() == 0) {
 262  6
                         node.removeChild(child);
 263  6
                         ret = true;
 264  
                     }
 265  33
                 }// for
 266  
             }// if (node.getChildren().size() > 0)
 267  
         }// if (node != null)
 268  
 
 269  35
         if (ret && callReloadAfterRemoval) {
 270  2
             reload();
 271  
         }
 272  35
         return ret;
 273  
     }
 274  
 
 275  
     /** @see MyTreeTableModel#reload() */
 276  
     public synchronized void reload() {
 277  44
         reload(visibleRoot);
 278  44
     }
 279  
 
 280  
     /** @see MyTreeTableModel#reload(MyTreeNode) */
 281  
     @SuppressWarnings("unchecked")
 282  
     public synchronized void reload(T node) {
 283  
         //
 284  44
         int n = getChildCount(node);
 285  44
         int[] childIdx = new int[n];
 286  44
         T[] children = (T[]) (T[]) Array.newInstance(MyTreeNode.class, n);
 287  
 
 288  99
         for (int i = 0; i < n; i++) {
 289  55
             childIdx[i] = i;
 290  55
             children[i] = getChild(node, i);
 291  
         }
 292  
 
 293  44
         fireTreeStructureChanged(this, getPathToRoot(node), childIdx, (T[]) children);
 294  44
     }
 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  44
         if (node == visibleRoot) {
 306  44
             return new TreePath(visibleRoot);
 307  0
         } else if (node == absoluteRoot) {
 308  0
             throw new IllegalArgumentException("The visibleRoot is not an ancestror of the given node");
 309  
         } else {
 310  0
             MyTreeNode parent = (MyTreeNode) node.getParent();
 311  0
             if (parent == null) {
 312  0
                 return null;
 313  
             } else {
 314  0
                 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  12
         fireTreeNode(CHANGED, source, path, childIndices, children);
 322  12
     }
 323  
 
 324  
     /** @see MyTreeTableModel#fireTreeNodesInserted(MyTreeNode, MyTreeNode[], int[], MyTreeNode[]) */
 325  
     public void fireTreeNodesInserted(Object source, TreePath path, int[] childIndices, T[] children) {
 326  686
         fireTreeNode(INSERTED, source, path, childIndices, children);
 327  686
     }
 328  
 
 329  
     /** @see MyTreeTableModel#fireTreeNodesRemoved(MyTreeNode, MyTreeNode[], int[], MyTreeNode[]) */
 330  
     public void fireTreeNodesRemoved(Object source, TreePath path, int[] childIndices, T[] children) {
 331  0
         fireTreeNode(REMOVED, source, path, childIndices, children);
 332  0
     }
 333  
 
 334  
     /** @see MyTreeTableModel#fireTreeStructureChanged(MyTreeNode, MyTreeNode[], int[], MyTreeNode[]) */
 335  
     public void fireTreeStructureChanged(Object source, TreePath path, int[] childIndices, T[] children) {
 336  44
         fireTreeNode(STRUCTURE_CHANGED, source, path, childIndices, children);
 337  44
     }
 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  109
         return (T) ((T) parent).getChild(index);
 348  
     }
 349  
 
 350  
     /** @see TreeModel#getChildCount(Object) */
 351  
     @SuppressWarnings("unchecked")
 352  
     public final int getChildCount(Object child) {
 353  237
         return ((T) child).getChildCount();
 354  
     }
 355  
 
 356  
     /** @see TreeModel#isLeaf(Object) */
 357  
     @SuppressWarnings("unchecked")
 358  
     public final boolean isLeaf(Object node) {
 359  371
         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  0
         uploadPolicy.displayErr("The method MyAbstractTreeTableModel.valueForPathChanged should not be called");
 369  0
     }
 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  211
         return ((T) parent).getChildren().indexOf((T) child);
 379  
     }
 380  
 
 381  
     public abstract Object getValueAt(T nodeForRow, int column);
 382  
 
 383  
 }