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


package org.netbeans.modules.masterfs;

import java.io.File;
import org.netbeans.modules.masterfs.filebasedfs.FileBasedFileSystem;
import org.netbeans.modules.masterfs.filebasedfs.fileobjects.BaseFileObj;
import org.netbeans.modules.masterfs.filebasedfs.naming.FileNaming;

import org.openide.ErrorManager;
import org.openide.filesystems.*;
import org.openide.util.Utilities;
import org.netbeans.modules.masterfs.filebasedfs.fileobjects.ReplaceForSerialization;

import javax.swing.event.EventListenerList;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
import org.netbeans.modules.masterfs.filebasedfs.utils.FileInfo;
import org.netbeans.modules.masterfs.providers.ProvidedExtensions;

/**
 * Implements FileObject, hosts delegate and mostly uses it whenever possible.
 *
 * @author  Radek Matous
 */
final class MasterFileObject extends BasedOnResourcePath {
    /** generated Serialized Version UID  */
    static final long serialVersionUID = -1244651324997356809L;
    transient private final Delegate delegate;
    /** listeners - registered to listen on this FileObject*/
    transient private EventListenerList listeners;

    private static final FileSystem.AtomicAction referenceAction = new MasterFileObject.AtomicAction (null);
    transient private boolean isFolder;
    

    /** Don`t call this constructor directly, use rather getOrCreate*/
    MasterFileObject(ResourcePath resourceName, FileObject deleg) {
        super(resourceName);
        if (deleg != null) {
            isFolder = deleg.isFolder();
        } else {
            File f = resourceName.getFile();
            if (f != null) {
                isFolder = f.isDirectory();
            } else {
                isFolder = true;
            }
        }
        delegate = new Delegate(deleg, new FileChangeListenerImpl(), this);
    }

    /**
     * Implements abstract FileObject.lastModified()
     */
    public java.util.Date lastModified() {
        FileObject deleg = getValidOrInvalid(getDelegate().getPrefered());
        return deleg.lastModified();
    }

    /**
     * Implements abstract FileObject.isValid()
     */
    public boolean isValid() {
        FileObject deleg = getValidOrInvalid(getDelegate().getPrefered());
        File f = (deleg.isVirtual()) ? null: getResource().getFile();                
        return (f != null) ?  (deleg.isValid() && (f.exists())) : deleg.isValid();
    }

    /**
     * Implements abstract FileObject.getAttribute(String attrName)
     */
    public Object getAttribute(String attrName) {
        if (attrName.equals("FileSystem.rootPath")) {
            return "";//NOI18N
        }
        
        if (attrName.equals("java.io.File")) {
            File file = getResource().getFile();
            if (file != null && file.exists()) {
                return file;
            }
        }
        
        
        FileObject deleg = getValidOrInvalid(getDelegate().getPrefered());
        Object attribute = deleg.getAttribute(attrName);
        if (attribute == null) {
            FileObject secDeleg = getDelegate().get();
            if (secDeleg != null && secDeleg.isValid() && secDeleg.isRoot()) {
                attribute = secDeleg.getAttribute(attrName);                
            }
        }
        return attribute;
    }

    /**
     * Implements abstract FileObject.setAttribute(String attrName, Object value)
     */
    public void setAttribute(final String attrName, final Object value) throws IOException {
        FileObject deleg = getValidOrInvalid(getDelegate().getPrefered());
        deleg.setAttribute(attrName, value);
    }

    /**
     * Implements abstract FileObject.getAttributes()
     */
    public Enumeration getAttributes() {
        FileObject deleg = getValidOrInvalid(getDelegate().getPrefered());
        return deleg.getAttributes();
    }

    /**
     * Implements abstract FileObject.getSize()
     */
    public long getSize() {
        FileObject deleg = getValidOrInvalid(getDelegate().getPrefered());
        return deleg.getSize();
    }

    /**
     * Implements abstract FileObject.getInputStream()
     */
    public InputStream getInputStream() throws java.io.FileNotFoundException {
        FileObject deleg = getValidOrInvalid(getDelegate().getPrefered());
        return deleg.getInputStream();
    }

    /**
     * Implements abstract FileObject.getOutputStream(FileLock lock)
     */
    public OutputStream getOutputStream(FileLock lock) throws java.io.IOException {
        FileObject deleg = getValidOrInvalid(getDelegate().getPrefered());
        FileLock lck = (deleg.isValid()) ? Delegate.getLockForDelegate(lock, deleg) : null;

        return deleg.getOutputStream(lck);
    }

    /**
     * Implements abstract FileObject.lock()
     */
    public FileLock lock() throws IOException {
        return getDelegate().lock();
    }

    /**
     * Implements abstract FileObject.setImportant(boolean b)
     */
    public void setImportant(final boolean b) {
        FileObject deleg = getValidOrInvalid(getDelegate().get());
        deleg.setImportant(b);
    }


    /**
     * Implements abstract FileObject.isReadOnly()
     */
    public boolean isReadOnly() {
        FileObject deleg = getValidOrInvalid(getDelegate().getPrefered());
        return deleg.isReadOnly();
    }

