~ubuntu-branches/ubuntu/quantal/netbeans/quantal

« back to all changes in this revision

Viewing changes to openide/fs/src/org/openide/filesystems/MultiFileObject.java

  • Committer: Bazaar Package Importer
  • Author(s): Marek Slama
  • Date: 2008-01-29 14:11:22 UTC
  • Revision ID: james.westby@ubuntu.com-20080129141122-fnzjbo11ntghxfu7
Tags: upstream-6.0.1
ImportĀ upstreamĀ versionĀ 6.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 
3
 *
 
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 
5
 *
 
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]"
 
23
 *
 
24
 * Contributor(s):
 
25
 *
 
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.
 
29
 *
 
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.
 
40
 */
 
41
 
 
42
package org.openide.filesystems;
 
43
 
 
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;
 
59
import java.util.Map;
 
60
import java.util.Properties;
 
61
import java.util.Set;
 
62
import java.util.WeakHashMap;
 
63
 
 
64
/** Implementation of the file object for multi file system.
 
65
*
 
66
* @author Jaroslav Tulach,
 
67
*/
 
68
final class MultiFileObject extends AbstractFolder implements FileObject.PriorityFileChangeListener {
 
69
    /** generated Serialized Version UID */
 
70
    static final long serialVersionUID = -2343651324897646809L;
 
71
 
 
72
    /** default extension separator */
 
73
    private static final char EXT_SEP = '.';
 
74
 
 
75
    /** default path separator */
 
76
    private static final char PATH_SEP = '/';
 
77
    private static final FileSystem.AtomicAction markAtomicAction = new FileSystem.AtomicAction() {
 
78
            public void run() {
 
79
            }
 
80
        };
 
81
 
 
82
    static final ThreadLocal<FileObject> attrAskedFileObject = new ThreadLocal<FileObject>();
 
83
 
 
84
    /** list of objects that we delegate to and that already
 
85
    * has been created.
 
86
    */
 
87
    private Set delegates;
 
88
 
 
89
    /** current delegate (the first object to delegate to), never null */
 
90
    private FileObject leader;
 
91
 
 
92
    /** Reference to lock or null */
 
93
    private Reference<MfLock> lock;
 
94
 
 
95
    /** listener */
 
96
    private FileChangeListener weakL;
 
97
 
 
98
    /**performance trick:  holds delegetad FileObject used last time to getAttributes.
 
99
     * It may looks simply, but all more sophisticated solutions was less efficient.
 
100
     */
 
101
    private static Map<MultiFileObject, AttributeCache> fo2AttribCache = 
 
102
            new WeakHashMap<MultiFileObject, AttributeCache>();
 
103
 
 
104
    /** Constructor. Takes reference to file system this file belongs to.
 
105
    *
 
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>)
 
109
    */
 
110
    public MultiFileObject(MultiFileSystem fs, MultiFileObject parent, String name) {
 
111
        super(fs, parent, name);
 
112
 
 
113
        weakL = org.openide.util.WeakListeners.create(
 
114
                FileObject.PriorityFileChangeListener.class, FileChangeListener.class, this, null
 
115
            );
 
116
 
 
117
        update();
 
118
 
 
119
        if (leader == null) {
 
120
            leader = new AbstractFileObject.Invalid(name);
 
121
            validFlag = false;
 
122
        }
 
123
    }
 
124
 
 
125
    /** Constructor for root.
 
126
    *
 
127
    * @param fs the file system
 
128
    */
 
129
    public MultiFileObject(MultiFileSystem fs) {
 
130
        this(fs, null, ""); // NOI18N
 
131
    }
 
132
 
 
133
    /** File system.
 
134
    */
 
135
    public FileSystem getLeaderFileSystem() throws FileStateInvalidException {
 
136
        return leader.getFileSystem();
 
137
    }
 
138
 
 
139
    static synchronized void freeAllAttribCaches() {
 
140
        fo2AttribCache.clear();
 
141
    }
 
142
    
 
143
    /** Frees cached objects - added with perf.changes  */
 
144
    private void freeAttribCache() {
 
145
        getAttributeCache().free();
 
146
    }
 
147
    
 
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);
 
154
            }
 
155
            return retval;
 
156
        }
 
157
    }
 
158
 
 
159
    /** Updates list of all references.
 
160
    */
 
161
    private void update() {
 
162
        MultiFileSystem mfs = getMultiFileSystem();
 
163
        FileSystem[] arr = mfs.getDelegates();
 
164
 
 
165
        Set now = (delegates == null) ? Collections.EMPTY_SET : delegates;
 
166
        Set<FileObject> del = new HashSet<FileObject>(arr.length * 2);
 
167
        FileObject led = null;
 
168
 
 
169
        String name = getPath();
 
170
 
 
171
        for (int i = 0; i < arr.length; i++) {
 
172
            if (arr[i] != null) {
 
173
                FileObject fo = mfs.findResourceOn(arr[i], name);
 
174
 
 
175
                if (fo != null) {
 
176
                    del.add(fo);
 
177
 
 
178
                    if (!now.remove(fo)) {
 
179
                        // now there yet
 
180
                        fo.addFileChangeListener(weakL);
 
181
                    }
 
182
 
 
183
                    if ((led == null) && fo.isValid()) {
 
184
                        led = fo;
 
185
                    }
 
186
                }
 
187
            }
 
188
        }
 
189
 
 
190
        Iterator it = now.iterator();
 
191
 
 
192
        while (it.hasNext()) {
 
193
            FileObject fo = (FileObject) it.next();
 
194
            fo.removeFileChangeListener(weakL);
 
195
        }
 
196
 
 
197
        if (led != null) {
 
198
            // otherwise leave the leader to be last file that represented
 
199
            // this one
 
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));
 
204
                }
 
205
 
 
206
                getMultiFileSystem().notifyMigration(this);
 
207
            }
 
208
 
 
209
            this.leader = led;
 
210
        }
 
211
 
 
212
        this.delegates = del;
 
213
    }
 
214
 
 
215
    /** Update all existing subobjects.
 
216
    */
 
217
    void updateAll() {
 
218
        FileSystem mfs = getMultiFileSystem();
 
219
 
 
220
        try {
 
221
            mfs.beginAtomicAction();
 
222
 
 
223
            // enumeration of all existing objects
 
224
            Enumeration<AbstractFolder> en = existingSubFiles(true);
 
225
 
 
226
            while (en.hasMoreElements()) {
 
227
                MultiFileObject mfo = (MultiFileObject) en.nextElement();
 
228
 
 
229
                if (mfo.isFolder() && !mfo.isInitialized()) {
 
230
                    continue;
 
231
                }
 
232
 
 
233
                mfo.freeAttribCache();
 
234
                mfo.superRefresh(true);
 
235
            }
 
236
        } finally {
 
237
            mfs.finishAtomicAction();
 
238
        }
 
239
    }
 
