001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018:
019: package org.apache.tools.ant.types;
020:
021: import java.io.File;
022: import java.util.Collections;
023: import java.util.Iterator;
024: import java.util.Locale;
025: import java.util.Stack;
026: import java.util.Vector;
027:
028: import org.apache.tools.ant.BuildException;
029: import org.apache.tools.ant.PathTokenizer;
030: import org.apache.tools.ant.Project;
031: import org.apache.tools.ant.types.resources.Union;
032: import org.apache.tools.ant.types.resources.FileResourceIterator;
033: import org.apache.tools.ant.util.FileUtils;
034: import org.apache.tools.ant.util.JavaEnvUtils;
035:
036: /**
037: * This object represents a path as used by CLASSPATH or PATH
038: * environment variable. A path might also be described as a collection
039: * of unique filesystem resources.
040: * <p>
041: * <code>
042: * <sometask><br>
043: * <somepath><br>
044: * <pathelement location="/path/to/file.jar" /><br>
045: * <pathelement
046: * path="/path/to/file2.jar:/path/to/class2;/path/to/class3" />
047: * <br>
048: * <pathelement location="/path/to/file3.jar" /><br>
049: * <pathelement location="/path/to/file4.jar" /><br>
050: * </somepath><br>
051: * </sometask><br>
052: * </code>
053: * <p>
054: * The object implemention <code>sometask</code> must provide a method called
055: * <code>createSomepath</code> which returns an instance of <code>Path</code>.
056: * Nested path definitions are handled by the Path object and must be labeled
057: * <code>pathelement</code>.<p>
058: *
059: * The path element takes a parameter <code>path</code> which will be parsed
060: * and split into single elements. It will usually be used
061: * to define a path from an environment variable.
062: */
063:
064: public class Path extends DataType implements Cloneable,
065: ResourceCollection {
066: // CheckStyle:VisibilityModifier OFF - bc
067:
068: /** The system classpath as a Path object */
069: public static Path systemClasspath = new Path(null, System
070: .getProperty("java.class.path"));
071:
072: /**
073: * The system bootclasspath as a Path object.
074: *
075: * @since Ant 1.6.2
076: */
077: public static Path systemBootClasspath = new Path(null, System
078: .getProperty("sun.boot.class.path"));
079:
080: private static final Iterator EMPTY_ITERATOR = Collections.EMPTY_SET
081: .iterator();
082:
083: // CheckStyle:VisibilityModifier OFF - bc
084:
085: /**
086: * Helper class, holds the nested <code><pathelement></code> values.
087: */
088: public class PathElement implements ResourceCollection {
089: private String[] parts;
090:
091: /**
092: * Set the location.
093: *
094: * @param loc a <code>File</code> value
095: */
096: public void setLocation(File loc) {
097: parts = new String[] { translateFile(loc.getAbsolutePath()) };
098: }
099:
100: /**
101: * Set the path.
102: *
103: * @param path a <code>String</code> value
104: */
105: public void setPath(String path) {
106: parts = Path.translatePath(getProject(), path);
107: }
108:
109: /**
110: * Return the converted pathelements.
111: *
112: * @return a <code>String[]</code> value
113: */
114: public String[] getParts() {
115: return parts;
116: }
117:
118: /**
119: * Create an iterator.
120: * @return an iterator.
121: */
122: public Iterator iterator() {
123: return new FileResourceIterator(null, parts);
124: }
125:
126: /**
127: * Check if this resource is only for filesystems.
128: * @return true.
129: */
130: public boolean isFilesystemOnly() {
131: return true;
132: }
133:
134: /**
135: * Get the number of resources.
136: * @return the number of parts.
137: */
138: public int size() {
139: return parts == null ? 0 : parts.length;
140: }
141:
142: }
143:
144: private Union union = null;
145:
146: /**
147: * Invoked by IntrospectionHelper for <code>setXXX(Path p)</code>
148: * attribute setters.
149: * @param p the <code>Project</code> for this path.
150: * @param path the <code>String</code> path definition.
151: */
152: public Path(Project p, String path) {
153: this (p);
154: createPathElement().setPath(path);
155: }
156:
157: /**
158: * Construct an empty <code>Path</code>.
159: * @param project the <code>Project</code> for this path.
160: */
161: public Path(Project project) {
162: setProject(project);
163: }
164:
165: /**
166: * Adds a element definition to the path.
167: * @param location the location of the element to add (must not be
168: * <code>null</code> nor empty.
169: * @throws BuildException on error
170: */
171: public void setLocation(File location) throws BuildException {
172: checkAttributesAllowed();
173: createPathElement().setLocation(location);
174: }
175:
176: /**
177: * Parses a path definition and creates single PathElements.
178: * @param path the <code>String</code> path definition.
179: * @throws BuildException on error
180: */
181: public void setPath(String path) throws BuildException {
182: checkAttributesAllowed();
183: createPathElement().setPath(path);
184: }
185:
186: /**
187: * Makes this instance in effect a reference to another Path instance.
188: *
189: * <p>You must not set another attribute or nest elements inside
190: * this element if you make it a reference.</p>
191: * @param r the reference to another Path
192: * @throws BuildException on error
193: */
194: public void setRefid(Reference r) throws BuildException {
195: if (union != null) {
196: throw tooManyAttributes();
197: }
198: super .setRefid(r);
199: }
200:
201: /**
202: * Creates the nested <code><pathelement></code> element.
203: * @return the <code>PathElement</code> to be configured
204: * @throws BuildException on error
205: */
206: public PathElement createPathElement() throws BuildException {
207: if (isReference()) {
208: throw noChildrenAllowed();
209: }
210: PathElement pe = new PathElement();
211: add(pe);
212: return pe;
213: }
214:
215: /**
216: * Adds a nested <code><fileset></code> element.
217: * @param fs a <code>FileSet</code> to be added to the path
218: * @throws BuildException on error
219: */
220: public void addFileset(FileSet fs) throws BuildException {
221: if (fs.getProject() == null) {
222: fs.setProject(getProject());
223: }
224: add(fs);
225: }
226:
227: /**
228: * Adds a nested <code><filelist></code> element.
229: * @param fl a <code>FileList</code> to be added to the path
230: * @throws BuildException on error
231: */
232: public void addFilelist(FileList fl) throws BuildException {
233: if (fl.getProject() == null) {
234: fl.setProject(getProject());
235: }
236: add(fl);
237: }
238:
239: /**
240: * Adds a nested <code><dirset></code> element.
241: * @param dset a <code>DirSet</code> to be added to the path
242: * @throws BuildException on error
243: */
244: public void addDirset(DirSet dset) throws BuildException {
245: if (dset.getProject() == null) {
246: dset.setProject(getProject());
247: }
248: add(dset);
249: }
250:
251: /**
252: * Adds a nested path
253: * @param path a <code>Path</code> to be added to the path
254: * @throws BuildException on error
255: * @since Ant 1.6
256: */
257: public void add(Path path) throws BuildException {
258: if (path == this ) {
259: throw circularReference();
260: }
261: if (path.getProject() == null) {
262: path.setProject(getProject());
263: }
264: add((ResourceCollection) path);
265: }
266:
267: /**
268: * Add a nested <code>ResourceCollection</code>.
269: * @param c the ResourceCollection to add.
270: * @since Ant 1.7
271: */
272: public void add(ResourceCollection c) {
273: checkChildrenAllowed();
274: if (c == null) {
275: return;
276: }
277: if (union == null) {
278: union = new Union();
279: union.setProject(getProject());
280: union.setCache(false);
281: }
282: union.add(c);
283: setChecked(false);
284: }
285:
286: /**
287: * Creates a nested <code><path></code> element.
288: * @return a <code>Path</code> to be configured
289: * @throws BuildException on error
290: */
291: public Path createPath() throws BuildException {
292: Path p = new Path(getProject());
293: add(p);
294: return p;
295: }
296:
297: /**
298: * Append the contents of the other Path instance to this.
299: * @param other a <code>Path</code> to be added to the path
300: */
301: public void append(Path other) {
302: if (other == null) {
303: return;
304: }
305: add(other);
306: }
307:
308: /**
309: * Adds the components on the given path which exist to this
310: * Path. Components that don't exist aren't added.
311: *
312: * @param source - source path whose components are examined for existence
313: */
314: public void addExisting(Path source) {
315: addExisting(source, false);
316: }
317:
318: /**
319: * Same as addExisting, but support classpath behavior if tryUserDir
320: * is true. Classpaths are relative to user dir, not the project base.
321: * That used to break jspc test
322: *
323: * @param source the source path
324: * @param tryUserDir if true try the user directory if the file is not present
325: */
326: public void addExisting(Path source, boolean tryUserDir) {
327: String[] list = source.list();
328: File userDir = (tryUserDir) ? new File(System
329: .getProperty("user.dir")) : null;
330:
331: for (int i = 0; i < list.length; i++) {
332: File f = resolveFile(getProject(), list[i]);
333:
334: // probably not the best choice, but it solves the problem of
335: // relative paths in CLASSPATH
336: if (tryUserDir && !f.exists()) {
337: f = new File(userDir, list[i]);
338: }
339: if (f.exists()) {
340: setLocation(f);
341: } else {
342: log("dropping " + f + " from path as it doesn't exist",
343: Project.MSG_VERBOSE);
344: }
345: }
346: }
347:
348: /**
349: * Returns all path elements defined by this and nested path objects.
350: * @return list of path elements.
351: */
352: public String[] list() {
353: if (isReference()) {
354: return ((Path) getCheckedRef()).list();
355: }
356: return assertFilesystemOnly(union) == null ? new String[0]
357: : union.list();
358: }
359:
360: /**
361: * Returns a textual representation of the path, which can be used as
362: * CLASSPATH or PATH environment variable definition.
363: * @return a textual representation of the path.
364: */
365: public String toString() {
366: return isReference() ? getCheckedRef().toString()
367: : union == null ? "" : union.toString();
368: }
369:
370: /**
371: * Splits a PATH (with : or ; as separators) into its parts.
372: * @param project the project to use
373: * @param source a <code>String</code> value
374: * @return an array of strings, one for each path element
375: */
376: public static String[] translatePath(Project project, String source) {
377: final Vector result = new Vector();
378: if (source == null) {
379: return new String[0];
380: }
381: PathTokenizer tok = new PathTokenizer(source);
382: StringBuffer element = new StringBuffer();
383: while (tok.hasMoreTokens()) {
384: String pathElement = tok.nextToken();
385: try {
386: element.append(resolveFile(project, pathElement)
387: .getPath());
388: } catch (BuildException e) {
389: project
390: .log(
391: "Dropping path element "
392: + pathElement
393: + " as it is not valid relative to the project",
394: Project.MSG_VERBOSE);
395: }
396: for (int i = 0; i < element.length(); i++) {
397: translateFileSep(element, i);
398: }
399: result.addElement(element.toString());
400: element = new StringBuffer();
401: }
402: String[] res = new String[result.size()];
403: result.copyInto(res);
404: return res;
405: }
406:
407: /**
408: * Returns its argument with all file separator characters
409: * replaced so that they match the local OS conventions.
410: * @param source the path to convert
411: * @return the converted path
412: */
413: public static String translateFile(String source) {
414: if (source == null) {
415: return "";
416: }
417: final StringBuffer result = new StringBuffer(source);
418: for (int i = 0; i < result.length(); i++) {
419: translateFileSep(result, i);
420: }
421: return result.toString();
422: }
423:
424: /**
425: * Translates occurrences at a position of / or \ to correct separator of the
426: * current platform and returns whether it had to do a
427: * replacement.
428: * @param buffer a buffer containing a string
429: * @param pos the position in the string buffer to convert
430: * @return true if the character was a / or \
431: */
432: protected static boolean translateFileSep(StringBuffer buffer,
433: int pos) {
434: if (buffer.charAt(pos) == '/' || buffer.charAt(pos) == '\\') {
435: buffer.setCharAt(pos, File.separatorChar);
436: return true;
437: }
438: return false;
439: }
440:
441: /**
442: * Fulfill the ResourceCollection contract.
443: * @return number of elements as int.
444: */
445: public synchronized int size() {
446: if (isReference()) {
447: return ((Path) getCheckedRef()).size();
448: }
449: dieOnCircularReference();
450: return union == null ? 0 : assertFilesystemOnly(union).size();
451: }
452:
453: /**
454: * Clone this Path.
455: * @return Path with shallowly cloned Resource children.
456: */
457: public Object clone() {
458: try {
459: Path result = (Path) super .clone();
460: result.union = union == null ? union : (Union) union
461: .clone();
462: return result;
463: } catch (CloneNotSupportedException e) {
464: throw new BuildException(e);
465: }
466: }
467:
468: /**
469: * Overrides the version of DataType to recurse on all DataType
470: * child elements that may have been added.
471: * @param stk the stack of data types to use (recursively).
472: * @param p the project to use to dereference the references.
473: * @throws BuildException on error.
474: */
475: protected synchronized void dieOnCircularReference(Stack stk,
476: Project p) throws BuildException {
477: if (isChecked()) {
478: return;
479: }
480: if (isReference()) {
481: super .dieOnCircularReference(stk, p);
482: } else {
483: if (union != null) {
484: stk.push(union);
485: invokeCircularReferenceCheck(union, stk, p);
486: stk.pop();
487: }
488: setChecked(true);
489: }
490: }
491:
492: /**
493: * Resolve a filename with Project's help - if we know one that is.
494: */
495: private static File resolveFile(Project project, String relativeName) {
496: return FileUtils.getFileUtils().resolveFile(
497: (project == null) ? null : project.getBaseDir(),
498: relativeName);
499: }
500:
501: /**
502: * Concatenates the system class path in the order specified by
503: * the ${build.sysclasspath} property - using "last" as
504: * default value.
505: * @return the concatenated path
506: */
507: public Path concatSystemClasspath() {
508: return concatSystemClasspath("last");
509: }
510:
511: /**
512: * Concatenates the system class path in the order specified by
513: * the ${build.sysclasspath} property - using the supplied value
514: * if ${build.sysclasspath} has not been set.
515: * @param defValue the order ("first", "last", "only")
516: * @return the concatenated path
517: */
518: public Path concatSystemClasspath(String defValue) {
519: return concatSpecialPath(defValue, Path.systemClasspath);
520: }
521:
522: /**
523: * Concatenates the system boot class path in the order specified
524: * by the ${build.sysclasspath} property - using the supplied
525: * value if ${build.sysclasspath} has not been set.
526: * @param defValue the order ("first", "last", "only")
527: * @return the concatenated path
528: */
529: public Path concatSystemBootClasspath(String defValue) {
530: return concatSpecialPath(defValue, Path.systemBootClasspath);
531: }
532:
533: /**
534: * Concatenates a class path in the order specified by the
535: * ${build.sysclasspath} property - using the supplied value if
536: * ${build.sysclasspath} has not been set.
537: */
538: private Path concatSpecialPath(String defValue, Path p) {
539: Path result = new Path(getProject());
540:
541: String order = defValue;
542: if (getProject() != null) {
543: String o = getProject().getProperty("build.sysclasspath");
544: if (o != null) {
545: order = o;
546: }
547: }
548: if (order.equals("only")) {
549: // only: the developer knows what (s)he is doing
550: result.addExisting(p, true);
551:
552: } else if (order.equals("first")) {
553: // first: developer could use a little help
554: result.addExisting(p, true);
555: result.addExisting(this );
556:
557: } else if (order.equals("ignore")) {
558: // ignore: don't trust anyone
559: result.addExisting(this );
560:
561: } else {
562: // last: don't trust the developer
563: if (!order.equals("last")) {
564: log("invalid value for build.sysclasspath: " + order,
565: Project.MSG_WARN);
566: }
567: result.addExisting(this );
568: result.addExisting(p, true);
569: }
570: return result;
571: }
572:
573: /**
574: * Add the Java Runtime classes to this Path instance.
575: */
576: public void addJavaRuntime() {
577: if (JavaEnvUtils.isKaffe()) {
578: // newer versions of Kaffe (1.1.1+) won't have this,
579: // but this will be sorted by FileSet anyway.
580: File kaffeShare = new File(System.getProperty("java.home")
581: + File.separator + "share" + File.separator
582: + "kaffe");
583: if (kaffeShare.isDirectory()) {
584: FileSet kaffeJarFiles = new FileSet();
585: kaffeJarFiles.setDir(kaffeShare);
586: kaffeJarFiles.setIncludes("*.jar");
587: addFileset(kaffeJarFiles);
588: }
589: } else if ("GNU libgcj".equals(System
590: .getProperty("java.vm.name"))) {
591: addExisting(systemBootClasspath);
592: }
593:
594: if (System.getProperty("java.vendor").toLowerCase(Locale.US)
595: .indexOf("microsoft") >= 0) {
596: // XXX is this code still necessary? is there any 1.2+ port?
597: // Pull in *.zip from packages directory
598: FileSet msZipFiles = new FileSet();
599: msZipFiles.setDir(new File(System.getProperty("java.home")
600: + File.separator + "Packages"));
601: msZipFiles.setIncludes("*.ZIP");
602: addFileset(msZipFiles);
603: } else {
604: // JDK 1.2+ seems to set java.home to the JRE directory.
605: addExisting(new Path(null, System.getProperty("java.home")
606: + File.separator + "lib" + File.separator
607: + "rt.jar"));
608: // Just keep the old version as well and let addExisting
609: // sort it out.
610: addExisting(new Path(null, System.getProperty("java.home")
611: + File.separator + "jre" + File.separator + "lib"
612: + File.separator + "rt.jar"));
613:
614: // Sun's and Apple's 1.4 have JCE and JSSE in separate jars.
615: String[] secJars = { "jce", "jsse" };
616: for (int i = 0; i < secJars.length; i++) {
617: addExisting(new Path(null, System
618: .getProperty("java.home")
619: + File.separator
620: + "lib"
621: + File.separator
622: + secJars[i] + ".jar"));
623: addExisting(new Path(null, System
624: .getProperty("java.home")
625: + File.separator
626: + ".."
627: + File.separator
628: + "Classes"
629: + File.separator
630: + secJars[i]
631: + ".jar"));
632: }
633:
634: // IBM's 1.4 has rt.jar split into 4 smaller jars and a combined
635: // JCE/JSSE in security.jar.
636: String[] ibmJars = { "core", "graphics", "security",
637: "server", "xml" };
638: for (int i = 0; i < ibmJars.length; i++) {
639: addExisting(new Path(null, System
640: .getProperty("java.home")
641: + File.separator
642: + "lib"
643: + File.separator
644: + ibmJars[i] + ".jar"));
645: }
646:
647: // Added for MacOS X
648: addExisting(new Path(null, System.getProperty("java.home")
649: + File.separator + ".." + File.separator
650: + "Classes" + File.separator + "classes.jar"));
651: addExisting(new Path(null, System.getProperty("java.home")
652: + File.separator + ".." + File.separator
653: + "Classes" + File.separator + "ui.jar"));
654: }
655: }
656:
657: /**
658: * Emulation of extdirs feature in java >= 1.2.
659: * This method adds all files in the given
660: * directories (but not in sub-directories!) to the classpath,
661: * so that you don't have to specify them all one by one.
662: * @param extdirs - Path to append files to
663: */
664: public void addExtdirs(Path extdirs) {
665: if (extdirs == null) {
666: String extProp = System.getProperty("java.ext.dirs");
667: if (extProp != null) {
668: extdirs = new Path(getProject(), extProp);
669: } else {
670: return;
671: }
672: }
673:
674: String[] dirs = extdirs.list();
675: for (int i = 0; i < dirs.length; i++) {
676: File dir = resolveFile(getProject(), dirs[i]);
677: if (dir.exists() && dir.isDirectory()) {
678: FileSet fs = new FileSet();
679: fs.setDir(dir);
680: fs.setIncludes("*");
681: addFileset(fs);
682: }
683: }
684: }
685:
686: /**
687: * Fulfill the ResourceCollection contract. The Iterator returned
688: * will throw ConcurrentModificationExceptions if ResourceCollections
689: * are added to this container while the Iterator is in use.
690: * @return a "fail-fast" Iterator.
691: */
692: public final synchronized Iterator iterator() {
693: if (isReference()) {
694: return ((Path) getCheckedRef()).iterator();
695: }
696: dieOnCircularReference();
697: return union == null ? EMPTY_ITERATOR : assertFilesystemOnly(
698: union).iterator();
699: }
700:
701: /**
702: * Fulfill the ResourceCollection contract.
703: * @return whether this is a filesystem-only resource collection.
704: */
705: public synchronized boolean isFilesystemOnly() {
706: if (isReference()) {
707: return ((Path) getCheckedRef()).isFilesystemOnly();
708: }
709: dieOnCircularReference();
710: assertFilesystemOnly(union);
711: return true;
712: }
713:
714: /**
715: * Verify the specified ResourceCollection is filesystem-only.
716: * @param rc the ResourceCollection to check.
717: * @throws BuildException if <code>rc</code> is not filesystem-only.
718: * @return the passed in ResourceCollection.
719: */
720: protected ResourceCollection assertFilesystemOnly(
721: ResourceCollection rc) {
722: if (rc != null && !(rc.isFilesystemOnly())) {
723: throw new BuildException(getDataTypeName()
724: + " allows only filesystem resources.");
725: }
726: return rc;
727: }
728: }
|