KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > openide > filesystems > MultiFileSystem


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.openide.filesystems;
21
22 import java.io.IOException JavaDoc;
23 import java.util.ArrayList JavaDoc;
24 import java.util.Arrays JavaDoc;
25 import java.util.Collection JavaDoc;
26 import java.util.Collections JavaDoc;
27 import java.util.Enumeration JavaDoc;
28 import java.util.HashSet JavaDoc;
29 import java.util.Iterator JavaDoc;
30 import java.util.List JavaDoc;
31 import java.util.Set JavaDoc;
32 import java.util.StringTokenizer JavaDoc;
33 import org.openide.util.Enumerations;
34 import org.openide.util.NbCollections;
35 import org.openide.util.actions.SystemAction;
36
37 /**
38  * General base class for filesystems which proxy to others.
39  *
40  * <p>This filesystem has no form of storage in and of itself. Rather, it composes and proxies one or more
41  * "delegate" filesystems. The result is that of a "layered" sandwich of filesystems, each able to provide files
42  * to appear in the merged result. The layers are ordered so that a filesystem in "front" can override one in
43  * "back". Often the frontmost layer will be writable, and all changes to the filesystem are sent to this layer,
44  * but that behavior is configurable.
45  *
46  * <p>Creating a new <code>MultiFileSystem</code> is easy in the simplest cases: just call {@link
47  * #MultiFileSystem(FileSystem[])} and pass a list of delegates. If you pass it only read-only delegates, the
48  * composite will also be read-only. Or you may pass it one or more writable filesystems (make sure the first
49  * file system is one of them); then it will be able to act as a writable file system, by default storing all
50  * actual changes on the first file system.
51  *
52  * <p>This class is intended to be subclassed more than to be used as is, since typically a user of it will want
53  * finer-grain control over several aspects of its operation. When subclassed, delegates may be {@link
54  * #setDelegates changed dynamically} and the "contents" of the composite filesystem will
55  * automatically be updated.
56  *
57  * <p>One of the more common methods to override is {@link #createWritableOn}, which should provide the delegate
58  * filesystem which should be used to store changes to the indicated file (represented as a resource path).
59  * Normally, this is hardcoded to provide the first filesystem (assuming it is writable); subclasses may store
60  * changes on another filesystem, or they may select a filesystem to store changes on according to (for example)
61  * file extension. This could be used to separate source from compiled/deployable files, for instance. Or some
62  * filesystems may prefer to attempt to make changes in whatever filesystem provided the original file, assuming
63  * it is writable; this is also possible, getting information about the file's origin from {@link #findSystem}.
64  *
65  * <p>Another likely candidate for overriding is {@link #findResourceOn}. Normally this method just looks up
66  * resources by the normal means on delegate filesystems and provides the resultant file objects. However it
67  * could be customized to "warp" the delegates somehow; for example, selecting a different virtual root for one
68  * of them.
69  *
70  * <p>{@link #notifyMigration} provides a means for subclasses to update some state (for example, visual
71  * annotations) when the filesystem used to produce a given file changes dynamically. This most often happens
72  * when a file provided by a back delegate is written to, and the result stored on the foremost delegate, but it
73  * could occur in other situations too (e.g. change of delegates, removal of overriding file on underlying front
74  * delegate, etc.).
75  *
76  * <p>When new files are added to the <code>MultiFileSystem</code>, normally they will be physically added to
77  * the foremost delegate, although as previously mentioned they can be customized. When their contents (or
78  * attributes) are changed, by default these changes are again made in the foremost delegate. But what if a file
79  * is deleted? There must be a way to indicate on the foremost delegate that it should not appear in the
80  * composite (even while it remains in one of the delegates). For this reason, <code>MultiFileSystem</code> uses
81  * "masks" which are simply empty files named according to the file they should hide, but with the suffix
82  * <samp>_hidden</samp>. Thus, for example, if there is a file <samp>subdir/readme.txt_hidden</samp> in a front
83  * delegate it will hide any files named <samp>subdir/readme.txt</samp> in delegates further back. These masks
84  * are automatically created as needed when files are "deleted" from <code>MultiFileSystem</code>; or delegate
85  * filesystems may explicitly provide them. Normally the mask files are not themselves visible as {@link
86  * FileObject}s, since they are an artifact of the deletion logic. However, when nesting
87  * <code>MultiFileSystem</code>s inside other <code>MultiFileSystem</code>s, it can be useful to have delegates
88  * be able to mask files not provided by their immediate siblings, but by cousins. For this reason, nested
89  * subclasses may call {@link #setPropagateMasks} to make the mask files propagate up one or more nesting levels
90  * and thus remain potent against cousin delegates.
91  */