    /**
     * Implements FileObject.getMIMEType()
     */
    public String getMIMEType() {
        FileObject deleg = getValidOrInvalid(getDelegate().get());
        return deleg.getMIMEType();
    }

    /**
     * Implements FileObject.existsExt(String extI)
     */
    public boolean existsExt(String ext) {
        FileObject deleg = getValidOrInvalid(getDelegate().getPrefered());
        return deleg.existsExt(ext);
    }

    /**
     * Implements abstract FileObject.isData()
     */
    public boolean isData() {
        return (!isFolder());
    }

    /**
     * Implements abstract FileObject.isFolder()
     */
    public boolean isFolder() {
/*
        if (getDelegate().hasMountAbleFlag()) return true;

        FileObject deleg = getValidOrInvalid(getDelegate().get(true));
        return deleg.isFolder();
*/
        return isFolder;
    }

    /**
     * Implements FileObject.isVirtual()
     */
    public boolean isVirtual() {
        FileObject deleg = getValidOrInvalid(getDelegate().get());
        return deleg.isVirtual();
    }

    /**
     * Implements abstract FileObject.addFileChangeListener(FileChangeListener fcl)
     */
    public void addFileChangeListener(final FileChangeListener fcl) {
        synchronized (this) {
            if (listeners == null) {
                listeners = new EventListenerList();
            }
            listeners.add(FileChangeListener.class, fcl);
        }
    }

    /**
     * Implements abstract FileObject.removeFileChangeListener(FileChangeListener fcl)
     */
    public void removeFileChangeListener(FileChangeListener fcl) {
        synchronized (this) {
            if (listeners != null) {
                listeners.remove(FileChangeListener.class, fcl);
            }
        }
    }

    /**
     * Implements abstract FileObject.transformChildren()
     */
    public FileObject[] getChildren() {
        //TODO: tryAutoMount() causes #42772: Deadlock in masterfs
        //tryAutoMount();
        enterCriticalSection();
        try {
            FileObject deleg = getValidOrInvalid(getDelegate().get());
            return transformChildren(deleg.getChildren());
        } finally {
            finishCriticalSection();
        }
    }

    /**
     * Implements abstract FileObject.getFileObject(String nameI, String extI)
     */
    public FileObject getFileObject(String name, String ext) {
        //TODO: tryAutoMount() causes #42772: Deadlock in masterfs
        //tryAutoMount();
        ResourcePath parentResource = getResource();
        ResourcePath childResourcePath = parentResource.getChild(name, ext);
        FileObject retVal = null;
            /*since rev. 1.31 added following condition cause
            #47885: MasterFS impl of FileObject.getFileObject() should be tighten up
             */
        if (childResourcePath != null && parentResource.equals(childResourcePath.getParent())) {
            FileObject deleg = getValidOrInvalid(getDelegate().get());            
            if (deleg instanceof BaseFileObj) {//#54856
                FileObject child = deleg.getFileObject(name, ext);
                retVal = (child != null) ? transformChild(child) : null;                
            } else {
                retVal = getCache().getOrCreate(childResourcePath);            
            }
            
            
            if (retVal == null) {
                File f = childResourcePath.getFile();
                boolean canRefresh = true;
                if ((Utilities.isWindows() || (Utilities.getOperatingSystem() == Utilities.OS_OS2)) && childResourcePath.getParent().isRoot()) {
                    canRefresh = (SpecialDelegates.checkValidWindowsDrive(f) != null);
                }
                if (canRefresh) {
                    if (f != null && f.exists()) {
                        if (getDeleteHandler(f) == null) {
                            this.refresh();
                            if (deleg instanceof BaseFileObj) {//#54856
                                FileObject child = deleg.getFileObject(name, ext);
                                retVal = (child != null) ? transformChild(child) : null;                
                            } else {
                                retVal = getCache().getOrCreate(childResourcePath);            
                            }                        
                        }
                    }
                }
            }
        }
        return retVal;
    }
    /**
     * Implements FileObject.transformChildren(boolean rec)
     */
    public Enumeration getChildren(boolean rec) {
        Enumeration my = org.openide.util.Enumerations.array (this.getChildren());
        
        if (rec == false)
            return my;

        return org.openide.util.Enumerations.queue (
            my, getChildsEnum ()
        );
    }

    /**
     * Implements FileObject.getFolders(boolean rec)
     */
    public Enumeration getFolders(boolean rec) {
        return org.openide.util.Enumerations.filter (
            getChildren (rec), new AcceptFolders (true)
        );
    }

    /**
     * Implements FileObject.getData(boolean rec)
     */
    public Enumeration getData(boolean rec) {
        return org.openide.util.Enumerations.filter (
            getChildren (rec), new AcceptFolders (false)
        );
    }

    /** Selector from folders and data files.
     */
    private final class AcceptFolders implements org.openide.util.Enumerations.Processor {
        private boolean folders;
        
        public AcceptFolders (boolean folders) {
            this.folders = folders;
        }
        