240
 
 
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.
 
245
     */
 
246
    void updateAllAfterSetDelegates(FileSystem[] oldFileSystems) {
 
247
        try {
 
248
            getMultiFileSystem().beginAtomicAction();
 
249
 
 
250
            FileSystem[] fileSystems = getMultiFileSystem().getDelegates();
 
251
            Enumeration<AbstractFolder> en = existingSubFiles(true);
 
252
 
 
253
            while (en.hasMoreElements()) {
 
254
                MultiFileObject mfo = (MultiFileObject) en.nextElement();
 
255
 
 
256
                if (mfo.isFolder() && !mfo.isInitialized()) {
 
257
                    continue;
 
258
                }
 
259
 
 
260
                if (mfo.hasListeners()) {
 
261
                    String path = mfo.getPath();
 
262
                    FileObject oldLeader = findLeader(oldFileSystems, path);
 
263
                    FileObject newLeader = findLeader(fileSystems, path);
 
264
 
 
265
                    if ((oldLeader != null) && (newLeader != null) && !oldLeader.equals(newLeader)) {
 
266
                        mfo.fileAttributeChanged0(new FileAttributeEvent(mfo, null, null, null));
 
267
                    }
 
268
                }
 
269
 
 
270
                mfo.freeAttribCache();
 
271
                mfo.refresh(true);
 
272
            }
 
273
        } finally {
 
274
            getMultiFileSystem().finishAtomicAction();
 
275
        }
 
276
    }
 
277
 
 
278
    private void refreshAfterEvent(FileEvent fe) {
 
279
        FileObject fFile = fe.getFile();
 
280
        superRefresh(false);
 
281
 
 
282
        MultiFileObject mFile = (MultiFileObject) getFileObject(fFile.getName(), fFile.getExt());
 
283
 
 
284
        if (mFile != null) {
 
285
            mFile.superRefresh(false);
 
286
        }
 
287
    }
 
288
 
 
289
    private void superRefresh(boolean expected) {
 
290
        super.refresh(expected);
 
291
    }
 
292
 
 
293
    private FileObject findLeader(FileSystem[] fs, String path) {
 
294
        MultiFileSystem mfs = getMultiFileSystem();
 
295
 
 
296
        for (FileSystem f : fs) {
 
297
            if (f == null) {
 
298
                continue;
 
299
            }
 
300
            FileObject fo = mfs.findResourceOn(f, path);
 
301
 
 
302
            if (fo != null) {
 
303
                return fo;
 
304
            }
 
305
        }
 
306
 
 
307
        return null;
 
308
    }
 
309
 
 
310
    /** Getter for the right file system */
 
311
    private MultiFileSystem getMultiFileSystem() {
 
312
        return (MultiFileSystem) getFileSystem();
 
313
    }
 
314
 
 
315
    /** Getter for one of children.
 
316
    */
 
317
    private MultiFileObject getMultiChild(String name) {
 
318
        return (MultiFileObject) getChild(name);
 
319
    }
 
320
 
 
321
    /** Converts the file to be writable.
 
322
    * The file has to be locked!
 
323
    *
 
324
    * @return file object (new leader) that is writable
 
325
    * @exception IOException if the object cannot be writable
 
326
    */
 
327
    private FileObject writable() throws IOException {
 
328
        MultiFileSystem fs = getMultiFileSystem();
 
329
        FileSystem single = fs.createWritableOn(getPath());
 
330
 
 
331
        if (single != leader.getFileSystem()) {
 
332
            // if writing to a file that is not on writable fs =>
 
333
            // copy it
 
334
            if (leader.isFolder()) {
 
335
                leader = FileUtil.createFolder(root(single), getPath());
 
336
            } else {
 
337
                FileObject folder = FileUtil.createFolder(root(single), getParent().getPath());
 
338
                leader = leader.copy(folder, leader.getName(), leader.getExt());
 
339
            }
 
340
 
 
341
            MfLock l = ((lock == null) ? null : lock.get());
 
342
 
 
343
            if (l != null) {
 
344
                // update the lock
 
345
                l.addLock(leader);
 
346
            }
 
347
        }
 
348
 
 
349
        return leader;
 
350
    }
 
351
 
 
352
    /** All objects that are beyond this one.
 
353
    * @return enumeration of FileObject
 
354
    */
 
355
    private Enumeration<FileObject> delegates() {
 
356
        return getMultiFileSystem().delegates(getPath());
 
357
    }
 
358
 
 
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.
 
361
    *
 
362
    * @param fo folder to check
 
363
    * @exception IOException if something locks cannot be updated
 
364
    */
 
365
    private static void updateFoldersLock(FileObject fo)
 
366
    throws IOException {
 
367
        while (fo != null) {
 
368
            MultiFileObject mfo = (MultiFileObject) fo;
 
369
 
 
370
            MfLock l = (mfo.lock == null) ? null : mfo.lock.get();
 
371
 
 
372
            if (l != null) {
 
373
                // the file has been locked => update the lock
 
374
                mfo.writable();
 
375
            }
 
376
 
 
377
            fo = fo.getParent();
 
378
        }
 
379
    }
 
380
 
 
381
    //
 
382
    // List
 
383
    //
 
384
 
 
385
    /** Method that allows subclasses to return its children.
 
386
    *
 
387
    * @return names (name . ext) of subfiles
 
388
    */
 
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);
 
393
 
 
394
        Enumeration<FileObject> it = delegates();
 
395
 
 
396
        while (it.hasMoreElements()) { // cycle 1
 
397
 
 
398
            FileObject folder = it.nextElement();
 
399
 
 
400
            if ((folder == null) || !folder.isFolder()) {
 
401
                continue;
 
402
            }
 
403
 
 
404
            FileObject[] arr = folder.getChildren();
 
405
            Properties local = null;
 
406
 
 
407
            for (int i = 0; i < arr.length; i++) {
 
408
                String name = arr[i].getNameExt();
 
409
 
 
410
                if (name.endsWith(MultiFileSystem.MASK)) {
 
411
                    String basename = name.substring(0, name.length() - MultiFileSystem.MASK.length());
 
412
 
 
413
                    // this name should be excluded from next rounds of cycle 1
 
414
                    if (local == null) {
 
415
                        local = new Properties(exclude);
 
416
                    }
 
417
 
 
418
                    local.setProperty(basename, basename);
 
419
 
 
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.
 
424
                        continue;
 
425
                    }
 
426
                }
 
427
 
 
428
                // lets add this name, if not added yet and is not excluded by
 
429
                // previous level
 
430
                if (!addSet.contains(name) && (exclude.getProperty(name) == null)) {
 
431
                    addSet.add(name);
 
432
                    addList.add(name);
 
433
                }
 
434
            }
 
