You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2013/11/12 15:31:46 UTC

svn commit: r1541083 [2/2] - /commons/proper/vfs/trunk/core/src/main/java/org/apache/commons/vfs2/provider/AbstractFileObject.java

Modified: commons/proper/vfs/trunk/core/src/main/java/org/apache/commons/vfs2/provider/AbstractFileObject.java
URL: http://svn.apache.org/viewvc/commons/proper/vfs/trunk/core/src/main/java/org/apache/commons/vfs2/provider/AbstractFileObject.java?rev=1541083&r1=1541082&r2=1541083&view=diff
==============================================================================
--- commons/proper/vfs/trunk/core/src/main/java/org/apache/commons/vfs2/provider/AbstractFileObject.java (original)
+++ commons/proper/vfs/trunk/core/src/main/java/org/apache/commons/vfs2/provider/AbstractFileObject.java Tue Nov 12 14:31:45 2013
@@ -69,20 +69,67 @@ public abstract class AbstractFileObject
 
     private static final int INITIAL_LIST_SIZE = 5;
 
+    /**
+     * Traverses a file.
+     */
+    private static void traverse(final DefaultFileSelectorInfo fileInfo,
+                                 final FileSelector selector,
+                                 final boolean depthwise,
+                                 final List<FileObject> selected)
+        throws Exception
+    {
+        // Check the file itself
+        final FileObject file = fileInfo.getFile();
+        final int index = selected.size();
+
+        // If the file is a folder, traverse it
+        if (file.getType().hasChildren() && selector.traverseDescendents(fileInfo))
+        {
+            final int curDepth = fileInfo.getDepth();
+            fileInfo.setDepth(curDepth + 1);
+
+            // Traverse the children
+            final FileObject[] children = file.getChildren();
+            for (final FileObject child : children)
+            {
+                fileInfo.setFile(child);
+                traverse(fileInfo, selector, depthwise, selected);
+            }
+
+            fileInfo.setFile(file);
+            fileInfo.setDepth(curDepth);
+        }
+
+        // Add the file if doing depthwise traversal
+        if (selector.includeFile(fileInfo))
+        {
+            if (depthwise)
+            {
+                // Add this file after its descendants
+                selected.add(file);
+            }
+            else
+            {
+                // Add this file before its descendants
+                selected.add(index, file);
+            }
+        }
+    }
     private final AbstractFileName fileName;
+
     private final AFS fs;
 
     private FileContent content;
-
     // Cached info
     private boolean attached;
     private FileType type;
-    private FileObject parent;
 
+    private FileObject parent;
     // Changed to hold only the name of the children and let the object
     // go into the global files cache
     // private FileObject[] children;
     private FileName[] children;
+
     private List<Object> objects;
 
     /**
@@ -105,1330 +152,1057 @@ public abstract class AbstractFileObject
     }
 
     /**
-     * Attaches this file object to its file resource.  This method is called
-     * before any of the doBlah() or onBlah() methods.  Sub-classes can use
-     * this method to perform lazy initialisation.
-     * <p/>
-     * This implementation does nothing.
-     * @throws Exception if an error occurs.
+     * Attaches to the file.
+     * @throws FileSystemException if an error occurs.
      */
-    protected void doAttach() throws Exception
+    private void attach() throws FileSystemException
     {
-    }
+        synchronized (fs)
+        {
+            if (attached)
+            {
+                return;
+            }
 
-    /**
-     * Detaches this file object from its file resource.
-     * <p/>
-     * <p>Called when this file is closed.  Note that the file object may be
-     * reused later, so should be able to be reattached.
-     * <p/>
-     * This implementation does nothing.
-     * @throws Exception if an error occurs.
-     */
-    protected void doDetach() throws Exception
-    {
-    }
+            try
+            {
+                // Attach and determine the file type
+                doAttach();
+                attached = true;
+                // now the type could already be injected by doAttach (e.g from parent to child)
 
-    /**
-     * Determines the type of this file.  Must not return null.  The return
-     * value of this method is cached, so the implementation can be expensive.
-     * @return the type of the file.
-     * @throws Exception if an error occurs.
-     */
-    protected abstract FileType doGetType() throws Exception;
+                /* VFS-210: determine the type when really asked fore
+                if (type == null)
+                {
+                    setFileType(doGetType());
+                }
+                if (type == null)
+                {
+                    setFileType(FileType.IMAGINARY);
+                }
+                */
+            }
+            catch (final Exception exc)
+            {
+                throw new FileSystemException("vfs.provider/get-type.error", exc, fileName);
+            }
 
-    /**
-     * Determines if this file is executable.  Is only called if {@link #doGetType}
-     * does not return {@link FileType#IMAGINARY}.
-     * <p/>
-     * This implementation always returns false.
-     * @return true if the file is executable, false otherwise.
-     * @throws Exception if an error occurs.
-     */
-    protected boolean doIsExecutable() throws Exception
-    {
-        return false;
+            // fs.fileAttached(this);
+        }
     }
 
     /**
-     * Determines if this file is hidden.  Is only called if {@link #doGetType}
-     * does not return {@link FileType#IMAGINARY}.
-     * <p/>
-     * This implementation always returns false.
-     * @return true if the file is hidden, false otherwise.
-     * @throws Exception if an error occurs.
+     * Queries the object if a simple rename to the filename of {@code newfile}
+     * is possible.
+     *
+     * @param newfile the new filename
+     * @return true if rename is possible
      */
-    protected boolean doIsHidden() throws Exception
+    @Override
+    public boolean canRenameTo(final FileObject newfile)
     {
-        return false;
+        return fs == newfile.getFileSystem();
     }
 
     /**
-     * Determines if this file can be read.  Is only called if {@link #doGetType}
-     * does not return {@link FileType#IMAGINARY}.
-     * <p/>
-     * This implementation always returns true.
-     * @return true if the file is readable, false otherwise.
+     * Notifies the file that its children have changed.
+     * @param childName The name of the child.
+     * @param newType The type of the child.
      * @throws Exception if an error occurs.
      */
-    protected boolean doIsReadable() throws Exception
+    protected void childrenChanged(final FileName childName, final FileType newType) throws Exception
     {
-        return true;
+        // TODO - this may be called when not attached
+
+        if (children != null)
+        {
+            if (childName != null && newType != null)
+            {
+                // TODO - figure out if children[] can be replaced by list
+                final ArrayList<FileName> list = new ArrayList<FileName>(Arrays.asList(children));
+                if (newType.equals(FileType.IMAGINARY))
+                {
+                    list.remove(childName);
+                }
+                else
+                {
+                    list.add(childName);
+                }
+                children = new FileName[list.size()];
+                list.toArray(children);
+            }
+        }
+
+        // removeChildrenCache();
+        onChildrenChanged(childName, newType);
     }
 
     /**
-     * Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}.
-     *
-     * @param readable
-     *            True to allow access, false to disallow
-     * @param ownerOnly
-     *            If {@code true}, the permission applies only to the owner; otherwise, it applies to everybody.
-     * @return true if the operation succeeded
-     * @see #setReadable(boolean, boolean)
-     * @since 2.1
+     * Closes this file, and its content.
+     * @throws FileSystemException if an error occurs.
      */
-    protected boolean doSetReadable(final boolean readable, final boolean ownerOnly) throws Exception
+    @Override
+    public void close() throws FileSystemException
     {
-        return false;
+        FileSystemException exc = null;
+
+        // Close the content
+        if (content != null)
+        {
+            try
+            {
+                content.close();
+                content = null;
+            }
+            catch (final FileSystemException e)
+            {
+                exc = e;
+            }
+        }
+
+        // Detach from the file
+        try
+        {
+            detach();
+        }
+        catch (final Exception e)
+        {
+            exc = new FileSystemException("vfs.provider/close.error", fileName, e);
+        }
+
+        if (exc != null)
+        {
+            throw exc;
+        }
     }
 
     /**
-     * Determines if this file can be written to.  Is only called if
-     * {@link #doGetType} does not return {@link FileType#IMAGINARY}.
-     * <p/>
-     * This implementation always returns true.
-     * @return true if the file is writable.
-     * @throws Exception if an error occurs.
+     * Compares two FileObjects (ignores case).
+     * 
+     * @param file
+     *            the object to compare.
+     * @return a negative integer, zero, or a positive integer when this object is less than, equal to, or greater than
+     *         the given object.
      */
-    protected boolean doIsWriteable() throws Exception
+    @Override
+    public int compareTo(final FileObject file)
     {
-        return true;
+        if (file == null)
+        {
+            return 1;
+        }
+        return this.toString().compareToIgnoreCase(file.toString());
     }
 
     /**
-     * Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}.
+     * Copies another file to this file.
      *
-     * @param writable
-     *            True to allow access, false to disallow
-     * @param ownerOnly
-     *            If {@code true}, the permission applies only to the owner; otherwise, it applies to everybody.
-     * @return true if the operation succeeded
-     * @see #setWritable(boolean, boolean)
-     * @since 2.1
+     * @param file The FileObject to copy.
+     * @param selector The FileSelector.
+     * @throws FileSystemException if an error occurs.
      */
