Browse Source

Added VFS proposal.

git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@271053 13f79535-47bb-0310-9956-ffa450edef68
master
adammurdoch 23 years ago
parent
commit
1e702432eb
65 changed files with 6941 additions and 3 deletions
  1. +48
    -3
      proposal/myrmidon/build.xml
  2. +0
    -0
      proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/dir1/file1.txt
  3. +0
    -0
      proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/dir1/file2.txt
  4. +0
    -0
      proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/dir1/file3.txt
  5. +0
    -0
      proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/empty.txt
  6. +2
    -0
      proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/file1.txt
  7. +160
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/FileContent.java
  8. +87
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/FileName.java
  9. +214
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/FileObject.java
  10. +41
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/FileSystemException.java
  11. +94
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/FileSystemManager.java
  12. +52
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/FileType.java
  13. +62
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/NameScope.java
  14. +2
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/Resources.properties
  15. +10
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/package.html
  16. +644
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileObject.java
  17. +92
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileSystem.java
  18. +83
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileSystemProvider.java
  19. +381
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileContent.java
  20. +123
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileName.java
  21. +205
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileSystemManager.java
  22. +41
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystem.java
  23. +31
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystemProvider.java
  24. +29
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystemProviderContext.java
  25. +95
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ParsedUri.java
  26. +43
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/Resources.properties
  27. +630
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/UriParser.java
  28. +51
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileNameParser.java
  29. +225
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileObject.java
  30. +102
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileSystem.java
  31. +60
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileSystemProvider.java
  32. +41
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/ParsedFtpUri.java
  33. +9
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/Resources.properties
  34. +133
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFile.java
  35. +228
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileNameParser.java
  36. +41
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileSystem.java
  37. +72
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileSystemProvider.java
  38. +30
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/ParsedFileUri.java
  39. +5
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/Resources.properties
  40. +30
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/ParsedSmbUri.java
  41. +2
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/Resources.properties
  42. +69
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileNameParser.java
  43. +144
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileObject.java
  44. +36
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileSystem.java
  45. +45
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileSystemProvider.java
  46. +30
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ParsedZipUri.java
  47. +1
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/Resources.properties
  48. +81
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileNameParser.java
  49. +103
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileObject.java
  50. +112
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileSystem.java
  51. +50
    -0
      proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileSystemProvider.java
  52. +587
    -0
      proposal/myrmidon/src/test/org/apache/aut/vfs/BasicFileSystemTestBase.java
  53. +37
    -0
      proposal/myrmidon/src/test/org/apache/aut/vfs/FtpFileSystemTest.java
  54. +64
    -0
      proposal/myrmidon/src/test/org/apache/aut/vfs/LocalFileSystemTest.java
  55. +22
    -0
      proposal/myrmidon/src/test/org/apache/aut/vfs/ReadOnlyFileSystemTestBase.java
  56. +37
    -0
      proposal/myrmidon/src/test/org/apache/aut/vfs/SmbFileSystemTest.java
  57. +255
    -0
      proposal/myrmidon/src/test/org/apache/aut/vfs/WritableFileSystemTestBase.java
  58. +34
    -0
      proposal/myrmidon/src/test/org/apache/aut/vfs/ZipFileSystemTest.java
  59. +587
    -0
      proposal/myrmidon/src/testcases/org/apache/aut/vfs/BasicFileSystemTestBase.java
  60. +37
    -0
      proposal/myrmidon/src/testcases/org/apache/aut/vfs/FtpFileSystemTest.java
  61. +64
    -0
      proposal/myrmidon/src/testcases/org/apache/aut/vfs/LocalFileSystemTest.java
  62. +22
    -0
      proposal/myrmidon/src/testcases/org/apache/aut/vfs/ReadOnlyFileSystemTestBase.java
  63. +37
    -0
      proposal/myrmidon/src/testcases/org/apache/aut/vfs/SmbFileSystemTest.java
  64. +255
    -0
      proposal/myrmidon/src/testcases/org/apache/aut/vfs/WritableFileSystemTestBase.java
  65. +34
    -0
      proposal/myrmidon/src/testcases/org/apache/aut/vfs/ZipFileSystemTest.java

+ 48
- 3
proposal/myrmidon/build.xml View File

@@ -59,7 +59,9 @@ Legal:
<property name="dist.lib" value="${dist.dir}/lib"/>
<property name="dist.ext" value="${dist.dir}/ext"/>

<property name="test.classes" value="${build.dir}/test/classes"/>
<property name="test.dir" value="${build.dir}/test"/>
<property name="test.working.dir" value="${test.dir}/testcases"/>
<property name="test.classes" value="${test.dir}/classes"/>

<property name="constants.file" value="org/apache/myrmidon/Constants.java"/>

@@ -112,6 +114,9 @@ Legal:
<available property="netcomp.present"
classname="com.oroinc.net.ftp.FTPClient"
classpathref="project.class.path" />
<available property="jcifs.present"
classname="jcifs.smb.SmbFile"
classpathref="project.class.path" />
<available property="starteam.present"
classname="com.starbase.util.Platform"
classpathref="project.class.path" />
@@ -187,6 +192,7 @@ Legal:

<property name="ant.package" value="org/apache/tools/ant"/>
<property name="antlib.package" value="org/apache/antlib"/>
<property name="vfs.package" value="org/apache/aut/vfs"/>
<property name="optional.package" value="${ant.package}/taskdefs/optional"/>
<property name="optional.type.package" value="${ant.package}/types/optional"/>
<property name="util.package" value="${ant.package}/util"/>
@@ -221,6 +227,8 @@ Legal:
unless="jdk1.2+" />
<exclude name="${ant.package}/listener/Log4jListener.java"
unless="log4j.present" />
<exclude name="${vfs.package}/provider/ftp/**" unless="netcomp.present"/>
<exclude name="${vfs.package}/provider/smb/**" unless="jcifs.present"/>

<exclude name="${optional.package}/IContract.java" unless="icontract.present" />
<exclude name="${optional.package}/Script.java" unless="bsf.present" />
@@ -278,7 +286,6 @@ Legal:
unless="jdk1.2+" />
</javac>


<copy todir="${build.classes}">
<fileset dir="${java.dir}">
<include name="**/*.properties"/>
@@ -373,6 +380,18 @@ Legal:
</zipfileset>
</jar>

<!--
<jar jarfile="${build.lib}/vfile.atl" basedir="${build.classes}">
<include name="org/apache/antlib/vfile/**" />
<zipfileset dir="${manifest.dir}" fullpath="META-INF/ant-descriptor.xml">
<include name="vfile-ant-descriptor.xml"/>
</zipfileset>
<zipfileset dir="${manifest.dir}" fullpath="META-INF/ant-roles.xml">
<include name="vfile-ant-roles.xml"/>
</zipfileset>
</jar>
-->

<jar jarfile="${build.lib}/selftest.atl"
basedir="${build.classes}"
manifest="${manifest.dir}/selftest.mf">
@@ -436,6 +455,7 @@ Legal:

<!-- Compiles and runs the unit tests -->
<target name="test" depends="compile" if="junit.present">
<!-- Compile the unit tests -->
<mkdir dir="${test.classes}"/>
<javac srcdir="src/testcases"
destdir="${test.classes}"
@@ -444,13 +464,38 @@ Legal:
deprecation="${deprecation}">
<classpath refid="project.class.path"/>
</javac>

<property name="test.local.dir" location="${test.working.dir}/localfs"/>
<property name="test.zip.file" location="${test.working.dir}/zipfs/test.zip"/>

<!-- Prepare test files -->
<delete dir="${test.working.dir}"/>
<copy todir="${test.local.dir}/read-tests">
<fileset dir="etc/testcases/org/apache/aut/vfs/basedir"/>
</copy>
<mkdir dir="${test.local.dir}/read-tests/emptydir"/>
<mkdir dir="${test.working.dir}/zipfs"/>
<zip zipfile="${test.zip.file}">
<zipfileset dir="${test.local.dir}/read-tests" prefix="/basedir"/>
</zip>

<junit printsummary="on"
fork="false">
<formatter type="brief" usefile="false"/>
<classpath refid="project.class.path"/>
<classpath location="${test.classes}"/>

<!-- Pass config to the tests -->
<sysproperty key="test.local.dir" value="${test.local.dir}"/>
<sysproperty key="test.zip.file" value="${test.zip.file}"/>
<sysproperty key="test.smb.uri" value="smb://${vfs.user}:${vfs.password}@${vfs.host}/${vfs.user}/vfs"/>
<sysproperty key="test.ftp.uri" value="ftp://${vfs.user}:${vfs.password}@${vfs.host}/home/${vfs.user}/vfs"/>

<batchtest>
<fileset dir="${test.classes}" includes="**/*Test.class"/>
<fileset dir="${test.classes}" includes="**/*Test.class">
<exclude name="**/SmbFileSystemTest.class" unless="test.smb"/>
<exclude name="**/FtpFileSystemTest.class" unless="test.ftp"/>
</fileset>
</batchtest>
</junit>
</target>


+ 0
- 0
proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/dir1/file1.txt View File


+ 0
- 0
proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/dir1/file2.txt View File


+ 0
- 0
proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/dir1/file3.txt View File


+ 0
- 0
proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/empty.txt View File


+ 2
- 0
proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/file1.txt View File

@@ -0,0 +1,2 @@
This is a test file.
With 2 lines in it.

+ 160
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/FileContent.java View File