435
 
 
436
            // change the excludes to the new ones produced at this round
 
437
            if (local != null) {
 
438
                exclude = local;
 
439
            }
 
440
        }
 
441
 
 
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());
 
446
        }
 
447
 
 
448
        String[] res = addList.toArray(new String[addList.size()]);
 
449
 
 
450
        return res;
 
451
    }
 
452
 
 
453
    /** When refreshing, also update the state of delegates.
 
454
    */
 
455
    /** [PENDING] expected rename of some refresh method */
 
456
 
 
457
    /*protected void internalRefresh (
 
458
        String add, String remove, boolean fire, boolean expected, String[] list
 
459
     **/
 
460
    protected void refresh(String add, String remove, boolean fire, boolean expected) {
 
461
        try {
 
462
            getFileSystem().beginAtomicAction();
 
463
 
 
464
            synchronized (this) {
 
465
                update();
 
466
 
 
467
                /** [PENDING] expected rename of some refresh method */
 
468
 
 
469
                //super.internalRefresh (add, remove, fire, expected, list);
 
470
                super.refresh(add, remove, fire, expected);
 
471
            }
 
472
 
 
473
            validFlag &= leader.isValid();
 
474
        } finally {
 
475
            getFileSystem().finishAtomicAction();
 
476
        }
 
477
    }
 
478
 
 
479
    /** Method to create a file object for given subfile.
 
480
    * @param name of the subfile
 
481
    * @return the file object
 
482
    */
 
483
    protected final AbstractFolder createFile(String name) {
 
484
        return new MultiFileObject(getMultiFileSystem(), this, name);
 
485
    }
 
486
 
 
487
    //
 
488
    // Info
 
489
    //
 
490
 
 
491
    /* Test whether this object is a folder.
 
492
    * @return true if the file object is a folder (i.e., can have children)
 
493
    */
 
494
    public boolean isFolder() {
 
495
        return (parent == null) || leader.isFolder();
 
496
    }
 
497
 
 
498
    /*
 
499
    * Get last modification time.
 
500
    * @return the date
 
501
    */
 
502
    public java.util.Date lastModified() {
 
503
        return leader.lastModified();
 
504
    }
 
505
 
 
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)
 
509
    */
 
510
    public boolean isData() {
 
511
        return leader.isData();
 
512
    }
 
513
 
 
514
    /* Test whether this FileObject is Virtual.
 
515
    * @return true if the file object represents a Virtual File
 
516
    */
 
517
    public boolean isVirtual() {
 
518
        return leader.isVirtual();
 
519
    }
 
520
 
 
521
    @Deprecated // have to override for compat
 
522
    public boolean isReadOnly() {
 
523
        MultiFileSystem fs = getMultiFileSystem();
 
524
 
 
525
        if (fs.isReadOnly()) {
 
526
            return true;
 
527
        }
 
528
 
 
529
        if (leader.isReadOnly()) {
 
530
            // if we can make it writable then nothing
 
531
            try {
 
532
                FileSystem simple = fs.createWritableOn(getPath());
 
533
 
 
534
                return simple == leader.getFileSystem();
 
535
            } catch (IOException e) {
 
536
                return true;
 
537
            }
 
538
        }
 
539
 
 
540
        return false;
 
541
    }
 
542
 
 
543
    public boolean canWrite() {
 
544
        MultiFileSystem fs = getMultiFileSystem();
 
545
 
 
546
        if (fs.isReadOnly()) {
 
547
            return false;
 
548
        }
 
549
 
 
550
        if (!leader.canWrite()) {
 
551
            // if we can make it writable then nothing
 
552
            try {
 
553
                FileSystem simple = fs.createWritableOn(getPath());
 
554
 
 
555
                return simple != leader.getFileSystem();
 
556
            } catch (IOException e) {
 
557
                return false;
 
558
            }
 
559
        }
 
560
 
 
561
        return true;
 
562
    }
 
563
 
 
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.
 
567
    * <P>
 
568
    * The default implementation calls {@link FileUtil#getMIMEType}.
 
569
    *
 
570
    * @return the MIME type textual representation, e.g. <code>"text/plain"</code>
 
571
    */
 
572
    public String getMIMEType() {
 
573
        return leader.getMIMEType();
 
574
    }
 
575
 
 
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).
 
579
    */
 
580
    public long getSize() {
 
581
        return leader.getSize();
 
582
    }
 
583
 
 
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
 
588
    */
 
589
    public InputStream getInputStream() throws java.io.FileNotFoundException {
 
590
        return leader.getInputStream();
 
591
    }
 
592
 
 
593
    /* Get output stream.
 
594
    * @param lock the lock that belongs to this file (obtained by a call to
 
595
    *   {@link #lock})
 
596
    * @return output stream to overwrite the contents of this file
 
597
    * @exception IOException if an error occures (the file is invalid, etc.)
 
598
    */
 
599
    public OutputStream getOutputStream(FileLock lock)
 
600
    throws java.io.IOException {
 
601
        MfLock l;
 
602
        FileLock lWritable;
 
603
        FileObject fo;
 
604
 
 
605
        try {
 
606
            getFileSystem().beginAtomicAction(markAtomicAction);
 
607
 
 
608
            synchronized (this) {
 
609
                l = testLock(lock);
 
610
 
 
611
                // this can also change lock in l.lock
 
612
                fo = writable();
 
613
                lWritable = l.findLock(fo);
 
614
            }
 
615
 
 
616
            return fo.getOutputStream(lWritable);
 
617
        } finally {
 
618
            getFileSystem().finishAtomicAction();
 
619
        }
 
620
    }
 
621
 
 
622
    @Override
 
623
    public synchronized boolean isLocked() {
 
624
        return lock != null && lock.get() != null;
 
625
    }
 
626
    
 
627
    /* Lock this file.
 
628
    * @return lock that can be used to perform various modifications on the file
 
629
    * @throws FileAlreadyLockedException if the file is already locked
 
630
    */
 
631
    public synchronized FileLock lock() throws IOException {
 
632
        if (lock != null) {
 
633
            FileLock f = (FileLock) lock.get();
 
634
 
 
635
            if (f != null) {
 
636
                // [PENDING] construct localized message
 
637
                throw new FileAlreadyLockedException(getPath());
 
638
            }
 
639
        }
 
640
 
 
641
        java.util.Set set = getMultiFileSystem().createLocksOn(getPath());
 
642
        MfLock l = new MfLock(leader, delegates(), set);
 
643
 
 
644
        lock = new WeakReference<MfLock>(l);
 
645
 
 
646
        //    Thread.dumpStack ();
 
647
        //    System.out.println ("Locking file: " + this); // NOI18N
 
648
        return l;
 
649
    }
 
