* Added FileObject.copy(). * Renamed <v-copy> task attributes file -> srcfile, tofile -> destfile, todir -> destdir. * Moved provider instantiation out of DefaultFileSystemManager, and into myrmidon-aware VfsManager. Providers are instantiated using the TypeManager. The list of providers isn't configurable yet. * Some support for %nn encoded URI (not quite complete). * Zip file system now handles zip files from any file system, not just local files. Still read-only at this stage. Uses a truely dodgy and very temporary replication mechanism. * Zip file system now handles relative paths in URI (e.g. zip:relpath.zip), that are resolved against the base dir. * Fixed bug in resolving names against the root file of a file system. * Changed behaviour of FileName.resolveName( ".." ) for the root file of a file system. * Added more test cases. * A bucketload of other minor changes. git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@271803 13f79535-47bb-0310-9956-ffa450edef68master
@@ -557,6 +557,9 @@ Legal: | |||||
<zip zipfile="${test.vfs.dir}/test.zip"> | <zip zipfile="${test.vfs.dir}/test.zip"> | ||||
<fileset dir="${test.vfs.dir}" includes="basedir/**"/> | <fileset dir="${test.vfs.dir}" includes="basedir/**"/> | ||||
</zip> | </zip> | ||||
<zip zipfile="${test.vfs.dir}/nested.zip"> | |||||
<fileset dir="${test.vfs.dir}" includes="test.zip"/> | |||||
</zip> | |||||
<!-- Prepare deployer tests --> | <!-- Prepare deployer tests --> | ||||
<property name="test.deployer.dir" | <property name="test.deployer.dir" | ||||
@@ -117,15 +117,22 @@ | |||||
<blockquote> | <blockquote> | ||||
<p>The VFS needs plenty of work:</p> | <p>The VFS needs plenty of work:</p> | ||||
<ul> | <ul> | ||||
<li>Move and copy files/folders.</li> | |||||
<li>Move files/folders.</li> | |||||
<li>Recursive folders copy.</li> | |||||
<li>Search through a file hierarchy, using Ant-style wildcards.</li> | <li>Search through a file hierarchy, using Ant-style wildcards.</li> | ||||
<li>Search through a file hierarchy, using a Selector interface.</li> | <li>Search through a file hierarchy, using a Selector interface.</li> | ||||
<li>The in-memory caching mechanism is pretty rudimentary at this stage. | <li>The in-memory caching mechanism is pretty rudimentary at this stage. | ||||
It needs work to make it size capped. In addition, some mechanism needs | It needs work to make it size capped. In addition, some mechanism needs | ||||
to be provided to release and refresh cached info. | to be provided to release and refresh cached info. | ||||
</li> | </li> | ||||
<li>Convert files/folders into local files, for handing off | |||||
to external commands, or legacy tasks.</li> | |||||
<li>Refactor the replication mechanism out of ZipFileSystemProvder, | |||||
and make more general pluggable.</li> | |||||
<li>Capabilities discovery.</li> | <li>Capabilities discovery.</li> | ||||
<li>Attributes and attribute schema.</li> | <li>Attributes and attribute schema.</li> | ||||
<li>Handle file canonicalisation better (for cases like case-insensitive | |||||
file systems, symbolic links, name encoding, etc).</li> | |||||
<li>File system layering. That is, the ability for a file system to | <li>File system layering. That is, the ability for a file system to | ||||
sit on top of another file system, or a file from another file system | sit on top of another file system, or a file from another file system | ||||
(e.g. Zip/Jar/Tar file systems, gzip/encoding file systems, virtual file | (e.g. Zip/Jar/Tar file systems, gzip/encoding file systems, virtual file | ||||
@@ -300,10 +307,6 @@ | |||||
<p>A completely unordered list of items, big and small:</p> | <p>A completely unordered list of items, big and small:</p> | ||||
<ul> | <ul> | ||||
<li>Search through the code for 'TODO' items and fix them.</li> | <li>Search through the code for 'TODO' items and fix them.</li> | ||||
<li>Tidy-up CLIMain so that it calls System.exit() with a non-zero exit code, | |||||
if the build fails.</li> | |||||
<li>Tidy-up the 'build failed' message, so that the stack trace is only | |||||
printed out if the log level is verbose/debug.</li> | |||||
<li>Allow service factories to be configured from the contents of the | <li>Allow service factories to be configured from the contents of the | ||||
<code>ant-services.xml</code> descriptor.</li> | <code>ant-services.xml</code> descriptor.</li> | ||||
<li>Route external process stdout and stderr through the logger.</li> | <li>Route external process stdout and stderr through the logger.</li> | ||||
@@ -314,11 +317,10 @@ | |||||
<li>Fire ProjectListener events projectStarted() and projectFinished() | <li>Fire ProjectListener events projectStarted() and projectFinished() | ||||
events on start and finish of referenced projects, adding indicator methods | events on start and finish of referenced projects, adding indicator methods | ||||
to ProjectEvent.</li> | to ProjectEvent.</li> | ||||
<li>Convert PropertyUtil to a non-static PropertyResolver service.</li> | |||||
<li>Validate project and target names in DefaultProjectBuilder - reject dodgy | <li>Validate project and target names in DefaultProjectBuilder - reject dodgy | ||||
names like "," or "", or " ". Probably want to exclude names that start or | |||||
names like "," or "", or " ". Probably want to reject names that start or | |||||
end with white-space (though internal whitespace is probably fine). We also | end with white-space (though internal whitespace is probably fine). We also | ||||
want to reserve certain punctuation characters like . , : ? [ ] { }, etc for | |||||
want to reserve certain punctuation characters like , : ? $ [ ] { } < >, etc for | |||||
future use.</li> | future use.</li> | ||||
<li>Similarly, validate property names, using the same rules.</li> | <li>Similarly, validate property names, using the same rules.</li> | ||||
<li>Detect duplicate type names.</li> | <li>Detect duplicate type names.</li> | ||||
@@ -330,6 +332,7 @@ | |||||
an antlib.</li> | an antlib.</li> | ||||
<li>Split up <code><is-set></code> condition into is-set and is-true conditions.</li> | <li>Split up <code><is-set></code> condition into is-set and is-true conditions.</li> | ||||
<li>Allow the <code><if></code> task to take any condition implementation.</li> | <li>Allow the <code><if></code> task to take any condition implementation.</li> | ||||
<li>Add an else block to the <code><if></code> task.</li> | |||||
<li>Unit tests.</li> | <li>Unit tests.</li> | ||||
</ul> | </ul> | ||||
</blockquote> | </blockquote> | ||||
@@ -125,7 +125,8 @@ files are found in the <code>lib</code> directory:</p> | |||||
<td bgcolor="#a0ddf0" colspan="" rowspan="" | <td bgcolor="#a0ddf0" colspan="" rowspan="" | ||||
valign="top" align="left"> | valign="top" align="left"> | ||||
<font color="#000000" size="-1" face="arial,helvetica,sanserif"> | <font color="#000000" size="-1" face="arial,helvetica,sanserif"> | ||||
<a href="http://jcifs.samba.org">jcifs.samba.org</a> | |||||
<a href="http://jcifs.samba.org">jcifs.samba.org</a>. | |||||
<p>Note: there are problems using the 0.6.1 release. Try 0.6.0 instead.</p> | |||||
</font> | </font> | ||||
</td> | </td> | ||||
</tr> | </tr> | ||||
@@ -1,7 +1,7 @@ | |||||
<project version="2.0"> | <project version="2.0"> | ||||
<target name="copy"> | <target name="copy"> | ||||
<v-fileset id="src-files" dir="src"/> | <v-fileset id="src-files" dir="src"/> | ||||
<v-copy todir="dest"> | |||||
<v-copy destdir="dest"> | |||||
<v-fileset-ref id="src-files"/> | <v-fileset-ref id="src-files"/> | ||||
</v-copy> | </v-copy> | ||||
</target> | </target> | ||||
@@ -7,8 +7,6 @@ | |||||
*/ | */ | ||||
package org.apache.antlib.vfile; | package org.apache.antlib.vfile; | ||||
import java.io.InputStream; | |||||
import java.io.OutputStream; | |||||
import java.util.ArrayList; | import java.util.ArrayList; | ||||
import java.util.Iterator; | import java.util.Iterator; | ||||
import org.apache.aut.vfs.FileObject; | import org.apache.aut.vfs.FileObject; | ||||
@@ -41,7 +39,7 @@ public class CopyFilesTask | |||||
/** | /** | ||||
* Sets the source file. | * Sets the source file. | ||||
*/ | */ | ||||
public void setFile( final FileObject file ) | |||||
public void setSrcfile( final FileObject file ) | |||||
{ | { | ||||
m_srcFile = file; | m_srcFile = file; | ||||
} | } | ||||
@@ -49,7 +47,7 @@ public class CopyFilesTask | |||||
/** | /** | ||||
* Sets the destination file. | * Sets the destination file. | ||||
*/ | */ | ||||
public void setTofile( final FileObject file ) | |||||
public void setDestfile( final FileObject file ) | |||||
{ | { | ||||
m_destFile = file; | m_destFile = file; | ||||
} | } | ||||
@@ -57,11 +55,19 @@ public class CopyFilesTask | |||||
/** | /** | ||||
* Sets the destination directory. | * Sets the destination directory. | ||||
*/ | */ | ||||
public void setTodir( final FileObject file ) | |||||
public void setDestdir( final FileObject file ) | |||||
{ | { | ||||
m_destDir = file; | m_destDir = file; | ||||
} | } | ||||
/** | |||||
* Sets the source directory. | |||||
*/ | |||||
public void setSrcdir( final FileObject dir ) | |||||
{ | |||||
add( new DefaultFileSet( dir ) ); | |||||
} | |||||
/** | /** | ||||
* Adds a source file set. | * Adds a source file set. | ||||
*/ | */ | ||||
@@ -107,7 +113,8 @@ public class CopyFilesTask | |||||
m_destFile = m_destDir.resolveFile( m_srcFile.getName().getBaseName() ); | m_destFile = m_destDir.resolveFile( m_srcFile.getName().getBaseName() ); | ||||
} | } | ||||
copyFile( m_srcFile, m_destFile ); | |||||
getLogger().info( "copy " + m_srcFile + " to " + m_destFile ); | |||||
m_destFile.copy( m_srcFile ); | |||||
} | } | ||||
// Copy the contents of the filesets across | // Copy the contents of the filesets across | ||||
@@ -134,7 +141,8 @@ public class CopyFilesTask | |||||
final FileObject destFile = m_destDir.resolveFile( path, NameScope.DESCENDENT ); | final FileObject destFile = m_destDir.resolveFile( path, NameScope.DESCENDENT ); | ||||
// Copy the file across | // Copy the file across | ||||
copyFile( srcFile, destFile ); | |||||
getLogger().info( "copy " + srcFile + " to " + destFile ); | |||||
destFile.copy( srcFile ); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -144,42 +152,4 @@ public class CopyFilesTask | |||||
} | } | ||||
} | } | ||||
/** | |||||
* Copies a file. | |||||
*/ | |||||
private void copyFile( final FileObject srcFile, final FileObject destFile ) | |||||
throws TaskException | |||||
{ | |||||
getLogger().info( "copy " + srcFile + " to " + destFile ); | |||||
try | |||||
{ | |||||
// TODO - move copy behind FileObject interface | |||||
InputStream instr = srcFile.getContent().getInputStream(); | |||||
try | |||||
{ | |||||
OutputStream outstr = destFile.getContent().getOutputStream(); | |||||
byte[] buffer = new byte[ 4096 ]; | |||||
while( true ) | |||||
{ | |||||
int nread = instr.read( buffer ); | |||||
if( nread == -1 ) | |||||
{ | |||||
break; | |||||
} | |||||
outstr.write( buffer, 0, nread ); | |||||
} | |||||
outstr.close(); | |||||
} | |||||
finally | |||||
{ | |||||
instr.close(); | |||||
} | |||||
} | |||||
catch( Exception exc ) | |||||
{ | |||||
final String message = REZ.getString( "copyfilestask.copy-file.error", srcFile, destFile ); | |||||
throw new TaskException( message, exc ); | |||||
} | |||||
} | |||||
} | } |
@@ -36,6 +36,15 @@ public class DefaultFileSet | |||||
private FileObject m_dir; | private FileObject m_dir; | ||||
private final AndFileSelector m_selector = new AndFileSelector(); | private final AndFileSelector m_selector = new AndFileSelector(); | ||||
public DefaultFileSet() | |||||
{ | |||||
} | |||||
public DefaultFileSet( final FileObject dir ) | |||||
{ | |||||
m_dir = dir; | |||||
} | |||||
/** | /** | ||||
* Sets the root directory. | * Sets the root directory. | ||||
*/ | */ | ||||
@@ -7,6 +7,7 @@ | |||||
*/ | */ | ||||
package org.apache.antlib.vfile; | package org.apache.antlib.vfile; | ||||
import java.util.ArrayList; | |||||
import org.apache.aut.vfs.FileObject; | import org.apache.aut.vfs.FileObject; | ||||
import org.apache.myrmidon.api.AbstractTask; | import org.apache.myrmidon.api.AbstractTask; | ||||
import org.apache.myrmidon.api.TaskException; | import org.apache.myrmidon.api.TaskException; | ||||
@@ -22,11 +23,11 @@ import org.apache.myrmidon.api.TaskException; | |||||
public class ListFileSetTask | public class ListFileSetTask | ||||
extends AbstractTask | extends AbstractTask | ||||
{ | { | ||||
private FileSet m_fileSet; | |||||
private final ArrayList m_fileSets = new ArrayList(); | |||||
public void set( final FileSet fileSet ) | |||||
public void add( final FileSet fileSet ) | |||||
{ | { | ||||
m_fileSet = fileSet; | |||||
m_fileSets.add( fileSet ); | |||||
} | } | ||||
/** | /** | ||||
@@ -35,14 +36,19 @@ public class ListFileSetTask | |||||
public void execute() | public void execute() | ||||
throws TaskException | throws TaskException | ||||
{ | { | ||||
FileSetResult result = m_fileSet.getResult( getContext() ); | |||||
final FileObject[] files = result.getFiles(); | |||||
final String[] paths = result.getPaths(); | |||||
for( int i = 0; i < files.length; i++ ) | |||||
final int count = m_fileSets.size(); | |||||
for( int i = 0; i < count; i++ ) | |||||
{ | { | ||||
final FileObject file = files[ i ]; | |||||
final String path = paths[ i ]; | |||||
getLogger().info( path + " = " + file ); | |||||
final FileSet fileSet = (FileSet)m_fileSets.get(i ); | |||||
FileSetResult result = fileSet.getResult( getContext() ); | |||||
final FileObject[] files = result.getFiles(); | |||||
final String[] paths = result.getPaths(); | |||||
for( int j = 0; j < files.length; j++ ) | |||||
{ | |||||
final FileObject file = files[ j ]; | |||||
final String path = paths[ j ]; | |||||
getLogger().info( path + " = " + file ); | |||||
} | |||||
} | } | ||||
} | } | ||||
} | } |
@@ -6,6 +6,5 @@ fileset.list-files.error=Could not list the files in folder "{0}". | |||||
copyfilestask.no-source.error=No source files specified for {0} task. | copyfilestask.no-source.error=No source files specified for {0} task. | ||||
copyfilestask.no-destination.error=No destination file or directory specified for {0} task. | copyfilestask.no-destination.error=No destination file or directory specified for {0} task. | ||||
copyfilestask.no-destination.error=No destination directory specified for {0} task. | copyfilestask.no-destination.error=No destination directory specified for {0} task. | ||||
copyfilestask.copy-file.error=Could not copy "{0}" to "{1}". | |||||
filteredfilelist.no-selector.error=No filter criteria specified. | filteredfilelist.no-selector.error=No filter criteria specified. |
@@ -181,6 +181,20 @@ public interface FileObject | |||||
*/ | */ | ||||
void create( FileType type ) throws FileSystemException; | void create( FileType type ) throws FileSystemException; | ||||
/** | |||||
* Copies the content of another file to this file. | |||||
* | |||||
* If this file does not exist, it is created. Its parent folder is also | |||||
* created, if necessary. If this file does exist, its content is replaced. | |||||
* | |||||
* @param file The file to copy the content from. | |||||
* | |||||
* @throws FileSystemException | |||||
* If this file is read-only, or is a folder, or if the supplied file | |||||
* is not a file, or on error copying the content. | |||||
*/ | |||||
void copy( FileObject file ) throws FileSystemException; | |||||
/** | /** | ||||
* Returns this file's content. The {@link FileContent} returned by this | * Returns this file's content. The {@link FileContent} returned by this | ||||
* method can be used to read and write the content of the file. | * method can be used to read and write the content of the file. | ||||
@@ -189,6 +203,12 @@ public interface FileObject | |||||
* the returned {@link FileContent} can be used to create the file | * the returned {@link FileContent} can be used to create the file | ||||
* by writing its content. | * by writing its content. | ||||
* | * | ||||
* @todo Do not throw an exception if this file is a folder. Instead, | |||||
* throw the exceptions when (if) any methods on the returned object | |||||
* are called. This is to hand 2 cases: when the folder is deleted | |||||
* and recreated as a file, and to allow attributes of the folder | |||||
* to be set (last modified time, permissions, etc). | |||||
* | |||||
* @return | * @return | ||||
* This file's content. | * This file's content. | ||||
* | * | ||||
@@ -71,7 +71,8 @@ public interface FileSystemManager | |||||
* @throws FileSystemException | * @throws FileSystemException | ||||
* On error parsing the file name. | * On error parsing the file name. | ||||
*/ | */ | ||||
FileObject resolveFile( String name ) throws FileSystemException; | |||||
FileObject resolveFile( String name ) | |||||
throws FileSystemException; | |||||
/** | /** | ||||
* Locates a file by name. The name is resolved as described | * Locates a file by name. The name is resolved as described | ||||
@@ -90,7 +91,8 @@ public interface FileSystemManager | |||||
* @throws FileSystemException | * @throws FileSystemException | ||||
* On error parsing the file name. | * On error parsing the file name. | ||||
*/ | */ | ||||
FileObject resolveFile( FileObject baseFile, String name ) throws FileSystemException; | |||||
FileObject resolveFile( FileObject baseFile, String name ) | |||||
throws FileSystemException; | |||||
/** | /** | ||||
* Locates a file by name. See {@link #resolveFile(FileObject, String)} | * Locates a file by name. See {@link #resolveFile(FileObject, String)} | ||||
@@ -106,5 +108,39 @@ public interface FileSystemManager | |||||
* On error parsing the file name. | * On error parsing the file name. | ||||
* | * | ||||
*/ | */ | ||||
FileObject resolveFile( File baseFile, String name ) throws FileSystemException; | |||||
FileObject resolveFile( File baseFile, String name ) | |||||
throws FileSystemException; | |||||
/** | |||||
* Converts a local file into a {@link FileObject}. | |||||
* | |||||
* @param file | |||||
* The file to convert. | |||||
* | |||||
* @return | |||||
* The {@link FileObject} that represents the local file. | |||||
* | |||||
* @throws FileSystemException | |||||
* On error converting the file. | |||||
*/ | |||||
FileObject convert( File file ) | |||||
throws FileSystemException; | |||||
/** | |||||
* Creates a layered file system. A layered file system is a file system | |||||
* that is created from the contents of another file file, such as a zip | |||||
* or tar file. | |||||
* | |||||
* @param provider | |||||
* The name of the file system provider to use. This name is | |||||
* the same as the scheme used in URI to identify the provider. | |||||
* | |||||
* @param file | |||||
* The file to use to create the file system. | |||||
* | |||||
* @throws FileSystemException | |||||
* On error creating the file system. | |||||
*/ | |||||
FileObject createFileSystem( String provider, FileObject file ) | |||||
throws FileSystemException; | |||||
} | } |
@@ -9,14 +9,11 @@ package org.apache.aut.vfs.impl; | |||||
import java.io.File; | import java.io.File; | ||||
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.Iterator; | |||||
import java.util.Map; | import java.util.Map; | ||||
import org.apache.aut.vfs.FileObject; | import org.apache.aut.vfs.FileObject; | ||||
import org.apache.aut.vfs.FileSystemException; | import org.apache.aut.vfs.FileSystemException; | ||||
import org.apache.aut.vfs.FileSystemManager; | import org.apache.aut.vfs.FileSystemManager; | ||||
import org.apache.aut.vfs.provider.FileSystem; | |||||
import org.apache.aut.vfs.provider.FileSystemProvider; | import org.apache.aut.vfs.provider.FileSystemProvider; | ||||
import org.apache.aut.vfs.provider.FileSystemProviderContext; | |||||
import org.apache.aut.vfs.provider.UriParser; | import org.apache.aut.vfs.provider.UriParser; | ||||
import org.apache.aut.vfs.provider.local.LocalFileSystemProvider; | import org.apache.aut.vfs.provider.local.LocalFileSystemProvider; | ||||
import org.apache.avalon.excalibur.i18n.ResourceManager; | import org.apache.avalon.excalibur.i18n.ResourceManager; | ||||
@@ -25,7 +22,8 @@ import org.apache.avalon.excalibur.i18n.Resources; | |||||
/** | /** | ||||
* A default file system manager implementation. | * A default file system manager implementation. | ||||
* | * | ||||
* @author Adam Murdoch | |||||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||||
* @version $Revision$ $Date$ | |||||
*/ | */ | ||||
public class DefaultFileSystemManager | public class DefaultFileSystemManager | ||||
implements FileSystemManager | implements FileSystemManager | ||||
@@ -40,69 +38,55 @@ public class DefaultFileSystemManager | |||||
private final Map m_providers = new HashMap(); | private final Map m_providers = new HashMap(); | ||||
/** The provider context. */ | /** The provider context. */ | ||||
private final ProviderContextImpl m_context = new ProviderContextImpl(); | |||||
private final DefaultProviderContext m_context = new DefaultProviderContext( this ); | |||||
/** The base file to use for relative URI. */ | /** The base file to use for relative URI. */ | ||||
private FileObject m_baseFile; | private FileObject m_baseFile; | ||||
/** | |||||
* The cached file systems. This is a mapping from root URI to | |||||
* FileSystem object. | |||||
*/ | |||||
private final Map m_fileSystems = new HashMap(); | |||||
public DefaultFileSystemManager() throws Exception | |||||
public DefaultFileSystemManager() | |||||
{ | { | ||||
// Create the local provider | // Create the local provider | ||||
m_localFileProvider = new LocalFileSystemProvider(); | m_localFileProvider = new LocalFileSystemProvider(); | ||||
m_providers.put( "file", m_localFileProvider ); | m_providers.put( "file", m_localFileProvider ); | ||||
m_localFileProvider.setContext( m_context ); | |||||
} | |||||
// 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 ); | |||||
} | |||||
/** | |||||
* Registers a file system provider. | |||||
*/ | |||||
public void addProvider( final String urlScheme, | |||||
final FileSystemProvider provider ) | |||||
throws FileSystemException | |||||
{ | |||||
addProvider( new String[] { urlScheme }, provider ); | |||||
} | } | ||||
/** | /** | ||||
* Creates a provider instance, returns null if the provider class is | |||||
* not found. | |||||
* Registers a file system provider. | |||||
*/ | */ | ||||
private FileSystemProvider createProvider( final String className ) | |||||
throws Exception | |||||
public void addProvider( final String[] urlSchemes, | |||||
final FileSystemProvider provider ) | |||||
throws FileSystemException | |||||
{ | { | ||||
try | |||||
// Check for duplicates | |||||
for( int i = 0; i < urlSchemes.length; i++ ) | |||||
{ | { | ||||
// TODO - wrap exceptions | |||||
return (FileSystemProvider)Class.forName( className ).newInstance(); | |||||
final String scheme = urlSchemes[i ]; | |||||
if( m_providers.containsKey( scheme ) ) | |||||
{ | |||||
final String message = REZ.getString( "multiple-providers-for-scheme.error", scheme ); | |||||
throw new FileSystemException( message ); | |||||
} | |||||
} | } | ||||
catch( ClassNotFoundException e ) | |||||
// Contextualise | |||||
provider.setContext( m_context ); | |||||
// Add to map | |||||
for( int i = 0; i < urlSchemes.length; i++ ) | |||||
{ | { | ||||
// This is fine, for now | |||||
return null; | |||||
final String scheme = urlSchemes[ i ]; | |||||
m_providers.put( scheme, provider ); | |||||
} | } | ||||
} | } | ||||
@@ -152,7 +136,7 @@ public class DefaultFileSystemManager | |||||
public FileObject resolveFile( final File baseFile, final String uri ) | public FileObject resolveFile( final File baseFile, final String uri ) | ||||
throws FileSystemException | throws FileSystemException | ||||
{ | { | ||||
final FileObject baseFileObj = m_localFileProvider.findFileByLocalName( baseFile ); | |||||
final FileObject baseFileObj = m_localFileProvider.findLocalFile( baseFile ); | |||||
return resolveFile( baseFileObj, uri ); | return resolveFile( baseFileObj, uri ); | ||||
} | } | ||||
@@ -170,14 +154,17 @@ public class DefaultFileSystemManager | |||||
final FileSystemProvider provider = (FileSystemProvider)m_providers.get( scheme ); | final FileSystemProvider provider = (FileSystemProvider)m_providers.get( scheme ); | ||||
if( provider != null ) | if( provider != null ) | ||||
{ | { | ||||
return provider.findFile( uri ); | |||||
return provider.findFile( baseFile, uri ); | |||||
} | } | ||||
} | } | ||||
// Decode the URI (remove %nn encodings) | |||||
final String decodedUri = UriParser.decode( uri ); | |||||
// Handle absolute file names | // Handle absolute file names | ||||
if( m_localFileProvider.isAbsoluteLocalName( uri ) ) | |||||
if( m_localFileProvider.isAbsoluteLocalName( decodedUri ) ) | |||||
{ | { | ||||
return m_localFileProvider.findLocalFile( uri ); | |||||
return m_localFileProvider.findLocalFile( decodedUri ); | |||||
} | } | ||||
// Assume a bad scheme | // Assume a bad scheme | ||||
@@ -193,32 +180,31 @@ public class DefaultFileSystemManager | |||||
final String message = REZ.getString( "find-rel-file.error", uri ); | final String message = REZ.getString( "find-rel-file.error", uri ); | ||||
throw new FileSystemException( message ); | throw new FileSystemException( message ); | ||||
} | } | ||||
return baseFile.resolveFile( uri ); | |||||
return baseFile.resolveFile( decodedUri ); | |||||
} | } | ||||
/** | /** | ||||
* A provider context implementation. | |||||
* Converts a local file into a {@link FileObject}. | |||||
*/ | */ | ||||
private final class ProviderContextImpl | |||||
implements FileSystemProviderContext | |||||
public FileObject convert( final File file ) | |||||
throws FileSystemException | |||||
{ | { | ||||
/** | |||||
* Locates a cached file system by root URI. | |||||
*/ | |||||
public FileSystem getFileSystem( final String rootURI ) | |||||
{ | |||||
// TODO - need to have a per-fs uri comparator | |||||
return (FileSystem)m_fileSystems.get( rootURI ); | |||||
} | |||||
return m_localFileProvider.findLocalFile( file ); | |||||
} | |||||
/** | |||||
* Registers a file system for caching. | |||||
*/ | |||||
public void putFileSystem( final String rootURI, final FileSystem fs ) | |||||
throws FileSystemException | |||||
/** | |||||
* Creates a layered file system. | |||||
*/ | |||||
public FileObject createFileSystem( final String scheme, | |||||
final FileObject file ) | |||||
throws FileSystemException | |||||
{ | |||||
FileSystemProvider provider = (FileSystemProvider)m_providers.get( scheme ); | |||||
if( provider == null ) | |||||
{ | { | ||||
// TODO - should really check that there's not one already cached | |||||
m_fileSystems.put( rootURI, fs ); | |||||
final String message = REZ.getString( "unknown-provider.error", scheme ); | |||||
throw new FileSystemException( message ); | |||||
} | } | ||||
return provider.createFileSystem( scheme, file ); | |||||
} | } | ||||
} | } |
@@ -0,0 +1,66 @@ | |||||
/* | |||||
* 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.impl; | |||||
import java.util.HashMap; | |||||
import java.util.Map; | |||||
import org.apache.aut.vfs.FileObject; | |||||
import org.apache.aut.vfs.FileSystemException; | |||||
import org.apache.aut.vfs.provider.FileSystem; | |||||
import org.apache.aut.vfs.provider.FileSystemProviderContext; | |||||
/** | |||||
* A provider context implementation. | |||||
* | |||||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||||
* @version $Revision$ $Date$ | |||||
*/ | |||||
final class DefaultProviderContext | |||||
implements FileSystemProviderContext | |||||
{ | |||||
private final DefaultFileSystemManager m_manager; | |||||
/** | |||||
* The cached file systems. This is a mapping from root URI to | |||||
* FileSystem object. | |||||
*/ | |||||
private final Map m_fileSystems = new HashMap(); | |||||
public DefaultProviderContext( final DefaultFileSystemManager manager ) | |||||
{ | |||||
m_manager = manager; | |||||
} | |||||
/** | |||||
* Locate a file by name. | |||||
*/ | |||||
public FileObject resolveFile( final FileObject baseFile, final String name ) | |||||
throws FileSystemException | |||||
{ | |||||
return m_manager.resolveFile( baseFile, name ); | |||||
} | |||||
/** | |||||
* Locates a cached file system by root URI. | |||||
*/ | |||||
public FileSystem getFileSystem( final String rootURI ) | |||||
{ | |||||
// TODO - need to have a per-fs uri comparator | |||||
return (FileSystem)m_fileSystems.get( rootURI ); | |||||
} | |||||
/** | |||||
* Registers a file system for caching. | |||||
*/ | |||||
public void putFileSystem( final String rootURI, final FileSystem fs ) | |||||
throws FileSystemException | |||||
{ | |||||
// TODO - should really check that there's not one already cached | |||||
m_fileSystems.put( rootURI, fs ); | |||||
} | |||||
} |
@@ -1,3 +1,5 @@ | |||||
# DefaultFileSystemManager | # DefaultFileSystemManager | ||||
unknown-scheme.error=Unknown scheme "{0}" in URI "{1}". | unknown-scheme.error=Unknown scheme "{0}" in URI "{1}". | ||||
find-rel-file.error=Could not find file with URI "{0}" because it is a relative path, and no base URI was provided. | find-rel-file.error=Could not find file with URI "{0}" because it is a relative path, and no base URI was provided. | ||||
multiple-providers-for-scheme.error=Multiple file system providers registered for URL scheme "{0}". | |||||
unknown-provider.error=Unknown file system provider "{0}". |
@@ -21,6 +21,7 @@ import org.apache.aut.vfs.FileType; | |||||
import org.apache.aut.vfs.NameScope; | import org.apache.aut.vfs.NameScope; | ||||
import org.apache.avalon.excalibur.i18n.ResourceManager; | import org.apache.avalon.excalibur.i18n.ResourceManager; | ||||
import org.apache.avalon.excalibur.i18n.Resources; | import org.apache.avalon.excalibur.i18n.Resources; | ||||
import org.apache.avalon.excalibur.io.IOUtil; | |||||
/** | /** | ||||
* A partial file object implementation. | * A partial file object implementation. | ||||
@@ -349,7 +350,7 @@ public abstract class AbstractFileObject | |||||
} | } | ||||
// Update cached info | // Update cached info | ||||
updateType( null ); | |||||
updateType(); | |||||
} | } | ||||
/** | /** | ||||
@@ -463,7 +464,41 @@ public abstract class AbstractFileObject | |||||
} | } | ||||
// Update cached info | // Update cached info | ||||
updateType( type ); | |||||
updateType(); | |||||
} | |||||
/** | |||||
* Copies the content of another file to this file. | |||||
*/ | |||||
public void copy( final FileObject file ) throws FileSystemException | |||||
{ | |||||
try | |||||
{ | |||||
final InputStream instr = file.getContent().getInputStream(); | |||||
try | |||||
{ | |||||
// Create the output strea via getContent(), to pick up the | |||||
// validation it does | |||||
final OutputStream outstr = getContent().getOutputStream(); | |||||
try | |||||
{ | |||||
IOUtil.copy( instr, outstr ); | |||||
} | |||||
finally | |||||
{ | |||||
IOUtil.shutdownStream( outstr ); | |||||
} | |||||
} | |||||
finally | |||||
{ | |||||
IOUtil.shutdownStream( instr ); | |||||
} | |||||
} | |||||
catch( final Exception exc ) | |||||
{ | |||||
final String message = REZ.getString( "copy-file.error", file.getName(), m_name ); | |||||
throw new FileSystemException( message, exc ); | |||||
} | |||||
} | } | ||||
/** | /** | ||||
@@ -598,14 +633,14 @@ public abstract class AbstractFileObject | |||||
*/ | */ | ||||
public void endOutput() throws Exception | public void endOutput() throws Exception | ||||
{ | { | ||||
updateType( FileType.FILE ); | |||||
updateType(); | |||||
doEndOutput(); | doEndOutput(); | ||||
} | } | ||||
/** | /** | ||||
* Update cached info when this file's type changes. | * Update cached info when this file's type changes. | ||||
*/ | */ | ||||
private void updateType( FileType type ) | |||||
private void updateType() | |||||
{ | { | ||||
// Notify parent that its child list may no longer be valid | // Notify parent that its child list may no longer be valid | ||||
notifyParent(); | notifyParent(); | ||||
@@ -23,13 +23,21 @@ public abstract class AbstractFileSystemProvider | |||||
private final static Resources REZ = | private final static Resources REZ = | ||||
ResourceManager.getPackageResources( AbstractFileSystemProvider.class ); | ResourceManager.getPackageResources( AbstractFileSystemProvider.class ); | ||||
protected FileSystemProviderContext m_context; | |||||
private FileSystemProviderContext m_context; | |||||
/** | |||||
* Returns the context for this provider. | |||||
*/ | |||||
protected FileSystemProviderContext getContext() | |||||
{ | |||||
return m_context; | |||||
} | |||||
/** | /** | ||||
* Sets the context for this file system provider. This method is called | * Sets the context for this file system provider. This method is called | ||||
* before any of the other provider methods. | * before any of the other provider methods. | ||||
*/ | */ | ||||
public void setContext( FileSystemProviderContext context ) | |||||
public void setContext( final FileSystemProviderContext context ) | |||||
{ | { | ||||
m_context = context; | m_context = context; | ||||
} | } | ||||
@@ -40,13 +48,14 @@ public abstract class AbstractFileSystemProvider | |||||
* @param uri | * @param uri | ||||
* The absolute URI of the file to find. | * The absolute URI of the file to find. | ||||
*/ | */ | ||||
public FileObject findFile( String uri ) throws FileSystemException | |||||
public FileObject findFile( final FileObject baseFile, | |||||
final String uri ) throws FileSystemException | |||||
{ | { | ||||
// Parse the URI | // Parse the URI | ||||
ParsedUri parsedURI = null; | |||||
ParsedUri parsedUri = null; | |||||
try | try | ||||
{ | { | ||||
parsedURI = parseURI( uri ); | |||||
parsedUri = parseUri( baseFile, uri ); | |||||
} | } | ||||
catch( FileSystemException exc ) | catch( FileSystemException exc ) | ||||
{ | { | ||||
@@ -54,31 +63,70 @@ public abstract class AbstractFileSystemProvider | |||||
throw new FileSystemException( message, exc ); | throw new FileSystemException( message, exc ); | ||||
} | } | ||||
// Locate the file | |||||
return findFile( parsedUri ); | |||||
} | |||||
/** | |||||
* Locates a file from its parsed URI. | |||||
*/ | |||||
private FileObject findFile( final ParsedUri parsedUri ) | |||||
throws FileSystemException | |||||
{ | |||||
// Check in the cache for the file system | // Check in the cache for the file system | ||||
FileSystem fs = m_context.getFileSystem( parsedURI.getRootURI() ); | |||||
final String rootUri = parsedUri.getRootUri(); | |||||
FileSystem fs = m_context.getFileSystem( rootUri ); | |||||
if( fs == null ) | if( fs == null ) | ||||
{ | { | ||||
// Need to create the file system | |||||
fs = createFileSystem( parsedURI ); | |||||
m_context.putFileSystem( parsedURI.getRootURI(), fs ); | |||||
// Need to create the file system, and cache it | |||||
fs = createFileSystem( parsedUri ); | |||||
m_context.putFileSystem( rootUri, fs ); | |||||
} | } | ||||
// Locate the file | // Locate the file | ||||
return fs.findFile( parsedURI.getPath() ); | |||||
return fs.findFile( parsedUri.getPath() ); | |||||
} | |||||
/** | |||||
* Creates a layered file system. | |||||
*/ | |||||
public FileObject createFileSystem( final String scheme, final FileObject file ) | |||||
throws FileSystemException | |||||
{ | |||||
// TODO - this is a pretty shonky model for layered FS; need to revise | |||||
// Build the URI | |||||
final ParsedUri uri = buildUri( scheme, file ); | |||||
// Locate the file | |||||
return findFile( uri ); | |||||
} | } | ||||
/** | /** | ||||
* Parses a URI into its components. The returned value is used to | * 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. | |||||
* locate the file system in the cache (using the root prefix). | |||||
* | * | ||||
* <p>The provider can annotate this object with any additional | * <p>The provider can annotate this object with any additional | ||||
* information it requires to create a file system from the URI. | * information it requires to create a file system from the URI. | ||||
*/ | */ | ||||
protected abstract ParsedUri parseURI( String uri ) throws FileSystemException; | |||||
protected abstract ParsedUri parseUri( final FileObject baseFile, final String uri ) | |||||
throws FileSystemException; | |||||
/** | |||||
* Builds the URI for the root of a layered file system. | |||||
*/ | |||||
protected ParsedUri buildUri( final String scheme, | |||||
final FileObject file ) | |||||
throws FileSystemException | |||||
{ | |||||
final String message = REZ.getString( "not-layered-fs.error" ); | |||||
throw new FileSystemException( message ); | |||||
} | |||||
/** | /** | ||||
* Creates the filesystem. | * Creates the filesystem. | ||||
*/ | */ | ||||
protected abstract org.apache.aut.vfs.provider.FileSystem createFileSystem( ParsedUri uri ) throws FileSystemException; | |||||
protected abstract FileSystem createFileSystem( final ParsedUri uri ) | |||||
throws FileSystemException; | |||||
} | } |
@@ -12,6 +12,11 @@ import org.apache.aut.vfs.FileSystemException; | |||||
/** | /** | ||||
* A file system provider, or factory. | * A file system provider, or factory. | ||||
* | |||||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||||
* @version $Revision$ $Date$ | |||||
* | |||||
* @ant:role shorthand="file-system" | |||||
*/ | */ | ||||
public interface FileSystemProvider | public interface FileSystemProvider | ||||
{ | { | ||||
@@ -24,8 +29,16 @@ public interface FileSystemProvider | |||||
/** | /** | ||||
* Locates a file object, by absolute URI. | * Locates a file object, by absolute URI. | ||||
* | * | ||||
* @param baseFile | |||||
* The base file to use for resolving the individual parts of | |||||
* a compound URI. | |||||
* @param uri | * @param uri | ||||
* The absolute URI of the file to find. | * The absolute URI of the file to find. | ||||
*/ | */ | ||||
FileObject findFile( String uri ) throws FileSystemException; | |||||
FileObject findFile( FileObject baseFile, String uri ) throws FileSystemException; | |||||
/** | |||||
* Creates a layered file system. | |||||
*/ | |||||
FileObject createFileSystem( String scheme, FileObject file ) throws FileSystemException; | |||||
} | } |
@@ -7,16 +7,27 @@ | |||||
*/ | */ | ||||
package org.apache.aut.vfs.provider; | package org.apache.aut.vfs.provider; | ||||
import org.apache.aut.vfs.FileObject; | |||||
import org.apache.aut.vfs.FileSystemException; | import org.apache.aut.vfs.FileSystemException; | ||||
import org.apache.aut.vfs.FileSystemManager; | |||||
/** | /** | ||||
* Used for a file system provider to access the services it needs, such | * Used for a file system provider to access the services it needs, such | ||||
* as the file system cache or other file system providers. | * as the file system cache or other file system providers. | ||||
* | * | ||||
* @author Adam Murdoch | |||||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||||
* @version $Revision$ $Date$ | |||||
*/ | */ | ||||
public interface FileSystemProviderContext | public interface FileSystemProviderContext | ||||
{ | { | ||||
/** | |||||
* Locate a file by name. See | |||||
* {@link FileSystemManager#resolveFile(FileObject, String)} for a | |||||
* description of how this works. | |||||
*/ | |||||
FileObject resolveFile( FileObject baseFile, String name ) | |||||
throws FileSystemException; | |||||
/** | /** | ||||
* Locates a cached file system by root URI. | * Locates a cached file system by root URI. | ||||
*/ | */ | ||||
@@ -34,13 +34,13 @@ public class ParsedUri | |||||
} | } | ||||
/** Returns the root URI, used to identify the file system. */ | /** Returns the root URI, used to identify the file system. */ | ||||
public String getRootURI() | |||||
public String getRootUri() | |||||
{ | { | ||||
return m_rootURI; | return m_rootURI; | ||||
} | } | ||||
/** Sets the root URI. */ | /** Sets the root URI. */ | ||||
public void setRootURI( String rootPrefix ) | |||||
public void setRootUri( String rootPrefix ) | |||||
{ | { | ||||
m_rootURI = rootPrefix; | m_rootURI = rootPrefix; | ||||
} | } | ||||
@@ -17,6 +17,7 @@ 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-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-in-use.error=Could not write to "{0}" because it is already in use. | ||||
write.error=Could not write to "{0}". | write.error=Could not write to "{0}". | ||||
copy-file.error=Could not copy "{0}" to "{1}". | |||||
# DefaultFileContent | # DefaultFileContent | ||||
get-size-no-exist.error=Could not determine the size of file "{0}" because it does not exist. | get-size-no-exist.error=Could not determine the size of file "{0}" because it does not exist. | ||||
@@ -30,6 +31,7 @@ close-outstr.error=Could not close file output stream. | |||||
# AbstractFileSystemProvider | # AbstractFileSystemProvider | ||||
invalid-absolute-uri.error=Invalid absolute URI "{0}". | invalid-absolute-uri.error=Invalid absolute URI "{0}". | ||||
not-layered-fs.error=File system is not a layered file system. | |||||
# UriParser | # UriParser | ||||
missing-double-slashes.error=Expecting // to follow the scheme in URI "{0}". | missing-double-slashes.error=Expecting // to follow the scheme in URI "{0}". | ||||
@@ -38,3 +40,5 @@ missing-port.error=Port number is missing from URI "{0}". | |||||
missing-hostname-path-sep.error=Expecting / to follow the hostname in URI "{0}". | missing-hostname-path-sep.error=Expecting / to follow the hostname in URI "{0}". | ||||
invalid-childname.error=Invalid file base-name "{0}". | invalid-childname.error=Invalid file base-name "{0}". | ||||
invalid-descendent-name.error=Invalid descendent file name "{0}". | invalid-descendent-name.error=Invalid descendent file name "{0}". | ||||
invalid-escape-sequence.error=Invalid URI escape sequence "{0}". | |||||
invalid-relative-path.error=Invalid relative file name. |
@@ -84,14 +84,41 @@ public class UriParser | |||||
* Parses an absolute URI, splitting it into its components. This | * Parses an absolute URI, splitting it into its components. This | ||||
* implementation assumes a "generic URI", as defined by RFC 2396. See | * implementation assumes a "generic URI", as defined by RFC 2396. See | ||||
* {@link #parseGenericUri} for more info. | * {@link #parseGenericUri} for more info. | ||||
* | |||||
* <p>Sub-classes should override this method. | |||||
*/ | */ | ||||
public ParsedUri parseUri( final String uriStr ) throws FileSystemException | public ParsedUri parseUri( final String uriStr ) throws FileSystemException | ||||
{ | { | ||||
final ParsedUri retval = new ParsedUri(); | |||||
parseGenericUri( uriStr, retval ); | |||||
return retval; | |||||
// Parse the URI | |||||
final ParsedUri uri = new ParsedUri(); | |||||
parseGenericUri( uriStr, uri ); | |||||
// Build the root URI | |||||
final StringBuffer rootUri = new StringBuffer(); | |||||
appendRootUri( uri, rootUri ); | |||||
uri.setRootUri( rootUri.toString() ); | |||||
return uri; | |||||
} | |||||
/** | |||||
* Assembles a generic URI, appending to the supplied StringBuffer. | |||||
*/ | |||||
protected void appendRootUri( final ParsedUri uri, final StringBuffer rootUri ) | |||||
{ | |||||
rootUri.append( uri.getScheme() ); | |||||
rootUri.append( "://" ); | |||||
final String userInfo = uri.getUserInfo(); | |||||
if( userInfo != null && userInfo.length() != 0 ) | |||||
{ | |||||
rootUri.append( userInfo ); | |||||
rootUri.append( "@" ); | |||||
} | |||||
rootUri.append( uri.getHostName() ); | |||||
final String port = uri.getPort(); | |||||
if( port != null && port.length() > 0 ) | |||||
{ | |||||
rootUri.append( ":" ); | |||||
rootUri.append( port ); | |||||
} | |||||
} | } | ||||
/** | /** | ||||
@@ -119,7 +146,8 @@ public class UriParser | |||||
// Extract the scheme and authority parts | // Extract the scheme and authority parts | ||||
extractToPath( uriStr, name, uri ); | extractToPath( uriStr, name, uri ); | ||||
// Normalise the file name | |||||
// Decode and normalise the file name | |||||
decode( name, 0, name.length() ); | |||||
normalisePath( name ); | normalisePath( name ); | ||||
uri.setPath( name.toString() ); | uri.setPath( name.toString() ); | ||||
@@ -128,7 +156,7 @@ public class UriParser | |||||
rootUri.append( uri.getScheme() ); | rootUri.append( uri.getScheme() ); | ||||
rootUri.append( "://" ); | rootUri.append( "://" ); | ||||
rootUri.append( uri.getHostName() ); | rootUri.append( uri.getHostName() ); | ||||
uri.setRootURI( rootUri.toString() ); | |||||
uri.setRootUri( rootUri.toString() ); | |||||
} | } | ||||
/** | /** | ||||
@@ -404,9 +432,9 @@ public class UriParser | |||||
if( scope == NameScope.CHILD ) | if( scope == NameScope.CHILD ) | ||||
{ | { | ||||
final int baseLen = baseFile.length(); | final int baseLen = baseFile.length(); | ||||
if( ! resolvedPath.startsWith( baseFile ) | |||||
if( !resolvedPath.startsWith( baseFile ) | |||||
|| resolvedPath.length() == baseLen | || resolvedPath.length() == baseLen | ||||
|| resolvedPath.charAt( baseLen ) != m_separatorChar | |||||
|| ( baseLen > 1 && resolvedPath.charAt( baseLen ) != m_separatorChar ) | |||||
|| resolvedPath.indexOf( m_separatorChar, baseLen + 1 ) != -1 ) | || resolvedPath.indexOf( m_separatorChar, baseLen + 1 ) != -1 ) | ||||
{ | { | ||||
final String message = REZ.getString( "invalid-childname.error", path ); | final String message = REZ.getString( "invalid-childname.error", path ); | ||||
@@ -416,9 +444,9 @@ public class UriParser | |||||
else if( scope == NameScope.DESCENDENT ) | else if( scope == NameScope.DESCENDENT ) | ||||
{ | { | ||||
final int baseLen = baseFile.length(); | final int baseLen = baseFile.length(); | ||||
if( ! resolvedPath.startsWith( baseFile ) | |||||
if( !resolvedPath.startsWith( baseFile ) | |||||
|| resolvedPath.length() == baseLen | || resolvedPath.length() == baseLen | ||||
|| resolvedPath.charAt( baseLen ) != m_separatorChar ) | |||||
|| ( baseLen > 1 && resolvedPath.charAt( baseLen ) != m_separatorChar ) ) | |||||
{ | { | ||||
final String message = REZ.getString( "invalid-descendent-name.error", path ); | final String message = REZ.getString( "invalid-descendent-name.error", path ); | ||||
throw new FileSystemException( message ); | throw new FileSystemException( message ); | ||||
@@ -463,7 +491,8 @@ public class UriParser | |||||
* <li>Removes trailing separator. | * <li>Removes trailing separator. | ||||
* </ul> | * </ul> | ||||
*/ | */ | ||||
public void normalisePath( final StringBuffer path ) throws FileSystemException | |||||
public void normalisePath( final StringBuffer path ) | |||||
throws FileSystemException | |||||
{ | { | ||||
if( path.length() == 0 ) | if( path.length() == 0 ) | ||||
{ | { | ||||
@@ -515,14 +544,20 @@ public class UriParser | |||||
path.charAt( startElem + 1 ) == '.' ) | path.charAt( startElem + 1 ) == '.' ) | ||||
{ | { | ||||
// A '..' element - remove the previous element | // A '..' element - remove the previous element | ||||
if( startElem > startFirstElem ) | |||||
if( startElem == startFirstElem ) | |||||
{ | { | ||||
int pos = startElem - 2; | |||||
for( ; pos >= 0 && path.charAt( pos ) != m_separatorChar; pos -- ) | |||||
{ | |||||
} | |||||
startElem = pos + 1; | |||||
// Previous element is missing | |||||
final String message = REZ.getString( "invalid-relative-path.error" ); | |||||
throw new FileSystemException( message ); | |||||
} | } | ||||
// Find start of previous element | |||||
int pos = startElem - 2; | |||||
for( ; pos >= 0 && path.charAt( pos ) != m_separatorChar; pos-- ) | |||||
{ | |||||
} | |||||
startElem = pos + 1; | |||||
path.delete( startElem, endElem + 1 ); | path.delete( startElem, endElem + 1 ); | ||||
maxlen = path.length(); | maxlen = path.length(); | ||||
continue; | continue; | ||||
@@ -595,7 +630,8 @@ public class UriParser | |||||
* @return | * @return | ||||
* The scheme name. Returns null if there is no scheme. | * The scheme name. Returns null if there is no scheme. | ||||
*/ | */ | ||||
protected static String extractScheme( final String uri, final StringBuffer buffer ) | |||||
public static String extractScheme( final String uri, | |||||
final StringBuffer buffer ) | |||||
{ | { | ||||
if( buffer != null ) | if( buffer != null ) | ||||
{ | { | ||||
@@ -642,4 +678,103 @@ public class UriParser | |||||
// No scheme in URI | // No scheme in URI | ||||
return null; | return null; | ||||
} | } | ||||
/** | |||||
* Removes %nn encodings from a string. | |||||
*/ | |||||
public static String decode( final String encodedStr ) | |||||
throws FileSystemException | |||||
{ | |||||
final StringBuffer buffer = new StringBuffer( encodedStr ); | |||||
decode( buffer, 0, buffer.length() ); | |||||
return buffer.toString(); | |||||
} | |||||
/** | |||||
* Removes %nn encodings from a string. | |||||
*/ | |||||
public static void decode( final StringBuffer buffer, | |||||
final int offset, | |||||
final int length ) | |||||
throws FileSystemException | |||||
{ | |||||
int index = offset; | |||||
int count = length; | |||||
for( ; count > 0; count--, index++ ) | |||||
{ | |||||
final char ch = buffer.charAt( index ); | |||||
if( ch != '%' ) | |||||
{ | |||||
continue; | |||||
} | |||||
if( count < 3 ) | |||||
{ | |||||
final String message = REZ.getString( "invalid-escape-sequence.error", buffer.substring( index, index + count ) ); | |||||
throw new FileSystemException( message ); | |||||
} | |||||
// Decode | |||||
int dig1 = Character.digit( buffer.charAt( index + 1 ), 16 ); | |||||
int dig2 = Character.digit( buffer.charAt( index + 2 ), 16 ); | |||||
if( dig1 == -1 || dig2 == -1 ) | |||||
{ | |||||
final String message = REZ.getString( "invalid-escape-sequence.error", buffer.substring( index, index + 3 ) ); | |||||
throw new FileSystemException( message ); | |||||
} | |||||
char value = (char)( dig1 << 4 | dig2 ); | |||||
// Replace | |||||
buffer.setCharAt( index, value ); | |||||
buffer.delete( index + 1, index + 3 ); | |||||
count -= 2; | |||||
} | |||||
} | |||||
/** | |||||
* Encodes and appends a string to a StringBuffer. | |||||
*/ | |||||
public static void appendEncoded( final StringBuffer buffer, | |||||
final String unencodedValue, | |||||
final char[] reserved ) | |||||
{ | |||||
final int offset = buffer.length(); | |||||
buffer.append( unencodedValue ); | |||||
encode( buffer, offset, unencodedValue.length(), reserved ); | |||||
} | |||||
/** | |||||
* Encodes a set of reserved characters in a StringBuffer, using the URI | |||||
* %nn encoding. Always encodes % characters. | |||||
*/ | |||||
public static void encode( final StringBuffer buffer, | |||||
final int offset, | |||||
final int length, | |||||
final char[] reserved ) | |||||
{ | |||||
int index = offset; | |||||
int count = length; | |||||
for( ; count > 0; index++, count-- ) | |||||
{ | |||||
final char ch = buffer.charAt( index ); | |||||
boolean match = ( ch == '%' ); | |||||
for( int i = 0; !match && i < reserved.length; i++ ) | |||||
{ | |||||
if( ch == reserved[ i ] ) | |||||
{ | |||||
match = true; | |||||
} | |||||
} | |||||
if( match ) | |||||
{ | |||||
// Encode | |||||
char[] digits = { | |||||
Character.forDigit( ( ( ch >> 4 ) & 0xF ), 16 ), | |||||
Character.forDigit( ( ch & 0xF ), 16 ) | |||||
}; | |||||
buffer.setCharAt( index, '%' ); | |||||
buffer.insert( index + 1, digits ); | |||||
index += 2; | |||||
} | |||||
} | |||||
} | |||||
} | } |
@@ -8,7 +8,6 @@ | |||||
package org.apache.aut.vfs.provider.ftp; | package org.apache.aut.vfs.provider.ftp; | ||||
import org.apache.aut.vfs.FileSystemException; | import org.apache.aut.vfs.FileSystemException; | ||||
import org.apache.aut.vfs.provider.ParsedUri; | |||||
import org.apache.aut.vfs.provider.UriParser; | import org.apache.aut.vfs.provider.UriParser; | ||||
/** | /** | ||||
@@ -21,15 +20,27 @@ public class FtpFileNameParser extends UriParser | |||||
/** | /** | ||||
* Parses an absolute URI, splitting it into its components. | * Parses an absolute URI, splitting it into its components. | ||||
*/ | */ | ||||
public ParsedUri parseUri( String uriStr ) throws FileSystemException | |||||
public ParsedFtpUri parseFtpUri( final String uriStr ) | |||||
throws FileSystemException | |||||
{ | { | ||||
ParsedFtpUri uri = new ParsedFtpUri(); | |||||
final ParsedFtpUri uri = new ParsedFtpUri(); | |||||
// FTP URI are generic URI (as per RFC 2396) | // FTP URI are generic URI (as per RFC 2396) | ||||
parseGenericUri( uriStr, uri ); | parseGenericUri( uriStr, uri ); | ||||
// Adjust the hostname to lower-case | |||||
final String hostname = uri.getHostName().toLowerCase(); | |||||
uri.setHostName( hostname ); | |||||
// Drop the port if it is 21 | |||||
final String port = uri.getPort(); | |||||
if( port != null && port.equals( "21" ) ) | |||||
{ | |||||
uri.setPort( null ); | |||||
} | |||||
// Split up the userinfo into a username and password | // Split up the userinfo into a username and password | ||||
String userInfo = uri.getUserInfo(); | |||||
final String userInfo = uri.getUserInfo(); | |||||
if( userInfo != null ) | if( userInfo != null ) | ||||
{ | { | ||||
int idx = userInfo.indexOf( ':' ); | int idx = userInfo.indexOf( ':' ); | ||||
@@ -46,6 +57,11 @@ public class FtpFileNameParser extends UriParser | |||||
} | } | ||||
} | } | ||||
// Now build the root URI | |||||
final StringBuffer rootUri = new StringBuffer(); | |||||
appendRootUri( uri, rootUri ); | |||||
uri.setRootUri( rootUri.toString() ); | |||||
return uri; | return uri; | ||||
} | } | ||||
} | } |
@@ -8,39 +8,44 @@ | |||||
package org.apache.aut.vfs.provider.ftp; | package org.apache.aut.vfs.provider.ftp; | ||||
import org.apache.aut.vfs.FileName; | import org.apache.aut.vfs.FileName; | ||||
import org.apache.aut.vfs.FileObject; | |||||
import org.apache.aut.vfs.FileSystemException; | import org.apache.aut.vfs.FileSystemException; | ||||
import org.apache.aut.vfs.provider.AbstractFileSystemProvider; | import org.apache.aut.vfs.provider.AbstractFileSystemProvider; | ||||
import org.apache.aut.vfs.provider.DefaultFileName; | import org.apache.aut.vfs.provider.DefaultFileName; | ||||
import org.apache.aut.vfs.provider.FileSystem; | import org.apache.aut.vfs.provider.FileSystem; | ||||
import org.apache.aut.vfs.provider.ParsedUri; | import org.apache.aut.vfs.provider.ParsedUri; | ||||
import org.apache.aut.vfs.provider.UriParser; | |||||
/** | /** | ||||
* A provider for FTP file systems. | * A provider for FTP file systems. | ||||
* | * | ||||
* @author Adam Murdoch | * @author Adam Murdoch | ||||
* | |||||
* @ant:type type="file-system" name="ftp" | |||||
*/ | */ | ||||
public class FtpFileSystemProvider extends AbstractFileSystemProvider | public class FtpFileSystemProvider extends AbstractFileSystemProvider | ||||
{ | { | ||||
private UriParser m_parser = new FtpFileNameParser(); | |||||
private final FtpFileNameParser m_parser = new FtpFileNameParser(); | |||||
/** | /** | ||||
* Parses a URI into its components. | * Parses a URI into its components. | ||||
*/ | */ | ||||
protected ParsedUri parseURI( String uri ) throws FileSystemException | |||||
protected ParsedUri parseUri( final FileObject baseFile, | |||||
final String uri ) | |||||
throws FileSystemException | |||||
{ | { | ||||
return m_parser.parseUri( uri ); | |||||
return m_parser.parseFtpUri( uri ); | |||||
} | } | ||||
/** | /** | ||||
* Creates the filesystem. | * Creates the filesystem. | ||||
*/ | */ | ||||
protected FileSystem createFileSystem( ParsedUri uri ) throws FileSystemException | |||||
protected FileSystem createFileSystem( final ParsedUri uri ) | |||||
throws FileSystemException | |||||
{ | { | ||||
ParsedFtpUri ftpUri = (ParsedFtpUri)uri; | |||||
final ParsedFtpUri ftpUri = (ParsedFtpUri)uri; | |||||
// Build the root name | // Build the root name | ||||
FileName rootName = new DefaultFileName( m_parser, ftpUri.getRootURI(), "/" ); | |||||
final FileName rootName = new DefaultFileName( m_parser, ftpUri.getRootUri(), "/" ); | |||||
// Determine the username and password to use | // Determine the username and password to use | ||||
String username = ftpUri.getUserName(); | String username = ftpUri.getUserName(); | ||||
@@ -50,21 +50,22 @@ abstract class LocalFileNameParser | |||||
* | * | ||||
* @param uriStr The URI. | * @param uriStr The URI. | ||||
*/ | */ | ||||
public ParsedUri parseUri( final String uriStr ) | |||||
public ParsedUri parseFileUri( final String uriStr ) | |||||
throws FileSystemException | throws FileSystemException | ||||
{ | { | ||||
StringBuffer name = new StringBuffer(); | |||||
ParsedFileUri uri = new ParsedFileUri(); | |||||
final StringBuffer name = new StringBuffer(); | |||||
final ParsedFileUri uri = new ParsedFileUri(); | |||||
// Extract the scheme | // Extract the scheme | ||||
String scheme = extractScheme( uriStr, name ); | |||||
final String scheme = extractScheme( uriStr, name ); | |||||
uri.setScheme( scheme ); | uri.setScheme( scheme ); | ||||
// Adjust the separators | |||||
// Remove encoding, and adjust the separators | |||||
decode( name, 0, name.length() ); | |||||
fixSeparators( name ); | fixSeparators( name ); | ||||
// Extract the root prefix | // Extract the root prefix | ||||
String rootFile = extractRootPrefix( uriStr, name ); | |||||
final String rootFile = extractRootPrefix( uriStr, name ); | |||||
uri.setRootFile( rootFile ); | uri.setRootFile( rootFile ); | ||||
// Normalise the path | // Normalise the path | ||||
@@ -72,11 +73,11 @@ abstract class LocalFileNameParser | |||||
uri.setPath( name.toString() ); | uri.setPath( name.toString() ); | ||||
// Build the root URI | // Build the root URI | ||||
StringBuffer rootUri = new StringBuffer(); | |||||
final StringBuffer rootUri = new StringBuffer(); | |||||
rootUri.append( scheme ); | rootUri.append( scheme ); | ||||
rootUri.append( "://" ); | rootUri.append( "://" ); | ||||
rootUri.append( rootFile ); | rootUri.append( rootFile ); | ||||
uri.setRootURI( rootUri.toString() ); | |||||
uri.setRootUri( rootUri.toString() ); | |||||
return uri; | return uri; | ||||
} | } | ||||
@@ -51,20 +51,22 @@ public class LocalFileSystemProvider extends AbstractFileSystemProvider | |||||
/** | /** | ||||
* Finds a local file, from its local name. | * Finds a local file, from its local name. | ||||
*/ | */ | ||||
public FileObject findLocalFile( final String name ) throws FileSystemException | |||||
public FileObject findLocalFile( final String name ) | |||||
throws FileSystemException | |||||
{ | { | ||||
// TODO - tidy this up, no need to turn the name into an absolute URI, | // TODO - tidy this up, no need to turn the name into an absolute URI, | ||||
// and then straight back again | // and then straight back again | ||||
return findFile( "file:" + name ); | |||||
return findFile( null, "file:" + name ); | |||||
} | } | ||||
/** | /** | ||||
* Finds a local file. | * Finds a local file. | ||||
*/ | */ | ||||
public FileObject findFileByLocalName( final File file ) throws FileSystemException | |||||
public FileObject findLocalFile( final File file ) | |||||
throws FileSystemException | |||||
{ | { | ||||
// TODO - tidy this up, should build file object straight from the file | // TODO - tidy this up, should build file object straight from the file | ||||
return findFile( "file:" + file.getAbsolutePath() ); | |||||
return findFile( null, "file:" + file.getAbsolutePath() ); | |||||
} | } | ||||
/** | /** | ||||
@@ -75,22 +77,25 @@ public class LocalFileSystemProvider extends AbstractFileSystemProvider | |||||
* <p>The provider can annotate this object with any additional | * <p>The provider can annotate this object with any additional | ||||
* information it requires to create a file system from the URI. | * information it requires to create a file system from the URI. | ||||
*/ | */ | ||||
protected ParsedUri parseURI( final String uri ) throws FileSystemException | |||||
protected ParsedUri parseUri( final FileObject baseFile, | |||||
final String uri ) | |||||
throws FileSystemException | |||||
{ | { | ||||
return m_parser.parseUri( uri ); | |||||
return m_parser.parseFileUri( uri ); | |||||
} | } | ||||
/** | /** | ||||
* Creates the filesystem. | * Creates the filesystem. | ||||
*/ | */ | ||||
protected FileSystem createFileSystem( final ParsedUri uri ) throws FileSystemException | |||||
protected FileSystem createFileSystem( final ParsedUri uri ) | |||||
throws FileSystemException | |||||
{ | { | ||||
// Build the name of the root file. | // Build the name of the root file. | ||||
final ParsedFileUri fileUri = (ParsedFileUri)uri; | final ParsedFileUri fileUri = (ParsedFileUri)uri; | ||||
final String rootFile = fileUri.getRootFile(); | final String rootFile = fileUri.getRootFile(); | ||||
// Create the file system | // Create the file system | ||||
final DefaultFileName rootName = new DefaultFileName( m_parser, fileUri.getRootURI(), "/" ); | |||||
final DefaultFileName rootName = new DefaultFileName( m_parser, fileUri.getRootUri(), "/" ); | |||||
return new LocalFileSystem( rootName, rootFile ); | return new LocalFileSystem( rootName, rootFile ); | ||||
} | } | ||||
} | } |
@@ -27,19 +27,27 @@ public class SmbFileNameParser | |||||
/** | /** | ||||
* Parses an absolute URI, splitting it into its components. | * Parses an absolute URI, splitting it into its components. | ||||
*/ | */ | ||||
public ParsedUri parseUri( String uriStr ) throws FileSystemException | |||||
public ParsedUri parseSmbUri( final String uriStr ) | |||||
throws FileSystemException | |||||
{ | { | ||||
ParsedSmbUri uri = new ParsedSmbUri(); | |||||
StringBuffer name = new StringBuffer(); | |||||
final ParsedSmbUri uri = new ParsedSmbUri(); | |||||
final StringBuffer name = new StringBuffer(); | |||||
// Extract the scheme and authority parts | // Extract the scheme and authority parts | ||||
extractToPath( uriStr, name, uri ); | extractToPath( uriStr, name, uri ); | ||||
// Normalise paths | |||||
// Convert the hostname to lowercase | |||||
final String hostname = uri.getHostName().toLowerCase(); | |||||
uri.setHostName( hostname ); | |||||
// TODO - drop the default port | |||||
// Decode and adjust separators | |||||
decode( name, 0, name.length() ); | |||||
fixSeparators( name ); | fixSeparators( name ); | ||||
// Extract the share | // Extract the share | ||||
String share = extractFirstElement( name ); | |||||
final String share = extractFirstElement( name ); | |||||
if( share == null ) | if( share == null ) | ||||
{ | { | ||||
final String message = REZ.getString( "missing-share-name.error", uriStr ); | final String message = REZ.getString( "missing-share-name.error", uriStr ); | ||||
@@ -47,23 +55,18 @@ public class SmbFileNameParser | |||||
} | } | ||||
uri.setShare( share ); | uri.setShare( share ); | ||||
// Normalise the path | |||||
normalisePath( name ); | |||||
// Set the path | // Set the path | ||||
uri.setPath( name.toString() ); | uri.setPath( name.toString() ); | ||||
// Set the root URI | // Set the root URI | ||||
StringBuffer rootUri = new StringBuffer(); | 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() ); | |||||
appendRootUri( uri, rootUri ); | |||||
rootUri.append( '/' ); | rootUri.append( '/' ); | ||||
rootUri.append( share ); | rootUri.append( share ); | ||||
uri.setRootURI( rootUri.toString() ); | |||||
uri.setRootUri( rootUri.toString() ); | |||||
return uri; | return uri; | ||||
} | } | ||||
@@ -8,6 +8,7 @@ | |||||
package org.apache.aut.vfs.provider.smb; | package org.apache.aut.vfs.provider.smb; | ||||
import org.apache.aut.vfs.FileName; | import org.apache.aut.vfs.FileName; | ||||
import org.apache.aut.vfs.FileObject; | |||||
import org.apache.aut.vfs.FileSystemException; | import org.apache.aut.vfs.FileSystemException; | ||||
import org.apache.aut.vfs.provider.AbstractFileSystemProvider; | import org.apache.aut.vfs.provider.AbstractFileSystemProvider; | ||||
import org.apache.aut.vfs.provider.DefaultFileName; | import org.apache.aut.vfs.provider.DefaultFileName; | ||||
@@ -19,27 +20,31 @@ import org.apache.aut.vfs.provider.ParsedUri; | |||||
* A provider for SMB (Samba, Windows share) file systems. | * A provider for SMB (Samba, Windows share) file systems. | ||||
* | * | ||||
* @author Adam Murdoch | * @author Adam Murdoch | ||||
* | |||||
* @ant:type type="file-system" name="smb" | |||||
*/ | */ | ||||
public class SmbFileSystemProvider extends AbstractFileSystemProvider implements FileSystemProvider | public class SmbFileSystemProvider extends AbstractFileSystemProvider implements FileSystemProvider | ||||
{ | { | ||||
SmbFileNameParser m_parser = new SmbFileNameParser(); | |||||
private final SmbFileNameParser m_parser = new SmbFileNameParser(); | |||||
/** | /** | ||||
* Parses a URI into its components. | * Parses a URI into its components. | ||||
*/ | */ | ||||
protected ParsedUri parseURI( String uri ) throws FileSystemException | |||||
protected ParsedUri parseUri( final FileObject baseFile, | |||||
final String uri ) | |||||
throws FileSystemException | |||||
{ | { | ||||
return m_parser.parseUri( uri ); | |||||
return m_parser.parseSmbUri( uri ); | |||||
} | } | ||||
/** | /** | ||||
* Creates the filesystem. | * Creates the filesystem. | ||||
*/ | */ | ||||
protected FileSystem createFileSystem( ParsedUri uri ) throws FileSystemException | |||||
protected FileSystem createFileSystem( final ParsedUri uri ) | |||||
throws FileSystemException | |||||
{ | { | ||||
ParsedSmbUri smbUri = (ParsedSmbUri)uri; | |||||
FileName rootName = new DefaultFileName( m_parser, smbUri.getRootURI(), "/" ); | |||||
final ParsedSmbUri smbUri = (ParsedSmbUri)uri; | |||||
final FileName rootName = new DefaultFileName( m_parser, smbUri.getRootUri(), "/" ); | |||||
return new SmbFileSystem( rootName ); | return new SmbFileSystem( rootName ); | ||||
} | } | ||||
} | } |
@@ -7,6 +7,7 @@ | |||||
*/ | */ | ||||
package org.apache.aut.vfs.provider.zip; | package org.apache.aut.vfs.provider.zip; | ||||
import org.apache.aut.vfs.FileObject; | |||||
import org.apache.aut.vfs.provider.ParsedUri; | import org.apache.aut.vfs.provider.ParsedUri; | ||||
/** | /** | ||||
@@ -16,14 +17,25 @@ import org.apache.aut.vfs.provider.ParsedUri; | |||||
*/ | */ | ||||
public class ParsedZipUri extends ParsedUri | public class ParsedZipUri extends ParsedUri | ||||
{ | { | ||||
private String m_zipFile; | |||||
private String m_zipFileName; | |||||
private FileObject m_zipFile; | |||||
public String getZipFile() | |||||
public String getZipFileName() | |||||
{ | |||||
return m_zipFileName; | |||||
} | |||||
public void setZipFileName( final String zipFileName ) | |||||
{ | |||||
m_zipFileName = zipFileName; | |||||
} | |||||
public FileObject getZipFile() | |||||
{ | { | ||||
return m_zipFile; | return m_zipFile; | ||||
} | } | ||||
public void setZipFile( String zipFile ) | |||||
public void setZipFile( final FileObject zipFile ) | |||||
{ | { | ||||
m_zipFile = zipFile; | m_zipFile = zipFile; | ||||
} | } | ||||
@@ -8,7 +8,6 @@ | |||||
package org.apache.aut.vfs.provider.zip; | package org.apache.aut.vfs.provider.zip; | ||||
import org.apache.aut.vfs.FileSystemException; | import org.apache.aut.vfs.FileSystemException; | ||||
import org.apache.aut.vfs.provider.ParsedUri; | |||||
import org.apache.aut.vfs.provider.UriParser; | import org.apache.aut.vfs.provider.UriParser; | ||||
/** | /** | ||||
@@ -16,66 +15,77 @@ import org.apache.aut.vfs.provider.UriParser; | |||||
* | * | ||||
* @author Adam Murdoch | * @author Adam Murdoch | ||||
*/ | */ | ||||
public class ZipFileNameParser extends UriParser | |||||
public class ZipFileNameParser | |||||
extends UriParser | |||||
{ | { | ||||
private static final char[] ZIP_URL_RESERVED_CHARS = { '!' }; | |||||
/** | /** | ||||
* Parses an absolute URI, splitting it into its components. | * Parses an absolute URI, splitting it into its components. | ||||
* | * | ||||
* @param name | |||||
* @param uriStr | |||||
* The URI. | * The URI. | ||||
*/ | */ | ||||
public ParsedUri parseUri( String uriStr ) throws FileSystemException | |||||
public ParsedZipUri parseZipUri( final String uriStr ) | |||||
throws FileSystemException | |||||
{ | { | ||||
StringBuffer name = new StringBuffer(); | |||||
ParsedZipUri uri = new ParsedZipUri(); | |||||
final StringBuffer name = new StringBuffer(); | |||||
final ParsedZipUri uri = new ParsedZipUri(); | |||||
// Extract the scheme | // Extract the scheme | ||||
String scheme = extractScheme( uriStr, name ); | |||||
final String scheme = extractScheme( uriStr, name ); | |||||
uri.setScheme( scheme ); | uri.setScheme( scheme ); | ||||
// Extract the Zip file name | // Extract the Zip file name | ||||
String zipName = extractZipName( name ); | |||||
uri.setZipFile( zipName ); | |||||
// Adjust the separators | |||||
fixSeparators( name ); | |||||
final String zipName = extractZipName( name ); | |||||
uri.setZipFileName( zipName ); | |||||
// Normalise the file name | |||||
// Decode and normalise the file name | |||||
decode( name, 0, name.length() ); | |||||
normalisePath( name ); | normalisePath( name ); | ||||
uri.setPath( name.toString() ); | uri.setPath( name.toString() ); | ||||
// Build root URI | |||||
StringBuffer rootUri = new StringBuffer(); | |||||
rootUri.append( scheme ); | |||||
return uri; | |||||
} | |||||
/** | |||||
* Assembles a root URI from the components of a parsed URI. | |||||
*/ | |||||
public String buildRootUri( final ParsedZipUri uri ) | |||||
{ | |||||
final StringBuffer rootUri = new StringBuffer(); | |||||
rootUri.append( uri.getScheme() ); | |||||
rootUri.append( ":" ); | rootUri.append( ":" ); | ||||
rootUri.append( zipName ); | |||||
appendEncoded( rootUri, uri.getZipFile().getName().getURI(), ZIP_URL_RESERVED_CHARS ); | |||||
rootUri.append( "!" ); | rootUri.append( "!" ); | ||||
uri.setRootURI( rootUri.toString() ); | |||||
return uri; | |||||
return rootUri.toString(); | |||||
} | } | ||||
/** | /** | ||||
* Pops the root prefix off a URI, which has had the scheme removed. | * Pops the root prefix off a URI, which has had the scheme removed. | ||||
*/ | */ | ||||
protected String extractZipName( StringBuffer uri ) throws FileSystemException | |||||
private String extractZipName( final StringBuffer uri ) | |||||
throws FileSystemException | |||||
{ | { | ||||
// Looking for <name>!<abspath> | // Looking for <name>!<abspath> | ||||
// TODO - how does '!' in the file name get escaped? | |||||
int maxlen = uri.length(); | int maxlen = uri.length(); | ||||
for( int pos = 0; pos < maxlen; pos++ ) | |||||
int pos = 0; | |||||
for( ; pos < maxlen && uri.charAt( pos ) != '!'; pos++ ) | |||||
{ | |||||
} | |||||
// Extract the name | |||||
String prefix = uri.substring( 0, pos ); | |||||
if( pos < maxlen ) | |||||
{ | |||||
uri.delete( 0, pos + 1 ); | |||||
} | |||||
else | |||||
{ | { | ||||
if( uri.charAt( pos ) == '!' ) | |||||
{ | |||||
String prefix = uri.substring( 0, pos ); | |||||
uri.delete( 0, pos + 1 ); | |||||
return prefix; | |||||
} | |||||
uri.setLength( 0 ); | |||||
} | } | ||||
// Assume the URI is the Jar file name | |||||
String prefix = uri.toString(); | |||||
uri.setLength( 0 ); | |||||
return prefix; | |||||
// Decode the name | |||||
return decode( prefix ); | |||||
} | } | ||||
} | } |
@@ -8,6 +8,8 @@ | |||||
package org.apache.aut.vfs.provider.zip; | package org.apache.aut.vfs.provider.zip; | ||||
import java.io.File; | import java.io.File; | ||||
import java.io.IOException; | |||||
import org.apache.aut.vfs.FileObject; | |||||
import org.apache.aut.vfs.FileSystemException; | import org.apache.aut.vfs.FileSystemException; | ||||
import org.apache.aut.vfs.provider.AbstractFileSystemProvider; | import org.apache.aut.vfs.provider.AbstractFileSystemProvider; | ||||
import org.apache.aut.vfs.provider.DefaultFileName; | import org.apache.aut.vfs.provider.DefaultFileName; | ||||
@@ -20,31 +22,83 @@ import org.apache.aut.vfs.provider.ParsedUri; | |||||
* systems, for local Zip files only. | * systems, for local Zip files only. | ||||
* | * | ||||
* @author Adam Murdoch | * @author Adam Murdoch | ||||
* | |||||
* @ant:type type="file-system" name="zip" | |||||
*/ | */ | ||||
public class ZipFileSystemProvider extends AbstractFileSystemProvider | |||||
public class ZipFileSystemProvider | |||||
extends AbstractFileSystemProvider | |||||
implements FileSystemProvider | implements FileSystemProvider | ||||
{ | { | ||||
private ZipFileNameParser m_parser = new ZipFileNameParser(); | |||||
private final ZipFileNameParser m_parser = new ZipFileNameParser(); | |||||
/** | /** | ||||
* Parses a URI into its components. | * Parses a URI into its components. | ||||
*/ | */ | ||||
protected ParsedUri parseURI( String uri ) throws FileSystemException | |||||
protected ParsedUri parseUri( final FileObject baseFile, | |||||
final String uriStr ) | |||||
throws FileSystemException | |||||
{ | { | ||||
return m_parser.parseUri( uri ); | |||||
// Parse the URI | |||||
final ParsedZipUri uri = m_parser.parseZipUri( uriStr ); | |||||
// Make the URI canonical | |||||
// Resolve the Zip file name | |||||
final String fileName = uri.getZipFileName(); | |||||
final FileObject file = getContext().resolveFile( baseFile, fileName ); | |||||
uri.setZipFile( file ); | |||||
// Rebuild the root URI | |||||
final String rootUri = m_parser.buildRootUri( uri ); | |||||
uri.setRootUri( rootUri ); | |||||
return uri; | |||||
} | |||||
/** | |||||
* Builds the URI for the root of a layered file system. | |||||
*/ | |||||
protected ParsedUri buildUri( final String scheme, | |||||
final FileObject file ) | |||||
throws FileSystemException | |||||
{ | |||||
ParsedZipUri uri = new ParsedZipUri(); | |||||
uri.setScheme( scheme ); | |||||
uri.setZipFile( file ); | |||||
final String rootUri = m_parser.buildRootUri( uri ); | |||||
uri.setRootUri( rootUri ); | |||||
uri.setPath( "/" ); | |||||
return uri; | |||||
} | } | ||||
/** | /** | ||||
* Creates the filesystem. | * Creates the filesystem. | ||||
*/ | */ | ||||
protected FileSystem createFileSystem( ParsedUri uri ) throws FileSystemException | |||||
protected FileSystem createFileSystem( final 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 ); | |||||
final ParsedZipUri zipUri = (ParsedZipUri)uri; | |||||
final FileObject file = zipUri.getZipFile(); | |||||
// TODO - temporary hack; need to use a converter | |||||
File destFile = null; | |||||
try | |||||
{ | |||||
final File cacheDir = new File( "ant_vfs_cache" ); | |||||
cacheDir.mkdirs(); | |||||
destFile = File.createTempFile( "cache_", "_" + file.getName().getBaseName(), cacheDir ); | |||||
destFile.deleteOnExit(); | |||||
} | |||||
catch( IOException e ) | |||||
{ | |||||
throw new FileSystemException( "Could not replicate file", e ); | |||||
} | |||||
FileObject destFileObj = getContext().resolveFile( null, destFile.getAbsolutePath() ); | |||||
destFileObj.copy( file ); | |||||
// Create the file system | |||||
DefaultFileName name = new DefaultFileName( m_parser, zipUri.getRootUri(), "/" ); | |||||
return new ZipFileSystem( name, destFile ); | |||||
} | } | ||||
} | } |
@@ -1,2 +1,3 @@ | |||||
missing-home-dir.error=Cannot locate antRun scripts: Property 'myrmidon.home' not specified | missing-home-dir.error=Cannot locate antRun scripts: Property 'myrmidon.home' not specified | ||||
create-vfs-manager.error=Could not create the VFS manager. | |||||
create-vfs-manager.error=Could not create the VFS manager. | |||||
create-provider.error=Could not create file system provider "{0}". |
@@ -0,0 +1,108 @@ | |||||
/* | |||||
* 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.myrmidon.framework.factories; | |||||
import org.apache.aut.vfs.impl.DefaultFileSystemManager; | |||||
import org.apache.aut.vfs.provider.FileSystemProvider; | |||||
import org.apache.aut.vfs.FileSystemException; | |||||
import org.apache.avalon.framework.activity.Disposable; | |||||
import org.apache.avalon.framework.activity.Initializable; | |||||
import org.apache.avalon.framework.service.Serviceable; | |||||
import org.apache.avalon.framework.service.ServiceManager; | |||||
import org.apache.avalon.framework.service.ServiceException; | |||||
import org.apache.avalon.excalibur.i18n.ResourceManager; | |||||
import org.apache.avalon.excalibur.i18n.Resources; | |||||
import org.apache.myrmidon.interfaces.type.TypeManager; | |||||
import org.apache.myrmidon.interfaces.type.TypeFactory; | |||||
import org.apache.myrmidon.interfaces.type.TypeException; | |||||
/** | |||||
* The myrmidon FileSystemManager implementation. | |||||
* | |||||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||||
* @version $Revision$ $Date$ | |||||
*/ | |||||
public class VfsManager | |||||
extends DefaultFileSystemManager | |||||
implements Serviceable, Initializable, Disposable | |||||
{ | |||||
private final static Resources REZ | |||||
= ResourceManager.getPackageResources( VfsManager.class ); | |||||
private TypeFactory m_typeFactory; | |||||
/** | |||||
* Locate the services used by this service. | |||||
*/ | |||||
public void service( final ServiceManager serviceManager ) throws ServiceException | |||||
{ | |||||
final TypeManager typeManager = (TypeManager)serviceManager.lookup( TypeManager.ROLE ); | |||||
try | |||||
{ | |||||
m_typeFactory = typeManager.getFactory( FileSystemProvider.class ); | |||||
} | |||||
catch( TypeException e ) | |||||
{ | |||||
throw new ServiceException( e.getMessage(), e ); | |||||
} | |||||
} | |||||
/** | |||||
* Initialises this service. | |||||
*/ | |||||
public void initialize() throws Exception | |||||
{ | |||||
// TODO - make this list configurable | |||||
// Required providers | |||||
addProvider( new String[] { "zip", "jar" }, "zip", false ); | |||||
// Optional providers | |||||
addProvider( new String[] { "smb" }, "smb", true ); | |||||
addProvider( new String[] { "ftp" }, "ftp", true ); | |||||
} | |||||
/** | |||||
* Disposes this service. | |||||
*/ | |||||
public void dispose() | |||||
{ | |||||
// Clean-up | |||||
close(); | |||||
} | |||||
/** | |||||
* Registers a file system provider. | |||||
*/ | |||||
public void addProvider( final String[] urlSchemes, | |||||
final String providerName, | |||||
final boolean ignoreIfNotPresent ) | |||||
throws FileSystemException | |||||
{ | |||||
// Create an instance | |||||
if( ignoreIfNotPresent && ! m_typeFactory.canCreate( providerName ) ) | |||||
{ | |||||
return; | |||||
} | |||||
final FileSystemProvider provider; | |||||
try | |||||
{ | |||||
provider = (FileSystemProvider)m_typeFactory.create( providerName ); | |||||
} | |||||
catch( Exception e ) | |||||
{ | |||||
final String message = REZ.getString( "create-provider.error", providerName ); | |||||
throw new FileSystemException( message, e ); | |||||
} | |||||
// Register the provider | |||||
addProvider( urlSchemes, provider ); | |||||
} | |||||
} |
@@ -8,7 +8,6 @@ | |||||
package org.apache.myrmidon.framework.factories; | package org.apache.myrmidon.framework.factories; | ||||
import org.apache.aut.vfs.FileSystemManager; | import org.apache.aut.vfs.FileSystemManager; | ||||
import org.apache.aut.vfs.impl.DefaultFileSystemManager; | |||||
import org.apache.avalon.excalibur.i18n.ResourceManager; | import org.apache.avalon.excalibur.i18n.ResourceManager; | ||||
import org.apache.avalon.excalibur.i18n.Resources; | import org.apache.avalon.excalibur.i18n.Resources; | ||||
import org.apache.myrmidon.interfaces.service.AntServiceException; | import org.apache.myrmidon.interfaces.service.AntServiceException; | ||||
@@ -34,7 +33,7 @@ public class VfsManagerFactory | |||||
{ | { | ||||
try | try | ||||
{ | { | ||||
return new DefaultFileSystemManager(); | |||||
return new VfsManager(); | |||||
} | } | ||||
catch( Exception e ) | catch( Exception e ) | ||||
{ | { | ||||
@@ -1,9 +1,4 @@ | |||||
<services version="1.0"> | <services version="1.0"> | ||||
<exec-manager factory="org.apache.myrmidon.framework.factories.ExecManagerFactory"/> | <exec-manager factory="org.apache.myrmidon.framework.factories.ExecManagerFactory"/> | ||||
<file-system-manager factory="org.apache.myrmidon.framework.factories.VfsManagerFactory"> | |||||
<provider scheme="zip" classname="org.apache.aut.vfs.provider.zip.ZipFileSystemProvider"/> | |||||
<provider scheme="jar" classname="org.apache.aut.vfs.provider.zip.ZipFileSystemProvider"/> | |||||
<provider scheme="smb" classname="org.apache.aut.vfs.provider.smb.SmbFileSystemProvider"/> | |||||
<provider scheme="ftp" classname="org.apache.aut.vfs.provider.ftp.FtpFileSystemProvider"/> | |||||
</file-system-manager> | |||||
<file-system-manager factory="org.apache.myrmidon.framework.factories.VfsManagerFactory"/> | |||||
</services> | </services> |
@@ -66,9 +66,9 @@ public abstract class AbstractFileSystemTest | |||||
} | } | ||||
/** | /** | ||||
* Returns the URI for the base folder. | |||||
* Returns the base folder to run the tests against. | |||||
*/ | */ | ||||
protected abstract String getBaseFolderURI() throws Exception; | |||||
protected abstract FileObject getBaseFolder() throws Exception; | |||||
/** | /** | ||||
* Sets up the test | * Sets up the test | ||||
@@ -79,7 +79,10 @@ public abstract class AbstractFileSystemTest | |||||
m_manager = new DefaultFileSystemManager(); | m_manager = new DefaultFileSystemManager(); | ||||
// Locate the base folder | // Locate the base folder | ||||
m_baseFolder = m_manager.resolveFile( getBaseFolderURI() ); | |||||
m_baseFolder = getBaseFolder(); | |||||
// Make some assumptions absout the name | |||||
assertTrue( ! m_baseFolder.getName().getPath().equals( "/" ) ); | |||||
// Build the expected content of "file1.txt" | // Build the expected content of "file1.txt" | ||||
final String eol = System.getProperty( "line.separator" ); | final String eol = System.getProperty( "line.separator" ); | ||||
@@ -123,6 +126,66 @@ public abstract class AbstractFileSystemTest | |||||
assertSame( "file object", m_baseFolder.getParent(), file ); | assertSame( "file object", m_baseFolder.getParent(), file ); | ||||
} | } | ||||
/** | |||||
* Tests encoding of relative URI. | |||||
*/ | |||||
public void testRelativeUriEncoding() throws Exception | |||||
{ | |||||
// Build base dir | |||||
m_manager.setBaseFile( m_baseFolder ); | |||||
final String path = m_baseFolder.getName().getPath(); | |||||
// Encode "some file" | |||||
FileObject file = m_manager.resolveFile( "%73%6f%6d%65%20%66%69%6c%65" ); | |||||
assertEquals( path + "/some file", file.getName().getPath() ); | |||||
// Encode "." | |||||
file = m_manager.resolveFile( "%2e" ); | |||||
assertEquals( path, file.getName().getPath() ); | |||||
// Encode '%' | |||||
file = m_manager.resolveFile( "a%25" ); | |||||
assertEquals( path + "/a%", file.getName().getPath() ); | |||||
// Encode / | |||||
file = m_manager.resolveFile( "dir%2fchild" ); | |||||
assertEquals( path + "/dir/child", file.getName().getPath() ); | |||||
// Encode \ | |||||
file = m_manager.resolveFile( "dir%5cchild" ); | |||||
assertEquals( path + "/dir/child", file.getName().getPath() ); | |||||
// Use "%" literal | |||||
try | |||||
{ | |||||
m_manager.resolveFile( "%" ); | |||||
fail(); | |||||
} | |||||
catch( FileSystemException e ) | |||||
{ | |||||
} | |||||
// Not enough digits in encoded char | |||||
try | |||||
{ | |||||
m_manager.resolveFile( "%5" ); | |||||
fail(); | |||||
} | |||||
catch( FileSystemException e ) | |||||
{ | |||||
} | |||||
// Invalid digit in encoded char | |||||
try | |||||
{ | |||||
m_manager.resolveFile( "%q" ); | |||||
fail(); | |||||
} | |||||
catch( FileSystemException e ) | |||||
{ | |||||
} | |||||
} | |||||
/** | /** | ||||
* Tests the root file name. | * Tests the root file name. | ||||
*/ | */ | ||||
@@ -176,7 +239,7 @@ public abstract class AbstractFileSystemTest | |||||
final NameScope scope ) | final NameScope scope ) | ||||
throws Exception | throws Exception | ||||
{ | { | ||||
// Make some assumptions about the name explicit | |||||
// Make some assumptions about the name | |||||
assertTrue( !name.getPath().equals( "/" ) ); | assertTrue( !name.getPath().equals( "/" ) ); | ||||
assertTrue( !name.getPath().endsWith( "/a" ) ); | assertTrue( !name.getPath().endsWith( "/a" ) ); | ||||
assertTrue( !name.getPath().endsWith( "/a/b" ) ); | assertTrue( !name.getPath().endsWith( "/a/b" ) ); | ||||
@@ -329,6 +392,46 @@ public abstract class AbstractFileSystemTest | |||||
checkDescendentNames( baseName, NameScope.DESCENDENT ); | checkDescendentNames( baseName, NameScope.DESCENDENT ); | ||||
} | } | ||||
/** | |||||
* Tests resolution of absolute names. | |||||
*/ | |||||
public void testAbsoluteNames() throws Exception | |||||
{ | |||||
// Test against the base folder | |||||
FileName name = m_baseFolder.getName(); | |||||
checkAbsoluteNames( name ); | |||||
// Test against the root | |||||
name = m_baseFolder.getRoot().getName(); | |||||
checkAbsoluteNames( name ); | |||||
// Test against some unknown file | |||||
name = name.resolveName( "a/b/unknown" ); | |||||
checkAbsoluteNames( name ); | |||||
} | |||||
/** | |||||
* Tests resolution of absolute names. | |||||
*/ | |||||
private void checkAbsoluteNames( final FileName name ) throws Exception | |||||
{ | |||||
// Root | |||||
assertSameName( "/", name, "/" ); | |||||
assertSameName( "/", name, "//" ); | |||||
assertSameName( "/", name, "/." ); | |||||
assertSameName( "/", name, "/some file/.." ); | |||||
// Some absolute names | |||||
assertSameName( "/a", name, "/a" ); | |||||
assertSameName( "/a", name, "/./a" ); | |||||
assertSameName( "/a", name, "/a/." ); | |||||
assertSameName( "/a/b", name, "/a/b" ); | |||||
// Some bad names | |||||
assertBadName( name, "/..", NameScope.FILE_SYSTEM ); | |||||
assertBadName( name, "/a/../..", NameScope.FILE_SYSTEM ); | |||||
} | |||||
/** | /** | ||||
* Asserts that a particular relative name is invalid for a particular | * Asserts that a particular relative name is invalid for a particular | ||||
* scope. | * scope. | ||||
@@ -340,7 +443,7 @@ public abstract class AbstractFileSystemTest | |||||
try | try | ||||
{ | { | ||||
name.resolveName( relName, scope ); | name.resolveName( relName, scope ); | ||||
fail(); | |||||
fail( "expected failure" ); | |||||
} | } | ||||
catch( FileSystemException e ) | catch( FileSystemException e ) | ||||
{ | { | ||||
@@ -16,7 +16,8 @@ import java.util.Set; | |||||
* | * | ||||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | ||||
*/ | */ | ||||
public abstract class AbstractWritableFileSystemTest extends AbstractFileSystemTest | |||||
public abstract class AbstractWritableFileSystemTest | |||||
extends AbstractFileSystemTest | |||||
{ | { | ||||
public AbstractWritableFileSystemTest( String name ) | public AbstractWritableFileSystemTest( String name ) | ||||
{ | { | ||||
@@ -26,14 +27,14 @@ public abstract class AbstractWritableFileSystemTest extends AbstractFileSystemT | |||||
/** | /** | ||||
* Returns the URI for the area to do tests in. | * Returns the URI for the area to do tests in. | ||||
*/ | */ | ||||
protected abstract String getWriteFolderURI() throws Exception; | |||||
protected abstract FileObject getWriteFolder() throws Exception; | |||||
/** | /** | ||||
* Sets up a scratch folder for the test to use. | * Sets up a scratch folder for the test to use. | ||||
*/ | */ | ||||
protected FileObject createScratchFolder() throws Exception | protected FileObject createScratchFolder() throws Exception | ||||
{ | { | ||||
FileObject scratchFolder = m_manager.resolveFile( getWriteFolderURI() ); | |||||
FileObject scratchFolder = getWriteFolder(); | |||||
// Make sure the test folder is empty | // Make sure the test folder is empty | ||||
scratchFolder.delete(); | scratchFolder.delete(); | ||||
@@ -7,12 +7,15 @@ | |||||
*/ | */ | ||||
package org.apache.aut.vfs; | package org.apache.aut.vfs; | ||||
import org.apache.aut.vfs.provider.ftp.FtpFileSystemProvider; | |||||
/** | /** | ||||
* Tests for FTP file systems. | * Tests for FTP file systems. | ||||
* | * | ||||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | ||||
*/ | */ | ||||
public class FtpFileSystemTest extends AbstractWritableFileSystemTest | |||||
public class FtpFileSystemTest | |||||
extends AbstractWritableFileSystemTest | |||||
{ | { | ||||
public FtpFileSystemTest( String name ) | public FtpFileSystemTest( String name ) | ||||
{ | { | ||||
@@ -22,16 +25,19 @@ public class FtpFileSystemTest extends AbstractWritableFileSystemTest | |||||
/** | /** | ||||
* Returns the URI for the base folder. | * Returns the URI for the base folder. | ||||
*/ | */ | ||||
protected String getBaseFolderURI() | |||||
protected FileObject getBaseFolder() throws Exception | |||||
{ | { | ||||
return System.getProperty( "test.ftp.uri" ) + "/read-tests"; | |||||
final String uri = System.getProperty( "test.ftp.uri" ) + "/read-tests"; | |||||
m_manager.addProvider( "ftp", new FtpFileSystemProvider() ); | |||||
return m_manager.resolveFile( uri ); | |||||
} | } | ||||
/** | /** | ||||
* Returns the URI for the area to do tests in. | * Returns the URI for the area to do tests in. | ||||
*/ | */ | ||||
protected String getWriteFolderURI() | |||||
protected FileObject getWriteFolder() throws Exception | |||||
{ | { | ||||
return System.getProperty( "test.ftp.uri" ) + "/write-tests"; | |||||
final String uri = System.getProperty( "test.ftp.uri" ) + "/write-tests"; | |||||
return m_manager.resolveFile( uri ); | |||||
} | } | ||||
} | } |
@@ -24,21 +24,19 @@ public class LocalFileSystemTest extends AbstractWritableFileSystemTest | |||||
/** | /** | ||||
* Returns the URI for the base folder. | * Returns the URI for the base folder. | ||||
*/ | */ | ||||
protected String getBaseFolderURI() | |||||
throws Exception | |||||
protected FileObject getBaseFolder() throws Exception | |||||
{ | { | ||||
final File testDir = getTestResource( "basedir" ); | final File testDir = getTestResource( "basedir" ); | ||||
return testDir.toURL().toString(); | |||||
return m_manager.convert( testDir ); | |||||
} | } | ||||
/** | /** | ||||
* Returns the URI for the area to do tests in. | * Returns the URI for the area to do tests in. | ||||
*/ | */ | ||||
protected String getWriteFolderURI() | |||||
throws Exception | |||||
protected FileObject getWriteFolder() throws Exception | |||||
{ | { | ||||
final File testDir = getTestResource( "write-tests" ); | final File testDir = getTestResource( "write-tests" ); | ||||
return testDir.toURL().toString(); | |||||
return m_manager.convert( testDir ); | |||||
} | } | ||||
/** | /** | ||||
@@ -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.aut.vfs.provider.zip.ZipFileSystemProvider; | |||||
/** | |||||
* Tests for the Zip file system, using a zip file nested inside another zip file. | |||||
* | |||||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||||
*/ | |||||
public class NestedZipFileSystemTest | |||||
extends AbstractReadOnlyFileSystemTest | |||||
{ | |||||
public NestedZipFileSystemTest( String name ) | |||||
{ | |||||
super( name ); | |||||
} | |||||
/** | |||||
* Returns the URI for the base folder. | |||||
*/ | |||||
protected FileObject getBaseFolder() throws Exception | |||||
{ | |||||
m_manager.addProvider( "zip", new ZipFileSystemProvider() ); | |||||
// Locate the base Zip file | |||||
final String zipFilePath = getTestResource( "nested.zip" ).getAbsolutePath(); | |||||
String uri = "zip:" + zipFilePath + "!/test.zip"; | |||||
final FileObject zipFile = m_manager.resolveFile( uri ); | |||||
// Now build the nested file system | |||||
final FileObject nestedFS = m_manager.createFileSystem( "zip", zipFile ); | |||||
return nestedFS.resolveFile( "/basedir" ); | |||||
} | |||||
} |
@@ -7,6 +7,8 @@ | |||||
*/ | */ | ||||
package org.apache.aut.vfs; | package org.apache.aut.vfs; | ||||
import org.apache.aut.vfs.provider.smb.SmbFileSystemProvider; | |||||
/** | /** | ||||
* Tests for the SMB file system. | * Tests for the SMB file system. | ||||
* | * | ||||
@@ -22,16 +24,19 @@ public class SmbFileSystemTest extends AbstractWritableFileSystemTest | |||||
/** | /** | ||||
* Returns the URI for the base folder. | * Returns the URI for the base folder. | ||||
*/ | */ | ||||
protected String getBaseFolderURI() | |||||
protected FileObject getBaseFolder() throws Exception | |||||
{ | { | ||||
return System.getProperty( "test.smb.uri" ) + "/read-tests"; | |||||
final String uri = System.getProperty( "test.smb.uri" ) + "/read-tests"; | |||||
m_manager.addProvider( "smb", new SmbFileSystemProvider() ); | |||||
return m_manager.resolveFile( uri ); | |||||
} | } | ||||
/** | /** | ||||
* Returns the URI for the area to do tests in. | * Returns the URI for the area to do tests in. | ||||
*/ | */ | ||||
protected String getWriteFolderURI() | |||||
protected FileObject getWriteFolder() throws Exception | |||||
{ | { | ||||
return System.getProperty( "test.smb.uri" ) + "/write-tests"; | |||||
final String uri = System.getProperty( "test.smb.uri" ) + "/write-tests"; | |||||
return m_manager.resolveFile( uri ); | |||||
} | } | ||||
} | } |
@@ -8,6 +8,7 @@ | |||||
package org.apache.aut.vfs; | package org.apache.aut.vfs; | ||||
import java.io.File; | import java.io.File; | ||||
import org.apache.aut.vfs.provider.zip.ZipFileSystemProvider; | |||||
/** | /** | ||||
* Tests for the Zip file system. | * Tests for the Zip file system. | ||||
@@ -24,10 +25,11 @@ public class ZipFileSystemTest extends AbstractReadOnlyFileSystemTest | |||||
/** | /** | ||||
* Returns the URI for the base folder. | * Returns the URI for the base folder. | ||||
*/ | */ | ||||
protected String getBaseFolderURI() | |||||
protected FileObject getBaseFolder() throws Exception | |||||
{ | { | ||||
File zipFile = getTestResource( "test.zip" ); | File zipFile = getTestResource( "test.zip" ); | ||||
String uri = "zip:" + zipFile + "!basedir"; | |||||
return uri; | |||||
String uri = "zip:" + zipFile.getAbsolutePath() + "!basedir"; | |||||
m_manager.addProvider( "zip", new ZipFileSystemProvider() ); | |||||
return m_manager.resolveFile( uri ); | |||||
} | } | ||||
} | } |
@@ -66,9 +66,9 @@ public abstract class AbstractFileSystemTest | |||||
} | } | ||||
/** | /** | ||||
* Returns the URI for the base folder. | |||||
* Returns the base folder to run the tests against. | |||||
*/ | */ | ||||
protected abstract String getBaseFolderURI() throws Exception; | |||||
protected abstract FileObject getBaseFolder() throws Exception; | |||||
/** | /** | ||||
* Sets up the test | * Sets up the test | ||||
@@ -79,7 +79,10 @@ public abstract class AbstractFileSystemTest | |||||
m_manager = new DefaultFileSystemManager(); | m_manager = new DefaultFileSystemManager(); | ||||
// Locate the base folder | // Locate the base folder | ||||
m_baseFolder = m_manager.resolveFile( getBaseFolderURI() ); | |||||
m_baseFolder = getBaseFolder(); | |||||
// Make some assumptions absout the name | |||||
assertTrue( ! m_baseFolder.getName().getPath().equals( "/" ) ); | |||||
// Build the expected content of "file1.txt" | // Build the expected content of "file1.txt" | ||||
final String eol = System.getProperty( "line.separator" ); | final String eol = System.getProperty( "line.separator" ); | ||||
@@ -123,6 +126,66 @@ public abstract class AbstractFileSystemTest | |||||
assertSame( "file object", m_baseFolder.getParent(), file ); | assertSame( "file object", m_baseFolder.getParent(), file ); | ||||
} | } | ||||
/** | |||||
* Tests encoding of relative URI. | |||||
*/ | |||||
public void testRelativeUriEncoding() throws Exception | |||||
{ | |||||
// Build base dir | |||||
m_manager.setBaseFile( m_baseFolder ); | |||||
final String path = m_baseFolder.getName().getPath(); | |||||
// Encode "some file" | |||||
FileObject file = m_manager.resolveFile( "%73%6f%6d%65%20%66%69%6c%65" ); | |||||
assertEquals( path + "/some file", file.getName().getPath() ); | |||||
// Encode "." | |||||
file = m_manager.resolveFile( "%2e" ); | |||||
assertEquals( path, file.getName().getPath() ); | |||||
// Encode '%' | |||||
file = m_manager.resolveFile( "a%25" ); | |||||
assertEquals( path + "/a%", file.getName().getPath() ); | |||||
// Encode / | |||||
file = m_manager.resolveFile( "dir%2fchild" ); | |||||
assertEquals( path + "/dir/child", file.getName().getPath() ); | |||||
// Encode \ | |||||
file = m_manager.resolveFile( "dir%5cchild" ); | |||||
assertEquals( path + "/dir/child", file.getName().getPath() ); | |||||
// Use "%" literal | |||||
try | |||||
{ | |||||
m_manager.resolveFile( "%" ); | |||||
fail(); | |||||
} | |||||
catch( FileSystemException e ) | |||||
{ | |||||
} | |||||
// Not enough digits in encoded char | |||||
try | |||||
{ | |||||
m_manager.resolveFile( "%5" ); | |||||
fail(); | |||||
} | |||||
catch( FileSystemException e ) | |||||
{ | |||||
} | |||||
// Invalid digit in encoded char | |||||
try | |||||
{ | |||||
m_manager.resolveFile( "%q" ); | |||||
fail(); | |||||
} | |||||
catch( FileSystemException e ) | |||||
{ | |||||
} | |||||
} | |||||
/** | /** | ||||
* Tests the root file name. | * Tests the root file name. | ||||
*/ | */ | ||||
@@ -176,7 +239,7 @@ public abstract class AbstractFileSystemTest | |||||
final NameScope scope ) | final NameScope scope ) | ||||
throws Exception | throws Exception | ||||
{ | { | ||||
// Make some assumptions about the name explicit | |||||
// Make some assumptions about the name | |||||
assertTrue( !name.getPath().equals( "/" ) ); | assertTrue( !name.getPath().equals( "/" ) ); | ||||
assertTrue( !name.getPath().endsWith( "/a" ) ); | assertTrue( !name.getPath().endsWith( "/a" ) ); | ||||
assertTrue( !name.getPath().endsWith( "/a/b" ) ); | assertTrue( !name.getPath().endsWith( "/a/b" ) ); | ||||
@@ -329,6 +392,46 @@ public abstract class AbstractFileSystemTest | |||||
checkDescendentNames( baseName, NameScope.DESCENDENT ); | checkDescendentNames( baseName, NameScope.DESCENDENT ); | ||||
} | } | ||||
/** | |||||
* Tests resolution of absolute names. | |||||
*/ | |||||
public void testAbsoluteNames() throws Exception | |||||
{ | |||||
// Test against the base folder | |||||
FileName name = m_baseFolder.getName(); | |||||
checkAbsoluteNames( name ); | |||||
// Test against the root | |||||
name = m_baseFolder.getRoot().getName(); | |||||
checkAbsoluteNames( name ); | |||||
// Test against some unknown file | |||||
name = name.resolveName( "a/b/unknown" ); | |||||
checkAbsoluteNames( name ); | |||||
} | |||||
/** | |||||
* Tests resolution of absolute names. | |||||
*/ | |||||
private void checkAbsoluteNames( final FileName name ) throws Exception | |||||
{ | |||||
// Root | |||||
assertSameName( "/", name, "/" ); | |||||
assertSameName( "/", name, "//" ); | |||||
assertSameName( "/", name, "/." ); | |||||
assertSameName( "/", name, "/some file/.." ); | |||||
// Some absolute names | |||||
assertSameName( "/a", name, "/a" ); | |||||
assertSameName( "/a", name, "/./a" ); | |||||
assertSameName( "/a", name, "/a/." ); | |||||
assertSameName( "/a/b", name, "/a/b" ); | |||||
// Some bad names | |||||
assertBadName( name, "/..", NameScope.FILE_SYSTEM ); | |||||
assertBadName( name, "/a/../..", NameScope.FILE_SYSTEM ); | |||||
} | |||||
/** | /** | ||||
* Asserts that a particular relative name is invalid for a particular | * Asserts that a particular relative name is invalid for a particular | ||||
* scope. | * scope. | ||||
@@ -340,7 +443,7 @@ public abstract class AbstractFileSystemTest | |||||
try | try | ||||
{ | { | ||||
name.resolveName( relName, scope ); | name.resolveName( relName, scope ); | ||||
fail(); | |||||
fail( "expected failure" ); | |||||
} | } | ||||
catch( FileSystemException e ) | catch( FileSystemException e ) | ||||
{ | { | ||||
@@ -16,7 +16,8 @@ import java.util.Set; | |||||
* | * | ||||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | ||||
*/ | */ | ||||
public abstract class AbstractWritableFileSystemTest extends AbstractFileSystemTest | |||||
public abstract class AbstractWritableFileSystemTest | |||||
extends AbstractFileSystemTest | |||||
{ | { | ||||
public AbstractWritableFileSystemTest( String name ) | public AbstractWritableFileSystemTest( String name ) | ||||
{ | { | ||||
@@ -26,14 +27,14 @@ public abstract class AbstractWritableFileSystemTest extends AbstractFileSystemT | |||||
/** | /** | ||||
* Returns the URI for the area to do tests in. | * Returns the URI for the area to do tests in. | ||||
*/ | */ | ||||
protected abstract String getWriteFolderURI() throws Exception; | |||||
protected abstract FileObject getWriteFolder() throws Exception; | |||||
/** | /** | ||||
* Sets up a scratch folder for the test to use. | * Sets up a scratch folder for the test to use. | ||||
*/ | */ | ||||
protected FileObject createScratchFolder() throws Exception | protected FileObject createScratchFolder() throws Exception | ||||
{ | { | ||||
FileObject scratchFolder = m_manager.resolveFile( getWriteFolderURI() ); | |||||
FileObject scratchFolder = getWriteFolder(); | |||||
// Make sure the test folder is empty | // Make sure the test folder is empty | ||||
scratchFolder.delete(); | scratchFolder.delete(); | ||||
@@ -7,12 +7,15 @@ | |||||
*/ | */ | ||||
package org.apache.aut.vfs; | package org.apache.aut.vfs; | ||||
import org.apache.aut.vfs.provider.ftp.FtpFileSystemProvider; | |||||
/** | /** | ||||
* Tests for FTP file systems. | * Tests for FTP file systems. | ||||
* | * | ||||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | * @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | ||||
*/ | */ | ||||
public class FtpFileSystemTest extends AbstractWritableFileSystemTest | |||||
public class FtpFileSystemTest | |||||
extends AbstractWritableFileSystemTest | |||||
{ | { | ||||
public FtpFileSystemTest( String name ) | public FtpFileSystemTest( String name ) | ||||
{ | { | ||||
@@ -22,16 +25,19 @@ public class FtpFileSystemTest extends AbstractWritableFileSystemTest | |||||
/** | /** | ||||
* Returns the URI for the base folder. | * Returns the URI for the base folder. | ||||
*/ | */ | ||||
protected String getBaseFolderURI() | |||||
protected FileObject getBaseFolder() throws Exception | |||||
{ | { | ||||
return System.getProperty( "test.ftp.uri" ) + "/read-tests"; | |||||
final String uri = System.getProperty( "test.ftp.uri" ) + "/read-tests"; | |||||
m_manager.addProvider( "ftp", new FtpFileSystemProvider() ); | |||||
return m_manager.resolveFile( uri ); | |||||
} | } | ||||
/** | /** | ||||
* Returns the URI for the area to do tests in. | * Returns the URI for the area to do tests in. | ||||
*/ | */ | ||||
protected String getWriteFolderURI() | |||||
protected FileObject getWriteFolder() throws Exception | |||||
{ | { | ||||
return System.getProperty( "test.ftp.uri" ) + "/write-tests"; | |||||
final String uri = System.getProperty( "test.ftp.uri" ) + "/write-tests"; | |||||
return m_manager.resolveFile( uri ); | |||||
} | } | ||||
} | } |
@@ -24,21 +24,19 @@ public class LocalFileSystemTest extends AbstractWritableFileSystemTest | |||||
/** | /** | ||||
* Returns the URI for the base folder. | * Returns the URI for the base folder. | ||||
*/ | */ | ||||
protected String getBaseFolderURI() | |||||
throws Exception | |||||
protected FileObject getBaseFolder() throws Exception | |||||
{ | { | ||||
final File testDir = getTestResource( "basedir" ); | final File testDir = getTestResource( "basedir" ); | ||||
return testDir.toURL().toString(); | |||||
return m_manager.convert( testDir ); | |||||
} | } | ||||
/** | /** | ||||
* Returns the URI for the area to do tests in. | * Returns the URI for the area to do tests in. | ||||
*/ | */ | ||||
protected String getWriteFolderURI() | |||||
throws Exception | |||||
protected FileObject getWriteFolder() throws Exception | |||||
{ | { | ||||
final File testDir = getTestResource( "write-tests" ); | final File testDir = getTestResource( "write-tests" ); | ||||
return testDir.toURL().toString(); | |||||
return m_manager.convert( testDir ); | |||||
} | } | ||||
/** | /** | ||||
@@ -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.aut.vfs.provider.zip.ZipFileSystemProvider; | |||||
/** | |||||
* Tests for the Zip file system, using a zip file nested inside another zip file. | |||||
* | |||||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||||
*/ | |||||
public class NestedZipFileSystemTest | |||||
extends AbstractReadOnlyFileSystemTest | |||||
{ | |||||
public NestedZipFileSystemTest( String name ) | |||||
{ | |||||
super( name ); | |||||
} | |||||
/** | |||||
* Returns the URI for the base folder. | |||||
*/ | |||||
protected FileObject getBaseFolder() throws Exception | |||||
{ | |||||
m_manager.addProvider( "zip", new ZipFileSystemProvider() ); | |||||
// Locate the base Zip file | |||||
final String zipFilePath = getTestResource( "nested.zip" ).getAbsolutePath(); | |||||
String uri = "zip:" + zipFilePath + "!/test.zip"; | |||||
final FileObject zipFile = m_manager.resolveFile( uri ); | |||||
// Now build the nested file system | |||||
final FileObject nestedFS = m_manager.createFileSystem( "zip", zipFile ); | |||||
return nestedFS.resolveFile( "/basedir" ); | |||||
} | |||||
} |
@@ -7,6 +7,8 @@ | |||||
*/ | */ | ||||
package org.apache.aut.vfs; | package org.apache.aut.vfs; | ||||
import org.apache.aut.vfs.provider.smb.SmbFileSystemProvider; | |||||
/** | /** | ||||
* Tests for the SMB file system. | * Tests for the SMB file system. | ||||
* | * | ||||
@@ -22,16 +24,19 @@ public class SmbFileSystemTest extends AbstractWritableFileSystemTest | |||||
/** | /** | ||||
* Returns the URI for the base folder. | * Returns the URI for the base folder. | ||||
*/ | */ | ||||
protected String getBaseFolderURI() | |||||
protected FileObject getBaseFolder() throws Exception | |||||
{ | { | ||||
return System.getProperty( "test.smb.uri" ) + "/read-tests"; | |||||
final String uri = System.getProperty( "test.smb.uri" ) + "/read-tests"; | |||||
m_manager.addProvider( "smb", new SmbFileSystemProvider() ); | |||||
return m_manager.resolveFile( uri ); | |||||
} | } | ||||
/** | /** | ||||
* Returns the URI for the area to do tests in. | * Returns the URI for the area to do tests in. | ||||
*/ | */ | ||||
protected String getWriteFolderURI() | |||||
protected FileObject getWriteFolder() throws Exception | |||||
{ | { | ||||
return System.getProperty( "test.smb.uri" ) + "/write-tests"; | |||||
final String uri = System.getProperty( "test.smb.uri" ) + "/write-tests"; | |||||
return m_manager.resolveFile( uri ); | |||||
} | } | ||||
} | } |
@@ -8,6 +8,7 @@ | |||||
package org.apache.aut.vfs; | package org.apache.aut.vfs; | ||||
import java.io.File; | import java.io.File; | ||||
import org.apache.aut.vfs.provider.zip.ZipFileSystemProvider; | |||||
/** | /** | ||||
* Tests for the Zip file system. | * Tests for the Zip file system. | ||||
@@ -24,10 +25,11 @@ public class ZipFileSystemTest extends AbstractReadOnlyFileSystemTest | |||||
/** | /** | ||||
* Returns the URI for the base folder. | * Returns the URI for the base folder. | ||||
*/ | */ | ||||
protected String getBaseFolderURI() | |||||
protected FileObject getBaseFolder() throws Exception | |||||
{ | { | ||||
File zipFile = getTestResource( "test.zip" ); | File zipFile = getTestResource( "test.zip" ); | ||||
String uri = "zip:" + zipFile + "!basedir"; | |||||
return uri; | |||||
String uri = "zip:" + zipFile.getAbsolutePath() + "!basedir"; | |||||
m_manager.addProvider( "zip", new ZipFileSystemProvider() ); | |||||
return m_manager.resolveFile( uri ); | |||||
} | } | ||||
} | } |
@@ -37,15 +37,22 @@ | |||||
<p>The VFS needs plenty of work:</p> | <p>The VFS needs plenty of work:</p> | ||||
<ul> | <ul> | ||||
<li>Move and copy files/folders.</li> | |||||
<li>Move files/folders.</li> | |||||
<li>Recursive folders copy.</li> | |||||
<li>Search through a file hierarchy, using Ant-style wildcards.</li> | <li>Search through a file hierarchy, using Ant-style wildcards.</li> | ||||
<li>Search through a file hierarchy, using a Selector interface.</li> | <li>Search through a file hierarchy, using a Selector interface.</li> | ||||
<li>The in-memory caching mechanism is pretty rudimentary at this stage. | <li>The in-memory caching mechanism is pretty rudimentary at this stage. | ||||
It needs work to make it size capped. In addition, some mechanism needs | It needs work to make it size capped. In addition, some mechanism needs | ||||
to be provided to release and refresh cached info. | to be provided to release and refresh cached info. | ||||
</li> | </li> | ||||
<li>Convert files/folders into local files, for handing off | |||||
to external commands, or legacy tasks.</li> | |||||
<li>Refactor the replication mechanism out of ZipFileSystemProvder, | |||||
and make more general pluggable.</li> | |||||
<li>Capabilities discovery.</li> | <li>Capabilities discovery.</li> | ||||
<li>Attributes and attribute schema.</li> | <li>Attributes and attribute schema.</li> | ||||
<li>Handle file canonicalisation better (for cases like case-insensitive | |||||
file systems, symbolic links, name encoding, etc).</li> | |||||
<li>File system layering. That is, the ability for a file system to | <li>File system layering. That is, the ability for a file system to | ||||
sit on top of another file system, or a file from another file system | sit on top of another file system, or a file from another file system | ||||
(e.g. Zip/Jar/Tar file systems, gzip/encoding file systems, virtual file | (e.g. Zip/Jar/Tar file systems, gzip/encoding file systems, virtual file | ||||
@@ -192,10 +199,6 @@ | |||||
<ul> | <ul> | ||||
<li>Search through the code for 'TODO' items and fix them.</li> | <li>Search through the code for 'TODO' items and fix them.</li> | ||||
<li>Tidy-up CLIMain so that it calls System.exit() with a non-zero exit code, | |||||
if the build fails.</li> | |||||
<li>Tidy-up the 'build failed' message, so that the stack trace is only | |||||
printed out if the log level is verbose/debug.</li> | |||||
<li>Allow service factories to be configured from the contents of the | <li>Allow service factories to be configured from the contents of the | ||||
<code>ant-services.xml</code> descriptor.</li> | <code>ant-services.xml</code> descriptor.</li> | ||||
<li>Route external process stdout and stderr through the logger.</li> | <li>Route external process stdout and stderr through the logger.</li> | ||||
@@ -206,11 +209,10 @@ | |||||
<li>Fire ProjectListener events projectStarted() and projectFinished() | <li>Fire ProjectListener events projectStarted() and projectFinished() | ||||
events on start and finish of referenced projects, adding indicator methods | events on start and finish of referenced projects, adding indicator methods | ||||
to ProjectEvent.</li> | to ProjectEvent.</li> | ||||
<li>Convert PropertyUtil to a non-static PropertyResolver service.</li> | |||||
<li>Validate project and target names in DefaultProjectBuilder - reject dodgy | <li>Validate project and target names in DefaultProjectBuilder - reject dodgy | ||||
names like "," or "", or " ". Probably want to exclude names that start or | |||||
names like "," or "", or " ". Probably want to reject names that start or | |||||
end with white-space (though internal whitespace is probably fine). We also | end with white-space (though internal whitespace is probably fine). We also | ||||
want to reserve certain punctuation characters like . , : ? [ ] { }, etc for | |||||
want to reserve certain punctuation characters like , : ? $ [ ] { } < >, etc for | |||||
future use.</li> | future use.</li> | ||||
<li>Similarly, validate property names, using the same rules.</li> | <li>Similarly, validate property names, using the same rules.</li> | ||||
<li>Detect duplicate type names.</li> | <li>Detect duplicate type names.</li> | ||||
@@ -222,6 +224,7 @@ | |||||
an antlib.</li> | an antlib.</li> | ||||
<li>Split up <code><is-set></code> condition into is-set and is-true conditions.</li> | <li>Split up <code><is-set></code> condition into is-set and is-true conditions.</li> | ||||
<li>Allow the <code><if></code> task to take any condition implementation.</li> | <li>Allow the <code><if></code> task to take any condition implementation.</li> | ||||
<li>Add an else block to the <code><if></code> task.</li> | |||||
<li>Unit tests.</li> | <li>Unit tests.</li> | ||||
</ul> | </ul> | ||||
@@ -32,7 +32,8 @@ files are found in the <code>lib</code> directory:</p> | |||||
<tr> | <tr> | ||||
<td>SMB VFS support (Samba, Windows shares)</td> | <td>SMB VFS support (Samba, Windows shares)</td> | ||||
<td>jcifs.jar</td> | <td>jcifs.jar</td> | ||||
<td><a href="http://jcifs.samba.org">jcifs.samba.org</a></td> | |||||
<td><a href="http://jcifs.samba.org">jcifs.samba.org</a>. | |||||
<p>Note: there are problems using the 0.6.1 release. Try 0.6.0 instead.</p></td> | |||||
</tr> | </tr> | ||||
<tr> | <tr> | ||||
<td>FTP VFS support</td> | <td>FTP VFS support</td> | ||||