All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.openide.filesystems.MultiFileObject Maven / Gradle / Ivy

There is a newer version: RELEASE240
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.openide.filesystems;

import java.io.Externalizable;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.io.OutputStream;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.openide.util.Enumerations;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;

/** Implementation of the file object for multi file system.
*
* @author Jaroslav Tulach,
*/
final class MultiFileObject extends AbstractFolder implements FileObject.PriorityFileChangeListener {
    /** generated Serialized Version UID */
    static final long serialVersionUID = -2343651324897646809L;

    /** default extension separator */
    private static final char EXT_SEP = '.';

    /** default path separator */
    private static final char PATH_SEP = '/';
    static final String WEIGHT_ATTRIBUTE = "weight"; // NOI18N
    private static final FileSystem.AtomicAction markAtomicAction = new FileSystem.AtomicAction() {
            public void run() {
            }
        };

    static final ThreadLocal attrAskedFileObject = new ThreadLocal();

    /** list of objects that we delegate to and that already
    * has been created.
    */
    private Set delegates;

    /** current delegate (the first object to delegate to), never null */
    private FileObject leader;

    /** Reference to lock or null */
    private Reference lock;
    
    /** Stack trace of locking thread. */
    protected Throwable lockedBy;

    /** listener */
    private FileChangeListener weakL;

    /**performance trick:  holds delegetad FileObject used last time to getAttributes.
     * It may looks simply, but all more sophisticated solutions was less efficient.
     */
    private static Map fo2AttribCache = 
            new WeakHashMap();

    /** Constructor. Takes reference to file system this file belongs to.
    *
    * @param fs the file system
    * @param parent the parent object (folder)
    * @param name name of the object (e.g. filename.ext)
    */
    public MultiFileObject(MultiFileSystem fs, MultiFileObject parent, String name) {
        super(fs, parent, name);

        weakL = org.openide.util.WeakListeners.create(
                FileObject.PriorityFileChangeListener.class, FileChangeListener.class, this, null
            );

        update();

        if (leader == null) {
            leader = new AbstractFileObject.Invalid(name);
            validFlag = false;
        }
    }

    /** Constructor for root.
    *
    * @param fs the file system
    */
    public MultiFileObject(MultiFileSystem fs) {
        this(fs, null, ""); // NOI18N
    }

    /** File system.
    */
    public FileSystem getLeaderFileSystem() throws FileStateInvalidException {
        return leader.getFileSystem();
    }

    static synchronized void freeAllAttribCaches() {
        fo2AttribCache.clear();
    }
    
    /** Frees cached objects - added with perf.changes  */
    private void freeAttribCache() {
        getAttributeCache().free();
    }
    
    private AttributeCache getAttributeCache() {
        synchronized (MultiFileObject.class) {
            AttributeCache retval = fo2AttribCache.get(this);
            if (retval == null) {
                retval = new AttributeCache();
                fo2AttribCache.put(this, retval);
            }
            return retval;
        }
    }

    private boolean setAttributeOnFO(FileObject fo, String attrToSet, Object value, boolean fire) throws IOException {
        if (fo instanceof AbstractFolder) {
            ((AbstractFolder) fo).setAttribute(attrToSet, value, false);
        } else {
            /** cannot disable firing from underlaying delegate if not AbstractFolder
             * without API change. So I don`t fire events from this method with one
             * exception - root.
             */
            fire = fire && fo.isRoot();
            fo.setAttribute(attrToSet, value);
        }
        return fire;
    }

    /** Updates list of all references.
    */
    private void update() {
        MultiFileSystem mfs = getMultiFileSystem();
        FileSystem[] arr = mfs.getDelegates();

        Set now = (delegates == null) ? Collections.EMPTY_SET : delegates;
        Set del = new HashSet(arr.length * 2);
        Number maxWeight = 0;
        FileObject led = null;

        String name = getPath();
        FileSystem writable = mfs.writableLayer(name);

        for (int i = 0; i < arr.length; i++) {
            if (arr[i] != null) {
                FileObject fo = mfs.findResourceOn(arr[i], name);

                if (fo != null) {
                    del.add(fo);

                    if (!now.remove(fo)) {
                        // now there yet
                        fo.addFileChangeListener(weakL);
                    }

                    if (fo.isValid()) {
                        Number weight = 
                            fo.isRoot() &&
                            !mfs.canHaveRootAttributeOnReadOnlyFS(WEIGHT_ATTRIBUTE)
                            ? 0 : weightOf(fo, writable);
                        if (led == null || weight.doubleValue() > maxWeight.doubleValue()) {
                            led = fo;
                            maxWeight = weight;
                        }
                    }
                }
            }
        }

        Iterator it = now.iterator();

        while (it.hasNext()) {
            FileObject fo = (FileObject) it.next();
            fo.removeFileChangeListener(weakL);
        }

        if (led != null) {
            // otherwise leave the leader to be last file that represented
            // this one
            if (!led.equals(this.leader) && (this.leader != null)) {
                // isValid prevents here from firing events after MFO.delete
                if (isData() && isValid()) {
                    fileChanged0(new FileEvent(this));
                }

                getMultiFileSystem().notifyMigration(this);
            }

            this.leader = led;
        }

        this.delegates = del;
    }

    /** Update all existing subobjects.
    */
    void updateAll() {
        FileSystem mfs = getMultiFileSystem();

        try {
            mfs.beginAtomicAction();

            // enumeration of all existing objects
            Enumeration en = existingSubFiles(true);

            while (en.hasMoreElements()) {
                MultiFileObject mfo = (MultiFileObject) en.nextElement();

                if (mfo.isFolder() && !mfo.isInitialized()) {
                    continue;
                }

                mfo.freeAttribCache();
                mfo.superRefresh(true);
            }
        } finally {
            mfs.finishAtomicAction();
        }
    }

    /** This method was added to achieve firing events that attributes
     * were changed after setDelegates. Events are not fired reliable but this solution was
     * choosed because of performance reasons. Attributes name is set to null - what means
     * that one of attributes was probably changed.
     */
    void updateAllAfterSetDelegates(FileSystem[] oldFileSystems) {
        try {
            getMultiFileSystem().beginAtomicAction();

            FileSystem[] fileSystems = getMultiFileSystem().getDelegates();
            Enumeration en = existingSubFiles(true);

            while (en.hasMoreElements()) {
                MultiFileObject mfo = (MultiFileObject) en.nextElement();

                if (mfo.isFolder() && !mfo.isInitialized()) {
                    continue;
                }

                if (mfo.hasListeners()) {
                    String path = mfo.getPath();
                    FileObject oldLeader = findLeader(oldFileSystems, path);
                    FileObject newLeader = findLeader(fileSystems, path);

                    if ((oldLeader != null) && (newLeader != null) && !oldLeader.equals(newLeader)) {
                        mfo.fileAttributeChanged0(new FileAttributeEvent(mfo, null, null, null));
                    }
                }

                mfo.freeAttribCache();
                mfo.refresh(true);
            }
        } finally {
            getMultiFileSystem().finishAtomicAction();
        }
    }