-    protected boolean doSetWritable(final boolean writable, final boolean ownerOnly) throws Exception
+    @Override
+    public void copyFrom(final FileObject file, final FileSelector selector)
+        throws FileSystemException
     {
-        return false;
-    }
-
-    /**
-     * Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}.
-     *
-     * @param writable
-     *            True to allow access, false to disallow
-     * @param ownerOnly
-     *            If {@code true}, the permission applies only to the owner; otherwise, it applies to everybody.
-     * @return true if the operation succeeded
-     * @see #setExecutable(boolean, boolean)
-     * @since 2.1
-     */
-    protected boolean doSetExecutable(final boolean writable, final boolean ownerOnly) throws Exception
-    {
-        return false;
-    }
+        if (!file.exists())
+        {
+            throw new FileSystemException("vfs.provider/copy-missing-file.error", file);
+        }
+        /* we do not alway know if a file is writeable
+        if (!isWriteable())
+        {
+            throw new FileSystemException("vfs.provider/copy-read-only.error", new Object[]{file.getType(),
+            file.getName(), this}, null);
+        }
+        */
 
-    /**
-     * Lists the children of this file.  Is only called if {@link #doGetType}
-     * returns {@link FileType#FOLDER}.  The return value of this method
-     * is cached, so the implementation can be expensive.<br />
-     * @return a possible empty String array if the file is a directory or null or an exception if the
-     * file is not a directory or can't be read.
-     * @throws Exception if an error occurs.
-     */
-    protected abstract String[] doListChildren() throws Exception;
+        // Locate the files to copy across
+        final ArrayList<FileObject> files = new ArrayList<FileObject>();
+        file.findFiles(selector, false, files);
 
-    /**
-     * Lists the children of this file.  Is only called if {@link #doGetType}
-     * returns {@link FileType#FOLDER}.  The return value of this method
-     * is cached, so the implementation can be expensive.<br>
-     * Other than {@code doListChildren} you could return FileObject's to e.g. reinitialize the
-     * type of the file.<br>
-     * (Introduced for Webdav: "permission denied on resource" during getType())
-     * @return The children of this FileObject.
-     * @throws Exception if an error occurs.
-     */
-    protected FileObject[] doListChildrenResolved() throws Exception
-    {
-        return null;
-    }
+        // Copy everything across
+        for (final FileObject srcFile : files)
+        {
+            // Determine the destination file
+            final String relPath = file.getName().getRelativeName(srcFile.getName());
+            final FileObject destFile = resolveFile(relPath, NameScope.DESCENDENT_OR_SELF);
 
-    /**
-     * Deletes the file.  Is only called when:
-     * <ul>
-     * <li>{@link #doGetType} does not return {@link FileType#IMAGINARY}.
-     * <li>{@link #doIsWriteable} returns true.
-     * <li>This file has no children, if a folder.
-     * </ul>
-     * <p/>
-     * This implementation throws an exception.
-     * @throws Exception if an error occurs.
-     */
-    protected void doDelete() throws Exception
-    {
-        throw new FileSystemException("vfs.provider/delete-not-supported.error");
-    }
+            // Clean up the destination file, if necessary
+            if (destFile.exists() && destFile.getType() != srcFile.getType())
+            {
+                // The destination file exists, and is not of the same type,
+                // so delete it
+                // TODO - add a pluggable policy for deleting and overwriting existing files
+                destFile.deleteAll();
+            }
 
-    /**
-     * Renames the file.  Is only called when:
-     * <ul>
-     * <li>{@link #doIsWriteable} returns true.
-     * </ul>
-     * <p/>
-     * This implementation throws an exception.
-     * @param newFile A FileObject with the new file name.
-     * @throws Exception if an error occurs.
-     */
-    protected void doRename(final FileObject newFile) throws Exception
-    {
-        throw new FileSystemException("vfs.provider/rename-not-supported.error");
+            // Copy across
+            try
+            {
+                if (srcFile.getType().hasContent())
+                {
+                    FileUtil.copyContent(srcFile, destFile);
+                }
+                else if (srcFile.getType().hasChildren())
+                {
+                    destFile.createFolder();
+                }
+            }
+            catch (final IOException e)
+            {
+                throw new FileSystemException("vfs.provider/copy-file.error", e, srcFile, destFile);
+            }
+        }
     }
 
     /**
-     * Creates this file as a folder.  Is only called when:
-     * <ul>
-     * <li>{@link #doGetType} returns {@link FileType#IMAGINARY}.
-     * <li>The parent folder exists and is writeable, or this file is the
-     * root of the file system.
-     * </ul>
-     * <p/>
-     * This implementation throws an exception.
-     * @throws Exception if an error occurs.
+     * Creates this file, if it does not exist.
+     * @throws FileSystemException if an error occurs.
      */
-    protected void doCreateFolder() throws Exception
+    @Override
+    public void createFile() throws FileSystemException
     {
-        throw new FileSystemException("vfs.provider/create-folder-not-supported.error");
-    }
+        synchronized (fs)
+        {
+            try
+            {
+                // VFS-210: We do not want to trunc any existing file, checking for its existence is
+                // still required
+                if (exists() && !isFile())
+                {
+                    throw new FileSystemException("vfs.provider/create-file.error", fileName);
+                }
 
-    /**
-     * Called when the children of this file change.  Allows subclasses to
-     * refresh any cached information about the children of this file.
-     * <p/>
-     * This implementation does nothing.
-     * @param child The name of the child that changed.
-     * @param newType The type of the file.
-     * @throws Exception if an error occurs.
-     */
-    protected void onChildrenChanged(final FileName child, final FileType newType) throws Exception
-    {
+                if (!exists())
+                {
+                    getOutputStream().close();
+                    endOutput();
+                }
+            }
+            catch (final RuntimeException re)
+            {
+                throw re;
+            }
+            catch (final Exception e)
+            {
+                throw new FileSystemException("vfs.provider/create-file.error", fileName, e);
+            }
+        }
     }
 
     /**
-     * Called when the type or content of this file changes.
-     * <p/>
-     * This implementation does nothing.
-     * @throws Exception if an error occurs.
+     * Creates this folder, if it does not exist.  Also creates any ancestor
+     * files which do not exist.
+     * @throws FileSystemException if an error occurs.
      */
-    protected void onChange() throws Exception
+    @Override
+    public void createFolder() throws FileSystemException
     {
-    }
+        synchronized (fs)
+        {
+            // VFS-210: we create a folder only if it does not already exist. So this check should be safe.
+            if (getType().hasChildren())
+            {
+                // Already exists as correct type
+                return;
+            }
+            if (getType() != FileType.IMAGINARY)
+            {
+                throw new FileSystemException("vfs.provider/create-folder-mismatched-type.error", fileName);
+            }
+            /* VFS-210: checking for writeable is not always possible as the security constraint might
+               be more complex
+            if (!isWriteable())
+            {
+                throw new FileSystemException("vfs.provider/create-folder-read-only.error", name);
+            }
+            */
 
-    /**
-     * Returns the last modified time of this file.  Is only called if
-     * {@link #doGetType} does not return {@link FileType#IMAGINARY}.
-     * <p/>
-     * This implementation throws an exception.
-     * @return The last modification time.
-     * @throws Exception if an error occurs.
-     */
-    protected long doGetLastModifiedTime() throws Exception
-    {
-        throw new FileSystemException("vfs.provider/get-last-modified-not-supported.error");
-    }
+            // Traverse up the heirarchy and make sure everything is a folder
+            final FileObject parent = getParent();
+            if (parent != null)
+            {
+                parent.createFolder();
+            }
 
-    /**
-     * Sets the last modified time of this file.  Is only called if
-     * {@link #doGetType} does not return {@link FileType#IMAGINARY}.
-     * <p/>
-     * This implementation throws an exception.
-     * @param modtime The last modification time.
-     * @return true if the time was set.
-     * @throws Exception if an error occurs.
-     */
-    protected boolean doSetLastModifiedTime(final long modtime) throws Exception
-    {
-        throw new FileSystemException("vfs.provider/set-last-modified-not-supported.error");
-    }
+            try
+            {
+                // Create the folder
+                doCreateFolder();
 
-    /**
-     * Returns the attributes of this file.  Is only called if {@link #doGetType}
-     * does not return {@link FileType#IMAGINARY}.
-     * <p/>
-     * This implementation always returns an empty map.
-     * @return The attributes of the file.
-     * @throws Exception if an error occurs.
-     */
-    protected Map<String, Object> doGetAttributes() throws Exception
-    {
-        return Collections.emptyMap();
+                // Update cached info
+                handleCreate(FileType.FOLDER);
+            }
+            catch (final RuntimeException re)
+            {
+                throw re;
+            }
+            catch (final Exception exc)
+            {
+                throw new FileSystemException("vfs.provider/create-folder.error", fileName, exc);
+            }
+        }
     }
 
     /**
-     * Sets an attribute of this file.  Is only called if {@link #doGetType}
-     * does not return {@link FileType#IMAGINARY}.
-     * <p/>
-     * This implementation throws an exception.
-     * @param attrName The attribute name.
-     * @param value The value to be associated with the attribute name.
-     * @throws Exception if an error occurs.
+     * Deletes this file.
+     *
+     * @return true if this object has been deleted
+     * @todo This will not fail if this is a non-empty folder.
+     * @throws FileSystemException if an error occurs.
      */
-    protected void doSetAttribute(final String attrName, final Object value) throws Exception
+    @Override
+    public boolean delete() throws FileSystemException
     {
-        throw new FileSystemException("vfs.provider/set-attribute-not-supported.error");
+        return delete(Selectors.SELECT_SELF) > 0;
     }
 
     /**
-     * Removes an attribute of this file.  Is only called if {@link #doGetType}
-     * does not return {@link FileType#IMAGINARY}.
-     * <p/>
-     * This implementation throws an exception.
-     * @param attrName The name of the attribute to remove.
-     * @throws Exception if an error occurs.
-     * @since 2.0
+     * Deletes this file, and all children matching the {@code selector}.
+     *
+     * @param selector The FileSelector.
+     * @return the number of deleted files.
+     * @throws FileSystemException if an error occurs.
      */
