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

org.apache.commons.vfs2.provider.AbstractFileObject Maven / Gradle / Ivy

There is a newer version: 2.9.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.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.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  An AbstractFileSystem subclass
 */
public abstract class AbstractFileObject implements FileObject {
    /*
     * TODO - Chop this class up - move all the protected methods to several interfaces, so that structure and content
     * can be separately overridden.
     *
     * 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')
     */

    private static final FileName[] EMPTY_FILE_ARRAY = {};

    private static final int INITIAL_LIST_SIZE = 5;

    private final AbstractFileName fileName;
    private final AFS fs;

    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 objects;

    /**
     * FileServices instance.
     */
    private FileOperations operations;

    /**
     *
     * @param name the file name - muse be an instance of {@link AbstractFileName}
     * @param fs the file system
     * @throws ClassCastException if {@code name} is not an instance of {@link AbstractFileName}
     */
    protected AbstractFileObject(final AbstractFileName name, final AFS fs) {
        this.fileName = name;
        this.fs = fs;
        fs.fileObjectHanded(this);
    }

    /**
     * Traverses a file.
     */
    private static void traverse(final DefaultFileSelectorInfo fileInfo, final FileSelector selector,
            final boolean depthwise, final List 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);
            }
        }
    }

    /**
     * Attaches to the file.
     *
     * @throws FileSystemException if an error occurs.
     */
    private void attach() throws FileSystemException {
        synchronized (fs) {
            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 filename of {@code newfile} is possible.
     *
     * @param newfile the new filename
     * @return true if rename is possible
     */
    @Override
    public boolean canRenameTo(final FileObject newfile) {
        return fs == newfile.getFileSystem();
    }

    /**
     * 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 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;

        // 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 (!file.exists()) {
            throw new FileSystemException("vfs.provider/copy-missing-file.error", file);
        }

        // Locate the files to copy across
        final ArrayList 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 (destFile.exists() && destFile.getType() != srcFile.getType()) {
                // The destination file exists, and is not of the same type,
                // so delete it
                // TODO - add a pluggable policy for deleting and overwriting existing files
                destFile.deleteAll();
            }

            // 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 (fs) {
            try {
                // VFS-210: We do not want to trunc any existing file, checking for its existence is
                // still required
                if (exists() && !isFile()) {
                    throw new FileSystemException("vfs.provider/create-file.error", fileName);
                }

                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 (fs) {
            // VFS-210: we create a folder only if it does not already exist. So this check should be safe.
            if (getType().hasChildren()) {
                // Already exists as correct type
                return;
            }
            if (getType() != FileType.IMAGINARY) {
                throw new FileSystemException("vfs.provider/create-folder-mismatched-type.error", fileName);
            }
            /*
             * VFS-210: checking for writeable is not always possible as the security constraint might be more complex
             * if (!isWriteable()) { throw new FileSystemException("vfs.provider/create-folder-read-only.error", name);
             * }
             */

            // Traverse up the 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.
     * 

* TODO - This will not fail if this is a non-empty folder. * * @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 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 (fs) { // 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 (fs) { 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. *

* This method is called before any of the doBlah() or onBlah() methods. Sub-classes can use this method to perform * lazy initialisation. *

* This implementation does nothing. * * @throws Exception if an error occurs. */ protected void doAttach() throws Exception { } /** * 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: *

    *
  • {@link #doGetType} returns {@link FileType#IMAGINARY}.
  • *
  • The parent folder exists and is writeable, or this file is the root of the file system.
  • *
* 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: *
    *
  • {@link #doGetType} does not return {@link FileType#IMAGINARY}.
  • *
  • {@link #doIsWriteable} returns true.
  • *
  • This file has no children, if a folder.
  • *
* 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. *

* Called when this file is closed. Note that the file object may be reused later, so should be able to be * reattached. *

* This implementation does nothing. * * @throws Exception if an error occurs. */ protected void doDetach() throws Exception { } /** * Returns the attributes of this file. Is only called if {@link #doGetType} does not return * {@link FileType#IMAGINARY}. *

* This implementation always returns an empty map. * * @return The attributes of the file. * @throws Exception if an error occurs. */ protected Map 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}. *

* This implementation always returns null. * * @return The certificates used to sign the file. * @throws Exception if an error occurs. */ protected Certificate[] doGetCertificates() throws Exception { return null; } /** * 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}. *

* It is guaranteed that there are no open output streams for this file when this method is called. *

* The returned stream does not have to be buffered. * * @return An InputStream to read the file content. * @throws Exception if an error occurs. */ protected abstract InputStream doGetInputStream() throws Exception; /** * Returns the last modified time of this file. Is only called if {@link #doGetType} does not return * {@link FileType#IMAGINARY}. *

* This implementation throws an exception. * * @return The last modification time. * @throws Exception if an error occurs. */ protected long doGetLastModifiedTime() throws Exception { throw new FileSystemException("vfs.provider/get-last-modified-not-supported.error"); } /** * Creates an output stream to write the file content to. Is only called if: *

    *
  • {@link #doIsWriteable} returns true. *
  • {@link #doGetType} returns {@link FileType#FILE}, or {@link #doGetType} returns {@link FileType#IMAGINARY}, * and the file's parent exists and is a folder. *
* It is guaranteed that there are no open stream (input or output) for this file when this method is called. *

* The returned stream does not have to be buffered. *

* This implementation throws an exception. * * @param bAppend true if the file should be appended to, false if it should be overwritten. * @return An OutputStream to write to the file. * @throws Exception if an error occurs. */ protected OutputStream doGetOutputStream(final boolean bAppend) throws Exception { throw new FileSystemException("vfs.provider/write-not-supported.error"); } /** * Creates access to the file for random i/o. Is only called if {@link #doGetType} returns {@link FileType#FILE}. *

* It is guaranteed that there are no open output streams for this file when this method is called. * * @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}. *

* This implementation always returns false. * * @return true if the file is executable, false otherwise. * @throws Exception if an error occurs. */ protected boolean doIsExecutable() throws Exception { return false; } /** * Determines if this file is hidden. Is only called if {@link #doGetType} does not return * {@link FileType#IMAGINARY}. *

* This implementation always returns false. * * @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}. *

* This implementation always returns true. * * @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 filesystems like windows. * * @param destFile The file to compare to. * @return true if the FileObjects are the same. * @throws FileSystemException if an error occurs. */ protected boolean doIsSameFile(final FileObject destFile) throws FileSystemException { return false; } /** * Determines if this file can be written to. Is only called if {@link #doGetType} does not return * {@link FileType#IMAGINARY}. *

* This implementation always returns true. * * @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. *

* Is only called if {@link #doGetType} returns {@link FileType#FOLDER}. *

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

* (Introduced for Webdav: "permission denied on resource" during getType()) * * @return The children of this FileObject. * @throws Exception if an error occurs. */ protected FileObject[] doListChildrenResolved() throws Exception { return null; } /** * Removes an attribute of this file. *

* Is only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. *

* This implementation throws an exception. * * @param attrName The name of the attribute to remove. * @throws Exception if an error occurs. * @since 2.0 */ protected void doRemoveAttribute(final String attrName) throws Exception { throw new FileSystemException("vfs.provider/remove-attribute-not-supported.error"); } /** * Renames the file. *

* Is only called when: *

    *
  • {@link #doIsWriteable} returns true.
  • *
*

* This implementation throws an exception. * * @param newFile A FileObject with the new file name. * @throws Exception if an error occurs. */ protected void doRename(final FileObject newFile) throws Exception { throw new FileSystemException("vfs.provider/rename-not-supported.error"); } /** * Sets an attribute of this file. *

* Is only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. *

* This implementation throws an exception. * * @param attrName The attribute name. * @param value The value to be associated with the attribute name. * @throws Exception if an error occurs. */ protected void doSetAttribute(final String attrName, final Object value) throws Exception { throw new FileSystemException("vfs.provider/set-attribute-not-supported.error"); } /** * Make the file executable. *

* Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. *

* This implementation returns false. * * @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. *

* Is only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. *

* This implementation throws an exception. * * @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. *

* Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. *

* This implementation returns false. * * @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. *

* Only called if {@link #doGetType} does not return {@link FileType#IMAGINARY}. * * @param writable True to allow access, false to disallow * @param ownerOnly If {@code true}, the permission applies only to the owner; otherwise, it applies to everybody. * @return true if the operation succeeded * @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 { fs.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 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 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 fs; } /** * 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 (fs) { // VFS-210 if (!fs.hasCapability(Capability.LIST_CHILDREN)) { throw new FileNotFolderException(fileName); } /* * VFS-210 if (!getType().hasChildren()) { throw new * FileSystemException("vfs.provider/list-children-not-folder.error", name); } */ attach(); // Use cached info, if present if (children != null) { return resolveFiles(children); } // allow the filesystem to return resolved children. e.g. prefill type for webdav FileObject[] childrenObjects; try { childrenObjects = doListChildrenResolved(); children = extractNames(childrenObjects); } catch (final FileSystemException exc) { // VFS-210 throw exc; } catch (final Exception exc) { throw new FileSystemException("vfs.provider/list-children.error", exc, fileName); } if (childrenObjects != null) { return childrenObjects; } // List the children final String[] files; try { files = doListChildren(); } catch (final FileSystemException exc) { // VFS-210 throw exc; } catch (final Exception exc) { throw new FileSystemException("vfs.provider/list-children.error", exc, fileName); } if (files == null) { // VFS-210 // honor the new doListChildren contract // return null; throw new FileNotFolderException(fileName); } else if (files.length == 0) { // No children children = EMPTY_FILE_ARRAY; } else { // Create file objects for the children final FileName[] cache = new FileName[files.length]; for (int i = 0; i < files.length; i++) { final String file = files[i]; cache[i] = fs.getFileSystemManager().resolveName(fileName, file, NameScope.CHILD); } // VFS-285: only assign the children filenames after all of them have been // resolved successfully to prevent an inconsistent internal state children = cache; } return resolveFiles(children); } } /** * Returns the file's content. * * @return the FileContent for this FileObject. * @throws FileSystemException if an error occurs. */ @Override public FileContent getContent() throws FileSystemException { synchronized (fs) { attach(); if (content == null) { content = doCreateFileContent(); } return content; } } /** * Creates the FileContentInfo factory. * * @return The FileContentInfoFactory. */ protected FileContentInfoFactory getFileContentInfoFactory() { return fs.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 fs; } /** * 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; } /** * 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(); } /** * 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 filesystem does not support appending, a FileSystemException is thrown. * @return An OutputStream where the new contents of the file can be written. * @throws FileSystemException if an error occurs; for example:
* 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 && !fs.hasCapability(Capability.APPEND_CONTENT)) { throw new FileSystemException("vfs.provider/write-append-not-supported.error", fileName); } if (getType() == FileType.IMAGINARY) { // Does not exist - make sure parent does final FileObject parent = getParent(); if (parent != null) { parent.createFolder(); } } // Get the raw output stream try { return doGetOutputStream(bAppend); } catch (final RuntimeException re) { throw re; } catch (final Exception exc) { throw new FileSystemException("vfs.provider/write.error", exc, fileName); } } /** * Returns the parent of the file. * * @return the parent FileObject. * @throws FileSystemException if an error occurs. */ @Override public FileObject getParent() throws FileSystemException { if (this.compareTo(fs.getRoot()) == 0) // equals is not implemented :-/ { if (fs.getParentLayer() == null) { // Root file has no parent return null; } // Return the parent of the parent layer return fs.getParentLayer().getParent(); } synchronized (fs) { // Locate the parent of this file if (parent == null) { final FileName name = fileName.getParent(); if (name == null) { return null; } parent = fs.resolveFile(name); } return parent; } } /** * 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 (!fs.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 (!fs.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 (fs) { attach(); // VFS-210: get the type only if requested for try { if (type == null) { setFileType(doGetType()); } if (type == null) { setFileType(FileType.IMAGINARY); } } catch (final Exception e) { throw new FileSystemException("vfs.provider/get-type.error", e, fileName); } return type; } } /** * 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() { @Override public URL run() throws MalformedURLException { final StringBuilder buf = new StringBuilder(); final String scheme = UriParser.extractScheme(fileName.getURI(), buf); return new URL(scheme, "", -1, buf.toString(), new DefaultURLStreamHandler(fs.getContext(), fs.getFileSystemOptions())); } }); } catch (final PrivilegedActionException e) { throw new FileSystemException("vfs.provider/get-url.error", fileName, e.getException()); } } /** * Called when this file is changed. *

* This will only happen if you monitor the file using {@link org.apache.commons.vfs2.FileMonitor}. * * @throws Exception if an error occurs. */ protected void handleChanged() throws Exception { // Notify the file system fs.fireFileChanged(this); } /** * Called when this file is created. Updates cached info and notifies the parent and file system. * * @param newType The type of the file. * @throws Exception if an error occurs. */ protected void handleCreate(final FileType newType) throws Exception { synchronized (fs) { 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 fs.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 (fs) { 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 fs.fireFileDeleted(this); } } /** * This method is meant to add an object where this object holds a strong reference then. E.g. a archive-filesystem * 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 filesystems like windows. * * @param destFile The file to compare to. * @return true if the FileObjects are the same. * @throws FileSystemException if an error occurs. */ protected boolean isSameFile(final FileObject destFile) throws FileSystemException { attach(); return doIsSameFile(destFile); } /** * 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 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 listFiles(final FileSelector selector) throws FileSystemException { if (!exists() || selector == null) { return null; } final ArrayList 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)) && fs.hasCapability(Capability.GET_LAST_MODIFIED)) { destFile.getContent().setLastModifiedTime(this.getContent().getLastModifiedTime()); } deleteSelf(); } } /** * will be called after this file-object closed all its streams. */ protected void notifyAllStreamsClosed() { } /** * 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 = fs.getFileFromCache(parentName); } } if (parent != null) { FileObjectUtils.getAbstractFileObject(parent).childrenChanged(childName, newType); } } /** * Called when the type or content of this file changes. *

* This implementation does nothing. * * @throws Exception if an error occurs. */ protected void onChange() throws Exception { } /** * Called when the children of this file change. Allows subclasses to refresh any cached information about the * children of this file. *

* This implementation does nothing. * * @param child The name of the child that changed. * @param newType The type of the file. * @throws Exception if an error occurs. */ protected void onChildrenChanged(final FileName child, final FileType newType) throws Exception { } /** * This will prepare the fileObject to get resynchronized with the underlying filesystem 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 fs.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 = fs.getFileSystemManager().resolveName(fileName, path); return fs.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 fs.resolveFile(fs.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(); } }