To read from a file, use the {@link #getInputStream} method. + * + *
To write to a file, use the {@link #getOutputStream} method. This + * method will create the file and the parent folder, if necessary. + * + *
To prevent concurrency problems, only a single InputStream
,
+ * or OutputStream
may be open at any time, for each file.
+ *
+ *
TODO - allow multiple input streams? + * + * @see FileObject#getContent + * + * @author Adam Murdoch + */ +public interface FileContent +{ + /** + * Returns the file which this is the content of. + */ + FileObject getFile(); + + /** + * Determines the size of the file, in bytes. + * + * @return + * The size of the file, in bytes. + * + * @throws FileSystemException + * If the file does not exist, or is being written to, or on error + * determining the size. + */ + long getSize() throws FileSystemException; + + /** + * Determines the last-modified timestamp of the file. + * + * @return + * The last-modified timestamp. + * + * @throws FileSystemException + * If the file does not exist, or is being written to, or on error + * determining the last-modified timestamp. + */ + long getLastModifiedTime() throws FileSystemException; + + /** + * Sets the last-modified timestamp of the file. Creates the file if + * it does not exist. + * + * @param modTime + * The time to set the last-modified timestamp to. + * + * @throws FileSystemException + * If the file is read-only, or is being read, or on error setting + * the last-modified timestamp. + */ + void setLastModifiedTime( long modTime ) throws FileSystemException; + + /** + * Gets the value of an attribute of the file's content. + * + *
TODO - change to Map getAttributes()
instead?
+ *
+ *
TODO - define the standard attribute names, and define which attrs + * are guaranteed to be present. + * + * @param attrName + * The name of the attribute. + * + * @return + * The value of the attribute. + * + * @throws FileSystemException + * If the file does not exist, or is being written, or if the + * attribute is unknown. + */ + Object getAttribute( String attrName ) throws FileSystemException; + + /** + * Sets the value of an attribute of the file's content. Creates the + * file if it does not exist. + * + * @param attrName + * The name of the attribute. + * + * @param value + * The value of the attribute. + * + * @throws FileSystemException + * If the file is read-only, or is being read, or if the attribute + * is not supported, or on error setting the attribute. + */ + void setAttribute( String attrName, Object value ) throws FileSystemException; + + /** + * Returns an input stream for reading the file's content. + * + *
There may only be a single input or output stream open for the
+ * file at any time.
+ *
+ * @return
+ * An input stream to read the file's content from. The input
+ * stream is buffered, so there is no need to wrap it in a
+ * BufferedInputStream
.
+ *
+ * @throws FileSystemException
+ * If the file does not exist, or is being read, or is being written,
+ * or on error opening the stream.
+ */
+ InputStream getInputStream() throws FileSystemException;
+
+ /**
+ * Returns an output stream for writing the file's content.
+ *
+ * If the file does not exist, this method creates it, and the parent
+ * folder, if necessary. If the file does exist, it is replaced with
+ * whatever is written to the output stream.
+ *
+ *
There may only be a single input or output stream open for the
+ * file at any time.
+ *
+ * @return
+ * An output stream to write the file's content to. The stream is
+ * buffered, so there is no need to wrap it in a
+ * BufferedOutputStream
.
+ *
+ * @throws FileSystemException
+ * If the file is read-only, or is being read, or is being written,
+ * or on error opening the stream.
+ */
+ OutputStream getOutputStream() throws FileSystemException;
+
+ /**
+ * Closes all resources used by the content, including any open stream.
+ * Commits pending changes to the file.
+ *
+ *
This method is a hint to the implementation that it can release
+ * resources. This object can continue to be used after calling this
+ * method.
+ */
+ void close() throws FileSystemException;
+}
diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/FileName.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileName.java
new file mode 100644
index 000000000..2c1f58686
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileName.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE.txt file.
+ */
+package org.apache.aut.vfs;
+
+/**
+ * The interface is used to perform operations on a file name. File names
+ * are immutable, and work correctly as keys in hash tables.
+ *
+ * @see FileObject
+ *
+ * @author Adam Murdoch
+ */
+public interface FileName
+{
+ /**
+ * Returns the base name of the file. The base name of a file is the
+ * last element of its name. For example the base name of
+ * /somefolder/somefile
is somefile
.
+ *
+ *
The root file of a file system has an empty base name.
+ */
+ String getBaseName();
+
+ /**
+ * Returns the absolute path of the file, within its file system. This
+ * path is normalised, so that .
and ..
elements
+ * have been removed. Also, the path only contains /
as its
+ * separator character. The path always starts with /
+ *
+ *
The root of a file system has /
as its path.
+ */
+ String getPath();
+
+ /**
+ * Returns the absolute URI of the file.
+ */
+ String getURI();
+
+ /**
+ * Returns the name of the parent of the file. The root of a file system
+ * has no parent.
+ *
+ * @return
+ * A {@link FileName} object representing the parent name. Returns
+ * null for the root of a file system.
+ */
+ FileName getParent();
+
+ /**
+ * Resolves a name, relative to the file. Equivalent to calling
+ * resolveName( path, NameScope.FILE_SYSTEM )
.
+ *
+ * @param path
+ * The path to resolve.
+ *
+ * @return
+ * A {@link FileName} object representing the resolved name.
+ *
+ * @throws FileSystemException
+ * If the name is invalid.
+ */
+ FileName resolveName( String path ) throws FileSystemException;
+
+ /**
+ * Resolves a name, relative to the file. Refer to {@link NameScope#CHILD}
+ * and {@link NameScope#FILE_SYSTEM} for a description of how names are
+ * resolved.
+ *
+ * @param name
+ * The path to resolve.
+ *
+ * @param scope
+ * The scope to use when resolving the name.
+ *
+ * @return
+ * A {@link FileName} object representing the resolved name.
+ *
+ * @throws FileSystemException
+ * If the name is invalid.
+ */
+ FileName resolveName( String name, NameScope scope ) throws FileSystemException;
+}
diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/FileObject.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileObject.java
new file mode 100644
index 000000000..46545b84f
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileObject.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE.txt file.
+ */
+package org.apache.aut.vfs;
+
+/**
+ * This interface represents a file, and is used to access the content and
+ * structure of the file.
+ *
+ *
Files are arranged in a hierarchy. Each hierachy forms a + * file system. A file system represents things like a local OS + * file system, a windows share, an HTTP server, or the contents of a Zip file. + * + *
There are two types of files: Folders, which contain other files, + * and normal files, which contain data, or content. A folder may + * not have any content, and a normal file cannot contain other files. + * + *
TODO - write this. + * + *
Reading and writing a file, and all other operations on the file's + * content, is done using the {@link FileContent} object returned + * by {@link #getContent}. + * + *
A file is created using either {@link #create}, or by writing to the + * file using one of the {@link FileContent} methods. + * + *
A file is deleted using {@link #delete}. Deletion is recursive, so + * that when a folder is deleted, so are all its child files. + * + *
Other files in the same file system as this file can be found using: + *
To find files in another file system, use a {@link FileSystemManager}.
+ *
+ * @see FileSystemManager
+ * @see FileContent
+ * @see FileName
+ *
+ * @author Adam Murdoch
+ */
+public interface FileObject
+{
+ /**
+ * Returns the name of this file.
+ */
+ FileName getName();
+
+ /**
+ * Determines if this file exists.
+ *
+ * @return
+ * true
if this file exists, false
if not.
+ *
+ * @throws FileSystemException
+ * On error determining if this file exists.
+ */
+ boolean exists() throws FileSystemException;
+
+ /**
+ * Returns this file's type.
+ *
+ * @return
+ * Either {@link FileType#FILE} or {@link FileType#FOLDER}. Never
+ * returns null.
+ *
+ * @throws FileSystemException
+ * If the file does not exist, or on error determining the file's type.
+ */
+ FileType getType() throws FileSystemException;
+
+ /**
+ * Returns the folder that contains this file.
+ *
+ * @return
+ * The folder that contains this file. Returns null if this file is
+ * the root of a file system.
+ *
+ * @throws FileSystemException
+ * On error finding the file's parent.
+ */
+ FileObject getParent() throws FileSystemException;
+
+ /**
+ * Returns the root of the file system containing this file.
+ *
+ * @return
+ * The root of the file system.
+ *
+ * @throws FileSystemException
+ * On error finding the root of the file system.
+ */
+ FileObject getRoot() throws FileSystemException;
+
+ /**
+ * Lists the children of this file.
+ *
+ * @return
+ * An array containing the children of this file. The array is
+ * unordered. If the file does not have any children, a zero-length
+ * array is returned. This method never returns null.
+ *
+ * @throws FileSystemException
+ * If this file does not exist, or is not a folder, or on error
+ * listing this file's children.
+ */
+ FileObject[] getChildren() throws FileSystemException;
+
+ /**
+ * Finds a file, relative to this file. Refer to {@link NameScope#CHILD}
+ * and {@link NameScope#FILE_SYSTEM} for a description of how names
+ * are resolved in the different scopes.
+ *
+ * @param name
+ * The name to resolve.
+ *
+ * @return
+ * The file.
+ *
+ * @throws FileSystemException
+ * On error parsing the path, or on error finding the file.
+ */
+ FileObject resolveFile( String name, NameScope scope ) throws FileSystemException;
+
+ /**
+ * Finds a file, relative to this file. Equivalent to calling
+ * resolveFile( path, NameScope.FILE_SYSTEM )
.
+ *
+ * @param path
+ * The path of the file to locate. Can either be a relative
+ * path or an absolute path.
+ *
+ * @return
+ * The file.
+ *
+ * @throws FileSystemException
+ * On error parsing the path, or on error finding the file.
+ */
+ FileObject resolveFile( String path ) throws FileSystemException;
+
+ /**
+ * Deletes this file, and all children. Does nothing if the file
+ * does not exist.
+ *
+ *
This method is not transactional. If it fails and throws an + * exception, some of this file's descendents may have been deleted. + * + * @throws FileSystemException + * If this file or one of its descendents is read-only, or on error + * deleting this file or one of its descendents. + */ + void delete() throws FileSystemException; + + /** + * Creates this file, if it does not exist. Also creates any ancestor + * folders which do not exist. This method does nothing if the file + * already exists with the requested type. + * + * @param type + * The type of file to create. + * + * @throws FileSystemException + * If the file already exists with the wrong type, or the parent + * folder is read-only, or on error creating this file or one of + * its ancestors. + */ + void create( FileType type ) throws FileSystemException; + + /** + * Returns this file's content. The {@link FileContent} returned by this + * method can be used to read and write the content of the file. + * + *
This method can be called if the file does not exist, and + * the returned {@link FileContent} can be used to create the file + * by writing its content. + * + * @return + * This file's content. + * + * @throws FileSystemException + * If this file is a folder. + */ + FileContent getContent() throws FileSystemException; + + /** + * Closes this file, and its content. This method is a hint to the + * implementation that it can release any resources asociated with + * the file. + * + *
The file object can continue to be used after this method is called. + * + * @see FileContent#close + * + * @throws FileSystemEception + * On error closing the file. + */ + void close() throws FileSystemException; +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/FileSystemException.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileSystemException.java new file mode 100644 index 000000000..8e381c3cb --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileSystemException.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs; + +import org.apache.avalon.framework.CascadingException; + +/** + * Thrown for file system errors. + * + * @author Adam Murdoch + */ +public class FileSystemException extends CascadingException +{ + private Throwable m_cause; + + /** + * Constructs exception with the specified detail message. + * + * @param msg the detail message. + */ + public FileSystemException( String msg ) + { + super( msg ); + } + + /** + * Constructs exception with the specified detail message. + * + * @param msg the detail message. + * @param cause the cause. + */ + public FileSystemException( String msg, Throwable cause ) + { + super( msg, cause ); + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/FileSystemManager.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileSystemManager.java new file mode 100644 index 000000000..3d3e7467d --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileSystemManager.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs; + +import org.apache.avalon.framework.component.Component; + +/** + * A FileSystemManager is manages a set of file systems. This interface is + * used to locate a {@link FileObject} by name from one of those file systems. + * + *
To locate a {@link FileObject}, use one of the resolveFile()
+ * methods.
A file system manager can recognise several types of file names: + * + *
Absolute URI. These must start with a scheme, such as
+ * file:
or ftp:
, followed by a scheme dependent
+ * file name. Some examples:
+ * file:/c:/somefile + * ftp://somewhere.org/somefile + *+ * + *
Absolute local file name. For example,
+ * /home/someuser/a-file
or c:\dir\somefile.html
.
+ * Elements in the name can be separated using any of the following
+ * characters: /
, \
, or the native file separator
+ * character. For example, the following file names are the same:
+ * c:\somedir\somefile.xml + * c:/somedir/somefile.xml + *+ * + *
Relative path. For example: ../somefile
or
+ * somedir/file.txt
. The file system manager resolves relative
+ * paths against its base file. Elements in the relative path can be
+ * separated using /
, \
, or file system specific
+ * separator characters. Relative paths may also contain ..
and
+ * .
elements. See {@link FileObject#resolveFile} for more details.
resolveFile(uri, getBaseName())
.
+ *
+ * @param name
+ * The name of the file.
+ *
+ * @throws FileSystemException
+ * On error parsing the file name.
+ */
+ FileObject resolveFile( String name ) throws FileSystemException;
+
+ /**
+ * Locates a file by name. The name is resolved as described
+ * above. That is, the name can be either
+ * an absolute URI, an absolute file name, or a relative path to
+ * be resolved against baseFile
.
+ *
+ * Note that the file does not have to exist when this method is called. + * + * @param name + * The name of the file. + * + * @param baseFile + * The base file to use to resolve paths. + * + * @throws FileSystemException + * On error parsing the file name. + */ + FileObject resolveFile( FileObject baseFile, String name ) throws FileSystemException; +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/FileType.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileType.java new file mode 100644 index 000000000..310885967 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileType.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + * + * @author Adam Murdoch + */ +package org.apache.aut.vfs; + +import org.apache.avalon.excalibur.i18n.Resources; +import org.apache.avalon.excalibur.i18n.ResourceManager; + +/** + * An enumeration that represents a file's type. + */ +public final class FileType +{ + private final static Resources REZ = + ResourceManager.getPackageResources( FileType.class ); + + private String m_name; + + private FileType( String name ) + { + m_name = name; + } + + /** Returns the name of the type. */ + public String toString() + { + return m_name; + } + + /** Returns the name of the type. */ + public String getName() + { + return m_name; + } + + /** + * A folder, which can contain other files, but does not have any data + * content. + */ + public static final FileType FOLDER = new FileType( REZ.getString( "folder.name" ) ); + + /** + * A regular file, which has data content, but cannot contain other files. + */ + public static final FileType FILE = new FileType( REZ.getString( "file.name" ) ); +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/NameScope.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/NameScope.java new file mode 100644 index 000000000..fbf6870d9 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/NameScope.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs; + +/** + * An enumerated type for file name scope, used when resolving a name relative + * to a file. + * + * @author Adam Murdoch + */ +public final class NameScope +{ + private String m_name; + + private NameScope( String name ) + { + m_name = name; + } + + /** Returns the name of the scope. */ + public String toString() + { + return m_name; + } + + /** Returns the name of the scope. */ + public String getName() + { + return m_name; + } + + /** + * Resolve against the children of the base file. + * + *
The supplied name must be a valid element name. That is, it may
+ * not be empty, or .
, or ..
, or contain any
+ * separator characters.
+ */
+ public static final NameScope CHILD = new NameScope( "child" );
+
+ /**
+ * Resolve against files in the same file system as the base file.
+ *
+ *
If the supplied name is an absolute path, then it is resolved + * relative to the root of the file system that the base file belongs to. + * If a relative name is supplied, then it is resolved relative to the base + * file. + * + *
The path may use any mix of /
, \
, or file
+ * system specific separators to separate elements in the path. It may
+ * also contain .
and ..
elements.
+ *
+ *
A path is considered absolute if it starts with a separator character, + * and relative if it does not. + */ + public static final NameScope FILE_SYSTEM = new NameScope( "filesystem" ); +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/Resources.properties b/proposal/myrmidon/src/java/org/apache/aut/vfs/Resources.properties new file mode 100644 index 000000000..7f418a6dd --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/Resources.properties @@ -0,0 +1,2 @@ +folder.name=folder +file.name=file diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/package.html b/proposal/myrmidon/src/java/org/apache/aut/vfs/package.html new file mode 100644 index 000000000..a6b86b26e --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/package.html @@ -0,0 +1,10 @@ +
+This package contains the interfaces used to access the VFS.
+ +A {@link vfs.filesystem.FileSystemManager} is the starting point for +all file system access. It is used to locate a {@link vfs.filesystem.FileObject} +by name. Files are accessed using the {@link vfs.filesystem.FileObject} +interface. This interface allows a file's structure and content to be +accessed.
+ + \ No newline at end of file diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileObject.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileObject.java new file mode 100644 index 000000000..94d0464e9 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileObject.java @@ -0,0 +1,644 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.apache.aut.vfs.FileContent; +import org.apache.aut.vfs.FileName; +import org.apache.aut.vfs.FileObject; +import org.apache.aut.vfs.FileSystemException; +import org.apache.aut.vfs.FileType; +import org.apache.aut.vfs.NameScope; +import org.apache.avalon.excalibur.i18n.Resources; +import org.apache.avalon.excalibur.i18n.ResourceManager; + +/** + * A partial file object implementation. + * + * @author Adam Murdoch + */ +public abstract class AbstractFileObject implements FileObject +{ + private static final Resources REZ = + ResourceManager.getPackageResources( AbstractFileObject.class ); + + private FileName m_name; + private AbstractFileSystem m_fs; + private DefaultFileContent m_content; + + private static final FileObject[] EMPTY_FILE_ARRAY = {}; + + // Cached info + private boolean m_attached; + private AbstractFileObject m_parent; + private FileType m_type; + private FileObject[] m_children; + + protected AbstractFileObject( FileName name, AbstractFileSystem fs ) + { + m_name = name; + m_fs = fs; + } + + /** + * Returns true if this file is read-only. + */ + protected boolean isReadOnly() + { + return false; + } + + /** + * 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. + */ + protected void doAttach() throws Exception + { + } + + /** + * Detaches this file object from its file resource. + * + *Called when this file is closed, or its type changes. Note that + * the file object may be reused later, so should be able to be reattached. + */ + protected void doDetach() + { + } + + /** + * Determines the type of the file, returns null if the file does not + * exist. The return value of this method is cached, so the + * implementation can be expensive. + */ + protected abstract FileType doGetType() throws Exception; + + /** + * Lists the children of the 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. + */ + protected abstract String[] doListChildren() throws Exception; + + /** + * Deletes the file. Is only called when: + *
There is guaranteed never to be more than one stream for this file + * (input or output) open at any given time. + * + *
The returned stream does not have to be buffered. + */ + protected abstract InputStream doGetInputStream() throws Exception; + + /** + * Creates an output stream to write the file content to. Is only + * called if: + *
There is guaranteed never to be more than one stream for this file + * (input or output) open at any given time. + * + *
The returned stream does not have to be buffered. + */ + protected OutputStream doGetOutputStream() throws Exception + { + final String message = REZ.getString( "write-not-supported.error" ); + throw new FileSystemException( message ); + } + + /** + * Notification of the output stream being closed. + * TODO - get rid of this. + */ + protected void doEndOutput() throws Exception + { + } + + /** + * Notification of the input stream being closed. + * TODO - get rid of this. + */ + protected void doEndInput() throws Exception + { + } + + /** + * Returns the URI of the file. + */ + public String toString() + { + return m_name.getURI(); + } + + /** + * Returns the name of the file. + */ + public FileName getName() + { + return m_name; + } + + /** + * Determines if the file exists. + */ + public boolean exists() throws FileSystemException + { + attach(); + return ( m_type != null ); + } + + /** + * Returns the file's type. + */ + public FileType getType() throws FileSystemException + { + attach(); + if( m_type == null ) + { + final String message = REZ.getString( "get-type-no-exist.error", m_name ); + throw new FileSystemException( message ); + } + return m_type; + } + + /** + * Returns the parent of the file. + */ + public FileObject getParent() throws FileSystemException + { + if( this == m_fs.getRoot() ) + { + // Root file has no parent + return null; + } + + // Locate the parent of this file + if( m_parent == null ) + { + m_parent = (AbstractFileObject)m_fs.findFile( m_name.getParent() ); + } + return m_parent; + } + + /** + * Returns the root of the file system containing the file. + */ + public FileObject getRoot() throws FileSystemException + { + return m_fs.getRoot(); + } + + /** + * Returns the children of the file. + */ + public FileObject[] getChildren() throws FileSystemException + { + attach(); + if( m_type == null ) + { + final String message = REZ.getString( "list-children-no-exist.error", m_name ); + throw new FileSystemException( message ); + } + if( m_type != FileType.FOLDER ) + { + final String message = REZ.getString( "list-children-not-folder.error", m_name ); + throw new FileSystemException( message ); + } + + // Use cached info, if present + if( m_children != null ) + { + return m_children; + } + + // List the children + String[] files; + try + { + files = doListChildren(); + } + catch( Exception exc ) + { + final String message = REZ.getString( "list-children.error", m_name ); + throw new FileSystemException( message, exc ); + } + + if( files == null || files.length == 0 ) + { + // No children + m_children = EMPTY_FILE_ARRAY; + } + else + { + // Create file objects for the children + m_children = new FileObject[ files.length ]; + for( int i = 0; i < files.length; i++ ) + { + String file = files[ i ]; + m_children[ i ] = m_fs.findFile( m_name.resolveName( file, NameScope.CHILD ) ); + } + } + + return m_children; + } + + /** + * Returns a child by name. + */ + public FileObject resolveFile( String name, NameScope scope ) throws FileSystemException + { + // TODO - cache children (only if they exist) + return m_fs.findFile( m_name.resolveName( 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. + */ + public FileObject resolveFile( String path ) throws FileSystemException + { + FileName name = m_name.resolveName( path ); + return m_fs.findFile( name ); + } + + /** + * Deletes this file, once all its children have been deleted + */ + private void deleteSelf() throws FileSystemException + { + if( isReadOnly() ) + { + final String message = REZ.getString( "delete-read-only.error", m_name ); + throw new FileSystemException( message ); + } + + // Delete the file + try + { + doDelete(); + } + catch( Exception exc ) + { + final String message = REZ.getString( "delete.error", m_name ); + throw new FileSystemException( message, exc ); + } + + // Update cached info + updateType(null); + } + + /** + * Deletes this file, and all children. + */ + public void delete() throws FileSystemException + { + attach(); + if( m_type == null ) + { + // File does not exist + return; + } + + // Recursively delete this file and all its children + List queue = new ArrayList(); + Set expanded = new HashSet(); + queue.add( this ); + + // Recursively delete each file + // TODO - recover from errors + while( queue.size() > 0 ) + { + AbstractFileObject file = (AbstractFileObject)queue.get( 0 ); + file.attach(); + + if( file.m_type == null ) + { + // Shouldn't happen + queue.remove( 0 ); + } + else if( file.m_type == FileType.FILE ) + { + // Delete the file + file.deleteSelf(); + queue.remove( 0 ); + } + else if( expanded.contains( file ) ) + { + // Have already deleted all the children of this folder - + // delete it + file.deleteSelf(); + queue.remove( 0 ); + } + else + { + // Delete the folder's children + FileObject[] children = file.getChildren(); + for( int i = 0; i < children.length; i++ ) + { + FileObject child = children[ i ]; + queue.add( 0, child ); + } + expanded.add( file ); + } + } + + // Update parent's child list + notifyParent(); + } + + /** + * Creates this file, if it does not exist. Also creates any ancestor + * files which do not exist. + */ + public void create( FileType type ) throws FileSystemException + { + attach(); + if( m_type == type ) + { + // Already exists as correct type + return; + } + if( m_type != null ) + { + final String message = REZ.getString( "create-mismatched-type.error", type, m_name, m_type ); + throw new FileSystemException( message ); + } + if( isReadOnly() ) + { + final String message = REZ.getString( "create-read-only.error", type, m_name ); + throw new FileSystemException( message ); + } + + // Traverse up the heirarchy and make sure everything is a folder + FileObject parent = getParent(); + if( parent != null ) + { + parent.create( FileType.FOLDER ); + } + + // Create the folder + try + { + if( type == FileType.FOLDER ) + { + doCreateFolder(); + m_children = EMPTY_FILE_ARRAY; + } + else if( type == FileType.FILE ) + { + OutputStream outStr = doGetOutputStream(); + outStr.close(); + endOutput(); + } + } + catch( Exception exc ) + { + final String message = REZ.getString( "create.error", type, m_name ); + throw new FileSystemException( message, exc ); + } + + // Update cached info + updateType(type); + } + + /** + * Returns the file's content. + */ + public FileContent getContent() throws FileSystemException + { + attach(); + if( m_type == FileType.FOLDER ) + { + final String message = REZ.getString( "get-folder-content.error", m_name ); + throw new FileSystemException( message ); + } + if( m_content == null ) + { + m_content = new DefaultFileContent( this ); + } + return m_content; + } + + /** + * Closes this file, and its content. + */ + public void close() throws FileSystemException + { + FileSystemException exc = null; + + // Close the content + if( m_content != null ) + { + try + { + m_content.close(); + } + catch( FileSystemException e ) + { + exc = e; + } + } + + // Detach from the file + if( m_attached ) + { + doDetach(); + m_attached = false; + m_type = null; + m_children = null; + } + + if( exc != null ) + { + throw 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. + */ + public OutputStream getOutputStream() throws FileSystemException + { + attach(); + if( isReadOnly() ) + { + final String message = REZ.getString( "write-read-only.error", m_name ); + throw new FileSystemException( message ); + } + if( m_type == FileType.FOLDER ) + { + final String message = REZ.getString( "write-folder.error", m_name ); + } + + if( m_type == null ) + { + // Does not exist - make sure parent does + FileObject parent = getParent(); + if( parent != null ) + { + parent.create( FileType.FOLDER ); + } + } + + // Get the raw output stream + try + { + return doGetOutputStream(); + } + catch( FileSystemException exc ) + { + throw exc; + } + catch( Exception exc ) + { + final String message = REZ.getString( "write.error", m_name ); + throw new FileSystemException( message, exc ); + } + } + + /** + * Attaches to the file. + */ + private void attach() throws FileSystemException + { + if( m_attached ) + { + return; + } + + try + { + // Attach and determine the file type + doAttach(); + m_attached = true; + m_type = doGetType(); + } + catch( FileSystemException exc ) + { + throw exc; + } + catch( Exception exc ) + { + final String message = REZ.getString( "get-type.error", m_name ); + throw new FileSystemException( message, exc ); + } + + } + + /** + * Called when the ouput stream for this file is closed. + */ + public void endOutput() throws Exception + { + updateType( FileType.FILE ); + doEndOutput(); + } + + /** + * Update cached info when this file's type changes. + */ + private void updateType(FileType type) + { + // Notify parent that its child list may no longer be valid + notifyParent(); + + // Detach + doDetach(); + m_attached = false; + m_type = null; + m_children = null; + } + + /** + * Notify the parent of a change to its children, when a child is created + * or deleted. + */ + private void notifyParent() + { + if( m_parent == null ) + { + // Locate the parent, if it is cached + m_parent = (AbstractFileObject)m_fs.getFile( m_name.getParent() ); + } + + if( m_parent != null ) + { + m_parent.invalidateChildren(); + } + } + + /** + * Notifies a file that children have been created or deleted. + */ + private void invalidateChildren() + { + m_children = null; + onChildrenChanged(); + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileSystem.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileSystem.java new file mode 100644 index 000000000..a72cad269 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileSystem.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider; + +import java.util.HashMap; +import java.util.Map; +import org.apache.aut.vfs.FileName; +import org.apache.aut.vfs.FileObject; +import org.apache.aut.vfs.FileSystemException; + +/** + * A partial file system implementation. + * + * @author Adam Murdoch + */ +public abstract class AbstractFileSystem implements FileSystem +{ + private FileObject m_root; + private FileName m_rootName; + + /** Map from absolute file path to FileObject. */ + private Map m_files = new HashMap(); + + protected AbstractFileSystem( FileName rootName ) + { + m_rootName = rootName; + } + + /** + * Creates a file object. This method is called only if the requested + * file is not cached. + */ + protected abstract FileObject createFile( FileName name ) throws FileSystemException; + + /** + * Adds a file object to the cache. + */ + protected void putFile( FileObject file ) + { + m_files.put( file.getName().getPath(), file ); + } + + /** + * Returns a cached file. + */ + protected FileObject getFile( FileName name ) + { + return (FileObject)m_files.get( name.getPath() ); + } + + /** + * Returns the root file of this file system. + */ + public FileObject getRoot() throws FileSystemException + { + if( m_root == null ) + { + m_root = findFile( m_rootName ); + } + return m_root; + } + + /** + * Finds a file in this file system. + */ + public FileObject findFile( String nameStr ) throws FileSystemException + { + // Resolve the name, and create the file + FileName name = m_rootName.resolveName( nameStr ); + return findFile( name ); + } + + /** + * Finds a file in this file system. + */ + public FileObject findFile( FileName name ) throws FileSystemException + { + // TODO - assert that name is from this file system + FileObject file = (FileObject)m_files.get( name.getPath() ); + if( file == null ) + { + file = createFile( name ); + m_files.put( name.getPath(), file ); + } + return file; + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileSystemProvider.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileSystemProvider.java new file mode 100644 index 000000000..eb21f2222 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileSystemProvider.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider; + +import org.apache.aut.vfs.FileObject; +import org.apache.aut.vfs.FileSystemException; +import org.apache.avalon.excalibur.i18n.Resources; +import org.apache.avalon.excalibur.i18n.ResourceManager; + +/** + * A partial file system provider implementation. + * + * @author Adam Murdoch + */ +public abstract class AbstractFileSystemProvider implements FileSystemProvider +{ + private static final Resources REZ + = ResourceManager.getPackageResources( AbstractFileSystemProvider.class ); + + protected FileSystemProviderContext m_context; + + /** + * Sets the context for this file system provider. This method is called + * before any of the other provider methods. + */ + public void setContext( FileSystemProviderContext context ) + { + m_context = context; + } + + /** + * Locates a file object, by absolute URI. + * + * @param uri + * The absolute URI of the file to find. + */ + public FileObject findFile( String uri ) throws FileSystemException + { + // Parse the URI + ParsedUri parsedURI = null; + try + { + parsedURI = parseURI( uri ); + } + catch( FileSystemException exc ) + { + final String message = REZ.getString( "invalid-absolute-uri.error", uri ); + throw new FileSystemException( message, exc ); + } + + // Check in the cache for the file system + FileSystem fs = m_context.getFileSystem( parsedURI.getRootURI() ); + if( fs == null ) + { + // Need to create the file system + fs = createFileSystem( parsedURI ); + m_context.putFileSystem( parsedURI.getRootURI(), fs ); + } + + // Locate the file + return fs.findFile( parsedURI.getPath() ); + } + + /** + * Parses a URI into its components. The returned value is used to + * locate the file system in the cache (using the root prefix), and is + * passed to {@link #createFileSystem} to create the file system. + * + *
The provider can annotate this object with any additional + * information it requires to create a file system from the URI. + */ + protected abstract ParsedUri parseURI( String uri ) throws FileSystemException; + + /** + * Creates the filesystem. + */ + protected abstract org.apache.aut.vfs.provider.FileSystem createFileSystem( ParsedUri uri ) throws FileSystemException; +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileContent.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileContent.java new file mode 100644 index 000000000..416444a6d --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileContent.java @@ -0,0 +1,381 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.sql.Date; +import org.apache.aut.vfs.FileContent; +import org.apache.aut.vfs.FileObject; +import org.apache.aut.vfs.FileSystemException; +import org.apache.avalon.excalibur.i18n.Resources; +import org.apache.avalon.excalibur.i18n.ResourceManager; + +/** + * The content of a file. + * + * @author Adam Murdoch + */ +public class DefaultFileContent implements FileContent +{ + private static final Resources REZ + = ResourceManager.getPackageResources( DefaultFileContent.class ); + + private AbstractFileObject m_file; + private int _state = STATE_NONE; + private FileContentInputStream m_instr; + private FileContentOutputStream m_outstr; + + private static final int STATE_NONE = 0; + private static final int STATE_READING = 1; + private static final int STATE_WRITING = 2; + + public DefaultFileContent( AbstractFileObject file ) + { + m_file = file; + } + + /** + * Returns the file which this is the content of. + */ + public FileObject getFile() + { + return m_file; + } + + /** + * Returns the size of the content (in bytes). + */ + public long getSize() throws FileSystemException + { + // Do some checking + if( !m_file.exists() ) + { + final String message = REZ.getString( "get-size-no-exist.error", m_file ); + throw new FileSystemException( message ); + } + if( _state == STATE_WRITING ) + { + final String message = REZ.getString( "get-size-write.error", m_file ); + throw new FileSystemException( message ); + } + + try + { + // Get the size + return m_file.doGetContentSize(); + } + catch( Exception exc ) + { + final String message = REZ.getString( "get-size.error", m_file ); + throw new FileSystemException( message, exc ); + } + } + + /** + * Returns the last-modified timestamp. + */ + public long getLastModifiedTime() throws FileSystemException + { + // TODO - implement this + throw new FileSystemException( "Not implemented." ); + } + + /** + * Sets the last-modified timestamp. + */ + public void setLastModifiedTime( long modTime ) throws FileSystemException + { + // TODO - implement this + throw new FileSystemException( "Not implemented." ); + } + + /** + * Gets the value of an attribute. + */ + public Object getAttribute( String attrName ) throws FileSystemException + { + // TODO - implement this + throw new FileSystemException( "Not implemented." ); + } + + /** + * Sets the value of an attribute. + */ + public void setAttribute( String attrName, Object value ) throws FileSystemException + { + // TODO - implement this + throw new FileSystemException( "Not implemented." ); + } + + /** + * Returns an input stream for reading the content. + */ + public InputStream getInputStream() throws FileSystemException + { + if( !m_file.exists() ) + { + final String message = REZ.getString( "read-no-exist.error", m_file ); + throw new FileSystemException( message ); + } + if( _state != STATE_NONE ) + { + final String message = REZ.getString( "read-in-use.error", m_file ); + throw new FileSystemException( message ); + } + + // Get the raw input stream + InputStream instr = null; + try + { + instr = m_file.doGetInputStream(); + } + catch( Exception exc ) + { + final String message = REZ.getString( "read.error", m_file ); + throw new FileSystemException( message, exc ); + } + + // TODO - reuse + m_instr = new FileContentInputStream( instr ); + _state = STATE_READING; + return m_instr; + } + + /** + * Returns an output stream for writing the content. + */ + public OutputStream getOutputStream() throws FileSystemException + { + if( _state != STATE_NONE ) + { + final String message = REZ.getString( "write-in-use.error", m_file ); + throw new FileSystemException( message ); + } + + // Get the raw output stream + OutputStream outstr = m_file.getOutputStream(); + + // Create wrapper + // TODO - reuse + m_outstr = new FileContentOutputStream( outstr ); + _state = STATE_WRITING; + return m_outstr; + } + + /** + * Closes all resources used by the content, including all streams, readers + * and writers. + */ + public void close() throws FileSystemException + { + + try + { + // Close the input stream + if( m_instr != null ) + { + try + { + m_instr.close(); + } + catch( IOException ioe ) + { + final String message = REZ.getString( "close-instr.error" ); + throw new FileSystemException( message, ioe ); + } + } + + // Close the output stream + if( m_outstr != null ) + { + try + { + m_outstr.close(); + } + catch( IOException ioe ) + { + final String message = REZ.getString( "close-outstr.error" ); + throw new FileSystemException( message, ioe ); + } + } + } + finally + { + _state = STATE_NONE; + } + } + + /** + * Handles the end of input stream. + */ + private void endInput() throws Exception + { + m_instr = null; + _state = STATE_NONE; + m_file.doEndInput(); + } + + /** + * Handles the end of output stream. + */ + private void endOutput() throws Exception + { + m_outstr = null; + _state = STATE_NONE; + m_file.endOutput(); + } + + /** + * An input stream for reading content. Provides buffering, and + * end-of-stream monitoring. + */ + private final class FileContentInputStream extends BufferedInputStream + { + boolean _finished; + + FileContentInputStream( InputStream instr ) + { + super( instr ); + } + + /** + * Reads a character. + */ + public int read() throws IOException + { + if( _finished ) + { + return -1; + } + + int ch = super.read(); + if( ch != -1 ) + { + return ch; + } + + // End-of-stream + close(); + return -1; + } + + /** + * Reads bytes from this input stream.error occurs. + */ + public int read( byte b[], int off, int len ) + throws IOException + { + if( _finished ) + { + return -1; + } + + int nread = super.read( b, off, len ); + if( nread != -1 ) + { + return nread; + } + + // End-of-stream + close(); + return -1; + } + + /** + * Closes this input stream. + */ + public void close() throws IOException + { + if( _finished ) + { + return; + } + + // Close the stream + IOException exc = null; + try + { + super.close(); + } + catch( IOException e ) + { + exc = e; + } + + // Notify the file object + try + { + endInput(); + } + catch( Exception e ) + { + exc = new IOException( e.getMessage() ); + } + + _finished = true; + + if( exc != null ) + { + throw exc; + } + } + } + + /** + * An output stream for writing content. + */ + private final class FileContentOutputStream extends BufferedOutputStream + { + FileContentOutputStream( OutputStream outstr ) + { + super( outstr ); + } + + /** + * Closes this output stream. + */ + public void close() throws IOException + { + IOException exc = null; + + // Close the output stream + try + { + super.close(); + } + catch( IOException e ) + { + exc = e; + } + + // Notify of end of output + try + { + endOutput(); + } + catch( Exception e ) + { + exc = new IOException( e.getMessage() ); + } + + if( exc != null ) + { + throw exc; + } + } + } + +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileName.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileName.java new file mode 100644 index 000000000..6f169f142 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileName.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider; + +import org.apache.aut.vfs.FileName; +import org.apache.aut.vfs.FileSystemException; +import org.apache.aut.vfs.NameScope; + +/** + * A default file name implementation. + * + * @author Adam Murdoch + */ +public class DefaultFileName implements FileName +{ + private UriParser m_parser; + private String m_rootPrefix; + private String m_absPath; + + // Cached stuff + private String m_uri; + private String m_baseName; + + public DefaultFileName( UriParser parser, String rootPrefix, String absPath ) + { + m_parser = parser; + m_rootPrefix = rootPrefix; + m_absPath = absPath; + } + + // TODO - make these usable as hash keys + + /** + * Returns the URI of the file. + */ + public String toString() + { + return getURI(); + } + + /** + * Returns the base name of the file. + */ + public String getBaseName() + { + if( m_baseName == null ) + { + m_baseName = m_parser.getBaseName( m_absPath ); + } + return m_baseName; + } + + /** + * Returns the absolute path of the file, relative to the root of the + * file system that the file belongs to. + */ + public String getPath() + { + return m_absPath; + } + + /** + * Returns the name of a child of the file. + */ + public FileName resolveName( String name, NameScope scope ) throws FileSystemException + { + if( scope == NameScope.CHILD ) + { + String childPath = m_parser.getChildPath( m_absPath, name ); + return new DefaultFileName( m_parser, m_rootPrefix, childPath ); + } + else if( scope == NameScope.FILE_SYSTEM ) + { + String absPath = m_parser.resolvePath( m_absPath, name ); + return new DefaultFileName( m_parser, m_rootPrefix, absPath ); + } + else + { + throw new IllegalArgumentException(); + } + } + + /** + * Returns the name of the parent of the file. + */ + public FileName getParent() + { + String parentPath = m_parser.getParentPath( m_absPath ); + if( parentPath == null ) + { + return null; + } + return new DefaultFileName( m_parser, m_rootPrefix, parentPath ); + } + + /** + * Resolves a name, relative to the file. If the supplied name is an + * absolute path, then it is resolved relative to the root of the + * file system that the file belongs to. If a relative name is supplied, + * then it is resolved relative to this file name. + */ + public FileName resolveName( String path ) throws FileSystemException + { + return resolveName( path, NameScope.FILE_SYSTEM ); + } + + /** + * Returns the absolute URI of the file. + */ + public String getURI() + { + if( m_uri == null ) + { + m_uri = m_parser.getUri( m_rootPrefix, m_absPath ); + } + return m_uri; + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileSystemManager.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileSystemManager.java new file mode 100644 index 000000000..9ba8852fd --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileSystemManager.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.io.File; +import org.apache.aut.vfs.FileObject; +import org.apache.aut.vfs.FileSystemException; +import org.apache.aut.vfs.FileSystemManager; +import org.apache.aut.vfs.provider.local.LocalFileSystemProvider; +import org.apache.avalon.excalibur.i18n.Resources; +import org.apache.avalon.excalibur.i18n.ResourceManager; + +/** + * A default file system manager implementation. + * + * @author Adam Murdoch + */ +public class DefaultFileSystemManager implements FileSystemManager +{ + private static final Resources REZ + = ResourceManager.getPackageResources( DefaultFileSystemManager.class ); + + /** The default provider. */ + private LocalFileSystemProvider m_localFileProvider; + + /** Mapping from URI scheme to FileSystemProvider. */ + private Map m_providers = new HashMap(); + + /** The provider context. */ + private ProviderContextImpl m_context = new ProviderContextImpl(); + + /** The base file to use for relative URI. */ + private FileObject m_baseFile; + + /** + * The cached file systems. This is a mapping from root URI to + * FileSystem object. + */ + private Map m_fileSystems = new HashMap(); + + public DefaultFileSystemManager() throws Exception + { + // Create the local provider + m_localFileProvider = new LocalFileSystemProvider(); + m_providers.put( "file", m_localFileProvider ); + + // TODO - make this list configurable + // Create the providers + + FileSystemProvider provider = createProvider( "org.apache.aut.vfs.provider.zip.ZipFileSystemProvider" ); + if( provider != null ) + { + m_providers.put( "zip", provider ); + m_providers.put( "jar", provider ); + } + + provider = createProvider( "org.apache.aut.vfs.provider.smb.SmbFileSystemProvider" ); + if( provider != null ) + { + m_providers.put( "smb", provider ); + } + + provider = createProvider( "org.apache.aut.vfs.provider.ftp.FtpFileSystemProvider" ); + if( provider != null ) + { + m_providers.put( "ftp", provider ); + } + + // Contextualise the providers + for( Iterator iterator = m_providers.values().iterator(); iterator.hasNext(); ) + { + provider = (FileSystemProvider)iterator.next(); + provider.setContext( m_context ); + } + } + + /** + * Creates a provider instance, returns null if the provider class is + * not found. + */ + private FileSystemProvider createProvider( final String className ) throws Exception + { + try + { + // TODO - wrap exceptions + return (FileSystemProvider)Class.forName( className ).newInstance(); + } + catch( ClassNotFoundException e ) + { + // This is fine, for now + return null; + } + } + + /** + * Closes all file systems created by this file system manager. + */ + public void close() + { + // TODO - implement this + } + + /** + * Sets the base file to use when resolving relative URI. + */ + public void setBaseFile( FileObject baseFile ) throws FileSystemException + { + m_baseFile = baseFile; + } + + /** + * Sets the base file to use when resolving relative URI. + */ + public void setBaseFile( File baseFile ) throws FileSystemException + { + m_baseFile = m_localFileProvider.findFileByLocalName( baseFile.getAbsolutePath() ); + } + + /** + * Returns the base file used to resolve relative URI. + */ + public FileObject getBaseFile() + { + return m_baseFile; + } + + /** + * Locates a file by URI. + */ + public FileObject resolveFile( String URI ) throws FileSystemException + { + return resolveFile( m_baseFile, URI ); + } + + /** + * Resolves a URI, relative to a base file. + */ + public FileObject resolveFile( FileObject baseFile, String uri ) throws FileSystemException + { + // Extract the scheme + String scheme = UriParser.extractScheme( uri ); + if( scheme != null ) + { + // An absolute URI - locate the provider + FileSystemProvider provider = (FileSystemProvider)m_providers.get( scheme ); + if( provider != null ) + { + return provider.findFile( uri ); + } + } + + // Handle absolute file names + if( m_localFileProvider.isAbsoluteLocalName( uri ) ) + { + return m_localFileProvider.findFileByLocalName( uri ); + } + + // Assume a bad scheme + if( scheme != null ) + { + final String message = REZ.getString( "unknown-scheme.error", scheme, uri ); + throw new FileSystemException( message ); + } + + // Use the supplied base file + if( baseFile == null ) + { + final String message = REZ.getString( "find-rel-file.error", uri ); + throw new FileSystemException( message ); + } + return baseFile.resolveFile( uri ); + } + + /** + * A provider context implementation. + */ + private final class ProviderContextImpl implements FileSystemProviderContext + { + /** + * Locates a cached file system by root URI. + */ + public FileSystem getFileSystem( String rootURI ) + { + // TODO - need to have a per-fs uri comparator + return (org.apache.aut.vfs.provider.FileSystem)m_fileSystems.get( rootURI ); + } + + /** + * Registers a file system for caching. + */ + public void putFileSystem( String rootURI, org.apache.aut.vfs.provider.FileSystem fs ) throws FileSystemException + { + // TODO - should really check that there's not one already cached + m_fileSystems.put( rootURI, fs ); + } + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystem.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystem.java new file mode 100644 index 000000000..8c9d53c84 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystem.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider; + +import org.apache.aut.vfs.FileName; +import org.apache.aut.vfs.FileObject; +import org.apache.aut.vfs.FileSystemException; + +/** + * A file system. + * + * @author Adam Murdoch + */ +public interface FileSystem +{ + /** + * Returns the root of this file system. + */ + FileObject getRoot() throws FileSystemException; + + /** + * Finds a file in this file system. + * + * @param name + * The name of the file. + */ + FileObject findFile( FileName name ) throws FileSystemException; + + /** + * Finds a file in this file system. + * + * @param name + * The name of the file. This must be an absolute path. + */ + FileObject findFile( String name ) throws FileSystemException; +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystemProvider.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystemProvider.java new file mode 100644 index 000000000..80afef499 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystemProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider; + +import org.apache.aut.vfs.FileObject; +import org.apache.aut.vfs.FileSystemException; + +/** + * A file system provider, or factory. + */ +public interface FileSystemProvider +{ + /** + * Sets the context for this file system provider. This method is called + * before any of the other provider methods. + */ + void setContext( FileSystemProviderContext context ); + + /** + * Locates a file object, by absolute URI. + * + * @param uri + * The absolute URI of the file to find. + */ + FileObject findFile( String uri ) throws FileSystemException; +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystemProviderContext.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystemProviderContext.java new file mode 100644 index 000000000..6062df4b3 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystemProviderContext.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider; + +import org.apache.aut.vfs.FileSystemException; + +/** + * Used for a file system provider to access the services it needs, such + * as the file system cache or other file system providers. + * + * @author Adam Murdoch + */ +public interface FileSystemProviderContext +{ + /** + * Locates a cached file system by root URI. + */ + FileSystem getFileSystem( String rootURI ); + + /** + * Registers a file system for caching. + */ + void putFileSystem( String rootURI, FileSystem fs ) throws FileSystemException; +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ParsedUri.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ParsedUri.java new file mode 100644 index 000000000..2116d998d --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ParsedUri.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider; + +/** + * A data container for information parsed from an absolute URI. + * + * @author Adam Murdoch + */ +public class ParsedUri +{ + private String m_scheme; + private String m_rootURI; + private String m_path; + private String m_userInfo; + private String m_hostName; + private String m_port; + + /** Returns the scheme. */ + public String getScheme() + { + return m_scheme; + } + + /** Sets the scheme. */ + public void setScheme( String scheme ) + { + m_scheme = scheme; + } + + /** Returns the root URI, used to identify the file system. */ + public String getRootURI() + { + return m_rootURI; + } + + /** Sets the root URI. */ + public void setRootURI( String rootPrefix ) + { + m_rootURI = rootPrefix; + } + + /** Returns the user info part of the URI. */ + public String getUserInfo() + { + return m_userInfo; + } + + /** Sets the user info part of the URI. */ + public void setUserInfo( String userInfo ) + { + m_userInfo = userInfo; + } + + /** Returns the host name part of the URI. */ + public String getHostName() + { + return m_hostName; + } + + /** Sets the host name part of the URI. */ + public void setHostName( String hostName ) + { + m_hostName = hostName; + } + + /** Returns the port part of the URI. */ + public String getPort() + { + return m_port; + } + + /** Sets the port part of the URI. */ + public void setPort( String port ) + { + m_port = port; + } + + /** Returns the path part of the URI. */ + public String getPath() + { + return m_path; + } + + /** Sets the path part of the URI. */ + public void setPath( String absolutePath ) + { + m_path = absolutePath; + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/Resources.properties b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/Resources.properties new file mode 100644 index 000000000..321c7c799 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/Resources.properties @@ -0,0 +1,43 @@ +# AbstractFileObject +delete-not-supported.error=This file type does not support delete. +create-folder-not-supported.error=This file type does not support folder creation. +write-not-supported.error=This file type cannot be written to. +get-type-no-exist.error=Could not determine the type of file "{0}" because it does not exist. +get-type.error=Could not determine the type of file "{0}". +list-children-no-exist.error=Could not list the contents of folder "{0}" because it does not exist. +list-children-not-folder.error=Could not list the contents of "{0}" because it is not a folder. +list-children.error=Could not list the contents of folder "{0}". +delete-read-only.error=Could not delete "{0}" because it is read-only. +delete.error=Could not delete "{0}". +create-mismatched-type.error=Could not create {0} "{1}" because it already exists and is a {2}. +create-read-only.error=Could not create {0} "{1}" because the file system is read-only. +create.error=Could not create {0} "{1}". +get-folder-content.error=Could not get the content of "{0}" because it is a folder. +write-read-only.error=Could not write to "{0}" because it is read-only. +write-folder.error=Could not write to "{0}" because it is a folder. +write-in-use.error=Could not write to "{0}" because it is already in use. +write.error=Could not write to "{0}". + +# DefaultFileContent +get-size-no-exist.error=Could not determine the size of file "{0}" because it does not exist. +get-size-write.error=Could not determine the size of file "{0}" because it is being written to. +get-size.error=Could not determine the size of file "{0}". +read-no-exist.error=Could not read file "{0}" because it does not exist. +read-in-use.error=Could not read file "{0}" because it is already being used. +read.error=Could not read file "{0}". +close-instr.error=Could not close file input stream. +close-outstr.error=Could not close file output stream. + +# AbstractFileSystemProvider +invalid-absolute-uri.error=Invalid absolute URI "{0}". + +# DefaultFileSystemManager +unknown-scheme.error=Unknown scheme "{0}" in URI "{0}". +find-rel-file.error=Could not find file with URI "{0}" because it is a relative path, and no base URI was provided. + +# UriParser +missing-double-slashes.error=Expecting // to follow the scheme in URI "{0}". +missing-hostname.error=Hostname missing from URI "{0}". +missing-port.error=Port number is missing from URI "{0}". +missing-hostname-path-sep.error=Expecting / to follow the hostname in URI "{0}". +invalid-childname.error=Invalid file base-name "{0}". diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/UriParser.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/UriParser.java new file mode 100644 index 000000000..6fc81ebac --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/UriParser.java @@ -0,0 +1,630 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider; + +import java.util.HashSet; +import java.util.Iterator; +import org.apache.aut.vfs.FileSystemException; +import org.apache.avalon.excalibur.i18n.Resources; +import org.apache.avalon.excalibur.i18n.ResourceManager; + +/** + * A name parser which parses absolute URIs. See RFC 2396 for details. + * + * @author Adam Murdoch + */ +public class UriParser +{ + private static final Resources REZ + = ResourceManager.getPackageResources( UriParser.class ); + + /** The normalised separator to use. */ + private char m_separatorChar; + private String m_separator; + + /** + * The set of valid separators. These are all converted to the normalised one. + * Does not contain the normalised separator + */ + private char[] m_separators; + + /** + * Creates a parser, using '/' and '\' as the path separators. + */ + public UriParser() + { + this( null ); + } + + /** + * Creates a parser, using '/' and '\' as the path separators, along with + * a provider-specific set of separators. + * + * @param separators + * Additional legal separator characters. Any occurrences of + * these in paths are replaced with the separator char. + */ + protected UriParser( char[] separators ) + { + m_separatorChar = '/'; + + // Remove the separator char from the separators array + HashSet set = new HashSet(); + set.add( new Character( '\\' ) ); + if( separators != null ) + { + for( int i = 0; i < separators.length; i++ ) + { + char separator = separators[ i ]; + if( separator == m_separatorChar ) + { + continue; + } + set.add( new Character( separator ) ); + } + } + m_separators = new char[ set.size() ]; + Iterator iter = set.iterator(); + for( int i = 0; i < m_separators.length; i++ ) + { + Character ch = (Character)iter.next(); + m_separators[ i ] = ch.charValue(); + } + + m_separator = String.valueOf( m_separatorChar ); + } + + /** + * Parses an absolute URI, splitting it into its components. This + * implementation assumes a "generic URI", as defined by RFC 2396. See + * {@link #parseGenericUri} for more info. + * + *
Sub-classes should override this method. + */ + public ParsedUri parseUri( String uriStr ) throws FileSystemException + { + ParsedUri retval = new ParsedUri(); + parseGenericUri( uriStr, retval ); + return retval; + } + + /** + * Parses a generic URI, as defined by RFC 2396. Briefly, a generic URI + * looks like: + * + *
+ * <scheme> '://' [ <userinfo> '@' ] <hostname> [ ':' <port> ] '/' <path> + *+ * + *
This method differs from the RFC, in that either / or \ is allowed
+ * as a path separator.
+ *
+ * @param uriStr
+ * The URI to parse.
+ * @param uri
+ * Used to return the parsed components of the URI.
+ */
+ protected void parseGenericUri( String uriStr, ParsedUri uri ) throws FileSystemException
+ {
+ StringBuffer name = new StringBuffer();
+
+ // Extract the scheme and authority parts
+ extractToPath( uriStr, name, uri );
+
+ // Normalise the file name
+ normalisePath( name );
+ uri.setPath( name.toString() );
+
+ // Build the root uri
+ StringBuffer rootUri = new StringBuffer();
+ rootUri.append( uri.getScheme() );
+ rootUri.append( "://" );
+ rootUri.append( uri.getHostName() );
+ uri.setRootURI( rootUri.toString() );
+ }
+
+ /**
+ * Extracts the scheme, userinfo, hostname and port components of an
+ * absolute "generic URI".
+ *
+ * @param uri
+ * The absolute URI to parse.
+ *
+ * @param name
+ * Used to return the remainder of the URI.
+ *
+ * @parsedUri
+ * Used to return the extracted components.
+ */
+ protected void extractToPath( String uri, StringBuffer name, ParsedUri parsedUri )
+ throws FileSystemException
+ {
+ // Extract the scheme
+ String scheme = extractScheme( uri, name );
+ parsedUri.setScheme( scheme );
+
+ // Expecting "//"
+ if( name.length() < 2 || name.charAt( 0 ) != '/' || name.charAt( 1 ) != '/' )
+ {
+ final String message = REZ.getString( "missing-double-slashes.error", uri );
+ throw new FileSystemException( message );
+ }
+ name.delete( 0, 2 );
+
+ // Extract userinfo
+ String userInfo = extractUserInfo( name );
+ parsedUri.setUserInfo( userInfo );
+
+ // Extract hostname
+ String hostName = extractHostName( name );
+ if( hostName == null )
+ {
+ final String message = REZ.getString( "missing-hostname.error", uri );
+ throw new FileSystemException( message );
+ }
+ parsedUri.setHostName( hostName );
+
+ // Extract port
+ String port = extractPort( name );
+ if( port != null && port.length() == 0 )
+ {
+ final String message = REZ.getString( "missing-port.error", uri );
+ throw new FileSystemException( message );
+ }
+ parsedUri.setPort( port );
+
+ // Expecting '/' or empty name
+ if( name.length() > 0 && name.charAt( 0 ) != '/' )
+ {
+ final String message = REZ.getString( "missing-hostname-path-sep.error", uri );
+ throw new FileSystemException( message );
+ }
+ }
+
+ /**
+ * Extracts the user info from a URI. The The provider can annotate this object with any additional
+ * information it requires to create a file system from the URI.
+ */
+ protected ParsedUri parseURI( String uri ) throws FileSystemException
+ {
+ return m_parser.parseUri( uri );
+ }
+
+ /**
+ * Creates the filesystem.
+ */
+ protected FileSystem createFileSystem( ParsedUri uri ) throws FileSystemException
+ {
+ // Build the name of the root file.
+ ParsedFileUri fileUri = (ParsedFileUri)uri;
+ String rootFile = fileUri.getRootFile();
+
+ // Create the file system
+ DefaultFileName rootName = new DefaultFileName( m_parser, fileUri.getRootURI(), "/" );
+ return new LocalFileSystem( rootName, rootFile );
+ }
+}
diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/ParsedFileUri.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/ParsedFileUri.java
new file mode 100644
index 000000000..90df6d2bf
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/ParsedFileUri.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE.txt file.
+ */
+package org.apache.aut.vfs.provider.local;
+
+import org.apache.aut.vfs.provider.ParsedUri;
+
+/**
+ * A parsed file URI.
+ *
+ * @author Adam Murdoch
+ */
+public class ParsedFileUri extends ParsedUri
+{
+ private String m_rootFile;
+
+ public String getRootFile()
+ {
+ return m_rootFile;
+ }
+
+ public void setRootFile( String rootPrefix )
+ {
+ m_rootFile = rootPrefix;
+ }
+}
diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/Resources.properties b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/Resources.properties
new file mode 100644
index 000000000..df782dee9
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/Resources.properties
@@ -0,0 +1,5 @@
+get-type.error=Could not determine the type of "{0}".
+delete-file.error=Could not delete "{0}".
+create-folder.error=Could not create directory "{0}".
+not-absolute-file-name.error=URI "{0}" is not an absolute file name.
+missing-share-name.error=Share name missing from UNC file name "{0}".
diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/ParsedSmbUri.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/ParsedSmbUri.java
new file mode 100644
index 000000000..6ee462f94
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/ParsedSmbUri.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE.txt file.
+ */
+package org.apache.aut.vfs.provider.smb;
+
+import org.apache.aut.vfs.provider.ParsedUri;
+
+/**
+ * A parsed SMB URI.
+ *
+ * @author Adam Murdoch
+ */
+public class ParsedSmbUri extends ParsedUri
+{
+ private String m_share;
+
+ public String getShare()
+ {
+ return m_share;
+ }
+
+ public void setShare( String share )
+ {
+ m_share = share;
+ }
+}
diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/Resources.properties b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/Resources.properties
new file mode 100644
index 000000000..41e8f0245
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/Resources.properties
@@ -0,0 +1,2 @@
+missing-share-name.error=The share name is missing from URI "{0}".
+get-type.error=Could not detemine the type of "{0}".
\ No newline at end of file
diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileNameParser.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileNameParser.java
new file mode 100644
index 000000000..0e4f5a175
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileNameParser.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE.txt file.
+ */
+package org.apache.aut.vfs.provider.smb;
+
+import org.apache.aut.vfs.FileSystemException;
+import org.apache.aut.vfs.provider.ParsedUri;
+import org.apache.aut.vfs.provider.UriParser;
+import org.apache.avalon.excalibur.i18n.Resources;
+import org.apache.avalon.excalibur.i18n.ResourceManager;
+
+/**
+ * A parser for SMB URI.
+ *
+ * @author Adam Murdoch
+ */
+public class SmbFileNameParser extends UriParser
+{
+ private static final Resources REZ
+ = ResourceManager.getPackageResources( SmbFileNameParser.class );
+
+ /**
+ * Parses an absolute URI, splitting it into its components.
+ */
+ public ParsedUri parseUri( String uriStr ) throws FileSystemException
+ {
+ ParsedSmbUri uri = new ParsedSmbUri();
+ StringBuffer name = new StringBuffer();
+
+ // Extract the scheme and authority parts
+ extractToPath( uriStr, name, uri );
+
+ // Normalise paths
+ fixSeparators( name );
+
+ // Extract the share
+ String share = extractFirstElement( name );
+ if( share == null )
+ {
+ final String message = REZ.getString( "missing-share-name.error", uriStr );
+ throw new FileSystemException( message );
+ }
+ uri.setShare( share );
+
+ // Set the path
+ uri.setPath( name.toString() );
+
+ // Set the root URI
+ StringBuffer rootUri = new StringBuffer();
+ rootUri.append( uri.getScheme() );
+ rootUri.append( "://" );
+ String userInfo = uri.getUserInfo();
+ if( userInfo != null )
+ {
+ rootUri.append( userInfo );
+ rootUri.append( '@' );
+ }
+ rootUri.append( uri.getHostName() );
+ rootUri.append( '/' );
+ rootUri.append( share );
+ uri.setRootURI( rootUri.toString() );
+
+ return uri;
+ }
+}
diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileObject.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileObject.java
new file mode 100644
index 000000000..6620ffa95
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileObject.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE.txt file.
+ */
+package org.apache.aut.vfs.provider.smb;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+import jcifs.smb.SmbFile;
+import jcifs.smb.SmbFileInputStream;
+import jcifs.smb.SmbFileOutputStream;
+import org.apache.aut.vfs.FileName;
+import org.apache.aut.vfs.FileObject;
+import org.apache.aut.vfs.FileSystemException;
+import org.apache.aut.vfs.FileType;
+import org.apache.aut.vfs.provider.AbstractFileObject;
+import org.apache.avalon.excalibur.i18n.ResourceManager;
+import org.apache.avalon.excalibur.i18n.Resources;
+
+/**
+ * A file in an SMB file system.
+ *
+ * @author Adam Murdoch
+ */
+public class SmbFileObject extends AbstractFileObject implements FileObject
+{
+ private static final Resources REZ
+ = ResourceManager.getPackageResources( SmbFileObject.class );
+
+ private String m_fileName;
+ private SmbFile m_file;
+
+ protected SmbFileObject( String fileName, FileName name, SmbFileSystem fs )
+ {
+ super( name, fs );
+ m_fileName = fileName;
+ }
+
+ /**
+ * Attaches this file object to its file resource.
+ */
+ protected void doAttach() throws Exception
+ {
+ // Defer creation of the SmbFile to here
+ if( m_file == null )
+ {
+ m_file = new SmbFile( m_fileName );
+ }
+ }
+
+ /**
+ * Detaches this file object from its file resource.
+ */
+ protected void doDetach()
+ {
+ // Need to throw away the file when the file's type changes, because
+ // the SmbFile caches the type
+ m_file = null;
+ }
+
+ /**
+ * Determines the type of the file, returns null if the file does not
+ * exist.
+ */
+ protected FileType doGetType() throws Exception
+ {
+ // Need to check whether parent exists or not, because SmbFile.exists()
+ // throws an exception if it does not
+ // TODO - patch jCIFS?
+
+ FileObject parent = getParent();
+ if( parent != null && !parent.exists() )
+ {
+ return null;
+ }
+
+ if( !m_file.exists() )
+ {
+ return null;
+ }
+ if( m_file.isDirectory() )
+ {
+ return FileType.FOLDER;
+ }
+ if( m_file.isFile() )
+ {
+ return FileType.FILE;
+ }
+ final String message = REZ.getString( "get-type.error", getName() );
+ throw new FileSystemException( message );
+ }
+
+ /**
+ * Lists the children of the file. Is only called if {@link #doGetType}
+ * returns {@link FileType#FOLDER}.
+ */
+ protected String[] doListChildren() throws Exception
+ {
+ return m_file.list();
+ }
+
+ /**
+ * Deletes the file.
+ */
+ protected void doDelete() throws Exception
+ {
+ m_file.delete();
+ }
+
+ /**
+ * Creates this file as a folder.
+ */
+ protected void doCreateFolder() throws Exception
+ {
+ m_file.mkdir();
+ }
+
+ /**
+ * Returns the size of the file content (in bytes).
+ */
+ protected long doGetContentSize() throws Exception
+ {
+ return m_file.length();
+ }
+
+ /**
+ * Creates an input stream to read the file content from.
+ */
+ protected InputStream doGetInputStream() throws Exception
+ {
+ return new SmbFileInputStream( m_file );
+ }
+
+ /**
+ * Creates an output stream to write the file content to.
+ */
+ protected OutputStream doGetOutputStream() throws Exception
+ {
+ return new SmbFileOutputStream( m_file );
+ }
+}
diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileSystem.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileSystem.java
new file mode 100644
index 000000000..9c7e3b098
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileSystem.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE.txt file.
+ */
+package org.apache.aut.vfs.provider.smb;
+
+import org.apache.aut.vfs.FileName;
+import org.apache.aut.vfs.FileObject;
+import org.apache.aut.vfs.FileSystemException;
+import org.apache.aut.vfs.provider.AbstractFileSystem;
+import org.apache.aut.vfs.provider.FileSystem;
+
+/**
+ * A SMB file system.
+ *
+ * @author Adam Murdoch
+ */
+public class SmbFileSystem extends AbstractFileSystem implements FileSystem
+{
+ public SmbFileSystem( FileName rootName )
+ {
+ super( rootName );
+ }
+
+ /**
+ * Creates a file object.
+ */
+ protected FileObject createFile( FileName name ) throws FileSystemException
+ {
+ String fileName = name.getURI();
+ return new SmbFileObject( fileName, name, this );
+ }
+}
diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileSystemProvider.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileSystemProvider.java
new file mode 100644
index 000000000..f9e7eb276
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileSystemProvider.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE.txt file.
+ */
+package org.apache.aut.vfs.provider.smb;
+
+import org.apache.aut.vfs.FileName;
+import org.apache.aut.vfs.FileSystemException;
+import org.apache.aut.vfs.provider.AbstractFileSystemProvider;
+import org.apache.aut.vfs.provider.DefaultFileName;
+import org.apache.aut.vfs.provider.FileSystem;
+import org.apache.aut.vfs.provider.FileSystemProvider;
+import org.apache.aut.vfs.provider.ParsedUri;
+
+/**
+ * A provider for SMB (Samba, Windows share) file systems.
+ *
+ * @author Adam Murdoch
+ */
+public class SmbFileSystemProvider extends AbstractFileSystemProvider implements FileSystemProvider
+{
+ SmbFileNameParser m_parser = new SmbFileNameParser();
+
+ /**
+ * Parses a URI into its components.
+ */
+ protected ParsedUri parseURI( String uri ) throws FileSystemException
+ {
+ return m_parser.parseUri( uri );
+ }
+
+ /**
+ * Creates the filesystem.
+ */
+ protected FileSystem createFileSystem( ParsedUri uri ) throws FileSystemException
+ {
+ ParsedSmbUri smbUri = (ParsedSmbUri)uri;
+
+ FileName rootName = new DefaultFileName( m_parser, smbUri.getRootURI(), "/" );
+ return new SmbFileSystem( rootName );
+ }
+}
diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ParsedZipUri.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ParsedZipUri.java
new file mode 100644
index 000000000..0dda462ae
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ParsedZipUri.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE.txt file.
+ */
+package org.apache.aut.vfs.provider.zip;
+
+import org.apache.aut.vfs.provider.ParsedUri;
+
+/**
+ * A parsed Zip URI.
+ *
+ * @author Adam Murdoch
+ */
+public class ParsedZipUri extends ParsedUri
+{
+ private String m_zipFile;
+
+ public String getZipFile()
+ {
+ return m_zipFile;
+ }
+
+ public void setZipFile( String zipFile )
+ {
+ m_zipFile = zipFile;
+ }
+}
diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/Resources.properties b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/Resources.properties
new file mode 100644
index 000000000..4885c50ca
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/Resources.properties
@@ -0,0 +1 @@
+open-zip-file.error=Could not open Zip file "{0}".
diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileNameParser.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileNameParser.java
new file mode 100644
index 000000000..7f804efd7
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileNameParser.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE.txt file.
+ */
+package org.apache.aut.vfs.provider.zip;
+
+import org.apache.aut.vfs.FileSystemException;
+import org.apache.aut.vfs.provider.ParsedUri;
+import org.apache.aut.vfs.provider.UriParser;
+
+/**
+ * A parser for Zip file names.
+ *
+ * @author Adam Murdoch
+ */
+public class ZipFileNameParser extends UriParser
+{
+ /**
+ * Parses an absolute URI, splitting it into its components.
+ *
+ * @param name
+ * The URI.
+ */
+ public ParsedUri parseUri( String uriStr ) throws FileSystemException
+ {
+ StringBuffer name = new StringBuffer();
+ ParsedZipUri uri = new ParsedZipUri();
+
+ // Extract the scheme
+ String scheme = extractScheme( uriStr, name );
+ uri.setScheme( scheme );
+
+ // Extract the Zip file name
+ String zipName = extractZipName( name );
+ uri.setZipFile( zipName );
+
+ // Adjust the separators
+ fixSeparators( name );
+
+ // Normalise the file name
+ normalisePath( name );
+ uri.setPath( name.toString() );
+
+ // Build root URI
+ StringBuffer rootUri = new StringBuffer();
+ rootUri.append( scheme );
+ rootUri.append( ":" );
+ rootUri.append( zipName );
+ rootUri.append( "!" );
+ uri.setRootURI( rootUri.toString() );
+
+ return uri;
+ }
+
+ /**
+ * Pops the root prefix off a URI, which has had the scheme removed.
+ */
+ protected String extractZipName( StringBuffer uri ) throws FileSystemException
+ {
+ // Looking for
+ *
+ */
+ public void normalisePath( StringBuffer path ) throws FileSystemException
+ {
+ if( path.length() == 0 )
+ {
+ return;
+ }
+
+ // Adjust separators
+ fixSeparators( path );
+
+ // Determine the start of the first element
+ int startFirstElem = 0;
+ if( path.charAt( 0 ) == m_separatorChar )
+ {
+ if( path.length() == 1 )
+ {
+ return;
+ }
+ startFirstElem = 1;
+ }
+
+ // Iterate over each element
+ int startElem = startFirstElem;
+ int maxlen = path.length();
+ while( startElem < maxlen )
+ {
+ // Find the end of the element
+ int endElem = startElem;
+ for( ; endElem < maxlen && path.charAt( endElem ) != m_separatorChar; endElem++ )
+ {
+ }
+
+ int elemLen = endElem - startElem;
+ if( elemLen == 0 )
+ {
+ // An empty element - axe it
+ path.delete( endElem, endElem + 1 );
+ maxlen = path.length();
+ continue;
+ }
+ if( elemLen == 1 && path.charAt( startElem ) == '.' )
+ {
+ // A '.' element - axe it
+ path.delete( startElem, endElem + 1 );
+ maxlen = path.length();
+ continue;
+ }
+ if( elemLen == 2
+ && path.charAt( startElem ) == '.'
+ && path.charAt( startElem + 1 ) == '.' )
+ {
+ // A '..' element - remove the previous element
+ if( startElem > startFirstElem )
+ {
+ int pos = startElem - 2;
+ for( ; path.charAt( pos ) != m_separatorChar; pos-- )
+ {
+ }
+ startElem = pos + 1;
+ }
+ path.delete( startElem, endElem + 1 );
+ maxlen = path.length();
+ continue;
+ }
+
+ // A regular element
+ startElem = endElem + 1;
+ }
+
+ // Remove trailing separator
+ if( path.charAt( maxlen - 1 ) == m_separatorChar && maxlen > 1 )
+ {
+ path.delete( maxlen - 1, maxlen );
+ }
+ }
+
+ /**
+ * Adjusts the separators in a name.
+ */
+ protected boolean fixSeparators( StringBuffer name )
+ {
+ if( m_separators.length == 0 )
+ {
+ // Only one valid separator, so don't need to do anything
+ return false;
+ }
+
+ boolean changed = false;
+ int maxlen = name.length();
+ for( int i = 0; i < maxlen; i++ )
+ {
+ char ch = name.charAt( i );
+ for( int j = 0; j < m_separators.length; j++ )
+ {
+ char separator = m_separators[ j ];
+ if( ch == separator )
+ {
+ name.setCharAt( i, m_separatorChar );
+ changed = true;
+ break;
+ }
+ }
+ }
+ return changed;
+ }
+
+ /**
+ * Extracts the scheme from a URI.
+ *
+ * @param uri
+ * The URI.
+ *
+ * @return
+ * The scheme name. Returns null if there is no scheme.
+ */
+ public static String extractScheme( String uri )
+ {
+ return extractScheme( uri, null );
+ }
+
+ /**
+ * Extracts the scheme from a URI.
+ *
+ * @param uri
+ * The URI.
+ *
+ * @param buffer
+ * Returns the remainder of the URI.
+ *
+ * @return
+ * The scheme name. Returns null if there is no scheme.
+ */
+ protected static String extractScheme( String uri, StringBuffer buffer )
+ {
+ if( buffer != null )
+ {
+ buffer.setLength( 0 );
+ buffer.append( uri );
+ }
+
+ int maxPos = uri.length();
+ for( int pos = 0; pos < maxPos; pos++ )
+ {
+ char ch = uri.charAt( pos );
+
+ if( ch == ':' )
+ {
+ // Found the end of the scheme
+ String scheme = uri.substring( 0, pos );
+ if( buffer != null )
+ {
+ buffer.delete( 0, pos + 1 );
+ }
+ return scheme;
+ }
+
+ if( ( ch >= 'a' && ch <= 'z' )
+ || ( ch >= 'A' && ch <= 'Z' ) )
+ {
+ // A scheme character
+ continue;
+ }
+ if( pos > 0 &&
+ ( ( ch >= '0' && ch <= '9' )
+ || ch == '+' || ch == '-' || ch == '.' ) )
+ {
+ // A scheme character (these are not allowed as the first
+ // character of the scheme, but can be used as subsequent
+ // characters.
+ continue;
+ }
+
+ // Not a scheme character
+ break;
+ }
+
+ // No scheme in URI
+ return null;
+ }
+}
diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileNameParser.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileNameParser.java
new file mode 100644
index 000000000..a397d4f88
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileNameParser.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE.txt file.
+ */
+package org.apache.aut.vfs.provider.ftp;
+
+import org.apache.aut.vfs.provider.UriParser;
+import org.apache.aut.vfs.provider.ParsedUri;
+import org.apache.aut.vfs.FileSystemException;
+
+/**
+ * A parser for FTP URI.
+ *
+ * @author Adam Murdoch
+ */
+public class FtpFileNameParser extends UriParser
+{
+ /**
+ * Parses an absolute URI, splitting it into its components.
+ */
+ public ParsedUri parseUri( String uriStr ) throws FileSystemException
+ {
+ ParsedFtpUri uri = new ParsedFtpUri();
+
+ // FTP URI are generic URI (as per RFC 2396)
+ parseGenericUri( uriStr, uri );
+
+ // Split up the userinfo into a username and password
+ String userInfo = uri.getUserInfo();
+ if( userInfo != null )
+ {
+ int idx = userInfo.indexOf(':');
+ if( idx == -1 )
+ {
+ uri.setUserName( userInfo );
+ }
+ else
+ {
+ String userName = userInfo.substring(0, idx);
+ String password = userInfo.substring(idx+1);
+ uri.setUserName( userName );
+ uri.setPassword( password );
+ }
+ }
+
+ return uri;
+ }
+}
diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileObject.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileObject.java
new file mode 100644
index 000000000..b06abfb60
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileObject.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE.txt file.
+ */
+package org.apache.aut.vfs.provider.ftp;
+
+import com.oroinc.net.ftp.FTPFile;
+import java.io.InputStream;
+import java.io.OutputStream;
+import org.apache.aut.vfs.FileName;
+import org.apache.aut.vfs.FileSystemException;
+import org.apache.aut.vfs.FileType;
+import org.apache.aut.vfs.provider.AbstractFileObject;
+import org.apache.avalon.excalibur.i18n.Resources;
+import org.apache.avalon.excalibur.i18n.ResourceManager;
+
+/**
+ * An FTP file.
+ *
+ * @author Adam Murdoch
+ */
+class FtpFileObject extends AbstractFileObject
+{
+ private static final Resources REZ
+ = ResourceManager.getPackageResources( FtpFileObject.class );
+
+ private FtpFileSystem m_ftpFs;
+
+ // Cached info
+ private FTPFile m_fileInfo;
+ private FTPFile[] m_children;
+
+ private static final FTPFile[] EMPTY_FTP_FILE_ARRAY = {};
+
+ public FtpFileObject( FileName name, FtpFileSystem fs )
+ {
+ super( name, fs );
+ m_ftpFs = fs;
+ }
+
+ /**
+ * Called by child file objects, to locate their ftp file info.
+ */
+ private FTPFile getChildFile( String name ) throws Exception
+ {
+ if( m_children == null )
+ {
+ // List the children of this file
+ m_children = m_ftpFs.getClient().listFiles( getName().getPath() );
+ if( m_children == null )
+ {
+ m_children = EMPTY_FTP_FILE_ARRAY;
+ }
+ }
+
+ // Look for the requested child
+ // TODO - use hash table
+ for( int i = 0; i < m_children.length; i++ )
+ {
+ FTPFile child = m_children[ i ];
+ if( child.getName().equals( name ) )
+ {
+ // TODO - should be using something else to compare names
+ return child;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Attaches this file object to its file resource.
+ */
+ protected void doAttach() throws Exception
+ {
+ // Get the parent folder to find the info for this file
+ FtpFileObject parent = (FtpFileObject)getParent();
+ m_fileInfo = parent.getChildFile( getName().getBaseName() );
+ if( m_fileInfo == null || !m_fileInfo.isDirectory() )
+ {
+ m_children = EMPTY_FTP_FILE_ARRAY;
+ }
+ }
+
+ /**
+ * Detaches this file object from its file resource.
+ */
+ protected void doDetach()
+ {
+ m_fileInfo = null;
+ m_children = null;
+ }
+
+ /**
+ * Called when the children of this file change.
+ */
+ protected void onChildrenChanged()
+ {
+ m_children = null;
+ }
+
+ /**
+ * Determines the type of the file, returns null if the file does not
+ * exist.
+ */
+ protected FileType doGetType() throws Exception
+ {
+ if( m_fileInfo == null )
+ {
+ // Does not exist
+ return null;
+ }
+ if( m_fileInfo.isDirectory() )
+ {
+ return FileType.FOLDER;
+ }
+ if( m_fileInfo.isFile() )
+ {
+ return FileType.FILE;
+ }
+
+ final String message = REZ.getString( "get-type.error", getName() );
+ throw new FileSystemException( message );
+ }
+
+ /**
+ * Lists the children of the file.
+ */
+ protected String[] doListChildren() throws Exception
+ {
+ if( m_children == null )
+ {
+ // List the children of this file
+ m_children = m_ftpFs.getClient().listFiles( getName().getPath() );
+ if( m_children == null )
+ {
+ m_children = EMPTY_FTP_FILE_ARRAY;
+ }
+ }
+
+ String[] children = new String[ m_children.length ];
+ for( int i = 0; i < m_children.length; i++ )
+ {
+ FTPFile child = m_children[ i ];
+ children[ i ] = child.getName();
+ }
+
+ return children;
+ }
+
+ /**
+ * Deletes the file.
+ */
+ protected void doDelete() throws Exception
+ {
+ if( !m_ftpFs.getClient().deleteFile( getName().getPath() ) )
+ {
+ final String message = REZ.getString( "delete-file.error", getName() );
+ throw new FileSystemException( message );
+ }
+ }
+
+ /**
+ * Creates this file as a folder.
+ */
+ protected void doCreateFolder() throws Exception
+ {
+ if( !m_ftpFs.getClient().makeDirectory( getName().getPath() ) )
+ {
+ final String message = REZ.getString( "create-folder.error", getName() );
+ throw new FileSystemException( message );
+ }
+ }
+
+ /**
+ * Returns the size of the file content (in bytes).
+ */
+ protected long doGetContentSize() throws Exception
+ {
+ return m_fileInfo.getSize();
+ }
+
+ /**
+ * Creates an input stream to read the file content from.
+ */
+ protected InputStream doGetInputStream() throws Exception
+ {
+ return m_ftpFs.getClient().retrieveFileStream( getName().getPath() );
+ }
+
+ /**
+ * Notification of the input stream being closed.
+ */
+ protected void doEndInput() throws Exception
+ {
+ if( !m_ftpFs.getClient().completePendingCommand() )
+ {
+ final String message = REZ.getString( "finish-get.error", getName() );
+ throw new FileSystemException( message );
+ }
+ }
+
+ /**
+ * Creates an output stream to write the file content to.
+ */
+ protected OutputStream doGetOutputStream() throws Exception
+ {
+ return m_ftpFs.getClient().storeFileStream( getName().getPath() );
+ }
+
+ /**
+ * Notification of the output stream being closed.
+ */
+ protected void doEndOutput() throws Exception
+ {
+ if( !m_ftpFs.getClient().completePendingCommand() )
+ {
+ final String message = REZ.getString( "finish-put.error", getName() );
+ throw new FileSystemException( message );
+ }
+ }
+}
diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileSystem.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileSystem.java
new file mode 100644
index 000000000..6b0f5a8d5
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileSystem.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE.txt file.
+ */
+package org.apache.aut.vfs.provider.ftp;
+
+import com.oroinc.net.ftp.FTP;
+import com.oroinc.net.ftp.FTPClient;
+import com.oroinc.net.ftp.FTPReply;
+import java.io.IOException;
+import org.apache.aut.vfs.FileName;
+import org.apache.aut.vfs.FileObject;
+import org.apache.aut.vfs.FileSystemException;
+import org.apache.aut.vfs.provider.AbstractFileSystem;
+import org.apache.avalon.excalibur.i18n.Resources;
+import org.apache.avalon.excalibur.i18n.ResourceManager;
+
+/**
+ * An FTP file system.
+ *
+ * @author Adam Murdoch
+ */
+class FtpFileSystem extends AbstractFileSystem
+{
+ private static final Resources REZ
+ = ResourceManager.getPackageResources( FtpFileSystem.class );
+
+ private FTPClient m_client;
+
+ public FtpFileSystem( FileName rootName,
+ String hostname,
+ String username,
+ String password ) throws FileSystemException
+ {
+ super( rootName );
+ try
+ {
+ m_client = new FTPClient();
+ m_client.connect( hostname );
+
+ int reply = m_client.getReplyCode();
+ if( !FTPReply.isPositiveCompletion( reply ) )
+ {
+ final String message = REZ.getString( "connect-rejected.error", hostname );
+ throw new FileSystemException( message );
+ }
+
+ // Login
+ if( !m_client.login( username, password ) )
+ {
+ final String message = REZ.getString( "login.error", hostname, username );
+ throw new FileSystemException( message );
+ }
+
+ // Set binary mode
+ if( !m_client.setFileType( FTP.BINARY_FILE_TYPE ) )
+ {
+ final String message = REZ.getString( "set-binary.error", hostname );
+ throw new FileSystemException( message );
+ }
+ }
+ catch( Exception exc )
+ {
+ try
+ {
+ // Clean up
+ if( m_client.isConnected() )
+ {
+ m_client.disconnect();
+ }
+ }
+ catch( IOException e )
+ {
+ // Ignore
+ }
+
+ final String message = REZ.getString( "connect.error", hostname );
+ throw new FileSystemException( message, exc );
+ }
+
+ // TODO - close connection
+ }
+
+ /**
+ * Returns an FTP client to use.
+ */
+ public FTPClient getClient()
+ {
+ return m_client;
+ }
+
+ /**
+ * Creates a file object.
+ */
+ protected FileObject createFile( FileName name ) throws FileSystemException
+ {
+ return new FtpFileObject( name, this );
+ }
+}
diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileSystemProvider.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileSystemProvider.java
new file mode 100644
index 000000000..18670c235
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileSystemProvider.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE.txt file.
+ */
+package org.apache.aut.vfs.provider.ftp;
+
+import org.apache.aut.vfs.FileName;
+import org.apache.aut.vfs.FileSystemException;
+import org.apache.aut.vfs.provider.AbstractFileSystemProvider;
+import org.apache.aut.vfs.provider.DefaultFileName;
+import org.apache.aut.vfs.provider.FileSystem;
+import org.apache.aut.vfs.provider.ParsedUri;
+import org.apache.aut.vfs.provider.UriParser;
+
+/**
+ * A provider for FTP file systems.
+ *
+ * @author Adam Murdoch
+ */
+public class FtpFileSystemProvider extends AbstractFileSystemProvider
+{
+ private UriParser m_parser = new FtpFileNameParser();
+
+ /**
+ * Parses a URI into its components.
+ */
+ protected ParsedUri parseURI( String uri ) throws FileSystemException
+ {
+ return m_parser.parseUri( uri );
+ }
+
+ /**
+ * Creates the filesystem.
+ */
+ protected FileSystem createFileSystem( ParsedUri uri ) throws FileSystemException
+ {
+ ParsedFtpUri ftpUri = (ParsedFtpUri)uri;
+
+ // Build the root name
+ FileName rootName = new DefaultFileName( m_parser, ftpUri.getRootURI(), "/" );
+
+ // Determine the username and password to use
+ String username = ftpUri.getUserName();
+ if( username == null )
+ {
+ username = "anonymous";
+ }
+ String password = ftpUri.getPassword();
+ if( password == null )
+ {
+ password = "anonymous";
+ }
+
+ // Create the file system
+ return new FtpFileSystem( rootName, ftpUri.getHostName(), username, password );
+ }
+}
diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/ParsedFtpUri.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/ParsedFtpUri.java
new file mode 100644
index 000000000..03616ec27
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/ParsedFtpUri.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE.txt file.
+ */
+package org.apache.aut.vfs.provider.ftp;
+
+import org.apache.aut.vfs.provider.ParsedUri;
+
+/**
+ * A parsed FTP URI.
+ *
+ * @author Adam Murdoch
+ */
+public class ParsedFtpUri extends ParsedUri
+{
+ private String m_userName;
+ private String m_password;
+
+ public String getUserName()
+ {
+ return m_userName;
+ }
+
+ public void setUserName( String userName )
+ {
+ m_userName = userName;
+ }
+
+ public String getPassword()
+ {
+ return m_password;
+ }
+
+ public void setPassword( String password )
+ {
+ m_password = password;
+ }
+}
diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/Resources.properties b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/Resources.properties
new file mode 100644
index 000000000..d2c712051
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/Resources.properties
@@ -0,0 +1,9 @@
+get-type.error=Could not determine the file type of "{0}".
+delete-file.error=Could not delete FTP file "{0}".
+create-folder.error=Could not create FTP directory "{0}".
+finish-get.error=Could not get FTP file "{0}".
+finish-put.error=Could not put FTP file "{0}".
+connect-rejected.error=Connection to FTP server on "{0}" rejected.
+login.error=Could not login to FTP server on "{0}" as user "{1}".
+set-binary.error=Could not switch to binary transfer mode.
+connect.error=Could not connect to FTP server on "{0}".
\ No newline at end of file
diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFile.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFile.java
new file mode 100644
index 000000000..4ff461891
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFile.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE.txt file.
+ */
+package org.apache.aut.vfs.provider.local;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import org.apache.aut.vfs.FileName;
+import org.apache.aut.vfs.FileObject;
+import org.apache.aut.vfs.FileSystemException;
+import org.apache.aut.vfs.FileType;
+import org.apache.aut.vfs.provider.AbstractFileObject;
+import org.apache.avalon.excalibur.i18n.Resources;
+import org.apache.avalon.excalibur.i18n.ResourceManager;
+
+/**
+ * A file object implementation which uses direct file access.
+ *
+ * @author Adam Murdoch
+ */
+final class LocalFile extends AbstractFileObject implements FileObject
+{
+ private static final Resources REZ
+ = ResourceManager.getPackageResources( LocalFile.class );
+
+ private File m_file;
+ private String m_fileName;
+
+ /**
+ * Creates a non-root file.
+ */
+ public LocalFile( LocalFileSystem fs, String fileName, FileName name )
+ {
+ super( name, fs );
+ m_fileName = fileName;
+ }
+
+ /**
+ * Attaches this file object to its file resource.
+ */
+ protected void doAttach() throws Exception
+ {
+ if( m_file == null )
+ {
+ m_file = new File( m_fileName );
+ }
+ }
+
+ /**
+ * Returns the file's type.
+ */
+ protected FileType doGetType() throws Exception
+ {
+ if( !m_file.exists() )
+ {
+ return null;
+ }
+ if( m_file.isDirectory() )
+ {
+ return FileType.FOLDER;
+ }
+ if( m_file.isFile() )
+ {
+ return FileType.FILE;
+ }
+
+ final String message = REZ.getString( "get-type.error", m_file );
+ throw new FileSystemException( message );
+ }
+
+ /**
+ * Returns the children of the file.
+ */
+ protected String[] doListChildren() throws Exception
+ {
+ return m_file.list();
+ }
+
+ /**
+ * Deletes this file, and all children.
+ */
+ public void doDelete() throws Exception
+ {
+ if( !m_file.delete() )
+ {
+ final String message = REZ.getString( "delete-file.error", m_file );
+ throw new FileSystemException( message );
+ }
+ }
+
+ /**
+ * Creates this folder.
+ */
+ protected void doCreateFolder() throws Exception
+ {
+ if( !m_file.mkdir() )
+ {
+ final String message = REZ.getString( "create-folder.error", m_file );
+ throw new FileSystemException( message );
+ }
+ }
+
+ /**
+ * Creates an input stream to read the content from.
+ */
+ protected InputStream doGetInputStream() throws Exception
+ {
+ return new FileInputStream( m_file );
+ }
+
+ /**
+ * Creates an output stream to write the file content to.
+ */
+ protected OutputStream doGetOutputStream() throws Exception
+ {
+ return new FileOutputStream( m_file );
+ }
+
+ /**
+ * Returns the size of the file content (in bytes).
+ */
+ protected long doGetContentSize() throws Exception
+ {
+ return m_file.length();
+ }
+}
diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileNameParser.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileNameParser.java
new file mode 100644
index 000000000..40c0ec696
--- /dev/null
+++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileNameParser.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) The Apache Software Foundation. All rights reserved.
+ *
+ * This software is published under the terms of the Apache Software License
+ * version 1.1, a copy of which has been included with this distribution in
+ * the LICENSE.txt file.
+ */
+package org.apache.aut.vfs.provider.local;
+
+import java.io.File;
+import org.apache.aut.vfs.FileSystemException;
+import org.apache.aut.vfs.provider.ParsedUri;
+import org.apache.aut.vfs.provider.UriParser;
+import org.apache.avalon.excalibur.i18n.Resources;
+import org.apache.avalon.excalibur.i18n.ResourceManager;
+
+/**
+ * A name parser.
+ *
+ * @author Adam Murdoch
+ */
+class LocalFileNameParser extends UriParser
+{
+ private static final Resources REZ
+ = ResourceManager.getPackageResources( LocalFileNameParser.class );
+
+ private boolean m_windowsNames;
+
+ public LocalFileNameParser()
+ {
+ super( new char[]{File.separatorChar, '/', '\\'} );
+ m_windowsNames = ( System.getProperty( "os.name" ).toLowerCase().indexOf( "windows" ) != -1 );
+ }
+
+ /**
+ * Determines if a name is an absolute file name.
+ */
+ public boolean isAbsoluteName( String name )
+ {
+ // TODO - this is yucky
+ StringBuffer b = new StringBuffer( name );
+ try
+ {
+ fixSeparators( b );
+ extractRootPrefix( name, b );
+ return true;
+ }
+ catch( FileSystemException e )
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Parses an absolute URI, splitting it into its components.
+ *
+ * @param name
+ * The URI.
+ */
+ public ParsedUri parseUri( String uriStr ) throws FileSystemException
+ {
+ StringBuffer name = new StringBuffer();
+ ParsedFileUri uri = new ParsedFileUri();
+
+ // Extract the scheme
+ String scheme = extractScheme( uriStr, name );
+ uri.setScheme( scheme );
+
+ // Adjust the separators
+ fixSeparators( name );
+
+ // Extract the root prefix
+ String rootFile = extractRootPrefix( uriStr, name );
+ uri.setRootFile( rootFile );
+
+ // Normalise the path
+ normalisePath( name );
+ uri.setPath( name.toString() );
+
+ // Build the root URI
+ StringBuffer rootUri = new StringBuffer();
+ rootUri.append( scheme );
+ rootUri.append( "://" );
+ rootUri.append( rootFile );
+ uri.setRootURI( rootUri.toString() );
+
+ return uri;
+ }
+
+ /**
+ * Pops the root prefix off a URI, which has had the scheme removed.
+ */
+ private String extractRootPrefix( String uri,
+ StringBuffer name )
+ throws FileSystemException
+ {
+ // TODO - split this into sub-classes
+ if( m_windowsNames )
+ {
+ return extractWindowsRootPrefix( uri, name );
+ }
+ else
+ {
+ // Looking for