@@ -0,0 +1,160 @@
/*
* 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 java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.sql.Date;

/**
* This interface is used to access the data content of a file.
*
* <p>To read from a file, use the {@link #getInputStream} method.
*
* <p>To write to a file, use the {@link #getOutputStream} method. This
* method will create the file and the parent folder, if necessary.
*
* <p>To prevent concurrency problems, only a single <code>InputStream</code>,
* or <code>OutputStream</code> may be open at any time, for each file.
*
* <p>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.
*
* <p>TODO - change to <code>Map getAttributes()</code> instead?
*
* <p>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.
*
* <p>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
* <code>BufferedInputStream</code>.
*
* @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.
*
* <p>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
* <code>BufferedOutputStream</code>.
*
* @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.
*
* <p>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;
}

+ 87
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/FileName.java View File

@@ -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
* <code>/somefolder/somefile</code> is <code>somefile</code>.
*
* <p>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 <code>.</code> and <code>..</code> elements
* have been removed. Also, the path only contains <code>/</code> as its
* separator character. The path always starts with <code>/</code>
*
* <p>The root of a file system has <code>/</code> 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
* <code>resolveName( path, NameScope.FILE_SYSTEM )</code>.
*
* @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;
}

+ 214
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/FileObject.java View File

@@ -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.
*
* <p>Files are arranged in a hierarchy. Each hierachy forms a
* <i>file system</i>. A file system represents things like a local OS
* file system, a windows share, an HTTP server, or the contents of a Zip file.
*
* <p>There are two types of files: <i>Folders</i>, which contain other files,
* and <i>normal files</i>, which contain data, or <i>content</i>. A folder may
* not have any content, and a normal file cannot contain other files.
*
* <h4>File Naming</h4>
*
* <p>TODO - write this.
*
* <h4>Reading and Writing a File</h4>
*
* <p>Reading and writing a file, and all other operations on the file's
* <i>content</i>, is done using the {@link FileContent} object returned
* by {@link #getContent}.
*
* <h4>Creating and Deleting a File</h4>
*
* <p>A file is created using either {@link #create}, or by writing to the
* file using one of the {@link FileContent} methods.
*
* <p>A file is deleted using {@link #delete}. Deletion is recursive, so
* that when a folder is deleted, so are all its child files.
*
* <h4>Finding Files</h4>
*
* <p>Other files in the <i>same</i> file system as this file can be found using:
* <ul>
* <li>{@link #resolveFile} to find another file relative to this file.
* <li>{@link #getChildren} to find the children of this file.
* <li>{@link #getParent} to find the folder containing this file.
* <li>{@link #getRoot} to find the root folder of the file system.
* </ul>
*
* <p>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
* <code>true</code> if this file exists, <code>false</code> 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
* <code>resolveFile( path, NameScope.FILE_SYSTEM )</code>.
*
* @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.
*
* <p>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.
*
* <p>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.
*
* <p>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;
}

+ 41
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/FileSystemException.java View File

@@ -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 );
}
}

+ 94
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/FileSystemManager.java View File

@@ -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.
*
* <p>To locate a {@link FileObject}, use one of the <code>resolveFile()</code>
* methods.</p>
*
* <h4><a name="naming">File Naming</a></h4>
*
* <p>A file system manager can recognise several types of file names:
*
* <ul>
*
* <li><p>Absolute URI. These must start with a scheme, such as
* <code>file:</code> or <code>ftp:</code>, followed by a scheme dependent
* file name. Some examples:</p>
* <pre>
* file:/c:/somefile
* ftp://somewhere.org/somefile
* </pre>
*
* <li><p>Absolute local file name. For example,
* <code>/home/someuser/a-file</code> or <code>c:\dir\somefile.html</code>.
* Elements in the name can be separated using any of the following
* characters: <code>/</code>, <code>\</code>, or the native file separator
* character. For example, the following file names are the same:</p>
* <pre>
* c:\somedir\somefile.xml
* c:/somedir/somefile.xml
* </pre>
*
* <li><p>Relative path. For example: <code>../somefile</code> or
* <code>somedir/file.txt</code>. The file system manager resolves relative
* paths against its <i>base file</i>. Elements in the relative path can be
* separated using <code>/</code>, <code>\</code>, or file system specific
* separator characters. Relative paths may also contain <code>..</code> and
* <code>.</code> elements. See {@link FileObject#resolveFile} for more details.</p>
*
* </ul>
*
* @author Adam Murdoch
*/
public interface FileSystemManager
extends Component
{
String ROLE = "org.apache.aut.vfs.FileSystemManager";

/**
* Returns the base file used to resolve relative paths.
*/
FileObject getBaseFile();

/**
* Locates a file by name. Equivalent to calling
* <code>resolveFile(uri, getBaseName())</code>.
*
* @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
* <a href="#naming">above</a>. That is, the name can be either
* an absolute URI, an absolute file name, or a relative path to
* be resolved against <code>baseFile</code>.
*
* <p>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;
}

+ 52
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/FileType.java View File

@@ -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" ) );
}

+ 62
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/NameScope.java View File

@@ -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.
*
* <p>The supplied name must be a valid element name. That is, it may
* not be empty, or <code>.</code>, or <code>..</code>, 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.
*
* <p>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.
*
* <p>The path may use any mix of <code>/</code>, <code>\</code>, or file
* system specific separators to separate elements in the path. It may
* also contain <code>.</code> and <code>..</code> elements.
*
* <p>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" );
}

+ 2
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/Resources.properties View File

@@ -0,0 +1,2 @@
folder.name=folder
file.name=file

+ 10
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/package.html View File

@@ -0,0 +1,10 @@
<body>
<p>This package contains the interfaces used to access the VFS.</p>

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

</body>

+ 644
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileObject.java View File

@@ -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.
*
* <p>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:
* <ul>
* <li>{@link #isReadOnly} returns false.
* <li>{@link #doGetType} does not return null.
* <li>If this file is a folder, it has no children.
* </ul>
*/
protected void doDelete() throws Exception
{
final String message = REZ.getString( "delete-not-supported.error" );
throw new FileSystemException( message );
}

/**
* Creates this file as a folder. Is only called when:
* <ul>
* <li>{@link #isReadOnly} returns false.
* <li>{@link #doGetType} returns null.
* <li>The parent folder exists or this file is the root of the file
* system.
* </ul>
*/
protected void doCreateFolder() throws Exception
{
final String message = REZ.getString( "create-folder-not-supported.error" );
throw new FileSystemException( message );
}

/**
* Called when the children of this file change.
*/
protected void onChildrenChanged()
{
}

/**
* Returns the size of the file content (in bytes). Is only called if
* {@link #doGetType} returns {@link FileType#FILE}.
*/
protected abstract long doGetContentSize() throws Exception;

/**
* Creates an input stream to read the file content from. Is only called
* if {@link #doGetType} returns {@link FileType#FILE}.
*
* <p>There is guaranteed never to be more than one stream for this file
* (input or output) open at any given time.
*
* <p>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:
* <ul>
* <li>This file is not read-only.
* <li>{@link #doGetType} returns {@link FileType#FILE}, or
* {@link #doGetType} returns null, and the file's parent exists
* and is a folder.
* </ul>
*
* <p>There is guaranteed never to be more than one stream for this file
* (input or output) open at any given time.
*
* <p>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();
}
}

+ 92
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileSystem.java View File

@@ -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;
}
}

+ 83
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileSystemProvider.java View File

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

+ 381
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileContent.java View File

@@ -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;
}
}
}

}

+ 123
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileName.java View File

@@ -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;
}
}

+ 205
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileSystemManager.java View File

@@ -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 );
}
}
}

+ 41
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystem.java View File

@@ -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;
}

+ 31
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystemProvider.java View File

@@ -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;
}

+ 29
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystemProviderContext.java View File

@@ -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;
}

+ 95
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ParsedUri.java View File

@@ -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;
}
}

+ 43
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/Resources.properties View File

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

+ 630
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/UriParser.java View File

@@ -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 <i>not</i> 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.
*
* <p>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:
*
* <pre>
* &lt;scheme> '://' [ &lt;userinfo> '@' ] &lt;hostname> [ ':' &lt;port> ] '/' &lt;path>
* </pre>
*
* <p>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 <scheme>:// part has been removed
* already.
*/
protected String extractUserInfo( StringBuffer name )
{
int maxlen = name.length();
for( int pos = 0; pos < maxlen; pos++ )
{
char ch = name.charAt( pos );
if( ch == '@' )
{
// Found the end of the user info
String userInfo = name.substring( 0, pos );
name.delete( 0, pos + 1 );
return userInfo;
}
if( ch == '/' || ch == '?' )
{
// Not allowed in user info
break;
}
}

// Not found
return null;
}

/**
* Extracts the hostname from a URI. The <scheme>://<userinfo>@ part has
* been removed.
*/
protected String extractHostName( StringBuffer name )
{
int maxlen = name.length();
int pos = 0;
for( ; pos < maxlen; pos++ )
{
char ch = name.charAt( pos );
if( ch == '/' || ch == ';' || ch == '?' || ch == ':'
|| ch == '@' || ch == '&' || ch == '=' || ch == '+'
|| ch == '$' || ch == ',' )
{
break;
}
}
if( pos == 0 )
{
return null;
}

String hostname = name.substring( 0, pos );
name.delete( 0, pos );
return hostname;
}

/**
* Extracts the port from a URI. The <scheme>://<userinfo>@<hostname>
* part has been removed.
*/
protected String extractPort( StringBuffer name )
{
if( name.length() < 1 || name.charAt( 0 ) != ':' )
{
return null;
}
int maxlen = name.length();
int pos = 1;
for( ; pos < maxlen; pos++ )
{
char ch = name.charAt( pos );
if( ch < '0' || ch > '9' )
{
break;
}
}
String port = name.substring( 1, pos );
name.delete( 0, pos );
return port;
}

/**
* Extracts the first element of a path.
*/
protected String extractFirstElement( StringBuffer name )
{
int len = name.length();
if( len < 1 )
{
return null;
}
int startPos = 0;
if( name.charAt( 0 ) == m_separatorChar )
{
startPos = 1;
}
for( int pos = startPos; pos < len; pos++ )
{
if( name.charAt( pos ) == m_separatorChar )
{
// Found a separator
String elem = name.substring( startPos, pos );
name.delete( startPos, pos + 1 );
return elem;
}
}

// No separator
String elem = name.substring( startPos );
name.setLength( 0 );
return elem;
}

/**
* Builds a URI from a root URI and path.
*
* @param rootUri
* The root URI.
*
* @param path
* A <i>normalised</i> path.
*/
public String getUri( String rootUri, String path )
{
StringBuffer uri = new StringBuffer( rootUri );
int len = uri.length();
if( uri.charAt( len - 1 ) == m_separatorChar )
{
uri.delete( len - 1, len );
}
if( !path.startsWith( m_separator ) )
{
uri.append( m_separatorChar );
}
uri.append( path );
return uri.toString();
}

/**
* Returns the base name of a path.
*
* @param path
* A <i>normalised</i> path.
*/
public String getBaseName( String path )
{
int idx = path.lastIndexOf( m_separatorChar );
if( idx == -1 )
{
return path;
}
return path.substring( idx + 1 );
}

/**
* Resolves a path, relative to a base path. If the supplied path
* is an absolute path, it is normalised and returned. If the supplied
* path is a relative path, it is resolved relative to the base path.
*
* @param basePath
* A <i>normalised</i> path.
*
* @param path
* The path to resolve. Does not need to be normalised, but
* does need to be a path (i.e. not an absolute URI).
*
*/
public String resolvePath( String basePath, String path ) throws FileSystemException
{
StringBuffer buffer = new StringBuffer( path );

// Adjust separators
fixSeparators( buffer );

// Determine whether to prepend the base path
if( path.length() == 0 || path.charAt( 0 ) != m_separatorChar )
{
// Supplied path is not absolute
buffer.insert( 0, m_separatorChar );
buffer.insert( 0, basePath );
}

// Normalise the path
normalisePath( buffer );
return buffer.toString();
}

/**
* Returns a child path.
*
* @param parent
* A <i>normalised</i> path.
*
* @param name
* The child name. Must be a valid element name (i.e. no separators, etc).
*/
public String getChildPath( String parent, String name ) throws FileSystemException
{
// Validate the child name
if( name.length() == 0
|| name.equals( "." )
|| name.equals( ".." ) )
{
final String message = REZ.getString( "invalid-childname.error", name );
throw new FileSystemException( message );
}

// Check for separators
if( name.indexOf( m_separatorChar ) != -1 )
{
final String message = REZ.getString( "invalid-childname.error", name );
throw new FileSystemException( message );
}
for( int i = 0; i < m_separators.length; i++ )
{
char separator = m_separators[ i ];
if( name.indexOf( separator ) != -1 )
{
final String message = REZ.getString( "invalid-childname.error", name );
throw new FileSystemException( message );
}
}

if( parent.endsWith( m_separator ) )
{
// Either root, or the parent name already ends with the separator
return parent + name;
}
return parent + m_separatorChar + name;
}

/**
* Returns a parent path, or null if the path has no parent.
*
* @param path
* A <i>normalised</i> path.
*/
public String getParentPath( String path )
{
int idx = path.lastIndexOf( m_separatorChar );
if( idx == -1 || idx == path.length() - 1 )
{
// No parent
return null;
}
if( idx == 0 )
{
// Root is the parent
return m_separator;
}
return path.substring( 0, idx );
}

/**
* Normalises a path. Does the following:
* <ul>
* <li>Normalises separators, where more than one can be used.
* <li>Removes empty path elements.
* <li>Handles '.' and '..' elements.
* <li>Removes trailing separator.
* </ul>
*/
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;
}
}

