org.codehaus.plexus.util.PathTool Maven / Gradle / Ivy
Show all versions of plexus-utils Show documentation
package org.codehaus.plexus.util;
/*
* Copyright The Codehaus Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.File;
import java.util.StringTokenizer;
/**
* Path tool contains static methods to assist in determining path-related information such as relative paths.
*
* @author Pete Kazmier
* @author Vincent Massol
* @author Vincent Siveton
* @version $Id$
*/
public class PathTool
{
/**
* Determines the relative path of a filename from a base directory. This method is useful in building relative
* links within pages of a web site. It provides similar functionality to Anakia's $relativePath
* context variable. The arguments to this method may contain either forward or backward slashes as file separators.
* The relative path returned is formed using forward slashes as it is expected this path is to be used as a link in
* a web page (again mimicking Anakia's behavior).
*
*
This method is thread-safe.
*
*
* PathTool.getRelativePath( null, null ) = ""
* PathTool.getRelativePath( null, "/usr/local/java/bin" ) = ""
* PathTool.getRelativePath( "/usr/local/", null ) = ""
* PathTool.getRelativePath( "/usr/local/", "/usr/local/java/bin" ) = ".."
* PathTool.getRelativePath( "/usr/local/", "/usr/local/java/bin/java.sh" ) = "../.."
* PathTool.getRelativePath( "/usr/local/java/bin/java.sh", "/usr/local/" ) = ""
*
*
* @param basedir The base directory.
* @param filename The filename that is relative to the base directory.
* @return The relative path of the filename from the base directory. This value is not terminated with a forward
* slash. A zero-length string is returned if: the filename is not relative to the base directory,
* basedir
is null or zero-length, or filename
is null or zero-length.
*/
public static final String getRelativePath( String basedir, String filename )
{
basedir = uppercaseDrive( basedir );
filename = uppercaseDrive( filename );
/*
* Verify the arguments and make sure the filename is relative to the base directory.
*/
if ( basedir == null || basedir.length() == 0 || filename == null || filename.length() == 0
|| !filename.startsWith( basedir ) )
{
return "";
}
/*
* Normalize the arguments. First, determine the file separator that is being used, then strip that off the end
* of both the base directory and filename.
*/
String separator = determineSeparator( filename );
basedir = StringUtils.chompLast( basedir, separator );
filename = StringUtils.chompLast( filename, separator );
/*
* Remove the base directory from the filename to end up with a relative filename (relative to the base
* directory). This filename is then used to determine the relative path.
*/
String relativeFilename = filename.substring( basedir.length() );
return determineRelativePath( relativeFilename, separator );
}
/**
* Determines the relative path of a filename. This method is useful in building relative links within pages of a
* web site. It provides similar functionality to Anakia's $relativePath
context variable. The argument
* to this method may contain either forward or backward slashes as file separators. The relative path returned is
* formed using forward slashes as it is expected this path is to be used as a link in a web page (again mimicking
* Anakia's behavior).
*
* This method is thread-safe.
*
* @param filename The filename to be parsed.
* @return The relative path of the filename. This value is not terminated with a forward slash. A zero-length
* string is returned if: filename
is null or zero-length.
* @see #getRelativeFilePath(String, String)
*/
public static final String getRelativePath( String filename )
{
filename = uppercaseDrive( filename );
if ( filename == null || filename.length() == 0 )
{
return "";
}
/*
* Normalize the argument. First, determine the file separator that is being used, then strip that off the end
* of the filename. Then, if the filename doesn't begin with a separator, add one.
*/
String separator = determineSeparator( filename );
filename = StringUtils.chompLast( filename, separator );
if ( !filename.startsWith( separator ) )
{
filename = separator + filename;
}
return determineRelativePath( filename, separator );
}
/**
* Determines the directory component of a filename. This is useful within DVSL templates when used in conjunction
* with the DVSL's $context.getAppValue("infilename")
to get the current directory that is currently
* being processed.
*
* This method is thread-safe.
*
*
* PathTool.getDirectoryComponent( null ) = ""
* PathTool.getDirectoryComponent( "/usr/local/java/bin" ) = "/usr/local/java"
* PathTool.getDirectoryComponent( "/usr/local/java/bin/" ) = "/usr/local/java/bin"
* PathTool.getDirectoryComponent( "/usr/local/java/bin/java.sh" ) = "/usr/local/java/bin"
*
*
* @param filename The filename to be parsed.
* @return The directory portion of the filename
. If the filename does not contain a directory
* component, "." is returned.
*/
public static final String getDirectoryComponent( String filename )
{
if ( filename == null || filename.length() == 0 )
{
return "";
}
String separator = determineSeparator( filename );
String directory = StringUtils.chomp( filename, separator );
if ( filename.equals( directory ) )
{
return ".";
}
return directory;
}
/**
* Calculates the appropriate link given the preferred link and the relativePath of the document.
*
*
* PathTool.calculateLink( "/index.html", "../.." ) = "../../index.html"
* PathTool.calculateLink( "http://plexus.codehaus.org/plexus-utils/index.html", "../.." ) = "http://plexus.codehaus.org/plexus-utils/index.html"
* PathTool.calculateLink( "/usr/local/java/bin/java.sh", "../.." ) = "../../usr/local/java/bin/java.sh"
* PathTool.calculateLink( "../index.html", "/usr/local/java/bin" ) = "/usr/local/java/bin/../index.html"
* PathTool.calculateLink( "../index.html", "http://plexus.codehaus.org/plexus-utils" ) = "http://plexus.codehaus.org/plexus-utils/../index.html"
*
*
* @param link
* @param relativePath
* @return String
*/
public static final String calculateLink( String link, String relativePath )
{
if ( link == null )
{
link = "";
}
if ( relativePath == null )
{
relativePath = "";
}
// This must be some historical feature
if ( link.startsWith( "/site/" ) )
{
return link.substring( 5 );
}
// Allows absolute links in nav-bars etc
if ( link.startsWith( "/absolute/" ) )
{
return link.substring( 10 );
}
// This traps urls like http://
if ( link.contains( ":" ) )
{
return link;
}
// If relativepath is current directory, just pass the link through
if ( StringUtils.equals( relativePath, "." ) )
{
if ( link.startsWith( "/" ) )
{
return link.substring( 1 );
}
return link;
}
// If we don't do this, you can end up with ..//bob.html rather than ../bob.html
if ( relativePath.endsWith( "/" ) && link.startsWith( "/" ) )
{
return relativePath + "." + link.substring( 1 );
}
if ( relativePath.endsWith( "/" ) || link.startsWith( "/" ) )
{
return relativePath + link;
}
return relativePath + "/" + link;
}
/**
* This method can calculate the relative path between two paths on a web site.
*
*
* PathTool.getRelativeWebPath( null, null ) = ""
* PathTool.getRelativeWebPath( null, "http://plexus.codehaus.org/" ) = ""
* PathTool.getRelativeWebPath( "http://plexus.codehaus.org/", null ) = ""
* PathTool.getRelativeWebPath( "http://plexus.codehaus.org/",
* "http://plexus.codehaus.org/plexus-utils/index.html" ) = "plexus-utils/index.html"
* PathTool.getRelativeWebPath( "http://plexus.codehaus.org/plexus-utils/index.html",
* "http://plexus.codehaus.org/" = "../../"
*
*
* @param oldPath
* @param newPath
* @return a relative web path from oldPath
.
*/
public static final String getRelativeWebPath( final String oldPath, final String newPath )
{
if ( StringUtils.isEmpty( oldPath ) || StringUtils.isEmpty( newPath ) )
{
return "";
}
String resultPath = buildRelativePath( newPath, oldPath, '/' );
if ( newPath.endsWith( "/" ) && !resultPath.endsWith( "/" ) )
{
return resultPath + "/";
}
return resultPath;
}
/**
* This method can calculate the relative path between two paths on a file system.
*
*
* PathTool.getRelativeFilePath( null, null ) = ""
* PathTool.getRelativeFilePath( null, "/usr/local/java/bin" ) = ""
* PathTool.getRelativeFilePath( "/usr/local", null ) = ""
* PathTool.getRelativeFilePath( "/usr/local", "/usr/local/java/bin" ) = "java/bin"
* PathTool.getRelativeFilePath( "/usr/local", "/usr/local/java/bin/" ) = "java/bin"
* PathTool.getRelativeFilePath( "/usr/local/java/bin", "/usr/local/" ) = "../.."
* PathTool.getRelativeFilePath( "/usr/local/", "/usr/local/java/bin/java.sh" ) = "java/bin/java.sh"
* PathTool.getRelativeFilePath( "/usr/local/java/bin/java.sh", "/usr/local/" ) = "../../.."
* PathTool.getRelativeFilePath( "/usr/local/", "/bin" ) = "../../bin"
* PathTool.getRelativeFilePath( "/bin", "/usr/local/" ) = "../usr/local"
*
*
* Note: On Windows based system, the /
character should be replaced by \
character.
*
* @param oldPath
* @param newPath
* @return a relative file path from oldPath
.
*/
public static final String getRelativeFilePath( final String oldPath, final String newPath )
{
if ( StringUtils.isEmpty( oldPath ) || StringUtils.isEmpty( newPath ) )
{
return "";
}
// normalise the path delimiters
String fromPath = new File( oldPath ).getPath();
String toPath = new File( newPath ).getPath();
// strip any leading slashes if its a windows path
if ( toPath.matches( "^\\[a-zA-Z]:" ) )
{
toPath = toPath.substring( 1 );
}
if ( fromPath.matches( "^\\[a-zA-Z]:" ) )
{
fromPath = fromPath.substring( 1 );
}
// lowercase windows drive letters.
if ( fromPath.startsWith( ":", 1 ) )
{
fromPath = Character.toLowerCase( fromPath.charAt( 0 ) ) + fromPath.substring( 1 );
}
if ( toPath.startsWith( ":", 1 ) )
{
toPath = Character.toLowerCase( toPath.charAt( 0 ) ) + toPath.substring( 1 );
}
// check for the presence of windows drives. No relative way of
// traversing from one to the other.
if ( ( toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) )
&& ( !toPath.substring( 0, 1 ).equals( fromPath.substring( 0, 1 ) ) ) )
{
// they both have drive path element but they dont match, no
// relative path
return null;
}
if ( ( toPath.startsWith( ":", 1 ) && !fromPath.startsWith( ":", 1 ) )
|| ( !toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) )
{
// one has a drive path element and the other doesnt, no relative
// path.
return null;
}
String resultPath = buildRelativePath( toPath, fromPath, File.separatorChar );
if ( newPath.endsWith( File.separator ) && !resultPath.endsWith( File.separator ) )
{
return resultPath + File.separator;
}
return resultPath;
}
// ----------------------------------------------------------------------
// Private methods
// ----------------------------------------------------------------------
/**
* Determines the relative path of a filename. For each separator within the filename (except the leading if
* present), append the "../" string to the return value.
*
* @param filename The filename to parse.
* @param separator The separator used within the filename.
* @return The relative path of the filename. This value is not terminated with a forward slash. A zero-length
* string is returned if: the filename is zero-length.
*/
private static final String determineRelativePath( String filename, String separator )
{
if ( filename.length() == 0 )
{
return "";
}
/*
* Count the slashes in the relative filename, but exclude the leading slash. If the path has no slashes, then
* the filename is relative to the current directory.
*/
int slashCount = StringUtils.countMatches( filename, separator ) - 1;
if ( slashCount <= 0 )
{
return ".";
}
/*
* The relative filename contains one or more slashes indicating that the file is within one or more
* directories. Thus, each slash represents a "../" in the relative path.
*/
StringBuilder sb = new StringBuilder();
for ( int i = 0; i < slashCount; i++ )
{
sb.append( "../" );
}
/*
* Finally, return the relative path but strip the trailing slash to mimic Anakia's behavior.
*/
return StringUtils.chop( sb.toString() );
}
/**
* Helper method to determine the file separator (forward or backward slash) used in a filename. The slash that
* occurs more often is returned as the separator.
*
* @param filename The filename parsed to determine the file separator.
* @return The file separator used within filename
. This value is either a forward or backward slash.
*/
private static final String determineSeparator( String filename )
{
int forwardCount = StringUtils.countMatches( filename, "/" );
int backwardCount = StringUtils.countMatches( filename, "\\" );
return forwardCount >= backwardCount ? "/" : "\\";
}
/**
* Cygwin prefers lowercase drive letters, but other parts of maven use uppercase
*
* @param path
* @return String
*/
static final String uppercaseDrive( String path )
{
if ( path == null )
{
return null;
}
if ( path.length() >= 2 && path.charAt( 1 ) == ':' )
{
path = Character.toUpperCase( path.charAt( 0 ) ) + path.substring( 1 );
}
return path;
}
private static final String buildRelativePath( String toPath, String fromPath, final char separatorChar )
{
// use tokeniser to traverse paths and for lazy checking
StringTokenizer toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) );
StringTokenizer fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) );
int count = 0;
// walk along the to path looking for divergence from the from path
while ( toTokeniser.hasMoreTokens() && fromTokeniser.hasMoreTokens() )
{
if ( separatorChar == '\\' )
{
if ( !fromTokeniser.nextToken().equalsIgnoreCase( toTokeniser.nextToken() ) )
{
break;
}
}
else
{
if ( !fromTokeniser.nextToken().equals( toTokeniser.nextToken() ) )
{
break;
}
}
count++;
}
// reinitialise the tokenisers to count positions to retrieve the
// gobbled token
toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) );
fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) );
while ( count-- > 0 )
{
fromTokeniser.nextToken();
toTokeniser.nextToken();
}
String relativePath = "";
// add back refs for the rest of from location.
while ( fromTokeniser.hasMoreTokens() )
{
fromTokeniser.nextToken();
relativePath += "..";
if ( fromTokeniser.hasMoreTokens() )
{
relativePath += separatorChar;
}
}
if ( relativePath.length() != 0 && toTokeniser.hasMoreTokens() )
{
relativePath += separatorChar;
}
// add fwd fills for whatevers left of newPath.
while ( toTokeniser.hasMoreTokens() )
{
relativePath += toTokeniser.nextToken();
if ( toTokeniser.hasMoreTokens() )
{
relativePath += separatorChar;
}
}
return relativePath;
}
}