You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

Project.java 27 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774
  1. /*
  2. * The Apache Software License, Version 1.1
  3. *
  4. * Copyright (c) 1999 The Apache Software Foundation. All rights
  5. * reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions
  9. * are met:
  10. *
  11. * 1. Redistributions of source code must retain the above copyright
  12. * notice, this list of conditions and the following disclaimer.
  13. *
  14. * 2. Redistributions in binary form must reproduce the above copyright
  15. * notice, this list of conditions and the following disclaimer in
  16. * the documentation and/or other materials provided with the
  17. * distribution.
  18. *
  19. * 3. The end-user documentation included with the redistribution, if
  20. * any, must include the following acknowlegement:
  21. * "This product includes software developed by the
  22. * Apache Software Foundation (http://www.apache.org/)."
  23. * Alternately, this acknowlegement may appear in the software itself,
  24. * if and wherever such third-party acknowlegements normally appear.
  25. *
  26. * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
  27. * Foundation" must not be used to endorse or promote products derived
  28. * from this software without prior written permission. For written
  29. * permission, please contact apache@apache.org.
  30. *
  31. * 5. Products derived from this software may not be called "Apache"
  32. * nor may "Apache" appear in their names without prior written
  33. * permission of the Apache Group.
  34. *
  35. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  36. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  37. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  38. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  39. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  40. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  41. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  42. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  43. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  44. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  45. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  46. * SUCH DAMAGE.
  47. * ====================================================================
  48. *
  49. * This software consists of voluntary contributions made by many
  50. * individuals on behalf of the Apache Software Foundation. For more
  51. * information on the Apache Software Foundation, please see
  52. * <http://www.apache.org/>.
  53. */
  54. package org.apache.tools.ant;
  55. import java.io.*;
  56. import java.util.*;
  57. import java.text.*;
  58. /**
  59. * Central representation of an Ant project. This class defines a
  60. * Ant project with all of it's targets and tasks. It also provides
  61. * the mechanism to kick off a build using a particular target name.
  62. * <p>
  63. * This class also encapsulates methods which allow Files to be refered
  64. * to using abstract path names which are translated to native system
  65. * file paths at runtime as well as defining various project properties.
  66. *
  67. * @author duncan@x180.com
  68. */
  69. public class Project {
  70. public static final int MSG_ERR = 0;
  71. public static final int MSG_WARN = 1;
  72. public static final int MSG_INFO = 2;
  73. public static final int MSG_VERBOSE = 3;
  74. // private set of constants to represent the state
  75. // of a DFS of the Target dependencies
  76. private static final String VISITING = "VISITING";
  77. private static final String VISITED = "VISITED";
  78. private static String javaVersion;
  79. public static final String JAVA_1_0 = "1.0";
  80. public static final String JAVA_1_1 = "1.1";
  81. public static final String JAVA_1_2 = "1.2";
  82. public static final String JAVA_1_3 = "1.3";
  83. public static final String TOKEN_START = "@";
  84. public static final String TOKEN_END = "@";
  85. private String name;
  86. private PrintStream out;
  87. private int msgOutputLevel = MSG_INFO;
  88. private Hashtable properties = new Hashtable();
  89. private Hashtable userProperties = new Hashtable();
  90. private String defaultTarget;
  91. private Hashtable taskClassDefinitions = new Hashtable();
  92. private Hashtable targets = new Hashtable();
  93. private Hashtable filters = new Hashtable();
  94. private File baseDir;
  95. public Project(PrintStream out, int msgOutputLevel) {
  96. this.out = out;
  97. this.msgOutputLevel = msgOutputLevel;
  98. detectJavaVersion();
  99. String defs = "/org/apache/tools/ant/taskdefs/defaults.properties";
  100. try {
  101. Properties props = new Properties();
  102. InputStream in = this.getClass().getResourceAsStream(defs);
  103. props.load(in);
  104. in.close();
  105. Enumeration enum = props.propertyNames();
  106. while (enum.hasMoreElements()) {
  107. String key = (String) enum.nextElement();
  108. String value = props.getProperty(key);
  109. try {
  110. Class taskClass = Class.forName(value);
  111. addTaskDefinition(key, taskClass);
  112. } catch (ClassNotFoundException cnfe) {
  113. // ignore...
  114. }
  115. }
  116. Properties systemP = System.getProperties();
  117. Enumeration e = systemP.keys();
  118. while (e.hasMoreElements()) {
  119. String name = (String) e.nextElement();
  120. String value = (String) systemP.get(name);
  121. this.setProperty(name, value);
  122. }
  123. } catch (IOException ioe) {
  124. String msg = "Can't load default task list";
  125. System.out.println(msg);
  126. System.exit(1);
  127. }
  128. }
  129. public PrintStream getOutput() {
  130. return this.out;
  131. }
  132. public int getOutputLevel() {
  133. return this.msgOutputLevel;
  134. }
  135. public void log(String msg) {
  136. log(msg, MSG_INFO);
  137. }
  138. public void log(String msg, int msgLevel) {
  139. if (msgLevel <= msgOutputLevel) {
  140. out.println(msg);
  141. }
  142. }
  143. public void log(String msg, String tag, int msgLevel) {
  144. if (msgLevel <= msgOutputLevel) {
  145. out.println("[" + tag + "] " + msg);
  146. }
  147. }
  148. public void setProperty(String name, String value) {
  149. // command line properties take precedence
  150. if (null != userProperties.get(name))
  151. return;
  152. log("Setting project property: " + name + " -> " +
  153. value, MSG_VERBOSE);
  154. properties.put(name, value);
  155. }
  156. public void setUserProperty(String name, String value) {
  157. log("Setting ro project property: " + name + " -> " +
  158. value, MSG_VERBOSE);
  159. userProperties.put(name, value);
  160. properties.put(name, value);
  161. }
  162. public String getProperty(String name) {
  163. if (name == null) return null;
  164. String property = (String) properties.get(name);
  165. return property;
  166. }
  167. public Hashtable getProperties() {
  168. return properties;
  169. }
  170. public void setDefaultTarget(String defaultTarget) {
  171. this.defaultTarget = defaultTarget;
  172. }
  173. // deprecated, use setDefault
  174. public String getDefaultTarget() {
  175. return defaultTarget;
  176. }
  177. // match the attribute name
  178. public void setDefault(String defaultTarget) {
  179. this.defaultTarget = defaultTarget;
  180. }
  181. public void setName(String name) {
  182. this.name = name;
  183. }
  184. public String getName() {
  185. return name;
  186. }
  187. public void addFilter(String token, String value) {
  188. if (token == null) return;
  189. log("Setting token to filter: " + token + " -> "
  190. + value, MSG_VERBOSE);
  191. this.filters.put(token, value);
  192. }
  193. public Hashtable getFilters() {
  194. return filters;
  195. }
  196. // match basedir attribute in xml
  197. public void setBasedir(String baseD) throws BuildException {
  198. try {
  199. setBaseDir(new File(new File(baseD).getCanonicalPath()));
  200. } catch (IOException ioe) {
  201. String msg = "Can't set basedir " + baseDir + " due to " +
  202. ioe.getMessage();
  203. throw new BuildException(msg);
  204. }
  205. }
  206. public void setBaseDir(File baseDir) {
  207. this.baseDir = baseDir;
  208. String msg = "Project base dir set to: " + baseDir;
  209. log(msg, MSG_INFO);
  210. }
  211. public File getBaseDir() {
  212. if (baseDir == null) {
  213. try {
  214. setBasedir(".");
  215. } catch (BuildException ex) {
  216. ex.printStackTrace();
  217. }
  218. }
  219. return baseDir;
  220. }
  221. public static String getJavaVersion() {
  222. return javaVersion;
  223. }
  224. private void detectJavaVersion() {
  225. // Determine the Java version by looking at available classes
  226. // java.lang.StrictMath was introduced in JDK 1.3
  227. // java.lang.ThreadLocal was introduced in JDK 1.2
  228. // java.lang.Void was introduced in JDK 1.1
  229. // Count up version until a NoClassDefFoundError ends the try
  230. try {
  231. javaVersion = JAVA_1_0;
  232. Class.forName("java.lang.Void");
  233. javaVersion = JAVA_1_1;
  234. Class.forName("java.lang.ThreadLocal");
  235. javaVersion = JAVA_1_2;
  236. Class.forName("java.lang.StrictMath");
  237. javaVersion = JAVA_1_3;
  238. setProperty("ant.java.version", javaVersion);
  239. } catch (ClassNotFoundException cnfe) {
  240. // swallow as we've hit the max class version that
  241. // we have
  242. }
  243. // sanity check
  244. if (javaVersion == JAVA_1_0) {
  245. throw new BuildException("Ant cannot work on Java 1.0");
  246. }
  247. log("Detected Java Version: " + javaVersion, MSG_VERBOSE);
  248. log("Detected OS: " + System.getProperty("os.name"), MSG_VERBOSE);
  249. }
  250. public void addTaskDefinition(String taskName, Class taskClass) {
  251. String msg = " +User task: " + taskName + " " + taskClass.getName();
  252. log(msg, MSG_VERBOSE);
  253. taskClassDefinitions.put(taskName, taskClass);
  254. }
  255. /**
  256. * This call expects to add a <em>new</em> Target.
  257. * @param target is the Target to be added to the current
  258. * Project.
  259. * @exception BuildException if the Target already exists
  260. * in the project.
  261. * @see Project#addOrReplaceTarget to replace existing Targets.
  262. */
  263. public void addTarget(Target target) {
  264. String name = target.getName();
  265. if (targets.get(name) != null) {
  266. throw new BuildException("Duplicate target: `"+name+"'");
  267. }
  268. addOrReplaceTarget(name, target);
  269. }
  270. /**
  271. * This call expects to add a <em>new</em> Target.
  272. * @param target is the Target to be added to the current
  273. * Project.
  274. * @param targetName is the name to use for the Target
  275. * @exception BuildException if the Target already exists
  276. * in the project.
  277. * @see Project#addOrReplaceTarget to replace existing Targets.
  278. */
  279. public void addTarget(String targetName, Target target)
  280. throws BuildException {
  281. if (targets.get(targetName) != null) {
  282. throw new BuildException("Duplicate target: `"+targetName+"'");
  283. }
  284. addOrReplaceTarget(targetName, target);
  285. }
  286. /**
  287. * @param target is the Target to be added or replaced in
  288. * the current Project.
  289. */
  290. public void addOrReplaceTarget(Target target) {
  291. addOrReplaceTarget(target.getName(), target);
  292. }
  293. /**
  294. * @param target is the Target to be added/replaced in
  295. * the current Project.
  296. * @param targetName is the name to use for the Target
  297. */
  298. public void addOrReplaceTarget(String targetName, Target target) {
  299. String msg = " +Target: " + targetName;
  300. log(msg, MSG_VERBOSE);
  301. target.setProject(this);
  302. targets.put(targetName, target);
  303. }
  304. public Task createTask(String taskType) throws BuildException {
  305. Class c = (Class)taskClassDefinitions.get(taskType);
  306. // XXX
  307. // check for nulls, other sanity
  308. try {
  309. Object o=c.newInstance();
  310. Task task = null;
  311. if( o instanceof Task ) {
  312. task=(Task)o;
  313. } else {
  314. // "Generic" Bean - use the setter pattern
  315. // and an Adapter
  316. TaskAdapter taskA=new TaskAdapter();
  317. taskA.setProxy( o );
  318. task=taskA;
  319. }
  320. task.setProject(this);
  321. String msg = " +Task: " + taskType;
  322. log (msg, MSG_VERBOSE);
  323. return task;
  324. } catch (Exception e) {
  325. String msg = "Could not create task of type: "
  326. + taskType + " due to " + e;
  327. throw new BuildException(msg);
  328. }
  329. }
  330. public void executeTarget(String targetName) throws BuildException {
  331. // sanity check ourselves, if we've been asked to build nothing
  332. // then we should complain
  333. if (targetName == null) {
  334. String msg = "No target specified";
  335. throw new BuildException(msg);
  336. }
  337. // Sort the dependency tree, and run everything from the
  338. // beginning until we hit our targetName.
  339. // Sorting checks if all the targets (and dependencies)
  340. // exist, and if there is any cycle in the dependency
  341. // graph.
  342. Vector sortedTargets = topoSort(targetName, targets);
  343. int curidx = 0;
  344. String curtarget;
  345. do {
  346. curtarget = (String) sortedTargets.elementAt(curidx++);
  347. runTarget(curtarget, targets);
  348. } while (!curtarget.equals(targetName));
  349. }
  350. public File resolveFile(String fileName) {
  351. // deal with absolute files
  352. if (fileName.startsWith("/")) return new File( fileName );
  353. // Eliminate consecutive slashes after the drive spec
  354. if (fileName.length() >= 2 &&
  355. Character.isLetter(fileName.charAt(0)) &&
  356. fileName.charAt(1) == ':') {
  357. char[] ca = fileName.replace('/', '\\').toCharArray();
  358. char c;
  359. StringBuffer sb = new StringBuffer();
  360. for (int i = 0; i < ca.length; i++) {
  361. if ((ca[i] != '\\') ||
  362. (ca[i] == '\\' &&
  363. i > 0 &&
  364. ca[i - 1] != '\\')) {
  365. if (i == 0 &&
  366. Character.isLetter(ca[i]) &&
  367. i < ca.length - 1 &&
  368. ca[i + 1] == ':') {
  369. c = Character.toUpperCase(ca[i]);
  370. } else {
  371. c = ca[i];
  372. }
  373. sb.append(c);
  374. }
  375. }
  376. return new File(sb.toString());
  377. }
  378. File file = new File(baseDir.getAbsolutePath());
  379. StringTokenizer tok = new StringTokenizer(fileName, "/", false);
  380. while (tok.hasMoreTokens()) {
  381. String part = tok.nextToken();
  382. if (part.equals("..")) {
  383. file = new File(file.getParent());
  384. } else if (part.equals(".")) {
  385. // Do nothing here
  386. } else {
  387. file = new File(file, part);
  388. }
  389. }
  390. try {
  391. return new File(file.getCanonicalPath());
  392. }
  393. catch (IOException e) {
  394. log("IOException getting canonical path for " + file + ": " +
  395. e.getMessage(), MSG_ERR);
  396. return new File(file.getAbsolutePath());
  397. }
  398. }
  399. /**
  400. Translate a path into its native (platform specific)
  401. path. This should be extremely fast, code is
  402. borrowed from ECS project.
  403. <p>
  404. All it does is translate the : into ; and / into \
  405. if needed. In other words, it isn't perfect.
  406. @returns translated string or empty string if to_process is null or empty
  407. @author Jon S. Stevens <a href="mailto:jon@clearink.com">jon@clearink.com</a>
  408. */
  409. public String translatePath(String to_process) {
  410. if ( to_process == null || to_process.length() == 0 ) return "";
  411. StringBuffer bs = new StringBuffer(to_process.length() + 50);
  412. StringCharacterIterator sci = new StringCharacterIterator(to_process);
  413. String path = System.getProperty("path.separator");
  414. String file = System.getProperty("file.separator");
  415. String tmp = null;
  416. for (char c = sci.first(); c != CharacterIterator.DONE; c = sci.next()) {
  417. tmp = String.valueOf(c);
  418. if (tmp.equals(":")) {
  419. // could be a DOS drive or a Unix path separator...
  420. // if followed by a backslash, assume it is a drive
  421. c = sci.next();
  422. tmp = String.valueOf(c);
  423. bs.append( tmp.equals("\\") ? ":" : path );
  424. if (c == CharacterIterator.DONE) break;
  425. }
  426. if (tmp.equals(":") || tmp.equals(";"))
  427. tmp = path;
  428. else if (tmp.equals("/") || tmp.equals ("\\"))
  429. tmp = file;
  430. bs.append(tmp);
  431. }
  432. return(bs.toString());
  433. }
  434. /**
  435. * Convienence method to copy a file from a source to a destination.
  436. * No filtering is performed.
  437. *
  438. * @throws IOException
  439. */
  440. public void copyFile(String sourceFile, String destFile) throws IOException {
  441. copyFile(new File(sourceFile), new File(destFile), false);
  442. }
  443. /**
  444. * Convienence method to copy a file from a source to a destination
  445. * specifying if token filtering must be used.
  446. *
  447. * @throws IOException
  448. */
  449. public void copyFile(String sourceFile, String destFile, boolean filtering)
  450. throws IOException
  451. {
  452. copyFile(new File(sourceFile), new File(destFile), filtering);
  453. }
  454. /**
  455. * Convienence method to copy a file from a source to a destination.
  456. * No filtering is performed.
  457. *
  458. * @throws IOException
  459. */
  460. public void copyFile(File sourceFile, File destFile) throws IOException {
  461. copyFile(sourceFile, destFile, false);
  462. }
  463. /**
  464. * Convienence method to copy a file from a source to a destination
  465. * specifying if token filtering must be used.
  466. *
  467. * @throws IOException
  468. */
  469. public void copyFile(File sourceFile, File destFile, boolean filtering)
  470. throws IOException
  471. {
  472. if (destFile.lastModified() < sourceFile.lastModified()) {
  473. log("Copy: " + sourceFile.getAbsolutePath() + " > "
  474. + destFile.getAbsolutePath(), MSG_VERBOSE);
  475. // ensure that parent dir of dest file exists!
  476. // not using getParentFile method to stay 1.1 compat
  477. File parent = new File(destFile.getParent());
  478. if (!parent.exists()) {
  479. parent.mkdirs();
  480. }
  481. if (filtering) {
  482. BufferedReader in = new BufferedReader(new FileReader(sourceFile));
  483. BufferedWriter out = new BufferedWriter(new FileWriter(destFile));
  484. int length;
  485. String newline = null;
  486. String line = in.readLine();
  487. while (line != null) {
  488. if (line.length() == 0) {
  489. out.newLine();
  490. } else {
  491. newline = replace(line, filters);
  492. out.write(newline);
  493. out.newLine();
  494. }
  495. line = in.readLine();
  496. }
  497. out.close();
  498. in.close();
  499. } else {
  500. FileInputStream in = new FileInputStream(sourceFile);
  501. FileOutputStream out = new FileOutputStream(destFile);
  502. byte[] buffer = new byte[8 * 1024];
  503. int count = 0;
  504. do {
  505. out.write(buffer, 0, count);
  506. count = in.read(buffer, 0, buffer.length);
  507. } while (count != -1);
  508. in.close();
  509. out.close();
  510. }
  511. }
  512. }
  513. /**
  514. * Does replacement on the given string using the given token table.
  515. *
  516. * @returns the string with the token replaced.
  517. */
  518. private String replace(String s, Hashtable tokens) {
  519. int index = s.indexOf(TOKEN_START);
  520. if (index > -1) {
  521. try {
  522. StringBuffer b = new StringBuffer();
  523. int i = 0;
  524. String token = null;
  525. String value = null;
  526. do {
  527. token = s.substring(index + TOKEN_START.length(), s.indexOf(TOKEN_END, index + TOKEN_START.length() + 1));
  528. b.append(s.substring(i, index));
  529. if (tokens.containsKey(token)) {
  530. value = (String) tokens.get(token);
  531. log("Replacing: " + TOKEN_START + token + TOKEN_END + " -> " + value, MSG_VERBOSE);
  532. b.append(value);
  533. } else {
  534. b.append(TOKEN_START);
  535. b.append(token);
  536. b.append(TOKEN_END);
  537. }
  538. i = index + TOKEN_START.length() + token.length() + TOKEN_END.length();
  539. } while ((index = s.indexOf(TOKEN_START, i)) > -1);
  540. b.append(s.substring(i));
  541. return b.toString();
  542. } catch (StringIndexOutOfBoundsException e) {
  543. return s;
  544. }
  545. } else {
  546. return s;
  547. }
  548. }
  549. /**
  550. * returns the boolean equivalent of a string, which is considered true
  551. * if either "on", "true", or "yes" is found, ignoring case.
  552. */
  553. public static boolean toBoolean(String s) {
  554. return (s.equalsIgnoreCase("on") ||
  555. s.equalsIgnoreCase("true") ||
  556. s.equalsIgnoreCase("yes"));
  557. }
  558. // Given a string defining a target name, and a Hashtable
  559. // containing the "name to Target" mapping, pick out the
  560. // Target and execute it.
  561. private final void runTarget(String target, Hashtable targets)
  562. throws BuildException {
  563. Target t = (Target)targets.get(target);
  564. if (t == null) {
  565. throw new RuntimeException("Unexpected missing target `"+target+
  566. "' in this project.");
  567. }
  568. log("Executing Target: "+target, MSG_INFO);
  569. t.execute();
  570. }
  571. /**
  572. * Topologically sort a set of Targets.
  573. * @param root is the (String) name of the root Target. The sort is
  574. * created in such a way that the sequence of Targets uptil the root
  575. * target is the minimum possible such sequence.
  576. * @param targets is a Hashtable representing a "name to Target" mapping
  577. * @return a Vector of Strings with the names of the targets in
  578. * sorted order.
  579. * @exception BuildException if there is a cyclic dependency among the
  580. * Targets, or if a Target does not exist.
  581. */
  582. private final Vector topoSort(String root, Hashtable targets)
  583. throws BuildException {
  584. Vector ret = new Vector();
  585. Hashtable state = new Hashtable();
  586. Stack visiting = new Stack();
  587. // We first run a DFS based sort using the root as the starting node.
  588. // This creates the minimum sequence of Targets to the root node.
  589. // We then do a sort on any remaining unVISITED targets.
  590. // This is unnecessary for doing our build, but it catches
  591. // circular dependencies or missing Targets on the entire
  592. // dependency tree, not just on the Targets that depend on the
  593. // build Target.
  594. tsort(root, targets, state, visiting, ret);
  595. log("Build sequence for target `"+root+"' is "+ret, MSG_VERBOSE);
  596. for (Enumeration en=targets.keys(); en.hasMoreElements();) {
  597. String curTarget = (String)(en.nextElement());
  598. String st = (String) state.get(curTarget);
  599. if (st == null) {
  600. tsort(curTarget, targets, state, visiting, ret);
  601. }
  602. else if (st == VISITING) {
  603. throw new RuntimeException("Unexpected node in visiting state: "+curTarget);
  604. }
  605. }
  606. log("Complete build sequence is "+ret, MSG_VERBOSE);
  607. return ret;
  608. }
  609. // one step in a recursive DFS traversal of the Target dependency tree.
  610. // - The Hashtable "state" contains the state (VISITED or VISITING or null)
  611. // of all the target names.
  612. // - The Stack "visiting" contains a stack of target names that are
  613. // currently on the DFS stack. (NB: the target names in "visiting" are
  614. // exactly the target names in "state" that are in the VISITING state.)
  615. // 1. Set the current target to the VISITING state, and push it onto
  616. // the "visiting" stack.
  617. // 2. Throw a BuildException if any child of the current node is
  618. // in the VISITING state (implies there is a cycle.) It uses the
  619. // "visiting" Stack to construct the cycle.
  620. // 3. If any children have not been VISITED, tsort() the child.
  621. // 4. Add the current target to the Vector "ret" after the children
  622. // have been visited. Move the current target to the VISITED state.
  623. // "ret" now contains the sorted sequence of Targets upto the current
  624. // Target.
  625. private final void tsort(String root, Hashtable targets,
  626. Hashtable state, Stack visiting,
  627. Vector ret)
  628. throws BuildException {
  629. state.put(root, VISITING);
  630. visiting.push(root);
  631. Target target = (Target)(targets.get(root));
  632. // Make sure we exist
  633. if (target == null) {
  634. StringBuffer sb = new StringBuffer("Target `");
  635. sb.append(root);
  636. sb.append("' does not exist in this project. ");
  637. visiting.pop();
  638. if (!visiting.empty()) {
  639. String parent = (String)visiting.peek();
  640. sb.append("It is used from target `");
  641. sb.append(parent);
  642. sb.append("'.");
  643. }
  644. throw new BuildException(new String(sb));
  645. }
  646. for (Enumeration en=target.getDependencies(); en.hasMoreElements();) {
  647. String cur = (String) en.nextElement();
  648. String m=(String)state.get(cur);
  649. if (m == null) {
  650. // Not been visited
  651. tsort(cur, targets, state, visiting, ret);
  652. }
  653. else if (m == VISITING) {
  654. // Currently visiting this node, so have a cycle
  655. throw makeCircularException(cur, visiting);
  656. }
  657. }
  658. String p = (String) visiting.pop();
  659. if (root != p) {
  660. throw new RuntimeException("Unexpected internal error: expected to pop "+root+" but got "+p);
  661. }
  662. state.put(root, VISITED);
  663. ret.addElement(root);
  664. }
  665. private static BuildException makeCircularException(String end, Stack stk) {
  666. StringBuffer sb = new StringBuffer("Circular dependency: ");
  667. sb.append(end);
  668. String c;
  669. do {
  670. c = (String)stk.pop();
  671. sb.append(" <- ");
  672. sb.append(c);
  673. } while(!c.equals(end));
  674. return new BuildException(new String(sb));
  675. }
  676. }