650
 
 
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
 
654
    */
 
655
    private MfLock testLock(FileLock l) throws java.io.IOException {
 
656
        if (lock == null) {
 
657
            FSException.io("EXC_InvalidLock", l, getPath(), getMultiFileSystem().getDisplayName(), lock); // NOI18N
 
658
        }
 
659
 
 
660
        if (lock.get() != l) {
 
661
            FSException.io("EXC_InvalidLock", l, getPath(), getMultiFileSystem().getDisplayName(), lock.get()); // NOI18N                
 
662
        }
 
663
 
 
664
        return (MfLock) l;
 
665
    }
 
666
 
 
667
    @Deprecated // have to override for compat
 
668
    public void setImportant(boolean b) {
 
669
        Enumeration<FileObject> en = delegates();
 
670
 
 
671
        while (en.hasMoreElements()) {
 
672
            FileObject fo = en.nextElement();
 
673
            fo.setImportant(b);
 
674
        }
 
675
 
 
676
        if (!b) {
 
677
            getMultiFileSystem().markUnimportant(this);
 
678
        }
 
679
    }
 
680
 
 
681
    /** Add one void-wrapping to an object. */
 
682
    private static final Object voidify(Object o) {
 
683
        if (o == null) {
 
684
            return new VoidValue(0);
 
685
        } else if (o instanceof VoidValue) {
 
686
            VoidValue vv = (VoidValue) o;
 
687
 
 
688
            return new VoidValue(vv.level + 1);
 
689
        } else {
 
690
            return o;
 
691
        }
 
692
    }
 
693
 
 
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;
 
698
 
 
699
            if (vv.level == 0) {
 
700
                return null;
 
701
            } else {
 
702
                return new VoidValue(vv.level - 1);
 
703
            }
 
704
        } else {
 
705
            return o;
 
706
        }
 
707
    }
 
708
 
 
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)
 
712
    */
 
713
    public Object getAttribute(String attrName) {
 
714
        // Performance optimization (avoid calling getPath() too many times):
 
715
        return getAttribute(attrName, getPath());
 
716
    }
 
717
 
 
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));
 
728
 
 
729
        { /*There was proved that this block enhances performance*/
 
730
 
 
731
            Object oPerf;
 
732
            FileObject localFo = getAttributeCache().getDelegate();
 
733
            String cachedAttrName = getAttributeCache().getAttributeName();
 
734
 
 
735
            if ((localFo != null) && !localFo.equals(this) && cachedAttrName.equals(attrName)) {
 
736
                if (localFo.isRoot() && (prefixattr != null)) {
 
737
                    try {
 
738
                        FileSystem foFs = localFo.getFileSystem();
 
739
 
 
740
                        if (!(foFs instanceof XMLFileSystem)) {
 
741
                            localFo = foFs.getRoot();
 
742
                            oPerf = getAttribute(localFo, prefixattr, ""); // NOI18N
 
743
 
 
744
                            if (oPerf != null) {
 
745
                                return devoidify(oPerf);
 
746
                            }
 
747
                        }
 
748
                    } catch (FileStateInvalidException fiex) {
 
749
                        //then continue
 
750
                    }
 
751
                }
 
752
 
 
753
                /** There is no chance to cache localFo.getPath(), because every
 
754
                 *  rename up to it in hierarchy makes this cache invalid.
 
755
                 */
 
756
                oPerf = getAttribute(localFo, attrName, localFo.getPath());
 
757
 
 
758
                if (oPerf != null) {
 
759
                    return devoidify(oPerf);
 
760
                }
 
761
            }
 
762
        }
 
763
 
 
764
        FileSystem[] systems = getMultiFileSystem().getDelegates();
 
765
 
 
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) {
 
769
                continue;
 
770
            }
 
771
 
 
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 ()) {
 
776
            //                continue;
 
777
            //            }
 
778
            // The normal check:
 
779
            FileObject fo = getMultiFileSystem().findResourceOn(systems[i], path);
 
780
 
 
781
            if (fo != null) {
 
782
                Object o = getAttribute(fo, attrName, fo.getPath()); // Performance tricks:                
 
783
 
 
784
                if (o != null) {
 
785
                    return devoidify(o);
 
786
                }
 
787
            }
 
788
 
 
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
 
792
            // quite right.
 
793
            if ((prefixattr != null) && !(systems[i] instanceof XMLFileSystem)) {
 
794
                fo = systems[i].getRoot();
 
795
 
 
796
                Object o = getAttribute(fo, prefixattr, ""); // NOI18N
 
797
 
 
798
                if (o != null) {
 
799
                    return devoidify(o);
 
800
                }
 
801
            }
 
802
        }
 
803
 
 
804
        return null;
 
805
    }
 
806
 
 
807
    private Object getAttribute(FileObject fo, String attrName, String path) {
 
808
        Object o;
 
809
 
 
810
        FileObject topFO = attrAskedFileObject.get();
 
811
 
 
812
        if (topFO == null) {
 
813
            attrAskedFileObject.set(this);
 
814
        }
 
815
 
 
816
        try {
 
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);
 
821
            } else {
 
822
                o = fo.getAttribute(attrName);
 
823
            }
 
824
        } finally {
 
825
            if (topFO == null) {
 
826
                attrAskedFileObject.set(null);
 
827
            }
 
828
        }
 
829
 
 
830
        if (o != null) {
 
831
            getAttributeCache().setDelegate(fo);
 
832
            getAttributeCache().setAttributeName(attrName);
 
833
        }
 
834
 
 
835
        return o;
 
836
    }
 
837
 
 
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}.
 
842
    */
 
843
    public void setAttribute(String attrName, Object value)
 
844
    throws IOException {
 
845
        setAttribute(attrName, value, true); //NOI18N        
 
846
    }
 
847
 
 
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)
 
852
    throws IOException {
 
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;
 
861
 
 
862
        if (fire) {
 
863
            oldValue = getAttribute(attrName);
 
864
        }
 
865
 
 
866
        if (fo == null) {
 
867
            fo = fs.getRoot();
 
868
            attrToSet = path.replace('/', '\\') + '\\' + attrName;
 
869
        }
 
870
 
 
871
        getAttributeCache().setDelegate(fo);
 
872
        getAttributeCache().setAttributeName(attrToSet);
 
873
        
 
874
        if (fo instanceof AbstractFolder) {
 
875
            ((AbstractFolder) fo).setAttribute(attrToSet, voidify(value), false);
 
876
        } else {
 
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
 
879
             * exception - root.
 
880
             */
 
881
            fire = fire && fo.isRoot();
 
882
            fo.setAttribute(attrToSet, voidify(value));
 
883
        }
 
884
 
 
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).
 