        public Object process (Object o, Collection nothing) {
            final FileObject fo = (FileObject) o;
            if (folders) {
                return fo.isFolder() ? fo : null;
            } else {
                return fo.isData() ? fo : null;
            }
        }
    } // end of AcceptFolders

    /**
     * Implements abstract FileObject.getFileSystem()
     */

    public FileSystem getFileSystem() throws FileStateInvalidException {
        return MasterFileSystem.getDefault();
    }

    /**
     * Implements abstract FileObject.createFolder(String nameI)
     */
    public FileObject createFolder(final String name) throws IOException {
        if (name.indexOf('\\') != -1 || name.indexOf('/') != -1) {//NOI18N
            throw new IllegalArgumentException(name);
        }
        
        MasterFileSystem.StatusImpl status = (MasterFileSystem.StatusImpl)MasterFileSystem.getDefault().getStatus();
        ProvidedExtensions extensions =  status.getExtensions();
        extensions.beforeCreate(this, name, true);
        IOException toThrow = null;
        FileObject newFolder = null;
        try {
            newFolder = new AtomicAction(this).createFolder(name);
            return newFolder;
        } catch(IOException iex) {
            toThrow = iex;
            throw iex;
        } finally {
            if (toThrow == null) {
                extensions.createSuccess(newFolder);
            } else {
                extensions.createFailure(this, name, true);
            }
        }        
    }

    /**
     * Implements FileObject.createData(String nameI)
     */
    public FileObject createData(final String name) throws IOException {
        if (name.indexOf('\\') != -1 || name.indexOf('/') != -1) {//NOI18N
            throw new IllegalArgumentException(name);
        }
        
        MasterFileSystem.StatusImpl status = (MasterFileSystem.StatusImpl)MasterFileSystem.getDefault().getStatus();
        ProvidedExtensions extensions =  status.getExtensions();
        extensions.beforeCreate(this, name, false);
        IOException toThrow = null;
        FileObject newFile = null;
        try {
            newFile = new AtomicAction(this).createData(name);
            return newFile;
        } catch(IOException iex) {
            toThrow = iex;
            throw iex;
        } finally {
            if (extensions != null) {
                if (toThrow == null) {
                    extensions.createSuccess(newFile);
                } else {
                    extensions.createFailure(this, name, false);
                }
            }
        }
    }

    /**
     * Implements abstract FileObject.createData(String nameI, String extI)
     */
    public FileObject createData(final String name, final String ext) throws IOException {        
        if (name.indexOf('\\') != -1 || name.indexOf('/') != -1) {//NOI18N
            throw new IllegalArgumentException(name);
        }
        
        MasterFileSystem.StatusImpl status = (MasterFileSystem.StatusImpl)MasterFileSystem.getDefault().getStatus();
        ProvidedExtensions extensions =  status.getExtensions();
        extensions.beforeCreate(this, FileInfo.composeName(name, ext), false);
        IOException toThrow = null;
        FileObject newFile = null;
        try {
            newFile = new AtomicAction(this).createData(name, ext);
            return newFile;
        } catch(IOException iex) {
            toThrow = iex;
            throw iex;
        } finally {
            if (extensions != null) {
                if (toThrow == null) {
                    extensions.createSuccess(newFile);
                } else {
                    extensions.createFailure(this, FileInfo.composeName(name, ext), false);
                }
            }
        }
    }

    /**
     * Implements abstract FileObject.copy (FileObject target, String nameI, String extI)
     */
    public FileObject copy(FileObject target, String name, String ext) throws IOException {
        // overwritten only because of included in sync. section
        return new AtomicAction(this).copy(target, name, ext);
    }

    /**
     * Implements abstract FileObject.delete(FileLock lock)
     */
    public void delete(FileLock lock) throws IOException {
        if (getDelegate().hasMountAbleFlag() || 
                MountTable.getDefault().getMountedFileSystem(this.getResource().getNormalizedPath()) != null) {
            FileObject deleg = getDelegate().get();
            if (deleg != null && deleg.isRoot())
                MountTable.getDefault().unmount(getDelegateFileSystem());
        }
        MasterFileSystem.StatusImpl status = (MasterFileSystem.StatusImpl)MasterFileSystem.getDefault().getStatus();
        ProvidedExtensions extensions =  status.getExtensions();
        extensions.beforeDelete(this);
        IOException toThrow = null; 
        try {
            new AtomicAction(this).delete(lock);
        } catch(IOException iex) {
            toThrow = iex;
            throw iex;
        } finally {
            if (toThrow == null) {
                extensions.deleteSuccess(this);
            } else {
                extensions.deleteFailure(this);
            }
        }        
    }

    /**
     * Implements abstract FileObject.move (FileLock lock, FileObject target, String nameI, String extI)
     */
    public FileObject move(FileLock lock, FileObject target, String name, String ext) throws IOException {
        // overwritten only because of included in sync. section
        enterExclusiveCriticalSection();
        try {
            if (getParent().equals(target)) {
                rename(lock,name, ext);
                return this;
            }
            return new AtomicAction(this).move(lock, target, name, ext);
        } finally {
            finishExclusiveCriticalSection();
        }
    }