-    protected void doRemoveAttribute(final String attrName) throws Exception
+    @Override
+    public int delete(final FileSelector selector) throws FileSystemException
     {
-        throw new FileSystemException("vfs.provider/remove-attribute-not-supported.error");
-    }
+        int nuofDeleted = 0;
 
-    /**
-     * Returns the certificates used to sign this file.  Is only called if
-     * {@link #doGetType} does not return {@link FileType#IMAGINARY}.
-     * <p/>
-     * This implementation always returns null.
-     * @return The certificates used to sign the file.
-     * @throws Exception if an error occurs.
-     */
-    protected Certificate[] doGetCertificates() throws Exception
-    {
-        return null;
-    }
+        /* VFS-210
+        if (getType() == FileType.IMAGINARY)
+        {
+            // File does not exist
+            return nuofDeleted;
+        }
+        */
 
-    /**
-     * Returns the size of the file content (in bytes).  Is only called if
-     * {@link #doGetType} returns {@link FileType#FILE}.
-     * @return The size of the file in bytes.
-     * @throws Exception if an error occurs.
-     */
-    protected abstract long doGetContentSize() throws Exception;
+        // Locate all the files to delete
+        final ArrayList<FileObject> files = new ArrayList<FileObject>();
+        findFiles(selector, true, files);
 
-    /**
-     * Creates an input stream to read the file content from.  Is only called
-     * if {@link #doGetType} returns {@link FileType#FILE}.
-     * <p/>
-     * <p>It is guaranteed that there are no open output streams for this file
-     * when this method is called.
-     * <p/>
-     * <p>The returned stream does not have to be buffered.
-     * @return An InputStream to read the file content.
-     * @throws Exception if an error occurs.
-     */
-    protected abstract InputStream doGetInputStream() throws Exception;
+        // Delete 'em
+        final int count = files.size();
+        for (int i = 0; i < count; i++)
+        {
+            final AbstractFileObject file = FileObjectUtils.getAbstractFileObject(files.get(i));
+            // file.attach();
 
-    /**
-     * Creates access to the file for random i/o.  Is only called
-     * if {@link #doGetType} returns {@link FileType#FILE}.
-     * <p/>
-     * <p>It is guaranteed that there are no open output streams for this file
-     * when this method is called.
-     * <p/>
-     * @param mode The mode to access the file.
-     * @return The RandomAccessContext.
-     * @throws Exception if an error occurs.
-     */
-    protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception
-    {
-        throw new FileSystemException("vfs.provider/random-access-not-supported.error");
-    }
+            // VFS-210: It seems impossible to me that findFiles will return a list with hidden files/directories
+            // in it, else it would not be hidden. Checking for the file-type seems ok in this case
+            // If the file is a folder, make sure all its children have been deleted
+            if (file.getType().hasChildren() && file.getChildren().length != 0)
+            {
+                // Skip - as the selector forced us not to delete all files
+                continue;
+            }
 
-    /**
-     * Creates an output stream to write the file content to.  Is only
-     * called if:
-     * <ul>
-     * <li>{@link #doIsWriteable} returns true.
-     * <li>{@link #doGetType} returns {@link FileType#FILE}, or
-     * {@link #doGetType} returns {@link FileType#IMAGINARY}, and the file's
-     * parent exists and is a folder.
-     * </ul>
-     * <p/>
-     * <p>It is guaranteed that there are no open stream (input or output) for
-     * this file when this method is called.
-     * <p/>
-     * <p>The returned stream does not have to be buffered.
-     * <p/>
-     * This implementation throws an exception.
-     * @param bAppend true if the file should be appended to, false if it should be overwritten.
-     * @return An OutputStream to write to the file.
-     * @throws Exception if an error occurs.
-     */
-    protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception
-    {
-        throw new FileSystemException("vfs.provider/write-not-supported.error");
-    }
+            // Delete the file
+            if (file.deleteSelf())
+            {
+                nuofDeleted++;
+            }
+        }
 
-    /**
-     * Returns the URI as a String.
-     * 
-     * @return Returns the URI as a String.
-     */
-    @Override
-    public String toString()
-    {
-        return fileName.getURI();
-    }
-    
-    /**
-     * Returns the name of the file.
-     * @return The FileName.
-     */
-    @Override
-    public FileName getName()
-    {
-        return fileName;
+        return nuofDeleted;
     }
 
     /**
-     * Returns the file system this file belongs to.
-     * @return The FileSystem this file is associated with.
+     * Deletes this file and all children. Shorthand for {@code delete(Selectors.SELECT_ALL)}
+     *
+     * @return the number of deleted files.
+     * @throws FileSystemException if an error occurs.
+     * @see #delete(FileSelector)
+     * @see Selectors#SELECT_ALL
      */
     @Override
-    public FileSystem getFileSystem()
-    {
-        return fs;
-    }
-
-    /**
-     * Returns the file system this file belongs to.
-     * @return The FileSystem this file is associated with.
-     */
-    protected AFS getAbstractFileSystem()
+    public int deleteAll() throws FileSystemException
     {
-        return fs;
+        return this.delete(Selectors.SELECT_ALL);
     }
 
     /**
-     * Returns a URL representation of the file.
-     * @return The URL representation of the file.
+     * Deletes this file, once all its children have been deleted
+     *
+     * @return true if this file has been deleted
      * @throws FileSystemException if an error occurs.
      */
