2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
42
package org.openide.filesystems;
44
import java.io.Externalizable;
45
import java.io.IOException;
46
import java.io.InputStream;
47
import java.io.ObjectInput;
48
import java.io.ObjectOutput;
49
import java.io.OutputStream;
50
import java.lang.ref.Reference;
51
import java.lang.ref.WeakReference;
52
import java.util.Collections;
53
import java.util.Enumeration;
54
import java.util.HashMap;
55
import java.util.HashSet;
56
import java.util.Iterator;
57
import java.util.LinkedList;
58
import java.util.List;
60
import java.util.Properties;
62
import java.util.WeakHashMap;
64
/** Implementation of the file object for multi file system.
66
* @author Jaroslav Tulach,
68
final class MultiFileObject extends AbstractFolder implements FileObject.PriorityFileChangeListener {
69
/** generated Serialized Version UID */
70
static final long serialVersionUID = -2343651324897646809L;
72
/** default extension separator */
73
private static final char EXT_SEP = '.';
75
/** default path separator */
76
private static final char PATH_SEP = '/';
77
private static final FileSystem.AtomicAction markAtomicAction = new FileSystem.AtomicAction() {
82
static final ThreadLocal<FileObject> attrAskedFileObject = new ThreadLocal<FileObject>();
84
/** list of objects that we delegate to and that already
87
private Set delegates;
89
/** current delegate (the first object to delegate to), never null */
90
private FileObject leader;
92
/** Reference to lock or null */
93
private Reference<MfLock> lock;
96
private FileChangeListener weakL;
98
/**performance trick: holds delegetad FileObject used last time to getAttributes.
99
* It may looks simply, but all more sophisticated solutions was less efficient.
101
private static Map<MultiFileObject, AttributeCache> fo2AttribCache =
102
new WeakHashMap<MultiFileObject, AttributeCache>();
104
/** Constructor. Takes reference to file system this file belongs to.
106
* @param fs the file system
107
* @param parent the parent object (folder)
108
* @param name name of the object (e.g. <code>filename.ext</code>)
110
public MultiFileObject(MultiFileSystem fs, MultiFileObject parent, String name) {
111
super(fs, parent, name);
113
weakL = org.openide.util.WeakListeners.create(
114
FileObject.PriorityFileChangeListener.class, FileChangeListener.class, this, null
119
if (leader == null) {
120
leader = new AbstractFileObject.Invalid(name);
125
/** Constructor for root.
127
* @param fs the file system
129
public MultiFileObject(MultiFileSystem fs) {
130
this(fs, null, ""); // NOI18N
135
public FileSystem getLeaderFileSystem() throws FileStateInvalidException {
136
return leader.getFileSystem();
139
static synchronized void freeAllAttribCaches() {
140
fo2AttribCache.clear();
143
/** Frees cached objects - added with perf.changes */
144
private void freeAttribCache() {
145
getAttributeCache().free();
148
private AttributeCache getAttributeCache() {
149
synchronized (MultiFileObject.class) {
150
AttributeCache retval = fo2AttribCache.get(this);
151
if (retval == null) {
152
retval = new AttributeCache();
153
fo2AttribCache.put(this, retval);
159
/** Updates list of all references.
161
private void update() {
162
MultiFileSystem mfs = getMultiFileSystem();
163
FileSystem[] arr = mfs.getDelegates();
165
Set now = (delegates == null) ? Collections.EMPTY_SET : delegates;
166
Set<FileObject> del = new HashSet<FileObject>(arr.length * 2);
167
FileObject led = null;
169
String name = getPath();
171
for (int i = 0; i < arr.length; i++) {
172
if (arr[i] != null) {
173
FileObject fo = mfs.findResourceOn(arr[i], name);
178
if (!now.remove(fo)) {
180
fo.addFileChangeListener(weakL);
183
if ((led == null) && fo.isValid()) {
190
Iterator it = now.iterator();
192
while (it.hasNext()) {
193
FileObject fo = (FileObject) it.next();
194
fo.removeFileChangeListener(weakL);
198
// otherwise leave the leader to be last file that represented
200
if (!led.equals(this.leader) && (this.leader != null)) {
201
// isValid prevents here from firing events after MFO.delete
202
if (isData() && isValid()) {
203
fileChanged0(new FileEvent(this));
206
getMultiFileSystem().notifyMigration(this);
212
this.delegates = del;
215
/** Update all existing subobjects.
218
FileSystem mfs = getMultiFileSystem();
221
mfs.beginAtomicAction();
223
// enumeration of all existing objects
224
Enumeration<AbstractFolder> en = existingSubFiles(true);
226
while (en.hasMoreElements()) {
227
MultiFileObject mfo = (MultiFileObject) en.nextElement();
229
if (mfo.isFolder() && !mfo.isInitialized()) {
233
mfo.freeAttribCache();
234
mfo.superRefresh(true);
237
mfs.finishAtomicAction();
241
/** This method was added to achieve firing events that attributes
242
* were changed after setDelegates. Events are not fired reliable but this solution was
243
* choosed because of performance reasons. Attributes name is set to null - what means
244
* that one of attributes was probably changed.
246
void updateAllAfterSetDelegates(FileSystem[] oldFileSystems) {
248
getMultiFileSystem().beginAtomicAction();
250
FileSystem[] fileSystems = getMultiFileSystem().getDelegates();
251
Enumeration<AbstractFolder> en = existingSubFiles(true);
253
while (en.hasMoreElements()) {
254
MultiFileObject mfo = (MultiFileObject) en.nextElement();
256
if (mfo.isFolder() && !mfo.isInitialized()) {
260
if (mfo.hasListeners()) {
261
String path = mfo.getPath();
262
FileObject oldLeader = findLeader(oldFileSystems, path);
263
FileObject newLeader = findLeader(fileSystems, path);
265
if ((oldLeader != null) && (newLeader != null) && !oldLeader.equals(newLeader)) {
266
mfo.fileAttributeChanged0(new FileAttributeEvent(mfo, null, null, null));
270
mfo.freeAttribCache();
274
getMultiFileSystem().finishAtomicAction();
278
private void refreshAfterEvent(FileEvent fe) {
279
FileObject fFile = fe.getFile();
282
MultiFileObject mFile = (MultiFileObject) getFileObject(fFile.getName(), fFile.getExt());
285
mFile.superRefresh(false);
289
private void superRefresh(boolean expected) {
290
super.refresh(expected);
293
private FileObject findLeader(FileSystem[] fs, String path) {
294
MultiFileSystem mfs = getMultiFileSystem();
296
for (FileSystem f : fs) {
300
FileObject fo = mfs.findResourceOn(f, path);
310
/** Getter for the right file system */
311
private MultiFileSystem getMultiFileSystem() {
312
return (MultiFileSystem) getFileSystem();
315
/** Getter for one of children.
317
private MultiFileObject getMultiChild(String name) {
318
return (MultiFileObject) getChild(name);
321
/** Converts the file to be writable.
322
* The file has to be locked!
324
* @return file object (new leader) that is writable
325
* @exception IOException if the object cannot be writable
327
private FileObject writable() throws IOException {
328
MultiFileSystem fs = getMultiFileSystem();
329
FileSystem single = fs.createWritableOn(getPath());
331
if (single != leader.getFileSystem()) {
332
// if writing to a file that is not on writable fs =>
334
if (leader.isFolder()) {
335
leader = FileUtil.createFolder(root(single), getPath());
337
FileObject folder = FileUtil.createFolder(root(single), getParent().getPath());
338
leader = leader.copy(folder, leader.getName(), leader.getExt());
341
MfLock l = ((lock == null) ? null : lock.get());
352
/** All objects that are beyond this one.
353
* @return enumeration of FileObject
355
private Enumeration<FileObject> delegates() {
356
return getMultiFileSystem().delegates(getPath());
359
/** Method that goes upon list of folders and updates its locks. This is used when
360
* an object is masked which may lead to creation of folders on a disk.
362
* @param fo folder to check
363
* @exception IOException if something locks cannot be updated
365
private static void updateFoldersLock(FileObject fo)
368
MultiFileObject mfo = (MultiFileObject) fo;
370
MfLock l = (mfo.lock == null) ? null : mfo.lock.get();
373
// the file has been locked => update the lock
385
/** Method that allows subclasses to return its children.
387
* @return names (name . ext) of subfiles
389
protected final String[] list() {
390
Properties exclude = new Properties();
391
List<String> addList = new LinkedList<String>();
392
Set<String> addSet = new HashSet<String>(101);
394
Enumeration<FileObject> it = delegates();
396
while (it.hasMoreElements()) { // cycle 1
398
FileObject folder = it.nextElement();
400
if ((folder == null) || !folder.isFolder()) {
404
FileObject[] arr = folder.getChildren();
405
Properties local = null;
407
for (int i = 0; i < arr.length; i++) {
408
String name = arr[i].getNameExt();
410
if (name.endsWith(MultiFileSystem.MASK)) {
411
String basename = name.substring(0, name.length() - MultiFileSystem.MASK.length());
413
// this name should be excluded from next rounds of cycle 1
415
local = new Properties(exclude);
418
local.setProperty(basename, basename);
420
if (!getMultiFileSystem().getPropagateMasks()) {
421
// By default, unused mask files are not displayed as file children.
422
// When propagate masks is turned on, though, they should be.
423
// This is useful e.g. when using MFSs as delegates for other MFSs.
428
// lets add this name, if not added yet and is not excluded by
430
if (!addSet.contains(name) && (exclude.getProperty(name) == null)) {
436
// change the excludes to the new ones produced at this round
442
if (getMultiFileSystem().getPropagateMasks()) {
443
// remove all masked files from the array, even if they were
444
// masked on the same level as they were added
445
addList.removeAll(exclude.keySet());
448
String[] res = addList.toArray(new String[addList.size()]);
453
/** When refreshing, also update the state of delegates.
455
/** [PENDING] expected rename of some refresh method */
457
/*protected void internalRefresh (
458
String add, String remove, boolean fire, boolean expected, String[] list
460
protected void refresh(String add, String remove, boolean fire, boolean expected) {
462
getFileSystem().beginAtomicAction();
464
synchronized (this) {
467
/** [PENDING] expected rename of some refresh method */
469
//super.internalRefresh (add, remove, fire, expected, list);
470
super.refresh(add, remove, fire, expected);
473
validFlag &= leader.isValid();
475
getFileSystem().finishAtomicAction();
479
/** Method to create a file object for given subfile.
480
* @param name of the subfile
481
* @return the file object
483
protected final AbstractFolder createFile(String name) {
484
return new MultiFileObject(getMultiFileSystem(), this, name);
491
/* Test whether this object is a folder.
492
* @return true if the file object is a folder (i.e., can have children)
494
public boolean isFolder() {
495
return (parent == null) || leader.isFolder();
499
* Get last modification time.
502
public java.util.Date lastModified() {
503
return leader.lastModified();
506
/* Test whether this object is a data object.
507
* This is exclusive with {@link #isFolder}.
508
* @return true if the file object represents data (i.e., can be read and written)
510
public boolean isData() {
511
return leader.isData();
514
/* Test whether this FileObject is Virtual.
515
* @return true if the file object represents a Virtual File
517
public boolean isVirtual() {
518
return leader.isVirtual();
521
@Deprecated // have to override for compat
522
public boolean isReadOnly() {
523
MultiFileSystem fs = getMultiFileSystem();
525
if (fs.isReadOnly()) {
529
if (leader.isReadOnly()) {
530
// if we can make it writable then nothing
532
FileSystem simple = fs.createWritableOn(getPath());
534
return simple == leader.getFileSystem();
535
} catch (IOException e) {
543
public boolean canWrite() {
544
MultiFileSystem fs = getMultiFileSystem();
546
if (fs.isReadOnly()) {
550
if (!leader.canWrite()) {
551
// if we can make it writable then nothing
553
FileSystem simple = fs.createWritableOn(getPath());
555
return simple != leader.getFileSystem();
556
} catch (IOException e) {
564
/* Get the MIME type of this file.
565
* The MIME type identifies the type of the file's contents and should be used in the same way as in the <B>Java
566
* Activation Framework</B> or in the {@link java.awt.datatransfer} package.
568
* The default implementation calls {@link FileUtil#getMIMEType}.
570
* @return the MIME type textual representation, e.g. <code>"text/plain"</code>
572
public String getMIMEType() {
573
return leader.getMIMEType();
576
/* Get the size of the file.
577
* @return the size of the file in bytes or zero if the file does not contain data (does not
578
* exist or is a folder).
580
public long getSize() {
581
return leader.getSize();
584
/** Get input stream.
585
* @return an input stream to read the contents of this file
586
* @exception FileNotFoundException if the file does not exists, is a folder
587
* rather than a regular file or is invalid
589
public InputStream getInputStream() throws java.io.FileNotFoundException {
590
return leader.getInputStream();
593
/* Get output stream.
594
* @param lock the lock that belongs to this file (obtained by a call to
596
* @return output stream to overwrite the contents of this file
597
* @exception IOException if an error occures (the file is invalid, etc.)
599
public OutputStream getOutputStream(FileLock lock)
600
throws java.io.IOException {
606
getFileSystem().beginAtomicAction(markAtomicAction);
608
synchronized (this) {
611
// this can also change lock in l.lock
613
lWritable = l.findLock(fo);
616
return fo.getOutputStream(lWritable);
618
getFileSystem().finishAtomicAction();
623
public synchronized boolean isLocked() {
624
return lock != null && lock.get() != null;
628
* @return lock that can be used to perform various modifications on the file
629
* @throws FileAlreadyLockedException if the file is already locked
631
public synchronized FileLock lock() throws IOException {
633
FileLock f = (FileLock) lock.get();
636
// [PENDING] construct localized message
637
throw new FileAlreadyLockedException(getPath());
641
java.util.Set set = getMultiFileSystem().createLocksOn(getPath());
642
MfLock l = new MfLock(leader, delegates(), set);
644
lock = new WeakReference<MfLock>(l);
646
// Thread.dumpStack ();
647
// System.out.println ("Locking file: " + this); // NOI18N
651
/** Tests the lock if it is valid, if not throws exception.
652
* @param l lock to test
653
* @return the mf lock for this file object
655
private MfLock testLock(FileLock l) throws java.io.IOException {
657
FSException.io("EXC_InvalidLock", l, getPath(), getMultiFileSystem().getDisplayName(), lock); // NOI18N
660
if (lock.get() != l) {
661
FSException.io("EXC_InvalidLock", l, getPath(), getMultiFileSystem().getDisplayName(), lock.get()); // NOI18N
667
@Deprecated // have to override for compat
668
public void setImportant(boolean b) {
669
Enumeration<FileObject> en = delegates();
671
while (en.hasMoreElements()) {
672
FileObject fo = en.nextElement();
677
getMultiFileSystem().markUnimportant(this);
681
/** Add one void-wrapping to an object. */
682
private static final Object voidify(Object o) {
684
return new VoidValue(0);
685
} else if (o instanceof VoidValue) {
686
VoidValue vv = (VoidValue) o;
688
return new VoidValue(vv.level + 1);
694
/** Strip off one void-wrapping from an object. */
695
private static final Object devoidify(Object o) {
696
if (o instanceof VoidValue) {
697
VoidValue vv = (VoidValue) o;
702
return new VoidValue(vv.level - 1);
709
/* Get the file attribute with the specified name.
710
* @param attrName name of the attribute
711
* @return appropriate (serializable) value or <CODE>null</CODE> if the attribute is unset (or could not be properly restored for some reason)
713
public Object getAttribute(String attrName) {
714
// Performance optimization (avoid calling getPath() too many times):
715
return getAttribute(attrName, getPath());
718
private final Object getAttribute(String attrName, String path) {
719
// Look for attribute in any file system starting at the front.
720
// Additionally, look for attribute in root folder, where
721
// the relative path from the folder to the target file is
722
// prepended to the attribute name, all separated with slashes.
723
// This search scheme permits writable front systems to set file
724
// attributes on deeply buried files in back systems without
725
// actually creating copies of the files or even their parent folders.
726
// [PENDING] consider the effects of mask files
727
String prefixattr = ((path.length() == 0) ? null : (path.replace('/', '\\') + '\\' + attrName));
729
{ /*There was proved that this block enhances performance*/
732
FileObject localFo = getAttributeCache().getDelegate();
733
String cachedAttrName = getAttributeCache().getAttributeName();
735
if ((localFo != null) && !localFo.equals(this) && cachedAttrName.equals(attrName)) {
736
if (localFo.isRoot() && (prefixattr != null)) {
738
FileSystem foFs = localFo.getFileSystem();
740
if (!(foFs instanceof XMLFileSystem)) {
741
localFo = foFs.getRoot();
742
oPerf = getAttribute(localFo, prefixattr, ""); // NOI18N
745
return devoidify(oPerf);
748
} catch (FileStateInvalidException fiex) {
753
/** There is no chance to cache localFo.getPath(), because every
754
* rename up to it in hierarchy makes this cache invalid.
756
oPerf = getAttribute(localFo, attrName, localFo.getPath());
759
return devoidify(oPerf);
764
FileSystem[] systems = getMultiFileSystem().getDelegates();
766
// boolean isLoaderAttr = /* DataObject.EA_ASSIGNED_LOADER */ "NetBeansAttrAssignedLoader".equals (attrName); // NOI18N
767
for (int i = 0; i < systems.length; i++) {
768
if (systems[i] == null) {
772
// Don't check for any assigned loader overrides except for leader & writable systems.
773
// Note that this prevents front layers from overriding default loader!
774
// Could be revisited but in the meantime this is a performance optimization.
775
// if (isLoaderAttr && leaderfs != null && systems[i] != leaderfs && systems[i].isReadOnly ()) {
779
FileObject fo = getMultiFileSystem().findResourceOn(systems[i], path);
782
Object o = getAttribute(fo, attrName, fo.getPath()); // Performance tricks:
789
// Don't check for root override on XMLFileSystem's; the override
790
// could only have been made on a writable filesystem to begin with.
791
// Could skip all RO FSs but then multi-user installs would not work
793
if ((prefixattr != null) && !(systems[i] instanceof XMLFileSystem)) {
794
fo = systems[i].getRoot();
796
Object o = getAttribute(fo, prefixattr, ""); // NOI18N
807
private Object getAttribute(FileObject fo, String attrName, String path) {
810
FileObject topFO = attrAskedFileObject.get();
813
attrAskedFileObject.set(this);
817
if (fo instanceof MultiFileObject) {
818
o = ((MultiFileObject) fo).getAttribute(attrName, path);
819
} else if (fo instanceof AbstractFileObject) {
820
o = ((AbstractFileObject) fo).getAttribute(attrName, path);
822
o = fo.getAttribute(attrName);
826
attrAskedFileObject.set(null);
831
getAttributeCache().setDelegate(fo);
832
getAttributeCache().setAttributeName(attrName);
838
/* Set the file attribute with the specified name.
839
* @param attrName name of the attribute
840
* @param value new value or <code>null</code> to clear the attribute. Must be serializable, although particular file systems may or may not use serialization to store attribute values.
841
* @exception IOException if the attribute cannot be set. If serialization is used to store it, this may in fact be a subclass such as {@link NotSerializableException}.
843
public void setAttribute(String attrName, Object value)
845
setAttribute(attrName, value, true); //NOI18N
848
/* helper method for MFO.setAttribute. MFO can disable firing from underlaying
849
* layers. Should be reviewed in 3.4 or later
850
*@see MultiFileObject#setAttribute*/
851
void setAttribute(String attrName, Object value, boolean fire)
853
// Similar to getAttribute. Here we use createWritableOn to decide which fs
854
// the attribute should be stored on; it is stored on the actual file if
855
// possible, if not then the lowest containing folder.
856
String path = getPath();
857
FileSystem fs = getMultiFileSystem().createWritableOn(path);
858
FileObject fo = getMultiFileSystem().findResourceOn(fs, path);
859
Object oldValue = null;
860
String attrToSet = attrName;
863
oldValue = getAttribute(attrName);
868
attrToSet = path.replace('/', '\\') + '\\' + attrName;
871
getAttributeCache().setDelegate(fo);
872
getAttributeCache().setAttributeName(attrToSet);
874
if (fo instanceof AbstractFolder) {
875
((AbstractFolder) fo).setAttribute(attrToSet, voidify(value), false);
877
/** cannot disable firing from underlaying delegate if not AbstractFolder
878
* without API change. So I don`t fire events from this method with one
881
fire = fire && fo.isRoot();
882
fo.setAttribute(attrToSet, voidify(value));
885
// fire changes for original attribute name even if the attr is actually
886
// stored in the root FO if FO instanceof AbstractFolder (that has supressed firing).
888
/* [PENDING] only this MultiFileObject should fire event and it`s delegate
889
* should not fire event (nevertheless this delegate is physically used
890
* to write given attribute - only implementation detail -
891
* nobody should rely on current implementation).
893
if (fire && (oldValue != value) && hasAtLeastOneListeners()) {
894
fileAttributeChanged0(new FileAttributeEvent(this, attrName, oldValue, value));
898
/* Get all file attribute names for this file.
899
* @return enumeration of keys (as strings)
901
public Enumeration<String> getAttributes() {
902
return getAttributes(getPath());
905
private final Enumeration<String> getAttributes(String path) {
906
Set<String> s = new HashSet<String>();
907
FileSystem[] systems = getMultiFileSystem().getDelegates();
909
// [PENDING] will not remove from the enumeration voided-out attributes
910
// (though this is probably not actually necessary)
911
String prefix = (path.length() == 0) ? null : (path.replace('/', '\\') + '\\');
913
for (int i = 0; i < systems.length; i++) {
914
if (systems[i] == null) {
918
FileObject fo = getMultiFileSystem().findResourceOn(systems[i], path);
921
Enumeration<String> e = fo.getAttributes();
923
while (e.hasMoreElements()) {
924
String attr = e.nextElement();
929
if (prefix != null) {
930
fo = systems[i].getRoot();
932
Enumeration<String> e;
934
if (fo instanceof MultiFileObject) {
935
e = ((MultiFileObject) fo).getAttributes(""); // NOI18N
936
} else if (fo instanceof AbstractFileObject) {
937
e = ((AbstractFileObject) fo).getAttributes(""); // NOI18N
939
e = fo.getAttributes();
942
while (e.hasMoreElements()) {
943
String attr = e.nextElement();
945
if (attr.startsWith(prefix) && (attr.substring(prefix.length()).indexOf('\\') == -1)) {
946
s.add(attr.substring(prefix.length()));
952
return Collections.enumeration(s);
955
/* Create a new folder below this one with the specified name. Fires
956
* <code>fileCreated</code> event.
958
* @param name the name of folder to create (without extension)
959
* @return the new folder
960
* @exception IOException if the folder cannot be created (e.g. already exists)
962
public FileObject createFolder(String name) throws IOException {
966
getFileSystem().beginAtomicAction();
968
synchronized (this) {
969
MultiFileSystem fs = getMultiFileSystem();
971
if (fs.isReadOnly()) {
972
FSException.io("EXC_FSisRO", fs.getDisplayName()); // NOI18N
976
FSException.io("EXC_FisRO", name, fs.getDisplayName()); // NOI18N
979
String fullName = getPath() + PATH_SEP + name;
982
FSException.io("EXC_FoNotFolder", name, getPath(), fs.getDisplayName()); // NOI18N
985
if (this.getFileObject(name) != null) {
986
FSException.io("EXC_FolderAlreadyExist", name, fs.getDisplayName()); // NOI18N
989
FileSystem simple = fs.createWritableOn(fullName);
992
FileUtil.createFolder(root(simple), fullName);
994
// try to unmask if necessary
995
getMultiFileSystem().unmaskFileOnAll(simple, fullName);
997
/** [PENDING] expected rename of some refresh method */
998
//internalRefresh (name, null, true, false,null);
999
refresh(name, null, true, false);
1001
fo = getMultiChild(name);
1005
throw new FileStateInvalidException(
1006
FileSystem.getString("EXC_ApplicationCreateError", getPath(), name)
1010
FileObject[] chlds = fo.getChildren();
1012
for (int i = 0; i < chlds.length; i++) {
1013
getMultiFileSystem().maskFile(simple, chlds[i].getPath());
1016
if (hasListeners()) {
1017
fileCreated0(new FileEvent(this, fo), false);
1021
getFileSystem().finishAtomicAction();
1027
/* Create new data file in this folder with the specified name. Fires
1028
* <code>fileCreated</code> event.
1030
* @param name the name of data object to create (should not contain a period)
1031
* @param ext the extension of the file (or <code>null</code> or <code>""</code>)
1033
* @return the new data file object
1034
* @exception IOException if the file cannot be created (e.g. already exists)
1036
public FileObject createData(String name, String ext)
1037
throws IOException {
1041
getFileSystem().beginAtomicAction();
1043
synchronized (this) {
1044
MultiFileSystem fs = getMultiFileSystem();
1046
if (fs.isReadOnly()) {
1047
FSException.io("EXC_FSisRO", fs.getDisplayName()); // NOI18N
1051
FSException.io("EXC_FisRO", name, fs.getDisplayName()); // NOI18N
1054
String n = "".equals(ext) ? name : (name + EXT_SEP + ext); // NOI18N
1057
FSException.io("EXC_FoNotFolder", n, getPath(), fs.getDisplayName()); // NOI18N
1060
if (this.getFileObject(name, ext) != null) {
1061
FSException.io("EXC_DataAlreadyExist", n, fs.getDisplayName()); // NOI18N
1064
String fullName = getPath() + PATH_SEP + n;
1066
FileSystem simple = fs.createWritableOn(fullName);
1069
FileUtil.createData(root(simple), fullName);
1071
// try to unmask if necessary
1072
getMultiFileSystem().unmaskFileOnAll(simple, fullName);
1074
/** [PENDING] expected rename of some refresh method */
1075
//internalRefresh (n, null, true, false,null);
1076
refresh(n, null, true, false);
1078
fo = getMultiChild(n);
1082
throw new FileStateInvalidException(
1083
FileSystem.getString("EXC_ApplicationCreateError", getPath(), n)
1087
if (hasListeners()) {
1088
fileCreated0(new FileEvent(this, fo), true);
1092
getFileSystem().finishAtomicAction();
1098
/* Renames this file (or folder).
1099
* Both the new basename and new extension should be specified.
1101
* Note that using this call, it is currently only possible to rename <em>within</em>
1102
* a parent folder, and not to do moves <em>across</em> folders.
1103
* Conversely, implementing file systems need only implement "simple" renames.
1104
* If you wish to move a file across folders, you should call {@link FileUtil#moveFile}.
1105
* @param lock File must be locked before renaming.
1106
* @param name new basename of file
1107
* @param ext new extension of file (ignored for folders)
1109
public void rename(FileLock lock, String name, String ext)
1110
throws IOException {
1111
MultiFileSystem fs = getMultiFileSystem();
1113
if (parent == null) {
1114
FSException.io("EXC_CannotRenameRoot", fs.getDisplayName()); // NOI18N
1118
getFileSystem().beginAtomicAction();
1120
synchronized (parent) {
1121
// synchronize on your folder
1122
MfLock l = testLock(lock);
1124
String newFullName = parent.getPath() + PATH_SEP + name;
1127
newFullName += (EXT_SEP + ext);
1130
String oldFullName = getPath();
1133
FSException.io("EXC_CannotRename", getPath(), getMultiFileSystem().getDisplayName(), newFullName); // NOI18N
1136
if (getFileSystem().isReadOnly()) {
1137
FSException.io("EXC_FSisRO", getMultiFileSystem().getDisplayName()); // NOI18N
1140
String on = getName();
1141
String oe = getExt();
1143
//!!! getMultiFileSystem ().change.rename (oldFullName, newFullName);
1144
FileSystem single = fs.createWritableOnForRename(oldFullName, newFullName);
1146
if (single == leader.getFileSystem()) {
1147
// delete the file if we can on the selected
1149
leader.rename(l.findLock(leader), name, ext);
1150
getMultiFileSystem().unmaskFileOnAll(single, newFullName);
1151
copyContent(this, leader);
1153
// rename file that is on different file system
1155
FileObject previousLeader = leader;
1159
FileObject folder = FileUtil.createFolder(root(single), getParent().getPath());
1160
leader = leader.copy(folder, name, ext);
1161
copyAttrs(this, leader);
1164
FileObject fo = FileUtil.createFolder(root(single), newFullName);
1165
copyContent(this, fo);
1168
this.name = name; // must be done before update
1172
// releases lock for previousLeader and aquiares
1174
l.changeLocks(previousLeader, leader);
1177
if (getMultiFileSystem().delegates(oldFullName).hasMoreElements()) {
1178
// if there is older version of the file
1179
// then we have to mask it
1180
getMultiFileSystem().maskFile(single, oldFullName);
1181
updateFoldersLock(getParent());
1185
name = name + EXT_SEP + ext;
1188
String oldName = this.name;
1192
System.out.println ("Resulting file is: " + getPath ());
1193
System.out.println ("Bedw file is: " + newFullName);
1194
System.out.println ("Name: " + name);
1195
System.out.println ("Old : " + oldName);
1198
/** [PENDING] expected to delete*/
1199
parent.refresh(name, oldName);
1201
//!!! getMultiFileSystem ().attr.renameAttributes (oldFullName, newFullName);
1202
if (hasAtLeastOneListeners()) {
1203
fileRenamed0(new FileRenameEvent(this, on, oe));
1207
getFileSystem().finishAtomicAction();
1211
/* Delete this file. If the file is a folder and it is not empty then
1212
* all of its contents are also recursively deleted.
1214
* @param lock the lock obtained by a call to {@link #lock}
1215
* @exception IOException if the file could not be deleted
1217
void handleDelete(FileLock lock) throws IOException {
1218
if (parent == null) {
1219
FSException.io("EXC_CannotDeleteRoot", getMultiFileSystem().getDisplayName() // NOI18N
1223
MultiFileSystem fs = getMultiFileSystem();
1226
getFileSystem().beginAtomicAction();
1228
synchronized (parent) {
1229
String fullName = getPath();
1230
FileSystem single = fs.createWritableOn(fullName);
1232
if (needsMask(lock, true)) {
1233
getMultiFileSystem().maskFile(single, fullName);
1234
updateFoldersLock(getParent());
1240
/** [PENDING] expected rename of some refresh method */
1241
//parent.internalRefresh (null, n, true, false, null);
1242
parent.refresh(null, n, true, false);
1244
if (hasAtLeastOneListeners()) {
1245
fileDeleted0(new FileEvent(this));
1249
getFileSystem().finishAtomicAction();
1257
/** Copies this file. This allows the filesystem to perform any additional
1258
* operation associated with the copy. But the default implementation is simple
1259
* copy of the file and its attributes
1261
* @param target target folder to move this file to
1262
* @param name new basename of file
1263
* @param ext new extension of file (ignored for folders)
1264
* @return the newly created file object representing the moved file
1266
public FileObject copy(FileObject target, String name, String ext)
1267
throws IOException {
1268
return leader.copy(target, name, ext);
1271
/** Moves this file. This allows the filesystem to perform any additional
1272
* operation associated with the move. But the default implementation is encapsulated
1273
* as copy and delete.
1275
* @param lock File must be locked before renaming.
1276
* @param target target folder to move this file to
1277
* @param name new basename of file
1278
* @param ext new extension of file (ignored for folders)
1279
* @return the newly created file object representing the moved file
1281
public FileObject move(FileLock lock, FileObject target, String name, String ext)
1282
throws IOException {
1283
MultiFileSystem fs = getMultiFileSystem();
1286
fs.beginAtomicAction();
1288
if (parent == null) {
1289
FSException.io("EXC_CannotDeleteRoot", fs.getDisplayName() // NOI18N
1293
MfLock lck = testLock(lock);
1294
FileLock l = lck.findLock(leader);
1296
FileSystem simple = fs.createWritableOn(getPath());
1298
if (fs.isReadOnly()) {
1299
FSException.io("EXC_FSisRO", fs.getDisplayName()); // NOI18N
1302
if ((l == null) && (leader.getFileSystem() != simple)) {
1303
leader = writable();
1304
l = lck.findLock(leader);
1307
if (needsMask(lock, false)) {
1308
getMultiFileSystem().maskFile(simple, getPath());
1309
updateFoldersLock(getParent());
1312
return leader.move(l, target, name, ext);
1314
fs.finishAtomicAction();
1318
/* Refresh the contents of a folder. Rescans the list of children names.
1320
public final void refresh(boolean expected) {
1321
if (!isInitialized() && isFolder()) {
1325
Enumeration<FileObject> en = delegates();
1327
while (en.hasMoreElements()) {
1328
FileObject fo = en.nextElement();
1329
fo.refresh(expected);
1332
super.refresh(expected);
1339
/** Fired when a new folder is created. This action can only be
1340
* listened to in folders containing the created folder up to the root of
1343
* @param fe the event describing context where action has taken place
1345
public void fileFolderCreated(FileEvent fe) {
1346
/*One of underlaing layers notifies that new folder was created.
1347
And this folder may have any other childern anywhere deep in hierarchy
1348
and then must be updated and refreshed deep down*/
1352
/** Fired when a new file is created. This action can only be
1353
* listened in folders containing the created file up to the root of
1356
* @param fe the event describing context where action has taken place
1358
public void fileDataCreated(FileEvent fe) {
1359
refreshAfterEvent(fe);
1362
/** Fired when a file is changed.
1363
* @param fe the event describing context where action has taken place
1365
public void fileChanged(FileEvent fe) {
1366
FileObject changedFile = this;
1368
if (fe.getSource().equals(leader) && hasAtLeastOneListeners() && !fe.firedFrom(markAtomicAction)) {
1369
/**There should not dissapear information about source and changed file*/
1370
if (!fe.getFile().equals(fe.getSource())) {
1371
changedFile = getFileObject(fe.getFile().getName(), fe.getFile().getExt());
1374
/**fileChanged1 - should not fire event for this.getParent ().
1375
* I think that already bottom layer forked event.*/
1376
/** [PENDING] fix of #16926, #16895. But there should be investigated
1377
* why this MFO doesn`t know about child ?*/
1378
if (changedFile != null) {
1379
fileChanged1(new FileEvent(this, changedFile, fe.getTime()));
1384
/** Fired when a file is deleted.
1385
* @param fe the event describing context where action has taken place
1387
public void fileDeleted(FileEvent fe) {
1388
if (fe.getFile().isFolder()) {
1391
refreshAfterEvent(fe);
1395
/** Fired when a file is renamed.
1396
* @param fe the event describing context where action has taken place
1397
* and the original name and extension.
1399
public void fileRenamed(FileRenameEvent fe) {
1403
/** Fired when a file attribute is changed.
1404
* @param fe the event describing context where action has taken place,
1405
* the name of attribute and the old and new values.
1407
public void fileAttributeChanged(FileAttributeEvent fe) {
1408
// [PENDING] this is not at all sufficient to notify every change in attributes.
1409
// One, parent dirs of front filesystems can now hold attributes for missing
1410
// files. Two, non-leader files can have attributes too which are merged in.
1411
// In principle all files/folders whose path is a prefix of this path on all
1412
// contained filesystems should be listened to for attribute change events.
1413
if (!hasAtLeastOneListeners() || (leader == null)) {
1417
/** If change is not fired from leader then leader may mask this attribute
1418
* and then event should not be fired */
1419
if (!fe.getFile().equals(leader) && (fe.getName() != null) && (leader.getAttribute(fe.getName()) != null)) {
1423
/** If change is not fired from leader then another delegate may mask this attribute
1424
* and then event should not be fired. */
1426
!fe.getFile().equals(leader) && (fe.getNewValue() != null) && (fe.getName() != null) &&
1427
!fe.getNewValue().equals(getAttribute(fe.getName()))
1432
fileAttributeChanged0(new FileAttributeEvent(this, fe.getName(), fe.getOldValue(), fe.getNewValue()));
1435
/** Copies content of one folder into another.
1436
* @param source source folder
1437
* @param target target folder
1438
* @exception IOException if it fails
1440
private static void copyContent(FileObject source, FileObject target)
1441
throws IOException {
1442
FileObject[] srcArr = source.getChildren();
1444
copyAttrs(source, target); //added
1446
for (int i = 0; i < srcArr.length; i++) {
1447
FileObject child = srcArr[i];
1449
if (MultiFileSystem.isMaskFile(child)) {
1453
if (target.getFileObject(child.getName(), child.getExt()) == null) {
1454
if (child.isData()) {
1455
FileObject fo = FileUtil.copyFile(child, target, child.getName(), child.getExt());
1458
copyAttrs(child, fo);
1461
FileObject targetChild = target.createFolder(child.getName());
1462
copyContent(child, targetChild);
1468
/** Copies attributes of one FileObject into another.
1469
* @param source source folder or file
1470
* @param target target folder or file
1471
* @exception IOException if it fails
1473
private static void copyAttrs(FileObject source, FileObject target) {
1474
Enumeration<String> en = source.getAttributes();
1476
while (en.hasMoreElements()) {
1477
String key = en.nextElement();
1478
Object value = source.getAttribute(key);
1481
target.setAttribute(key, value);
1482
} catch (IOException ie) {
1488
* auxiliary method that returns true if mask is needed and deletes all delegates
1489
* on writable layers if deleteDelegates is true.
1491
* @param deleteDelegates if true all delegates on writable layers will be deleted
1492
* @throws IOException is thrown if lock is not valid.
1493
* @return true if mask is necessary*/
1494
private boolean needsMask(FileLock lock, boolean deleteDelegates)
1495
throws IOException {
1496
MfLock lck = testLock(lock);
1497
Enumeration<FileObject> e = getMultiFileSystem().delegates(getPath());
1498
boolean needsMask = false;
1500
while (e.hasMoreElements()) {
1501
FileObject fo = e.nextElement();
1502
FileLock lockForFo = lck.findLock(fo);
1504
if (lockForFo == null) {
1505
// we will need to create mask
1508
if (deleteDelegates) {
1509
fo.delete(lockForFo);
1517
/** Finds a root for given file system. It also counts with
1518
* redefined method findResourceOn.
1520
* @param fs the filesystem to seach on
1521
* @return the root on the fs
1523
private FileObject root(FileSystem fs) {
1524
return getMultiFileSystem().findResourceOn(fs, ""); // NOI18N
1527
final FileObject getLeader() {
1531
/** Special value used to indicate null masking of an attribute.
1532
* The level is zero in simple cases; incremented when one MFS asks
1533
* another to store a VoidValue.
1535
private static final class VoidValue implements Externalizable {
1537
private static final long serialVersionUID = -2743645909916238684L;
1540
VoidValue(int level) {
1544
public VoidValue() {
1547
public String toString() {
1548
return "org.openide.filesystems.MultiFileObject.VoidValue#" + level; // NOI18N
1551
public void writeExternal(ObjectOutput out) throws IOException {
1552
out.writeInt(level);
1555
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
1556
level = in.readInt();
1560
/** Implementation of lock for abstract files.
1562
private class MfLock extends FileLock {
1563
/** lock for all files (map from FileObject to FileLock) */
1564
private Map<FileObject, FileLock> map = new HashMap<FileObject, FileLock>(11);
1567
* @param leader leader file object
1568
* @param delegates all delegates for this file object
1569
* @param systems a set of filesystems we should create lock on
1570
* @exception IOException if the lock cannot be obtained
1572
public MfLock(FileObject leader, Enumeration<FileObject> delegates, Set systems)
1573
throws IOException {
1574
while (delegates.hasMoreElements()) {
1575
FileObject fo = delegates.nextElement();
1577
if (systems.contains(fo.getFileSystem())) {
1578
FileLock l = fo.lock();
1583
/* JST: Commented out because cause problems when locking a file that
1584
is only of filesystems that are readonly (for example when
1585
file is only on shared installation and locks are allowed only
1586
on local => then there is nothing to lock.
1588
if (map.isEmpty()) {
1589
// trouble, the filesystem returned wrong set of systems to lock
1590
// on => warn about possibly wrong implementation
1591
// to correct the problem override appropriatelly createLocksOn
1592
// method of MultiFileSystem
1593
throw new IOException ("Writable file is not on filesystem from createLocksOn"); // NOI18N
1598
/** Finds lock for given file object.
1599
* @param fo one of delegates
1600
* @return the lock or null
1602
public FileLock findLock(FileObject fo) {
1606
/** Adds another lock into this lock.
1607
* @param fo file object to keep the lock for
1608
* @exception IOException if the lock cannot be obtained
1610
public void addLock(FileObject fo) throws IOException {
1611
map.put(fo, fo.lock());
1614
/** Releases lock for old file object and
1615
* takes new one from newFo
1617
public void changeLocks(FileObject old, FileObject n)
1618
throws IOException {
1619
FileLock l = map.remove(old);
1628
public void releaseLock() {
1629
if (this.isValid()) {
1630
super.releaseLock();
1631
releaseLockForDelegates();
1633
if (getCurrentMfLock() == this) {
1634
// clears the reference to this lock from the file object
1635
MultiFileObject.this.lock = null;
1640
private FileLock getCurrentMfLock() {
1641
FileLock currentLock = null;
1645
currentLock = (FileLock) lock.get();
1651
private void releaseLockForDelegates() {
1652
Iterator it = map.values().iterator();
1654
while (it.hasNext()) {
1655
FileLock l = (FileLock) it.next();
1662
// for better debugging
1663
public String toString() {
1664
return super.toString() + " for " + MultiFileObject.this + " valid=" + isValid(); // NOI18N
1669
private static class AttributeCache {
1670
private FileObject delegate;
1671
private String attribName = ""; // NOI18N
1673
private void free() {
1675
attribName = ""; // NOI18N
1678
private void setDelegate(FileObject delegate) {
1679
this.delegate = delegate;
1682
private void setAttributeName(String attribName) {
1683
this.attribName = attribName;
1686
private FileObject getDelegate() {
1690
private String getAttributeName() {