    private FileObject superMove(FileLock lock, FileObject target, String name, String ext) throws IOException {
        return super.move(lock, target, name, ext);
    }

    /**
     * Implements FileObject.refresh(boolean expected)
     */
    public void refresh(final boolean expected) {
        try {
            if (expected)
                new AtomicAction(this).refreshExpected();
            else
                new AtomicAction(this).refresh();
        } catch (IOException e) {
            // shouldn't occure
            ErrorManager.getDefault().notify(e);
        }
    }

    /**
     * Implements abstract FileObject.getParent()
     */
    public FileObject getParent() {
        FileObject retVal = null;
        ResourcePath parentRes = getResource().getParent();
        if (parentRes != null) {
            parentRes = (parentRes.getParent() == null) ? ResourcePath.getRoot() : parentRes;
            retVal = Cache.getDefault().getOrCreate(parentRes);
            if (retVal == null) {
                retVal = Cache.getDefault().getValidOrInvalid(parentRes);
            }
        }
        return retVal;
    }

    /**
     * Implements abstract FileObject.rename()
     *
     * Note: uses fs.unmount uses exclusiveSection, then this method rename should not
     * be called from synchronized section inside MasterFileObject.
     */
    public void rename(FileLock lock, String name, String ext) throws IOException {
        //TODO: rename is wrong implemeted
        FileObject deleg = null;
        ProvidedExtensions.IOHandler renameHandler = getRenameHandler(name, ext);
        enterCriticalSection();
        try {
            deleg = getValidOrInvalid(getDelegate().get());
            if (isReadOnly()) {
                Utils.throwIOException("EXC_CannotRename",
                        new Object[]{getPath(), getHFs().getDisplayName()});
            }

            ResourcePath oldResName = getResource();
            //TODO: should'nt be renamed before deleg.rename, which can fail
            //TODO: write new test, if also children were renamed
            setResource(oldResName.getParent().getChild(name, ext));
            Cache.getDefault().replace(oldResName.getNormalizedPath(), this);
            if (isRoot() || !deleg.isRoot()) {
                try {
                    //TODO: here is fired exception sometimes
                    FileLock lck = Delegate.getLockForDelegate(lock, deleg);
                    if (renameHandler == null) {
                        deleg.rename(lck, name, ext);
                    } else {
                        ((BaseFileObj)deleg).rename(lck, name, ext, renameHandler);
                    }
                    MountTable.getDefault().renameCachedFileObjects(oldResName.getNormalizedPath(), getResource().getNormalizedPath());
                    return;
                } catch (IOException iex) {
                    setResource(oldResName);
                    throw iex;
                }
            }
            // ugly, time consuming piece of code:
            // fs must be unmounted, renamed and mounted again
            // Can`t be implemented before SPI will be introduced
            // because there is no uniform manner how to create filesystems:
            // something like createFileSystem (java.io.File)
            //deleg = Delegate.getPrefered(this);
            throw new IOException("Not implemented yet");
        } finally {
            finishCriticalSection();
        }
    }
    
    private ProvidedExtensions.IOHandler getRenameHandler(final String name, final String ext) {
        ProvidedExtensions pe;
        pe = ((MasterFileSystem.StatusImpl)MasterFileSystem.getDefault().getStatus()).getExtensions();

        File fileToRename = getResource().getFile();
        return pe.getRenameHandler(fileToRename, FileInfo.composeName(name, ext));
    }

    public final Object writeReplace() {
        return new Replace(getPath());
    }

    Delegate getDelegate() {
        return delegate;
    }

    private static MasterFileSystem getHFs() {
        return MasterFileSystem.getDefault();
    }

    private static Cache getCache() {
        return Cache.getDefault();
    }

    private MasterFileObject[] transformChildren(FileObject[] delegs) {
        final ArrayList aList = new ArrayList(delegs.length);
        for (int i = 0; i < delegs.length; i++) {
            FileObject hfo = transformChild(delegs[i]);
            if (hfo != null)
                aList.add(hfo);
        }
        MasterFileObject[] retVal = new MasterFileObject[aList.size()];
        aList.toArray(retVal);
        return retVal;
    }

    private FileObject transformChild(final FileObject deleg) {
        FileObject hfo;
        if (deleg instanceof InvalidDummy) {
            InvalidDummy res = (InvalidDummy) deleg;
            hfo = getCache().getOrCreate(res.getResource());
        } else {
            ResourcePath res = getResource().getChild(deleg.getNameExt());
            if (MountTable.getDefault().getMountedFileSystem(res.getNormalizedPath()) != null)
                hfo = getCache().getOrCreate(res, null);
            else 
                hfo = getCache().getOrCreate(res, deleg);
        }
        return hfo;
    }

