* 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"> | |||
<fileset dir="${test.vfs.dir}" includes="basedir/**"/> | |||
</zip> | |||
<zip zipfile="${test.vfs.dir}/nested.zip"> | |||
<fileset dir="${test.vfs.dir}" includes="test.zip"/> | |||
</zip> | |||
<!-- Prepare deployer tests --> | |||
<property name="test.deployer.dir" | |||
@@ -117,15 +117,22 @@ | |||
<blockquote> | |||
<p>The VFS needs plenty of work:</p> | |||
<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 a Selector interface.</li> | |||
<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 | |||
to be provided to release and refresh cached info. | |||
</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>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 | |||
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 | |||
@@ -300,10 +307,6 @@ | |||
<p>A completely unordered list of items, big and small:</p> | |||
<ul> | |||
<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 | |||
<code>ant-services.xml</code> descriptor.</li> | |||
<li>Route external process stdout and stderr through the logger.</li> | |||
@@ -314,11 +317,10 @@ | |||
<li>Fire ProjectListener events projectStarted() and projectFinished() | |||
events on start and finish of referenced projects, adding indicator methods | |||
to ProjectEvent.</li> | |||
<li>Convert PropertyUtil to a non-static PropertyResolver service.</li> | |||
<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 | |||
want to reserve certain punctuation characters like . , : ? [ ] { }, etc for | |||
want to reserve certain punctuation characters like , : ? $ [ ] { } < >, etc for | |||
future use.</li> | |||
<li>Similarly, validate property names, using the same rules.</li> | |||
<li>Detect duplicate type names.</li> | |||
@@ -330,6 +332,7 @@ | |||
an antlib.</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>Add an else block to the <code><if></code> task.</li> | |||
<li>Unit tests.</li> | |||
</ul> | |||
</blockquote> | |||
@@ -125,7 +125,8 @@ files are found in the <code>lib</code> directory:</p> | |||
<td bgcolor="#a0ddf0" colspan="" rowspan="" | |||
valign="top" align="left"> | |||
<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> | |||
</td> | |||
</tr> | |||
@@ -1,7 +1,7 @@ | |||
<project version="2.0"> | |||
<target name="copy"> | |||
<v-fileset id="src-files" dir="src"/> | |||
<v-copy todir="dest"> | |||
<v-copy destdir="dest"> | |||
<v-fileset-ref id="src-files"/> | |||
</v-copy> | |||
</target> | |||
@@ -7,8 +7,6 @@ | |||
*/ | |||
package org.apache.antlib.vfile; | |||
import java.io.InputStream; | |||
import java.io.OutputStream; | |||
import java.util.ArrayList; | |||
import java.util.Iterator; | |||
import org.apache.aut.vfs.FileObject; | |||
@@ -41,7 +39,7 @@ public class CopyFilesTask | |||
/** | |||
* Sets the source file. | |||
*/ | |||
public void setFile( final FileObject file ) | |||
public void setSrcfile( final FileObject file ) | |||
{ | |||
m_srcFile = file; | |||
} | |||
@@ -49,7 +47,7 @@ public class CopyFilesTask | |||
/** | |||
* Sets the destination file. | |||
*/ | |||
public void setTofile( final FileObject file ) | |||
public void setDestfile( final FileObject file ) | |||
{ | |||
m_destFile = file; | |||
} | |||
@@ -57,11 +55,19 @@ public class CopyFilesTask | |||
/** | |||
* Sets the destination directory. | |||
*/ | |||
public void setTodir( final FileObject file ) | |||
public void setDestdir( final FileObject file ) | |||
{ | |||
m_destDir = file; | |||
} | |||
/** | |||
* Sets the source directory. | |||
*/ | |||
public void setSrcdir( final FileObject dir ) | |||
{ | |||
add( new DefaultFileSet( dir ) ); | |||
} | |||
/** | |||
* Adds a source file set. | |||
*/ | |||
@@ -107,7 +113,8 @@ public class CopyFilesTask | |||
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 | |||
@@ -134,7 +141,8 @@ public class CopyFilesTask | |||
final FileObject destFile = m_destDir.resolveFile( path, NameScope.DESCENDENT ); | |||
// 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 final AndFileSelector m_selector = new AndFileSelector(); | |||
public DefaultFileSet() | |||
{ | |||
} | |||
public DefaultFileSet( final FileObject dir ) | |||
{ | |||
m_dir = dir; | |||
} | |||
/** | |||
* Sets the root directory. | |||
*/ | |||
@@ -7,6 +7,7 @@ | |||
*/ | |||
package org.apache.antlib.vfile; | |||
import java.util.ArrayList; | |||
import org.apache.aut.vfs.FileObject; | |||
import org.apache.myrmidon.api.AbstractTask; | |||
import org.apache.myrmidon.api.TaskException; | |||
@@ -22,11 +23,11 @@ import org.apache.myrmidon.api.TaskException; | |||
public class ListFileSetTask | |||
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() | |||
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-destination.error=No destination file or 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. |
@@ -181,6 +181,20 @@ public interface FileObject | |||
*/ | |||
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 | |||
* 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 | |||
* 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 | |||
* This file's content. | |||
* | |||
@@ -71,7 +71,8 @@ public interface FileSystemManager | |||
* @throws FileSystemException | |||
* 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 | |||
@@ -90,7 +91,8 @@ public interface FileSystemManager | |||
* @throws FileSystemException | |||
* 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)} | |||
@@ -106,5 +108,39 @@ public interface FileSystemManager | |||
* 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.util.HashMap; | |||
import java.util.Iterator; | |||
import java.util.Map; | |||
import org.apache.aut.vfs.FileObject; | |||
import org.apache.aut.vfs.FileSystemException; | |||
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.FileSystemProviderContext; | |||
import org.apache.aut.vfs.provider.UriParser; | |||
import org.apache.aut.vfs.provider.local.LocalFileSystemProvider; | |||
import org.apache.avalon.excalibur.i18n.ResourceManager; | |||
@@ -25,7 +22,8 @@ import org.apache.avalon.excalibur.i18n.Resources; | |||
/** | |||
* 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 | |||
implements FileSystemManager | |||
@@ -40,69 +38,55 @@ public class DefaultFileSystemManager | |||
private final Map m_providers = new HashMap(); | |||
/** 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. */ | |||
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 | |||
m_localFileProvider = new LocalFileSystemProvider(); | |||
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 ) | |||
throws FileSystemException | |||
{ | |||
final FileObject baseFileObj = m_localFileProvider.findFileByLocalName( baseFile ); | |||
final FileObject baseFileObj = m_localFileProvider.findLocalFile( baseFile ); | |||
return resolveFile( baseFileObj, uri ); | |||
} | |||
@@ -170,14 +154,17 @@ public class DefaultFileSystemManager | |||
final FileSystemProvider provider = (FileSystemProvider)m_providers.get( scheme ); | |||
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 | |||
if( m_localFileProvider.isAbsoluteLocalName( uri ) ) | |||
if( m_localFileProvider.isAbsoluteLocalName( decodedUri ) ) | |||
{ | |||
return m_localFileProvider.findLocalFile( uri ); | |||
return m_localFileProvider.findLocalFile( decodedUri ); | |||
} | |||
// Assume a bad scheme | |||
@@ -193,32 +180,31 @@ public class DefaultFileSystemManager | |||
final String message = REZ.getString( "find-rel-file.error", uri ); | |||
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 | |||
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. | |||
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.avalon.excalibur.i18n.ResourceManager; | |||
import org.apache.avalon.excalibur.i18n.Resources; | |||
import org.apache.avalon.excalibur.io.IOUtil; | |||
/** | |||
* A partial file object implementation. | |||
@@ -349,7 +350,7 @@ public abstract class AbstractFileObject | |||
} | |||
// Update cached info | |||
updateType( null ); | |||
updateType(); | |||
} | |||
/** | |||
@@ -463,7 +464,41 @@ public abstract class AbstractFileObject | |||
} | |||
// 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 | |||
{ | |||
updateType( FileType.FILE ); | |||
updateType(); | |||
doEndOutput(); | |||
} | |||
/** | |||
* 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 | |||
notifyParent(); | |||
@@ -23,13 +23,21 @@ public abstract class AbstractFileSystemProvider | |||
private final static Resources REZ = | |||
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 | |||
* before any of the other provider methods. | |||
*/ | |||
public void setContext( FileSystemProviderContext context ) | |||
public void setContext( final FileSystemProviderContext context ) | |||
{ | |||
m_context = context; | |||
} | |||
@@ -40,13 +48,14 @@ public abstract class AbstractFileSystemProvider | |||
* @param uri | |||
* 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 | |||
ParsedUri parsedURI = null; | |||
ParsedUri parsedUri = null; | |||
try | |||
{ | |||
parsedURI = parseURI( uri ); | |||
parsedUri = parseUri( baseFile, uri ); | |||
} | |||
catch( FileSystemException exc ) | |||
{ | |||
@@ -54,31 +63,70 @@ public abstract class AbstractFileSystemProvider | |||
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 | |||
FileSystem fs = m_context.getFileSystem( parsedURI.getRootURI() ); | |||
final String rootUri = parsedUri.getRootUri(); | |||
FileSystem fs = m_context.getFileSystem( rootUri ); | |||
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 | |||
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 | |||
* 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 | |||
* 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. | |||
*/ | |||
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. | |||
* | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
* | |||
* @ant:role shorthand="file-system" | |||
*/ | |||
public interface FileSystemProvider | |||
{ | |||
@@ -24,8 +29,16 @@ public interface FileSystemProvider | |||
/** | |||
* 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 | |||
* 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; | |||
import org.apache.aut.vfs.FileObject; | |||
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 | |||
* 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 | |||
{ | |||
/** | |||
* 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. | |||
*/ | |||
@@ -34,13 +34,13 @@ public class ParsedUri | |||
} | |||
/** Returns the root URI, used to identify the file system. */ | |||
public String getRootURI() | |||
public String getRootUri() | |||
{ | |||
return m_rootURI; | |||
} | |||
/** Sets the root URI. */ | |||
public void setRootURI( String rootPrefix ) | |||
public void setRootUri( String 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-in-use.error=Could not write to "{0}" because it is already in use. | |||
write.error=Could not write to "{0}". | |||
copy-file.error=Could not copy "{0}" to "{1}". | |||
# DefaultFileContent | |||
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 | |||
invalid-absolute-uri.error=Invalid absolute URI "{0}". | |||
not-layered-fs.error=File system is not a layered file system. | |||
# UriParser | |||
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}". | |||
invalid-childname.error=Invalid file base-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 | |||
* implementation assumes a "generic URI", as defined by RFC 2396. See | |||
* {@link #parseGenericUri} for more info. | |||
* | |||
* <p>Sub-classes should override this method. | |||
*/ | |||
public ParsedUri parseUri( 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 | |||
extractToPath( uriStr, name, uri ); | |||
// Normalise the file name | |||
// Decode and normalise the file name | |||
decode( name, 0, name.length() ); | |||
normalisePath( name ); | |||
uri.setPath( name.toString() ); | |||
@@ -128,7 +156,7 @@ public class UriParser | |||
rootUri.append( uri.getScheme() ); | |||
rootUri.append( "://" ); | |||
rootUri.append( uri.getHostName() ); | |||
uri.setRootURI( rootUri.toString() ); | |||
uri.setRootUri( rootUri.toString() ); | |||
} | |||
/** | |||
@@ -404,9 +432,9 @@ public class UriParser | |||
if( scope == NameScope.CHILD ) | |||
{ | |||
final int baseLen = baseFile.length(); | |||
if( ! resolvedPath.startsWith( baseFile ) | |||
if( !resolvedPath.startsWith( baseFile ) | |||
|| resolvedPath.length() == baseLen | |||
|| resolvedPath.charAt( baseLen ) != m_separatorChar | |||
|| ( baseLen > 1 && resolvedPath.charAt( baseLen ) != m_separatorChar ) | |||
|| resolvedPath.indexOf( m_separatorChar, baseLen + 1 ) != -1 ) | |||
{ | |||
final String message = REZ.getString( "invalid-childname.error", path ); | |||
@@ -416,9 +444,9 @@ public class UriParser | |||
else if( scope == NameScope.DESCENDENT ) | |||
{ | |||
final int baseLen = baseFile.length(); | |||
if( ! resolvedPath.startsWith( baseFile ) | |||
if( !resolvedPath.startsWith( baseFile ) | |||
|| 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 ); | |||
throw new FileSystemException( message ); | |||
@@ -463,7 +491,8 @@ public class UriParser | |||
* <li>Removes trailing separator. | |||
* </ul> | |||
*/ | |||
public void normalisePath( final StringBuffer path ) throws FileSystemException | |||
public void normalisePath( final StringBuffer path ) | |||
throws FileSystemException | |||
{ | |||
if( path.length() == 0 ) | |||
{ | |||
@@ -515,14 +544,20 @@ public class UriParser | |||
path.charAt( startElem + 1 ) == '.' ) | |||
{ | |||
// 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 ); | |||
maxlen = path.length(); | |||
continue; | |||
@@ -595,7 +630,8 @@ public class UriParser | |||
* @return | |||
* 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 ) | |||
{ | |||
@@ -642,4 +678,103 @@ public class UriParser | |||
// No scheme in URI | |||
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; | |||
import org.apache.aut.vfs.FileSystemException; | |||
import org.apache.aut.vfs.provider.ParsedUri; | |||
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. | |||
*/ | |||
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) | |||
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 | |||
String userInfo = uri.getUserInfo(); | |||
final String userInfo = uri.getUserInfo(); | |||
if( userInfo != null ) | |||
{ | |||
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; | |||
} | |||
} |
@@ -8,39 +8,44 @@ | |||
package org.apache.aut.vfs.provider.ftp; | |||
import org.apache.aut.vfs.FileName; | |||
import org.apache.aut.vfs.FileObject; | |||
import org.apache.aut.vfs.FileSystemException; | |||
import org.apache.aut.vfs.provider.AbstractFileSystemProvider; | |||
import org.apache.aut.vfs.provider.DefaultFileName; | |||
import org.apache.aut.vfs.provider.FileSystem; | |||
import org.apache.aut.vfs.provider.ParsedUri; | |||
import org.apache.aut.vfs.provider.UriParser; | |||
/** | |||
* A provider for FTP file systems. | |||
* | |||
* @author Adam Murdoch | |||
* | |||
* @ant:type type="file-system" name="ftp" | |||
*/ | |||
public class FtpFileSystemProvider extends AbstractFileSystemProvider | |||
{ | |||
private UriParser m_parser = new FtpFileNameParser(); | |||
private final FtpFileNameParser m_parser = new FtpFileNameParser(); | |||
/** | |||
* 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. | |||
*/ | |||
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 | |||
FileName rootName = new DefaultFileName( m_parser, ftpUri.getRootURI(), "/" ); | |||
final FileName rootName = new DefaultFileName( m_parser, ftpUri.getRootUri(), "/" ); | |||
// Determine the username and password to use | |||
String username = ftpUri.getUserName(); | |||
@@ -50,21 +50,22 @@ abstract class LocalFileNameParser | |||
* | |||
* @param uriStr The URI. | |||
*/ | |||
public ParsedUri parseUri( final String uriStr ) | |||
public ParsedUri parseFileUri( final String uriStr ) | |||
throws FileSystemException | |||
{ | |||
StringBuffer name = new StringBuffer(); | |||
ParsedFileUri uri = new ParsedFileUri(); | |||
final StringBuffer name = new StringBuffer(); | |||
final ParsedFileUri uri = new ParsedFileUri(); | |||
// Extract the scheme | |||
String scheme = extractScheme( uriStr, name ); | |||
final String scheme = extractScheme( uriStr, name ); | |||
uri.setScheme( scheme ); | |||
// Adjust the separators | |||
// Remove encoding, and adjust the separators | |||
decode( name, 0, name.length() ); | |||
fixSeparators( name ); | |||
// Extract the root prefix | |||
String rootFile = extractRootPrefix( uriStr, name ); | |||
final String rootFile = extractRootPrefix( uriStr, name ); | |||
uri.setRootFile( rootFile ); | |||
// Normalise the path | |||
@@ -72,11 +73,11 @@ abstract class LocalFileNameParser | |||
uri.setPath( name.toString() ); | |||
// Build the root URI | |||
StringBuffer rootUri = new StringBuffer(); | |||
final StringBuffer rootUri = new StringBuffer(); | |||
rootUri.append( scheme ); | |||
rootUri.append( "://" ); | |||
rootUri.append( rootFile ); | |||
uri.setRootURI( rootUri.toString() ); | |||
uri.setRootUri( rootUri.toString() ); | |||
return uri; | |||
} | |||
@@ -51,20 +51,22 @@ public class LocalFileSystemProvider extends AbstractFileSystemProvider | |||
/** | |||
* 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, | |||
// and then straight back again | |||
return findFile( "file:" + name ); | |||
return findFile( null, "file:" + name ); | |||
} | |||
/** | |||
* 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 | |||
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 | |||
* 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. | |||
*/ | |||
protected FileSystem createFileSystem( final ParsedUri uri ) throws FileSystemException | |||
protected FileSystem createFileSystem( final ParsedUri uri ) | |||
throws FileSystemException | |||
{ | |||
// Build the name of the root file. | |||
final ParsedFileUri fileUri = (ParsedFileUri)uri; | |||
final String rootFile = fileUri.getRootFile(); | |||
// 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 ); | |||
} | |||
} |
@@ -27,19 +27,27 @@ public class SmbFileNameParser | |||
/** | |||
* 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 | |||
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 ); | |||
// Extract the share | |||
String share = extractFirstElement( name ); | |||
final String share = extractFirstElement( name ); | |||
if( share == null ) | |||
{ | |||
final String message = REZ.getString( "missing-share-name.error", uriStr ); | |||
@@ -47,23 +55,18 @@ public class SmbFileNameParser | |||
} | |||
uri.setShare( share ); | |||
// Normalise the path | |||
normalisePath( name ); | |||
// Set the path | |||
uri.setPath( name.toString() ); | |||
// Set the root URI | |||
StringBuffer rootUri = new StringBuffer(); | |||
rootUri.append( uri.getScheme() ); | |||
rootUri.append( "://" ); | |||
String userInfo = uri.getUserInfo(); | |||
if( userInfo != null ) | |||
{ | |||
rootUri.append( userInfo ); | |||
rootUri.append( '@' ); | |||
} | |||
rootUri.append( uri.getHostName() ); | |||
appendRootUri( uri, rootUri ); | |||
rootUri.append( '/' ); | |||
rootUri.append( share ); | |||
uri.setRootURI( rootUri.toString() ); | |||
uri.setRootUri( rootUri.toString() ); | |||
return uri; | |||
} | |||
@@ -8,6 +8,7 @@ | |||
package org.apache.aut.vfs.provider.smb; | |||
import org.apache.aut.vfs.FileName; | |||
import org.apache.aut.vfs.FileObject; | |||
import org.apache.aut.vfs.FileSystemException; | |||
import org.apache.aut.vfs.provider.AbstractFileSystemProvider; | |||
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. | |||
* | |||
* @author Adam Murdoch | |||
* | |||
* @ant:type type="file-system" name="smb" | |||
*/ | |||
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. | |||
*/ | |||
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. | |||
*/ | |||
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 ); | |||
} | |||
} |
@@ -7,6 +7,7 @@ | |||
*/ | |||
package org.apache.aut.vfs.provider.zip; | |||
import org.apache.aut.vfs.FileObject; | |||
import org.apache.aut.vfs.provider.ParsedUri; | |||
/** | |||
@@ -16,14 +17,25 @@ import org.apache.aut.vfs.provider.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; | |||
} | |||
public void setZipFile( String zipFile ) | |||
public void setZipFile( final FileObject zipFile ) | |||
{ | |||
m_zipFile = zipFile; | |||
} | |||
@@ -8,7 +8,6 @@ | |||
package org.apache.aut.vfs.provider.zip; | |||
import org.apache.aut.vfs.FileSystemException; | |||
import org.apache.aut.vfs.provider.ParsedUri; | |||
import org.apache.aut.vfs.provider.UriParser; | |||
/** | |||
@@ -16,66 +15,77 @@ import org.apache.aut.vfs.provider.UriParser; | |||
* | |||
* @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. | |||
* | |||
* @param name | |||
* @param uriStr | |||
* 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 | |||
String scheme = extractScheme( uriStr, name ); | |||
final String scheme = extractScheme( uriStr, name ); | |||
uri.setScheme( scheme ); | |||
// Extract the Zip file name | |||
String zipName = extractZipName( name ); | |||
uri.setZipFile( zipName ); | |||
// Adjust the separators | |||
fixSeparators( name ); | |||
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 ); | |||
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( zipName ); | |||
appendEncoded( rootUri, uri.getZipFile().getName().getURI(), ZIP_URL_RESERVED_CHARS ); | |||
rootUri.append( "!" ); | |||
uri.setRootURI( rootUri.toString() ); | |||
return uri; | |||
return rootUri.toString(); | |||
} | |||
/** | |||
* 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> | |||
// TODO - how does '!' in the file name get escaped? | |||
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; | |||
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.provider.AbstractFileSystemProvider; | |||
import org.apache.aut.vfs.provider.DefaultFileName; | |||
@@ -20,31 +22,83 @@ import org.apache.aut.vfs.provider.ParsedUri; | |||
* systems, for local Zip files only. | |||
* | |||
* @author Adam Murdoch | |||
* | |||
* @ant:type type="file-system" name="zip" | |||
*/ | |||
public class ZipFileSystemProvider extends AbstractFileSystemProvider | |||
public class ZipFileSystemProvider | |||
extends AbstractFileSystemProvider | |||
implements FileSystemProvider | |||
{ | |||
private ZipFileNameParser m_parser = new ZipFileNameParser(); | |||
private final ZipFileNameParser m_parser = new ZipFileNameParser(); | |||
/** | |||
* 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. | |||
*/ | |||
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 | |||
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; | |||
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.Resources; | |||
import org.apache.myrmidon.interfaces.service.AntServiceException; | |||
@@ -34,7 +33,7 @@ public class VfsManagerFactory | |||
{ | |||
try | |||
{ | |||
return new DefaultFileSystemManager(); | |||
return new VfsManager(); | |||
} | |||
catch( Exception e ) | |||
{ | |||
@@ -1,9 +1,4 @@ | |||
<services version="1.0"> | |||
<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> |
@@ -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 | |||
@@ -79,7 +79,10 @@ public abstract class AbstractFileSystemTest | |||
m_manager = new DefaultFileSystemManager(); | |||
// 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" | |||
final String eol = System.getProperty( "line.separator" ); | |||
@@ -123,6 +126,66 @@ public abstract class AbstractFileSystemTest | |||
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. | |||
*/ | |||
@@ -176,7 +239,7 @@ public abstract class AbstractFileSystemTest | |||
final NameScope scope ) | |||
throws Exception | |||
{ | |||
// Make some assumptions about the name explicit | |||
// Make some assumptions about the name | |||
assertTrue( !name.getPath().equals( "/" ) ); | |||
assertTrue( !name.getPath().endsWith( "/a" ) ); | |||
assertTrue( !name.getPath().endsWith( "/a/b" ) ); | |||
@@ -329,6 +392,46 @@ public abstract class AbstractFileSystemTest | |||
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 | |||
* scope. | |||
@@ -340,7 +443,7 @@ public abstract class AbstractFileSystemTest | |||
try | |||
{ | |||
name.resolveName( relName, scope ); | |||
fail(); | |||
fail( "expected failure" ); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
@@ -16,7 +16,8 @@ import java.util.Set; | |||
* | |||
* @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 ) | |||
{ | |||
@@ -26,14 +27,14 @@ public abstract class AbstractWritableFileSystemTest extends AbstractFileSystemT | |||
/** | |||
* 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. | |||
*/ | |||
protected FileObject createScratchFolder() throws Exception | |||
{ | |||
FileObject scratchFolder = m_manager.resolveFile( getWriteFolderURI() ); | |||
FileObject scratchFolder = getWriteFolder(); | |||
// Make sure the test folder is empty | |||
scratchFolder.delete(); | |||
@@ -7,12 +7,15 @@ | |||
*/ | |||
package org.apache.aut.vfs; | |||
import org.apache.aut.vfs.provider.ftp.FtpFileSystemProvider; | |||
/** | |||
* Tests for FTP file systems. | |||
* | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
*/ | |||
public class FtpFileSystemTest extends AbstractWritableFileSystemTest | |||
public class FtpFileSystemTest | |||
extends AbstractWritableFileSystemTest | |||
{ | |||
public FtpFileSystemTest( String name ) | |||
{ | |||
@@ -22,16 +25,19 @@ public class FtpFileSystemTest extends AbstractWritableFileSystemTest | |||
/** | |||
* 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. | |||
*/ | |||
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. | |||
*/ | |||
protected String getBaseFolderURI() | |||
throws Exception | |||
protected FileObject getBaseFolder() throws Exception | |||
{ | |||
final File testDir = getTestResource( "basedir" ); | |||
return testDir.toURL().toString(); | |||
return m_manager.convert( testDir ); | |||
} | |||
/** | |||
* 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" ); | |||
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; | |||
import org.apache.aut.vfs.provider.smb.SmbFileSystemProvider; | |||
/** | |||
* Tests for the SMB file system. | |||
* | |||
@@ -22,16 +24,19 @@ public class SmbFileSystemTest extends AbstractWritableFileSystemTest | |||
/** | |||
* 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. | |||
*/ | |||
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; | |||
import java.io.File; | |||
import org.apache.aut.vfs.provider.zip.ZipFileSystemProvider; | |||
/** | |||
* Tests for the Zip file system. | |||
@@ -24,10 +25,11 @@ public class ZipFileSystemTest extends AbstractReadOnlyFileSystemTest | |||
/** | |||
* Returns the URI for the base folder. | |||
*/ | |||
protected String getBaseFolderURI() | |||
protected FileObject getBaseFolder() throws Exception | |||
{ | |||
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 | |||
@@ -79,7 +79,10 @@ public abstract class AbstractFileSystemTest | |||
m_manager = new DefaultFileSystemManager(); | |||
// 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" | |||
final String eol = System.getProperty( "line.separator" ); | |||
@@ -123,6 +126,66 @@ public abstract class AbstractFileSystemTest | |||
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. | |||
*/ | |||
@@ -176,7 +239,7 @@ public abstract class AbstractFileSystemTest | |||
final NameScope scope ) | |||
throws Exception | |||
{ | |||
// Make some assumptions about the name explicit | |||
// Make some assumptions about the name | |||
assertTrue( !name.getPath().equals( "/" ) ); | |||
assertTrue( !name.getPath().endsWith( "/a" ) ); | |||
assertTrue( !name.getPath().endsWith( "/a/b" ) ); | |||
@@ -329,6 +392,46 @@ public abstract class AbstractFileSystemTest | |||
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 | |||
* scope. | |||
@@ -340,7 +443,7 @@ public abstract class AbstractFileSystemTest | |||
try | |||
{ | |||
name.resolveName( relName, scope ); | |||
fail(); | |||
fail( "expected failure" ); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
@@ -16,7 +16,8 @@ import java.util.Set; | |||
* | |||
* @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 ) | |||
{ | |||
@@ -26,14 +27,14 @@ public abstract class AbstractWritableFileSystemTest extends AbstractFileSystemT | |||
/** | |||
* 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. | |||
*/ | |||
protected FileObject createScratchFolder() throws Exception | |||
{ | |||
FileObject scratchFolder = m_manager.resolveFile( getWriteFolderURI() ); | |||
FileObject scratchFolder = getWriteFolder(); | |||
// Make sure the test folder is empty | |||
scratchFolder.delete(); | |||
@@ -7,12 +7,15 @@ | |||
*/ | |||
package org.apache.aut.vfs; | |||
import org.apache.aut.vfs.provider.ftp.FtpFileSystemProvider; | |||
/** | |||
* Tests for FTP file systems. | |||
* | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
*/ | |||
public class FtpFileSystemTest extends AbstractWritableFileSystemTest | |||
public class FtpFileSystemTest | |||
extends AbstractWritableFileSystemTest | |||
{ | |||
public FtpFileSystemTest( String name ) | |||
{ | |||
@@ -22,16 +25,19 @@ public class FtpFileSystemTest extends AbstractWritableFileSystemTest | |||
/** | |||
* 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. | |||
*/ | |||
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. | |||
*/ | |||
protected String getBaseFolderURI() | |||
throws Exception | |||
protected FileObject getBaseFolder() throws Exception | |||
{ | |||
final File testDir = getTestResource( "basedir" ); | |||
return testDir.toURL().toString(); | |||
return m_manager.convert( testDir ); | |||
} | |||
/** | |||
* 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" ); | |||
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; | |||
import org.apache.aut.vfs.provider.smb.SmbFileSystemProvider; | |||
/** | |||
* Tests for the SMB file system. | |||
* | |||
@@ -22,16 +24,19 @@ public class SmbFileSystemTest extends AbstractWritableFileSystemTest | |||
/** | |||
* 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. | |||
*/ | |||
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; | |||
import java.io.File; | |||
import org.apache.aut.vfs.provider.zip.ZipFileSystemProvider; | |||
/** | |||
* Tests for the Zip file system. | |||
@@ -24,10 +25,11 @@ public class ZipFileSystemTest extends AbstractReadOnlyFileSystemTest | |||
/** | |||
* Returns the URI for the base folder. | |||
*/ | |||
protected String getBaseFolderURI() | |||
protected FileObject getBaseFolder() throws Exception | |||
{ | |||
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> | |||
<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 a Selector interface.</li> | |||
<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 | |||
to be provided to release and refresh cached info. | |||
</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>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 | |||
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 | |||
@@ -192,10 +199,6 @@ | |||
<ul> | |||
<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 | |||
<code>ant-services.xml</code> descriptor.</li> | |||
<li>Route external process stdout and stderr through the logger.</li> | |||
@@ -206,11 +209,10 @@ | |||
<li>Fire ProjectListener events projectStarted() and projectFinished() | |||
events on start and finish of referenced projects, adding indicator methods | |||
to ProjectEvent.</li> | |||
<li>Convert PropertyUtil to a non-static PropertyResolver service.</li> | |||
<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 | |||
want to reserve certain punctuation characters like . , : ? [ ] { }, etc for | |||
want to reserve certain punctuation characters like , : ? $ [ ] { } < >, etc for | |||
future use.</li> | |||
<li>Similarly, validate property names, using the same rules.</li> | |||
<li>Detect duplicate type names.</li> | |||
@@ -222,6 +224,7 @@ | |||
an antlib.</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>Add an else block to the <code><if></code> task.</li> | |||
<li>Unit tests.</li> | |||
</ul> | |||
@@ -32,7 +32,8 @@ files are found in the <code>lib</code> directory:</p> | |||
<tr> | |||
<td>SMB VFS support (Samba, Windows shares)</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> | |||
<td>FTP VFS support</td> | |||