887
 
 
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).
 
892
         **/
 
893
        if (fire && (oldValue != value) && hasAtLeastOneListeners()) {
 
894
            fileAttributeChanged0(new FileAttributeEvent(this, attrName, oldValue, value));
 
895
        }
 
896
    }
 
897
 
 
898
    /* Get all file attribute names for this file.
 
899
    * @return enumeration of keys (as strings)
 
900
    */
 
901
    public Enumeration<String> getAttributes() {
 
902
        return getAttributes(getPath());
 
903
    }
 
904
 
 
905
    private final Enumeration<String> getAttributes(String path) {
 
906
        Set<String> s = new HashSet<String>();
 
907
        FileSystem[] systems = getMultiFileSystem().getDelegates();
 
908
 
 
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('/', '\\') + '\\');
 
912
 
 
913
        for (int i = 0; i < systems.length; i++) {
 
914
            if (systems[i] == null) {
 
915
                continue;
 
916
            }
 
917
 
 
918
            FileObject fo = getMultiFileSystem().findResourceOn(systems[i], path);
 
919
 
 
920
            if (fo != null) {
 
921
                Enumeration<String> e = fo.getAttributes();
 
922
 
 
923
                while (e.hasMoreElements()) {
 
924
                    String attr = e.nextElement();
 
925
                    s.add(attr);
 
926
                }
 
927
            }
 
928
 
 
929
            if (prefix != null) {
 
930
                fo = systems[i].getRoot();
 
931
 
 
932
                Enumeration<String> e;
 
933
 
 
934
                if (fo instanceof MultiFileObject) {
 
935
                    e = ((MultiFileObject) fo).getAttributes(""); // NOI18N
 
936
                } else if (fo instanceof AbstractFileObject) {
 
937
                    e = ((AbstractFileObject) fo).getAttributes(""); // NOI18N
 
938
                } else {
 
939
                    e = fo.getAttributes();
 
940
                }
 
941
 
 
942
                while (e.hasMoreElements()) {
 
943
                    String attr = e.nextElement();
 
944
 
 
945
                    if (attr.startsWith(prefix) && (attr.substring(prefix.length()).indexOf('\\') == -1)) {
 
946
                        s.add(attr.substring(prefix.length()));
 
947
                    }
 
948
                }
 
949
            }
 
950
        }
 
951
 
 
952
        return Collections.enumeration(s);
 
953
    }
 
954
 
 
955
    /* Create a new folder below this one with the specified name. Fires
 
956
    * <code>fileCreated</code> event.
 
957
    *
 
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)
 
961
    */
 
962
    public FileObject createFolder(String name) throws IOException {
 
963
        MultiFileObject fo;
 
964
 
 
965
        try {
 
966
            getFileSystem().beginAtomicAction();
 
967
 
 
968
            synchronized (this) {
 
969
                MultiFileSystem fs = getMultiFileSystem();
 
970
 
 
971
                if (fs.isReadOnly()) {
 
972
                    FSException.io("EXC_FSisRO", fs.getDisplayName()); // NOI18N
 
973
                }
 
974
 
 
975
                if (isReadOnly()) {
 
976
                    FSException.io("EXC_FisRO", name, fs.getDisplayName()); // NOI18N
 
977
                }
 
978
 
 
979
                String fullName = getPath() + PATH_SEP + name;
 
980
 
 
981
                if (!isFolder()) {
 
982
                    FSException.io("EXC_FoNotFolder", name, getPath(), fs.getDisplayName()); // NOI18N
 
983
                }
 
984
 
 
985
                if (this.getFileObject(name) != null) {
 
986
                    FSException.io("EXC_FolderAlreadyExist", name, fs.getDisplayName()); // NOI18N
 
987
                }
 
988
 
 
989
                FileSystem simple = fs.createWritableOn(fullName);
 
990
 
 
991
                // create
 
992
                FileUtil.createFolder(root(simple), fullName);
 
993
 
 
994
                // try to unmask if necessary
 
995
                getMultiFileSystem().unmaskFileOnAll(simple, fullName);
 
996
 
 
997
                /** [PENDING] expected rename of some refresh method */
 
998
                //internalRefresh (name, null, true, false,null);
 
999
                refresh(name, null, true, false);
 
1000
 
 
1001
                fo = getMultiChild(name);
 
1002
 
 
1003
                if (fo == null) {
 
1004
                    // system error
 
1005
                    throw new FileStateInvalidException(
 
1006
                        FileSystem.getString("EXC_ApplicationCreateError", getPath(), name)
 
1007
                    );
 
1008
                }
 
1009
 
 
1010
                FileObject[] chlds = fo.getChildren();
 
1011
 
 
1012
                for (int i = 0; i < chlds.length; i++) {
 
1013
                    getMultiFileSystem().maskFile(simple, chlds[i].getPath());
 
1014
                }
 
1015
 
 
1016
                if (hasListeners()) {
 
1017
                    fileCreated0(new FileEvent(this, fo), false);
 
1018
                }
 
1019
            }
 
1020
        } finally {
 
1021
            getFileSystem().finishAtomicAction();
 
1022
        }
 
1023
 
 
1024
        return fo;
 
1025
    }
 
1026
 
 
1027
    /* Create new data file in this folder with the specified name. Fires
 
1028
    * <code>fileCreated</code> event.
 
1029
    *
 
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>)
 
1032
    *
 
1033
    * @return the new data file object
 
1034
    * @exception IOException if the file cannot be created (e.g. already exists)
 
1035
    */
 
1036
    public FileObject createData(String name, String ext)
 