    private void tryAutoMount() {
        SyncSection.getDefault().enterExclusiveSection();
        try {
            FileObject deleg = getDelegate().get();
            if (getDelegate().hasMountAbleFlag() && deleg != null && !deleg.isRoot()) {
                FileSystem fs = ProviderCall.createFileSystem(getPath());
                if (fs != null) {
                    try {
                        MountTable.getDefault().mount(getPath(), fs);
                    } catch (IOException iex) {
                        ErrorManager.getDefault().notify(iex);
                    }
                }
            }
        } finally {
            SyncSection.getDefault().finishExclusiveSection();
        }

    }

    /** Transforms fe.getFile to it`s wraper*/
    private MasterFileObject eventFileToMasterFileObject(FileEvent fe) {
        FileObject file = fe.getFile();
        if (file != null) {
            FileObject dFile = null;
            ResourcePath child = getResource().getChild(file.getNameExt());
            
            dFile = getCache().get(child);
            
            if (file.isVirtual() && !file.isValid() && dFile == null) {
                //hack for #56995 
                FileObject file56995 = getCache().getValidOrInvalid(child);
                if (file56995 != null && file56995.isVirtual()) {
                    dFile = file56995;
                }
            }
                       
            dFile = (dFile != null) ? dFile : getCache().getOrCreate(child);            
            dFile = (dFile != null) ? dFile : getCache().getValidOrInvalid(child);
            dFile = (dFile != null) ? dFile : this;
            return (MasterFileObject) dFile;
        }
        return this;
    }


    private org.openide.util.Enumerations.Processor getChildsEnum() {
        return new org.openide.util.Enumerations.Processor  () {
            public Object process(final Object o, Collection toAdd) {
                final FileObject fo = (FileObject) o;
                if (fo != null)
                    toAdd.addAll (Arrays.asList (fo.getChildren()));
                return fo;
            }
        };
    }


    private Enumeration getEnumOfListeners() {
        Object[] fcls;
        synchronized (this) {
            if (listeners == null) {
                return org.openide.util.Enumerations.empty();
            }
            fcls = listeners.getListenerList();
        }

        if (fcls == null || fcls.length == 0) {
            return org.openide.util.Enumerations.empty();
        }

        class OnlyListeners implements org.openide.util.Enumerations.Processor {
            public Object process(final Object o, Collection toAdd) {
                if (o instanceof FileChangeListener) {
                    return o;
                }
                return null;
            }
        }
        
        return org.openide.util.Enumerations.filter (
            org.openide.util.Enumerations.array (fcls), new OnlyListeners ()
        );
    }

