Coverage Report - wjhk.jupload2.gui.filepanel.treeview.FolderNode
 
Classes in this File Line Coverage Branch Coverage Complexity
FolderNode
91 %
147/161
83 %
50/60
2,152
 
 1  
 //
 2  
 // $Id$
 3  
 //
 4  
 // jupload - A file upload applet.
 5  
 //
 6  
 // Copyright 2015 The JUpload Team
 7  
 //
 8  
 // Created: 6 févr. 2015
 9  
 // Creator: etienne_sf
 10  
 // Last modified: $Date$
 11  
 //
 12  
 // This program is free software; you can redistribute it and/or modify
 13  
 // it under the terms of the GNU General Public License as published by
 14  
 // the Free Software Foundation; either version 2 of the License, or
 15  
 // (at your option) any later version.
 16  
 //
 17  
 // This program is distributed in the hope that it will be useful,
 18  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 19  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 20  
 // GNU General Public License for more details.
 21  
 //
 22  
 // You should have received a copy of the GNU General Public License
 23  
 // along with this program; if not, write to the Free Software
 24  
 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 25  
 
 26  
 package wjhk.jupload2.gui.filepanel.treeview;
 27  
 
 28  
 import java.io.File;
 29  
 import java.io.IOException;
 30  
 import java.io.InputStream;
 31  
 import java.lang.reflect.Array;
 32  
 import java.util.ArrayList;
 33  
 import java.util.Date;
 34  
 import java.util.List;
 35  
 
 36  
 import javax.swing.tree.TreeModel;
 37  
 import javax.swing.tree.TreePath;
 38  
 
 39  
 import wjhk.jupload2.exception.JUploadException;
 40  
 import wjhk.jupload2.exception.JUploadExceptionStopAddingFiles;
 41  
 import wjhk.jupload2.exception.JUploadIOException;
 42  
 import wjhk.jupload2.filedata.FileData;
 43  
 import wjhk.jupload2.gui.filepanel.FilePanelFlatDataModel2;
 44  
 import wjhk.jupload2.policies.UploadPolicy;
 45  
 import wjhk.jupload2.upload.helper.ByteArrayEncoder;
 46  
 
 47  
 /**
 48  
  * @author etienne_sf
 49  
  */
 50  116
 public class FolderNode implements TreeFileDataNode {
 51  
 
 52  
     /** The mode which manages this node. Used to fire events, when nodes are changed */
 53  
     // FIXME Remove the model ref. Let the model generate all relevant messages (would correct bugs and be quicker)!
 54  694
     MyTreeTableModel<TreeFileDataNode> treeModel = null;
 55  
 
 56  
     /**
 57  
      * The flat list file is still responsible to manage the final list of files to download. So each
 58  
      * {@link FileDataNode} creation (as a child of FolderNode) must trigger adding a row into the flat list. This is
 59  
      * done through its TableModel, this attribute.
 60  
      * 
 61  
      * @see #addChild(File)
 62  
      */
 63  694
     FilePanelFlatDataModel2 flatModel = null;
 64  
 
 65  
     /** The {@link FileData} instance to which all calls to the {@link FileData} interface are delegated. */
 66  694
     File file = null;
 67  
 
 68  
     /**
 69  
      * Contains the parent of this node, in the hierarchy. May be null, if this node has not been attached to a hierachy
 70  
      * yet.
 71  
      */
 72  694
     TreeFileDataNode parent = null;
 73  
 
 74  
     /** The list of children, for this folder */
 75  694
     List<TreeFileDataNode> children = null;
 76  
 
 77  
     Date fileModified;
 78  
 
 79  694
     boolean uploadFlag = true;
 80  
 
 81  694
     UploadPolicy uploadPolicy = null;
 82  
 
 83  
     /**
 84  
      * Returns the filename of the file. This utility method is specially used, to manage root folders. For instance new
 85  
      * File("E:\\").getName() returns the empty String, instead of "E:\"!
 86  
      * 
 87  
      * @param f
 88  
      * @return The filename, when File.getname() returns a non empty String. Otherwise, it returns File.getCanonical()
 89  
      *         (for instance, C:\ for a root in Windows)
 90  
      */
 91  
     static public String getFilename(File file) {
 92  2641
         String filename = file.getName();
 93  
         try {
 94  2641
             filename = (filename.equals("")) ? file.getCanonicalPath() : filename;
 95  0
         } catch (IOException e) {
 96  0
             throw new IllegalArgumentException(e.getMessage()
 97  0
                     + " exception, when trying to resolve the canonical path for " + file.getAbsolutePath(), e);
 98  2641
         }
 99  2641
         return filename;
 100  
     }
 101  
 
 102  
     /**
 103  
      * Retrieves the Absolute Path of a node, within the current visible hierarchy. All node names are concatanated,
 104  
      * separated by {@link File#separator}. The path starts with a leading {@link File#separator}.
 105  
      * 
 106  
      * @param node
 107  
      * @return
 108  
      */
 109  
     static public String getAbsolutePath(TreeFileDataNode node) {
 110  3
         StringBuffer sb = new StringBuffer();
 111  3
         int depth = 0;
 112  15
         for (Object o : node.getTreePath().getPath()) {
 113  12
             String oName = o.toString();
 114  
 
 115  12
             sb.append(oName);
 116  
             // We add a Separator, if relevant:
 117  12
             if (depth == 1 && oName.substring(1, 2).equals(":")) {
 118  
                 // Windows Root (C:, D:...).
 119  
                 // Our FolderNode already contains the trailing slash, we don't add it.
 120  
                 // Than, as we already added a leading slah, we must remove it:
 121  1
                 sb.setLength(0);
 122  1
                 sb.append(oName);
 123  11
             } else if (o != node) {
 124  
                 // We don't add the trailing slash, after the last path component.
 125  8
                 sb.append(File.separator);
 126  
             }
 127  12
             depth += 1;
 128  
         }
 129  3
         return sb.toString();
 130  
     }
 131  
 
 132  
     public static int[] getIntArray(int... indexOf) {
 133  698
         int[] ints = new int[indexOf.length];
 134  1396
         for (int i = 0; i < indexOf.length; i += 1) {
 135  698
             ints[i] = indexOf[i];
 136  
         }
 137  698
         return ints;
 138  
     }
 139  
 
 140  
     public static TreeFileDataNode[] getItemArray(TreeFileDataNode... child) {
 141  698
         TreeFileDataNode[] treeNodes = (TreeFileDataNode[]) Array.newInstance(TreeFileDataNode.class, child.length);
 142  1396
         for (int i = 0; i < child.length; i += 1) {
 143  698
             treeNodes[i] = child[i];
 144  
         }
 145  698
         return treeNodes;
 146  
     }
 147  
 
 148  
     protected FolderNode(UploadPolicy uploadPolicy, MyTreeTableModel<TreeFileDataNode> model,
 149  694
             FilePanelFlatDataModel2 flatModel) {
 150  694
         this.uploadPolicy = uploadPolicy;
 151  694
         this.treeModel = model;
 152  694
         this.flatModel = flatModel;
 153  694
         this.children = new ArrayList<TreeFileDataNode>();
 154  694
     }
 155  
 
 156  
     /**
 157  
      * Creates a node, with no link to any parent. This constructor add no children (subfolders, files) to the newly
 158  
      * created FolderNode.
 159  
      * 
 160  
      * @param file The {@link FileData} instance to which all calls to the {@link FileData} interface are delegated
 161  
      */
 162  
     public FolderNode(File file, UploadPolicy uploadPolicy, MyTreeTableModel<TreeFileDataNode> model,
 163  
             FilePanelFlatDataModel2 flatModel) {
 164  
         // First: call the default constructor.
 165  427
         this(uploadPolicy, model, flatModel);
 166  
 
 167  427
         if (!file.isDirectory()) {
 168  1
             throw new IllegalArgumentException("Internal error: " + file.getAbsolutePath() + " should be a folder");
 169  
         }
 170  426
         this.file = file;
 171  426
     }
 172  
 
 173  
     /** @see TreeFileDataNode#getTotalChildCount() */
 174  
     public int getTotalChildCount() {
 175  176
         int total = 0;
 176  176
         for (TreeFileDataNode child : children) {
 177  224
             total += 1 + child.getTotalChildCount();
 178  224
         }
 179  176
         return total;
 180  
     }
 181  
 
 182  
     @SuppressWarnings("unchecked")
 183  
     /** {@inheritDoc} */
 184  
     public List<MyTreeNode> getChildren() {
 185  434
         return (List<MyTreeNode>) (List<?>) children;
 186  
     }
 187  
 
 188  
     /** {@inheritDoc} */
 189  
     public void appendFileProperties(ByteArrayEncoder bae, int index) throws JUploadIOException {
 190  1
         throw new IllegalAccessError("Internal error: appendFileProperties should not be called from "
 191  1
                 + this.getClass().getName());
 192  
     }
 193  
 
 194  
     /** {@inheritDoc} */
 195  
     public void beforeUpload(String uploadPathRoot) throws JUploadException {
 196  1
         throw new IllegalAccessError("Internal error: beforeUpload should not be called from "
 197  1
                 + this.getClass().getName());
 198  
     }
 199  
 
 200  
     /** {@inheritDoc} */
 201  
     public long getUploadLength() {
 202  1
         throw new IllegalAccessError("Internal error: getUploadLength should not be called from "
 203  1
                 + this.getClass().getName());
 204  
     }
 205  
 
 206  
     /** {@inheritDoc} */
 207  
     public void afterUpload() {
 208  1
         throw new IllegalAccessError("Internal error: afterUpload should not be called from "
 209  1
                 + this.getClass().getName());
 210  
     }
 211  
 
 212  
     /** {@inheritDoc} */
 213  
     public InputStream getInputStream() throws JUploadException {
 214  1
         throw new IllegalAccessError("Internal error: getInputStream should not be called from "
 215  1
                 + this.getClass().getName());
 216  
     }
 217  
 
 218  
     /** {@inheritDoc} */
 219  
     public String getFileName() {
 220  
         // We must use the static method, to properly resolve all folder kinds, especially the windows file system root
 221  
         // (C:, D:..)
 222  1277
         return FolderNode.getFilename(file);
 223  
     }
 224  
 
 225  
     /** {@inheritDoc} */
 226  
     public String getFileExtension() {
 227  1
         throw new IllegalAccessError("Internal error: getFileExtension should not be called from "
 228  1
                 + this.getClass().getName());
 229  
     }
 230  
 
 231  
     /** {@inheritDoc} */
 232  
     public long getFileLength() {
 233  1
         return -1;
 234  
     }
 235  
 
 236  
     /** {@inheritDoc} */
 237  
     public Date getLastModified() {
 238  1
         if (this.fileModified == null) {
 239  1
             this.fileModified = new Date(this.file.lastModified());
 240  
         }
 241  1
         return this.fileModified;
 242  
     }
 243  
 
 244  
     /** {@inheritDoc} */
 245  
     public boolean getUploadFlag() {
 246  6
         return uploadFlag;
 247  
     }
 248  
 
 249  
     /** {@inheritDoc} */
 250  
     public void setUploadFlag(boolean uploadFlag) {
 251  4
         if (this.uploadFlag != uploadFlag) {
 252  4
             this.uploadFlag = uploadFlag;
 253  4
             for (TreeFileDataNode tfdn : children) {
 254  10
                 tfdn.setUploadFlag(uploadFlag);
 255  10
             }
 256  4
             if (getParent() == null) {
 257  
                 // Special management for root change
 258  0
                 treeModel.fireTreeNodesChanged(this, null, null, null);
 259  
             } else {
 260  8
                 treeModel.fireTreeNodesChanged(this, treeModel.getTreePath((TreeFileDataNode) getParent()),
 261  4
                         getIntArray((getParent().getChildren()).indexOf(this)), getItemArray((TreeFileDataNode) this));
 262  
             }
 263  
         }
 264  4
     }
 265  
 
 266  
     /** {@inheritDoc} */
 267  
     public String getDirectory() {
 268  1
         return this.file.getAbsoluteFile().getParent();
 269  
     }
 270  
 
 271  
     /** {@inheritDoc} */
 272  
     public String getMD5() throws JUploadException {
 273  1
         throw new IllegalAccessError("Internal error: getMD5 should not be called from " + this.getClass().getName());
 274  
     }
 275  
 
 276  
     /** {@inheritDoc} */
 277  
     public String getMimeType() {
 278  1
         throw new IllegalAccessError("Internal error: getMimeType should not be called from "
 279  1
                 + this.getClass().getName());
 280  
     }
 281  
 
 282  
     /** {@inheritDoc} */
 283  
     public boolean canRead() {
 284  1
         return file.canRead();
 285  
     }
 286  
 
 287  
     protected File getFile() {
 288  0
         throw new IllegalAccessError("Internal error: getFile is deprecated and should not be called from "
 289  0
                 + this.getClass().getName());
 290  
     }
 291  
 
 292  
     /** {@inheritDoc} */
 293  
     public String getRelativeDir() {
 294  0
         throw new IllegalAccessError("Internal error: getRelativeDir should not be called from "
 295  0
                 + this.getClass().getName());
 296  
     }
 297  
 
 298  
     /** {@inheritDoc} */
 299  
     public String getAbsolutePath() {
 300  2
         return FolderNode.getAbsolutePath(this);
 301  
     }
 302  
 
 303  
     /** {@inheritDoc} */
 304  
     public boolean isPreparedForUpload() {
 305  1
         throw new IllegalAccessError("Internal error: isPreparedForUpload should not be called from "
 306  1
                 + this.getClass().getName());
 307  
     }
 308  
 
 309  
     /** {@inheritDoc} */
 310  
     public int getChildCount() {
 311  311
         return (children == null) ? 0 : children.size();
 312  
     }
 313  
 
 314  
     /** {@inheritDoc} */
 315  
     public TreeFileDataNode getChild(int index) {
 316  117
         return (children == null) ? null : children.get(index);
 317  
     }
 318  
 
 319  
     /** {@inheritDoc} */
 320  
     public TreeFileDataNode getChild(String name) {
 321  2053
         for (TreeFileDataNode node : children) {
 322  2796
             if (node.getFileName().equals(name)) {
 323  351
                 return node;
 324  
             }
 325  2445
         }// for
 326  
 
 327  
         // No child with name...
 328  1702
         return null;
 329  
     }
 330  
 
 331  
     /** {@inheritDoc} */
 332  
     public TreeFileDataNode getChild(File file) {
 333  1364
         return getChild(FolderNode.getFilename(file));
 334  
     }
 335  
 
 336  
     /** {@inheritDoc} */
 337  
     public MyTreeNode getParent() {
 338  10069
         return this.parent;
 339  
     }
 340  
 
 341  
     /** {@inheritDoc} */
 342  
 
 343  
     public void setParent(MyTreeNode parent) {
 344  437
         this.parent = (TreeFileDataNode) parent;
 345  437
     }
 346  
 
 347  
     /** @see MyTreeNode#setTreeModel(TreeModel) */
 348  
     @SuppressWarnings("unchecked")
 349  
     public void setTreeModel(TreeModel model) {
 350  969
         this.treeModel = (MyTreeTableModel<TreeFileDataNode>) model;
 351  969
     }
 352  
 
 353  
     /** {@inheritDoc} */
 354  
     public void setFlatModel(FilePanelFlatDataModel2 flatModel) {
 355  12
         this.flatModel = flatModel;
 356  12
     }
 357  
 
 358  
     /**
 359  
      * Removes one child from the children list.
 360  
      * 
 361  
      * @see wjhk.jupload2.gui.filepanel.treeview.MyTreeNode#removeChild(wjhk.jupload2.gui.filepanel.treeview.MyTreeNode)
 362  
      */
 363  
     public void removeChild(MyTreeNode child) {
 364  35
         if (!children.remove(child)) {
 365  1
             throw new IllegalArgumentException(child.toString() + " is not a child of " + getFileName());
 366  
         }
 367  
         // if (fireTreeModelEvent && model != null) {
 368  
         // model.fireTreeNodesRemoved(this, model.getTreePath(this), indexes, itemRemoved);
 369  
         // FIXME this must be very expensive. But it works. To be optimized.
 370  
         // model.reload(this);
 371  
         // }
 372  
         // Let's separate this node from the context. This is necessary, to avoid memory leak.
 373  34
         child.setParent(null);
 374  34
         child.setTreeModel(null);
 375  34
         child.setFlatModel(null);
 376  
         ;
 377  
 
 378  
         // If the child to detach is a FolderNode, we must also detach all its children, to avoid memory leak. It will
 379  
         // be done recursively, for all descendants.
 380  41
         while (child.getChildCount() > 0) {
 381  
             // If child has children, it is a FolderNode
 382  7
             ((FolderNode) child).removeChild(child.getChild(0));
 383  
         }// while
 384  34
     }
 385  
 
 386  
     /**
 387  
      * This method adds a new FileData in the children list, for the current object. It checks if this the given child
 388  
      * has already been created. If yes, it displays a warning, and returns it. If node, it created a new node, and add
 389  
      * it to the children list for this node.
 390  
      * 
 391  
      * @param node
 392  
      */
 393  
     public TreeFileDataNode addChild(FileData fileData) {
 394  1
         return addChild(new FileDataNode(fileData));
 395  
     }
 396  
 
 397  
     /**
 398  
      * Add a new child to the children of this folder. It first checks that this child doesn't already exists. If yes,
 399  
      * an error is displayed.
 400  
      * 
 401  
      * @param child
 402  
      * @return
 403  
      */
 404  
     public TreeFileDataNode addChild(TreeFileDataNode child) {
 405  688
         TreeFileDataNode alreadyExistingChild = (TreeFileDataNode) getChild(child.getFileName());
 406  688
         if (alreadyExistingChild == null) {
 407  
             // This child is not already a child of this folder.
 408  686
             children.add(child);
 409  686
             child.setTreeModel(this.treeModel);
 410  686
             child.setParent(this);
 411  1372
             treeModel.fireTreeNodesInserted(this, treeModel.getTreePath(this), getIntArray(children.indexOf(child)),
 412  686
                     getItemArray((TreeFileDataNode) child));
 413  686
             return child;
 414  
         } else {
 415  4
             uploadPolicy.displayWarn("The FileData for " + child.getAbsolutePath() + " already exists for the folder "
 416  2
                     + getFileName());
 417  2
             return alreadyExistingChild;
 418  
         }
 419  
     }
 420  
 
 421  
     /**
 422  
      * This method checks if this the given subfolder has already been created. And it returns it, if it wasn't. The new
 423  
      * node is also added to the children list of this node.
 424  
      * 
 425  
      * @param node
 426  
      */
 427  
     public FolderNode getSubfolderOrCreateIt(File file) throws JUploadExceptionStopAddingFiles {
 428  2
         if (!file.isDirectory()) {
 429  0
             throw new JUploadExceptionStopAddingFiles(file.getAbsolutePath() + " must be a directory");
 430  
         }
 431  2
         TreeFileDataNode child = addChild(new FolderNode(file, uploadPolicy, treeModel, flatModel));
 432  
 
 433  2
         if (!(child instanceof FolderNode)) {
 434  0
             throw new JUploadExceptionStopAddingFiles("A child with the same name (" + file.getName()
 435  
                     + ") already exists, but is not a folder");
 436  
         } else {
 437  2
             return (FolderNode) child;
 438  
         }
 439  
     }
 440  
 
 441  
     /** {@inheritDoc} */
 442  
     public boolean isLeaf() {
 443  377
         return false;
 444  
     }
 445  
 
 446  
     /**
 447  
      * Important: this method is called by the JTree class, as the 'main' name for the node.
 448  
      * 
 449  
      * @see java.lang.Object#toString()
 450  
      */
 451  
     @Override
 452  
     public String toString() {
 453  244
         return getFileName();
 454  
     }
 455  
 
 456  
     /** {@inheritDoc} */
 457  
     public TreeFileDataNode getTreeFileDataNode() {
 458  0
         return this;
 459  
     }
 460  
 
 461  
     /** {@inheritDoc} */
 462  
     public void setTreeFileDataNode(TreeFileDataNode node) {
 463  0
         throw new IllegalStateException("setTreeFileDataNode may not be called againts a FolderNode");
 464  
     }
 465  
 
 466  
     /** {@inheritDoc} */
 467  
     public TreePath getTreePath() {
 468  14
         if (parent == null) {
 469  3
             return new TreePath(this);
 470  
         } else {
 471  11
             return parent.getTreePath().pathByAddingChild(this);
 472  
         }
 473  
     }
 474  
 
 475  
     /**
 476  
      * Adds a given file as a Child of the current node. This file may or may not be in the file of the parent node.
 477  
      * 
 478  
      * @param f The file to add. If f is a folder, none of its descendants (files, folders) will be added. Only f is
 479  
      *            added.
 480  
      * @return The created child
 481  
      * @throws JUploadExceptionStopAddingFiles
 482  
      */
 483  
     public TreeFileDataNode addChild(File f) throws JUploadExceptionStopAddingFiles {
 484  408
         if (getChild(f) != null) {
 485  0
             throw new JUploadExceptionStopAddingFiles("Internal error: " + f.getAbsolutePath()
 486  0
                     + " is already a child of the node for " + this.file.getAbsolutePath());
 487  
         }
 488  
         // It's not already a child of this node. We add it.
 489  408
         if (f.isDirectory()) {
 490  407
             return addChild(new FolderNode(f, uploadPolicy, treeModel, flatModel));
 491  
         } else {
 492  1
             FileData fd = flatModel.addFile(f);
 493  1
             return addChild(new FileDataNode(fd));
 494  
         }
 495  
     }
 496  
 
 497  
     /**
 498  
      * Adds a given file as a Child of the current node. This file may or may not be in the file of the parent node.
 499  
      * 
 500  
      * @param f The file to add.
 501  
      * @return The total number of files that have been added, not counting the folders.
 502  
      * @throws JUploadExceptionStopAddingFiles
 503  
      */
 504  
     public int addChildAndDescendants(File f) throws JUploadExceptionStopAddingFiles {
 505  313
         int nbFiles = 0;
 506  
 
 507  313
         TreeFileDataNode child = getChild(f);
 508  313
         if (child == null) {
 509  
             // It's not already a child of this node. We add it.
 510  300
             if (f.isDirectory()) {
 511  102
                 child = addChild(f);
 512  
             } else {
 513  198
                 FileData fd = flatModel.addFile(f);
 514  
                 // The upload policy may decide to not add this file. In this case, fd is null
 515  198
                 if (fd != null) {
 516  198
                     child = addChild(new FileDataNode(fd));
 517  198
                     fd.setTreeFileDataNode(child);
 518  198
                     nbFiles += 1;
 519  
                 }
 520  
             }
 521  
         }
 522  
 
 523  
         // If it's a folder, we need to add all its children (whether or not this FolderNode already existed in the
 524  
         // hierarchy), as we need to be also sure that all descendant are in the hierarchy.
 525  313
         if (f.isDirectory()) {
 526  
             // We add files here, in order to calculate nbFiles
 527  398
             for (File file : f.listFiles()) {
 528  288
                 nbFiles += ((FolderNode) child).addChildAndDescendants(file);
 529  
             } // for
 530  
         }
 531  313
         return nbFiles;
 532  
     }
 533  
 
 534  
 }