1037
    throws IOException {
 
1038
        MultiFileObject fo;
 
1039
 
 
1040
        try {
 
1041
            getFileSystem().beginAtomicAction();
 
1042
 
 
1043
            synchronized (this) {
 
1044
                MultiFileSystem fs = getMultiFileSystem();
 
1045
 
 
1046
                if (fs.isReadOnly()) {
 
1047
                    FSException.io("EXC_FSisRO", fs.getDisplayName()); // NOI18N
 
1048
                }
 
1049
 
 
1050
                if (isReadOnly()) {
 
1051
                    FSException.io("EXC_FisRO", name, fs.getDisplayName()); // NOI18N
 
1052
                }
 
1053
 
 
1054
                String n = "".equals(ext) ? name : (name + EXT_SEP + ext); // NOI18N
 
1055
 
 
1056
                if (!isFolder()) {
 
1057
                    FSException.io("EXC_FoNotFolder", n, getPath(), fs.getDisplayName()); // NOI18N
 
1058
                }
 
1059
 
 
1060
                if (this.getFileObject(name, ext) != null) {
 
1061
                    FSException.io("EXC_DataAlreadyExist", n, fs.getDisplayName()); // NOI18N
 
1062
                }
 
1063
 
 
1064
                String fullName = getPath() + PATH_SEP + n;
 
1065
 
 
1066
                FileSystem simple = fs.createWritableOn(fullName);
 
1067
 
 
1068
                // create
 
1069
                FileUtil.createData(root(simple), fullName);
 
1070
 
 
1071
                // try to unmask if necessary
 
1072
                getMultiFileSystem().unmaskFileOnAll(simple, fullName);
 
1073
 
 
1074
                /** [PENDING] expected rename of some refresh method */
 
1075
                //internalRefresh (n, null, true, false,null);
 
1076
                refresh(n, null, true, false);
 
1077
 
 
1078
                fo = getMultiChild(n);
 
1079
 
 
1080
                if (fo == null) {
 
1081
                    // system error
 
1082
                    throw new FileStateInvalidException(
 
1083
                        FileSystem.getString("EXC_ApplicationCreateError", getPath(), n)
 
1084
                    );
 
1085
                }
 
1086
 
 
1087
                if (hasListeners()) {
 
1088
                    fileCreated0(new FileEvent(this, fo), true);
 
1089
                }
 
1090
            }
 
1091
        } finally {
 
1092
            getFileSystem().finishAtomicAction();
 
1093
        }
 
1094
 
 
1095
        return fo;
 
1096
    }
 
1097
 
 
1098
    /* Renames this file (or folder).
 
1099
    * Both the new basename and new extension should be specified.
 
1100
    * <p>
 
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)
 
1108
    */
 
1109
    public void rename(FileLock lock, String name, String ext)
 
1110
    throws IOException {
 
1111
        MultiFileSystem fs = getMultiFileSystem();
 
1112
 
 
1113
        if (parent == null) {
 
1114
            FSException.io("EXC_CannotRenameRoot", fs.getDisplayName()); // NOI18N
 
1115
        }
 
1116
 
 
1117
        try {
 
1118
            getFileSystem().beginAtomicAction();
 
1119
 
 
1120
            synchronized (parent) {
 
1121
                // synchronize on your folder
 
1122
                MfLock l = testLock(lock);
 
1123
 
 
1124
                String newFullName = parent.getPath() + PATH_SEP + name;
 
1125
 
 
1126
                if (isData()) {
 
1127
                    newFullName += (EXT_SEP + ext);
 
1128
                }
 
1129
 
 
1130
                String oldFullName = getPath();
 
1131
 
 
1132
                if (isReadOnly()) {
 
1133
                    FSException.io("EXC_CannotRename", getPath(), getMultiFileSystem().getDisplayName(), newFullName); // NOI18N
 
1134
                }
 
1135
 
 
1136
                if (getFileSystem().isReadOnly()) {
 
1137
                    FSException.io("EXC_FSisRO", getMultiFileSystem().getDisplayName()); // NOI18N
 
1138
                }
 
1139
 
 
1140
                String on = getName();
 
1141
                String oe = getExt();
 
1142
 
 
1143
                //!!!      getMultiFileSystem ().change.rename (oldFullName, newFullName);
 
1144
                FileSystem single = fs.createWritableOnForRename(oldFullName, newFullName);
 
1145
 
 
1146
                if (single == leader.getFileSystem()) {
 
1147
                    // delete the file if we can on the selected
 
1148
                    // system
 
1149
                    leader.rename(l.findLock(leader), name, ext);
 
1150
                    getMultiFileSystem().unmaskFileOnAll(single, newFullName);
 
1151
                    copyContent(this, leader);
 
1152
                } else {
 
1153
                    // rename file that is on different file system
 
1154
                    // means to copy it
 
1155
                    FileObject previousLeader = leader;
 
1156
 
 
1157
                    if (isData()) {
 
1158
                        // data
 
1159
                        FileObject folder = FileUtil.createFolder(root(single), getParent().getPath());
 
1160
                        leader = leader.copy(folder, name, ext);
 
1161
                        copyAttrs(this, leader);
 
1162
                    } else {
 
1163
                        // folder
 
1164
                        FileObject fo = FileUtil.createFolder(root(single), newFullName);
 
1165
                        copyContent(this, fo);
 
1166
 
 
1167
                        leader = fo;
 
1168
                        this.name = name; // must be done before update                        
 
1169
                        update();
 
1170
                    }
 
1171
 
 
1172
                    // releases lock for previousLeader and aquiares 
 
1173
                    // new for leader
 
1174
                    l.changeLocks(previousLeader, leader);
 
1175
                }
 
1176
 
 
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());
 
1182
                }
 
1183
 
 
1184
                if (isData()) {
 
1185
                    name = name + EXT_SEP + ext;
 
1186
                }
 
1187
 
 
1188
                String oldName = this.name;
 
1189
                this.name = name;
 
1190
 
 
1191
                /*
 
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);
 
1196
                */
 
1197
 
 
1198
                /** [PENDING] expected to delete*/
 
1199
                parent.refresh(name, oldName);
 
1200
 
 
1201
                //!!!      getMultiFileSystem ().attr.renameAttributes (oldFullName, newFullName);
 
1202
                if (hasAtLeastOneListeners()) {
 
1203
                    fileRenamed0(new FileRenameEvent(this, on, oe));
 
1204
                }
 
1205
            }
 
1206
        } finally {
 
1207
            getFileSystem().finishAtomicAction();
 
1208
        }
 
1209
    }
 
1210
 
 
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.
 
1213
    *
 
