blob: e0034bdb2938e90be3d711c290d9586125a9cc85 [file] [log] [blame]
/*
* 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.vfs.provider;
import org.apache.commons.vfs.operations.DefaultFileOperations;
import org.apache.commons.vfs.operations.FileOperations;
import org.apache.commons.vfs.util.FileObjectUtils;
import org.apache.commons.vfs.util.RandomAccessMode;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileName;
import org.apache.commons.vfs.FileContent;
import org.apache.commons.vfs.FileType;
import org.apache.commons.vfs.FileSystemException;
import org.apache.commons.vfs.RandomAccessContent;
import org.apache.commons.vfs.FileSystem;
import org.apache.commons.vfs.Capability;
import org.apache.commons.vfs.FileNotFolderException;
import org.apache.commons.vfs.NameScope;
import org.apache.commons.vfs.Selectors;
import org.apache.commons.vfs.FileSelector;
import org.apache.commons.vfs.FileContentInfoFactory;
import org.apache.commons.vfs.FileUtil;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileNotFoundException;
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.List;
import java.util.Map;
/**
* A partial file object implementation.
*
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a>
* @author Gary D. Gregory
* @version $Revision$ $Date$
* @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')
*/
public abstract class AbstractFileObject implements FileObject
{
// private static final FileObject[] EMPTY_FILE_ARRAY = {};
private static final FileName[] EMPTY_FILE_ARRAY = {};
private static final int INITIAL_LISTSZ = 5;
private final AbstractFileName name;
private final AbstractFileSystem 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;
protected AbstractFileObject(final FileName name,
final AbstractFileSystem fs)
{
this.name = (AbstractFileName) name;
this.fs = fs;
fs.fileObjectHanded(this);
}
/**
* Attaches this file object to its file resource. This method is called
* before any of the doBlah() or onBlah() methods. Sub-classes can use
* this method to perform lazy initialisation.
* <p/>
* This implementation does nothing.
*/
protected void doAttach() throws Exception
{
}
/**
* Detaches this file object from its file resource.
* <p/>
* <p>Called when this file is closed. Note that the file object may be
* reused later, so should be able to be reattached.
* <p/>
* This implementation does nothing.
*/
protected void doDetach() throws Exception
{
}
/**
* Determines the type of this file. Must not return null. The return
* value of this method is cached, so the implementation can be expensive.
*/
protected abstract FileType doGetType() throws Exception;
/**
* Determines if this file is hidden. Is only called if {@link #doGetType}
* does not return {@link FileType#IMAGINARY}.
* <p/>
* This implementation always returns false.
*/
protected boolean doIsHidden() throws Exception
{
return false;
}
/**
* Determines if this file can be read. Is only called if {@link #doGetType}
* does not return {@link FileType#IMAGINARY}.
* <p/>
* This implementation always returns true.
*/
protected boolean doIsReadable() throws Exception
{
return true;
}
/**
* Determines if this file can be written to. Is only called if
* {@link #doGetType} does not return {@link FileType#IMAGINARY}.
* <p/>
* This implementation always returns true.
*/
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.<br />
* @return a possible empty String array if the file is a directory or null or an exception if the
* file is not a directory or can't be read
*/
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.<br>
* Other than <code>doListChildren</code> you could return FileObject's to e.g. reinitialize the
* type of the file.<br>
* (Introduced for Webdav: "permission denied on resource" during getType())
*/
protected FileObject[] doListChildrenResolved() throws Exception
{
return null;
}
/**
* Deletes the file. Is only called when:
* <ul>
* <li>{@link #doGetType} does not return {@link FileType#IMAGINARY}.
* <li>{@link #doIsWriteable} returns true.
* <li>This file has no children, if a folder.
* </ul>
* <p/>
* This implementation throws an exception.
*/
protected void doDelete() throws Exception
{
throw new FileSystemException("vfs.provider/delete-not-supported.error");
}
/**
* Renames the file. Is only called when:
* <ul>
* <li>{@link #doIsWriteable} returns true.
* </ul>
* <p/>
* This implementation throws an exception.
*/
protected void doRename(FileObject newfile) throws Exception
{
throw new FileSystemException("vfs.provider/rename-not-supported.error");
}
/**
* Creates this file as a folder. Is only called when:
* <ul>
* <li>{@link #doGetType} returns {@link FileType#IMAGINARY}.
* <li>The parent folder exists and is writeable, or this file is the
* root of the file system.
* </ul>
* <p/>
* This implementation throws an exception.
*/
protected void doCreateFolder() throws Exception
{
throw new FileSystemException("vfs.provider/create-folder-not-supported.error");
}
/**
* Called when the children of this file change. Allows subclasses to
* refresh any cached information about the children of this file.
* <p/>
* This implementation does nothing.
*/
protected void onChildrenChanged(FileName child, FileType newType) throws Exception
{
}
/**
* Called when the type or content of this file changes.
* <p/>
* This implementation does nothing.
*/
protected void onChange() throws Exception
{
}
/**
* Returns the last modified time of this file. Is only called if
* {@link #doGetType} does not return {@link FileType#IMAGINARY}.
* <p/>
* This implementation throws an exception.
*/
protected long doGetLastModifiedTime() throws Exception
{
throw new FileSystemException("vfs.provider/get-last-modified-not-supported.error");
}
/**
* Sets the last modified time of this file. Is only called if
* {@link #doGetType} does not return {@link FileType#IMAGINARY}.
* <p/>
* This implementation throws an exception.
*
* @return false if it was not possible to change the time
*/
protected boolean doSetLastModTime(final long modtime)
throws Exception
{
doSetLastModifiedTime(modtime);
return true;
}
/**
* Sets the last modified time of this file. Is only called if
* {@link #doGetType} does not return {@link FileType#IMAGINARY}.
* <p/>
* This implementation throws an exception.
*
* @deprecated use {@link #doSetLastModTime}
*/
protected void doSetLastModifiedTime(final long modtime)
throws Exception
{
throw new FileSystemException("vfs.provider/set-last-modified-not-supported.error");
}
/**
* Returns the attributes of this file. Is only called if {@link #doGetType}
* does not return {@link FileType#IMAGINARY}.
* <p/>
* This implementation always returns an empty map.
*/
protected Map doGetAttributes()
throws Exception
{
return Collections.EMPTY_MAP;
}
/**
* Sets an attribute of this file. Is only called if {@link #doGetType}
* does not return {@link FileType#IMAGINARY}.
* <p/>
* This implementation throws an exception.
*/
protected void doSetAttribute(final String atttrName, final Object value)
throws Exception
{
throw new FileSystemException("vfs.provider/set-attribute-not-supported.error");
}
/**
* Removes an attribute of this file. Is only called if {@link #doGetType}
* does not return {@link FileType#IMAGINARY}.
* <p/>
* This implementation throws an exception.
* @returns true if removing the attribute succeed. In this case we remove the attribute from
* our cache
*/
protected void doRemoveAttribute(final String atttrName)
throws Exception
{
throw new FileSystemException("vfs.provider/remove-attribute-not-supported.error");
}
/**
* Returns the certificates used to sign this file. Is only called if
* {@link #doGetType} does not return {@link FileType#IMAGINARY}.
* <p/>
* This implementation always returns null.
*/
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}.
*/
protected abstract long doGetContentSize() throws Exception;
/**
* Creates an input stream to read the file content from. Is only called
* if {@link #doGetType} returns {@link FileType#FILE}.
* <p/>
* <p>It is guaranteed that there are no open output streams for this file
* when this method is called.
* <p/>
* <p>The returned stream does not have to be buffered.
*/
protected abstract InputStream doGetInputStream() throws Exception;
/**
* Creates access to the file for random i/o. Is only called
* if {@link #doGetType} returns {@link FileType#FILE}.
* <p/>
* <p>It is guaranteed that there are no open output streams for this file
* when this method is called.
* <p/>
*/
protected RandomAccessContent doGetRandomAccessContent(final RandomAccessMode mode) throws Exception
{
throw new FileSystemException("vfs.provider/random-access-not-supported.error");
}
/**
* Creates an output stream to write the file content to. Is only
* called if:
* <ul>
* <li>{@link #doIsWriteable} returns true.
* <li>{@link #doGetType} returns {@link FileType#FILE}, or
* {@link #doGetType} returns {@link FileType#IMAGINARY}, and the file's
* parent exists and is a folder.
* </ul>
* <p/>
* <p>It is guaranteed that there are no open stream (input or output) for
* this file when this method is called.
* <p/>
* <p>The returned stream does not have to be buffered.
* <p/>
* This implementation throws an exception.
*/
protected OutputStream doGetOutputStream(boolean bAppend) throws Exception
{
throw new FileSystemException("vfs.provider/write-not-supported.error");
}
/**
* Returns the URI of the file.
* @return The URI of the file.
*/
public String toString()
{
return name.getURI();
}
/**
* Returns the name of the file.
* @return The FileName.
*/
public FileName getName()
{
return name;
}
/**
* Returns the file system this file belongs to.
* @return The FileSystem this file is associated with.
*/
public FileSystem getFileSystem()
{
return fs;
}
/**
* Returns a URL representation of the file.
* @return The URL representation of the file.
* @throws FileSystemException if an error occurs.
*/
public URL getURL() throws FileSystemException
{
final StringBuffer buf = new StringBuffer();
try
{
return (URL) AccessController.doPrivileged(new PrivilegedExceptionAction()
{
public Object run() throws MalformedURLException
{
return new URL(UriParser.extractScheme(name.getURI(), buf), "", -1,
buf.toString(), new DefaultURLStreamHandler(fs.getContext(), fs.getFileSystemOptions()));
}
});
}
catch (final PrivilegedActionException e)
{
throw new FileSystemException("vfs.provider/get-url.error", name, e.getException());
}
}
/**
* Determines if the file exists.
* @return true if the file exists, false otherwise,
* @throws FileSystemException if an error occurs.
*/
public boolean exists() throws FileSystemException
{
return getType() != FileType.IMAGINARY;
}
/**
* Returns the file's type.
* @return The FileType.
* @throws FileSystemException if an error occurs.
*/
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 (Exception e)
{
throw new FileSystemException("vfs.provider/get-type.error", new Object[]{name}, e);
}
return type;
}
}
/**
* Determines if this file can be read.
* @return true if the file is a hidden file, false otherwise.
* @throws FileSystemException if an error occurs.
*/
public boolean isHidden() throws FileSystemException
{
try
{
if (exists())
{
return doIsHidden();
}
else
{
return false;
}
}
catch (final Exception exc)
{
throw new FileSystemException("vfs.provider/check-is-hidden.error", name, exc);
}
}
/**
* Determines if this file can be read.
* @return true if the file can be read, false otherwise.
* @throws FileSystemException if an error occurs.
*/
public boolean isReadable() throws FileSystemException
{
try
{
if (exists())
{
return doIsReadable();
}
else
{
return false;
}
}
catch (final Exception exc)
{
throw new FileSystemException("vfs.provider/check-is-readable.error", name, 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.
*/
public boolean isWriteable() throws FileSystemException
{
try
{
if (exists())
{
return doIsWriteable();
}
else
{
final FileObject parent = getParent();
if (parent != null)
{
return parent.isWriteable();
}
return true;
}
}
catch (final Exception exc)
{
throw new FileSystemException("vfs.provider/check-is-writeable.error", name, exc);
}
}
/**
* Returns the parent of the file.
* @return the parent FileObject.
* @throws FileSystemException if an error occurs.
*/
public FileObject getParent() throws FileSystemException
{
if (this == fs.getRoot())
{
if (fs.getParentLayer() != null)
{
// Return the parent of the parent layer
return fs.getParentLayer().getParent();
}
else
{
// Root file has no parent
return null;
}
}
synchronized (fs)
{
// Locate the parent of this file
if (parent == null)
{
parent = fs.resolveFile(name.getParent());
}
}
return parent;
}
/**
* Returns the children of the file.
* @return an array of FileObjects, one per child.
* @throws FileSystemException if an error occurs.
*/
public FileObject[] getChildren() throws FileSystemException
{
synchronized (fs)
{
// VFS-210
if (!getFileSystem().hasCapability(Capability.LIST_CHILDREN))
{
throw new FileNotFolderException(name);
}
/* 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 (FileSystemException exc)
{
// VFS-210
throw exc;
}
catch (Exception exc)
{
throw new FileSystemException("vfs.provider/list-children.error", new Object[]{name}, exc);
}
if (childrenObjects != null)
{
return childrenObjects;
}
// List the children
final String[] files;
try
{
files = doListChildren();
}
catch (FileSystemException exc)
{
// VFS-210
throw exc;
}
catch (Exception exc)
{
throw new FileSystemException("vfs.provider/list-children.error", new Object[]{name}, exc);
}
if (files == null)
{
// VFS-210
// honor the new doListChildren contract
// return null;
throw new FileNotFolderException(name);
}
else if (files.length == 0)
{
// No children
children = EMPTY_FILE_ARRAY;
}
else
{
// Create file objects for the children
// children = new FileObject[files.length];
children = new FileName[files.length];
for (int i = 0; i < files.length; i++)
{
final String file = files[i];
// children[i] = fs.resolveFile(name.resolveName(file, NameScope.CHILD));
// children[i] = name.resolveName(file, NameScope.CHILD);
children[i] = getFileSystem().getFileSystemManager().resolveName(name, file, NameScope.CHILD);
}
}
return resolveFiles(children);
}
}
private FileName[] extractNames(FileObject[] objects)
{
if (objects == null)
{
return null;
}
FileName[] names = new FileName[objects.length];
for (int iterObjects = 0; iterObjects < objects.length; iterObjects++)
{
names[iterObjects] = objects[iterObjects].getName();
}
return names;
}
private FileObject[] resolveFiles(FileName[] children) throws FileSystemException
{
if (children == null)
{
return null;
}
FileObject[] objects = new FileObject[children.length];
for (int iterChildren = 0; iterChildren < children.length; iterChildren++)
{
objects[iterChildren] = resolveFile(children[iterChildren]);
}
return objects;
}
private FileObject resolveFile(FileName child) throws FileSystemException
{
return fs.resolveFile(child);
}
/**
* 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.
*/
public FileObject getChild(final String name) throws FileSystemException
{
// TODO - use a hashtable when there are a large number of children
FileObject[] children = getChildren();
for (int i = 0; i < children.length; i++)
{
// final FileObject child = children[i];
final FileName child = children[i].getName();
// TODO - use a comparator to compare names
// if (child.getName().getBaseName().equals(name))
if (child.getBaseName().equals(name))
{
return resolveFile(child);
}
}
return null;
}
/**
* 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.
*/
public FileObject resolveFile(final String name, final NameScope scope)
throws FileSystemException
{
// return fs.resolveFile(this.name.resolveName(name, scope));
return fs.resolveFile(getFileSystem().getFileSystemManager().resolveName(this.name, name, scope));
}
/**
* 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.
*/
public FileObject resolveFile(final String path) throws FileSystemException
{
final FileName otherName = getFileSystem().getFileSystemManager().resolveName(name, path);
return fs.resolveFile(otherName);
}
/**
* Deletes this file, once all its children have been deleted
*
* @return true if this file has been deleted
*/
private boolean deleteSelf() throws FileSystemException
{
synchronized (fs)
{
/* Its possible to delete a read-only file if you have write-execute access to the directory
if (!isWriteable())
{
throw new FileSystemException("vfs.provider/delete-read-only.error", name);
}
*/
/* VFS-210
if (getType() == FileType.IMAGINARY)
{
// File does not exist
return false;
}
*/
try
{
// Delete the file
doDelete();
// Update cached info
handleDelete();
}
catch (final RuntimeException re)
{
throw re;
}
catch (final Exception exc)
{
throw new FileSystemException("vfs.provider/delete.error", new Object[]{name}, exc);
}
return true;
}
}
/**
* Deletes this file.
*
* @return true if this object has been deleted
* @todo This will not fail if this is a non-empty folder.
* @throws FileSystemException if an error occurs.
*/
public boolean delete() throws FileSystemException
{
return delete(Selectors.SELECT_SELF) > 0;
}
/**
* Deletes this file, and all children.
*
* @param selector The FileSelector.
* @return the number of deleted files.
* @throws FileSystemException if an error occurs.
*/
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
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((FileObject) 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
boolean deleted = file.deleteSelf();
if (deleted)
{
nuofDeleted++;
}
}
return nuofDeleted;
}
/**
* Creates this file, if it does not exist.
* @throws FileSystemException if an error occurs.
*/
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() && !FileType.FILE.equals(getType()))
{
throw new FileSystemException("vfs.provider/create-file.error", name);
}
if (!exists())
{
getOutputStream().close();
endOutput();
}
}
catch (final RuntimeException re)
{
throw re;
}
catch (final Exception e)
{
throw new FileSystemException("vfs.provider/create-file.error", name, e);
}
}
}
/**
* Creates this folder, if it does not exist. Also creates any ancestor
* files which do not exist.
* @throws FileSystemException if an error occurs.
*/
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", name);
}
/* VFS-210: checking for writeable is not always possible as the security constraint might
be more complex
if (!isWriteable())
{
throw new FileSystemException("vfs.provider/create-folder-read-only.error", name);
}
*/
// Traverse up the heirarchy and make sure everything is a folder
final FileObject parent = getParent();
if (parent != null)
{
parent.createFolder();
}
try
{
// Create the folder
doCreateFolder();
// Update cached info
handleCreate(FileType.FOLDER);
}
catch (final RuntimeException re)
{
throw re;
}
catch (final Exception exc)
{
throw new FileSystemException("vfs.provider/create-folder.error", name, exc);
}
}
}
/**
* Copies another file to this file.
* @param file The FileObject to copy.
* @param selector The FileSelector.
* @throws FileSystemException if an error occurs.
*/
public void copyFrom(final FileObject file, final FileSelector selector)
throws FileSystemException
{
if (!file.exists())
{
throw new FileSystemException("vfs.provider/copy-missing-file.error", file);
}
/* we do not alway know if a file is writeable
if (!isWriteable())
{
throw new FileSystemException("vfs.provider/copy-read-only.error", new Object[]{file.getType(),
file.getName(), this}, null);
}
*/
// Locate the files to copy across
final ArrayList files = new ArrayList();
file.findFiles(selector, false, files);
// Copy everything across
final int count = files.size();
for (int i = 0; i < count; i++)
{
final FileObject srcFile = (FileObject) files.get(i);
// 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.delete(Selectors.SELECT_ALL);
}
// 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", new Object[]{srcFile, destFile}, e);
}
}
}
/**
* Moves (rename) the file to another one.
* @param destFile The target FileObject.
* @throws FileSystemException if an error occurs.
*/
public void moveTo(FileObject destFile) throws FileSystemException
{
if (canRenameTo(destFile))
{
if (!getParent().isWriteable())
{
throw new FileSystemException("vfs.provider/rename-parent-read-only.error", new FileName[]{getName(),
getParent().getName()});
}
}
else
{
if (!isWriteable())
{
throw new FileSystemException("vfs.provider/rename-read-only.error", getName());
}
}
if (destFile.exists() && !isSameFile(destFile))
{
destFile.delete(Selectors.SELECT_ALL);
// throw new FileSystemException("vfs.provider/rename-dest-exists.error", destFile.getName());
}
if (canRenameTo(destFile))
{
// issue rename on same filesystem
try
{
attach();
doRename(destFile);
(FileObjectUtils.getAbstractFileObject(destFile)).handleCreate(getType());
destFile.close(); // now the destFile is no longer imaginary. force reattach.
handleDelete(); // fire delete-events. This file-object (src) is like deleted.
}
catch (final RuntimeException re)
{
throw re;
}
catch (final Exception exc)
{
throw new FileSystemException("vfs.provider/rename.error", new Object[]
{
getName(),
destFile.getName()
}, exc);
}
}
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)))
&& getFileSystem().hasCapability(Capability.GET_LAST_MODIFIED))
{
destFile.getContent().setLastModifiedTime(this.getContent().getLastModifiedTime());
}
deleteSelf();
}
}
/**
* Checks if this fileObject is the same file as <code>destFile</code> just with a different
* name.<br />
* E.g. for case insensitive filesystems like windows.
*/
protected boolean isSameFile(FileObject destFile) throws FileSystemException
{
attach();
return doIsSameFile(destFile);
}
/**
* Checks if this fileObject is the same file as <code>destFile</code> just with a different
* name.<br />
* E.g. for case insensitive filesystems like windows.
*/
protected boolean doIsSameFile(FileObject destFile) throws FileSystemException
{
return false;
}
/**
* Queries the object if a simple rename to the filename of <code>newfile</code>
* is possible.
*
* @param newfile the new filename
* @return true if rename is possible
*/
public boolean canRenameTo(FileObject newfile)
{
if (getFileSystem() == newfile.getFileSystem())
{
return true;
}
return false;
}
/**
* Finds the set of matching descendents 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.
*/
public FileObject[] findFiles(final FileSelector selector) throws FileSystemException
{
if (!exists())
{
return null;
}
final ArrayList list = new ArrayList();
findFiles(selector, true, list);
return (FileObject[]) list.toArray(new FileObject[list.size()]);
}
/**
* Returns the file's content.
* @return the FileContent for this FileObject.
* @throws FileSystemException if an error occurs.
*/
public FileContent getContent() throws FileSystemException
{
synchronized (fs)
{
attach();
if (content == null)
{
content = doCreateFileContent();
}
return content;
}
}
/**
* Create a FileContent implementation.
*/
protected FileContent doCreateFileContent() throws FileSystemException
{
return new DefaultFileContent(this, getFileContentInfoFactory());
}
/**
* This will prepare the fileObject to get resynchronized with the underlaying filesystem if required.
* @throws FileSystemException if an error occurs.
*/
public void refresh() throws FileSystemException
{
// Detach from the file
try
{
detach();
}
catch (final Exception e)
{
throw new FileSystemException("vfs.provider/resync.error", name, e);
}
}
/**
* Closes this file, and its content.
* @throws FileSystemException if an error occurs.
*/
public void close() throws FileSystemException
{
FileSystemException exc = null;
// Close the content
if (content != null)
{
try
{
content.close();
content = null;
}
catch (FileSystemException e)
{
exc = e;
}
}
// Detach from the file
try
{
detach();
}
catch (final Exception e)
{
exc = new FileSystemException("vfs.provider/close.error", name, e);
}
if (exc != null)
{
throw exc;
}
}
/**
* 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.vfs.FileNotFoundException exc)
{
throw new org.apache.commons.vfs.FileNotFoundException(name, exc);
}
catch (final FileNotFoundException exc)
{
throw new org.apache.commons.vfs.FileNotFoundException(name, exc);
}
catch (final FileSystemException exc)
{
throw exc;
}
catch (final Exception exc)
{
throw new FileSystemException("vfs.provider/read.error", name, exc);
}
}
/**
* 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 (!getFileSystem().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", name);
}
}
if (mode.requestWrite())
{
if (!getFileSystem().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", name);
}
}
// Get the raw input stream
try
{
return doGetRandomAccessContent(mode);
}
catch (final Exception exc)
{
throw new FileSystemException("vfs.provider/random-access.error", name, exc);
}
}
/**
* Prepares this file for writing. Makes sure it is either a file,
* or its parent folder exists. Returns an output stream to use to
* write the content of the file to.
* @return An OutputStream where the new contents of the file can be written.
* @throws FileSystemException if an error occurs.
*/
public OutputStream getOutputStream() throws FileSystemException
{
return getOutputStream(false);
}
/**
* Prepares this file for writing. Makes sure it is either a file,
* or its parent folder exists. Returns an output stream to use to
* write the content of the file to.<br>
*
* @param bAppend true when append to the file.<br>
* Note: If the underlaying filesystem do not support this, it wont work.
* @return An OutputStream where the new contents of the file can be written.
* @throws FileSystemException if an error occurs.
*/
public OutputStream getOutputStream(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 && !getFileSystem().hasCapability(Capability.APPEND_CONTENT))
{
throw new FileSystemException("vfs.provider/write-append-not-supported.error", name);
}
if (getType() == FileType.IMAGINARY)
{
// Does not exist - make sure parent does
FileObject parent = getParent();
if (parent != null)
{
parent.createFolder();
}
}
// Get the raw output stream
try
{
return doGetOutputStream(bAppend);
}
catch (RuntimeException re)
{
throw re;
}
catch (Exception exc)
{
throw new FileSystemException("vfs.provider/write.error", new Object[]{name}, exc);
}
}
/**
* Detaches this file, invaliating all cached info. This will force
* a call to {@link #doAttach} next time this file is used.
*/
private void detach() throws Exception
{
synchronized (fs)
{
if (attached)
{
try
{
doDetach();
}
finally
{
attached = false;
setFileType(null);
parent = null;
// fs.fileDetached(this);
removeChildrenCache();
// children = null;
}
}
}
}
private void removeChildrenCache()
{
/*
if (children != null)
{
for (int iterChildren = 0; iterChildren < children.length; iterChildren++)
{
fs.removeFileFromCache(children[iterChildren].getName());
}
children = null;
}
*/
children = null;
}
/**
* Attaches to the file.
*/
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 (Exception exc)
{
throw new FileSystemException("vfs.provider/get-type.error", new Object[]{name}, exc);
}
// fs.fileAttached(this);
}
}
/**
* Called when the ouput stream for this file is closed.
*/
protected void endOutput() throws Exception
{
if (getType() == FileType.IMAGINARY)
{
// File was created
handleCreate(FileType.FILE);
}
else
{
// File has changed
onChange();
}
}
/**
* Called when this file is created. Updates cached info and notifies
* the parent and file system.
*/
protected void handleCreate(final FileType newType) throws Exception
{
synchronized (fs)
{
if (attached)
{
// Fix up state
injectType(newType);
removeChildrenCache();
// children = EMPTY_FILE_ARRAY;
// Notify subclass
onChange();
}
// Notify parent that its child list may no longer be valid
notifyParent(this.getName(), newType);
// Notify the file system
fs.fireFileCreated(this);
}
}
/**
* Called when this file is deleted. Updates cached info and notifies
* subclasses, parent and file system.
*/
protected void handleDelete() throws Exception
{
synchronized (fs)
{
if (attached)
{
// Fix up state
injectType(FileType.IMAGINARY);
removeChildrenCache();
// children = null;
// 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);
}
}
/**
* Called when this file is changed.<br />
* This will only happen if you monitor the file using {@link org.apache.commons.vfs.FileMonitor}.
*/
protected void handleChanged() throws Exception
{
// Notify the file system
fs.fireFileChanged(this);
}
/**
* Notifies the file that its children have changed.
*
* @deprecated use {@link #childrenChanged(FileName,FileType)}
*/
protected void childrenChanged() throws Exception
{
childrenChanged(null, null);
}
/**
* Notifies the file that its children have changed.
*/
protected void childrenChanged(FileName childName, FileType newType) throws Exception
{
// TODO - this may be called when not attached
if (children != null)
{
if (childName != null && newType != null)
{
// TODO - figure out if children[] can be replaced by list
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);
}
/**
* Notify the parent of a change to its children, when a child is created
* or deleted.
*/
private void notifyParent(FileName childName, FileType newType) throws Exception
{
if (parent == null)
{
FileName parentName = name.getParent();
if (parentName != null)
{
// Locate the parent, if it is cached
parent = fs.getFileFromCache(parentName);
}
}
if (parent != null)
{
FileObjectUtils.getAbstractFileObject(parent).childrenChanged(childName, newType);
}
}
/**
* Traverses the descendents 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.
*/
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", name, e);
}
}
/**
* 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 (int i = 0; i < children.length; i++)
{
final FileObject child = children[i];
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 descendents
selected.add(file);
}
else
{
// Add this file before its descendents
selected.add(index, file);
}
}
}
/**
* Check if the content stream is open.
*
* @return true if this is the case
*/
public boolean isContentOpen()
{
if (content == null)
{
return false;
}
return content.isOpen();
}
/**
* Check if the internal state is "attached".
*
* @return true if this is the case
*/
public boolean isAttached()
{
return attached;
}
/**
* create the filecontentinfo implementation
*/
protected FileContentInfoFactory getFileContentInfoFactory()
{
return getFileSystem().getFileSystemManager().getFileContentInfoFactory();
}
protected void injectType(FileType fileType)
{
setFileType(fileType);
}
private void setFileType(FileType type)
{
if (type != null && type != FileType.IMAGINARY)
{
try
{
name.setType(type);
}
catch (FileSystemException e)
{
throw new RuntimeException(e.getMessage());
}
}
this.type = type;
}
/**
* 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 childs and they shouldnt get
* garbage collected until the container is garbage collected
*
* @param strongRef The Object to add.
*/
public void holdObject(Object strongRef)
{
if (objects == null)
{
objects = new ArrayList(INITIAL_LISTSZ);
}
objects.add(strongRef);
}
/**
* will be called after this file-object closed all its streams.
*/
protected void notifyAllStreamsClosed()
{
}
// --- OPERATIONS ---
/**
* @return FileOperations interface that provides access to the operations
* API.
* @throws FileSystemException if an error occurs.
*/
public FileOperations getFileOperations() throws FileSystemException
{
if (operations == null)
{
operations = new DefaultFileOperations(this);
}
return operations;
}
protected void finalize() throws Throwable
{
fs.fileObjectDestroyed(this);
super.finalize();
}
}