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:43 UTC

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

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 ...