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