* Changed semantics of FileName.resolveName() with 'child' scope. * Fixed a couple of problems in UriParser.normalise(). * Split up LocalFileNameParser into a Windows specifc parser and a generic parser. * More test cases. git-svn-id: https://svn.apache.org/repos/asf/ant/core/trunk@271668 13f79535-47bb-0310-9956-ffa450edef68master
@@ -67,9 +67,8 @@ public interface FileName | |||
FileName resolveName( String path ) throws FileSystemException; | |||
/** | |||
* Resolves a name, relative to the file. Refer to {@link NameScope#CHILD} | |||
* and {@link NameScope#FILE_SYSTEM} for a description of how names are | |||
* resolved. | |||
* Resolves a name, relative to the file. Refer to {@link NameScope} | |||
* for a description of how names are resolved. | |||
* | |||
* @param name | |||
* The path to resolve. | |||
@@ -123,9 +123,8 @@ public interface FileObject | |||
FileObject[] getChildren() throws FileSystemException; | |||
/** | |||
* Finds a file, relative to this file. Refer to {@link NameScope#CHILD} | |||
* and {@link NameScope#FILE_SYSTEM} for a description of how names | |||
* are resolved in the different scopes. | |||
* Finds a file, relative to this file. Refer to {@link NameScope} | |||
* for a description of how names are resolved in the different scopes. | |||
* | |||
* @param name | |||
* The name to resolve. | |||
@@ -207,7 +206,7 @@ public interface FileObject | |||
* | |||
* @see FileContent#close | |||
* | |||
* @throws FileSystemEception | |||
* @throws FileSystemException | |||
* On error closing the file. | |||
*/ | |||
void close() throws FileSystemException; | |||
@@ -16,14 +16,19 @@ package org.apache.aut.vfs; | |||
public final class NameScope | |||
{ | |||
/** | |||
* Resolve against the children of the base file. | |||
* | |||
* <p>The supplied name must be a valid element name. That is, it may | |||
* not be empty, or <code>.</code>, or <code>..</code>, or contain any | |||
* separator characters. | |||
* Resolve against the children 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 direct child of the base file. | |||
*/ | |||
public final static NameScope CHILD = new NameScope( "child" ); | |||
/** | |||
* 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. | |||
*/ | |||
public final static NameScope DESCENDENT = new NameScope( "descendent" ); | |||
/** | |||
* Resolve against files in the same file system as the base file. | |||
* | |||
@@ -18,22 +18,39 @@ import org.apache.aut.vfs.NameScope; | |||
*/ | |||
public class DefaultFileName implements FileName | |||
{ | |||
private UriParser m_parser; | |||
private String m_rootPrefix; | |||
private String m_absPath; | |||
private final UriParser m_parser; | |||
private final String m_rootPrefix; | |||
private final String m_absPath; | |||
// Cached stuff | |||
private String m_uri; | |||
private String m_baseName; | |||
public DefaultFileName( UriParser parser, String rootPrefix, String absPath ) | |||
public DefaultFileName( final UriParser parser, | |||
final String rootPrefix, | |||
final String absPath ) | |||
{ | |||
m_parser = parser; | |||
m_rootPrefix = rootPrefix; | |||
m_absPath = absPath; | |||
} | |||
// TODO - make these usable as hash keys | |||
/** | |||
* Returns the hashcode for this name. | |||
*/ | |||
public int hashCode() | |||
{ | |||
return ( m_rootPrefix.hashCode() ^ m_absPath.hashCode() ); | |||
} | |||
/** | |||
* Determines if this object is equal to another. | |||
*/ | |||
public boolean equals( final Object obj ) | |||
{ | |||
final DefaultFileName name = (DefaultFileName)obj; | |||
return ( m_rootPrefix.equals( name.m_rootPrefix ) && m_absPath.equals( m_absPath ) ); | |||
} | |||
/** | |||
* Returns the URI of the file. | |||
@@ -67,22 +84,12 @@ public class DefaultFileName implements FileName | |||
/** | |||
* Returns the name of a child of the file. | |||
*/ | |||
public FileName resolveName( String name, NameScope scope ) throws FileSystemException | |||
public FileName resolveName( final String name, | |||
final NameScope scope ) | |||
throws FileSystemException | |||
{ | |||
if( scope == NameScope.CHILD ) | |||
{ | |||
String childPath = m_parser.getChildPath( m_absPath, name ); | |||
return new DefaultFileName( m_parser, m_rootPrefix, childPath ); | |||
} | |||
else if( scope == NameScope.FILE_SYSTEM ) | |||
{ | |||
String absPath = m_parser.resolvePath( m_absPath, name ); | |||
return new DefaultFileName( m_parser, m_rootPrefix, absPath ); | |||
} | |||
else | |||
{ | |||
throw new IllegalArgumentException(); | |||
} | |||
final String absPath = m_parser.resolvePath( m_absPath, name, scope ); | |||
return new DefaultFileName( m_parser, m_rootPrefix, absPath ); | |||
} | |||
/** | |||
@@ -90,7 +97,7 @@ public class DefaultFileName implements FileName | |||
*/ | |||
public FileName getParent() | |||
{ | |||
String parentPath = m_parser.getParentPath( m_absPath ); | |||
final String parentPath = m_parser.getParentPath( m_absPath ); | |||
if( parentPath == null ) | |||
{ | |||
return null; | |||
@@ -104,7 +111,7 @@ public class DefaultFileName implements FileName | |||
* file system that the file belongs to. If a relative name is supplied, | |||
* then it is resolved relative to this file name. | |||
*/ | |||
public FileName resolveName( String path ) throws FileSystemException | |||
public FileName resolveName( final String path ) throws FileSystemException | |||
{ | |||
return resolveName( path, NameScope.FILE_SYSTEM ); | |||
} | |||
@@ -37,3 +37,4 @@ missing-hostname.error=Hostname missing from URI "{0}". | |||
missing-port.error=Port number is missing from URI "{0}". | |||
missing-hostname-path-sep.error=Expecting / to follow the hostname in URI "{0}". | |||
invalid-childname.error=Invalid file base-name "{0}". | |||
invalid-descendent-name.error=Invalid descendent file name "{0}". |
@@ -10,6 +10,7 @@ package org.apache.aut.vfs.provider; | |||
import java.util.HashSet; | |||
import java.util.Iterator; | |||
import org.apache.aut.vfs.FileSystemException; | |||
import org.apache.aut.vfs.NameScope; | |||
import org.apache.avalon.excalibur.i18n.ResourceManager; | |||
import org.apache.avalon.excalibur.i18n.Resources; | |||
@@ -24,14 +25,14 @@ public class UriParser | |||
ResourceManager.getPackageResources( UriParser.class ); | |||
/** The normalised separator to use. */ | |||
private char m_separatorChar; | |||
private String m_separator; | |||
private final char m_separatorChar; | |||
private final String m_separator; | |||
/** | |||
* The set of valid separators. These are all converted to the normalised one. | |||
* Does <i>not</i> contain the normalised separator | |||
*/ | |||
private char[] m_separators; | |||
private final char[] m_separators; | |||
/** | |||
* Creates a parser, using '/' and '\' as the path separators. | |||
@@ -49,12 +50,12 @@ public class UriParser | |||
* Additional legal separator characters. Any occurrences of | |||
* these in paths are replaced with the separator char. | |||
*/ | |||
protected UriParser( char[] separators ) | |||
protected UriParser( final char[] separators ) | |||
{ | |||
m_separatorChar = '/'; | |||
// Remove the separator char from the separators array | |||
HashSet set = new HashSet(); | |||
final HashSet set = new HashSet(); | |||
set.add( new Character( '\\' ) ); | |||
if( separators != null ) | |||
{ | |||
@@ -69,10 +70,10 @@ public class UriParser | |||
} | |||
} | |||
m_separators = new char[ set.size() ]; | |||
Iterator iter = set.iterator(); | |||
final Iterator iter = set.iterator(); | |||
for( int i = 0; i < m_separators.length; i++ ) | |||
{ | |||
Character ch = (Character)iter.next(); | |||
final Character ch = (Character)iter.next(); | |||
m_separators[ i ] = ch.charValue(); | |||
} | |||
@@ -86,9 +87,9 @@ public class UriParser | |||
* | |||
* <p>Sub-classes should override this method. | |||
*/ | |||
public ParsedUri parseUri( String uriStr ) throws FileSystemException | |||
public ParsedUri parseUri( final String uriStr ) throws FileSystemException | |||
{ | |||
ParsedUri retval = new ParsedUri(); | |||
final ParsedUri retval = new ParsedUri(); | |||
parseGenericUri( uriStr, retval ); | |||
return retval; | |||
} | |||
@@ -109,9 +110,11 @@ public class UriParser | |||
* @param uri | |||
* Used to return the parsed components of the URI. | |||
*/ | |||
protected void parseGenericUri( String uriStr, ParsedUri uri ) throws FileSystemException | |||
protected void parseGenericUri( final String uriStr, | |||
final ParsedUri uri ) | |||
throws FileSystemException | |||
{ | |||
StringBuffer name = new StringBuffer(); | |||
final StringBuffer name = new StringBuffer(); | |||
// Extract the scheme and authority parts | |||
extractToPath( uriStr, name, uri ); | |||
@@ -121,7 +124,7 @@ public class UriParser | |||
uri.setPath( name.toString() ); | |||
// Build the root uri | |||
StringBuffer rootUri = new StringBuffer(); | |||
final StringBuffer rootUri = new StringBuffer(); | |||
rootUri.append( uri.getScheme() ); | |||
rootUri.append( "://" ); | |||
rootUri.append( uri.getHostName() ); | |||
@@ -141,11 +144,13 @@ public class UriParser | |||
* @parsedUri | |||
* Used to return the extracted components. | |||
*/ | |||
protected void extractToPath( String uri, StringBuffer name, ParsedUri parsedUri ) | |||
protected void extractToPath( final String uri, | |||
final StringBuffer name, | |||
final ParsedUri parsedUri ) | |||
throws FileSystemException | |||
{ | |||
// Extract the scheme | |||
String scheme = extractScheme( uri, name ); | |||
final String scheme = extractScheme( uri, name ); | |||
parsedUri.setScheme( scheme ); | |||
// Expecting "//" | |||
@@ -157,11 +162,11 @@ public class UriParser | |||
name.delete( 0, 2 ); | |||
// Extract userinfo | |||
String userInfo = extractUserInfo( name ); | |||
final String userInfo = extractUserInfo( name ); | |||
parsedUri.setUserInfo( userInfo ); | |||
// Extract hostname | |||
String hostName = extractHostName( name ); | |||
final String hostName = extractHostName( name ); | |||
if( hostName == null ) | |||
{ | |||
final String message = REZ.getString( "missing-hostname.error", uri ); | |||
@@ -170,7 +175,7 @@ public class UriParser | |||
parsedUri.setHostName( hostName ); | |||
// Extract port | |||
String port = extractPort( name ); | |||
final String port = extractPort( name ); | |||
if( port != null && port.length() == 0 ) | |||
{ | |||
final String message = REZ.getString( "missing-port.error", uri ); | |||
@@ -190,12 +195,12 @@ public class UriParser | |||
* Extracts the user info from a URI. The <scheme>:// part has been removed | |||
* already. | |||
*/ | |||
protected String extractUserInfo( StringBuffer name ) | |||
protected String extractUserInfo( final StringBuffer name ) | |||
{ | |||
int maxlen = name.length(); | |||
final int maxlen = name.length(); | |||
for( int pos = 0; pos < maxlen; pos++ ) | |||
{ | |||
char ch = name.charAt( pos ); | |||
final char ch = name.charAt( pos ); | |||
if( ch == '@' ) | |||
{ | |||
// Found the end of the user info | |||
@@ -218,13 +223,13 @@ public class UriParser | |||
* Extracts the hostname from a URI. The <scheme>://<userinfo>@ part has | |||
* been removed. | |||
*/ | |||
protected String extractHostName( StringBuffer name ) | |||
protected String extractHostName( final StringBuffer name ) | |||
{ | |||
int maxlen = name.length(); | |||
final int maxlen = name.length(); | |||
int pos = 0; | |||
for( ; pos < maxlen; pos++ ) | |||
{ | |||
char ch = name.charAt( pos ); | |||
final char ch = name.charAt( pos ); | |||
if( ch == '/' || ch == ';' || ch == '?' || ch == ':' | |||
|| ch == '@' || ch == '&' || ch == '=' || ch == '+' | |||
|| ch == '$' || ch == ',' ) | |||
@@ -237,7 +242,7 @@ public class UriParser | |||
return null; | |||
} | |||
String hostname = name.substring( 0, pos ); | |||
final String hostname = name.substring( 0, pos ); | |||
name.delete( 0, pos ); | |||
return hostname; | |||
} | |||
@@ -246,23 +251,25 @@ public class UriParser | |||
* Extracts the port from a URI. The <scheme>://<userinfo>@<hostname> | |||
* part has been removed. | |||
*/ | |||
protected String extractPort( StringBuffer name ) | |||
protected String extractPort( final StringBuffer name ) | |||
{ | |||
if( name.length() < 1 || name.charAt( 0 ) != ':' ) | |||
{ | |||
return null; | |||
} | |||
int maxlen = name.length(); | |||
final int maxlen = name.length(); | |||
int pos = 1; | |||
for( ; pos < maxlen; pos++ ) | |||
{ | |||
char ch = name.charAt( pos ); | |||
final char ch = name.charAt( pos ); | |||
if( ch < '0' || ch > '9' ) | |||
{ | |||
break; | |||
} | |||
} | |||
String port = name.substring( 1, pos ); | |||
final String port = name.substring( 1, pos ); | |||
name.delete( 0, pos ); | |||
return port; | |||
} | |||
@@ -270,9 +277,9 @@ public class UriParser | |||
/** | |||
* Extracts the first element of a path. | |||
*/ | |||
protected String extractFirstElement( StringBuffer name ) | |||
protected String extractFirstElement( final StringBuffer name ) | |||
{ | |||
int len = name.length(); | |||
final int len = name.length(); | |||
if( len < 1 ) | |||
{ | |||
return null; | |||
@@ -287,14 +294,14 @@ public class UriParser | |||
if( name.charAt( pos ) == m_separatorChar ) | |||
{ | |||
// Found a separator | |||
String elem = name.substring( startPos, pos ); | |||
final String elem = name.substring( startPos, pos ); | |||
name.delete( startPos, pos + 1 ); | |||
return elem; | |||
} | |||
} | |||
// No separator | |||
String elem = name.substring( startPos ); | |||
final String elem = name.substring( startPos ); | |||
name.setLength( 0 ); | |||
return elem; | |||
} | |||
@@ -308,10 +315,11 @@ public class UriParser | |||
* @param path | |||
* A <i>normalised</i> path. | |||
*/ | |||
public String getUri( String rootUri, String path ) | |||
public String getUri( final String rootUri, | |||
final String path ) | |||
{ | |||
StringBuffer uri = new StringBuffer( rootUri ); | |||
int len = uri.length(); | |||
final StringBuffer uri = new StringBuffer( rootUri ); | |||
final int len = uri.length(); | |||
if( uri.charAt( len - 1 ) == m_separatorChar ) | |||
{ | |||
uri.delete( len - 1, len ); | |||
@@ -330,9 +338,9 @@ public class UriParser | |||
* @param path | |||
* A <i>normalised</i> path. | |||
*/ | |||
public String getBaseName( String path ) | |||
public String getBaseName( final String path ) | |||
{ | |||
int idx = path.lastIndexOf( m_separatorChar ); | |||
final int idx = path.lastIndexOf( m_separatorChar ); | |||
if( idx == -1 ) | |||
{ | |||
return path; | |||
@@ -353,9 +361,11 @@ public class UriParser | |||
* does need to be a path (i.e. not an absolute URI). | |||
* | |||
*/ | |||
public String resolvePath( String basePath, String path ) throws FileSystemException | |||
public String resolvePath( final String basePath, | |||
final String path ) | |||
throws FileSystemException | |||
{ | |||
StringBuffer buffer = new StringBuffer( path ); | |||
final StringBuffer buffer = new StringBuffer( path ); | |||
// Adjust separators | |||
fixSeparators( buffer ); | |||
@@ -374,47 +384,52 @@ public class UriParser | |||
} | |||
/** | |||
* Returns a child path. | |||
* Resolved a name, relative to a base file. | |||
* | |||
* @param parent | |||
* @param baseFile | |||
* A <i>normalised</i> path. | |||
* | |||
* @param name | |||
* The child name. Must be a valid element name (i.e. no separators, etc). | |||
* @param path | |||
* The path to resolve. | |||
* | |||
* @param scope | |||
* The scope to resolve and validate the name in. | |||
*/ | |||
public String getChildPath( String parent, String name ) throws FileSystemException | |||
public String resolvePath( final String baseFile, | |||
final String path, | |||
final NameScope scope ) | |||
throws FileSystemException | |||
{ | |||
// Validate the child name | |||
if( name.length() == 0 | |||
|| name.equals( "." ) | |||
|| name.equals( ".." ) ) | |||
{ | |||
final String message = REZ.getString( "invalid-childname.error", name ); | |||
throw new FileSystemException( message ); | |||
} | |||
// Check for separators | |||
if( name.indexOf( m_separatorChar ) != -1 ) | |||
{ | |||
final String message = REZ.getString( "invalid-childname.error", name ); | |||
throw new FileSystemException( message ); | |||
final String resolvedPath = resolvePath( baseFile, path ); | |||
if( scope == NameScope.CHILD ) | |||
{ | |||
final int baseLen = baseFile.length(); | |||
if( ! resolvedPath.startsWith( baseFile ) | |||
|| resolvedPath.length() == baseLen | |||
|| resolvedPath.charAt( baseLen ) != m_separatorChar | |||
|| resolvedPath.indexOf( m_separatorChar, baseLen + 1 ) != -1 ) | |||
{ | |||
final String message = REZ.getString( "invalid-childname.error", path ); | |||
throw new FileSystemException( message ); | |||
} | |||
} | |||
for( int i = 0; i < m_separators.length; i++ ) | |||
else if( scope == NameScope.DESCENDENT ) | |||
{ | |||
char separator = m_separators[ i ]; | |||
if( name.indexOf( separator ) != -1 ) | |||
final int baseLen = baseFile.length(); | |||
if( ! resolvedPath.startsWith( baseFile ) | |||
|| resolvedPath.length() == baseLen | |||
|| resolvedPath.charAt( baseLen ) != m_separatorChar ) | |||
{ | |||
final String message = REZ.getString( "invalid-childname.error", name ); | |||
final String message = REZ.getString( "invalid-descendent-name.error", path ); | |||
throw new FileSystemException( message ); | |||
} | |||
} | |||
if( parent.endsWith( m_separator ) ) | |||
else if( scope != NameScope.FILE_SYSTEM ) | |||
{ | |||
// Either root, or the parent name already ends with the separator | |||
return parent + name; | |||
throw new IllegalArgumentException(); | |||
} | |||
return parent + m_separatorChar + name; | |||
return resolvedPath; | |||
} | |||
/** | |||
@@ -423,9 +438,9 @@ public class UriParser | |||
* @param path | |||
* A <i>normalised</i> path. | |||
*/ | |||
public String getParentPath( String path ) | |||
public String getParentPath( final String path ) | |||
{ | |||
int idx = path.lastIndexOf( m_separatorChar ); | |||
final int idx = path.lastIndexOf( m_separatorChar ); | |||
if( idx == -1 || idx == path.length() - 1 ) | |||
{ | |||
// No parent | |||
@@ -448,7 +463,7 @@ public class UriParser | |||
* <li>Removes trailing separator. | |||
* </ul> | |||
*/ | |||
public void normalisePath( StringBuffer path ) throws FileSystemException | |||
public void normalisePath( final StringBuffer path ) throws FileSystemException | |||
{ | |||
if( path.length() == 0 ) | |||
{ | |||
@@ -480,7 +495,7 @@ public class UriParser | |||
{ | |||
} | |||
int elemLen = endElem - startElem; | |||
final int elemLen = endElem - startElem; | |||
if( elemLen == 0 ) | |||
{ | |||
// An empty element - axe it | |||
@@ -503,11 +518,8 @@ public class UriParser | |||
if( startElem > startFirstElem ) | |||
{ | |||
int pos = startElem - 2; | |||
char ch = path.charAt( pos ); | |||
while( ch != m_separatorChar ) | |||
for( ; pos >= 0 && path.charAt( pos ) != m_separatorChar; pos -- ) | |||
{ | |||
pos--; | |||
ch = path.charAt( pos ); | |||
} | |||
startElem = pos + 1; | |||
} | |||
@@ -521,7 +533,7 @@ public class UriParser | |||
} | |||
// Remove trailing separator | |||
if( path.charAt( maxlen - 1 ) == m_separatorChar && maxlen > 1 ) | |||
if( maxlen > 0 && path.charAt( maxlen - 1 ) == m_separatorChar && maxlen > 1 ) | |||
{ | |||
path.delete( maxlen - 1, maxlen ); | |||
} | |||
@@ -530,7 +542,7 @@ public class UriParser | |||
/** | |||
* Adjusts the separators in a name. | |||
*/ | |||
protected boolean fixSeparators( StringBuffer name ) | |||
protected boolean fixSeparators( final StringBuffer name ) | |||
{ | |||
if( m_separators.length == 0 ) | |||
{ | |||
@@ -539,10 +551,10 @@ public class UriParser | |||
} | |||
boolean changed = false; | |||
int maxlen = name.length(); | |||
final int maxlen = name.length(); | |||
for( int i = 0; i < maxlen; i++ ) | |||
{ | |||
char ch = name.charAt( i ); | |||
final char ch = name.charAt( i ); | |||
for( int j = 0; j < m_separators.length; j++ ) | |||
{ | |||
char separator = m_separators[ j ]; | |||
@@ -566,7 +578,7 @@ public class UriParser | |||
* @return | |||
* The scheme name. Returns null if there is no scheme. | |||
*/ | |||
public static String extractScheme( String uri ) | |||
public static String extractScheme( final String uri ) | |||
{ | |||
return extractScheme( uri, null ); | |||
} | |||
@@ -583,7 +595,7 @@ public class UriParser | |||
* @return | |||
* The scheme name. Returns null if there is no scheme. | |||
*/ | |||
protected static String extractScheme( String uri, StringBuffer buffer ) | |||
protected static String extractScheme( final String uri, final StringBuffer buffer ) | |||
{ | |||
if( buffer != null ) | |||
{ | |||
@@ -591,15 +603,15 @@ public class UriParser | |||
buffer.append( uri ); | |||
} | |||
int maxPos = uri.length(); | |||
final int maxPos = uri.length(); | |||
for( int pos = 0; pos < maxPos; pos++ ) | |||
{ | |||
char ch = uri.charAt( pos ); | |||
final char ch = uri.charAt( pos ); | |||
if( ch == ':' ) | |||
{ | |||
// Found the end of the scheme | |||
String scheme = uri.substring( 0, pos ); | |||
final String scheme = uri.substring( 0, pos ); | |||
if( buffer != null ) | |||
{ | |||
buffer.delete( 0, pos + 1 ); | |||
@@ -0,0 +1,44 @@ | |||
/* | |||
* Copyright (C) The Apache Software Foundation. All rights reserved. | |||
* | |||
* This software is published under the terms of the Apache Software License | |||
* version 1.1, a copy of which has been included with this distribution in | |||
* the LICENSE.txt file. | |||
*/ | |||
package org.apache.aut.vfs.provider.local; | |||
import org.apache.aut.vfs.FileSystemException; | |||
import org.apache.avalon.excalibur.i18n.ResourceManager; | |||
import org.apache.avalon.excalibur.i18n.Resources; | |||
/** | |||
* A general-purpose file name parser. | |||
* | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
class GenericFileNameParser | |||
extends LocalFileNameParser | |||
{ | |||
private final static Resources REZ | |||
= ResourceManager.getPackageResources( GenericFileNameParser.class ); | |||
/** | |||
* Pops the root prefix off a URI, which has had the scheme removed. | |||
*/ | |||
protected String extractRootPrefix( final String uri, | |||
final StringBuffer name ) | |||
throws FileSystemException | |||
{ | |||
// TODO - this class isn't generic at all. Need to fix this | |||
// Looking for <sep> | |||
if( name.length() == 0 || name.charAt( 0 ) != '/' ) | |||
{ | |||
final String message = REZ.getString( "not-absolute-file-name.error", uri ); | |||
throw new FileSystemException( message ); | |||
} | |||
return "/"; | |||
} | |||
} |
@@ -23,7 +23,8 @@ import org.apache.avalon.excalibur.i18n.Resources; | |||
/** | |||
* A file object implementation which uses direct file access. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
final class LocalFile | |||
extends AbstractFileObject | |||
@@ -33,7 +34,7 @@ final class LocalFile | |||
ResourceManager.getPackageResources( LocalFile.class ); | |||
private File m_file; | |||
private String m_fileName; | |||
private final String m_fileName; | |||
/** | |||
* Creates a non-root file. | |||
@@ -11,26 +11,19 @@ import java.io.File; | |||
import org.apache.aut.vfs.FileSystemException; | |||
import org.apache.aut.vfs.provider.ParsedUri; | |||
import org.apache.aut.vfs.provider.UriParser; | |||
import org.apache.avalon.excalibur.i18n.ResourceManager; | |||
import org.apache.avalon.excalibur.i18n.Resources; | |||
/** | |||
* A name parser. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
class LocalFileNameParser | |||
abstract class LocalFileNameParser | |||
extends UriParser | |||
{ | |||
private final static Resources REZ = | |||
ResourceManager.getPackageResources( LocalFileNameParser.class ); | |||
private boolean m_windowsNames; | |||
public LocalFileNameParser() | |||
{ | |||
super( new char[]{File.separatorChar, '/', '\\'} ); | |||
m_windowsNames = ( System.getProperty( "os.name" ).toLowerCase().indexOf( "windows" ) != -1 ); | |||
} | |||
/** | |||
@@ -91,141 +84,8 @@ class LocalFileNameParser | |||
/** | |||
* Pops the root prefix off a URI, which has had the scheme removed. | |||
*/ | |||
private String extractRootPrefix( final String uri, | |||
final StringBuffer name ) | |||
throws FileSystemException | |||
{ | |||
// TODO - split this into sub-classes | |||
if( m_windowsNames ) | |||
{ | |||
return extractWindowsRootPrefix( uri, name ); | |||
} | |||
else | |||
{ | |||
// Looking for <sep> | |||
if( name.length() == 0 || name.charAt( 0 ) != '/' ) | |||
{ | |||
final String message = REZ.getString( "not-absolute-file-name.error", uri ); | |||
throw new FileSystemException( message ); | |||
} | |||
// TODO - this isn't always true | |||
return "/"; | |||
} | |||
} | |||
/** | |||
* Extracts a Windows root prefix from a name. | |||
*/ | |||
private String extractWindowsRootPrefix( final String uri, | |||
final StringBuffer name ) | |||
throws FileSystemException | |||
{ | |||
// Looking for: | |||
// ('/'){0, 3} <letter> ':' '/' | |||
// ['/'] '//' <name> '/' <name> ( '/' | <end> ) | |||
protected abstract String extractRootPrefix( final String uri, | |||
final StringBuffer name ) | |||
throws FileSystemException; | |||
// Skip over first 3 leading '/' chars | |||
int startPos = 0; | |||
int maxlen = Math.min( 3, name.length() ); | |||
for( ; startPos < maxlen && name.charAt( startPos ) == '/'; startPos++ ) | |||
{ | |||
} | |||
if( startPos == maxlen ) | |||
{ | |||
// Too many '/' | |||
final String message = REZ.getString( "not-absolute-file-name.error", uri ); | |||
throw new FileSystemException( message ); | |||
} | |||
name.delete( 0, startPos ); | |||
// Look for drive name | |||
String driveName = extractDrivePrefix( name ); | |||
if( driveName != null ) | |||
{ | |||
return driveName; | |||
} | |||
// Look for UNC name | |||
if( startPos < 2 ) | |||
{ | |||
final String message = REZ.getString( "not-absolute-file-name.error", uri ); | |||
throw new FileSystemException( message ); | |||
} | |||
return "//" + extractUNCPrefix( uri, name ); | |||
} | |||
/** | |||
* Extracts a drive prefix from a path. Leading '/' chars have been removed. | |||
*/ | |||
private String extractDrivePrefix( final StringBuffer name ) | |||
throws FileSystemException | |||
{ | |||
// Looking for <letter> ':' '/' | |||
if( name.length() < 3 ) | |||
{ | |||
// Too short | |||
return null; | |||
} | |||
char ch = name.charAt( 0 ); | |||
if( ch == '/' || ch == ':' ) | |||
{ | |||
// Missing drive letter | |||
return null; | |||
} | |||
if( name.charAt( 1 ) != ':' ) | |||
{ | |||
// Missing ':' | |||
return null; | |||
} | |||
if( name.charAt( 2 ) != '/' ) | |||
{ | |||
// Missing separator | |||
return null; | |||
} | |||
String prefix = name.substring( 0, 2 ); | |||
name.delete( 0, 2 ); | |||
return prefix; | |||
} | |||
/** | |||
* Extracts a UNC name from a path. Leading '/' chars have been removed. | |||
*/ | |||
private String extractUNCPrefix( final String uri, | |||
final StringBuffer name ) | |||
throws FileSystemException | |||
{ | |||
// Looking for <name> '/' <name> ( '/' | <end> ) | |||
// Look for first separator | |||
int maxpos = name.length(); | |||
int pos = 0; | |||
for( ; pos < maxpos && name.charAt( pos ) != '/'; pos++ ) | |||
{ | |||
} | |||
pos++; | |||
if( pos >= maxpos ) | |||
{ | |||
final String message = REZ.getString( "missing-share-name.error", uri ); | |||
throw new FileSystemException( message ); | |||
} | |||
// Now have <name> '/' | |||
int startShareName = pos; | |||
for( ; pos < maxpos && name.charAt( pos ) != '/'; pos++ ) | |||
{ | |||
} | |||
if( pos == startShareName ) | |||
{ | |||
final String message = REZ.getString( "missing-share-name.error", uri ); | |||
throw new FileSystemException( message ); | |||
} | |||
// Now have <name> '/' <name> ( '/' | <end> ) | |||
String prefix = name.substring( 0, pos ); | |||
name.delete( 0, pos ); | |||
return prefix; | |||
} | |||
} |
@@ -17,7 +17,8 @@ import org.apache.aut.vfs.provider.FileSystem; | |||
/** | |||
* A local file system. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
class LocalFileSystem extends AbstractFileSystem implements FileSystem | |||
{ | |||
@@ -8,6 +8,7 @@ | |||
package org.apache.aut.vfs.provider.local; | |||
import java.io.File; | |||
import org.apache.aut.nativelib.Os; | |||
import org.apache.aut.vfs.FileObject; | |||
import org.apache.aut.vfs.FileSystemException; | |||
import org.apache.aut.vfs.provider.AbstractFileSystemProvider; | |||
@@ -19,12 +20,25 @@ import org.apache.aut.vfs.provider.ParsedUri; | |||
/** | |||
* A file system provider, which uses direct file access. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public class LocalFileSystemProvider extends AbstractFileSystemProvider | |||
implements FileSystemProvider | |||
{ | |||
private final LocalFileNameParser m_parser = new LocalFileNameParser(); | |||
private final LocalFileNameParser m_parser; | |||
public LocalFileSystemProvider() | |||
{ | |||
if( Os.isFamily( Os.OS_FAMILY_WINDOWS ) ) | |||
{ | |||
m_parser = new WindowsFileNameParser(); | |||
} | |||
else | |||
{ | |||
m_parser = new GenericFileNameParser(); | |||
} | |||
} | |||
/** | |||
* Determines if a name is an absolute file name. | |||
@@ -12,9 +12,10 @@ import org.apache.aut.vfs.provider.ParsedUri; | |||
/** | |||
* A parsed file URI. | |||
* | |||
* @author Adam Murdoch | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
public class ParsedFileUri extends ParsedUri | |||
class ParsedFileUri extends ParsedUri | |||
{ | |||
private String m_rootFile; | |||
@@ -23,7 +24,7 @@ public class ParsedFileUri extends ParsedUri | |||
return m_rootFile; | |||
} | |||
public void setRootFile( String rootPrefix ) | |||
public void setRootFile( final String rootPrefix ) | |||
{ | |||
m_rootFile = rootPrefix; | |||
} | |||
@@ -0,0 +1,150 @@ | |||
/* | |||
* Copyright (C) The Apache Software Foundation. All rights reserved. | |||
* | |||
* This software is published under the terms of the Apache Software License | |||
* version 1.1, a copy of which has been included with this distribution in | |||
* the LICENSE.txt file. | |||
*/ | |||
package org.apache.aut.vfs.provider.local; | |||
import org.apache.aut.vfs.FileSystemException; | |||
import org.apache.avalon.excalibur.i18n.ResourceManager; | |||
import org.apache.avalon.excalibur.i18n.Resources; | |||
/** | |||
* A parser for Windows file names. | |||
* | |||
* @author <a href="mailto:adammurdoch@apache.org">Adam Murdoch</a> | |||
* @version $Revision$ $Date$ | |||
*/ | |||
class WindowsFileNameParser | |||
extends LocalFileNameParser | |||
{ | |||
private final static Resources REZ | |||
= ResourceManager.getPackageResources( WindowsFileNameParser.class ); | |||
/** | |||
* Pops the root prefix off a URI, which has had the scheme removed. | |||
*/ | |||
protected String extractRootPrefix( final String uri, | |||
final StringBuffer name ) | |||
throws FileSystemException | |||
{ | |||
return extractWindowsRootPrefix( uri, name ); | |||
} | |||
/** | |||
* Extracts a Windows root prefix from a name. | |||
*/ | |||
private String extractWindowsRootPrefix( final String uri, | |||
final StringBuffer name ) | |||
throws FileSystemException | |||
{ | |||
// Looking for: | |||
// ('/'){0, 3} <letter> ':' '/' | |||
// ['/'] '//' <name> '/' <name> ( '/' | <end> ) | |||
// Skip over first 3 leading '/' chars | |||
int startPos = 0; | |||
int maxlen = Math.min( 3, name.length() ); | |||
for( ; startPos < maxlen && name.charAt( startPos ) == '/'; startPos++ ) | |||
{ | |||
} | |||
if( startPos == maxlen ) | |||
{ | |||
// Too many '/' | |||
final String message = REZ.getString( "not-absolute-file-name.error", uri ); | |||
throw new FileSystemException( message ); | |||
} | |||
name.delete( 0, startPos ); | |||
// Look for drive name | |||
String driveName = extractDrivePrefix( name ); | |||
if( driveName != null ) | |||
{ | |||
return driveName; | |||
} | |||
// Look for UNC name | |||
if( startPos < 2 ) | |||
{ | |||
final String message = REZ.getString( "not-absolute-file-name.error", uri ); | |||
throw new FileSystemException( message ); | |||
} | |||
return "//" + extractUNCPrefix( uri, name ); | |||
} | |||
/** | |||
* Extracts a drive prefix from a path. Leading '/' chars have been removed. | |||
*/ | |||
private String extractDrivePrefix( final StringBuffer name ) | |||
throws FileSystemException | |||
{ | |||
// Looking for <letter> ':' '/' | |||
if( name.length() < 3 ) | |||
{ | |||
// Too short | |||
return null; | |||
} | |||
char ch = name.charAt( 0 ); | |||
if( ch == '/' || ch == ':' ) | |||
{ | |||
// Missing drive letter | |||
return null; | |||
} | |||
if( name.charAt( 1 ) != ':' ) | |||
{ | |||
// Missing ':' | |||
return null; | |||
} | |||
if( name.charAt( 2 ) != '/' ) | |||
{ | |||
// Missing separator | |||
return null; | |||
} | |||
String prefix = name.substring( 0, 2 ); | |||
name.delete( 0, 2 ); | |||
return prefix; | |||
} | |||
/** | |||
* Extracts a UNC name from a path. Leading '/' chars have been removed. | |||
*/ | |||
private String extractUNCPrefix( final String uri, | |||
final StringBuffer name ) | |||
throws FileSystemException | |||
{ | |||
// Looking for <name> '/' <name> ( '/' | <end> ) | |||
// Look for first separator | |||
int maxpos = name.length(); | |||
int pos = 0; | |||
for( ; pos < maxpos && name.charAt( pos ) != '/'; pos++ ) | |||
{ | |||
} | |||
pos++; | |||
if( pos >= maxpos ) | |||
{ | |||
final String message = REZ.getString( "missing-share-name.error", uri ); | |||
throw new FileSystemException( message ); | |||
} | |||
// Now have <name> '/' | |||
int startShareName = pos; | |||
for( ; pos < maxpos && name.charAt( pos ) != '/'; pos++ ) | |||
{ | |||
} | |||
if( pos == startShareName ) | |||
{ | |||
final String message = REZ.getString( "missing-share-name.error", uri ); | |||
throw new FileSystemException( message ); | |||
} | |||
// Now have <name> '/' <name> ( '/' | <end> ) | |||
String prefix = name.substring( 0, pos ); | |||
name.delete( 0, pos ); | |||
return prefix; | |||
} | |||
} |
@@ -15,6 +15,9 @@ import java.util.HashMap; | |||
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.avalon.excalibur.i18n.ResourceManager; | |||
import org.apache.avalon.excalibur.i18n.Resources; | |||
import org.apache.myrmidon.AbstractMyrmidonTest; | |||
/** | |||
@@ -29,6 +32,9 @@ import org.apache.myrmidon.AbstractMyrmidonTest; | |||
public abstract class AbstractFileSystemTest | |||
extends AbstractMyrmidonTest | |||
{ | |||
private final static Resources REZ | |||
= ResourceManager.getPackageResources( AbstractFileObject.class ); | |||
protected FileObject m_baseFolder; | |||
protected DefaultFileSystemManager m_manager; | |||
@@ -46,12 +52,12 @@ public abstract class AbstractFileSystemTest | |||
private FileInfo buildExpectedStructure() | |||
{ | |||
// Build the expected structure | |||
FileInfo base = new FileInfo( "test", FileType.FOLDER ); | |||
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 ) ); | |||
FileInfo dir = new FileInfo( "dir1", 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 ) ); | |||
@@ -76,7 +82,7 @@ public abstract class AbstractFileSystemTest | |||
m_baseFolder = m_manager.resolveFile( getBaseFolderURI() ); | |||
// Build the expected content of "file1.txt" | |||
String eol = System.getProperty( "line.separator" ); | |||
final String eol = System.getProperty( "line.separator" ); | |||
m_charContent = "This is a test file." + eol + "With 2 lines in it." + eol; | |||
} | |||
@@ -86,8 +92,8 @@ public abstract class AbstractFileSystemTest | |||
public void testAbsoluteURI() throws Exception | |||
{ | |||
// Try fetching base folder again by its URI | |||
String uri = m_baseFolder.getName().getURI(); | |||
FileObject file = m_manager.resolveFile( uri ); | |||
final String uri = m_baseFolder.getName().getURI(); | |||
final FileObject file = m_manager.resolveFile( uri ); | |||
assertSame( "file object", m_baseFolder, file ); | |||
} | |||
@@ -123,7 +129,7 @@ public abstract class AbstractFileSystemTest | |||
public void testRootFileName() throws Exception | |||
{ | |||
// Locate the root file | |||
FileName rootName = m_baseFolder.getRoot().getName(); | |||
final FileName rootName = m_baseFolder.getRoot().getName(); | |||
// Test that the root path is "/" | |||
assertEquals( "root path", "/", rootName.getPath() ); | |||
@@ -140,9 +146,9 @@ public abstract class AbstractFileSystemTest | |||
*/ | |||
public void testChildName() throws Exception | |||
{ | |||
FileName baseName = m_baseFolder.getName(); | |||
String basePath = baseName.getPath(); | |||
FileName name = baseName.resolveName( "some-child", NameScope.CHILD ); | |||
final FileName baseName = m_baseFolder.getName(); | |||
final String basePath = baseName.getPath(); | |||
final FileName name = baseName.resolveName( "some-child", NameScope.CHILD ); | |||
// Test path is absolute | |||
assertTrue( "is absolute", basePath.startsWith( "/" ) ); | |||
@@ -157,76 +163,95 @@ public abstract class AbstractFileSystemTest | |||
assertEquals( "parent absolute path", basePath, name.getParent().getPath() ); | |||
// Try using a compound name to find a child | |||
try | |||
{ | |||
name.resolveName( "a/b", NameScope.CHILD ); | |||
assertTrue( false ); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
} | |||
assertBadName( name, "a/b", NameScope.CHILD ); | |||
// Try using a empty name to find a child | |||
try | |||
{ | |||
name.resolveName( "", NameScope.CHILD ); | |||
assertTrue( false ); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
} | |||
// Try using '.' to find a child | |||
try | |||
{ | |||
name.resolveName( ".", NameScope.CHILD ); | |||
assertTrue( false ); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
} | |||
// Check other invalid names | |||
checkDescendentNames( name, NameScope.CHILD ); | |||
} | |||
// Try using '..' to find a child | |||
try | |||
{ | |||
name.resolveName( "..", NameScope.CHILD ); | |||
assertTrue( false ); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
} | |||
/** | |||
* Name resolution tests that are common for CHILD or DESCENDENT scope. | |||
*/ | |||
private void checkDescendentNames( final FileName name, | |||
final NameScope scope ) | |||
throws Exception | |||
{ | |||
// Make some assumptions about the name explicit | |||
assertTrue( !name.getPath().equals( "/" ) ); | |||
assertTrue( !name.getPath().endsWith( "/a" ) ); | |||
assertTrue( !name.getPath().endsWith( "/a/b" ) ); | |||
// Test names with the same prefix | |||
String path = name.getPath() + "/a"; | |||
assertSameName( path, name, path, scope ); | |||
assertSameName( path, name, "../" + name.getBaseName() + "/a", scope ); | |||
// Test an empty name | |||
assertBadName( name, "", scope ); | |||
// Test . name | |||
assertBadName( name, ".", scope ); | |||
assertBadName( name, "./", scope ); | |||
// Test ancestor names | |||
assertBadName( name, "..", scope ); | |||
assertBadName( name, "../a", scope ); | |||
assertBadName( name, "../" + name.getBaseName() + "a", scope ); | |||
assertBadName( name, "a/..", scope ); | |||
// Test absolute names | |||
assertBadName( name, "/", scope ); | |||
assertBadName( name, "/a", scope ); | |||
assertBadName( name, "/a/b", scope ); | |||
assertBadName( name, name.getPath(), scope ); | |||
assertBadName( name, name.getPath() + "a", scope ); | |||
} | |||
/** | |||
* Checks that a relative name resolves to the expected absolute path. | |||
* Tests both forward and back slashes. | |||
*/ | |||
private void assertSameName( String expectedPath, | |||
FileName baseName, | |||
String relName ) throws Exception | |||
private void assertSameName( final String expectedPath, | |||
final FileName baseName, | |||
final String relName, | |||
final NameScope scope ) | |||
throws Exception | |||
{ | |||
FileName name = baseName.resolveName( relName ); | |||
// Try the supplied name | |||
FileName name = baseName.resolveName( relName, scope ); | |||
assertEquals( expectedPath, name.getPath() ); | |||
// Replace the separators | |||
relName.replace( '\\', '/' ); | |||
name = baseName.resolveName( relName ); | |||
name = baseName.resolveName( relName, scope ); | |||
assertEquals( expectedPath, name.getPath() ); | |||
// And again | |||
relName.replace( '/', '\\' ); | |||
name = baseName.resolveName( relName ); | |||
name = baseName.resolveName( relName, scope ); | |||
assertEquals( expectedPath, name.getPath() ); | |||
} | |||
/** | |||
* Checks that a relative name resolves to the expected absolute path. | |||
* Tests both forward and back slashes. | |||
*/ | |||
private void assertSameName( String expectedPath, | |||
FileName baseName, | |||
String relName ) throws Exception | |||
{ | |||
assertSameName( expectedPath, baseName, relName, NameScope.FILE_SYSTEM ); | |||
} | |||
/** | |||
* Tests relative name resolution, relative to the base folder. | |||
*/ | |||
public void testNameResolution() throws Exception | |||
{ | |||
FileName baseName = m_baseFolder.getName(); | |||
String parentPath = baseName.getParent().getPath(); | |||
String path = baseName.getPath(); | |||
String childPath = path + "/some-child"; | |||
final FileName baseName = m_baseFolder.getName(); | |||
final String parentPath = baseName.getParent().getPath(); | |||
final String path = baseName.getPath(); | |||
final String childPath = path + "/some-child"; | |||
// Test empty relative path | |||
assertSameName( path, baseName, "" ); | |||
@@ -280,23 +305,78 @@ public abstract class AbstractFileSystemTest | |||
} | |||
/** | |||
* Walks the folder structure, asserting it contains exactly the | |||
* Tests descendent name resolution. | |||
*/ | |||
public void testDescendentName() | |||
throws Exception | |||
{ | |||
final FileName baseName = m_baseFolder.getName(); | |||
// Test direct child | |||
String path = baseName.getPath() + "/some-child"; | |||
assertSameName( path, baseName, "some-child", NameScope.DESCENDENT ); | |||
// Test compound name | |||
path = path + "/grand-child"; | |||
assertSameName( path, baseName, "some-child/grand-child", NameScope.DESCENDENT ); | |||
// Test relative names | |||
assertSameName( path, baseName, "./some-child/grand-child", NameScope.DESCENDENT ); | |||
assertSameName( path, baseName, "./nada/../some-child/grand-child", NameScope.DESCENDENT ); | |||
assertSameName( path, baseName, "some-child/./grand-child", NameScope.DESCENDENT ); | |||
// Test badly formed descendent names | |||
checkDescendentNames( baseName, NameScope.DESCENDENT ); | |||
} | |||
/** | |||
* Asserts that a particular relative name is invalid for a particular | |||
* scope. | |||
*/ | |||
private void assertBadName( final FileName name, | |||
final String relName, | |||
final NameScope scope ) | |||
{ | |||
try | |||
{ | |||
name.resolveName( relName, scope ); | |||
fail(); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
// TODO - should check error message | |||
} | |||
} | |||
/** | |||
* Walks the base folder structure, asserting it contains exactly the | |||
* expected files and folders. | |||
*/ | |||
public void testStructure() throws Exception | |||
{ | |||
final FileInfo baseInfo = buildExpectedStructure(); | |||
assertSameStructure( m_baseFolder, baseInfo ); | |||
} | |||
/** | |||
* Walks a folder structure, asserting it contains exactly the | |||
* expected files and folders. | |||
*/ | |||
protected void assertSameStructure( final FileObject folder, | |||
final FileInfo expected ) | |||
throws Exception | |||
{ | |||
// Setup the structure | |||
List queueExpected = new ArrayList(); | |||
FileInfo baseInfo = buildExpectedStructure(); | |||
queueExpected.add( baseInfo ); | |||
final List queueExpected = new ArrayList(); | |||
queueExpected.add( expected ); | |||
List queueActual = new ArrayList(); | |||
queueActual.add( m_baseFolder ); | |||
final List queueActual = new ArrayList(); | |||
queueActual.add( folder ); | |||
while( queueActual.size() > 0 ) | |||
{ | |||
FileObject file = (FileObject)queueActual.remove( 0 ); | |||
FileInfo info = (FileInfo)queueExpected.remove( 0 ); | |||
final FileObject file = (FileObject)queueActual.remove( 0 ); | |||
final FileInfo info = (FileInfo)queueExpected.remove( 0 ); | |||
// Check the type is correct | |||
assertSame( file.getType(), info._type ); | |||
@@ -307,7 +387,7 @@ public abstract class AbstractFileSystemTest | |||
} | |||
// Check children | |||
FileObject[] children = file.getChildren(); | |||
final FileObject[] children = file.getChildren(); | |||
// Make sure all children were found | |||
assertNotNull( children ); | |||
@@ -316,8 +396,8 @@ public abstract class AbstractFileSystemTest | |||
// Recursively check each child | |||
for( int i = 0; i < children.length; i++ ) | |||
{ | |||
FileObject child = children[ i ]; | |||
FileInfo childInfo = (FileInfo)info._children.get( child.getName().getBaseName() ); | |||
final FileObject child = children[ i ]; | |||
final FileInfo childInfo = (FileInfo)info._children.get( child.getName().getBaseName() ); | |||
// Make sure the child is expected | |||
assertNotNull( childInfo ); | |||
@@ -366,16 +446,16 @@ public abstract class AbstractFileSystemTest | |||
// Test an unknown file | |||
file = m_baseFolder.resolveFile( "unknown-child" ); | |||
FileSystemException exc = null; | |||
try | |||
{ | |||
file.getType(); | |||
fail(); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
exc = e; | |||
final String message = REZ.getString( "get-type-no-exist.error", file ); | |||
assertSameMessage( message, e ); | |||
} | |||
assertNotNull( exc ); | |||
} | |||
/** | |||
@@ -419,10 +499,12 @@ public abstract class AbstractFileSystemTest | |||
try | |||
{ | |||
file.getChildren(); | |||
assertTrue( false ); | |||
fail(); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
final String message = REZ.getString( "list-children-not-folder.error", file ); | |||
assertSameMessage( message, e ); | |||
} | |||
// Should be able to get child by name | |||
@@ -435,10 +517,12 @@ public abstract class AbstractFileSystemTest | |||
try | |||
{ | |||
file.getChildren(); | |||
assertTrue( false ); | |||
fail(); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
final String message = REZ.getString( "list-children-no-exist.error", file ); | |||
assertSameMessage( message, e ); | |||
} | |||
// Should be able to get child by name | |||
@@ -468,23 +552,33 @@ public abstract class AbstractFileSystemTest | |||
* a byte stream, and as a char stream, and compares the result with | |||
* the expected content. Assumes files are encoded using UTF-8. | |||
*/ | |||
public void assertSameContent( String expected, FileContent content ) throws Exception | |||
protected void assertSameContent( final String expected, | |||
final FileContent content ) | |||
throws Exception | |||
{ | |||
// Get file content as a binary stream | |||
byte[] expectedBin = expected.getBytes( "utf-8" ); | |||
final byte[] expectedBin = expected.getBytes( "utf-8" ); | |||
// Check lengths | |||
assertEquals( "same content length", expectedBin.length, content.getSize() ); | |||
// Read content into byte array | |||
InputStream instr = content.getInputStream(); | |||
ByteArrayOutputStream outstr = new ByteArrayOutputStream(); | |||
byte[] buffer = new byte[ 256 ]; | |||
int nread = 0; | |||
while( nread >= 0 ) | |||
final InputStream instr = content.getInputStream(); | |||
final ByteArrayOutputStream outstr; | |||
try | |||
{ | |||
outstr = new ByteArrayOutputStream(); | |||
final byte[] buffer = new byte[ 256 ]; | |||
int nread = 0; | |||
while( nread >= 0 ) | |||
{ | |||
outstr.write( buffer, 0, nread ); | |||
nread = instr.read( buffer ); | |||
} | |||
} | |||
finally | |||
{ | |||
outstr.write( buffer, 0, nread ); | |||
nread = instr.read( buffer ); | |||
instr.close(); | |||
} | |||
// Compare | |||
@@ -501,10 +595,12 @@ public abstract class AbstractFileSystemTest | |||
try | |||
{ | |||
folder.getContent(); | |||
assertTrue( false ); | |||
fail(); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
final String message = REZ.getString( "get-folder-content.error", folder ); | |||
assertSameMessage( message, e ); | |||
} | |||
// Try getting the content of an unknown file | |||
@@ -513,18 +609,22 @@ public abstract class AbstractFileSystemTest | |||
try | |||
{ | |||
content.getInputStream(); | |||
assertTrue( false ); | |||
fail(); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
final String message = REZ.getString( "read-no-exist.error", unknownFile ); | |||
assertSameMessage( message, e ); | |||
} | |||
try | |||
{ | |||
content.getSize(); | |||
assertTrue( false ); | |||
fail(); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
final String message = REZ.getString( "get-size-no-exist.error", unknownFile ); | |||
assertSameMessage( message, e ); | |||
} | |||
} | |||
@@ -557,7 +657,7 @@ public abstract class AbstractFileSystemTest | |||
/** | |||
* Info about a file. | |||
*/ | |||
private static final class FileInfo | |||
protected static final class FileInfo | |||
{ | |||
String _baseName; | |||
FileType _type; | |||
@@ -15,6 +15,9 @@ import java.util.HashMap; | |||
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.avalon.excalibur.i18n.ResourceManager; | |||
import org.apache.avalon.excalibur.i18n.Resources; | |||
import org.apache.myrmidon.AbstractMyrmidonTest; | |||
/** | |||
@@ -29,6 +32,9 @@ import org.apache.myrmidon.AbstractMyrmidonTest; | |||
public abstract class AbstractFileSystemTest | |||
extends AbstractMyrmidonTest | |||
{ | |||
private final static Resources REZ | |||
= ResourceManager.getPackageResources( AbstractFileObject.class ); | |||
protected FileObject m_baseFolder; | |||
protected DefaultFileSystemManager m_manager; | |||
@@ -46,12 +52,12 @@ public abstract class AbstractFileSystemTest | |||
private FileInfo buildExpectedStructure() | |||
{ | |||
// Build the expected structure | |||
FileInfo base = new FileInfo( "test", FileType.FOLDER ); | |||
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 ) ); | |||
FileInfo dir = new FileInfo( "dir1", 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 ) ); | |||
@@ -76,7 +82,7 @@ public abstract class AbstractFileSystemTest | |||
m_baseFolder = m_manager.resolveFile( getBaseFolderURI() ); | |||
// Build the expected content of "file1.txt" | |||
String eol = System.getProperty( "line.separator" ); | |||
final String eol = System.getProperty( "line.separator" ); | |||
m_charContent = "This is a test file." + eol + "With 2 lines in it." + eol; | |||
} | |||
@@ -86,8 +92,8 @@ public abstract class AbstractFileSystemTest | |||
public void testAbsoluteURI() throws Exception | |||
{ | |||
// Try fetching base folder again by its URI | |||
String uri = m_baseFolder.getName().getURI(); | |||
FileObject file = m_manager.resolveFile( uri ); | |||
final String uri = m_baseFolder.getName().getURI(); | |||
final FileObject file = m_manager.resolveFile( uri ); | |||
assertSame( "file object", m_baseFolder, file ); | |||
} | |||
@@ -123,7 +129,7 @@ public abstract class AbstractFileSystemTest | |||
public void testRootFileName() throws Exception | |||
{ | |||
// Locate the root file | |||
FileName rootName = m_baseFolder.getRoot().getName(); | |||
final FileName rootName = m_baseFolder.getRoot().getName(); | |||
// Test that the root path is "/" | |||
assertEquals( "root path", "/", rootName.getPath() ); | |||
@@ -140,9 +146,9 @@ public abstract class AbstractFileSystemTest | |||
*/ | |||
public void testChildName() throws Exception | |||
{ | |||
FileName baseName = m_baseFolder.getName(); | |||
String basePath = baseName.getPath(); | |||
FileName name = baseName.resolveName( "some-child", NameScope.CHILD ); | |||
final FileName baseName = m_baseFolder.getName(); | |||
final String basePath = baseName.getPath(); | |||
final FileName name = baseName.resolveName( "some-child", NameScope.CHILD ); | |||
// Test path is absolute | |||
assertTrue( "is absolute", basePath.startsWith( "/" ) ); | |||
@@ -157,76 +163,95 @@ public abstract class AbstractFileSystemTest | |||
assertEquals( "parent absolute path", basePath, name.getParent().getPath() ); | |||
// Try using a compound name to find a child | |||
try | |||
{ | |||
name.resolveName( "a/b", NameScope.CHILD ); | |||
assertTrue( false ); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
} | |||
assertBadName( name, "a/b", NameScope.CHILD ); | |||
// Try using a empty name to find a child | |||
try | |||
{ | |||
name.resolveName( "", NameScope.CHILD ); | |||
assertTrue( false ); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
} | |||
// Try using '.' to find a child | |||
try | |||
{ | |||
name.resolveName( ".", NameScope.CHILD ); | |||
assertTrue( false ); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
} | |||
// Check other invalid names | |||
checkDescendentNames( name, NameScope.CHILD ); | |||
} | |||
// Try using '..' to find a child | |||
try | |||
{ | |||
name.resolveName( "..", NameScope.CHILD ); | |||
assertTrue( false ); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
} | |||
/** | |||
* Name resolution tests that are common for CHILD or DESCENDENT scope. | |||
*/ | |||
private void checkDescendentNames( final FileName name, | |||
final NameScope scope ) | |||
throws Exception | |||
{ | |||
// Make some assumptions about the name explicit | |||
assertTrue( !name.getPath().equals( "/" ) ); | |||
assertTrue( !name.getPath().endsWith( "/a" ) ); | |||
assertTrue( !name.getPath().endsWith( "/a/b" ) ); | |||
// Test names with the same prefix | |||
String path = name.getPath() + "/a"; | |||
assertSameName( path, name, path, scope ); | |||
assertSameName( path, name, "../" + name.getBaseName() + "/a", scope ); | |||
// Test an empty name | |||
assertBadName( name, "", scope ); | |||
// Test . name | |||
assertBadName( name, ".", scope ); | |||
assertBadName( name, "./", scope ); | |||
// Test ancestor names | |||
assertBadName( name, "..", scope ); | |||
assertBadName( name, "../a", scope ); | |||
assertBadName( name, "../" + name.getBaseName() + "a", scope ); | |||
assertBadName( name, "a/..", scope ); | |||
// Test absolute names | |||
assertBadName( name, "/", scope ); | |||
assertBadName( name, "/a", scope ); | |||
assertBadName( name, "/a/b", scope ); | |||
assertBadName( name, name.getPath(), scope ); | |||
assertBadName( name, name.getPath() + "a", scope ); | |||
} | |||
/** | |||
* Checks that a relative name resolves to the expected absolute path. | |||
* Tests both forward and back slashes. | |||
*/ | |||
private void assertSameName( String expectedPath, | |||
FileName baseName, | |||
String relName ) throws Exception | |||
private void assertSameName( final String expectedPath, | |||
final FileName baseName, | |||
final String relName, | |||
final NameScope scope ) | |||
throws Exception | |||
{ | |||
FileName name = baseName.resolveName( relName ); | |||
// Try the supplied name | |||
FileName name = baseName.resolveName( relName, scope ); | |||
assertEquals( expectedPath, name.getPath() ); | |||
// Replace the separators | |||
relName.replace( '\\', '/' ); | |||
name = baseName.resolveName( relName ); | |||
name = baseName.resolveName( relName, scope ); | |||
assertEquals( expectedPath, name.getPath() ); | |||
// And again | |||
relName.replace( '/', '\\' ); | |||
name = baseName.resolveName( relName ); | |||
name = baseName.resolveName( relName, scope ); | |||
assertEquals( expectedPath, name.getPath() ); | |||
} | |||
/** | |||
* Checks that a relative name resolves to the expected absolute path. | |||
* Tests both forward and back slashes. | |||
*/ | |||
private void assertSameName( String expectedPath, | |||
FileName baseName, | |||
String relName ) throws Exception | |||
{ | |||
assertSameName( expectedPath, baseName, relName, NameScope.FILE_SYSTEM ); | |||
} | |||
/** | |||
* Tests relative name resolution, relative to the base folder. | |||
*/ | |||
public void testNameResolution() throws Exception | |||
{ | |||
FileName baseName = m_baseFolder.getName(); | |||
String parentPath = baseName.getParent().getPath(); | |||
String path = baseName.getPath(); | |||
String childPath = path + "/some-child"; | |||
final FileName baseName = m_baseFolder.getName(); | |||
final String parentPath = baseName.getParent().getPath(); | |||
final String path = baseName.getPath(); | |||
final String childPath = path + "/some-child"; | |||
// Test empty relative path | |||
assertSameName( path, baseName, "" ); | |||
@@ -280,23 +305,78 @@ public abstract class AbstractFileSystemTest | |||
} | |||
/** | |||
* Walks the folder structure, asserting it contains exactly the | |||
* Tests descendent name resolution. | |||
*/ | |||
public void testDescendentName() | |||
throws Exception | |||
{ | |||
final FileName baseName = m_baseFolder.getName(); | |||
// Test direct child | |||
String path = baseName.getPath() + "/some-child"; | |||
assertSameName( path, baseName, "some-child", NameScope.DESCENDENT ); | |||
// Test compound name | |||
path = path + "/grand-child"; | |||
assertSameName( path, baseName, "some-child/grand-child", NameScope.DESCENDENT ); | |||
// Test relative names | |||
assertSameName( path, baseName, "./some-child/grand-child", NameScope.DESCENDENT ); | |||
assertSameName( path, baseName, "./nada/../some-child/grand-child", NameScope.DESCENDENT ); | |||
assertSameName( path, baseName, "some-child/./grand-child", NameScope.DESCENDENT ); | |||
// Test badly formed descendent names | |||
checkDescendentNames( baseName, NameScope.DESCENDENT ); | |||
} | |||
/** | |||
* Asserts that a particular relative name is invalid for a particular | |||
* scope. | |||
*/ | |||
private void assertBadName( final FileName name, | |||
final String relName, | |||
final NameScope scope ) | |||
{ | |||
try | |||
{ | |||
name.resolveName( relName, scope ); | |||
fail(); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
// TODO - should check error message | |||
} | |||
} | |||
/** | |||
* Walks the base folder structure, asserting it contains exactly the | |||
* expected files and folders. | |||
*/ | |||
public void testStructure() throws Exception | |||
{ | |||
final FileInfo baseInfo = buildExpectedStructure(); | |||
assertSameStructure( m_baseFolder, baseInfo ); | |||
} | |||
/** | |||
* Walks a folder structure, asserting it contains exactly the | |||
* expected files and folders. | |||
*/ | |||
protected void assertSameStructure( final FileObject folder, | |||
final FileInfo expected ) | |||
throws Exception | |||
{ | |||
// Setup the structure | |||
List queueExpected = new ArrayList(); | |||
FileInfo baseInfo = buildExpectedStructure(); | |||
queueExpected.add( baseInfo ); | |||
final List queueExpected = new ArrayList(); | |||
queueExpected.add( expected ); | |||
List queueActual = new ArrayList(); | |||
queueActual.add( m_baseFolder ); | |||
final List queueActual = new ArrayList(); | |||
queueActual.add( folder ); | |||
while( queueActual.size() > 0 ) | |||
{ | |||
FileObject file = (FileObject)queueActual.remove( 0 ); | |||
FileInfo info = (FileInfo)queueExpected.remove( 0 ); | |||
final FileObject file = (FileObject)queueActual.remove( 0 ); | |||
final FileInfo info = (FileInfo)queueExpected.remove( 0 ); | |||
// Check the type is correct | |||
assertSame( file.getType(), info._type ); | |||
@@ -307,7 +387,7 @@ public abstract class AbstractFileSystemTest | |||
} | |||
// Check children | |||
FileObject[] children = file.getChildren(); | |||
final FileObject[] children = file.getChildren(); | |||
// Make sure all children were found | |||
assertNotNull( children ); | |||
@@ -316,8 +396,8 @@ public abstract class AbstractFileSystemTest | |||
// Recursively check each child | |||
for( int i = 0; i < children.length; i++ ) | |||
{ | |||
FileObject child = children[ i ]; | |||
FileInfo childInfo = (FileInfo)info._children.get( child.getName().getBaseName() ); | |||
final FileObject child = children[ i ]; | |||
final FileInfo childInfo = (FileInfo)info._children.get( child.getName().getBaseName() ); | |||
// Make sure the child is expected | |||
assertNotNull( childInfo ); | |||
@@ -366,16 +446,16 @@ public abstract class AbstractFileSystemTest | |||
// Test an unknown file | |||
file = m_baseFolder.resolveFile( "unknown-child" ); | |||
FileSystemException exc = null; | |||
try | |||
{ | |||
file.getType(); | |||
fail(); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
exc = e; | |||
final String message = REZ.getString( "get-type-no-exist.error", file ); | |||
assertSameMessage( message, e ); | |||
} | |||
assertNotNull( exc ); | |||
} | |||
/** | |||
@@ -419,10 +499,12 @@ public abstract class AbstractFileSystemTest | |||
try | |||
{ | |||
file.getChildren(); | |||
assertTrue( false ); | |||
fail(); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
final String message = REZ.getString( "list-children-not-folder.error", file ); | |||
assertSameMessage( message, e ); | |||
} | |||
// Should be able to get child by name | |||
@@ -435,10 +517,12 @@ public abstract class AbstractFileSystemTest | |||
try | |||
{ | |||
file.getChildren(); | |||
assertTrue( false ); | |||
fail(); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
final String message = REZ.getString( "list-children-no-exist.error", file ); | |||
assertSameMessage( message, e ); | |||
} | |||
// Should be able to get child by name | |||
@@ -468,23 +552,33 @@ public abstract class AbstractFileSystemTest | |||
* a byte stream, and as a char stream, and compares the result with | |||
* the expected content. Assumes files are encoded using UTF-8. | |||
*/ | |||
public void assertSameContent( String expected, FileContent content ) throws Exception | |||
protected void assertSameContent( final String expected, | |||
final FileContent content ) | |||
throws Exception | |||
{ | |||
// Get file content as a binary stream | |||
byte[] expectedBin = expected.getBytes( "utf-8" ); | |||
final byte[] expectedBin = expected.getBytes( "utf-8" ); | |||
// Check lengths | |||
assertEquals( "same content length", expectedBin.length, content.getSize() ); | |||
// Read content into byte array | |||
InputStream instr = content.getInputStream(); | |||
ByteArrayOutputStream outstr = new ByteArrayOutputStream(); | |||
byte[] buffer = new byte[ 256 ]; | |||
int nread = 0; | |||
while( nread >= 0 ) | |||
final InputStream instr = content.getInputStream(); | |||
final ByteArrayOutputStream outstr; | |||
try | |||
{ | |||
outstr = new ByteArrayOutputStream(); | |||
final byte[] buffer = new byte[ 256 ]; | |||
int nread = 0; | |||
while( nread >= 0 ) | |||
{ | |||
outstr.write( buffer, 0, nread ); | |||
nread = instr.read( buffer ); | |||
} | |||
} | |||
finally | |||
{ | |||
outstr.write( buffer, 0, nread ); | |||
nread = instr.read( buffer ); | |||
instr.close(); | |||
} | |||
// Compare | |||
@@ -501,10 +595,12 @@ public abstract class AbstractFileSystemTest | |||
try | |||
{ | |||
folder.getContent(); | |||
assertTrue( false ); | |||
fail(); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
final String message = REZ.getString( "get-folder-content.error", folder ); | |||
assertSameMessage( message, e ); | |||
} | |||
// Try getting the content of an unknown file | |||
@@ -513,18 +609,22 @@ public abstract class AbstractFileSystemTest | |||
try | |||
{ | |||
content.getInputStream(); | |||
assertTrue( false ); | |||
fail(); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
final String message = REZ.getString( "read-no-exist.error", unknownFile ); | |||
assertSameMessage( message, e ); | |||
} | |||
try | |||
{ | |||
content.getSize(); | |||
assertTrue( false ); | |||
fail(); | |||
} | |||
catch( FileSystemException e ) | |||
{ | |||
final String message = REZ.getString( "get-size-no-exist.error", unknownFile ); | |||
assertSameMessage( message, e ); | |||
} | |||
} | |||
@@ -557,7 +657,7 @@ public abstract class AbstractFileSystemTest | |||
/** | |||
* Info about a file. | |||
*/ | |||
private static final class FileInfo | |||
protected static final class FileInfo | |||
{ | |||
String _baseName; | |||
FileType _type; | |||