/* * Copyright 2005 by Oracle USA * 500 Oracle Parkway, Redwood Shores, California, 94065, U.S.A. * All rights reserved. */ package javax.ide.net; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.net.UnknownServiceException; import java.util.ArrayList; import java.util.StringTokenizer; /** * The {@link VirtualFileSystemHelper} class specifies the * {@link VirtualFileSystem} operations that may have scheme-specific * handling. By default, the {@link VirtualFileSystem} delegates its * operations to {@link VirtualFileSystemHelper}. However, a subclass of * {@link VirtualFileSystemHelper} can be registered with the * {@link VirtualFileSystem} to handle the {@link VirtualFileSystem} operations * for a particular scheme. A helper class is registered through the * {@link VirtualFileSystem#registerHelper(String, VirtualFileSystemHelper)} * method.
* * Special implementation note: classes that extend * {@link VirtualFileSystemHelper} must be completely thread-safe because a * single instance of each registered helper is held by the * {@link VirtualFileSystem} and reused for all threads. * * @see VirtualFileSystem */ public class VirtualFileSystemHelper { //-------------------------------------------------------------------------- // constructors... //-------------------------------------------------------------------------- protected VirtualFileSystemHelper() { // NOP. } //-------------------------------------------------------------------------- // VirtualFileSystemHelper public API... //-------------------------------------------------------------------------- /** * Returns a canonical form of the {@link URI}, if one is available. *
*
* The default implementation just returns the specified {@link URI}
* as-is.
*/
public URI canonicalize( URI uri ) throws IOException
{
return uri;
}
/**
* Tests whether the application can read the resource at the
* specified {@link URI}.
*
* @return true
if and only if the specified
* {@link URI} points to a resource that exists and can be
* read by the application; false
otherwise.
*/
public boolean canRead( URI uri )
{
return false;
}
/**
* Tests whether the application can modify the resource at the
* specified {@link URI}.
*
* @return true
if and only if the specified
* {@link URI} points to a file that exists and the
* application is allowed to write to the file; false
* otherwise.
*/
public boolean canWrite( URI uri )
{
return false;
}
/**
* Tests whether the application can create the resource at the specified
* {@link URI}. This method tests that all components of the path can
* be created. If the resource pointed by the {@link URI} is read-only,
* this method returns false
.
*
* @return true
if the resource at the specified {@link URI}
* exists or can be created; false
otherwise.
*/
public boolean canCreate( URI uri )
{
return true;
}
/**
* Tests whether the specified {@link URI} is valid. If the resource
* pointed by the {@link URI} exists the method returns true
.
* If the resource does not exist, the method tests that all components
* of the path can be created.
*
* @return true
if the {@link URI} is valid.
*/
public boolean isValid( URI uri )
{
if ( exists( uri ) )
{
return true;
}
return canCreate( uri );
}
/**
* Takes the given {@link URI} and checks if its {@link #toString()}
* representation ends with the specified oldSuffix
. If
* it does, the suffix is replaced with newSuffix
. Both
* suffix parameters must include the leading dot ('.') if the dot is
* part of the suffix. If the specified {@link URI} does not end
* with the oldSuffix
, then the newSuffix
* is simply appended to the end of the original {@link URI}.
*/
public URI convertSuffix( URI uri, String oldSuffix, String newSuffix )
{
final String path = uri.getPath();
final String newPath;
if ( path.endsWith( oldSuffix ) )
{
final int suffixIndex = path.length() - oldSuffix.length();
newPath = path.substring( 0, suffixIndex ) + newSuffix;
}
else
{
newPath = path + newSuffix;
}
return replacePathPart( uri, newPath );
}
/**
* Deletes the content pointed to by the specified {@link URI}. If
* the content is a file (or analogous to a file), then the file is
* removed from its directory (or container). If the content is a
* directory (or analogous to a directory), then the directory is
* removed only if it is empty (i.e. contains no other files or
* directories).
*
* The default implementation simply returns false
* without doing anything.
*
* @return true
if and only if the file or directory
* is successfully deleted; false
otherwise.
*/
public boolean delete( URI uri )
{
return false;
}
/**
* This method ensures that the specified {@link URI} ends with the
* specified suffix
. The suffix does not necessarily
* have to start with a ".", so if a leading "." is required, the
* specified suffix must contain it -- e.g. ".java", ".class".
* * If the {@link URI} already ends in the specified suffix, then * the {@link URI} itself is returned. Otherwise, a new * {@link URI} is created with the the specified suffix appended * to the original {@link URI}'s path part, and the new {@link URI} * is returned.
*
* The default implementation first checks with {@link
* #hasSuffix(URI, String)} to see if the {@link URI} already ends
* with the specified suffix. If not, the suffix is simply appended
* to the path part of the {@link URI}, and the new {@link URI} is
* returned.
*
* @return An {@link URI}, based on the specified {@link URI}, whose
* path part ends with the specified suffix.
*
* @exception NullPointerException if either the specified {@link
* URI} or suffix
is null
. The caller is
* responsible for checking that they are not null
.
*/
public URI ensureSuffix( URI uri, String suffix )
{
if ( hasSuffix( uri, suffix ) )
{
return uri;
}
final String path = uri.getPath();
return replacePathPart( uri, path + suffix );
}
/**
* Compares the specified {@link URI} objects to determine whether
* they point to the same resource. This method returns
* true
if the {@link URI}s point to the same resource
* and returns false
if the {@link URI}s do not point
* to the same resource.
*
* This method and all subclass implementations can assume that both
* {@link URI} parameters are not null
. The
* {@link VirtualFileSystem#equals(URI, URI)} method is responsible for
* checking that the two {@link URI}s are not null
.
*
* It can also be assumed that both {@link URI} parameters have
* the same scheme and that the scheme is appropriate for this
* VirtualFileSystemHelper
. This determination is also
* the responsibility of {@link VirtualFileSystem#equals(URI, URI)}.
*
* The default implementation for this method delegates to
* {@link URI#equals(Object)}.
*/
public boolean equals( URI uri1, URI uri2 )
{
return uri1.equals( uri2 );
}
/**
* Tests whether a resource at the specified {@link URI} location
* currently exists. The test for existence only checks the actual
* location and does not check any in-memory caches.
*
* The default implementation simply returns false
* without doing anything.
*
* @return true
if and only if a resource already exists
* at the specified {@link URI} location; false
* otherwise.
*/
public boolean exists( URI uri )
{
return false;
}
/**
* Returns the name of the file contained by the {@link URI}, not
* including any scheme, authority, directory path, query, or
* fragment. This simply returns the simple filename. For example,
* if you pass in an {@link URI} whose string representation is:
*
*
* scheme://userinfo@host:1010/dir1/dir2/file.ext?query#fragment
*
*
* the returned value is "file.ext
" (without the
* quotes).
*
* @return The simple filename of the specified {@link URI}. This
* value should only be used for display purposes and not for opening
* streams or otherwise trying to locate the document.
*/
public String getFileName( URI uri )
{
if ( uri == null )
{
return ""; //NOTRANS
}
final String path = uri.getPath();
if ( path == null || path.length() == 0 )
{
return ""; //NOTRANS
}
if ( path.equals( "/" ) ) //NOTRANS
{
return "/"; //NOTRANS
}
final int lastSep = path.lastIndexOf( '/' ); //NOTRANS
if ( lastSep == path.length() - 1 )
{
final int lastSep2 = path.lastIndexOf( '/', lastSep - 1 ); //NOTRANS
return path.substring( lastSep2 + 1, lastSep );
}
else
{
return path.substring( lastSep + 1 );
}
}
/**
* Returns the number of bytes contained in the resource that the
* specified {@link URI} points to. If the length cannot be
* determined, -1
is returned.
* * The default implementation returns -1. * * @return the size in bytes of the document at the specified * {@link URI}. */ public long getLength( URI uri ) { return -1; } /** * Returns the name of the file contained by the {@link URI}, not * including any scheme, authority, directory path, file extension, * query, or fragment. This simply returns the simple filename. For * example, if you pass in an {@link URI} whose string representation * is: * *
* scheme://userinfo@host:1010/dir1/dir2/file.ext1.ext2?query#fragment
*
*
* the returned value is "file
" (without the quotes).* * The returned file name should only be used for display purposes * and not for opening streams or otherwise trying to locate the * resource indicated by the {@link URI}.
*
* The default implementation first calls {@link #getFileName(URI)} to
* get the file name part. Then all characters starting with the
* first occurrence of '.' are removed. The remaining string is then
* returned.
*/
public String getName( URI uri )
{
final String fileName = getFileName( uri );
final int firstDot = fileName.indexOf( '.' );
return firstDot > 0 ? fileName.substring( 0, firstDot ) : fileName;
}
/**
* Returns the {@link URI} representing the parent directory of
* the specified {@link URI}. If there is no parent directory,
* then null
is returned.
*
* The default implementation returns the value of invoking
* uri.resolve( ".." )
.
*/
public URI getParent( URI uri )
{
return uri != null ? uri.resolve( ".." ) : null; // NOTRANS
}
/**
* Returns the path part of the {@link URI}. The returned string
* is acceptable to use in one of the {@link URIFactory} methods
* that takes a path.
* * The default implementation delegates to {@link URI#getPath()}. */ public String getPath( URI uri ) { return uri.getPath(); } /** * Returns the path part of the {@link URI} without the last file * extension. To clarify, the following examples demonstrate the * different cases: * *
/dir/file.ext |
* /dir/file |
*
/dir/file.ext1.ext2 |
* /dir/file.ext1 |
*
/dir1.ext1/dir2.ext2/file.ext1.ext2 |
* /dir1.ext1/dir2.ext2/file.ext1 |
*
/file.ext |
* /file |
*
/dir.ext/file |
* /dir.ext/file |
*
/dir/file |
* /dir/file |
*
/file |
* /file |
*
/.ext |
* / |
*
*
* The default implementation returns uri.toString()
.
* If the {@link URI} is null
, the empty string is
* returned.
*
* @return The path portion of the specified {@link URI} in
* platform-dependent notation. This value should only be used for
* display purposes and not for opening streams or otherwise trying
* to locate the document.
*/
public String getPlatformPathName( URI uri )
{
return uri != null ? uri.toString() : ""; //NOTRANS
}
/**
* If a dot ('.') occurs in the filename part of the path part of
* the {@link URI}, then all of the text starting at the last dot is
* returned, including the dot. If the last dot is also the last
* character in the path, then the dot by itself is returned. If
* there is no dot in the file name (even if a dot occurs elsewhere
* in the path), then the empty string is returned.
* * Examples: *
true
if the path part of the {@link URI}
* ends with the given suffix
String. The suffix can
* be any String and doesn't necessarily have to be one that begins
* with a dot ('.'). If you are trying to test whether the path
* part of the {@link URI} ends with a particular file extension,
* then the suffix
parameter must begin with a '.'
* character to ensure that you get the right return value.
*/
public boolean hasSuffix( URI uri, String suffix )
{
final String path = uri.getPath();
return path.endsWith( suffix );
}
/**
* Returns true
if uri1
represents a
* a directory and uri2
points to a location within
* uri1
's directory tree.
*/
public boolean isBaseURIFor( URI uri1, URI uri2 )
{
if ( !isDirectoryPath( uri1 ) )
{
return false;
}
final String uri1String = uri1.toString();
final String uri2String = uri2.toString();
return uri2String.startsWith( uri1String );
}
/**
* Tests whether the location indicated by the {@link URI} is
* a directory.
*
* The default implementation always returns false
.
*
* @return true
if and only if the location indicated
* by the {@link URI} exists and is a directory;
* false
otherwise.
*/
public boolean isDirectory( URI uri )
{
return false;
}
/**
* Tests whether the location indicated by the {@link URI}
* represents a directory path. The directory path specified by
* the {@link URI} need not exist.
* * This method is intended to be a higher performance version of * the {@link #isDirectory(URI)} method. Implementations of this * method should attempt to ascertain whether the specified {@link * URI} represents a directory path by simply examining the {@link * URI} itself. Time consuming i/o operations should be * avoided.
*
* The default implementation returns true
if the path
* part of the {@link URI} ends with a '/' and the query and ref
* parts of the {@link URI} are null.
*
* @return true
if the location indicated by the
* {@link URI} represents a directory path; the directory path need
* not exist.
*/
public boolean isDirectoryPath( URI uri )
{
return ( uri != null &&
uri.getPath().endsWith( "/" ) && //NOTRANS
uri.getQuery() == null &&
uri.getFragment() == null );
}
/**
* Tests whether the resource indiciated by the {@link URI} is a
* hidden file. The exact definition of hidden is
* scheme-dependent and possibly system-dependent. On UNIX
* systems, a file is considered to be hidden if its name begins
* with a period character ('.'). On Win32 systems, a file is
* considered to be hidden if it has been marked as such in the
* file system.
*
* The default implementation always returns false
.
*/
public boolean isHidden( URI uri )
{
return false;
}
/**
* Returns true
if the resource is read-only. A return
* value of false
means that trying to get an
* {@link OutputStream} or trying to write to an {@link OutputStream}
* based on the {@link URI} will cause an IOException to be thrown.
* If the read-only status cannot be determined for some reason,
* this method returns true
.
*
* The default implementation always returns true
. This
* means that all resources are considered read-only unless a
* scheme-specific {@link VirtualFileSystemHelper} is registered for the
* specified {@link URI} and is able to determine that the resource
* underlying the specified {@link URI} is not read-only.
*/
public boolean isReadOnly( URI uri )
{
return true;
}
/**
* Tests whether the resource indiciated by the {@link URI} is
* a regular file. A regular is a file that is not a
* directory and, in addition, satisfies other system-dependent
* criteria.
*
* The default implementation returns the value of
* exists( uri ) && !isDirectory( uri )
.
*
* @return true
if and only if the resource
* indicated by the {@link URI} exists and is a normal
* file.
*/
public boolean isRegularFile( URI uri )
{
return exists( uri ) && !isDirectory( uri );
}
/**
* Returns the last modified time of the resource pointed to by the
* {@link URI}. The returned long
is the number of
* milliseconds since the epoch (00:00:00 GMT Jan 1, 1970). If no
* timestamp is available or if the {@link URI} passed in is
* null
, -1
is returned.
*
* The default implementation returns -1.
*
* @return The last modified time of the document pointed to by the
* specified {@link URI}.
*/
public long lastModified( URI uri )
{
return -1;
}
/**
* Returns an array of {@link URI}s naming files and directories in
* the directory indicated by the {@link URI}. If the specified
* {@link URI} does not represent a directory, then this method
* returns null
. Otherwise, an array of {@link URI}s
* is returned, one for each file or directory in the directory.
* {@link URI}s representing the directory itself or its parent are
* not included in the result. There is no guarantee that the
* {@link URI}s will occur in any particular order.
*
* The default implementation always returns an empty {@link URI}
* array.
*
* @return An array of {@link URI}s naming the files and directories
* in the directory indicated by the {@link URI}. The array will
* be empty if the directory is empty. Returns null
* if the {@link URI} does not represent a directory or if an
* I/O error occurs.
*/
public URI[] list( URI uri )
{
return new URI[0];
}
/**
* Returns an array of {@link URI}s naming files and directories in
* the directory indicated by the {@link URI}; the specified
* {@link URIFilter} is applied to determine which {@link URI}s will
* be returned. If the specified {@link URI} does not represent a
* directory, then this method returns null
.
* Otherwise, an array of {@link URI}s is returned, one for each file
* or directory in the directory that is accepted by the specified
* filter. {@link URI}s representing the directory itself or its
* parent are not included in the result. There is no guarantee that
* the {@link URI}s will occur in any particular order.
*
* If the specified {@link URIFilter} is null
then
* no filtering behavior is done.
*
* The default implementation calls {@link #list(URI)} first and
* then applies the {@link URIFilter} to the resulting list.
*
* @return An array of {@link URI}s naming the files and directories
* in the directory indicated by the {@link URI} that are accepted
* by the specified {@link URIFilter}. The array will be empty if
* the directory is empty. Returns null
if the
* {@link URI} does not represent a directory or if an I/O error
* occurs.
*/
public URI[] list( URI uri, URIFilter filter )
{
final URI[] list = list( uri );
if ( list == null )
{
return null;
}
if ( filter == null )
{
return list;
}
final ArrayList filteredList = new ArrayList();
for ( int i = list.length - 1; i >= 0; i-- )
{
final URI fileURI = list[i];
if ( filter.accept( fileURI ) )
{
filteredList.add( fileURI );
}
}
return (URI[]) filteredList.toArray( new URI[filteredList.size()] );
}
/**
* Lists the root "file systems" that are supported by this helper. The
* returned URIs are used by file chooser dialogs to list all of the
* roots that are available for the user to browse. If no root file
* systems are supported, this method must return null
or
* an empty URI array. If the returned array is not empty, then each
* URI contained in it must represent a directory and must not be null.
*
*
* The default implementation always returns null
.
*/
public URI[] listRoots()
{
return null;
}
/**
* Creates the directory indicated by the {@link URI}.
*
* The default implementation always returns false
.
*
* @return true
if and only if the directory was
* created; false
otherwise.
*/
public boolean mkdir( URI uri )
{
return false;
}
/**
* Creates the directory indicated by the specified {@link URI}
* including any necessary but nonexistent parent directories. Note
* that if this operation fails, it may have succeeded in creating
* some of the necessary parent directories. This method returns
* true
if the directory was created along with all
* necessary parent directories or if the directories already
* exist; it returns false
otherwise.
*
* @return true
if all directories were created
* successfully or exist; false
if there was a
* failure somewhere.
* Note that even if false
is returned, some directories
* may still have been created.
*/
public boolean mkdirs( URI uri )
{
return false;
}
/**
* Creates a new empty temporary file in the specified directory using the
* given prefix and suffix strings to generate its name.
*
* Subclasses must implement this method given that default implementation
* does nothing and returns null.
*
* @param prefix The prefix string to be used in generating the file's name;
* must be at least three characters long
*
* @param suffix The directory in which the file is to be created, or
* null if the default temporary-file directory is to be used
*
* @param directory The directory in which the file is to be created,
* or null if the default temporary-file directory is to be used
*
* @return The URI
to the temporary file.
*/
public URI createTempFile( String prefix, String suffix, URI directory )
throws IOException
{
return null;
}
/**
* Opens an {@link InputStream} on the specified {@link URI}.
* * The default implementation throws {@link UnknownServiceException}. * * @return The {@link InputStream} associated with the {@link URI}. * * @exception java.io.FileNotFoundException if the resource at the * specified URI does not exist. * * @exception IOException if an I/O error occurs when trying to open * the {@link InputStream}. * * @exception java.net.UnknownServiceException (a runtime exception) if * the scheme does not support opening an {@link InputStream}. */ public InputStream openInputStream( URI uri ) throws IOException { throw new UnknownServiceException(); } /** * Opens an {@link OutputStream} on the {@link URI}. If the file * does not exist, the file should be created. If the directory * path to the file does not exist, all necessary directories * should be created.
* * The default implementation throws {@link UnknownServiceException}. * * @param uri An {@link OutputStream} is opened on the given * {@link URI}. The operation is scheme-dependent. * * @return The {@link OutputStream} associated with the {@link URI}. * * @exception IOException if an I/O error occurs when trying to open * the {@link OutputStream}. * * @exception java.net.UnknownServiceException (a runtime exception) * if the scheme does not support opening an {@link OutputStream}. */ public OutputStream openOutputStream( URI uri ) throws IOException { throw new UnknownServiceException(); } /** * Renames the resource indicated by the first {@link URI} to the * name indicated by the second {@link URI}.
*
* The default implementation simply returns false
* without doing anything.
*
* If either {@link URI} parameter is null
or if both
* of the specified {@link URI} parameters refer to the same
* resource, then the rename is not attempted and failure is
* returned.
*
* If the specified {@link URI} parameters do not have the same
* scheme, then the VirtualFileSystem
handles the rename
* by first copying the resource to the destination with {@link
* VirtualFileSystem#copy(URI, URI)} and then deleting the original
* resource with {@link VirtualFileSystem#delete(URI)}; if either
* operation fails, then failure is returned.
*
* Otherwise, the scheme helper is called to perform the actual
* rename operation. Scheme helper implementations may therefore
* assume that both {@link URI} parameters are not
* null
, do not refer to the same resource, and have
* the same scheme.
*
* If the original {@link URI} refers to a nonexistent resource,
* then the scheme helper implementations should return failure.
* It is left up to the scheme helper implementations to decide
* whether to overwrite the destination or return failure if the
* destination {@link URI} refers to an existing resource.
*
* @param oldURI the {@link URI} of the original resource
* @param newURI the desired {@link URI} for the renamed resource
*
* @return true
if and only if the resource is
* successfully renamed; false
otherwise.
*
* @see VirtualFileSystem#renameTo(URI, URI)
*/
public boolean renameTo( URI oldURI, URI newURI )
{
return false;
}
/**
* Sets the last-modified timestamp of the resource indicated by
* the {@link URI} to the time specified by time
.
* The time is specified in the number of milliseconds since
* the epoch (00:00:00 GMT Jan 1, 1970). The return value
* indicates whether or not the setting of the timestamp
* succeeded.
*
* The default implementation always returns false
* without doing anything.
*/
public boolean setLastModified( URI uri, long time )
{
return false;
}
/**
* Sets the read-only status of the resource indicated by the
* {@link URI} according to the specified readOnly
* flag. The return value indicates whether or not the setting
* of the read-only flag succeeded.
*
* The default implementation always returns false
* without doing anything.
*/
public boolean setReadOnly( URI uri, boolean readOnly )
{
return false;
}
/**
* Returns a displayable form of the complete {@link URI}.
*
* The default implementation delegates to {@link URI#toString()}.
*/
public String toDisplayString( URI uri )
{
return uri == null ? "" : uri.toString(); //NOTRANS
}
/**
* This method attempts all possible ways of deriving a
* relative URI reference as described in
* RFC 2396
* using the uri
parameter as the {@link URI}
* whose relative URI reference is to be determined and the
* base
parameter as the {@link URI} that serves as the
* base document for the uri
pararmeter. If it is not
* possible to produce a relative URI reference because the two
* {@link URI}s are too different, then a full, absolute
* reference for the uri
parameter is returned.
*
* Whatever value is returned by this method, it can be used in
* conjunction with the base
{@link URI} to
* reconstruct the fully-qualified {@link URI} by using one of the
* {@link URI} constructors that takes a context {@link URI} plus
* a {@link String} spec (i.e. the {@link String} returned by this
* method).
*
* Both the uri
and base
parameters should
* point to documents and be absolute {@link URI}s. Specifically,
* the base
parameter does not need to be modified to
* represent the base directory if the base
parameter
* already points to a document that is in the directory to which
* the uri
parameter will be made relative. This
* relationship between uri
and base
is
* exactly how relative references are treated within HTML documents.
* Relative references in an HTML page are resolved against the HTML
* page's base URI. The base URI is the HTML page itself, not the
* directory that contains it.
*
* If either the uri
or base
parameter
* needs to represent a directory rather than a file, they must end
* with a "/" in the path part of the {@link URI}, such as:
*
* http://host.com/root/my_directory/
*
*
* The algorithm used by this method to determine the relative * reference closely follows the recommendations made in * RFC 2396. The * following steps are performed, in order, to determine the * relative reference: *
uri
* were consumed, then those path elements are appended
* to the resulting relative path as well. If the first
* remaining path element in uri
contains
* a ':' character and there is no "../" sequence was
* prepended to the relative reference, then a "./"
* sequence is prepended to prevent the ':' character
* from being interpreted as a scheme delimiter (this
* is a special case in RFC
* 2396).
* uri
are not appended.* * This method is implemented using the template method * design pattern, so it is possible for subclasses to override just * part of the algorithm in order to handle scheme-specific * details. */ public String toRelativeSpec( URI uri, URI base ) { return toRelativeSpec( uri, base, false ); } /** * Variant of {@link #toRelativeSpec(URI, URI)} that has a flag * that indicates whether the base {@link URI} should be fully * consumed in the process of calculating the relative spec.
*
* If mustConsumeBase
is true
, then
* this method will return a non-null
relative
* spec if and only if the base {@link URI} was fully consumed
* in the process of calculating the relative spec. Otherwise,
* if any part of the base {@link URI} remained, then this
* method returns null
.
*
* If mustConsumeBase
is false
, then
* this method will return a non-null
relative
* spec regardless of how much of the base {@link URI} is
* consumed during the determination.
*/
public String toRelativeSpec( URI uri, URI base, boolean mustConsumeBase )
{
// The first step is to check that the scheme parts are
// identical. If they are not identical, then the returned
// reference should just be absolute.
if ( !haveSameScheme( uri, base ) ||
!haveSameAuthority( uri, base ) )
{
return mustConsumeBase ? null : uri.toString();
}
final StringBuffer relativeURI = new StringBuffer();
final boolean baseFullyConsumed = appendRelativePath( uri, base, relativeURI, mustConsumeBase );
if ( mustConsumeBase && !baseFullyConsumed )
{
return null;
}
return relativeURI.toString();
}
/**
* This method gets the base directory fully containing the relative path.
*
* The uri
should be absolute and point to a directory.
* It must end with a "/" in the path part of the {@link URI}, such as:
*
* http://host.com/root/my_directory/
*
* If the uri
does not end with a "/", it will be assumed
* that the uri
points to a document. The document name will
* then be stripped in order to determine the parent directory.
*
* The relativeSpec
parameter should be a relative path.
* If the relativeSpec
does not end with a "/", it will be
* assumed that the relativeSpec
points to a document.
* The document name will then be stripped in order to determine the
* parent directory.
*
* For example, if the uri
points to:
*
* file://c:/root/dir1/dir2/dir3/
*
* and the relativeSpec
is:
*
* dir2/dir3
*
* The returned value would be:
*
* file://c:/root/dir1/
*
*
* If the relativeSpec
path elements are not fully
* contained in the last part of the uri
path the
* value returned is the uri itself if the uri path ends with a
* "/" or the uri parent otherwise.
*/
public URI getBaseParent( URI uri, String relativeSpec )
{
String basePath = uri.getPath();
final int basePathIndex = basePath.lastIndexOf( '/' ); //NOTRANS
// If the base path ends with a "/" get the parent uri.
if ( basePathIndex < basePath.length() - 1 )
{
uri = getParent(uri);
basePath = uri.getPath();
}
final String baseDir = basePath.substring( 0, basePathIndex );
final int relPathIndex = relativeSpec.lastIndexOf( '/' );
// If the relative path does not containe a "/" we assume we
// are dealing with a document whose base parent directory
// is just the specified uri.
if ( relPathIndex < 0 )
{
return uri;
}
final String relDir =
relativeSpec.substring( 0, relativeSpec.lastIndexOf( '/' ) ); //NOTRANS
final StringTokenizer relTokenizer = new StringTokenizer( relDir, "/" ); //NOTRANS
String relToken = relTokenizer.nextToken();
final StringTokenizer baseTokenizer = new StringTokenizer( baseDir, "/" ); //NOTRANS
int ctr = 0;
boolean foundBasePath = false;
while ( baseTokenizer.hasMoreTokens() )
{
final String baseToken = baseTokenizer.nextToken();
if ( areEqualPathElems( relToken, baseToken ) )
{
final int baseCount = baseTokenizer.countTokens();
final int relCount = relTokenizer.countTokens();
if ( baseCount == relCount )
{
if ( relCount != 0 )
{
relToken = relTokenizer.nextToken();
ctr++;
}
else
{
foundBasePath = true;
break;
}
}
}
else if ( ctr != 0 )
{
ctr = 0;
break;
}
}
if ( foundBasePath )
{
for ( int i = ctr; i >= 0; i-- )
{
uri = getParent( uri );
}
}
return uri;
}
/**
* Get an {@link URL} from an {@link URI}. This method just calls the
* {@link URI#toURL} method.
*/
public URL toURL( URI uri ) throws MalformedURLException
{
return uri.toURL();
}
//--------------------------------------------------------------------------
// helpers for equals
//--------------------------------------------------------------------------
/**
* Returns true
if the URIs user infos are equal.
*/
protected boolean haveSameUserInfo( URI uri1, URI uri2 )
{
return areEqual( uri1.getUserInfo(), uri2.getUserInfo() );
}
/**
* Returns true
if the URIs hosts are equal.
*/
protected boolean haveSameHost( URI uri1, URI uri2 )
{
return areEqual( uri1.getHost(), uri2.getHost() );
}
/**
* Returns true
if the URIs paths are equal.
*/
protected boolean haveSamePath( URI uri1, URI uri2 )
{
return areEqual( uri1.getPath(), uri2.getPath() );
}
/**
* Returns true
if the URIs queries are equal.
*/
protected boolean haveSameQuery( URI uri1, URI uri2 )
{
return areEqual( uri1.getQuery(), uri2.getQuery() );
}
/**
* Returns true
if the URIs refs are equal.
*/
protected boolean haveSameRef( URI uri1, URI uri2 )
{
return areEqual( uri1.getFragment(), uri2.getFragment() );
}
/**
* Returns true
if the URIs ports are equal.
*/
protected boolean haveSamePort( URI uri1, URI uri2 )
{
return uri1.getPort() == uri2.getPort();
}
/**
* Compares the two string for equality.
*/
protected final boolean areEqual( String s1, String s2 )
{
return s1 == s2 || ( s1 != null && s1.equals( s2 ) );
}
//--------------------------------------------------------------------------
// template method helpers for toRelativeSpec(...)
//--------------------------------------------------------------------------
/**
* This is a helper for the {@link #toRelativeSpec(URI, URI)} method,
* which uses the template method design pattern.
*
* By default, the uri
and base
parameters
* must have identical schemes as a prerequisite to being able to
* produce a relative {@link URI} spec.
*/
protected boolean haveSameScheme( URI uri, URI base )
{
if ( uri == null || base == null )
{
return false;
}
final String uriScheme = uri.getScheme();
final String baseScheme = base.getScheme();
return uriScheme.equals( baseScheme );
}
/**
* This is a helper for the {@link #toRelativeSpec(URI, URI)} method,
* which uses the template method design pattern.
* * The "authority" part is a combination of the user info, hostname, * and port number. The full syntax in the {@link URI} string is: *
* userinfo@hostname:port
*
*
* It may appear in an {@link URI} such as:
*
* ftp://jsr198eg@ide.com:21/builds/ri.zip
*
*
* The authority part may be null
, if the {@link URI}
* scheme does not require one.
*
* By default, the uri
and base
parameters
* must have identical authority strings as a prerequisite to being
* able to produce a relative {@link URI} spec.
*/
protected boolean haveSameAuthority( URI uri, URI base )
{
if ( uri == base )
{
return true;
}
else if ( uri == null || base == null )
{
return false;
}
final String uriAuthority = uri.getAuthority();
final String baseAuthority = base.getAuthority();
if ( uriAuthority == null )
{
return baseAuthority == null;
}
else if ( baseAuthority != null )
{
return uriAuthority.equals( baseAuthority );
}
else
{
return false;
}
}
/**
* This is a helper for the {@link #toRelativeSpec(URI, URI)} method,
* which uses the template method design pattern.
*
* @return true
if the entire base {@link URI} was
* consumed in the process of determining the relative path;
* false
otherwise (i.e. not all of the base {@link URI}
* was consumed).
*/
protected boolean appendRelativePath( URI uri, URI base, StringBuffer relativeURI,
boolean mustConsumeBase )
{
// By this point, it's known that the scheme and authority parts
// of the URIs match, so that it is possible to generate a relative
// URI spec that omits the scheme and authority parts. The
// process of converting the path part to a relative form begins
// here.
final String uriPath = uri.getPath();
final int uriLastSlash = uriPath.lastIndexOf( '/' );
// Chop off the file name from the uri parameter so that its
// filename can't match a directory in the base URI that happens to
// have the same name.
final String uriDir = uriPath.substring( 0, uriLastSlash );
final String uriFileName = uriPath.substring( uriLastSlash + 1 );
final StringTokenizer uriTokenizer = new StringTokenizer( uriDir, "/" ); //NOTRANS
final String basePath = base.getPath();
final String baseDir = basePath.substring( 0, basePath.lastIndexOf( '/' ) ); //NOTRANS
final StringTokenizer baseTokenizer = new StringTokenizer( baseDir, "/" ); //NOTRANS
final int numBaseTokens = baseTokenizer.countTokens();
// Iterate through the parts of the uriDir and baseDir, until either
// a non-matching part is found or either uriDir or baseDir runs out
// of tokens.
int numMatches = 0;
String uriToken = null;
while ( uriTokenizer.hasMoreTokens() && baseTokenizer.hasMoreTokens() )
{
uriToken = uriTokenizer.nextToken();
final String baseToken = baseTokenizer.nextToken();
if ( areEqualPathElems( uriToken, baseToken ) )
{
numMatches++;
uriToken = null;
continue;
}
break;
}
final boolean baseFullyConsumed = ( numBaseTokens == numMatches );
if ( mustConsumeBase && !baseFullyConsumed )
{
// Abort processing and just return false.
return false;
}
if ( numMatches == 0 )
{
// If there are no matching parts in uriDir and baseDir at all,
// then the absolute path should be used in the relative URI spec.
// This covers the drive-letter case on Win32.
relativeURI.append( uriPath );
}
else
{
// If there were some matches, first prepend one or more "../"
// sequences to the resulting relative URI spec if not all of
// the baseDir tokens were consumed.
final int numDotDotsNeeded = numBaseTokens - numMatches;
for ( int i = numDotDotsNeeded; i > 0; i-- )
{
relativeURI.append( "../" ); // NOTRANS
}
if ( uriToken != null && uriToken.length() > 0 )
{
if ( numDotDotsNeeded == 0 && uriToken.indexOf( ':' ) >= 0 ) //NOTRANS
{
// This is a special case in RFC 2396. If the relative path
// contains a ':' in the first part of the path, then a
// "./" sequence needs to be prepended to prevent the ':'
// from being interpreted as delimiting the scheme. The
// "./" does not need to be prepended if any "../" sequences
// have alread been prepended. RFC 2396 states that a ':'
// can appear in the path part only after the first "/"
// character.
relativeURI.append( "./" ); // NOTRANS //NOTRANS
}
relativeURI.append( uriToken ).append( '/' ); //NOTRANS
}
// Append any other remaining path elements from the uri
// parameter.
while ( uriTokenizer.hasMoreTokens() )
{
relativeURI.append( uriTokenizer.nextToken() ).append( '/' ); //NOTRANS
}
// Must append the filename too! It was chopped off during the
// processing of path elements to prevent the filename from
// matching a directory name in the base URI.
relativeURI.append( uriFileName );
}
return baseFullyConsumed;
}
/**
* This is a helper for the
* {@link #appendRelativePath(URI, URI, StringBuffer, boolean)} method,
* which uses the template method design pattern.
*
* The two {@link String}s that are passed in represent elements
* of the path parts of the uri
and base
* parameters that are passed into
* {@link #appendRelativePath(URI, URI, StringBuffer, boolean)}.
* * By default, path elements are compared exactly in a * case-sensitive manner using regular {@link String} comparison. */ protected boolean areEqualPathElems( String uriElem, String baseElem ) { return ( uriElem.equals( baseElem ) ); } private URI replacePathPart( URI uri, String newPath ) { try { return new URI( uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), newPath, uri.getQuery(), uri.getFragment() ); } catch ( Exception e ) { e.printStackTrace(); return null; } } }