KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > de > schlichtherle > io > swing > tree > FileTreeModel


1 /*
2  * FileTreeModel.java
3  *
4  * Created on 13. Februar 2006, 15:43
5  */

6 /*
7  * Copyright 2006 Schlichtherle IT Services
8  *
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  * http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  */

21
22 package de.schlichtherle.io.swing.tree;
23
24 import java.io.FileFilter JavaDoc;
25 import java.io.IOException JavaDoc;
26 import java.io.InputStream JavaDoc;
27 import java.text.Collator JavaDoc;
28 import java.util.Arrays JavaDoc;
29 import java.util.Comparator JavaDoc;
30 import java.util.EventListener JavaDoc;
31 import java.util.HashMap JavaDoc;
32 import java.util.Map JavaDoc;
33
34 import javax.swing.event.EventListenerList JavaDoc;
35 import javax.swing.event.TreeModelEvent JavaDoc;
36 import javax.swing.event.TreeModelListener JavaDoc;
37 import javax.swing.tree.TreeModel JavaDoc;
38 import javax.swing.tree.TreePath JavaDoc;
39
40 /**
41  * A {@link TreeModel} which traverses {@link java.io.File java.io.File}
42  * instances.
43  * If the root of this tree model is actually an instance of
44  * {@link de.schlichtherle.io.File de.schlichtherle.io.File}, its ZipDetector
45  * is used to detect any ZIP compatible files in the directory tree.
46  * This allows you to traverse ZIP compatible files just like directories.
47  *
48  * @author Christian Schlichtherle
49  * @version @version@
50  * @since TrueZIP 5.1
51  */