    private void refreshAfterEvent(FileEvent fe) {
        FileObject fFile = fe.getFile();
        superRefresh(false);

        MultiFileObject mFile = (MultiFileObject) getFileObject(fFile.getName(), fFile.getExt());

        if (mFile != null) {
            mFile.superRefresh(false);
        }
    }

    private void superRefresh(boolean expected) {
        super.refresh(expected);
    }

    private FileObject findLeader(FileSystem[] fs, String path) {
        MultiFileSystem mfs = getMultiFileSystem();

        Number maxWeight = 0;
        FileObject _leader = null;
        FileSystem writable = mfs.writableLayer(path);
        
        for (FileSystem f : fs) {
            if (f == null) {
                continue;
            }
            FileObject fo = mfs.findResourceOn(f, path);

            if (fo != null) {
                Number weight = weightOf(fo, writable);
                if (_leader == null || weight.doubleValue() > maxWeight.doubleValue()) {
                    _leader = fo;
                    maxWeight = weight;
                }
            }
        }

        return _leader;
    }

    private static Number weightOf(FileObject f, FileSystem writable) {
        try {
            if (f.getFileSystem() == writable) {
                return Double.MAX_VALUE;
            }
        } catch (FileStateInvalidException x) {/* ignore */}
        Object weight = f.getAttribute(WEIGHT_ATTRIBUTE);
        if (weight instanceof Number) {
            return (Number) weight;
        } else if (weight == null) {
            return 0;
        } else {
            try {
                Logger.getLogger(MultiFileObject.class.getName()).log(
                        Level.WARNING, "File {0} in {1} has nonnumeric weight {2} of type {3}",
                        new Object[] {f.getPath(), f.getFileSystem(), weight, weight.getClass().getName()});
            } catch (FileStateInvalidException x) {/* ignore */}
            return 0;
        }
    }

    /** Getter for the right file system */
    private MultiFileSystem getMultiFileSystem() {
        return (MultiFileSystem) getFileSystem();
    }

    /** Getter for one of children.
    */
    private MultiFileObject getMultiChild(String name) {
        return (MultiFileObject) getChild(name);
    }

    /** Converts the file to be writable.
    * The file has to be locked!
    *
    * @return file object (new leader) that is writable
    * @exception IOException if the object cannot be writable
    */
    private FileObject writable(boolean copyContents) throws IOException {
        MultiFileSystem fs = getMultiFileSystem();
        FileSystem single = fs.createWritableOn(getPath());

        if (single != leader.getFileSystem()) {
            // if writing to a file that is not on writable fs =>
            // copy it
            if (leader.isFolder()) {
                leader = FileUtil.createFolder(root(single), getPath());
            } else {
                FileObject folder = FileUtil.createFolder(root(single), getParent().getPath());
                if (copyContents) {
                    leader = leader.copy(folder, leader.getName(), leader.getExt());
                } else {
                    leader = folder.createData(leader.getNameExt());
                }
            }

            MfLock l = ((lock == null) ? null : lock.get());

            if (l != null) {
                // update the lock
                l.addLock(leader);
            }
        }

        return leader;
    }

    /** All objects that are beyond this one.
    * @return enumeration of FileObject
    */
    private Enumeration delegates() {
        return getMultiFileSystem().delegates(getPath());
    }

    /** Method that goes upon list of folders and updates its locks. This is used when
    * an object is masked which may lead to creation of folders on a disk.
    *
    * @param fo folder to check
    * @exception IOException if something locks cannot be updated
    */
    private static void updateFoldersLock(FileObject fo)
    throws IOException {
        while (fo != null) {
            MultiFileObject mfo = (MultiFileObject) fo;

            MfLock l = (mfo.lock == null) ? null : mfo.lock.get();

            if (l != null) {
                // the file has been locked => update the lock
                mfo.writable(true);
            }

            fo = fo.getParent();
        }
    }

    //
    // List
    //

    /** Method that allows subclasses to return its children.
    *
    * @return names (name . ext) of subfiles
    */
    protected final String[] list() {
        Properties exclude = new Properties();
        List addList = new LinkedList();
        Set addSet = new HashSet(101);

        Enumeration it = delegates();

        while (it.hasMoreElements()) { // cycle 1

            FileObject folder = it.nextElement();

            if ((folder == null) || !folder.isFolder()) {
                continue;
            }

            FileObject[] arr = folder.getChildren();
            Properties local = null;

            for (int i = 0; i < arr.length; i++) {
                String name = arr[i].getNameExt();

                if (name.endsWith(MultiFileSystem.MASK)) {
                    String basename = name.substring(0, name.length() - MultiFileSystem.MASK.length());

                    // this name should be excluded from next rounds of cycle 1
                    if (local == null) {
                        local = new Properties();
                        local.putAll(exclude);
                    }

                    local.setProperty(basename, basename);

                    if (!getMultiFileSystem().getPropagateMasks()) {
                        // By default, unused mask files are not displayed as file children.
                        // When propagate masks is turned on, though, they should be.
                        // This is useful e.g. when using MFSs as delegates for other MFSs.
                        continue;
                    }
                }

                // lets add this name, if not added yet and is not excluded by
                // previous level
                if (!addSet.contains(name) && (exclude.getProperty(name) == null)) {
                    addSet.add(name);
                    addList.add(name);
                }
            }

            // change the excludes to the new ones produced at this round
            if (local != null) {
                exclude = local;
            }
        }

        if (getMultiFileSystem().getPropagateMasks()) {
            // remove all masked files from the array, even if they were
            // masked on the same level as they were added
            addList.removeAll(exclude.keySet());
        }

        String[] res = addList.toArray(new String[addList.size()]);

        return res;
    }

    /** When refreshing, also update the state of delegates.
    */
    /** [PENDING] expected rename of some refresh method */

    /*protected void internalRefresh (
        String add, String remove, boolean fire, boolean expected, String[] list
     **/
    protected void refresh(String add, String remove, boolean fire, boolean expected) {
        try {
            getFileSystem().beginAtomicAction();

            synchronized (this) {
                update();

                /** [PENDING] expected rename of some refresh method */

                //super.internalRefresh (add, remove, fire, expected, list);
                super.refresh(add, remove, fire, expected);
            }

            validFlag &= leader.isValid();
        } finally {
            getFileSystem().finishAtomicAction();
        }
    }

    /** Method to create a file object for given subfile.
    * @param name of the subfile
    * @return the file object
    */
    protected final AbstractFolder createFile(String name) {
        return new MultiFileObject(getMultiFileSystem(), this, name);
    }

    //
    // Info
    //