+ 51
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileNameParser.java View File

@@ -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;
}
}

+ 225
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileObject.java View File

@@ -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 );
}
}
}

+ 102
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileSystem.java View File

@@ -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 );
}
}

+ 60
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileSystemProvider.java View File

@@ -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 );
}
}

+ 41
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/ParsedFtpUri.java View File

@@ -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;
}
}

+ 9
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/Resources.properties View File

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

+ 133
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFile.java View File

@@ -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();
}
}

+ 228
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileNameParser.java View File

@@ -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 <sep>
if( name.length() == 0 || name.charAt( 0 ) != '/' )
{
final String message = REZ.getString( "not-absolute-file-name.error", uri );
throw new FileSystemException( message );
}

// TODO - this isn't always true
return "/";
}
}

/**
* Extracts a Windows root prefix from a name.
*/
private String extractWindowsRootPrefix( String uri,
StringBuffer name )
throws FileSystemException
{
// Looking for:
// ('/'){0, 3} <letter> ':' '/'
// ['/'] '//' <name> '/' <name> ( '/' | <end> )

// Skip over first 3 leading '/' chars
int startPos = 0;
int maxlen = Math.min( 3, name.length() );
for( ; startPos < maxlen && name.charAt( startPos ) == '/'; startPos++ )
{
}
if( startPos == maxlen )
{
// Too many '/'
final String message = REZ.getString( "not-absolute-file-name.error", uri );
throw new FileSystemException( message );
}
name.delete( 0, startPos );

// Look for drive name
String driveName = extractDrivePrefix( name );
if( driveName != null )
{
return driveName;
}

// Look for UNC name
if( startPos < 2 )
{
final String message = REZ.getString( "not-absolute-file-name.error", uri );
throw new FileSystemException( message );
}

return "//" + extractUNCPrefix( uri, name );
}

/**
* Extracts a drive prefix from a path. Leading '/' chars have been removed.
*/
private String extractDrivePrefix( StringBuffer name ) throws FileSystemException
{
// Looking for <letter> ':' '/'
if( name.length() < 3 )
{
// Too short
return null;
}
char ch = name.charAt( 0 );
if( ch == '/' || ch == ':' )
{
// Missing drive letter
return null;
}
if( name.charAt( 1 ) != ':' )
{
// Missing ':'
return null;
}
if( name.charAt( 2 ) != '/' )
{
// Missing separator
return null;
}

String prefix = name.substring( 0, 2 );
name.delete( 0, 2 );
return prefix;
}

/**
* Extracts a UNC name from a path. Leading '/' chars have been removed.
*/
private String extractUNCPrefix( String uri,
StringBuffer name ) throws FileSystemException
{
// Looking for <name> '/' <name> ( '/' | <end> )

// Look for first separator
int maxpos = name.length();
int pos = 0;
for( ; pos < maxpos && name.charAt( pos ) != '/'; pos++ )
{
}
pos++;
if( pos >= maxpos )
{
final String message = REZ.getString( "missing-share-name.error", uri );
throw new FileSystemException( message );
}

// Now have <name> '/'
int startShareName = pos;
for( ; pos < maxpos && name.charAt( pos ) != '/'; pos++ )
{
}
if( pos == startShareName )
{
final String message = REZ.getString( "missing-share-name.error", uri );
throw new FileSystemException( message );
}

// Now have <name> '/' <name> ( '/' | <end> )
String prefix = name.substring( 0, pos );
name.delete( 0, pos );
return prefix;
}
}

+ 41
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileSystem.java View File

@@ -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.local;

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.DefaultFileName;
import org.apache.aut.vfs.provider.FileSystem;

/**
* A local file system.
*
* @author Adam Murdoch
*/
class LocalFileSystem extends AbstractFileSystem implements FileSystem
{
private String m_rootFile;

public LocalFileSystem( DefaultFileName rootName, String rootFile )
{
super( rootName );
m_rootFile = rootFile;
}

/**
* Creates a file object.
*/
protected FileObject createFile( FileName name ) throws FileSystemException
{
// Create the file
String fileName = m_rootFile + name.getPath();
return new LocalFile( this, fileName, name );
}
}

+ 72
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileSystemProvider.java View File

@@ -0,0 +1,72 @@
/*
* 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.FileObject;
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 file system provider, which uses direct file access.
*
* @author Adam Murdoch
*/
public class LocalFileSystemProvider extends AbstractFileSystemProvider
implements FileSystemProvider
{
private LocalFileNameParser m_parser = new LocalFileNameParser();

/**
* Determines if a name is an absolute file name.
*/
public boolean isAbsoluteLocalName( String name )
{
return m_parser.isAbsoluteName( name );
}

/**
* Finds a file by local file name.
*/
public FileObject findFileByLocalName( String name ) throws FileSystemException
{
// TODO - tidy this up, no need to turn the name into an absolute URI,
// and then straight back again
return findFile( "file:" + name );
}

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

+ 30
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/ParsedFileUri.java View File

@@ -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;
}
}

+ 5
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/Resources.properties View File

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

+ 30
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/ParsedSmbUri.java View File

@@ -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;
}
}

+ 2
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/Resources.properties View File

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

+ 69
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileNameParser.java View File

@@ -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;
}
}

+ 144
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileObject.java View File

@@ -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 );
}
}

+ 36
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileSystem.java View File

@@ -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 );
}
}

+ 45
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileSystemProvider.java View File

@@ -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 );
}
}

+ 30
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ParsedZipUri.java View File

@@ -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;
}
}

+ 1
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/Resources.properties View File

@@ -0,0 +1 @@
open-zip-file.error=Could not open Zip file "{0}".

+ 81
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileNameParser.java View File

@@ -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 <name>!<abspath>
// TODO - how does '!' in the file name get escaped?
int maxlen = uri.length();
for( int pos = 0; pos < maxlen; pos++ )
{
if( uri.charAt( pos ) == '!' )
{
String prefix = uri.substring( 0, pos );
uri.delete( 0, pos + 1 );
return prefix;
}
}

// Assume the URI is the Jar file name
String prefix = uri.toString();
uri.setLength( 0 );
return prefix;
}
}

+ 103
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileObject.java View File

@@ -0,0 +1,103 @@
/*
* 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 java.io.InputStream;
import java.util.HashSet;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.apache.aut.vfs.FileName;
import org.apache.aut.vfs.FileObject;
import org.apache.aut.vfs.FileType;
import org.apache.aut.vfs.provider.AbstractFileObject;

/**
* A file in a Zip file system.
*
* @author Adam Murdoch
*/
class ZipFileObject extends AbstractFileObject implements FileObject
{
private ZipEntry m_entry;
private ZipFile m_file;
private FileType m_type;
private HashSet m_children = new HashSet();

public ZipFileObject( FileName name,
ZipEntry entry,
ZipFile zipFile,
ZipFileSystem fs )
{
super( name, fs );
m_type = FileType.FILE;
m_entry = entry;
m_file = zipFile;
}

public ZipFileObject( FileName name, boolean exists, ZipFileSystem fs )
{
super( name, fs );
if( exists )
{
m_type = FileType.FOLDER;
}
// else _type = null
}

/**
* Attaches a child
*/
public void attachChild( FileName childName )
{
m_children.add( childName.getBaseName() );
}

/**
* Returns true if this file is read-only.
*/
protected boolean isReadOnly()
{
return true;
}

/**
* Returns the file's type.
*/
protected FileType doGetType()
{
return m_type;
}

/**
* Lists the children of the file.
*/
protected String[] doListChildren()
{
return (String[])m_children.toArray( new String[ m_children.size() ] );
}

/**
* Returns the size of the file content (in bytes). Is only called if
* {@link #doGetType} returns {@link FileType#FILE}.
*/
protected long doGetContentSize()
{
return m_entry.getSize();
}

/**
* Creates an input stream to read the file content from. Is only called
* if {@link #doGetType} returns {@link FileType#FILE}. The input stream
* returned by this method is guaranteed to be closed before this
* method is called again.
*/
protected InputStream doGetInputStream() throws Exception
{
return m_file.getInputStream( m_entry );
}
}

+ 112
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileSystem.java View File