-    @Override
-    public URL getURL() throws FileSystemException
+    private boolean deleteSelf() throws FileSystemException
     {
-        try
+        synchronized (fs)
         {
-            return AccessController.doPrivileged(new PrivilegedExceptionAction<URL>()
+            /* Its possible to delete a read-only file if you have write-execute access to the directory
+            if (!isWriteable())
             {
-                @Override
-                public URL run() throws MalformedURLException
-                {
-                    final StringBuilder buf = new StringBuilder();
-                    final String scheme = UriParser.extractScheme(fileName.getURI(), buf);
-                    return new URL(scheme, "", -1,
-                        buf.toString(), new DefaultURLStreamHandler(fs.getContext(), fs.getFileSystemOptions()));
-                }
-            });
-        }
-        catch (final PrivilegedActionException e)
-        {
-            throw new FileSystemException("vfs.provider/get-url.error", fileName, e.getException());
-        }
-    }
+                throw new FileSystemException("vfs.provider/delete-read-only.error", name);
+            }
+            */
 
-    /**
-     * Determines if the file exists.
-     * @return true if the file exists, false otherwise,
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public boolean exists() throws FileSystemException
-    {
-        return getType() != FileType.IMAGINARY;
+            /* VFS-210
+            if (getType() == FileType.IMAGINARY)
+            {
+                // File does not exist
+                return false;
+            }
+            */
+
+            try
+            {
+                // Delete the file
+                doDelete();
+
+                // Update cached info
+                handleDelete();
+            }
+            catch (final RuntimeException re)
+            {
+                throw re;
+            }
+            catch (final Exception exc)
+            {
+                throw new FileSystemException("vfs.provider/delete.error", exc, fileName);
+            }
+
+            return true;
+        }
     }
 
     /**
-     * Returns the file's type.
-     * @return The FileType.
-     * @throws FileSystemException if an error occurs.
+     * Detaches this file, invaliating all cached info.  This will force
+     * a call to {@link #doAttach} next time this file is used.
+     * @throws Exception if an error occurs.
      */
-    @Override
-    public FileType getType() throws FileSystemException
+    private void detach() throws Exception
     {
         synchronized (fs)
         {
-            attach();
-
-            // VFS-210: get the type only if requested for
-            try
+            if (attached)
             {
-                if (type == null)
+                try
                 {
-                    setFileType(doGetType());
+                    doDetach();
                 }
-                if (type == null)
+                finally
                 {
-                    setFileType(FileType.IMAGINARY);
+                    attached = false;
+                    setFileType(null);
+                    parent = null;
+
+                    // fs.fileDetached(this);
+
+                    removeChildrenCache();
+                    // children = null;
                 }
             }
-            catch (final Exception e)
-            {
-                throw new FileSystemException("vfs.provider/get-type.error", e, fileName);
-            }
-
-            return type;
         }
     }
 
     /**
-     * Checks if this file is a regular file by using its file type.
-     *
-     * @return true if this file is a regular file.
-     * @throws FileSystemException if an error occurs.
-     * @see #getType()
-     * @see FileType#FILE
+     * Attaches this file object to its file resource.  This method is called
+     * before any of the doBlah() or onBlah() methods.  Sub-classes can use
+     * this method to perform lazy initialisation.
+     * <p/>
+     * This implementation does nothing.
+     * @throws Exception if an error occurs.
      */
-    @Override
-    public boolean isFile() throws FileSystemException
+    protected void doAttach() throws Exception
     {
-        // Use equals instead of == to avoid any class loader worries.
-        return FileType.FILE.equals(this.getType());
     }
 
     /**
-     * Checks if this file is a folder by using its file type.
-     *
-     * @return true if this file is a regular file.
+     * Create a FileContent implementation.
+     * @return The FileContent.
      * @throws FileSystemException if an error occurs.
-     * @see #getType()
-     * @see FileType#FOLDER
+     * @since 2.0
      */
-    @Override
-    public boolean isFolder() throws FileSystemException
+    protected FileContent doCreateFileContent() throws FileSystemException
     {
-        // Use equals instead of == to avoid any class loader worries.
-        return FileType.FOLDER.equals(this.getType());
+        return new DefaultFileContent(this, getFileContentInfoFactory());
     }
 
     /**
-     * Determines if this file is executable.
-     *
-     * @return {@code true} if this file is executable, {@code false} if not.
-     * @throws FileSystemException On error determining if this file exists.
+     * Creates this file as a folder.  Is only called when:
+     * <ul>
+     * <li>{@link #doGetType} returns {@link FileType#IMAGINARY}.
+     * <li>The parent folder exists and is writeable, or this file is the
+     * root of the file system.
+     * </ul>
+     * <p/>
+     * This implementation throws an exception.
+     * @throws Exception if an error occurs.
      */
-    @Override
-    public boolean isExecutable() throws FileSystemException
+    protected void doCreateFolder() throws Exception
     {
-        try
-        {
-            return exists() ? doIsExecutable() : false;
-        }
-        catch (final Exception exc)
-        {
-            throw new FileSystemException("vfs.provider/check-is-executable.error", fileName, exc);
-        }
+        throw new FileSystemException("vfs.provider/create-folder-not-supported.error");
     }
 
     /**
-     * Determines if this file can be read.
-     * @return true if the file is a hidden file, false otherwise.
-     * @throws FileSystemException if an error occurs.
+     * Deletes the file.  Is only called when:
+     * <ul>
+     * <li>{@link #doGetType} does not return {@link FileType#IMAGINARY}.
+     * <li>{@link #doIsWriteable} returns true.
+     * <li>This file has no children, if a folder.
+     * </ul>
+     * <p/>
+     * This implementation throws an exception.
+     * @throws Exception if an error occurs.
      */
-    @Override
-    public boolean isHidden() throws FileSystemException
+    protected void doDelete() throws Exception
     {
-        try
-        {
-            return exists() ? doIsHidden() : false;
-        }
-        catch (final Exception exc)
-        {
-            throw new FileSystemException("vfs.provider/check-is-hidden.error", fileName, exc);
-        }
+        throw new FileSystemException("vfs.provider/delete-not-supported.error");
     }
 
     /**
-     * Determines if this file can be read.
-     * @return true if the file can be read, false otherwise.
-     * @throws FileSystemException if an error occurs.
+     * Detaches this file object from its file resource.
+     * <p/>
+     * <p>Called when this file is closed.  Note that the file object may be
+     * reused later, so should be able to be reattached.
+     * <p/>
+     * This implementation does nothing.
+     * @throws Exception if an error occurs.
      */
-    @Override
-    public boolean isReadable() throws FileSystemException
+    protected void doDetach() throws Exception
     {
-        try
-        {
-            return exists() ? doIsReadable() : false;
-        }
-        catch (final Exception exc)
-        {
-            throw new FileSystemException("vfs.provider/check-is-readable.error", fileName, exc);
-        }
     }
 
-    @Override
-    public boolean setReadable(final boolean readable, final boolean ownerOnly) throws FileSystemException
+    /**
+     * Returns the attributes of this file.  Is only called if {@link #doGetType}
+     * does not return {@link FileType#IMAGINARY}.
+     * <p/>
+     * This implementation always returns an empty map.
+     * @return The attributes of the file.
+     * @throws Exception if an error occurs.
+     */
+    protected Map<String, Object> doGetAttributes() throws Exception
     {
-        try
-        {
-            return exists() ? doSetReadable(readable, ownerOnly) : false;
-        }
-        catch (final Exception exc)
-        {
-            throw new FileSystemException("vfs.provider/set-readable.error", fileName, exc);
-        }
+        return Collections.emptyMap();
     }
 
     /**
-     * Determines if this file can be written to.
-     * @return true if the file can be written to, false otherwise.
-     * @throws FileSystemException if an error occurs.
+     * Returns the certificates used to sign this file.  Is only called if
+     * {@link #doGetType} does not return {@link FileType#IMAGINARY}.
+     * <p/>
+     * This implementation always returns null.
+     * @return The certificates used to sign the file.
+     * @throws Exception if an error occurs.
      */
-    @Override
-    public boolean isWriteable() throws FileSystemException
+    protected Certificate[] doGetCertificates() throws Exception
     {
-        try
-        {
-            if (exists())
-            {
-                return doIsWriteable();
-            }
-            else
-            {
-                final FileObject parent = getParent();
-                if (parent != null)
-                {
-                    return parent.isWriteable();
-                }
-                return true;
-            }
-        }
-        catch (final Exception exc)
-        {
-            throw new FileSystemException("vfs.provider/check-is-writeable.error", fileName, exc);
-        }
+        return null;
     }
 
-    @Override
-    public boolean setWritable(final boolean readable, final boolean ownerOnly) throws FileSystemException
+    /**
+     * Returns the size of the file content (in bytes).  Is only called if
+     * {@link #doGetType} returns {@link FileType#FILE}.
+     * @return The size of the file in bytes.
+     * @throws Exception if an error occurs.
+     */
+    protected abstract long doGetContentSize() throws Exception;
+
+    /**
+     * Creates an input stream to read the file content from.  Is only called
+     * if {@link #doGetType} returns {@link FileType#FILE}.
+     * <p/>
+     * <p>It is guaranteed that there are no open output streams for this file
+     * when this method is called.
+     * <p/>
+     * <p>The returned stream does not have to be buffered.
+     * @return An InputStream to read the file content.
+     * @throws Exception if an error occurs.
+     */
+    protected abstract InputStream doGetInputStream() throws Exception;
+
+    /**
+     * Returns the last modified time of this file.  Is only called if
+     * {@link #doGetType} does not return {@link FileType#IMAGINARY}.
+     * <p/>
+     * This implementation throws an exception.
+     * @return The last modification time.
+     * @throws Exception if an error occurs.
+     */
+    protected long doGetLastModifiedTime() throws Exception
     {
-        try
-        {
-            return exists() ? doSetWritable(readable, ownerOnly) : false;
-        }
-        catch (final Exception exc)
-        {
-            throw new FileSystemException("vfs.provider/set-writeable.error", fileName, exc);
-        }
+        throw new FileSystemException("vfs.provider/get-last-modified-not-supported.error");
+    }
+
+    /**
+     * Creates an output stream to write the file content to.  Is only
+     * called if:
+     * <ul>
+     * <li>{@link #doIsWriteable} returns true.
+     * <li>{@link #doGetType} returns {@link FileType#FILE}, or
+     * {@link #doGetType} returns {@link FileType#IMAGINARY}, and the file's
+     * parent exists and is a folder.
+     * </ul>
+     * <p/>
+     * <p>It is guaranteed that there are no open stream (input or output) for
+     * this file when this method is called.
+     * <p/>
+     * <p>The returned stream does not have to be buffered.
+     * <p/>
+     * This implementation throws an exception.
+     * @param bAppend true if the file should be appended to, false if it should be overwritten.
+     * @return An OutputStream to write to the file.
+     * @throws Exception if an error occurs.
+     */
+    protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception
+    {
+        throw new FileSystemException("vfs.provider/write-not-supported.error");
     }
 
-    @Override
-    public boolean setExecutable(final boolean readable, final boolean ownerOnly) throws FileSystemException
+    /**
+     * Creates access to the file for random i/o.  Is only called
+     * if {@link #doGetType} returns {@link FileType#FILE}.
+     * <p/>
+     * <p>It is guaranteed that there are no open output streams for this file
+     * when this method is called.
+     * <p/>
+     * @param mode The mode to access the file.
+     * @return The RandomAccessContext.
+     * @throws Exception if an error occurs.
+     */
+    protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception
     {
-        try
-        {
-            return exists() ? doSetExecutable(readable, ownerOnly) : false;
-        }
-        catch (final Exception exc)
-        {
-            throw new FileSystemException("vfs.provider/set-executable.error", fileName, exc);
-        }
+        throw new FileSystemException("vfs.provider/random-access-not-supported.error");
     }
 
     /**
-     * Returns an iterator over a set of all FileObject in this file object.
-     *
-     * @return an Iterator.
+     * Determines the type of this file.  Must not return null.  The return
+     * value of this method is cached, so the implementation can be expensive.
+     * @return the type of the file.
+     * @throws Exception if an error occurs.
      */
-    @Override
-    public Iterator<FileObject> iterator()
+    protected abstract FileType doGetType() throws Exception;
+
+    /**
+     * Determines if this file is executable.  Is only called if {@link #doGetType}
+     * does not return {@link FileType#IMAGINARY}.
+     * <p/>
+     * This implementation always returns false.
+     * @return true if the file is executable, false otherwise.
+     * @throws Exception if an error occurs.
+     */
+    protected boolean doIsExecutable() throws Exception
     {
-        try
-        {
-            return listFiles(Selectors.SELECT_ALL).iterator();
-        }
-        catch (final FileSystemException e)
-        {
-            throw new IllegalStateException(e);
-        }
+        return false;
     }
-
+    
     /**
-     * Lists the set of matching descendants of this file, in depthwise
-     * order.
-     *
-     * @param selector The FileSelector.
-     * @return list of files or null if the base file (this object) do not exist or the {@code selector} is null
-     * @throws FileSystemException if an error occurs.
+     * Determines if this file is hidden.  Is only called if {@link #doGetType}
+     * does not return {@link FileType#IMAGINARY}.
+     * <p/>
+     * This implementation always returns false.
+     * @return true if the file is hidden, false otherwise.
+     * @throws Exception if an error occurs.
      */
-    public List<FileObject> listFiles(final FileSelector selector) throws FileSystemException
+    protected boolean doIsHidden() throws Exception
     {
-        if (!exists() || selector == null)
-        {
-            return null;
-        }
-
-        final ArrayList<FileObject> list = new ArrayList<FileObject>();
-        this.findFiles(selector, true, list);
-        return list;
+        return false;
     }
 
-
     /**
-     * Returns the parent of the file.
-     * @return the parent FileObject.
-     * @throws FileSystemException if an error occurs.
+     * Determines if this file can be read.  Is only called if {@link #doGetType}
+     * does not return {@link FileType#IMAGINARY}.
+     * <p/>
+     * This implementation always returns true.
+     * @return true if the file is readable, false otherwise.
+     * @throws Exception if an error occurs.
      */
-    @Override
-    public FileObject getParent() throws FileSystemException
+    protected boolean doIsReadable() throws Exception
     {
-        if (this == fs.getRoot())
-        {
-            if (fs.getParentLayer() == null)
-            {
-                // Root file has no parent
-                return null;
-            }
-            else
-            {
-                // Return the parent of the parent layer
-                return fs.getParentLayer().getParent();
-            }
-        }
-
-        synchronized (fs)
-        {
-            // Locate the parent of this file
-            if (parent == null)
-            {
-                parent = fs.resolveFile(fileName.getParent());
-            }
-        }
-        return parent;
+        return true;
     }
 
     /**
-     * Returns the children of the file.
-     * @return an array of FileObjects, one per child.
+     * Checks if this fileObject is the same file as {@code destFile} just with a different
+     * name.<br />
+     * E.g. for case insensitive filesystems like windows.
+     * @param destFile The file to compare to.
+     * @return true if the FileObjects are the same.
      * @throws FileSystemException if an error occurs.
      */
-    @Override
-    public FileObject[] getChildren() throws FileSystemException
+    protected boolean doIsSameFile(final FileObject destFile) throws FileSystemException
     {
-        synchronized (fs)
-        {
-            // VFS-210
-            if (!fs.hasCapability(Capability.LIST_CHILDREN))
-            {
-                throw new FileNotFolderException(fileName);
-            }
-
-            /* VFS-210
-            if (!getType().hasChildren())
-            {
-                throw new FileSystemException("vfs.provider/list-children-not-folder.error", name);
-            }
-            */
-            attach();
-
-            // Use cached info, if present
-            if (children != null)
-            {
-                return resolveFiles(children);
-            }
-
-            // allow the filesystem to return resolved children. e.g. prefill type for webdav
-            FileObject[] childrenObjects;
-            try
-            {
-                childrenObjects = doListChildrenResolved();
-                children = extractNames(childrenObjects);
-            }
-            catch (final FileSystemException exc)
-            {
-                // VFS-210
-                throw exc;
-            }
-            catch (final Exception exc)
-            {
-                throw new FileSystemException("vfs.provider/list-children.error", exc, fileName);
-            }
-
-            if (childrenObjects != null)
-            {
-                return childrenObjects;
-            }
-
-            // List the children
-            final String[] files;
-            try
-            {
-                files = doListChildren();
-            }
-            catch (final FileSystemException exc)
-            {
-                // VFS-210
-                throw exc;
-            }
-            catch (final Exception exc)
-            {
-                throw new FileSystemException("vfs.provider/list-children.error", exc, fileName);
-            }
-
-            if (files == null)
-            {
-                // VFS-210
-                // honor the new doListChildren contract
-                // return null;
-                throw new FileNotFolderException(fileName);
-            }
-            else if (files.length == 0)
-            {
-                // No children
-                children = EMPTY_FILE_ARRAY;
-            }
-            else
-            {
-                // Create file objects for the children
-                // children = new FileObject[files.length];
-                final FileName[] cache = new FileName[files.length];
-                for (int i = 0; i < files.length; i++)
-                {
-                    final String file = files[i];
-                    // children[i] = fs.resolveFile(name.resolveName(file, NameScope.CHILD));
-                    // children[i] = name.resolveName(file, NameScope.CHILD);
-                    cache[i] = fs.getFileSystemManager().resolveName(fileName, file, NameScope.CHILD);
-                }
-                // VFS-285: only assign the children filenames after all of them have been
-                // resolved successfully to prevent an inconsistent internal state
-                children = cache;
-            }
-
-            return resolveFiles(children);
-        }
+        return false;
     }
 
-    private FileName[] extractNames(final FileObject[] objects)
+    /**
+     * Determines if this file can be written to.  Is only called if
+     * {@link #doGetType} does not return {@link FileType#IMAGINARY}.
+     * <p/>
+     * This implementation always returns true.
+     * @return true if the file is writable.
+     * @throws Exception if an error occurs.
+     */
+    protected boolean doIsWriteable() throws Exception
     {
-        if (objects == null)
-        {
-            return null;
-        }
+        return true;
+    }
 
-        final FileName[] names = new FileName[objects.length];
-        for (int iterObjects = 0; iterObjects < objects.length; iterObjects++)
-        {
-            names[iterObjects] = objects[iterObjects].getName();
-        }
+    /**
+     * Lists the children of this file.  Is only called if {@link #doGetType}
+     * returns {@link FileType#FOLDER}.  The return value of this method
+     * is cached, so the implementation can be expensive.<br />
+     * @return a possible empty String array if the file is a directory or null or an exception if the
+     * file is not a directory or can't be read.
+     * @throws Exception if an error occurs.
+     */
+    protected abstract String[] doListChildren() throws Exception;
 
-        return names;
+    /**
+     * Lists the children of this file.  Is only called if {@link #doGetType}
+     * returns {@link FileType#FOLDER}.  The return value of this method
+     * is cached, so the implementation can be expensive.<br>
+     * Other than {@code doListChildren} you could return FileObject's to e.g. reinitialize the
+     * type of the file.<br>
+     * (Introduced for Webdav: "permission denied on resource" during getType())
+     * @return The children of this FileObject.
+     * @throws Exception if an error occurs.
+     */
+    protected FileObject[] doListChildrenResolved() throws Exception
+    {
+        return null;
     }
 
-    private FileObject[] resolveFiles(final FileName[] children) throws FileSystemException
+    /**
+     * Removes an attribute of this file.  Is only called if {@link #doGetType}
+     * does not return {@link FileType#IMAGINARY}.
+     * <p/>
+     * This implementation throws an exception.
+     * @param attrName The name of the attribute to remove.
+     * @throws Exception if an error occurs.
+     * @since 2.0
+     */
+    protected void doRemoveAttribute(final String attrName) throws Exception
     {
-        if (children == null)
-        {
-            return null;
-        }
-
-        final FileObject[] objects = new FileObject[children.length];
-        for (int iterChildren = 0; iterChildren < children.length; iterChildren++)
-        {
-            objects[iterChildren] = resolveFile(children[iterChildren]);
-        }
+        throw new FileSystemException("vfs.provider/remove-attribute-not-supported.error");
+    }
 
-        return objects;
+    /**
+     * Renames the file.  Is only called when:
+     * <ul>
+     * <li>{@link #doIsWriteable} returns true.
+     * </ul>
+     * <p/>
+     * This implementation throws an exception.
+     * @param newFile A FileObject with the new file name.
+     * @throws Exception if an error occurs.
+     */
+    protected void doRename(final FileObject newFile) throws Exception
+    {
+        throw new FileSystemException("vfs.provider/rename-not-supported.error");
     }
 
-    private FileObject resolveFile(final FileName child) throws FileSystemException
+    /**
+     * Sets an attribute of this file.  Is only called if {@link #doGetType}
+     * does not return {@link FileType#IMAGINARY}.
+     * <p/>
+     * This implementation throws an exception.
+     * @param attrName The attribute name.
+     * @param value The value to be associated with the attribute name.
+     * @throws Exception if an error occurs.
+     */
+    protected void doSetAttribute(final String attrName, final Object value) throws Exception
     {
-        return fs.resolveFile(child);
+        throw new FileSystemException("vfs.provider/set-attribute-not-supported.error");
     }
 
     /**
-     * Returns a child of this file.
-     * @param name The name of the child to locate.
-     * @return The FileObject for the file or null if the child does not exist.
-     * @throws FileSystemException if an error occurs.
+     * Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}.
+     *
+     * @param writable
+     *            True to allow access, false to disallow
+     * @param ownerOnly
+     *            If {@code true}, the permission applies only to the owner; otherwise, it applies to everybody.
+     * @return true if the operation succeeded
+     * @see #setExecutable(boolean, boolean)
+     * @since 2.1
      */
-    @Override
-    public FileObject getChild(final String name) throws FileSystemException
+    protected boolean doSetExecutable(final boolean writable, final boolean ownerOnly) throws Exception
     {
-        // TODO - use a hashtable when there are a large number of children
-        final FileObject[] children = getChildren();
-        for (final FileObject element : children)
-        {
-            // final FileObject child = children[i];
-            final FileName child = element.getName();
-            // TODO - use a comparator to compare names
-            // if (child.getName().getBaseName().equals(name))
-            if (child.getBaseName().equals(name))
-            {
-                return resolveFile(child);
-            }
-        }
-        return null;
+        return false;
     }
 
     /**
-     * Returns a child by name.
-     * @param name The name of the child to locate.
-     * @param scope the NameScope.
-     * @return The FileObject for the file or null if the child does not exist.
-     * @throws FileSystemException if an error occurs.
+     * Sets the last modified time of this file.  Is only called if
+     * {@link #doGetType} does not return {@link FileType#IMAGINARY}.
+     * <p/>
+     * This implementation throws an exception.
+     * @param modtime The last modification time.
+     * @return true if the time was set.
+     * @throws Exception if an error occurs.
      */
-    @Override
-    public FileObject resolveFile(final String name, final NameScope scope)
-        throws FileSystemException
+    protected boolean doSetLastModifiedTime(final long modtime) throws Exception
     {
-        // return fs.resolveFile(this.name.resolveName(name, scope));
-        return fs.resolveFile(fs.getFileSystemManager().resolveName(this.fileName, name, scope));
+        throw new FileSystemException("vfs.provider/set-last-modified-not-supported.error");
     }
 
     /**
-     * Finds a file, relative to this file.
+     * Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}.
      *
-     * @param path The path of the file to locate.  Can either be a relative
-     *             path, which is resolved relative to this file, or an
-     *             absolute path, which is resolved relative to the file system
-     *             that contains this file.
-     * @return The FileObject.
-     * @throws FileSystemException if an error occurs.
+     * @param readable
+     *            True to allow access, false to disallow
+     * @param ownerOnly
+     *            If {@code true}, the permission applies only to the owner; otherwise, it applies to everybody.
+     * @return true if the operation succeeded
+     * @see #setReadable(boolean, boolean)
+     * @since 2.1
      */
-    @Override
-    public FileObject resolveFile(final String path) throws FileSystemException
+    protected boolean doSetReadable(final boolean readable, final boolean ownerOnly) throws Exception
     {
-        final FileName otherName = fs.getFileSystemManager().resolveName(fileName, path);
-        return fs.resolveFile(otherName);
+        return false;
     }
 
     /**
-     * Deletes this file, once all its children have been deleted
+     * Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}.
      *
-     * @return true if this file has been deleted
-     * @throws FileSystemException if an error occurs.
+     * @param writable
+     *            True to allow access, false to disallow
+     * @param ownerOnly
+     *            If {@code true}, the permission applies only to the owner; otherwise, it applies to everybody.
+     * @return true if the operation succeeded
+     * @see #setWritable(boolean, boolean)
+     * @since 2.1
      */
-    private boolean deleteSelf() throws FileSystemException
+    protected boolean doSetWritable(final boolean writable, final boolean ownerOnly) throws Exception
     {
-        synchronized (fs)
-        {
-            /* Its possible to delete a read-only file if you have write-execute access to the directory
-            if (!isWriteable())
-            {
-                throw new FileSystemException("vfs.provider/delete-read-only.error", name);
-            }
-            */
-
-            /* VFS-210
-            if (getType() == FileType.IMAGINARY)
-            {
-                // File does not exist
-                return false;
-            }
-            */
-
-            try
-            {
-                // Delete the file
-                doDelete();
-
-                // Update cached info
-                handleDelete();
-            }
-            catch (final RuntimeException re)
-            {
-                throw re;
-            }
-            catch (final Exception exc)
-            {
-                throw new FileSystemException("vfs.provider/delete.error", exc, fileName);
-            }
-
-            return true;
-        }
+        return false;
     }
 
     /**
-     * Deletes this file.
-     *
-     * @return true if this object has been deleted
-     * @todo This will not fail if this is a non-empty folder.
-     * @throws FileSystemException if an error occurs.
+     * Called when the ouput stream for this file is closed.
+     * @throws Exception if an error occurs.
      */
-    @Override
-    public boolean delete() throws FileSystemException
+    protected void endOutput() throws Exception
     {
-        return delete(Selectors.SELECT_SELF) > 0;
+        if (getType() == FileType.IMAGINARY)
+        {
+            // File was created
+            handleCreate(FileType.FILE);
+        }
+        else
+        {
+            // File has changed
+            onChange();
+        }
     }
 
     /**
-     * Deletes this file, and all children matching the {@code selector}.
-     *
-     * @param selector The FileSelector.
-     * @return the number of deleted files.
+     * Determines if the file exists.
+     * @return true if the file exists, false otherwise,
      * @throws FileSystemException if an error occurs.
      */
     @Override
-    public int delete(final FileSelector selector) throws FileSystemException
+    public boolean exists() throws FileSystemException
     {
-        int nuofDeleted = 0;
+        return getType() != FileType.IMAGINARY;
+    }
 
-        /* VFS-210
-        if (getType() == FileType.IMAGINARY)
+    private FileName[] extractNames(final FileObject[] objects)
+    {
+        if (objects == null)
         {
-            // File does not exist
-            return nuofDeleted;
+            return null;
         }
-        */
-
-        // Locate all the files to delete
-        final ArrayList<FileObject> files = new ArrayList<FileObject>();
-        findFiles(selector, true, files);
 
-        // Delete 'em
-        final int count = files.size();
-        for (int i = 0; i < count; i++)
+        final FileName[] names = new FileName[objects.length];
+        for (int iterObjects = 0; iterObjects < objects.length; iterObjects++)
         {
-            final AbstractFileObject file = FileObjectUtils.getAbstractFileObject(files.get(i));
-            // file.attach();
+            names[iterObjects] = objects[iterObjects].getName();
+        }
 
-            // VFS-210: It seems impossible to me that findFiles will return a list with hidden files/directories
-            // in it, else it would not be hidden. Checking for the file-type seems ok in this case
-            // If the file is a folder, make sure all its children have been deleted
-            if (file.getType().hasChildren() && file.getChildren().length != 0)
-            {
-                // Skip - as the selector forced us not to delete all files
-                continue;
-            }
+        return names;
+    }
 
