1. /*
  2. * Copyright 2000-2004 The Apache Software Foundation
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. *
  16. */
  17. package org.apache.tools.ant.types;
  18. import java.io.File;
  19. import java.util.Enumeration;
  20. import java.util.Locale;
  21. import java.util.Stack;
  22. import java.util.Vector;
  23. import org.apache.tools.ant.BuildException;
  24. import org.apache.tools.ant.DirectoryScanner;
  25. import org.apache.tools.ant.PathTokenizer;
  26. import org.apache.tools.ant.Project;
  27. import org.apache.tools.ant.util.JavaEnvUtils;
  28. /**
  29. * This object represents a path as used by CLASSPATH or PATH
  30. * environment variable.
  31. * <p>
  32. * <code>
  33. * <sometask><br>
  34. *   <somepath><br>
  35. *     <pathelement location="/path/to/file.jar" /><br>
  36. *     <pathelement path="/path/to/file2.jar:/path/to/class2;/path/to/class3" /><br>
  37. *     <pathelement location="/path/to/file3.jar" /><br>
  38. *     <pathelement location="/path/to/file4.jar" /><br>
  39. *   </somepath><br>
  40. * </sometask><br>
  41. * </code>
  42. * <p>
  43. * The object implemention <code>sometask</code> must provide a method called
  44. * <code>createSomepath</code> which returns an instance of <code>Path</code>.
  45. * Nested path definitions are handled by the Path object and must be labeled
  46. * <code>pathelement</code>.<p>
  47. *
  48. * The path element takes a parameter <code>path</code> which will be parsed
  49. * and split into single elements. It will usually be used
  50. * to define a path from an environment variable.
  51. *
  52. */
  53. public class Path extends DataType implements Cloneable {
  54. private Vector elements;
  55. /** The system classspath as a Path object */
  56. public static Path systemClasspath =
  57. new Path(null, System.getProperty("java.class.path"));
  58. /**
  59. * The system bootclassspath as a Path object.
  60. *
  61. * @since Ant 1.6.2
  62. */
  63. public static Path systemBootClasspath =
  64. new Path(null, System.getProperty("sun.boot.class.path"));
  65. /**
  66. * Helper class, holds the nested <code><pathelement></code> values.
  67. */
  68. public class PathElement {
  69. private String[] parts;
  70. public void setLocation(File loc) {
  71. parts = new String[] {translateFile(loc.getAbsolutePath())};
  72. }
  73. public void setPath(String path) {
  74. parts = Path.translatePath(getProject(), path);
  75. }
  76. public String[] getParts() {
  77. return parts;
  78. }
  79. }
  80. /**
  81. * Invoked by IntrospectionHelper for <code>setXXX(Path p)</code>
  82. * attribute setters.
  83. */
  84. public Path(Project p, String path) {
  85. this(p);
  86. createPathElement().setPath(path);
  87. }
  88. public Path(Project project) {
  89. setProject(project);
  90. elements = new Vector();
  91. }
  92. /**
  93. * Adds a element definition to the path.
  94. * @param location the location of the element to add (must not be
  95. * <code>null</code> nor empty.
  96. */
  97. public void setLocation(File location) throws BuildException {
  98. if (isReference()) {
  99. throw tooManyAttributes();
  100. }
  101. createPathElement().setLocation(location);
  102. }
  103. /**
  104. * Parses a path definition and creates single PathElements.
  105. * @param path the path definition.
  106. */
  107. public void setPath(String path) throws BuildException {
  108. if (isReference()) {
  109. throw tooManyAttributes();
  110. }
  111. createPathElement().setPath(path);
  112. }
  113. /**
  114. * Makes this instance in effect a reference to another Path instance.
  115. *
  116. * <p>You must not set another attribute or nest elements inside
  117. * this element if you make it a reference.</p>
  118. */
  119. public void setRefid(Reference r) throws BuildException {
  120. if (!elements.isEmpty()) {
  121. throw tooManyAttributes();
  122. }
  123. elements.addElement(r);
  124. super.setRefid(r);
  125. }
  126. /**
  127. * Creates the nested <code><pathelement></code> element.
  128. */
  129. public PathElement createPathElement() throws BuildException {
  130. if (isReference()) {
  131. throw noChildrenAllowed();
  132. }
  133. PathElement pe = new PathElement();
  134. elements.addElement(pe);
  135. return pe;
  136. }
  137. /**
  138. * Adds a nested <code><fileset></code> element.
  139. */
  140. public void addFileset(FileSet fs) throws BuildException {
  141. if (isReference()) {
  142. throw noChildrenAllowed();
  143. }
  144. elements.addElement(fs);
  145. setChecked(false);
  146. }
  147. /**
  148. * Adds a nested <code><filelist></code> element.
  149. */
  150. public void addFilelist(FileList fl) throws BuildException {
  151. if (isReference()) {
  152. throw noChildrenAllowed();
  153. }
  154. elements.addElement(fl);
  155. setChecked(false);
  156. }
  157. /**
  158. * Adds a nested <code><dirset></code> element.
  159. */
  160. public void addDirset(DirSet dset) throws BuildException {
  161. if (isReference()) {
  162. throw noChildrenAllowed();
  163. }
  164. elements.addElement(dset);
  165. setChecked(false);
  166. }
  167. /**
  168. * Adds a nested path
  169. * @since Ant 1.6
  170. */
  171. public void add(Path path) throws BuildException {
  172. if (isReference()) {
  173. throw noChildrenAllowed();
  174. }
  175. elements.addElement(path);
  176. setChecked(false);
  177. }
  178. /**
  179. * Creates a nested <code><path></code> element.
  180. */
  181. public Path createPath() throws BuildException {
  182. if (isReference()) {
  183. throw noChildrenAllowed();
  184. }
  185. Path p = new Path(getProject());
  186. elements.addElement(p);
  187. setChecked(false);
  188. return p;
  189. }
  190. /**
  191. * Append the contents of the other Path instance to this.
  192. */
  193. public void append(Path other) {
  194. if (other == null) {
  195. return;
  196. }
  197. String[] l = other.list();
  198. for (int i = 0; i < l.length; i++) {
  199. if (elements.indexOf(l[i]) == -1) {
  200. elements.addElement(l[i]);
  201. }
  202. }
  203. }
  204. /**
  205. * Adds the components on the given path which exist to this
  206. * Path. Components that don't exist, aren't added.
  207. *
  208. * @param source - source path whose components are examined for existence
  209. */
  210. public void addExisting(Path source) {
  211. addExisting(source, false);
  212. }
  213. /** Same as addExisting, but support classpath behavior if tryUserDir
  214. * is true. Classpaths are relative to user dir, not the project base.
  215. * That used to break jspc test
  216. *
  217. * @param source
  218. * @param tryUserDir
  219. */
  220. public void addExisting(Path source, boolean tryUserDir) {
  221. String[] list = source.list();
  222. File userDir = (tryUserDir) ? new File(System.getProperty("user.dir"))
  223. : null;
  224. for (int i = 0; i < list.length; i++) {
  225. File f = null;
  226. if (getProject() != null) {
  227. f = getProject().resolveFile(list[i]);
  228. } else {
  229. f = new File(list[i]);
  230. }
  231. // probably not the best choice, but it solves the problem of
  232. // relative paths in CLASSPATH
  233. if (tryUserDir && !f.exists()) {
  234. f = new File(userDir, list[i]);
  235. }
  236. if (f.exists()) {
  237. setLocation(f);
  238. } else {
  239. log("dropping " + f + " from path as it doesn't exist",
  240. Project.MSG_VERBOSE);
  241. }
  242. }
  243. }
  244. /**
  245. * Returns all path elements defined by this and nested path objects.
  246. * @return list of path elements.
  247. */
  248. public String[] list() {
  249. if (!isChecked()) {
  250. // make sure we don't have a circular reference here
  251. Stack stk = new Stack();
  252. stk.push(this);
  253. dieOnCircularReference(stk, getProject());
  254. }
  255. Vector result = new Vector(2 * elements.size());
  256. for (int i = 0; i < elements.size(); i++) {
  257. Object o = elements.elementAt(i);
  258. if (o instanceof Reference) {
  259. Reference r = (Reference) o;
  260. o = r.getReferencedObject(getProject());
  261. // we only support references to paths right now
  262. if (!(o instanceof Path)) {
  263. String msg = r.getRefId() + " doesn\'t denote a path " + o;
  264. throw new BuildException(msg);
  265. }
  266. }
  267. if (o instanceof String) {
  268. // obtained via append
  269. addUnlessPresent(result, (String) o);
  270. } else if (o instanceof PathElement) {
  271. String[] parts = ((PathElement) o).getParts();
  272. if (parts == null) {
  273. throw new BuildException("You must either set location or"
  274. + " path on <pathelement>");
  275. }
  276. for (int j = 0; j < parts.length; j++) {
  277. addUnlessPresent(result, parts[j]);
  278. }
  279. } else if (o instanceof Path) {
  280. Path p = (Path) o;
  281. if (p.getProject() == null) {
  282. p.setProject(getProject());
  283. }
  284. String[] parts = p.list();
  285. for (int j = 0; j < parts.length; j++) {
  286. addUnlessPresent(result, parts[j]);
  287. }
  288. } else if (o instanceof DirSet) {
  289. DirSet dset = (DirSet) o;
  290. DirectoryScanner ds = dset.getDirectoryScanner(getProject());
  291. String[] s = ds.getIncludedDirectories();
  292. File dir = dset.getDir(getProject());
  293. addUnlessPresent(result, dir, s);
  294. } else if (o instanceof FileSet) {
  295. FileSet fs = (FileSet) o;
  296. DirectoryScanner ds = fs.getDirectoryScanner(getProject());
  297. String[] s = ds.getIncludedFiles();
  298. File dir = fs.getDir(getProject());
  299. addUnlessPresent(result, dir, s);
  300. } else if (o instanceof FileList) {
  301. FileList fl = (FileList) o;
  302. String[] s = fl.getFiles(getProject());
  303. File dir = fl.getDir(getProject());
  304. addUnlessPresent(result, dir, s);
  305. }
  306. }
  307. String[] res = new String[result.size()];
  308. result.copyInto(res);
  309. return res;
  310. }
  311. /**
  312. * Returns a textual representation of the path, which can be used as
  313. * CLASSPATH or PATH environment variable definition.
  314. * @return a textual representation of the path.
  315. */
  316. public String toString() {
  317. final String[] list = list();
  318. // empty path return empty string
  319. if (list.length == 0) {
  320. return "";
  321. }
  322. // path containing one or more elements
  323. final StringBuffer result = new StringBuffer(list[0].toString());
  324. for (int i = 1; i < list.length; i++) {
  325. result.append(File.pathSeparatorChar);
  326. result.append(list[i]);
  327. }
  328. return result.toString();
  329. }
  330. /**
  331. * Splits a PATH (with : or ; as separators) into its parts.
  332. */
  333. public static String[] translatePath(Project project, String source) {
  334. final Vector result = new Vector();
  335. if (source == null) {
  336. return new String[0];
  337. }
  338. PathTokenizer tok = new PathTokenizer(source);
  339. StringBuffer element = new StringBuffer();
  340. while (tok.hasMoreTokens()) {
  341. String pathElement = tok.nextToken();
  342. try {
  343. element.append(resolveFile(project, pathElement));
  344. } catch (BuildException e) {
  345. project.log("Dropping path element " + pathElement
  346. + " as it is not valid relative to the project",
  347. Project.MSG_VERBOSE);
  348. }
  349. for (int i = 0; i < element.length(); i++) {
  350. translateFileSep(element, i);
  351. }
  352. result.addElement(element.toString());
  353. element = new StringBuffer();
  354. }
  355. String[] res = new String[result.size()];
  356. result.copyInto(res);
  357. return res;
  358. }
  359. /**
  360. * Returns its argument with all file separator characters
  361. * replaced so that they match the local OS conventions.
  362. */
  363. public static String translateFile(String source) {
  364. if (source == null) {
  365. return "";
  366. }
  367. final StringBuffer result = new StringBuffer(source);
  368. for (int i = 0; i < result.length(); i++) {
  369. translateFileSep(result, i);
  370. }
  371. return result.toString();
  372. }
  373. /**
  374. * Translates all occurrences of / or \ to correct separator of the
  375. * current platform and returns whether it had to do any
  376. * replacements.
  377. */
  378. protected static boolean translateFileSep(StringBuffer buffer, int pos) {
  379. if (buffer.charAt(pos) == '/' || buffer.charAt(pos) == '\\') {
  380. buffer.setCharAt(pos, File.separatorChar);
  381. return true;
  382. }
  383. return false;
  384. }
  385. /**
  386. * How many parts does this Path instance consist of.
  387. */
  388. public int size() {
  389. return list().length;
  390. }
  391. /**
  392. * Return a Path that holds the same elements as this instance.
  393. */
  394. public Object clone() {
  395. try {
  396. Path p = (Path) super.clone();
  397. p.elements = (Vector) elements.clone();
  398. return p;
  399. } catch (CloneNotSupportedException e) {
  400. throw new BuildException(e);
  401. }
  402. }
  403. /**
  404. * Overrides the version of DataType to recurse on all DataType
  405. * child elements that may have been added.
  406. */
  407. protected void dieOnCircularReference(Stack stk, Project p)
  408. throws BuildException {
  409. if (isChecked()) {
  410. return;
  411. }
  412. Enumeration e = elements.elements();
  413. while (e.hasMoreElements()) {
  414. Object o = e.nextElement();
  415. if (o instanceof Reference) {
  416. o = ((Reference) o).getReferencedObject(p);
  417. }
  418. if (o instanceof DataType) {
  419. if (stk.contains(o)) {
  420. throw circularReference();
  421. } else {
  422. stk.push(o);
  423. ((DataType) o).dieOnCircularReference(stk, p);
  424. stk.pop();
  425. }
  426. }
  427. }
  428. setChecked(true);
  429. }
  430. /**
  431. * Resolve a filename with Project's help - if we know one that is.
  432. *
  433. * <p>Assume the filename is absolute if project is null.</p>
  434. */
  435. private static String resolveFile(Project project, String relativeName) {
  436. if (project != null) {
  437. File f = project.resolveFile(relativeName);
  438. return f.getAbsolutePath();
  439. }
  440. return relativeName;
  441. }
  442. /**
  443. * Adds a String to the Vector if it isn't already included.
  444. */
  445. private static void addUnlessPresent(Vector v, String s) {
  446. if (v.indexOf(s) == -1) {
  447. v.addElement(s);
  448. }
  449. }
  450. /**
  451. * Adds absolute path names of listed files in the given directory
  452. * to the Vector if they are not already included.
  453. */
  454. private static void addUnlessPresent(Vector v, File dir, String[] s) {
  455. for (int j = 0; j < s.length; j++) {
  456. File d = new File(dir, s[j]);
  457. String absolutePath = d.getAbsolutePath();
  458. addUnlessPresent(v, translateFile(absolutePath));
  459. }
  460. }
  461. /**
  462. * Concatenates the system class path in the order specified by
  463. * the ${build.sysclasspath} property - using "last" as
  464. * default value.
  465. */
  466. public Path concatSystemClasspath() {
  467. return concatSystemClasspath("last");
  468. }
  469. /**
  470. * Concatenates the system class path in the order specified by
  471. * the ${build.sysclasspath} property - using the supplied value
  472. * if ${build.sysclasspath} has not been set.
  473. */
  474. public Path concatSystemClasspath(String defValue) {
  475. Path result = new Path(getProject());
  476. String order = defValue;
  477. if (getProject() != null) {
  478. String o = getProject().getProperty("build.sysclasspath");
  479. if (o != null) {
  480. order = o;
  481. }
  482. }
  483. if (order.equals("only")) {
  484. // only: the developer knows what (s)he is doing
  485. result.addExisting(Path.systemClasspath, true);
  486. } else if (order.equals("first")) {
  487. // first: developer could use a little help
  488. result.addExisting(Path.systemClasspath, true);
  489. result.addExisting(this);
  490. } else if (order.equals("ignore")) {
  491. // ignore: don't trust anyone
  492. result.addExisting(this);
  493. } else {
  494. // last: don't trust the developer
  495. if (!order.equals("last")) {
  496. log("invalid value for build.sysclasspath: " + order,
  497. Project.MSG_WARN);
  498. }
  499. result.addExisting(this);
  500. result.addExisting(Path.systemClasspath, true);
  501. }
  502. return result;
  503. }
  504. /**
  505. * Add the Java Runtime classes to this Path instance.
  506. */
  507. public void addJavaRuntime() {
  508. if ("Kaffe".equals(System.getProperty("java.vm.name"))) {
  509. // newer versions of Kaffe (1.1.1+) won't have this,
  510. // but this will be sorted by FileSet anyway.
  511. File kaffeShare = new File(System.getProperty("java.home")
  512. + File.separator + "share"
  513. + File.separator + "kaffe");
  514. if (kaffeShare.isDirectory()) {
  515. FileSet kaffeJarFiles = new FileSet();
  516. kaffeJarFiles.setDir(kaffeShare);
  517. kaffeJarFiles.setIncludes("*.jar");
  518. addFileset(kaffeJarFiles);
  519. }
  520. } else if ("GNU libgcj".equals(System.getProperty("java.vm.name"))) {
  521. addExisting(systemBootClasspath);
  522. }
  523. if (System.getProperty("java.vendor").toLowerCase(Locale.US).indexOf("microsoft") >= 0) {
  524. // Pull in *.zip from packages directory
  525. FileSet msZipFiles = new FileSet();
  526. msZipFiles.setDir(new File(System.getProperty("java.home")
  527. + File.separator + "Packages"));
  528. msZipFiles.setIncludes("*.ZIP");
  529. addFileset(msZipFiles);
  530. } else if (JavaEnvUtils.isJavaVersion(JavaEnvUtils.JAVA_1_1)) {
  531. addExisting(new Path(null,
  532. System.getProperty("java.home")
  533. + File.separator + "lib"
  534. + File.separator
  535. + "classes.zip"));
  536. } else {
  537. // JDK > 1.1 seems to set java.home to the JRE directory.
  538. addExisting(new Path(null,
  539. System.getProperty("java.home")
  540. + File.separator + "lib"
  541. + File.separator + "rt.jar"));
  542. // Just keep the old version as well and let addExisting
  543. // sort it out.
  544. addExisting(new Path(null,
  545. System.getProperty("java.home")
  546. + File.separator + "jre"
  547. + File.separator + "lib"
  548. + File.separator + "rt.jar"));
  549. // Sun's and Apple's 1.4 have JCE and JSSE in separate jars.
  550. String[] secJars = {"jce", "jsse"};
  551. for (int i = 0; i < secJars.length; i++) {
  552. addExisting(new Path(null,
  553. System.getProperty("java.home")
  554. + File.separator + "lib"
  555. + File.separator + secJars[i] + ".jar"));
  556. addExisting(new Path(null,
  557. System.getProperty("java.home")
  558. + File.separator + ".."
  559. + File.separator + "Classes"
  560. + File.separator + secJars[i] + ".jar"));
  561. }
  562. // IBM's 1.4 has rt.jar split into 4 smaller jars and a combined
  563. // JCE/JSSE in security.jar.
  564. String[] ibmJars
  565. = {"core", "graphics", "security", "server", "xml"};
  566. for (int i = 0; i < ibmJars.length; i++) {
  567. addExisting(new Path(null,
  568. System.getProperty("java.home")
  569. + File.separator + "lib"
  570. + File.separator + ibmJars[i] + ".jar"));
  571. }
  572. // Added for MacOS X
  573. addExisting(new Path(null,
  574. System.getProperty("java.home")
  575. + File.separator + ".."
  576. + File.separator + "Classes"
  577. + File.separator + "classes.jar"));
  578. addExisting(new Path(null,
  579. System.getProperty("java.home")
  580. + File.separator + ".."
  581. + File.separator + "Classes"
  582. + File.separator + "ui.jar"));
  583. }
  584. }
  585. /**
  586. * Emulation of extdirs feature in java >= 1.2.
  587. * This method adds all files in the given
  588. * directories (but not in sub-directories!) to the classpath,
  589. * so that you don't have to specify them all one by one.
  590. * @param extdirs - Path to append files to
  591. */
  592. public void addExtdirs(Path extdirs) {
  593. if (extdirs == null) {
  594. String extProp = System.getProperty("java.ext.dirs");
  595. if (extProp != null) {
  596. extdirs = new Path(getProject(), extProp);
  597. } else {
  598. return;
  599. }
  600. }
  601. String[] dirs = extdirs.list();
  602. for (int i = 0; i < dirs.length; i++) {
  603. File dir = getProject().resolveFile(dirs[i]);
  604. if (dir.exists() && dir.isDirectory()) {
  605. FileSet fs = new FileSet();
  606. fs.setDir(dir);
  607. fs.setIncludes("*");
  608. addFileset(fs);
  609. }
  610. }
  611. }
  612. }