1214
    * @param lock the lock obtained by a call to {@link #lock}
 
1215
    * @exception IOException if the file could not be deleted
 
1216
    */
 
1217
    void handleDelete(FileLock lock) throws IOException {
 
1218
        if (parent == null) {
 
1219
            FSException.io("EXC_CannotDeleteRoot", getMultiFileSystem().getDisplayName() // NOI18N
 
1220
            );
 
1221
        }
 
1222
 
 
1223
        MultiFileSystem fs = getMultiFileSystem();
 
1224
 
 
1225
        try {
 
1226
            getFileSystem().beginAtomicAction();
 
1227
 
 
1228
            synchronized (parent) {
 
1229
                String fullName = getPath();
 
1230
                FileSystem single = fs.createWritableOn(fullName);
 
1231
 
 
1232
                if (needsMask(lock, true)) {
 
1233
                    getMultiFileSystem().maskFile(single, fullName);
 
1234
                    updateFoldersLock(getParent());
 
1235
                }
 
1236
 
 
1237
                String n = name;
 
1238
                validFlag = false;
 
1239
 
 
1240
                /** [PENDING] expected rename of some refresh method */
 
1241
                //parent.internalRefresh (null, n, true, false, null);
 
1242
                parent.refresh(null, n, true, false);
 
1243
 
 
1244
                if (hasAtLeastOneListeners()) {
 
1245
                    fileDeleted0(new FileEvent(this));
 
1246
                }
 
1247
            }
 
1248
        } finally {
 
1249
            getFileSystem().finishAtomicAction();
 
1250
        }
 
1251
    }
 
1252
 
 
1253
    //
 
1254
    // Transfer
 
1255
    //
 
1256
 
 
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
 
1260
    *
 
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
 
1265
    */
 
1266
    public FileObject copy(FileObject target, String name, String ext)
 
1267
    throws IOException {
 
1268
        return leader.copy(target, name, ext);
 
1269
    }
 
1270
 
 
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.
 
1274
    *
 
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
 
1280
    */
 
1281
    public FileObject move(FileLock lock, FileObject target, String name, String ext)
 
1282
    throws IOException {
 
1283
        MultiFileSystem fs = getMultiFileSystem();
 
1284
 
 
1285
        try {
 
1286
            fs.beginAtomicAction();
 
1287
 
 
1288
            if (parent == null) {
 
1289
                FSException.io("EXC_CannotDeleteRoot", fs.getDisplayName() // NOI18N
 
1290
                );
 
1291
            }
 
1292
 
 
1293
            MfLock lck = testLock(lock);
 
1294
            FileLock l = lck.findLock(leader);
 
1295
 
 
1296
            FileSystem simple = fs.createWritableOn(getPath());
 
1297
 
 
1298
            if (fs.isReadOnly()) {
 
1299
                FSException.io("EXC_FSisRO", fs.getDisplayName()); // NOI18N
 
1300
            }
 
1301
 
 
1302
            if ((l == null) && (leader.getFileSystem() != simple)) {
 
1303
                leader = writable();
 
1304
                l = lck.findLock(leader);
 
1305
            }
 
1306
 
 
1307
            if (needsMask(lock, false)) {
 
1308
                getMultiFileSystem().maskFile(simple, getPath());
 
1309
                updateFoldersLock(getParent());
 
1310
            }
 
1311
 
 
1312
            return leader.move(l, target, name, ext);
 
1313
        } finally {
 
1314
            fs.finishAtomicAction();
 
1315
        }
 
1316
    }
 
1317
 
 
1318
    /* Refresh the contents of a folder. Rescans the list of children names.
 
1319
    */
 
1320
    public final void refresh(boolean expected) {
 
1321
        if (!isInitialized() && isFolder()) {
 
1322
            return;
 
1323
        }
 
1324
 
 
1325
        Enumeration<FileObject> en = delegates();
 
1326
 
 
1327
        while (en.hasMoreElements()) {
 
1328
            FileObject fo = en.nextElement();
 
1329
            fo.refresh(expected);
 
1330
        }
 
1331
 
 
1332
        super.refresh(expected);
 
1333
    }
 
1334
 
 
1335
    //
 
1336
    // Listeners
 
1337
    //
 
1338
 
 
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
 
1341
     * file system.
 
1342
     *
 
1343
     * @param fe the event describing context where action has taken place
 
1344
     */
 
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*/
 
1349
        updateAll();
 
1350
    }
 
1351
 
 
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
 
1354
     * file system.
 
1355
     *
 
1356
     * @param fe the event describing context where action has taken place
 
1357
     */
 
1358
    public void fileDataCreated(FileEvent fe) {
 
1359
        refreshAfterEvent(fe);
 
1360
    }
 
1361
 
 
1362
    /** Fired when a file is changed.
 
1363
     * @param fe the event describing context where action has taken place
 
1364
     */
 
1365
    public void fileChanged(FileEvent fe) {
 
1366
        FileObject changedFile = this;
 
1367
 
 
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());
 
1372
            }
 
1373
 
 
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()));
 
1380
            }
 
1381
        }
 
1382
    }
 
1383
 
 
1384
    /** Fired when a file is deleted.
 
1385
     * @param fe the event describing context where action has taken place
 
1386
     */
 
1387
    public void fileDeleted(FileEvent fe) {
 
1388
        if (fe.getFile().isFolder()) {
 
1389
            updateAll();
 
1390
        } else {
 
1391
            refreshAfterEvent(fe);
 
1392
        }
 
1393
    }
 
1394
 
 
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.
 
1398
     */
 
1399
    public void fileRenamed(FileRenameEvent fe) {
 
1400
        updateAll();
 
1401
    }
 
1402
 
 
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.
 
1406
     */
 
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)) {
 
1414
            return;
 
1415
        }
 
1416
 
 
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)) {
 
1420
            return;
 
1421
        }
 
1422
 
 
1423
        /** If change is not fired from leader then another delegate may mask this attribute
 
1424
         *  and then event should not be fired. */
 
1425
        if (
 
1426
            !fe.getFile().equals(leader) && (fe.getNewValue() != null) && (fe.getName() != null) &&
 
1427
                !fe.getNewValue().equals(getAttribute(fe.getName()))
 
1428
        ) {
 
1429
            return;
 
1430
        }
 
1431
 
 
1432
        fileAttributeChanged0(new FileAttributeEvent(this, fe.getName(), fe.getOldValue(), fe.getNewValue()));
 
1433
    }
 
1434
 
 
1435
    /** Copies content of one folder into another.
 
1436
    * @param source source folder
 
1437
    * @param target target folder
 
1438
    * @exception IOException if it fails
 
1439
    */
 
1440
    private static void copyContent(FileObject source, FileObject target)
 
1441
    throws IOException {
 
1442
        FileObject[] srcArr = source.getChildren();
 
1443
 
 
1444
        copyAttrs(source, target); //added
 
1445
 
 
1446
        for (int i = 0; i < srcArr.length; i++) {
 
1447
            FileObject child = srcArr[i];
 
1448
 
 
1449
            if (MultiFileSystem.isMaskFile(child)) {
 
1450
                continue;
 
1451
            }
 
1452
 
 
1453
            if (target.getFileObject(child.getName(), child.getExt()) == null) {
 
1454
                if (child.isData()) {
 
1455
                    FileObject fo = FileUtil.copyFile(child, target, child.getName(), child.getExt());
 
1456
 
 
1457
                    if (fo != null) {
 
1458
                        copyAttrs(child, fo);
 
1459
                    }
 
1460
                } else {
 
1461
                    FileObject targetChild = target.createFolder(child.getName());
 
1462
                    copyContent(child, targetChild);
 
1463
                }
 
1464
            }
 
1465
        }
 
1466
    }
 