-            // Delete the file
-            if (file.deleteSelf())
-            {
-                nuofDeleted++;
-            }
-        }
+    @Override
+    protected void finalize() throws Throwable
+    {
+        fs.fileObjectDestroyed(this);
 
-        return nuofDeleted;
+        super.finalize();
     }
 
+
     /**
-     * Deletes this file and all children. Shorthand for {@code delete(Selectors.SELECT_ALL)}
+     * Finds the set of matching descendants of this file, in depthwise
+     * order.
      *
-     * @return the number of deleted files.
+     * @param selector The FileSelector.
+     * @return list of files or null if the base file (this object) do not exist
      * @throws FileSystemException if an error occurs.
-     * @see #delete(FileSelector)
-     * @see Selectors#SELECT_ALL
      */
     @Override
-    public int deleteAll() throws FileSystemException
+    public FileObject[] findFiles(final FileSelector selector) throws FileSystemException
     {
-        return this.delete(Selectors.SELECT_ALL);
+        final List<FileObject> list = this.listFiles(selector);
+        return list == null ? null : list.toArray(new FileObject[list.size()]);
     }
 
     /**
-     * Creates this file, if it does not exist.
+     * Traverses the descendants of this file, and builds a list of selected
+     * files.
+     * @param selector The FileSelector.
+     * @param depthwise if true files are added after their descendants, before otherwise.
+     * @param selected A List of the located FileObjects.
      * @throws FileSystemException if an error occurs.
      */
     @Override
