/*
* Copyright 2000-2004 The Apache Software 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.
*
*/
package org.apache.tools.ant.types;
import java.io.File;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Stack;
import java.util.Vector;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.PathTokenizer;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.util.JavaEnvUtils;
/**
* This object represents a path as used by CLASSPATH or PATH
* environment variable.
* <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 implemention <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.
*
* @author Thomas.Haas@softwired-inc.com
* @author Stefan Bodewig
*/
public class Path extends DataType implements Cloneable {
private Vector elements;
/** The system classspath as a Path object */
public static Path systemClasspath =
new Path(null, System.getProperty("java.class.path"));
/**
* Helper class, holds the nested <code><pathelement></code> values.
*/
public class PathElement {
private String[] parts;
public void setLocation(File loc) {
parts = new String[] {translateFile(loc.getAbsolutePath())};
}
public void setPath(String path) {
parts = Path.translatePath(getProject(), path);
}
public String[] getParts() {
return parts;
}
}
/**
* Invoked by IntrospectionHelper for <code>setXXX(Path p)</code>
* attribute setters.
*/
public Path(Project p, String path) {
this(p);
createPathElement().setPath(path);
}
public Path(Project project) {
setProject(project);
elements = new Vector();
}
/**
* Adds a element definition to the path.
* @param location the location of the element to add (must not be
* <code>null</code> nor empty.
*/
public void setLocation(File location) throws BuildException {
if (isReference()) {
throw tooManyAttributes();
}
createPathElement().setLocation(location);
}
/**
* Parses a path definition and creates single PathElements.
* @param path the path definition.
*/
public void setPath(String path) throws BuildException {
if (isReference()) {
throw tooManyAttributes();
}
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>
*/
public void setRefid(Reference r) throws BuildException {
if (!elements.isEmpty()) {
throw tooManyAttributes();
}
elements.addElement(r);
super.setRefid(r);
}
/**
* Creates the nested <code><pathelement></code> element.
*/
public PathElement createPathElement() throws BuildException {
if (isReference()) {
throw noChildrenAllowed();
}
PathElement pe = new PathElement();
elements.addElement(pe);
return pe;
}
/**
* Adds a nested <code><fileset></code> element.
*/
public void addFileset(FileSet fs) throws BuildException {
if (isReference()) {
throw noChildrenAllowed();
}
elements.addElement(fs);
setChecked(false);
}
/**
* Adds a nested <code><filelist></code> element.
*/
public void addFilelist(FileList fl) throws BuildException {
if (isReference()) {
throw noChildrenAllowed();
}
elements.addElement(fl);
setChecked(false);
}
/**
* Adds a nested <code><dirset></code> element.
*/
public void addDirset(DirSet dset) throws BuildException {
if (isReference()) {
throw noChildrenAllowed();
}
elements.addElement(dset);
setChecked(false);
}
/**
* Adds a nested path
* @since Ant 1.6
*/
public void add(Path path) throws BuildException {
if (isReference()) {
throw noChildrenAllowed();
}
elements.addElement(path);
setChecked(false);
}
/**
* Creates a nested <code><path></code> element.
*/
public Path createPath() throws BuildException {
if (isReference()) {
throw noChildrenAllowed();
}
Path p = new Path(getProject());
elements.addElement(p);
setChecked(false);
return p;
}
/**
* Append the contents of the other Path instance to this.
*/
public void append(Path other) {
if (other == null) {
return;
}
String[] l = other.list();
for (int i = 0; i < l.length; i++) {
if (elements.indexOf(l[i]) == -1) {
elements.addElement(l[i]);
}
}
}
/**
* 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
* @param tryUserDir
*/
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 = null;
if (getProject() != null) {
f = getProject().resolveFile(list[i]);
} else {
f = new File(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 {
log("dropping " + f + " from path as it doesn't exist",
Project.MSG_VERBOSE);
}
}
}
/**
* Returns all path elements defined by this and nested path objects.
* @return list of path elements.
*/
public String[] list() {
if (!isChecked()) {
// make sure we don't have a circular reference here
Stack stk = new Stack();
stk.push(this);
dieOnCircularReference(stk, getProject());
}
Vector result = new Vector(2 * elements.size());
for (int i = 0; i < elements.size(); i++) {
Object o = elements.elementAt(i);
if (o instanceof Reference) {
Reference r = (Reference) o;
o = r.getReferencedObject(getProject());
// we only support references to paths right now
if (!(o instanceof Path)) {
String msg = r.getRefId() + " doesn\'t denote a path " + o;
throw new BuildException(msg);
}
}
if (o instanceof String) {
// obtained via append
addUnlessPresent(result, (String) o);
} else if (o instanceof PathElement) {
String[] parts = ((PathElement) o).getParts();
if (parts == null) {
throw new BuildException("You must either set location or"
+ " path on <pathelement>");
}
for (int j = 0; j < parts.length; j++) {
addUnlessPresent(result, parts[j]);
}
} else if (o instanceof Path) {
Path p = (Path) o;
if (p.getProject() == null) {
p.setProject(getProject());
}
String[] parts = p.list();
for (int j = 0; j < parts.length; j++) {
addUnlessPresent(result, parts[j]);
}
} else if (o instanceof DirSet) {
DirSet dset = (DirSet) o;
DirectoryScanner ds = dset.getDirectoryScanner(getProject());
String[] s = ds.getIncludedDirectories();
File dir = dset.getDir(getProject());
addUnlessPresent(result, dir, s);
} else if (o instanceof FileSet) {
FileSet fs = (FileSet) o;
DirectoryScanner ds = fs.getDirectoryScanner(getProject());
String[] s = ds.getIncludedFiles();
File dir = fs.getDir(getProject());
addUnlessPresent(result, dir, s);
} else if (o instanceof FileList) {
FileList fl = (FileList) o;
String[] s = fl.getFiles(getProject());
File dir = fl.getDir(getProject());
addUnlessPresent(result, dir, s);
}
}
String[] res = new String[result.size()];
result.copyInto(res);
return res;
}
/**
* 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.
*/
public String toString() {
final String[] list = list();
// empty path return empty string
if (list.length == 0) {
return "";
}
// path containing one or more elements
final StringBuffer result = new StringBuffer(list[0].toString());
for (int i = 1; i < list.length; i++) {
result.append(File.pathSeparatorChar);
result.append(list[i]);
}
return result.toString();
}
/**
* Splits a PATH (with : or ; as separators) into its parts.
*/
public static String[] translatePath(Project project, String source) {
final Vector result = new Vector();
if (source == null) {
return new String[0];
}
PathTokenizer tok = new PathTokenizer(source);
StringBuffer element = new StringBuffer();
while (tok.hasMoreTokens()) {
String pathElement = tok.nextToken();
try {
element.append(resolveFile(project, pathElement));
} 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.addElement(element.toString());
element = new StringBuffer();
}
String[] res = new String[result.size()];
result.copyInto(res);
return res;
}
/**
* Returns its argument with all file separator characters
* replaced so that they match the local OS conventions.
*/
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 all occurrences of / or \ to correct separator of the
* current platform and returns whether it had to do any
* replacements.
*/
protected static boolean translateFileSep(StringBuffer buffer, int pos) {
if (buffer.charAt(pos) == '/' || buffer.charAt(pos) == '\\') {
buffer.setCharAt(pos, File.separatorChar);
return true;
}
return false;
}
/**
* How many parts does this Path instance consist of.
*/
public int size() {
return list().length;
}
/**
* Return a Path that holds the same elements as this instance.
*/
public Object clone() {
try {
Path p = (Path) super.clone();
p.elements = (Vector) elements.clone();
return p;
} catch (CloneNotSupportedException e) {
throw new BuildException(e);
}
}
/**
* Overrides the version of DataType to recurse on all DataType
* child elements that may have been added.
*/
protected void dieOnCircularReference(Stack stk, Project p)
throws BuildException {
if (isChecked()) {
return;
}
Enumeration e = elements.elements();
while (e.hasMoreElements()) {
Object o = e.nextElement();
if (o instanceof Reference) {
o = ((Reference) o).getReferencedObject(p);
}
if (o instanceof DataType) {
if (stk.contains(o)) {
throw circularReference();
} else {
stk.push(o);
((DataType) o).dieOnCircularReference(stk, p);
stk.pop();
}
}
}
setChecked(true);
}
/**
* Resolve a filename with Project's help - if we know one that is.
*
* <p>Assume the filename is absolute if project is null.</p>
*/
private static String resolveFile(Project project, String relativeName) {
if (project != null) {
File f = project.resolveFile(relativeName);
return f.getAbsolutePath();
}
return relativeName;
}
/**
* Adds a String to the Vector if it isn't already included.
*/
private static void addUnlessPresent(Vector v, String s) {
if (v.indexOf(s) == -1) {
v.addElement(s);
}
}
/**
* Adds absolute path names of listed files in the given directory
* to the Vector if they are not already included.
*/
private static void addUnlessPresent(Vector v, File dir, String[] s) {
for (int j = 0; j < s.length; j++) {
File d = new File(dir, s[j]);
String absolutePath = d.getAbsolutePath();
addUnlessPresent(v, translateFile(absolutePath));
}
}
/**
* Concatenates the system class path in the order specified by
* the ${build.sysclasspath} property - using "last" as
* default value.
*/
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.
*/
public Path concatSystemClasspath(String defValue) {
Path result = new Path(getProject());
String order = defValue;
if (getProject() != null) {
String o = getProject().getProperty("build.sysclasspath");
if (o != null) {
order = o;
}
}
if (order.equals("only")) {
// only: the developer knows what (s)he is doing
result.addExisting(Path.systemClasspath, true);
} else if (order.equals("first")) {
// first: developer could use a little help
result.addExisting(Path.systemClasspath, true);
result.addExisting(this);
} else if (order.equals("ignore")) {
// ignore: don't trust anyone
result.addExisting(this);
} else {
// last: don't trust the developer
if (!order.equals("last")) {
log("invalid value for build.sysclasspath: " + order,
Project.MSG_WARN);
}
result.addExisting(this);
result.addExisting(Path.systemClasspath, true);
}
return result;
}
/**
* Add the Java Runtime classes to this Path instance.
*/
public void addJavaRuntime() {
if ("Kaffe".equals(System.getProperty("java.vm.name"))) {
// 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);
}
}
if (System.getProperty("java.vendor").toLowerCase(Locale.US).indexOf("microsoft") >= 0) {
// 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 if (JavaEnvUtils.isJavaVersion(JavaEnvUtils.JAVA_1_1)) {
addExisting(new Path(null,
System.getProperty("java.home")
+ File.separator + "lib"
+ File.separator
+ "classes.zip"));
} else {
// JDK > 1.1 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 = getProject().resolveFile(dirs[i]);
if (dir.exists() && dir.isDirectory()) {
FileSet fs = new FileSet();
fs.setDir(dir);
fs.setIncludes("*");
addFileset(fs);
}
}
}
}