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 32 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926
  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 Hashtable properties = new Hashtable();
  87. private Hashtable userProperties = new Hashtable();
  88. private Hashtable references = new Hashtable();
  89. private String defaultTarget;
  90. private Hashtable taskClassDefinitions = new Hashtable();
  91. private Hashtable targets = new Hashtable();
  92. private Hashtable filters = new Hashtable();
  93. private File baseDir;
  94. private Vector listeners = new Vector();
  95. static {
  96. // Determine the Java version by looking at available classes
  97. // java.lang.StrictMath was introduced in JDK 1.3
  98. // java.lang.ThreadLocal was introduced in JDK 1.2
  99. // java.lang.Void was introduced in JDK 1.1
  100. // Count up version until a NoClassDefFoundError ends the try
  101. try {
  102. javaVersion = JAVA_1_0;
  103. Class.forName("java.lang.Void");
  104. javaVersion = JAVA_1_1;
  105. Class.forName("java.lang.ThreadLocal");
  106. javaVersion = JAVA_1_2;
  107. Class.forName("java.lang.StrictMath");
  108. javaVersion = JAVA_1_3;
  109. } catch (ClassNotFoundException cnfe) {
  110. // swallow as we've hit the max class version that
  111. // we have
  112. }
  113. }
  114. public Project() {
  115. }
  116. /**
  117. * Initialise the project.
  118. *
  119. * This involves setting the default task definitions and loading the
  120. * system properties.
  121. */
  122. public void init() throws BuildException {
  123. setJavaVersionProperty();
  124. String defs = "/org/apache/tools/ant/taskdefs/defaults.properties";
  125. try {
  126. Properties props = new Properties();
  127. InputStream in = this.getClass().getResourceAsStream(defs);
  128. props.load(in);
  129. in.close();
  130. Enumeration enum = props.propertyNames();
  131. while (enum.hasMoreElements()) {
  132. String key = (String) enum.nextElement();
  133. String value = props.getProperty(key);
  134. try {
  135. Class taskClass = Class.forName(value);
  136. addTaskDefinition(key, taskClass);
  137. } catch (NoClassDefFoundError ncdfe) {
  138. // ignore...
  139. } catch (ClassNotFoundException cnfe) {
  140. // ignore...
  141. }
  142. }
  143. Properties systemP = System.getProperties();
  144. Enumeration e = systemP.keys();
  145. while (e.hasMoreElements()) {
  146. String name = (String) e.nextElement();
  147. String value = (String) systemP.get(name);
  148. this.setProperty(name, value);
  149. }
  150. } catch (IOException ioe) {
  151. throw new BuildException("Can't load default task list");
  152. }
  153. }
  154. public void addBuildListener(BuildListener listener) {
  155. listeners.addElement(listener);
  156. }
  157. public void removeBuildListener(BuildListener listener) {
  158. listeners.removeElement(listener);
  159. }
  160. public Vector getBuildListeners() {
  161. return listeners;
  162. }
  163. public void log(String msg) {
  164. log(msg, MSG_INFO);
  165. }
  166. public void log(String msg, int msgLevel) {
  167. fireMessageLogged(this, msg, msgLevel);
  168. }
  169. public void log(Task task, String msg, int msgLevel) {
  170. fireMessageLogged(task, msg, msgLevel);
  171. }
  172. public void log(Target target, String msg, int msgLevel) {
  173. fireMessageLogged(target, msg, msgLevel);
  174. }
  175. public void setProperty(String name, String value) {
  176. // command line properties take precedence
  177. if (null != userProperties.get(name))
  178. return;
  179. log("Setting project property: " + name + " -> " +
  180. value, MSG_VERBOSE);
  181. properties.put(name, value);
  182. }
  183. public void setUserProperty(String name, String value) {
  184. log("Setting ro project property: " + name + " -> " +
  185. value, MSG_VERBOSE);
  186. userProperties.put(name, value);
  187. properties.put(name, value);
  188. }
  189. public String getProperty(String name) {
  190. if (name == null) return null;
  191. String property = (String) properties.get(name);
  192. return property;
  193. }
  194. public String getUserProperty(String name) {
  195. if (name == null) return null;
  196. String property = (String) userProperties.get(name);
  197. return property;
  198. }
  199. public Hashtable getProperties() {
  200. return properties;
  201. }
  202. public Hashtable getUserProperties() {
  203. return userProperties;
  204. }
  205. public void setDefaultTarget(String defaultTarget) {
  206. this.defaultTarget = defaultTarget;
  207. }
  208. // deprecated, use setDefault
  209. public String getDefaultTarget() {
  210. return defaultTarget;
  211. }
  212. // match the attribute name
  213. public void setDefault(String defaultTarget) {
  214. this.defaultTarget = defaultTarget;
  215. }
  216. public void setName(String name) {
  217. this.name = name;
  218. }
  219. public String getName() {
  220. return name;
  221. }
  222. public void addFilter(String token, String value) {
  223. if (token == null) return;
  224. log("Setting token to filter: " + token + " -> "
  225. + value, MSG_VERBOSE);
  226. this.filters.put(token, value);
  227. }
  228. public Hashtable getFilters() {
  229. return filters;
  230. }
  231. // match basedir attribute in xml
  232. public void setBasedir(String baseD) throws BuildException {
  233. try {
  234. setBaseDir(new File(new File(baseD).getCanonicalPath()));
  235. } catch (IOException ioe) {
  236. String msg = "Can't set basedir " + baseDir + " due to " +
  237. ioe.getMessage();
  238. throw new BuildException(msg);
  239. }
  240. }
  241. public void setBaseDir(File baseDir) {
  242. this.baseDir = baseDir;
  243. setProperty( "basedir", baseDir.getAbsolutePath());
  244. String msg = "Project base dir set to: " + baseDir;
  245. log(msg, MSG_VERBOSE);
  246. }
  247. public File getBaseDir() {
  248. if (baseDir == null) {
  249. try {
  250. setBasedir(".");
  251. } catch (BuildException ex) {
  252. ex.printStackTrace();
  253. }
  254. }
  255. return baseDir;
  256. }
  257. public static String getJavaVersion() {
  258. return javaVersion;
  259. }
  260. public void setJavaVersionProperty() {
  261. setProperty("ant.java.version", javaVersion);
  262. // sanity check
  263. if (javaVersion == JAVA_1_0) {
  264. throw new BuildException("Ant cannot work on Java 1.0");
  265. }
  266. log("Detected Java Version: " + javaVersion, MSG_VERBOSE);
  267. log("Detected OS: " + System.getProperty("os.name"), MSG_VERBOSE);
  268. }
  269. public void addTaskDefinition(String taskName, Class taskClass) {
  270. String msg = " +User task: " + taskName + " " + taskClass.getName();
  271. log(msg, MSG_VERBOSE);
  272. taskClassDefinitions.put(taskName, taskClass);
  273. }
  274. public Hashtable getTaskDefinitions() {
  275. return taskClassDefinitions;
  276. }
  277. /**
  278. * This call expects to add a <em>new</em> Target.
  279. * @param target is the Target to be added to the current
  280. * Project.
  281. * @exception BuildException if the Target already exists
  282. * in the project.
  283. * @see Project#addOrReplaceTarget to replace existing Targets.
  284. */
  285. public void addTarget(Target target) {
  286. String name = target.getName();
  287. if (targets.get(name) != null) {
  288. throw new BuildException("Duplicate target: `"+name+"'");
  289. }
  290. addOrReplaceTarget(name, target);
  291. }
  292. /**
  293. * This call expects to add a <em>new</em> Target.
  294. * @param target is the Target to be added to the current
  295. * Project.
  296. * @param targetName is the name to use for the Target
  297. * @exception BuildException if the Target already exists
  298. * in the project.
  299. * @see Project#addOrReplaceTarget to replace existing Targets.
  300. */
  301. public void addTarget(String targetName, Target target)
  302. throws BuildException {
  303. if (targets.get(targetName) != null) {
  304. throw new BuildException("Duplicate target: `"+targetName+"'");
  305. }
  306. addOrReplaceTarget(targetName, target);
  307. }
  308. /**
  309. * @param target is the Target to be added or replaced in
  310. * the current Project.
  311. */
  312. public void addOrReplaceTarget(Target target) {
  313. addOrReplaceTarget(target.getName(), target);
  314. }
  315. /**
  316. * @param target is the Target to be added/replaced in
  317. * the current Project.
  318. * @param targetName is the name to use for the Target
  319. */
  320. public void addOrReplaceTarget(String targetName, Target target) {
  321. String msg = " +Target: " + targetName;
  322. log(msg, MSG_VERBOSE);
  323. target.setProject(this);
  324. targets.put(targetName, target);
  325. }
  326. public Hashtable getTargets() {
  327. return targets;
  328. }
  329. public Task createTask(String taskType) throws BuildException {
  330. Class c = (Class) taskClassDefinitions.get(taskType);
  331. if (c == null)
  332. throw new BuildException("Could not create task of type: "+taskType+
  333. " because I can't find it in the list of task"+
  334. " class definitions");
  335. try {
  336. Object o = c.newInstance();
  337. Task task = null;
  338. if( o instanceof Task ) {
  339. task=(Task)o;
  340. } else {
  341. // "Generic" Bean - use the setter pattern
  342. // and an Adapter
  343. TaskAdapter taskA=new TaskAdapter();
  344. taskA.setProxy( o );
  345. task=taskA;
  346. }
  347. task.setProject(this);
  348. task.setTaskType(taskType);
  349. // set default value, can be changed by the user
  350. task.setTaskName(taskType);
  351. String msg = " +Task: " + taskType;
  352. log (msg, MSG_VERBOSE);
  353. return task;
  354. } catch (Exception e) {
  355. String msg = "Could not create task of type: "
  356. + taskType + " due to " + e;
  357. throw new BuildException(msg);
  358. }
  359. }
  360. public void executeTargets(Vector targetNames) throws BuildException {
  361. Throwable error = null;
  362. for (int i = 0; i < targetNames.size(); i++) {
  363. executeTarget((String)targetNames.elementAt(i));
  364. }
  365. }
  366. public void executeTarget(String targetName) throws BuildException {
  367. // sanity check ourselves, if we've been asked to build nothing
  368. // then we should complain
  369. if (targetName == null) {
  370. String msg = "No target specified";
  371. throw new BuildException(msg);
  372. }
  373. // Sort the dependency tree, and run everything from the
  374. // beginning until we hit our targetName.
  375. // Sorting checks if all the targets (and dependencies)
  376. // exist, and if there is any cycle in the dependency
  377. // graph.
  378. Vector sortedTargets = topoSort(targetName, targets);
  379. int curidx = 0;
  380. Target curtarget;
  381. do {
  382. curtarget = (Target) sortedTargets.elementAt(curidx++);
  383. runTarget(curtarget);
  384. } while (!curtarget.getName().equals(targetName));
  385. }
  386. public File resolveFile(String fileName) {
  387. fileName = fileName.replace('/', File.separatorChar).replace('\\', File.separatorChar);
  388. // deal with absolute files
  389. if (fileName.startsWith(File.separator))
  390. return new File( fileName );
  391. // Eliminate consecutive slashes after the drive spec
  392. if (fileName.length() >= 2 &&
  393. Character.isLetter(fileName.charAt(0)) &&
  394. fileName.charAt(1) == ':') {
  395. char[] ca = fileName.replace('/', '\\').toCharArray();
  396. char c;
  397. StringBuffer sb = new StringBuffer();
  398. for (int i = 0; i < ca.length; i++) {
  399. if ((ca[i] != '\\') ||
  400. (ca[i] == '\\' &&
  401. i > 0 &&
  402. ca[i - 1] != '\\')) {
  403. if (i == 0 &&
  404. Character.isLetter(ca[i]) &&
  405. i < ca.length - 1 &&
  406. ca[i + 1] == ':') {
  407. c = Character.toUpperCase(ca[i]);
  408. } else {
  409. c = ca[i];
  410. }
  411. sb.append(c);
  412. }
  413. }
  414. return new File(sb.toString());
  415. }
  416. File file = new File(baseDir.getAbsolutePath());
  417. StringTokenizer tok = new StringTokenizer(fileName, File.separator, false);
  418. while (tok.hasMoreTokens()) {
  419. String part = tok.nextToken();
  420. if (part.equals("..")) {
  421. file = new File(file.getParent());
  422. } else if (part.equals(".")) {
  423. // Do nothing here
  424. } else {
  425. file = new File(file, part);
  426. }
  427. }
  428. try {
  429. return new File(file.getCanonicalPath());
  430. }
  431. catch (IOException e) {
  432. log("IOException getting canonical path for " + file + ": " +
  433. e.getMessage(), MSG_ERR);
  434. return new File(file.getAbsolutePath());
  435. }
  436. }
  437. /**
  438. * Translate a path into its native (platform specific) format.
  439. * <p>
  440. * This method uses the PathTokenizer class to separate the input path
  441. * into its components. This handles DOS style paths in a relatively
  442. * sensible way. The file separators are then converted to their platform
  443. * specific versions.
  444. *
  445. * @param to_process the path to be converted
  446. *
  447. * @return the native version of to_process or
  448. * an empty string if to_process is null or empty
  449. */
  450. static public String translatePath(String to_process) {
  451. if ( to_process == null || to_process.length() == 0 ) {
  452. return "";
  453. }
  454. StringBuffer path = new StringBuffer(to_process.length() + 50);
  455. PathTokenizer tokenizer = new PathTokenizer(to_process);
  456. while (tokenizer.hasMoreTokens()) {
  457. String pathComponent = tokenizer.nextToken();
  458. pathComponent = pathComponent.replace('/', File.separatorChar);
  459. pathComponent = pathComponent.replace('\\', File.separatorChar);
  460. if (path.length() != 0) {
  461. path.append(File.pathSeparatorChar);
  462. }
  463. path.append(pathComponent);
  464. }
  465. return path.toString();
  466. }
  467. /**
  468. * Convienence method to copy a file from a source to a destination.
  469. * No filtering is performed.
  470. *
  471. * @throws IOException
  472. */
  473. public void copyFile(String sourceFile, String destFile) throws IOException {
  474. copyFile(new File(sourceFile), new File(destFile), false);
  475. }
  476. /**
  477. * Convienence method to copy a file from a source to a destination
  478. * specifying if token filtering must be used.
  479. *
  480. * @throws IOException
  481. */
  482. public void copyFile(String sourceFile, String destFile, boolean filtering)
  483. throws IOException
  484. {
  485. copyFile(new File(sourceFile), new File(destFile), filtering);
  486. }
  487. /**
  488. * Convienence method to copy a file from a source to a
  489. * destination specifying if token filtering must be used and if
  490. * source files may overwrite newer destination files.
  491. *
  492. * @throws IOException
  493. */
  494. public void copyFile(String sourceFile, String destFile, boolean filtering,
  495. boolean overwrite) throws IOException {
  496. copyFile(new File(sourceFile), new File(destFile), filtering,
  497. overwrite);
  498. }
  499. /**
  500. * Convienence method to copy a file from a source to a destination.
  501. * No filtering is performed.
  502. *
  503. * @throws IOException
  504. */
  505. public void copyFile(File sourceFile, File destFile) throws IOException {
  506. copyFile(sourceFile, destFile, false);
  507. }
  508. /**
  509. * Convienence method to copy a file from a source to a destination
  510. * specifying if token filtering must be used.
  511. *
  512. * @throws IOException
  513. */
  514. public void copyFile(File sourceFile, File destFile, boolean filtering)
  515. throws IOException
  516. {
  517. copyFile(sourceFile, destFile, filtering, false);
  518. }
  519. /**
  520. * Convienence method to copy a file from a source to a
  521. * destination specifying if token filtering must be used and if
  522. * source files may overwrite newer destination files.
  523. *
  524. * @throws IOException
  525. */
  526. public void copyFile(File sourceFile, File destFile, boolean filtering,
  527. boolean overwrite) throws IOException {
  528. if (overwrite ||
  529. destFile.lastModified() < sourceFile.lastModified()) {
  530. log("Copy: " + sourceFile.getAbsolutePath() + " > "
  531. + destFile.getAbsolutePath(), MSG_VERBOSE);
  532. // ensure that parent dir of dest file exists!
  533. // not using getParentFile method to stay 1.1 compat
  534. File parent = new File(destFile.getParent());
  535. if (!parent.exists()) {
  536. parent.mkdirs();
  537. }
  538. if (filtering) {
  539. BufferedReader in = new BufferedReader(new FileReader(sourceFile));
  540. BufferedWriter out = new BufferedWriter(new FileWriter(destFile));
  541. int length;
  542. String newline = null;
  543. String line = in.readLine();
  544. while (line != null) {
  545. if (line.length() == 0) {
  546. out.newLine();
  547. } else {
  548. newline = replace(line, filters);
  549. out.write(newline);
  550. out.newLine();
  551. }
  552. line = in.readLine();
  553. }
  554. out.close();
  555. in.close();
  556. } else {
  557. FileInputStream in = new FileInputStream(sourceFile);
  558. FileOutputStream out = new FileOutputStream(destFile);
  559. byte[] buffer = new byte[8 * 1024];
  560. int count = 0;
  561. do {
  562. out.write(buffer, 0, count);
  563. count = in.read(buffer, 0, buffer.length);
  564. } while (count != -1);
  565. in.close();
  566. out.close();
  567. }
  568. }
  569. }
  570. /**
  571. * Does replacement on the given string using the given token table.
  572. *
  573. * @returns the string with the token replaced.
  574. */
  575. private String replace(String s, Hashtable tokens) {
  576. int index = s.indexOf(TOKEN_START);
  577. if (index > -1) {
  578. try {
  579. StringBuffer b = new StringBuffer();
  580. int i = 0;
  581. String token = null;
  582. String value = null;
  583. do {
  584. token = s.substring(index + TOKEN_START.length(), s.indexOf(TOKEN_END, index + TOKEN_START.length() + 1));
  585. b.append(s.substring(i, index));
  586. if (tokens.containsKey(token)) {
  587. value = (String) tokens.get(token);
  588. log("Replacing: " + TOKEN_START + token + TOKEN_END + " -> " + value, MSG_VERBOSE);
  589. b.append(value);
  590. } else {
  591. b.append(TOKEN_START);
  592. b.append(token);
  593. b.append(TOKEN_END);
  594. }
  595. i = index + TOKEN_START.length() + token.length() + TOKEN_END.length();
  596. } while ((index = s.indexOf(TOKEN_START, i)) > -1);
  597. b.append(s.substring(i));
  598. return b.toString();
  599. } catch (StringIndexOutOfBoundsException e) {
  600. return s;
  601. }
  602. } else {
  603. return s;
  604. }
  605. }
  606. /**
  607. * returns the boolean equivalent of a string, which is considered true
  608. * if either "on", "true", or "yes" is found, ignoring case.
  609. */
  610. public static boolean toBoolean(String s) {
  611. return (s.equalsIgnoreCase("on") ||
  612. s.equalsIgnoreCase("true") ||
  613. s.equalsIgnoreCase("yes"));
  614. }
  615. // Given a string defining a target name, and a Hashtable
  616. // containing the "name to Target" mapping, pick out the
  617. // Target and execute it.
  618. public void runTarget(Target target)
  619. throws BuildException {
  620. try {
  621. fireTargetStarted(target);
  622. target.execute();
  623. fireTargetFinished(target, null);
  624. }
  625. catch(RuntimeException exc) {
  626. fireTargetFinished(target, exc);
  627. throw exc;
  628. }
  629. }
  630. /**
  631. * Topologically sort a set of Targets.
  632. * @param root is the (String) name of the root Target. The sort is
  633. * created in such a way that the sequence of Targets uptil the root
  634. * target is the minimum possible such sequence.
  635. * @param targets is a Hashtable representing a "name to Target" mapping
  636. * @return a Vector of Strings with the names of the targets in
  637. * sorted order.
  638. * @exception BuildException if there is a cyclic dependency among the
  639. * Targets, or if a Target does not exist.
  640. */
  641. private final Vector topoSort(String root, Hashtable targets)
  642. throws BuildException {
  643. Vector ret = new Vector();
  644. Hashtable state = new Hashtable();
  645. Stack visiting = new Stack();
  646. // We first run a DFS based sort using the root as the starting node.
  647. // This creates the minimum sequence of Targets to the root node.
  648. // We then do a sort on any remaining unVISITED targets.
  649. // This is unnecessary for doing our build, but it catches
  650. // circular dependencies or missing Targets on the entire
  651. // dependency tree, not just on the Targets that depend on the
  652. // build Target.
  653. tsort(root, targets, state, visiting, ret);
  654. log("Build sequence for target `"+root+"' is "+ret, MSG_VERBOSE);
  655. for (Enumeration en=targets.keys(); en.hasMoreElements();) {
  656. String curTarget = (String)(en.nextElement());
  657. String st = (String) state.get(curTarget);
  658. if (st == null) {
  659. tsort(curTarget, targets, state, visiting, ret);
  660. }
  661. else if (st == VISITING) {
  662. throw new RuntimeException("Unexpected node in visiting state: "+curTarget);
  663. }
  664. }
  665. log("Complete build sequence is "+ret, MSG_VERBOSE);
  666. return ret;
  667. }
  668. // one step in a recursive DFS traversal of the Target dependency tree.
  669. // - The Hashtable "state" contains the state (VISITED or VISITING or null)
  670. // of all the target names.
  671. // - The Stack "visiting" contains a stack of target names that are
  672. // currently on the DFS stack. (NB: the target names in "visiting" are
  673. // exactly the target names in "state" that are in the VISITING state.)
  674. // 1. Set the current target to the VISITING state, and push it onto
  675. // the "visiting" stack.
  676. // 2. Throw a BuildException if any child of the current node is
  677. // in the VISITING state (implies there is a cycle.) It uses the
  678. // "visiting" Stack to construct the cycle.
  679. // 3. If any children have not been VISITED, tsort() the child.
  680. // 4. Add the current target to the Vector "ret" after the children
  681. // have been visited. Move the current target to the VISITED state.
  682. // "ret" now contains the sorted sequence of Targets upto the current
  683. // Target.
  684. private final void tsort(String root, Hashtable targets,
  685. Hashtable state, Stack visiting,
  686. Vector ret)
  687. throws BuildException {
  688. state.put(root, VISITING);
  689. visiting.push(root);
  690. Target target = (Target)(targets.get(root));
  691. // Make sure we exist
  692. if (target == null) {
  693. StringBuffer sb = new StringBuffer("Target `");
  694. sb.append(root);
  695. sb.append("' does not exist in this project. ");
  696. visiting.pop();
  697. if (!visiting.empty()) {
  698. String parent = (String)visiting.peek();
  699. sb.append("It is used from target `");
  700. sb.append(parent);
  701. sb.append("'.");
  702. }
  703. throw new BuildException(new String(sb));
  704. }
  705. for (Enumeration en=target.getDependencies(); en.hasMoreElements();) {
  706. String cur = (String) en.nextElement();
  707. String m=(String)state.get(cur);
  708. if (m == null) {
  709. // Not been visited
  710. tsort(cur, targets, state, visiting, ret);
  711. }
  712. else if (m == VISITING) {
  713. // Currently visiting this node, so have a cycle
  714. throw makeCircularException(cur, visiting);
  715. }
  716. }
  717. String p = (String) visiting.pop();
  718. if (root != p) {
  719. throw new RuntimeException("Unexpected internal error: expected to pop "+root+" but got "+p);
  720. }
  721. state.put(root, VISITED);
  722. ret.addElement(target);
  723. }
  724. private static BuildException makeCircularException(String end, Stack stk) {
  725. StringBuffer sb = new StringBuffer("Circular dependency: ");
  726. sb.append(end);
  727. String c;
  728. do {
  729. c = (String)stk.pop();
  730. sb.append(" <- ");
  731. sb.append(c);
  732. } while(!c.equals(end));
  733. return new BuildException(new String(sb));
  734. }
  735. public void addReference(String name, Object value) {
  736. references.put(name,value);
  737. }
  738. public Hashtable getReferences() {
  739. return references;
  740. }
  741. protected void fireBuildStarted() {
  742. BuildEvent event = new BuildEvent(this);
  743. for (int i = 0; i < listeners.size(); i++) {
  744. BuildListener listener = (BuildListener) listeners.elementAt(i);
  745. listener.buildStarted(event);
  746. }
  747. }
  748. protected void fireBuildFinished(Throwable exception) {
  749. BuildEvent event = new BuildEvent(this);
  750. event.setException(exception);
  751. for (int i = 0; i < listeners.size(); i++) {
  752. BuildListener listener = (BuildListener) listeners.elementAt(i);
  753. listener.buildFinished(event);
  754. }
  755. }
  756. protected void fireTargetStarted(Target target) {
  757. BuildEvent event = new BuildEvent(target);
  758. for (int i = 0; i < listeners.size(); i++) {
  759. BuildListener listener = (BuildListener) listeners.elementAt(i);
  760. listener.targetStarted(event);
  761. }
  762. }
  763. protected void fireTargetFinished(Target target, Throwable exception) {
  764. BuildEvent event = new BuildEvent(target);
  765. event.setException(exception);
  766. for (int i = 0; i < listeners.size(); i++) {
  767. BuildListener listener = (BuildListener) listeners.elementAt(i);
  768. listener.targetFinished(event);
  769. }
  770. }
  771. protected void fireTaskStarted(Task task) {
  772. BuildEvent event = new BuildEvent(task);
  773. for (int i = 0; i < listeners.size(); i++) {
  774. BuildListener listener = (BuildListener) listeners.elementAt(i);
  775. listener.taskStarted(event);
  776. }
  777. }
  778. protected void fireTaskFinished(Task task, Throwable exception) {
  779. BuildEvent event = new BuildEvent(task);
  780. for (int i = 0; i < listeners.size(); i++) {
  781. BuildListener listener = (BuildListener) listeners.elementAt(i);
  782. listener.taskFinished(event);
  783. }
  784. }
  785. private void fireMessageLoggedEvent(BuildEvent event, String message, int priority) {
  786. event.setMessage(message, priority);
  787. for (int i = 0; i < listeners.size(); i++) {
  788. BuildListener listener = (BuildListener) listeners.elementAt(i);
  789. listener.messageLogged(event);
  790. }
  791. }
  792. protected void fireMessageLogged(Project project, String message, int priority) {
  793. BuildEvent event = new BuildEvent(project);
  794. fireMessageLoggedEvent(event, message, priority);
  795. }
  796. protected void fireMessageLogged(Target target, String message, int priority) {
  797. BuildEvent event = new BuildEvent(target);
  798. fireMessageLoggedEvent(event, message, priority);
  799. }
  800. protected void fireMessageLogged(Task task, String message, int priority) {
  801. BuildEvent event = new BuildEvent(task);
  802. fireMessageLoggedEvent(event, message, priority);
  803. }
  804. }