-    public void createFile() throws FileSystemException
+    public void findFiles(final FileSelector selector,
+                          final boolean depthwise,
+                          final List<FileObject> selected) throws FileSystemException
     {
-        synchronized (fs)
+        try
         {
-            try
-            {
-                // VFS-210: We do not want to trunc any existing file, checking for its existence is
-                // still required
-                if (exists() && !isFile())
-                {
-                    throw new FileSystemException("vfs.provider/create-file.error", fileName);
-                }
-
-                if (!exists())
-                {
-                    getOutputStream().close();
-                    endOutput();
-                }
-            }
-            catch (final RuntimeException re)
-            {
-                throw re;
-            }
-            catch (final Exception e)
+            if (exists())
             {
-                throw new FileSystemException("vfs.provider/create-file.error", fileName, e);
+                // Traverse starting at this file
+                final DefaultFileSelectorInfo info = new DefaultFileSelectorInfo();
+                info.setBaseFolder(this);
+                info.setDepth(0);
+                info.setFile(this);
+                traverse(info, selector, depthwise, selected);
             }
         }
+        catch (final Exception e)
+        {
+            throw new FileSystemException("vfs.provider/find-files.error", fileName, e);
+        }
     }
 
     /**
-     * Creates this folder, if it does not exist.  Also creates any ancestor
-     * files which do not exist.
-     * @throws FileSystemException if an error occurs.
+     * Returns the file system this file belongs to.
+     * @return The FileSystem this file is associated with.
      */
-    @Override
-    public void createFolder() throws FileSystemException
+    protected AFS getAbstractFileSystem()
     {
-        synchronized (fs)
-        {
-            // VFS-210: we create a folder only if it does not already exist. So this check should be safe.
-            if (getType().hasChildren())
-            {
-                // Already exists as correct type
-                return;
-            }
-            if (getType() != FileType.IMAGINARY)
-            {
-                throw new FileSystemException("vfs.provider/create-folder-mismatched-type.error", fileName);
-            }
-            /* VFS-210: checking for writeable is not always possible as the security constraint might
-               be more complex
-            if (!isWriteable())
-            {
-                throw new FileSystemException("vfs.provider/create-folder-read-only.error", name);
-            }
-            */
-
-            // Traverse up the heirarchy and make sure everything is a folder
-            final FileObject parent = getParent();
-            if (parent != null)
-            {
-                parent.createFolder();
-            }
-
-            try
-            {
-                // Create the folder
-                doCreateFolder();
-
-                // Update cached info
-                handleCreate(FileType.FOLDER);
-            }
-            catch (final RuntimeException re)
-            {
-                throw re;
-            }
-            catch (final Exception exc)
-            {
-                throw new FileSystemException("vfs.provider/create-folder.error", fileName, exc);
-            }
-        }
+        return fs;
     }
 
     /**
-     * Compares two FileObjects (ignores case).
-     * 
-     * @param file
-     *            the object to compare.
-     * @return a negative integer, zero, or a positive integer when this object is less than, equal to, or greater than
-     *         the given object.
+     * Returns a child of this file.
+     * @param name The name of the child to locate.
+     * @return The FileObject for the file or null if the child does not exist.
+     * @throws FileSystemException if an error occurs.
      */
     @Override