    /* Test whether this object is a folder.
    * @return true if the file object is a folder (i.e., can have children)
    */
    public boolean isFolder() {
        return (parent == null) || leader.isFolder();
    }

    /*
    * Get last modification time.
    * @return the date
    */
    public java.util.Date lastModified() {
        return leader.lastModified();
    }

    /* Test whether this object is a data object.
    * This is exclusive with {@link #isFolder}.
    * @return true if the file object represents data (i.e., can be read and written)
    */
    public boolean isData() {
        return leader.isData();
    }

    /* Test whether this FileObject is Virtual.
    * @return true if the file object represents a Virtual File
    */
    public boolean isVirtual() {
        return leader.isVirtual();
    }

    @Deprecated // have to override for compat
    public boolean isReadOnly() {
        MultiFileSystem fs = getMultiFileSystem();

        if (fs.isReadOnly()) {
            return true;
        }

        if (leader.isReadOnly()) {
            // if we can make it writable then nothing
            try {
                FileSystem simple = fs.createWritableOn(getPath());

                return simple == leader.getFileSystem();
            } catch (IOException e) {
                return true;
            }
        }

        return false;
    }

    public boolean canWrite() {
        MultiFileSystem fs = getMultiFileSystem();

        if (fs.isReadOnly()) {
            return false;
        }

        if (!leader.canWrite()) {
            // if we can make it writable then nothing
            try {
                FileSystem simple = fs.createWritableOn(getPath());

                return simple != leader.getFileSystem();
            } catch (IOException e) {
                return false;
            }
        }

        return true;
    }

