diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/FileName.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileName.java index 2c1f58686..a265c0f4b 100644 --- a/proposal/myrmidon/src/java/org/apache/aut/vfs/FileName.java +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileName.java @@ -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. diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/FileObject.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileObject.java index 46545b84f..fc5a720ef 100644 --- a/proposal/myrmidon/src/java/org/apache/aut/vfs/FileObject.java +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/FileObject.java @@ -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; diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/NameScope.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/NameScope.java index 3e339f18c..ffd61771d 100644 --- a/proposal/myrmidon/src/java/org/apache/aut/vfs/NameScope.java +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/NameScope.java @@ -16,14 +16,19 @@ package org.apache.aut.vfs; public final class NameScope { /** - * Resolve against the children of the base file. - * - *

The supplied name must be a valid element name. That is, it may - * not be empty, or ., or .., or contain any - * separator characters. + * 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. * diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileName.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileName.java index 6f169f142..abb9a174b 100644 --- a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileName.java +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/DefaultFileName.java @@ -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 ); } diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/Resources.properties b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/Resources.properties index 1d2d95d7b..82b2cd6dd 100644 --- a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/Resources.properties +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/Resources.properties @@ -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}". diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/UriParser.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/UriParser.java index fbba9e791..a45cba92c 100644 --- a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/UriParser.java +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/UriParser.java @@ -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 not 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 * *

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 :// 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 ://@ 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 ://@ * 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 normalised 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 normalised 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 normalised 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 normalised 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 *

  • Removes trailing separator. * */ - 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 ); diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/GenericFileNameParser.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/GenericFileNameParser.java new file mode 100644 index 000000000..042c2451e --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/GenericFileNameParser.java @@ -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 Adam Murdoch + * @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 + if( name.length() == 0 || name.charAt( 0 ) != '/' ) + { + final String message = REZ.getString( "not-absolute-file-name.error", uri ); + throw new FileSystemException( message ); + } + + return "/"; + } +} diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFile.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFile.java index 3a59556be..f9fe1c4b0 100644 --- a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFile.java +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFile.java @@ -23,7 +23,8 @@ import org.apache.avalon.excalibur.i18n.Resources; /** * A file object implementation which uses direct file access. * - * @author Adam Murdoch + * @author Adam Murdoch + * @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. diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileNameParser.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileNameParser.java index dfa5417d0..d55db0f27 100644 --- a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileNameParser.java +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileNameParser.java @@ -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 Adam Murdoch + * @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 - 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} ':' '/' - // ['/'] '//' '/' ( '/' | ) + 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 ':' '/' - 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 '/' ( '/' | ) - - // Look for first separator - int maxpos = name.length(); - int pos = 0; - for( ; pos < maxpos && name.charAt( pos ) != '/'; pos++ ) - { - } - pos++; - if( pos >= maxpos ) - { - final String message = REZ.getString( "missing-share-name.error", uri ); - throw new FileSystemException( message ); - } - - // Now have '/' - int startShareName = pos; - for( ; pos < maxpos && name.charAt( pos ) != '/'; pos++ ) - { - } - if( pos == startShareName ) - { - final String message = REZ.getString( "missing-share-name.error", uri ); - throw new FileSystemException( message ); - } - - // Now have '/' ( '/' | ) - String prefix = name.substring( 0, pos ); - name.delete( 0, pos ); - return prefix; - } } diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileSystem.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileSystem.java index 79ffc4bfc..2c8712f0a 100644 --- a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileSystem.java +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileSystem.java @@ -17,7 +17,8 @@ import org.apache.aut.vfs.provider.FileSystem; /** * A local file system. * - * @author Adam Murdoch + * @author Adam Murdoch + * @version $Revision$ $Date$ */ class LocalFileSystem extends AbstractFileSystem implements FileSystem { diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileSystemProvider.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileSystemProvider.java index 7cd1ef5ff..694f3a0a8 100644 --- a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileSystemProvider.java +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/LocalFileSystemProvider.java @@ -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 Adam Murdoch + * @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. diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/ParsedFileUri.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/ParsedFileUri.java index 90df6d2bf..7a3be01a1 100644 --- a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/ParsedFileUri.java +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/ParsedFileUri.java @@ -12,9 +12,10 @@ import org.apache.aut.vfs.provider.ParsedUri; /** * A parsed file URI. * - * @author Adam Murdoch + * @author Adam Murdoch + * @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; } diff --git a/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/WindowsFileNameParser.java b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/WindowsFileNameParser.java new file mode 100644 index 000000000..1d2931b08 --- /dev/null +++ b/proposal/myrmidon/src/java/org/apache/aut/vfs/provider/local/WindowsFileNameParser.java @@ -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 Adam Murdoch + * @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} ':' '/' + // ['/'] '//' '/' ( '/' | ) + + // 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 ':' '/' + 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 '/' ( '/' | ) + + // Look for first separator + int maxpos = name.length(); + int pos = 0; + for( ; pos < maxpos && name.charAt( pos ) != '/'; pos++ ) + { + } + pos++; + if( pos >= maxpos ) + { + final String message = REZ.getString( "missing-share-name.error", uri ); + throw new FileSystemException( message ); + } + + // Now have '/' + int startShareName = pos; + for( ; pos < maxpos && name.charAt( pos ) != '/'; pos++ ) + { + } + if( pos == startShareName ) + { + final String message = REZ.getString( "missing-share-name.error", uri ); + throw new FileSystemException( message ); + } + + // Now have '/' ( '/' | ) + String prefix = name.substring( 0, pos ); + name.delete( 0, pos ); + return prefix; + } +} diff --git a/proposal/myrmidon/src/test/org/apache/aut/vfs/AbstractFileSystemTest.java b/proposal/myrmidon/src/test/org/apache/aut/vfs/AbstractFileSystemTest.java index 5660f5afc..a48f7a139 100644 --- a/proposal/myrmidon/src/test/org/apache/aut/vfs/AbstractFileSystemTest.java +++ b/proposal/myrmidon/src/test/org/apache/aut/vfs/AbstractFileSystemTest.java @@ -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; diff --git a/proposal/myrmidon/src/testcases/org/apache/aut/vfs/AbstractFileSystemTest.java b/proposal/myrmidon/src/testcases/org/apache/aut/vfs/AbstractFileSystemTest.java index 5660f5afc..a48f7a139 100644 --- a/proposal/myrmidon/src/testcases/org/apache/aut/vfs/AbstractFileSystemTest.java +++ b/proposal/myrmidon/src/testcases/org/apache/aut/vfs/AbstractFileSystemTest.java @@ -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;