* Added FileSelector, which allows files to be chosen when doing recursive operations. Added a couple of implementations. Added a selector parameter to FileObject.delete() and copy(). * Added FileObject.replicateFile(), which converts a FileObject into a local File. * Moved replication code out of the Zip provider, into a shared FileReplicator component. The implemenation is pretty brain-dead, but at very least should properly clean up temporary files in ant_vfs_cache. Also, local files will no longer be replicated, but used directly. * Added FileName.getRelativeName(), and NameScope.DESCENDENT_OR_SELF. * Now handles providers which are LogEnabled and Disposable. * Made the local file provider pluggable. * Providers are now responsible for thier own caching. * FTP and Zip providers clean up properly. Fixed FTP directory delete. git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@272262 13f79535-47bb-0310-9956-ffa450edef68master
@@ -682,7 +682,7 @@ Legal: | |||
<!-- Pass config to the tests --> | |||
<sysproperty key="test.basedir" value="${test.working.dir}"/> | |||
<sysproperty key="test.smb.uri" value="smb://${vfs.user}:${vfs.password}@${vfs.host}/${vfs.user}/vfs"/> | |||
<sysproperty key="test.ftp.uri" value="ftp://${vfs.user}:${vfs.password}@${vfs.host}/home/${vfs.user}/vfs"/> | |||
<sysproperty key="test.ftp.uri" value="ftp://${vfs.user}:${vfs.password}@${vfs.host}/vfs"/> | |||
<batchtest> | |||
<fileset dir="${test.classes}"> | |||
@@ -13,6 +13,7 @@ import org.apache.aut.vfs.FileObject; | |||
import org.apache.aut.vfs.FileSystemException; | |||
import org.apache.aut.vfs.FileType; | |||
import org.apache.aut.vfs.NameScope; | |||
import org.apache.aut.vfs.FileConstants; | |||
import org.apache.avalon.excalibur.i18n.ResourceManager; | |||
import org.apache.avalon.excalibur.i18n.Resources; | |||
import org.apache.myrmidon.api.AbstractTask; | |||
@@ -114,7 +115,7 @@ public class CopyFilesTask | |||
} | |||
getContext().verbose( "copy " + m_srcFile + " to " + m_destFile ); | |||
m_destFile.copy( m_srcFile ); | |||
m_destFile.copyFrom( m_srcFile, FileConstants.SELECT_SELF ); | |||
} | |||
// Copy the contents of the filesets across | |||
@@ -142,7 +143,7 @@ public class CopyFilesTask | |||
// Copy the file across | |||
getContext().verbose( "copy " + srcFile + " to " + destFile ); | |||
destFile.copy( srcFile ); | |||
destFile.copyFrom( srcFile, FileConstants.SELECT_SELF ); | |||
} | |||
} | |||
} | |||
@@ -0,0 +1,36 @@ | |||
/* | |||
* Copyright (C) The Apache Software Foundation. All rights reserved. | |||
* | |||
* This software is published under the terms of the Apache Software License | |||
* version 1.1, a copy of which has been included with this distribution in | |||
* the LICENSE.txt file. | |||
*/ | |||
package org.apache.aut.vfs; | |||
/** | |||
* A {@link FileSelector} which selects everything. | |||
* | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public class AllFileSelector | |||
implements FileSelector | |||
{ | |||
/** | |||
* Determines if a file or folder should be selected. | |||
*/ | |||
public boolean includeFile( final FileSelectInfo fileInfo ) | |||
throws FileSystemException | |||
{ | |||
return true; | |||
} | |||
/** | |||
* Determines whether a folder should be traversed. | |||
*/ | |||
public boolean traverseDescendents( final FileSelectInfo fileInfo ) | |||
throws FileSystemException | |||
{ | |||
return true; | |||
} | |||
} |
@@ -0,0 +1,46 @@ | |||
/* | |||
* 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; | |||
/** | |||
* Several constants for use in the VFS API. | |||
* | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public interface FileConstants | |||
{ | |||
/** | |||
* A {@link FileSelector} which selects only the base file/folder. | |||
*/ | |||
FileSelector SELECT_SELF = new FileDepthSelector( 0, 0 ); | |||
/** | |||
* A {@link FileSelector} which selects the base file/folder and its | |||
* direct children. | |||
*/ | |||
FileSelector SELECT_SELF_AND_CHILDREN = new FileDepthSelector( 0, 1 ); | |||
/** | |||
* A {@link FileSelector} which selects only the direct children | |||
* of the base folder. | |||
*/ | |||
FileSelector SELECT_CHILDREN = new FileDepthSelector( 1, 1 ); | |||
/** | |||
* A {@link FileSelector} which selects all the descendents of the | |||
* base folder, but does not select the base folder itself. | |||
*/ | |||
FileSelector EXCLUDE_SELF = new FileDepthSelector( 1, Integer.MAX_VALUE ); | |||
/** | |||
* A {@link FileSelector} which selects the base file/folder, plus all | |||
* its descendents. | |||
*/ | |||
FileSelector SELECT_ALL = new AllFileSelector(); | |||
} |
@@ -25,7 +25,8 @@ import java.io.OutputStream; | |||
* | |||
* @see FileObject#getContent | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public interface FileContent | |||
{ | |||
@@ -0,0 +1,46 @@ | |||
/* | |||
* 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; | |||
/** | |||
* A {@link FileSelector} which selects all files in a particular depth range. | |||
* | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public class FileDepthSelector | |||
implements FileSelector | |||
{ | |||
private final int m_minDepth; | |||
private final int m_maxDepth; | |||
public FileDepthSelector( int minDepth, int maxDepth ) | |||
{ | |||
m_minDepth = minDepth; | |||
m_maxDepth = maxDepth; | |||
} | |||
/** | |||
* Determines if a file or folder should be selected. | |||
*/ | |||
public boolean includeFile( final FileSelectInfo fileInfo ) | |||
throws FileSystemException | |||
{ | |||
final int depth = fileInfo.getDepth(); | |||
return m_minDepth <= depth && depth <= m_maxDepth; | |||
} | |||
/** | |||
* Determines whether a folder should be traversed. | |||
*/ | |||
public boolean traverseDescendents( final FileSelectInfo fileInfo ) | |||
throws FileSystemException | |||
{ | |||
return fileInfo.getDepth() < m_maxDepth; | |||
} | |||
} |
@@ -13,13 +13,14 @@ package org.apache.aut.vfs; | |||
* | |||
* @see FileObject | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public interface FileName | |||
{ | |||
/** | |||
* Returns the base name of the file. The base name of a file is the | |||
* last element of its name. For example the base name of | |||
* Returns the base name of this file. The base name is the last element | |||
* of the file name. For example the base name of | |||
* <code>/somefolder/somefile</code> is <code>somefile</code>. | |||
* | |||
* <p>The root file of a file system has an empty base name. | |||
@@ -27,23 +28,23 @@ public interface FileName | |||
String getBaseName(); | |||
/** | |||
* Returns the absolute path of the file, within its file system. This | |||
* Returns the absolute path of this file, within its file system. This | |||
* path is normalised, so that <code>.</code> and <code>..</code> elements | |||
* have been removed. Also, the path only contains <code>/</code> as its | |||
* separator character. The path always starts with <code>/</code> | |||
* | |||
* <p>The root of a file system has <code>/</code> as its path. | |||
* <p>The root of a file system has <code>/</code> as its absolute path. | |||
*/ | |||
String getPath(); | |||
/** | |||
* Returns the absolute URI of the file. | |||
* Returns the absolute URI of this file. | |||
*/ | |||
String getURI(); | |||
/** | |||
* Returns the name of the parent of the file. The root of a file system | |||
* has no parent. | |||
* Returns the file name of the parent of this file. The root of a | |||
* file system has no parent. | |||
* | |||
* @return | |||
* A {@link FileName} object representing the parent name. Returns | |||
@@ -52,35 +53,49 @@ public interface FileName | |||
FileName getParent(); | |||
/** | |||
* Resolves a name, relative to the file. Equivalent to calling | |||
* Resolves a name, relative to this file name. Equivalent to calling | |||
* <code>resolveName( path, NameScope.FILE_SYSTEM )</code>. | |||
* | |||
* @param path | |||
* The path to resolve. | |||
* @param name | |||
* The name to resolve. | |||
* | |||
* @return | |||
* A {@link FileName} object representing the resolved name. | |||
* A {@link FileName} object representing the resolved file name. | |||
* | |||
* @throws FileSystemException | |||
* If the name is invalid. | |||
*/ | |||
FileName resolveName( String path ) throws FileSystemException; | |||
FileName resolveName( String name ) throws FileSystemException; | |||
/** | |||
* Resolves a name, relative to the file. Refer to {@link NameScope} | |||
* Resolves a name, relative to this file name. Refer to {@link NameScope} | |||
* for a description of how names are resolved. | |||
* | |||
* @param name | |||
* The path to resolve. | |||
* The name to resolve. | |||
* | |||
* @param scope | |||
* The scope to use when resolving the name. | |||
* | |||
* @return | |||
* A {@link FileName} object representing the resolved name. | |||
* A {@link FileName} object representing the resolved file name. | |||
* | |||
* @throws FileSystemException | |||
* If the name is invalid. | |||
*/ | |||
FileName resolveName( String name, NameScope scope ) throws FileSystemException; | |||
/** | |||
* Converts a file name to a relative name, relative to this file name. | |||
* | |||
* @param name | |||
* The name to convert to a relative path. | |||
* | |||
* @return | |||
* The relative name. | |||
* | |||
* @throws FileSystemException | |||
* On error. | |||
*/ | |||
String getRelativeName( FileName name ) throws FileSystemException; | |||
} |
@@ -7,6 +7,8 @@ | |||
*/ | |||
package org.apache.aut.vfs; | |||
import java.io.File; | |||
/** | |||
* This interface represents a file, and is used to access the content and | |||
* structure of the file. | |||
@@ -53,7 +55,8 @@ package org.apache.aut.vfs; | |||
* @see FileContent | |||
* @see FileName | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public interface FileObject | |||
{ | |||
@@ -154,17 +157,19 @@ public interface FileObject | |||
FileObject resolveFile( String path ) throws FileSystemException; | |||
/** | |||
* Deletes this file, and all children. Does nothing if the file | |||
* Deletes this file, and all descendents. Does nothing if the file | |||
* does not exist. | |||
* | |||
* <p>This method is not transactional. If it fails and throws an | |||
* exception, some of this file's descendents may have been deleted. | |||
* exception, this file will potentially only be partially deleted. | |||
* | |||
* @param selector The selector to use to select which files to delete. | |||
* | |||
* @throws FileSystemException | |||
* If this file or one of its descendents is read-only, or on error | |||
* deleting this file or one of its descendents. | |||
*/ | |||
void delete() throws FileSystemException; | |||
void delete( FileSelector selector ) throws FileSystemException; | |||
/** | |||
* Creates this file, if it does not exist. Also creates any ancestor | |||
@@ -182,18 +187,42 @@ public interface FileObject | |||
void create( FileType type ) throws FileSystemException; | |||
/** | |||
* Copies the content of another file to this file. | |||
* Copies another file, and all its descendents, 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. | |||
* created, if necessary. If this file does exist, it is deleted first. | |||
* | |||
* <p>This method is not transactional. If it fails and throws an | |||
* exception, this file will potentially only be partially copied. | |||
* | |||
* @param srcFile The source file to copy. | |||
* @param selector The selector to use to select which files to copy. | |||
* | |||
* @throws FileSystemException | |||
* If this file is read-only, or if the source file does not exist, | |||
* or on error copying the file. | |||
*/ | |||
void copyFrom( FileObject srcFile, FileSelector selector ) throws FileSystemException; | |||
/** | |||
* Creates a temporary local copy of this file, and its descendents. If | |||
* this file is a local file, a copy is not made. | |||
* | |||
* <p>Note that the local copy may include additonal files, that were | |||
* not selected by the given selector. | |||
* | |||
* @todo Add options to indicate whether the caller is happy to deal with | |||
* extra files being present locally (eg if the file has been | |||
* replicated previously), or whether the caller expects only | |||
* the selected files to be present. | |||
* | |||
* @param file The file to copy the content from. | |||
* @param selector the selector to use to select the files to replicate. | |||
* @return The local copy of this file. | |||
* | |||
* @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. | |||
* If this file does not exist, or on error replicating the file. | |||
*/ | |||
void copy( FileObject file ) throws FileSystemException; | |||
File replicateFile( FileSelector selector ) throws FileSystemException; | |||
/** | |||
* Returns this file's content. The {@link FileContent} returned by this | |||
@@ -0,0 +1,33 @@ | |||
/* | |||
* 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; | |||
/** | |||
* Information about a file, that is used to select files during the | |||
* traversal of a hierarchy. | |||
* | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public interface FileSelectInfo | |||
{ | |||
/** | |||
* Returns the base folder of the traversal. | |||
*/ | |||
FileObject getBaseFolder(); | |||
/** | |||
* Returns the file (or folder) to be considered. | |||
*/ | |||
FileObject getFile(); | |||
/** | |||
* Returns the depth of the file relative to the base folder. | |||
*/ | |||
int getDepth(); | |||
} |
@@ -0,0 +1,38 @@ | |||
/* | |||
* Copyright (C) The Apache Software Foundation. All rights reserved. | |||
* | |||
* This software is published under the terms of the Apache Software License | |||
* version 1.1, a copy of which has been included with this distribution in | |||
* the LICENSE.txt file. | |||
*/ | |||
package org.apache.aut.vfs; | |||
/** | |||
* This interface is used to select files when traversing a file hierarchy. | |||
* | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public interface FileSelector | |||
{ | |||
/** | |||
* Determines if a file or folder should be selected. | |||
* | |||
* @param fileInfo the file or folder to select. | |||
* @return true if the file should be selected. | |||
*/ | |||
boolean includeFile( FileSelectInfo fileInfo ) | |||
throws FileSystemException; | |||
/** | |||
* Determines whether a folder should be traversed. If this method returns | |||
* true, {@link #includeFile} is called for each of the children of | |||
* the folder, and each of the child folders is recursively traversed. | |||
* | |||
* @param fileInfo the file or folder to select. | |||
* | |||
* @return true if the folder should be traversed. | |||
*/ | |||
boolean traverseDescendents( FileSelectInfo fileInfo ) | |||
throws FileSystemException; | |||
} |
@@ -10,7 +10,8 @@ package org.apache.aut.vfs; | |||
/** | |||
* Thrown for file system errors. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public class FileSystemException | |||
extends Exception | |||
@@ -49,7 +49,9 @@ import java.io.File; | |||
* | |||
* </ul> | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
* | |||
* @ant:role shorthand="file-system-manager" | |||
*/ | |||
public interface FileSystemManager | |||
@@ -128,7 +130,7 @@ public interface FileSystemManager | |||
/** | |||
* 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 | |||
* that is created from the contents of another file, such as a zip | |||
* or tar file. | |||
* | |||
* @param provider | |||
@@ -5,7 +5,8 @@ | |||
* version 1.1, a copy of which has been included with this distribution in | |||
* the LICENSE.txt file. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
package org.apache.aut.vfs; | |||
@@ -11,7 +11,8 @@ package org.apache.aut.vfs; | |||
* An enumerated type for file name scope, used when resolving a name relative | |||
* to a file. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public final class NameScope | |||
{ | |||
@@ -29,6 +30,14 @@ public final class NameScope | |||
*/ | |||
public static final NameScope DESCENDENT = new NameScope( "descendent" ); | |||
/** | |||
* Resolve against the descendents of the base file. The name is resolved | |||
* as described by {@link #FILE_SYSTEM}. However, an exception is thrown | |||
* if the resolved file is not a descendent of the base file, or the base | |||
* files itself. | |||
*/ | |||
public static final NameScope DESCENDENT_OR_SELF = new NameScope( "descendent_or_self" ); | |||
/** | |||
* Resolve against files in the same file system as the base file. | |||
* | |||
@@ -46,7 +55,7 @@ public final class NameScope | |||
*/ | |||
public static final NameScope FILE_SYSTEM = new NameScope( "filesystem" ); | |||
private String m_name; | |||
private final String m_name; | |||
private NameScope( final String name ) | |||
{ | |||
@@ -0,0 +1,98 @@ | |||
/* | |||
* 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.io.File; | |||
import java.util.ArrayList; | |||
import org.apache.aut.vfs.FileConstants; | |||
import org.apache.aut.vfs.FileObject; | |||
import org.apache.aut.vfs.FileSelector; | |||
import org.apache.aut.vfs.FileSystemException; | |||
import org.apache.aut.vfs.provider.FileReplicator; | |||
import org.apache.avalon.excalibur.i18n.ResourceManager; | |||
import org.apache.avalon.excalibur.i18n.Resources; | |||
import org.apache.avalon.framework.activity.Disposable; | |||
import org.apache.avalon.framework.logger.AbstractLogEnabled; | |||
/** | |||
* A simple file replicator. | |||
* | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public class DefaultFileReplicator | |||
extends AbstractLogEnabled | |||
implements FileReplicator, Disposable | |||
{ | |||
private static final Resources REZ = | |||
ResourceManager.getPackageResources( DefaultFileReplicator.class ); | |||
private final DefaultFileSystemManager m_manager; | |||
private final File m_tempDir; | |||
private final ArrayList m_copies = new ArrayList(); | |||
private long m_filecount; | |||
public DefaultFileReplicator( final DefaultFileSystemManager manager ) | |||
{ | |||
m_manager = manager; | |||
m_tempDir = new File( "ant_vfs_cache" ).getAbsoluteFile(); | |||
} | |||
/** | |||
* Deletes the temporary files. | |||
*/ | |||
public void dispose() | |||
{ | |||
while( m_copies.size() > 0 ) | |||
{ | |||
final FileObject file = (FileObject)m_copies.remove( 0 ); | |||
try | |||
{ | |||
file.delete( FileConstants.SELECT_ALL ); | |||
} | |||
catch( final FileSystemException e ) | |||
{ | |||
final String message = REZ.getString( "delete-temp.warn", file.getName() ); | |||
getLogger().warn( message, e ); | |||
} | |||
} | |||
} | |||
/** | |||
* Creates a local copy of the file, and all its descendents. | |||
*/ | |||
public File replicateFile( final FileObject srcFile, | |||
final FileSelector selector ) | |||
throws FileSystemException | |||
{ | |||
// TODO - this is awful | |||
// Create a unique-ish file name | |||
final String basename = m_filecount + "_" + srcFile.getName().getBaseName(); | |||
m_filecount++; | |||
final File file = new File( m_tempDir, basename ); | |||
try | |||
{ | |||
// Copy from the source file | |||
final FileObject destFile = m_manager.convert( file ); | |||
destFile.copyFrom( srcFile, selector ); | |||
// Keep track of the copy | |||
m_copies.add( destFile ); | |||
} | |||
catch( final FileSystemException e ) | |||
{ | |||
final String message = REZ.getString( "replicate-file.error", srcFile.getName(), file ); | |||
throw new FileSystemException( message, e ); | |||
} | |||
return file; | |||
} | |||
} |
@@ -9,48 +9,50 @@ package org.apache.aut.vfs.impl; | |||
import java.io.File; | |||
import java.util.HashMap; | |||
import java.util.HashSet; | |||
import java.util.Iterator; | |||
import java.util.Map; | |||
import java.util.Set; | |||
import org.apache.aut.vfs.FileObject; | |||
import org.apache.aut.vfs.FileSystemException; | |||
import org.apache.aut.vfs.FileSystemManager; | |||
import org.apache.aut.vfs.provider.FileReplicator; | |||
import org.apache.aut.vfs.provider.FileSystemProvider; | |||
import org.apache.aut.vfs.provider.LocalFileSystemProvider; | |||
import org.apache.aut.vfs.provider.UriParser; | |||
import org.apache.aut.vfs.provider.local.LocalFileSystemProvider; | |||
import org.apache.avalon.excalibur.i18n.ResourceManager; | |||
import org.apache.avalon.excalibur.i18n.Resources; | |||
import org.apache.avalon.framework.activity.Disposable; | |||
import org.apache.avalon.framework.logger.AbstractLogEnabled; | |||
import org.apache.avalon.framework.logger.Logger; | |||
/** | |||
* A default file system manager implementation. | |||
* | |||
* @todo - Extract an AbstractFileSystemManager super-class from this class. | |||
* | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public class DefaultFileSystemManager | |||
implements FileSystemManager | |||
extends AbstractLogEnabled | |||
implements FileSystemManager, Disposable | |||
{ | |||
private static final Resources REZ | |||
= ResourceManager.getPackageResources( DefaultFileSystemManager.class ); | |||
/** The default provider. */ | |||
private final LocalFileSystemProvider m_localFileProvider; | |||
/** The provider for local files. */ | |||
private LocalFileSystemProvider m_localFileProvider; | |||
/** The file replicator to use. */ | |||
private final DefaultFileReplicator m_fileReplicator = new DefaultFileReplicator( this ); | |||
/** Mapping from URI scheme to FileSystemProvider. */ | |||
private final Map m_providers = new HashMap(); | |||
/** The provider context. */ | |||
private final DefaultProviderContext m_context = new DefaultProviderContext( this ); | |||
/** The base file to use for relative URI. */ | |||
private FileObject m_baseFile; | |||
public DefaultFileSystemManager() | |||
{ | |||
// Create the local provider | |||
m_localFileProvider = new LocalFileSystemProvider(); | |||
m_providers.put( "file", m_localFileProvider ); | |||
m_localFileProvider.setContext( m_context ); | |||
} | |||
/** | |||
* Registers a file system provider. | |||
*/ | |||
@@ -58,7 +60,7 @@ public class DefaultFileSystemManager | |||
final FileSystemProvider provider ) | |||
throws FileSystemException | |||
{ | |||
addProvider( new String[] { urlScheme }, provider ); | |||
addProvider( new String[]{urlScheme}, provider ); | |||
} | |||
/** | |||
@@ -71,7 +73,7 @@ public class DefaultFileSystemManager | |||
// Check for duplicates | |||
for( int i = 0; i < urlSchemes.length; i++ ) | |||
{ | |||
final String scheme = urlSchemes[i ]; | |||
final String scheme = urlSchemes[ i ]; | |||
if( m_providers.containsKey( scheme ) ) | |||
{ | |||
final String message = REZ.getString( "multiple-providers-for-scheme.error", scheme ); | |||
@@ -80,7 +82,8 @@ public class DefaultFileSystemManager | |||
} | |||
// Contextualise | |||
provider.setContext( m_context ); | |||
setupLogger( provider ); | |||
provider.setContext( new DefaultProviderContext( this ) ); | |||
// Add to map | |||
for( int i = 0; i < urlSchemes.length; i++ ) | |||
@@ -88,14 +91,55 @@ public class DefaultFileSystemManager | |||
final String scheme = urlSchemes[ i ]; | |||
m_providers.put( scheme, provider ); | |||
} | |||
if( provider instanceof LocalFileSystemProvider ) | |||
{ | |||
m_localFileProvider = (LocalFileSystemProvider)provider; | |||
} | |||
} | |||
/** | |||
* Closes all file systems created by this file system manager. | |||
* Returns the file replicator. | |||
* | |||
* @return The file replicator. Never returns null. | |||
*/ | |||
public void close() | |||
public FileReplicator getReplicator() | |||
throws FileSystemException | |||
{ | |||
// TODO - implement this | |||
return m_fileReplicator; | |||
} | |||
/** | |||
* Enable logging. | |||
*/ | |||
public void enableLogging( final Logger logger ) | |||
{ | |||
super.enableLogging( logger ); | |||
setupLogger( m_fileReplicator ); | |||
} | |||
/** | |||
* Closes all files created by this manager, and cleans up any temporary | |||
* files. | |||
*/ | |||
public void dispose() | |||
{ | |||
// Dispose the providers (making sure we only dispose each provider | |||
// once | |||
final Set providers = new HashSet(); | |||
providers.addAll( m_providers.values() ); | |||
for( Iterator iterator = providers.iterator(); iterator.hasNext(); ) | |||
{ | |||
Object provider = iterator.next(); | |||
if( provider instanceof Disposable ) | |||
{ | |||
Disposable disposable = (Disposable)provider; | |||
disposable.dispose(); | |||
} | |||
} | |||
m_providers.clear(); | |||
m_fileReplicator.dispose(); | |||
} | |||
/** | |||
@@ -111,7 +155,7 @@ public class DefaultFileSystemManager | |||
*/ | |||
public void setBaseFile( final File baseFile ) throws FileSystemException | |||
{ | |||
m_baseFile = m_localFileProvider.findLocalFile( baseFile.getAbsolutePath() ); | |||
m_baseFile = getLocalFileProvider().findLocalFile( baseFile ); | |||
} | |||
/** | |||
@@ -136,7 +180,7 @@ public class DefaultFileSystemManager | |||
public FileObject resolveFile( final File baseFile, final String uri ) | |||
throws FileSystemException | |||
{ | |||
final FileObject baseFileObj = m_localFileProvider.findLocalFile( baseFile ); | |||
final FileObject baseFileObj = getLocalFileProvider().findLocalFile( baseFile ); | |||
return resolveFile( baseFileObj, uri ); | |||
} | |||
@@ -162,19 +206,20 @@ public class DefaultFileSystemManager | |||
final String decodedUri = UriParser.decode( uri ); | |||
// Handle absolute file names | |||
if( m_localFileProvider.isAbsoluteLocalName( decodedUri ) ) | |||
if( m_localFileProvider != null | |||
&& m_localFileProvider.isAbsoluteLocalName( decodedUri ) ) | |||
{ | |||
return m_localFileProvider.findLocalFile( decodedUri ); | |||
} | |||
// Assume a bad scheme | |||
if( scheme != null ) | |||
{ | |||
// Assume a bad scheme | |||
final String message = REZ.getString( "unknown-scheme.error", scheme, uri ); | |||
throw new FileSystemException( message ); | |||
} | |||
// Use the supplied base file | |||
// Assume a relative name - use the supplied base file | |||
if( baseFile == null ) | |||
{ | |||
final String message = REZ.getString( "find-rel-file.error", uri ); | |||
@@ -189,7 +234,7 @@ public class DefaultFileSystemManager | |||
public FileObject convert( final File file ) | |||
throws FileSystemException | |||
{ | |||
return m_localFileProvider.findLocalFile( file ); | |||
return getLocalFileProvider().findLocalFile( file ); | |||
} | |||
/** | |||
@@ -207,4 +252,18 @@ public class DefaultFileSystemManager | |||
} | |||
return provider.createFileSystem( scheme, file ); | |||
} | |||
/** | |||
* Locates the local file provider. | |||
*/ | |||
private LocalFileSystemProvider getLocalFileProvider() | |||
throws FileSystemException | |||
{ | |||
if( m_localFileProvider == null ) | |||
{ | |||
final String message = REZ.getString( "no-local-file-provider.error" ); | |||
throw new FileSystemException( message ); | |||
} | |||
return m_localFileProvider; | |||
} | |||
} |
@@ -7,11 +7,9 @@ | |||
*/ | |||
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.FileReplicator; | |||
import org.apache.aut.vfs.provider.FileSystemProviderContext; | |||
/** | |||
@@ -25,12 +23,6 @@ final class DefaultProviderContext | |||
{ | |||
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; | |||
@@ -46,21 +38,10 @@ final class DefaultProviderContext | |||
} | |||
/** | |||
* Locates a cached file system by root URI. | |||
* Locates a file replicator for the provider to use. | |||
*/ | |||
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 | |||
public FileReplicator getReplicator() throws FileSystemException | |||
{ | |||
// TODO - should really check that there's not one already cached | |||
m_fileSystems.put( rootURI, fs ); | |||
return m_manager.getReplicator(); | |||
} | |||
} |
@@ -2,4 +2,7 @@ | |||
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}". | |||
unknown-provider.error=No file system provider is registered for URI scheme "{0}". | |||
no-local-file-provider.error=Could not find a file system provider which can handle local files. | |||
replicate-file.error=Could not replicate "{0}" to "{1}". | |||
delete-temp.warn=Could not clean up temporary file "{0}". |
@@ -7,15 +7,16 @@ | |||
*/ | |||
package org.apache.aut.vfs.provider; | |||
import java.io.File; | |||
import java.io.InputStream; | |||
import java.io.OutputStream; | |||
import java.util.ArrayList; | |||
import java.util.HashSet; | |||
import java.util.List; | |||
import java.util.Set; | |||
import org.apache.aut.vfs.FileConstants; | |||
import org.apache.aut.vfs.FileContent; | |||
import org.apache.aut.vfs.FileName; | |||
import org.apache.aut.vfs.FileObject; | |||
import org.apache.aut.vfs.FileSelector; | |||
import org.apache.aut.vfs.FileSystemException; | |||
import org.apache.aut.vfs.FileType; | |||
import org.apache.aut.vfs.NameScope; | |||
@@ -26,7 +27,11 @@ import org.apache.avalon.excalibur.io.IOUtil; | |||
/** | |||
* A partial file object implementation. | |||
* | |||
* @author Adam Murdoch | |||
* @todo Chop this class up - move all the protected methods to several | |||
* interfaces, so that structure and content can be separately overridden. | |||
* | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public abstract class AbstractFileObject | |||
implements FileObject | |||
@@ -98,7 +103,7 @@ public abstract class AbstractFileObject | |||
* <ul> | |||
* <li>{@link #isReadOnly} returns false. | |||
* <li>{@link #doGetType} does not return null. | |||
* <li>If this file is a folder, it has no children. | |||
* <li>This file has no children. | |||
* </ul> | |||
*/ | |||
protected void doDelete() throws Exception | |||
@@ -122,6 +127,15 @@ public abstract class AbstractFileObject | |||
throw new FileSystemException( message ); | |||
} | |||
/** | |||
* Creates a local copy of this file. | |||
*/ | |||
protected File doReplicateFile( final FileSelector selector ) throws FileSystemException | |||
{ | |||
final FileReplicator replicator = m_fs.getContext().getReplicator(); | |||
return replicator.replicateFile( this, selector ); | |||
} | |||
/** | |||
* Called when the children of this file change. | |||
*/ | |||
@@ -321,9 +335,9 @@ public abstract class AbstractFileObject | |||
* absolute path, which is resolved relative to the file system | |||
* that contains this file. | |||
*/ | |||
public FileObject resolveFile( String path ) throws FileSystemException | |||
public FileObject resolveFile( final String path ) throws FileSystemException | |||
{ | |||
FileName name = m_name.resolveName( path ); | |||
final FileName name = m_name.resolveName( path ); | |||
return m_fs.findFile( name ); | |||
} | |||
@@ -356,7 +370,7 @@ public abstract class AbstractFileObject | |||
/** | |||
* Deletes this file, and all children. | |||
*/ | |||
public void delete() throws FileSystemException | |||
public void delete( final FileSelector selector ) throws FileSystemException | |||
{ | |||
attach(); | |||
if( m_type == null ) | |||
@@ -365,51 +379,27 @@ public abstract class AbstractFileObject | |||
return; | |||
} | |||
// Recursively delete this file and all its children | |||
List queue = new ArrayList(); | |||
Set expanded = new HashSet(); | |||
queue.add( this ); | |||
// Locate all the files to delete | |||
ArrayList files = new ArrayList(); | |||
findFiles( selector, true, files ); | |||
// Recursively delete each file | |||
// TODO - recover from errors | |||
while( queue.size() > 0 ) | |||
// Delete 'em | |||
final int count = files.size(); | |||
for( int i = 0; i < count; i++ ) | |||
{ | |||
AbstractFileObject file = (AbstractFileObject)queue.get( 0 ); | |||
final AbstractFileObject file = (AbstractFileObject)files.get( i ); | |||
file.attach(); | |||
if( file.m_type == null ) | |||
{ | |||
// Shouldn't happen | |||
queue.remove( 0 ); | |||
} | |||
else if( file.m_type == FileType.FILE ) | |||
{ | |||
// Delete the file | |||
file.deleteSelf(); | |||
queue.remove( 0 ); | |||
} | |||
else if( expanded.contains( file ) ) | |||
// If the file is a folder, make sure all its children have been deleted | |||
if( file.m_type == FileType.FOLDER && file.getChildren().length != 0 ) | |||
{ | |||
// Have already deleted all the children of this folder - | |||
// delete it | |||
file.deleteSelf(); | |||
queue.remove( 0 ); | |||
// Skip | |||
continue; | |||
} | |||
else | |||
{ | |||
// Delete the folder's children | |||
FileObject[] children = file.getChildren(); | |||
for( int i = 0; i < children.length; i++ ) | |||
{ | |||
FileObject child = children[ i ]; | |||
queue.add( 0, child ); | |||
} | |||
expanded.add( file ); | |||
} | |||
} | |||
// Update parent's child list | |||
notifyParent(); | |||
// Delete the file | |||
file.deleteSelf(); | |||
} | |||
} | |||
/** | |||
@@ -467,19 +457,88 @@ public abstract class AbstractFileObject | |||
updateType(); | |||
} | |||
/** | |||
* Copies another file to this file. | |||
*/ | |||
public void copyFrom( final FileObject file, final FileSelector selector ) | |||
throws FileSystemException | |||
{ | |||
if( !file.exists() ) | |||
{ | |||
final String message = REZ.getString( "copy-missing-file.error", file.getName() ); | |||
throw new FileSystemException( message ); | |||
} | |||
if( isReadOnly() ) | |||
{ | |||
final String message = REZ.getString( "copy-read-only.error", file.getType(), file.getName(), m_name ); | |||
throw new FileSystemException( message ); | |||
} | |||
// Locate the files to copy across | |||
final ArrayList files = new ArrayList(); | |||
( (AbstractFileObject)file ).findFiles( selector, false, files ); | |||
// Copy everything across | |||
final int count = files.size(); | |||
for( int i = 0; i < count; i++ ) | |||
{ | |||
final FileObject srcFile = (FileObject)files.get( i ); | |||
// Determine the destination file | |||
final String relPath = file.getName().getRelativeName( srcFile.getName() ); | |||
final FileObject destFile = resolveFile( relPath, NameScope.DESCENDENT_OR_SELF ); | |||
// Clean up the destination file, if necessary | |||
if( destFile.exists() && destFile.getType() != srcFile.getType() ) | |||
{ | |||
// The destination file exists, and is not of the same type, | |||
// so delete it | |||
// TODO - add a pluggable policy for deleting and overwriting existing files | |||
destFile.delete( FileConstants.SELECT_ALL ); | |||
} | |||
// Copy across | |||
if( srcFile.getType() == FileType.FILE ) | |||
{ | |||
copyContent( srcFile, destFile ); | |||
} | |||
else | |||
{ | |||
destFile.create( FileType.FOLDER ); | |||
} | |||
} | |||
} | |||
/** | |||
* Creates a temporary local copy of this file, and its descendents. | |||
*/ | |||
public File replicateFile( final FileSelector selector ) | |||
throws FileSystemException | |||
{ | |||
if( !exists() ) | |||
{ | |||
final String message = REZ.getString( "copy-missing-file.error", m_name ); | |||
throw new FileSystemException( message ); | |||
} | |||
return doReplicateFile( selector ); | |||
} | |||
/** | |||
* Copies the content of another file to this file. | |||
*/ | |||
public void copy( final FileObject file ) throws FileSystemException | |||
private static void copyContent( final FileObject srcFile, | |||
final FileObject destFile ) | |||
throws FileSystemException | |||
{ | |||
try | |||
{ | |||
final InputStream instr = file.getContent().getInputStream(); | |||
final InputStream instr = srcFile.getContent().getInputStream(); | |||
try | |||
{ | |||
// Create the output strea via getContent(), to pick up the | |||
// Create the output stream via getContent(), to pick up the | |||
// validation it does | |||
final OutputStream outstr = getContent().getOutputStream(); | |||
final OutputStream outstr = destFile.getContent().getOutputStream(); | |||
try | |||
{ | |||
IOUtil.copy( instr, outstr ); | |||
@@ -496,7 +555,7 @@ public abstract class AbstractFileObject | |||
} | |||
catch( final Exception exc ) | |||
{ | |||
final String message = REZ.getString( "copy-file.error", file.getName(), m_name ); | |||
final String message = REZ.getString( "copy-file.error", srcFile.getName(), destFile.getName() ); | |||
throw new FileSystemException( message, exc ); | |||
} | |||
} | |||
@@ -678,4 +737,69 @@ public abstract class AbstractFileObject | |||
m_children = null; | |||
onChildrenChanged(); | |||
} | |||
/** | |||
* Traverses the descendents of this file, and builds a list of selected | |||
* files. | |||
*/ | |||
void findFiles( final FileSelector selector, | |||
final boolean depthwise, | |||
final List selected ) throws FileSystemException | |||
{ | |||
if( exists() ) | |||
{ | |||
// Traverse starting at this file | |||
final DefaultFileSelectorInfo info = new DefaultFileSelectorInfo(); | |||
info.setBaseFolder( this ); | |||
info.setDepth( 0 ); | |||
info.setFile( this ); | |||
traverse( info, selector, depthwise, selected ); | |||
} | |||
} | |||
/** | |||
* Traverses a file. | |||
*/ | |||
private void traverse( final DefaultFileSelectorInfo fileInfo, | |||
final FileSelector selector, | |||
final boolean depthwise, | |||
final List selected ) | |||
throws FileSystemException | |||
{ | |||
// Check the file itself | |||
final boolean includeFile = selector.includeFile( fileInfo ); | |||
final FileObject file = fileInfo.getFile(); | |||
// Add the file if not doing depthwise traversal | |||
if( !depthwise && includeFile ) | |||
{ | |||
selected.add( file ); | |||
} | |||
// If the file is a folder, traverse it | |||
if( file.getType() == FileType.FOLDER && selector.traverseDescendents( fileInfo ) ) | |||
{ | |||
final int curDepth = fileInfo.getDepth(); | |||
fileInfo.setDepth( curDepth + 1 ); | |||
// Traverse the children | |||
final FileObject[] children = file.getChildren(); | |||
for( int i = 0; i < children.length; i++ ) | |||
{ | |||
final FileObject child = children[ i ]; | |||
fileInfo.setFile( child ); | |||
traverse( fileInfo, selector, depthwise, selected ); | |||
} | |||
fileInfo.setFile( file ); | |||
fileInfo.setDepth( curDepth ); | |||
} | |||
// Add the file if doing depthwise traversal | |||
if( depthwise && includeFile ) | |||
{ | |||
selected.add( file ); | |||
} | |||
} | |||
} |
@@ -12,45 +12,67 @@ import java.util.Map; | |||
import org.apache.aut.vfs.FileName; | |||
import org.apache.aut.vfs.FileObject; | |||
import org.apache.aut.vfs.FileSystemException; | |||
import org.apache.avalon.framework.activity.Disposable; | |||
import org.apache.avalon.framework.logger.AbstractLogEnabled; | |||
/** | |||
* A partial file system implementation. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public abstract class AbstractFileSystem implements FileSystem | |||
public abstract class AbstractFileSystem | |||
extends AbstractLogEnabled | |||
implements FileSystem, Disposable | |||
{ | |||
private FileObject m_root; | |||
private FileName m_rootName; | |||
private final FileName m_rootName; | |||
private final FileSystemProviderContext m_context; | |||
/** Map from absolute file path to FileObject. */ | |||
private Map m_files = new HashMap(); | |||
/** Map from FileName to FileObject. */ | |||
private final Map m_files = new HashMap(); | |||
protected AbstractFileSystem( FileName rootName ) | |||
protected AbstractFileSystem( final FileSystemProviderContext context, | |||
final FileName rootName ) | |||
{ | |||
m_rootName = rootName; | |||
m_context = context; | |||
} | |||
public void dispose() | |||
{ | |||
// Clean-up | |||
m_files.clear(); | |||
} | |||
/** | |||
* Creates a file object. This method is called only if the requested | |||
* file is not cached. | |||
*/ | |||
protected abstract FileObject createFile( FileName name ) throws FileSystemException; | |||
protected abstract FileObject createFile( final FileName name ) throws FileSystemException; | |||
/** | |||
* Adds a file object to the cache. | |||
*/ | |||
protected void putFile( FileObject file ) | |||
protected void putFile( final FileObject file ) | |||
{ | |||
m_files.put( file.getName().getPath(), file ); | |||
m_files.put( file.getName(), file ); | |||
} | |||
/** | |||
* Returns a cached file. | |||
*/ | |||
protected FileObject getFile( FileName name ) | |||
protected FileObject getFile( final FileName name ) | |||
{ | |||
return (FileObject)m_files.get( name ); | |||
} | |||
/** | |||
* Returns the context fir this file system. | |||
*/ | |||
public FileSystemProviderContext getContext() | |||
{ | |||
return (FileObject)m_files.get( name.getPath() ); | |||
return m_context; | |||
} | |||
/** | |||
@@ -68,24 +90,24 @@ public abstract class AbstractFileSystem implements FileSystem | |||
/** | |||
* Finds a file in this file system. | |||
*/ | |||
public FileObject findFile( String nameStr ) throws FileSystemException | |||
public FileObject findFile( final String nameStr ) throws FileSystemException | |||
{ | |||
// Resolve the name, and create the file | |||
FileName name = m_rootName.resolveName( nameStr ); | |||
final FileName name = m_rootName.resolveName( nameStr ); | |||
return findFile( name ); | |||
} | |||
/** | |||
* Finds a file in this file system. | |||
*/ | |||
public FileObject findFile( FileName name ) throws FileSystemException | |||
public FileObject findFile( final FileName name ) throws FileSystemException | |||
{ | |||
// TODO - assert that name is from this file system | |||
FileObject file = (FileObject)m_files.get( name.getPath() ); | |||
FileObject file = (FileObject)m_files.get( name ); | |||
if( file == null ) | |||
{ | |||
file = createFile( name ); | |||
m_files.put( name.getPath(), file ); | |||
m_files.put( name, file ); | |||
} | |||
return file; | |||
} | |||
@@ -7,24 +7,37 @@ | |||
*/ | |||
package org.apache.aut.vfs.provider; | |||
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.avalon.excalibur.i18n.ResourceManager; | |||
import org.apache.avalon.excalibur.i18n.Resources; | |||
import org.apache.avalon.framework.activity.Disposable; | |||
import org.apache.avalon.framework.logger.AbstractLogEnabled; | |||
/** | |||
* A partial file system provider implementation. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public abstract class AbstractFileSystemProvider | |||
implements FileSystemProvider | |||
extends AbstractLogEnabled | |||
implements FileSystemProvider, Disposable | |||
{ | |||
private static final Resources REZ = | |||
ResourceManager.getPackageResources( AbstractFileSystemProvider.class ); | |||
private FileSystemProviderContext m_context; | |||
/** | |||
* The cached file systems. This is a mapping from root URI to | |||
* FileSystem object. | |||
*/ | |||
private final Map m_fileSystems = new HashMap(); | |||
/** | |||
* Returns the context for this provider. | |||
*/ | |||
@@ -42,6 +55,23 @@ public abstract class AbstractFileSystemProvider | |||
m_context = context; | |||
} | |||
/** | |||
* Closes the file systems created by this provider. | |||
*/ | |||
public void dispose() | |||
{ | |||
for( Iterator iterator = m_fileSystems.values().iterator(); iterator.hasNext(); ) | |||
{ | |||
FileSystem fileSystem = (FileSystem)iterator.next(); | |||
if( fileSystem instanceof Disposable ) | |||
{ | |||
Disposable disposable = (Disposable)fileSystem; | |||
disposable.dispose(); | |||
} | |||
} | |||
m_fileSystems.clear(); | |||
} | |||
/** | |||
* Locates a file object, by absolute URI. | |||
* | |||
@@ -65,7 +95,6 @@ public abstract class AbstractFileSystemProvider | |||
// Locate the file | |||
return findFile( parsedUri ); | |||
} | |||
/** | |||
@@ -76,12 +105,13 @@ public abstract class AbstractFileSystemProvider | |||
{ | |||
// Check in the cache for the file system | |||
final String rootUri = parsedUri.getRootUri(); | |||
FileSystem fs = m_context.getFileSystem( rootUri ); | |||
FileSystem fs = (FileSystem)m_fileSystems.get( rootUri ); | |||
if( fs == null ) | |||
{ | |||
// Need to create the file system, and cache it | |||
fs = createFileSystem( parsedUri ); | |||
m_context.putFileSystem( rootUri, fs ); | |||
setupLogger( fs ); | |||
m_fileSystems.put( rootUri, fs ); | |||
} | |||
// Locate the file | |||
@@ -21,7 +21,8 @@ import org.apache.avalon.excalibur.i18n.Resources; | |||
/** | |||
* The content of a file. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public class DefaultFileContent | |||
implements FileContent | |||
@@ -14,7 +14,8 @@ import org.apache.aut.vfs.NameScope; | |||
/** | |||
* A default file name implementation. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public class DefaultFileName implements FileName | |||
{ | |||
@@ -127,4 +128,12 @@ public class DefaultFileName implements FileName | |||
} | |||
return m_uri; | |||
} | |||
/** | |||
* Converts a file name to a relative name, relative to this file name. | |||
*/ | |||
public String getRelativeName( final FileName name ) throws FileSystemException | |||
{ | |||
return m_parser.makeRelative( m_absPath, name.getPath() ); | |||
} | |||
} |
@@ -0,0 +1,55 @@ | |||
/* | |||
* Copyright (C) The Apache Software Foundation. All rights reserved. | |||
* | |||
* This software is published under the terms of the Apache Software License | |||
* version 1.1, a copy of which has been included with this distribution in | |||
* the LICENSE.txt file. | |||
*/ | |||
package org.apache.aut.vfs.provider; | |||
import org.apache.aut.vfs.FileObject; | |||
import org.apache.aut.vfs.FileSelectInfo; | |||
/** | |||
* A default {@link FileSelectInfo} implementation. | |||
* | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
class DefaultFileSelectorInfo | |||
implements FileSelectInfo | |||
{ | |||
private FileObject m_baseFolder; | |||
private FileObject m_file; | |||
private int m_depth; | |||
public FileObject getBaseFolder() | |||
{ | |||
return m_baseFolder; | |||
} | |||
public void setBaseFolder( final FileObject baseFolder ) | |||
{ | |||
m_baseFolder = baseFolder; | |||
} | |||
public FileObject getFile() | |||
{ | |||
return m_file; | |||
} | |||
public void setFile( final FileObject file ) | |||
{ | |||
m_file = file; | |||
} | |||
public int getDepth() | |||
{ | |||
return m_depth; | |||
} | |||
public void setDepth( final int depth ) | |||
{ | |||
m_depth = depth; | |||
} | |||
} |
@@ -0,0 +1,36 @@ | |||
/* | |||
* Copyright (C) The Apache Software Foundation. All rights reserved. | |||
* | |||
* This software is published under the terms of the Apache Software License | |||
* version 1.1, a copy of which has been included with this distribution in | |||
* the LICENSE.txt file. | |||
*/ | |||
package org.apache.aut.vfs.provider; | |||
import java.io.File; | |||
import org.apache.aut.vfs.FileObject; | |||
import org.apache.aut.vfs.FileSelector; | |||
import org.apache.aut.vfs.FileSystemException; | |||
/** | |||
* Responsible for making local replicas of files. | |||
* | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public interface FileReplicator | |||
{ | |||
/** | |||
* Creates a local copy of the file, and all its descendents. | |||
* | |||
* @param srcFile The file to copy. | |||
* @param selector Selects the files to copy. | |||
* | |||
* @return The local copy of the source file. | |||
* | |||
* @throws FileSystemException | |||
* If the source files does not exist, or on error copying. | |||
*/ | |||
File replicateFile( FileObject srcFile, FileSelector selector ) | |||
throws FileSystemException; | |||
} |
@@ -14,7 +14,8 @@ import org.apache.aut.vfs.FileSystemException; | |||
/** | |||
* A file system. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public interface FileSystem | |||
{ | |||
@@ -25,6 +25,9 @@ public interface FileSystemProvider | |||
/** | |||
* Sets the context for this file system provider. This method is called | |||
* before any of the other provider methods. | |||
* | |||
* @todo - move this to a lifecycle interface (this interface is accessable to | |||
* other providers, so need to prevent this being called). | |||
*/ | |||
void setContext( FileSystemProviderContext context ); | |||
@@ -29,12 +29,7 @@ public interface FileSystemProviderContext | |||
throws FileSystemException; | |||
/** | |||
* Locates a cached file system by root URI. | |||
* Locates a file replicator for the provider to use. | |||
*/ | |||
FileSystem getFileSystem( String rootURI ); | |||
/** | |||
* Registers a file system for caching. | |||
*/ | |||
void putFileSystem( String rootURI, FileSystem fs ) throws FileSystemException; | |||
FileReplicator getReplicator() throws FileSystemException; | |||
} |
@@ -0,0 +1,43 @@ | |||
/* | |||
* Copyright (C) The Apache Software Foundation. All rights reserved. | |||
* | |||
* This software is published under the terms of the Apache Software License | |||
* version 1.1, a copy of which has been included with this distribution in | |||
* the LICENSE.txt file. | |||
*/ | |||
package org.apache.aut.vfs.provider; | |||
import java.io.File; | |||
import org.apache.aut.vfs.FileObject; | |||
import org.apache.aut.vfs.FileSystemException; | |||
/** | |||
* A file system provider which handles local file systems. | |||
* | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public interface LocalFileSystemProvider | |||
extends FileSystemProvider | |||
{ | |||
/** | |||
* Determines if a name is an absolute file name. | |||
* | |||
* @todo Move this to a general file name parser interface. | |||
* | |||
* @param name The name to test. | |||
*/ | |||
boolean isAbsoluteLocalName( final String name ); | |||
/** | |||
* Finds a local file, from its local name. | |||
*/ | |||
FileObject findLocalFile( final String name ) | |||
throws FileSystemException; | |||
/** | |||
* Finds a local file. | |||
*/ | |||
FileObject findLocalFile( final File file ) | |||
throws FileSystemException; | |||
} |
@@ -10,7 +10,8 @@ package org.apache.aut.vfs.provider; | |||
/** | |||
* A data container for information parsed from an absolute URI. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public class ParsedUri | |||
{ | |||
@@ -18,6 +18,8 @@ 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}". | |||
copy-read-only.error=Could not copy {0} "{1}" to "{2}" because the destination file is read-only. | |||
copy-missing-file.error=Could not copy "{0}" because is does not exist. | |||
# DefaultFileContent | |||
get-size-no-exist.error=Could not determine the size of file "{0}" because it does not exist. | |||
@@ -17,7 +17,8 @@ import org.apache.avalon.excalibur.i18n.Resources; | |||
/** | |||
* A name parser which parses absolute URIs. See RFC 2396 for details. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public class UriParser | |||
{ | |||
@@ -452,6 +453,17 @@ public class UriParser | |||
throw new FileSystemException( message ); | |||
} | |||
} | |||
else if( scope == NameScope.DESCENDENT_OR_SELF ) | |||
{ | |||
final int baseLen = baseFile.length(); | |||
if( !resolvedPath.startsWith( baseFile ) | |||
|| ( resolvedPath.length() != baseLen | |||
&& resolvedPath.charAt( baseLen ) != m_separatorChar ) ) | |||
{ | |||
final String message = REZ.getString( "invalid-descendent-name.error", path ); | |||
throw new FileSystemException( message ); | |||
} | |||
} | |||
else if( scope != NameScope.FILE_SYSTEM ) | |||
{ | |||
throw new IllegalArgumentException(); | |||
@@ -482,6 +494,67 @@ public class UriParser | |||
return path.substring( 0, idx ); | |||
} | |||
/** | |||
* Converts an absolute path into a relative path. | |||
* | |||
* @param basePath The base path. | |||
* @param path The path to convert. | |||
*/ | |||
public String makeRelative( final String basePath, final String path ) | |||
{ | |||
// Calculate the common prefix | |||
final int basePathLen = basePath.length(); | |||
final int pathLen = path.length(); | |||
// Deal with root | |||
if( basePathLen == 1 && pathLen == 1 ) | |||
{ | |||
return "."; | |||
} | |||
else if( basePathLen == 1 ) | |||
{ | |||
return path.substring( 1 ); | |||
} | |||
final int maxlen = Math.min( basePathLen, pathLen ); | |||
int pos = 0; | |||
for( ; pos < maxlen && basePath.charAt( pos ) == path.charAt( pos ); pos++ ) | |||
{ | |||
} | |||
if( pos == basePathLen && pos == pathLen ) | |||
{ | |||
// Same names | |||
return "."; | |||
} | |||
else if( pos == basePathLen && pos < pathLen && path.charAt( pos ) == m_separatorChar ) | |||
{ | |||
// A descendent of the base path | |||
return path.substring( pos + 1 ); | |||
} | |||
// Strip the common prefix off the path | |||
final StringBuffer buffer = new StringBuffer(); | |||
if( pathLen > 1 && ( pos < pathLen || basePath.charAt( pos ) != m_separatorChar ) ) | |||
{ | |||
// Not a direct ancestor, need to back up | |||
pos = basePath.lastIndexOf( m_separatorChar, pos ); | |||
buffer.append( path.substring( pos ) ); | |||
} | |||
// Prepend a '../' for each element in the base path past the common | |||
// prefix | |||
buffer.insert( 0, ".." ); | |||
pos = basePath.indexOf( m_separatorChar, pos + 1 ); | |||
while( pos != -1 ) | |||
{ | |||
buffer.insert( 0, "../" ); | |||
pos = basePath.indexOf( m_separatorChar, pos + 1 ); | |||
} | |||
return buffer.toString(); | |||
} | |||
/** | |||
* Normalises a path. Does the following: | |||
* <ul> | |||
@@ -13,7 +13,8 @@ import org.apache.aut.vfs.provider.UriParser; | |||
/** | |||
* A parser for FTP URI. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public class FtpFileNameParser extends UriParser | |||
{ | |||
@@ -7,6 +7,7 @@ | |||
*/ | |||
package org.apache.aut.vfs.provider.ftp; | |||
import com.oroinc.net.ftp.FTPClient; | |||
import com.oroinc.net.ftp.FTPFile; | |||
import java.io.InputStream; | |||
import java.io.OutputStream; | |||
@@ -20,7 +21,8 @@ import org.apache.avalon.excalibur.i18n.Resources; | |||
/** | |||
* An FTP file. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
class FtpFileObject | |||
extends AbstractFileObject | |||
@@ -160,7 +162,17 @@ class FtpFileObject | |||
*/ | |||
protected void doDelete() throws Exception | |||
{ | |||
if( !m_ftpFs.getClient().deleteFile( getName().getPath() ) ) | |||
final FTPClient ftpClient = m_ftpFs.getClient(); | |||
boolean ok; | |||
if( m_fileInfo.isDirectory() ) | |||
{ | |||
ok = ftpClient.removeDirectory( getName().getPath() ); | |||
} | |||
else | |||
{ | |||
ok = ftpClient.deleteFile( getName().getPath() ); | |||
} | |||
if( !ok ) | |||
{ | |||
final String message = REZ.getString( "delete-file.error", getName() ); | |||
throw new FileSystemException( message ); | |||
@@ -15,13 +15,15 @@ import org.apache.aut.vfs.FileName; | |||
import org.apache.aut.vfs.FileObject; | |||
import org.apache.aut.vfs.FileSystemException; | |||
import org.apache.aut.vfs.provider.AbstractFileSystem; | |||
import org.apache.aut.vfs.provider.FileSystemProviderContext; | |||
import org.apache.avalon.excalibur.i18n.ResourceManager; | |||
import org.apache.avalon.excalibur.i18n.Resources; | |||
/** | |||
* An FTP file system. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
class FtpFileSystem | |||
extends AbstractFileSystem | |||
@@ -31,13 +33,14 @@ class FtpFileSystem | |||
private FTPClient m_client; | |||
public FtpFileSystem( final FileName rootName, | |||
public FtpFileSystem( final FileSystemProviderContext context, | |||
final FileName rootName, | |||
final String hostname, | |||
final String username, | |||
final String password ) | |||
throws FileSystemException | |||
{ | |||
super( rootName ); | |||
super( context, rootName ); | |||
try | |||
{ | |||
m_client = new FTPClient(); | |||
@@ -64,26 +67,39 @@ class FtpFileSystem | |||
throw new FileSystemException( message ); | |||
} | |||
} | |||
catch( Exception exc ) | |||
catch( final Exception exc ) | |||
{ | |||
try | |||
{ | |||
// Clean up | |||
if( m_client.isConnected() ) | |||
{ | |||
m_client.disconnect(); | |||
} | |||
} | |||
catch( IOException e ) | |||
{ | |||
// Ignore | |||
} | |||
closeConnection(); | |||
final String message = REZ.getString( "connect.error", hostname ); | |||
throw new FileSystemException( message, exc ); | |||
} | |||
} | |||
// TODO - close connection | |||
public void dispose() | |||
{ | |||
// Clean up the connection | |||
super.dispose(); | |||
closeConnection(); | |||
} | |||
/** | |||
* Cleans up the connection to the server. | |||
*/ | |||
private void closeConnection() | |||
{ | |||
try | |||
{ | |||
// Clean up | |||
if( m_client.isConnected() ) | |||
{ | |||
m_client.disconnect(); | |||
} | |||
} | |||
catch( final IOException e ) | |||
{ | |||
final String message = REZ.getString( "close-connection.error" ); | |||
getLogger().warn( message, e ); | |||
} | |||
} | |||
/** | |||
@@ -91,6 +107,7 @@ class FtpFileSystem | |||
*/ | |||
public FTPClient getClient() | |||
{ | |||
// TODO - connect on demand, and garbage collect connections | |||
return m_client; | |||
} | |||
@@ -18,7 +18,8 @@ import org.apache.aut.vfs.provider.ParsedUri; | |||
/** | |||
* A provider for FTP file systems. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
* | |||
* @ant.type type="file-system" name="ftp" | |||
*/ | |||
@@ -60,6 +61,6 @@ public class FtpFileSystemProvider extends AbstractFileSystemProvider | |||
} | |||
// Create the file system | |||
return new FtpFileSystem( rootName, ftpUri.getHostName(), username, password ); | |||
return new FtpFileSystem( getContext(), rootName, ftpUri.getHostName(), username, password ); | |||
} | |||
} |
@@ -12,7 +12,8 @@ import org.apache.aut.vfs.provider.ParsedUri; | |||
/** | |||
* A parsed FTP URI. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public class ParsedFtpUri extends ParsedUri | |||
{ | |||
@@ -6,4 +6,5 @@ finish-put.error=Could not put FTP file "{0}". | |||
connect-rejected.error=Connection to FTP server on "{0}" rejected. | |||
login.error=Could not login to FTP server on "{0}" as user "{1}". | |||
set-binary.error=Could not switch to binary transfer mode. | |||
connect.error=Could not connect to FTP server on "{0}". | |||
connect.error=Could not connect to FTP server on "{0}". | |||
close-connection.error=Could not close connection to FTP server. |
@@ -14,7 +14,7 @@ import org.apache.aut.vfs.FileSystemException; | |||
import org.apache.aut.vfs.provider.AbstractFileSystemProvider; | |||
import org.apache.aut.vfs.provider.DefaultFileName; | |||
import org.apache.aut.vfs.provider.FileSystem; | |||
import org.apache.aut.vfs.provider.FileSystemProvider; | |||
import org.apache.aut.vfs.provider.LocalFileSystemProvider; | |||
import org.apache.aut.vfs.provider.ParsedUri; | |||
/** | |||
@@ -22,13 +22,17 @@ import org.apache.aut.vfs.provider.ParsedUri; | |||
* | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
* | |||
* @ant.type type="file-system" name="file" | |||
* | |||
*/ | |||
public class LocalFileSystemProvider extends AbstractFileSystemProvider | |||
implements FileSystemProvider | |||
public class DefaultLocalFileSystemProvider | |||
extends AbstractFileSystemProvider | |||
implements LocalFileSystemProvider | |||
{ | |||
private final LocalFileNameParser m_parser; | |||
public LocalFileSystemProvider() | |||
public DefaultLocalFileSystemProvider() | |||
{ | |||
if( Os.isFamily( Os.OS_FAMILY_WINDOWS ) ) | |||
{ | |||
@@ -96,6 +100,6 @@ public class LocalFileSystemProvider extends AbstractFileSystemProvider | |||
// Create the file system | |||
final DefaultFileName rootName = new DefaultFileName( m_parser, fileUri.getRootUri(), "/" ); | |||
return new LocalFileSystem( rootName, rootFile ); | |||
return new LocalFileSystem( getContext(), rootName, rootFile ); | |||
} | |||
} |
@@ -14,6 +14,7 @@ import java.io.InputStream; | |||
import java.io.OutputStream; | |||
import org.apache.aut.vfs.FileName; | |||
import org.apache.aut.vfs.FileObject; | |||
import org.apache.aut.vfs.FileSelector; | |||
import org.apache.aut.vfs.FileSystemException; | |||
import org.apache.aut.vfs.FileType; | |||
import org.apache.aut.vfs.provider.AbstractFileObject; | |||
@@ -94,7 +95,7 @@ final class LocalFile | |||
/** | |||
* Deletes this file, and all children. | |||
*/ | |||
public void doDelete() | |||
protected void doDelete() | |||
throws Exception | |||
{ | |||
if( !m_file.delete() ) | |||
@@ -143,4 +144,13 @@ final class LocalFile | |||
{ | |||
return m_file.length(); | |||
} | |||
/** | |||
* Creates a temporary local copy of this file, and its descendents. | |||
*/ | |||
protected File doReplicateFile( final FileSelector selector ) | |||
throws FileSystemException | |||
{ | |||
return m_file; | |||
} | |||
} |
@@ -13,6 +13,7 @@ import org.apache.aut.vfs.FileSystemException; | |||
import org.apache.aut.vfs.provider.AbstractFileSystem; | |||
import org.apache.aut.vfs.provider.DefaultFileName; | |||
import org.apache.aut.vfs.provider.FileSystem; | |||
import org.apache.aut.vfs.provider.FileSystemProviderContext; | |||
/** | |||
* A local file system. | |||
@@ -24,19 +25,21 @@ class LocalFileSystem extends AbstractFileSystem implements FileSystem | |||
{ | |||
private String m_rootFile; | |||
public LocalFileSystem( DefaultFileName rootName, String rootFile ) | |||
public LocalFileSystem( final FileSystemProviderContext context, | |||
final DefaultFileName rootName, | |||
final String rootFile ) | |||
{ | |||
super( rootName ); | |||
super( context, rootName ); | |||
m_rootFile = rootFile; | |||
} | |||
/** | |||
* Creates a file object. | |||
*/ | |||
protected FileObject createFile( FileName name ) throws FileSystemException | |||
protected FileObject createFile( final FileName name ) throws FileSystemException | |||
{ | |||
// Create the file | |||
String fileName = m_rootFile + name.getPath(); | |||
final String fileName = m_rootFile + name.getPath(); | |||
return new LocalFile( this, fileName, name ); | |||
} | |||
} |
@@ -12,7 +12,8 @@ import org.apache.aut.vfs.provider.ParsedUri; | |||
/** | |||
* A parsed SMB URI. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public class ParsedSmbUri extends ParsedUri | |||
{ | |||
@@ -16,7 +16,8 @@ import org.apache.avalon.excalibur.i18n.Resources; | |||
/** | |||
* A parser for SMB URI. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public class SmbFileNameParser | |||
extends UriParser | |||
@@ -23,7 +23,8 @@ import org.apache.avalon.excalibur.i18n.Resources; | |||
/** | |||
* A file in an SMB file system. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public class SmbFileObject | |||
extends AbstractFileObject | |||
@@ -12,25 +12,28 @@ import org.apache.aut.vfs.FileObject; | |||
import org.apache.aut.vfs.FileSystemException; | |||
import org.apache.aut.vfs.provider.AbstractFileSystem; | |||
import org.apache.aut.vfs.provider.FileSystem; | |||
import org.apache.aut.vfs.provider.FileSystemProviderContext; | |||
/** | |||
* A SMB file system. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public class SmbFileSystem extends AbstractFileSystem implements FileSystem | |||
{ | |||
public SmbFileSystem( FileName rootName ) | |||
public SmbFileSystem( final FileSystemProviderContext context, | |||
final FileName rootName ) | |||
{ | |||
super( rootName ); | |||
super( context, rootName ); | |||
} | |||
/** | |||
* Creates a file object. | |||
*/ | |||
protected FileObject createFile( FileName name ) throws FileSystemException | |||
protected FileObject createFile( final FileName name ) throws FileSystemException | |||
{ | |||
String fileName = name.getURI(); | |||
final String fileName = name.getURI(); | |||
return new SmbFileObject( fileName, name, this ); | |||
} | |||
} |
@@ -19,7 +19,8 @@ import org.apache.aut.vfs.provider.ParsedUri; | |||
/** | |||
* A provider for SMB (Samba, Windows share) file systems. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
* | |||
* @ant.type type="file-system" name="smb" | |||
*/ | |||
@@ -45,6 +46,6 @@ public class SmbFileSystemProvider extends AbstractFileSystemProvider implements | |||
{ | |||
final ParsedSmbUri smbUri = (ParsedSmbUri)uri; | |||
final FileName rootName = new DefaultFileName( m_parser, smbUri.getRootUri(), "/" ); | |||
return new SmbFileSystem( rootName ); | |||
return new SmbFileSystem( getContext(), rootName ); | |||
} | |||
} |
@@ -13,7 +13,8 @@ import org.apache.aut.vfs.provider.ParsedUri; | |||
/** | |||
* A parsed Zip URI. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public class ParsedZipUri extends ParsedUri | |||
{ | |||
@@ -1 +1,2 @@ | |||
open-zip-file.error=Could not open Zip file "{0}". | |||
close-zip-file.error=Could not close Zip file "{0}". |
@@ -13,12 +13,13 @@ import org.apache.aut.vfs.provider.UriParser; | |||
/** | |||
* A parser for Zip file names. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public class ZipFileNameParser | |||
extends UriParser | |||
{ | |||
private static final char[] ZIP_URL_RESERVED_CHARS = { '!' }; | |||
private static final char[] ZIP_URL_RESERVED_CHARS = {'!'}; | |||
/** | |||
* Parses an absolute URI, splitting it into its components. | |||
@@ -19,7 +19,8 @@ import org.apache.aut.vfs.provider.AbstractFileObject; | |||
/** | |||
* A file in a Zip file system. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
class ZipFileObject extends AbstractFileObject implements FileObject | |||
{ | |||
@@ -18,15 +18,19 @@ import org.apache.aut.vfs.FileSystemException; | |||
import org.apache.aut.vfs.provider.AbstractFileSystem; | |||
import org.apache.aut.vfs.provider.DefaultFileName; | |||
import org.apache.aut.vfs.provider.FileSystem; | |||
import org.apache.aut.vfs.provider.FileSystemProviderContext; | |||
import org.apache.avalon.excalibur.i18n.ResourceManager; | |||
import org.apache.avalon.excalibur.i18n.Resources; | |||
/** | |||
* A read-only file system for Zip/Jar files. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public class ZipFileSystem extends AbstractFileSystem implements FileSystem | |||
public class ZipFileSystem | |||
extends AbstractFileSystem | |||
implements FileSystem | |||
{ | |||
private static final Resources REZ = | |||
ResourceManager.getPackageResources( ZipFileSystem.class ); | |||
@@ -34,9 +38,11 @@ public class ZipFileSystem extends AbstractFileSystem implements FileSystem | |||
private File m_file; | |||
private ZipFile m_zipFile; | |||
public ZipFileSystem( DefaultFileName rootName, File file ) throws FileSystemException | |||
public ZipFileSystem( final FileSystemProviderContext context, | |||
final DefaultFileName rootName, | |||
final File file ) throws FileSystemException | |||
{ | |||
super( rootName ); | |||
super( context, rootName ); | |||
m_file = file; | |||
// Open the Zip file | |||
@@ -101,6 +107,22 @@ public class ZipFileSystem extends AbstractFileSystem implements FileSystem | |||
} | |||
} | |||
public void dispose() | |||
{ | |||
super.dispose(); | |||
// Release the zip file | |||
try | |||
{ | |||
m_zipFile.close(); | |||
} | |||
catch( final IOException e ) | |||
{ | |||
final String message = REZ.getString( "close-zip-file.error", m_file ); | |||
getLogger().warn( message, e ); | |||
} | |||
} | |||
/** | |||
* Creates a file object. | |||
*/ | |||
@@ -8,7 +8,7 @@ | |||
package org.apache.aut.vfs.provider.zip; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import org.apache.aut.vfs.FileConstants; | |||
import org.apache.aut.vfs.FileObject; | |||
import org.apache.aut.vfs.FileSystemException; | |||
import org.apache.aut.vfs.provider.AbstractFileSystemProvider; | |||
@@ -21,7 +21,8 @@ import org.apache.aut.vfs.provider.ParsedUri; | |||
* A file system provider for Zip/Jar files. Provides read-only file | |||
* systems, for local Zip files only. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
* | |||
* @ant.type type="file-system" name="zip" | |||
*/ | |||
@@ -80,25 +81,12 @@ public class ZipFileSystemProvider | |||
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 ); | |||
// Make a local copy of the file | |||
final File zipFile = file.replicateFile( FileConstants.SELECT_SELF ); | |||
// Create the file system | |||
DefaultFileName name = new DefaultFileName( m_parser, zipUri.getRootUri(), "/" ); | |||
return new ZipFileSystem( name, destFile ); | |||
return new ZipFileSystem( getContext(), name, zipFile ); | |||
} | |||
} |
@@ -12,7 +12,6 @@ import org.apache.aut.vfs.impl.DefaultFileSystemManager; | |||
import org.apache.aut.vfs.provider.FileSystemProvider; | |||
import org.apache.avalon.excalibur.i18n.ResourceManager; | |||
import org.apache.avalon.excalibur.i18n.Resources; | |||
import org.apache.avalon.framework.activity.Disposable; | |||
import org.apache.avalon.framework.activity.Initializable; | |||
import org.apache.avalon.framework.service.ServiceException; | |||
import org.apache.avalon.framework.service.ServiceManager; | |||
@@ -28,7 +27,7 @@ import org.apache.myrmidon.interfaces.type.TypeManager; | |||
*/ | |||
public class VfsManager | |||
extends DefaultFileSystemManager | |||
implements Serviceable, Initializable, Disposable | |||
implements Serviceable, Initializable | |||
{ | |||
private static final Resources REZ | |||
= ResourceManager.getPackageResources( VfsManager.class ); | |||
@@ -53,6 +52,7 @@ public class VfsManager | |||
// TODO - make this list configurable | |||
// Required providers | |||
addProvider( factory, new String[]{"file"}, "file", false ); | |||
addProvider( factory, new String[]{"zip", "jar"}, "zip", false ); | |||
// Optional providers | |||
@@ -60,15 +60,6 @@ public class VfsManager | |||
addProvider( factory, new String[]{"ftp"}, "ftp", true ); | |||
} | |||
/** | |||
* Disposes this service. | |||
*/ | |||
public void dispose() | |||
{ | |||
// Clean-up | |||
close(); | |||
} | |||
/** | |||
* Registers a file system provider. | |||
*/ | |||
@@ -16,6 +16,8 @@ import java.util.List; | |||
import java.util.Map; | |||
import org.apache.aut.vfs.impl.DefaultFileSystemManager; | |||
import org.apache.aut.vfs.provider.AbstractFileObject; | |||
import org.apache.aut.vfs.provider.LocalFileSystemProvider; | |||
import org.apache.aut.vfs.provider.local.DefaultLocalFileSystemProvider; | |||
import org.apache.aut.vfs.FileObject; | |||
import org.apache.aut.vfs.FileType; | |||
import org.apache.aut.vfs.FileSystemException; | |||
@@ -59,15 +61,15 @@ public abstract class AbstractFileSystemTestCase | |||
{ | |||
// Build the expected structure | |||
final FileInfo base = new FileInfo( "test", FileType.FOLDER ); | |||
base.addChild( new FileInfo( "file1.txt", FileType.FILE ) ); | |||
base.addChild( new FileInfo( "empty.txt", FileType.FILE ) ); | |||
base.addChild( new FileInfo( "emptydir", FileType.FOLDER ) ); | |||
base.addChild( "file1.txt", FileType.FILE ); | |||
base.addChild( "empty.txt", FileType.FILE ); | |||
base.addChild( "emptydir", FileType.FOLDER ); | |||
final FileInfo dir = new FileInfo( "dir1", FileType.FOLDER ); | |||
base.addChild( dir ); | |||
dir.addChild( new FileInfo( "file1.txt", FileType.FILE ) ); | |||
dir.addChild( new FileInfo( "file2.txt", FileType.FILE ) ); | |||
dir.addChild( new FileInfo( "file3.txt", FileType.FILE ) ); | |||
dir.addChild( "file1.txt", FileType.FILE ); | |||
dir.addChild( "file2.txt", FileType.FILE ); | |||
dir.addChild( "file3.txt", FileType.FILE ); | |||
return base; | |||
} | |||
@@ -83,6 +85,8 @@ public abstract class AbstractFileSystemTestCase | |||
{ | |||
// Create the file system manager | |||
m_manager = new DefaultFileSystemManager(); | |||
m_manager.enableLogging( getLogger() ); | |||
m_manager.addProvider( "file", new DefaultLocalFileSystemProvider() ); | |||
// Locate the base folder | |||
m_baseFolder = getBaseFolder(); | |||
@@ -100,7 +104,7 @@ public abstract class AbstractFileSystemTestCase | |||
*/ | |||
protected void tearDown() throws Exception | |||
{ | |||
m_manager.close(); | |||
m_manager.dispose(); | |||
} | |||
/** | |||
@@ -465,6 +469,70 @@ public abstract class AbstractFileSystemTestCase | |||
} | |||
} | |||
/** | |||
* Tests conversion from absolute to relative names. | |||
*/ | |||
public void testAbsoluteNameConvert() throws Exception | |||
{ | |||
final FileName baseName = m_baseFolder.getName(); | |||
String path = "/test1/test2"; | |||
FileName name = baseName.resolveName( path ); | |||
assertEquals( path, name.getPath() ); | |||
// Try child and descendent names | |||
testRelName( name, "child" ); | |||
testRelName( name, "child1/child2" ); | |||
// Try own name | |||
testRelName( name, "." ); | |||
// Try parent, and root | |||
testRelName( name, ".." ); | |||
testRelName( name, "../.." ); | |||
// Try sibling and descendent of sibling | |||
testRelName( name, "../sibling" ); | |||
testRelName( name, "../sibling/child" ); | |||
// Try siblings with similar names | |||
testRelName( name, "../test2_not" ); | |||
testRelName( name, "../test2_not/child" ); | |||
testRelName( name, "../test" ); | |||
testRelName( name, "../test/child" ); | |||
// Try unrelated | |||
testRelName( name, "../../unrelated" ); | |||
testRelName( name, "../../test" ); | |||
testRelName( name, "../../test/child" ); | |||
// Test against root | |||
path = "/"; | |||
name = baseName.resolveName( path ); | |||
assertEquals( path, name.getPath() ); | |||
// Try child and descendent names (against root) | |||
testRelName( name, "child" ); | |||
testRelName( name, "child1/child2" ); | |||
// Try own name (against root) | |||
testRelName( name, "." ); | |||
} | |||
/** | |||
* Checks that a file name converts to an expected relative path | |||
*/ | |||
private void testRelName( final FileName baseName, | |||
final String relPath ) | |||
throws Exception | |||
{ | |||
final FileName expectedName = baseName.resolveName( relPath ); | |||
// Convert to relative path, and check | |||
final String actualRelPath = baseName.getRelativeName( expectedName ); | |||
assertEquals( relPath, actualRelPath ); | |||
} | |||
/** | |||
* Walks the base folder structure, asserting it contains exactly the | |||
* expected files and folders. | |||
@@ -496,9 +564,9 @@ public abstract class AbstractFileSystemTestCase | |||
final FileInfo info = (FileInfo)queueExpected.remove( 0 ); | |||
// Check the type is correct | |||
assertSame( file.getType(), info._type ); | |||
assertSame( file.getType(), info.m_type ); | |||
if( info._type == FileType.FILE ) | |||
if( info.m_type == FileType.FILE ) | |||
{ | |||
continue; | |||
} | |||
@@ -508,13 +576,13 @@ public abstract class AbstractFileSystemTestCase | |||
// Make sure all children were found | |||
assertNotNull( children ); | |||
assertEquals( "count children of \"" + file.getName() + "\"", info._children.size(), children.length ); | |||
assertEquals( "count children of \"" + file.getName() + "\"", info.m_children.size(), children.length ); | |||
// Recursively check each child | |||
for( int i = 0; i < children.length; i++ ) | |||
{ | |||
final FileObject child = children[ i ]; | |||
final FileInfo childInfo = (FileInfo)info._children.get( child.getName().getBaseName() ); | |||
final FileInfo childInfo = (FileInfo)info.m_children.get( child.getName().getBaseName() ); | |||
// Make sure the child is expected | |||
assertNotNull( childInfo ); | |||
@@ -776,20 +844,36 @@ public abstract class AbstractFileSystemTestCase | |||
*/ | |||
protected static final class FileInfo | |||
{ | |||
String _baseName; | |||
FileType _type; | |||
Map _children = new HashMap(); | |||
String m_baseName; | |||
FileType m_type; | |||
Map m_children = new HashMap(); | |||
public FileInfo( final String name, final FileType type ) | |||
{ | |||
m_baseName = name; | |||
m_type = type; | |||
} | |||
public FileInfo( String name, FileType type ) | |||
/** Adds a child. */ | |||
public void addChild( final FileInfo child ) | |||
{ | |||
_baseName = name; | |||
_type = type; | |||
m_children.put( child.m_baseName, child ); | |||
} | |||
/** Adds a child. */ | |||
public void addChild( FileInfo child ) | |||
public void addChild( final String baseName, final FileType type ) | |||
{ | |||
addChild( new FileInfo( baseName, type ) ); | |||
} | |||
/** Adds a bunch of children. */ | |||
public void addChildren( final String[] baseNames, final FileType type ) | |||
{ | |||
_children.put( child._baseName, child ); | |||
for( int i = 0; i < baseNames.length; i++ ) | |||
{ | |||
String baseName = baseNames[i ]; | |||
addChild( new FileInfo( baseName, type ) ); | |||
} | |||
} | |||
} | |||
} |
@@ -14,6 +14,7 @@ import org.apache.aut.vfs.test.AbstractFileSystemTestCase; | |||
import org.apache.aut.vfs.FileObject; | |||
import org.apache.aut.vfs.FileType; | |||
import org.apache.aut.vfs.FileSystemException; | |||
import org.apache.aut.vfs.FileConstants; | |||
/** | |||
* File system test that check that a file system can be modified. | |||
@@ -41,7 +42,7 @@ public abstract class AbstractWritableFileSystemTestCase | |||
FileObject scratchFolder = getWriteFolder(); | |||
// Make sure the test folder is empty | |||
scratchFolder.delete(); | |||
scratchFolder.delete( FileConstants.EXCLUDE_SELF ); | |||
scratchFolder.create( FileType.FOLDER ); | |||
return scratchFolder; | |||
@@ -59,6 +60,7 @@ public abstract class AbstractWritableFileSystemTestCase | |||
assertTrue( !folder.exists() ); | |||
folder.create( FileType.FOLDER ); | |||
assertTrue( folder.exists() ); | |||
assertSame( FileType.FOLDER, folder.getType() ); | |||
assertEquals( 0, folder.getChildren().length ); | |||
// Create a descendant, where the intermediate folders don't exist | |||
@@ -68,11 +70,13 @@ public abstract class AbstractWritableFileSystemTestCase | |||
assertTrue( !folder.getParent().getParent().exists() ); | |||
folder.create( FileType.FOLDER ); | |||
assertTrue( folder.exists() ); | |||
assertSame( FileType.FOLDER, folder.getType() ); | |||
assertEquals( 0, folder.getChildren().length ); | |||
assertTrue( folder.getParent().exists() ); | |||
assertTrue( folder.getParent().getParent().exists() ); | |||
// Test creating a folder that already exists | |||
assertTrue( folder.exists() ); | |||
folder.create( FileType.FOLDER ); | |||
} | |||
@@ -88,6 +92,7 @@ public abstract class AbstractWritableFileSystemTestCase | |||
assertTrue( !file.exists() ); | |||
file.create( FileType.FILE ); | |||
assertTrue( file.exists() ); | |||
assertSame( FileType.FILE, file.getType() ); | |||
assertEquals( 0, file.getContent().getSize() ); | |||
// Create a descendant, where the intermediate folders don't exist | |||
@@ -97,11 +102,13 @@ public abstract class AbstractWritableFileSystemTestCase | |||
assertTrue( !file.getParent().getParent().exists() ); | |||
file.create( FileType.FILE ); | |||
assertTrue( file.exists() ); | |||
assertSame( FileType.FILE, file.getType() ); | |||
assertEquals( 0, file.getContent().getSize() ); | |||
assertTrue( file.getParent().exists() ); | |||
assertTrue( file.getParent().getParent().exists() ); | |||
// Test creating a file that already exists | |||
assertTrue( file.exists() ); | |||
file.create( FileType.FILE ); | |||
} | |||
@@ -125,7 +132,7 @@ public abstract class AbstractWritableFileSystemTestCase | |||
try | |||
{ | |||
folder.create( FileType.FILE ); | |||
assertTrue( false ); | |||
fail(); | |||
} | |||
catch( FileSystemException exc ) | |||
{ | |||
@@ -135,7 +142,7 @@ public abstract class AbstractWritableFileSystemTestCase | |||
try | |||
{ | |||
file.create( FileType.FOLDER ); | |||
assertTrue( false ); | |||
fail(); | |||
} | |||
catch( FileSystemException exc ) | |||
{ | |||
@@ -146,7 +153,7 @@ public abstract class AbstractWritableFileSystemTestCase | |||
try | |||
{ | |||
folder2.create( FileType.FOLDER ); | |||
assertTrue( false ); | |||
fail(); | |||
} | |||
catch( FileSystemException exc ) | |||
{ | |||
@@ -168,13 +175,13 @@ public abstract class AbstractWritableFileSystemTestCase | |||
// Delete a file | |||
FileObject file = folder.resolveFile( "file1.txt" ); | |||
assertTrue( file.exists() ); | |||
file.delete(); | |||
file.delete( FileConstants.SELECT_ALL ); | |||
assertTrue( !file.exists() ); | |||
// Delete an empty folder | |||
file = folder.resolveFile( "emptydir" ); | |||
assertTrue( file.exists() ); | |||
file.delete(); | |||
file.delete( FileConstants.SELECT_ALL ); | |||
assertTrue( !file.exists() ); | |||
// Recursive delete | |||
@@ -182,14 +189,14 @@ public abstract class AbstractWritableFileSystemTestCase | |||
FileObject file2 = file.resolveFile( "dir2/file2.txt" ); | |||
assertTrue( file.exists() ); | |||
assertTrue( file2.exists() ); | |||
file.delete(); | |||
file.delete( FileConstants.SELECT_ALL ); | |||
assertTrue( !file.exists() ); | |||
assertTrue( !file2.exists() ); | |||
// Delete a file that does not exist | |||
file = folder.resolveFile( "some-folder/some-file" ); | |||
assertTrue( !file.exists() ); | |||
file.delete(); | |||
file.delete( FileConstants.SELECT_ALL ); | |||
assertTrue( !file.exists() ); | |||
} | |||
@@ -226,17 +233,17 @@ public abstract class AbstractWritableFileSystemTestCase | |||
assertSameFileSet( names, folder.getChildren() ); | |||
// Delete a child folder | |||
folder.resolveFile( "dir1" ).delete(); | |||
folder.resolveFile( "dir1" ).delete( FileConstants.SELECT_ALL ); | |||
names.remove( "dir1" ); | |||
assertSameFileSet( names, folder.getChildren() ); | |||
// Delete a child file | |||
folder.resolveFile( "file1.html" ).delete(); | |||
folder.resolveFile( "file1.html" ).delete( FileConstants.SELECT_ALL ); | |||
names.remove( "file1.html" ); | |||
assertSameFileSet( names, folder.getChildren() ); | |||
// Recreate the folder | |||
folder.delete(); | |||
folder.delete( FileConstants.SELECT_ALL ); | |||
folder.create( FileType.FOLDER ); | |||
assertEquals( 0, folder.getChildren().length ); | |||
} | |||
@@ -174,12 +174,6 @@ public abstract class AbstractMyrmidonTest | |||
*/ | |||
protected void assertSameMessage( final String[] messages, final Throwable throwable ) | |||
{ | |||
//System.out.println( "exception:" ); | |||
//for( Throwable t = throwable; t != null; t = ExceptionUtil.getCause( t, true ) ) | |||
//{ | |||
// System.out.println( " " + t.getMessage() ); | |||
//} | |||
Throwable current = throwable; | |||
for( int i = 0; i < messages.length; i++ ) | |||
{ | |||
@@ -10,6 +10,7 @@ package org.apache.myrmidon; | |||
import java.io.File; | |||
import org.apache.myrmidon.frontends.EmbeddedAnt; | |||
import org.apache.myrmidon.listeners.ProjectListener; | |||
import org.apache.avalon.framework.ExceptionUtil; | |||
/** | |||
* A base class for test cases which need to execute projects. | |||
@@ -72,28 +73,35 @@ public class AbstractProjectTest | |||
final ProjectListener listener ) | |||
throws Exception | |||
{ | |||
// Create the project and workspace | |||
final EmbeddedAnt embeddor = new EmbeddedAnt(); | |||
embeddor.setHomeDirectory( getInstallDirectory() ); | |||
embeddor.enableLogging( getLogger() ); | |||
embeddor.setSharedClassLoader( getClass().getClassLoader() ); | |||
embeddor.setProjectFile( projectFile.getAbsolutePath() ); | |||
embeddor.setProjectListener( null ); | |||
// Add a listener to make sure all is good | |||
final TrackingProjectListener tracker = new TrackingProjectListener(); | |||
embeddor.addProjectListener( tracker ); | |||
// Add supplied listener | |||
if( listener != null ) | |||
try | |||
{ | |||
embeddor.addProjectListener( listener ); | |||
} | |||
// Configure embeddor | |||
embeddor.setHomeDirectory( getInstallDirectory() ); | |||
embeddor.enableLogging( getLogger() ); | |||
embeddor.setSharedClassLoader( getClass().getClassLoader() ); | |||
embeddor.setProjectFile( projectFile.getAbsolutePath() ); | |||
embeddor.setProjectListener( null ); | |||
// Add a listener to make sure all is good | |||
embeddor.addProjectListener( tracker ); | |||
// Now execute the target | |||
embeddor.executeTargets( new String[] { targetName } ); | |||
// Add supplied listener | |||
if( listener != null ) | |||
{ | |||
embeddor.addProjectListener( listener ); | |||
} | |||
// Now execute the target | |||
embeddor.executeTargets( new String[] { targetName } ); | |||
} | |||
finally | |||
{ | |||
embeddor.stop(); | |||
} | |||
embeddor.stop(); | |||
// Make sure all expected events were delivered | |||
tracker.assertComplete(); | |||
@@ -335,7 +335,7 @@ public class DefaultClassLoaderManagerTestCase | |||
* add some classes to common loader only. | |||
* | |||
* unknown extension | |||
* multiple versions of extensions | |||
* multiple versions of the same extension | |||
* extn with requirement on itself | |||
* | |||
* jar with 1 and 2 extns: | |||