    /* Get the MIME type of this file.
    * The MIME type identifies the type of the file's contents and should be used in the same way as in the Java
    * Activation Framework or in the {@link java.awt.datatransfer} package.
    * 

* The default implementation calls {@link FileUtil#getMIMEType}. * * @return the MIME type textual representation, e.g. "text/plain" */ public String getMIMEType() { return leader.getMIMEType(); } /* Get the size of the file. * @return the size of the file in bytes or zero if the file does not contain data (does not * exist or is a folder). */ public long getSize() { return leader.getSize(); } /** Get input stream. * @return an input stream to read the contents of this file * @exception FileNotFoundException if the file does not exists, is a folder * rather than a regular file or is invalid */ public InputStream getInputStream() throws java.io.FileNotFoundException { return leader.getInputStream(); } /* Get output stream. * @param lock the lock that belongs to this file (obtained by a call to * {@link #lock}) * @return output stream to overwrite the contents of this file * @exception IOException if an error occures (the file is invalid, etc.) */ public OutputStream getOutputStream(FileLock lock) throws java.io.IOException { MfLock l; FileLock lWritable; FileObject fo; try { getFileSystem().beginAtomicAction(markAtomicAction); synchronized (this) { l = testLock(lock); // this can also change lock in l.lock fo = writable(false); lWritable = l.findLock(fo); } return fo.getOutputStream(lWritable); } finally { getFileSystem().finishAtomicAction(); } } @Override public synchronized boolean isLocked() { return lock != null && lock.get() != null; } /* Lock this file. * @return lock that can be used to perform various modifications on the file * @throws FileAlreadyLockedException if the file is already locked */ public synchronized FileLock lock() throws IOException { if (lock != null) { FileLock f = (FileLock) lock.get(); if (f != null) { FileAlreadyLockedException alreadyLockedException = new FileAlreadyLockedException(getPath()); alreadyLockedException.initCause(lockedBy); throw alreadyLockedException; } } Set set = getMultiFileSystem().createLocksOn(getPath()); MfLock l = new MfLock(leader, delegates(), set); lock = new WeakReference(l); assert (lockedBy = new Throwable("Locked by:")) != null; //NOI18N return l; } /** Tests the lock if it is valid, if not throws exception. * @param l lock to test * @return the mf lock for this file object */ private MfLock testLock(FileLock l) throws java.io.IOException { if (lock == null) { throw new FSException(NbBundle.getMessage(MultiFileObject.class, "EXC_InvalidLock", l, getPath(), getMultiFileSystem().getDisplayName(), lock)); } if (lock.get() != l) { throw new FSException(NbBundle.getMessage(MultiFileObject.class, "EXC_InvalidLock", l, getPath(), getMultiFileSystem().getDisplayName(), lock.get())); } return (MfLock) l; } @Deprecated // have to override for compat public void setImportant(boolean b) { Enumeration en = delegates(); while (en.hasMoreElements()) { FileObject fo = en.nextElement(); fo.setImportant(b); } if (!b) { getMultiFileSystem().markUnimportant(this); } } /** Add one void-wrapping to an object. */ private static final Object voidify(Object o) { if (o == null) { return new VoidValue(0); } else if (o instanceof VoidValue) { VoidValue vv = (VoidValue) o; return new VoidValue(vv.level + 1); } else { return o; } } /** Strip off one void-wrapping from an object. */ private static final Object devoidify(Object o) { if (o instanceof VoidValue) { VoidValue vv = (VoidValue) o; if (vv.level == 0) { return null; } else { return new VoidValue(vv.level - 1); } } else { return o; } } /* Get the file attribute with the specified name. * @param attrName name of the attribute * @return appropriate (serializable) value or null if the attribute is unset (or could not be properly restored for some reason) */ @Override public Object getAttribute(String attrName) { // Performance optimization (avoid calling getPath() too many times): final String path = getPath(); if (path.isEmpty() && attrName.indexOf('\\') >= 0) { return null; } return getAttribute(attrName, path); } /** Special attributes which should not be checked for weight. See RemoveWritablesTest. */ private static final Set SPECIAL_ATTR_NAMES = new HashSet(Arrays.asList(FileObject.REMOVE_WRITABLES_ATTR, WEIGHT_ATTRIBUTE, "java.io.File")); // NOI18N private final Object getAttribute(String attrName, String path) { // Look for attribute in any file system starting at the front. // Additionally, look for attribute in root folder, where // the relative path from the folder to the target file is // prepended to the attribute name, all separated with slashes. // This search scheme permits writable front systems to set file // attributes on deeply buried files in back systems without // actually creating copies of the files or even their parent folders. // [PENDING] consider the effects of mask files String prefixattr = ((path.length() == 0) ? null : (path.replace('/', '\\') + '\\' + attrName)); { /*There was proved that this block enhances performance*/ Object oPerf; FileObject localFo = getAttributeCache().getDelegate(); String cachedAttrName = getAttributeCache().getAttributeName(); if ((localFo != null) && !localFo.equals(this) && cachedAttrName.equals(attrName)) { if (localFo.isRoot() && (prefixattr != null)) { try { FileSystem foFs = localFo.getFileSystem(); if (!foFs.isReadOnly() || getMultiFileSystem().canHaveRootAttributeOnReadOnlyFS(prefixattr)) { localFo = foFs.getRoot(); oPerf = getAttribute(localFo, prefixattr, ""); // NOI18N if (oPerf != null) { return devoidify(oPerf); } } } catch (FileStateInvalidException fiex) { //then continue } } /** There is no chance to cache localFo.getPath(), because every * rename up to it in hierarchy makes this cache invalid. */ oPerf = getAttribute(localFo, attrName, localFo.getPath()); if (oPerf != null) { return devoidify(oPerf); } } } FileSystem[] systems = getMultiFileSystem().getDelegates(); Number maxWeight = 0; Object attr = null; FileSystem writable = getMultiFileSystem().writableLayer(path); boolean revealEntries = isFolder() && "revealEntries".equals(attrName) && writable != null && !writable.isReadOnly(); //NOI18N if (revealEntries) { return collectRevealedFiles(); } // boolean isLoaderAttr = /* DataObject.EA_ASSIGNED_LOADER */ "NetBeansAttrAssignedLoader".equals (attrName); // NOI18N for (int i = 0; i < systems.length; i++) { if (systems[i] == null) { continue; } // Don't check for any assigned loader overrides except for leader & writable systems. // Note that this prevents front layers from overriding default loader! // Could be revisited but in the meantime this is a performance optimization. // if (isLoaderAttr && leaderfs != null && systems[i] != leaderfs && systems[i].isReadOnly ()) { // continue; // } // The normal check: FileObject fo = getMultiFileSystem().findResourceOn(systems[i], path); if (fo != null) { Object o = getAttribute(fo, attrName, fo.getPath()); // Performance tricks: if (o != null) { if (SPECIAL_ATTR_NAMES.contains(attrName)) { return devoidify(o); } Number weight = weightOf(fo, writable); if (attr == null || weight.doubleValue() > maxWeight.doubleValue()) { getAttributeCache().setDelegate(fo); getAttributeCache().setAttributeName(attrName); attr = o; maxWeight = weight; } } } if (prefixattr != null) { if ( !systems[i].isReadOnly() || getMultiFileSystem().canHaveRootAttributeOnReadOnlyFS(prefixattr) ) { fo = systems[i].getRoot(); Object o = getAttribute(fo, prefixattr, ""); // NOI18N if (o != null) { Number weight = weightOf(fo, writable); if (attr == null || weight.doubleValue() > maxWeight.doubleValue()) { getAttributeCache().setDelegate(fo); getAttributeCache().setAttributeName(attrName); attr = o; maxWeight = weight; } } } } } return devoidify(attr); } private static boolean sameFullName(FileObject f1, FileObject f2) { while (f1 != null && f2 != null) { if (!f1.getNameExt().equals(f2.getNameExt())) { return false; } f1 = f1.getParent(); f2 = f2.getParent(); } return f1 == null && f2 == null; } private Object getAttribute(FileObject fo, String attrName, String path) { Object o; FileObject previousFO = attrAskedFileObject.get(); if (previousFO == null || !sameFullName(previousFO, this)) { attrAskedFileObject.set(this); } try { if (fo instanceof MultiFileObject) { o = ((MultiFileObject) fo).getAttribute(attrName, path); } else if (fo instanceof AbstractFileObject) { o = ((AbstractFileObject) fo).getAttribute(attrName, path); } else { o = fo.getAttribute(attrName); } } finally { attrAskedFileObject.set(previousFO); } return o; } /* Set the file attribute with the specified name. * @param attrName name of the attribute * @param value new value or null to clear the attribute. Must be serializable, although particular file systems may or may not use serialization to store attribute values. * @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}. */ public void setAttribute(String attrName, Object value) throws IOException { setAttribute(attrName, value, true); //NOI18N } /* helper method for MFO.setAttribute. MFO can disable firing from underlaying * layers. Should be reviewed in 3.4 or later *@see MultiFileObject#setAttribute*/ void setAttribute(String attrName, Object value, boolean fire) throws IOException { // Similar to getAttribute. Here we use createWritableOn to decide which fs // the attribute should be stored on; it is stored on the actual file if // possible, if not then the lowest containing folder. String path = getPath(); FileSystem fs = getMultiFileSystem().createWritableOn(path); FileObject fo = getMultiFileSystem().findResourceOn(fs, path); Object oldValue = null; String attrToSet = attrName; if (fire) { oldValue = getAttribute(attrName); if (oldValue == value) { return; } } if (fo == null) { fo = fs.getRoot(); attrToSet = path.replace('/', '\\') + '\\' + attrName; } getAttributeCache().setDelegate(fo); getAttributeCache().setAttributeName(attrToSet); if (value == null) { fire = setAttributeOnFO(fo, attrToSet, null, fire); if (fo.getAttribute(attrToSet) != null) { fire = setAttributeOnFO(fo, attrToSet, voidify(null), fire); } } else { fire = setAttributeOnFO(fo, attrToSet, voidify(value), fire); } // fire changes for original attribute name even if the attr is actually // stored in the root FO if FO instanceof AbstractFolder (that has supressed firing). /* [PENDING] only this MultiFileObject should fire event and it`s delegate * should not fire event (nevertheless this delegate is physically used * to write given attribute - only implementation detail - * nobody should rely on current implementation). **/ if (fire && (oldValue != value) && hasAtLeastOneListeners()) { fileAttributeChanged0(new FileAttributeEvent(this, attrName, oldValue, value)); } } /* Get all file attribute names for this file. * @return enumeration of keys (as strings) */ public Enumeration getAttributes() { return getAttributes(getPath()); } private final Enumeration getAttributes(String path) { Set s = new HashSet(); FileSystem[] systems = getMultiFileSystem().getDelegates(); final boolean empty = path.length() == 0; // [PENDING] will not remove from the enumeration voided-out attributes // (though this is probably not actually necessary) String prefix = empty ? null : (path.replace('/', '\\') + '\\'); for (int i = 0; i < systems.length; i++) { if (systems[i] == null) { continue; } FileObject fo = getMultiFileSystem().findResourceOn(systems[i], path); if (fo != null) { Enumeration e = fo.getAttributes(); while (e.hasMoreElements()) { String attr = e.nextElement(); if (empty && attr.indexOf('\\') >= 0) { continue; } s.add(attr); } } if (prefix != null) { fo = systems[i].getRoot(); Enumeration e; if (fo instanceof MultiFileObject) { e = ((MultiFileObject) fo).getAttributes(""); // NOI18N } else if (fo instanceof AbstractFileObject) { e = ((AbstractFileObject) fo).getAttributes(""); // NOI18N } else { e = fo.getAttributes(); } while (e.hasMoreElements()) { String attr = e.nextElement(); if (attr.startsWith(prefix) && (attr.substring(prefix.length()).indexOf('\\') == -1)) { s.add(attr.substring(prefix.length())); } } } } return Collections.enumeration(s); } /* Create a new folder below this one with the specified name. Fires * fileCreated event. * * @param name the name of folder to create (without extension) * @return the new folder * @exception IOException if the folder cannot be created (e.g. already exists) */ @Override public FileObject createFolder(String name) throws IOException { MultiFileObject fo; try { getFileSystem().beginAtomicAction(); synchronized (this) { MultiFileSystem fs = getMultiFileSystem(); if (fs.isReadOnly()) { throw new FSException(NbBundle.getMessage(MultiFileObject.class, "EXC_FSisRO", fs.getDisplayName())); } if (isReadOnly()) { IOException ex = new IOException("Read only: " + leader + " delegates: " + delegates); // NOI18N Exceptions.attachLocalizedMessage(ex, NbBundle.getMessage(MultiFileObject.class, "EXC_FisRO", name, fs.getDisplayName())); throw ex; } String fullName = getPath() + PATH_SEP + name; if (!isFolder()) { throw new FSException(NbBundle.getMessage(MultiFileObject.class, "EXC_FoNotFolder", name, getPath(), fs.getDisplayName())); } if (this.getFileObject(name) != null) { throw new FSException(NbBundle.getMessage(MultiFileObject.class, "EXC_FolderAlreadyExist", name, fs.getDisplayName(), getPath())); } FileSystem simple = fs.createWritableOn(fullName); // create FileUtil.createFolder(root(simple), fullName); // try to unmask if necessary getMultiFileSystem().unmaskFileOnAll(simple, fullName); /** [PENDING] expected rename of some refresh method */ //internalRefresh (name, null, true, false,null); refresh(name, null, true, false); fo = getMultiChild(name); if (fo == null) { // system error throw new FileStateInvalidException(NbBundle.getMessage(MultiFileObject.class, "EXC_ApplicationCreateError", getPath(), name)); } FileObject[] chlds = fo.getChildren(); for (int i = 0; i < chlds.length; i++) { getMultiFileSystem().maskFile(simple, chlds[i].getPath()); } if (hasListeners()) { fileCreated0(new FileEvent(this, fo), false); } } } finally { getFileSystem().finishAtomicAction(); } return fo; } /* Create new data file in this folder with the specified name. Fires * fileCreated event. * * @param name the name of data object to create (should not contain a period) * @param ext the extension of the file (or null or "") * * @return the new data file object * @exception IOException if the file cannot be created (e.g. already exists) */ public FileObject createData(String name, String ext) throws IOException { MultiFileObject fo; try { getFileSystem().beginAtomicAction(); synchronized (this) { MultiFileSystem fs = getMultiFileSystem(); if (fs.isReadOnly()) { throw new FSException(NbBundle.getMessage(MultiFileObject.class, "EXC_FSisRO", fs.getDisplayName())); } if (isReadOnly()) { throw new FSException(NbBundle.getMessage(MultiFileObject.class, "EXC_FisRO", name, fs.getDisplayName())); } String n = "".equals(ext) ? name : (name + EXT_SEP + ext); // NOI18N if (!isFolder()) { throw new FSException(NbBundle.getMessage(MultiFileObject.class, "EXC_FoNotFolder", n, getPath(), fs.getDisplayName())); } if (this.getFileObject(name, ext) != null) { throw new FSException(NbBundle.getMessage(MultiFileObject.class, "EXC_DataAlreadyExist", n, fs.getDisplayName(), getPath())); } String fullName = getPath() + PATH_SEP + n; FileSystem simple = fs.createWritableOn(fullName); // create FileUtil.createData(root(simple), fullName); // try to unmask if necessary getMultiFileSystem().unmaskFileOnAll(simple, fullName); /** [PENDING] expected rename of some refresh method */ //internalRefresh (n, null, true, false,null); refresh(n, null, true, false); fo = getMultiChild(n); if (fo == null) { // system error throw new FileStateInvalidException(NbBundle.getMessage( MultiFileObject.class, "EXC_ApplicationCreateErrorMulti", //NOI18N getPath(), n, simple, simple.getRoot().getFileObject(fullName))); } if (hasListeners()) { fileCreated0(new FileEvent(this, fo), true); } } } finally { getFileSystem().finishAtomicAction(); } return fo; } /* Renames this file (or folder). * Both the new basename and new extension should be specified. *

* Note that using this call, it is currently only possible to rename within * a parent folder, and not to do moves across folders. * Conversely, implementing file systems need only implement "simple" renames. * If you wish to move a file across folders, you should call {@link FileUtil#moveFile}. * @param lock File must be locked before renaming. * @param name new basename of file * @param ext new extension of file (ignored for folders) */ public void rename(FileLock lock, String name, String ext) throws IOException { MultiFileSystem fs = getMultiFileSystem(); if (parent == null) { throw new FSException(NbBundle.getMessage(MultiFileObject.class, "EXC_CannotRenameRoot", fs.getDisplayName())); } try { getFileSystem().beginAtomicAction(); synchronized (parent) { // synchronize on your folder MfLock l = testLock(lock); String newFullName = parent.getPath() + PATH_SEP + name; if (isData() && ext != null && ext.trim().length() > 0) { newFullName += (EXT_SEP + ext); } String oldFullName = getPath(); if (isReadOnly()) { throw new FSException(NbBundle.getMessage(MultiFileObject.class, "EXC_CannotRenameFromTo", getPath(), getMultiFileSystem().getDisplayName(), newFullName)); } if (getFileSystem().isReadOnly()) { throw new FSException(NbBundle.getMessage(MultiFileObject.class, "EXC_FSisRO", getMultiFileSystem().getDisplayName())); } String on = getName(); String oe = getExt(); //!!! getMultiFileSystem ().change.rename (oldFullName, newFullName); FileSystem single = fs.createWritableOnForRename(oldFullName, newFullName); if (single == leader.getFileSystem()) { // delete the file if we can on the selected // system leader.rename(l.findLock(leader), name, ext); getMultiFileSystem().unmaskFileOnAll(single, newFullName); copyContent(this, leader); } else { // rename file that is on different file system // means to copy it FileObject previousLeader = leader; if (isData()) { // data FileObject folder = FileUtil.createFolder(root(single), getParent().getPath()); leader = leader.copy(folder, name, ext); copyAttrs(this, leader); } else { // folder FileObject fo = FileUtil.createFolder(root(single), newFullName); copyContent(this, fo); leader = fo; this.name = name; // must be done before update update(); } // releases lock for previousLeader and aquiares // new for leader l.changeLocks(previousLeader, leader); } if (getMultiFileSystem().delegates(oldFullName).hasMoreElements()) { // if there is older version of the file // then we have to mask it getMultiFileSystem().maskFile(single, oldFullName); updateFoldersLock(getParent()); } if (isData()) { name = name + EXT_SEP + ext; } String oldName = this.name; this.name = name; /* System.out.println ("Resulting file is: " + getPath ()); System.out.println ("Bedw file is: " + newFullName); System.out.println ("Name: " + name); System.out.println ("Old : " + oldName); */ /** [PENDING] expected to delete*/ parent.refresh(name, oldName); //!!! getMultiFileSystem ().attr.renameAttributes (oldFullName, newFullName); if (hasAtLeastOneListeners()) { fileRenamed0(new FileRenameEvent(this, on, oe)); } } } finally { getFileSystem().finishAtomicAction(); } } /* Delete this file. If the file is a folder and it is not empty then * all of its contents are also recursively deleted. * * @param lock the lock obtained by a call to {@link #lock} * @exception IOException if the file could not be deleted */ void handleDelete(FileLock lock) throws IOException { if (parent == null) { throw new FSException(NbBundle.getMessage(MultiFileObject.class, "EXC_CannotDeleteRoot", getMultiFileSystem().getDisplayName())); } MultiFileSystem fs = getMultiFileSystem(); try { getFileSystem().beginAtomicAction(); synchronized (parent) { String fullName = getPath(); FileSystem single = fs.createWritableOn(fullName); if (needsMask(lock, true)) { getMultiFileSystem().maskFile(single, fullName); updateFoldersLock(getParent()); } String n = name; validFlag = false; /** [PENDING] expected rename of some refresh method */ //parent.internalRefresh (null, n, true, false, null); parent.refresh(null, n, true, false); if (hasAtLeastOneListeners()) { fileDeleted0(new FileEvent(this)); } } } finally { getFileSystem().finishAtomicAction(); } } // // Transfer // /** Copies this file. This allows the filesystem to perform any additional * operation associated with the copy. But the default implementation is simple * copy of the file and its attributes * * @param target target folder to move this file to * @param name new basename of file * @param ext new extension of file (ignored for folders) * @return the newly created file object representing the moved file */ public FileObject copy(FileObject target, String name, String ext) throws IOException { if (FileUtil.isParentOf(this, target)) { throw new FSException(NbBundle.getMessage(FileObject.class, "EXC_CopyChild", this, target)); // NOI18N } return leader.copy(target, name, ext); } /** Moves this file. This allows the filesystem to perform any additional * operation associated with the move. But the default implementation is encapsulated * as copy and delete. * * @param lock File must be locked before renaming. * @param target target folder to move this file to * @param name new basename of file * @param ext new extension of file (ignored for folders) * @return the newly created file object representing the moved file */ public FileObject move(FileLock lock, FileObject target, String name, String ext) throws IOException { if (FileUtil.isParentOf(this, target)) { throw new FSException(NbBundle.getMessage(FileObject.class, "EXC_MoveChild", this, target)); // NOI18N } MultiFileSystem fs = getMultiFileSystem(); try { fs.beginAtomicAction(); if (parent == null) { throw new FSException(NbBundle.getMessage(MultiFileObject.class, "EXC_CannotDeleteRoot", fs.getDisplayName())); } MfLock lck = testLock(lock); FileLock l = lck.findLock(leader); FileSystem simple = fs.createWritableOn(getPath()); if (fs.isReadOnly()) { throw new FSException(NbBundle.getMessage(MultiFileObject.class, "EXC_FSisRO", fs.getDisplayName())); } if ((l == null) && (leader.getFileSystem() != simple)) { leader = writable(true); l = lck.findLock(leader); } if (needsMask(lock, false)) { getMultiFileSystem().maskFile(simple, getPath()); updateFoldersLock(getParent()); } FileObject ret = leader.move(l, target, name, ext); FileObjectLkp.reassign(this, ret); return ret; } finally { fs.finishAtomicAction(); } } /* Refresh the contents of a folder. Rescans the list of children names. */ public final void refresh(boolean expected) { if (!isInitialized() && isFolder()) { return; } Enumeration en = delegates(); while (en.hasMoreElements()) { FileObject fo = en.nextElement(); fo.refresh(expected); } super.refresh(expected); } // // Listeners // /** Fired when a new folder is created. This action can only be * listened to in folders containing the created folder up to the root of * file system. * * @param fe the event describing context where action has taken place */ public void fileFolderCreated(FileEvent fe) { /*One of underlaing layers notifies that new folder was created. And this folder may have any other childern anywhere deep in hierarchy and then must be updated and refreshed deep down*/ updateAll(); } /** Fired when a new file is created. This action can only be * listened in folders containing the created file up to the root of * file system. * * @param fe the event describing context where action has taken place */ public void fileDataCreated(FileEvent fe) { refreshAfterEvent(fe); } /** Fired when a file is changed. * @param fe the event describing context where action has taken place */ public void fileChanged(FileEvent fe) { FileObject changedFile = this; if (fe.getSource().equals(leader) && hasAtLeastOneListeners() && !fe.firedFrom(markAtomicAction)) { /**There should not dissapear information about source and changed file*/ if (!fe.getFile().equals(fe.getSource())) { changedFile = getFileObject(fe.getFile().getName(), fe.getFile().getExt()); } /**fileChanged1 - should not fire event for this.getParent (). * I think that already bottom layer forked event.*/ /** [PENDING] fix of #16926, #16895. But there should be investigated * why this MFO doesn`t know about child ?*/ if (changedFile != null) { fileChanged1(new FileEvent(this, changedFile, fe.getTime())); } } } /** Fired when a file is deleted. * @param fe the event describing context where action has taken place */ public void fileDeleted(FileEvent fe) { if (fe.getFile().isFolder()) { updateAll(); } else { refreshAfterEvent(fe); } } /** Fired when a file is renamed. * @param fe the event describing context where action has taken place * and the original name and extension. */ public void fileRenamed(FileRenameEvent fe) { updateAll(); } /** Fired when a file attribute is changed. * @param fe the event describing context where action has taken place, * the name of attribute and the old and new values. */ public void fileAttributeChanged(FileAttributeEvent fe) { // [PENDING] this is not at all sufficient to notify every change in attributes. // One, parent dirs of front filesystems can now hold attributes for missing // files. Two, non-leader files can have attributes too which are merged in. // In principle all files/folders whose path is a prefix of this path on all // contained filesystems should be listened to for attribute change events. if (!hasAtLeastOneListeners() || (leader == null)) { return; } /** If change is not fired from leader then leader may mask this attribute * and then event should not be fired */ if (!fe.getFile().equals(leader) && (fe.getName() != null) && (leader.getAttribute(fe.getName()) != null)) { return; } /** If change is not fired from leader then another delegate may mask this attribute * and then event should not be fired. */ if ( !fe.getFile().equals(leader) && (fe.getNewValue() != null) && (fe.getName() != null) && !fe.getNewValue().equals(getAttribute(fe.getName())) ) { return; } final FileAttributeEvent ev = new FileAttributeEvent(this, fe.getName(), fe.getOldValue(), fe.getNewValue()); try { ev.inheritPostNotify(fe); fileAttributeChanged0(ev); } finally { ev.setPostNotify(null); } } /** Copies content of one folder into another. * @param source source folder * @param target target folder * @exception IOException if it fails */ private static void copyContent(FileObject source, FileObject target) throws IOException { FileObject[] srcArr = source.getChildren(); copyAttrs(source, target); //added for (int i = 0; i < srcArr.length; i++) { FileObject child = srcArr[i]; if (MultiFileSystem.isMaskFile(child)) { continue; } if (target.getFileObject(child.getName(), child.getExt()) == null) { if (child.isData()) { FileObject fo = FileUtil.copyFile(child, target, child.getName(), child.getExt()); if (fo != null) { copyAttrs(child, fo); } } else { FileObject targetChild = target.createFolder(child.getName()); copyContent(child, targetChild); } } } } /** Copies attributes of one FileObject into another. * @param source source folder or file * @param target target folder or file * @exception IOException if it fails */ private static void copyAttrs(FileObject source, FileObject target) { Enumeration en = source.getAttributes(); while (en.hasMoreElements()) { String key = en.nextElement(); Object value = source.getAttribute(key); try { target.setAttribute(key, value); } catch (IOException ie) { } } } /** * auxiliary method that returns true if mask is needed and deletes all delegates * on writable layers if deleteDelegates is true. * @param lock * @param deleteDelegates if true all delegates on writable layers will be deleted * @throws IOException is thrown if lock is not valid. * @return true if mask is necessary*/ private boolean needsMask(FileLock lock, boolean deleteDelegates) throws IOException { MfLock lck = testLock(lock); Enumeration e = getMultiFileSystem().delegates(getPath()); boolean needsMask = false; while (e.hasMoreElements()) { FileObject fo = e.nextElement(); FileLock lockForFo = lck.findLock(fo); if (lockForFo == null) { // we will need to create mask needsMask = true; } else { if (deleteDelegates) { fo.delete(lockForFo); } } } return needsMask; } /** Finds a root for given file system. It also counts with * redefined method findResourceOn. * * @param fs the filesystem to seach on * @return the root on the fs */ private FileObject root(FileSystem fs) { return getMultiFileSystem().findResourceOn(fs, ""); // NOI18N } final FileObject getLeader() { return leader; } /** Special value used to indicate null masking of an attribute. * The level is zero in simple cases; incremented when one MFS asks * another to store a VoidValue. */ static final class VoidValue implements Externalizable { // Externalizable: private static final long serialVersionUID = -2743645909916238684L; int level; VoidValue(int level) { this.level = level; } public VoidValue() { } public String toString() { return "org.openide.filesystems.MultiFileObject.VoidValue#" + level; // NOI18N } public void writeExternal(ObjectOutput out) throws IOException { out.writeInt(level); } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { level = in.readInt(); } } /** Implementation of lock for abstract files. */ private class MfLock extends FileLock { /** lock for all files (map from FileObject to FileLock) */ private Map map = new HashMap(11); /** * @param leader leader file object * @param delegates all delegates for this file object * @param systems a set of filesystems we should create lock on * @exception IOException if the lock cannot be obtained */ public MfLock(FileObject leader, Enumeration delegates, Set systems) throws IOException { while (delegates.hasMoreElements()) { FileObject fo = delegates.nextElement(); if (systems.contains(fo.getFileSystem())) { FileLock l = fo.lock(); map.put(fo, l); } } /* JST: Commented out because cause problems when locking a file that is only of filesystems that are readonly (for example when file is only on shared installation and locks are allowed only on local => then there is nothing to lock. if (map.isEmpty()) { // trouble, the filesystem returned wrong set of systems to lock // on => warn about possibly wrong implementation // to correct the problem override appropriatelly createLocksOn // method of MultiFileSystem throw new IOException ("Writable file is not on filesystem from createLocksOn"); // NOI18N } */ } /** Finds lock for given file object. * @param fo one of delegates * @return the lock or null */ public FileLock findLock(FileObject fo) { return map.get(fo); } /** Adds another lock into this lock. * @param fo file object to keep the lock for * @exception IOException if the lock cannot be obtained */ public void addLock(FileObject fo) throws IOException { map.put(fo, fo.lock()); } /** Releases lock for old file object and * takes new one from newFo */ public void changeLocks(FileObject old, FileObject n) throws IOException { FileLock l = map.remove(old); if (l != null) { l.releaseLock(); } addLock(n); } public void releaseLock() { if (this.isValid()) { super.releaseLock(); releaseLockForDelegates(); if (getCurrentMfLock() == this) { // clears the reference to this lock from the file object MultiFileObject.this.lock = null; } } } private FileLock getCurrentMfLock() { FileLock currentLock = null; ; if (lock != null) { currentLock = (FileLock) lock.get(); } return currentLock; } private void releaseLockForDelegates() { Iterator it = map.values().iterator(); while (it.hasNext()) { FileLock l = (FileLock) it.next(); l.releaseLock(); } map.clear(); } // for better debugging public String toString() { return super.toString() + " for " + MultiFileObject.this + " valid=" + isValid(); // NOI18N } } // MfLock private static class AttributeCache { private FileObject delegate; private String attribName = ""; // NOI18N private void free() { delegate = null; attribName = ""; // NOI18N } private void setDelegate(FileObject delegate) { this.delegate = delegate; } private void setAttributeName(String attribName) { this.attribName = attribName; } private FileObject getDelegate() { return delegate; } private String getAttributeName() { return attribName; } } /** * Revealed FileObject represents a FileObject which has been either overriden by * the writable layer, or masked out by the layer. It delegates to the original object, * and its Callable mixing interface allows to revert the change by removing the * override file. */ private final class RevealedFileObject extends FileObject implements Callable { /** * The masking file. It may be either _hidden file, or a replacing * file on the writable layer */ private final FileObject maskFile; /** * Original file on lower layer */ private final FileObject delegate; public RevealedFileObject(FileObject parent, FileObject delegate) { this.maskFile = parent; this.delegate = delegate; } @Override public Object getAttribute(String attrName) { return delegate.getAttribute(attrName); } /** * Return the revived file from the MFS * @return the revived file, if the operation succeeds. * @throws IOException from the deletion of the masking file. */ @Override public FileObject call() throws IOException { if (maskFile.isValid()) { maskFile.delete(); } return MultiFileObject.this.getFileObject(maskFile.getNameExt()); } @Override public String getName() { return delegate.getName(); } @Override public String getExt() { return delegate.getExt(); } @Override public void rename(FileLock lock, String name, String ext) throws IOException { throw new IOException("Unsupported oepration"); // NOI18N } @Override public FileObject copy(FileObject target, String name, String ext) throws IOException { return delegate.copy(target, name, ext); } @Override public FileObject move(FileLock lock, FileObject target, String name, String ext) throws IOException { throw new IOException("Unsupported oepration"); // NOI18N } @Override public String toString() { return delegate.toString(); } @Override public String getPath() { return delegate.getPath(); } @Override public String getPackageNameExt(char separatorChar, char extSepChar) { return delegate.getPackageNameExt(separatorChar, extSepChar); } @Override public String getPackageName(char separatorChar) { return delegate.getPackageName(separatorChar); } @Override public String getNameExt() { return delegate.getNameExt(); } @Override public FileSystem getFileSystem() throws FileStateInvalidException { return MultiFileObject.this.getFileSystem(); } @Override public FileObject getParent() { return MultiFileObject.this; } @Override public boolean isFolder() { return delegate.isFolder(); } @Override public Date lastModified() { return delegate.lastModified(); } @Override public boolean isRoot() { return delegate.isRoot(); } @Override public boolean isData() { return delegate.isData(); } @Override public boolean isValid() { return false; } @Override public boolean existsExt(String ext) { return delegate.existsExt(ext); } @Override public void delete(FileLock lock) throws IOException { throw new IOException("Unsupported oepration"); //NOI18N } @Override public Lookup getLookup() { return delegate.getLookup(); } @Override public void setAttribute(String attrName, Object value) throws IOException { throw new IOException("Unsupported oepration"); //NOI18N } @Override public Enumeration getAttributes() { return delegate.getAttributes(); } @Override boolean isHasExtOverride() { return delegate.isHasExtOverride(); } @Override boolean hasExtOverride(String ext) { return delegate.hasExtOverride(ext); } @Override public void addFileChangeListener(FileChangeListener fcl) { delegate.addFileChangeListener(fcl); } @Override public void removeFileChangeListener(FileChangeListener fcl) { delegate.removeFileChangeListener(fcl); } @Override public void addRecursiveListener(FileChangeListener fcl) { delegate.addRecursiveListener(fcl); } @Override public void removeRecursiveListener(FileChangeListener fcl) { delegate.removeRecursiveListener(fcl); } @Override protected void fireFileDataCreatedEvent(Enumeration en, FileEvent fe) { } @Override protected void fireFileFolderCreatedEvent(Enumeration en, FileEvent fe) { } @Override protected void fireFileChangedEvent(Enumeration en, FileEvent fe) { } @Override protected void fireFileDeletedEvent(Enumeration en, FileEvent fe) { } @Override protected void fireFileAttributeChangedEvent(Enumeration en, FileAttributeEvent fe) { } @Override protected void fireFileRenamedEvent(Enumeration en, FileRenameEvent fe) { } @Override public String getMIMEType() { return delegate.getMIMEType(); } @Override public String getMIMEType(String... withinMIMETypes) { return delegate.getMIMEType(withinMIMETypes); } @Override public long getSize() { return delegate.getSize(); } @Override public InputStream getInputStream() throws FileNotFoundException { return delegate.getInputStream(); } @Override public byte[] asBytes() throws IOException { return delegate.asBytes(); } @Override public String asText(String encoding) throws IOException { return delegate.asText(encoding); } @Override public String asText() throws IOException { return delegate.asText(); } @Override public List asLines() throws IOException { return delegate.asLines(); } @Override public List asLines(String encoding) throws IOException { return delegate.asLines(encoding); } @Override public OutputStream getOutputStream(FileLock lock) throws IOException { throw new IOException("Unsupported oepration"); //NOI18N } @Override public FileLock lock() throws IOException { throw new IOException("Unsupported oepration"); //NOI18N } @Override public boolean isLocked() { return delegate.isLocked(); } @Override public void setImportant(boolean b) { } @Override public FileObject[] getChildren() { return new FileObject[0]; } @Override public Enumeration getChildren(boolean rec) { return Enumerations.empty(); } @Override public Enumeration getFolders(boolean rec) { return Enumerations.empty(); } @Override public Enumeration getData(boolean rec) { return Enumerations.empty(); } @Override public FileObject getFileObject(String name, String ext) { if (maskFile.isFolder()) { return maskFile.getFileObject(name, ext); } else { return null; } } @Override public FileObject getFileObject(String relativePath) { if (maskFile.isFolder()) { return maskFile.getFileObject(relativePath); } else { return null; } } @Override public FileObject createFolder(String name) throws IOException { throw new IOException("Unsupported oepration"); //NOI18N } @Override public FileObject createData(String name, String ext) throws IOException { throw new IOException("Unsupported oepration"); //NOI18N } @Override public FileObject createData(String name) throws IOException { throw new IOException("Unsupported oepration"); //NOI18N } @Override public OutputStream createAndOpen(String name) throws IOException { throw new IOException("Unsupported oepration"); //NOI18N } @Override public boolean isReadOnly() { return delegate.isReadOnly(); } @Override public boolean canWrite() { return false; } @Override public boolean canRead() { return delegate.canRead(); } @Override public void refresh(boolean expected) { delegate.refresh(expected); } @Override public void refresh() { delegate.refresh(); } @Override public boolean isVirtual() { return delegate.isVirtual(); } public int hashCode() { return 0xaa ^ delegate.hashCode() << 3; } public boolean equals(Object o) { if (!(o instanceof RevealedFileObject)) { return false; } RevealedFileObject other = (RevealedFileObject)o; return other.delegate.equals(delegate) && other.maskFile.equals(maskFile); } } /** * The returned collection contains hidden or removed FileObjects, from * the lower layers. The returned FileObjects are 'special' in that DataObjects * cannot be created from them, and they cannot be changed. All mutation methods * will throw IOException - unsupported operation. */ private Collection collectRevealedFiles() { String path = getPath(); FileSystem[] systems = getMultiFileSystem().getDelegates(); FileSystem writable = getMultiFileSystem().writableLayer(path); if (writable == null) { return Collections.emptyList(); } FileObject writableFolder = writable.findResource(path); // no overrides as the containing folder does not exist on the writable fs. if (writableFolder == null) { return Collections.emptyList(); } FileObject[] ch = writableFolder.getChildren(); int sl = systems.length; Collection result = null; String parentPath = writableFolder.getPath(); for (FileObject mask : ch) { String fn = mask.getNameExt(); if (fn.endsWith(MultiFileSystem.MASK)) { fn = fn.substring(0, fn.length() - MultiFileSystem.MASK.length()); } String p = parentPath + "/" + fn; // NOI18N for (int i = 0; i < sl; i++) { if (writable == systems[i]) { continue; } FileObject hiddenFo = systems[i].findResource(p); if (hiddenFo != null) { if (result == null) { result = new ArrayList(ch.length); } result.add(new RevealedFileObject(mask, hiddenFo)); break; } } } if (result == null) { return Collections.emptyList(); } else { return result; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy