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.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  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; /** * 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); } } } 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 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 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 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. *

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

* 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 { // 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: *
    *
  • {@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 { // noop } /** * 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 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}. *

* This implementation always returns false. *

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

* 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 { 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 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 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() { @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. *

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

* This implementation does nothing. *

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

* 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 { // 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(); } }