/* * 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 * * 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. * */ package org.apache.tools.ant.types; import java.io.File; import java.lang.reflect.Method; import java.util.ArrayList; 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. * <p> * <code> * <sometask><br> *   <somepath><br> *     <pathelement location="/path/to/file.jar" /><br> *     <pathelement * path="/path/to/file2.jar:/path/to/class2;/path/to/class3" /> * <br> *     <pathelement location="/path/to/file3.jar" /><br> *     <pathelement location="/path/to/file4.jar" /><br> *   </somepath><br> * </sometask><br> * </code> * <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> * * 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. */ 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 */ 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 */ public static final Path systemBootClasspath = new Path(null, System.getProperty("sun.boot.class.path")); // CheckStyle:VisibilityModifier OFF - bc /** * Helper class, holds the nested <code><pathelement></code> values. */ public class PathElement implements ResourceCollection { private String[] parts; /** * Set the location. * * @param loc a <code>File</code> value */ public void setLocation(File loc) { parts = new String[] { translateFile(loc.getAbsolutePath()) }; } /** * Set the path. * * @param path a <code>String</code> value */ public void setPath(String path) { parts = Path.translatePath(getProject(), path); } /** * Return the converted pathelements. * * @return a <code>String[]</code> value */ public String[] getParts() { return parts; } /** * 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. * @return true. */ @Override public boolean isFilesystemOnly() { return true; } /** * 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 <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 <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. * @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. * @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. * * <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 <code><pathelement></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 <code><fileset></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 <code><filelist></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 <code><dirset></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 * @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 <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 <code><path></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. * @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. * * @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 * * @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) { String[] list = source.list(); File userDir = (tryUserDir) ? new File(System.getProperty("user.dir")) : null; for (int i = 0; i < list.length; i++) { File f = resolveFile(getProject(), list[i]); // probably not the best choice, but it solves the problem of // relative paths in CLASSPATH if (tryUserDir && !f.exists()) { f = new File(userDir, list[i]); } 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. * @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. * @return list of path elements. */ public String[] list() { if (isReference()) { return ((Path) getCheckedRef()).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. * @return a textual representation of the path. */ @Override public String toString() { return isReference() ? getCheckedRef().toString() : union == null ? "" : union.toString(); } /** * 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. * @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. * @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. * @return number of elements as int. */ @Override public synchronized int size() { if (isReference()) { return ((Path) getCheckedRef()).size(); } dieOnCircularReference(); return union == null ? 0 : assertFilesystemOnly(union).size(); } /** * 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. * @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. */ 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. * @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. * @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. * @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. */ 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. */ 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(System.getProperty("java.home") + 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).indexOf("microsoft") >= 0) { // 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(System.getProperty("java.home") + 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, System.getProperty("java.home") + File.separator + "lib" + File.separator + "rt.jar")); // Just keep the old version as well and let addExisting // sort it out. addExisting(new Path(null, System.getProperty("java.home") + File.separator + "jre" + File.separator + "lib" + File.separator + "rt.jar")); // Sun's and Apple's 1.4 have JCE and JSSE in separate jars. String[] secJars = {"jce", "jsse"}; for (int i = 0; i < secJars.length; i++) { addExisting(new Path(null, System.getProperty("java.home") + File.separator + "lib" + File.separator + secJars[i] + ".jar")); addExisting(new Path(null, System.getProperty("java.home") + File.separator + ".." + File.separator + "Classes" + File.separator + secJars[i] + ".jar")); } // IBM's 1.4 has rt.jar split into 4 smaller jars and a combined // JCE/JSSE in security.jar. String[] ibmJars = {"core", "graphics", "security", "server", "xml"}; for (int i = 0; i < ibmJars.length; i++) { addExisting(new Path(null, System.getProperty("java.home") + File.separator + "lib" + File.separator + ibmJars[i] + ".jar")); } // Added for MacOS X addExisting(new Path(null, System.getProperty("java.home") + File.separator + ".." + File.separator + "Classes" + File.separator + "classes.jar")); addExisting(new Path(null, System.getProperty("java.home") + 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. * @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; } } String[] dirs = extdirs.list(); for (int i = 0; i < dirs.length; i++) { File dir = resolveFile(getProject(), dirs[i]); 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. * @return a "fail-fast" Iterator. */ @Override public final synchronized Iterator<Resource> iterator() { if (isReference()) { return ((Path) getCheckedRef()).iterator(); } dieOnCircularReference(); if (getPreserveBC()) { return new FileResourceIterator(getProject(), null, list()); } return union == null ? Collections.<Resource> emptySet().iterator() : assertFilesystemOnly(union).iterator(); } /** * Fulfill the ResourceCollection contract. * @return whether this is a filesystem-only resource collection. */ @Override public synchronized boolean isFilesystemOnly() { if (isReference()) { return ((Path) getCheckedRef()).isFilesystemOnly(); } dieOnCircularReference(); assertFilesystemOnly(union); return true; } /** * 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 <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.booleanValue(); } /** * Does the given file name contain wildcards? * @since Ant 1.8.2 */ private static boolean containsWildcards(String path) { return path != null && (path.indexOf('*') > -1 || path.indexOf('?') > -1); } }