-    public int compareTo(final FileObject file)
+    public FileObject getChild(final String name) throws FileSystemException
     {
-        if (file == null)
+        // TODO - use a hashtable when there are a large number of children
+        final FileObject[] children = getChildren();
+        for (final FileObject element : children)
         {
-            return 1;
+            // final FileObject child = children[i];
+            final FileName child = element.getName();
+            // TODO - use a comparator to compare names
+            // if (child.getName().getBaseName().equals(name))
+            if (child.getBaseName().equals(name))
+            {
+                return resolveFile(child);
+            }
         }
-        return this.toString().compareToIgnoreCase(file.toString());
+        return null;
     }
 
     /**
-     * Copies another file to this file.
-     *
-     * @param file The FileObject to copy.
-     * @param selector The FileSelector.
+     * Returns the children of the file.
+     * @return an array of FileObjects, one per child.
      * @throws FileSystemException if an error occurs.
      */
     @Override
-    public void copyFrom(final FileObject file, final FileSelector selector)
-        throws FileSystemException
+    public FileObject[] getChildren() throws FileSystemException
     {
-        if (!file.exists())
-        {
-            throw new FileSystemException("vfs.provider/copy-missing-file.error", file);
-        }
-        /* we do not alway know if a file is writeable
-        if (!isWriteable())
+        synchronized (fs)
         {
-            throw new FileSystemException("vfs.provider/copy-read-only.error", new Object[]{file.getType(),
-            file.getName(), this}, null);
-        }
-        */
-
-        // Locate the files to copy across
-        final ArrayList<FileObject> files = new ArrayList<FileObject>();
-        file.findFiles(selector, false, files);
+            // VFS-210
+            if (!fs.hasCapability(Capability.LIST_CHILDREN))
+            {
+                throw new FileNotFolderException(fileName);
+            }
 
-        // Copy everything across
-        for (final FileObject srcFile : files)
-        {
-            // Determine the destination file
-            final String relPath = file.getName().getRelativeName(srcFile.getName());
-            final FileObject destFile = resolveFile(relPath, NameScope.DESCENDENT_OR_SELF);
+            /* VFS-210
+            if (!getType().hasChildren())
+            {
+                throw new FileSystemException("vfs.provider/list-children-not-folder.error", name);
+            }
+            */
+            attach();
 
-            // Clean up the destination file, if necessary
-            if (destFile.exists() && destFile.getType() != srcFile.getType())
+            // Use cached info, if present
+            if (children != null)
             {
-                // The destination file exists, and is not of the same type,
-                // so delete it
-                // TODO - add a pluggable policy for deleting and overwriting existing files
-                destFile.deleteAll();
+                return resolveFiles(children);
             }
 
-            // Copy across
+            // allow the filesystem to return resolved children. e.g. prefill type for webdav
+            FileObject[] childrenObjects;
             try
             {
-                if (srcFile.getType().hasContent())
-                {
-                    FileUtil.copyContent(srcFile, destFile);
-                }
-                else if (srcFile.getType().hasChildren())
-                {
-                    destFile.createFolder();
-                }
+                childrenObjects = doListChildrenResolved();
+                children = extractNames(childrenObjects);
             }
-            catch (final IOException e)
+            catch (final FileSystemException exc)
             {
-                throw new FileSystemException("vfs.provider/copy-file.error", e, srcFile, destFile);
+                // VFS-210
+                throw exc;
             }
-        }
-    }
-
-    /**
-     * Moves (rename) the file to another one.
-     * @param destFile The target FileObject.
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public void moveTo(final FileObject destFile) throws FileSystemException
-    {
-        if (canRenameTo(destFile))
-        {
-            if (!getParent().isWriteable())
+            catch (final Exception exc)
             {
-                throw new FileSystemException("vfs.provider/rename-parent-read-only.error",
-                        getName(),
-                        getParent().getName());
+                throw new FileSystemException("vfs.provider/list-children.error", exc, fileName);
             }
-        }
-        else
-        {
-            if (!isWriteable())
+
+            if (childrenObjects != null)
             {
-                throw new FileSystemException("vfs.provider/rename-read-only.error", getName());
+                return childrenObjects;
             }
-        }
-
-        if (destFile.exists() && !isSameFile(destFile))
-        {
-            destFile.deleteAll();
-            // throw new FileSystemException("vfs.provider/rename-dest-exists.error", destFile.getName());
-        }
 
-        if (canRenameTo(destFile))
-        {
-            // issue rename on same filesystem
+            // List the children
+            final String[] files;
             try
             {
-                attach();
-                doRename(destFile);
-
-                FileObjectUtils.getAbstractFileObject(destFile).handleCreate(getType());
-
-                destFile.close(); // now the destFile is no longer imaginary. force reattach.
-
-                handleDelete(); // fire delete-events. This file-object (src) is like deleted.
+                files = doListChildren();
             }
-            catch (final RuntimeException re)
+            catch (final FileSystemException exc)
             {
-                throw re;
+                // VFS-210
+                throw exc;
             }
             catch (final Exception exc)
             {
-                throw new FileSystemException("vfs.provider/rename.error",  exc,
-                        getName(),
-                        destFile.getName());
+                throw new FileSystemException("vfs.provider/list-children.error", exc, fileName);
             }
-        }
-        else
-        {
-            // different fs - do the copy/delete stuff
 
-            destFile.copyFrom(this, Selectors.SELECT_SELF);
-
-            if ((destFile.getType().hasContent()
-                    && destFile.getFileSystem().hasCapability(Capability.SET_LAST_MODIFIED_FILE)
-                  || destFile.getType().hasChildren()
-                    && destFile.getFileSystem().hasCapability(Capability.SET_LAST_MODIFIED_FOLDER))
-                    && fs.hasCapability(Capability.GET_LAST_MODIFIED))
+            if (files == null)
             {
-                destFile.getContent().setLastModifiedTime(this.getContent().getLastModifiedTime());
+                // VFS-210
+                // honor the new doListChildren contract
+                // return null;
+                throw new FileNotFolderException(fileName);
+            }
+            else if (files.length == 0)
+            {
+                // No children
+                children = EMPTY_FILE_ARRAY;
+            }
+            else
+            {
+                // Create file objects for the children
+                // children = new FileObject[files.length];
+                final FileName[] cache = new FileName[files.length];
+                for (int i = 0; i < files.length; i++)
+                {
+                    final String file = files[i];
+                    // children[i] = fs.resolveFile(name.resolveName(file, NameScope.CHILD));
+                    // children[i] = name.resolveName(file, NameScope.CHILD);
+                    cache[i] = fs.getFileSystemManager().resolveName(fileName, file, NameScope.CHILD);
+                }
+                // VFS-285: only assign the children filenames after all of them have been
+                // resolved successfully to prevent an inconsistent internal state
+                children = cache;
             }
 
-            deleteSelf();
+            return resolveFiles(children);
         }
-
-    }
-
-    /**
-     * Checks if this fileObject is the same file as {@code destFile} just with a different
-     * name.<br />
-     * E.g. for case insensitive filesystems like windows.
-     * @param destFile The file to compare to.
-     * @return true if the FileObjects are the same.
-     * @throws FileSystemException if an error occurs.
-     */
-    protected boolean isSameFile(final FileObject destFile) throws FileSystemException
-    {
-        attach();
-        return doIsSameFile(destFile);
-    }
-
-    /**
-     * Checks if this fileObject is the same file as {@code destFile} just with a different
-     * name.<br />
-     * E.g. for case insensitive filesystems like windows.
-     * @param destFile The file to compare to.
-     * @return true if the FileObjects are the same.
-     * @throws FileSystemException if an error occurs.
-     */
-    protected boolean doIsSameFile(final FileObject destFile) throws FileSystemException
-    {
-        return false;
-    }
-
-    /**
-     * Queries the object if a simple rename to the filename of {@code newfile}
-     * is possible.
-     *
-     * @param newfile the new filename
-     * @return true if rename is possible
-     */
-    @Override
-    public boolean canRenameTo(final FileObject newfile)
-    {
-        return fs == newfile.getFileSystem();
-    }
-
-    /**
-     * Finds the set of matching descendants of this file, in depthwise
-     * order.
-     *
-     * @param selector The FileSelector.
-     * @return list of files or null if the base file (this object) do not exist
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public FileObject[] findFiles(final FileSelector selector) throws FileSystemException
-    {
-        final List<FileObject> list = this.listFiles(selector);
-        return list == null ? null : list.toArray(new FileObject[list.size()]);
     }
 
     /**
@@ -1451,71 +1225,38 @@ public abstract class AbstractFileObject
     }
 
     /**
-     * Create a FileContent implementation.
-     * @return The FileContent.
-     * @throws FileSystemException if an error occurs.
-     * @since 2.0
+     * create the filecontentinfo implementation.
+     * @return The FileContentInfoFactory.
      */