    FileSystem getDelegateFileSystem() {
        FileObject deleg = getDelegate().get();
        try {
            return (deleg != null) ? deleg.getFileSystem() : null;
        } catch (FileStateInvalidException fsx) {
            ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, fsx);
        }
        return null;
    }

    static void refreshAfterMount(FileObject newDelegate, FileObject oldDelegate, MasterFileObject hfo) {
        ResourcePath parentRes = hfo.getResource().getParent();
        MasterFileObject parent = (parentRes != null) ? getCache().get(parentRes) : null;

        // basically refresh on file
        if (newDelegate != null) {
            // added children
            boolean isRootFileSystem = false;
            try {
                isRootFileSystem = (newDelegate.getFileSystem() instanceof FileBasedFileSystem);
            } catch (FileStateInvalidException fsx) {
                ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, fsx);
            }
            
            if (newDelegate.isFolder() && !isRootFileSystem) {
                // compare old children and new children
                Set oldChildren = new HashSet(Arrays.asList(oldDelegate.getChildren()));
                Set newChildren = new HashSet(Arrays.asList(newDelegate.getChildren()));
                retainOnlyDifferent(oldChildren, newChildren);
                handleCreated(newChildren, hfo);
            }
            // changed

/*
            if (oldDelegate.isFolder() == newDelegate.isFolder()) {
                handleChanged(hfo, parent);
            }
*/
        } else {
            // deleted
            handleDeleted(hfo, parent);
        }
    }

    private static void handleDeleted(MasterFileObject hfo, MasterFileObject parent) {
        hfo.fireFileDeletedEvent(hfo.getEnumOfListeners(), new FileEvent(hfo));
        if (parent != null)
            parent.fireFileDeletedEvent(parent.getEnumOfListeners(), new FileEvent(parent, hfo));
    }

    private static void handleChanged(MasterFileObject hfo, MasterFileObject parent) {
        hfo.fireFileChangedEvent(hfo.getEnumOfListeners(), new FileEvent(hfo));
        if (parent != null)
            parent.fireFileChangedEvent(parent.getEnumOfListeners(), new FileEvent(parent, hfo));
    }

    private static void handleCreated(Set newChildren, MasterFileObject hfo) {
        Iterator addIt = newChildren.iterator();
        while (addIt.hasNext()) {
            FileObject addDeleg = (FileObject) addIt.next();
            ResourcePath name = hfo.getResource().getChild(addDeleg.getNameExt());
            MasterFileObject addHfo = getCache().get(name);
            boolean resetDelg = false;
            if (addHfo == null) {
                addHfo = getCache().getOrCreate(name, addDeleg);
                resetDelg = true;
            }
            if (addHfo.isFolder())
                hfo.fireFileFolderCreatedEvent(hfo.getEnumOfListeners(), new FileEvent(hfo, addHfo));
            else
                hfo.fireFileDataCreatedEvent(hfo.getEnumOfListeners(), new FileEvent(hfo, addHfo));

            if (resetDelg)
                addHfo.getDelegate ().reset(addHfo.getResource());
        }
    }

    private static void retainOnlyDifferent(Set oldChildren, Set newChildren) {
        Iterator oldIt = oldChildren.iterator();
        Iterator newIt = newChildren.iterator();
        while (oldIt.hasNext()) {
            if (!newIt.hasNext()) break;
            FileObject oldFo = (FileObject) oldIt.next();
            while (newIt.hasNext()) {
                FileObject newFo = (FileObject) newIt.next();
                if (newFo.getNameExt().equals(oldFo.getNameExt())) {
                    newIt.remove();
                    oldIt.remove();
                    break;
                }
            }
            newIt = newChildren.iterator();
        }
    }

    private FileObject getValidOrInvalid(FileObject deleg) {
        return getValidOrInvalid(deleg, this);
    }

    private static FileObject getValidOrInvalid(FileObject deleg, final MasterFileObject hfo) {
        if (deleg == null) deleg = new InvalidDummy(hfo.getResource());
        return deleg;
    }

    private void finishCriticalSection() {
        SyncSection.getDefault().finishSection();
    }

    private void enterCriticalSection() {
        SyncSection.getDefault().enterSection();
    }

    private void finishExclusiveCriticalSection() {
        SyncSection.getDefault().finishExclusiveSection();
    }

    private void enterExclusiveCriticalSection() {
        SyncSection.getDefault().enterExclusiveSection();
    }

    public static final class Replace extends Object implements java.io.Serializable {
        /** generated Serialized Version UID */
        static final long serialVersionUID = -8552332135435542113L;
        private final String path;

        /** Constructor
         */
        public Replace(final String path) {
            this.path = path;
        }

        /** Finds the right file.
         */
        public Object readResolve() {
            FileSystem fs = MasterFileSystem.getDefault();
            FileObject retVal = fs == null ? null : fs.findResource(path);
            if (retVal == null) {
                retVal = (FileObject)new ReplaceForSerialization(new File (path)).readResolve();
            }
            return retVal;
        }

    } // end of Replace

    private final class FileChangeListenerImpl implements FileChangeListener {
        /**
         * Implements FileChangeListener.fileDataCreated(FileEvent fe)
         */
        public void fileDataCreated(FileEvent fe) {
            MasterFileObject eventFile = eventFileToMasterFileObject(fe);            
            FileEvent fe2Fire = new FileEvent(MasterFileObject.this, eventFile, fe.isExpected());
            FileObject eventFileDelegate = eventFile.getDelegate().get();
            FileObject meDelegate = MasterFileObject.this.getDelegate().get();
            
            if (eventFileDelegate == fe.getFile() && meDelegate == fe.getSource()) {                        
                fireFileDataCreatedEvent(getEnumOfListeners(), fe2Fire);
            }
        }

        /**
         * Implements FileChangeListener.fileFolderCreated(FileEvent fe)
         */
        public void fileFolderCreated(FileEvent fe) {
            MasterFileObject eventFile = eventFileToMasterFileObject(fe);
            FileEvent fe2Fire = new FileEvent(MasterFileObject.this, eventFile, fe.isExpected());
            FileObject eventFileDelegate = eventFile.getDelegate().get();
            FileObject meDelegate = MasterFileObject.this.getDelegate().get();
            
            if (eventFileDelegate == fe.getFile() && meDelegate == fe.getSource()) {                        
                fireFileFolderCreatedEvent(getEnumOfListeners(), fe2Fire);
            }
        }

        /**
         * Implements FileChangeListener.fileDeleted(FileEvent fe)
         */
        public void fileDeleted(FileEvent fe) {
            MasterFileObject eventFile = eventFileToMasterFileObject(fe);                        
            FileEvent fe2Fire = new FileEvent(MasterFileObject.this, eventFile, fe.isExpected());
            FileObject eventFileDelegate = eventFile.getDelegate().get();
            FileObject meDelegate = MasterFileObject.this.getDelegate().get();
            if (fe.getFile() instanceof BaseFileObj && eventFileDelegate instanceof BaseFileObj 
                    && fe.getSource() instanceof BaseFileObj && meDelegate instanceof BaseFileObj) {
                FileNaming fName = ((BaseFileObj)fe.getFile()).getFileName();
                FileNaming sName = ((BaseFileObj)fe.getSource()).getFileName();                
                FileNaming f1Name = ((BaseFileObj)eventFileDelegate).getFileName();
                FileNaming s1Name = ((BaseFileObj)meDelegate).getFileName();
                if (fName.equals(f1Name) && sName.equals(s1Name)) {
                    fireFileDeletedEvent(getEnumOfListeners(), fe2Fire);
                }                
            } else {
                if (eventFileDelegate == fe.getFile() && meDelegate == fe.getSource()) {
                    fireFileDeletedEvent(getEnumOfListeners(), fe2Fire);
                }
            }
        }

        /**
         * Implements FileChangeListener.fileChanged(FileEvent fe)
         */
        public void fileChanged(FileEvent fe) {
            MasterFileObject eventFile = eventFileToMasterFileObject(fe);                        
            FileEvent fe2Fire = new FileEvent(MasterFileObject.this, eventFile, fe.isExpected());
            FileObject eventFileDelegate = eventFile.getDelegate().get();
            FileObject meDelegate = MasterFileObject.this.getDelegate().get();
            
            if (eventFileDelegate == fe.getFile() && meDelegate == fe.getSource()) {                        
                fireFileChangedEvent(getEnumOfListeners(), fe2Fire);
            }
        }

        /**
         * Implements FileChangeListener.fileRenamed(FileRenameEvent fe)
         */
        public void fileRenamed(FileRenameEvent fe) {
            String name = fe.getName();
            String ext = fe.getExt();

            MasterFileObject eventFile = eventFileToMasterFileObject(fe);                        
            FileRenameEvent fe2Fire = new FileRenameEvent(MasterFileObject.this, eventFile, name, ext);
            FileObject eventFileDelegate = eventFile.getDelegate().get();
            FileObject meDelegate = MasterFileObject.this.getDelegate().get();
            
            if (eventFileDelegate == fe.getFile() && meDelegate == fe.getSource()) {                        
                fireFileRenamedEvent(getEnumOfListeners(), fe2Fire);
            }
        }

        /**
         * Implements FileChangeListener.fileAttributeChanged(FileAttributeEvent fe)
         */
        public void fileAttributeChanged(FileAttributeEvent fe) {
            MasterFileObject eventFile = eventFileToMasterFileObject(fe);                        
            FileAttributeEvent fe2Fire = new FileAttributeEvent(MasterFileObject.this, eventFile,
                    fe.getName(), fe.getOldValue(), fe.getNewValue());
            FileObject eventFileDelegate = eventFile.getDelegate().get();
            FileObject meDelegate = MasterFileObject.this.getDelegate().get();
            
            if (eventFileDelegate == fe.getFile() && meDelegate == fe.getSource()) {                        
                fireFileAttributeChangedEvent(getEnumOfListeners(), fe2Fire);
            }
        }
    }

    private static ProvidedExtensions.DeleteHandler getDeleteHandler(File f) {
        ProvidedExtensions pe;
        pe = ((MasterFileSystem.StatusImpl)MasterFileSystem.getDefault().getStatus()).getExtensions();
        
        return pe.getDeleteHandler(f);
    }
    
    private static final class AtomicAction implements FileSystem.AtomicAction {
        private int operation;
        private final MasterFileObject hfoI;
        private String nameI;
        private String extI;
        private FileObject targetI;
        private FileLock fLockI;
        private FileObject retVal;

        private static final int CREATE_DATA_OP = 0;
        private static final int CREATE_FOLDER_OP = 1;
        private static final int CREATE_DATA_EXT_OP = 2;
        private static final int COPY_OP = 3;
        private static final int REFRESH_EXPECTED_OP = 4;
        private static final int REFRESH_OP = 5;
        private static final int DELETE_OP = 6;
        private static final int MOVE_OP = 7;

        private AtomicAction(final MasterFileObject hfo) {
            this.hfoI = hfo;
        }

        private FileObject createFolder(final String name) throws IOException {
            init(name);
            operation = CREATE_FOLDER_OP;
            // calls createFolder ()
            getHFs().runAtomicAction(this);
            return retVal;
        }


        FileObject createData(final String name) throws IOException {
            init(name);
            operation = CREATE_DATA_OP;
            //calls createData
            getHFs().runAtomicAction(this);
            return retVal;
        }

        FileObject createData(final String name, final String ext) throws IOException {
            init(name, ext);
            operation = CREATE_DATA_EXT_OP;
            //calls createDataExt
            getHFs().runAtomicAction(this);
            return retVal;
        }

        FileObject copy(FileObject target, String name, String ext) throws IOException {
            init(target, name, ext);
            operation = COPY_OP;
            //calls copy
            getHFs().runAtomicAction(this);
            return retVal;
        }

        void delete(FileLock fLock) throws IOException {
            init(fLock, null, null, null);
            operation = DELETE_OP;
            //calls delete
            getHFs().runAtomicAction(this);
        }

        FileObject move(FileLock lock, FileObject target, String name, String ext) throws IOException {
            init(lock, target, name, ext);
            operation = MOVE_OP;
            //calls move
            getHFs().runAtomicAction(this);
            return retVal;
        }

        void refreshExpected() throws IOException {
            operation = REFRESH_EXPECTED_OP;
            //calls iRefreshExpected
            getHFs().runAtomicAction(this);
        }

        void refresh() throws IOException {
            operation = REFRESH_OP;
            //calls iRefresh
            getHFs().runAtomicAction(this);
        }

        public void run() throws IOException {
            hfoI.enterCriticalSection();
            try {
                switch (operation) {
                    case CREATE_FOLDER_OP:
                        createFolder();
                        break;
                    case CREATE_DATA_OP:
                        createData();
                        break;
                    case CREATE_DATA_EXT_OP:
                        createDataExt();
                        break;
                    case COPY_OP:
                        copy();
                        break;
                    case REFRESH_EXPECTED_OP:
                        iRefreshExpected();
                        break;
                    case REFRESH_OP:
                        iRefresh();
                        break;
                    case DELETE_OP:
                        delete();
                        break;
                    case MOVE_OP:
                        move();
                        break;
                }
            } finally {
                hfoI.finishCriticalSection();
            }
        }

        private void init(FileLock fLock, final FileObject target, final String name, final String ext) {
            init(target, name, ext);
            this.fLockI = fLock;
        }

        private void init(final FileObject target, final String name, final String ext) {
            init(name, ext);
            this.targetI = target;
        }

        private void init(final String name, final String ext) {
            init(name);
            this.extI = ext;
        }

        private void init(final String name) {
            this.nameI = name;
            retVal = null;
        }

        // real implementation
        private void createFolder() throws IOException {
            FileObject deleg = getValidOrInvalid(hfoI.getDelegate().get(), hfoI);
            deleg.createFolder(nameI);
            retVal = getCache().getOrCreate(hfoI.getResource().getChild(nameI));
        }

        private void createData() throws IOException {
            FileObject deleg = getValidOrInvalid(hfoI.getDelegate().get(), hfoI);
            deleg.createData(nameI);
            retVal = getCache().getOrCreate(hfoI.getResource().getChild(nameI));
        }

        private void createDataExt() throws IOException {
            FileObject deleg = getValidOrInvalid(hfoI.getDelegate().get(), hfoI);
            deleg.createData(nameI, extI);
            retVal = getCache().getOrCreate(hfoI.getResource().getChild(nameI, extI));
        }

        private void copy() throws IOException {
            FileObject deleg = getValidOrInvalid(hfoI.getDelegate().getPrefered(), hfoI);
            retVal = deleg.copy(targetI, nameI, extI);
        }

        private void delete() throws IOException {
            FileObject deleg = getValidOrInvalid(hfoI.getDelegate().get(), hfoI);
            FileLock lck = Delegate.getLockForDelegate(fLockI, deleg);
            if (deleg instanceof BaseFileObj) {
                ((BaseFileObj)deleg).delete(lck, 
                        MasterFileObject.getDeleteHandler(((BaseFileObj)deleg).getFileName().getFile()));
            } else {
                deleg.delete(lck);
            }
        }

        private void move() throws IOException {
            ProvidedExtensions.IOHandler moveHandler = getMoveHandler();
            if (moveHandler != null) {
                if (targetI instanceof MasterFileObject) {
                    BaseFileObj deleg = (BaseFileObj)getValidOrInvalid(hfoI.getDelegate().get(), hfoI);
                    FileLock lck = Delegate.getLockForDelegate(fLockI, deleg);
                    MasterFileObject targetMfo = (MasterFileObject)targetI;
                    BaseFileObj targetDeleg = (BaseFileObj)getValidOrInvalid(targetMfo.getDelegate().get(), targetMfo);
                    deleg.move(lck,targetDeleg,nameI, extI, moveHandler);
                    retVal = getCache().getOrCreate(targetMfo.getResource().getChild(nameI, extI));
                    FileUtil.copyAttributes(hfoI, retVal);
                } else {
                    //!!! NOT NICE: #73042 - Subversion support
                    moveHandler.handle();
                    hfoI.refresh(true);
                    //perfromance bottleneck to call refresh on folder
                    //(especially for many files to be moved)
                    targetI.refresh(true);
                    retVal = targetI.getFileObject(nameI, extI);
                    assert (retVal != null);
                    //!!! hfoI is already deleted when copyAttributes is invoked
                    FileUtil.copyAttributes(hfoI, retVal);
                    return;
                }
            } else {
                retVal = hfoI.superMove(fLockI, targetI, nameI, extI);
            }
        }
        
        private ProvidedExtensions.IOHandler getMoveHandler() {
            ProvidedExtensions pe;
            pe = ((MasterFileSystem.StatusImpl)MasterFileSystem.getDefault().getStatus()).getExtensions();
            
            String fName = FileInfo.composeName(nameI, extI);
            File from = hfoI.getResource().getFile();
            File to = ((MasterFileObject)targetI).getResource().getChild(fName).getFile();
            return pe.getMoveHandler(from,to);
        }

        private void iRefreshExpected() {
            FileObject deleg = getValidOrInvalid(hfoI.getDelegate().get(), hfoI);
            if (deleg != null) deleg.refresh(true);
        }

        private void iRefresh() {
            FileObject deleg = getValidOrInvalid(hfoI.getDelegate().get(), hfoI);
            if (deleg != null) deleg.refresh(false);
        }
        
        public boolean equals(Object obj) {
            return (obj instanceof AtomicAction);
        }

        public int hashCode() {
            return AtomicAction.class.hashCode();
        }
        
    }

}