@@ -0,0 +1,112 @@
/*
* 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 java.io.File;
import java.io.IOException;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
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.DefaultFileName;
import org.apache.aut.vfs.provider.FileSystem;
import org.apache.avalon.excalibur.i18n.Resources;
import org.apache.avalon.excalibur.i18n.ResourceManager;

/**
* A read-only file system for Zip/Jar files.
*
* @author Adam Murdoch
*/
public class ZipFileSystem extends AbstractFileSystem implements FileSystem
{
private static final Resources REZ
= ResourceManager.getPackageResources( ZipFileSystem.class );

private File m_file;
private ZipFile m_zipFile;

public ZipFileSystem( DefaultFileName rootName, File file ) throws FileSystemException
{
super( rootName );
m_file = file;

// Open the Zip file
if( !file.exists() )
{
// Don't need to do anything
return;
}

try
{
m_zipFile = new ZipFile( m_file );
}
catch( IOException ioe )
{
final String message = REZ.getString( "open-zip-file.error", m_file );
throw new FileSystemException( message, ioe );
}

// Build the index
Enumeration entries = m_zipFile.entries();
while( entries.hasMoreElements() )
{
ZipEntry entry = (ZipEntry)entries.nextElement();
FileName name = rootName.resolveName( entry.getName() );

// Create the file
ZipFileObject fileObj;
if( entry.isDirectory() )
{
if( getFile( name ) != null )
{
// Already created implicitly
continue;
}
fileObj = new ZipFileObject( name, true, this );
}
else
{
fileObj = new ZipFileObject( name, entry, m_zipFile, this );
}
putFile( fileObj );

// Make sure all ancestors exist
// TODO - create these on demand
ZipFileObject parent = null;
for( FileName parentName = name.getParent();
parentName != null;
fileObj = parent, parentName = parentName.getParent() )
{
// Locate the parent
parent = (ZipFileObject)getFile( parentName );
if( parent == null )
{
parent = new ZipFileObject( parentName, true, this );
putFile( parent );
}

// Attach child to parent
parent.attachChild( fileObj.getName() );
}
}
}

/**
* Creates a file object.
*/
protected FileObject createFile( FileName name ) throws FileSystemException
{
// This is only called for files which do not exist in the Zip file
return new ZipFileObject( name, false, this );
}
}

+ 50
- 0
proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileSystemProvider.java View File