52 public class FileTreeModel implements TreeModel JavaDoc {
53
54     /**
55      * A collator for file names which considers case according to the
56      * platform's standard.
57      */

58     private static final Collator JavaDoc collator = Collator.getInstance();
59     static {
60         // Set minimum requirements for maximum performance.
61
collator.setDecomposition(Collator.NO_DECOMPOSITION);
62         collator.setStrength(java.io.File.separatorChar == '\\'
63                 ? Collator.SECONDARY
64                 : Collator.TERTIARY);
65     }
66
67     /** A comparator which sorts directories entries to the beginning. */
68     public static final Comparator JavaDoc FILE_NAME_COMPARATOR = new Comparator JavaDoc() {
69         public final int compare(Object JavaDoc o1, Object JavaDoc o2) {
70             return compare((java.io.File JavaDoc) o1, (java.io.File JavaDoc) o2);
71         }
72
73         public int compare(java.io.File JavaDoc f1, java.io.File JavaDoc f2) {
74             if (f1.isDirectory())
75                 if (f2.isDirectory())
76                     return collator.compare(f1.getName(), f2.getName());
77                 else
78                     return -1;
79             else
80                 if (f2.isDirectory())
81                     return 1;
82                 else
83                     return collator.compare(f1.getName(), f2.getName());
84         }
85     };
86
87     /**
88      * Used to cache the contents of directories.
89      * Maps {@link java.io.File} -> {@link java.io.File}[] instances.
90      */

91     // Tactical note: Working with a WeakHashMap shows strange results.
92
private transient final Map JavaDoc cache = new HashMap JavaDoc();
93
94     private final java.io.File JavaDoc root;
95
96     private final FileFilter JavaDoc filter;
97
98     /**
99      * Creates an empty <code>FileTreeModel</code> with no root.
100      * You shouldn't use this constructor.
101      * It's only provided to implement the JavaBean pattern.
102      */

103     public FileTreeModel() {
104         this.root = null;
105         this.filter = null;
106     }
107     
108     /**
109      * Creates a new <code>FileTreeModel</code> which traverses the given
110      * root <code>File</code>.
111      * The ZipDetector of the given file is used to detect and configure any
112      * ZIP compatible files in this directory tree.
113      *
114      * @param root The root file of this <code>TreeModel</code>.
115      * If this is <code>null</code>, an empty tree is created.
116      */

117     public FileTreeModel(java.io.File JavaDoc root) {
118         this.root = root;
119         this.filter = null;
120     }
121     
122     /**
123      * Creates a new <code>FileTreeModel</code> which traverses the given
124      * root <code>File</code>.
125      * The ZipDetector of the given file is used to detect and configure any
126      * ZIP compatible files in this directory tree.
127      *
128      * @param root The root file of this <code>TreeModel</code>.
129      * If this is <code>null</code>, an empty tree is created.
130      * @param filter Used to filter the files and directories which are
131      * present in this <code>TreeModel</code>.
132      */

133     public FileTreeModel(java.io.File JavaDoc root, FileFilter JavaDoc filter) {
134         this.root = root;
135         this.filter = filter;
136     }
137
138     //
139
// TreeModel implementation.
140
//
141

142     /**
143      * Returns the root of this tree model.
144      * This is actually an instance of {@link java.io.File java.io.File} or
145      * a subclass, like
146      * {@link de.schlichtherle.io.File de.schlichtherle.io.File}.
147      *
148      * @return A <code>File</code> object or <code>null</code> if this tree
149      * model has not been created with a <code>File</code> object.
150      */

151     public Object JavaDoc getRoot() {
152         return root;
153     }
154
155     public Object JavaDoc getChild(Object JavaDoc parent, int index) {
156         final java.io.File JavaDoc[] children = getChildren((java.io.File JavaDoc) parent);
157         return children != null ? children[index] : null;
158     }
159
160     public int getChildCount(Object JavaDoc parent) {
161         final java.io.File JavaDoc[] children = getChildren((java.io.File JavaDoc) parent);
162         return children != null ? children.length : 0;
163     }
164
165     public boolean isLeaf(Object JavaDoc node) {
166         return !((java.io.File JavaDoc) node).isDirectory();
167     }
168
169     public void valueForPathChanged(TreePath JavaDoc path, Object JavaDoc newValue) {
170     }
171
172     public int getIndexOfChild(Object JavaDoc parent, Object JavaDoc child) {
173         if (parent == null || child == null)
174             return -1;
175         final java.io.File JavaDoc[] children = getChildren((java.io.File JavaDoc) parent);
176         if (children == null)
177             return -1;
178
179         for (int i = 0, l = children.length; i < l; i++)
180             if (children[i].equals(child))
181                 return i;
182
183         return -1;
184     }
185
186     private java.io.File JavaDoc[] getChildren(final java.io.File JavaDoc parent) {
187         assert parent != null;
188
189         java.io.File JavaDoc[] children = (java.io.File JavaDoc[]) cache.get(parent);
190         if (children == null) {
191             if (cache.containsKey(parent))
192                 return null; // parent is file or inaccessible directory
193
children = parent.listFiles(filter);
194
195             // Order is important here: FILE_NAME_COMPARATOR causes a
196
// recursion if the children contain an RAES encrypted ZIP file
197
// for which a password needs to be prompted.
198
// This is caused by the painting manager which repaints the tree
199
// model in the background while the prompting dialog is showing
200
// in the foreground.
201
// In this case, we will simply return the unsorted result in the
202
// recursion, which is then used for repainting.
203
cache.put(parent, children);
204             if (children != null)
205                 Arrays.sort(children, FILE_NAME_COMPARATOR);
206         }
207
208         return children;
209     }
210
211     //
212
// TreePath retrieval.
213
//
214

215     /**
216      * Returns a {@link TreePath} for the given <code>node</code> or
217      * <code>null</code> if the node is not part of this file tree.
218      */

219     public TreePath JavaDoc getTreePath(java.io.File JavaDoc node) {
220         java.io.File JavaDoc[] elements = getPath(node);
221         return elements != null ? new TreePath JavaDoc(elements) : null;
222     }
223
224     /**
225      * Returns an array of {@link java.io.File} objects indicating the path
226      * from the root to the given node.
227      *
228      * @param node The <code>File</code> object to get the path for.
229      *
230      * @return An array of <code>File</code> objects, suitable as a constructor
231      * argument for {@link TreePath}.
232      */

233     private java.io.File JavaDoc[] getPath(java.io.File JavaDoc node) {
234         if (root == null /*|| !de.schlichtherle.io.File.contains(root, node)*/)
235             return null;
236
237         // Do not apply the filter here! The filter could depend on the file's
238
// state and this method may get called before the node is initialized
239
// to a state which would be accepted by the filter.
240
/*if (filter != null && !((FileFilter) filter).accept(node))
241             return null;*/

242
243         return getPath(node, 1);
244     }
245
246     private java.io.File JavaDoc[] getPath(final java.io.File JavaDoc node, int level) {
247         final java.io.File JavaDoc[] path;
248
249         if (/*node == null ||*/ root.equals(node)) {
250             path = new java.io.File JavaDoc[level];
251             path[0] = root;
252         } else if (node != null) {
253             path = getPath(node.getParentFile(), level + 1);
254             if (path != null)
255                 path[path.length - level] = node;
256         } else {
257             path = null;
258         }
259
260         return path;
261     }
262
263     //
264
// File system operations.
265
//
266

267     /**
268      * Creates <code>node</code> as a new file
269      * and updates the tree accordingly.
270      * However, the current selection may get lost.
271      * If you would like to create a new file with initial content, please
272      * check {@link #copyFrom(de.schlichtherle.io.File, InputStream)}.
273      *
274      * @return Whether or not the file has been newly created.
275      *
276      * @throws IOException If an I/O error occurred.
277      */

278     public boolean createNewFile(final java.io.File JavaDoc node)
279     throws IOException JavaDoc {
280         if (!node.createNewFile())
281             return false;
282
283         nodeInserted(node);
284         
285         return true;
286     }
287
288     /**
289      * Creates <code>node</code> as a new directory
290      * and updates the tree accordingly.
291      * However, the current selection may get lost.
292      *
293      * @return Whether or not the file has been newly created.
294      *
295      * @throws IOException If an I/O error occurred.
296      */

297     public boolean mkdir(final java.io.File JavaDoc node) {
298         if (!node.mkdir())
299             return false;
300
301         nodeInserted(node);
302         
303         return true;
304     }
305
306     /**
307      * Creates <code>node</code> as a new directory, including all parents,
308      * and updates the tree accordingly.
309      * However, the current selection may get lost.
310      *
311      * @return Whether or not the file has been newly created.
312      *
313      * @throws IOException If an I/O error occurred.
314      */

315     public boolean mkdirs(final java.io.File JavaDoc node) {
316         if (!node.mkdirs())
317             return false;
318
319         nodeInserted(node);
320         
321         return true;
322     }
323
324     /**
325      * Creates <code>node</code> as a new file with the contents read from
326      * <code>in</code> and updates the tree accordingly.
327      * However, the current selection may get lost.
328      * Note that the given stream is <em>always</em> closed.
329      *
330      * @return Whether or not the file has been newly created.
331      *
332      * @throws IOException If an I/O error occurred.
333      */

334     public boolean copyFrom(final de.schlichtherle.io.File node, final InputStream JavaDoc in) {
335         if (!node.copyFrom(in))
336             return false;
337
338         nodeInsertedOrStructureChanged(node);
339         
340         return true;
341     }
342
343     /**
344      * Copies <code>oldNode</code> to <code>node</code>
345      * and updates the tree accordingly.
346      * However, the current selection may get lost.
347      *
348      * @return Whether or not the file has been successfully renamed.
349      */

350     public boolean copyTo(final de.schlichtherle.io.File oldNode, final java.io.File JavaDoc node) {
351         if (!oldNode.copyTo(node))
352             return false;
353
354         nodeInsertedOrStructureChanged(node);
355         
356         return true;
357     }
358
359     /**
360      * Copies <code>oldNode</code> to <code>node</code> recursively
361      * and updates the tree accordingly.
362      * However, the current selection may get lost.
363      *
364      * @return Whether or not the file has been successfully renamed.
365      */

366     public boolean copyAllTo(final de.schlichtherle.io.File oldNode, final java.io.File JavaDoc node) {
367         final boolean ok = oldNode.copyAllTo(node);
368         nodeInsertedOrStructureChanged(node);
369         return ok;
370     }
371
372     /**
373      * Copies <code>oldNode</code> to <code>node</code>, preserving
374      * its last modification time
375      * and updates the tree accordingly.
376      * However, the current selection may get lost.
377      *
378      * @return Whether or not the file has been successfully renamed.
379      */

380     public boolean archiveCopyTo(final de.schlichtherle.io.File oldNode, final java.io.File JavaDoc node) {
381         if (!oldNode.archiveCopyTo(node))
382             return false;
383
384         nodeInsertedOrStructureChanged(node);
385         
386         return true;
387     }
388
389     /**
390      * Copies <code>oldNode</code> to <code>node</code> recursively, preserving
391      * its last modification time
392      * and updates the tree accordingly.
393      * However, the current selection may get lost.
394      *
395      * @return Whether or not the file has been successfully renamed.
396      */

397     public boolean archiveCopyAllTo(final de.schlichtherle.io.File oldNode, final java.io.File JavaDoc node) {
398         final boolean ok = oldNode.archiveCopyAllTo(node);
399         nodeInsertedOrStructureChanged(node);
400         return ok;
401     }
402
403     /**
404      * Renames <code>oldNode</code> to <code>node</code>
405      * and updates the tree accordingly.
406      * However, the current selection may get lost.
407      *
408      * @return Whether or not the file has been successfully renamed.
409      */

410     public boolean renameTo(final java.io.File JavaDoc oldNode, final java.io.File JavaDoc node) {
411         if (!oldNode.renameTo(node))
412             return false;
413
414         nodeRemoved(oldNode);
415         nodeInserted(node);
416         
417         return true;
418     }
419
420     /**
421      * Deletes the file or empty directory <code>node</code>
422      * and updates the tree accordingly.
423      * However, the current selection may get lost.
424      *
425      * @return Whether or not the file or directory has been successfully deleted.
426      *
427      * @throws IOException If an I/O error occurred.
428      */

429     public boolean delete(final java.io.File JavaDoc node) {
430         if (!node.delete())
431             return false;
432
433         nodeRemoved(node);
434         
435         return true;
436     }
437
438     /**
439      * Deletes the file or (probably not empty) directory <code>node</code>
440      * and updates the tree accordingly.
441      * However, the current selection may get lost.
442      *
443      * @return Whether or not the file or directory has been successfully deleted.
444      *
445      * @throws IOException If an I/O error occurred.
446      */

447     public boolean deleteAll(final de.schlichtherle.io.File node) {
448         if (!node.deleteAll())
449             return false;
450
451         nodeRemoved(node);
452         
453         return true;
454     }
455
456     //
457
// File system change notifications.
458
//
459

460     /**
461      * Inserts the given node in the tree or reloads the tree structure for
462      * the given node if it already exists.
463      * This method calls {@link TreeModelListener#treeNodesInserted(TreeModelEvent)}
464      * on all listeners of this <code>TreeModel</code>.
465      */

466     public void nodeInsertedOrStructureChanged(final java.io.File JavaDoc node) {
467         if (node == null)
468             throw new NullPointerException JavaDoc();
469
470         if (cache.containsKey(node))
471             structureChanged(node);
472         else
473             nodeInserted(node);
474     }
475
476     /**
477      * Inserts the given node in the tree.
478      * If <code>node</code> already exists, nothing happens.
479      * This method calls {@link TreeModelListener#treeNodesInserted(TreeModelEvent)}
480      * on all listeners of this <code>TreeModel</code>.
481      */

482     public void nodeInserted(final java.io.File JavaDoc node) {
483         if (cache.containsKey(node))
484             return;
485         final java.io.File JavaDoc parent = node.getParentFile();
486         assert parent != null;
487         forget(parent, false);
488         final int index = getIndexOfChild(parent, node);
489         if (index == -1)
490             return;
491         fireTreeNodesInserted(new TreeModelEvent JavaDoc(
492                 this, getTreePath(parent),
493                 new int[] { index }, new java.io.File JavaDoc[] { node }));
494     }
495
496     /**
497      * Updates the given node in the tree.
498      * This method calls {@link TreeModelListener#treeNodesChanged(TreeModelEvent)}
499      * on all listeners of this <code>TreeModel</code>.
500      */

501     public void nodeChanged(final java.io.File JavaDoc node) {
502         final java.io.File JavaDoc parent = node.getParentFile();
503         assert parent != null;
504         final int index = getIndexOfChild(parent, node);
505         if (index == -1)
506             return;
507         fireTreeNodesChanged(new TreeModelEvent JavaDoc(
508                 this, getTreePath(parent),
509                 new int[] { index }, new java.io.File JavaDoc[] { node }));
510     }
511
512     /**
513      * Removes the given node from the tree.
514      * This method calls {@link TreeModelListener#treeNodesRemoved(TreeModelEvent)}
515      * on all listeners of this <code>TreeModel</code>.
516      */

517     public void nodeRemoved(final java.io.File JavaDoc node) {
518         final java.io.File JavaDoc parent = node.getParentFile();
519         assert parent != null;
520         if (!cache.containsKey(parent))
521             return;
522         final int index = getIndexOfChild(parent, node);
523         if (index == -1)
524             return;
525         forget(node);
526         forget(parent, false);
527         fireTreeNodesRemoved(new TreeModelEvent JavaDoc(
528                 this, getTreePath(parent),
529                 new int[] { index }, new java.io.File JavaDoc[] { node }));
530     }
531
532     /**
533      * Refreshes the tree structure for the entire tree.
534      * This method calls {@link TreeModelListener#treeStructureChanged(TreeModelEvent)}
535      * on all listeners of this <code>TreeModel</code>.
536      */

537     public void refresh() {
538         cache.clear();
539         if (root != null)
540             fireTreeStructureChanged(
541                     new TreeModelEvent JavaDoc(this, getTreePath(root), null, null));
542     }
543
544     /** Alias for {@link #structureChanged(java.io.File)}. */
545     public final void refresh(final java.io.File JavaDoc node) {
546         structureChanged(node);
547     }
548
549     /**
550      * Reloads the tree structure for the given node.
551      * This method calls {@link TreeModelListener#treeStructureChanged(TreeModelEvent)}
552      * on all listeners of this <code>TreeModel</code>.
553      */

554     public void structureChanged(final java.io.File JavaDoc node) {
555         if (node == null)
556             throw new NullPointerException JavaDoc();
557
558         forget(node);
559         fireTreeStructureChanged(
560                 new TreeModelEvent JavaDoc(this, getTreePath(node), null, null));
561     }
562
563     /**
564      * This method is <em>not</em> intended for public use - do not use it!
565      * Clears the internal cache associated with <code>node</code> and all
566      * of its children.
567      */

568     // This method is only public in order to make it available
569
// to {@link de.schlichtherle.io.swing.JFileTree}.
570
// In particular, this method does <em>not</em> notify the
571
// tree of any structural changes in the file system.
572
public final void forget(final java.io.File JavaDoc node) {
573         forget(node, true);
574     }
575
576     /**
577      * Clears the internal cache associated with <code>node</code>.
578      *
579      * @param childrenToo If and only if <code>true</code>, the internal
580      * cache for all children is cleared, too.
581      *
582      * @deprecated This method is public in order to make it available to
583      * {@link de.schlichtherle.io.swing.JFileTree} only
584      * - it is <em>not</em> intended for public use.
585      * In particular, this method does <em>not</em> notify the
586      * tree of any structural changes in the file system.
587      */

588     private void forget(final java.io.File JavaDoc node, final boolean childrenToo) {
589         final java.io.File JavaDoc[] children = (java.io.File JavaDoc[]) cache.remove(node);
590         if (children != null && childrenToo)
591             for (int i = 0, l = children.length; i < l; i++)
592                 forget(children[i], childrenToo);
593     }
594
595     //
596
// Event handling.
597
//
598

599     private transient final EventListenerList JavaDoc listeners = new EventListenerList JavaDoc();
600
601     /**
602      * Adds a listener to this model.
603      *
604      * @param l The listener to add.
605      */

606     public void addTreeModelListener(TreeModelListener JavaDoc l) {
607         listeners.add(TreeModelListener JavaDoc.class, l);
608     }
609
610     /**
611      * Removes a listener from this model.
612      *
613      * @param l The listener to remove.
614      */

615     public void removeTreeModelListener(TreeModelListener JavaDoc l) {
616         listeners.remove(TreeModelListener JavaDoc.class, l);
617     }
618
619     /**
620      * This method calls {@link TreeModelListener#treeStructureChanged(TreeModelEvent)}
621      * on all listeners of this <code>TreeModel</code>.
622      * May be used to tell the listeners about a change in the file system.
623      */

624     protected void fireTreeNodesChanged(final TreeModelEvent JavaDoc evt) {
625         final EventListener JavaDoc[] l = listeners.getListeners(TreeModelListener JavaDoc.class);
626         for (int i = 0, ll = l.length; i < ll; i++)
627             ((TreeModelListener JavaDoc) l[i]).treeNodesChanged(evt);
628     }
629
630     /**
631      * This method calls {@link TreeModelListener#treeStructureChanged(TreeModelEvent)}
632      * on all listeners of this <code>TreeModel</code>.
633      * May be used to tell the listeners about a change in the file system.
634      */

635     protected void fireTreeNodesInserted(final TreeModelEvent JavaDoc evt) {
636         final EventListener JavaDoc[] l = listeners.getListeners(TreeModelListener JavaDoc.class);
637         for (int i = 0, ll = l.length; i < ll; i++)
638             ((TreeModelListener JavaDoc) l[i]).treeNodesInserted(evt);
639     }
640
641     /**
642      * This method calls {@link TreeModelListener#treeStructureChanged(TreeModelEvent)}
643      * on all listeners of this <code>TreeModel</code>.
644      * May be used to tell the listeners about a change in the file system.
645      */

646     protected void fireTreeNodesRemoved(final TreeModelEvent JavaDoc evt) {
647         final EventListener JavaDoc[] l = listeners.getListeners(TreeModelListener JavaDoc.class);
648         for (int i = 0, ll = l.length; i < ll; i++)
649             ((TreeModelListener JavaDoc) l[i]).treeNodesRemoved(evt);
650     }
651
652     /**
653      * This method calls {@link TreeModelListener#treeStructureChanged(TreeModelEvent)}
654      * on all listeners of this <code>TreeModel</code>.
655      * May be used to tell the listeners about a change in the file system.
656      */

657     protected void fireTreeStructureChanged(final TreeModelEvent JavaDoc evt) {
658         final EventListener JavaDoc[] l = listeners.getListeners(TreeModelListener JavaDoc.class);
659         for (int i = 0, ll = l.length; i < ll; i++)
660             ((TreeModelListener JavaDoc) l[i]).treeStructureChanged(evt);
661     }
662 }
663
Popular Tags