From 1e702432eb3051decf5e14a4882d6adfc1d24853 Mon Sep 17 00:00:00 2001 From: adammurdoch Date: Sat, 2 Feb 2002 03:29:09 +0000 Subject: [PATCH] Added VFS proposal. git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@271053 13f79535-47bb-0310-9956-ffa450edef68 --- proposal/myrmidon/build.xml | 51 +- .../org/apache/aut/vfs/basedir/dir1/file1.txt | 0 .../org/apache/aut/vfs/basedir/dir1/file2.txt | 0 .../org/apache/aut/vfs/basedir/dir1/file3.txt | 0 .../org/apache/aut/vfs/basedir/empty.txt | 0 .../org/apache/aut/vfs/basedir/file1.txt | 2 + .../java/org/apache/aut/vfs/FileContent.java | 160 +++++ .../src/java/org/apache/aut/vfs/FileName.java | 87 +++ .../java/org/apache/aut/vfs/FileObject.java | 214 ++++++ .../apache/aut/vfs/FileSystemException.java | 41 ++ .../org/apache/aut/vfs/FileSystemManager.java | 94 +++ .../src/java/org/apache/aut/vfs/FileType.java | 52 ++ .../java/org/apache/aut/vfs/NameScope.java | 62 ++ .../org/apache/aut/vfs/Resources.properties | 2 + .../src/java/org/apache/aut/vfs/package.html | 10 + .../aut/vfs/provider/AbstractFileObject.java | 644 ++++++++++++++++++ .../aut/vfs/provider/AbstractFileSystem.java | 92 +++ .../provider/AbstractFileSystemProvider.java | 83 +++ .../aut/vfs/provider/DefaultFileContent.java | 381 +++++++++++ .../aut/vfs/provider/DefaultFileName.java | 123 ++++ .../provider/DefaultFileSystemManager.java | 205 ++++++ .../apache/aut/vfs/provider/FileSystem.java | 41 ++ .../aut/vfs/provider/FileSystemProvider.java | 31 + .../provider/FileSystemProviderContext.java | 29 + .../apache/aut/vfs/provider/ParsedUri.java | 95 +++ .../aut/vfs/provider/Resources.properties | 43 ++ .../apache/aut/vfs/provider/UriParser.java | 630 +++++++++++++++++ .../vfs/provider/ftp/FtpFileNameParser.java | 51 ++ .../aut/vfs/provider/ftp/FtpFileObject.java | 225 ++++++ .../aut/vfs/provider/ftp/FtpFileSystem.java | 102 +++ .../provider/ftp/FtpFileSystemProvider.java | 60 ++ .../aut/vfs/provider/ftp/ParsedFtpUri.java | 41 ++ .../aut/vfs/provider/ftp/Resources.properties | 9 + .../aut/vfs/provider/local/LocalFile.java | 133 ++++ .../provider/local/LocalFileNameParser.java | 228 +++++++ .../vfs/provider/local/LocalFileSystem.java | 41 ++ .../local/LocalFileSystemProvider.java | 72 ++ .../aut/vfs/provider/local/ParsedFileUri.java | 30 + .../vfs/provider/local/Resources.properties | 5 + .../aut/vfs/provider/smb/ParsedSmbUri.java | 30 + .../aut/vfs/provider/smb/Resources.properties | 2 + .../vfs/provider/smb/SmbFileNameParser.java | 69 ++ .../aut/vfs/provider/smb/SmbFileObject.java | 144 ++++ .../aut/vfs/provider/smb/SmbFileSystem.java | 36 + .../provider/smb/SmbFileSystemProvider.java | 45 ++ .../aut/vfs/provider/zip/ParsedZipUri.java | 30 + .../aut/vfs/provider/zip/Resources.properties | 1 + .../vfs/provider/zip/ZipFileNameParser.java | 81 +++ .../aut/vfs/provider/zip/ZipFileObject.java | 103 +++ .../aut/vfs/provider/zip/ZipFileSystem.java | 112 +++ .../provider/zip/ZipFileSystemProvider.java | 50 ++ .../aut/vfs/BasicFileSystemTestBase.java | 587 ++++++++++++++++ .../org/apache/aut/vfs/FtpFileSystemTest.java | 37 + .../apache/aut/vfs/LocalFileSystemTest.java | 64 ++ .../aut/vfs/ReadOnlyFileSystemTestBase.java | 22 + .../org/apache/aut/vfs/SmbFileSystemTest.java | 37 + .../aut/vfs/WritableFileSystemTestBase.java | 255 +++++++ .../org/apache/aut/vfs/ZipFileSystemTest.java | 34 + .../aut/vfs/BasicFileSystemTestBase.java | 587 ++++++++++++++++ .../org/apache/aut/vfs/FtpFileSystemTest.java | 37 + .../apache/aut/vfs/LocalFileSystemTest.java | 64 ++ .../aut/vfs/ReadOnlyFileSystemTestBase.java | 22 + .../org/apache/aut/vfs/SmbFileSystemTest.java | 37 + .../aut/vfs/WritableFileSystemTestBase.java | 255 +++++++ .../org/apache/aut/vfs/ZipFileSystemTest.java | 34 + 65 files changed, 6941 insertions(+), 3 deletions(-) create mode 100644 proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/dir1/file1.txt create mode 100644 proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/dir1/file2.txt create mode 100644 proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/dir1/file3.txt create mode 100644 proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/empty.txt create mode 100644 proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/file1.txt create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/FileContent.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/FileName.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/FileObject.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/FileSystemException.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/FileSystemManager.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/FileType.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/NameScope.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/Resources.properties create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/package.html create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileObject.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileSystem.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileSystemProvider.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileContent.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileName.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileSystemManager.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystem.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystemProvider.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystemProviderContext.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ParsedUri.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/Resources.properties create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/UriParser.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileNameParser.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileObject.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileSystem.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileSystemProvider.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/ParsedFtpUri.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/Resources.properties create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFile.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileNameParser.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileSystem.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileSystemProvider.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/ParsedFileUri.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/Resources.properties create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/ParsedSmbUri.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/Resources.properties create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileNameParser.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileObject.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileSystem.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileSystemProvider.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ParsedZipUri.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/Resources.properties create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileNameParser.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileObject.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileSystem.java create mode 100644 proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileSystemProvider.java create mode 100644 proposal/myrmidon/src/test/org/apache/aut/vfs/BasicFileSystemTestBase.java create mode 100644 proposal/myrmidon/src/test/org/apache/aut/vfs/FtpFileSystemTest.java create mode 100644 proposal/myrmidon/src/test/org/apache/aut/vfs/LocalFileSystemTest.java create mode 100644 proposal/myrmidon/src/test/org/apache/aut/vfs/ReadOnlyFileSystemTestBase.java create mode 100644 proposal/myrmidon/src/test/org/apache/aut/vfs/SmbFileSystemTest.java create mode 100644 proposal/myrmidon/src/test/org/apache/aut/vfs/WritableFileSystemTestBase.java create mode 100644 proposal/myrmidon/src/test/org/apache/aut/vfs/ZipFileSystemTest.java create mode 100644 proposal/myrmidon/src/testcases/org/apache/aut/vfs/BasicFileSystemTestBase.java create mode 100644 proposal/myrmidon/src/testcases/org/apache/aut/vfs/FtpFileSystemTest.java create mode 100644 proposal/myrmidon/src/testcases/org/apache/aut/vfs/LocalFileSystemTest.java create mode 100644 proposal/myrmidon/src/testcases/org/apache/aut/vfs/ReadOnlyFileSystemTestBase.java create mode 100644 proposal/myrmidon/src/testcases/org/apache/aut/vfs/SmbFileSystemTest.java create mode 100644 proposal/myrmidon/src/testcases/org/apache/aut/vfs/WritableFileSystemTestBase.java create mode 100644 proposal/myrmidon/src/testcases/org/apache/aut/vfs/ZipFileSystemTest.java diff --git a/proposal/myrmidon/build.xml b/proposal/myrmidon/build.xml index 3530ac06c..d9867d6d1 100644 --- a/proposal/myrmidon/build.xml +++ b/proposal/myrmidon/build.xml @@ -59,7 +59,9 @@ Legal: - + + + @@ -112,6 +114,9 @@ Legal: + @@ -187,6 +192,7 @@ Legal: + @@ -221,6 +227,8 @@ Legal: unless="jdk1.2+" /> + + @@ -278,7 +286,6 @@ Legal: unless="jdk1.2+" /> - @@ -373,6 +380,18 @@ Legal: + + @@ -436,6 +455,7 @@ Legal: + + + + + + + + + + + + + + + + + + + + + + + - + + + + diff --git a/proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/dir1/file1.txt b/proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/dir1/file1.txt new file mode 100644 index 000000000..e69de29bb diff --git a/proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/dir1/file2.txt b/proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/dir1/file2.txt new file mode 100644 index 000000000..e69de29bb diff --git a/proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/dir1/file3.txt b/proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/dir1/file3.txt new file mode 100644 index 000000000..e69de29bb diff --git a/proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/empty.txt b/proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/empty.txt new file mode 100644 index 000000000..e69de29bb diff --git a/proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/file1.txt b/proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/file1.txt new file mode 100644 index 000000000..e7dead2c1 --- /dev/null +++ b/proposal/myrmidon/etc/testcases/org/apache/aut/vfs/basedir/file1.txt @@ -0,0 +1,2 @@ +This is a test file. +With 2 lines in it. diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/FileContent.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileContent.java new file mode 100644 index 000000000..7e920290d --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileContent.java @@ -0,0 +1,160 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Reader; +import java.io.Writer; +import java.sql.Date; + +/** + * This interface is used to access the data content of a file. + * + *

To read from a file, use the {@link #getInputStream} method. + * + *

To write to a file, use the {@link #getOutputStream} method. This + * method will create the file and the parent folder, if necessary. + * + *

To prevent concurrency problems, only a single InputStream, + * or OutputStream may be open at any time, for each file. + * + *

TODO - allow multiple input streams? + * + * @see FileObject#getContent + * + * @author Adam Murdoch + */ +public interface FileContent +{ + /** + * Returns the file which this is the content of. + */ + FileObject getFile(); + + /** + * Determines the size of the file, in bytes. + * + * @return + * The size of the file, in bytes. + * + * @throws FileSystemException + * If the file does not exist, or is being written to, or on error + * determining the size. + */ + long getSize() throws FileSystemException; + + /** + * Determines the last-modified timestamp of the file. + * + * @return + * The last-modified timestamp. + * + * @throws FileSystemException + * If the file does not exist, or is being written to, or on error + * determining the last-modified timestamp. + */ + long getLastModifiedTime() throws FileSystemException; + + /** + * Sets the last-modified timestamp of the file. Creates the file if + * it does not exist. + * + * @param modTime + * The time to set the last-modified timestamp to. + * + * @throws FileSystemException + * If the file is read-only, or is being read, or on error setting + * the last-modified timestamp. + */ + void setLastModifiedTime( long modTime ) throws FileSystemException; + + /** + * Gets the value of an attribute of the file's content. + * + *

TODO - change to Map getAttributes() instead? + * + *

TODO - define the standard attribute names, and define which attrs + * are guaranteed to be present. + * + * @param attrName + * The name of the attribute. + * + * @return + * The value of the attribute. + * + * @throws FileSystemException + * If the file does not exist, or is being written, or if the + * attribute is unknown. + */ + Object getAttribute( String attrName ) throws FileSystemException; + + /** + * Sets the value of an attribute of the file's content. Creates the + * file if it does not exist. + * + * @param attrName + * The name of the attribute. + * + * @param value + * The value of the attribute. + * + * @throws FileSystemException + * If the file is read-only, or is being read, or if the attribute + * is not supported, or on error setting the attribute. + */ + void setAttribute( String attrName, Object value ) throws FileSystemException; + + /** + * Returns an input stream for reading the file's content. + * + *

There may only be a single input or output stream open for the + * file at any time. + * + * @return + * An input stream to read the file's content from. The input + * stream is buffered, so there is no need to wrap it in a + * BufferedInputStream. + * + * @throws FileSystemException + * If the file does not exist, or is being read, or is being written, + * or on error opening the stream. + */ + InputStream getInputStream() throws FileSystemException; + + /** + * Returns an output stream for writing the file's content. + * + * If the file does not exist, this method creates it, and the parent + * folder, if necessary. If the file does exist, it is replaced with + * whatever is written to the output stream. + * + *

There may only be a single input or output stream open for the + * file at any time. + * + * @return + * An output stream to write the file's content to. The stream is + * buffered, so there is no need to wrap it in a + * BufferedOutputStream. + * + * @throws FileSystemException + * If the file is read-only, or is being read, or is being written, + * or on error opening the stream. + */ + OutputStream getOutputStream() throws FileSystemException; + + /** + * Closes all resources used by the content, including any open stream. + * Commits pending changes to the file. + * + *

This method is a hint to the implementation that it can release + * resources. This object can continue to be used after calling this + * method. + */ + void close() throws FileSystemException; +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/FileName.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileName.java new file mode 100644 index 000000000..2c1f58686 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileName.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs; + +/** + * The interface is used to perform operations on a file name. File names + * are immutable, and work correctly as keys in hash tables. + * + * @see FileObject + * + * @author Adam Murdoch + */ +public interface FileName +{ + /** + * Returns the base name of the file. The base name of a file is the + * last element of its name. For example the base name of + * /somefolder/somefile is somefile. + * + *

The root file of a file system has an empty base name. + */ + String getBaseName(); + + /** + * Returns the absolute path of the file, within its file system. This + * path is normalised, so that . and .. elements + * have been removed. Also, the path only contains / as its + * separator character. The path always starts with / + * + *

The root of a file system has / as its path. + */ + String getPath(); + + /** + * Returns the absolute URI of the file. + */ + String getURI(); + + /** + * Returns the name of the parent of the file. The root of a file system + * has no parent. + * + * @return + * A {@link FileName} object representing the parent name. Returns + * null for the root of a file system. + */ + FileName getParent(); + + /** + * Resolves a name, relative to the file. Equivalent to calling + * resolveName( path, NameScope.FILE_SYSTEM ). + * + * @param path + * The path to resolve. + * + * @return + * A {@link FileName} object representing the resolved name. + * + * @throws FileSystemException + * If the name is invalid. + */ + FileName resolveName( String path ) throws FileSystemException; + + /** + * Resolves a name, relative to the file. Refer to {@link NameScope#CHILD} + * and {@link NameScope#FILE_SYSTEM} for a description of how names are + * resolved. + * + * @param name + * The path to resolve. + * + * @param scope + * The scope to use when resolving the name. + * + * @return + * A {@link FileName} object representing the resolved name. + * + * @throws FileSystemException + * If the name is invalid. + */ + FileName resolveName( String name, NameScope scope ) throws FileSystemException; +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/FileObject.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileObject.java new file mode 100644 index 000000000..46545b84f --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileObject.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs; + +/** + * This interface represents a file, and is used to access the content and + * structure of the file. + * + *

Files are arranged in a hierarchy. Each hierachy forms a + * file system. A file system represents things like a local OS + * file system, a windows share, an HTTP server, or the contents of a Zip file. + * + *

There are two types of files: Folders, which contain other files, + * and normal files, which contain data, or content. A folder may + * not have any content, and a normal file cannot contain other files. + * + *

File Naming

+ * + *

TODO - write this. + * + *

Reading and Writing a File

+ * + *

Reading and writing a file, and all other operations on the file's + * content, is done using the {@link FileContent} object returned + * by {@link #getContent}. + * + *

Creating and Deleting a File

+ * + *

A file is created using either {@link #create}, or by writing to the + * file using one of the {@link FileContent} methods. + * + *

A file is deleted using {@link #delete}. Deletion is recursive, so + * that when a folder is deleted, so are all its child files. + * + *

Finding Files

+ * + *

Other files in the same file system as this file can be found using: + *

    + *
  • {@link #resolveFile} to find another file relative to this file. + *
  • {@link #getChildren} to find the children of this file. + *
  • {@link #getParent} to find the folder containing this file. + *
  • {@link #getRoot} to find the root folder of the file system. + *
+ * + *

To find files in another file system, use a {@link FileSystemManager}. + * + * @see FileSystemManager + * @see FileContent + * @see FileName + * + * @author Adam Murdoch + */ +public interface FileObject +{ + /** + * Returns the name of this file. + */ + FileName getName(); + + /** + * Determines if this file exists. + * + * @return + * true if this file exists, false if not. + * + * @throws FileSystemException + * On error determining if this file exists. + */ + boolean exists() throws FileSystemException; + + /** + * Returns this file's type. + * + * @return + * Either {@link FileType#FILE} or {@link FileType#FOLDER}. Never + * returns null. + * + * @throws FileSystemException + * If the file does not exist, or on error determining the file's type. + */ + FileType getType() throws FileSystemException; + + /** + * Returns the folder that contains this file. + * + * @return + * The folder that contains this file. Returns null if this file is + * the root of a file system. + * + * @throws FileSystemException + * On error finding the file's parent. + */ + FileObject getParent() throws FileSystemException; + + /** + * Returns the root of the file system containing this file. + * + * @return + * The root of the file system. + * + * @throws FileSystemException + * On error finding the root of the file system. + */ + FileObject getRoot() throws FileSystemException; + + /** + * Lists the children of this file. + * + * @return + * An array containing the children of this file. The array is + * unordered. If the file does not have any children, a zero-length + * array is returned. This method never returns null. + * + * @throws FileSystemException + * If this file does not exist, or is not a folder, or on error + * listing this file's children. + */ + FileObject[] getChildren() throws FileSystemException; + + /** + * Finds a file, relative to this file. Refer to {@link NameScope#CHILD} + * and {@link NameScope#FILE_SYSTEM} for a description of how names + * are resolved in the different scopes. + * + * @param name + * The name to resolve. + * + * @return + * The file. + * + * @throws FileSystemException + * On error parsing the path, or on error finding the file. + */ + FileObject resolveFile( String name, NameScope scope ) throws FileSystemException; + + /** + * Finds a file, relative to this file. Equivalent to calling + * resolveFile( path, NameScope.FILE_SYSTEM ). + * + * @param path + * The path of the file to locate. Can either be a relative + * path or an absolute path. + * + * @return + * The file. + * + * @throws FileSystemException + * On error parsing the path, or on error finding the file. + */ + FileObject resolveFile( String path ) throws FileSystemException; + + /** + * Deletes this file, and all children. Does nothing if the file + * does not exist. + * + *

This method is not transactional. If it fails and throws an + * exception, some of this file's descendents may have been deleted. + * + * @throws FileSystemException + * If this file or one of its descendents is read-only, or on error + * deleting this file or one of its descendents. + */ + void delete() throws FileSystemException; + + /** + * Creates this file, if it does not exist. Also creates any ancestor + * folders which do not exist. This method does nothing if the file + * already exists with the requested type. + * + * @param type + * The type of file to create. + * + * @throws FileSystemException + * If the file already exists with the wrong type, or the parent + * folder is read-only, or on error creating this file or one of + * its ancestors. + */ + void create( FileType type ) throws FileSystemException; + + /** + * Returns this file's content. The {@link FileContent} returned by this + * method can be used to read and write the content of the file. + * + *

This method can be called if the file does not exist, and + * the returned {@link FileContent} can be used to create the file + * by writing its content. + * + * @return + * This file's content. + * + * @throws FileSystemException + * If this file is a folder. + */ + FileContent getContent() throws FileSystemException; + + /** + * Closes this file, and its content. This method is a hint to the + * implementation that it can release any resources asociated with + * the file. + * + *

The file object can continue to be used after this method is called. + * + * @see FileContent#close + * + * @throws FileSystemEception + * On error closing the file. + */ + void close() throws FileSystemException; +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/FileSystemException.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileSystemException.java new file mode 100644 index 000000000..8e381c3cb --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileSystemException.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs; + +import org.apache.avalon.framework.CascadingException; + +/** + * Thrown for file system errors. + * + * @author Adam Murdoch + */ +public class FileSystemException extends CascadingException +{ + private Throwable m_cause; + + /** + * Constructs exception with the specified detail message. + * + * @param msg the detail message. + */ + public FileSystemException( String msg ) + { + super( msg ); + } + + /** + * Constructs exception with the specified detail message. + * + * @param msg the detail message. + * @param cause the cause. + */ + public FileSystemException( String msg, Throwable cause ) + { + super( msg, cause ); + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/FileSystemManager.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileSystemManager.java new file mode 100644 index 000000000..3d3e7467d --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileSystemManager.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs; + +import org.apache.avalon.framework.component.Component; + +/** + * A FileSystemManager is manages a set of file systems. This interface is + * used to locate a {@link FileObject} by name from one of those file systems. + * + *

To locate a {@link FileObject}, use one of the resolveFile() + * methods.

+ * + *

File Naming

+ * + *

A file system manager can recognise several types of file names: + * + *

    + * + *
  • Absolute URI. These must start with a scheme, such as + * file: or ftp:, followed by a scheme dependent + * file name. Some examples:

    + *
    + * file:/c:/somefile
    + * ftp://somewhere.org/somefile
    + * 
    + * + *
  • Absolute local file name. For example, + * /home/someuser/a-file or c:\dir\somefile.html. + * Elements in the name can be separated using any of the following + * characters: /, \, or the native file separator + * character. For example, the following file names are the same:

    + *
    + * c:\somedir\somefile.xml
    + * c:/somedir/somefile.xml
    + * 
    + * + *
  • Relative path. For example: ../somefile or + * somedir/file.txt. The file system manager resolves relative + * paths against its base file. Elements in the relative path can be + * separated using /, \, or file system specific + * separator characters. Relative paths may also contain .. and + * . elements. See {@link FileObject#resolveFile} for more details.

    + * + *
+ * + * @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 + * resolveFile(uri, getBaseName()). + * + * @param name + * The name of the file. + * + * @throws FileSystemException + * On error parsing the file name. + */ + FileObject resolveFile( String name ) throws FileSystemException; + + /** + * Locates a file by name. The name is resolved as described + * above. That is, the name can be either + * an absolute URI, an absolute file name, or a relative path to + * be resolved against baseFile. + * + *

Note that the file does not have to exist when this method is called. + * + * @param name + * The name of the file. + * + * @param baseFile + * The base file to use to resolve paths. + * + * @throws FileSystemException + * On error parsing the file name. + */ + FileObject resolveFile( FileObject baseFile, String name ) throws FileSystemException; +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/FileType.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileType.java new file mode 100644 index 000000000..310885967 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileType.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + * + * @author Adam Murdoch + */ +package org.apache.aut.vfs; + +import org.apache.avalon.excalibur.i18n.Resources; +import org.apache.avalon.excalibur.i18n.ResourceManager; + +/** + * An enumeration that represents a file's type. + */ +public final class FileType +{ + private final static Resources REZ = + ResourceManager.getPackageResources( FileType.class ); + + private String m_name; + + private FileType( String name ) + { + m_name = name; + } + + /** Returns the name of the type. */ + public String toString() + { + return m_name; + } + + /** Returns the name of the type. */ + public String getName() + { + return m_name; + } + + /** + * A folder, which can contain other files, but does not have any data + * content. + */ + public static final FileType FOLDER = new FileType( REZ.getString( "folder.name" ) ); + + /** + * A regular file, which has data content, but cannot contain other files. + */ + public static final FileType FILE = new FileType( REZ.getString( "file.name" ) ); +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/NameScope.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/NameScope.java new file mode 100644 index 000000000..fbf6870d9 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/NameScope.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs; + +/** + * An enumerated type for file name scope, used when resolving a name relative + * to a file. + * + * @author Adam Murdoch + */ +public final class NameScope +{ + private String m_name; + + private NameScope( String name ) + { + m_name = name; + } + + /** Returns the name of the scope. */ + public String toString() + { + return m_name; + } + + /** Returns the name of the scope. */ + public String getName() + { + return m_name; + } + + /** + * Resolve against the children of the base file. + * + *

The supplied name must be a valid element name. That is, it may + * not be empty, or ., or .., or contain any + * separator characters. + */ + public static final NameScope CHILD = new NameScope( "child" ); + + /** + * Resolve against files in the same file system as the base file. + * + *

If the supplied name is an absolute path, then it is resolved + * relative to the root of the file system that the base file belongs to. + * If a relative name is supplied, then it is resolved relative to the base + * file. + * + *

The path may use any mix of /, \, or file + * system specific separators to separate elements in the path. It may + * also contain . and .. elements. + * + *

A path is considered absolute if it starts with a separator character, + * and relative if it does not. + */ + public static final NameScope FILE_SYSTEM = new NameScope( "filesystem" ); +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/Resources.properties b/proposal/myrmidon/src/java/org/apache/aut/vfs/Resources.properties new file mode 100644 index 000000000..7f418a6dd --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/Resources.properties @@ -0,0 +1,2 @@ +folder.name=folder +file.name=file diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/package.html b/proposal/myrmidon/src/java/org/apache/aut/vfs/package.html new file mode 100644 index 000000000..a6b86b26e --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/package.html @@ -0,0 +1,10 @@ + +

This package contains the interfaces used to access the VFS.

+ +

A {@link vfs.filesystem.FileSystemManager} is the starting point for +all file system access. It is used to locate a {@link vfs.filesystem.FileObject} +by name. Files are accessed using the {@link vfs.filesystem.FileObject} +interface. This interface allows a file's structure and content to be +accessed.

+ + \ No newline at end of file diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileObject.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileObject.java new file mode 100644 index 000000000..94d0464e9 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileObject.java @@ -0,0 +1,644 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider; + +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.apache.aut.vfs.FileContent; +import org.apache.aut.vfs.FileName; +import org.apache.aut.vfs.FileObject; +import org.apache.aut.vfs.FileSystemException; +import org.apache.aut.vfs.FileType; +import org.apache.aut.vfs.NameScope; +import org.apache.avalon.excalibur.i18n.Resources; +import org.apache.avalon.excalibur.i18n.ResourceManager; + +/** + * A partial file object implementation. + * + * @author Adam Murdoch + */ +public abstract class AbstractFileObject implements FileObject +{ + private static final Resources REZ = + ResourceManager.getPackageResources( AbstractFileObject.class ); + + private FileName m_name; + private AbstractFileSystem m_fs; + private DefaultFileContent m_content; + + private static final FileObject[] EMPTY_FILE_ARRAY = {}; + + // Cached info + private boolean m_attached; + private AbstractFileObject m_parent; + private FileType m_type; + private FileObject[] m_children; + + protected AbstractFileObject( FileName name, AbstractFileSystem fs ) + { + m_name = name; + m_fs = fs; + } + + /** + * Returns true if this file is read-only. + */ + protected boolean isReadOnly() + { + return false; + } + + /** + * Attaches this file object to its file resource. This method is called + * before any of the doBlah() or onBlah() methods. Sub-classes can use + * this method to perform lazy initialisation. + */ + protected void doAttach() throws Exception + { + } + + /** + * Detaches this file object from its file resource. + * + *

Called when this file is closed, or its type changes. Note that + * the file object may be reused later, so should be able to be reattached. + */ + protected void doDetach() + { + } + + /** + * Determines the type of the file, returns null if the file does not + * exist. The return value of this method is cached, so the + * implementation can be expensive. + */ + protected abstract FileType doGetType() throws Exception; + + /** + * Lists the children of the file. Is only called if {@link #doGetType} + * returns {@link FileType#FOLDER}. The return value of this method + * is cached, so the implementation can be expensive. + */ + protected abstract String[] doListChildren() throws Exception; + + /** + * Deletes the file. Is only called when: + *

    + *
  • {@link #isReadOnly} returns false. + *
  • {@link #doGetType} does not return null. + *
  • If this file is a folder, it has no children. + *
+ */ + 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: + *
    + *
  • {@link #isReadOnly} returns false. + *
  • {@link #doGetType} returns null. + *
  • The parent folder exists or this file is the root of the file + * system. + *
+ */ + 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}. + * + *

There is guaranteed never to be more than one stream for this file + * (input or output) open at any given time. + * + *

The returned stream does not have to be buffered. + */ + protected abstract InputStream doGetInputStream() throws Exception; + + /** + * Creates an output stream to write the file content to. Is only + * called if: + *

    + *
  • This file is not read-only. + *
  • {@link #doGetType} returns {@link FileType#FILE}, or + * {@link #doGetType} returns null, and the file's parent exists + * and is a folder. + *
+ * + *

There is guaranteed never to be more than one stream for this file + * (input or output) open at any given time. + * + *

The returned stream does not have to be buffered. + */ + protected OutputStream doGetOutputStream() throws Exception + { + final String message = REZ.getString( "write-not-supported.error" ); + throw new FileSystemException( message ); + } + + /** + * Notification of the output stream being closed. + * TODO - get rid of this. + */ + protected void doEndOutput() throws Exception + { + } + + /** + * Notification of the input stream being closed. + * TODO - get rid of this. + */ + protected void doEndInput() throws Exception + { + } + + /** + * Returns the URI of the file. + */ + public String toString() + { + return m_name.getURI(); + } + + /** + * Returns the name of the file. + */ + public FileName getName() + { + return m_name; + } + + /** + * Determines if the file exists. + */ + public boolean exists() throws FileSystemException + { + attach(); + return ( m_type != null ); + } + + /** + * Returns the file's type. + */ + public FileType getType() throws FileSystemException + { + attach(); + if( m_type == null ) + { + final String message = REZ.getString( "get-type-no-exist.error", m_name ); + throw new FileSystemException( message ); + } + return m_type; + } + + /** + * Returns the parent of the file. + */ + public FileObject getParent() throws FileSystemException + { + if( this == m_fs.getRoot() ) + { + // Root file has no parent + return null; + } + + // Locate the parent of this file + if( m_parent == null ) + { + m_parent = (AbstractFileObject)m_fs.findFile( m_name.getParent() ); + } + return m_parent; + } + + /** + * Returns the root of the file system containing the file. + */ + public FileObject getRoot() throws FileSystemException + { + return m_fs.getRoot(); + } + + /** + * Returns the children of the file. + */ + public FileObject[] getChildren() throws FileSystemException + { + attach(); + if( m_type == null ) + { + final String message = REZ.getString( "list-children-no-exist.error", m_name ); + throw new FileSystemException( message ); + } + if( m_type != FileType.FOLDER ) + { + final String message = REZ.getString( "list-children-not-folder.error", m_name ); + throw new FileSystemException( message ); + } + + // Use cached info, if present + if( m_children != null ) + { + return m_children; + } + + // List the children + String[] files; + try + { + files = doListChildren(); + } + catch( Exception exc ) + { + final String message = REZ.getString( "list-children.error", m_name ); + throw new FileSystemException( message, exc ); + } + + if( files == null || files.length == 0 ) + { + // No children + m_children = EMPTY_FILE_ARRAY; + } + else + { + // Create file objects for the children + m_children = new FileObject[ files.length ]; + for( int i = 0; i < files.length; i++ ) + { + String file = files[ i ]; + m_children[ i ] = m_fs.findFile( m_name.resolveName( file, NameScope.CHILD ) ); + } + } + + return m_children; + } + + /** + * Returns a child by name. + */ + public FileObject resolveFile( String name, NameScope scope ) throws FileSystemException + { + // TODO - cache children (only if they exist) + return m_fs.findFile( m_name.resolveName( name, scope ) ); + } + + /** + * Finds a file, relative to this file. + * + * @param path + * The path of the file to locate. Can either be a relative + * path, which is resolved relative to this file, or an + * absolute path, which is resolved relative to the file system + * that contains this file. + */ + public FileObject resolveFile( String path ) throws FileSystemException + { + FileName name = m_name.resolveName( path ); + return m_fs.findFile( name ); + } + + /** + * Deletes this file, once all its children have been deleted + */ + private void deleteSelf() throws FileSystemException + { + if( isReadOnly() ) + { + final String message = REZ.getString( "delete-read-only.error", m_name ); + throw new FileSystemException( message ); + } + + // Delete the file + try + { + doDelete(); + } + catch( Exception exc ) + { + final String message = REZ.getString( "delete.error", m_name ); + throw new FileSystemException( message, exc ); + } + + // Update cached info + updateType(null); + } + + /** + * Deletes this file, and all children. + */ + public void delete() throws FileSystemException + { + attach(); + if( m_type == null ) + { + // File does not exist + return; + } + + // Recursively delete this file and all its children + List queue = new ArrayList(); + Set expanded = new HashSet(); + queue.add( this ); + + // Recursively delete each file + // TODO - recover from errors + while( queue.size() > 0 ) + { + AbstractFileObject file = (AbstractFileObject)queue.get( 0 ); + file.attach(); + + if( file.m_type == null ) + { + // Shouldn't happen + queue.remove( 0 ); + } + else if( file.m_type == FileType.FILE ) + { + // Delete the file + file.deleteSelf(); + queue.remove( 0 ); + } + else if( expanded.contains( file ) ) + { + // Have already deleted all the children of this folder - + // delete it + file.deleteSelf(); + queue.remove( 0 ); + } + else + { + // Delete the folder's children + FileObject[] children = file.getChildren(); + for( int i = 0; i < children.length; i++ ) + { + FileObject child = children[ i ]; + queue.add( 0, child ); + } + expanded.add( file ); + } + } + + // Update parent's child list + notifyParent(); + } + + /** + * Creates this file, if it does not exist. Also creates any ancestor + * files which do not exist. + */ + public void create( FileType type ) throws FileSystemException + { + attach(); + if( m_type == type ) + { + // Already exists as correct type + return; + } + if( m_type != null ) + { + final String message = REZ.getString( "create-mismatched-type.error", type, m_name, m_type ); + throw new FileSystemException( message ); + } + if( isReadOnly() ) + { + final String message = REZ.getString( "create-read-only.error", type, m_name ); + throw new FileSystemException( message ); + } + + // Traverse up the heirarchy and make sure everything is a folder + FileObject parent = getParent(); + if( parent != null ) + { + parent.create( FileType.FOLDER ); + } + + // Create the folder + try + { + if( type == FileType.FOLDER ) + { + doCreateFolder(); + m_children = EMPTY_FILE_ARRAY; + } + else if( type == FileType.FILE ) + { + OutputStream outStr = doGetOutputStream(); + outStr.close(); + endOutput(); + } + } + catch( Exception exc ) + { + final String message = REZ.getString( "create.error", type, m_name ); + throw new FileSystemException( message, exc ); + } + + // Update cached info + updateType(type); + } + + /** + * Returns the file's content. + */ + public FileContent getContent() throws FileSystemException + { + attach(); + if( m_type == FileType.FOLDER ) + { + final String message = REZ.getString( "get-folder-content.error", m_name ); + throw new FileSystemException( message ); + } + if( m_content == null ) + { + m_content = new DefaultFileContent( this ); + } + return m_content; + } + + /** + * Closes this file, and its content. + */ + public void close() throws FileSystemException + { + FileSystemException exc = null; + + // Close the content + if( m_content != null ) + { + try + { + m_content.close(); + } + catch( FileSystemException e ) + { + exc = e; + } + } + + // Detach from the file + if( m_attached ) + { + doDetach(); + m_attached = false; + m_type = null; + m_children = null; + } + + if( exc != null ) + { + throw exc; + } + } + + /** + * Prepares this file for writing. Makes sure it is either a file, + * or its parent folder exists. Returns an output stream to use to + * write the content of the file to. + */ + public OutputStream getOutputStream() throws FileSystemException + { + attach(); + if( isReadOnly() ) + { + final String message = REZ.getString( "write-read-only.error", m_name ); + throw new FileSystemException( message ); + } + if( m_type == FileType.FOLDER ) + { + final String message = REZ.getString( "write-folder.error", m_name ); + } + + if( m_type == null ) + { + // Does not exist - make sure parent does + FileObject parent = getParent(); + if( parent != null ) + { + parent.create( FileType.FOLDER ); + } + } + + // Get the raw output stream + try + { + return doGetOutputStream(); + } + catch( FileSystemException exc ) + { + throw exc; + } + catch( Exception exc ) + { + final String message = REZ.getString( "write.error", m_name ); + throw new FileSystemException( message, exc ); + } + } + + /** + * Attaches to the file. + */ + private void attach() throws FileSystemException + { + if( m_attached ) + { + return; + } + + try + { + // Attach and determine the file type + doAttach(); + m_attached = true; + m_type = doGetType(); + } + catch( FileSystemException exc ) + { + throw exc; + } + catch( Exception exc ) + { + final String message = REZ.getString( "get-type.error", m_name ); + throw new FileSystemException( message, exc ); + } + + } + + /** + * Called when the ouput stream for this file is closed. + */ + public void endOutput() throws Exception + { + updateType( FileType.FILE ); + doEndOutput(); + } + + /** + * Update cached info when this file's type changes. + */ + private void updateType(FileType type) + { + // Notify parent that its child list may no longer be valid + notifyParent(); + + // Detach + doDetach(); + m_attached = false; + m_type = null; + m_children = null; + } + + /** + * Notify the parent of a change to its children, when a child is created + * or deleted. + */ + private void notifyParent() + { + if( m_parent == null ) + { + // Locate the parent, if it is cached + m_parent = (AbstractFileObject)m_fs.getFile( m_name.getParent() ); + } + + if( m_parent != null ) + { + m_parent.invalidateChildren(); + } + } + + /** + * Notifies a file that children have been created or deleted. + */ + private void invalidateChildren() + { + m_children = null; + onChildrenChanged(); + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileSystem.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileSystem.java new file mode 100644 index 000000000..a72cad269 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileSystem.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider; + +import java.util.HashMap; +import java.util.Map; +import org.apache.aut.vfs.FileName; +import org.apache.aut.vfs.FileObject; +import org.apache.aut.vfs.FileSystemException; + +/** + * A partial file system implementation. + * + * @author Adam Murdoch + */ +public abstract class AbstractFileSystem implements FileSystem +{ + private FileObject m_root; + private FileName m_rootName; + + /** Map from absolute file path to FileObject. */ + private Map m_files = new HashMap(); + + protected AbstractFileSystem( FileName rootName ) + { + m_rootName = rootName; + } + + /** + * Creates a file object. This method is called only if the requested + * file is not cached. + */ + protected abstract FileObject createFile( FileName name ) throws FileSystemException; + + /** + * Adds a file object to the cache. + */ + protected void putFile( FileObject file ) + { + m_files.put( file.getName().getPath(), file ); + } + + /** + * Returns a cached file. + */ + protected FileObject getFile( FileName name ) + { + return (FileObject)m_files.get( name.getPath() ); + } + + /** + * Returns the root file of this file system. + */ + public FileObject getRoot() throws FileSystemException + { + if( m_root == null ) + { + m_root = findFile( m_rootName ); + } + return m_root; + } + + /** + * Finds a file in this file system. + */ + public FileObject findFile( String nameStr ) throws FileSystemException + { + // Resolve the name, and create the file + FileName name = m_rootName.resolveName( nameStr ); + return findFile( name ); + } + + /** + * Finds a file in this file system. + */ + public FileObject findFile( FileName name ) throws FileSystemException + { + // TODO - assert that name is from this file system + FileObject file = (FileObject)m_files.get( name.getPath() ); + if( file == null ) + { + file = createFile( name ); + m_files.put( name.getPath(), file ); + } + return file; + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileSystemProvider.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileSystemProvider.java new file mode 100644 index 000000000..eb21f2222 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/AbstractFileSystemProvider.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider; + +import org.apache.aut.vfs.FileObject; +import org.apache.aut.vfs.FileSystemException; +import org.apache.avalon.excalibur.i18n.Resources; +import org.apache.avalon.excalibur.i18n.ResourceManager; + +/** + * A partial file system provider implementation. + * + * @author Adam Murdoch + */ +public abstract class AbstractFileSystemProvider implements FileSystemProvider +{ + private static final Resources REZ + = ResourceManager.getPackageResources( AbstractFileSystemProvider.class ); + + protected FileSystemProviderContext m_context; + + /** + * Sets the context for this file system provider. This method is called + * before any of the other provider methods. + */ + public void setContext( FileSystemProviderContext context ) + { + m_context = context; + } + + /** + * Locates a file object, by absolute URI. + * + * @param uri + * The absolute URI of the file to find. + */ + public FileObject findFile( String uri ) throws FileSystemException + { + // Parse the URI + ParsedUri parsedURI = null; + try + { + parsedURI = parseURI( uri ); + } + catch( FileSystemException exc ) + { + final String message = REZ.getString( "invalid-absolute-uri.error", uri ); + throw new FileSystemException( message, exc ); + } + + // Check in the cache for the file system + FileSystem fs = m_context.getFileSystem( parsedURI.getRootURI() ); + if( fs == null ) + { + // Need to create the file system + fs = createFileSystem( parsedURI ); + m_context.putFileSystem( parsedURI.getRootURI(), fs ); + } + + // Locate the file + return fs.findFile( parsedURI.getPath() ); + } + + /** + * Parses a URI into its components. The returned value is used to + * locate the file system in the cache (using the root prefix), and is + * passed to {@link #createFileSystem} to create the file system. + * + *

The provider can annotate this object with any additional + * information it requires to create a file system from the URI. + */ + protected abstract ParsedUri parseURI( String uri ) throws FileSystemException; + + /** + * Creates the filesystem. + */ + protected abstract org.apache.aut.vfs.provider.FileSystem createFileSystem( ParsedUri uri ) throws FileSystemException; +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileContent.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileContent.java new file mode 100644 index 000000000..416444a6d --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileContent.java @@ -0,0 +1,381 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.sql.Date; +import org.apache.aut.vfs.FileContent; +import org.apache.aut.vfs.FileObject; +import org.apache.aut.vfs.FileSystemException; +import org.apache.avalon.excalibur.i18n.Resources; +import org.apache.avalon.excalibur.i18n.ResourceManager; + +/** + * The content of a file. + * + * @author Adam Murdoch + */ +public class DefaultFileContent implements FileContent +{ + private static final Resources REZ + = ResourceManager.getPackageResources( DefaultFileContent.class ); + + private AbstractFileObject m_file; + private int _state = STATE_NONE; + private FileContentInputStream m_instr; + private FileContentOutputStream m_outstr; + + private static final int STATE_NONE = 0; + private static final int STATE_READING = 1; + private static final int STATE_WRITING = 2; + + public DefaultFileContent( AbstractFileObject file ) + { + m_file = file; + } + + /** + * Returns the file which this is the content of. + */ + public FileObject getFile() + { + return m_file; + } + + /** + * Returns the size of the content (in bytes). + */ + public long getSize() throws FileSystemException + { + // Do some checking + if( !m_file.exists() ) + { + final String message = REZ.getString( "get-size-no-exist.error", m_file ); + throw new FileSystemException( message ); + } + if( _state == STATE_WRITING ) + { + final String message = REZ.getString( "get-size-write.error", m_file ); + throw new FileSystemException( message ); + } + + try + { + // Get the size + return m_file.doGetContentSize(); + } + catch( Exception exc ) + { + final String message = REZ.getString( "get-size.error", m_file ); + throw new FileSystemException( message, exc ); + } + } + + /** + * Returns the last-modified timestamp. + */ + public long getLastModifiedTime() throws FileSystemException + { + // TODO - implement this + throw new FileSystemException( "Not implemented." ); + } + + /** + * Sets the last-modified timestamp. + */ + public void setLastModifiedTime( long modTime ) throws FileSystemException + { + // TODO - implement this + throw new FileSystemException( "Not implemented." ); + } + + /** + * Gets the value of an attribute. + */ + public Object getAttribute( String attrName ) throws FileSystemException + { + // TODO - implement this + throw new FileSystemException( "Not implemented." ); + } + + /** + * Sets the value of an attribute. + */ + public void setAttribute( String attrName, Object value ) throws FileSystemException + { + // TODO - implement this + throw new FileSystemException( "Not implemented." ); + } + + /** + * Returns an input stream for reading the content. + */ + public InputStream getInputStream() throws FileSystemException + { + if( !m_file.exists() ) + { + final String message = REZ.getString( "read-no-exist.error", m_file ); + throw new FileSystemException( message ); + } + if( _state != STATE_NONE ) + { + final String message = REZ.getString( "read-in-use.error", m_file ); + throw new FileSystemException( message ); + } + + // Get the raw input stream + InputStream instr = null; + try + { + instr = m_file.doGetInputStream(); + } + catch( Exception exc ) + { + final String message = REZ.getString( "read.error", m_file ); + throw new FileSystemException( message, exc ); + } + + // TODO - reuse + m_instr = new FileContentInputStream( instr ); + _state = STATE_READING; + return m_instr; + } + + /** + * Returns an output stream for writing the content. + */ + public OutputStream getOutputStream() throws FileSystemException + { + if( _state != STATE_NONE ) + { + final String message = REZ.getString( "write-in-use.error", m_file ); + throw new FileSystemException( message ); + } + + // Get the raw output stream + OutputStream outstr = m_file.getOutputStream(); + + // Create wrapper + // TODO - reuse + m_outstr = new FileContentOutputStream( outstr ); + _state = STATE_WRITING; + return m_outstr; + } + + /** + * Closes all resources used by the content, including all streams, readers + * and writers. + */ + public void close() throws FileSystemException + { + + try + { + // Close the input stream + if( m_instr != null ) + { + try + { + m_instr.close(); + } + catch( IOException ioe ) + { + final String message = REZ.getString( "close-instr.error" ); + throw new FileSystemException( message, ioe ); + } + } + + // Close the output stream + if( m_outstr != null ) + { + try + { + m_outstr.close(); + } + catch( IOException ioe ) + { + final String message = REZ.getString( "close-outstr.error" ); + throw new FileSystemException( message, ioe ); + } + } + } + finally + { + _state = STATE_NONE; + } + } + + /** + * Handles the end of input stream. + */ + private void endInput() throws Exception + { + m_instr = null; + _state = STATE_NONE; + m_file.doEndInput(); + } + + /** + * Handles the end of output stream. + */ + private void endOutput() throws Exception + { + m_outstr = null; + _state = STATE_NONE; + m_file.endOutput(); + } + + /** + * An input stream for reading content. Provides buffering, and + * end-of-stream monitoring. + */ + private final class FileContentInputStream extends BufferedInputStream + { + boolean _finished; + + FileContentInputStream( InputStream instr ) + { + super( instr ); + } + + /** + * Reads a character. + */ + public int read() throws IOException + { + if( _finished ) + { + return -1; + } + + int ch = super.read(); + if( ch != -1 ) + { + return ch; + } + + // End-of-stream + close(); + return -1; + } + + /** + * Reads bytes from this input stream.error occurs. + */ + public int read( byte b[], int off, int len ) + throws IOException + { + if( _finished ) + { + return -1; + } + + int nread = super.read( b, off, len ); + if( nread != -1 ) + { + return nread; + } + + // End-of-stream + close(); + return -1; + } + + /** + * Closes this input stream. + */ + public void close() throws IOException + { + if( _finished ) + { + return; + } + + // Close the stream + IOException exc = null; + try + { + super.close(); + } + catch( IOException e ) + { + exc = e; + } + + // Notify the file object + try + { + endInput(); + } + catch( Exception e ) + { + exc = new IOException( e.getMessage() ); + } + + _finished = true; + + if( exc != null ) + { + throw exc; + } + } + } + + /** + * An output stream for writing content. + */ + private final class FileContentOutputStream extends BufferedOutputStream + { + FileContentOutputStream( OutputStream outstr ) + { + super( outstr ); + } + + /** + * Closes this output stream. + */ + public void close() throws IOException + { + IOException exc = null; + + // Close the output stream + try + { + super.close(); + } + catch( IOException e ) + { + exc = e; + } + + // Notify of end of output + try + { + endOutput(); + } + catch( Exception e ) + { + exc = new IOException( e.getMessage() ); + } + + if( exc != null ) + { + throw exc; + } + } + } + +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileName.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileName.java new file mode 100644 index 000000000..6f169f142 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileName.java @@ -0,0 +1,123 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider; + +import org.apache.aut.vfs.FileName; +import org.apache.aut.vfs.FileSystemException; +import org.apache.aut.vfs.NameScope; + +/** + * A default file name implementation. + * + * @author Adam Murdoch + */ +public class DefaultFileName implements FileName +{ + private UriParser m_parser; + private String m_rootPrefix; + private String m_absPath; + + // Cached stuff + private String m_uri; + private String m_baseName; + + public DefaultFileName( UriParser parser, String rootPrefix, String absPath ) + { + m_parser = parser; + m_rootPrefix = rootPrefix; + m_absPath = absPath; + } + + // TODO - make these usable as hash keys + + /** + * Returns the URI of the file. + */ + public String toString() + { + return getURI(); + } + + /** + * Returns the base name of the file. + */ + public String getBaseName() + { + if( m_baseName == null ) + { + m_baseName = m_parser.getBaseName( m_absPath ); + } + return m_baseName; + } + + /** + * Returns the absolute path of the file, relative to the root of the + * file system that the file belongs to. + */ + public String getPath() + { + return m_absPath; + } + + /** + * Returns the name of a child of the file. + */ + public FileName resolveName( String name, NameScope scope ) throws FileSystemException + { + if( scope == NameScope.CHILD ) + { + String childPath = m_parser.getChildPath( m_absPath, name ); + return new DefaultFileName( m_parser, m_rootPrefix, childPath ); + } + else if( scope == NameScope.FILE_SYSTEM ) + { + String absPath = m_parser.resolvePath( m_absPath, name ); + return new DefaultFileName( m_parser, m_rootPrefix, absPath ); + } + else + { + throw new IllegalArgumentException(); + } + } + + /** + * Returns the name of the parent of the file. + */ + public FileName getParent() + { + String parentPath = m_parser.getParentPath( m_absPath ); + if( parentPath == null ) + { + return null; + } + return new DefaultFileName( m_parser, m_rootPrefix, parentPath ); + } + + /** + * Resolves a name, relative to the file. If the supplied name is an + * absolute path, then it is resolved relative to the root of the + * file system that the file belongs to. If a relative name is supplied, + * then it is resolved relative to this file name. + */ + public FileName resolveName( String path ) throws FileSystemException + { + return resolveName( path, NameScope.FILE_SYSTEM ); + } + + /** + * Returns the absolute URI of the file. + */ + public String getURI() + { + if( m_uri == null ) + { + m_uri = m_parser.getUri( m_rootPrefix, m_absPath ); + } + return m_uri; + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileSystemManager.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileSystemManager.java new file mode 100644 index 000000000..9ba8852fd --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileSystemManager.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.io.File; +import org.apache.aut.vfs.FileObject; +import org.apache.aut.vfs.FileSystemException; +import org.apache.aut.vfs.FileSystemManager; +import org.apache.aut.vfs.provider.local.LocalFileSystemProvider; +import org.apache.avalon.excalibur.i18n.Resources; +import org.apache.avalon.excalibur.i18n.ResourceManager; + +/** + * A default file system manager implementation. + * + * @author Adam Murdoch + */ +public class DefaultFileSystemManager implements FileSystemManager +{ + private static final Resources REZ + = ResourceManager.getPackageResources( DefaultFileSystemManager.class ); + + /** The default provider. */ + private LocalFileSystemProvider m_localFileProvider; + + /** Mapping from URI scheme to FileSystemProvider. */ + private Map m_providers = new HashMap(); + + /** The provider context. */ + private ProviderContextImpl m_context = new ProviderContextImpl(); + + /** The base file to use for relative URI. */ + private FileObject m_baseFile; + + /** + * The cached file systems. This is a mapping from root URI to + * FileSystem object. + */ + private Map m_fileSystems = new HashMap(); + + public DefaultFileSystemManager() throws Exception + { + // Create the local provider + m_localFileProvider = new LocalFileSystemProvider(); + m_providers.put( "file", m_localFileProvider ); + + // TODO - make this list configurable + // Create the providers + + FileSystemProvider provider = createProvider( "org.apache.aut.vfs.provider.zip.ZipFileSystemProvider" ); + if( provider != null ) + { + m_providers.put( "zip", provider ); + m_providers.put( "jar", provider ); + } + + provider = createProvider( "org.apache.aut.vfs.provider.smb.SmbFileSystemProvider" ); + if( provider != null ) + { + m_providers.put( "smb", provider ); + } + + provider = createProvider( "org.apache.aut.vfs.provider.ftp.FtpFileSystemProvider" ); + if( provider != null ) + { + m_providers.put( "ftp", provider ); + } + + // Contextualise the providers + for( Iterator iterator = m_providers.values().iterator(); iterator.hasNext(); ) + { + provider = (FileSystemProvider)iterator.next(); + provider.setContext( m_context ); + } + } + + /** + * Creates a provider instance, returns null if the provider class is + * not found. + */ + private FileSystemProvider createProvider( final String className ) throws Exception + { + try + { + // TODO - wrap exceptions + return (FileSystemProvider)Class.forName( className ).newInstance(); + } + catch( ClassNotFoundException e ) + { + // This is fine, for now + return null; + } + } + + /** + * Closes all file systems created by this file system manager. + */ + public void close() + { + // TODO - implement this + } + + /** + * Sets the base file to use when resolving relative URI. + */ + public void setBaseFile( FileObject baseFile ) throws FileSystemException + { + m_baseFile = baseFile; + } + + /** + * Sets the base file to use when resolving relative URI. + */ + public void setBaseFile( File baseFile ) throws FileSystemException + { + m_baseFile = m_localFileProvider.findFileByLocalName( baseFile.getAbsolutePath() ); + } + + /** + * Returns the base file used to resolve relative URI. + */ + public FileObject getBaseFile() + { + return m_baseFile; + } + + /** + * Locates a file by URI. + */ + public FileObject resolveFile( String URI ) throws FileSystemException + { + return resolveFile( m_baseFile, URI ); + } + + /** + * Resolves a URI, relative to a base file. + */ + public FileObject resolveFile( FileObject baseFile, String uri ) throws FileSystemException + { + // Extract the scheme + String scheme = UriParser.extractScheme( uri ); + if( scheme != null ) + { + // An absolute URI - locate the provider + FileSystemProvider provider = (FileSystemProvider)m_providers.get( scheme ); + if( provider != null ) + { + return provider.findFile( uri ); + } + } + + // Handle absolute file names + if( m_localFileProvider.isAbsoluteLocalName( uri ) ) + { + return m_localFileProvider.findFileByLocalName( uri ); + } + + // Assume a bad scheme + if( scheme != null ) + { + final String message = REZ.getString( "unknown-scheme.error", scheme, uri ); + throw new FileSystemException( message ); + } + + // Use the supplied base file + if( baseFile == null ) + { + final String message = REZ.getString( "find-rel-file.error", uri ); + throw new FileSystemException( message ); + } + return baseFile.resolveFile( uri ); + } + + /** + * A provider context implementation. + */ + private final class ProviderContextImpl implements FileSystemProviderContext + { + /** + * Locates a cached file system by root URI. + */ + public FileSystem getFileSystem( String rootURI ) + { + // TODO - need to have a per-fs uri comparator + return (org.apache.aut.vfs.provider.FileSystem)m_fileSystems.get( rootURI ); + } + + /** + * Registers a file system for caching. + */ + public void putFileSystem( String rootURI, org.apache.aut.vfs.provider.FileSystem fs ) throws FileSystemException + { + // TODO - should really check that there's not one already cached + m_fileSystems.put( rootURI, fs ); + } + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystem.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystem.java new file mode 100644 index 000000000..8c9d53c84 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystem.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider; + +import org.apache.aut.vfs.FileName; +import org.apache.aut.vfs.FileObject; +import org.apache.aut.vfs.FileSystemException; + +/** + * A file system. + * + * @author Adam Murdoch + */ +public interface FileSystem +{ + /** + * Returns the root of this file system. + */ + FileObject getRoot() throws FileSystemException; + + /** + * Finds a file in this file system. + * + * @param name + * The name of the file. + */ + FileObject findFile( FileName name ) throws FileSystemException; + + /** + * Finds a file in this file system. + * + * @param name + * The name of the file. This must be an absolute path. + */ + FileObject findFile( String name ) throws FileSystemException; +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystemProvider.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystemProvider.java new file mode 100644 index 000000000..80afef499 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystemProvider.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider; + +import org.apache.aut.vfs.FileObject; +import org.apache.aut.vfs.FileSystemException; + +/** + * A file system provider, or factory. + */ +public interface FileSystemProvider +{ + /** + * Sets the context for this file system provider. This method is called + * before any of the other provider methods. + */ + void setContext( FileSystemProviderContext context ); + + /** + * Locates a file object, by absolute URI. + * + * @param uri + * The absolute URI of the file to find. + */ + FileObject findFile( String uri ) throws FileSystemException; +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystemProviderContext.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystemProviderContext.java new file mode 100644 index 000000000..6062df4b3 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/FileSystemProviderContext.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider; + +import org.apache.aut.vfs.FileSystemException; + +/** + * Used for a file system provider to access the services it needs, such + * as the file system cache or other file system providers. + * + * @author Adam Murdoch + */ +public interface FileSystemProviderContext +{ + /** + * Locates a cached file system by root URI. + */ + FileSystem getFileSystem( String rootURI ); + + /** + * Registers a file system for caching. + */ + void putFileSystem( String rootURI, FileSystem fs ) throws FileSystemException; +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ParsedUri.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ParsedUri.java new file mode 100644 index 000000000..2116d998d --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ParsedUri.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider; + +/** + * A data container for information parsed from an absolute URI. + * + * @author Adam Murdoch + */ +public class ParsedUri +{ + private String m_scheme; + private String m_rootURI; + private String m_path; + private String m_userInfo; + private String m_hostName; + private String m_port; + + /** Returns the scheme. */ + public String getScheme() + { + return m_scheme; + } + + /** Sets the scheme. */ + public void setScheme( String scheme ) + { + m_scheme = scheme; + } + + /** Returns the root URI, used to identify the file system. */ + public String getRootURI() + { + return m_rootURI; + } + + /** Sets the root URI. */ + public void setRootURI( String rootPrefix ) + { + m_rootURI = rootPrefix; + } + + /** Returns the user info part of the URI. */ + public String getUserInfo() + { + return m_userInfo; + } + + /** Sets the user info part of the URI. */ + public void setUserInfo( String userInfo ) + { + m_userInfo = userInfo; + } + + /** Returns the host name part of the URI. */ + public String getHostName() + { + return m_hostName; + } + + /** Sets the host name part of the URI. */ + public void setHostName( String hostName ) + { + m_hostName = hostName; + } + + /** Returns the port part of the URI. */ + public String getPort() + { + return m_port; + } + + /** Sets the port part of the URI. */ + public void setPort( String port ) + { + m_port = port; + } + + /** Returns the path part of the URI. */ + public String getPath() + { + return m_path; + } + + /** Sets the path part of the URI. */ + public void setPath( String absolutePath ) + { + m_path = absolutePath; + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/Resources.properties b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/Resources.properties new file mode 100644 index 000000000..321c7c799 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/Resources.properties @@ -0,0 +1,43 @@ +# AbstractFileObject +delete-not-supported.error=This file type does not support delete. +create-folder-not-supported.error=This file type does not support folder creation. +write-not-supported.error=This file type cannot be written to. +get-type-no-exist.error=Could not determine the type of file "{0}" because it does not exist. +get-type.error=Could not determine the type of file "{0}". +list-children-no-exist.error=Could not list the contents of folder "{0}" because it does not exist. +list-children-not-folder.error=Could not list the contents of "{0}" because it is not a folder. +list-children.error=Could not list the contents of folder "{0}". +delete-read-only.error=Could not delete "{0}" because it is read-only. +delete.error=Could not delete "{0}". +create-mismatched-type.error=Could not create {0} "{1}" because it already exists and is a {2}. +create-read-only.error=Could not create {0} "{1}" because the file system is read-only. +create.error=Could not create {0} "{1}". +get-folder-content.error=Could not get the content of "{0}" because it is a folder. +write-read-only.error=Could not write to "{0}" because it is read-only. +write-folder.error=Could not write to "{0}" because it is a folder. +write-in-use.error=Could not write to "{0}" because it is already in use. +write.error=Could not write to "{0}". + +# DefaultFileContent +get-size-no-exist.error=Could not determine the size of file "{0}" because it does not exist. +get-size-write.error=Could not determine the size of file "{0}" because it is being written to. +get-size.error=Could not determine the size of file "{0}". +read-no-exist.error=Could not read file "{0}" because it does not exist. +read-in-use.error=Could not read file "{0}" because it is already being used. +read.error=Could not read file "{0}". +close-instr.error=Could not close file input stream. +close-outstr.error=Could not close file output stream. + +# AbstractFileSystemProvider +invalid-absolute-uri.error=Invalid absolute URI "{0}". + +# DefaultFileSystemManager +unknown-scheme.error=Unknown scheme "{0}" in URI "{0}". +find-rel-file.error=Could not find file with URI "{0}" because it is a relative path, and no base URI was provided. + +# UriParser +missing-double-slashes.error=Expecting // to follow the scheme in URI "{0}". +missing-hostname.error=Hostname missing from URI "{0}". +missing-port.error=Port number is missing from URI "{0}". +missing-hostname-path-sep.error=Expecting / to follow the hostname in URI "{0}". +invalid-childname.error=Invalid file base-name "{0}". diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/UriParser.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/UriParser.java new file mode 100644 index 000000000..6fc81ebac --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/UriParser.java @@ -0,0 +1,630 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider; + +import java.util.HashSet; +import java.util.Iterator; +import org.apache.aut.vfs.FileSystemException; +import org.apache.avalon.excalibur.i18n.Resources; +import org.apache.avalon.excalibur.i18n.ResourceManager; + +/** + * A name parser which parses absolute URIs. See RFC 2396 for details. + * + * @author Adam Murdoch + */ +public class UriParser +{ + private static final Resources REZ + = ResourceManager.getPackageResources( UriParser.class ); + + /** The normalised separator to use. */ + private char m_separatorChar; + private String m_separator; + + /** + * The set of valid separators. These are all converted to the normalised one. + * Does not contain the normalised separator + */ + private char[] m_separators; + + /** + * Creates a parser, using '/' and '\' as the path separators. + */ + public UriParser() + { + this( null ); + } + + /** + * Creates a parser, using '/' and '\' as the path separators, along with + * a provider-specific set of separators. + * + * @param separators + * Additional legal separator characters. Any occurrences of + * these in paths are replaced with the separator char. + */ + protected UriParser( char[] separators ) + { + m_separatorChar = '/'; + + // Remove the separator char from the separators array + HashSet set = new HashSet(); + set.add( new Character( '\\' ) ); + if( separators != null ) + { + for( int i = 0; i < separators.length; i++ ) + { + char separator = separators[ i ]; + if( separator == m_separatorChar ) + { + continue; + } + set.add( new Character( separator ) ); + } + } + m_separators = new char[ set.size() ]; + Iterator iter = set.iterator(); + for( int i = 0; i < m_separators.length; i++ ) + { + Character ch = (Character)iter.next(); + m_separators[ i ] = ch.charValue(); + } + + m_separator = String.valueOf( m_separatorChar ); + } + + /** + * Parses an absolute URI, splitting it into its components. This + * implementation assumes a "generic URI", as defined by RFC 2396. See + * {@link #parseGenericUri} for more info. + * + *

Sub-classes should override this method. + */ + public ParsedUri parseUri( String uriStr ) throws FileSystemException + { + ParsedUri retval = new ParsedUri(); + parseGenericUri( uriStr, retval ); + return retval; + } + + /** + * Parses a generic URI, as defined by RFC 2396. Briefly, a generic URI + * looks like: + * + *

+     * <scheme> '://' [ <userinfo> '@' ] <hostname> [ ':' <port> ] '/' <path>
+     * 
+ * + *

This method differs from the RFC, in that either / or \ is allowed + * as a path separator. + * + * @param uriStr + * The URI to parse. + * @param uri + * Used to return the parsed components of the URI. + */ + protected void parseGenericUri( String uriStr, ParsedUri uri ) throws FileSystemException + { + StringBuffer name = new StringBuffer(); + + // Extract the scheme and authority parts + extractToPath( uriStr, name, uri ); + + // Normalise the file name + normalisePath( name ); + uri.setPath( name.toString() ); + + // Build the root uri + StringBuffer rootUri = new StringBuffer(); + rootUri.append( uri.getScheme() ); + rootUri.append( "://" ); + rootUri.append( uri.getHostName() ); + uri.setRootURI( rootUri.toString() ); + } + + /** + * Extracts the scheme, userinfo, hostname and port components of an + * absolute "generic URI". + * + * @param uri + * The absolute URI to parse. + * + * @param name + * Used to return the remainder of the URI. + * + * @parsedUri + * Used to return the extracted components. + */ + protected void extractToPath( String uri, StringBuffer name, ParsedUri parsedUri ) + throws FileSystemException + { + // Extract the scheme + String scheme = extractScheme( uri, name ); + parsedUri.setScheme( scheme ); + + // Expecting "//" + if( name.length() < 2 || name.charAt( 0 ) != '/' || name.charAt( 1 ) != '/' ) + { + final String message = REZ.getString( "missing-double-slashes.error", uri ); + throw new FileSystemException( message ); + } + name.delete( 0, 2 ); + + // Extract userinfo + String userInfo = extractUserInfo( name ); + parsedUri.setUserInfo( userInfo ); + + // Extract hostname + String hostName = extractHostName( name ); + if( hostName == null ) + { + final String message = REZ.getString( "missing-hostname.error", uri ); + throw new FileSystemException( message ); + } + parsedUri.setHostName( hostName ); + + // Extract port + String port = extractPort( name ); + if( port != null && port.length() == 0 ) + { + final String message = REZ.getString( "missing-port.error", uri ); + throw new FileSystemException( message ); + } + parsedUri.setPort( port ); + + // Expecting '/' or empty name + if( name.length() > 0 && name.charAt( 0 ) != '/' ) + { + final String message = REZ.getString( "missing-hostname-path-sep.error", uri ); + throw new FileSystemException( message ); + } + } + + /** + * Extracts the user info from a URI. The :// 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 ://@ 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 ://@ + * 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 normalised 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 normalised 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 normalised 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 normalised 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 normalised 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: + *

    + *
  • Normalises separators, where more than one can be used. + *
  • Removes empty path elements. + *
  • Handles '.' and '..' elements. + *
  • Removes trailing separator. + *
+ */ + public void normalisePath( StringBuffer path ) throws FileSystemException + { + if( path.length() == 0 ) + { + return; + } + + // Adjust separators + fixSeparators( path ); + + // Determine the start of the first element + int startFirstElem = 0; + if( path.charAt( 0 ) == m_separatorChar ) + { + if( path.length() == 1 ) + { + return; + } + startFirstElem = 1; + } + + // Iterate over each element + int startElem = startFirstElem; + int maxlen = path.length(); + while( startElem < maxlen ) + { + // Find the end of the element + int endElem = startElem; + for( ; endElem < maxlen && path.charAt( endElem ) != m_separatorChar; endElem++ ) + { + } + + int elemLen = endElem - startElem; + if( elemLen == 0 ) + { + // An empty element - axe it + path.delete( endElem, endElem + 1 ); + maxlen = path.length(); + continue; + } + if( elemLen == 1 && path.charAt( startElem ) == '.' ) + { + // A '.' element - axe it + path.delete( startElem, endElem + 1 ); + maxlen = path.length(); + continue; + } + if( elemLen == 2 + && path.charAt( startElem ) == '.' + && path.charAt( startElem + 1 ) == '.' ) + { + // A '..' element - remove the previous element + if( startElem > startFirstElem ) + { + int pos = startElem - 2; + for( ; path.charAt( pos ) != m_separatorChar; pos-- ) + { + } + startElem = pos + 1; + } + path.delete( startElem, endElem + 1 ); + maxlen = path.length(); + continue; + } + + // A regular element + startElem = endElem + 1; + } + + // Remove trailing separator + if( path.charAt( maxlen - 1 ) == m_separatorChar && maxlen > 1 ) + { + path.delete( maxlen - 1, maxlen ); + } + } + + /** + * Adjusts the separators in a name. + */ + protected boolean fixSeparators( StringBuffer name ) + { + if( m_separators.length == 0 ) + { + // Only one valid separator, so don't need to do anything + return false; + } + + boolean changed = false; + int maxlen = name.length(); + for( int i = 0; i < maxlen; i++ ) + { + char ch = name.charAt( i ); + for( int j = 0; j < m_separators.length; j++ ) + { + char separator = m_separators[ j ]; + if( ch == separator ) + { + name.setCharAt( i, m_separatorChar ); + changed = true; + break; + } + } + } + return changed; + } + + /** + * Extracts the scheme from a URI. + * + * @param uri + * The URI. + * + * @return + * The scheme name. Returns null if there is no scheme. + */ + public static String extractScheme( String uri ) + { + return extractScheme( uri, null ); + } + + /** + * Extracts the scheme from a URI. + * + * @param uri + * The URI. + * + * @param buffer + * Returns the remainder of the URI. + * + * @return + * The scheme name. Returns null if there is no scheme. + */ + protected static String extractScheme( String uri, StringBuffer buffer ) + { + if( buffer != null ) + { + buffer.setLength( 0 ); + buffer.append( uri ); + } + + int maxPos = uri.length(); + for( int pos = 0; pos < maxPos; pos++ ) + { + char ch = uri.charAt( pos ); + + if( ch == ':' ) + { + // Found the end of the scheme + String scheme = uri.substring( 0, pos ); + if( buffer != null ) + { + buffer.delete( 0, pos + 1 ); + } + return scheme; + } + + if( ( ch >= 'a' && ch <= 'z' ) + || ( ch >= 'A' && ch <= 'Z' ) ) + { + // A scheme character + continue; + } + if( pos > 0 && + ( ( ch >= '0' && ch <= '9' ) + || ch == '+' || ch == '-' || ch == '.' ) ) + { + // A scheme character (these are not allowed as the first + // character of the scheme, but can be used as subsequent + // characters. + continue; + } + + // Not a scheme character + break; + } + + // No scheme in URI + return null; + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileNameParser.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileNameParser.java new file mode 100644 index 000000000..a397d4f88 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileNameParser.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider.ftp; + +import org.apache.aut.vfs.provider.UriParser; +import org.apache.aut.vfs.provider.ParsedUri; +import org.apache.aut.vfs.FileSystemException; + +/** + * A parser for FTP URI. + * + * @author Adam Murdoch + */ +public class FtpFileNameParser extends UriParser +{ + /** + * Parses an absolute URI, splitting it into its components. + */ + public ParsedUri parseUri( String uriStr ) throws FileSystemException + { + ParsedFtpUri uri = new ParsedFtpUri(); + + // FTP URI are generic URI (as per RFC 2396) + parseGenericUri( uriStr, uri ); + + // Split up the userinfo into a username and password + String userInfo = uri.getUserInfo(); + if( userInfo != null ) + { + int idx = userInfo.indexOf(':'); + if( idx == -1 ) + { + uri.setUserName( userInfo ); + } + else + { + String userName = userInfo.substring(0, idx); + String password = userInfo.substring(idx+1); + uri.setUserName( userName ); + uri.setPassword( password ); + } + } + + return uri; + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileObject.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileObject.java new file mode 100644 index 000000000..b06abfb60 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileObject.java @@ -0,0 +1,225 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider.ftp; + +import com.oroinc.net.ftp.FTPFile; +import java.io.InputStream; +import java.io.OutputStream; +import org.apache.aut.vfs.FileName; +import org.apache.aut.vfs.FileSystemException; +import org.apache.aut.vfs.FileType; +import org.apache.aut.vfs.provider.AbstractFileObject; +import org.apache.avalon.excalibur.i18n.Resources; +import org.apache.avalon.excalibur.i18n.ResourceManager; + +/** + * An FTP file. + * + * @author Adam Murdoch + */ +class FtpFileObject extends AbstractFileObject +{ + private static final Resources REZ + = ResourceManager.getPackageResources( FtpFileObject.class ); + + private FtpFileSystem m_ftpFs; + + // Cached info + private FTPFile m_fileInfo; + private FTPFile[] m_children; + + private static final FTPFile[] EMPTY_FTP_FILE_ARRAY = {}; + + public FtpFileObject( FileName name, FtpFileSystem fs ) + { + super( name, fs ); + m_ftpFs = fs; + } + + /** + * Called by child file objects, to locate their ftp file info. + */ + private FTPFile getChildFile( String name ) throws Exception + { + if( m_children == null ) + { + // List the children of this file + m_children = m_ftpFs.getClient().listFiles( getName().getPath() ); + if( m_children == null ) + { + m_children = EMPTY_FTP_FILE_ARRAY; + } + } + + // Look for the requested child + // TODO - use hash table + for( int i = 0; i < m_children.length; i++ ) + { + FTPFile child = m_children[ i ]; + if( child.getName().equals( name ) ) + { + // TODO - should be using something else to compare names + return child; + } + } + + return null; + } + + /** + * Attaches this file object to its file resource. + */ + protected void doAttach() throws Exception + { + // Get the parent folder to find the info for this file + FtpFileObject parent = (FtpFileObject)getParent(); + m_fileInfo = parent.getChildFile( getName().getBaseName() ); + if( m_fileInfo == null || !m_fileInfo.isDirectory() ) + { + m_children = EMPTY_FTP_FILE_ARRAY; + } + } + + /** + * Detaches this file object from its file resource. + */ + protected void doDetach() + { + m_fileInfo = null; + m_children = null; + } + + /** + * Called when the children of this file change. + */ + protected void onChildrenChanged() + { + m_children = null; + } + + /** + * Determines the type of the file, returns null if the file does not + * exist. + */ + protected FileType doGetType() throws Exception + { + if( m_fileInfo == null ) + { + // Does not exist + return null; + } + if( m_fileInfo.isDirectory() ) + { + return FileType.FOLDER; + } + if( m_fileInfo.isFile() ) + { + return FileType.FILE; + } + + final String message = REZ.getString( "get-type.error", getName() ); + throw new FileSystemException( message ); + } + + /** + * Lists the children of the file. + */ + protected String[] doListChildren() throws Exception + { + if( m_children == null ) + { + // List the children of this file + m_children = m_ftpFs.getClient().listFiles( getName().getPath() ); + if( m_children == null ) + { + m_children = EMPTY_FTP_FILE_ARRAY; + } + } + + String[] children = new String[ m_children.length ]; + for( int i = 0; i < m_children.length; i++ ) + { + FTPFile child = m_children[ i ]; + children[ i ] = child.getName(); + } + + return children; + } + + /** + * Deletes the file. + */ + protected void doDelete() throws Exception + { + if( !m_ftpFs.getClient().deleteFile( getName().getPath() ) ) + { + final String message = REZ.getString( "delete-file.error", getName() ); + throw new FileSystemException( message ); + } + } + + /** + * Creates this file as a folder. + */ + protected void doCreateFolder() throws Exception + { + if( !m_ftpFs.getClient().makeDirectory( getName().getPath() ) ) + { + final String message = REZ.getString( "create-folder.error", getName() ); + throw new FileSystemException( message ); + } + } + + /** + * Returns the size of the file content (in bytes). + */ + protected long doGetContentSize() throws Exception + { + return m_fileInfo.getSize(); + } + + /** + * Creates an input stream to read the file content from. + */ + protected InputStream doGetInputStream() throws Exception + { + return m_ftpFs.getClient().retrieveFileStream( getName().getPath() ); + } + + /** + * Notification of the input stream being closed. + */ + protected void doEndInput() throws Exception + { + if( !m_ftpFs.getClient().completePendingCommand() ) + { + final String message = REZ.getString( "finish-get.error", getName() ); + throw new FileSystemException( message ); + } + } + + /** + * Creates an output stream to write the file content to. + */ + protected OutputStream doGetOutputStream() throws Exception + { + return m_ftpFs.getClient().storeFileStream( getName().getPath() ); + } + + /** + * Notification of the output stream being closed. + */ + protected void doEndOutput() throws Exception + { + if( !m_ftpFs.getClient().completePendingCommand() ) + { + final String message = REZ.getString( "finish-put.error", getName() ); + throw new FileSystemException( message ); + } + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileSystem.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileSystem.java new file mode 100644 index 000000000..6b0f5a8d5 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileSystem.java @@ -0,0 +1,102 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider.ftp; + +import com.oroinc.net.ftp.FTP; +import com.oroinc.net.ftp.FTPClient; +import com.oroinc.net.ftp.FTPReply; +import java.io.IOException; +import org.apache.aut.vfs.FileName; +import org.apache.aut.vfs.FileObject; +import org.apache.aut.vfs.FileSystemException; +import org.apache.aut.vfs.provider.AbstractFileSystem; +import org.apache.avalon.excalibur.i18n.Resources; +import org.apache.avalon.excalibur.i18n.ResourceManager; + +/** + * An FTP file system. + * + * @author Adam Murdoch + */ +class FtpFileSystem extends AbstractFileSystem +{ + private static final Resources REZ + = ResourceManager.getPackageResources( FtpFileSystem.class ); + + private FTPClient m_client; + + public FtpFileSystem( FileName rootName, + String hostname, + String username, + String password ) throws FileSystemException + { + super( rootName ); + try + { + m_client = new FTPClient(); + m_client.connect( hostname ); + + int reply = m_client.getReplyCode(); + if( !FTPReply.isPositiveCompletion( reply ) ) + { + final String message = REZ.getString( "connect-rejected.error", hostname ); + throw new FileSystemException( message ); + } + + // Login + if( !m_client.login( username, password ) ) + { + final String message = REZ.getString( "login.error", hostname, username ); + throw new FileSystemException( message ); + } + + // Set binary mode + if( !m_client.setFileType( FTP.BINARY_FILE_TYPE ) ) + { + final String message = REZ.getString( "set-binary.error", hostname ); + throw new FileSystemException( message ); + } + } + catch( Exception exc ) + { + try + { + // Clean up + if( m_client.isConnected() ) + { + m_client.disconnect(); + } + } + catch( IOException e ) + { + // Ignore + } + + final String message = REZ.getString( "connect.error", hostname ); + throw new FileSystemException( message, exc ); + } + + // TODO - close connection + } + + /** + * Returns an FTP client to use. + */ + public FTPClient getClient() + { + return m_client; + } + + /** + * Creates a file object. + */ + protected FileObject createFile( FileName name ) throws FileSystemException + { + return new FtpFileObject( name, this ); + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileSystemProvider.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileSystemProvider.java new file mode 100644 index 000000000..18670c235 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/FtpFileSystemProvider.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider.ftp; + +import org.apache.aut.vfs.FileName; +import org.apache.aut.vfs.FileSystemException; +import org.apache.aut.vfs.provider.AbstractFileSystemProvider; +import org.apache.aut.vfs.provider.DefaultFileName; +import org.apache.aut.vfs.provider.FileSystem; +import org.apache.aut.vfs.provider.ParsedUri; +import org.apache.aut.vfs.provider.UriParser; + +/** + * A provider for FTP file systems. + * + * @author Adam Murdoch + */ +public class FtpFileSystemProvider extends AbstractFileSystemProvider +{ + private UriParser m_parser = new FtpFileNameParser(); + + /** + * Parses a URI into its components. + */ + protected ParsedUri parseURI( String uri ) throws FileSystemException + { + return m_parser.parseUri( uri ); + } + + /** + * Creates the filesystem. + */ + protected FileSystem createFileSystem( ParsedUri uri ) throws FileSystemException + { + ParsedFtpUri ftpUri = (ParsedFtpUri)uri; + + // Build the root name + FileName rootName = new DefaultFileName( m_parser, ftpUri.getRootURI(), "/" ); + + // Determine the username and password to use + String username = ftpUri.getUserName(); + if( username == null ) + { + username = "anonymous"; + } + String password = ftpUri.getPassword(); + if( password == null ) + { + password = "anonymous"; + } + + // Create the file system + return new FtpFileSystem( rootName, ftpUri.getHostName(), username, password ); + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/ParsedFtpUri.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/ParsedFtpUri.java new file mode 100644 index 000000000..03616ec27 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/ParsedFtpUri.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider.ftp; + +import org.apache.aut.vfs.provider.ParsedUri; + +/** + * A parsed FTP URI. + * + * @author Adam Murdoch + */ +public class ParsedFtpUri extends ParsedUri +{ + private String m_userName; + private String m_password; + + public String getUserName() + { + return m_userName; + } + + public void setUserName( String userName ) + { + m_userName = userName; + } + + public String getPassword() + { + return m_password; + } + + public void setPassword( String password ) + { + m_password = password; + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/Resources.properties b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/Resources.properties new file mode 100644 index 000000000..d2c712051 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/ftp/Resources.properties @@ -0,0 +1,9 @@ +get-type.error=Could not determine the file type of "{0}". +delete-file.error=Could not delete FTP file "{0}". +create-folder.error=Could not create FTP directory "{0}". +finish-get.error=Could not get FTP file "{0}". +finish-put.error=Could not put FTP file "{0}". +connect-rejected.error=Connection to FTP server on "{0}" rejected. +login.error=Could not login to FTP server on "{0}" as user "{1}". +set-binary.error=Could not switch to binary transfer mode. +connect.error=Could not connect to FTP server on "{0}". \ No newline at end of file diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFile.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFile.java new file mode 100644 index 000000000..4ff461891 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFile.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider.local; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.io.OutputStream; +import org.apache.aut.vfs.FileName; +import org.apache.aut.vfs.FileObject; +import org.apache.aut.vfs.FileSystemException; +import org.apache.aut.vfs.FileType; +import org.apache.aut.vfs.provider.AbstractFileObject; +import org.apache.avalon.excalibur.i18n.Resources; +import org.apache.avalon.excalibur.i18n.ResourceManager; + +/** + * A file object implementation which uses direct file access. + * + * @author Adam Murdoch + */ +final class LocalFile extends AbstractFileObject implements FileObject +{ + private static final Resources REZ + = ResourceManager.getPackageResources( LocalFile.class ); + + private File m_file; + private String m_fileName; + + /** + * Creates a non-root file. + */ + public LocalFile( LocalFileSystem fs, String fileName, FileName name ) + { + super( name, fs ); + m_fileName = fileName; + } + + /** + * Attaches this file object to its file resource. + */ + protected void doAttach() throws Exception + { + if( m_file == null ) + { + m_file = new File( m_fileName ); + } + } + + /** + * Returns the file's type. + */ + protected FileType doGetType() throws Exception + { + if( !m_file.exists() ) + { + return null; + } + if( m_file.isDirectory() ) + { + return FileType.FOLDER; + } + if( m_file.isFile() ) + { + return FileType.FILE; + } + + final String message = REZ.getString( "get-type.error", m_file ); + throw new FileSystemException( message ); + } + + /** + * Returns the children of the file. + */ + protected String[] doListChildren() throws Exception + { + return m_file.list(); + } + + /** + * Deletes this file, and all children. + */ + public void doDelete() throws Exception + { + if( !m_file.delete() ) + { + final String message = REZ.getString( "delete-file.error", m_file ); + throw new FileSystemException( message ); + } + } + + /** + * Creates this folder. + */ + protected void doCreateFolder() throws Exception + { + if( !m_file.mkdir() ) + { + final String message = REZ.getString( "create-folder.error", m_file ); + throw new FileSystemException( message ); + } + } + + /** + * Creates an input stream to read the content from. + */ + protected InputStream doGetInputStream() throws Exception + { + return new FileInputStream( m_file ); + } + + /** + * Creates an output stream to write the file content to. + */ + protected OutputStream doGetOutputStream() throws Exception + { + return new FileOutputStream( m_file ); + } + + /** + * Returns the size of the file content (in bytes). + */ + protected long doGetContentSize() throws Exception + { + return m_file.length(); + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileNameParser.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileNameParser.java new file mode 100644 index 000000000..40c0ec696 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileNameParser.java @@ -0,0 +1,228 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider.local; + +import java.io.File; +import org.apache.aut.vfs.FileSystemException; +import org.apache.aut.vfs.provider.ParsedUri; +import org.apache.aut.vfs.provider.UriParser; +import org.apache.avalon.excalibur.i18n.Resources; +import org.apache.avalon.excalibur.i18n.ResourceManager; + +/** + * A name parser. + * + * @author Adam Murdoch + */ +class LocalFileNameParser extends UriParser +{ + private static final Resources REZ + = ResourceManager.getPackageResources( LocalFileNameParser.class ); + + private boolean m_windowsNames; + + public LocalFileNameParser() + { + super( new char[]{File.separatorChar, '/', '\\'} ); + m_windowsNames = ( System.getProperty( "os.name" ).toLowerCase().indexOf( "windows" ) != -1 ); + } + + /** + * Determines if a name is an absolute file name. + */ + public boolean isAbsoluteName( String name ) + { + // TODO - this is yucky + StringBuffer b = new StringBuffer( name ); + try + { + fixSeparators( b ); + extractRootPrefix( name, b ); + return true; + } + catch( FileSystemException e ) + { + return false; + } + } + + /** + * Parses an absolute URI, splitting it into its components. + * + * @param name + * The URI. + */ + public ParsedUri parseUri( String uriStr ) throws FileSystemException + { + StringBuffer name = new StringBuffer(); + ParsedFileUri uri = new ParsedFileUri(); + + // Extract the scheme + String scheme = extractScheme( uriStr, name ); + uri.setScheme( scheme ); + + // Adjust the separators + fixSeparators( name ); + + // Extract the root prefix + String rootFile = extractRootPrefix( uriStr, name ); + uri.setRootFile( rootFile ); + + // Normalise the path + normalisePath( name ); + uri.setPath( name.toString() ); + + // Build the root URI + StringBuffer rootUri = new StringBuffer(); + rootUri.append( scheme ); + rootUri.append( "://" ); + rootUri.append( rootFile ); + uri.setRootURI( rootUri.toString() ); + + return uri; + } + + /** + * Pops the root prefix off a URI, which has had the scheme removed. + */ + private String extractRootPrefix( String uri, + StringBuffer name ) + throws FileSystemException + { + // TODO - split this into sub-classes + if( m_windowsNames ) + { + return extractWindowsRootPrefix( uri, name ); + } + else + { + // Looking for + 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} ':' '/' + // ['/'] '//' '/' ( '/' | ) + + // 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 ':' '/' + 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 '/' ( '/' | ) + + // 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 '/' + 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 '/' ( '/' | ) + String prefix = name.substring( 0, pos ); + name.delete( 0, pos ); + return prefix; + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileSystem.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileSystem.java new file mode 100644 index 000000000..79ffc4bfc --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileSystem.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider.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 ); + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileSystemProvider.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileSystemProvider.java new file mode 100644 index 000000000..7cf654c23 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileSystemProvider.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider.local; + +import org.apache.aut.vfs.FileObject; +import org.apache.aut.vfs.FileSystemException; +import org.apache.aut.vfs.provider.AbstractFileSystemProvider; +import org.apache.aut.vfs.provider.DefaultFileName; +import org.apache.aut.vfs.provider.FileSystem; +import org.apache.aut.vfs.provider.FileSystemProvider; +import org.apache.aut.vfs.provider.ParsedUri; + +/** + * A file system provider, which uses direct file access. + * + * @author Adam Murdoch + */ +public class LocalFileSystemProvider extends AbstractFileSystemProvider + implements FileSystemProvider +{ + private LocalFileNameParser m_parser = new LocalFileNameParser(); + + /** + * Determines if a name is an absolute file name. + */ + public boolean isAbsoluteLocalName( String name ) + { + return m_parser.isAbsoluteName( name ); + } + + /** + * Finds a file by local file name. + */ + public FileObject findFileByLocalName( String name ) throws FileSystemException + { + // TODO - tidy this up, no need to turn the name into an absolute URI, + // and then straight back again + return findFile( "file:" + name ); + } + + /** + * Parses a URI into its components. The returned value is used to + * locate the file system in the cache (using the root prefix), and is + * passed to {@link #createFileSystem} to create the file system. + * + *

The provider can annotate this object with any additional + * information it requires to create a file system from the URI. + */ + protected ParsedUri parseURI( String uri ) throws FileSystemException + { + return m_parser.parseUri( uri ); + } + + /** + * Creates the filesystem. + */ + protected FileSystem createFileSystem( ParsedUri uri ) throws FileSystemException + { + // Build the name of the root file. + ParsedFileUri fileUri = (ParsedFileUri)uri; + String rootFile = fileUri.getRootFile(); + + // Create the file system + DefaultFileName rootName = new DefaultFileName( m_parser, fileUri.getRootURI(), "/" ); + return new LocalFileSystem( rootName, rootFile ); + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/ParsedFileUri.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/ParsedFileUri.java new file mode 100644 index 000000000..90df6d2bf --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/ParsedFileUri.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider.local; + +import org.apache.aut.vfs.provider.ParsedUri; + +/** + * A parsed file URI. + * + * @author Adam Murdoch + */ +public class ParsedFileUri extends ParsedUri +{ + private String m_rootFile; + + public String getRootFile() + { + return m_rootFile; + } + + public void setRootFile( String rootPrefix ) + { + m_rootFile = rootPrefix; + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/Resources.properties b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/Resources.properties new file mode 100644 index 000000000..df782dee9 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/Resources.properties @@ -0,0 +1,5 @@ +get-type.error=Could not determine the type of "{0}". +delete-file.error=Could not delete "{0}". +create-folder.error=Could not create directory "{0}". +not-absolute-file-name.error=URI "{0}" is not an absolute file name. +missing-share-name.error=Share name missing from UNC file name "{0}". diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/ParsedSmbUri.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/ParsedSmbUri.java new file mode 100644 index 000000000..6ee462f94 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/ParsedSmbUri.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider.smb; + +import org.apache.aut.vfs.provider.ParsedUri; + +/** + * A parsed SMB URI. + * + * @author Adam Murdoch + */ +public class ParsedSmbUri extends ParsedUri +{ + private String m_share; + + public String getShare() + { + return m_share; + } + + public void setShare( String share ) + { + m_share = share; + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/Resources.properties b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/Resources.properties new file mode 100644 index 000000000..41e8f0245 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/Resources.properties @@ -0,0 +1,2 @@ +missing-share-name.error=The share name is missing from URI "{0}". +get-type.error=Could not detemine the type of "{0}". \ No newline at end of file diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileNameParser.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileNameParser.java new file mode 100644 index 000000000..0e4f5a175 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileNameParser.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider.smb; + +import org.apache.aut.vfs.FileSystemException; +import org.apache.aut.vfs.provider.ParsedUri; +import org.apache.aut.vfs.provider.UriParser; +import org.apache.avalon.excalibur.i18n.Resources; +import org.apache.avalon.excalibur.i18n.ResourceManager; + +/** + * A parser for SMB URI. + * + * @author Adam Murdoch + */ +public class SmbFileNameParser extends UriParser +{ + private static final Resources REZ + = ResourceManager.getPackageResources( SmbFileNameParser.class ); + + /** + * Parses an absolute URI, splitting it into its components. + */ + public ParsedUri parseUri( String uriStr ) throws FileSystemException + { + ParsedSmbUri uri = new ParsedSmbUri(); + StringBuffer name = new StringBuffer(); + + // Extract the scheme and authority parts + extractToPath( uriStr, name, uri ); + + // Normalise paths + fixSeparators( name ); + + // Extract the share + String share = extractFirstElement( name ); + if( share == null ) + { + final String message = REZ.getString( "missing-share-name.error", uriStr ); + throw new FileSystemException( message ); + } + uri.setShare( share ); + + // Set the path + uri.setPath( name.toString() ); + + // Set the root URI + StringBuffer rootUri = new StringBuffer(); + rootUri.append( uri.getScheme() ); + rootUri.append( "://" ); + String userInfo = uri.getUserInfo(); + if( userInfo != null ) + { + rootUri.append( userInfo ); + rootUri.append( '@' ); + } + rootUri.append( uri.getHostName() ); + rootUri.append( '/' ); + rootUri.append( share ); + uri.setRootURI( rootUri.toString() ); + + return uri; + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileObject.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileObject.java new file mode 100644 index 000000000..6620ffa95 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileObject.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider.smb; + +import java.io.InputStream; +import java.io.OutputStream; +import jcifs.smb.SmbFile; +import jcifs.smb.SmbFileInputStream; +import jcifs.smb.SmbFileOutputStream; +import org.apache.aut.vfs.FileName; +import org.apache.aut.vfs.FileObject; +import org.apache.aut.vfs.FileSystemException; +import org.apache.aut.vfs.FileType; +import org.apache.aut.vfs.provider.AbstractFileObject; +import org.apache.avalon.excalibur.i18n.ResourceManager; +import org.apache.avalon.excalibur.i18n.Resources; + +/** + * A file in an SMB file system. + * + * @author Adam Murdoch + */ +public class SmbFileObject extends AbstractFileObject implements FileObject +{ + private static final Resources REZ + = ResourceManager.getPackageResources( SmbFileObject.class ); + + private String m_fileName; + private SmbFile m_file; + + protected SmbFileObject( String fileName, FileName name, SmbFileSystem fs ) + { + super( name, fs ); + m_fileName = fileName; + } + + /** + * Attaches this file object to its file resource. + */ + protected void doAttach() throws Exception + { + // Defer creation of the SmbFile to here + if( m_file == null ) + { + m_file = new SmbFile( m_fileName ); + } + } + + /** + * Detaches this file object from its file resource. + */ + protected void doDetach() + { + // Need to throw away the file when the file's type changes, because + // the SmbFile caches the type + m_file = null; + } + + /** + * Determines the type of the file, returns null if the file does not + * exist. + */ + protected FileType doGetType() throws Exception + { + // Need to check whether parent exists or not, because SmbFile.exists() + // throws an exception if it does not + // TODO - patch jCIFS? + + FileObject parent = getParent(); + if( parent != null && !parent.exists() ) + { + return null; + } + + if( !m_file.exists() ) + { + return null; + } + if( m_file.isDirectory() ) + { + return FileType.FOLDER; + } + if( m_file.isFile() ) + { + return FileType.FILE; + } + final String message = REZ.getString( "get-type.error", getName() ); + throw new FileSystemException( message ); + } + + /** + * Lists the children of the file. Is only called if {@link #doGetType} + * returns {@link FileType#FOLDER}. + */ + protected String[] doListChildren() throws Exception + { + return m_file.list(); + } + + /** + * Deletes the file. + */ + protected void doDelete() throws Exception + { + m_file.delete(); + } + + /** + * Creates this file as a folder. + */ + protected void doCreateFolder() throws Exception + { + m_file.mkdir(); + } + + /** + * Returns the size of the file content (in bytes). + */ + protected long doGetContentSize() throws Exception + { + return m_file.length(); + } + + /** + * Creates an input stream to read the file content from. + */ + protected InputStream doGetInputStream() throws Exception + { + return new SmbFileInputStream( m_file ); + } + + /** + * Creates an output stream to write the file content to. + */ + protected OutputStream doGetOutputStream() throws Exception + { + return new SmbFileOutputStream( m_file ); + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileSystem.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileSystem.java new file mode 100644 index 000000000..9c7e3b098 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileSystem.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider.smb; + +import org.apache.aut.vfs.FileName; +import org.apache.aut.vfs.FileObject; +import org.apache.aut.vfs.FileSystemException; +import org.apache.aut.vfs.provider.AbstractFileSystem; +import org.apache.aut.vfs.provider.FileSystem; + +/** + * A SMB file system. + * + * @author Adam Murdoch + */ +public class SmbFileSystem extends AbstractFileSystem implements FileSystem +{ + public SmbFileSystem( FileName rootName ) + { + super( rootName ); + } + + /** + * Creates a file object. + */ + protected FileObject createFile( FileName name ) throws FileSystemException + { + String fileName = name.getURI(); + return new SmbFileObject( fileName, name, this ); + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileSystemProvider.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileSystemProvider.java new file mode 100644 index 000000000..f9e7eb276 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/smb/SmbFileSystemProvider.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider.smb; + +import org.apache.aut.vfs.FileName; +import org.apache.aut.vfs.FileSystemException; +import org.apache.aut.vfs.provider.AbstractFileSystemProvider; +import org.apache.aut.vfs.provider.DefaultFileName; +import org.apache.aut.vfs.provider.FileSystem; +import org.apache.aut.vfs.provider.FileSystemProvider; +import org.apache.aut.vfs.provider.ParsedUri; + +/** + * A provider for SMB (Samba, Windows share) file systems. + * + * @author Adam Murdoch + */ +public class SmbFileSystemProvider extends AbstractFileSystemProvider implements FileSystemProvider +{ + SmbFileNameParser m_parser = new SmbFileNameParser(); + + /** + * Parses a URI into its components. + */ + protected ParsedUri parseURI( String uri ) throws FileSystemException + { + return m_parser.parseUri( uri ); + } + + /** + * Creates the filesystem. + */ + protected FileSystem createFileSystem( ParsedUri uri ) throws FileSystemException + { + ParsedSmbUri smbUri = (ParsedSmbUri)uri; + + FileName rootName = new DefaultFileName( m_parser, smbUri.getRootURI(), "/" ); + return new SmbFileSystem( rootName ); + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ParsedZipUri.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ParsedZipUri.java new file mode 100644 index 000000000..0dda462ae --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ParsedZipUri.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider.zip; + +import org.apache.aut.vfs.provider.ParsedUri; + +/** + * A parsed Zip URI. + * + * @author Adam Murdoch + */ +public class ParsedZipUri extends ParsedUri +{ + private String m_zipFile; + + public String getZipFile() + { + return m_zipFile; + } + + public void setZipFile( String zipFile ) + { + m_zipFile = zipFile; + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/Resources.properties b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/Resources.properties new file mode 100644 index 000000000..4885c50ca --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/Resources.properties @@ -0,0 +1 @@ +open-zip-file.error=Could not open Zip file "{0}". diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileNameParser.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileNameParser.java new file mode 100644 index 000000000..7f804efd7 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileNameParser.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider.zip; + +import org.apache.aut.vfs.FileSystemException; +import org.apache.aut.vfs.provider.ParsedUri; +import org.apache.aut.vfs.provider.UriParser; + +/** + * A parser for Zip file names. + * + * @author Adam Murdoch + */ +public class ZipFileNameParser extends UriParser +{ + /** + * Parses an absolute URI, splitting it into its components. + * + * @param name + * The URI. + */ + public ParsedUri parseUri( String uriStr ) throws FileSystemException + { + StringBuffer name = new StringBuffer(); + ParsedZipUri uri = new ParsedZipUri(); + + // Extract the scheme + String scheme = extractScheme( uriStr, name ); + uri.setScheme( scheme ); + + // Extract the Zip file name + String zipName = extractZipName( name ); + uri.setZipFile( zipName ); + + // Adjust the separators + fixSeparators( name ); + + // Normalise the file name + normalisePath( name ); + uri.setPath( name.toString() ); + + // Build root URI + StringBuffer rootUri = new StringBuffer(); + rootUri.append( scheme ); + rootUri.append( ":" ); + rootUri.append( zipName ); + rootUri.append( "!" ); + uri.setRootURI( rootUri.toString() ); + + return uri; + } + + /** + * Pops the root prefix off a URI, which has had the scheme removed. + */ + protected String extractZipName( StringBuffer uri ) throws FileSystemException + { + // Looking for ! + // 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; + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileObject.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileObject.java new file mode 100644 index 000000000..9bbf7e484 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileObject.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider.zip; + +import java.io.InputStream; +import java.util.HashSet; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.apache.aut.vfs.FileName; +import org.apache.aut.vfs.FileObject; +import org.apache.aut.vfs.FileType; +import org.apache.aut.vfs.provider.AbstractFileObject; + +/** + * A file in a Zip file system. + * + * @author Adam Murdoch + */ +class ZipFileObject extends AbstractFileObject implements FileObject +{ + private ZipEntry m_entry; + private ZipFile m_file; + private FileType m_type; + private HashSet m_children = new HashSet(); + + public ZipFileObject( FileName name, + ZipEntry entry, + ZipFile zipFile, + ZipFileSystem fs ) + { + super( name, fs ); + m_type = FileType.FILE; + m_entry = entry; + m_file = zipFile; + } + + public ZipFileObject( FileName name, boolean exists, ZipFileSystem fs ) + { + super( name, fs ); + if( exists ) + { + m_type = FileType.FOLDER; + } + // else _type = null + } + + /** + * Attaches a child + */ + public void attachChild( FileName childName ) + { + m_children.add( childName.getBaseName() ); + } + + /** + * Returns true if this file is read-only. + */ + protected boolean isReadOnly() + { + return true; + } + + /** + * Returns the file's type. + */ + protected FileType doGetType() + { + return m_type; + } + + /** + * Lists the children of the file. + */ + protected String[] doListChildren() + { + return (String[])m_children.toArray( new String[ m_children.size() ] ); + } + + /** + * Returns the size of the file content (in bytes). Is only called if + * {@link #doGetType} returns {@link FileType#FILE}. + */ + protected long doGetContentSize() + { + return m_entry.getSize(); + } + + /** + * Creates an input stream to read the file content from. Is only called + * if {@link #doGetType} returns {@link FileType#FILE}. The input stream + * returned by this method is guaranteed to be closed before this + * method is called again. + */ + protected InputStream doGetInputStream() throws Exception + { + return m_file.getInputStream( m_entry ); + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileSystem.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileSystem.java new file mode 100644 index 000000000..919003a91 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileSystem.java @@ -0,0 +1,112 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider.zip; + +import java.io.File; +import java.io.IOException; +import java.util.Enumeration; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import org.apache.aut.vfs.FileName; +import org.apache.aut.vfs.FileObject; +import org.apache.aut.vfs.FileSystemException; +import org.apache.aut.vfs.provider.AbstractFileSystem; +import org.apache.aut.vfs.provider.DefaultFileName; +import org.apache.aut.vfs.provider.FileSystem; +import org.apache.avalon.excalibur.i18n.Resources; +import org.apache.avalon.excalibur.i18n.ResourceManager; + +/** + * A read-only file system for Zip/Jar files. + * + * @author Adam Murdoch + */ +public class ZipFileSystem extends AbstractFileSystem implements FileSystem +{ + private static final Resources REZ + = ResourceManager.getPackageResources( ZipFileSystem.class ); + + private File m_file; + private ZipFile m_zipFile; + + public ZipFileSystem( DefaultFileName rootName, File file ) throws FileSystemException + { + super( rootName ); + m_file = file; + + // Open the Zip file + if( !file.exists() ) + { + // Don't need to do anything + return; + } + + try + { + m_zipFile = new ZipFile( m_file ); + } + catch( IOException ioe ) + { + final String message = REZ.getString( "open-zip-file.error", m_file ); + throw new FileSystemException( message, ioe ); + } + + // Build the index + Enumeration entries = m_zipFile.entries(); + while( entries.hasMoreElements() ) + { + ZipEntry entry = (ZipEntry)entries.nextElement(); + FileName name = rootName.resolveName( entry.getName() ); + + // Create the file + ZipFileObject fileObj; + if( entry.isDirectory() ) + { + if( getFile( name ) != null ) + { + // Already created implicitly + continue; + } + fileObj = new ZipFileObject( name, true, this ); + } + else + { + fileObj = new ZipFileObject( name, entry, m_zipFile, this ); + } + putFile( fileObj ); + + // Make sure all ancestors exist + // TODO - create these on demand + ZipFileObject parent = null; + for( FileName parentName = name.getParent(); + parentName != null; + fileObj = parent, parentName = parentName.getParent() ) + { + // Locate the parent + parent = (ZipFileObject)getFile( parentName ); + if( parent == null ) + { + parent = new ZipFileObject( parentName, true, this ); + putFile( parent ); + } + + // Attach child to parent + parent.attachChild( fileObj.getName() ); + } + } + } + + /** + * Creates a file object. + */ + protected FileObject createFile( FileName name ) throws FileSystemException + { + // This is only called for files which do not exist in the Zip file + return new ZipFileObject( name, false, this ); + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileSystemProvider.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileSystemProvider.java new file mode 100644 index 000000000..21f540646 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/zip/ZipFileSystemProvider.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs.provider.zip; + +import java.io.File; +import org.apache.aut.vfs.FileSystemException; +import org.apache.aut.vfs.provider.AbstractFileSystemProvider; +import org.apache.aut.vfs.provider.DefaultFileName; +import org.apache.aut.vfs.provider.FileSystem; +import org.apache.aut.vfs.provider.FileSystemProvider; +import org.apache.aut.vfs.provider.ParsedUri; + +/** + * A file system provider for Zip/Jar files. Provides read-only file + * systems, for local Zip files only. + * + * @author Adam Murdoch + */ +public class ZipFileSystemProvider extends AbstractFileSystemProvider + implements FileSystemProvider +{ + private ZipFileNameParser m_parser = new ZipFileNameParser(); + + /** + * Parses a URI into its components. + */ + protected ParsedUri parseURI( String uri ) throws FileSystemException + { + return m_parser.parseUri( uri ); + } + + /** + * Creates the filesystem. + */ + protected FileSystem createFileSystem( ParsedUri uri ) throws FileSystemException + { + // Locate the Zip file + ParsedZipUri zipUri = (ParsedZipUri)uri; + String fileName = zipUri.getZipFile(); + // TODO - use the context to resolve zip file to a FileObject + File file = new File( fileName ).getAbsoluteFile(); + DefaultFileName name = new DefaultFileName( m_parser, zipUri.getRootURI(), "/" ); + return new ZipFileSystem( name, file ); + } +} diff --git a/proposal/myrmidon/src/test/org/apache/aut/vfs/BasicFileSystemTestBase.java b/proposal/myrmidon/src/test/org/apache/aut/vfs/BasicFileSystemTestBase.java new file mode 100644 index 000000000..d792b3fa6 --- /dev/null +++ b/proposal/myrmidon/src/test/org/apache/aut/vfs/BasicFileSystemTestBase.java @@ -0,0 +1,587 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import junit.framework.TestCase; +import org.apache.aut.vfs.provider.DefaultFileSystemManager; + +/** + * File system test cases, which verifies the structure and naming + * functionality. + * + * Works from a base folder, and assumes a particular structure under + * that base folder. + * + * @author Adam Murdoch + */ +public abstract class BasicFileSystemTestBase extends TestCase +{ + protected FileObject m_baseFolder; + protected DefaultFileSystemManager m_manager; + + // Contents of "file1.txt" + private String m_charContent; + + public BasicFileSystemTestBase( String name ) + { + super( name ); + } + + /** + * Builds the expected folder structure. + */ + private FileInfo buildExpectedStructure() + { + // Build the expected structure + FileInfo base = new FileInfo( "test", FileType.FOLDER ); + base.addChild( new FileInfo( "file1.txt", FileType.FILE ) ); + base.addChild( new FileInfo( "empty.txt", FileType.FILE ) ); + base.addChild( new FileInfo( "emptydir", FileType.FOLDER ) ); + + FileInfo dir = new FileInfo( "dir1", FileType.FOLDER ); + base.addChild( dir ); + dir.addChild( new FileInfo( "file1.txt", FileType.FILE ) ); + dir.addChild( new FileInfo( "file2.txt", FileType.FILE ) ); + dir.addChild( new FileInfo( "file3.txt", FileType.FILE ) ); + return base; + } + + /** + * Returns the URI for the base folder. + */ + protected abstract String getBaseFolderURI(); + + /** + * Sets up the test + */ + protected void setUp() throws Exception + { + // Create the file system manager + m_manager = new DefaultFileSystemManager(); + + // Locate the base folder + m_baseFolder = m_manager.resolveFile( getBaseFolderURI() ); + + // Build the expected content of "file1.txt" + String eol = System.getProperty( "line.separator" ); + m_charContent = "This is a test file." + eol + "With 2 lines in it." + eol; + } + + /** + * Tests resolution of absolute URI. + */ + public void testAbsoluteURI() throws Exception + { + // Try fetching base folder again by its URI + String uri = m_baseFolder.getName().getURI(); + FileObject file = m_manager.resolveFile( uri ); + + assertSame( "file object", m_baseFolder, file ); + } + + /** + * Tests resolution of relative file names via the FS manager + */ + public void testRelativeURI() throws Exception + { + // Build base dir + m_manager.setBaseFile( m_baseFolder ); + + // Locate the base dir + FileObject file = m_manager.resolveFile( "." ); + assertSame( "file object", m_baseFolder, file ); + + // Locate a child + file = m_manager.resolveFile( "some-child" ); + assertSame( "file object", m_baseFolder, file.getParent() ); + + // Locate a descendent + file = m_manager.resolveFile( "some-folder/some-file" ); + assertSame( "file object", m_baseFolder, file.getParent().getParent() ); + + // Locate parent + file = m_manager.resolveFile( ".." ); + assertSame( "file object", m_baseFolder.getParent(), file ); + } + + /** + * Tests the root file name. + */ + public void testRootFileName() throws Exception + { + // Locate the root file + FileName rootName = m_baseFolder.getRoot().getName(); + + // Test that the root path is "/" + assertEquals( "root path", "/", rootName.getPath() ); + + // Test that the root basname is "" + assertEquals( "root base name", "", rootName.getBaseName() ); + + // Test that the root name has no parent + assertNull( "root parent", rootName.getParent() ); + } + + /** + * Tests child file names. + */ + public void testChildName() throws Exception + { + FileName baseName = m_baseFolder.getName(); + String basePath = baseName.getPath(); + FileName name = baseName.resolveName( "some-child", NameScope.CHILD ); + + // Test path is absolute + assertTrue( "is absolute", basePath.startsWith( "/" ) ); + + // Test base name + assertEquals( "base name", "some-child", name.getBaseName() ); + + // Test absolute path + assertEquals( "absolute path", basePath + "/some-child", name.getPath() ); + + // Test parent path + assertEquals( "parent absolute path", basePath, name.getParent().getPath() ); + + // Try using a compound name to find a child + try + { + FileName name2 = name.resolveName( "a/b", NameScope.CHILD ); + assertTrue( false ); + } + catch( FileSystemException e ) + { + } + + // Try using a empty name to find a child + try + { + FileName name2 = name.resolveName( "", NameScope.CHILD ); + assertTrue( false ); + } + catch( FileSystemException e ) + { + } + + // Try using '.' to find a child + try + { + FileName name2 = name.resolveName( ".", NameScope.CHILD ); + assertTrue( false ); + } + catch( FileSystemException e ) + { + } + + // Try using '..' to find a child + try + { + FileName name2 = name.resolveName( "..", NameScope.CHILD ); + assertTrue( false ); + } + catch( FileSystemException e ) + { + } + } + + /** + * Checks that a relative name resolves to the expected absolute path. + */ + private void assertSameName(String expectedPath, + FileName baseName, + String relName ) throws Exception + { + FileName name = baseName.resolveName(relName); + assertEquals( expectedPath, name.getPath() ); + + // Replace the separators + relName.replace('\\', '/'); + name = baseName.resolveName(relName); + assertEquals( expectedPath, name.getPath() ); + + // And again + relName.replace('/', '\\'); + name = baseName.resolveName(relName); + assertEquals( expectedPath, name.getPath() ); + } + + /** + * Tests relative name resolution, relative to the base folder. + */ + public void testNameResolution() throws Exception + { + FileName baseName = m_baseFolder.getName(); + String parentPath = baseName.getParent().getPath(); + String path = baseName.getPath(); + String childPath = path + "/some-child"; + + // Test empty relative path + assertSameName( path, baseName, "" ); + + // Test . relative path + assertSameName( path, baseName, "." ); + + // Test ./ relative path + assertSameName( path, baseName, "./" ); + + // Test .// relative path + assertSameName( path, baseName, ".//" ); + + // Test .///.///. relative path + assertSameName( path, baseName, ".///.///." ); + assertSameName( path, baseName, "./\\/.\\//." ); + + // Test /.. 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 /../.. relative path + assertSameName( parentPath, baseName, "a/../.." ); + + // Test relative path + assertSameName( childPath, baseName, "some-child" ); + + // Test ./ relative path + assertSameName( childPath, baseName, "./some-child" ); + + // Test .// relative path + assertSameName( childPath, baseName, "./some-child/" ); + + // Test /././././ relative path + assertSameName( childPath, baseName, "./some-child/././././" ); + + // Test /../ relative path + assertSameName( childPath, baseName, "a/../some-child" ); + + // Test //../../ 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 ); + } + } +} diff --git a/proposal/myrmidon/src/test/org/apache/aut/vfs/FtpFileSystemTest.java b/proposal/myrmidon/src/test/org/apache/aut/vfs/FtpFileSystemTest.java new file mode 100644 index 000000000..c0ad5e886 --- /dev/null +++ b/proposal/myrmidon/src/test/org/apache/aut/vfs/FtpFileSystemTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs; + +/** + * Tests for FTP file systems. + * + * @author Adam Murdoch + */ +public class FtpFileSystemTest extends WritableFileSystemTestBase +{ + public FtpFileSystemTest( String name ) + { + super( name ); + } + + /** + * Returns the URI for the base folder. + */ + protected String getBaseFolderURI() + { + return System.getProperty( "test.ftp.uri" ) + "/read-tests"; + } + + /** + * Returns the URI for the area to do tests in. + */ + protected String getWriteFolderURI() + { + return System.getProperty( "test.ftp.uri" ) + "/write-tests"; + } +} diff --git a/proposal/myrmidon/src/test/org/apache/aut/vfs/LocalFileSystemTest.java b/proposal/myrmidon/src/test/org/apache/aut/vfs/LocalFileSystemTest.java new file mode 100644 index 000000000..5a0a1ea2a --- /dev/null +++ b/proposal/myrmidon/src/test/org/apache/aut/vfs/LocalFileSystemTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs; + +import java.io.File; + +/** + * Tests for the local file system. + * + * @author Adam Murdoch + */ +public class LocalFileSystemTest extends WritableFileSystemTestBase +{ + private File m_baseDir; + + public LocalFileSystemTest( String name ) + { + super( name ); + String baseDir = System.getProperty( "test.local.dir" ); + m_baseDir = new File( baseDir ); + } + + /** + * Returns the URI for the base folder. + */ + protected String getBaseFolderURI() + { + String testDir = new File( m_baseDir, "read-tests" ).getAbsolutePath(); + String uri = "file:/" + testDir; + return uri; + } + + /** + * Returns the URI for the area to do tests in. + */ + protected String getWriteFolderURI() + { + String testDir = new File( m_baseDir, "write-tests" ).getAbsolutePath(); + String uri = "file:/" + testDir; + return uri; + } + + /** + * Tests resolution of an absolute file name. + */ + public void testAbsoluteFileName() throws Exception + { + // Locate file by absolute file name + String fileName = new File( "testdir" ).getAbsolutePath(); + FileObject absFile = m_manager.resolveFile( fileName ); + + // Locate file by URI + String uri = "file://" + fileName.replace( File.separatorChar, '/' ); + FileObject uriFile = m_manager.resolveFile( uri ); + + assertSame( "file object", absFile, uriFile ); + } + +} diff --git a/proposal/myrmidon/src/test/org/apache/aut/vfs/ReadOnlyFileSystemTestBase.java b/proposal/myrmidon/src/test/org/apache/aut/vfs/ReadOnlyFileSystemTestBase.java new file mode 100644 index 000000000..9cd87e0a6 --- /dev/null +++ b/proposal/myrmidon/src/test/org/apache/aut/vfs/ReadOnlyFileSystemTestBase.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs; + +/** + * File system tests which check that a read-only file system cannot be + * changed. + * + * @author Adam Murdoch + */ +public abstract class ReadOnlyFileSystemTestBase extends BasicFileSystemTestBase +{ + public ReadOnlyFileSystemTestBase( String name ) + { + super( name ); + } +} diff --git a/proposal/myrmidon/src/test/org/apache/aut/vfs/SmbFileSystemTest.java b/proposal/myrmidon/src/test/org/apache/aut/vfs/SmbFileSystemTest.java new file mode 100644 index 000000000..2e09a77e0 --- /dev/null +++ b/proposal/myrmidon/src/test/org/apache/aut/vfs/SmbFileSystemTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs; + +/** + * Tests for the SMB file system. + * + * @author Adam Murdoch + */ +public class SmbFileSystemTest extends WritableFileSystemTestBase +{ + public SmbFileSystemTest( String name ) + { + super( name ); + } + + /** + * Returns the URI for the base folder. + */ + protected String getBaseFolderURI() + { + return System.getProperty( "test.smb.uri" ) + "/read-tests"; + } + + /** + * Returns the URI for the area to do tests in. + */ + protected String getWriteFolderURI() + { + return System.getProperty( "test.smb.uri" ) + "/write-tests"; + } +} diff --git a/proposal/myrmidon/src/test/org/apache/aut/vfs/WritableFileSystemTestBase.java b/proposal/myrmidon/src/test/org/apache/aut/vfs/WritableFileSystemTestBase.java new file mode 100644 index 000000000..65bc254f5 --- /dev/null +++ b/proposal/myrmidon/src/test/org/apache/aut/vfs/WritableFileSystemTestBase.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs; + +import java.io.Writer; +import java.io.OutputStream; +import java.util.HashSet; +import java.util.Set; + +/** + * File system test that check that a file system can be modified. + * + * @author Adam Murdoch + */ +public abstract class WritableFileSystemTestBase extends BasicFileSystemTestBase +{ + public WritableFileSystemTestBase( String name ) + { + super( name ); + } + + /** + * Returns the URI for the area to do tests in. + */ + protected abstract String getWriteFolderURI(); + + /** + * Sets up a scratch folder for the test to use. + */ + protected FileObject createScratchFolder() throws Exception + { + FileObject scratchFolder = m_manager.resolveFile( getWriteFolderURI() ); + + // Make sure the test folder is empty + scratchFolder.delete(); + scratchFolder.create( FileType.FOLDER ); + + return scratchFolder; + } + + /** + * Tests folder creation. + */ + public void testFolderCreate() throws Exception + { + FileObject scratchFolder = createScratchFolder(); + + // Create direct child of the test folder + FileObject folder = scratchFolder.resolveFile( "dir1" ); + assertTrue( !folder.exists() ); + folder.create( FileType.FOLDER ); + assertTrue( folder.exists() ); + assertEquals( 0, folder.getChildren().length ); + + // Create a descendant, where the intermediate folders don't exist + folder = scratchFolder.resolveFile( "dir2/dir1/dir1" ); + assertTrue( !folder.exists() ); + assertTrue( !folder.getParent().exists() ); + assertTrue( !folder.getParent().getParent().exists() ); + folder.create( FileType.FOLDER ); + assertTrue( folder.exists() ); + assertEquals( 0, folder.getChildren().length ); + assertTrue( folder.getParent().exists() ); + assertTrue( folder.getParent().getParent().exists() ); + + // Test creating a folder that already exists + folder.create( FileType.FOLDER ); + } + + /** + * Tests file creation + */ + public void testFileCreate() throws Exception + { + FileObject scratchFolder = createScratchFolder(); + + // Create direct child of the test folder + FileObject file = scratchFolder.resolveFile( "file1.txt" ); + assertTrue( !file.exists() ); + file.create( FileType.FILE ); + assertTrue( file.exists() ); + assertEquals( 0, file.getContent().getSize() ); + + // Create a descendant, where the intermediate folders don't exist + file = scratchFolder.resolveFile( "dir1/dir1/file1.txt" ); + assertTrue( !file.exists() ); + assertTrue( !file.getParent().exists() ); + assertTrue( !file.getParent().getParent().exists() ); + file.create( FileType.FILE ); + assertTrue( file.exists() ); + assertEquals( 0, file.getContent().getSize() ); + assertTrue( file.getParent().exists() ); + assertTrue( file.getParent().getParent().exists() ); + + // Test creating a file that already exists + file.create( FileType.FILE ); + } + + /** + * Tests file/folder creation with mismatched types. + */ + public void testFileCreateMismatched() throws Exception + { + FileObject scratchFolder = createScratchFolder(); + + // Create a test file and folder + FileObject file = scratchFolder.resolveFile( "dir1/file1.txt" ); + file.create( FileType.FILE ); + assertEquals( FileType.FILE, file.getType() ); + + FileObject folder = scratchFolder.resolveFile( "dir1/dir2" ); + folder.create( FileType.FOLDER ); + assertEquals( FileType.FOLDER, folder.getType() ); + + // Attempt to create a file that already exists as a folder + try + { + folder.create( FileType.FILE ); + assertTrue( false ); + } + catch( FileSystemException exc ) + { + } + + // Attempt to create a folder that already exists as a file + try + { + file.create( FileType.FOLDER ); + assertTrue( false ); + } + catch( FileSystemException exc ) + { + } + + // Attempt to create a folder as a child of a file + FileObject folder2 = file.resolveFile( "some-child" ); + try + { + folder2.create( FileType.FOLDER ); + assertTrue( false ); + } + catch( FileSystemException exc ) + { + } + } + + /** + * Tests deletion + */ + public void testDelete() throws Exception + { + // Set-up the test structure + FileObject folder = createScratchFolder(); + folder.resolveFile( "file1.txt" ).create( FileType.FILE ); + folder.resolveFile( "emptydir" ).create( FileType.FOLDER ); + folder.resolveFile( "dir1/file1.txt" ).create( FileType.FILE ); + folder.resolveFile( "dir1/dir2/file2.txt" ).create( FileType.FILE ); + + // Delete a file + FileObject file = folder.resolveFile( "file1.txt" ); + assertTrue( file.exists() ); + file.delete(); + assertTrue( !file.exists() ); + + // Delete an empty folder + file = folder.resolveFile( "emptydir" ); + assertTrue( file.exists() ); + file.delete(); + assertTrue( !file.exists() ); + + // Recursive delete + file = folder.resolveFile( "dir1" ); + FileObject file2 = file.resolveFile( "dir2/file2.txt" ); + assertTrue( file.exists() ); + assertTrue( file2.exists() ); + file.delete(); + assertTrue( !file.exists() ); + assertTrue( !file2.exists() ); + + // Delete a file that does not exist + file = folder.resolveFile( "some-folder/some-file" ); + assertTrue( !file.exists() ); + file.delete(); + assertTrue( !file.exists() ); + } + + /** + * Test that children are handled correctly by create and delete. + */ + public void testListChildren() throws Exception + { + FileObject folder = createScratchFolder(); + HashSet names = new HashSet(); + + // Make sure the folder is empty + assertEquals( 0, folder.getChildren().length ); + + // Create a child folder + folder.resolveFile( "dir1" ).create( FileType.FOLDER ); + names.add( "dir1" ); + assertSameFileSet( names, folder.getChildren() ); + + // Create a child file + folder.resolveFile( "file1.html" ).create( FileType.FILE ); + names.add( "file1.html" ); + assertSameFileSet( names, folder.getChildren() ); + + // Create a descendent + folder.resolveFile( "dir2/file1.txt" ).create( FileType.FILE ); + names.add( "dir2" ); + assertSameFileSet( names, folder.getChildren() ); + + // Create a child file via an output stream + OutputStream outstr = folder.resolveFile( "file2.txt" ).getContent().getOutputStream(); + outstr.close(); + names.add( "file2.txt" ); + assertSameFileSet( names, folder.getChildren() ); + + // Delete a child folder + folder.resolveFile( "dir1" ).delete(); + names.remove( "dir1" ); + assertSameFileSet( names, folder.getChildren() ); + + // Delete a child file + folder.resolveFile( "file1.html" ).delete(); + names.remove( "file1.html" ); + assertSameFileSet( names, folder.getChildren() ); + + // Recreate the folder + folder.delete(); + folder.create( FileType.FOLDER ); + assertEquals( 0, folder.getChildren().length ); + } + + /** + * Ensures the names of a set of files match an expected set. + */ + private void assertSameFileSet( Set names, FileObject[] files ) + { + // Make sure the sets are the same length + assertEquals( names.size(), files.length ); + + // Check for unexpected names + for( int i = 0; i < files.length; i++ ) + { + FileObject file = files[ i ]; + assertTrue( names.contains( file.getName().getBaseName() ) ); + } + } +} diff --git a/proposal/myrmidon/src/test/org/apache/aut/vfs/ZipFileSystemTest.java b/proposal/myrmidon/src/test/org/apache/aut/vfs/ZipFileSystemTest.java new file mode 100644 index 000000000..f9723d7ea --- /dev/null +++ b/proposal/myrmidon/src/test/org/apache/aut/vfs/ZipFileSystemTest.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs; + +import java.io.File; + +/** + * Tests for the Zip file system. + * + * @author Adam Murdoch + */ +public class ZipFileSystemTest extends ReadOnlyFileSystemTestBase +{ + public ZipFileSystemTest( String name ) + { + super( name ); + } + + /** + * Returns the URI for the base folder. + */ + protected String getBaseFolderURI() + { + String zipFileName = System.getProperty( "test.zip.file" ); + String zipFile = new File( zipFileName ).getAbsolutePath(); + String uri = "zip:" + zipFile + "!basedir"; + return uri; + } +} diff --git a/proposal/myrmidon/src/testcases/org/apache/aut/vfs/BasicFileSystemTestBase.java b/proposal/myrmidon/src/testcases/org/apache/aut/vfs/BasicFileSystemTestBase.java new file mode 100644 index 000000000..d792b3fa6 --- /dev/null +++ b/proposal/myrmidon/src/testcases/org/apache/aut/vfs/BasicFileSystemTestBase.java @@ -0,0 +1,587 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.io.Reader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import junit.framework.TestCase; +import org.apache.aut.vfs.provider.DefaultFileSystemManager; + +/** + * File system test cases, which verifies the structure and naming + * functionality. + * + * Works from a base folder, and assumes a particular structure under + * that base folder. + * + * @author Adam Murdoch + */ +public abstract class BasicFileSystemTestBase extends TestCase +{ + protected FileObject m_baseFolder; + protected DefaultFileSystemManager m_manager; + + // Contents of "file1.txt" + private String m_charContent; + + public BasicFileSystemTestBase( String name ) + { + super( name ); + } + + /** + * Builds the expected folder structure. + */ + private FileInfo buildExpectedStructure() + { + // Build the expected structure + FileInfo base = new FileInfo( "test", FileType.FOLDER ); + base.addChild( new FileInfo( "file1.txt", FileType.FILE ) ); + base.addChild( new FileInfo( "empty.txt", FileType.FILE ) ); + base.addChild( new FileInfo( "emptydir", FileType.FOLDER ) ); + + FileInfo dir = new FileInfo( "dir1", FileType.FOLDER ); + base.addChild( dir ); + dir.addChild( new FileInfo( "file1.txt", FileType.FILE ) ); + dir.addChild( new FileInfo( "file2.txt", FileType.FILE ) ); + dir.addChild( new FileInfo( "file3.txt", FileType.FILE ) ); + return base; + } + + /** + * Returns the URI for the base folder. + */ + protected abstract String getBaseFolderURI(); + + /** + * Sets up the test + */ + protected void setUp() throws Exception + { + // Create the file system manager + m_manager = new DefaultFileSystemManager(); + + // Locate the base folder + m_baseFolder = m_manager.resolveFile( getBaseFolderURI() ); + + // Build the expected content of "file1.txt" + String eol = System.getProperty( "line.separator" ); + m_charContent = "This is a test file." + eol + "With 2 lines in it." + eol; + } + + /** + * Tests resolution of absolute URI. + */ + public void testAbsoluteURI() throws Exception + { + // Try fetching base folder again by its URI + String uri = m_baseFolder.getName().getURI(); + FileObject file = m_manager.resolveFile( uri ); + + assertSame( "file object", m_baseFolder, file ); + } + + /** + * Tests resolution of relative file names via the FS manager + */ + public void testRelativeURI() throws Exception + { + // Build base dir + m_manager.setBaseFile( m_baseFolder ); + + // Locate the base dir + FileObject file = m_manager.resolveFile( "." ); + assertSame( "file object", m_baseFolder, file ); + + // Locate a child + file = m_manager.resolveFile( "some-child" ); + assertSame( "file object", m_baseFolder, file.getParent() ); + + // Locate a descendent + file = m_manager.resolveFile( "some-folder/some-file" ); + assertSame( "file object", m_baseFolder, file.getParent().getParent() ); + + // Locate parent + file = m_manager.resolveFile( ".." ); + assertSame( "file object", m_baseFolder.getParent(), file ); + } + + /** + * Tests the root file name. + */ + public void testRootFileName() throws Exception + { + // Locate the root file + FileName rootName = m_baseFolder.getRoot().getName(); + + // Test that the root path is "/" + assertEquals( "root path", "/", rootName.getPath() ); + + // Test that the root basname is "" + assertEquals( "root base name", "", rootName.getBaseName() ); + + // Test that the root name has no parent + assertNull( "root parent", rootName.getParent() ); + } + + /** + * Tests child file names. + */ + public void testChildName() throws Exception + { + FileName baseName = m_baseFolder.getName(); + String basePath = baseName.getPath(); + FileName name = baseName.resolveName( "some-child", NameScope.CHILD ); + + // Test path is absolute + assertTrue( "is absolute", basePath.startsWith( "/" ) ); + + // Test base name + assertEquals( "base name", "some-child", name.getBaseName() ); + + // Test absolute path + assertEquals( "absolute path", basePath + "/some-child", name.getPath() ); + + // Test parent path + assertEquals( "parent absolute path", basePath, name.getParent().getPath() ); + + // Try using a compound name to find a child + try + { + FileName name2 = name.resolveName( "a/b", NameScope.CHILD ); + assertTrue( false ); + } + catch( FileSystemException e ) + { + } + + // Try using a empty name to find a child + try + { + FileName name2 = name.resolveName( "", NameScope.CHILD ); + assertTrue( false ); + } + catch( FileSystemException e ) + { + } + + // Try using '.' to find a child + try + { + FileName name2 = name.resolveName( ".", NameScope.CHILD ); + assertTrue( false ); + } + catch( FileSystemException e ) + { + } + + // Try using '..' to find a child + try + { + FileName name2 = name.resolveName( "..", NameScope.CHILD ); + assertTrue( false ); + } + catch( FileSystemException e ) + { + } + } + + /** + * Checks that a relative name resolves to the expected absolute path. + */ + private void assertSameName(String expectedPath, + FileName baseName, + String relName ) throws Exception + { + FileName name = baseName.resolveName(relName); + assertEquals( expectedPath, name.getPath() ); + + // Replace the separators + relName.replace('\\', '/'); + name = baseName.resolveName(relName); + assertEquals( expectedPath, name.getPath() ); + + // And again + relName.replace('/', '\\'); + name = baseName.resolveName(relName); + assertEquals( expectedPath, name.getPath() ); + } + + /** + * Tests relative name resolution, relative to the base folder. + */ + public void testNameResolution() throws Exception + { + FileName baseName = m_baseFolder.getName(); + String parentPath = baseName.getParent().getPath(); + String path = baseName.getPath(); + String childPath = path + "/some-child"; + + // Test empty relative path + assertSameName( path, baseName, "" ); + + // Test . relative path + assertSameName( path, baseName, "." ); + + // Test ./ relative path + assertSameName( path, baseName, "./" ); + + // Test .// relative path + assertSameName( path, baseName, ".//" ); + + // Test .///.///. relative path + assertSameName( path, baseName, ".///.///." ); + assertSameName( path, baseName, "./\\/.\\//." ); + + // Test /.. 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 /../.. relative path + assertSameName( parentPath, baseName, "a/../.." ); + + // Test relative path + assertSameName( childPath, baseName, "some-child" ); + + // Test ./ relative path + assertSameName( childPath, baseName, "./some-child" ); + + // Test .// relative path + assertSameName( childPath, baseName, "./some-child/" ); + + // Test /././././ relative path + assertSameName( childPath, baseName, "./some-child/././././" ); + + // Test /../ relative path + assertSameName( childPath, baseName, "a/../some-child" ); + + // Test //../../ 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 ); + } + } +} diff --git a/proposal/myrmidon/src/testcases/org/apache/aut/vfs/FtpFileSystemTest.java b/proposal/myrmidon/src/testcases/org/apache/aut/vfs/FtpFileSystemTest.java new file mode 100644 index 000000000..c0ad5e886 --- /dev/null +++ b/proposal/myrmidon/src/testcases/org/apache/aut/vfs/FtpFileSystemTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs; + +/** + * Tests for FTP file systems. + * + * @author Adam Murdoch + */ +public class FtpFileSystemTest extends WritableFileSystemTestBase +{ + public FtpFileSystemTest( String name ) + { + super( name ); + } + + /** + * Returns the URI for the base folder. + */ + protected String getBaseFolderURI() + { + return System.getProperty( "test.ftp.uri" ) + "/read-tests"; + } + + /** + * Returns the URI for the area to do tests in. + */ + protected String getWriteFolderURI() + { + return System.getProperty( "test.ftp.uri" ) + "/write-tests"; + } +} diff --git a/proposal/myrmidon/src/testcases/org/apache/aut/vfs/LocalFileSystemTest.java b/proposal/myrmidon/src/testcases/org/apache/aut/vfs/LocalFileSystemTest.java new file mode 100644 index 000000000..5a0a1ea2a --- /dev/null +++ b/proposal/myrmidon/src/testcases/org/apache/aut/vfs/LocalFileSystemTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs; + +import java.io.File; + +/** + * Tests for the local file system. + * + * @author Adam Murdoch + */ +public class LocalFileSystemTest extends WritableFileSystemTestBase +{ + private File m_baseDir; + + public LocalFileSystemTest( String name ) + { + super( name ); + String baseDir = System.getProperty( "test.local.dir" ); + m_baseDir = new File( baseDir ); + } + + /** + * Returns the URI for the base folder. + */ + protected String getBaseFolderURI() + { + String testDir = new File( m_baseDir, "read-tests" ).getAbsolutePath(); + String uri = "file:/" + testDir; + return uri; + } + + /** + * Returns the URI for the area to do tests in. + */ + protected String getWriteFolderURI() + { + String testDir = new File( m_baseDir, "write-tests" ).getAbsolutePath(); + String uri = "file:/" + testDir; + return uri; + } + + /** + * Tests resolution of an absolute file name. + */ + public void testAbsoluteFileName() throws Exception + { + // Locate file by absolute file name + String fileName = new File( "testdir" ).getAbsolutePath(); + FileObject absFile = m_manager.resolveFile( fileName ); + + // Locate file by URI + String uri = "file://" + fileName.replace( File.separatorChar, '/' ); + FileObject uriFile = m_manager.resolveFile( uri ); + + assertSame( "file object", absFile, uriFile ); + } + +} diff --git a/proposal/myrmidon/src/testcases/org/apache/aut/vfs/ReadOnlyFileSystemTestBase.java b/proposal/myrmidon/src/testcases/org/apache/aut/vfs/ReadOnlyFileSystemTestBase.java new file mode 100644 index 000000000..9cd87e0a6 --- /dev/null +++ b/proposal/myrmidon/src/testcases/org/apache/aut/vfs/ReadOnlyFileSystemTestBase.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs; + +/** + * File system tests which check that a read-only file system cannot be + * changed. + * + * @author Adam Murdoch + */ +public abstract class ReadOnlyFileSystemTestBase extends BasicFileSystemTestBase +{ + public ReadOnlyFileSystemTestBase( String name ) + { + super( name ); + } +} diff --git a/proposal/myrmidon/src/testcases/org/apache/aut/vfs/SmbFileSystemTest.java b/proposal/myrmidon/src/testcases/org/apache/aut/vfs/SmbFileSystemTest.java new file mode 100644 index 000000000..2e09a77e0 --- /dev/null +++ b/proposal/myrmidon/src/testcases/org/apache/aut/vfs/SmbFileSystemTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs; + +/** + * Tests for the SMB file system. + * + * @author Adam Murdoch + */ +public class SmbFileSystemTest extends WritableFileSystemTestBase +{ + public SmbFileSystemTest( String name ) + { + super( name ); + } + + /** + * Returns the URI for the base folder. + */ + protected String getBaseFolderURI() + { + return System.getProperty( "test.smb.uri" ) + "/read-tests"; + } + + /** + * Returns the URI for the area to do tests in. + */ + protected String getWriteFolderURI() + { + return System.getProperty( "test.smb.uri" ) + "/write-tests"; + } +} diff --git a/proposal/myrmidon/src/testcases/org/apache/aut/vfs/WritableFileSystemTestBase.java b/proposal/myrmidon/src/testcases/org/apache/aut/vfs/WritableFileSystemTestBase.java new file mode 100644 index 000000000..65bc254f5 --- /dev/null +++ b/proposal/myrmidon/src/testcases/org/apache/aut/vfs/WritableFileSystemTestBase.java @@ -0,0 +1,255 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs; + +import java.io.Writer; +import java.io.OutputStream; +import java.util.HashSet; +import java.util.Set; + +/** + * File system test that check that a file system can be modified. + * + * @author Adam Murdoch + */ +public abstract class WritableFileSystemTestBase extends BasicFileSystemTestBase +{ + public WritableFileSystemTestBase( String name ) + { + super( name ); + } + + /** + * Returns the URI for the area to do tests in. + */ + protected abstract String getWriteFolderURI(); + + /** + * Sets up a scratch folder for the test to use. + */ + protected FileObject createScratchFolder() throws Exception + { + FileObject scratchFolder = m_manager.resolveFile( getWriteFolderURI() ); + + // Make sure the test folder is empty + scratchFolder.delete(); + scratchFolder.create( FileType.FOLDER ); + + return scratchFolder; + } + + /** + * Tests folder creation. + */ + public void testFolderCreate() throws Exception + { + FileObject scratchFolder = createScratchFolder(); + + // Create direct child of the test folder + FileObject folder = scratchFolder.resolveFile( "dir1" ); + assertTrue( !folder.exists() ); + folder.create( FileType.FOLDER ); + assertTrue( folder.exists() ); + assertEquals( 0, folder.getChildren().length ); + + // Create a descendant, where the intermediate folders don't exist + folder = scratchFolder.resolveFile( "dir2/dir1/dir1" ); + assertTrue( !folder.exists() ); + assertTrue( !folder.getParent().exists() ); + assertTrue( !folder.getParent().getParent().exists() ); + folder.create( FileType.FOLDER ); + assertTrue( folder.exists() ); + assertEquals( 0, folder.getChildren().length ); + assertTrue( folder.getParent().exists() ); + assertTrue( folder.getParent().getParent().exists() ); + + // Test creating a folder that already exists + folder.create( FileType.FOLDER ); + } + + /** + * Tests file creation + */ + public void testFileCreate() throws Exception + { + FileObject scratchFolder = createScratchFolder(); + + // Create direct child of the test folder + FileObject file = scratchFolder.resolveFile( "file1.txt" ); + assertTrue( !file.exists() ); + file.create( FileType.FILE ); + assertTrue( file.exists() ); + assertEquals( 0, file.getContent().getSize() ); + + // Create a descendant, where the intermediate folders don't exist + file = scratchFolder.resolveFile( "dir1/dir1/file1.txt" ); + assertTrue( !file.exists() ); + assertTrue( !file.getParent().exists() ); + assertTrue( !file.getParent().getParent().exists() ); + file.create( FileType.FILE ); + assertTrue( file.exists() ); + assertEquals( 0, file.getContent().getSize() ); + assertTrue( file.getParent().exists() ); + assertTrue( file.getParent().getParent().exists() ); + + // Test creating a file that already exists + file.create( FileType.FILE ); + } + + /** + * Tests file/folder creation with mismatched types. + */ + public void testFileCreateMismatched() throws Exception + { + FileObject scratchFolder = createScratchFolder(); + + // Create a test file and folder + FileObject file = scratchFolder.resolveFile( "dir1/file1.txt" ); + file.create( FileType.FILE ); + assertEquals( FileType.FILE, file.getType() ); + + FileObject folder = scratchFolder.resolveFile( "dir1/dir2" ); + folder.create( FileType.FOLDER ); + assertEquals( FileType.FOLDER, folder.getType() ); + + // Attempt to create a file that already exists as a folder + try + { + folder.create( FileType.FILE ); + assertTrue( false ); + } + catch( FileSystemException exc ) + { + } + + // Attempt to create a folder that already exists as a file + try + { + file.create( FileType.FOLDER ); + assertTrue( false ); + } + catch( FileSystemException exc ) + { + } + + // Attempt to create a folder as a child of a file + FileObject folder2 = file.resolveFile( "some-child" ); + try + { + folder2.create( FileType.FOLDER ); + assertTrue( false ); + } + catch( FileSystemException exc ) + { + } + } + + /** + * Tests deletion + */ + public void testDelete() throws Exception + { + // Set-up the test structure + FileObject folder = createScratchFolder(); + folder.resolveFile( "file1.txt" ).create( FileType.FILE ); + folder.resolveFile( "emptydir" ).create( FileType.FOLDER ); + folder.resolveFile( "dir1/file1.txt" ).create( FileType.FILE ); + folder.resolveFile( "dir1/dir2/file2.txt" ).create( FileType.FILE ); + + // Delete a file + FileObject file = folder.resolveFile( "file1.txt" ); + assertTrue( file.exists() ); + file.delete(); + assertTrue( !file.exists() ); + + // Delete an empty folder + file = folder.resolveFile( "emptydir" ); + assertTrue( file.exists() ); + file.delete(); + assertTrue( !file.exists() ); + + // Recursive delete + file = folder.resolveFile( "dir1" ); + FileObject file2 = file.resolveFile( "dir2/file2.txt" ); + assertTrue( file.exists() ); + assertTrue( file2.exists() ); + file.delete(); + assertTrue( !file.exists() ); + assertTrue( !file2.exists() ); + + // Delete a file that does not exist + file = folder.resolveFile( "some-folder/some-file" ); + assertTrue( !file.exists() ); + file.delete(); + assertTrue( !file.exists() ); + } + + /** + * Test that children are handled correctly by create and delete. + */ + public void testListChildren() throws Exception + { + FileObject folder = createScratchFolder(); + HashSet names = new HashSet(); + + // Make sure the folder is empty + assertEquals( 0, folder.getChildren().length ); + + // Create a child folder + folder.resolveFile( "dir1" ).create( FileType.FOLDER ); + names.add( "dir1" ); + assertSameFileSet( names, folder.getChildren() ); + + // Create a child file + folder.resolveFile( "file1.html" ).create( FileType.FILE ); + names.add( "file1.html" ); + assertSameFileSet( names, folder.getChildren() ); + + // Create a descendent + folder.resolveFile( "dir2/file1.txt" ).create( FileType.FILE ); + names.add( "dir2" ); + assertSameFileSet( names, folder.getChildren() ); + + // Create a child file via an output stream + OutputStream outstr = folder.resolveFile( "file2.txt" ).getContent().getOutputStream(); + outstr.close(); + names.add( "file2.txt" ); + assertSameFileSet( names, folder.getChildren() ); + + // Delete a child folder + folder.resolveFile( "dir1" ).delete(); + names.remove( "dir1" ); + assertSameFileSet( names, folder.getChildren() ); + + // Delete a child file + folder.resolveFile( "file1.html" ).delete(); + names.remove( "file1.html" ); + assertSameFileSet( names, folder.getChildren() ); + + // Recreate the folder + folder.delete(); + folder.create( FileType.FOLDER ); + assertEquals( 0, folder.getChildren().length ); + } + + /** + * Ensures the names of a set of files match an expected set. + */ + private void assertSameFileSet( Set names, FileObject[] files ) + { + // Make sure the sets are the same length + assertEquals( names.size(), files.length ); + + // Check for unexpected names + for( int i = 0; i < files.length; i++ ) + { + FileObject file = files[ i ]; + assertTrue( names.contains( file.getName().getBaseName() ) ); + } + } +} diff --git a/proposal/myrmidon/src/testcases/org/apache/aut/vfs/ZipFileSystemTest.java b/proposal/myrmidon/src/testcases/org/apache/aut/vfs/ZipFileSystemTest.java new file mode 100644 index 000000000..f9723d7ea --- /dev/null +++ b/proposal/myrmidon/src/testcases/org/apache/aut/vfs/ZipFileSystemTest.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) The Apache Software Foundation. All rights reserved. + * + * This software is published under the terms of the Apache Software License + * version 1.1, a copy of which has been included with this distribution in + * the LICENSE.txt file. + */ +package org.apache.aut.vfs; + +import java.io.File; + +/** + * Tests for the Zip file system. + * + * @author Adam Murdoch + */ +public class ZipFileSystemTest extends ReadOnlyFileSystemTestBase +{ + public ZipFileSystemTest( String name ) + { + super( name ); + } + + /** + * Returns the URI for the base folder. + */ + protected String getBaseFolderURI() + { + String zipFileName = System.getProperty( "test.zip.file" ); + String zipFile = new File( zipFileName ).getAbsolutePath(); + String uri = "zip:" + zipFile + "!basedir"; + return uri; + } +}