1467
 
 
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
 
1472
    */
 
1473
    private static void copyAttrs(FileObject source, FileObject target) {
 
1474
        Enumeration<String> en = source.getAttributes();
 
1475
 
 
1476
        while (en.hasMoreElements()) {
 
1477
            String key = en.nextElement();
 
1478
            Object value = source.getAttribute(key);
 
1479
 
 
1480
            try {
 
1481
                target.setAttribute(key, value);
 
1482
            } catch (IOException ie) {
 
1483
            }
 
1484
        }
 
1485
    }
 
1486
 
 
1487
    /**
 
1488
     * auxiliary method that returns true if mask is needed and deletes all delegates
 
1489
     * on writable layers if deleteDelegates is true.
 
1490
     * @param lock
 
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;
 
1499
 
 
1500
        while (e.hasMoreElements()) {
 
1501
            FileObject fo = e.nextElement();
 
1502
            FileLock lockForFo = lck.findLock(fo);
 
1503
 
 
1504
            if (lockForFo == null) {
 
1505
                // we will need to create mask
 
1506
                needsMask = true;
 
1507
            } else {
 
1508
                if (deleteDelegates) {
 
1509
                    fo.delete(lockForFo);
 
1510
                }
 
1511
            }
 
1512
        }
 
1513
 
 
1514
        return needsMask;
 
1515
    }
 
1516
 
 
1517
    /** Finds a root for given file system. It also counts with
 
1518
    * redefined method findResourceOn.
 
1519
    *
 
1520
    * @param fs the filesystem to seach on
 
1521
    * @return the root on the fs
 
1522
    */
 
1523
    private FileObject root(FileSystem fs) {
 
1524
        return getMultiFileSystem().findResourceOn(fs, ""); // NOI18N
 
1525
    }
 
1526
 
 
1527
    final FileObject getLeader() {
 
1528
        return leader;
 
1529
    }
 
1530
 
 
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.
 
1534
     */
 
1535
    private static final class VoidValue implements Externalizable {
 
1536
        // Externalizable:
 
1537
        private static final long serialVersionUID = -2743645909916238684L;
 
1538
        int level;
 
1539
 
 
1540
        VoidValue(int level) {
 
1541
            this.level = level;
 
1542
        }
 
1543
 
 
1544
        public VoidValue() {
 
1545
        }
 
1546
 
 
1547
        public String toString() {
 
1548
            return "org.openide.filesystems.MultiFileObject.VoidValue#" + level; // NOI18N
 
1549
        }
 
1550
 
 
1551
        public void writeExternal(ObjectOutput out) throws IOException {
 
1552
            out.writeInt(level);
 
1553
        }
 
1554
 
 
1555
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
 
1556
            level = in.readInt();
 
1557
        }
 
1558
    }
 
1559
 
 
1560
    /** Implementation of lock for abstract files.
 
1561
    */
 
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);
 
1565
 
 
1566
        /**
 
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
 
1571
        */
 
1572
        public MfLock(FileObject leader, Enumeration<FileObject> delegates, Set systems)
 
1573
        throws IOException {
 
1574
            while (delegates.hasMoreElements()) {
 
1575
                FileObject fo = delegates.nextElement();
 
1576
 
 
1577
                if (systems.contains(fo.getFileSystem())) {
 
1578
                    FileLock l = fo.lock();
 
1579
                    map.put(fo, l);
 
1580
                }
 
1581
            }
 
1582
 
 
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.
 
1587
 
 
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
 
1594
                          }
 
1595
            */
 
1596
        }
 
1597
 
 
1598
        /** Finds lock for given file object.
 
1599
        * @param fo one of delegates
 
1600
        * @return the lock or null
 
1601
        */
 
1602
        public FileLock findLock(FileObject fo) {
 
1603
            return map.get(fo);
 
1604
        }
 
1605
 
 
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
 
1609
        */
 
1610
        public void addLock(FileObject fo) throws IOException {
 
1611
            map.put(fo, fo.lock());
 
1612
        }
 
1613
 
 
1614
        /** Releases lock for old file object and
 
1615
        * takes new one from newFo
 
1616
        */
 
1617
        public void changeLocks(FileObject old, FileObject n)
 
1618
        throws IOException {
 
1619
            FileLock l = map.remove(old);
 
1620
 
 
1621
            if (l != null) {
 
1622
                l.releaseLock();
 
1623
            }
 
1624
 
 
1625
            addLock(n);
 
1626
        }
 
1627
 
 
1628
        public void releaseLock() {
 
1629
            if (this.isValid()) {
 
1630
                super.releaseLock();
 
1631
                releaseLockForDelegates();
 
1632
 
 
1633
                if (getCurrentMfLock() == this) {
 
1634
                    // clears the reference to this lock from the file object
 
1635
                    MultiFileObject.this.lock = null;
 
1636
                }
 
1637
            }
 
1638
        }
 
1639
 
 
1640
        private FileLock getCurrentMfLock() {
 
1641
            FileLock currentLock = null;
 
1642
            ;
 
1643
 
 
1644
            if (lock != null) {
 
1645
                currentLock = (FileLock) lock.get();
 
1646
            }
 
1647
 
 
1648
            return currentLock;
 
1649
        }
 
1650
 
 
1651
        private void releaseLockForDelegates() {
 
1652
            Iterator it = map.values().iterator();
 
1653
 
 
1654
            while (it.hasNext()) {
 
1655
                FileLock l = (FileLock) it.next();
 
1656
                l.releaseLock();
 
1657
            }
 
1658
 
 
1659
            map.clear();
 
1660
        }
 
1661
 
 
1662
        // for better debugging
 
1663
        public String toString() {
 
1664
            return super.toString() + " for " + MultiFileObject.this + " valid=" + isValid(); // NOI18N
 
1665
        }
 
1666
    }    
 
1667
    // MfLock
 
1668
    
 
1669
    private static class AttributeCache {
 
1670
        private FileObject delegate;
 
1671
        private String attribName = ""; // NOI18N        
 
1672
        
 
1673
        private void free() {
 
1674
            delegate = null;
 
1675
            attribName = ""; // NOI18N            
 
1676
        }
 
1677
        
 
1678
        private void setDelegate(FileObject delegate) {
 
1679
            this.delegate = delegate;            
 
1680
        }
 
1681
        
 
1682
        private void setAttributeName(String attribName) {
 
1683
            this.attribName = attribName;
 
1684
        }
 
1685
                        
 
1686
        private FileObject getDelegate() {
 
1687
            return delegate;
 
1688
        }
 
1689
        
 
1690
        private String getAttributeName() {
 
1691
            return attribName;
 
1692
        }
 
1693
    }
 
1694
}