-    protected FileContent doCreateFileContent() throws FileSystemException
+    protected FileContentInfoFactory getFileContentInfoFactory()
     {
-        return new DefaultFileContent(this, getFileContentInfoFactory());
+        return fs.getFileSystemManager().getFileContentInfoFactory();
     }
 
     /**
-     * This will prepare the fileObject to get resynchronized with the underlaying filesystem if required.
+     * @return FileOperations interface that provides access to the operations
+     *         API.
      * @throws FileSystemException if an error occurs.
      */
     @Override
-    public void refresh() throws FileSystemException
+    public FileOperations getFileOperations() throws FileSystemException
     {
-        // Detach from the file
-        try
-        {
-            detach();
-        }
-        catch (final Exception e)
+        if (operations == null)
         {
-            throw new FileSystemException("vfs.provider/resync.error", fileName, e);
+            operations = new DefaultFileOperations(this);
         }
+
+        return operations;
     }
 
     /**
-     * Closes this file, and its content.
-     * @throws FileSystemException if an error occurs.
+     * Returns the file system this file belongs to.
+     * @return The FileSystem this file is associated with.
      */
     @Override
-    public void close() throws FileSystemException
+    public FileSystem getFileSystem()
     {
-        FileSystemException exc = null;
-
-        // Close the content
-        if (content != null)
-        {
-            try
-            {
-                content.close();
-                content = null;
-            }
-            catch (final FileSystemException e)
-            {
-                exc = e;
-            }
-        }
-
-        // Detach from the file
-        try
-        {
-            detach();
-        }
-        catch (final Exception e)
-        {
-            exc = new FileSystemException("vfs.provider/close.error", fileName, e);
-        }
-
-        if (exc != null)
-        {
-            throw exc;
-        }
+        return fs;
     }
 
     /**
@@ -1551,12 +1292,122 @@ public abstract class AbstractFileObject
         }
         catch (final FileSystemException exc)
         {
-            throw exc;
+            throw exc;
+        }
+        catch (final Exception exc)
+        {
+            throw new FileSystemException("vfs.provider/read.error", fileName, exc);
+        }
+    }
+
+    /**
+     * Returns the name of the file.
+     * @return The FileName.
+     */
+    @Override
+    public FileName getName()
+    {
+        return fileName;
+    }
+
+    /**
+     * Prepares this file for writing.  Makes sure it is either a file,
+     * or its parent folder exists.  Returns an output stream to use to
+     * write the content of the file to.
+     * @return An OutputStream where the new contents of the file can be written.
+     * @throws FileSystemException if an error occurs.
+     */
+    public OutputStream getOutputStream() throws FileSystemException
+    {
+        return getOutputStream(false);
+    }
+
+    /**
+     * Prepares this file for writing.  Makes sure it is either a file,
+     * or its parent folder exists.  Returns an output stream to use to
+     * write the content of the file to.<br>
+     *
+     * @param bAppend true when append to the file.<br>
+     *                Note: If the underlaying filesystem does not support appending,
+     *                a FileSystemException is thrown.
+     * @return An OutputStream where the new contents of the file can be written.
+     * @throws FileSystemException if an error occurs; for example:<br>
+     *         bAppend is true, and the unbderlying FileSystem does not support it
+     */
+    public OutputStream getOutputStream(final boolean bAppend) throws FileSystemException
+    {
+        /* VFS-210
+        if (getType() != FileType.IMAGINARY && !getType().hasContent())
+        {
+            throw new FileSystemException("vfs.provider/write-not-file.error", name);
+        }
+        if (!isWriteable())
+        {
+            throw new FileSystemException("vfs.provider/write-read-only.error", name);
+        }
+        */
+
+        if (bAppend && !fs.hasCapability(Capability.APPEND_CONTENT))
+        {
+            throw new FileSystemException("vfs.provider/write-append-not-supported.error", fileName);
+        }
+
+        if (getType() == FileType.IMAGINARY)
+        {
+// Does not exist - make sure parent does
+            final FileObject parent = getParent();
+            if (parent != null)
+            {
+                parent.createFolder();
+            }
+        }
+
+// Get the raw output stream
+        try
+        {
+            return doGetOutputStream(bAppend);
+        }
+        catch (final RuntimeException re)
+        {
+            throw re;
+        }
+        catch (final Exception exc)
+        {
+            throw new FileSystemException("vfs.provider/write.error", exc, fileName);
+        }
+    }
+
+    /**
+     * Returns the parent of the file.
+     * @return the parent FileObject.
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public FileObject getParent() throws FileSystemException
+    {
+        if (this == fs.getRoot())
+        {
+            if (fs.getParentLayer() == null)
+            {
+                // Root file has no parent
+                return null;
+            }
+            else
+            {
+                // Return the parent of the parent layer
+                return fs.getParentLayer().getParent();
+            }
         }
-        catch (final Exception exc)
+
+        synchronized (fs)
         {
-            throw new FileSystemException("vfs.provider/read.error", fileName, exc);
+            // Locate the parent of this file
+            if (parent == null)
+            {
+                parent = fs.resolveFile(fileName.getParent());
+            }
         }
+        return parent;
     }
 
     /**
@@ -1611,276 +1462,432 @@ public abstract class AbstractFileObject
     }
 
     /**
-     * Prepares this file for writing.  Makes sure it is either a file,
-     * or its parent folder exists.  Returns an output stream to use to
-     * write the content of the file to.
-     * @return An OutputStream where the new contents of the file can be written.
+     * Returns the file's type.
+     * @return The FileType.
      * @throws FileSystemException if an error occurs.
      */
-    public OutputStream getOutputStream() throws FileSystemException
+    @Override
+    public FileType getType() throws FileSystemException
     {
-        return getOutputStream(false);
+        synchronized (fs)
+        {
+            attach();
+
+            // VFS-210: get the type only if requested for
+            try
+            {
+                if (type == null)
+                {
+                    setFileType(doGetType());
+                }
+                if (type == null)
+                {
+                    setFileType(FileType.IMAGINARY);
+                }
+            }
+            catch (final Exception e)
+            {
+                throw new FileSystemException("vfs.provider/get-type.error", e, fileName);
+            }
+
+            return type;
+        }
     }
 
     /**
-     * Prepares this file for writing.  Makes sure it is either a file,
-     * or its parent folder exists.  Returns an output stream to use to
-     * write the content of the file to.<br>
-     *
-     * @param bAppend true when append to the file.<br>
-     *                Note: If the underlaying filesystem does not support appending,
-     *                a FileSystemException is thrown.
-     * @return An OutputStream where the new contents of the file can be written.
-     * @throws FileSystemException if an error occurs; for example:<br>
-     *         bAppend is true, and the unbderlying FileSystem does not support it
+     * Returns a URL representation of the file.
+     * @return The URL representation of the file.
+     * @throws FileSystemException if an error occurs.
      */
-    public OutputStream getOutputStream(final boolean bAppend) throws FileSystemException
+    @Override
+    public URL getURL() throws FileSystemException
     {
-        /* VFS-210
-        if (getType() != FileType.IMAGINARY && !getType().hasContent())
+        try
         {
-            throw new FileSystemException("vfs.provider/write-not-file.error", name);
+            return AccessController.doPrivileged(new PrivilegedExceptionAction<URL>()
+            {
+                @Override
+                public URL run() throws MalformedURLException
+                {
+                    final StringBuilder buf = new StringBuilder();
+                    final String scheme = UriParser.extractScheme(fileName.getURI(), buf);
+                    return new URL(scheme, "", -1,
+                        buf.toString(), new DefaultURLStreamHandler(fs.getContext(), fs.getFileSystemOptions()));
+                }
+            });
         }
-        if (!isWriteable())
+        catch (final PrivilegedActionException e)
         {
-            throw new FileSystemException("vfs.provider/write-read-only.error", name);
+            throw new FileSystemException("vfs.provider/get-url.error", fileName, e.getException());
         }
-        */
+    }
 
-        if (bAppend && !fs.hasCapability(Capability.APPEND_CONTENT))
+    /**
+     * Called when this file is changed.<br />
+     * This will only happen if you monitor the file using {@link org.apache.commons.vfs2.FileMonitor}.
+     * @throws Exception if an error occurs.
+     */
+    protected void handleChanged() throws Exception
+    {
+        // Notify the file system
+        fs.fireFileChanged(this);
+    }
+
+    /**
+     * Called when this file is created.  Updates cached info and notifies
+     * the parent and file system.
+     * @param newType The type of the file.
+     * @throws Exception if an error occurs.
+     */
+    protected void handleCreate(final FileType newType) throws Exception
+    {
+        synchronized (fs)
         {
-            throw new FileSystemException("vfs.provider/write-append-not-supported.error", fileName);
+            if (attached)
+            {
+                // Fix up state
+                injectType(newType);
+
+                removeChildrenCache();
+                // children = EMPTY_FILE_ARRAY;
+
+                // Notify subclass
+                onChange();
+            }
+
+            // Notify parent that its child list may no longer be valid
+            notifyParent(this.getName(), newType);
+
+            // Notify the file system
+            fs.fireFileCreated(this);
         }
+    }
 
-        if (getType() == FileType.IMAGINARY)
+    /**
+     * Called when this file is deleted.  Updates cached info and notifies
+     * subclasses, parent and file system.
+     * @throws Exception if an error occurs.
+     */
+    protected void handleDelete() throws Exception
+    {
+        synchronized (fs)
         {

[... 760 lines stripped ...]