View Javadoc
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  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      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      FilePanelFlatDataModel2 flatModel = null;
64  
65      /** The {@link FileData} instance to which all calls to the {@link FileData} interface are delegated. */
66      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      TreeFileDataNode parent = null;
73  
74      /** The list of children, for this folder */
75      List<TreeFileDataNode> children = null;
76  
77      Date fileModified;
78  
79      boolean uploadFlag = true;
80  
81      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          String filename = file.getName();
93          try {
94              filename = (filename.equals("")) ? file.getCanonicalPath() : filename;
95          } catch (IOException e) {
96              throw new IllegalArgumentException(e.getMessage()
97                      + " exception, when trying to resolve the canonical path for " + file.getAbsolutePath(), e);
98          }
99          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         StringBuffer sb = new StringBuffer();
111         int depth = 0;
112         for (Object o : node.getTreePath().getPath()) {
113             String oName = o.toString();
114 
115             sb.append(oName);
116             // We add a Separator, if relevant:
117             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                 sb.setLength(0);
122                 sb.append(oName);
123             } else if (o != node) {
124                 // We don't add the trailing slash, after the last path component.
125                 sb.append(File.separator);
126             }
127             depth += 1;
128         }
129         return sb.toString();
130     }
131 
132     public static int[] getIntArray(int... indexOf) {
133         int[] ints = new int[indexOf.length];
134         for (int i = 0; i < indexOf.length; i += 1) {
135             ints[i] = indexOf[i];
136         }
137         return ints;
138     }
139 
140     public static TreeFileDataNode[] getItemArray(TreeFileDataNode... child) {
141         TreeFileDataNode[] treeNodes = (TreeFileDataNode[]) Array.newInstance(TreeFileDataNode.class, child.length);
142         for (int i = 0; i < child.length; i += 1) {
143             treeNodes[i] = child[i];
144         }
145         return treeNodes;
146     }
147 
148     protected FolderNode(UploadPolicy uploadPolicy, MyTreeTableModel<TreeFileDataNode> model,
149             FilePanelFlatDataModel2 flatModel) {
150         this.uploadPolicy = uploadPolicy;
151         this.treeModel = model;
152         this.flatModel = flatModel;
153         this.children = new ArrayList<TreeFileDataNode>();
154     }
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         this(uploadPolicy, model, flatModel);
166 
167         if (!file.isDirectory()) {
168             throw new IllegalArgumentException("Internal error: " + file.getAbsolutePath() + " should be a folder");
169         }
170         this.file = file;
171     }
172 
173     /** @see TreeFileDataNode#getTotalChildCount() */
174     public int getTotalChildCount() {
175         int total = 0;
176         for (TreeFileDataNode child : children) {
177             total += 1 + child.getTotalChildCount();
178         }
179         return total;
180     }
181 
182     @SuppressWarnings("unchecked")
183     /** {@inheritDoc} */
184     public List<MyTreeNode> getChildren() {
185         return (List<MyTreeNode>) (List<?>) children;
186     }
187 
188     /** {@inheritDoc} */
189     public void appendFileProperties(ByteArrayEncoder bae, int index) throws JUploadIOException {
190         throw new IllegalAccessError("Internal error: appendFileProperties should not be called from "
191                 + this.getClass().getName());
192     }
193 
194     /** {@inheritDoc} */
195     public void beforeUpload(String uploadPathRoot) throws JUploadException {
196         throw new IllegalAccessError("Internal error: beforeUpload should not be called from "
197                 + this.getClass().getName());
198     }
199 
200     /** {@inheritDoc} */
201     public long getUploadLength() {
202         throw new IllegalAccessError("Internal error: getUploadLength should not be called from "
203                 + this.getClass().getName());
204     }
205 
206     /** {@inheritDoc} */
207     public void afterUpload() {
208         throw new IllegalAccessError("Internal error: afterUpload should not be called from "
209                 + this.getClass().getName());
210     }
211 
212     /** {@inheritDoc} */
213     public InputStream getInputStream() throws JUploadException {
214         throw new IllegalAccessError("Internal error: getInputStream should not be called from "
215                 + 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         return FolderNode.getFilename(file);
223     }
224 
225     /** {@inheritDoc} */
226     public String getFileExtension() {
227         throw new IllegalAccessError("Internal error: getFileExtension should not be called from "
228                 + this.getClass().getName());
229     }
230 
231     /** {@inheritDoc} */
232     public long getFileLength() {
233         return -1;
234     }
235 
236     /** {@inheritDoc} */
237     public Date getLastModified() {
238         if (this.fileModified == null) {
239             this.fileModified = new Date(this.file.lastModified());
240         }
241         return this.fileModified;
242     }
243 
244     /** {@inheritDoc} */
245     public boolean getUploadFlag() {
246         return uploadFlag;
247     }
248 
249     /** {@inheritDoc} */
250     public void setUploadFlag(boolean uploadFlag) {
251         if (this.uploadFlag != uploadFlag) {
252             this.uploadFlag = uploadFlag;
253             for (TreeFileDataNode tfdn : children) {
254                 tfdn.setUploadFlag(uploadFlag);
255             }
256             if (getParent() == null) {
257                 // Special management for root change
258                 treeModel.fireTreeNodesChanged(this, null, null, null);
259             } else {
260                 treeModel.fireTreeNodesChanged(this, treeModel.getTreePath((TreeFileDataNode) getParent()),
261                         getIntArray((getParent().getChildren()).indexOf(this)), getItemArray((TreeFileDataNode) this));
262             }
263         }
264     }
265 
266     /** {@inheritDoc} */
267     public String getDirectory() {
268         return this.file.getAbsoluteFile().getParent();
269     }
270 
271     /** {@inheritDoc} */
272     public String getMD5() throws JUploadException {
273         throw new IllegalAccessError("Internal error: getMD5 should not be called from " + this.getClass().getName());
274     }
275 
276     /** {@inheritDoc} */
277     public String getMimeType() {
278         throw new IllegalAccessError("Internal error: getMimeType should not be called from "
279                 + this.getClass().getName());
280     }
281 
282     /** {@inheritDoc} */
283     public boolean canRead() {
284         return file.canRead();
285     }
286 
287     protected File getFile() {
288         throw new IllegalAccessError("Internal error: getFile is deprecated and should not be called from "
289                 + this.getClass().getName());
290     }
291 
292     /** {@inheritDoc} */
293     public String getRelativeDir() {
294         throw new IllegalAccessError("Internal error: getRelativeDir should not be called from "
295                 + this.getClass().getName());
296     }
297 
298     /** {@inheritDoc} */
299     public String getAbsolutePath() {
300         return FolderNode.getAbsolutePath(this);
301     }
302 
303     /** {@inheritDoc} */
304     public boolean isPreparedForUpload() {
305         throw new IllegalAccessError("Internal error: isPreparedForUpload should not be called from "
306                 + this.getClass().getName());
307     }
308 
309     /** {@inheritDoc} */
310     public int getChildCount() {
311         return (children == null) ? 0 : children.size();
312     }
313 
314     /** {@inheritDoc} */
315     public TreeFileDataNode getChild(int index) {
316         return (children == null) ? null : children.get(index);
317     }
318 
319     /** {@inheritDoc} */
320     public TreeFileDataNode getChild(String name) {
321         for (TreeFileDataNode node : children) {
322             if (node.getFileName().equals(name)) {
323                 return node;
324             }
325         }// for
326 
327         // No child with name...
328         return null;
329     }
330 
331     /** {@inheritDoc} */
332     public TreeFileDataNode getChild(File file) {
333         return getChild(FolderNode.getFilename(file));
334     }
335 
336     /** {@inheritDoc} */
337     public MyTreeNode getParent() {
338         return this.parent;
339     }
340 
341     /** {@inheritDoc} */
342 
343     public void setParent(MyTreeNode parent) {
344         this.parent = (TreeFileDataNode) parent;
345     }
346 
347     /** @see MyTreeNode#setTreeModel(TreeModel) */
348     @SuppressWarnings("unchecked")
349     public void setTreeModel(TreeModel model) {
350         this.treeModel = (MyTreeTableModel<TreeFileDataNode>) model;
351     }
352 
353     /** {@inheritDoc} */
354     public void setFlatModel(FilePanelFlatDataModel2 flatModel) {
355         this.flatModel = flatModel;
356     }
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         if (!children.remove(child)) {
365             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         child.setParent(null);
374         child.setTreeModel(null);
375         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         while (child.getChildCount() > 0) {
381             // If child has children, it is a FolderNode
382             ((FolderNode) child).removeChild(child.getChild(0));
383         }// while
384     }
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         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         TreeFileDataNode alreadyExistingChild = (TreeFileDataNode) getChild(child.getFileName());
406         if (alreadyExistingChild == null) {
407             // This child is not already a child of this folder.
408             children.add(child);
409             child.setTreeModel(this.treeModel);
410             child.setParent(this);
411             treeModel.fireTreeNodesInserted(this, treeModel.getTreePath(this), getIntArray(children.indexOf(child)),
412                     getItemArray((TreeFileDataNode) child));
413             return child;
414         } else {
415             uploadPolicy.displayWarn("The FileData for " + child.getAbsolutePath() + " already exists for the folder "
416                     + getFileName());
417             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         if (!file.isDirectory()) {
429             throw new JUploadExceptionStopAddingFiles(file.getAbsolutePath() + " must be a directory");
430         }
431         TreeFileDataNode child = addChild(new FolderNode(file, uploadPolicy, treeModel, flatModel));
432 
433         if (!(child instanceof FolderNode)) {
434             throw new JUploadExceptionStopAddingFiles("A child with the same name (" + file.getName()
435                     + ") already exists, but is not a folder");
436         } else {
437             return (FolderNode) child;
438         }
439     }
440 
441     /** {@inheritDoc} */
442     public boolean isLeaf() {
443         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         return getFileName();
454     }
455 
456     /** {@inheritDoc} */
457     public TreeFileDataNode getTreeFileDataNode() {
458         return this;
459     }
460 
461     /** {@inheritDoc} */
462     public void setTreeFileDataNode(TreeFileDataNode node) {
463         throw new IllegalStateException("setTreeFileDataNode may not be called againts a FolderNode");
464     }
465 
466     /** {@inheritDoc} */
467     public TreePath getTreePath() {
468         if (parent == null) {
469             return new TreePath(this);
470         } else {
471             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         if (getChild(f) != null) {
485             throw new JUploadExceptionStopAddingFiles("Internal error: " + f.getAbsolutePath()
486                     + " 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         if (f.isDirectory()) {
490             return addChild(new FolderNode(f, uploadPolicy, treeModel, flatModel));
491         } else {
492             FileData fd = flatModel.addFile(f);
493             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         int nbFiles = 0;
506 
507         TreeFileDataNode child = getChild(f);
508         if (child == null) {
509             // It's not already a child of this node. We add it.
510             if (f.isDirectory()) {
511                 child = addChild(f);
512             } else {
513                 FileData fd = flatModel.addFile(f);
514                 // The upload policy may decide to not add this file. In this case, fd is null
515                 if (fd != null) {
516                     child = addChild(new FileDataNode(fd));
517                     fd.setTreeFileDataNode(child);
518                     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         if (f.isDirectory()) {
526             // We add files here, in order to calculate nbFiles
527             for (File file : f.listFiles()) {
528                 nbFiles += ((FolderNode) child).addChildAndDescendants(file);
529             } // for
530         }
531         return nbFiles;
532     }
533 
534 }