/* * 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: * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Path part of input {@link URI}
Output {@link String} *
/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 gets the path from {@link * #getPath(URI)} and then trims off all of the characters beginning * with the last "." in the path, if and only if the last "." comes * after the last "/" in the path. If the last "." comes before * the last "/" or if there is no "." at all, then the entire path * is returned. */ public String getPathNoExt( URI uri ) { final String path = getPath( uri ); final int lastSlash = path.lastIndexOf( "/" ); //NOTRANS final int lastDot = path.lastIndexOf( "." ); //NOTRANS if ( lastDot <= lastSlash ) { // When the lastDot < lastSlash, it means that one of the // directories has an extension, but the filename itself has // no extension. In this case, returning the whole path is // the correct behavior. // // The only time that lastDot and lastSlash can be equal occurs // when both of them are -1. In that case, returning the whole // path is the correct behavior. return path; } // At this point, we know that lastDot must be non-negative, so // we can return the whole path string up to the last dot. return path.substring( 0, lastDot ); } /** * Returns the platform-dependent String representation of the * {@link URI}; the returned string should be considered acceptable * for users to read. In general, the returned string should omit * as many parts of the {@link URI} as possible. For the "file" * scheme, therefore, the platform pathname should just be the * pathname alone (no scheme) using the appropriate file separator * character for the current platform. For other schemes, it may * be necessary to reformat the {@link URI} string into a more * human-readable form. That decision is left to each * {@link VirtualFileSystemHelper} implementor.

* * 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: *

*/ public String getSuffix( URI uri ) { final String path = uri.getPath(); final int lastDot = path.lastIndexOf( '.' ); final int lastSlash = path.lastIndexOf( '/' ); return ( lastDot >= 0 && lastDot > lastSlash ) ? path.substring( lastDot ) : ""; //NOTRANS } /** * Returns 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: *

    *
  1. The scheme parts are checked first. If they do not * match exactly, then an absolute reference is returned. *
  2. The authority parts are checked next. If they do not * match exactly, then an absolute reference is returned. *
  3. If the scheme and authority parts match exactly, then * it is possible to calculate a relative reference. The * path parts are then compared element-by-element to * determine the relative path, using the following steps, * in order: *
      *
    1. If no path elements are in common, then an absolute * path is used and relative-path determination stops. *
    2. Otherwise, any path parts that are in common are * omitted from the relative path. Comparison of path * elements when the scheme is "file" is done using * instance of {@link java.io.File} so that, for example, * on Win32 the comparison is case-insensitive, * whereas on Unix the comparison is * case-sensitive. When the scheme is not * "file", comparison is always case-sensitive. *
    3. If, after matching as many path elements as possible, * there are still path elements remaining in the base * {@link URI} (except for the document name itself), * then a "../" sequence is prepended to the resulting * relative path for each base path element that was not * consumed while matching path elements. *
    4. If not all of the path elements in 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). *
    *
* * After the path part has been processed, no further processing * is done. In particular, the query part and path part of the * 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; } } }