ant-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From adammurd...@apache.org
Subject cvs commit: jakarta-ant/proposal/myrmidon/src/testcases/org/apache/aut/vfs BasicFileSystemTestBase.java FtpFileSystemTest.java LocalFileSystemTest.java ReadOnlyFileSystemTestBase.java SmbFileSystemTest.java WritableFileSystemTestBase.java ZipFileSystemTest.java
Date Sat, 02 Feb 2002 03:29:09 GMT
adammurdoch    02/02/01 19:29:09

  Modified:    proposal/myrmidon build.xml
  Added:       proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir
                        empty.txt file1.txt
               proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/dir1
                        file1.txt file2.txt file3.txt
               proposal/myrmidon/src/java/org/apache/aut/vfs
                        FileContent.java FileName.java FileObject.java
                        FileSystemException.java FileSystemManager.java
                        FileType.java NameScope.java Resources.properties
                        package.html
               proposal/myrmidon/src/java/org/apache/aut/vfs/provider
                        AbstractFileObject.java AbstractFileSystem.java
                        AbstractFileSystemProvider.java
                        DefaultFileContent.java DefaultFileName.java
                        DefaultFileSystemManager.java FileSystem.java
                        FileSystemProvider.java
                        FileSystemProviderContext.java ParsedUri.java
                        Resources.properties UriParser.java
               proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp
                        FtpFileNameParser.java FtpFileObject.java
                        FtpFileSystem.java FtpFileSystemProvider.java
                        ParsedFtpUri.java Resources.properties
               proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local
                        LocalFile.java LocalFileNameParser.java
                        LocalFileSystem.java LocalFileSystemProvider.java
                        ParsedFileUri.java Resources.properties
               proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb
                        ParsedSmbUri.java Resources.properties
                        SmbFileNameParser.java SmbFileObject.java
                        SmbFileSystem.java SmbFileSystemProvider.java
               proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip
                        ParsedZipUri.java Resources.properties
                        ZipFileNameParser.java ZipFileObject.java
                        ZipFileSystem.java ZipFileSystemProvider.java
               proposal/myrmidon/src/testcases/org/apache/aut/vfs
                        BasicFileSystemTestBase.java FtpFileSystemTest.java
                        LocalFileSystemTest.java
                        ReadOnlyFileSystemTestBase.java
                        SmbFileSystemTest.java
                        WritableFileSystemTestBase.java
                        ZipFileSystemTest.java
  Log:
  Added VFS proposal.
  
  Revision  Changes    Path
  1.43      +48 -3     jakarta-ant/proposal/myrmidon/build.xml
  
  Index: build.xml
  ===================================================================
  RCS file: /home/cvs/jakarta-ant/proposal/myrmidon/build.xml,v
  retrieving revision 1.42
  retrieving revision 1.43
  diff -u -r1.42 -r1.43
  --- build.xml	25 Jan 2002 23:56:27 -0000	1.42
  +++ build.xml	2 Feb 2002 03:29:07 -0000	1.43
  @@ -59,7 +59,9 @@
     <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 @@
       <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 @@
   
       <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 @@
                  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 @@
                  unless="jdk1.2+" />
       </javac>
   
  -
       <copy todir="${build.classes}">
         <fileset dir="${java.dir}">
           <include name="**/*.properties"/>
  @@ -373,6 +380,18 @@
         </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 @@
   
     <!-- 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 @@
              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>
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/empty.txt
  
  	<<Binary file>>
  
  
  1.1                  jakarta-ant/proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/file1.txt
  
  Index: file1.txt
  ===================================================================
  This is a test file.
  With 2 lines in it.
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/dir1/file1.txt
  
  	<<Binary file>>
  
  
  1.1                  jakarta-ant/proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/dir1/file2.txt
  
  	<<Binary file>>
  
  
  1.1                  jakarta-ant/proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/dir1/file3.txt
  
  	<<Binary file>>
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/FileContent.java
  
  Index: FileContent.java
  ===================================================================
  /*
   * 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;
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/FileName.java
  
  Index: FileName.java
  ===================================================================
  /*
   * 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;
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/FileObject.java
  
  Index: FileObject.java
  ===================================================================
  /*
   * 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;
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/FileSystemException.java
  
  Index: FileSystemException.java
  ===================================================================
  /*
   * 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 );
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/FileSystemManager.java
  
  Index: FileSystemManager.java
  ===================================================================
  /*
   * 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;
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/FileType.java
  
  Index: FileType.java
  ===================================================================
  /*
   * 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" ) );
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/NameScope.java
  
  Index: NameScope.java
  ===================================================================
  /*
   * 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" );
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/Resources.properties
  
  Index: Resources.properties
  ===================================================================
  folder.name=folder
  file.name=file
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/package.html
  
  Index: package.html
  ===================================================================
  <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>
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileObject.java
  
  Index: AbstractFileObject.java
  ===================================================================
  /*
   * 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();
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileSystem.java
  
  Index: AbstractFileSystem.java
  ===================================================================
  /*
   * 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;
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileSystemProvider.java
  
  Index: AbstractFileSystemProvider.java
  ===================================================================
  /*
   * 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;
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileContent.java
  
  Index: DefaultFileContent.java
  ===================================================================
  /*
   * 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;
              }
          }
      }
  
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileName.java
  
  Index: DefaultFileName.java
  ===================================================================
  /*
   * 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;
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileSystemManager.java
  
  Index: DefaultFileSystemManager.java
  ===================================================================
  /*
   * 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 );
          }
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystem.java
  
  Index: FileSystem.java
  ===================================================================
  /*
   * 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;
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystemProvider.java
  
  Index: FileSystemProvider.java
  ===================================================================
  /*
   * 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;
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystemProviderContext.java
  
  Index: FileSystemProviderContext.java
  ===================================================================
  /*
   * 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;
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ParsedUri.java
  
  Index: ParsedUri.java
  ===================================================================
  /*
   * 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;
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/Resources.properties
  
  Index: Resources.properties
  ===================================================================
  # 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}".
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/UriParser.java
  
  Index: UriParser.java
  ===================================================================
  /*
   * 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;
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileNameParser.java
  
  Index: FtpFileNameParser.java
  ===================================================================
  /*
   * 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;
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileObject.java
  
  Index: FtpFileObject.java
  ===================================================================
  /*
   * 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 );
          }
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileSystem.java
  
  Index: FtpFileSystem.java
  ===================================================================
  /*
   * 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 );
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileSystemProvider.java
  
  Index: FtpFileSystemProvider.java
  ===================================================================
  /*
   * 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 );
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/ParsedFtpUri.java
  
  Index: ParsedFtpUri.java
  ===================================================================
  /*
   * 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;
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/Resources.properties
  
  Index: Resources.properties
  ===================================================================
  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}".
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFile.java
  
  Index: LocalFile.java
  ===================================================================
  /*
   * 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();
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileNameParser.java
  
  Index: LocalFileNameParser.java
  ===================================================================
  /*
   * 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;
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileSystem.java
  
  Index: LocalFileSystem.java
  ===================================================================
  /*
   * 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 );
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileSystemProvider.java
  
  Index: LocalFileSystemProvider.java
  ===================================================================
  /*
   * 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 );
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/ParsedFileUri.java
  
  Index: ParsedFileUri.java
  ===================================================================
  /*
   * 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;
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/Resources.properties
  
  Index: Resources.properties
  ===================================================================
  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}".
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/ParsedSmbUri.java
  
  Index: ParsedSmbUri.java
  ===================================================================
  /*
   * 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;
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/Resources.properties
  
  Index: Resources.properties
  ===================================================================
  missing-share-name.error=The share name is missing from URI "{0}".
  get-type.error=Could not detemine the type of "{0}".
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileNameParser.java
  
  Index: SmbFileNameParser.java
  ===================================================================
  /*
   * 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;
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileObject.java
  
  Index: SmbFileObject.java
  ===================================================================
  /*
   * 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 );
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileSystem.java
  
  Index: SmbFileSystem.java
  ===================================================================
  /*
   * 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 );
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileSystemProvider.java
  
  Index: SmbFileSystemProvider.java
  ===================================================================
  /*
   * 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 );
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ParsedZipUri.java
  
  Index: ParsedZipUri.java
  ===================================================================
  /*
   * 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.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/Resources.properties
  
  Index: Resources.properties
  ===================================================================
  open-zip-file.error=Could not open Zip file "{0}".
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileNameParser.java
  
  Index: ZipFileNameParser.java
  ===================================================================
  /*
   * 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;
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileObject.java
  
  Index: ZipFileObject.java
  ===================================================================
  /*
   * 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 );
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileSystem.java
  
  Index: ZipFileSystem.java
  ===================================================================
  /*
   * 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 );
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileSystemProvider.java
  
  Index: ZipFileSystemProvider.java
  ===================================================================
  /*
   * 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 );
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/testcases/org/apache/aut/vfs/BasicFileSystemTestBase.java
  
  Index: BasicFileSystemTestBase.java
  ===================================================================
  /*
   * 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 );
          }
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/testcases/org/apache/aut/vfs/FtpFileSystemTest.java
  
  Index: FtpFileSystemTest.java
  ===================================================================
  /*
   * 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";
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/testcases/org/apache/aut/vfs/LocalFileSystemTest.java
  
  Index: LocalFileSystemTest.java
  ===================================================================
  /*
   * 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 );
      }
  
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/testcases/org/apache/aut/vfs/ReadOnlyFileSystemTestBase.java
  
  Index: ReadOnlyFileSystemTestBase.java
  ===================================================================
  /*
   * 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 );
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/testcases/org/apache/aut/vfs/SmbFileSystemTest.java
  
  Index: SmbFileSystemTest.java
  ===================================================================
  /*
   * 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";
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/testcases/org/apache/aut/vfs/WritableFileSystemTestBase.java
  
  Index: WritableFileSystemTestBase.java
  ===================================================================
  /*
   * 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() ) );
          }
      }
  }
  
  
  
  1.1                  jakarta-ant/proposal/myrmidon/src/testcases/org/apache/aut/vfs/ZipFileSystemTest.java
  
  Index: ZipFileSystemTest.java
  ===================================================================
  /*
   * 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;
      }
  }
  
  
  

--
To unsubscribe, e-mail:   <mailto:ant-dev-unsubscribe@jakarta.apache.org>
For additional commands, e-mail: <mailto:ant-dev-help@jakarta.apache.org>


Mime
View raw message