@@ -0,0 +1,50 @@
/*
* 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 java.io.File;
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 file system provider for Zip/Jar files. Provides read-only file
* systems, for local Zip files only.
*
* @author Adam Murdoch
*/
public class ZipFileSystemProvider extends AbstractFileSystemProvider
implements FileSystemProvider
{
private ZipFileNameParser m_parser = new ZipFileNameParser();

/**
* 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
{
// Locate the Zip file
ParsedZipUri zipUri = (ParsedZipUri)uri;
String fileName = zipUri.getZipFile();
// TODO - use the context to resolve zip file to a FileObject
File file = new File( fileName ).getAbsoluteFile();
DefaultFileName name = new DefaultFileName( m_parser, zipUri.getRootURI(), "/" );
return new ZipFileSystem( name, file );
}
}

+ 587
- 0
proposal/myrmidon/src/test/org/apache/aut/vfs/BasicFileSystemTestBase.java View File

@@ -0,0 +1,587 @@
/*
* 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 java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import junit.framework.TestCase;
import org.apache.aut.vfs.provider.DefaultFileSystemManager;

/**
* File system test cases, which verifies the structure and naming
* functionality.
*
* Works from a base folder, and assumes a particular structure under
* that base folder.
*
* @author Adam Murdoch
*/
public abstract class BasicFileSystemTestBase extends TestCase
{
protected FileObject m_baseFolder;
protected DefaultFileSystemManager m_manager;

// Contents of "file1.txt"
private String m_charContent;

public BasicFileSystemTestBase( String name )
{
super( name );
}

/**
* Builds the expected folder structure.
*/
private FileInfo buildExpectedStructure()
{
// Build the expected structure
FileInfo base = new FileInfo( "test", FileType.FOLDER );
base.addChild( new FileInfo( "file1.txt", FileType.FILE ) );
base.addChild( new FileInfo( "empty.txt", FileType.FILE ) );
base.addChild( new FileInfo( "emptydir", FileType.FOLDER ) );

FileInfo dir = new FileInfo( "dir1", FileType.FOLDER );
base.addChild( dir );
dir.addChild( new FileInfo( "file1.txt", FileType.FILE ) );
dir.addChild( new FileInfo( "file2.txt", FileType.FILE ) );
dir.addChild( new FileInfo( "file3.txt", FileType.FILE ) );
return base;
}

/**
* Returns the URI for the base folder.
*/
protected abstract String getBaseFolderURI();

/**
* Sets up the test
*/
protected void setUp() throws Exception
{
// Create the file system manager
m_manager = new DefaultFileSystemManager();

// Locate the base folder
m_baseFolder = m_manager.resolveFile( getBaseFolderURI() );

// Build the expected content of "file1.txt"
String eol = System.getProperty( "line.separator" );
m_charContent = "This is a test file." + eol + "With 2 lines in it." + eol;
}

/**
* Tests resolution of absolute URI.
*/
public void testAbsoluteURI() throws Exception
{
// Try fetching base folder again by its URI
String uri = m_baseFolder.getName().getURI();
FileObject file = m_manager.resolveFile( uri );

assertSame( "file object", m_baseFolder, file );
}

/**
* Tests resolution of relative file names via the FS manager
*/
public void testRelativeURI() throws Exception
{
// Build base dir
m_manager.setBaseFile( m_baseFolder );

// Locate the base dir
FileObject file = m_manager.resolveFile( "." );
assertSame( "file object", m_baseFolder, file );

// Locate a child
file = m_manager.resolveFile( "some-child" );
assertSame( "file object", m_baseFolder, file.getParent() );

// Locate a descendent
file = m_manager.resolveFile( "some-folder/some-file" );
assertSame( "file object", m_baseFolder, file.getParent().getParent() );

// Locate parent
file = m_manager.resolveFile( ".." );
assertSame( "file object", m_baseFolder.getParent(), file );
}

/**
* Tests the root file name.
*/
public void testRootFileName() throws Exception
{
// Locate the root file
FileName rootName = m_baseFolder.getRoot().getName();

// Test that the root path is "/"
assertEquals( "root path", "/", rootName.getPath() );

// Test that the root basname is ""
assertEquals( "root base name", "", rootName.getBaseName() );

// Test that the root name has no parent
assertNull( "root parent", rootName.getParent() );
}

/**
* Tests child file names.
*/
public void testChildName() throws Exception
{
FileName baseName = m_baseFolder.getName();
String basePath = baseName.getPath();
FileName name = baseName.resolveName( "some-child", NameScope.CHILD );

// Test path is absolute
assertTrue( "is absolute", basePath.startsWith( "/" ) );

// Test base name
assertEquals( "base name", "some-child", name.getBaseName() );

// Test absolute path
assertEquals( "absolute path", basePath + "/some-child", name.getPath() );

// Test parent path
assertEquals( "parent absolute path", basePath, name.getParent().getPath() );

// Try using a compound name to find a child
try
{
FileName name2 = name.resolveName( "a/b", NameScope.CHILD );
assertTrue( false );
}
catch( FileSystemException e )
{
}

// Try using a empty name to find a child
try
{
FileName name2 = name.resolveName( "", NameScope.CHILD );
assertTrue( false );
}
catch( FileSystemException e )
{
}

// Try using '.' to find a child
try
{
FileName name2 = name.resolveName( ".", NameScope.CHILD );
assertTrue( false );
}
catch( FileSystemException e )
{
}

// Try using '..' to find a child
try
{
FileName name2 = name.resolveName( "..", NameScope.CHILD );
assertTrue( false );
}
catch( FileSystemException e )
{
}
}

/**
* Checks that a relative name resolves to the expected absolute path.
*/
private void assertSameName(String expectedPath,
FileName baseName,
String relName ) throws Exception
{
FileName name = baseName.resolveName(relName);
assertEquals( expectedPath, name.getPath() );

// Replace the separators
relName.replace('\\', '/');
name = baseName.resolveName(relName);
assertEquals( expectedPath, name.getPath() );

// And again
relName.replace('/', '\\');
name = baseName.resolveName(relName);
assertEquals( expectedPath, name.getPath() );
}

/**
* Tests relative name resolution, relative to the base folder.
*/
public void testNameResolution() throws Exception
{
FileName baseName = m_baseFolder.getName();
String parentPath = baseName.getParent().getPath();
String path = baseName.getPath();
String childPath = path + "/some-child";

// Test empty relative path
assertSameName( path, baseName, "" );

// Test . relative path
assertSameName( path, baseName, "." );

// Test ./ relative path
assertSameName( path, baseName, "./" );

// Test .// relative path
assertSameName( path, baseName, ".//" );

// Test .///.///. relative path
assertSameName( path, baseName, ".///.///." );
assertSameName( path, baseName, "./\\/.\\//." );

// Test <elem>/.. relative path
assertSameName( path, baseName, "a/.." );

// Test .. relative path
assertSameName( parentPath, baseName, ".." );

// Test ../ relative path
assertSameName( parentPath, baseName, "../" );

// Test ..//./ relative path
assertSameName( parentPath, baseName, "..//./" );
assertSameName( parentPath, baseName, "..//.\\" );

// Test <elem>/../.. relative path
assertSameName( parentPath, baseName, "a/../.." );

// Test <elem> relative path
assertSameName( childPath, baseName, "some-child" );

// Test ./<elem> relative path
assertSameName( childPath, baseName, "./some-child" );

// Test ./<elem>/ relative path
assertSameName( childPath, baseName, "./some-child/" );

// Test <elem>/././././ relative path
assertSameName( childPath, baseName, "./some-child/././././" );

// Test <elem>/../<elem> relative path
assertSameName( childPath, baseName, "a/../some-child" );

// Test <elem>/<elem>/../../<elem> relative path
assertSameName( childPath, baseName, "a/b/../../some-child" );
}

/**
* Tests relative name resolution, relative to the root file.
*/
public void testNameResolutionRoot() throws Exception
{
FileName rootName = m_baseFolder.getRoot().getName();
}

/**
* Walks the folder structure, asserting it contains exactly the
* expected files and folders.
*/
public void testStructure() throws Exception
{
// Setup the structure
List queueExpected = new ArrayList();
FileInfo baseInfo = buildExpectedStructure();
queueExpected.add( baseInfo );

List queueActual = new ArrayList();
queueActual.add( m_baseFolder );

while( queueActual.size() > 0 )
{
FileObject file = (FileObject)queueActual.remove( 0 );
FileInfo info = (FileInfo)queueExpected.remove( 0 );

// Check the type is correct
assertSame( file.getType(), info._type );

if( info._type == FileType.FILE )
{
continue;
}

// Check children
FileObject[] children = file.getChildren();

// Make sure all children were found
assertNotNull( children );
assertEquals( "count children of \"" + file.getName() + "\"", info._children.size(), children.length );

// Recursively check each child
for( int i = 0; i < children.length; i++ )
{
FileObject child = children[ i ];
FileInfo childInfo = (FileInfo)info._children.get( child.getName().getBaseName() );

// Make sure the child is expected
assertNotNull( childInfo );

// Add to the queue of files to check
queueExpected.add( childInfo );
queueActual.add( child );
}
}
}

/**
* Tests existence determination.
*/
public void testExists() throws Exception
{
// Test a file
FileObject file = m_baseFolder.resolveFile( "file1.txt" );
assertTrue( "file exists", file.exists() );

// Test a folder
file = m_baseFolder.resolveFile( "dir1" );
assertTrue( "folder exists", file.exists() );

// Test an unknown file
file = m_baseFolder.resolveFile( "unknown-child" );
assertTrue( "unknown file does not exist", !file.exists() );

// Test an unknown file in an unknown folder
file = m_baseFolder.resolveFile( "unknown-folder/unknown-child" );
assertTrue( "unknown file does not exist", !file.exists() );
}

/**
* Tests type determination.
*/
public void testType() throws Exception
{
// Test a file
FileObject file = m_baseFolder.resolveFile( "file1.txt" );
assertSame( FileType.FILE, file.getType() );

// Test a folder
file = m_baseFolder.resolveFile( "dir1" );
assertSame( FileType.FOLDER, file.getType() );

// Test an unknown file
file = m_baseFolder.resolveFile( "unknown-child" );
FileSystemException exc = null;
try
{
file.getType();
}
catch( FileSystemException e )
{
exc = e;
}
assertNotNull( exc );
}

/**
* Tests parent identity
*/
public void testParent() throws FileSystemException
{
// Test when both exist
FileObject folder = m_baseFolder.resolveFile( "dir1" );
FileObject child = folder.resolveFile( "file3.txt" );
assertTrue( "folder exists", folder.exists() );
assertTrue( "child exists", child.exists() );
assertSame( folder, child.getParent() );

// Test when file does not exist
child = folder.resolveFile( "unknown-file" );
assertTrue( "folder exists", folder.exists() );
assertTrue( "child does not exist", !child.exists() );
assertSame( folder, child.getParent() );

// Test when neither exists
folder = m_baseFolder.resolveFile( "unknown-folder" );
child = folder.resolveFile( "unknown-file" );
assertTrue( "folder does not exist", !folder.exists() );
assertTrue( "child does not exist", !child.exists() );
assertSame( folder, child.getParent() );

// Test root of the file system has no parent
FileObject root = m_baseFolder.getRoot();
assertNull( "root has null parent", root.getParent() );
}

/**
* Tests that children cannot be listed for non-folders.
*/
public void testChildren() throws FileSystemException
{
// Check for file
FileObject file = m_baseFolder.resolveFile( "file1.txt" );
assertSame( FileType.FILE, file.getType() );
try
{
file.getChildren();
assertTrue( false );
}
catch( FileSystemException e )
{
}

// Should be able to get child by name
file = file.resolveFile( "some-child" );
assertNotNull( file );

// Check for unknown file
file = m_baseFolder.resolveFile( "unknown-file" );
assertTrue( !file.exists() );
try
{
file.getChildren();
assertTrue( false );
}
catch( FileSystemException e )
{
}

// Should be able to get child by name
FileObject child = file.resolveFile( "some-child" );
assertNotNull( child );
}

/**
* Tests content.
*/
public void testContent() throws Exception
{
// Test non-empty file
FileObject file = m_baseFolder.resolveFile( "file1.txt" );
FileContent content = file.getContent();
assertSameContent(m_charContent, content);

// Test empty file
file = m_baseFolder.resolveFile( "empty.txt" );
content = file.getContent();
assertSameContent("", content);
}

/**
* Asserts that the content of a file is the same as expected. Checks the
* length reported by getSize() is correct, then reads the content as
* a byte stream, and as a char stream, and compares the result with
* the expected content. Assumes files are encoded using UTF-8.
*/
public void assertSameContent( String expected, FileContent content ) throws Exception
{
// Get file content as a binary stream
byte[] expectedBin = expected.getBytes( "utf-8" );

// Check lengths
assertEquals( "same content length", expectedBin.length, content.getSize() );

// Read content into byte array
InputStream instr = content.getInputStream();
ByteArrayOutputStream outstr = new ByteArrayOutputStream();
byte[] buffer = new byte[ 256 ];
int nread = 0;
while( nread >= 0 )
{
outstr.write( buffer, 0, nread );
nread = instr.read( buffer );
}

// Compare
assertTrue( "same binary content", Arrays.equals( expectedBin, outstr.toByteArray() ) );
}

/**
* Tests that folders and unknown files have no content.
*/
public void testNoContent() throws Exception
{
// Try getting the content of a folder
FileObject folder = m_baseFolder.resolveFile( "dir1" );
try
{
folder.getContent();
assertTrue( false );
}
catch( FileSystemException e )
{
}

// Try getting the content of an unknown file
FileObject unknownFile = m_baseFolder.resolveFile( "unknown-file" );
FileContent content = unknownFile.getContent();
try
{
content.getInputStream();
assertTrue( false );
}
catch( FileSystemException e )
{
}
try
{
content.getSize();
assertTrue( false );
}
catch( FileSystemException e )
{
}
}

/**
* Tests that content and file objects are usable after being closed.
*/
public void testReuse() throws Exception
{
// Get the test file
FileObject file = m_baseFolder.resolveFile( "file1.txt" );
assertEquals( FileType.FILE, file.getType() );

// Get the file content
FileContent content = file.getContent();
assertSameContent( m_charContent, content );

// Read the content again
content = file.getContent();
assertSameContent( m_charContent, content );

// Close the content + file
content.close();
file.close();

// Read the content again
content = file.getContent();
assertSameContent( m_charContent, content );
}

/**
* Info about a file.
*/
private static final class FileInfo
{
String _baseName;
FileType _type;
Map _children = new HashMap();

public FileInfo( String name, FileType type )
{
_baseName = name;
_type = type;
}

/** Adds a child. */
public void addChild( FileInfo child )
{
_children.put( child._baseName, child );
}
}
}

+ 37
- 0
proposal/myrmidon/src/test/org/apache/aut/vfs/FtpFileSystemTest.java View File

@@ -0,0 +1,37 @@
/*
* 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;

/**
* Tests for FTP file systems.
*
* @author Adam Murdoch
*/
public class FtpFileSystemTest extends WritableFileSystemTestBase
{
public FtpFileSystemTest( String name )
{
super( name );
}

/**
* Returns the URI for the base folder.
*/
protected String getBaseFolderURI()
{
return System.getProperty( "test.ftp.uri" ) + "/read-tests";
}

/**
* Returns the URI for the area to do tests in.
*/
protected String getWriteFolderURI()
{
return System.getProperty( "test.ftp.uri" ) + "/write-tests";
}
}

+ 64
- 0
proposal/myrmidon/src/test/org/apache/aut/vfs/LocalFileSystemTest.java View File

@@ -0,0 +1,64 @@
/*
* 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 java.io.File;

/**
* Tests for the local file system.
*
* @author Adam Murdoch
*/
public class LocalFileSystemTest extends WritableFileSystemTestBase
{
private File m_baseDir;

public LocalFileSystemTest( String name )
{
super( name );
String baseDir = System.getProperty( "test.local.dir" );
m_baseDir = new File( baseDir );
}

/**
* Returns the URI for the base folder.
*/
protected String getBaseFolderURI()
{
String testDir = new File( m_baseDir, "read-tests" ).getAbsolutePath();
String uri = "file:/" + testDir;
return uri;
}

/**
* Returns the URI for the area to do tests in.
*/
protected String getWriteFolderURI()
{
String testDir = new File( m_baseDir, "write-tests" ).getAbsolutePath();
String uri = "file:/" + testDir;
return uri;
}

/**
* Tests resolution of an absolute file name.
*/
public void testAbsoluteFileName() throws Exception
{
// Locate file by absolute file name
String fileName = new File( "testdir" ).getAbsolutePath();
FileObject absFile = m_manager.resolveFile( fileName );

// Locate file by URI
String uri = "file://" + fileName.replace( File.separatorChar, '/' );
FileObject uriFile = m_manager.resolveFile( uri );

assertSame( "file object", absFile, uriFile );
}

}

+ 22
- 0
proposal/myrmidon/src/test/org/apache/aut/vfs/ReadOnlyFileSystemTestBase.java View File

@@ -0,0 +1,22 @@
/*
* 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;

/**
* File system tests which check that a read-only file system cannot be
* changed.
*
* @author Adam Murdoch
*/
public abstract class ReadOnlyFileSystemTestBase extends BasicFileSystemTestBase
{
public ReadOnlyFileSystemTestBase( String name )
{
super( name );
}
}

+ 37
- 0
proposal/myrmidon/src/test/org/apache/aut/vfs/SmbFileSystemTest.java View File

@@ -0,0 +1,37 @@
/*
* 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;

/**
* Tests for the SMB file system.
*
* @author Adam Murdoch
*/
public class SmbFileSystemTest extends WritableFileSystemTestBase
{
public SmbFileSystemTest( String name )
{
super( name );
}

/**
* Returns the URI for the base folder.
*/
protected String getBaseFolderURI()
{
return System.getProperty( "test.smb.uri" ) + "/read-tests";
}

/**
* Returns the URI for the area to do tests in.
*/
protected String getWriteFolderURI()
{
return System.getProperty( "test.smb.uri" ) + "/write-tests";
}
}

+ 255
- 0
proposal/myrmidon/src/test/org/apache/aut/vfs/WritableFileSystemTestBase.java View File

@@ -0,0 +1,255 @@
/*
* 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 java.io.Writer;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Set;

/**
* File system test that check that a file system can be modified.
*
* @author Adam Murdoch
*/
public abstract class WritableFileSystemTestBase extends BasicFileSystemTestBase
{
public WritableFileSystemTestBase( String name )
{
super( name );
}

/**
* Returns the URI for the area to do tests in.
*/
protected abstract String getWriteFolderURI();

/**
* Sets up a scratch folder for the test to use.
*/
protected FileObject createScratchFolder() throws Exception
{
FileObject scratchFolder = m_manager.resolveFile( getWriteFolderURI() );

// Make sure the test folder is empty
scratchFolder.delete();
scratchFolder.create( FileType.FOLDER );

return scratchFolder;
}

/**
* Tests folder creation.
*/
public void testFolderCreate() throws Exception
{
FileObject scratchFolder = createScratchFolder();

// Create direct child of the test folder
FileObject folder = scratchFolder.resolveFile( "dir1" );
assertTrue( !folder.exists() );
folder.create( FileType.FOLDER );
assertTrue( folder.exists() );
assertEquals( 0, folder.getChildren().length );

// Create a descendant, where the intermediate folders don't exist
folder = scratchFolder.resolveFile( "dir2/dir1/dir1" );
assertTrue( !folder.exists() );
assertTrue( !folder.getParent().exists() );
assertTrue( !folder.getParent().getParent().exists() );
folder.create( FileType.FOLDER );
assertTrue( folder.exists() );
assertEquals( 0, folder.getChildren().length );
assertTrue( folder.getParent().exists() );
assertTrue( folder.getParent().getParent().exists() );

// Test creating a folder that already exists
folder.create( FileType.FOLDER );
}

/**
* Tests file creation
*/
public void testFileCreate() throws Exception
{
FileObject scratchFolder = createScratchFolder();

// Create direct child of the test folder
FileObject file = scratchFolder.resolveFile( "file1.txt" );
assertTrue( !file.exists() );
file.create( FileType.FILE );
assertTrue( file.exists() );
assertEquals( 0, file.getContent().getSize() );

// Create a descendant, where the intermediate folders don't exist
file = scratchFolder.resolveFile( "dir1/dir1/file1.txt" );
assertTrue( !file.exists() );
assertTrue( !file.getParent().exists() );
assertTrue( !file.getParent().getParent().exists() );
file.create( FileType.FILE );
assertTrue( file.exists() );
assertEquals( 0, file.getContent().getSize() );
assertTrue( file.getParent().exists() );
assertTrue( file.getParent().getParent().exists() );

// Test creating a file that already exists
file.create( FileType.FILE );
}

/**
* Tests file/folder creation with mismatched types.
*/
public void testFileCreateMismatched() throws Exception
{
FileObject scratchFolder = createScratchFolder();

// Create a test file and folder
FileObject file = scratchFolder.resolveFile( "dir1/file1.txt" );
file.create( FileType.FILE );
assertEquals( FileType.FILE, file.getType() );

FileObject folder = scratchFolder.resolveFile( "dir1/dir2" );
folder.create( FileType.FOLDER );
assertEquals( FileType.FOLDER, folder.getType() );

// Attempt to create a file that already exists as a folder
try
{
folder.create( FileType.FILE );
assertTrue( false );
}
catch( FileSystemException exc )
{
}

// Attempt to create a folder that already exists as a file
try
{
file.create( FileType.FOLDER );
assertTrue( false );
}
catch( FileSystemException exc )
{
}

// Attempt to create a folder as a child of a file
FileObject folder2 = file.resolveFile( "some-child" );
try
{
folder2.create( FileType.FOLDER );
assertTrue( false );
}
catch( FileSystemException exc )
{
}
}

/**
* Tests deletion
*/
public void testDelete() throws Exception
{
// Set-up the test structure
FileObject folder = createScratchFolder();
folder.resolveFile( "file1.txt" ).create( FileType.FILE );
folder.resolveFile( "emptydir" ).create( FileType.FOLDER );
folder.resolveFile( "dir1/file1.txt" ).create( FileType.FILE );
folder.resolveFile( "dir1/dir2/file2.txt" ).create( FileType.FILE );

// Delete a file
FileObject file = folder.resolveFile( "file1.txt" );
assertTrue( file.exists() );
file.delete();
assertTrue( !file.exists() );

// Delete an empty folder
file = folder.resolveFile( "emptydir" );
assertTrue( file.exists() );
file.delete();
assertTrue( !file.exists() );

// Recursive delete
file = folder.resolveFile( "dir1" );
FileObject file2 = file.resolveFile( "dir2/file2.txt" );
assertTrue( file.exists() );
assertTrue( file2.exists() );
file.delete();
assertTrue( !file.exists() );
assertTrue( !file2.exists() );

// Delete a file that does not exist
file = folder.resolveFile( "some-folder/some-file" );
assertTrue( !file.exists() );
file.delete();
assertTrue( !file.exists() );
}

/**
* Test that children are handled correctly by create and delete.
*/
public void testListChildren() throws Exception
{
FileObject folder = createScratchFolder();
HashSet names = new HashSet();

// Make sure the folder is empty
assertEquals( 0, folder.getChildren().length );

// Create a child folder
folder.resolveFile( "dir1" ).create( FileType.FOLDER );
names.add( "dir1" );
assertSameFileSet( names, folder.getChildren() );

// Create a child file
folder.resolveFile( "file1.html" ).create( FileType.FILE );
names.add( "file1.html" );
assertSameFileSet( names, folder.getChildren() );

// Create a descendent
folder.resolveFile( "dir2/file1.txt" ).create( FileType.FILE );
names.add( "dir2" );
assertSameFileSet( names, folder.getChildren() );

// Create a child file via an output stream
OutputStream outstr = folder.resolveFile( "file2.txt" ).getContent().getOutputStream();
outstr.close();
names.add( "file2.txt" );
assertSameFileSet( names, folder.getChildren() );

// Delete a child folder
folder.resolveFile( "dir1" ).delete();
names.remove( "dir1" );
assertSameFileSet( names, folder.getChildren() );

// Delete a child file
folder.resolveFile( "file1.html" ).delete();
names.remove( "file1.html" );
assertSameFileSet( names, folder.getChildren() );

// Recreate the folder
folder.delete();
folder.create( FileType.FOLDER );
assertEquals( 0, folder.getChildren().length );
}

/**
* Ensures the names of a set of files match an expected set.
*/
private void assertSameFileSet( Set names, FileObject[] files )
{
// Make sure the sets are the same length
assertEquals( names.size(), files.length );

// Check for unexpected names
for( int i = 0; i < files.length; i++ )
{
FileObject file = files[ i ];
assertTrue( names.contains( file.getName().getBaseName() ) );
}
}
}

+ 34
- 0
proposal/myrmidon/src/test/org/apache/aut/vfs/ZipFileSystemTest.java View File

@@ -0,0 +1,34 @@
/*
* 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 java.io.File;

/**
* Tests for the Zip file system.
*
* @author Adam Murdoch
*/
public class ZipFileSystemTest extends ReadOnlyFileSystemTestBase
{
public ZipFileSystemTest( String name )
{
super( name );
}

/**
* Returns the URI for the base folder.
*/
protected String getBaseFolderURI()
{
String zipFileName = System.getProperty( "test.zip.file" );
String zipFile = new File( zipFileName ).getAbsolutePath();
String uri = "zip:" + zipFile + "!basedir";
return uri;
}
}

+ 587
- 0
proposal/myrmidon/src/testcases/org/apache/aut/vfs/BasicFileSystemTestBase.java View File

@@ -0,0 +1,587 @@
/*
* 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 java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import junit.framework.TestCase;
import org.apache.aut.vfs.provider.DefaultFileSystemManager;

/**
* File system test cases, which verifies the structure and naming
* functionality.
*
* Works from a base folder, and assumes a particular structure under
* that base folder.
*
* @author Adam Murdoch
*/
public abstract class BasicFileSystemTestBase extends TestCase
{
protected FileObject m_baseFolder;
protected DefaultFileSystemManager m_manager;

// Contents of "file1.txt"
private String m_charContent;

public BasicFileSystemTestBase( String name )
{
super( name );
}

/**
* Builds the expected folder structure.
*/
private FileInfo buildExpectedStructure()
{
// Build the expected structure
FileInfo base = new FileInfo( "test", FileType.FOLDER );
base.addChild( new FileInfo( "file1.txt", FileType.FILE ) );
base.addChild( new FileInfo( "empty.txt", FileType.FILE ) );
base.addChild( new FileInfo( "emptydir", FileType.FOLDER ) );

FileInfo dir = new FileInfo( "dir1", FileType.FOLDER );
base.addChild( dir );
dir.addChild( new FileInfo( "file1.txt", FileType.FILE ) );
dir.addChild( new FileInfo( "file2.txt", FileType.FILE ) );
dir.addChild( new FileInfo( "file3.txt", FileType.FILE ) );
return base;
}

/**
* Returns the URI for the base folder.
*/
protected abstract String getBaseFolderURI();

/**
* Sets up the test
*/
protected void setUp() throws Exception
{
// Create the file system manager
m_manager = new DefaultFileSystemManager();

// Locate the base folder
m_baseFolder = m_manager.resolveFile( getBaseFolderURI() );

// Build the expected content of "file1.txt"
String eol = System.getProperty( "line.separator" );
m_charContent = "This is a test file." + eol + "With 2 lines in it." + eol;
}

/**
* Tests resolution of absolute URI.
*/
public void testAbsoluteURI() throws Exception
{
// Try fetching base folder again by its URI
String uri = m_baseFolder.getName().getURI();
FileObject file = m_manager.resolveFile( uri );

assertSame( "file object", m_baseFolder, file );
}

/**
* Tests resolution of relative file names via the FS manager
*/
public void testRelativeURI() throws Exception
{
// Build base dir
m_manager.setBaseFile( m_baseFolder );

// Locate the base dir
FileObject file = m_manager.resolveFile( "." );
assertSame( "file object", m_baseFolder, file );

// Locate a child
file = m_manager.resolveFile( "some-child" );
assertSame( "file object", m_baseFolder, file.getParent() );

// Locate a descendent
file = m_manager.resolveFile( "some-folder/some-file" );
assertSame( "file object", m_baseFolder, file.getParent().getParent() );

// Locate parent
file = m_manager.resolveFile( ".." );
assertSame( "file object", m_baseFolder.getParent(), file );
}

/**
* Tests the root file name.
*/
public void testRootFileName() throws Exception
{
// Locate the root file
FileName rootName = m_baseFolder.getRoot().getName();

// Test that the root path is "/"
assertEquals( "root path", "/", rootName.getPath() );

// Test that the root basname is ""
assertEquals( "root base name", "", rootName.getBaseName() );

// Test that the root name has no parent
assertNull( "root parent", rootName.getParent() );
}

/**
* Tests child file names.
*/
public void testChildName() throws Exception
{
FileName baseName = m_baseFolder.getName();
String basePath = baseName.getPath();
FileName name = baseName.resolveName( "some-child", NameScope.CHILD );

// Test path is absolute
assertTrue( "is absolute", basePath.startsWith( "/" ) );

// Test base name
assertEquals( "base name", "some-child", name.getBaseName() );

// Test absolute path
assertEquals( "absolute path", basePath + "/some-child", name.getPath() );

// Test parent path
assertEquals( "parent absolute path", basePath, name.getParent().getPath() );

// Try using a compound name to find a child
try
{
FileName name2 = name.resolveName( "a/b", NameScope.CHILD );
assertTrue( false );
}
catch( FileSystemException e )
{
}

// Try using a empty name to find a child
try
{
FileName name2 = name.resolveName( "", NameScope.CHILD );
assertTrue( false );
}
catch( FileSystemException e )
{
}

// Try using '.' to find a child
try
{
FileName name2 = name.resolveName( ".", NameScope.CHILD );
assertTrue( false );
}
catch( FileSystemException e )
{
}

// Try using '..' to find a child
try
{
FileName name2 = name.resolveName( "..", NameScope.CHILD );
assertTrue( false );
}
catch( FileSystemException e )
{
}
}

/**
* Checks that a relative name resolves to the expected absolute path.
*/
private void assertSameName(String expectedPath,
FileName baseName,
String relName ) throws Exception
{
FileName name = baseName.resolveName(relName);
assertEquals( expectedPath, name.getPath() );

// Replace the separators
relName.replace('\\', '/');
name = baseName.resolveName(relName);
assertEquals( expectedPath, name.getPath() );

// And again
relName.replace('/', '\\');
name = baseName.resolveName(relName);
assertEquals( expectedPath, name.getPath() );
}

/**
* Tests relative name resolution, relative to the base folder.
*/
public void testNameResolution() throws Exception
{
FileName baseName = m_baseFolder.getName();
String parentPath = baseName.getParent().getPath();
String path = baseName.getPath();
String childPath = path + "/some-child";

// Test empty relative path
assertSameName( path, baseName, "" );

// Test . relative path
assertSameName( path, baseName, "." );

// Test ./ relative path
assertSameName( path, baseName, "./" );

// Test .// relative path
assertSameName( path, baseName, ".//" );

// Test .///.///. relative path
assertSameName( path, baseName, ".///.///." );
assertSameName( path, baseName, "./\\/.\\//." );

// Test <elem>/.. relative path
assertSameName( path, baseName, "a/.." );

// Test .. relative path
assertSameName( parentPath, baseName, ".." );

// Test ../ relative path
assertSameName( parentPath, baseName, "../" );

// Test ..//./ relative path
assertSameName( parentPath, baseName, "..//./" );
assertSameName( parentPath, baseName, "..//.\\" );

// Test <elem>/../.. relative path
assertSameName( parentPath, baseName, "a/../.." );

// Test <elem> relative path
assertSameName( childPath, baseName, "some-child" );

// Test ./<elem> relative path
assertSameName( childPath, baseName, "./some-child" );

// Test ./<elem>/ relative path
assertSameName( childPath, baseName, "./some-child/" );

// Test <elem>/././././ relative path
assertSameName( childPath, baseName, "./some-child/././././" );

// Test <elem>/../<elem> relative path
assertSameName( childPath, baseName, "a/../some-child" );

// Test <elem>/<elem>/../../<elem> relative path
assertSameName( childPath, baseName, "a/b/../../some-child" );
}

/**
* Tests relative name resolution, relative to the root file.
*/
public void testNameResolutionRoot() throws Exception
{
FileName rootName = m_baseFolder.getRoot().getName();
}

/**
* Walks the folder structure, asserting it contains exactly the
* expected files and folders.
*/
public void testStructure() throws Exception
{
// Setup the structure
List queueExpected = new ArrayList();
FileInfo baseInfo = buildExpectedStructure();
queueExpected.add( baseInfo );

List queueActual = new ArrayList();
queueActual.add( m_baseFolder );

while( queueActual.size() > 0 )
{
FileObject file = (FileObject)queueActual.remove( 0 );
FileInfo info = (FileInfo)queueExpected.remove( 0 );

// Check the type is correct
assertSame( file.getType(), info._type );

if( info._type == FileType.FILE )
{
continue;
}

// Check children
FileObject[] children = file.getChildren();

// Make sure all children were found
assertNotNull( children );
assertEquals( "count children of \"" + file.getName() + "\"", info._children.size(), children.length );

// Recursively check each child
for( int i = 0; i < children.length; i++ )
{
FileObject child = children[ i ];
FileInfo childInfo = (FileInfo)info._children.get( child.getName().getBaseName() );

// Make sure the child is expected
assertNotNull( childInfo );

// Add to the queue of files to check
queueExpected.add( childInfo );
queueActual.add( child );
}
}
}

/**
* Tests existence determination.
*/
public void testExists() throws Exception
{
// Test a file
FileObject file = m_baseFolder.resolveFile( "file1.txt" );
assertTrue( "file exists", file.exists() );

// Test a folder
file = m_baseFolder.resolveFile( "dir1" );
assertTrue( "folder exists", file.exists() );

// Test an unknown file
file = m_baseFolder.resolveFile( "unknown-child" );
assertTrue( "unknown file does not exist", !file.exists() );

// Test an unknown file in an unknown folder
file = m_baseFolder.resolveFile( "unknown-folder/unknown-child" );
assertTrue( "unknown file does not exist", !file.exists() );
}

/**
* Tests type determination.
*/
public void testType() throws Exception
{
// Test a file
FileObject file = m_baseFolder.resolveFile( "file1.txt" );
assertSame( FileType.FILE, file.getType() );

// Test a folder
file = m_baseFolder.resolveFile( "dir1" );
assertSame( FileType.FOLDER, file.getType() );

// Test an unknown file
file = m_baseFolder.resolveFile( "unknown-child" );
FileSystemException exc = null;
try
{
file.getType();
}
catch( FileSystemException e )
{
exc = e;
}
assertNotNull( exc );
}

/**
* Tests parent identity
*/
public void testParent() throws FileSystemException
{
// Test when both exist
FileObject folder = m_baseFolder.resolveFile( "dir1" );
FileObject child = folder.resolveFile( "file3.txt" );
assertTrue( "folder exists", folder.exists() );
assertTrue( "child exists", child.exists() );
assertSame( folder, child.getParent() );

// Test when file does not exist
child = folder.resolveFile( "unknown-file" );
assertTrue( "folder exists", folder.exists() );
assertTrue( "child does not exist", !child.exists() );
assertSame( folder, child.getParent() );

// Test when neither exists
folder = m_baseFolder.resolveFile( "unknown-folder" );
child = folder.resolveFile( "unknown-file" );
assertTrue( "folder does not exist", !folder.exists() );
assertTrue( "child does not exist", !child.exists() );
assertSame( folder, child.getParent() );

// Test root of the file system has no parent
FileObject root = m_baseFolder.getRoot();
assertNull( "root has null parent", root.getParent() );
}

/**
* Tests that children cannot be listed for non-folders.
*/
public void testChildren() throws FileSystemException
{
// Check for file
FileObject file = m_baseFolder.resolveFile( "file1.txt" );
assertSame( FileType.FILE, file.getType() );
try
{
file.getChildren();
assertTrue( false );
}
catch( FileSystemException e )
{
}

// Should be able to get child by name
file = file.resolveFile( "some-child" );
assertNotNull( file );

// Check for unknown file
file = m_baseFolder.resolveFile( "unknown-file" );
assertTrue( !file.exists() );
try
{
file.getChildren();
assertTrue( false );
}
catch( FileSystemException e )
{
}

// Should be able to get child by name
FileObject child = file.resolveFile( "some-child" );
assertNotNull( child );
}

/**
* Tests content.
*/
public void testContent() throws Exception
{
// Test non-empty file
FileObject file = m_baseFolder.resolveFile( "file1.txt" );
FileContent content = file.getContent();
assertSameContent(m_charContent, content);

// Test empty file
file = m_baseFolder.resolveFile( "empty.txt" );
content = file.getContent();
assertSameContent("", content);
}

/**
* Asserts that the content of a file is the same as expected. Checks the
* length reported by getSize() is correct, then reads the content as
* a byte stream, and as a char stream, and compares the result with
* the expected content. Assumes files are encoded using UTF-8.
*/
public void assertSameContent( String expected, FileContent content ) throws Exception
{
// Get file content as a binary stream
byte[] expectedBin = expected.getBytes( "utf-8" );

// Check lengths
assertEquals( "same content length", expectedBin.length, content.getSize() );

// Read content into byte array
InputStream instr = content.getInputStream();
ByteArrayOutputStream outstr = new ByteArrayOutputStream();
byte[] buffer = new byte[ 256 ];
int nread = 0;
while( nread >= 0 )
{
outstr.write( buffer, 0, nread );
nread = instr.read( buffer );
}

// Compare
assertTrue( "same binary content", Arrays.equals( expectedBin, outstr.toByteArray() ) );
}

/**
* Tests that folders and unknown files have no content.
*/
public void testNoContent() throws Exception
{
// Try getting the content of a folder
FileObject folder = m_baseFolder.resolveFile( "dir1" );
try
{
folder.getContent();
assertTrue( false );
}
catch( FileSystemException e )
{
}

// Try getting the content of an unknown file
FileObject unknownFile = m_baseFolder.resolveFile( "unknown-file" );
FileContent content = unknownFile.getContent();
try
{
content.getInputStream();
assertTrue( false );
}
catch( FileSystemException e )
{
}
try
{
content.getSize();
assertTrue( false );
}
catch( FileSystemException e )
{
}
}

/**
* Tests that content and file objects are usable after being closed.
*/
public void testReuse() throws Exception
{
// Get the test file
FileObject file = m_baseFolder.resolveFile( "file1.txt" );
assertEquals( FileType.FILE, file.getType() );

// Get the file content
FileContent content = file.getContent();
assertSameContent( m_charContent, content );

// Read the content again
content = file.getContent();
assertSameContent( m_charContent, content );

// Close the content + file
content.close();
file.close();

// Read the content again
content = file.getContent();
assertSameContent( m_charContent, content );
}

/**
* Info about a file.
*/
private static final class FileInfo
{
String _baseName;
FileType _type;
Map _children = new HashMap();

public FileInfo( String name, FileType type )
{
_baseName = name;
_type = type;
}

/** Adds a child. */
public void addChild( FileInfo child )
{
_children.put( child._baseName, child );
}
}
}

+ 37
- 0
proposal/myrmidon/src/testcases/org/apache/aut/vfs/FtpFileSystemTest.java View File

@@ -0,0 +1,37 @@
/*
* 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;

/**
* Tests for FTP file systems.
*
* @author Adam Murdoch
*/
public class FtpFileSystemTest extends WritableFileSystemTestBase
{
public FtpFileSystemTest( String name )
{
super( name );
}

/**
* Returns the URI for the base folder.
*/
protected String getBaseFolderURI()
{
return System.getProperty( "test.ftp.uri" ) + "/read-tests";
}

/**
* Returns the URI for the area to do tests in.
*/
protected String getWriteFolderURI()
{
return System.getProperty( "test.ftp.uri" ) + "/write-tests";
}
}

+ 64
- 0
proposal/myrmidon/src/testcases/org/apache/aut/vfs/LocalFileSystemTest.java View File

@@ -0,0 +1,64 @@
/*
* 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 java.io.File;

/**
* Tests for the local file system.
*
* @author Adam Murdoch
*/
public class LocalFileSystemTest extends WritableFileSystemTestBase
{
private File m_baseDir;

public LocalFileSystemTest( String name )
{
super( name );
String baseDir = System.getProperty( "test.local.dir" );
m_baseDir = new File( baseDir );
}

/**
* Returns the URI for the base folder.
*/
protected String getBaseFolderURI()
{
String testDir = new File( m_baseDir, "read-tests" ).getAbsolutePath();
String uri = "file:/" + testDir;
return uri;
}

/**
* Returns the URI for the area to do tests in.
*/
protected String getWriteFolderURI()
{
String testDir = new File( m_baseDir, "write-tests" ).getAbsolutePath();
String uri = "file:/" + testDir;
return uri;
}

/**
* Tests resolution of an absolute file name.
*/
public void testAbsoluteFileName() throws Exception
{
// Locate file by absolute file name
String fileName = new File( "testdir" ).getAbsolutePath();
FileObject absFile = m_manager.resolveFile( fileName );

// Locate file by URI
String uri = "file://" + fileName.replace( File.separatorChar, '/' );
FileObject uriFile = m_manager.resolveFile( uri );

assertSame( "file object", absFile, uriFile );
}

}

+ 22
- 0
proposal/myrmidon/src/testcases/org/apache/aut/vfs/ReadOnlyFileSystemTestBase.java View File

@@ -0,0 +1,22 @@
/*
* 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;

/**
* File system tests which check that a read-only file system cannot be
* changed.
*
* @author Adam Murdoch
*/
public abstract class ReadOnlyFileSystemTestBase extends BasicFileSystemTestBase
{
public ReadOnlyFileSystemTestBase( String name )
{
super( name );
}
}

+ 37
- 0
proposal/myrmidon/src/testcases/org/apache/aut/vfs/SmbFileSystemTest.java View File

@@ -0,0 +1,37 @@
/*
* 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;

/**
* Tests for the SMB file system.
*
* @author Adam Murdoch
*/
public class SmbFileSystemTest extends WritableFileSystemTestBase
{
public SmbFileSystemTest( String name )
{
super( name );
}

/**
* Returns the URI for the base folder.
*/
protected String getBaseFolderURI()
{
return System.getProperty( "test.smb.uri" ) + "/read-tests";
}

/**
* Returns the URI for the area to do tests in.
*/
protected String getWriteFolderURI()
{
return System.getProperty( "test.smb.uri" ) + "/write-tests";
}
}

+ 255
- 0
proposal/myrmidon/src/testcases/org/apache/aut/vfs/WritableFileSystemTestBase.java View File

@@ -0,0 +1,255 @@
/*
* 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 java.io.Writer;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Set;

/**
* File system test that check that a file system can be modified.
*
* @author Adam Murdoch
*/
public abstract class WritableFileSystemTestBase extends BasicFileSystemTestBase
{
public WritableFileSystemTestBase( String name )
{
super( name );
}

/**
* Returns the URI for the area to do tests in.
*/
protected abstract String getWriteFolderURI();

/**
* Sets up a scratch folder for the test to use.
*/
protected FileObject createScratchFolder() throws Exception
{
FileObject scratchFolder = m_manager.resolveFile( getWriteFolderURI() );

// Make sure the test folder is empty
scratchFolder.delete();
scratchFolder.create( FileType.FOLDER );

return scratchFolder;
}

/**
* Tests folder creation.
*/
public void testFolderCreate() throws Exception
{
FileObject scratchFolder = createScratchFolder();

// Create direct child of the test folder
FileObject folder = scratchFolder.resolveFile( "dir1" );
assertTrue( !folder.exists() );
folder.create( FileType.FOLDER );
assertTrue( folder.exists() );
assertEquals( 0, folder.getChildren().length );

// Create a descendant, where the intermediate folders don't exist
folder = scratchFolder.resolveFile( "dir2/dir1/dir1" );
assertTrue( !folder.exists() );
assertTrue( !folder.getParent().exists() );
assertTrue( !folder.getParent().getParent().exists() );
folder.create( FileType.FOLDER );
assertTrue( folder.exists() );
assertEquals( 0, folder.getChildren().length );
assertTrue( folder.getParent().exists() );
assertTrue( folder.getParent().getParent().exists() );

// Test creating a folder that already exists
folder.create( FileType.FOLDER );
}

/**
* Tests file creation
*/
public void testFileCreate() throws Exception
{
FileObject scratchFolder = createScratchFolder();

// Create direct child of the test folder
FileObject file = scratchFolder.resolveFile( "file1.txt" );
assertTrue( !file.exists() );
file.create( FileType.FILE );
assertTrue( file.exists() );
assertEquals( 0, file.getContent().getSize() );

// Create a descendant, where the intermediate folders don't exist
file = scratchFolder.resolveFile( "dir1/dir1/file1.txt" );
assertTrue( !file.exists() );
assertTrue( !file.getParent().exists() );
assertTrue( !file.getParent().getParent().exists() );
file.create( FileType.FILE );
assertTrue( file.exists() );
assertEquals( 0, file.getContent().getSize() );
assertTrue( file.getParent().exists() );
assertTrue( file.getParent().getParent().exists() );

// Test creating a file that already exists
file.create( FileType.FILE );
}

/**
* Tests file/folder creation with mismatched types.
*/
public void testFileCreateMismatched() throws Exception
{
FileObject scratchFolder = createScratchFolder();

// Create a test file and folder
FileObject file = scratchFolder.resolveFile( "dir1/file1.txt" );
file.create( FileType.FILE );
assertEquals( FileType.FILE, file.getType() );

FileObject folder = scratchFolder.resolveFile( "dir1/dir2" );
folder.create( FileType.FOLDER );
assertEquals( FileType.FOLDER, folder.getType() );

// Attempt to create a file that already exists as a folder
try
{
folder.create( FileType.FILE );
assertTrue( false );
}
catch( FileSystemException exc )
{
}

// Attempt to create a folder that already exists as a file
try
{
file.create( FileType.FOLDER );
assertTrue( false );
}
catch( FileSystemException exc )
{
}

// Attempt to create a folder as a child of a file
FileObject folder2 = file.resolveFile( "some-child" );
try
{
folder2.create( FileType.FOLDER );
assertTrue( false );
}
catch( FileSystemException exc )
{
}
}

/**
* Tests deletion
*/
public void testDelete() throws Exception
{
// Set-up the test structure
FileObject folder = createScratchFolder();
folder.resolveFile( "file1.txt" ).create( FileType.FILE );
folder.resolveFile( "emptydir" ).create( FileType.FOLDER );
folder.resolveFile( "dir1/file1.txt" ).create( FileType.FILE );
folder.resolveFile( "dir1/dir2/file2.txt" ).create( FileType.FILE );

// Delete a file
FileObject file = folder.resolveFile( "file1.txt" );
assertTrue( file.exists() );
file.delete();
assertTrue( !file.exists() );

// Delete an empty folder
file = folder.resolveFile( "emptydir" );
assertTrue( file.exists() );
file.delete();
assertTrue( !file.exists() );

// Recursive delete
file = folder.resolveFile( "dir1" );
FileObject file2 = file.resolveFile( "dir2/file2.txt" );
assertTrue( file.exists() );
assertTrue( file2.exists() );
file.delete();
assertTrue( !file.exists() );
assertTrue( !file2.exists() );

// Delete a file that does not exist
file = folder.resolveFile( "some-folder/some-file" );
assertTrue( !file.exists() );
file.delete();
assertTrue( !file.exists() );
}

/**
* Test that children are handled correctly by create and delete.
*/
public void testListChildren() throws Exception
{
FileObject folder = createScratchFolder();
HashSet names = new HashSet();

// Make sure the folder is empty
assertEquals( 0, folder.getChildren().length );

// Create a child folder
folder.resolveFile( "dir1" ).create( FileType.FOLDER );
names.add( "dir1" );
assertSameFileSet( names, folder.getChildren() );

// Create a child file
folder.resolveFile( "file1.html" ).create( FileType.FILE );
names.add( "file1.html" );
assertSameFileSet( names, folder.getChildren() );

// Create a descendent
folder.resolveFile( "dir2/file1.txt" ).create( FileType.FILE );
names.add( "dir2" );
assertSameFileSet( names, folder.getChildren() );

// Create a child file via an output stream
OutputStream outstr = folder.resolveFile( "file2.txt" ).getContent().getOutputStream();
outstr.close();
names.add( "file2.txt" );
assertSameFileSet( names, folder.getChildren() );

// Delete a child folder
folder.resolveFile( "dir1" ).delete();
names.remove( "dir1" );
assertSameFileSet( names, folder.getChildren() );

// Delete a child file
folder.resolveFile( "file1.html" ).delete();
names.remove( "file1.html" );
assertSameFileSet( names, folder.getChildren() );

// Recreate the folder
folder.delete();
folder.create( FileType.FOLDER );
assertEquals( 0, folder.getChildren().length );
}

/**
* Ensures the names of a set of files match an expected set.
*/
private void assertSameFileSet( Set names, FileObject[] files )
{
// Make sure the sets are the same length
assertEquals( names.size(), files.length );

// Check for unexpected names
for( int i = 0; i < files.length; i++ )
{
FileObject file = files[ i ];
assertTrue( names.contains( file.getName().getBaseName() ) );
}
}
}

+ 34
- 0
proposal/myrmidon/src/testcases/org/apache/aut/vfs/ZipFileSystemTest.java View File

@@ -0,0 +1,34 @@
/*
* 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 java.io.File;

/**
* Tests for the Zip file system.
*
* @author Adam Murdoch
*/
public class ZipFileSystemTest extends ReadOnlyFileSystemTestBase
{
public ZipFileSystemTest( String name )
{
super( name );
}

/**
* Returns the URI for the base folder.
*/
protected String getBaseFolderURI()
{
String zipFileName = System.getProperty( "test.zip.file" );
String zipFile = new File( zipFileName ).getAbsolutePath();
String uri = "zip:" + zipFile + "!basedir";
return uri;
}
}

Loading…
Cancel
Save