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 2019/08/16 20:15:40 UTC

[commons-vfs] branch master updated (28f3c22 -> 953aa40)

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/commons-vfs.git.


    from 28f3c22  Slot for next release.
     new cb5325a  Sort members.
     new 5aaa0b5  Sort members.
     new 953aa40  [VFS-726]getInputStream(int bufferSize) on SftpFileObject effectively ig nores buffer size.

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../commons/vfs2/provider/mime/MimeFileObject.java |  520 +--
 .../commons/vfs2/provider/smb/SmbFileObject.java   |  450 +--
 .../commons/vfs2/provider/AbstractFileObject.java  | 3759 ++++++++++----------
 .../commons/vfs2/provider/DefaultFileContent.java  | 1625 +++++----
 .../commons/vfs2/provider/DelegateFileObject.java  |  830 ++---
 .../vfs2/provider/bzip2/Bzip2FileObject.java       |    4 +-
 .../vfs2/provider/ftp/FTPClientWrapper.java        |  571 +--
 .../commons/vfs2/provider/ftp/FtpClient.java       |  137 +-
 .../commons/vfs2/provider/ftp/FtpFileObject.java   | 1259 +++----
 .../commons/vfs2/provider/gzip/GzipFileObject.java |    4 +-
 .../commons/vfs2/provider/hdfs/HdfsFileObject.java |  522 +--
 .../commons/vfs2/provider/http/HttpFileObject.java |  499 +--
 .../vfs2/provider/http4/Http4FileObject.java       |  460 +--
 .../MonitoredHttpResponseContentInputStream.java   |   93 +-
 .../commons/vfs2/provider/local/LocalFile.java     |    2 +-
 .../commons/vfs2/provider/ram/RamFileObject.java   |  508 +--
 .../commons/vfs2/provider/sftp/SftpFileObject.java | 1042 +++---
 .../commons/vfs2/provider/tar/TarFileObject.java   |    2 +-
 .../commons/vfs2/provider/url/UrlFileObject.java   |  284 +-
 .../commons/vfs2/provider/zip/ZipFileObject.java   |  324 +-
 src/changes/changes.xml                            |    3 +
 21 files changed, 6489 insertions(+), 6409 deletions(-)


[commons-vfs] 01/03: Sort members.

Posted by gg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-vfs.git

commit cb5325abc31c5e397b7113f2e0f6455eda2e654b
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Fri Aug 16 11:56:06 2019 -0700

    Sort members.
---
 .../commons/vfs2/provider/ftp/FtpClient.java       | 128 ++++++++++-----------
 1 file changed, 64 insertions(+), 64 deletions(-)

diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/ftp/FtpClient.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/ftp/FtpClient.java
index 90c6c91..794e1ee 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/ftp/FtpClient.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/ftp/FtpClient.java
@@ -1,64 +1,64 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.vfs2.provider.ftp;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import org.apache.commons.net.ftp.FTPFile;
-import org.apache.commons.net.ftp.FTPReply;
-import org.apache.commons.vfs2.FileSystemException;
-
-/**
- * What VFS expects from an FTP client to provide.
- */
-public interface FtpClient {
-
-    boolean isConnected() throws FileSystemException;
-
-    void disconnect() throws IOException;
-
-    FTPFile[] listFiles(String relPath) throws IOException;
-
-    boolean removeDirectory(String relPath) throws IOException;
-
-    boolean deleteFile(String relPath) throws IOException;
-
-    boolean rename(String oldName, String newName) throws IOException;
-
-    boolean makeDirectory(String relPath) throws IOException;
-
-    boolean completePendingCommand() throws IOException;
-
-    InputStream retrieveFileStream(String relPath) throws IOException;
-
-    InputStream retrieveFileStream(String relPath, long restartOffset) throws IOException;
-
-    OutputStream appendFileStream(String relPath) throws IOException;
-
-    OutputStream storeFileStream(String relPath) throws IOException;
-
-    boolean abort() throws IOException;
-
-    String getReplyString() throws IOException;
-
-    default int getReplyCode() throws IOException {
-        return FTPReply.COMMAND_OK;
-    }
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.vfs2.provider.ftp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.commons.net.ftp.FTPFile;
+import org.apache.commons.net.ftp.FTPReply;
+import org.apache.commons.vfs2.FileSystemException;
+
+/**
+ * What VFS expects from an FTP client to provide.
+ */
+public interface FtpClient {
+
+    boolean abort() throws IOException;
+
+    OutputStream appendFileStream(String relPath) throws IOException;
+
+    boolean completePendingCommand() throws IOException;
+
+    boolean deleteFile(String relPath) throws IOException;
+
+    void disconnect() throws IOException;
+
+    default int getReplyCode() throws IOException {
+        return FTPReply.COMMAND_OK;
+    }
+
+    String getReplyString() throws IOException;
+
+    boolean isConnected() throws FileSystemException;
+
+    FTPFile[] listFiles(String relPath) throws IOException;
+
+    boolean makeDirectory(String relPath) throws IOException;
+
+    boolean removeDirectory(String relPath) throws IOException;
+
+    boolean rename(String oldName, String newName) throws IOException;
+
+    InputStream retrieveFileStream(String relPath) throws IOException;
+
+    InputStream retrieveFileStream(String relPath, long restartOffset) throws IOException;
+
+    OutputStream storeFileStream(String relPath) throws IOException;
+
+}


[commons-vfs] 03/03: [VFS-726]getInputStream(int bufferSize) on SftpFileObject effectively ig nores buffer size.

Posted by gg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-vfs.git

commit 953aa407df78f0d10da54e29323d404cda3e4f6b
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Fri Aug 16 13:15:34 2019 -0700

    [VFS-726]getInputStream(int bufferSize) on SftpFileObject effectively ig
    nores buffer size.
---
 .../commons/vfs2/provider/mime/MimeFileObject.java |  520 +--
 .../commons/vfs2/provider/smb/SmbFileObject.java   |  450 +--
 .../commons/vfs2/provider/AbstractFileObject.java  | 3759 ++++++++++----------
 .../commons/vfs2/provider/DefaultFileContent.java  | 1625 +++++----
 .../commons/vfs2/provider/DelegateFileObject.java  |  830 ++---
 .../vfs2/provider/bzip2/Bzip2FileObject.java       |    4 +-
 .../vfs2/provider/ftp/FTPClientWrapper.java        |   19 +
 .../commons/vfs2/provider/ftp/FtpClient.java       |    9 +
 .../commons/vfs2/provider/ftp/FtpFileObject.java   | 1259 +++----
 .../commons/vfs2/provider/gzip/GzipFileObject.java |    4 +-
 .../commons/vfs2/provider/hdfs/HdfsFileObject.java |  522 +--
 .../commons/vfs2/provider/http/HttpFileObject.java |  499 +--
 .../vfs2/provider/http4/Http4FileObject.java       |  460 +--
 .../MonitoredHttpResponseContentInputStream.java   |   93 +-
 .../commons/vfs2/provider/local/LocalFile.java     |    2 +-
 .../commons/vfs2/provider/ram/RamFileObject.java   |  508 +--
 .../commons/vfs2/provider/sftp/SftpFileObject.java | 1042 +++---
 .../commons/vfs2/provider/tar/TarFileObject.java   |    2 +-
 .../commons/vfs2/provider/url/UrlFileObject.java   |  284 +-
 .../commons/vfs2/provider/zip/ZipFileObject.java   |  324 +-
 src/changes/changes.xml                            |    3 +
 21 files changed, 6149 insertions(+), 6069 deletions(-)

diff --git a/commons-vfs2-sandbox/src/main/java/org/apache/commons/vfs2/provider/mime/MimeFileObject.java b/commons-vfs2-sandbox/src/main/java/org/apache/commons/vfs2/provider/mime/MimeFileObject.java
index 8a91e72..64314b7 100644
--- a/commons-vfs2-sandbox/src/main/java/org/apache/commons/vfs2/provider/mime/MimeFileObject.java
+++ b/commons-vfs2-sandbox/src/main/java/org/apache/commons/vfs2/provider/mime/MimeFileObject.java
@@ -1,260 +1,260 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.vfs2.provider.mime;
-
-import java.io.ByteArrayInputStream;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.Map;
-
-import javax.mail.Header;
-import javax.mail.Message;
-import javax.mail.MessagingException;
-import javax.mail.Multipart;
-import javax.mail.Part;
-import javax.mail.internet.MimeMultipart;
-
-import org.apache.commons.vfs2.FileContentInfoFactory;
-import org.apache.commons.vfs2.FileObject;
-import org.apache.commons.vfs2.FileSystemException;
-import org.apache.commons.vfs2.FileType;
-import org.apache.commons.vfs2.NameScope;
-import org.apache.commons.vfs2.provider.AbstractFileName;
-import org.apache.commons.vfs2.provider.AbstractFileObject;
-import org.apache.commons.vfs2.provider.UriParser;
-import org.apache.commons.vfs2.util.FileObjectUtils;
-
-/**
- * A part of a MIME message.
- */
-public class MimeFileObject extends AbstractFileObject<MimeFileSystem> implements FileObject {
-    private Part part;
-    private Map<String, Object> attributeMap;
-
-    protected MimeFileObject(final AbstractFileName name, final Part part, final MimeFileSystem fileSystem)
-            throws FileSystemException {
-        super(name, fileSystem);
-        setPart(part);
-    }
-
-    /**
-     * Attaches this file object to its file resource.
-     */
-    @Override
-    protected void doAttach() throws Exception {
-        if (part == null) {
-            if (!getName().equals(getFileSystem().getRootName())) {
-                final MimeFileObject foParent = (MimeFileObject) FileObjectUtils.getAbstractFileObject(getParent());
-                setPart(foParent.findPart(getName().getBaseName()));
-                return;
-            }
-
-            setPart(((MimeFileSystem) getFileSystem()).createCommunicationLink());
-        }
-    }
-
-    private Part findPart(final String partName) throws Exception {
-        if (getType() == FileType.IMAGINARY) {
-            // not existent
-            return null;
-        }
-
-        if (isMultipart()) {
-            final Multipart multipart = (Multipart) part.getContent();
-            if (partName.startsWith(MimeFileSystem.NULL_BP_NAME)) {
-                final int partNumber = Integer.parseInt(partName.substring(MimeFileSystem.NULL_BP_NAME.length()), 10);
-                if (partNumber < 0 || partNumber + 1 > multipart.getCount()) {
-                    // non existent
-                    return null;
-                }
-
-                return multipart.getBodyPart(partNumber);
-            }
-
-            for (int i = 0; i < multipart.getCount(); i++) {
-                final Part childPart = multipart.getBodyPart(i);
-                if (partName.equals(childPart.getFileName())) {
-                    return childPart;
-                }
-            }
-        }
-
-        return null;
-    }
-
-    @Override
-    protected void doDetach() throws Exception {
-    }
-
-    /**
-     * Determines the type of the file, returns null if the file does not exist.
-     */
-    @Override
-    protected FileType doGetType() throws Exception {
-        if (part == null) {
-            return FileType.IMAGINARY;
-        }
-
-        if (isMultipart()) {
-            // we cant have children ...
-            return FileType.FILE_OR_FOLDER;
-        }
-
-        return FileType.FILE;
-    }
-
-    @Override
-    protected String[] doListChildren() throws Exception {
-        return null;
-    }
-
-    /**
-     * Lists the children of the file. Is only called if {@link #doGetType} returns
-     * {@link org.apache.commons.vfs2.FileType#FOLDER}.
-     */
-    @Override
-    protected FileObject[] doListChildrenResolved() throws Exception {
-        if (part == null) {
-            return null;
-        }
-
-        final List<MimeFileObject> vfs = new ArrayList<>();
-        if (isMultipart()) {
-            final Object container = part.getContent();
-            if (container instanceof Multipart) {
-                final Multipart multipart = (Multipart) container;
-
-                for (int i = 0; i < multipart.getCount(); i++) {
-                    final Part part = multipart.getBodyPart(i);
-
-                    String filename = UriParser.encode(part.getFileName());
-                    if (filename == null) {
-                        filename = MimeFileSystem.NULL_BP_NAME + i;
-                    }
-
-                    final MimeFileObject fo = (MimeFileObject) FileObjectUtils
-                            .getAbstractFileObject(getFileSystem().resolveFile(getFileSystem().getFileSystemManager()
-                                    .resolveName(getName(), filename, NameScope.CHILD)));
-                    fo.setPart(part);
-                    vfs.add(fo);
-                }
-            }
-        }
-
-        return vfs.toArray(new MimeFileObject[vfs.size()]);
-    }
-
-    private void setPart(final Part part) {
-        this.part = part;
-        this.attributeMap = null;
-    }
-
-    /**
-     * Returns the size of the file content (in bytes).
-     */
-    @Override
-    protected long doGetContentSize() throws Exception {
-        return part.getSize();
-    }
-
-    /**
-     * Returns the last modified time of this file.
-     */
-    @Override
-    protected long doGetLastModifiedTime() throws Exception {
-        final Message mm = getMessage();
-        if (mm == null) {
-            return -1;
-        }
-        if (mm.getSentDate() != null) {
-            return mm.getSentDate().getTime();
-        }
-        if (mm.getReceivedDate() != null) {
-            mm.getReceivedDate();
-        }
-        return 0;
-    }
-
-    private Message getMessage() throws FileSystemException {
-        if (part instanceof Message) {
-            return (Message) part;
-        }
-
-        return ((MimeFileObject) FileObjectUtils.getAbstractFileObject(getParent())).getMessage();
-    }
-
-    /**
-     * Creates an input stream to read the file content from.
-     */
-    @Override
-    protected InputStream doGetInputStream() throws Exception {
-        if (isMultipart()) {
-            // deliver the preamble as the only content
-
-            final String preamble = ((MimeMultipart) part.getContent()).getPreamble();
-            if (preamble == null) {
-                return new ByteArrayInputStream(new byte[] {});
-            }
-            return new ByteArrayInputStream(preamble.getBytes(MimeFileSystem.PREAMBLE_CHARSET));
-        }
-
-        return part.getInputStream();
-    }
-
-    boolean isMultipart() throws MessagingException {
-        return part.getContentType() != null && part.getContentType().startsWith("multipart/");
-    }
-
-    @Override
-    protected FileContentInfoFactory getFileContentInfoFactory() {
-        return new MimeFileContentInfoFactory();
-    }
-
-    protected Part getPart() {
-        return part;
-    }
-
-    /**
-     * Returns all headers of this part.
-     * <p>
-     * The map key is a java.lang.String and the value is a:
-     * <ul>
-     * <li>{@code java.lang.Strings} for single entries or a</li>
-     * <li>{@code java.utils.List<java.lang.Strings>} for entries with multiple values</li>
-     * </ul>
-     */
-    @Override
-    protected Map<String, Object> doGetAttributes() throws Exception {
-        if (attributeMap == null) {
-            if (part != null) {
-                attributeMap = new MimeAttributesMap(part);
-            } else {
-                attributeMap = Collections.emptyMap();
-            }
-        }
-
-        return attributeMap;
-    }
-
-    @SuppressWarnings("unchecked") // Javadoc says Part returns Header
-    protected Enumeration<Header> getAllHeaders() throws MessagingException {
-        return part.getAllHeaders();
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.vfs2.provider.mime;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+
+import javax.mail.Header;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.Part;
+import javax.mail.internet.MimeMultipart;
+
+import org.apache.commons.vfs2.FileContentInfoFactory;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileType;
+import org.apache.commons.vfs2.NameScope;
+import org.apache.commons.vfs2.provider.AbstractFileName;
+import org.apache.commons.vfs2.provider.AbstractFileObject;
+import org.apache.commons.vfs2.provider.UriParser;
+import org.apache.commons.vfs2.util.FileObjectUtils;
+
+/**
+ * A part of a MIME message.
+ */
+public class MimeFileObject extends AbstractFileObject<MimeFileSystem> implements FileObject {
+    private Part part;
+    private Map<String, Object> attributeMap;
+
+    protected MimeFileObject(final AbstractFileName name, final Part part, final MimeFileSystem fileSystem)
+            throws FileSystemException {
+        super(name, fileSystem);
+        setPart(part);
+    }
+
+    /**
+     * Attaches this file object to its file resource.
+     */
+    @Override
+    protected void doAttach() throws Exception {
+        if (part == null) {
+            if (!getName().equals(getFileSystem().getRootName())) {
+                final MimeFileObject foParent = (MimeFileObject) FileObjectUtils.getAbstractFileObject(getParent());
+                setPart(foParent.findPart(getName().getBaseName()));
+                return;
+            }
+
+            setPart(((MimeFileSystem) getFileSystem()).createCommunicationLink());
+        }
+    }
+
+    private Part findPart(final String partName) throws Exception {
+        if (getType() == FileType.IMAGINARY) {
+            // not existent
+            return null;
+        }
+
+        if (isMultipart()) {
+            final Multipart multipart = (Multipart) part.getContent();
+            if (partName.startsWith(MimeFileSystem.NULL_BP_NAME)) {
+                final int partNumber = Integer.parseInt(partName.substring(MimeFileSystem.NULL_BP_NAME.length()), 10);
+                if (partNumber < 0 || partNumber + 1 > multipart.getCount()) {
+                    // non existent
+                    return null;
+                }
+
+                return multipart.getBodyPart(partNumber);
+            }
+
+            for (int i = 0; i < multipart.getCount(); i++) {
+                final Part childPart = multipart.getBodyPart(i);
+                if (partName.equals(childPart.getFileName())) {
+                    return childPart;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    @Override
+    protected void doDetach() throws Exception {
+    }
+
+    /**
+     * Determines the type of the file, returns null if the file does not exist.
+     */
+    @Override
+    protected FileType doGetType() throws Exception {
+        if (part == null) {
+            return FileType.IMAGINARY;
+        }
+
+        if (isMultipart()) {
+            // we cant have children ...
+            return FileType.FILE_OR_FOLDER;
+        }
+
+        return FileType.FILE;
+    }
+
+    @Override
+    protected String[] doListChildren() throws Exception {
+        return null;
+    }
+
+    /**
+     * Lists the children of the file. Is only called if {@link #doGetType} returns
+     * {@link org.apache.commons.vfs2.FileType#FOLDER}.
+     */
+    @Override
+    protected FileObject[] doListChildrenResolved() throws Exception {
+        if (part == null) {
+            return null;
+        }
+
+        final List<MimeFileObject> vfs = new ArrayList<>();
+        if (isMultipart()) {
+            final Object container = part.getContent();
+            if (container instanceof Multipart) {
+                final Multipart multipart = (Multipart) container;
+
+                for (int i = 0; i < multipart.getCount(); i++) {
+                    final Part part = multipart.getBodyPart(i);
+
+                    String filename = UriParser.encode(part.getFileName());
+                    if (filename == null) {
+                        filename = MimeFileSystem.NULL_BP_NAME + i;
+                    }
+
+                    final MimeFileObject fo = (MimeFileObject) FileObjectUtils
+                            .getAbstractFileObject(getFileSystem().resolveFile(getFileSystem().getFileSystemManager()
+                                    .resolveName(getName(), filename, NameScope.CHILD)));
+                    fo.setPart(part);
+                    vfs.add(fo);
+                }
+            }
+        }
+
+        return vfs.toArray(new MimeFileObject[vfs.size()]);
+    }
+
+    private void setPart(final Part part) {
+        this.part = part;
+        this.attributeMap = null;
+    }
+
+    /**
+     * Returns the size of the file content (in bytes).
+     */
+    @Override
+    protected long doGetContentSize() throws Exception {
+        return part.getSize();
+    }
+
+    /**
+     * Returns the last modified time of this file.
+     */
+    @Override
+    protected long doGetLastModifiedTime() throws Exception {
+        final Message mm = getMessage();
+        if (mm == null) {
+            return -1;
+        }
+        if (mm.getSentDate() != null) {
+            return mm.getSentDate().getTime();
+        }
+        if (mm.getReceivedDate() != null) {
+            mm.getReceivedDate();
+        }
+        return 0;
+    }
+
+    private Message getMessage() throws FileSystemException {
+        if (part instanceof Message) {
+            return (Message) part;
+        }
+
+        return ((MimeFileObject) FileObjectUtils.getAbstractFileObject(getParent())).getMessage();
+    }
+
+    /**
+     * Creates an input stream to read the file content from.
+     */
+    @Override
+    protected InputStream doGetInputStream(int bufferSize) throws Exception {
+        if (isMultipart()) {
+            // deliver the preamble as the only content
+
+            final String preamble = ((MimeMultipart) part.getContent()).getPreamble();
+            if (preamble == null) {
+                return new ByteArrayInputStream(new byte[] {});
+            }
+            return new ByteArrayInputStream(preamble.getBytes(MimeFileSystem.PREAMBLE_CHARSET));
+        }
+
+        return part.getInputStream();
+    }
+
+    boolean isMultipart() throws MessagingException {
+        return part.getContentType() != null && part.getContentType().startsWith("multipart/");
+    }
+
+    @Override
+    protected FileContentInfoFactory getFileContentInfoFactory() {
+        return new MimeFileContentInfoFactory();
+    }
+
+    protected Part getPart() {
+        return part;
+    }
+
+    /**
+     * Returns all headers of this part.
+     * <p>
+     * The map key is a java.lang.String and the value is a:
+     * <ul>
+     * <li>{@code java.lang.Strings} for single entries or a</li>
+     * <li>{@code java.utils.List<java.lang.Strings>} for entries with multiple values</li>
+     * </ul>
+     */
+    @Override
+    protected Map<String, Object> doGetAttributes() throws Exception {
+        if (attributeMap == null) {
+            if (part != null) {
+                attributeMap = new MimeAttributesMap(part);
+            } else {
+                attributeMap = Collections.emptyMap();
+            }
+        }
+
+        return attributeMap;
+    }
+
+    @SuppressWarnings("unchecked") // Javadoc says Part returns Header
+    protected Enumeration<Header> getAllHeaders() throws MessagingException {
+        return part.getAllHeaders();
+    }
+}
diff --git a/commons-vfs2-sandbox/src/main/java/org/apache/commons/vfs2/provider/smb/SmbFileObject.java b/commons-vfs2-sandbox/src/main/java/org/apache/commons/vfs2/provider/smb/SmbFileObject.java
index 7a9dc40..88d488f 100644
--- a/commons-vfs2-sandbox/src/main/java/org/apache/commons/vfs2/provider/smb/SmbFileObject.java
+++ b/commons-vfs2-sandbox/src/main/java/org/apache/commons/vfs2/provider/smb/SmbFileObject.java
@@ -1,225 +1,225 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.vfs2.provider.smb;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.MalformedURLException;
-
-import jcifs.smb.NtlmPasswordAuthentication;
-import jcifs.smb.SmbException;
-import jcifs.smb.SmbFile;
-import jcifs.smb.SmbFileInputStream;
-import jcifs.smb.SmbFileOutputStream;
-
-import org.apache.commons.vfs2.FileName;
-import org.apache.commons.vfs2.FileObject;
-import org.apache.commons.vfs2.FileSystemException;
-import org.apache.commons.vfs2.FileType;
-import org.apache.commons.vfs2.FileTypeHasNoContentException;
-import org.apache.commons.vfs2.RandomAccessContent;
-import org.apache.commons.vfs2.UserAuthenticationData;
-import org.apache.commons.vfs2.provider.AbstractFileName;
-import org.apache.commons.vfs2.provider.AbstractFileObject;
-import org.apache.commons.vfs2.provider.UriParser;
-import org.apache.commons.vfs2.util.RandomAccessMode;
-import org.apache.commons.vfs2.util.UserAuthenticatorUtils;
-
-/**
- * A file in an SMB file system.
- */
-public class SmbFileObject extends AbstractFileObject<SmbFileSystem> {
-    // private final String fileName;
-    private SmbFile file;
-
-    protected SmbFileObject(final AbstractFileName name, final SmbFileSystem fileSystem) throws FileSystemException {
-        super(name, fileSystem);
-        // this.fileName = UriParser.decode(name.getURI());
-    }
-
-    /**
-     * Attaches this file object to its file resource.
-     */
-    @Override
-    protected void doAttach() throws Exception {
-        // Defer creation of the SmbFile to here
-        if (file == null) {
-            file = createSmbFile(getName());
-        }
-    }
-
-    @Override
-    protected void doDetach() throws Exception {
-        // file closed through content-streams
-        file = null;
-    }
-
-    private SmbFile createSmbFile(final FileName fileName)
-            throws MalformedURLException, SmbException, FileSystemException {
-        final SmbFileName smbFileName = (SmbFileName) fileName;
-
-        final String path = smbFileName.getUriWithoutAuth();
-
-        UserAuthenticationData authData = null;
-        SmbFile file;
-        try {
-            authData = UserAuthenticatorUtils.authenticate(getFileSystem().getFileSystemOptions(),
-                    SmbFileProvider.AUTHENTICATOR_TYPES);
-
-            NtlmPasswordAuthentication auth = null;
-            if (authData != null) {
-                auth = new NtlmPasswordAuthentication(
-                        UserAuthenticatorUtils.toString(UserAuthenticatorUtils.getData(authData,
-                                UserAuthenticationData.DOMAIN, UserAuthenticatorUtils.toChar(smbFileName.getDomain()))),
-                        UserAuthenticatorUtils
-                                .toString(UserAuthenticatorUtils.getData(authData, UserAuthenticationData.USERNAME,
-                                        UserAuthenticatorUtils.toChar(smbFileName.getUserName()))),
-                        UserAuthenticatorUtils
-                                .toString(UserAuthenticatorUtils.getData(authData, UserAuthenticationData.PASSWORD,
-                                        UserAuthenticatorUtils.toChar(smbFileName.getPassword()))));
-            }
-
-            // if auth == null SmbFile uses default credentials
-            // ("jcifs.smb.client.domain", "?"), ("jcifs.smb.client.username", "GUEST"),
-            // ("jcifs.smb.client.password", BLANK);
-            // ANONYMOUS=("","","")
-            file = new SmbFile(path, auth);
-
-            if (file.isDirectory() && !file.toString().endsWith("/")) {
-                file = new SmbFile(path + "/", auth);
-            }
-            return file;
-        } finally {
-            UserAuthenticatorUtils.cleanup(authData); // might be null
-        }
-    }
-
-    /**
-     * Determines the type of the file, returns null if the file does not exist.
-     */
-    @Override
-    protected FileType doGetType() throws Exception {
-        if (!file.exists()) {
-            return FileType.IMAGINARY;
-        } else if (file.isDirectory()) {
-            return FileType.FOLDER;
-        } else if (file.isFile()) {
-            return FileType.FILE;
-        }
-
-        throw new FileSystemException("vfs.provider.smb/get-type.error", getName());
-    }
-
-    /**
-     * Lists the children of the file. Is only called if {@link #doGetType} returns {@link FileType#FOLDER}.
-     */
-    @Override
-    protected String[] doListChildren() throws Exception {
-        // VFS-210: do not try to get listing for anything else than directories
-        if (!file.isDirectory()) {
-            return null;
-        }
-
-        return UriParser.encode(file.list());
-    }
-
-    /**
-     * Determines if this file is hidden.
-     */
-    @Override
-    protected boolean doIsHidden() throws Exception {
-        return file.isHidden();
-    }
-
-    /**
-     * Deletes the file.
-     */
-    @Override
-    protected void doDelete() throws Exception {
-        file.delete();
-    }
-
-    @Override
-    protected void doRename(final FileObject newfile) throws Exception {
-        file.renameTo(createSmbFile(newfile.getName()));
-    }
-
-    /**
-     * Creates this file as a folder.
-     */
-    @Override
-    protected void doCreateFolder() throws Exception {
-        file.mkdir();
-        file = createSmbFile(getName());
-    }
-
-    /**
-     * Returns the size of the file content (in bytes).
-     */
-    @Override
-    protected long doGetContentSize() throws Exception {
-        return file.length();
-    }
-
-    /**
-     * Returns the last modified time of this file.
-     */
-    @Override
-    protected long doGetLastModifiedTime() throws Exception {
-        return file.getLastModified();
-    }
-
-    /**
-     * Creates an input stream to read the file content from.
-     */
-    @Override
-    protected InputStream doGetInputStream() throws Exception {
-        try {
-            return new SmbFileInputStream(file);
-        } catch (final SmbException e) {
-            if (e.getNtStatus() == SmbException.NT_STATUS_NO_SUCH_FILE) {
-                throw new org.apache.commons.vfs2.FileNotFoundException(getName());
-            } else if (file.isDirectory()) {
-                throw new FileTypeHasNoContentException(getName());
-            }
-
-            throw e;
-        }
-    }
-
-    /**
-     * Creates an output stream to write the file content to.
-     */
-    @Override
-    protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception {
-        return new SmbFileOutputStream(file, bAppend);
-    }
-
-    /**
-     * random access
-     */
-    @Override
-    protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception {
-        return new SmbFileRandomAccessContent(file, mode);
-    }
-
-    @Override
-    protected boolean doSetLastModifiedTime(final long modtime) throws Exception {
-        file.setLastModified(modtime);
-        return true;
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.vfs2.provider.smb;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+
+import jcifs.smb.NtlmPasswordAuthentication;
+import jcifs.smb.SmbException;
+import jcifs.smb.SmbFile;
+import jcifs.smb.SmbFileInputStream;
+import jcifs.smb.SmbFileOutputStream;
+
+import org.apache.commons.vfs2.FileName;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileType;
+import org.apache.commons.vfs2.FileTypeHasNoContentException;
+import org.apache.commons.vfs2.RandomAccessContent;
+import org.apache.commons.vfs2.UserAuthenticationData;
+import org.apache.commons.vfs2.provider.AbstractFileName;
+import org.apache.commons.vfs2.provider.AbstractFileObject;
+import org.apache.commons.vfs2.provider.UriParser;
+import org.apache.commons.vfs2.util.RandomAccessMode;
+import org.apache.commons.vfs2.util.UserAuthenticatorUtils;
+
+/**
+ * A file in an SMB file system.
+ */
+public class SmbFileObject extends AbstractFileObject<SmbFileSystem> {
+    // private final String fileName;
+    private SmbFile file;
+
+    protected SmbFileObject(final AbstractFileName name, final SmbFileSystem fileSystem) throws FileSystemException {
+        super(name, fileSystem);
+        // this.fileName = UriParser.decode(name.getURI());
+    }
+
+    /**
+     * Attaches this file object to its file resource.
+     */
+    @Override
+    protected void doAttach() throws Exception {
+        // Defer creation of the SmbFile to here
+        if (file == null) {
+            file = createSmbFile(getName());
+        }
+    }
+
+    @Override
+    protected void doDetach() throws Exception {
+        // file closed through content-streams
+        file = null;
+    }
+
+    private SmbFile createSmbFile(final FileName fileName)
+            throws MalformedURLException, SmbException, FileSystemException {
+        final SmbFileName smbFileName = (SmbFileName) fileName;
+
+        final String path = smbFileName.getUriWithoutAuth();
+
+        UserAuthenticationData authData = null;
+        SmbFile file;
+        try {
+            authData = UserAuthenticatorUtils.authenticate(getFileSystem().getFileSystemOptions(),
+                    SmbFileProvider.AUTHENTICATOR_TYPES);
+
+            NtlmPasswordAuthentication auth = null;
+            if (authData != null) {
+                auth = new NtlmPasswordAuthentication(
+                        UserAuthenticatorUtils.toString(UserAuthenticatorUtils.getData(authData,
+                                UserAuthenticationData.DOMAIN, UserAuthenticatorUtils.toChar(smbFileName.getDomain()))),
+                        UserAuthenticatorUtils
+                                .toString(UserAuthenticatorUtils.getData(authData, UserAuthenticationData.USERNAME,
+                                        UserAuthenticatorUtils.toChar(smbFileName.getUserName()))),
+                        UserAuthenticatorUtils
+                                .toString(UserAuthenticatorUtils.getData(authData, UserAuthenticationData.PASSWORD,
+                                        UserAuthenticatorUtils.toChar(smbFileName.getPassword()))));
+            }
+
+            // if auth == null SmbFile uses default credentials
+            // ("jcifs.smb.client.domain", "?"), ("jcifs.smb.client.username", "GUEST"),
+            // ("jcifs.smb.client.password", BLANK);
+            // ANONYMOUS=("","","")
+            file = new SmbFile(path, auth);
+
+            if (file.isDirectory() && !file.toString().endsWith("/")) {
+                file = new SmbFile(path + "/", auth);
+            }
+            return file;
+        } finally {
+            UserAuthenticatorUtils.cleanup(authData); // might be null
+        }
+    }
+
+    /**
+     * Determines the type of the file, returns null if the file does not exist.
+     */
+    @Override
+    protected FileType doGetType() throws Exception {
+        if (!file.exists()) {
+            return FileType.IMAGINARY;
+        } else if (file.isDirectory()) {
+            return FileType.FOLDER;
+        } else if (file.isFile()) {
+            return FileType.FILE;
+        }
+
+        throw new FileSystemException("vfs.provider.smb/get-type.error", getName());
+    }
+
+    /**
+     * Lists the children of the file. Is only called if {@link #doGetType} returns {@link FileType#FOLDER}.
+     */
+    @Override
+    protected String[] doListChildren() throws Exception {
+        // VFS-210: do not try to get listing for anything else than directories
+        if (!file.isDirectory()) {
+            return null;
+        }
+
+        return UriParser.encode(file.list());
+    }
+
+    /**
+     * Determines if this file is hidden.
+     */
+    @Override
+    protected boolean doIsHidden() throws Exception {
+        return file.isHidden();
+    }
+
+    /**
+     * Deletes the file.
+     */
+    @Override
+    protected void doDelete() throws Exception {
+        file.delete();
+    }
+
+    @Override
+    protected void doRename(final FileObject newfile) throws Exception {
+        file.renameTo(createSmbFile(newfile.getName()));
+    }
+
+    /**
+     * Creates this file as a folder.
+     */
+    @Override
+    protected void doCreateFolder() throws Exception {
+        file.mkdir();
+        file = createSmbFile(getName());
+    }
+
+    /**
+     * Returns the size of the file content (in bytes).
+     */
+    @Override
+    protected long doGetContentSize() throws Exception {
+        return file.length();
+    }
+
+    /**
+     * Returns the last modified time of this file.
+     */
+    @Override
+    protected long doGetLastModifiedTime() throws Exception {
+        return file.getLastModified();
+    }
+
+    /**
+     * Creates an input stream to read the file content from.
+     */
+    @Override
+    protected InputStream doGetInputStream(int bufferSize) throws Exception {
+        try {
+            return new SmbFileInputStream(file);
+        } catch (final SmbException e) {
+            if (e.getNtStatus() == SmbException.NT_STATUS_NO_SUCH_FILE) {
+                throw new org.apache.commons.vfs2.FileNotFoundException(getName());
+            } else if (file.isDirectory()) {
+                throw new FileTypeHasNoContentException(getName());
+            }
+
+            throw e;
+        }
+    }
+
+    /**
+     * Creates an output stream to write the file content to.
+     */
+    @Override
+    protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception {
+        return new SmbFileOutputStream(file, bAppend);
+    }
+
+    /**
+     * random access
+     */
+    @Override
+    protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception {
+        return new SmbFileRandomAccessContent(file, mode);
+    }
+
+    @Override
+    protected boolean doSetLastModifiedTime(final long modtime) throws Exception {
+        file.setLastModified(modtime);
+        return true;
+    }
+}
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/AbstractFileObject.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/AbstractFileObject.java
index 2515849..bad8896 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/AbstractFileObject.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/AbstractFileObject.java
@@ -1,1865 +1,1894 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.vfs2.provider;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.security.AccessController;
-import java.security.PrivilegedActionException;
-import java.security.PrivilegedExceptionAction;
-import java.security.cert.Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.commons.vfs2.Capability;
-import org.apache.commons.vfs2.FileContent;
-import org.apache.commons.vfs2.FileContentInfoFactory;
-import org.apache.commons.vfs2.FileName;
-import org.apache.commons.vfs2.FileNotFolderException;
-import org.apache.commons.vfs2.FileObject;
-import org.apache.commons.vfs2.FileSelector;
-import org.apache.commons.vfs2.FileSystem;
-import org.apache.commons.vfs2.FileSystemException;
-import org.apache.commons.vfs2.FileType;
-import org.apache.commons.vfs2.FileUtil;
-import org.apache.commons.vfs2.NameScope;
-import org.apache.commons.vfs2.RandomAccessContent;
-import org.apache.commons.vfs2.Selectors;
-import org.apache.commons.vfs2.VFS;
-import org.apache.commons.vfs2.operations.DefaultFileOperations;
-import org.apache.commons.vfs2.operations.FileOperations;
-import org.apache.commons.vfs2.util.FileObjectUtils;
-import org.apache.commons.vfs2.util.RandomAccessMode;
-
-/**
- * A partial file object implementation.
- *
- * @param <AFS> An AbstractFileSystem subclass
- */
-public abstract class AbstractFileObject<AFS extends AbstractFileSystem> implements FileObject {
-
-    /*
-     * TODO - Chop this class up - move all the protected methods to several interfaces, so that structure and content
-     * can be separately overridden.
-     *
-     * <p>
-     * TODO - Check caps in methods like getChildren(), etc, and give better error messages (eg 'this file type does not
-     * support listing children', vs 'this is not a folder')
-     * </p>
-     */
-
-    private static final FileName[] EMPTY_FILE_ARRAY = {};
-
-    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 fileSystem;
-    private FileContent content;
-    // Cached info
-    private boolean attached;
-
-    private FileType type;
-    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;
-
-    /**
-     * FileServices instance.
-     */
-    private FileOperations operations;
-
-    /**
-     *
-     * @param name the file name - muse be an instance of {@link AbstractFileName}
-     * @param fileSystem the file system
-     * @throws ClassCastException if {@code name} is not an instance of {@link AbstractFileName}
-     */
-    protected AbstractFileObject(final AbstractFileName name, final AFS fileSystem) {
-        this.fileName = name;
-        this.fileSystem = fileSystem;
-        fileSystem.fileObjectHanded(this);
-    }
-
-    /**
-     * Attaches to the file.
-     *
-     * @throws FileSystemException if an error occurs.
-     */
-    private void attach() throws FileSystemException {
-        synchronized (fileSystem) {
-            if (attached) {
-                return;
-            }
-
-            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)
-
-                /*
-                 * 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);
-            }
-
-            // fs.fileAttached(this);
-        }
-    }
-
-    /**
-     * Queries the object if a simple rename to the file name of {@code newfile} is possible.
-     *
-     * @param newfile the new file name
-     * @return true if rename is possible
-     */
-    @Override
-    public boolean canRenameTo(final FileObject newfile) {
-        return fileSystem == newfile.getFileSystem();
-    }
-
-    /**
-     * 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 void childrenChanged(final FileName childName, final FileType newType) throws Exception {
-        // TODO - this may be called when not attached
-
-        if (children != null && childName != null && newType != null) {
-            // TODO - figure out if children[] can be replaced by list
-            final ArrayList<FileName> list = new ArrayList<>(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);
-    }
-
-    /**
-     * Closes this file, and its content.
-     *
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public void close() throws FileSystemException {
-        FileSystemException exc = null;
-
-        synchronized (fileSystem) {
-            // 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;
-            }
-        }
-    }
-
-    /**
-     * 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.
-     */
-    @Override
-    public int compareTo(final FileObject file) {
-        if (file == null) {
-            return 1;
-        }
-        return this.toString().compareToIgnoreCase(file.toString());
-    }
-
-    /**
-     * Copies another file to this file.
-     *
-     * @param file The FileObject to copy.
-     * @param selector The FileSelector.
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public void copyFrom(final FileObject file, final FileSelector selector) throws FileSystemException {
-        if (!FileObjectUtils.exists(file)) {
-            throw new FileSystemException("vfs.provider/copy-missing-file.error", file);
-        }
-
-        // Locate the files to copy across
-        final ArrayList<FileObject> files = new ArrayList<>();
-        file.findFiles(selector, false, files);
-
-        // 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);
-
-            // Clean up the destination file, if necessary
-            if (FileObjectUtils.exists(destFile) && 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();
-            }
-
-            // 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, if it does not exist.
-     *
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public void createFile() throws FileSystemException {
-        synchronized (fileSystem) {
-            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) {
-                throw new FileSystemException("vfs.provider/create-file.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.
-     */
-    @Override
-    public void createFolder() throws FileSystemException {
-        synchronized (fileSystem) {
-            // 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 hierarchy 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);
-            }
-        }
-    }
-
-    /**
-     * Deletes this file.
-     * <p>
-     * TODO - This will not fail if this is a non-empty folder.
-     * </p>
-     *
-     * @return true if this object has been deleted
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public boolean delete() throws FileSystemException {
-        return delete(Selectors.SELECT_SELF) > 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.
-     */
-    @Override
-    public int delete(final FileSelector selector) throws FileSystemException {
-        int nuofDeleted = 0;
-
-        /*
-         * VFS-210 if (getType() == FileType.IMAGINARY) { // File does not exist return nuofDeleted; }
-         */
-
-        // Locate all the files to delete
-        final ArrayList<FileObject> files = new ArrayList<>();
-        findFiles(selector, true, files);
-
-        // Delete 'em
-        final int count = files.size();
-        for (int i = 0; i < count; i++) {
-            final AbstractFileObject file = FileObjectUtils.getAbstractFileObject(files.get(i));
-            // file.attach();
-
-            // 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;
-            }
-
-            // Delete the file
-            if (file.deleteSelf()) {
-                nuofDeleted++;
-            }
-        }
-
-        return nuofDeleted;
-    }
-
-    /**
-     * 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 int deleteAll() throws FileSystemException {
-        return this.delete(Selectors.SELECT_ALL);
-    }
-
-    /**
-     * Deletes this file, once all its children have been deleted
-     *
-     * @return true if this file has been deleted
-     * @throws FileSystemException if an error occurs.
-     */
-    private boolean deleteSelf() throws FileSystemException {
-        synchronized (fileSystem) {
-            // Its possible to delete a read-only file if you have write-execute access to the directory
-
-            /*
-             * 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;
-        }
-    }
-
-    /**
-     * Detaches this file, invalidating all cached info. This will force a call to {@link #doAttach} next time this file
-     * is used.
-     *
-     * @throws Exception if an error occurs.
-     */
-    private void detach() throws Exception {
-        synchronized (fileSystem) {
-            if (attached) {
-                try {
-                    doDetach();
-                } finally {
-                    attached = false;
-                    setFileType(null);
-                    parent = null;
-
-                    // fs.fileDetached(this);
-
-                    removeChildrenCache();
-                    // children = null;
-                }
-            }
-        }
-    }
-
-    /**
-     * Attaches this file object to its file resource.
-     * <p>
-     * This method is called before any of the doBlah() or onBlah() methods. Sub-classes can use this method to perform
-     * lazy initialisation.
-     * </p>
-     * <p>
-     * This implementation does nothing.
-     * </p>
-     *
-     * @throws Exception if an error occurs.
-     */
-    protected void doAttach() throws Exception {
-        // noop
-    }
-
-    /**
-     * Create a FileContent implementation.
-     *
-     * @return The FileContent.
-     * @throws FileSystemException if an error occurs.
-     * @since 2.0
-     */
-    protected FileContent doCreateFileContent() throws FileSystemException {
-        return new DefaultFileContent(this, getFileContentInfoFactory());
-    }
-
-    /**
-     * Creates this file as a folder. Is only called when:
-     * <ul>
-     * <li>{@link #doGetType} returns {@link FileType#IMAGINARY}.</li>
-     * <li>The parent folder exists and is writeable, or this file is the root of the file system.</li>
-     * </ul>
-     * This implementation throws an exception.
-     *
-     * @throws Exception if an error occurs.
-     */
-    protected void doCreateFolder() throws Exception {
-        throw new FileSystemException("vfs.provider/create-folder-not-supported.error");
-    }
-
-    /**
-     * Deletes the file. Is only called when:
-     * <ul>
-     * <li>{@link #doGetType} does not return {@link FileType#IMAGINARY}.</li>
-     * <li>{@link #doIsWriteable} returns true.</li>
-     * <li>This file has no children, if a folder.</li>
-     * </ul>
-     * 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");
-    }
-
-    /**
-     * Detaches this file object from its file resource.
-     * <p>
-     * Called when this file is closed. Note that the file object may be reused later, so should be able to be
-     * reattached.
-     * </p>
-     * <p>
-     * This implementation does nothing.
-     * </p>
-     *
-     * @throws Exception if an error occurs.
-     */
-    protected void doDetach() throws Exception {
-        // noop
-    }
-
-    /**
-     * 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.
-     * </p>
-     *
-     * @return The attributes of the file.
-     * @throws Exception if an error occurs.
-     */
-    protected Map<String, Object> doGetAttributes() throws Exception {
-        return Collections.emptyMap();
-    }
-
-    /**
-     * 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.
-     * </p>
-     *
-     * @return The certificates used to sign the file.
-     * @throws Exception if an error occurs.
-     */
-    protected Certificate[] doGetCertificates() throws Exception {
-        return null;
-    }
-
-    /**
-     * 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>
-     * 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.
-     * </p>
-     *
-     * @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.
-     * </p>
-     *
-     * @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");
-    }
-
-    /**
-     * 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>
-     * It is guaranteed that there are no open stream (input or output) for this file when this method is called.
-     * <p>
-     * The returned stream does not have to be buffered.
-     * </p>
-     * <p>
-     * This implementation throws an exception.
-     * </p>
-     *
-     * @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");
-    }
-
-    /**
-     * Creates access to the file for random i/o. Is only called if {@link #doGetType} returns {@link FileType#FILE}.
-     * <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");
-    }
-
-    /**
-     * 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;
-
-    /**
-     * Determines if this file is executable. Is only called if {@link #doGetType} does not return
-     * {@link FileType#IMAGINARY}.
-     * <p>
-     * This implementation always returns false.
-     * </p>
-     *
-     * @return true if the file is executable, false otherwise.
-     * @throws Exception if an error occurs.
-     */
-    protected boolean doIsExecutable() throws Exception {
-        return false;
-    }
-
-    /**
-     * Determines if this file is hidden. Is only called if {@link #doGetType} does not return
-     * {@link FileType#IMAGINARY}.
-     * <p>
-     * This implementation always returns false.
-     * </p>
-     *
-     * @return true if the file is hidden, false otherwise.
-     * @throws Exception if an error occurs.
-     */
-    protected boolean doIsHidden() throws Exception {
-        return false;
-    }
-
-    /**
-     * 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.
-     * </p>
-     *
-     * @return true if the file is readable, false otherwise.
-     * @throws Exception if an error occurs.
-     */
-    protected boolean doIsReadable() throws Exception {
-        return true;
-    }
-
-    /**
-     * Checks if this fileObject is the same file as {@code destFile} just with a different name. E.g. for case
-     * insensitive file systems 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;
-    }
-
-    /**
-     * Determines if this file is a symbolic link. Is only called if {@link #doGetType} does not return
-     * {@link FileType#IMAGINARY}.
-     * <p>
-     * This implementation always returns false.
-     * </p>
-     *
-     * @return true if the file is readable, false otherwise.
-     * @throws Exception if an error occurs.
-     * @since 2.4
-     */
-    protected boolean doIsSymbolicLink() throws Exception {
-        return false;
-    }
-
-    /**
-     * 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.
-     * </p>
-     *
-     * @return true if the file is writable.
-     * @throws Exception if an error occurs.
-     */
-    protected boolean doIsWriteable() throws Exception {
-        return true;
-    }
-
-    /**
-     * 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.
-     *
-     * @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;
-
-    /**
-     * Lists the children of this file.
-     * <p>
-     * Is only called if {@link #doGetType} returns {@link FileType#FOLDER}.
-     * </p>
-     * <p>
-     * The return value of this method is cached, so the implementation can be expensive.
-     * Other than {@code doListChildren} you could return FileObject's to e.g. reinitialize the type of the file.
-     * </p>
-     * <p>
-     * (Introduced for Webdav: "permission denied on resource" during getType())
-     * </p>
-     *
-     * @return The children of this FileObject.
-     * @throws Exception if an error occurs.
-     */
-    protected FileObject[] doListChildrenResolved() throws Exception {
-        return null;
-    }
-
-    /**
-     * Removes an attribute of this file.
-     * <p>
-     * Is only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}.
-     * </p>
-     * <p>
-     * This implementation throws an exception.
-     * </p>
-     *
-     * @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 {
-        throw new FileSystemException("vfs.provider/remove-attribute-not-supported.error");
-    }
-
-    /**
-     * Renames the file.
-     * <p>
-     * Is only called when:
-     * </p>
-     * <ul>
-     * <li>{@link #doIsWriteable} returns true.</li>
-     * </ul>
-     * <p>
-     * This implementation throws an exception.
-     * </p>
-     *
-     * @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");
-    }
-
-    /**
-     * Sets an attribute of this file.
-     * <p>
-     * Is only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}.
-     * </p>
-     * <p>
-     * This implementation throws an exception.
-     * </p>
-     *
-     * @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 {
-        throw new FileSystemException("vfs.provider/set-attribute-not-supported.error");
-    }
-
-    /**
-     * Make the file executable.
-     * <p>
-     * Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}.
-     * </p>
-     * <p>
-     * This implementation returns false.
-     * </p>
-     *
-     * @param executable 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.
-     * @throws Exception Any Exception thrown is wrapped in FileSystemException.
-     * @see #setExecutable(boolean, boolean)
-     * @since 2.1
-     */
-    protected boolean doSetExecutable(final boolean executable, final boolean ownerOnly) throws Exception {
-        return false;
-    }
-
-    /**
-     * Sets the last modified time of this file.
-     * <p>
-     * Is only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}.
-     * </p>
-     * <p>
-     * This implementation throws an exception.
-     * </p>
-     *
-     * @param modtime The last modification time.
-     * @return true if the time was set.
-     * @throws Exception Any Exception thrown is wrapped in FileSystemException.
-     */
-    protected boolean doSetLastModifiedTime(final long modtime) throws Exception {
-        throw new FileSystemException("vfs.provider/set-last-modified-not-supported.error");
-    }
-
-    /**
-     * Make the file or folder readable.
-     * <p>
-     * Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}.
-     * </p>
-     * <p>
-     * This implementation returns false.
-     * </p>
-     *
-     * @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
-     * @throws Exception Any Exception thrown is wrapped in FileSystemException.
-     * @see #setReadable(boolean, boolean)
-     * @since 2.1
-     */
-    protected boolean doSetReadable(final boolean readable, final boolean ownerOnly) throws Exception {
-        return false;
-    }
-
-    /**
-     * Make the file or folder writeable.
-     * <p>
-     * Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}.
-     * </p>
-     *
-     * @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
-     * @throws Exception Any Exception thrown is wrapped in FileSystemException.
-     * @see #setWritable(boolean, boolean)
-     * @since 2.1
-     */
-    protected boolean doSetWritable(final boolean writable, final boolean ownerOnly) throws Exception {
-        return false;
-    }
-
-    /**
-     * Called when the output stream for this file is closed.
-     *
-     * @throws Exception if an error occurs.
-     */
-    protected void endOutput() throws Exception {
-        if (getType() == FileType.IMAGINARY) {
-            // File was created
-            handleCreate(FileType.FILE);
-        } else {
-            // File has changed
-            onChange();
-        }
-    }
-
-    /**
-     * 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;
-    }
-
-    private FileName[] extractNames(final FileObject[] objects) {
-        if (objects == null) {
-            return null;
-        }
-
-        final FileName[] names = new FileName[objects.length];
-        for (int iterObjects = 0; iterObjects < objects.length; iterObjects++) {
-            names[iterObjects] = objects[iterObjects].getName();
-        }
-
-        return names;
-    }
-
-    @Override
-    protected void finalize() throws Throwable {
-        fileSystem.fileObjectDestroyed(this);
-
-        super.finalize();
-    }
-
-    /**
-     * 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()]);
-    }
-
-    /**
-     * 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 findFiles(final FileSelector selector, final boolean depthwise, final List<FileObject> selected)
-            throws FileSystemException {
-        try {
-            if (exists()) {
-                // 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);
-        }
-    }
-
-    /**
-     * Returns the file system this file belongs to.
-     *
-     * @return The FileSystem this file is associated with.
-     */
-    protected AFS getAbstractFileSystem() {
-        return fileSystem;
-    }
-
-    /**
-     * 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 FileObject getChild(final String name) throws FileSystemException {
-        // TODO - use a hashtable when there are a large number of children
-        final FileObject[] children = getChildren();
-        for (final FileObject element : children) {
-            final FileName child = element.getName();
-            // TODO - use a comparator to compare names
-            if (child.getBaseName().equals(name)) {
-                return resolveFile(child);
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Returns the children of the file.
-     *
-     * @return an array of FileObjects, one per child.
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public FileObject[] getChildren() throws FileSystemException {
-        synchronized (fileSystem) {
-            // VFS-210
-            if (!fileSystem.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
-                final FileName[] cache = new FileName[files.length];
-                for (int i = 0; i < files.length; i++) {
-                    final String file = files[i];
-                    cache[i] = fileSystem.getFileSystemManager().resolveName(fileName, file, NameScope.CHILD);
-                }
-                // VFS-285: only assign the children file names after all of them have been
-                // resolved successfully to prevent an inconsistent internal state
-                children = cache;
-            }
-
-            return resolveFiles(children);
-        }
-    }
-
-    /**
-     * Returns the file's content.
-     *
-     * @return the FileContent for this FileObject.
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public FileContent getContent() throws FileSystemException {
-        synchronized (fileSystem) {
-            attach();
-            if (content == null) {
-                content = doCreateFileContent();
-            }
-            return content;
-        }
-    }
-
-    /**
-     * Creates the FileContentInfo factory.
-     *
-     * @return The FileContentInfoFactory.
-     */
-    protected FileContentInfoFactory getFileContentInfoFactory() {
-        return fileSystem.getFileSystemManager().getFileContentInfoFactory();
-    }
-
-    /**
-     * @return FileOperations interface that provides access to the operations API.
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public FileOperations getFileOperations() throws FileSystemException {
-        if (operations == null) {
-            operations = new DefaultFileOperations(this);
-        }
-
-        return operations;
-    }
-
-    /**
-     * Returns the file system this file belongs to.
-     *
-     * @return The FileSystem this file is associated with.
-     */
-    @Override
-    public FileSystem getFileSystem() {
-        return fileSystem;
-    }
-
-    /**
-     * Returns an input stream to use to read the content of the file.
-     *
-     * @return The InputStream to access this file's content.
-     * @throws FileSystemException if an error occurs.
-     */
-    public InputStream getInputStream() throws FileSystemException {
-        /*
-         * VFS-210 if (!getType().hasContent()) { throw new FileSystemException("vfs.provider/read-not-file.error",
-         * name); } if (!isReadable()) { throw new FileSystemException("vfs.provider/read-not-readable.error", name); }
-         */
-
-        // Get the raw input stream
-        try {
-            return doGetInputStream();
-        } catch (final org.apache.commons.vfs2.FileNotFoundException exc) {
-            throw new org.apache.commons.vfs2.FileNotFoundException(fileName, exc);
-        } catch (final FileNotFoundException exc) {
-            throw new org.apache.commons.vfs2.FileNotFoundException(fileName, exc);
-        } catch (final FileSystemException 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, never {@code null}.
-     */
-    @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.
-     *
-     * @param bAppend true when append to the file.
-     *            Note: If the underlying file system 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:
-     *             bAppend is true, and the underlying 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 && !fileSystem.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.compareTo(fileSystem.getRoot()) == 0) // equals is not implemented :-/
-        {
-            if (fileSystem.getParentLayer() == null) {
-                // Root file has no parent
-                return null;
-            }
-            // Return the parent of the parent layer
-            return fileSystem.getParentLayer().getParent();
-        }
-
-        synchronized (fileSystem) {
-            // Locate the parent of this file
-            if (parent == null) {
-                final FileName name = fileName.getParent();
-                if (name == null) {
-                    return null;
-                }
-                parent = fileSystem.resolveFile(name);
-            }
-            return parent;
-        }
-    }
-
-    /**
-     * Returns the receiver as a URI String for public display, like, without a password.
-     *
-     * @return A URI String without a password, never {@code null}.
-     */
-    @Override
-    public String getPublicURIString() {
-        return fileName.getFriendlyURI();
-    }
-
-    /**
-     * Returns an input/output stream to use to read and write the content of the file in and random manner.
-     *
-     * @param mode The RandomAccessMode.
-     * @return The RandomAccessContent.
-     * @throws FileSystemException if an error occurs.
-     */
-    public RandomAccessContent getRandomAccessContent(final RandomAccessMode mode) throws FileSystemException {
-        /*
-         * VFS-210 if (!getType().hasContent()) { throw new FileSystemException("vfs.provider/read-not-file.error",
-         * name); }
-         */
-
-        if (mode.requestRead()) {
-            if (!fileSystem.hasCapability(Capability.RANDOM_ACCESS_READ)) {
-                throw new FileSystemException("vfs.provider/random-access-read-not-supported.error");
-            }
-            if (!isReadable()) {
-                throw new FileSystemException("vfs.provider/read-not-readable.error", fileName);
-            }
-        }
-
-        if (mode.requestWrite()) {
-            if (!fileSystem.hasCapability(Capability.RANDOM_ACCESS_WRITE)) {
-                throw new FileSystemException("vfs.provider/random-access-write-not-supported.error");
-            }
-            if (!isWriteable()) {
-                throw new FileSystemException("vfs.provider/write-read-only.error", fileName);
-            }
-        }
-
-        // Get the raw input stream
-        try {
-            return doGetRandomAccessContent(mode);
-        } catch (final Exception exc) {
-            throw new FileSystemException("vfs.provider/random-access.error", fileName, exc);
-        }
-    }
-
-    /**
-     * Returns the file's type.
-     *
-     * @return The FileType.
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public FileType getType() throws FileSystemException {
-        synchronized (fileSystem) {
-            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;
-        }
-    }
-
-    /**
-     * Returns a URL representation of the file.
-     *
-     * @return The URL representation of the file.
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public URL getURL() throws FileSystemException {
-        try {
-            return AccessController.doPrivileged(new PrivilegedExceptionAction<URL>() {
-                @Override
-                public URL run() throws MalformedURLException, FileSystemException {
-                    final StringBuilder buf = new StringBuilder();
-                    final String scheme = UriParser.extractScheme(VFS.getManager().getSchemes(), fileName.getURI(), buf);
-                    return new URL(scheme, "", -1, buf.toString(),
-                            new DefaultURLStreamHandler(fileSystem.getContext(), fileSystem.getFileSystemOptions()));
-                }
-            });
-        } catch (final PrivilegedActionException e) {
-            throw new FileSystemException("vfs.provider/get-url.error", fileName, e.getException());
-        }
-    }
-
-    /**
-     * Called when this file is changed.
-     * <p>
-     * This will only happen if you monitor the file using {@link org.apache.commons.vfs2.FileMonitor}.
-     * </p>
-     *
-     * @throws Exception if an error occurs.
-     */
-    protected void handleChanged() throws Exception {
-        // Notify the file system
-        fileSystem.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 (fileSystem) {
-            if (attached) {
-                // Fix up state
-                injectType(newType);
-
-                removeChildrenCache();
-
-                // Notify subclass
-                onChange();
-            }
-
-            // Notify parent that its child list may no longer be valid
-            notifyParent(this.getName(), newType);
-
-            // Notify the file system
-            fileSystem.fireFileCreated(this);
-        }
-    }
-
-    /**
-     * 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 (fileSystem) {
-            if (attached) {
-                // Fix up state
-                injectType(FileType.IMAGINARY);
-                removeChildrenCache();
-
-                // Notify subclass
-                onChange();
-            }
-
-            // Notify parent that its child list may no longer be valid
-            notifyParent(this.getName(), FileType.IMAGINARY);
-
-            // Notify the file system
-            fileSystem.fireFileDeleted(this);
-        }
-    }
-
-    /**
-     * This method is meant to add an object where this object holds a strong reference then. E.g. a archive-file system
-     * creates a list of all children and they shouldn't get garbage collected until the container is garbage collected
-     *
-     * @param strongRef The Object to add.
-     */
-    // TODO should this be a FileObject?
-    public void holdObject(final Object strongRef) {
-        if (objects == null) {
-            objects = new ArrayList<>(INITIAL_LIST_SIZE);
-        }
-        objects.add(strongRef);
-    }
-
-    protected void injectType(final FileType fileType) {
-        setFileType(fileType);
-    }
-
-    /**
-     * Check if the internal state is "attached".
-     *
-     * @return true if this is the case
-     */
-    @Override
-    public boolean isAttached() {
-        return attached;
-    }
-
-    /**
-     * Check if the content stream is open.
-     *
-     * @return true if this is the case
-     */
-    @Override
-    public boolean isContentOpen() {
-        if (content == null) {
-            return false;
-        }
-
-        return content.isOpen();
-    }
-
-    /**
-     * 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.
-     */
-    @Override
-    public boolean isExecutable() throws FileSystemException {
-        try {
-            return exists() ? doIsExecutable() : false;
-        } catch (final Exception exc) {
-            throw new FileSystemException("vfs.provider/check-is-executable.error", fileName, exc);
-        }
-    }
-
-    /**
-     * 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
-     */
-    @Override
-    public boolean isFile() throws FileSystemException {
-        // 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.
-     * @throws FileSystemException if an error occurs.
-     * @see #getType()
-     * @see FileType#FOLDER
-     */
-    @Override
-    public boolean isFolder() throws FileSystemException {
-        // Use equals instead of == to avoid any class loader worries.
-        return FileType.FOLDER.equals(this.getType());
-    }
-
-    /**
-     * Determines if this file can be read.
-     *
-     * @return true if the file is a hidden file, false otherwise.
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public boolean isHidden() throws FileSystemException {
-        try {
-            return exists() ? doIsHidden() : false;
-        } catch (final Exception exc) {
-            throw new FileSystemException("vfs.provider/check-is-hidden.error", fileName, exc);
-        }
-    }
-
-    /**
-     * Determines if this file can be read.
-     *
-     * @return true if the file can be read, false otherwise.
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public boolean isReadable() throws FileSystemException {
-        try {
-            return exists() ? doIsReadable() : false;
-        } catch (final Exception exc) {
-            throw new FileSystemException("vfs.provider/check-is-readable.error", fileName, exc);
-        }
-    }
-
-    /**
-     * Checks if this fileObject is the same file as {@code destFile} just with a different name. E.g. for case
-     * insensitive file systems 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);
-    }
-
-    /**
-     * Determines if this file can be read.
-     *
-     * @return true if the file can be read, false otherwise.
-     * @throws FileSystemException if an error occurs.
-     * @since 2.4
-     */
-    @Override
-    public boolean isSymbolicLink() throws FileSystemException {
-        try {
-            return exists() ? doIsSymbolicLink() : false;
-        } catch (final Exception exc) {
-            throw new FileSystemException("vfs.provider/check-is-symbolic-link.error", fileName, exc);
-        }
-    }
-
-    /**
-     * 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.
-     */
-    @Override
-    public boolean isWriteable() throws FileSystemException {
-        try {
-            if (exists()) {
-                return doIsWriteable();
-            }
-            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);
-        }
-    }
-
-    /**
-     * Returns an iterator over a set of all FileObject in this file object.
-     *
-     * @return an Iterator.
-     */
-    @Override
-    public Iterator<FileObject> iterator() {
-        try {
-            return listFiles(Selectors.SELECT_ALL).iterator();
-        } catch (final FileSystemException e) {
-            throw new IllegalStateException(e);
-        }
-    }
-
-    /**
-     * 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.
-     */
-    public List<FileObject> listFiles(final FileSelector selector) throws FileSystemException {
-        if (!exists() || selector == null) {
-            return null;
-        }
-
-        final ArrayList<FileObject> list = new ArrayList<>();
-        this.findFiles(selector, true, list);
-        return list;
-    }
-
-    /**
-     * 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()) {
-                throw new FileSystemException("vfs.provider/rename-parent-read-only.error", getName(),
-                        getParent().getName());
-            }
-        } else {
-            if (!isWriteable()) {
-                throw new FileSystemException("vfs.provider/rename-read-only.error", getName());
-            }
-        }
-
-        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
-            try {
-                attach();
-                // remember type to avoid attach
-                final FileType srcType = getType();
-
-                doRename(destFile);
-
-                FileObjectUtils.getAbstractFileObject(destFile).handleCreate(srcType);
-                destFile.close(); // now the destFile is no longer imaginary. force reattach.
-
-                handleDelete(); // fire delete-events. This file-object (src) is like deleted.
-            } catch (final RuntimeException re) {
-                throw re;
-            } catch (final Exception exc) {
-                throw new FileSystemException("vfs.provider/rename.error", exc, getName(), destFile.getName());
-            }
-        } 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))
-                    && fileSystem.hasCapability(Capability.GET_LAST_MODIFIED)) {
-                destFile.getContent().setLastModifiedTime(this.getContent().getLastModifiedTime());
-            }
-
-            deleteSelf();
-        }
-
-    }
-
-    /**
-     * Clled after this file-object closed all its streams.
-     */
-    protected void notifyAllStreamsClosed() {
-        // noop
-    }
-
-    /**
-     * Notify the parent of a change to its children, when a child is created or deleted.
-     *
-     * @param childName The name of the child.
-     * @param newType The type of the child.
-     * @throws Exception if an error occurs.
-     */
-    private void notifyParent(final FileName childName, final FileType newType) throws Exception {
-        if (parent == null) {
-            final FileName parentName = fileName.getParent();
-            if (parentName != null) {
-                // Locate the parent, if it is cached
-                parent = fileSystem.getFileFromCache(parentName);
-            }
-        }
-
-        if (parent != null) {
-            FileObjectUtils.getAbstractFileObject(parent).childrenChanged(childName, newType);
-        }
-    }
-
-    /**
-     * Called when the type or content of this file changes.
-     * <p>
-     * This implementation does nothing.
-     * </p>
-     *
-     * @throws Exception if an error occurs.
-     */
-    protected void onChange() throws Exception {
-        // noop
-    }
-
-    /**
-     * 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.
-     * </p>
-     *
-     * @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 {
-        // noop
-    }
-
-    /**
-     * This will prepare the fileObject to get resynchronized with the underlying file system if required.
-     *
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public void refresh() throws FileSystemException {
-        // Detach from the file
-        try {
-            detach();
-        } catch (final Exception e) {
-            throw new FileSystemException("vfs.provider/resync.error", fileName, e);
-        }
-    }
-
-    private void removeChildrenCache() {
-        children = null;
-    }
-
-    private FileObject resolveFile(final FileName child) throws FileSystemException {
-        return fileSystem.resolveFile(child);
-    }
-
-    /**
-     * Finds a file, relative to this file.
-     *
-     * @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.
-     */
-    @Override
-    public FileObject resolveFile(final String path) throws FileSystemException {
-        final FileName otherName = fileSystem.getFileSystemManager().resolveName(fileName, path);
-        return fileSystem.resolveFile(otherName);
-    }
-
-    /**
-     * 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.
-     */
-    @Override
-    public FileObject resolveFile(final String name, final NameScope scope) throws FileSystemException {
-        // return fs.resolveFile(this.name.resolveName(name, scope));
-        return fileSystem.resolveFile(fileSystem.getFileSystemManager().resolveName(this.fileName, name, scope));
-    }
-
-    private FileObject[] resolveFiles(final FileName[] children) throws FileSystemException {
-        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]);
-        }
-
-        return objects;
-    }
-
-    @Override
-    public boolean setExecutable(final boolean readable, final boolean ownerOnly) throws FileSystemException {
-        try {
-            return exists() ? doSetExecutable(readable, ownerOnly) : false;
-        } catch (final Exception exc) {
-            throw new FileSystemException("vfs.provider/set-executable.error", fileName, exc);
-        }
-    }
-
-    private void setFileType(final FileType type) {
-        if (type != null && type != FileType.IMAGINARY) {
-            try {
-                fileName.setType(type);
-            } catch (final FileSystemException e) {
-                throw new RuntimeException(e.getMessage());
-            }
-        }
-        this.type = type;
-    }
-
-    @Override
-    public boolean setReadable(final boolean readable, final boolean ownerOnly) throws FileSystemException {
-        try {
-            return exists() ? doSetReadable(readable, ownerOnly) : false;
-        } catch (final Exception exc) {
-            throw new FileSystemException("vfs.provider/set-readable.error", fileName, exc);
-        }
-    }
-
-    // --- OPERATIONS ---
-
-    @Override
-    public boolean setWritable(final boolean readable, final boolean ownerOnly) throws FileSystemException {
-        try {
-            return exists() ? doSetWritable(readable, ownerOnly) : false;
-        } catch (final Exception exc) {
-            throw new FileSystemException("vfs.provider/set-writeable.error", fileName, exc);
-        }
-    }
-
-    /**
-     * Returns the URI as a String.
-     *
-     * @return Returns the URI as a String.
-     */
-    @Override
-    public String toString() {
-        return fileName.getURI();
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.vfs2.provider;
+
+import java.io.BufferedInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.security.cert.Certificate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.vfs2.Capability;
+import org.apache.commons.vfs2.FileContent;
+import org.apache.commons.vfs2.FileContentInfoFactory;
+import org.apache.commons.vfs2.FileName;
+import org.apache.commons.vfs2.FileNotFolderException;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSelector;
+import org.apache.commons.vfs2.FileSystem;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileType;
+import org.apache.commons.vfs2.FileUtil;
+import org.apache.commons.vfs2.NameScope;
+import org.apache.commons.vfs2.RandomAccessContent;
+import org.apache.commons.vfs2.Selectors;
+import org.apache.commons.vfs2.VFS;
+import org.apache.commons.vfs2.operations.DefaultFileOperations;
+import org.apache.commons.vfs2.operations.FileOperations;
+import org.apache.commons.vfs2.util.FileObjectUtils;
+import org.apache.commons.vfs2.util.RandomAccessMode;
+
+/**
+ * A partial file object implementation.
+ *
+ * TODO - Chop this class up - move all the protected methods to several interfaces, so that structure and content can
+ * be separately overridden.
+ *
+ * <p>
+ * TODO - Check caps in methods like getChildren(), etc, and give better error messages (eg 'this file type does not
+ * support listing children', vs 'this is not a folder')
+ * </p>
+ * 
+ * @param <AFS> An AbstractFileSystem subclass
+ */
+public abstract class AbstractFileObject<AFS extends AbstractFileSystem> implements FileObject {
+
+    /**
+     * Same as {@link BufferedInputStream}.
+     */
+    private static final int DEFAULT_BUFFER_SIZE = 8192;
+
+    private static final FileName[] EMPTY_FILE_ARRAY = {};
+
+    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 fileSystem;
+    private FileContent content;
+    // Cached info
+    private boolean attached;
+
+    private FileType type;
+    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;
+
+    /**
+     * FileServices instance.
+     */
+    private FileOperations operations;
+
+    /**
+     *
+     * @param name the file name - muse be an instance of {@link AbstractFileName}
+     * @param fileSystem the file system
+     * @throws ClassCastException if {@code name} is not an instance of {@link AbstractFileName}
+     */
+    protected AbstractFileObject(final AbstractFileName name, final AFS fileSystem) {
+        this.fileName = name;
+        this.fileSystem = fileSystem;
+        fileSystem.fileObjectHanded(this);
+    }
+
+    /**
+     * Attaches to the file.
+     *
+     * @throws FileSystemException if an error occurs.
+     */
+    private void attach() throws FileSystemException {
+        synchronized (fileSystem) {
+            if (attached) {
+                return;
+            }
+
+            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)
+
+                /*
+                 * 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);
+            }
+
+            // fs.fileAttached(this);
+        }
+    }
+
+    /**
+     * Queries the object if a simple rename to the file name of {@code newfile} is possible.
+     *
+     * @param newfile the new file name
+     * @return true if rename is possible
+     */
+    @Override
+    public boolean canRenameTo(final FileObject newfile) {
+        return fileSystem == newfile.getFileSystem();
+    }
+
+    /**
+     * 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 void childrenChanged(final FileName childName, final FileType newType) throws Exception {
+        // TODO - this may be called when not attached
+
+        if (children != null && childName != null && newType != null) {
+            // TODO - figure out if children[] can be replaced by list
+            final ArrayList<FileName> list = new ArrayList<>(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);
+    }
+
+    /**
+     * Closes this file, and its content.
+     *
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public void close() throws FileSystemException {
+        FileSystemException exc = null;
+
+        synchronized (fileSystem) {
+            // 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;
+            }
+        }
+    }
+
+    /**
+     * 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.
+     */
+    @Override
+    public int compareTo(final FileObject file) {
+        if (file == null) {
+            return 1;
+        }
+        return this.toString().compareToIgnoreCase(file.toString());
+    }
+
+    /**
+     * Copies another file to this file.
+     *
+     * @param file The FileObject to copy.
+     * @param selector The FileSelector.
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public void copyFrom(final FileObject file, final FileSelector selector) throws FileSystemException {
+        if (!FileObjectUtils.exists(file)) {
+            throw new FileSystemException("vfs.provider/copy-missing-file.error", file);
+        }
+
+        // Locate the files to copy across
+        final ArrayList<FileObject> files = new ArrayList<>();
+        file.findFiles(selector, false, files);
+
+        // 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);
+
+            // Clean up the destination file, if necessary
+            if (FileObjectUtils.exists(destFile) && 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();
+            }
+
+            // 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, if it does not exist.
+     *
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public void createFile() throws FileSystemException {
+        synchronized (fileSystem) {
+            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) {
+                throw new FileSystemException("vfs.provider/create-file.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.
+     */
+    @Override
+    public void createFolder() throws FileSystemException {
+        synchronized (fileSystem) {
+            // 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 hierarchy 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);
+            }
+        }
+    }
+
+    /**
+     * Deletes this file.
+     * <p>
+     * TODO - This will not fail if this is a non-empty folder.
+     * </p>
+     *
+     * @return true if this object has been deleted
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public boolean delete() throws FileSystemException {
+        return delete(Selectors.SELECT_SELF) > 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.
+     */
+    @Override
+    public int delete(final FileSelector selector) throws FileSystemException {
+        int nuofDeleted = 0;
+
+        /*
+         * VFS-210 if (getType() == FileType.IMAGINARY) { // File does not exist return nuofDeleted; }
+         */
+
+        // Locate all the files to delete
+        final ArrayList<FileObject> files = new ArrayList<>();
+        findFiles(selector, true, files);
+
+        // Delete 'em
+        final int count = files.size();
+        for (int i = 0; i < count; i++) {
+            final AbstractFileObject file = FileObjectUtils.getAbstractFileObject(files.get(i));
+            // file.attach();
+
+            // 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;
+            }
+
+            // Delete the file
+            if (file.deleteSelf()) {
+                nuofDeleted++;
+            }
+        }
+
+        return nuofDeleted;
+    }
+
+    /**
+     * 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 int deleteAll() throws FileSystemException {
+        return this.delete(Selectors.SELECT_ALL);
+    }
+
+    /**
+     * Deletes this file, once all its children have been deleted
+     *
+     * @return true if this file has been deleted
+     * @throws FileSystemException if an error occurs.
+     */
+    private boolean deleteSelf() throws FileSystemException {
+        synchronized (fileSystem) {
+            // Its possible to delete a read-only file if you have write-execute access to the directory
+
+            /*
+             * 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;
+        }
+    }
+
+    /**
+     * Detaches this file, invalidating all cached info. This will force a call to {@link #doAttach} next time this file
+     * is used.
+     *
+     * @throws Exception if an error occurs.
+     */
+    private void detach() throws Exception {
+        synchronized (fileSystem) {
+            if (attached) {
+                try {
+                    doDetach();
+                } finally {
+                    attached = false;
+                    setFileType(null);
+                    parent = null;
+
+                    // fs.fileDetached(this);
+
+                    removeChildrenCache();
+                    // children = null;
+                }
+            }
+        }
+    }
+
+    /**
+     * Attaches this file object to its file resource.
+     * <p>
+     * This method is called before any of the doBlah() or onBlah() methods. Sub-classes can use this method to perform
+     * lazy initialisation.
+     * </p>
+     * <p>
+     * This implementation does nothing.
+     * </p>
+     *
+     * @throws Exception if an error occurs.
+     */
+    protected void doAttach() throws Exception {
+        // noop
+    }
+
+    /**
+     * Create a FileContent implementation.
+     *
+     * @return The FileContent.
+     * @throws FileSystemException if an error occurs.
+     * @since 2.0
+     */
+    protected FileContent doCreateFileContent() throws FileSystemException {
+        return new DefaultFileContent(this, getFileContentInfoFactory());
+    }
+
+    /**
+     * Creates this file as a folder. Is only called when:
+     * <ul>
+     * <li>{@link #doGetType} returns {@link FileType#IMAGINARY}.</li>
+     * <li>The parent folder exists and is writeable, or this file is the root of the file system.</li>
+     * </ul>
+     * This implementation throws an exception.
+     *
+     * @throws Exception if an error occurs.
+     */
+    protected void doCreateFolder() throws Exception {
+        throw new FileSystemException("vfs.provider/create-folder-not-supported.error");
+    }
+
+    /**
+     * Deletes the file. Is only called when:
+     * <ul>
+     * <li>{@link #doGetType} does not return {@link FileType#IMAGINARY}.</li>
+     * <li>{@link #doIsWriteable} returns true.</li>
+     * <li>This file has no children, if a folder.</li>
+     * </ul>
+     * 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");
+    }
+
+    /**
+     * Detaches this file object from its file resource.
+     * <p>
+     * Called when this file is closed. Note that the file object may be reused later, so should be able to be
+     * reattached.
+     * </p>
+     * <p>
+     * This implementation does nothing.
+     * </p>
+     *
+     * @throws Exception if an error occurs.
+     */
+    protected void doDetach() throws Exception {
+        // noop
+    }
+
+    /**
+     * 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.
+     * </p>
+     *
+     * @return The attributes of the file.
+     * @throws Exception if an error occurs.
+     */
+    protected Map<String, Object> doGetAttributes() throws Exception {
+        return Collections.emptyMap();
+    }
+
+    /**
+     * 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.
+     * </p>
+     *
+     * @return The certificates used to sign the file.
+     * @throws Exception if an error occurs.
+     */
+    protected Certificate[] doGetCertificates() throws Exception {
+        return null;
+    }
+
+    /**
+     * 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>
+     * 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.
+     * </p>
+     *
+     * @return An InputStream to read the file content.
+     * @throws Exception if an error occurs.
+     */
+    protected InputStream doGetInputStream() throws Exception {
+        // Backward compatibility.
+        return doGetInputStream(DEFAULT_BUFFER_SIZE);
+    }
+
+    /**
+     * Creates an input stream to read the file content from. Is only called if {@link #doGetType} returns
+     * {@link FileType#FILE}.
+     * <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.
+     * </p>
+     * @param bufferSize Buffer size hint.
+     * @return An InputStream to read the file content.
+     * @throws Exception if an error occurs.
+     */
+    protected InputStream doGetInputStream(final int bufferSize) throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Returns the last modified time of this file. Is only called if {@link #doGetType} does not return
+     * <p>
+     * This implementation throws an exception.
+     * </p>
+     *
+     * @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");
+    }
+
+    /**
+     * 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>
+     * It is guaranteed that there are no open stream (input or output) for this file when this method is called.
+     * <p>
+     * The returned stream does not have to be buffered.
+     * </p>
+     * <p>
+     * This implementation throws an exception.
+     * </p>
+     *
+     * @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");
+    }
+
+    /**
+     * Creates access to the file for random i/o. Is only called if {@link #doGetType} returns {@link FileType#FILE}.
+     * <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");
+    }
+
+    /**
+     * 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;
+
+    /**
+     * Determines if this file is executable. Is only called if {@link #doGetType} does not return
+     * {@link FileType#IMAGINARY}.
+     * <p>
+     * This implementation always returns false.
+     * </p>
+     *
+     * @return true if the file is executable, false otherwise.
+     * @throws Exception if an error occurs.
+     */
+    protected boolean doIsExecutable() throws Exception {
+        return false;
+    }
+
+    /**
+     * Determines if this file is hidden. Is only called if {@link #doGetType} does not return
+     * {@link FileType#IMAGINARY}.
+     * <p>
+     * This implementation always returns false.
+     * </p>
+     *
+     * @return true if the file is hidden, false otherwise.
+     * @throws Exception if an error occurs.
+     */
+    protected boolean doIsHidden() throws Exception {
+        return false;
+    }
+
+    /**
+     * 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.
+     * </p>
+     *
+     * @return true if the file is readable, false otherwise.
+     * @throws Exception if an error occurs.
+     */
+    protected boolean doIsReadable() throws Exception {
+        return true;
+    }
+
+    /**
+     * Checks if this fileObject is the same file as {@code destFile} just with a different name. E.g. for case
+     * insensitive file systems 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;
+    }
+
+    /**
+     * Determines if this file is a symbolic link. Is only called if {@link #doGetType} does not return
+     * {@link FileType#IMAGINARY}.
+     * <p>
+     * This implementation always returns false.
+     * </p>
+     *
+     * @return true if the file is readable, false otherwise.
+     * @throws Exception if an error occurs.
+     * @since 2.4
+     */
+    protected boolean doIsSymbolicLink() throws Exception {
+        return false;
+    }
+
+    /**
+     * 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.
+     * </p>
+     *
+     * @return true if the file is writable.
+     * @throws Exception if an error occurs.
+     */
+    protected boolean doIsWriteable() throws Exception {
+        return true;
+    }
+
+    /**
+     * 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.
+     *
+     * @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;
+
+    /**
+     * Lists the children of this file.
+     * <p>
+     * Is only called if {@link #doGetType} returns {@link FileType#FOLDER}.
+     * </p>
+     * <p>
+     * The return value of this method is cached, so the implementation can be expensive.
+     * Other than {@code doListChildren} you could return FileObject's to e.g. reinitialize the type of the file.
+     * </p>
+     * <p>
+     * (Introduced for Webdav: "permission denied on resource" during getType())
+     * </p>
+     *
+     * @return The children of this FileObject.
+     * @throws Exception if an error occurs.
+     */
+    protected FileObject[] doListChildrenResolved() throws Exception {
+        return null;
+    }
+
+    /**
+     * Removes an attribute of this file.
+     * <p>
+     * Is only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}.
+     * </p>
+     * <p>
+     * This implementation throws an exception.
+     * </p>
+     *
+     * @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 {
+        throw new FileSystemException("vfs.provider/remove-attribute-not-supported.error");
+    }
+
+    /**
+     * Renames the file.
+     * <p>
+     * Is only called when:
+     * </p>
+     * <ul>
+     * <li>{@link #doIsWriteable} returns true.</li>
+     * </ul>
+     * <p>
+     * This implementation throws an exception.
+     * </p>
+     *
+     * @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");
+    }
+
+    /**
+     * Sets an attribute of this file.
+     * <p>
+     * Is only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}.
+     * </p>
+     * <p>
+     * This implementation throws an exception.
+     * </p>
+     *
+     * @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 {
+        throw new FileSystemException("vfs.provider/set-attribute-not-supported.error");
+    }
+
+    /**
+     * Make the file executable.
+     * <p>
+     * Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}.
+     * </p>
+     * <p>
+     * This implementation returns false.
+     * </p>
+     *
+     * @param executable 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.
+     * @throws Exception Any Exception thrown is wrapped in FileSystemException.
+     * @see #setExecutable(boolean, boolean)
+     * @since 2.1
+     */
+    protected boolean doSetExecutable(final boolean executable, final boolean ownerOnly) throws Exception {
+        return false;
+    }
+
+    /**
+     * Sets the last modified time of this file.
+     * <p>
+     * Is only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}.
+     * </p>
+     * <p>
+     * This implementation throws an exception.
+     * </p>
+     *
+     * @param modtime The last modification time.
+     * @return true if the time was set.
+     * @throws Exception Any Exception thrown is wrapped in FileSystemException.
+     */
+    protected boolean doSetLastModifiedTime(final long modtime) throws Exception {
+        throw new FileSystemException("vfs.provider/set-last-modified-not-supported.error");
+    }
+
+    /**
+     * Make the file or folder readable.
+     * <p>
+     * Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}.
+     * </p>
+     * <p>
+     * This implementation returns false.
+     * </p>
+     *
+     * @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
+     * @throws Exception Any Exception thrown is wrapped in FileSystemException.
+     * @see #setReadable(boolean, boolean)
+     * @since 2.1
+     */
+    protected boolean doSetReadable(final boolean readable, final boolean ownerOnly) throws Exception {
+        return false;
+    }
+
+    /**
+     * Make the file or folder writeable.
+     * <p>
+     * Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}.
+     * </p>
+     *
+     * @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
+     * @throws Exception Any Exception thrown is wrapped in FileSystemException.
+     * @see #setWritable(boolean, boolean)
+     * @since 2.1
+     */
+    protected boolean doSetWritable(final boolean writable, final boolean ownerOnly) throws Exception {
+        return false;
+    }
+
+    /**
+     * Called when the output stream for this file is closed.
+     *
+     * @throws Exception if an error occurs.
+     */
+    protected void endOutput() throws Exception {
+        if (getType() == FileType.IMAGINARY) {
+            // File was created
+            handleCreate(FileType.FILE);
+        } else {
+            // File has changed
+            onChange();
+        }
+    }
+
+    /**
+     * 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;
+    }
+
+    private FileName[] extractNames(final FileObject[] objects) {
+        if (objects == null) {
+            return null;
+        }
+
+        final FileName[] names = new FileName[objects.length];
+        for (int iterObjects = 0; iterObjects < objects.length; iterObjects++) {
+            names[iterObjects] = objects[iterObjects].getName();
+        }
+
+        return names;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        fileSystem.fileObjectDestroyed(this);
+
+        super.finalize();
+    }
+
+    /**
+     * 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()]);
+    }
+
+    /**
+     * 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 findFiles(final FileSelector selector, final boolean depthwise, final List<FileObject> selected)
+            throws FileSystemException {
+        try {
+            if (exists()) {
+                // 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);
+        }
+    }
+
+    /**
+     * Returns the file system this file belongs to.
+     *
+     * @return The FileSystem this file is associated with.
+     */
+    protected AFS getAbstractFileSystem() {
+        return fileSystem;
+    }
+
+    /**
+     * 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 FileObject getChild(final String name) throws FileSystemException {
+        // TODO - use a hashtable when there are a large number of children
+        final FileObject[] children = getChildren();
+        for (final FileObject element : children) {
+            final FileName child = element.getName();
+            // TODO - use a comparator to compare names
+            if (child.getBaseName().equals(name)) {
+                return resolveFile(child);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the children of the file.
+     *
+     * @return an array of FileObjects, one per child.
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public FileObject[] getChildren() throws FileSystemException {
+        synchronized (fileSystem) {
+            // VFS-210
+            if (!fileSystem.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
+                final FileName[] cache = new FileName[files.length];
+                for (int i = 0; i < files.length; i++) {
+                    final String file = files[i];
+                    cache[i] = fileSystem.getFileSystemManager().resolveName(fileName, file, NameScope.CHILD);
+                }
+                // VFS-285: only assign the children file names after all of them have been
+                // resolved successfully to prevent an inconsistent internal state
+                children = cache;
+            }
+
+            return resolveFiles(children);
+        }
+    }
+
+    /**
+     * Returns the file's content.
+     *
+     * @return the FileContent for this FileObject.
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public FileContent getContent() throws FileSystemException {
+        synchronized (fileSystem) {
+            attach();
+            if (content == null) {
+                content = doCreateFileContent();
+            }
+            return content;
+        }
+    }
+
+    /**
+     * Creates the FileContentInfo factory.
+     *
+     * @return The FileContentInfoFactory.
+     */
+    protected FileContentInfoFactory getFileContentInfoFactory() {
+        return fileSystem.getFileSystemManager().getFileContentInfoFactory();
+    }
+
+    /**
+     * @return FileOperations interface that provides access to the operations API.
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public FileOperations getFileOperations() throws FileSystemException {
+        if (operations == null) {
+            operations = new DefaultFileOperations(this);
+        }
+
+        return operations;
+    }
+
+    /**
+     * Returns the file system this file belongs to.
+     *
+     * @return The FileSystem this file is associated with.
+     */
+    @Override
+    public FileSystem getFileSystem() {
+        return fileSystem;
+    }
+
+    /**
+     * Returns an input stream to use to read the content of the file.
+     *
+     * @return The InputStream to access this file's content.
+     * @throws FileSystemException if an error occurs.
+     */
+    public InputStream getInputStream() throws FileSystemException {
+        return getInputStream(DEFAULT_BUFFER_SIZE);
+    }
+
+    /**
+     * Returns an input stream to use to read the content of the file.
+     * 
+     * @param bufferSize buffer size hint.
+     * @return The InputStream to access this file's content.
+     * @throws FileSystemException if an error occurs.
+     */
+    public InputStream getInputStream(final int bufferSize) throws FileSystemException {
+        // Get the raw input stream
+        try {
+            return doGetInputStream(bufferSize);
+        } catch (final org.apache.commons.vfs2.FileNotFoundException exc) {
+            throw new org.apache.commons.vfs2.FileNotFoundException(fileName, exc);
+        } catch (final FileNotFoundException exc) {
+            throw new org.apache.commons.vfs2.FileNotFoundException(fileName, exc);
+        } catch (final FileSystemException 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, never {@code null}.
+     */
+    @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.
+     *
+     * @param bAppend true when append to the file.
+     *            Note: If the underlying file system 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:
+     *             bAppend is true, and the underlying 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 && !fileSystem.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.compareTo(fileSystem.getRoot()) == 0) // equals is not implemented :-/
+        {
+            if (fileSystem.getParentLayer() == null) {
+                // Root file has no parent
+                return null;
+            }
+            // Return the parent of the parent layer
+            return fileSystem.getParentLayer().getParent();
+        }
+
+        synchronized (fileSystem) {
+            // Locate the parent of this file
+            if (parent == null) {
+                final FileName name = fileName.getParent();
+                if (name == null) {
+                    return null;
+                }
+                parent = fileSystem.resolveFile(name);
+            }
+            return parent;
+        }
+    }
+
+    /**
+     * Returns the receiver as a URI String for public display, like, without a password.
+     *
+     * @return A URI String without a password, never {@code null}.
+     */
+    @Override
+    public String getPublicURIString() {
+        return fileName.getFriendlyURI();
+    }
+
+    /**
+     * Returns an input/output stream to use to read and write the content of the file in and random manner.
+     *
+     * @param mode The RandomAccessMode.
+     * @return The RandomAccessContent.
+     * @throws FileSystemException if an error occurs.
+     */
+    public RandomAccessContent getRandomAccessContent(final RandomAccessMode mode) throws FileSystemException {
+        /*
+         * VFS-210 if (!getType().hasContent()) { throw new FileSystemException("vfs.provider/read-not-file.error",
+         * name); }
+         */
+
+        if (mode.requestRead()) {
+            if (!fileSystem.hasCapability(Capability.RANDOM_ACCESS_READ)) {
+                throw new FileSystemException("vfs.provider/random-access-read-not-supported.error");
+            }
+            if (!isReadable()) {
+                throw new FileSystemException("vfs.provider/read-not-readable.error", fileName);
+            }
+        }
+
+        if (mode.requestWrite()) {
+            if (!fileSystem.hasCapability(Capability.RANDOM_ACCESS_WRITE)) {
+                throw new FileSystemException("vfs.provider/random-access-write-not-supported.error");
+            }
+            if (!isWriteable()) {
+                throw new FileSystemException("vfs.provider/write-read-only.error", fileName);
+            }
+        }
+
+        // Get the raw input stream
+        try {
+            return doGetRandomAccessContent(mode);
+        } catch (final Exception exc) {
+            throw new FileSystemException("vfs.provider/random-access.error", fileName, exc);
+        }
+    }
+
+    /**
+     * Returns the file's type.
+     *
+     * @return The FileType.
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public FileType getType() throws FileSystemException {
+        synchronized (fileSystem) {
+            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;
+        }
+    }
+
+    /**
+     * Returns a URL representation of the file.
+     *
+     * @return The URL representation of the file.
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public URL getURL() throws FileSystemException {
+        try {
+            return AccessController.doPrivileged(new PrivilegedExceptionAction<URL>() {
+                @Override
+                public URL run() throws MalformedURLException, FileSystemException {
+                    final StringBuilder buf = new StringBuilder();
+                    final String scheme = UriParser.extractScheme(VFS.getManager().getSchemes(), fileName.getURI(), buf);
+                    return new URL(scheme, "", -1, buf.toString(),
+                            new DefaultURLStreamHandler(fileSystem.getContext(), fileSystem.getFileSystemOptions()));
+                }
+            });
+        } catch (final PrivilegedActionException e) {
+            throw new FileSystemException("vfs.provider/get-url.error", fileName, e.getException());
+        }
+    }
+
+    /**
+     * Called when this file is changed.
+     * <p>
+     * This will only happen if you monitor the file using {@link org.apache.commons.vfs2.FileMonitor}.
+     * </p>
+     *
+     * @throws Exception if an error occurs.
+     */
+    protected void handleChanged() throws Exception {
+        // Notify the file system
+        fileSystem.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 (fileSystem) {
+            if (attached) {
+                // Fix up state
+                injectType(newType);
+
+                removeChildrenCache();
+
+                // Notify subclass
+                onChange();
+            }
+
+            // Notify parent that its child list may no longer be valid
+            notifyParent(this.getName(), newType);
+
+            // Notify the file system
+            fileSystem.fireFileCreated(this);
+        }
+    }
+
+    /**
+     * 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 (fileSystem) {
+            if (attached) {
+                // Fix up state
+                injectType(FileType.IMAGINARY);
+                removeChildrenCache();
+
+                // Notify subclass
+                onChange();
+            }
+
+            // Notify parent that its child list may no longer be valid
+            notifyParent(this.getName(), FileType.IMAGINARY);
+
+            // Notify the file system
+            fileSystem.fireFileDeleted(this);
+        }
+    }
+
+    /**
+     * This method is meant to add an object where this object holds a strong reference then. E.g. a archive-file system
+     * creates a list of all children and they shouldn't get garbage collected until the container is garbage collected
+     *
+     * @param strongRef The Object to add.
+     */
+    // TODO should this be a FileObject?
+    public void holdObject(final Object strongRef) {
+        if (objects == null) {
+            objects = new ArrayList<>(INITIAL_LIST_SIZE);
+        }
+        objects.add(strongRef);
+    }
+
+    protected void injectType(final FileType fileType) {
+        setFileType(fileType);
+    }
+
+    /**
+     * Check if the internal state is "attached".
+     *
+     * @return true if this is the case
+     */
+    @Override
+    public boolean isAttached() {
+        return attached;
+    }
+
+    /**
+     * Check if the content stream is open.
+     *
+     * @return true if this is the case
+     */
+    @Override
+    public boolean isContentOpen() {
+        if (content == null) {
+            return false;
+        }
+
+        return content.isOpen();
+    }
+
+    /**
+     * 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.
+     */
+    @Override
+    public boolean isExecutable() throws FileSystemException {
+        try {
+            return exists() ? doIsExecutable() : false;
+        } catch (final Exception exc) {
+            throw new FileSystemException("vfs.provider/check-is-executable.error", fileName, exc);
+        }
+    }
+
+    /**
+     * 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
+     */
+    @Override
+    public boolean isFile() throws FileSystemException {
+        // 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.
+     * @throws FileSystemException if an error occurs.
+     * @see #getType()
+     * @see FileType#FOLDER
+     */
+    @Override
+    public boolean isFolder() throws FileSystemException {
+        // Use equals instead of == to avoid any class loader worries.
+        return FileType.FOLDER.equals(this.getType());
+    }
+
+    /**
+     * Determines if this file can be read.
+     *
+     * @return true if the file is a hidden file, false otherwise.
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public boolean isHidden() throws FileSystemException {
+        try {
+            return exists() ? doIsHidden() : false;
+        } catch (final Exception exc) {
+            throw new FileSystemException("vfs.provider/check-is-hidden.error", fileName, exc);
+        }
+    }
+
+    /**
+     * Determines if this file can be read.
+     *
+     * @return true if the file can be read, false otherwise.
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public boolean isReadable() throws FileSystemException {
+        try {
+            return exists() ? doIsReadable() : false;
+        } catch (final Exception exc) {
+            throw new FileSystemException("vfs.provider/check-is-readable.error", fileName, exc);
+        }
+    }
+
+    /**
+     * Checks if this fileObject is the same file as {@code destFile} just with a different name. E.g. for case
+     * insensitive file systems 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);
+    }
+
+    /**
+     * Determines if this file can be read.
+     *
+     * @return true if the file can be read, false otherwise.
+     * @throws FileSystemException if an error occurs.
+     * @since 2.4
+     */
+    @Override
+    public boolean isSymbolicLink() throws FileSystemException {
+        try {
+            return exists() ? doIsSymbolicLink() : false;
+        } catch (final Exception exc) {
+            throw new FileSystemException("vfs.provider/check-is-symbolic-link.error", fileName, exc);
+        }
+    }
+
+    /**
+     * 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.
+     */
+    @Override
+    public boolean isWriteable() throws FileSystemException {
+        try {
+            if (exists()) {
+                return doIsWriteable();
+            }
+            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);
+        }
+    }
+
+    /**
+     * Returns an iterator over a set of all FileObject in this file object.
+     *
+     * @return an Iterator.
+     */
+    @Override
+    public Iterator<FileObject> iterator() {
+        try {
+            return listFiles(Selectors.SELECT_ALL).iterator();
+        } catch (final FileSystemException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    /**
+     * 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.
+     */
+    public List<FileObject> listFiles(final FileSelector selector) throws FileSystemException {
+        if (!exists() || selector == null) {
+            return null;
+        }
+
+        final ArrayList<FileObject> list = new ArrayList<>();
+        this.findFiles(selector, true, list);
+        return list;
+    }
+
+    /**
+     * 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()) {
+                throw new FileSystemException("vfs.provider/rename-parent-read-only.error", getName(),
+                        getParent().getName());
+            }
+        } else {
+            if (!isWriteable()) {
+                throw new FileSystemException("vfs.provider/rename-read-only.error", getName());
+            }
+        }
+
+        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
+            try {
+                attach();
+                // remember type to avoid attach
+                final FileType srcType = getType();
+
+                doRename(destFile);
+
+                FileObjectUtils.getAbstractFileObject(destFile).handleCreate(srcType);
+                destFile.close(); // now the destFile is no longer imaginary. force reattach.
+
+                handleDelete(); // fire delete-events. This file-object (src) is like deleted.
+            } catch (final RuntimeException re) {
+                throw re;
+            } catch (final Exception exc) {
+                throw new FileSystemException("vfs.provider/rename.error", exc, getName(), destFile.getName());
+            }
+        } 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))
+                    && fileSystem.hasCapability(Capability.GET_LAST_MODIFIED)) {
+                destFile.getContent().setLastModifiedTime(this.getContent().getLastModifiedTime());
+            }
+
+            deleteSelf();
+        }
+
+    }
+
+    /**
+     * Clled after this file-object closed all its streams.
+     */
+    protected void notifyAllStreamsClosed() {
+        // noop
+    }
+
+    /**
+     * Notify the parent of a change to its children, when a child is created or deleted.
+     *
+     * @param childName The name of the child.
+     * @param newType The type of the child.
+     * @throws Exception if an error occurs.
+     */
+    private void notifyParent(final FileName childName, final FileType newType) throws Exception {
+        if (parent == null) {
+            final FileName parentName = fileName.getParent();
+            if (parentName != null) {
+                // Locate the parent, if it is cached
+                parent = fileSystem.getFileFromCache(parentName);
+            }
+        }
+
+        if (parent != null) {
+            FileObjectUtils.getAbstractFileObject(parent).childrenChanged(childName, newType);
+        }
+    }
+
+    /**
+     * Called when the type or content of this file changes.
+     * <p>
+     * This implementation does nothing.
+     * </p>
+     *
+     * @throws Exception if an error occurs.
+     */
+    protected void onChange() throws Exception {
+        // noop
+    }
+
+    /**
+     * 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.
+     * </p>
+     *
+     * @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 {
+        // noop
+    }
+
+    /**
+     * This will prepare the fileObject to get resynchronized with the underlying file system if required.
+     *
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public void refresh() throws FileSystemException {
+        // Detach from the file
+        try {
+            detach();
+        } catch (final Exception e) {
+            throw new FileSystemException("vfs.provider/resync.error", fileName, e);
+        }
+    }
+
+    private void removeChildrenCache() {
+        children = null;
+    }
+
+    private FileObject resolveFile(final FileName child) throws FileSystemException {
+        return fileSystem.resolveFile(child);
+    }
+
+    /**
+     * Finds a file, relative to this file.
+     *
+     * @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.
+     */
+    @Override
+    public FileObject resolveFile(final String path) throws FileSystemException {
+        final FileName otherName = fileSystem.getFileSystemManager().resolveName(fileName, path);
+        return fileSystem.resolveFile(otherName);
+    }
+
+    /**
+     * 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.
+     */
+    @Override
+    public FileObject resolveFile(final String name, final NameScope scope) throws FileSystemException {
+        // return fs.resolveFile(this.name.resolveName(name, scope));
+        return fileSystem.resolveFile(fileSystem.getFileSystemManager().resolveName(this.fileName, name, scope));
+    }
+
+    private FileObject[] resolveFiles(final FileName[] children) throws FileSystemException {
+        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]);
+        }
+
+        return objects;
+    }
+
+    @Override
+    public boolean setExecutable(final boolean readable, final boolean ownerOnly) throws FileSystemException {
+        try {
+            return exists() ? doSetExecutable(readable, ownerOnly) : false;
+        } catch (final Exception exc) {
+            throw new FileSystemException("vfs.provider/set-executable.error", fileName, exc);
+        }
+    }
+
+    private void setFileType(final FileType type) {
+        if (type != null && type != FileType.IMAGINARY) {
+            try {
+                fileName.setType(type);
+            } catch (final FileSystemException e) {
+                throw new RuntimeException(e.getMessage());
+            }
+        }
+        this.type = type;
+    }
+
+    @Override
+    public boolean setReadable(final boolean readable, final boolean ownerOnly) throws FileSystemException {
+        try {
+            return exists() ? doSetReadable(readable, ownerOnly) : false;
+        } catch (final Exception exc) {
+            throw new FileSystemException("vfs.provider/set-readable.error", fileName, exc);
+        }
+    }
+
+    // --- OPERATIONS ---
+
+    @Override
+    public boolean setWritable(final boolean readable, final boolean ownerOnly) throws FileSystemException {
+        try {
+            return exists() ? doSetWritable(readable, ownerOnly) : false;
+        } catch (final Exception exc) {
+            throw new FileSystemException("vfs.provider/set-writeable.error", fileName, exc);
+        }
+    }
+
+    /**
+     * Returns the URI as a String.
+     *
+     * @return Returns the URI as a String.
+     */
+    @Override
+    public String toString() {
+        return fileName.getURI();
+    }
+}
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/DefaultFileContent.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/DefaultFileContent.java
index 870f4ea..009a87a 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/DefaultFileContent.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/DefaultFileContent.java
@@ -1,813 +1,812 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.vfs2.provider;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.cert.Certificate;
-import java.util.Collections;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.vfs2.FileContent;
-import org.apache.commons.vfs2.FileContentInfo;
-import org.apache.commons.vfs2.FileContentInfoFactory;
-import org.apache.commons.vfs2.FileObject;
-import org.apache.commons.vfs2.FileSystemException;
-import org.apache.commons.vfs2.RandomAccessContent;
-import org.apache.commons.vfs2.util.MonitorInputStream;
-import org.apache.commons.vfs2.util.MonitorOutputStream;
-import org.apache.commons.vfs2.util.MonitorRandomAccessContent;
-import org.apache.commons.vfs2.util.RandomAccessMode;
-
-/**
- * The content of a file.
- */
-public final class DefaultFileContent implements FileContent {
-
-    /*
-     * static final int STATE_NONE = 0; static final int STATE_READING = 1; static final int STATE_WRITING = 2; static
-     * final int STATE_RANDOM_ACCESS = 3;
-     */
-
-    static final int STATE_CLOSED = 0;
-    static final int STATE_OPENED = 1;
-
-    /**
-     * The default buffer size for {@link #write(OutputStream)}
-     */
-    private static final int WRITE_BUFFER_SIZE = 4096;
-
-    private final AbstractFileObject fileObject;
-    private Map<String, Object> attrs;
-    private Map<String, Object> roAttrs;
-    private FileContentInfo fileContentInfo;
-    private final FileContentInfoFactory fileContentInfoFactory;
-
-    private final ThreadLocal<FileContentThreadData> threadLocal = new ThreadLocal<>();
-    private boolean resetAttributes;
-
-    /**
-     * Counts open streams for this file.
-     */
-    private int openStreams;
-
-    public DefaultFileContent(final AbstractFileObject file, final FileContentInfoFactory fileContentInfoFactory) {
-        this.fileObject = file;
-        this.fileContentInfoFactory = fileContentInfoFactory;
-    }
-
-    private FileContentThreadData getOrCreateThreadData() {
-        FileContentThreadData data = this.threadLocal.get();
-        if (data == null) {
-            data = new FileContentThreadData();
-            this.threadLocal.set(data);
-        }
-        return data;
-    }
-
-    void streamOpened() {
-        synchronized (this) {
-            openStreams++;
-        }
-        ((AbstractFileSystem) fileObject.getFileSystem()).streamOpened();
-    }
-
-    void streamClosed() {
-        synchronized (this) {
-            if (openStreams > 0) {
-                openStreams--;
-                if (openStreams < 1) {
-                    fileObject.notifyAllStreamsClosed();
-                }
-            }
-        }
-        ((AbstractFileSystem) fileObject.getFileSystem()).streamClosed();
-    }
-
-    /**
-     * Returns the file that this is the content of.
-     *
-     * @return the FileObject.
-     */
-    @Override
-    public FileObject getFile() {
-        return fileObject;
-    }
-
-    /**
-     * Returns the size of the content (in bytes).
-     *
-     * @return The size of the content (in bytes).
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public long getSize() throws FileSystemException {
-        // Do some checking
-        if (!fileObject.getType().hasContent()) {
-            throw new FileSystemException("vfs.provider/get-size-not-file.error", fileObject);
-        }
-        /*
-         * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw
-         * new FileSystemException("vfs.provider/get-size-write.error", file); }
-         */
-
-        try {
-            // Get the size
-            return fileObject.doGetContentSize();
-        } catch (final Exception exc) {
-            throw new FileSystemException("vfs.provider/get-size.error", exc, fileObject);
-        }
-    }
-
-    /**
-     * Returns the last-modified timestamp.
-     *
-     * @return The last modified timestamp.
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public long getLastModifiedTime() throws FileSystemException {
-        /*
-         * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw
-         * new FileSystemException("vfs.provider/get-last-modified-writing.error", file); }
-         */
-        if (!fileObject.getType().hasAttributes()) {
-            throw new FileSystemException("vfs.provider/get-last-modified-no-exist.error", fileObject);
-        }
-        try {
-            return fileObject.doGetLastModifiedTime();
-        } catch (final Exception e) {
-            throw new FileSystemException("vfs.provider/get-last-modified.error", fileObject, e);
-        }
-    }
-
-    /**
-     * Sets the last-modified timestamp.
-     *
-     * @param modTime The last modified timestamp.
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public void setLastModifiedTime(final long modTime) throws FileSystemException {
-        /*
-         * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw
-         * new FileSystemException("vfs.provider/set-last-modified-writing.error", file); }
-         */
-        if (!fileObject.getType().hasAttributes()) {
-            throw new FileSystemException("vfs.provider/set-last-modified-no-exist.error", fileObject);
-        }
-        try {
-            if (!fileObject.doSetLastModifiedTime(modTime)) {
-                throw new FileSystemException("vfs.provider/set-last-modified.error", fileObject);
-            }
-        } catch (final Exception e) {
-            throw new FileSystemException("vfs.provider/set-last-modified.error", fileObject, e);
-        }
-    }
-
-    /**
-     * Checks if an attribute exists.
-     *
-     * @param attrName The name of the attribute to check.
-     * @return true if the attribute is associated with the file.
-     * @throws FileSystemException if an error occurs.
-     * @since 2.0
-     */
-    @Override
-    public boolean hasAttribute(final String attrName) throws FileSystemException {
-        if (!fileObject.getType().hasAttributes()) {
-            throw new FileSystemException("vfs.provider/exists-attributes-no-exist.error", fileObject);
-        }
-        getAttributes();
-        return attrs.containsKey(attrName);
-    }
-
-    /**
-     * Returns a read-only map of this file's attributes.
-     *
-     * @return a Map of the file's attributes.
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public Map<String, Object> getAttributes() throws FileSystemException {
-        if (!fileObject.getType().hasAttributes()) {
-            throw new FileSystemException("vfs.provider/get-attributes-no-exist.error", fileObject);
-        }
-        if (resetAttributes || roAttrs == null) {
-            try {
-                synchronized (this) {
-                    attrs = fileObject.doGetAttributes();
-                    roAttrs = Collections.unmodifiableMap(attrs);
-                    resetAttributes = false;
-                }
-            } catch (final Exception e) {
-                throw new FileSystemException("vfs.provider/get-attributes.error", fileObject, e);
-            }
-        }
-        return roAttrs;
-    }
-
-    /**
-     * Used internally to flag situations where the file attributes should be reretrieved.
-     *
-     * @since 2.0
-     */
-    public void resetAttributes() {
-        resetAttributes = true;
-    }
-
-    /**
-     * Lists the attributes of this file.
-     *
-     * @return An array of attribute names.
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public String[] getAttributeNames() throws FileSystemException {
-        getAttributes();
-        final Set<String> names = attrs.keySet();
-        return names.toArray(new String[names.size()]);
-    }
-
-    /**
-     * Gets the value of an attribute.
-     *
-     * @param attrName The attribute name.
-     * @return The value of the attribute or null.
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public Object getAttribute(final String attrName) throws FileSystemException {
-        getAttributes();
-        return attrs.get(attrName);
-    }
-
-    /**
-     * Sets the value of an attribute.
-     *
-     * @param attrName The name of the attribute to add.
-     * @param value The value of the attribute.
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public void setAttribute(final String attrName, final Object value) throws FileSystemException {
-        if (!fileObject.getType().hasAttributes()) {
-            throw new FileSystemException("vfs.provider/set-attribute-no-exist.error", attrName, fileObject);
-        }
-        try {
-            fileObject.doSetAttribute(attrName, value);
-        } catch (final Exception e) {
-            throw new FileSystemException("vfs.provider/set-attribute.error", e, attrName, fileObject);
-        }
-
-        if (attrs != null) {
-            attrs.put(attrName, value);
-        }
-    }
-
-    /**
-     * Removes an attribute.
-     *
-     * @param attrName The name of the attribute to remove.
-     * @throws FileSystemException if an error occurs.
-     * @since 2.0
-     */
-    @Override
-    public void removeAttribute(final String attrName) throws FileSystemException {
-        if (!fileObject.getType().hasAttributes()) {
-            throw new FileSystemException("vfs.provider/remove-attribute-no-exist.error", fileObject);
-        }
-
-        try {
-            fileObject.doRemoveAttribute(attrName);
-        } catch (final Exception e) {
-            throw new FileSystemException("vfs.provider/remove-attribute.error", e, attrName, fileObject);
-        }
-
-        if (attrs != null) {
-            attrs.remove(attrName);
-        }
-    }
-
-    /**
-     * Returns the certificates used to sign this file.
-     *
-     * @return An array of Certificates.
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public Certificate[] getCertificates() throws FileSystemException {
-        if (!fileObject.exists()) {
-            throw new FileSystemException("vfs.provider/get-certificates-no-exist.error", fileObject);
-        }
-        /*
-         * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw
-         * new FileSystemException("vfs.provider/get-certificates-writing.error", file); }
-         */
-
-        try {
-            final Certificate[] certs = fileObject.doGetCertificates();
-            if (certs != null) {
-                return certs;
-            }
-            return new Certificate[0];
-        } catch (final Exception e) {
-            throw new FileSystemException("vfs.provider/get-certificates.error", fileObject, e);
-        }
-    }
-
-    /**
-     * Returns an input stream for reading the content.
-     *
-     * @return The InputStream
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public InputStream getInputStream() throws FileSystemException {
-        return buildInputStream(0);
-    }
-
-    /**
-     * Returns an input stream for reading the content.
-     *
-     * @param bufferSize The buffer size to use.
-     * @return The InputStream
-     * @throws FileSystemException if an error occurs.
-     * @since 2.4
-     */
-    @Override
-    public InputStream getInputStream(final int bufferSize) throws FileSystemException {
-        return buildInputStream(bufferSize);
-    }
-
-    /**
-     * Returns an input/output stream to use to read and write the content of the file in an random manner.
-     *
-     * @param mode The RandomAccessMode.
-     * @return A RandomAccessContent object to access the file.
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public RandomAccessContent getRandomAccessContent(final RandomAccessMode mode) throws FileSystemException {
-        /*
-         * if (getThreadData().getState() != STATE_NONE) { throw new
-         * FileSystemException("vfs.provider/read-in-use.error", file); }
-         */
-
-        // Get the content
-        final RandomAccessContent rastr = fileObject.getRandomAccessContent(mode);
-
-        final FileRandomAccessContent rac = new FileRandomAccessContent(fileObject, rastr);
-
-        getOrCreateThreadData().addRastr(rac);
-        streamOpened();
-
-        return rac;
-    }
-
-    /**
-     * Returns an output stream for writing the content.
-     *
-     * @return The OutputStream for the file.
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public OutputStream getOutputStream() throws FileSystemException {
-        return getOutputStream(false);
-    }
-
-    /**
-     * Returns an output stream for writing the content in append mode.
-     *
-     * @param bAppend true if the data written should be appended.
-     * @return The OutputStream for the file.
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public OutputStream getOutputStream(final boolean bAppend) throws FileSystemException {
-        return buildOutputStream(bAppend, 0);
-    }
-
-    /**
-     * Returns an output stream for writing the content.
-     *
-     * @param bufferSize The buffer size to use.
-     * @return The OutputStream for the file.
-     * @throws FileSystemException if an error occurs.
-     * @since 2.4
-     */
-    @Override
-    public OutputStream getOutputStream(final int bufferSize) throws FileSystemException {
-        return buildOutputStream(false, bufferSize);
-    }
-
-    /**
-     * Returns an output stream for writing the content in append mode.
-     *
-     * @param bAppend true if the data written should be appended.
-     * @param bufferSize The buffer size to use.
-     * @return The OutputStream for the file.
-     * @throws FileSystemException if an error occurs.
-     * @since 2.4
-     */
-    @Override
-    public OutputStream getOutputStream(final boolean bAppend, final int bufferSize) throws FileSystemException {
-        return buildOutputStream(bAppend, bufferSize);
-    }
-
-    /**
-     * Closes all resources used by the content, including all streams, readers and writers.
-     *
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public void close() throws FileSystemException {
-        FileSystemException caught = null;
-        try {
-            final FileContentThreadData fileContentThreadData = getOrCreateThreadData();
-
-            // Close the input stream
-            while (fileContentThreadData.getInstrsSize() > 0) {
-                final FileContentInputStream inputStream = (FileContentInputStream) fileContentThreadData
-                        .removeInstr(0);
-                try {
-                    inputStream.close();
-                } catch (final FileSystemException ex) {
-                    caught = ex;
-
-                }
-            }
-
-            // Close the randomAccess stream
-            while (fileContentThreadData.getRastrsSize() > 0) {
-                final FileRandomAccessContent randomAccessContent = (FileRandomAccessContent) fileContentThreadData
-                        .removeRastr(0);
-                try {
-                    randomAccessContent.close();
-                } catch (final FileSystemException ex) {
-                    caught = ex;
-                }
-            }
-
-            // Close the output stream
-            final FileContentOutputStream outputStream = fileContentThreadData.getOutstr();
-            if (outputStream != null) {
-                fileContentThreadData.setOutstr(null);
-                try {
-                    outputStream.close();
-                } catch (final FileSystemException ex) {
-                    caught = ex;
-                }
-            }
-        } finally {
-            threadLocal.remove();
-        }
-
-        // throw last error (out >> rac >> input) after all closes have been tried
-        if (caught != null) {
-            throw caught;
-        }
-    }
-
-    private InputStream buildInputStream(final int bufferSize) throws FileSystemException {
-        /*
-         * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw
-         * new FileSystemException("vfs.provider/read-in-use.error", file); }
-         */
-
-        // Get the raw input stream
-        final InputStream inputStream = fileObject.getInputStream();
-
-        final InputStream wrappedInputStream = bufferSize == 0 ?
-            new FileContentInputStream(fileObject, inputStream) :
-            new FileContentInputStream(fileObject, inputStream, bufferSize);
-
-        getOrCreateThreadData().addInstr(wrappedInputStream);
-        streamOpened();
-
-        return wrappedInputStream;
-    }
-
-    private OutputStream buildOutputStream(final boolean bAppend, final int bufferSize) throws FileSystemException {
-        /*
-         * if (getThreadData().getState() != STATE_NONE)
-         */
-        final FileContentThreadData streams = getOrCreateThreadData();
-
-        if (streams.getOutstr() != null) {
-            throw new FileSystemException("vfs.provider/write-in-use.error", fileObject);
-        }
-
-        // Get the raw output stream
-        final OutputStream outstr = fileObject.getOutputStream(bAppend);
-
-        // Create and set wrapper
-        final FileContentOutputStream wrapped = bufferSize == 0 ?
-            new FileContentOutputStream(fileObject, outstr) :
-            new FileContentOutputStream(fileObject, outstr, bufferSize);
-        streams.setOutstr(wrapped);
-        streamOpened();
-
-        return wrapped;
-    }
-
-    /**
-     * Handles the end of input stream.
-     */
-    private void endInput(final FileContentInputStream instr) {
-        final FileContentThreadData fileContentThreadData = threadLocal.get();
-        if (fileContentThreadData != null) {
-            fileContentThreadData.removeInstr(instr);
-        }
-        if (fileContentThreadData == null || !fileContentThreadData.hasStreams()) {
-            // remove even when no value is set to remove key
-            threadLocal.remove();
-        }
-        streamClosed();
-    }
-
-    /**
-     * Handles the end of random access.
-     */
-    private void endRandomAccess(final RandomAccessContent rac) {
-        final FileContentThreadData fileContentThreadData = threadLocal.get();
-        if (fileContentThreadData != null) {
-            fileContentThreadData.removeRastr(rac);
-        }
-        if (fileContentThreadData == null || !fileContentThreadData.hasStreams()) {
-            // remove even when no value is set to remove key
-            threadLocal.remove();
-        }
-        streamClosed();
-    }
-
-    /**
-     * Handles the end of output stream.
-     */
-    private void endOutput() throws Exception {
-        final FileContentThreadData fileContentThreadData = threadLocal.get();
-        if (fileContentThreadData != null) {
-            fileContentThreadData.setOutstr(null);
-        }
-        if (fileContentThreadData == null || !fileContentThreadData.hasStreams()) {
-            // remove even when no value is set to remove key
-            threadLocal.remove();
-        }
-        streamClosed();
-        fileObject.endOutput();
-    }
-
-    /**
-     * Checks if a input and/or output stream is open.
-     * <p>
-     * This checks only the scope of the current thread.
-     * </p>
-     *
-     * @return true if this is the case
-     */
-    @Override
-    public boolean isOpen() {
-        final FileContentThreadData fileContentThreadData = threadLocal.get();
-        if (fileContentThreadData != null && fileContentThreadData.hasStreams()) {
-            return true;
-        }
-        // threadData.get() created empty entry
-        threadLocal.remove();
-        return false;
-    }
-
-    /**
-     * Checks if an input or output stream is open. This checks all threads.
-     *
-     * @return true if this is the case
-     */
-    public boolean isOpenGlobal() {
-        synchronized (this) {
-            return openStreams > 0;
-        }
-    }
-
-    /**
-     * An input stream for reading content. Provides buffering, and end-of-stream monitoring.
-     */
-    private final class FileContentInputStream extends MonitorInputStream {
-        // avoid gc
-        private final FileObject file;
-
-        FileContentInputStream(final FileObject file, final InputStream instr) {
-            super(instr);
-            this.file = file;
-        }
-
-        FileContentInputStream(final FileObject file, final InputStream instr, final int bufferSize) {
-            super(instr, bufferSize);
-            this.file = file;
-        }
-
-        /**
-         * Closes this input stream.
-         */
-        @Override
-        public void close() throws FileSystemException {
-            try {
-                super.close();
-            } catch (final IOException e) {
-                throw new FileSystemException("vfs.provider/close-instr.error", file, e);
-            }
-        }
-
-        /**
-         * Called after the stream has been closed.
-         */
-        @Override
-        protected void onClose() throws IOException {
-            try {
-                super.onClose();
-            } finally {
-                endInput(this);
-            }
-        }
-    }
-
-    /**
-     * An input/output stream for reading/writing content on random positions
-     */
-    private final class FileRandomAccessContent extends MonitorRandomAccessContent {
-        // also avoids gc
-        private final FileObject file;
-
-        FileRandomAccessContent(final FileObject file, final RandomAccessContent content) {
-            super(content);
-            this.file = file;
-        }
-
-        /**
-         * Called after the stream has been closed.
-         */
-        @Override
-        protected void onClose() throws IOException {
-            try {
-                super.onClose();
-            } finally {
-                endRandomAccess(this);
-            }
-        }
-
-        @Override
-        public void close() throws FileSystemException {
-            try {
-                super.close();
-            } catch (final IOException e) {
-                throw new FileSystemException("vfs.provider/close-rac.error", file, e);
-            }
-        }
-    }
-
-    /**
-     * An output stream for writing content.
-     */
-    final class FileContentOutputStream extends MonitorOutputStream {
-        // avoid gc
-        private final FileObject file;
-
-        FileContentOutputStream(final FileObject file, final OutputStream outstr) {
-            super(outstr);
-            this.file = file;
-        }
-
-        FileContentOutputStream(final FileObject file, final OutputStream outstr, final int bufferSize) {
-            super(outstr, bufferSize);
-            this.file = file;
-        }
-
-        /**
-         * Closes this output stream.
-         */
-        @Override
-        public void close() throws FileSystemException {
-            try {
-                super.close();
-            } catch (final IOException e) {
-                throw new FileSystemException("vfs.provider/close-outstr.error", file, e);
-            }
-        }
-
-        /**
-         * Called after this stream is closed.
-         */
-        @Override
-        protected void onClose() throws IOException {
-            try {
-                super.onClose();
-            } finally {
-                try {
-                    endOutput();
-                } catch (final Exception e) {
-                    throw new FileSystemException("vfs.provider/close-outstr.error", file, e);
-                }
-            }
-        }
-    }
-
-    /**
-     * Gets the FileContentInfo which describes the content-type, content-encoding
-     *
-     * @return The FileContentInfo.
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public FileContentInfo getContentInfo() throws FileSystemException {
-        if (fileContentInfo == null) {
-            fileContentInfo = fileContentInfoFactory.create(this);
-        }
-
-        return fileContentInfo;
-    }
-
-    /**
-     * Writes this content to another FileContent.
-     *
-     * @param fileContent The target FileContent.
-     * @return the total number of bytes written
-     * @throws IOException if an error occurs writing the content.
-     * @since 2.1
-     */
-    @Override
-    public long write(final FileContent fileContent) throws IOException {
-        final OutputStream output = fileContent.getOutputStream();
-        try {
-            return this.write(output);
-        } finally {
-            output.close();
-        }
-    }
-
-    /**
-     * Writes this content to another FileObject.
-     *
-     * @param file The target FileObject.
-     * @return the total number of bytes written
-     * @throws IOException if an error occurs writing the content.
-     * @since 2.1
-     */
-    @Override
-    public long write(final FileObject file) throws IOException {
-        return write(file.getContent());
-    }
-
-    /**
-     * Writes this content to an OutputStream.
-     *
-     * @param output The target OutputStream.
-     * @return the total number of bytes written
-     * @throws IOException if an error occurs writing the content.
-     * @since 2.1
-     */
-    @Override
-    public long write(final OutputStream output) throws IOException {
-        return write(output, WRITE_BUFFER_SIZE);
-    }
-
-    /**
-     * Writes this content to an OutputStream.
-     *
-     * @param output The target OutputStream.
-     * @param bufferSize The buffer size to write data chunks.
-     * @return the total number of bytes written
-     * @throws IOException if an error occurs writing the file.
-     * @since 2.1
-     */
-    @Override
-    public long write(final OutputStream output, final int bufferSize) throws IOException {
-        final InputStream input = this.getInputStream();
-        long count = 0;
-        try {
-            // This read/write code from Apache Commons IO
-            final byte[] buffer = new byte[bufferSize];
-            int n = 0;
-            while (-1 != (n = input.read(buffer))) {
-                output.write(buffer, 0, n);
-                count += n;
-            }
-        } finally {
-            input.close();
-        }
-        return count;
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.vfs2.provider;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.cert.Certificate;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.vfs2.FileContent;
+import org.apache.commons.vfs2.FileContentInfo;
+import org.apache.commons.vfs2.FileContentInfoFactory;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.RandomAccessContent;
+import org.apache.commons.vfs2.util.MonitorInputStream;
+import org.apache.commons.vfs2.util.MonitorOutputStream;
+import org.apache.commons.vfs2.util.MonitorRandomAccessContent;
+import org.apache.commons.vfs2.util.RandomAccessMode;
+
+/**
+ * The content of a file.
+ */
+public final class DefaultFileContent implements FileContent {
+
+    /*
+     * static final int STATE_NONE = 0; static final int STATE_READING = 1; static final int STATE_WRITING = 2; static
+     * final int STATE_RANDOM_ACCESS = 3;
+     */
+
+    static final int STATE_CLOSED = 0;
+    static final int STATE_OPENED = 1;
+
+    /**
+     * The default buffer size for {@link #write(OutputStream)}
+     */
+    private static final int WRITE_BUFFER_SIZE = 4096;
+
+    private final AbstractFileObject fileObject;
+    private Map<String, Object> attrs;
+    private Map<String, Object> roAttrs;
+    private FileContentInfo fileContentInfo;
+    private final FileContentInfoFactory fileContentInfoFactory;
+
+    private final ThreadLocal<FileContentThreadData> threadLocal = new ThreadLocal<>();
+    private boolean resetAttributes;
+
+    /**
+     * Counts open streams for this file.
+     */
+    private int openStreams;
+
+    public DefaultFileContent(final AbstractFileObject file, final FileContentInfoFactory fileContentInfoFactory) {
+        this.fileObject = file;
+        this.fileContentInfoFactory = fileContentInfoFactory;
+    }
+
+    private FileContentThreadData getOrCreateThreadData() {
+        FileContentThreadData data = this.threadLocal.get();
+        if (data == null) {
+            data = new FileContentThreadData();
+            this.threadLocal.set(data);
+        }
+        return data;
+    }
+
+    void streamOpened() {
+        synchronized (this) {
+            openStreams++;
+        }
+        ((AbstractFileSystem) fileObject.getFileSystem()).streamOpened();
+    }
+
+    void streamClosed() {
+        synchronized (this) {
+            if (openStreams > 0) {
+                openStreams--;
+                if (openStreams < 1) {
+                    fileObject.notifyAllStreamsClosed();
+                }
+            }
+        }
+        ((AbstractFileSystem) fileObject.getFileSystem()).streamClosed();
+    }
+
+    /**
+     * Returns the file that this is the content of.
+     *
+     * @return the FileObject.
+     */
+    @Override
+    public FileObject getFile() {
+        return fileObject;
+    }
+
+    /**
+     * Returns the size of the content (in bytes).
+     *
+     * @return The size of the content (in bytes).
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public long getSize() throws FileSystemException {
+        // Do some checking
+        if (!fileObject.getType().hasContent()) {
+            throw new FileSystemException("vfs.provider/get-size-not-file.error", fileObject);
+        }
+        /*
+         * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw
+         * new FileSystemException("vfs.provider/get-size-write.error", file); }
+         */
+
+        try {
+            // Get the size
+            return fileObject.doGetContentSize();
+        } catch (final Exception exc) {
+            throw new FileSystemException("vfs.provider/get-size.error", exc, fileObject);
+        }
+    }
+
+    /**
+     * Returns the last-modified timestamp.
+     *
+     * @return The last modified timestamp.
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public long getLastModifiedTime() throws FileSystemException {
+        /*
+         * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw
+         * new FileSystemException("vfs.provider/get-last-modified-writing.error", file); }
+         */
+        if (!fileObject.getType().hasAttributes()) {
+            throw new FileSystemException("vfs.provider/get-last-modified-no-exist.error", fileObject);
+        }
+        try {
+            return fileObject.doGetLastModifiedTime();
+        } catch (final Exception e) {
+            throw new FileSystemException("vfs.provider/get-last-modified.error", fileObject, e);
+        }
+    }
+
+    /**
+     * Sets the last-modified timestamp.
+     *
+     * @param modTime The last modified timestamp.
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public void setLastModifiedTime(final long modTime) throws FileSystemException {
+        /*
+         * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw
+         * new FileSystemException("vfs.provider/set-last-modified-writing.error", file); }
+         */
+        if (!fileObject.getType().hasAttributes()) {
+            throw new FileSystemException("vfs.provider/set-last-modified-no-exist.error", fileObject);
+        }
+        try {
+            if (!fileObject.doSetLastModifiedTime(modTime)) {
+                throw new FileSystemException("vfs.provider/set-last-modified.error", fileObject);
+            }
+        } catch (final Exception e) {
+            throw new FileSystemException("vfs.provider/set-last-modified.error", fileObject, e);
+        }
+    }
+
+    /**
+     * Checks if an attribute exists.
+     *
+     * @param attrName The name of the attribute to check.
+     * @return true if the attribute is associated with the file.
+     * @throws FileSystemException if an error occurs.
+     * @since 2.0
+     */
+    @Override
+    public boolean hasAttribute(final String attrName) throws FileSystemException {
+        if (!fileObject.getType().hasAttributes()) {
+            throw new FileSystemException("vfs.provider/exists-attributes-no-exist.error", fileObject);
+        }
+        getAttributes();
+        return attrs.containsKey(attrName);
+    }
+
+    /**
+     * Returns a read-only map of this file's attributes.
+     *
+     * @return a Map of the file's attributes.
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public Map<String, Object> getAttributes() throws FileSystemException {
+        if (!fileObject.getType().hasAttributes()) {
+            throw new FileSystemException("vfs.provider/get-attributes-no-exist.error", fileObject);
+        }
+        if (resetAttributes || roAttrs == null) {
+            try {
+                synchronized (this) {
+                    attrs = fileObject.doGetAttributes();
+                    roAttrs = Collections.unmodifiableMap(attrs);
+                    resetAttributes = false;
+                }
+            } catch (final Exception e) {
+                throw new FileSystemException("vfs.provider/get-attributes.error", fileObject, e);
+            }
+        }
+        return roAttrs;
+    }
+
+    /**
+     * Used internally to flag situations where the file attributes should be reretrieved.
+     *
+     * @since 2.0
+     */
+    public void resetAttributes() {
+        resetAttributes = true;
+    }
+
+    /**
+     * Lists the attributes of this file.
+     *
+     * @return An array of attribute names.
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public String[] getAttributeNames() throws FileSystemException {
+        getAttributes();
+        final Set<String> names = attrs.keySet();
+        return names.toArray(new String[names.size()]);
+    }
+
+    /**
+     * Gets the value of an attribute.
+     *
+     * @param attrName The attribute name.
+     * @return The value of the attribute or null.
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public Object getAttribute(final String attrName) throws FileSystemException {
+        getAttributes();
+        return attrs.get(attrName);
+    }
+
+    /**
+     * Sets the value of an attribute.
+     *
+     * @param attrName The name of the attribute to add.
+     * @param value The value of the attribute.
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public void setAttribute(final String attrName, final Object value) throws FileSystemException {
+        if (!fileObject.getType().hasAttributes()) {
+            throw new FileSystemException("vfs.provider/set-attribute-no-exist.error", attrName, fileObject);
+        }
+        try {
+            fileObject.doSetAttribute(attrName, value);
+        } catch (final Exception e) {
+            throw new FileSystemException("vfs.provider/set-attribute.error", e, attrName, fileObject);
+        }
+
+        if (attrs != null) {
+            attrs.put(attrName, value);
+        }
+    }
+
+    /**
+     * Removes an attribute.
+     *
+     * @param attrName The name of the attribute to remove.
+     * @throws FileSystemException if an error occurs.
+     * @since 2.0
+     */
+    @Override
+    public void removeAttribute(final String attrName) throws FileSystemException {
+        if (!fileObject.getType().hasAttributes()) {
+            throw new FileSystemException("vfs.provider/remove-attribute-no-exist.error", fileObject);
+        }
+
+        try {
+            fileObject.doRemoveAttribute(attrName);
+        } catch (final Exception e) {
+            throw new FileSystemException("vfs.provider/remove-attribute.error", e, attrName, fileObject);
+        }
+
+        if (attrs != null) {
+            attrs.remove(attrName);
+        }
+    }
+
+    /**
+     * Returns the certificates used to sign this file.
+     *
+     * @return An array of Certificates.
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public Certificate[] getCertificates() throws FileSystemException {
+        if (!fileObject.exists()) {
+            throw new FileSystemException("vfs.provider/get-certificates-no-exist.error", fileObject);
+        }
+        /*
+         * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw
+         * new FileSystemException("vfs.provider/get-certificates-writing.error", file); }
+         */
+
+        try {
+            final Certificate[] certs = fileObject.doGetCertificates();
+            if (certs != null) {
+                return certs;
+            }
+            return new Certificate[0];
+        } catch (final Exception e) {
+            throw new FileSystemException("vfs.provider/get-certificates.error", fileObject, e);
+        }
+    }
+
+    /**
+     * Returns an input stream for reading the content.
+     *
+     * @return The InputStream
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public InputStream getInputStream() throws FileSystemException {
+        return buildInputStream(0);
+    }
+
+    /**
+     * Returns an input stream for reading the content.
+     *
+     * @param bufferSize The buffer size to use.
+     * @return The InputStream
+     * @throws FileSystemException if an error occurs.
+     * @since 2.4
+     */
+    @Override
+    public InputStream getInputStream(final int bufferSize) throws FileSystemException {
+        return buildInputStream(bufferSize);
+    }
+
+    /**
+     * Returns an input/output stream to use to read and write the content of the file in an random manner.
+     *
+     * @param mode The RandomAccessMode.
+     * @return A RandomAccessContent object to access the file.
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public RandomAccessContent getRandomAccessContent(final RandomAccessMode mode) throws FileSystemException {
+        /*
+         * if (getThreadData().getState() != STATE_NONE) { throw new
+         * FileSystemException("vfs.provider/read-in-use.error", file); }
+         */
+
+        // Get the content
+        final RandomAccessContent rastr = fileObject.getRandomAccessContent(mode);
+
+        final FileRandomAccessContent rac = new FileRandomAccessContent(fileObject, rastr);
+
+        getOrCreateThreadData().addRastr(rac);
+        streamOpened();
+
+        return rac;
+    }
+
+    /**
+     * Returns an output stream for writing the content.
+     *
+     * @return The OutputStream for the file.
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public OutputStream getOutputStream() throws FileSystemException {
+        return getOutputStream(false);
+    }
+
+    /**
+     * Returns an output stream for writing the content in append mode.
+     *
+     * @param bAppend true if the data written should be appended.
+     * @return The OutputStream for the file.
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public OutputStream getOutputStream(final boolean bAppend) throws FileSystemException {
+        return buildOutputStream(bAppend, 0);
+    }
+
+    /**
+     * Returns an output stream for writing the content.
+     *
+     * @param bufferSize The buffer size to use.
+     * @return The OutputStream for the file.
+     * @throws FileSystemException if an error occurs.
+     * @since 2.4
+     */
+    @Override
+    public OutputStream getOutputStream(final int bufferSize) throws FileSystemException {
+        return buildOutputStream(false, bufferSize);
+    }
+
+    /**
+     * Returns an output stream for writing the content in append mode.
+     *
+     * @param bAppend true if the data written should be appended.
+     * @param bufferSize The buffer size to use.
+     * @return The OutputStream for the file.
+     * @throws FileSystemException if an error occurs.
+     * @since 2.4
+     */
+    @Override
+    public OutputStream getOutputStream(final boolean bAppend, final int bufferSize) throws FileSystemException {
+        return buildOutputStream(bAppend, bufferSize);
+    }
+
+    /**
+     * Closes all resources used by the content, including all streams, readers and writers.
+     *
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public void close() throws FileSystemException {
+        FileSystemException caught = null;
+        try {
+            final FileContentThreadData fileContentThreadData = getOrCreateThreadData();
+
+            // Close the input stream
+            while (fileContentThreadData.getInstrsSize() > 0) {
+                final FileContentInputStream inputStream = (FileContentInputStream) fileContentThreadData
+                        .removeInstr(0);
+                try {
+                    inputStream.close();
+                } catch (final FileSystemException ex) {
+                    caught = ex;
+
+                }
+            }
+
+            // Close the randomAccess stream
+            while (fileContentThreadData.getRastrsSize() > 0) {
+                final FileRandomAccessContent randomAccessContent = (FileRandomAccessContent) fileContentThreadData
+                        .removeRastr(0);
+                try {
+                    randomAccessContent.close();
+                } catch (final FileSystemException ex) {
+                    caught = ex;
+                }
+            }
+
+            // Close the output stream
+            final FileContentOutputStream outputStream = fileContentThreadData.getOutstr();
+            if (outputStream != null) {
+                fileContentThreadData.setOutstr(null);
+                try {
+                    outputStream.close();
+                } catch (final FileSystemException ex) {
+                    caught = ex;
+                }
+            }
+        } finally {
+            threadLocal.remove();
+        }
+
+        // throw last error (out >> rac >> input) after all closes have been tried
+        if (caught != null) {
+            throw caught;
+        }
+    }
+
+    private InputStream buildInputStream(final int bufferSize) throws FileSystemException {
+        /*
+         * if (getThreadData().getState() == STATE_WRITING || getThreadData().getState() == STATE_RANDOM_ACCESS) { throw
+         * new FileSystemException("vfs.provider/read-in-use.error", file); }
+         */
+
+        // Get the raw input stream
+        final InputStream inputStream = bufferSize == 0 ? fileObject.getInputStream()
+                : fileObject.getInputStream(bufferSize);
+        final InputStream wrappedInputStream = bufferSize == 0 
+                    ? new FileContentInputStream(fileObject, inputStream)
+                    : new FileContentInputStream(fileObject, inputStream, bufferSize);
+        getOrCreateThreadData().addInstr(wrappedInputStream);
+        streamOpened();
+
+        return wrappedInputStream;
+    }
+
+    private OutputStream buildOutputStream(final boolean bAppend, final int bufferSize) throws FileSystemException {
+        /*
+         * if (getThreadData().getState() != STATE_NONE)
+         */
+        final FileContentThreadData streams = getOrCreateThreadData();
+
+        if (streams.getOutstr() != null) {
+            throw new FileSystemException("vfs.provider/write-in-use.error", fileObject);
+        }
+
+        // Get the raw output stream
+        final OutputStream outstr = fileObject.getOutputStream(bAppend);
+
+        // Create and set wrapper
+        final FileContentOutputStream wrapped = bufferSize == 0 ?
+            new FileContentOutputStream(fileObject, outstr) :
+            new FileContentOutputStream(fileObject, outstr, bufferSize);
+        streams.setOutstr(wrapped);
+        streamOpened();
+
+        return wrapped;
+    }
+
+    /**
+     * Handles the end of input stream.
+     */
+    private void endInput(final FileContentInputStream instr) {
+        final FileContentThreadData fileContentThreadData = threadLocal.get();
+        if (fileContentThreadData != null) {
+            fileContentThreadData.removeInstr(instr);
+        }
+        if (fileContentThreadData == null || !fileContentThreadData.hasStreams()) {
+            // remove even when no value is set to remove key
+            threadLocal.remove();
+        }
+        streamClosed();
+    }
+
+    /**
+     * Handles the end of random access.
+     */
+    private void endRandomAccess(final RandomAccessContent rac) {
+        final FileContentThreadData fileContentThreadData = threadLocal.get();
+        if (fileContentThreadData != null) {
+            fileContentThreadData.removeRastr(rac);
+        }
+        if (fileContentThreadData == null || !fileContentThreadData.hasStreams()) {
+            // remove even when no value is set to remove key
+            threadLocal.remove();
+        }
+        streamClosed();
+    }
+
+    /**
+     * Handles the end of output stream.
+     */
+    private void endOutput() throws Exception {
+        final FileContentThreadData fileContentThreadData = threadLocal.get();
+        if (fileContentThreadData != null) {
+            fileContentThreadData.setOutstr(null);
+        }
+        if (fileContentThreadData == null || !fileContentThreadData.hasStreams()) {
+            // remove even when no value is set to remove key
+            threadLocal.remove();
+        }
+        streamClosed();
+        fileObject.endOutput();
+    }
+
+    /**
+     * Checks if a input and/or output stream is open.
+     * <p>
+     * This checks only the scope of the current thread.
+     * </p>
+     *
+     * @return true if this is the case
+     */
+    @Override
+    public boolean isOpen() {
+        final FileContentThreadData fileContentThreadData = threadLocal.get();
+        if (fileContentThreadData != null && fileContentThreadData.hasStreams()) {
+            return true;
+        }
+        // threadData.get() created empty entry
+        threadLocal.remove();
+        return false;
+    }
+
+    /**
+     * Checks if an input or output stream is open. This checks all threads.
+     *
+     * @return true if this is the case
+     */
+    public boolean isOpenGlobal() {
+        synchronized (this) {
+            return openStreams > 0;
+        }
+    }
+
+    /**
+     * An input stream for reading content. Provides buffering, and end-of-stream monitoring.
+     */
+    private final class FileContentInputStream extends MonitorInputStream {
+        // avoid gc
+        private final FileObject file;
+
+        FileContentInputStream(final FileObject file, final InputStream instr) {
+            super(instr);
+            this.file = file;
+        }
+
+        FileContentInputStream(final FileObject file, final InputStream instr, final int bufferSize) {
+            super(instr, bufferSize);
+            this.file = file;
+        }
+
+        /**
+         * Closes this input stream.
+         */
+        @Override
+        public void close() throws FileSystemException {
+            try {
+                super.close();
+            } catch (final IOException e) {
+                throw new FileSystemException("vfs.provider/close-instr.error", file, e);
+            }
+        }
+
+        /**
+         * Called after the stream has been closed.
+         */
+        @Override
+        protected void onClose() throws IOException {
+            try {
+                super.onClose();
+            } finally {
+                endInput(this);
+            }
+        }
+    }
+
+    /**
+     * An input/output stream for reading/writing content on random positions
+     */
+    private final class FileRandomAccessContent extends MonitorRandomAccessContent {
+        // also avoids gc
+        private final FileObject file;
+
+        FileRandomAccessContent(final FileObject file, final RandomAccessContent content) {
+            super(content);
+            this.file = file;
+        }
+
+        /**
+         * Called after the stream has been closed.
+         */
+        @Override
+        protected void onClose() throws IOException {
+            try {
+                super.onClose();
+            } finally {
+                endRandomAccess(this);
+            }
+        }
+
+        @Override
+        public void close() throws FileSystemException {
+            try {
+                super.close();
+            } catch (final IOException e) {
+                throw new FileSystemException("vfs.provider/close-rac.error", file, e);
+            }
+        }
+    }
+
+    /**
+     * An output stream for writing content.
+     */
+    final class FileContentOutputStream extends MonitorOutputStream {
+        // avoid gc
+        private final FileObject file;
+
+        FileContentOutputStream(final FileObject file, final OutputStream outstr) {
+            super(outstr);
+            this.file = file;
+        }
+
+        FileContentOutputStream(final FileObject file, final OutputStream outstr, final int bufferSize) {
+            super(outstr, bufferSize);
+            this.file = file;
+        }
+
+        /**
+         * Closes this output stream.
+         */
+        @Override
+        public void close() throws FileSystemException {
+            try {
+                super.close();
+            } catch (final IOException e) {
+                throw new FileSystemException("vfs.provider/close-outstr.error", file, e);
+            }
+        }
+
+        /**
+         * Called after this stream is closed.
+         */
+        @Override
+        protected void onClose() throws IOException {
+            try {
+                super.onClose();
+            } finally {
+                try {
+                    endOutput();
+                } catch (final Exception e) {
+                    throw new FileSystemException("vfs.provider/close-outstr.error", file, e);
+                }
+            }
+        }
+    }
+
+    /**
+     * Gets the FileContentInfo which describes the content-type, content-encoding
+     *
+     * @return The FileContentInfo.
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public FileContentInfo getContentInfo() throws FileSystemException {
+        if (fileContentInfo == null) {
+            fileContentInfo = fileContentInfoFactory.create(this);
+        }
+
+        return fileContentInfo;
+    }
+
+    /**
+     * Writes this content to another FileContent.
+     *
+     * @param fileContent The target FileContent.
+     * @return the total number of bytes written
+     * @throws IOException if an error occurs writing the content.
+     * @since 2.1
+     */
+    @Override
+    public long write(final FileContent fileContent) throws IOException {
+        final OutputStream output = fileContent.getOutputStream();
+        try {
+            return this.write(output);
+        } finally {
+            output.close();
+        }
+    }
+
+    /**
+     * Writes this content to another FileObject.
+     *
+     * @param file The target FileObject.
+     * @return the total number of bytes written
+     * @throws IOException if an error occurs writing the content.
+     * @since 2.1
+     */
+    @Override
+    public long write(final FileObject file) throws IOException {
+        return write(file.getContent());
+    }
+
+    /**
+     * Writes this content to an OutputStream.
+     *
+     * @param output The target OutputStream.
+     * @return the total number of bytes written
+     * @throws IOException if an error occurs writing the content.
+     * @since 2.1
+     */
+    @Override
+    public long write(final OutputStream output) throws IOException {
+        return write(output, WRITE_BUFFER_SIZE);
+    }
+
+    /**
+     * Writes this content to an OutputStream.
+     *
+     * @param output The target OutputStream.
+     * @param bufferSize The buffer size to write data chunks.
+     * @return the total number of bytes written
+     * @throws IOException if an error occurs writing the file.
+     * @since 2.1
+     */
+    @Override
+    public long write(final OutputStream output, final int bufferSize) throws IOException {
+        final InputStream input = this.getInputStream();
+        long count = 0;
+        try {
+            // This read/write code from Apache Commons IO
+            final byte[] buffer = new byte[bufferSize];
+            int n = 0;
+            while (-1 != (n = input.read(buffer))) {
+                output.write(buffer, 0, n);
+                count += n;
+            }
+        } finally {
+            input.close();
+        }
+        return count;
+    }
+}
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/DelegateFileObject.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/DelegateFileObject.java
index 9d68532..4e414c9 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/DelegateFileObject.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/DelegateFileObject.java
@@ -1,415 +1,415 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.vfs2.provider;
-
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.security.cert.Certificate;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.commons.vfs2.FileChangeEvent;
-import org.apache.commons.vfs2.FileContentInfo;
-import org.apache.commons.vfs2.FileListener;
-import org.apache.commons.vfs2.FileName;
-import org.apache.commons.vfs2.FileNotFolderException;
-import org.apache.commons.vfs2.FileObject;
-import org.apache.commons.vfs2.FileSystemException;
-import org.apache.commons.vfs2.FileType;
-import org.apache.commons.vfs2.RandomAccessContent;
-import org.apache.commons.vfs2.util.RandomAccessMode;
-import org.apache.commons.vfs2.util.WeakRefFileListener;
-
-/**
- * A file backed by another file.
- * <p>
- * TODO - Extract subclass that overlays the children.
- * </p>
- *
- * @param <AFS> A subclass of AbstractFileSystem.
- */
-public class DelegateFileObject<AFS extends AbstractFileSystem> extends AbstractFileObject<AFS>
-        implements FileListener {
-    private FileObject file;
-    private final Set<String> children = new HashSet<>();
-    private boolean ignoreEvent;
-
-    public DelegateFileObject(final AbstractFileName name, final AFS fileSystem, final FileObject file)
-            throws FileSystemException {
-        super(name, fileSystem);
-        this.file = file;
-        if (file != null) {
-            WeakRefFileListener.installListener(file, this);
-        }
-    }
-
-    /**
-     * Get access to the delegated file.
-     *
-     * @return The FileObject.
-     * @since 2.0
-     */
-    public FileObject getDelegateFile() {
-        return file;
-    }
-
-    /**
-     * Adds a child to this file.
-     *
-     * @param baseName The base FileName.
-     * @param type The FileType.
-     * @throws Exception if an error occurs.
-     */
-    public void attachChild(final FileName baseName, final FileType type) throws Exception {
-        final FileType oldType = doGetType();
-        if (children.add(baseName.getBaseName())) {
-            childrenChanged(baseName, type);
-        }
-        maybeTypeChanged(oldType);
-    }
-
-    /**
-     * Attaches or detaches the target file.
-     *
-     * @param file The FileObject.
-     * @throws Exception if an error occurs.
-     */
-    public void setFile(final FileObject file) throws Exception {
-        final FileType oldType = doGetType();
-
-        if (file != null) {
-            WeakRefFileListener.installListener(file, this);
-        }
-        this.file = file;
-        maybeTypeChanged(oldType);
-    }
-
-    /**
-     * Checks whether the file's type has changed, and fires the appropriate events.
-     *
-     * @param oldType The old FileType.
-     * @throws Exception if an error occurs.
-     */
-    private void maybeTypeChanged(final FileType oldType) throws Exception {
-        final FileType newType = doGetType();
-        if (oldType == FileType.IMAGINARY && newType != FileType.IMAGINARY) {
-            handleCreate(newType);
-        } else if (oldType != FileType.IMAGINARY && newType == FileType.IMAGINARY) {
-            handleDelete();
-        }
-    }
-
-    /**
-     * Determines the type of the file, returns null if the file does not exist.
-     */
-    @Override
-    protected FileType doGetType() throws FileSystemException {
-        if (file != null) {
-            return file.getType();
-        } else if (children.size() > 0) {
-            return FileType.FOLDER;
-        } else {
-            return FileType.IMAGINARY;
-        }
-    }
-
-    /**
-     * Determines if this file can be read.
-     */
-    @Override
-    protected boolean doIsReadable() throws FileSystemException {
-        if (file != null) {
-            return file.isReadable();
-        }
-        return true;
-    }
-
-    /**
-     * Determines if this file can be written to.
-     */
-    @Override
-    protected boolean doIsWriteable() throws FileSystemException {
-        if (file != null) {
-            return file.isWriteable();
-        }
-        return false;
-    }
-
-    /**
-     * Determines if this file is executable.
-     */
-    @Override
-    protected boolean doIsExecutable() throws FileSystemException {
-        if (file != null) {
-            return file.isExecutable();
-        }
-        return false;
-    }
-
-    /**
-     * Determines if this file is hidden.
-     */
-    @Override
-    protected boolean doIsHidden() throws FileSystemException {
-        if (file != null) {
-            return file.isHidden();
-        }
-        return false;
-    }
-
-    /**
-     * Lists the children of the file.
-     */
-    @Override
-    protected String[] doListChildren() throws Exception {
-        if (file != null) {
-            final FileObject[] children;
-
-            try {
-                children = file.getChildren();
-            }
-            // VFS-210
-            catch (final FileNotFolderException e) {
-                throw new FileNotFolderException(getName(), e);
-            }
-
-            final String[] childNames = new String[children.length];
-            for (int i = 0; i < children.length; i++) {
-                childNames[i] = children[i].getName().getBaseName();
-            }
-            return childNames;
-        }
-        return children.toArray(new String[children.size()]);
-    }
-
-    /**
-     * Creates this file as a folder.
-     */
-    @Override
-    protected void doCreateFolder() throws Exception {
-        ignoreEvent = true;
-        try {
-            file.createFolder();
-        } finally {
-            ignoreEvent = false;
-        }
-    }
-
-    /**
-     * Deletes the file.
-     */
-    @Override
-    protected void doDelete() throws Exception {
-        ignoreEvent = true;
-        try {
-            file.delete();
-        } finally {
-            ignoreEvent = false;
-        }
-    }
-
-    /**
-     * Returns the size of the file content (in bytes). Is only called if {@link #doGetType} returns
-     * {@link FileType#FILE}.
-     */
-    @Override
-    protected long doGetContentSize() throws Exception {
-        return file.getContent().getSize();
-    }
-
-    /**
-     * Returns the attributes of this file.
-     */
-    @Override
-    protected Map<String, Object> doGetAttributes() throws Exception {
-        return file.getContent().getAttributes();
-    }
-
-    /**
-     * Sets an attribute of this file.
-     */
-    @Override
-    protected void doSetAttribute(final String atttrName, final Object value) throws Exception {
-        file.getContent().setAttribute(atttrName, value);
-    }
-
-    /**
-     * Returns the certificates of this file.
-     */
-    @Override
-    protected Certificate[] doGetCertificates() throws Exception {
-        return file.getContent().getCertificates();
-    }
-
-    /**
-     * Returns the last-modified time of this file.
-     */
-    @Override
-    protected long doGetLastModifiedTime() throws Exception {
-        return file.getContent().getLastModifiedTime();
-    }
-
-    /**
-     * Sets the last-modified time of this file.
-     *
-     * @since 2.0
-     */
-    @Override
-    protected boolean doSetLastModifiedTime(final long modtime) throws Exception {
-        file.getContent().setLastModifiedTime(modtime);
-        return true;
-    }
-
-    /**
-     * Creates an input stream to read the file content from.
-     */
-    @Override
-    protected InputStream doGetInputStream() throws Exception {
-        return file.getContent().getInputStream();
-    }
-
-    /**
-     * Creates an output stream to write the file content to.
-     */
-    @Override
-    protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception {
-        return file.getContent().getOutputStream(bAppend);
-    }
-
-    /**
-     * Called when a file is created.
-     *
-     * @param event The FileChangeEvent.
-     * @throws Exception if an error occurs.
-     */
-    @Override
-    public void fileCreated(final FileChangeEvent event) throws Exception {
-        if (event.getFile() != file) {
-            return;
-        }
-        if (!ignoreEvent) {
-            handleCreate(file.getType());
-        }
-    }
-
-    /**
-     * Called when a file is deleted.
-     *
-     * @param event The FileChangeEvent.
-     * @throws Exception if an error occurs.
-     */
-    @Override
-    public void fileDeleted(final FileChangeEvent event) throws Exception {
-        if (event.getFile() != file) {
-            return;
-        }
-        if (!ignoreEvent) {
-            handleDelete();
-        }
-    }
-
-    /**
-     * Called when a file is changed.
-     * <p>
-     * This will only happen if you monitor the file using {@link org.apache.commons.vfs2.FileMonitor}.
-     * </p>
-     *
-     * @param event The FileChangeEvent.
-     * @throws Exception if an error occurs.
-     */
-    @Override
-    public void fileChanged(final FileChangeEvent event) throws Exception {
-        if (event.getFile() != file) {
-            return;
-        }
-        if (!ignoreEvent) {
-            handleChanged();
-        }
-    }
-
-    /**
-     * Close the delegated file.
-     *
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public void close() throws FileSystemException {
-        super.close();
-
-        if (file != null) {
-            file.close();
-        }
-    }
-
-    /**
-     * Refresh file information.
-     *
-     * @throws FileSystemException if an error occurs.
-     * @since 2.0
-     */
-    @Override
-    public void refresh() throws FileSystemException {
-        super.refresh();
-        if (file != null) {
-            file.refresh();
-        }
-    }
-
-    /**
-     * Return file content info.
-     *
-     * @return the file content info of the delegee.
-     * @throws Exception Any thrown Exception is wrapped in FileSystemException.
-     * @since 2.0
-     */
-    protected FileContentInfo doGetContentInfo() throws Exception {
-        return file.getContent().getContentInfo();
-    }
-
-    /**
-     * Renames the file.
-     *
-     * @param newFile the new location/name.
-     * @throws Exception Any thrown Exception is wrapped in FileSystemException.
-     * @since 2.0
-     */
-    @Override
-    protected void doRename(final FileObject newFile) throws Exception {
-        file.moveTo(((DelegateFileObject) newFile).file);
-    }
-
-    /**
-     * Removes an attribute of this file.
-     *
-     * @since 2.0
-     */
-    @Override
-    protected void doRemoveAttribute(final String atttrName) throws Exception {
-        file.getContent().removeAttribute(atttrName);
-    }
-
-    /**
-     * Creates access to the file for random i/o.
-     *
-     * @since 2.0
-     */
-    @Override
-    protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception {
-        return file.getContent().getRandomAccessContent(mode);
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.vfs2.provider;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.cert.Certificate;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.vfs2.FileChangeEvent;
+import org.apache.commons.vfs2.FileContentInfo;
+import org.apache.commons.vfs2.FileListener;
+import org.apache.commons.vfs2.FileName;
+import org.apache.commons.vfs2.FileNotFolderException;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileType;
+import org.apache.commons.vfs2.RandomAccessContent;
+import org.apache.commons.vfs2.util.RandomAccessMode;
+import org.apache.commons.vfs2.util.WeakRefFileListener;
+
+/**
+ * A file backed by another file.
+ * <p>
+ * TODO - Extract subclass that overlays the children.
+ * </p>
+ *
+ * @param <AFS> A subclass of AbstractFileSystem.
+ */
+public class DelegateFileObject<AFS extends AbstractFileSystem> extends AbstractFileObject<AFS>
+        implements FileListener {
+    private FileObject file;
+    private final Set<String> children = new HashSet<>();
+    private boolean ignoreEvent;
+
+    public DelegateFileObject(final AbstractFileName name, final AFS fileSystem, final FileObject file)
+            throws FileSystemException {
+        super(name, fileSystem);
+        this.file = file;
+        if (file != null) {
+            WeakRefFileListener.installListener(file, this);
+        }
+    }
+
+    /**
+     * Get access to the delegated file.
+     *
+     * @return The FileObject.
+     * @since 2.0
+     */
+    public FileObject getDelegateFile() {
+        return file;
+    }
+
+    /**
+     * Adds a child to this file.
+     *
+     * @param baseName The base FileName.
+     * @param type The FileType.
+     * @throws Exception if an error occurs.
+     */
+    public void attachChild(final FileName baseName, final FileType type) throws Exception {
+        final FileType oldType = doGetType();
+        if (children.add(baseName.getBaseName())) {
+            childrenChanged(baseName, type);
+        }
+        maybeTypeChanged(oldType);
+    }
+
+    /**
+     * Attaches or detaches the target file.
+     *
+     * @param file The FileObject.
+     * @throws Exception if an error occurs.
+     */
+    public void setFile(final FileObject file) throws Exception {
+        final FileType oldType = doGetType();
+
+        if (file != null) {
+            WeakRefFileListener.installListener(file, this);
+        }
+        this.file = file;
+        maybeTypeChanged(oldType);
+    }
+
+    /**
+     * Checks whether the file's type has changed, and fires the appropriate events.
+     *
+     * @param oldType The old FileType.
+     * @throws Exception if an error occurs.
+     */
+    private void maybeTypeChanged(final FileType oldType) throws Exception {
+        final FileType newType = doGetType();
+        if (oldType == FileType.IMAGINARY && newType != FileType.IMAGINARY) {
+            handleCreate(newType);
+        } else if (oldType != FileType.IMAGINARY && newType == FileType.IMAGINARY) {
+            handleDelete();
+        }
+    }
+
+    /**
+     * Determines the type of the file, returns null if the file does not exist.
+     */
+    @Override
+    protected FileType doGetType() throws FileSystemException {
+        if (file != null) {
+            return file.getType();
+        } else if (children.size() > 0) {
+            return FileType.FOLDER;
+        } else {
+            return FileType.IMAGINARY;
+        }
+    }
+
+    /**
+     * Determines if this file can be read.
+     */
+    @Override
+    protected boolean doIsReadable() throws FileSystemException {
+        if (file != null) {
+            return file.isReadable();
+        }
+        return true;
+    }
+
+    /**
+     * Determines if this file can be written to.
+     */
+    @Override
+    protected boolean doIsWriteable() throws FileSystemException {
+        if (file != null) {
+            return file.isWriteable();
+        }
+        return false;
+    }
+
+    /**
+     * Determines if this file is executable.
+     */
+    @Override
+    protected boolean doIsExecutable() throws FileSystemException {
+        if (file != null) {
+            return file.isExecutable();
+        }
+        return false;
+    }
+
+    /**
+     * Determines if this file is hidden.
+     */
+    @Override
+    protected boolean doIsHidden() throws FileSystemException {
+        if (file != null) {
+            return file.isHidden();
+        }
+        return false;
+    }
+
+    /**
+     * Lists the children of the file.
+     */
+    @Override
+    protected String[] doListChildren() throws Exception {
+        if (file != null) {
+            final FileObject[] children;
+
+            try {
+                children = file.getChildren();
+            }
+            // VFS-210
+            catch (final FileNotFolderException e) {
+                throw new FileNotFolderException(getName(), e);
+            }
+
+            final String[] childNames = new String[children.length];
+            for (int i = 0; i < children.length; i++) {
+                childNames[i] = children[i].getName().getBaseName();
+            }
+            return childNames;
+        }
+        return children.toArray(new String[children.size()]);
+    }
+
+    /**
+     * Creates this file as a folder.
+     */
+    @Override
+    protected void doCreateFolder() throws Exception {
+        ignoreEvent = true;
+        try {
+            file.createFolder();
+        } finally {
+            ignoreEvent = false;
+        }
+    }
+
+    /**
+     * Deletes the file.
+     */
+    @Override
+    protected void doDelete() throws Exception {
+        ignoreEvent = true;
+        try {
+            file.delete();
+        } finally {
+            ignoreEvent = false;
+        }
+    }
+
+    /**
+     * Returns the size of the file content (in bytes). Is only called if {@link #doGetType} returns
+     * {@link FileType#FILE}.
+     */
+    @Override
+    protected long doGetContentSize() throws Exception {
+        return file.getContent().getSize();
+    }
+
+    /**
+     * Returns the attributes of this file.
+     */
+    @Override
+    protected Map<String, Object> doGetAttributes() throws Exception {
+        return file.getContent().getAttributes();
+    }
+
+    /**
+     * Sets an attribute of this file.
+     */
+    @Override
+    protected void doSetAttribute(final String atttrName, final Object value) throws Exception {
+        file.getContent().setAttribute(atttrName, value);
+    }
+
+    /**
+     * Returns the certificates of this file.
+     */
+    @Override
+    protected Certificate[] doGetCertificates() throws Exception {
+        return file.getContent().getCertificates();
+    }
+
+    /**
+     * Returns the last-modified time of this file.
+     */
+    @Override
+    protected long doGetLastModifiedTime() throws Exception {
+        return file.getContent().getLastModifiedTime();
+    }
+
+    /**
+     * Sets the last-modified time of this file.
+     *
+     * @since 2.0
+     */
+    @Override
+    protected boolean doSetLastModifiedTime(final long modtime) throws Exception {
+        file.getContent().setLastModifiedTime(modtime);
+        return true;
+    }
+
+    /**
+     * Creates an input stream to read the file content from.
+     */
+    @Override
+    protected InputStream doGetInputStream(final int bufferSize) throws Exception {
+        return file.getContent().getInputStream(bufferSize);
+    }
+
+    /**
+     * Creates an output stream to write the file content to.
+     */
+    @Override
+    protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception {
+        return file.getContent().getOutputStream(bAppend);
+    }
+
+    /**
+     * Called when a file is created.
+     *
+     * @param event The FileChangeEvent.
+     * @throws Exception if an error occurs.
+     */
+    @Override
+    public void fileCreated(final FileChangeEvent event) throws Exception {
+        if (event.getFile() != file) {
+            return;
+        }
+        if (!ignoreEvent) {
+            handleCreate(file.getType());
+        }
+    }
+
+    /**
+     * Called when a file is deleted.
+     *
+     * @param event The FileChangeEvent.
+     * @throws Exception if an error occurs.
+     */
+    @Override
+    public void fileDeleted(final FileChangeEvent event) throws Exception {
+        if (event.getFile() != file) {
+            return;
+        }
+        if (!ignoreEvent) {
+            handleDelete();
+        }
+    }
+
+    /**
+     * Called when a file is changed.
+     * <p>
+     * This will only happen if you monitor the file using {@link org.apache.commons.vfs2.FileMonitor}.
+     * </p>
+     *
+     * @param event The FileChangeEvent.
+     * @throws Exception if an error occurs.
+     */
+    @Override
+    public void fileChanged(final FileChangeEvent event) throws Exception {
+        if (event.getFile() != file) {
+            return;
+        }
+        if (!ignoreEvent) {
+            handleChanged();
+        }
+    }
+
+    /**
+     * Close the delegated file.
+     *
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public void close() throws FileSystemException {
+        super.close();
+
+        if (file != null) {
+            file.close();
+        }
+    }
+
+    /**
+     * Refresh file information.
+     *
+     * @throws FileSystemException if an error occurs.
+     * @since 2.0
+     */
+    @Override
+    public void refresh() throws FileSystemException {
+        super.refresh();
+        if (file != null) {
+            file.refresh();
+        }
+    }
+
+    /**
+     * Return file content info.
+     *
+     * @return the file content info of the delegee.
+     * @throws Exception Any thrown Exception is wrapped in FileSystemException.
+     * @since 2.0
+     */
+    protected FileContentInfo doGetContentInfo() throws Exception {
+        return file.getContent().getContentInfo();
+    }
+
+    /**
+     * Renames the file.
+     *
+     * @param newFile the new location/name.
+     * @throws Exception Any thrown Exception is wrapped in FileSystemException.
+     * @since 2.0
+     */
+    @Override
+    protected void doRename(final FileObject newFile) throws Exception {
+        file.moveTo(((DelegateFileObject) newFile).file);
+    }
+
+    /**
+     * Removes an attribute of this file.
+     *
+     * @since 2.0
+     */
+    @Override
+    protected void doRemoveAttribute(final String atttrName) throws Exception {
+        file.getContent().removeAttribute(atttrName);
+    }
+
+    /**
+     * Creates access to the file for random i/o.
+     *
+     * @since 2.0
+     */
+    @Override
+    protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception {
+        return file.getContent().getRandomAccessContent(mode);
+    }
+}
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/bzip2/Bzip2FileObject.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/bzip2/Bzip2FileObject.java
index 44c2909..5592e47 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/bzip2/Bzip2FileObject.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/bzip2/Bzip2FileObject.java
@@ -52,9 +52,9 @@ public class Bzip2FileObject extends CompressedFileFileObject<Bzip2FileSystem> {
     }
 
     @Override
-    protected InputStream doGetInputStream() throws Exception {
+    protected InputStream doGetInputStream(final int bufferSize) throws Exception {
         // check file
-        final InputStream is = getContainer().getContent().getInputStream();
+        final InputStream is = getContainer().getContent().getInputStream(bufferSize);
         return wrapInputStream(getName().getURI(), is);
     }
 
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/ftp/FTPClientWrapper.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/ftp/FTPClientWrapper.java
index 3c31df9..e956a9f 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/ftp/FTPClientWrapper.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/ftp/FTPClientWrapper.java
@@ -251,6 +251,20 @@ public class FTPClientWrapper implements FtpClient {
     }
 
     @Override
+    public InputStream retrieveFileStream(final String relPath, final int bufferSize) throws IOException {
+        try {
+            final FTPClient client = getFtpClient();
+            client.setBufferSize(bufferSize);
+            return client.retrieveFileStream(relPath);
+        } catch (final IOException e) {
+            disconnect();
+            final FTPClient client = getFtpClient();
+            client.setBufferSize(bufferSize);
+            return client.retrieveFileStream(relPath);
+        }
+    }
+
+    @Override
     public InputStream retrieveFileStream(final String relPath, final long restartOffset) throws IOException {
         try {
             final FTPClient client = getFtpClient();
@@ -265,6 +279,11 @@ public class FTPClientWrapper implements FtpClient {
     }
 
     @Override
+    public void setBufferSize(final int bufferSize) throws FileSystemException {
+        getFtpClient().setBufferSize(bufferSize);
+    }
+    
+    @Override
     public OutputStream storeFileStream(final String relPath) throws IOException {
         try {
             return getFtpClient().storeFileStream(relPath);
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/ftp/FtpClient.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/ftp/FtpClient.java
index 794e1ee..4afce34 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/ftp/FtpClient.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/ftp/FtpClient.java
@@ -57,8 +57,17 @@ public interface FtpClient {
 
     InputStream retrieveFileStream(String relPath) throws IOException;
 
+    default InputStream retrieveFileStream(String relPath, int bufferSize) throws IOException {
+        // Backward compatibility: no buffer size.
+        return retrieveFileStream(relPath);
+    }
+
     InputStream retrieveFileStream(String relPath, long restartOffset) throws IOException;
 
+    default void setBufferSize(int bufferSize) throws FileSystemException {
+        // Backward compatibility: do nothing.
+    }
+
     OutputStream storeFileStream(String relPath) throws IOException;
 
 }
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/ftp/FtpFileObject.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/ftp/FtpFileObject.java
index 09336f8..b75ad43 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/ftp/FtpFileObject.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/ftp/FtpFileObject.java
@@ -1,627 +1,632 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.vfs2.provider.ftp;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.commons.net.ftp.FTPFile;
-import org.apache.commons.vfs2.FileName;
-import org.apache.commons.vfs2.FileNotFolderException;
-import org.apache.commons.vfs2.FileObject;
-import org.apache.commons.vfs2.FileSystemException;
-import org.apache.commons.vfs2.FileType;
-import org.apache.commons.vfs2.RandomAccessContent;
-import org.apache.commons.vfs2.provider.AbstractFileName;
-import org.apache.commons.vfs2.provider.AbstractFileObject;
-import org.apache.commons.vfs2.provider.UriParser;
-import org.apache.commons.vfs2.util.FileObjectUtils;
-import org.apache.commons.vfs2.util.Messages;
-import org.apache.commons.vfs2.util.MonitorInputStream;
-import org.apache.commons.vfs2.util.MonitorOutputStream;
-import org.apache.commons.vfs2.util.RandomAccessMode;
-
-/**
- * An FTP file.
- */
-public class FtpFileObject extends AbstractFileObject<FtpFileSystem> {
-
-    private static final long DEFAULT_TIMESTAMP = 0L;
-    private static final Map<String, FTPFile> EMPTY_FTP_FILE_MAP = Collections
-            .unmodifiableMap(new TreeMap<String, FTPFile>());
-    private static final FTPFile UNKNOWN = new FTPFile();
-    private static final Log log = LogFactory.getLog(FtpFileObject.class);
-
-    private final String relPath;
-
-    // Cached info
-    private volatile FTPFile fileInfo;
-    private volatile Map<String, FTPFile> children;
-    private volatile FileObject linkDestination;
-    private final AtomicBoolean inRefresh = new AtomicBoolean();
-
-    protected FtpFileObject(final AbstractFileName name, final FtpFileSystem fileSystem, final FileName rootName)
-            throws FileSystemException {
-        super(name, fileSystem);
-        final String relPath = UriParser.decode(rootName.getRelativeName(name));
-        if (".".equals(relPath)) {
-            // do not use the "." as path against the ftp-server
-            // e.g. the uu.net ftp-server do a recursive listing then
-            // this.relPath = UriParser.decode(rootName.getPath());
-            // this.relPath = ".";
-            this.relPath = null;
-        } else {
-            this.relPath = relPath;
-        }
-    }
-
-    /**
-     * Called by child file objects, to locate their ftp file info.
-     *
-     * @param name the file name in its native form ie. without uri stuff (%nn)
-     * @param flush recreate children cache
-     */
-    private FTPFile getChildFile(final String name, final boolean flush) throws IOException {
-        /*
-         * If we should flush cached children, clear our children map unless we're in the middle of a refresh in which
-         * case we've just recently refreshed our children. No need to do it again when our children are refresh()ed,
-         * calling getChildFile() for themselves from within getInfo(). See getChildren().
-         */
-        if (flush && !inRefresh.get()) {
-            children = null;
-        }
-
-        // List the children of this file
-        doGetChildren();
-
-        // Look for the requested child
-        // VFS-210 adds the null check.
-        return children != null ? children.get(name) : null;
-    }
-
-    /**
-     * Fetches the children of this file, if not already cached.
-     */
-    private void doGetChildren() throws IOException {
-        if (children != null) {
-            return;
-        }
-
-        final FtpClient client = getAbstractFileSystem().getClient();
-        try {
-            final String path = fileInfo != null && fileInfo.isSymbolicLink()
-                    ? getFileSystem().getFileSystemManager().resolveName(getParent().getName(), fileInfo.getLink())
-                            .getPath()
-                    : relPath;
-            final FTPFile[] tmpChildren = client.listFiles(path);
-            if (tmpChildren == null || tmpChildren.length == 0) {
-                children = EMPTY_FTP_FILE_MAP;
-            } else {
-                children = new TreeMap<>();
-
-                // Remove '.' and '..' elements
-                for (int i = 0; i < tmpChildren.length; i++) {
-                    final FTPFile child = tmpChildren[i];
-                    if (child == null) {
-                        if (log.isDebugEnabled()) {
-                            log.debug(Messages.getString("vfs.provider.ftp/invalid-directory-entry.debug",
-                                    Integer.valueOf(i), relPath));
-                        }
-                        continue;
-                    }
-                    if (!".".equals(child.getName()) && !"..".equals(child.getName())) {
-                        children.put(child.getName(), child);
-                    }
-                }
-            }
-        } finally {
-            getAbstractFileSystem().putClient(client);
-        }
-    }
-
-    /**
-     * Attaches this file object to its file resource.
-     */
-    @Override
-    protected void doAttach() throws IOException {
-        // Get the parent folder to find the info for this file
-        // VFS-210 getInfo(false);
-    }
-
-    /**
-     * Fetches the info for this file.
-     */
-    private void getInfo(final boolean flush) throws IOException {
-        synchronized (getFileSystem()) {
-            final FtpFileObject parent = (FtpFileObject) FileObjectUtils.getAbstractFileObject(getParent());
-            FTPFile newFileInfo;
-            if (parent != null) {
-                newFileInfo = parent.getChildFile(UriParser.decode(getName().getBaseName()), flush);
-            } else {
-                // Assume the root is a directory and exists
-                newFileInfo = new FTPFile();
-                newFileInfo.setType(FTPFile.DIRECTORY_TYPE);
-            }
-
-            if (newFileInfo == null) {
-                this.fileInfo = UNKNOWN;
-            } else {
-                this.fileInfo = newFileInfo;
-            }
-        }}
-
-    /**
-     * @throws FileSystemException if an error occurs.
-     */
-    @Override
-    public void refresh() throws FileSystemException {
-        if (inRefresh.compareAndSet(false, true)) {
-            try {
-                super.refresh();
-                synchronized (getFileSystem()) {
-                    this.fileInfo = null;
-                }
-                /*
-                 * VFS-210 try { // this will tell the parent to recreate its children collection getInfo(true); } catch
-                 * (IOException e) { throw new FileSystemException(e); }
-                 */
-            } finally {
-                inRefresh.set(false);
-            }
-        }
-    }
-
-    /**
-     * Detaches this file object from its file resource.
-     */
-    @Override
-    protected void doDetach() {
-        synchronized (getFileSystem()) {
-            this.fileInfo = null;
-            this.children = null;
-        }
-    }
-
-    /**
-     * Called when the children of this file change.
-     */
-    @Override
-    protected void onChildrenChanged(final FileName child, final FileType newType) {
-        if (children != null && newType.equals(FileType.IMAGINARY)) {
-            try {
-                children.remove(UriParser.decode(child.getBaseName()));
-            } catch (final FileSystemException e) {
-                throw new RuntimeException(e.getMessage());
-            }
-        } else {
-            // if child was added we have to rescan the children
-            // TODO - get rid of this
-            children = null;
-        }
-    }
-
-    /**
-     * Called when the type or content of this file changes.
-     */
-    @Override
-    protected void onChange() throws IOException {
-        children = null;
-
-        if (getType().equals(FileType.IMAGINARY)) {
-            // file is deleted, avoid server lookup
-            synchronized (getFileSystem()) {
-                this.fileInfo = UNKNOWN;
-            }
-            return;
-        }
-
-        getInfo(true);
-    }
-
-    /**
-     * Determines the type of the file, returns null if the file does not exist.
-     */
-    @Override
-    protected FileType doGetType() throws Exception {
-        // VFS-210
-        synchronized (getFileSystem()) {
-            if (this.fileInfo == null) {
-                getInfo(false);
-            }
-
-            if (this.fileInfo == UNKNOWN) {
-                return FileType.IMAGINARY;
-            } else if (this.fileInfo.isDirectory()) {
-                return FileType.FOLDER;
-            } else if (this.fileInfo.isFile()) {
-                return FileType.FILE;
-            } else if (this.fileInfo.isSymbolicLink()) {
-                final FileObject linkDest = getLinkDestination();
-                // VFS-437: We need to check if the symbolic link links back to the symbolic link itself
-                if (this.isCircular(linkDest)) {
-                    // If the symbolic link links back to itself, treat it as an imaginary file to prevent following
-                    // this link. If the user tries to access the link as a file or directory, the user will end up with
-                    // a FileSystemException warning that the file cannot be accessed. This is to prevent the infinite
-                    // call back to doGetType() to prevent the StackOverFlow
-                    return FileType.IMAGINARY;
-                }
-                return linkDest.getType();
-
-            }
-        }
-        throw new FileSystemException("vfs.provider.ftp/get-type.error", getName());
-    }
-
-    private FileObject getLinkDestination() throws FileSystemException {
-        if (linkDestination == null) {
-            final String path;
-            synchronized (getFileSystem()) {
-                path = this.fileInfo == null ? null : this.fileInfo.getLink();
-            }
-            final FileName parent = getName().getParent();
-            final FileName relativeTo = parent == null ? getName() : parent;
-            final FileName linkDestinationName = getFileSystem().getFileSystemManager().resolveName(relativeTo, path);
-            linkDestination = getFileSystem().resolveFile(linkDestinationName);
-        }
-        return linkDestination;
-    }
-
-    @Override
-    protected FileObject[] doListChildrenResolved() throws Exception {
-        synchronized (getFileSystem()) {
-            if (this.fileInfo != null && this.fileInfo.isSymbolicLink()) {
-                final FileObject linkDest = getLinkDestination();
-                // VFS-437: Try to avoid a recursion loop.
-                if (this.isCircular(linkDest)) {
-                    return null;
-                }
-                return linkDest.getChildren();
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Returns the file's list of children.
-     *
-     * @return The list of children
-     * @throws FileSystemException If there was a problem listing children
-     * @see AbstractFileObject#getChildren()
-     * @since 2.0
-     */
-    @Override
-    public FileObject[] getChildren() throws FileSystemException {
-        try {
-            if (doGetType() != FileType.FOLDER) {
-                throw new FileNotFolderException(getName());
-            }
-        } catch (final Exception ex) {
-            throw new FileNotFolderException(getName(), ex);
-        }
-
-        try {
-            /*
-             * Wrap our parent implementation, noting that we're refreshing so that we don't refresh() ourselves and
-             * each of our parents for each children. Note that refresh() will list children. Meaning, if if this file
-             * has C children, P parents, there will be (C * P) listings made with (C * (P + 1)) refreshes, when there
-             * should really only be 1 listing and C refreshes.
-             */
-            this.inRefresh.set(true);
-            return super.getChildren();
-        } finally {
-            this.inRefresh.set(false);
-        }
-    }
-
-    /**
-     * Lists the children of the file.
-     */
-    @Override
-    protected String[] doListChildren() throws Exception {
-        // List the children of this file
-        doGetChildren();
-
-        // VFS-210
-        if (children == null) {
-            return null;
-        }
-
-        // TODO - get rid of this children stuff
-        final String[] childNames = new String[children.size()];
-        int childNum = -1;
-        final Iterator<FTPFile> iterChildren = children.values().iterator();
-        while (iterChildren.hasNext()) {
-            childNum++;
-            final FTPFile child = iterChildren.next();
-            childNames[childNum] = child.getName();
-        }
-
-        return UriParser.encode(childNames);
-    }
-
-    /**
-     * Deletes the file.
-     */
-    @Override
-    protected void doDelete() throws Exception {
-        synchronized (getFileSystem()) {
-            if (this.fileInfo != null) {
-                final boolean ok;
-                final FtpClient ftpClient = getAbstractFileSystem().getClient();
-                try {
-                    if (this.fileInfo.isDirectory()) {
-                        ok = ftpClient.removeDirectory(relPath);
-                    } else {
-                        ok = ftpClient.deleteFile(relPath);
-                    }
-                } finally {
-                    getAbstractFileSystem().putClient(ftpClient);
-                }
-
-                if (!ok) {
-                    throw new FileSystemException("vfs.provider.ftp/delete-file.error", getName());
-                }
-                this.fileInfo = null;
-            }
-            this.children = EMPTY_FTP_FILE_MAP;
-        }
-    }
-
-    /**
-     * Renames the file
-     */
-    @Override
-    protected void doRename(final FileObject newFile) throws Exception {
-        synchronized (getFileSystem()) {
-            final boolean ok;
-            final FtpClient ftpClient = getAbstractFileSystem().getClient();
-            try {
-                final String oldName = relPath;
-                final String newName = ((FtpFileObject) FileObjectUtils.getAbstractFileObject(newFile)).getRelPath();
-                ok = ftpClient.rename(oldName, newName);
-            } finally {
-                getAbstractFileSystem().putClient(ftpClient);
-            }
-
-            if (!ok) {
-                throw new FileSystemException("vfs.provider.ftp/rename-file.error", getName().toString(), newFile);
-            }
-            this.fileInfo = null;
-            this.children = EMPTY_FTP_FILE_MAP;
-        }
-    }
-
-    /**
-     * Creates this file as a folder.
-     */
-    @Override
-    protected void doCreateFolder() throws Exception {
-        final boolean ok;
-        final FtpClient client = getAbstractFileSystem().getClient();
-        try {
-            ok = client.makeDirectory(relPath);
-        } finally {
-            getAbstractFileSystem().putClient(client);
-        }
-
-        if (!ok) {
-            throw new FileSystemException("vfs.provider.ftp/create-folder.error", getName());
-        }
-    }
-
-    /**
-     * Returns the size of the file content (in bytes).
-     */
-    @Override
-    protected long doGetContentSize() throws Exception {
-        synchronized (getFileSystem()) {
-            if (this.fileInfo == null) {
-                return 0;
-            }
-            if (this.fileInfo.isSymbolicLink()) {
-                final FileObject linkDest = getLinkDestination();
-                // VFS-437: Try to avoid a recursion loop.
-                if (this.isCircular(linkDest)) {
-                    return this.fileInfo.getSize();
-                }
-                return linkDest.getContent().getSize();
-            }
-            return this.fileInfo.getSize();
-        }
-    }
-
-    /**
-     * get the last modified time on an ftp file
-     *
-     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doGetLastModifiedTime()
-     */
-    @Override
-    protected long doGetLastModifiedTime() throws Exception {
-        synchronized (getFileSystem()) {
-            if (this.fileInfo == null) {
-                return DEFAULT_TIMESTAMP;
-            }
-            if (this.fileInfo.isSymbolicLink()) {
-                final FileObject linkDest = getLinkDestination();
-                // VFS-437: Try to avoid a recursion loop.
-                if (this.isCircular(linkDest)) {
-                    return getTimestamp();
-                }
-                return linkDest.getContent().getLastModifiedTime();
-            }
-            return getTimestamp();
-        }
-    }
-
-    /**
-     * Creates an input stream to read the file content from.
-     */
-    @Override
-    protected InputStream doGetInputStream() throws Exception {
-        final FtpClient client = getAbstractFileSystem().getClient();
-        try {
-            final InputStream instr = client.retrieveFileStream(relPath);
-            // VFS-210
-            if (instr == null) {
-                throw new FileNotFoundException(getName().toString());
-            }
-            return new FtpInputStream(client, instr);
-        } catch (final Exception e) {
-            getAbstractFileSystem().putClient(client);
-            throw e;
-        }
-    }
-
-    @Override
-    protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception {
-        return new FtpRandomAccessContent(this, mode);
-    }
-
-    /**
-     * Creates an output stream to write the file content to.
-     */
-    @Override
-    protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception {
-        final FtpClient client = getAbstractFileSystem().getClient();
-        try {
-            OutputStream out = null;
-            if (bAppend) {
-                out = client.appendFileStream(relPath);
-            } else {
-                out = client.storeFileStream(relPath);
-            }
-
-            FileSystemException.requireNonNull(out, "vfs.provider.ftp/output-error.debug", this.getName(),
-                    client.getReplyString());
-
-            return new FtpOutputStream(client, out);
-        } catch (final Exception e) {
-            getAbstractFileSystem().putClient(client);
-            throw e;
-        }
-    }
-
-    String getRelPath() {
-        return relPath;
-    }
-
-    private long getTimestamp() {
-        final Calendar timestamp = this.fileInfo != null ? this.fileInfo.getTimestamp() : null;
-        return timestamp == null ? DEFAULT_TIMESTAMP : timestamp.getTime().getTime();
-    }
-
-    /**
-     * This is an over simplistic implementation for VFS-437.
-     */
-    private boolean isCircular(final FileObject linkDest) throws FileSystemException {
-        return linkDest.getName().getPathDecoded().equals(this.getName().getPathDecoded());
-    }
-
-    FtpInputStream getInputStream(final long filePointer) throws IOException {
-        final FtpClient client = getAbstractFileSystem().getClient();
-        try {
-            final InputStream instr = client.retrieveFileStream(relPath, filePointer);
-            FileSystemException.requireNonNull(instr, "vfs.provider.ftp/input-error.debug", this.getName(),
-                    client.getReplyString());
-            return new FtpInputStream(client, instr);
-        } catch (final IOException e) {
-            getAbstractFileSystem().putClient(client);
-            throw e;
-        }
-    }
-
-    /**
-     * An InputStream that monitors for end-of-file.
-     */
-    class FtpInputStream extends MonitorInputStream {
-        private final FtpClient client;
-
-        public FtpInputStream(final FtpClient client, final InputStream in) {
-            super(in);
-            this.client = client;
-        }
-
-        void abort() throws IOException {
-            client.abort();
-            close();
-        }
-
-        /**
-         * Called after the stream has been closed.
-         */
-        @Override
-        protected void onClose() throws IOException {
-            final boolean ok;
-            try {
-                ok = client.completePendingCommand() || isTransferAbortedOkReplyCode();
-            } finally {
-                getAbstractFileSystem().putClient(client);
-            }
-
-            if (!ok) {
-                throw new FileSystemException("vfs.provider.ftp/finish-get.error", getName());
-            }
-        }
-
-        private boolean isTransferAbortedOkReplyCode() throws IOException {
-            final List<Integer> transferAbortedOkReplyCodes = FtpFileSystemConfigBuilder
-                .getInstance()
-                .getTransferAbortedOkReplyCodes(getAbstractFileSystem().getFileSystemOptions());
-            return transferAbortedOkReplyCodes != null && transferAbortedOkReplyCodes.contains(client.getReplyCode());
-        }
-    }
-
-    /**
-     * An OutputStream that monitors for end-of-file.
-     */
-    private class FtpOutputStream extends MonitorOutputStream {
-        private final FtpClient client;
-
-        public FtpOutputStream(final FtpClient client, final OutputStream outstr) {
-            super(outstr);
-            this.client = client;
-        }
-
-        /**
-         * Called after this stream is closed.
-         */
-        @Override
-        protected void onClose() throws IOException {
-            final boolean ok;
-            try {
-                ok = client.completePendingCommand();
-            } finally {
-                getAbstractFileSystem().putClient(client);
-            }
-
-            if (!ok) {
-                throw new FileSystemException("vfs.provider.ftp/finish-put.error", getName());
-            }
-        }
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.vfs2.provider.ftp;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.net.ftp.FTPFile;
+import org.apache.commons.vfs2.FileName;
+import org.apache.commons.vfs2.FileNotFolderException;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileType;
+import org.apache.commons.vfs2.RandomAccessContent;
+import org.apache.commons.vfs2.provider.AbstractFileName;
+import org.apache.commons.vfs2.provider.AbstractFileObject;
+import org.apache.commons.vfs2.provider.UriParser;
+import org.apache.commons.vfs2.util.FileObjectUtils;
+import org.apache.commons.vfs2.util.Messages;
+import org.apache.commons.vfs2.util.MonitorInputStream;
+import org.apache.commons.vfs2.util.MonitorOutputStream;
+import org.apache.commons.vfs2.util.RandomAccessMode;
+
+/**
+ * An FTP file.
+ */
+public class FtpFileObject extends AbstractFileObject<FtpFileSystem> {
+
+    private static final long DEFAULT_TIMESTAMP = 0L;
+    private static final Map<String, FTPFile> EMPTY_FTP_FILE_MAP = Collections
+            .unmodifiableMap(new TreeMap<String, FTPFile>());
+    private static final FTPFile UNKNOWN = new FTPFile();
+    private static final Log log = LogFactory.getLog(FtpFileObject.class);
+
+    private final String relPath;
+
+    // Cached info
+    private volatile FTPFile fileInfo;
+    private volatile Map<String, FTPFile> children;
+    private volatile FileObject linkDestination;
+    private final AtomicBoolean inRefresh = new AtomicBoolean();
+
+    protected FtpFileObject(final AbstractFileName name, final FtpFileSystem fileSystem, final FileName rootName)
+            throws FileSystemException {
+        super(name, fileSystem);
+        final String relPath = UriParser.decode(rootName.getRelativeName(name));
+        if (".".equals(relPath)) {
+            // do not use the "." as path against the ftp-server
+            // e.g. the uu.net ftp-server do a recursive listing then
+            // this.relPath = UriParser.decode(rootName.getPath());
+            // this.relPath = ".";
+            this.relPath = null;
+        } else {
+            this.relPath = relPath;
+        }
+    }
+
+    /**
+     * Called by child file objects, to locate their ftp file info.
+     *
+     * @param name the file name in its native form ie. without uri stuff (%nn)
+     * @param flush recreate children cache
+     */
+    private FTPFile getChildFile(final String name, final boolean flush) throws IOException {
+        /*
+         * If we should flush cached children, clear our children map unless we're in the middle of a refresh in which
+         * case we've just recently refreshed our children. No need to do it again when our children are refresh()ed,
+         * calling getChildFile() for themselves from within getInfo(). See getChildren().
+         */
+        if (flush && !inRefresh.get()) {
+            children = null;
+        }
+
+        // List the children of this file
+        doGetChildren();
+
+        // Look for the requested child
+        // VFS-210 adds the null check.
+        return children != null ? children.get(name) : null;
+    }
+
+    /**
+     * Fetches the children of this file, if not already cached.
+     */
+    private void doGetChildren() throws IOException {
+        if (children != null) {
+            return;
+        }
+
+        final FtpClient client = getAbstractFileSystem().getClient();
+        try {
+            final String path = fileInfo != null && fileInfo.isSymbolicLink()
+                    ? getFileSystem().getFileSystemManager().resolveName(getParent().getName(), fileInfo.getLink())
+                            .getPath()
+                    : relPath;
+            final FTPFile[] tmpChildren = client.listFiles(path);
+            if (tmpChildren == null || tmpChildren.length == 0) {
+                children = EMPTY_FTP_FILE_MAP;
+            } else {
+                children = new TreeMap<>();
+
+                // Remove '.' and '..' elements
+                for (int i = 0; i < tmpChildren.length; i++) {
+                    final FTPFile child = tmpChildren[i];
+                    if (child == null) {
+                        if (log.isDebugEnabled()) {
+                            log.debug(Messages.getString("vfs.provider.ftp/invalid-directory-entry.debug",
+                                    Integer.valueOf(i), relPath));
+                        }
+                        continue;
+                    }
+                    if (!".".equals(child.getName()) && !"..".equals(child.getName())) {
+                        children.put(child.getName(), child);
+                    }
+                }
+            }
+        } finally {
+            getAbstractFileSystem().putClient(client);
+        }
+    }
+
+    /**
+     * Attaches this file object to its file resource.
+     */
+    @Override
+    protected void doAttach() throws IOException {
+        // Get the parent folder to find the info for this file
+        // VFS-210 getInfo(false);
+    }
+
+    /**
+     * Fetches the info for this file.
+     */
+    private void getInfo(final boolean flush) throws IOException {
+        synchronized (getFileSystem()) {
+            final FtpFileObject parent = (FtpFileObject) FileObjectUtils.getAbstractFileObject(getParent());
+            FTPFile newFileInfo;
+            if (parent != null) {
+                newFileInfo = parent.getChildFile(UriParser.decode(getName().getBaseName()), flush);
+            } else {
+                // Assume the root is a directory and exists
+                newFileInfo = new FTPFile();
+                newFileInfo.setType(FTPFile.DIRECTORY_TYPE);
+            }
+
+            if (newFileInfo == null) {
+                this.fileInfo = UNKNOWN;
+            } else {
+                this.fileInfo = newFileInfo;
+            }
+        }}
+
+    /**
+     * @throws FileSystemException if an error occurs.
+     */
+    @Override
+    public void refresh() throws FileSystemException {
+        if (inRefresh.compareAndSet(false, true)) {
+            try {
+                super.refresh();
+                synchronized (getFileSystem()) {
+                    this.fileInfo = null;
+                }
+                /*
+                 * VFS-210 try { // this will tell the parent to recreate its children collection getInfo(true); } catch
+                 * (IOException e) { throw new FileSystemException(e); }
+                 */
+            } finally {
+                inRefresh.set(false);
+            }
+        }
+    }
+
+    /**
+     * Detaches this file object from its file resource.
+     */
+    @Override
+    protected void doDetach() {
+        synchronized (getFileSystem()) {
+            this.fileInfo = null;
+            this.children = null;
+        }
+    }
+
+    /**
+     * Called when the children of this file change.
+     */
+    @Override
+    protected void onChildrenChanged(final FileName child, final FileType newType) {
+        if (children != null && newType.equals(FileType.IMAGINARY)) {
+            try {
+                children.remove(UriParser.decode(child.getBaseName()));
+            } catch (final FileSystemException e) {
+                throw new RuntimeException(e.getMessage());
+            }
+        } else {
+            // if child was added we have to rescan the children
+            // TODO - get rid of this
+            children = null;
+        }
+    }
+
+    /**
+     * Called when the type or content of this file changes.
+     */
+    @Override
+    protected void onChange() throws IOException {
+        children = null;
+
+        if (getType().equals(FileType.IMAGINARY)) {
+            // file is deleted, avoid server lookup
+            synchronized (getFileSystem()) {
+                this.fileInfo = UNKNOWN;
+            }
+            return;
+        }
+
+        getInfo(true);
+    }
+
+    /**
+     * Determines the type of the file, returns null if the file does not exist.
+     */
+    @Override
+    protected FileType doGetType() throws Exception {
+        // VFS-210
+        synchronized (getFileSystem()) {
+            if (this.fileInfo == null) {
+                getInfo(false);
+            }
+
+            if (this.fileInfo == UNKNOWN) {
+                return FileType.IMAGINARY;
+            } else if (this.fileInfo.isDirectory()) {
+                return FileType.FOLDER;
+            } else if (this.fileInfo.isFile()) {
+                return FileType.FILE;
+            } else if (this.fileInfo.isSymbolicLink()) {
+                final FileObject linkDest = getLinkDestination();
+                // VFS-437: We need to check if the symbolic link links back to the symbolic link itself
+                if (this.isCircular(linkDest)) {
+                    // If the symbolic link links back to itself, treat it as an imaginary file to prevent following
+                    // this link. If the user tries to access the link as a file or directory, the user will end up with
+                    // a FileSystemException warning that the file cannot be accessed. This is to prevent the infinite
+                    // call back to doGetType() to prevent the StackOverFlow
+                    return FileType.IMAGINARY;
+                }
+                return linkDest.getType();
+
+            }
+        }
+        throw new FileSystemException("vfs.provider.ftp/get-type.error", getName());
+    }
+
+    private FileObject getLinkDestination() throws FileSystemException {
+        if (linkDestination == null) {
+            final String path;
+            synchronized (getFileSystem()) {
+                path = this.fileInfo == null ? null : this.fileInfo.getLink();
+            }
+            final FileName parent = getName().getParent();
+            final FileName relativeTo = parent == null ? getName() : parent;
+            final FileName linkDestinationName = getFileSystem().getFileSystemManager().resolveName(relativeTo, path);
+            linkDestination = getFileSystem().resolveFile(linkDestinationName);
+        }
+        return linkDestination;
+    }
+
+    @Override
+    protected FileObject[] doListChildrenResolved() throws Exception {
+        synchronized (getFileSystem()) {
+            if (this.fileInfo != null && this.fileInfo.isSymbolicLink()) {
+                final FileObject linkDest = getLinkDestination();
+                // VFS-437: Try to avoid a recursion loop.
+                if (this.isCircular(linkDest)) {
+                    return null;
+                }
+                return linkDest.getChildren();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the file's list of children.
+     *
+     * @return The list of children
+     * @throws FileSystemException If there was a problem listing children
+     * @see AbstractFileObject#getChildren()
+     * @since 2.0
+     */
+    @Override
+    public FileObject[] getChildren() throws FileSystemException {
+        try {
+            if (doGetType() != FileType.FOLDER) {
+                throw new FileNotFolderException(getName());
+            }
+        } catch (final Exception ex) {
+            throw new FileNotFolderException(getName(), ex);
+        }
+
+        try {
+            /*
+             * Wrap our parent implementation, noting that we're refreshing so that we don't refresh() ourselves and
+             * each of our parents for each children. Note that refresh() will list children. Meaning, if if this file
+             * has C children, P parents, there will be (C * P) listings made with (C * (P + 1)) refreshes, when there
+             * should really only be 1 listing and C refreshes.
+             */
+            this.inRefresh.set(true);
+            return super.getChildren();
+        } finally {
+            this.inRefresh.set(false);
+        }
+    }
+
+    /**
+     * Lists the children of the file.
+     */
+    @Override
+    protected String[] doListChildren() throws Exception {
+        // List the children of this file
+        doGetChildren();
+
+        // VFS-210
+        if (children == null) {
+            return null;
+        }
+
+        // TODO - get rid of this children stuff
+        final String[] childNames = new String[children.size()];
+        int childNum = -1;
+        final Iterator<FTPFile> iterChildren = children.values().iterator();
+        while (iterChildren.hasNext()) {
+            childNum++;
+            final FTPFile child = iterChildren.next();
+            childNames[childNum] = child.getName();
+        }
+
+        return UriParser.encode(childNames);
+    }
+
+    /**
+     * Deletes the file.
+     */
+    @Override
+    protected void doDelete() throws Exception {
+        synchronized (getFileSystem()) {
+            if (this.fileInfo != null) {
+                final boolean ok;
+                final FtpClient ftpClient = getAbstractFileSystem().getClient();
+                try {
+                    if (this.fileInfo.isDirectory()) {
+                        ok = ftpClient.removeDirectory(relPath);
+                    } else {
+                        ok = ftpClient.deleteFile(relPath);
+                    }
+                } finally {
+                    getAbstractFileSystem().putClient(ftpClient);
+                }
+
+                if (!ok) {
+                    throw new FileSystemException("vfs.provider.ftp/delete-file.error", getName());
+                }
+                this.fileInfo = null;
+            }
+            this.children = EMPTY_FTP_FILE_MAP;
+        }
+    }
+
+    /**
+     * Renames the file
+     */
+    @Override
+    protected void doRename(final FileObject newFile) throws Exception {
+        synchronized (getFileSystem()) {
+            final boolean ok;
+            final FtpClient ftpClient = getAbstractFileSystem().getClient();
+            try {
+                final String oldName = relPath;
+                final String newName = ((FtpFileObject) FileObjectUtils.getAbstractFileObject(newFile)).getRelPath();
+                ok = ftpClient.rename(oldName, newName);
+            } finally {
+                getAbstractFileSystem().putClient(ftpClient);
+            }
+
+            if (!ok) {
+                throw new FileSystemException("vfs.provider.ftp/rename-file.error", getName().toString(), newFile);
+            }
+            this.fileInfo = null;
+            this.children = EMPTY_FTP_FILE_MAP;
+        }
+    }
+
+    /**
+     * Creates this file as a folder.
+     */
+    @Override
+    protected void doCreateFolder() throws Exception {
+        final boolean ok;
+        final FtpClient client = getAbstractFileSystem().getClient();
+        try {
+            ok = client.makeDirectory(relPath);
+        } finally {
+            getAbstractFileSystem().putClient(client);
+        }
+
+        if (!ok) {
+            throw new FileSystemException("vfs.provider.ftp/create-folder.error", getName());
+        }
+    }
+
+    /**
+     * Returns the size of the file content (in bytes).
+     */
+    @Override
+    protected long doGetContentSize() throws Exception {
+        synchronized (getFileSystem()) {
+            if (this.fileInfo == null) {
+                return 0;
+            }
+            if (this.fileInfo.isSymbolicLink()) {
+                final FileObject linkDest = getLinkDestination();
+                // VFS-437: Try to avoid a recursion loop.
+                if (this.isCircular(linkDest)) {
+                    return this.fileInfo.getSize();
+                }
+                return linkDest.getContent().getSize();
+            }
+            return this.fileInfo.getSize();
+        }
+    }
+
+    /**
+     * get the last modified time on an ftp file
+     *
+     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doGetLastModifiedTime()
+     */
+    @Override
+    protected long doGetLastModifiedTime() throws Exception {
+        synchronized (getFileSystem()) {
+            if (this.fileInfo == null) {
+                return DEFAULT_TIMESTAMP;
+            }
+            if (this.fileInfo.isSymbolicLink()) {
+                final FileObject linkDest = getLinkDestination();
+                // VFS-437: Try to avoid a recursion loop.
+                if (this.isCircular(linkDest)) {
+                    return getTimestamp();
+                }
+                return linkDest.getContent().getLastModifiedTime();
+            }
+            return getTimestamp();
+        }
+    }
+
+    /**
+     * Creates an input stream to read the file content from.
+     */
+    @Override
+    protected InputStream doGetInputStream(final int bufferSize) throws Exception {
+        final FtpClient client = getAbstractFileSystem().getClient();
+        try {
+            final InputStream instr = client.retrieveFileStream(relPath, 0);
+            // VFS-210
+            if (instr == null) {
+                throw new FileNotFoundException(getName().toString());
+            }
+            return new FtpInputStream(client, instr, bufferSize);
+        } catch (final Exception e) {
+            getAbstractFileSystem().putClient(client);
+            throw e;
+        }
+    }
+
+    @Override
+    protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception {
+        return new FtpRandomAccessContent(this, mode);
+    }
+
+    /**
+     * Creates an output stream to write the file content to.
+     */
+    @Override
+    protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception {
+        final FtpClient client = getAbstractFileSystem().getClient();
+        try {
+            OutputStream out = null;
+            if (bAppend) {
+                out = client.appendFileStream(relPath);
+            } else {
+                out = client.storeFileStream(relPath);
+            }
+
+            FileSystemException.requireNonNull(out, "vfs.provider.ftp/output-error.debug", this.getName(),
+                    client.getReplyString());
+
+            return new FtpOutputStream(client, out);
+        } catch (final Exception e) {
+            getAbstractFileSystem().putClient(client);
+            throw e;
+        }
+    }
+
+    String getRelPath() {
+        return relPath;
+    }
+
+    private long getTimestamp() {
+        final Calendar timestamp = this.fileInfo != null ? this.fileInfo.getTimestamp() : null;
+        return timestamp == null ? DEFAULT_TIMESTAMP : timestamp.getTime().getTime();
+    }
+
+    /**
+     * This is an over simplistic implementation for VFS-437.
+     */
+    private boolean isCircular(final FileObject linkDest) throws FileSystemException {
+        return linkDest.getName().getPathDecoded().equals(this.getName().getPathDecoded());
+    }
+
+    FtpInputStream getInputStream(final long filePointer) throws IOException {
+        final FtpClient client = getAbstractFileSystem().getClient();
+        try {
+            final InputStream instr = client.retrieveFileStream(relPath, filePointer);
+            FileSystemException.requireNonNull(instr, "vfs.provider.ftp/input-error.debug", this.getName(),
+                    client.getReplyString());
+            return new FtpInputStream(client, instr);
+        } catch (final IOException e) {
+            getAbstractFileSystem().putClient(client);
+            throw e;
+        }
+    }
+
+    /**
+     * An InputStream that monitors for end-of-file.
+     */
+    class FtpInputStream extends MonitorInputStream {
+        private final FtpClient client;
+
+        public FtpInputStream(final FtpClient client, final InputStream in) {
+            super(in);
+            this.client = client;
+        }
+
+        public FtpInputStream(final FtpClient client, final InputStream in, final int bufferSize) {
+            super(in, bufferSize);
+            this.client = client;
+        }
+
+        void abort() throws IOException {
+            client.abort();
+            close();
+        }
+
+        /**
+         * Called after the stream has been closed.
+         */
+        @Override
+        protected void onClose() throws IOException {
+            final boolean ok;
+            try {
+                ok = client.completePendingCommand() || isTransferAbortedOkReplyCode();
+            } finally {
+                getAbstractFileSystem().putClient(client);
+            }
+
+            if (!ok) {
+                throw new FileSystemException("vfs.provider.ftp/finish-get.error", getName());
+            }
+        }
+
+        private boolean isTransferAbortedOkReplyCode() throws IOException {
+            final List<Integer> transferAbortedOkReplyCodes = FtpFileSystemConfigBuilder
+                .getInstance()
+                .getTransferAbortedOkReplyCodes(getAbstractFileSystem().getFileSystemOptions());
+            return transferAbortedOkReplyCodes != null && transferAbortedOkReplyCodes.contains(client.getReplyCode());
+        }
+    }
+
+    /**
+     * An OutputStream that monitors for end-of-file.
+     */
+    private class FtpOutputStream extends MonitorOutputStream {
+        private final FtpClient client;
+
+        public FtpOutputStream(final FtpClient client, final OutputStream outstr) {
+            super(outstr);
+            this.client = client;
+        }
+
+        /**
+         * Called after this stream is closed.
+         */
+        @Override
+        protected void onClose() throws IOException {
+            final boolean ok;
+            try {
+                ok = client.completePendingCommand();
+            } finally {
+                getAbstractFileSystem().putClient(client);
+            }
+
+            if (!ok) {
+                throw new FileSystemException("vfs.provider.ftp/finish-put.error", getName());
+            }
+        }
+    }
+}
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/gzip/GzipFileObject.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/gzip/GzipFileObject.java
index 498ff0e..7706ba1 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/gzip/GzipFileObject.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/gzip/GzipFileObject.java
@@ -51,9 +51,9 @@ public class GzipFileObject extends CompressedFileFileObject<GzipFileSystem> {
     }
 
     @Override
-    protected InputStream doGetInputStream() throws Exception {
+    protected InputStream doGetInputStream(final int bufferSize) throws Exception {
         final InputStream is = getContainer().getContent().getInputStream();
-        return new GZIPInputStream(is);
+        return new GZIPInputStream(is, bufferSize);
     }
 
     @Override
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/hdfs/HdfsFileObject.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/hdfs/HdfsFileObject.java
index 3964d2d..8be836f 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/hdfs/HdfsFileObject.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/hdfs/HdfsFileObject.java
@@ -1,261 +1,261 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.vfs2.provider.hdfs;
-
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.commons.vfs2.FileNotFolderException;
-import org.apache.commons.vfs2.FileObject;
-import org.apache.commons.vfs2.FileSystemException;
-import org.apache.commons.vfs2.FileType;
-import org.apache.commons.vfs2.RandomAccessContent;
-import org.apache.commons.vfs2.provider.AbstractFileName;
-import org.apache.commons.vfs2.provider.AbstractFileObject;
-import org.apache.commons.vfs2.util.RandomAccessMode;
-import org.apache.hadoop.fs.FileStatus;
-import org.apache.hadoop.fs.FileSystem;
-import org.apache.hadoop.fs.Path;
-
-/**
- * A VFS representation of an HDFS file.
- *
- * @since 2.1
- */
-public class HdfsFileObject extends AbstractFileObject<HdfsFileSystem> {
-
-    private final HdfsFileSystem fs;
-    private final FileSystem hdfs;
-    private final Path path;
-    private FileStatus stat;
-
-    /**
-     * Constructs a new HDFS FileObject
-     *
-     * @param name FileName
-     * @param fs HdfsFileSystem instance
-     * @param hdfs Hadoop FileSystem instance
-     * @param p Path to the file in HDFS
-     */
-    protected HdfsFileObject(final AbstractFileName name, final HdfsFileSystem fs, final FileSystem hdfs,
-            final Path p) {
-        super(name, fs);
-        this.fs = fs;
-        this.hdfs = hdfs;
-        this.path = p;
-    }
-
-    /**
-     * @see org.apache.commons.vfs2.provider.AbstractFileObject#canRenameTo(org.apache.commons.vfs2.FileObject)
-     */
-    @Override
-    public boolean canRenameTo(final FileObject newfile) {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doAttach()
-     */
-    @Override
-    protected void doAttach() throws Exception {
-        try {
-            this.stat = this.hdfs.getFileStatus(this.path);
-        } catch (final FileNotFoundException e) {
-            this.stat = null;
-            return;
-        }
-    }
-
-    /**
-     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doGetAttributes()
-     */
-    @Override
-    protected Map<String, Object> doGetAttributes() throws Exception {
-        if (null == this.stat) {
-            return super.doGetAttributes();
-        }
-        final Map<String, Object> attrs = new HashMap<>();
-        attrs.put(HdfsFileAttributes.LAST_ACCESS_TIME.toString(), this.stat.getAccessTime());
-        attrs.put(HdfsFileAttributes.BLOCK_SIZE.toString(), this.stat.getBlockSize());
-        attrs.put(HdfsFileAttributes.GROUP.toString(), this.stat.getGroup());
-        attrs.put(HdfsFileAttributes.OWNER.toString(), this.stat.getOwner());
-        attrs.put(HdfsFileAttributes.PERMISSIONS.toString(), this.stat.getPermission().toString());
-        attrs.put(HdfsFileAttributes.LENGTH.toString(), this.stat.getLen());
-        attrs.put(HdfsFileAttributes.MODIFICATION_TIME.toString(), this.stat.getModificationTime());
-        return attrs;
-    }
-
-    /**
-     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doGetContentSize()
-     */
-    @Override
-    protected long doGetContentSize() throws Exception {
-        return stat.getLen();
-    }
-
-    /**
-     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doGetInputStream()
-     */
-    @Override
-    protected InputStream doGetInputStream() throws Exception {
-        return this.hdfs.open(this.path);
-    }
-
-    /**
-     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doGetLastModifiedTime()
-     */
-    @Override
-    protected long doGetLastModifiedTime() throws Exception {
-        if (null != this.stat) {
-            return this.stat.getModificationTime();
-        }
-        return -1;
-    }
-
-    /**
-     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doGetRandomAccessContent
-     *      (org.apache.commons.vfs2.util.RandomAccessMode)
-     */
-    @Override
-    protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception {
-        if (mode.equals(RandomAccessMode.READWRITE)) {
-            throw new UnsupportedOperationException();
-        }
-        return new HdfsRandomAccessContent(this.path, this.hdfs);
-    }
-
-    /**
-     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doGetType()
-     */
-    @Override
-    protected FileType doGetType() throws Exception {
-        try {
-            doAttach();
-            if (null == stat) {
-                return FileType.IMAGINARY;
-            }
-            if (stat.isDirectory()) {
-                return FileType.FOLDER;
-            }
-            return FileType.FILE;
-        } catch (final FileNotFoundException fnfe) {
-            return FileType.IMAGINARY;
-        }
-    }
-
-    /**
-     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doIsHidden()
-     */
-    @Override
-    protected boolean doIsHidden() throws Exception {
-        return false;
-    }
-
-    /**
-     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doIsReadable()
-     */
-    @Override
-    protected boolean doIsReadable() throws Exception {
-        return true;
-    }
-
-    /**
-     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doIsWriteable()
-     */
-    @Override
-    protected boolean doIsWriteable() throws Exception {
-        return false;
-    }
-
-    /**
-     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doListChildren()
-     */
-    @Override
-    protected String[] doListChildren() throws Exception {
-        if (this.doGetType() != FileType.FOLDER) {
-            throw new FileNotFolderException(this);
-        }
-
-        final FileStatus[] files = this.hdfs.listStatus(this.path);
-        final String[] children = new String[files.length];
-        int i = 0;
-        for (final FileStatus status : files) {
-            children[i++] = status.getPath().getName();
-        }
-        return children;
-    }
-
-    /**
-     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doListChildrenResolved()
-     */
-    @Override
-    protected FileObject[] doListChildrenResolved() throws Exception {
-        if (this.doGetType() != FileType.FOLDER) {
-            return null;
-        }
-        final String[] children = doListChildren();
-        final FileObject[] fo = new FileObject[children.length];
-        for (int i = 0; i < children.length; i++) {
-            final Path p = new Path(this.path, children[i]);
-            fo[i] = this.fs.resolveFile(p.toUri().toString());
-        }
-        return fo;
-    }
-
-    /**
-     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doRemoveAttribute(java.lang.String)
-     */
-    @Override
-    protected void doRemoveAttribute(final String attrName) throws Exception {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doSetAttribute(java.lang.String, java.lang.Object)
-     */
-    @Override
-    protected void doSetAttribute(final String attrName, final Object value) throws Exception {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doSetLastModifiedTime(long)
-     */
-    @Override
-    protected boolean doSetLastModifiedTime(final long modtime) throws Exception {
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * @see org.apache.commons.vfs2.provider.AbstractFileObject#exists()
-     * @return boolean true if file exists, false if not
-     */
-    @Override
-    public boolean exists() throws FileSystemException {
-        try {
-            doAttach();
-            return this.stat != null;
-        } catch (final FileNotFoundException fne) {
-            return false;
-        } catch (final Exception e) {
-            throw new FileSystemException("Unable to check existance ", e);
-        }
-    }
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.vfs2.provider.hdfs;
+
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.vfs2.FileNotFolderException;
+import org.apache.commons.vfs2.FileObject;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileType;
+import org.apache.commons.vfs2.RandomAccessContent;
+import org.apache.commons.vfs2.provider.AbstractFileName;
+import org.apache.commons.vfs2.provider.AbstractFileObject;
+import org.apache.commons.vfs2.util.RandomAccessMode;
+import org.apache.hadoop.fs.FileStatus;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.Path;
+
+/**
+ * A VFS representation of an HDFS file.
+ *
+ * @since 2.1
+ */
+public class HdfsFileObject extends AbstractFileObject<HdfsFileSystem> {
+
+    private final HdfsFileSystem fs;
+    private final FileSystem hdfs;
+    private final Path path;
+    private FileStatus stat;
+
+    /**
+     * Constructs a new HDFS FileObject
+     *
+     * @param name FileName
+     * @param fs HdfsFileSystem instance
+     * @param hdfs Hadoop FileSystem instance
+     * @param p Path to the file in HDFS
+     */
+    protected HdfsFileObject(final AbstractFileName name, final HdfsFileSystem fs, final FileSystem hdfs,
+            final Path p) {
+        super(name, fs);
+        this.fs = fs;
+        this.hdfs = hdfs;
+        this.path = p;
+    }
+
+    /**
+     * @see org.apache.commons.vfs2.provider.AbstractFileObject#canRenameTo(org.apache.commons.vfs2.FileObject)
+     */
+    @Override
+    public boolean canRenameTo(final FileObject newfile) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doAttach()
+     */
+    @Override
+    protected void doAttach() throws Exception {
+        try {
+            this.stat = this.hdfs.getFileStatus(this.path);
+        } catch (final FileNotFoundException e) {
+            this.stat = null;
+            return;
+        }
+    }
+
+    /**
+     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doGetAttributes()
+     */
+    @Override
+    protected Map<String, Object> doGetAttributes() throws Exception {
+        if (null == this.stat) {
+            return super.doGetAttributes();
+        }
+        final Map<String, Object> attrs = new HashMap<>();
+        attrs.put(HdfsFileAttributes.LAST_ACCESS_TIME.toString(), this.stat.getAccessTime());
+        attrs.put(HdfsFileAttributes.BLOCK_SIZE.toString(), this.stat.getBlockSize());
+        attrs.put(HdfsFileAttributes.GROUP.toString(), this.stat.getGroup());
+        attrs.put(HdfsFileAttributes.OWNER.toString(), this.stat.getOwner());
+        attrs.put(HdfsFileAttributes.PERMISSIONS.toString(), this.stat.getPermission().toString());
+        attrs.put(HdfsFileAttributes.LENGTH.toString(), this.stat.getLen());
+        attrs.put(HdfsFileAttributes.MODIFICATION_TIME.toString(), this.stat.getModificationTime());
+        return attrs;
+    }
+
+    /**
+     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doGetContentSize()
+     */
+    @Override
+    protected long doGetContentSize() throws Exception {
+        return stat.getLen();
+    }
+
+    /**
+     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doGetInputStream(int)
+     */
+    @Override
+    protected InputStream doGetInputStream(final int bufferSize) throws Exception {
+        return this.hdfs.open(this.path, bufferSize);
+    }
+
+    /**
+     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doGetLastModifiedTime()
+     */
+    @Override
+    protected long doGetLastModifiedTime() throws Exception {
+        if (null != this.stat) {
+            return this.stat.getModificationTime();
+        }
+        return -1;
+    }
+
+    /**
+     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doGetRandomAccessContent
+     *      (org.apache.commons.vfs2.util.RandomAccessMode)
+     */
+    @Override
+    protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception {
+        if (mode.equals(RandomAccessMode.READWRITE)) {
+            throw new UnsupportedOperationException();
+        }
+        return new HdfsRandomAccessContent(this.path, this.hdfs);
+    }
+
+    /**
+     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doGetType()
+     */
+    @Override
+    protected FileType doGetType() throws Exception {
+        try {
+            doAttach();
+            if (null == stat) {
+                return FileType.IMAGINARY;
+            }
+            if (stat.isDirectory()) {
+                return FileType.FOLDER;
+            }
+            return FileType.FILE;
+        } catch (final FileNotFoundException fnfe) {
+            return FileType.IMAGINARY;
+        }
+    }
+
+    /**
+     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doIsHidden()
+     */
+    @Override
+    protected boolean doIsHidden() throws Exception {
+        return false;
+    }
+
+    /**
+     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doIsReadable()
+     */
+    @Override
+    protected boolean doIsReadable() throws Exception {
+        return true;
+    }
+
+    /**
+     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doIsWriteable()
+     */
+    @Override
+    protected boolean doIsWriteable() throws Exception {
+        return false;
+    }
+
+    /**
+     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doListChildren()
+     */
+    @Override
+    protected String[] doListChildren() throws Exception {
+        if (this.doGetType() != FileType.FOLDER) {
+            throw new FileNotFolderException(this);
+        }
+
+        final FileStatus[] files = this.hdfs.listStatus(this.path);
+        final String[] children = new String[files.length];
+        int i = 0;
+        for (final FileStatus status : files) {
+            children[i++] = status.getPath().getName();
+        }
+        return children;
+    }
+
+    /**
+     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doListChildrenResolved()
+     */
+    @Override
+    protected FileObject[] doListChildrenResolved() throws Exception {
+        if (this.doGetType() != FileType.FOLDER) {
+            return null;
+        }
+        final String[] children = doListChildren();
+        final FileObject[] fo = new FileObject[children.length];
+        for (int i = 0; i < children.length; i++) {
+            final Path p = new Path(this.path, children[i]);
+            fo[i] = this.fs.resolveFile(p.toUri().toString());
+        }
+        return fo;
+    }
+
+    /**
+     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doRemoveAttribute(java.lang.String)
+     */
+    @Override
+    protected void doRemoveAttribute(final String attrName) throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doSetAttribute(java.lang.String, java.lang.Object)
+     */
+    @Override
+    protected void doSetAttribute(final String attrName, final Object value) throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @see org.apache.commons.vfs2.provider.AbstractFileObject#doSetLastModifiedTime(long)
+     */
+    @Override
+    protected boolean doSetLastModifiedTime(final long modtime) throws Exception {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @see org.apache.commons.vfs2.provider.AbstractFileObject#exists()
+     * @return boolean true if file exists, false if not
+     */
+    @Override
+    public boolean exists() throws FileSystemException {
+        try {
+            doAttach();
+            return this.stat != null;
+        } catch (final FileNotFoundException fne) {
+            return false;
+        } catch (final Exception e) {
+            throw new FileSystemException("Unable to check existance ", e);
+        }
+    }
+
+}
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/http/HttpFileObject.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/http/HttpFileObject.java
index 1e230cb..2066e54 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/http/HttpFileObject.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/http/HttpFileObject.java
@@ -1,247 +1,252 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.vfs2.provider.http;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.HttpURLConnection;
-
-import org.apache.commons.httpclient.Header;
-import org.apache.commons.httpclient.HttpClient;
-import org.apache.commons.httpclient.HttpMethod;
-import org.apache.commons.httpclient.URIException;
-import org.apache.commons.httpclient.methods.GetMethod;
-import org.apache.commons.httpclient.methods.HeadMethod;
-import org.apache.commons.httpclient.util.DateUtil;
-import org.apache.commons.httpclient.util.URIUtil;
-import org.apache.commons.vfs2.FileContentInfoFactory;
-import org.apache.commons.vfs2.FileNotFoundException;
-import org.apache.commons.vfs2.FileSystemException;
-import org.apache.commons.vfs2.FileSystemOptions;
-import org.apache.commons.vfs2.FileType;
-import org.apache.commons.vfs2.RandomAccessContent;
-import org.apache.commons.vfs2.provider.AbstractFileName;
-import org.apache.commons.vfs2.provider.AbstractFileObject;
-import org.apache.commons.vfs2.provider.URLFileName;
-import org.apache.commons.vfs2.util.MonitorInputStream;
-import org.apache.commons.vfs2.util.RandomAccessMode;
-
-/**
- * A file object backed by Apache Commons HttpClient.
- * <p>
- * TODO - status codes.
- * </p>
- *
- * @param <FS> An {@link HttpFileSystem} subclass
- */
-public class HttpFileObject<FS extends HttpFileSystem> extends AbstractFileObject<FS> {
-
-    /**
-     * An InputStream that cleans up the HTTP connection on close.
-     */
-    static class HttpInputStream extends MonitorInputStream {
-        private final GetMethod method;
-
-        public HttpInputStream(final GetMethod method) throws IOException {
-            super(method.getResponseBodyAsStream());
-            this.method = method;
-        }
-
-        /**
-         * Called after the stream has been closed.
-         */
-        @Override
-        protected void onClose() throws IOException {
-            method.releaseConnection();
-        }
-    }
-
-    private final String urlCharset;
-    private final String userAgent;
-    private final boolean followRedirect;
-
-    private HeadMethod method;
-
-    protected HttpFileObject(final AbstractFileName name, final FS fileSystem) {
-        this(name, fileSystem, HttpFileSystemConfigBuilder.getInstance());
-    }
-
-    protected HttpFileObject(final AbstractFileName name, final FS fileSystem,
-            final HttpFileSystemConfigBuilder builder) {
-        super(name, fileSystem);
-        final FileSystemOptions fileSystemOptions = fileSystem.getFileSystemOptions();
-        urlCharset = builder.getUrlCharset(fileSystemOptions);
-        userAgent = builder.getUserAgent(fileSystemOptions);
-        followRedirect = builder.getFollowRedirect(fileSystemOptions);
-    }
-
-    /**
-     * Detaches this file object from its file resource.
-     */
-    @Override
-    protected void doDetach() throws Exception {
-        method = null;
-    }
-
-    /**
-     * Returns the size of the file content (in bytes).
-     */
-    @Override
-    protected long doGetContentSize() throws Exception {
-        final Header header = method.getResponseHeader("content-length");
-        if (header == null) {
-            // Assume 0 content-length
-            return 0;
-        }
-        return Long.parseLong(header.getValue());
-    }
-
-    /**
-     * Creates an input stream to read the file content from. Is only called if {@link #doGetType} returns
-     * {@link FileType#FILE}.
-     * <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.
-     * </p>
-     */
-    @Override
-    protected InputStream doGetInputStream() throws Exception {
-        final GetMethod getMethod = new GetMethod();
-        setupMethod(getMethod);
-        final int status = getAbstractFileSystem().getClient().executeMethod(getMethod);
-        if (status == HttpURLConnection.HTTP_NOT_FOUND) {
-            throw new FileNotFoundException(getName());
-        }
-        if (status != HttpURLConnection.HTTP_OK) {
-            throw new FileSystemException("vfs.provider.http/get.error", getName(), Integer.valueOf(status));
-        }
-
-        return new HttpInputStream(getMethod);
-    }
-
-    /**
-     * Returns the last modified time of this file.
-     * <p>
-     * This implementation throws an exception.
-     * </p>
-     */
-    @Override
-    protected long doGetLastModifiedTime() throws Exception {
-        final Header header = method.getResponseHeader("last-modified");
-        FileSystemException.requireNonNull(header, "vfs.provider.http/last-modified.error", getName());
-        return DateUtil.parseDate(header.getValue()).getTime();
-    }
-
-    @Override
-    protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception {
-        return new HttpRandomAccessContent<>(this, mode);
-    }
-
-    /**
-     * Determines the type of this file. Must not return null. The return value of this method is cached, so the
-     * implementation can be expensive.
-     */
-    @Override
-    protected FileType doGetType() throws Exception {
-        // Use the HEAD method to probe the file.
-        final int status = this.getHeadMethod().getStatusCode();
-        if (status == HttpURLConnection.HTTP_OK
-                || status == HttpURLConnection.HTTP_BAD_METHOD /* method is bad, but resource exist */) {
-            return FileType.FILE;
-        } else if (status == HttpURLConnection.HTTP_NOT_FOUND || status == HttpURLConnection.HTTP_GONE) {
-            return FileType.IMAGINARY;
-        } else {
-            throw new FileSystemException("vfs.provider.http/head.error", getName(), Integer.valueOf(status));
-        }
-    }
-
-    @Override
-    protected boolean doIsWriteable() throws Exception {
-        return false;
-    }
-
-    /**
-     * Lists the children of this file.
-     */
-    @Override
-    protected String[] doListChildren() throws Exception {
-        throw new Exception("Not implemented.");
-    }
-
-    protected String encodePath(final String decodedPath) throws URIException {
-        return URIUtil.encodePath(decodedPath);
-    }
-
-    @Override
-    protected FileContentInfoFactory getFileContentInfoFactory() {
-        return new HttpFileContentInfoFactory();
-    }
-
-    protected boolean getFollowRedirect() {
-        return followRedirect;
-    }
-
-    protected String getUserAgent() {
-        return userAgent;
-    }
-
-    HeadMethod getHeadMethod() throws IOException {
-        if (method != null) {
-            return method;
-        }
-        method = new HeadMethod();
-        try {
-            setupMethod(method);
-            final HttpClient client = getAbstractFileSystem().getClient();
-            client.executeMethod(method);
-        } finally {
-            method.releaseConnection();
-        }
-        return method;
-    }
-
-    protected String getUrlCharset() {
-        return urlCharset;
-    }
-
-    /**
-     * Prepares a HttpMethod object.
-     *
-     * @param method The object which gets prepared to access the file object.
-     * @throws FileSystemException if an error occurs.
-     * @throws URIException if path cannot be represented.
-     * @since 2.0 (was package)
-     */
-    protected void setupMethod(final HttpMethod method) throws FileSystemException, URIException {
-        final String pathEncoded = ((URLFileName) getName()).getPathQueryEncoded(this.getUrlCharset());
-        method.setPath(pathEncoded);
-        method.setFollowRedirects(this.getFollowRedirect());
-        method.setRequestHeader("User-Agent", this.getUserAgent());
-    }
-
-    /*
-     * protected Map doGetAttributes() throws Exception { TreeMap map = new TreeMap();
-     *
-     * Header contentType = method.getResponseHeader("content-type"); if (contentType != null) { HeaderElement[] element
-     * = contentType.getValues(); if (element != null && element.length > 0) { map.put("content-type",
-     * element[0].getName()); } }
-     *
-     * map.put("content-encoding", method.getResponseCharSet()); return map; }
-     */
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.vfs2.provider.http;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+
+import org.apache.commons.httpclient.Header;
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.HttpMethod;
+import org.apache.commons.httpclient.URIException;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.methods.HeadMethod;
+import org.apache.commons.httpclient.util.DateUtil;
+import org.apache.commons.httpclient.util.URIUtil;
+import org.apache.commons.vfs2.FileContentInfoFactory;
+import org.apache.commons.vfs2.FileNotFoundException;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileSystemOptions;
+import org.apache.commons.vfs2.FileType;
+import org.apache.commons.vfs2.RandomAccessContent;
+import org.apache.commons.vfs2.provider.AbstractFileName;
+import org.apache.commons.vfs2.provider.AbstractFileObject;
+import org.apache.commons.vfs2.provider.URLFileName;
+import org.apache.commons.vfs2.util.MonitorInputStream;
+import org.apache.commons.vfs2.util.RandomAccessMode;
+
+/**
+ * A file object backed by Apache Commons HttpClient.
+ * <p>
+ * TODO - status codes.
+ * </p>
+ *
+ * @param <FS> An {@link HttpFileSystem} subclass
+ */
+public class HttpFileObject<FS extends HttpFileSystem> extends AbstractFileObject<FS> {
+
+    /**
+     * An InputStream that cleans up the HTTP connection on close.
+     */
+    static class HttpInputStream extends MonitorInputStream {
+        private final GetMethod method;
+
+        public HttpInputStream(final GetMethod method) throws IOException {
+            super(method.getResponseBodyAsStream());
+            this.method = method;
+        }
+
+        public HttpInputStream(final GetMethod method, final int bufferSize) throws IOException {
+            super(method.getResponseBodyAsStream(), bufferSize);
+            this.method = method;
+        }
+
+        /**
+         * Called after the stream has been closed.
+         */
+        @Override
+        protected void onClose() throws IOException {
+            method.releaseConnection();
+        }
+    }
+
+    private final String urlCharset;
+    private final String userAgent;
+    private final boolean followRedirect;
+
+    private HeadMethod method;
+
+    protected HttpFileObject(final AbstractFileName name, final FS fileSystem) {
+        this(name, fileSystem, HttpFileSystemConfigBuilder.getInstance());
+    }
+
+    protected HttpFileObject(final AbstractFileName name, final FS fileSystem,
+            final HttpFileSystemConfigBuilder builder) {
+        super(name, fileSystem);
+        final FileSystemOptions fileSystemOptions = fileSystem.getFileSystemOptions();
+        urlCharset = builder.getUrlCharset(fileSystemOptions);
+        userAgent = builder.getUserAgent(fileSystemOptions);
+        followRedirect = builder.getFollowRedirect(fileSystemOptions);
+    }
+
+    /**
+     * Detaches this file object from its file resource.
+     */
+    @Override
+    protected void doDetach() throws Exception {
+        method = null;
+    }
+
+    /**
+     * Returns the size of the file content (in bytes).
+     */
+    @Override
+    protected long doGetContentSize() throws Exception {
+        final Header header = method.getResponseHeader("content-length");
+        if (header == null) {
+            // Assume 0 content-length
+            return 0;
+        }
+        return Long.parseLong(header.getValue());
+    }
+
+    /**
+     * Creates an input stream to read the file content from. Is only called if {@link #doGetType} returns
+     * {@link FileType#FILE}.
+     * <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.
+     * </p>
+     */
+    @Override
+    protected InputStream doGetInputStream(final int bufferSize) throws Exception {
+        final GetMethod getMethod = new GetMethod();
+        setupMethod(getMethod);
+        final int status = getAbstractFileSystem().getClient().executeMethod(getMethod);
+        if (status == HttpURLConnection.HTTP_NOT_FOUND) {
+            throw new FileNotFoundException(getName());
+        }
+        if (status != HttpURLConnection.HTTP_OK) {
+            throw new FileSystemException("vfs.provider.http/get.error", getName(), Integer.valueOf(status));
+        }
+
+        return new HttpInputStream(getMethod, bufferSize);
+    }
+
+    /**
+     * Returns the last modified time of this file.
+     * <p>
+     * This implementation throws an exception.
+     * </p>
+     */
+    @Override
+    protected long doGetLastModifiedTime() throws Exception {
+        final Header header = method.getResponseHeader("last-modified");
+        FileSystemException.requireNonNull(header, "vfs.provider.http/last-modified.error", getName());
+        return DateUtil.parseDate(header.getValue()).getTime();
+    }
+
+    @Override
+    protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception {
+        return new HttpRandomAccessContent<>(this, mode);
+    }
+
+    /**
+     * Determines the type of this file. Must not return null. The return value of this method is cached, so the
+     * implementation can be expensive.
+     */
+    @Override
+    protected FileType doGetType() throws Exception {
+        // Use the HEAD method to probe the file.
+        final int status = this.getHeadMethod().getStatusCode();
+        if (status == HttpURLConnection.HTTP_OK
+                || status == HttpURLConnection.HTTP_BAD_METHOD /* method is bad, but resource exist */) {
+            return FileType.FILE;
+        } else if (status == HttpURLConnection.HTTP_NOT_FOUND || status == HttpURLConnection.HTTP_GONE) {
+            return FileType.IMAGINARY;
+        } else {
+            throw new FileSystemException("vfs.provider.http/head.error", getName(), Integer.valueOf(status));
+        }
+    }
+
+    @Override
+    protected boolean doIsWriteable() throws Exception {
+        return false;
+    }
+
+    /**
+     * Lists the children of this file.
+     */
+    @Override
+    protected String[] doListChildren() throws Exception {
+        throw new Exception("Not implemented.");
+    }
+
+    protected String encodePath(final String decodedPath) throws URIException {
+        return URIUtil.encodePath(decodedPath);
+    }
+
+    @Override
+    protected FileContentInfoFactory getFileContentInfoFactory() {
+        return new HttpFileContentInfoFactory();
+    }
+
+    protected boolean getFollowRedirect() {
+        return followRedirect;
+    }
+
+    protected String getUserAgent() {
+        return userAgent;
+    }
+
+    HeadMethod getHeadMethod() throws IOException {
+        if (method != null) {
+            return method;
+        }
+        method = new HeadMethod();
+        try {
+            setupMethod(method);
+            final HttpClient client = getAbstractFileSystem().getClient();
+            client.executeMethod(method);
+        } finally {
+            method.releaseConnection();
+        }
+        return method;
+    }
+
+    protected String getUrlCharset() {
+        return urlCharset;
+    }
+
+    /**
+     * Prepares a HttpMethod object.
+     *
+     * @param method The object which gets prepared to access the file object.
+     * @throws FileSystemException if an error occurs.
+     * @throws URIException if path cannot be represented.
+     * @since 2.0 (was package)
+     */
+    protected void setupMethod(final HttpMethod method) throws FileSystemException, URIException {
+        final String pathEncoded = ((URLFileName) getName()).getPathQueryEncoded(this.getUrlCharset());
+        method.setPath(pathEncoded);
+        method.setFollowRedirects(this.getFollowRedirect());
+        method.setRequestHeader("User-Agent", this.getUserAgent());
+    }
+
+    /*
+     * protected Map doGetAttributes() throws Exception { TreeMap map = new TreeMap();
+     *
+     * Header contentType = method.getResponseHeader("content-type"); if (contentType != null) { HeaderElement[] element
+     * = contentType.getValues(); if (element != null && element.length > 0) { map.put("content-type",
+     * element[0].getName()); } }
+     *
+     * map.put("content-encoding", method.getResponseCharSet()); return map; }
+     */
+}
diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/http4/Http4FileObject.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/http4/Http4FileObject.java
index 697fc09..6f37998 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/http4/Http4FileObject.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/http4/Http4FileObject.java
@@ -1,230 +1,230 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.vfs2.provider.http4;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URI;
-import java.net.URISyntaxException;
-
-import org.apache.commons.vfs2.FileContentInfoFactory;
-import org.apache.commons.vfs2.FileNotFoundException;
-import org.apache.commons.vfs2.FileSystemException;
-import org.apache.commons.vfs2.FileSystemOptions;
-import org.apache.commons.vfs2.FileType;
-import org.apache.commons.vfs2.RandomAccessContent;
-import org.apache.commons.vfs2.provider.AbstractFileName;
-import org.apache.commons.vfs2.provider.AbstractFileObject;
-import org.apache.commons.vfs2.provider.GenericURLFileName;
-import org.apache.commons.vfs2.util.RandomAccessMode;
-import org.apache.http.Header;
-import org.apache.http.HttpResponse;
-import org.apache.http.HttpStatus;
-import org.apache.http.client.HttpClient;
-import org.apache.http.client.methods.HttpGet;
-import org.apache.http.client.methods.HttpHead;
-import org.apache.http.client.methods.HttpUriRequest;
-import org.apache.http.client.protocol.HttpClientContext;
-import org.apache.http.client.utils.DateUtils;
-import org.apache.http.client.utils.URIUtils;
-import org.apache.http.protocol.HTTP;
-
-/**
- * A file object backed by Apache HttpComponents HttpClient.
- *
- * @param <FS> An {@link Http4FileSystem} subclass
- */
-public class Http4FileObject<FS extends Http4FileSystem> extends AbstractFileObject<FS> {
-
-    /**
-     * URL charset string.
-     */
-    private final String urlCharset;
-
-    /**
-     * Internal URI mapped to this <code>FileObject</code>.
-     * For example, the internal URI of <code>http4://example.com/a.txt</code> is <code>http://example.com/a.txt</code>.
-     */
-    private final URI internalURI;
-
-    /**
-     * The last executed HEAD <code>HttpResponse</code> object.
-     */
-    private HttpResponse lastHeadResponse;
-
-    /**
-     * Construct <code>Http4FileObject</code>.
-     *
-     * @param name file name
-     * @param fileSystem file system
-     * @throws FileSystemException if any error occurs
-     * @throws URISyntaxException if given file name cannot be converted to a URI due to URI syntax error
-     */
-    protected Http4FileObject(final AbstractFileName name, final FS fileSystem)
-            throws FileSystemException, URISyntaxException {
-        this(name, fileSystem, Http4FileSystemConfigBuilder.getInstance());
-    }
-
-    /**
-     * Construct <code>Http4FileObject</code>.
-     *
-     * @param name file name
-     * @param fileSystem file system
-     * @param builder <code>Http4FileSystemConfigBuilder</code> object
-     * @throws FileSystemException if any error occurs
-     * @throws URISyntaxException if given file name cannot be converted to a URI due to URI syntax error
-     */
-    protected Http4FileObject(final AbstractFileName name, final FS fileSystem,
-            final Http4FileSystemConfigBuilder builder) throws FileSystemException, URISyntaxException {
-        super(name, fileSystem);
-        final FileSystemOptions fileSystemOptions = fileSystem.getFileSystemOptions();
-        urlCharset = builder.getUrlCharset(fileSystemOptions);
-        final String pathEncoded = ((GenericURLFileName) name).getPathQueryEncoded(getUrlCharset());
-        internalURI = URIUtils.resolve(fileSystem.getInternalBaseURI(), pathEncoded);
-    }
-
-    @Override
-    protected FileType doGetType() throws Exception {
-        lastHeadResponse = executeHttpUriRequest(new HttpHead(getInternalURI()));
-        final int status = lastHeadResponse.getStatusLine().getStatusCode();
-
-        if (status == HttpStatus.SC_OK
-                || status == HttpStatus.SC_METHOD_NOT_ALLOWED /* method is not allowed, but resource exist */) {
-            return FileType.FILE;
-        } else if (status == HttpStatus.SC_NOT_FOUND || status == HttpStatus.SC_GONE) {
-            return FileType.IMAGINARY;
-        } else {
-            throw new FileSystemException("vfs.provider.http/head.error", getName(), Integer.valueOf(status));
-        }
-    }
-
-    @Override
-    protected long doGetContentSize() throws Exception {
-        if (lastHeadResponse == null) {
-            return 0L;
-        }
-
-        final Header header = lastHeadResponse.getFirstHeader(HTTP.CONTENT_LEN);
-
-        if (header == null) {
-            // Assume 0 content-length
-            return 0;
-        }
-
-        return Long.parseLong(header.getValue());
-    }
-
-    @Override
-    protected long doGetLastModifiedTime() throws Exception {
-        FileSystemException.requireNonNull(lastHeadResponse, "vfs.provider.http/last-modified.error", getName());
-
-        final Header header = lastHeadResponse.getFirstHeader("Last-Modified");
-
-        FileSystemException.requireNonNull(header, "vfs.provider.http/last-modified.error", getName());
-
-        return DateUtils.parseDate(header.getValue()).getTime();
-    }
-
-
-    @Override
-    protected InputStream doGetInputStream() throws Exception {
-        final HttpGet getRequest = new HttpGet(getInternalURI());
-        final HttpResponse httpResponse = executeHttpUriRequest(getRequest);
-        final int status = httpResponse.getStatusLine().getStatusCode();
-
-        if (status == HttpStatus.SC_NOT_FOUND) {
-            throw new FileNotFoundException(getName());
-        }
-
-        if (status != HttpStatus.SC_OK) {
-            throw new FileSystemException("vfs.provider.http/get.error", getName(), Integer.valueOf(status));
-        }
-
-        return new MonitoredHttpResponseContentInputStream(httpResponse);
-    }
-
-    @Override
-    protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception {
-        return new Http4RandomAccessContent<>(this, mode);
-    }
-
-    @Override
-    protected String[] doListChildren() throws Exception {
-        throw new UnsupportedOperationException("Not implemented.");
-    }
-
-    @Override
-    protected boolean doIsWriteable() throws Exception {
-        return false;
-    }
-
-    @Override
-    protected FileContentInfoFactory getFileContentInfoFactory() {
-        return new Http4FileContentInfoFactory();
-    }
-
-    @Override
-    protected void doDetach() throws Exception {
-        lastHeadResponse = null;
-    }
-
-    /**
-     * Return URL charset string.
-     * @return URL charset string
-     */
-    protected String getUrlCharset() {
-        return urlCharset;
-    }
-
-    /**
-     * Return the internal <code>URI</code> object mapped to this file object.
-     *
-     * @return the internal <code>URI</code> object mapped to this file object
-     * @throws FileSystemException if any error occurs
-     */
-    protected URI getInternalURI() throws FileSystemException {
-        return internalURI;
-    }
-
-    /**
-     * Return the last executed HEAD <code>HttpResponse</code> object.
-     *
-     * @return the last executed HEAD <code>HttpResponse</code> object
-     * @throws IOException if IO error occurs
-     */
-    HttpResponse getLastHeadResponse() throws IOException {
-        if (lastHeadResponse != null) {
-            return lastHeadResponse;
-        }
-
-        return executeHttpUriRequest(new HttpHead(getInternalURI()));
-    }
-
-    /**
-     * Execute the request using the given {@code httpRequest} and return a <code>HttpResponse</code> from the execution.
-     *
-     * @param httpRequest <code>HttpUriRequest</code> object
-     * @return <code>HttpResponse</code> from the execution
-     * @throws IOException if IO error occurs
-     */
-    HttpResponse executeHttpUriRequest(final HttpUriRequest httpRequest) throws IOException {
-        final HttpClient httpClient = getAbstractFileSystem().getHttpClient();
-        final HttpClientContext httpClientContext = getAbstractFileSystem().getHttpClientContext();
-        return httpClient.execute(httpRequest, httpClientContext);
-    }
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.vfs2.provider.http4;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import org.apache.commons.vfs2.FileContentInfoFactory;
+import org.apache.commons.vfs2.FileNotFoundException;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileSystemOptions;
+import org.apache.commons.vfs2.FileType;
+import org.apache.commons.vfs2.RandomAccessContent;
+import org.apache.commons.vfs2.provider.AbstractFileName;
+import org.apache.commons.vfs2.provider.AbstractFileObject;
+import org.apache.commons.vfs2.provider.GenericURLFileName;
+import org.apache.commons.vfs2.util.RandomAccessMode;
+import org.apache.http.Header;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpHead;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.client.utils.DateUtils;
+import org.apache.http.client.utils.URIUtils;
+import org.apache.http.protocol.HTTP;
+
+/**
+ * A file object backed by Apache HttpComponents HttpClient.
+ *
+ * @param <FS> An {@link Http4FileSystem} subclass
+ */
+public class Http4FileObject<FS extends Http4FileSystem> extends AbstractFileObject<FS> {
+
+    /**
+     * URL charset string.
+     */
+    private final String urlCharset;
+
+    /**
+     * Internal URI mapped to this <code>FileObject</code>.
+     * For example, the internal URI of <code>http4://example.com/a.txt</code> is <code>http://example.com/a.txt</code>.
+     */
+    private final URI internalURI;
+
+    /**
+     * The last executed HEAD <code>HttpResponse</code> object.
+     */
+    private HttpResponse lastHeadResponse;
+
+    /**
+     * Construct <code>Http4FileObject</code>.
+     *
+     * @param name file name
+     * @param fileSystem file system
+     * @throws FileSystemException if any error occurs
+     * @throws URISyntaxException if given file name cannot be converted to a URI due to URI syntax error
+     */
+    protected Http4FileObject(final AbstractFileName name, final FS fileSystem)
+            throws FileSystemException, URISyntaxException {
+        this(name, fileSystem, Http4FileSystemConfigBuilder.getInstance());
+    }
+
+    /**
+     * Construct <code>Http4FileObject</code>.
+     *
+     * @param name file name
+     * @param fileSystem file system
+     * @param builder <code>Http4FileSystemConfigBuilder</code> object
+     * @throws FileSystemException if any error occurs
+     * @throws URISyntaxException if given file name cannot be converted to a URI due to URI syntax error
+     */
+    protected Http4FileObject(final AbstractFileName name, final FS fileSystem,
+            final Http4FileSystemConfigBuilder builder) throws FileSystemException, URISyntaxException {
+        super(name, fileSystem);
+        final FileSystemOptions fileSystemOptions = fileSystem.getFileSystemOptions();
+        urlCharset = builder.getUrlCharset(fileSystemOptions);
+        final String pathEncoded = ((GenericURLFileName) name).getPathQueryEncoded(getUrlCharset());
+        internalURI = URIUtils.resolve(fileSystem.getInternalBaseURI(), pathEncoded);
+    }
+
+    @Override
+    protected FileType doGetType() throws Exception {
+        lastHeadResponse = executeHttpUriRequest(new HttpHead(getInternalURI()));
+        final int status = lastHeadResponse.getStatusLine().getStatusCode();
+
+        if (status == HttpStatus.SC_OK
+                || status == HttpStatus.SC_METHOD_NOT_ALLOWED /* method is not allowed, but resource exist */) {
+            return FileType.FILE;
+        } else if (status == HttpStatus.SC_NOT_FOUND || status == HttpStatus.SC_GONE) {
+            return FileType.IMAGINARY;
+        } else {
+            throw new FileSystemException("vfs.provider.http/head.error", getName(), Integer.valueOf(status));
+        }
+    }
+
+    @Override
+    protected long doGetContentSize() throws Exception {
+        if (lastHeadResponse == null) {
+            return 0L;
+        }
+
+        final Header header = lastHeadResponse.getFirstHeader(HTTP.CONTENT_LEN);
+
+        if (header == null) {
+            // Assume 0 content-length
+            return 0;
+        }
+
+        return Long.parseLong(header.getValue());
+    }
+
+    @Override
+    protected long doGetLastModifiedTime() throws Exception {
+        FileSystemException.requireNonNull(lastHeadResponse, "vfs.provider.http/last-modified.error", getName());
+
+        final Header header = lastHeadResponse.getFirstHeader("Last-Modified");
+
+        FileSystemException.requireNonNull(header, "vfs.provider.http/last-modified.error", getName());
+
+        return DateUtils.parseDate(header.getValue()).getTime();
+    }
... 2406 lines suppressed ...


[commons-vfs] 02/03: Sort members.

Posted by gg...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-vfs.git

commit 5aaa0b5f6b86d5db97f53866151c02e2b46ecc8f
Author: Gary Gregory <ga...@gmail.com>
AuthorDate: Fri Aug 16 11:57:29 2019 -0700

    Sort members.
---
 .../vfs2/provider/ftp/FTPClientWrapper.java        | 552 ++++++++++-----------
 1 file changed, 276 insertions(+), 276 deletions(-)

diff --git a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/ftp/FTPClientWrapper.java b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/ftp/FTPClientWrapper.java
index 7d18a89..3c31df9 100644
--- a/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/ftp/FTPClientWrapper.java
+++ b/commons-vfs2/src/main/java/org/apache/commons/vfs2/provider/ftp/FTPClientWrapper.java
@@ -1,276 +1,276 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.vfs2.provider.ftp;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.commons.net.ftp.FTPClient;
-import org.apache.commons.net.ftp.FTPFile;
-import org.apache.commons.net.ftp.FTPReply;
-import org.apache.commons.vfs2.FileSystemException;
-import org.apache.commons.vfs2.FileSystemOptions;
-import org.apache.commons.vfs2.UserAuthenticationData;
-import org.apache.commons.vfs2.provider.GenericFileName;
-import org.apache.commons.vfs2.util.UserAuthenticatorUtils;
-
-/**
- * A wrapper to the FTPClient to allow automatic reconnect on connection loss.
- * <p>
- * I decided to not to use eg. noop() to determine the state of the connection to avoid unnecessary server round-trips.
- * </p>
- */
-public class FTPClientWrapper implements FtpClient {
-
-    private static final Log LOG = LogFactory.getLog(FTPClientWrapper.class);
-
-    protected final FileSystemOptions fileSystemOptions;
-    private final GenericFileName root;
-    private FTPClient ftpClient;
-
-    protected FTPClientWrapper(final GenericFileName root, final FileSystemOptions fileSystemOptions)
-            throws FileSystemException {
-        this.root = root;
-        this.fileSystemOptions = fileSystemOptions;
-        getFtpClient(); // fail-fast
-    }
-
-    public GenericFileName getRoot() {
-        return root;
-    }
-
-    public FileSystemOptions getFileSystemOptions() {
-        return fileSystemOptions;
-    }
-
-    private FTPClient createClient() throws FileSystemException {
-        final GenericFileName rootName = getRoot();
-
-        UserAuthenticationData authData = null;
-        try {
-            authData = UserAuthenticatorUtils.authenticate(fileSystemOptions, FtpFileProvider.AUTHENTICATOR_TYPES);
-
-            return createClient(rootName, authData);
-        } finally {
-            UserAuthenticatorUtils.cleanup(authData);
-        }
-    }
-
-    protected FTPClient createClient(final GenericFileName rootName, final UserAuthenticationData authData)
-            throws FileSystemException {
-        return FtpClientFactory.createConnection(rootName.getHostName(), rootName.getPort(),
-                UserAuthenticatorUtils.getData(authData, UserAuthenticationData.USERNAME,
-                        UserAuthenticatorUtils.toChar(rootName.getUserName())),
-                UserAuthenticatorUtils.getData(authData, UserAuthenticationData.PASSWORD,
-                        UserAuthenticatorUtils.toChar(rootName.getPassword())),
-                rootName.getPath(), getFileSystemOptions());
-    }
-
-    private FTPClient getFtpClient() throws FileSystemException {
-        if (ftpClient == null) {
-            ftpClient = createClient();
-        }
-
-        return ftpClient;
-    }
-
-    @Override
-    public boolean isConnected() throws FileSystemException {
-        return ftpClient != null && ftpClient.isConnected();
-    }
-
-    @Override
-    public void disconnect() throws IOException {
-        try {
-            getFtpClient().quit();
-        } catch (final IOException e) {
-            LOG.debug("I/O exception while trying to quit, probably it's a timed out connection, ignoring.", e);
-        } finally {
-            try {
-                getFtpClient().disconnect();
-            } catch (final IOException e) {
-                LOG.warn("I/O exception while trying to disconnect, probably it's a closed connection, ignoring.", e);
-            } finally {
-                ftpClient = null;
-            }
-        }
-    }
-
-    @Override
-    public FTPFile[] listFiles(final String relPath) throws IOException {
-        try {
-            // VFS-210: return getFtpClient().listFiles(relPath);
-            final FTPFile[] files = listFilesInDirectory(relPath);
-            return files;
-        } catch (final IOException e) {
-            disconnect();
-            final FTPFile[] files = listFilesInDirectory(relPath);
-            return files;
-        }
-    }
-
-    private FTPFile[] listFilesInDirectory(final String relPath) throws IOException {
-        FTPFile[] files;
-
-        // VFS-307: no check if we can simply list the files, this might fail if there are spaces in the path
-        files = getFtpClient().listFiles(relPath);
-        if (FTPReply.isPositiveCompletion(getFtpClient().getReplyCode())) {
-            return files;
-        }
-
-        // VFS-307: now try the hard way by cd'ing into the directory, list and cd back
-        // if VFS is required to fallback here the user might experience a real bad FTP performance
-        // as then every list requires 4 ftp commands.
-        String workingDirectory = null;
-        if (relPath != null) {
-            workingDirectory = getFtpClient().printWorkingDirectory();
-            if (!getFtpClient().changeWorkingDirectory(relPath)) {
-                return null;
-            }
-        }
-
-        files = getFtpClient().listFiles();
-
-        if (relPath != null && !getFtpClient().changeWorkingDirectory(workingDirectory)) {
-            throw new FileSystemException("vfs.provider.ftp.wrapper/change-work-directory-back.error",
-                    workingDirectory);
-        }
-        return files;
-    }
-
-    @Override
-    public boolean removeDirectory(final String relPath) throws IOException {
-        try {
-            return getFtpClient().removeDirectory(relPath);
-        } catch (final IOException e) {
-            disconnect();
-            return getFtpClient().removeDirectory(relPath);
-        }
-    }
-
-    @Override
-    public boolean deleteFile(final String relPath) throws IOException {
-        try {
-            return getFtpClient().deleteFile(relPath);
-        } catch (final IOException e) {
-            disconnect();
-            return getFtpClient().deleteFile(relPath);
-        }
-    }
-
-    @Override
-    public boolean rename(final String oldName, final String newName) throws IOException {
-        try {
-            return getFtpClient().rename(oldName, newName);
-        } catch (final IOException e) {
-            disconnect();
-            return getFtpClient().rename(oldName, newName);
-        }
-    }
-
-    @Override
-    public boolean makeDirectory(final String relPath) throws IOException {
-        try {
-            return getFtpClient().makeDirectory(relPath);
-        } catch (final IOException e) {
-            disconnect();
-            return getFtpClient().makeDirectory(relPath);
-        }
-    }
-
-    @Override
-    public boolean completePendingCommand() throws IOException {
-        if (ftpClient != null) {
-            return getFtpClient().completePendingCommand();
-        }
-
-        return true;
-    }
-
-    @Override
-    public InputStream retrieveFileStream(final String relPath) throws IOException {
-        try {
-            return getFtpClient().retrieveFileStream(relPath);
-        } catch (final IOException e) {
-            disconnect();
-            return getFtpClient().retrieveFileStream(relPath);
-        }
-    }
-
-    @Override
-    public InputStream retrieveFileStream(final String relPath, final long restartOffset) throws IOException {
-        try {
-            final FTPClient client = getFtpClient();
-            client.setRestartOffset(restartOffset);
-            return client.retrieveFileStream(relPath);
-        } catch (final IOException e) {
-            disconnect();
-            final FTPClient client = getFtpClient();
-            client.setRestartOffset(restartOffset);
-            return client.retrieveFileStream(relPath);
-        }
-    }
-
-    @Override
-    public OutputStream appendFileStream(final String relPath) throws IOException {
-        try {
-            return getFtpClient().appendFileStream(relPath);
-        } catch (final IOException e) {
-            disconnect();
-            return getFtpClient().appendFileStream(relPath);
-        }
-    }
-
-    @Override
-    public OutputStream storeFileStream(final String relPath) throws IOException {
-        try {
-            return getFtpClient().storeFileStream(relPath);
-        } catch (final IOException e) {
-            disconnect();
-            return getFtpClient().storeFileStream(relPath);
-        }
-    }
-
-    @Override
-    public boolean abort() throws IOException {
-        try {
-            // imario@apache.org: 2005-02-14
-            // it should be better to really "abort" the transfer, but
-            // currently I didnt manage to make it work - so lets "abort" the hard way.
-            // return getFtpClient().abort();
-
-            disconnect();
-            return true;
-        } catch (final IOException e) {
-            disconnect();
-        }
-        return true;
-    }
-
-    @Override
-    public int getReplyCode() throws IOException {
-        return getFtpClient().getReplyCode();
-    }
-
-    @Override
-    public String getReplyString() throws IOException {
-        return getFtpClient().getReplyString();
-    }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.vfs2.provider.ftp;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.net.ftp.FTPClient;
+import org.apache.commons.net.ftp.FTPFile;
+import org.apache.commons.net.ftp.FTPReply;
+import org.apache.commons.vfs2.FileSystemException;
+import org.apache.commons.vfs2.FileSystemOptions;
+import org.apache.commons.vfs2.UserAuthenticationData;
+import org.apache.commons.vfs2.provider.GenericFileName;
+import org.apache.commons.vfs2.util.UserAuthenticatorUtils;
+
+/**
+ * A wrapper to the FTPClient to allow automatic reconnect on connection loss.
+ * <p>
+ * I decided to not to use eg. noop() to determine the state of the connection to avoid unnecessary server round-trips.
+ * </p>
+ */
+public class FTPClientWrapper implements FtpClient {
+
+    private static final Log LOG = LogFactory.getLog(FTPClientWrapper.class);
+
+    protected final FileSystemOptions fileSystemOptions;
+    private FTPClient ftpClient;
+    private final GenericFileName root;
+
+    protected FTPClientWrapper(final GenericFileName root, final FileSystemOptions fileSystemOptions)
+            throws FileSystemException {
+        this.root = root;
+        this.fileSystemOptions = fileSystemOptions;
+        getFtpClient(); // fail-fast
+    }
+
+    @Override
+    public boolean abort() throws IOException {
+        try {
+            // imario@apache.org: 2005-02-14
+            // it should be better to really "abort" the transfer, but
+            // currently I didnt manage to make it work - so lets "abort" the hard way.
+            // return getFtpClient().abort();
+
+            disconnect();
+            return true;
+        } catch (final IOException e) {
+            disconnect();
+        }
+        return true;
+    }
+
+    @Override
+    public OutputStream appendFileStream(final String relPath) throws IOException {
+        try {
+            return getFtpClient().appendFileStream(relPath);
+        } catch (final IOException e) {
+            disconnect();
+            return getFtpClient().appendFileStream(relPath);
+        }
+    }
+
+    @Override
+    public boolean completePendingCommand() throws IOException {
+        if (ftpClient != null) {
+            return getFtpClient().completePendingCommand();
+        }
+
+        return true;
+    }
+
+    private FTPClient createClient() throws FileSystemException {
+        final GenericFileName rootName = getRoot();
+
+        UserAuthenticationData authData = null;
+        try {
+            authData = UserAuthenticatorUtils.authenticate(fileSystemOptions, FtpFileProvider.AUTHENTICATOR_TYPES);
+
+            return createClient(rootName, authData);
+        } finally {
+            UserAuthenticatorUtils.cleanup(authData);
+        }
+    }
+
+    protected FTPClient createClient(final GenericFileName rootName, final UserAuthenticationData authData)
+            throws FileSystemException {
+        return FtpClientFactory.createConnection(rootName.getHostName(), rootName.getPort(),
+                UserAuthenticatorUtils.getData(authData, UserAuthenticationData.USERNAME,
+                        UserAuthenticatorUtils.toChar(rootName.getUserName())),
+                UserAuthenticatorUtils.getData(authData, UserAuthenticationData.PASSWORD,
+                        UserAuthenticatorUtils.toChar(rootName.getPassword())),
+                rootName.getPath(), getFileSystemOptions());
+    }
+
+    @Override
+    public boolean deleteFile(final String relPath) throws IOException {
+        try {
+            return getFtpClient().deleteFile(relPath);
+        } catch (final IOException e) {
+            disconnect();
+            return getFtpClient().deleteFile(relPath);
+        }
+    }
+
+    @Override
+    public void disconnect() throws IOException {
+        try {
+            getFtpClient().quit();
+        } catch (final IOException e) {
+            LOG.debug("I/O exception while trying to quit, probably it's a timed out connection, ignoring.", e);
+        } finally {
+            try {
+                getFtpClient().disconnect();
+            } catch (final IOException e) {
+                LOG.warn("I/O exception while trying to disconnect, probably it's a closed connection, ignoring.", e);
+            } finally {
+                ftpClient = null;
+            }
+        }
+    }
+
+    public FileSystemOptions getFileSystemOptions() {
+        return fileSystemOptions;
+    }
+
+    private FTPClient getFtpClient() throws FileSystemException {
+        if (ftpClient == null) {
+            ftpClient = createClient();
+        }
+
+        return ftpClient;
+    }
+
+    @Override
+    public int getReplyCode() throws IOException {
+        return getFtpClient().getReplyCode();
+    }
+
+    @Override
+    public String getReplyString() throws IOException {
+        return getFtpClient().getReplyString();
+    }
+
+    public GenericFileName getRoot() {
+        return root;
+    }
+
+    @Override
+    public boolean isConnected() throws FileSystemException {
+        return ftpClient != null && ftpClient.isConnected();
+    }
+
+    @Override
+    public FTPFile[] listFiles(final String relPath) throws IOException {
+        try {
+            // VFS-210: return getFtpClient().listFiles(relPath);
+            final FTPFile[] files = listFilesInDirectory(relPath);
+            return files;
+        } catch (final IOException e) {
+            disconnect();
+            final FTPFile[] files = listFilesInDirectory(relPath);
+            return files;
+        }
+    }
+
+    private FTPFile[] listFilesInDirectory(final String relPath) throws IOException {
+        FTPFile[] files;
+
+        // VFS-307: no check if we can simply list the files, this might fail if there are spaces in the path
+        files = getFtpClient().listFiles(relPath);
+        if (FTPReply.isPositiveCompletion(getFtpClient().getReplyCode())) {
+            return files;
+        }
+
+        // VFS-307: now try the hard way by cd'ing into the directory, list and cd back
+        // if VFS is required to fallback here the user might experience a real bad FTP performance
+        // as then every list requires 4 ftp commands.
+        String workingDirectory = null;
+        if (relPath != null) {
+            workingDirectory = getFtpClient().printWorkingDirectory();
+            if (!getFtpClient().changeWorkingDirectory(relPath)) {
+                return null;
+            }
+        }
+
+        files = getFtpClient().listFiles();
+
+        if (relPath != null && !getFtpClient().changeWorkingDirectory(workingDirectory)) {
+            throw new FileSystemException("vfs.provider.ftp.wrapper/change-work-directory-back.error",
+                    workingDirectory);
+        }
+        return files;
+    }
+
+    @Override
+    public boolean makeDirectory(final String relPath) throws IOException {
+        try {
+            return getFtpClient().makeDirectory(relPath);
+        } catch (final IOException e) {
+            disconnect();
+            return getFtpClient().makeDirectory(relPath);
+        }
+    }
+
+    @Override
+    public boolean removeDirectory(final String relPath) throws IOException {
+        try {
+            return getFtpClient().removeDirectory(relPath);
+        } catch (final IOException e) {
+            disconnect();
+            return getFtpClient().removeDirectory(relPath);
+        }
+    }
+
+    @Override
+    public boolean rename(final String oldName, final String newName) throws IOException {
+        try {
+            return getFtpClient().rename(oldName, newName);
+        } catch (final IOException e) {
+            disconnect();
+            return getFtpClient().rename(oldName, newName);
+        }
+    }
+
+    @Override
+    public InputStream retrieveFileStream(final String relPath) throws IOException {
+        try {
+            return getFtpClient().retrieveFileStream(relPath);
+        } catch (final IOException e) {
+            disconnect();
+            return getFtpClient().retrieveFileStream(relPath);
+        }
+    }
+
+    @Override
+    public InputStream retrieveFileStream(final String relPath, final long restartOffset) throws IOException {
+        try {
+            final FTPClient client = getFtpClient();
+            client.setRestartOffset(restartOffset);
+            return client.retrieveFileStream(relPath);
+        } catch (final IOException e) {
+            disconnect();
+            final FTPClient client = getFtpClient();
+            client.setRestartOffset(restartOffset);
+            return client.retrieveFileStream(relPath);
+        }
+    }
+
+    @Override
+    public OutputStream storeFileStream(final String relPath) throws IOException {
+        try {
+            return getFtpClient().storeFileStream(relPath);
+        } catch (final IOException e) {
+            disconnect();
+            return getFtpClient().storeFileStream(relPath);
+        }
+    }
+}