92 public class MultiFileSystem extends FileSystem {
93     static final long serialVersionUID = -767493828111559560L;
94
95     /** what extension to add to file that mask another ones */
96     static final String JavaDoc MASK = "_hidden"; // NOI18N
97

98     /** index of the filesystem with write access */
99     private static final int WRITE_SYSTEM_INDEX = 0;
100
101     /** array of fs. the filesystem at position 0 can be null, because
102     * it is writable filesystem. Others are only for read access
103     */

104     private FileSystem[] systems;
105
106     /** @see #getPropagateMasks */
107     private boolean propagateMasks = false;
108
109     /** root */
110     private transient MultiFileObject root;
111
112     /** Creates new empty MultiFileSystem. Useful only for
113     * subclasses.
114     */

115     protected MultiFileSystem() {
116         this(new FileSystem[1]);
117     }
118
119     /** Creates new MultiFileSystem.
120     * @param fileSystems array of filesystems (can contain nulls)
121     */

122     public MultiFileSystem(FileSystem[] fileSystems) {
123         this.systems = fileSystems.clone();
124     }
125
126     /**
127      * Actually implements contract of FileSystem.refresh().
128      */

129     public void refresh(boolean expected) {
130         Enumeration JavaDoc<AbstractFolder> en = getMultiRoot().existingSubFiles(true);
131
132         while (en.hasMoreElements()) {
133             FileObject fo = en.nextElement();
134             fo.refresh(expected);
135         }
136     }
137
138     /** Changes the filesystems that this system delegates to
139     *
140     * @param fileSystems array of filesystems
141     */

142     protected final void setDelegates(FileSystem[] fileSystems) {
143         // save for notification
144
FileSystem[] oldSystems = systems;
145
146         // set them
147
this.systems = fileSystems;
148
149         getMultiRoot().updateAllAfterSetDelegates(oldSystems);
150
151         List JavaDoc<FileSystem> oldList = Arrays.asList(oldSystems);
152         List JavaDoc<FileSystem> newList = Arrays.asList(systems);
153
154         // notify removed filesystems
155
Set JavaDoc<FileSystem> toRemove = new HashSet JavaDoc<FileSystem>(oldList);
156         toRemove.removeAll(newList);
157
158         for (Iterator JavaDoc iter = toRemove.iterator(); iter.hasNext();) {
159             FileSystem fs = ((FileSystem) iter.next());
160
161             if (fs != null) {
162                 fs.removeNotify();
163             }
164         }
165
166         // notify added filesystems
167
Set JavaDoc<FileSystem> toAdd = new HashSet JavaDoc<FileSystem>(newList);
168         toAdd.removeAll(oldList);
169
170         for (Iterator JavaDoc iter = toAdd.iterator(); iter.hasNext();) {
171             FileSystem fs = ((FileSystem) iter.next());
172
173             if (fs != null) {
174                 fs.addNotify();
175             }
176         }
177     }
178
179     /** All filesystem that this system delegates to.
180     * @return the array of delegates
181     */

182     protected final FileSystem[] getDelegates() {
183         return systems;
184     }
185
186     /** Will mask files that are not used be listed as children?
187      * @return <code>true</code> if so
188      */

189     public final boolean getPropagateMasks() {
190         return propagateMasks;
191     }
192
193     /** Set whether unused mask files should be listed as children.
194      * @param pm <code>true</code> if so
195      */

196     protected final void setPropagateMasks(boolean pm) {
197         propagateMasks = pm;
198     }
199
200     /** This filesystem is readonly if it has not writable system.
201     */

202     public boolean isReadOnly() {
203         return (systems[WRITE_SYSTEM_INDEX] == null) || systems[WRITE_SYSTEM_INDEX].isReadOnly();
204     }
205
206     /** The name of the filesystem.
207     */

208     public String JavaDoc getDisplayName() {
209         return getString("CTL_MultiFileSystem");
210     }
211
212     /** Root of the filesystem.
213     */

214     public FileObject getRoot() {
215         return getMultiRoot();
216     }
217
218     /** Root of the filesystem.
219     */

220     private MultiFileObject getMultiRoot() {
221         synchronized (MultiFileSystem.class) {
222             if (root == null) {
223                 root = new MultiFileObject(this);
224             }
225
226             return root;
227         }
228     }
229
230     /** Merge actions from all delegates.
231     */

232     public SystemAction[] getActions() {
233         List JavaDoc<SystemAction> al = new ArrayList JavaDoc<SystemAction>(101); // randomly choosen constant
234
Set JavaDoc<SystemAction> uniq = new HashSet JavaDoc<SystemAction>(101); // not that randommly choosen
235

236         FileSystem[] del = this.getDelegates();
237
238         for (int i = 0; i < del.length; i++) {
239             if (del[i] == null) {
240                 continue;
241             }
242
243             SystemAction[] acts = del[i].getActions();
244
245             for (int j = 0; j < acts.length; j++) {
246                 if (uniq.add(acts[j])) {
247                     al.add(acts[j]);
248                 }
249             }
250         }
251
252         return al.toArray(new SystemAction[al.size()]);
253     }
254
255     public SystemAction[] getActions(final Set JavaDoc<FileObject> foSet) {
256         List JavaDoc<SystemAction> al = new ArrayList JavaDoc<SystemAction>(101); // randomly choosen constant
257
Set JavaDoc<SystemAction> uniq = new HashSet JavaDoc<SystemAction>(101); // not that randommly choosen
258

259         final FileSystem[] del = this.getDelegates();
260
261         for (int i = 0; i < del.length; i++) {
262             if (del[i] == null) {
263                 continue;
264             }
265
266             final SystemAction[] acts = del[i].getActions(foSet);
267
268             for (int j = 0; j < acts.length; j++) {
269                 if (uniq.add(acts[j])) {
270                     al.add(acts[j]);
271                 }
272             }
273         }
274
275         return al.toArray(new SystemAction[al.size()]);
276     }
277
278     @Deprecated JavaDoc // have to override for compat
279
public FileObject find(String JavaDoc aPackage, String JavaDoc name, String JavaDoc ext) {
280         // create enumeration of name to look for
281
Enumeration JavaDoc<String JavaDoc> st = NbCollections.checkedEnumerationByFilter(new StringTokenizer JavaDoc(aPackage, "."), String JavaDoc.class, true); // NOI18N
282
Enumeration JavaDoc<String JavaDoc> en;
283
284         if ((name == null) || (ext == null)) {
285             en = st;
286         } else {
287             en = Enumerations.concat(st, Enumerations.singleton(name + '.' + ext));
288         }
289
290         // tries to find it (can return null)
291
return getMultiRoot().find(en);
292     }
293
294     /* Finds file when its resource name is given.
295     * The name has the usual format for the {@link ClassLoader#getResource(String)}
296     * method. So it may consist of "package1/package2/filename.ext".
297     * If there is no package, it may consist only of "filename.ext".
298     *
299     * @param name resource name
300     *
301     * @return FileObject that represents file with given name or
302     * <CODE>null</CODE> if the file does not exist
303     */

304     public FileObject findResource(String JavaDoc name) {
305         if (name.length() == 0) {
306             return getMultiRoot();
307         } else {
308             Enumeration JavaDoc<String JavaDoc> tok = NbCollections.checkedEnumerationByFilter(new StringTokenizer JavaDoc(name, "/"), String JavaDoc.class, true); // NOI18N
309
return getMultiRoot().find(tok);
310         }
311     }
312
313     //
314
// Helper methods for subclasses
315
//
316

317     /** For given file object finds the filesystem that the object is placed on.
318     * The object must be created by this filesystem orherwise IllegalArgumentException
319     * is thrown.
320     *
321     * @param fo file object
322     * @return the filesystem (from the list we delegate to) the object has file on
323     * @exception IllegalArgumentException if the file object is not represented in this filesystem
324     */

325     protected final FileSystem findSystem(FileObject fo)
326     throws IllegalArgumentException JavaDoc {
327         try {
328             if (fo instanceof MultiFileObject) {
329                 MultiFileObject mfo = (MultiFileObject) fo;
330
331                 return mfo.getLeaderFileSystem();
332             }
333         } catch (FileStateInvalidException ex) {
334             // can happen if there is no delegate, I do not know what to return
335
// better, but we should not throw the exception
336
return this;
337         }
338
339         throw new IllegalArgumentException JavaDoc(fo.getPath());
340     }
341
342     /** Marks a resource as hidden. It will not be listed in the list of files.
343     * Uses createMaskOn method to determine on which filesystem to mark the file.
344     *
345     * @param res resource name of file to hide or show
346     * @param hide true if we should hide the file/false otherwise
347     * @exception IOException if it is not possible
348     */

349     protected final void hideResource(String JavaDoc res, boolean hide)
350     throws IOException JavaDoc {
351         if (hide) {
352             // mask file
353
maskFile(createWritableOn(res), res);
354         } else {
355             unmaskFile(createWritableOn(res), res);
356         }
357     }
358
359     /** Finds all hidden files on given filesystem. The methods scans all files for
360     * ones with hidden extension and returns enumeration of names of files
361     * that are hidden.
362     *
363     * @param folder folder to start at
364     * @param rec proceed recursivelly
365     * @return enumeration of String with names of hidden files
366     */

367     protected static Enumeration JavaDoc<String JavaDoc> hiddenFiles(FileObject folder, boolean rec) {
368         Enumeration JavaDoc<? extends FileObject> allFiles = folder.getChildren(rec);
369
370         class OnlyHidden implements Enumerations.Processor<FileObject, String JavaDoc> {
371             public String JavaDoc process(FileObject obj, Collection JavaDoc<FileObject> ignore) {
372                 String JavaDoc sf = obj.getPath();
373
374                 if (sf.endsWith(MASK)) {
375                     return sf.substring(0, sf.length() - MASK.length());
376                 } else {
377                     return null;
378                 }
379             }
380         }
381
382         return Enumerations.filter(allFiles, new OnlyHidden());
383     }
384
385     //
386
// methods for subclass customization
387
//
388

389     /** Finds a resource on given filesystem. The default
390     * implementation simply uses FileSystem.findResource, but
391     * subclasses may override this method to hide/show some
392     * resources.
393     *
394     * @param fs the filesystem to scan on
395     * @param res the resource name to look for
396     * @return the file object or null
397     */

398     protected FileObject findResourceOn(FileSystem fs, String JavaDoc res) {
399         return fs.findResource(res);
400     }
401
402     /** Finds the system to create writable version of the file on.
403     *
404     * @param name name of the file (full)
405     * @return the first one
406     * @exception IOException if the filesystem is readonly
407     */

408     protected FileSystem createWritableOn(String JavaDoc name)
409     throws IOException JavaDoc {
410         if ((systems[WRITE_SYSTEM_INDEX] == null) || systems[WRITE_SYSTEM_INDEX].isReadOnly()) {
411             FSException.io("EXC_FSisRO", getDisplayName()); // NOI18N
412
}
413
414         return systems[WRITE_SYSTEM_INDEX];
415     }
416
417     /** Special case of createWritableOn (@see #createWritableOn).
418     *
419     * @param oldName original name of the file (full)
420     * @param newName name new of the file (full)
421     * @return the first one
422     * @exception IOException if the filesystem is readonly
423     * @since 1.34
424     */

425     protected FileSystem createWritableOnForRename(String JavaDoc oldName, String JavaDoc newName)
426     throws IOException JavaDoc {
427         return createWritableOn(newName);
428     }
429
430     /** When a file is about to be locked this method is consulted to
431     * choose which delegates should be locked. By default this method
432     * returns only one filesystem; the same returned by createWritableOn.
433     * <P>
434     * If an delegate resides on a filesystem returned in the resulting
435     * set, it will be locked. All others will remain unlocked.
436     *
437     * @param name the resource name to lock
438     * @return set of filesystems
439     * @exception IOException if the resource cannot be locked
440     */

441     protected Set JavaDoc createLocksOn(String JavaDoc name) throws IOException JavaDoc {
442         FileSystem writable = createWritableOn(name);
443
444         return Collections.singleton(writable);
445     }
446
447     /** Notification that a file has migrated from one filesystem
448     * to another. Usually when somebody writes to file on readonly file
449     * system and the file has to be copied to write one.
450     * <P>
451     * This method allows subclasses to fire for example FileSystem.PROP_STATUS
452     * change to notify that annotation of this file should change.
453     *
454     * @param fo file object that change its actual filesystem
455     */

456     protected void notifyMigration(FileObject fo) {
457     }
458
459     /** Notification that a file has been marked unimportant.
460     *
461     *
462     * @param fo file object that change its actual filesystem
463     */

464     protected void markUnimportant(FileObject fo) {
465     }
466
467     /** Lets any sub filesystems prepare the environment.
468      * If they do not support it, it does not care.
469      * @deprecated Useless.
470      */

471     @Deprecated JavaDoc
472     public void prepareEnvironment(FileSystem.Environment env)
473     throws EnvironmentNotSupportedException {
474         FileSystem[] layers = getDelegates();
475
476         for (int i = 0; i < layers.length; i++) {
477             if (layers[i] != null) {
478                 try {
479                     layers[i].prepareEnvironment(env);
480                 } catch (EnvironmentNotSupportedException ense) {
481                     // Fine.
482
}
483             }
484         }
485     }
486
487     /** Notifies all encapsulated filesystems in advance
488     * to superclass behaviour. */

489     public void addNotify() {
490         super.addNotify();
491
492         for (int i = 0; i < systems.length; i++) {
493             if (systems[i] != null) {
494                 systems[i].addNotify();
495             }
496         }
497     }
498
499     /** Notifies all encapsulated filesystems in advance
500     * to superclass behaviour. */

501     public void removeNotify() {
502         super.removeNotify();
503
504         for (int i = 0; i < systems.length; i++) {
505             if (systems[i] != null) {
506                 systems[i].removeNotify();
507             }
508         }
509     }
510
511     //
512
// Private methods
513
//
514

515     /** Receives name of a resource and array of three elements and
516     * splits the name into folder, name and extension.
517     *
518     * @param res resource name
519     * @param store array to store data to
520     */

521     private static String JavaDoc[] split(String JavaDoc res, String JavaDoc[] store) {
522         if (store == null) {
523             store = new String JavaDoc[3];
524         }
525
526         int file = res.lastIndexOf('/');
527         int dot = res.lastIndexOf('.');
528
529         if (file == -1) {
530             store[0] = ""; // NOI18N
531
} else {
532             store[0] = res.substring(0, file);
533         }
534
535         file++;
536
537         if (dot == -1) {
538             store[1] = res.substring(file);
539             store[2] = ""; // NOI18N
540
} else {
541             store[1] = res.substring(file, dot);
542             store[2] = res.substring(dot + 1);
543         }
544
545         return store;
546     }
547
548     /** Computes a list of FileObjects in the right order
549     * that can represent this instance.
550     *
551     * @param name of resource to find
552     * @return enumeration of FileObject
553     */

554     Enumeration JavaDoc<FileObject> delegates(final String JavaDoc name) {
555         Enumeration JavaDoc<FileSystem> en = Enumerations.array(systems);
556
557         class Resources implements Enumerations.Processor<FileSystem, FileObject> {
558             public FileObject process(FileSystem fs, Collection JavaDoc<FileSystem> ignore) {
559                 if (fs == null) {
560                     return null;
561                 } else {
562                     return findResourceOn(fs, name);
563                 }
564             }
565         }
566
567         return Enumerations.filter(en, new Resources());
568     }
569
570     /** Creates a file object that will mask the given file.
571     * @param fs filesystem to work on
572     * @param res resource name of the file
573     * @exception IOException if it fails
574     */

575     void maskFile(FileSystem fs, String JavaDoc res) throws IOException JavaDoc {
576         FileObject where = findResourceOn(fs, fs.getRoot().getPath());
577         FileUtil.createData(where, res + MASK);
578     }
579
580     /** Deletes a file object that will mask the given file.
581     * @param fs filesystem to work on
582     * @param res resource name of the file
583     * @exception IOException if it fails
584     */

585     void unmaskFile(FileSystem fs, String JavaDoc res) throws IOException JavaDoc {
586         FileObject fo = findResourceOn(fs, res + MASK);
587
588         if (fo != null) {
589             FileLock lock = fo.lock();
590
591             try {
592                 fo.delete(lock);
593             } finally {
594                 lock.releaseLock();
595             }
596         }
597     }
598
599     /** Deletes a all mask files that mask the given file. All
600     * higher levels then fs are checked and mask is deleted if necessary
601     * @param fs filesystem where res is placed
602     * @param res resource name of the file that should be unmasked
603     * @exception IOException if it fails
604     */

605     void unmaskFileOnAll(FileSystem fs, String JavaDoc res) throws IOException JavaDoc {
606         FileSystem[] fss = this.getDelegates();
607
608         for (int i = 0; i < fss.length; i++) {
609             if ((fss[i] == null) || fss[i].isReadOnly()) {
610                 continue;
611             }
612
613             unmaskFile(fss[i], res);
614
615             /** unamsk on all higher levels, which mask files on fs-layer */
616             if (fss[i] == fs) {
617                 return;
618             }
619         }
620     }
621
622     static boolean isMaskFile(FileObject fo) {
623         return fo.getExt().endsWith(MASK);
624     }
625 }
626
Popular Tags