/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You 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
 *
 *      https://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.
 *
 */

package org.apache.tools.ant.types;

import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Stack;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.MagicNames;
import org.apache.tools.ant.PathTokenizer;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.types.resources.FileResourceIterator;
import org.apache.tools.ant.types.resources.Union;
import org.apache.tools.ant.util.FileUtils;
import org.apache.tools.ant.util.JavaEnvUtils;

This object represents a path as used by CLASSPATH or PATH environment variable. A path might also be described as a collection of unique filesystem resources.
<sometask>
  <somepath>
    <pathelement location="/path/to/file.jar"/>
    <pathelement path="/path/to/file2.jar:/path/to/class2;/path/to/class3"/>
    <pathelement location="/path/to/file3.jar"/>
    <pathelement location="/path/to/file4.jar"/>
  </somepath>
</sometask>

The object implementation sometask must provide a method called createSomepath which returns an instance of Path. Nested path definitions are handled by the Path object and must be labeled pathelement.

The path element takes a parameter path which will be parsed and split into single elements. It will usually be used to define a path from an environment variable.

/** * This object represents a path as used by CLASSPATH or PATH * environment variable. A path might also be described as a collection * of unique filesystem resources. * <pre> * &lt;sometask&gt; * &lt;somepath&gt; * &lt;pathelement location="/path/to/file.jar"/&gt; * &lt;pathelement path="/path/to/file2.jar:/path/to/class2;/path/to/class3"/&gt; * &lt;pathelement location="/path/to/file3.jar"/&gt; * &lt;pathelement location="/path/to/file4.jar"/&gt; * &lt;/somepath&gt; * &lt;/sometask&gt; * </pre> * <p> * The object implementation <code>sometask</code> must provide a method called * <code>createSomepath</code> which returns an instance of <code>Path</code>. * Nested path definitions are handled by the Path object and must be labeled * <code>pathelement</code>. * </p> * <p> * The path element takes a parameter <code>path</code> which will be parsed * and split into single elements. It will usually be used * to define a path from an environment variable. * </p> */
public class Path extends DataType implements Cloneable, ResourceCollection { // CheckStyle:VisibilityModifier OFF - bc // non-final as some IDE integrations (at least Eclipse) want to override it
The system classpath as a Path object
/** The system classpath as a Path object */
public static Path systemClasspath = //NOSONAR new Path(null, System.getProperty("java.class.path"));
The system bootclasspath as a Path object.
Since:Ant 1.6.2
/** * The system bootclasspath as a Path object. * * @since Ant 1.6.2 */
public static final Path systemBootClasspath = new Path(null, System.getProperty("sun.boot.class.path")); // CheckStyle:VisibilityModifier OFF - bc
Helper class, holds the nested <pathelement> values.
/** * Helper class, holds the nested <code>&lt;pathelement&gt;</code> values. */
public class PathElement implements ResourceCollection { private String[] parts;
Set the location.
Params:
  • loc – a File value
/** * Set the location. * * @param loc a <code>File</code> value */
public void setLocation(File loc) { parts = new String[] {translateFile(loc.getAbsolutePath())}; }
Set the path.
Params:
  • path – a String value
/** * Set the path. * * @param path a <code>String</code> value */
public void setPath(String path) { parts = Path.translatePath(getProject(), path); }
Return the converted pathelements.
Returns:a String[] value
/** * Return the converted pathelements. * * @return a <code>String[]</code> value */
public String[] getParts() { return parts; }
Create an iterator.
Returns:an iterator.
/** * Create an iterator. * @return an iterator. */
@Override public Iterator<Resource> iterator() { return new FileResourceIterator(getProject(), null, parts); }
Check if this resource is only for filesystems.
Returns:true.
/** * Check if this resource is only for filesystems. * @return true. */
@Override public boolean isFilesystemOnly() { return true; }
Get the number of resources.
Returns:the number of parts.
/** * Get the number of resources. * @return the number of parts. */
@Override public int size() { return parts == null ? 0 : parts.length; } } private Boolean preserveBC; private Union union = null; private boolean cache = false;
Invoked by IntrospectionHelper for setXXX(Path p) attribute setters.
Params:
  • p – the Project for this path.
  • path – the String path definition.
/** * Invoked by IntrospectionHelper for <code>setXXX(Path p)</code> * attribute setters. * @param p the <code>Project</code> for this path. * @param path the <code>String</code> path definition. */
public Path(Project p, String path) { this(p); createPathElement().setPath(path); }
Construct an empty Path.
Params:
  • project – the Project for this path.
/** * Construct an empty <code>Path</code>. * @param project the <code>Project</code> for this path. */
public Path(Project project) { setProject(project); }
Adds a element definition to the path.
Params:
  • location – the location of the element to add (must not be null nor empty.
Throws:
/** * Adds a element definition to the path. * @param location the location of the element to add (must not be * <code>null</code> nor empty. * @throws BuildException on error */
public void setLocation(File location) throws BuildException { checkAttributesAllowed(); createPathElement().setLocation(location); }
Parses a path definition and creates single PathElements.
Params:
  • path – the String path definition.
Throws:
/** * Parses a path definition and creates single PathElements. * @param path the <code>String</code> path definition. * @throws BuildException on error */
public void setPath(String path) throws BuildException { checkAttributesAllowed(); createPathElement().setPath(path); }
Makes this instance in effect a reference to another Path instance.

You must not set another attribute or nest elements inside this element if you make it a reference.

Params:
  • r – the reference to another Path
Throws:
/** * Makes this instance in effect a reference to another Path instance. * * <p>You must not set another attribute or nest elements inside * this element if you make it a reference.</p> * @param r the reference to another Path * @throws BuildException on error */
@Override public void setRefid(Reference r) throws BuildException { if (union != null) { throw tooManyAttributes(); } super.setRefid(r); }
Creates the nested <pathelement> element.
Throws:
Returns:the PathElement to be configured
/** * Creates the nested <code>&lt;pathelement&gt;</code> element. * @return the <code>PathElement</code> to be configured * @throws BuildException on error */
public PathElement createPathElement() throws BuildException { if (isReference()) { throw noChildrenAllowed(); } PathElement pe = new PathElement(); add(pe); return pe; }
Adds a nested <fileset> element.
Params:
  • fs – a FileSet to be added to the path
Throws:
/** * Adds a nested <code>&lt;fileset&gt;</code> element. * @param fs a <code>FileSet</code> to be added to the path * @throws BuildException on error */
public void addFileset(FileSet fs) throws BuildException { if (fs.getProject() == null) { fs.setProject(getProject()); } add(fs); }
Adds a nested <filelist> element.
Params:
  • fl – a FileList to be added to the path
Throws:
/** * Adds a nested <code>&lt;filelist&gt;</code> element. * @param fl a <code>FileList</code> to be added to the path * @throws BuildException on error */
public void addFilelist(FileList fl) throws BuildException { if (fl.getProject() == null) { fl.setProject(getProject()); } add(fl); }
Adds a nested <dirset> element.
Params:
  • dset – a DirSet to be added to the path
Throws:
/** * Adds a nested <code>&lt;dirset&gt;</code> element. * @param dset a <code>DirSet</code> to be added to the path * @throws BuildException on error */
public void addDirset(DirSet dset) throws BuildException { if (dset.getProject() == null) { dset.setProject(getProject()); } add(dset); }
Adds a nested path
Params:
  • path – a Path to be added to the path
Throws:
Since:Ant 1.6
/** * Adds a nested path * @param path a <code>Path</code> to be added to the path * @throws BuildException on error * @since Ant 1.6 */
public void add(Path path) throws BuildException { if (path == this) { throw circularReference(); } if (path.getProject() == null) { path.setProject(getProject()); } add((ResourceCollection) path); }
Add a nested ResourceCollection.
Params:
  • c – the ResourceCollection to add.
Since:Ant 1.7
/** * Add a nested <code>ResourceCollection</code>. * @param c the ResourceCollection to add. * @since Ant 1.7 */
public void add(ResourceCollection c) { checkChildrenAllowed(); if (c == null) { return; } if (union == null) { union = new Union(); union.setProject(getProject()); union.setCache(cache); } union.add(c); setChecked(false); }
Creates a nested <path> element.
Throws:
Returns:a Path to be configured
/** * Creates a nested <code>&lt;path&gt;</code> element. * @return a <code>Path</code> to be configured * @throws BuildException on error */
public Path createPath() throws BuildException { Path p = new Path(getProject()); add(p); return p; }
Append the contents of the other Path instance to this.
Params:
  • other – a Path to be added to the path
/** * Append the contents of the other Path instance to this. * @param other a <code>Path</code> to be added to the path */
public void append(Path other) { if (other == null) { return; } add(other); }
Adds the components on the given path which exist to this Path. Components that don't exist aren't added.
Params:
  • source – - source path whose components are examined for existence
/** * Adds the components on the given path which exist to this * Path. Components that don't exist aren't added. * * @param source - source path whose components are examined for existence */
public void addExisting(Path source) { addExisting(source, false); }
Same as addExisting, but support classpath behavior if tryUserDir is true. Classpaths are relative to user dir, not the project base. That used to break jspc test
Params:
  • source – the source path
  • tryUserDir – if true try the user directory if the file is not present
/** * Same as addExisting, but support classpath behavior if tryUserDir * is true. Classpaths are relative to user dir, not the project base. * That used to break jspc test * * @param source the source path * @param tryUserDir if true try the user directory if the file is not present */
public void addExisting(Path source, boolean tryUserDir) { File userDir = (tryUserDir) ? new File(System.getProperty("user.dir")) : null; for (String name : source.list()) { File f = resolveFile(getProject(), name); // probably not the best choice, but it solves the problem of // relative paths in CLASSPATH if (tryUserDir && !f.exists()) { f = new File(userDir, name); } if (f.exists()) { setLocation(f); } else if (f.getParentFile() != null && f.getParentFile().exists() && containsWildcards(f.getName())) { setLocation(f); log("adding " + f + " which contains wildcards and may not do what you intend it to do depending on your OS or version of Java", Project.MSG_VERBOSE); } else { log("dropping " + f + " from path as it doesn't exist", Project.MSG_VERBOSE); } } }
Whether to cache the current path.
Params:
  • b – boolean
Since:Ant 1.8.0
/** * Whether to cache the current path. * @param b boolean * @since Ant 1.8.0 */
public void setCache(boolean b) { checkAttributesAllowed(); cache = b; if (union != null) { union.setCache(b); } }
Returns all path elements defined by this and nested path objects.
Returns:list of path elements.
/** * Returns all path elements defined by this and nested path objects. * @return list of path elements. */
public String[] list() { if (isReference()) { return getRef().list(); } return assertFilesystemOnly(union) == null ? new String[0] : union.list(); }
Returns a textual representation of the path, which can be used as CLASSPATH or PATH environment variable definition.
Returns:a textual representation of the path.
/** * Returns a textual representation of the path, which can be used as * CLASSPATH or PATH environment variable definition. * @return a textual representation of the path. */
@Override public String toString() { return isReference() ? getRef().toString() : union == null ? "" : union.toString(); }
Splits a PATH (with : or ; as separators) into its parts.
Params:
  • project – the project to use
  • source – a String value
Returns:an array of strings, one for each path element
/** * Splits a PATH (with : or ; as separators) into its parts. * @param project the project to use * @param source a <code>String</code> value * @return an array of strings, one for each path element */
public static String[] translatePath(Project project, String source) { if (source == null) { return new String[0]; } final List<String> result = new ArrayList<>(); PathTokenizer tok = new PathTokenizer(source); while (tok.hasMoreTokens()) { StringBuffer element = new StringBuffer(); String pathElement = tok.nextToken(); try { element.append(resolveFile(project, pathElement).getPath()); } catch (BuildException e) { project.log("Dropping path element " + pathElement + " as it is not valid relative to the project", Project.MSG_VERBOSE); } for (int i = 0; i < element.length(); i++) { translateFileSep(element, i); } result.add(element.toString()); } return result.toArray(new String[result.size()]); }
Returns its argument with all file separator characters replaced so that they match the local OS conventions.
Params:
  • source – the path to convert
Returns:the converted path
/** * Returns its argument with all file separator characters * replaced so that they match the local OS conventions. * @param source the path to convert * @return the converted path */
public static String translateFile(String source) { if (source == null) { return ""; } final StringBuffer result = new StringBuffer(source); for (int i = 0; i < result.length(); i++) { translateFileSep(result, i); } return result.toString(); }
Translates occurrences at a position of / or \ to correct separator of the current platform and returns whether it had to do a replacement.
Params:
  • buffer – a buffer containing a string
  • pos – the position in the string buffer to convert
Returns:true if the character was a / or \
/** * Translates occurrences at a position of / or \ to correct separator of the * current platform and returns whether it had to do a * replacement. * @param buffer a buffer containing a string * @param pos the position in the string buffer to convert * @return true if the character was a / or \ */
protected static boolean translateFileSep(StringBuffer buffer, int pos) { if (buffer.charAt(pos) == '/' || buffer.charAt(pos) == '\\') { buffer.setCharAt(pos, File.separatorChar); return true; } return false; }
Fulfill the ResourceCollection contract.
Returns:number of elements as int.
/** * Fulfill the ResourceCollection contract. * @return number of elements as int. */
@Override public synchronized int size() { if (isReference()) { return getRef().size(); } dieOnCircularReference(); return union == null ? 0 : assertFilesystemOnly(union).size(); }
Clone this Path.
Returns:Path with shallowly cloned Resource children.
/** * Clone this Path. * @return Path with shallowly cloned Resource children. */
@Override public Object clone() { try { Path result = (Path) super.clone(); result.union = union == null ? union : (Union) union.clone(); return result; } catch (CloneNotSupportedException e) { throw new BuildException(e); } }
Overrides the version of DataType to recurse on all DataType child elements that may have been added.
Params:
  • stk – the stack of data types to use (recursively).
  • p – the project to use to dereference the references.
Throws:
/** * Overrides the version of DataType to recurse on all DataType * child elements that may have been added. * @param stk the stack of data types to use (recursively). * @param p the project to use to dereference the references. * @throws BuildException on error. */
@Override protected synchronized void dieOnCircularReference(Stack<Object> stk, Project p) throws BuildException { if (isChecked()) { return; } if (isReference()) { super.dieOnCircularReference(stk, p); } else { if (union != null) { pushAndInvokeCircularReferenceCheck(union, stk, p); } setChecked(true); } }
Resolve a filename with Project's help - if we know one that is.
/** * Resolve a filename with Project's help - if we know one that is. */
private static File resolveFile(Project project, String relativeName) { return FileUtils.getFileUtils().resolveFile( (project == null) ? null : project.getBaseDir(), relativeName); }
Concatenates the system class path in the order specified by the ${build.sysclasspath} property - using "last" as default value.
Returns:the concatenated path
/** * Concatenates the system class path in the order specified by * the ${build.sysclasspath} property - using &quot;last&quot; as * default value. * @return the concatenated path */
public Path concatSystemClasspath() { return concatSystemClasspath("last"); }
Concatenates the system class path in the order specified by the ${build.sysclasspath} property - using the supplied value if ${build.sysclasspath} has not been set.
Params:
  • defValue – the order ("first", "last", "only")
Returns:the concatenated path
/** * Concatenates the system class path in the order specified by * the ${build.sysclasspath} property - using the supplied value * if ${build.sysclasspath} has not been set. * @param defValue the order ("first", "last", "only") * @return the concatenated path */
public Path concatSystemClasspath(String defValue) { return concatSpecialPath(defValue, Path.systemClasspath); }
Concatenates the system boot class path in the order specified by the ${build.sysclasspath} property - using the supplied value if ${build.sysclasspath} has not been set.
Params:
  • defValue – the order ("first", "last", "only")
Returns:the concatenated path
/** * Concatenates the system boot class path in the order specified * by the ${build.sysclasspath} property - using the supplied * value if ${build.sysclasspath} has not been set. * @param defValue the order ("first", "last", "only") * @return the concatenated path */
public Path concatSystemBootClasspath(String defValue) { return concatSpecialPath(defValue, Path.systemBootClasspath); }
Concatenates a class path in the order specified by the ${build.sysclasspath} property - using the supplied value if ${build.sysclasspath} has not been set.
/** * Concatenates a class path in the order specified by the * ${build.sysclasspath} property - using the supplied value if * ${build.sysclasspath} has not been set. */
private Path concatSpecialPath(String defValue, Path p) { Path result = new Path(getProject()); String order = defValue; String o = getProject() != null ? getProject().getProperty(MagicNames.BUILD_SYSCLASSPATH) : System.getProperty(MagicNames.BUILD_SYSCLASSPATH); if (o != null) { order = o; } if ("only".equals(order)) { // only: the developer knows what (s)he is doing result.addExisting(p, true); } else if ("first".equals(order)) { // first: developer could use a little help result.addExisting(p, true); result.addExisting(this); } else if ("ignore".equals(order)) { // ignore: don't trust anyone result.addExisting(this); } else { // last: don't trust the developer if (!"last".equals(order)) { log("invalid value for " + MagicNames.BUILD_SYSCLASSPATH + ": " + order, Project.MSG_WARN); } result.addExisting(this); result.addExisting(p, true); } return result; }
Add the Java Runtime classes to this Path instance.
/** * Add the Java Runtime classes to this Path instance. */
public void addJavaRuntime() { if (JavaEnvUtils.isKaffe()) { // newer versions of Kaffe (1.1.1+) won't have this, // but this will be sorted by FileSet anyway. File kaffeShare = new File(JavaEnvUtils.getJavaHome() + File.separator + "share" + File.separator + "kaffe"); if (kaffeShare.isDirectory()) { FileSet kaffeJarFiles = new FileSet(); kaffeJarFiles.setDir(kaffeShare); kaffeJarFiles.setIncludes("*.jar"); addFileset(kaffeJarFiles); } } else if ("GNU libgcj".equals(System.getProperty("java.vm.name"))) { addExisting(systemBootClasspath); } if (System.getProperty("java.vendor").toLowerCase(Locale.ENGLISH).contains("microsoft")) { // TODO is this code still necessary? is there any 1.2+ port? // Pull in *.zip from packages directory FileSet msZipFiles = new FileSet(); msZipFiles.setDir(new File(JavaEnvUtils.getJavaHome() + File.separator + "Packages")); msZipFiles.setIncludes("*.ZIP"); addFileset(msZipFiles); } else { // JDK 1.2+ seems to set java.home to the JRE directory. addExisting(new Path(null, JavaEnvUtils.getJavaHome() + File.separator + "lib" + File.separator + "rt.jar")); // Just keep the old version as well and let addExisting // sort it out. addExisting(new Path(null, JavaEnvUtils.getJavaHome() + File.separator + "jre" + File.separator + "lib" + File.separator + "rt.jar")); // Sun's and Apple's 1.4 have JCE and JSSE in separate jars. for (String secJar : Arrays.asList("jce", "jsse")) { addExisting(new Path(null, JavaEnvUtils.getJavaHome() + File.separator + "lib" + File.separator + secJar + ".jar")); addExisting(new Path(null, JavaEnvUtils.getJavaHome() + File.separator + ".." + File.separator + "Classes" + File.separator + secJar + ".jar")); } // IBM's 1.4 has rt.jar split into 4 smaller jars and a combined // JCE/JSSE in security.jar. for (String ibmJar : Arrays.asList("core", "graphics", "security", "server", "xml")) { addExisting(new Path(null, JavaEnvUtils.getJavaHome() + File.separator + "lib" + File.separator + ibmJar + ".jar")); } // Added for MacOS X addExisting(new Path(null, JavaEnvUtils.getJavaHome() + File.separator + ".." + File.separator + "Classes" + File.separator + "classes.jar")); addExisting(new Path(null, JavaEnvUtils.getJavaHome() + File.separator + ".." + File.separator + "Classes" + File.separator + "ui.jar")); } }
Emulation of extdirs feature in Java >= 1.2. This method adds all files in the given directories (but not in sub-directories!) to the classpath, so that you don't have to specify them all one by one.
Params:
  • extdirs – - Path to append files to
/** * Emulation of extdirs feature in Java &gt;= 1.2. * This method adds all files in the given * directories (but not in sub-directories!) to the classpath, * so that you don't have to specify them all one by one. * @param extdirs - Path to append files to */
public void addExtdirs(Path extdirs) { if (extdirs == null) { String extProp = System.getProperty("java.ext.dirs"); if (extProp != null) { extdirs = new Path(getProject(), extProp); } else { return; } } for (String d : extdirs.list()) { File dir = resolveFile(getProject(), d); if (dir.exists() && dir.isDirectory()) { FileSet fs = new FileSet(); fs.setDir(dir); fs.setIncludes("*"); addFileset(fs); } } }
Fulfill the ResourceCollection contract. The Iterator returned will throw ConcurrentModificationExceptions if ResourceCollections are added to this container while the Iterator is in use.
Returns:a "fail-fast" Iterator.
/** * Fulfill the ResourceCollection contract. The Iterator returned * will throw ConcurrentModificationExceptions if ResourceCollections * are added to this container while the Iterator is in use. * @return a "fail-fast" Iterator. */
@Override public final synchronized Iterator<Resource> iterator() { if (isReference()) { return getRef().iterator(); } dieOnCircularReference(); if (getPreserveBC()) { return new FileResourceIterator(getProject(), null, list()); } return union == null ? Collections.<Resource> emptySet().iterator() : assertFilesystemOnly(union).iterator(); }
Fulfill the ResourceCollection contract.
Returns:whether this is a filesystem-only resource collection.
/** * Fulfill the ResourceCollection contract. * @return whether this is a filesystem-only resource collection. */
@Override public synchronized boolean isFilesystemOnly() { if (isReference()) { return getRef().isFilesystemOnly(); } dieOnCircularReference(); assertFilesystemOnly(union); return true; }
Verify the specified ResourceCollection is filesystem-only.
Params:
  • rc – the ResourceCollection to check.
Throws:
Returns:the passed in ResourceCollection.
/** * Verify the specified ResourceCollection is filesystem-only. * @param rc the ResourceCollection to check. * @throws BuildException if <code>rc</code> is not filesystem-only. * @return the passed in ResourceCollection. */
protected ResourceCollection assertFilesystemOnly(ResourceCollection rc) { if (rc != null && !(rc.isFilesystemOnly())) { throw new BuildException("%s allows only filesystem resources.", getDataTypeName()); } return rc; }
Helps determine whether to preserve BC by calling list() on subclasses. The default behavior of this method is to return true for any subclass that implements list(); this can, of course, be avoided by overriding this method to return false. It is not expected that the result of this method should change over time, thus it is called only once.
Returns:true if iterator() should delegate to list().
/** * Helps determine whether to preserve BC by calling <code>list()</code> on subclasses. * The default behavior of this method is to return <code>true</code> for any subclass * that implements <code>list()</code>; this can, of course, be avoided by overriding * this method to return <code>false</code>. It is not expected that the result of this * method should change over time, thus it is called only once. * @return <code>true</code> if <code>iterator()</code> should delegate to <code>list()</code>. */
protected boolean delegateIteratorToList() { if (getClass().equals(Path.class)) { return false; } try { Method listMethod = getClass().getMethod("list"); return !listMethod.getDeclaringClass().equals(Path.class); } catch (Exception e) { //shouldn't happen, but return false; } } private synchronized boolean getPreserveBC() { if (preserveBC == null) { preserveBC = delegateIteratorToList() ? Boolean.TRUE : Boolean.FALSE; } return preserveBC; }
Does the given file name contain wildcards?
Since:Ant 1.8.2
/** * Does the given file name contain wildcards? * @since Ant 1.8.2 */
private static boolean containsWildcards(String path) { return path != null && (path.contains("*") || path.contains("?")); } private Path getRef() { return getCheckedRef(Path.class); } }