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;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.io.EOFException;
24 import java.io.InputStream;
25 import java.lang.reflect.Method;
26 import java.lang.reflect.Modifier;
27 import java.util.Collections;
28 import java.util.Enumeration;
29 import java.util.Hashtable;
30 import java.util.Iterator;
31 import java.util.Properties;
32 import java.util.Stack;
33 import java.util.Vector;
34 import java.util.Set;
35 import java.util.HashSet;
36 import java.util.HashMap;
37 import java.util.Map;
38 import java.util.WeakHashMap;
39 import org.apache.tools.ant.input.DefaultInputHandler;
40 import org.apache.tools.ant.input.InputHandler;
41 import org.apache.tools.ant.helper.DefaultExecutor;
42 import org.apache.tools.ant.types.FilterSet;
43 import org.apache.tools.ant.types.FilterSetCollection;
44 import org.apache.tools.ant.types.Description;
45 import org.apache.tools.ant.types.Path;
46 import org.apache.tools.ant.types.Resource;
47 import org.apache.tools.ant.types.ResourceFactory;
48 import org.apache.tools.ant.types.resources.FileResource;
49 import org.apache.tools.ant.util.FileUtils;
50 import org.apache.tools.ant.util.JavaEnvUtils;
51 import org.apache.tools.ant.util.StringUtils;
52
53 /**
54 * Central representation of an Ant project. This class defines an
55 * Ant project with all of its targets, tasks and various other
56 * properties. It also provides the mechanism to kick off a build using
57 * a particular target name.
58 * <p>
59 * This class also encapsulates methods which allow files to be referred
60 * to using abstract path names which are translated to native system
61 * file paths at runtime.
62 *
63 */
64 public class Project implements ResourceFactory {
65 private static final String LINE_SEP = System.getProperty("line.separator");
66
67 /** Message priority of "error". */
68 public static final int MSG_ERR = 0;
69 /** Message priority of "warning". */
70 public static final int MSG_WARN = 1;
71 /** Message priority of "information". */
72 public static final int MSG_INFO = 2;
73 /** Message priority of "verbose". */
74 public static final int MSG_VERBOSE = 3;
75 /** Message priority of "debug". */
76 public static final int MSG_DEBUG = 4;
77
78 /**
79 * Constant for the "visiting" state, used when
80 * traversing a DFS of target dependencies.
81 */
82 private static final String VISITING = "VISITING";
83 /**
84 * Constant for the "visited" state, used when
85 * traversing a DFS of target dependencies.
86 */
87 private static final String VISITED = "VISITED";
88
89 /**
90 * Version constant for Java 1.0 .
91 *
92 * @deprecated since 1.5.x.
93 * Use {@link JavaEnvUtils#JAVA_1_0} instead.
94 */
95 public static final String JAVA_1_0 = JavaEnvUtils.JAVA_1_0;
96 /**
97 * Version constant for Java 1.1 .
98 *
99 * @deprecated since 1.5.x.
100 * Use {@link JavaEnvUtils#JAVA_1_1} instead.
101 */
102 public static final String JAVA_1_1 = JavaEnvUtils.JAVA_1_1;
103 /**
104 * Version constant for Java 1.2 .
105 *
106 * @deprecated since 1.5.x.
107 * Use {@link JavaEnvUtils#JAVA_1_2} instead.
108 */
109 public static final String JAVA_1_2 = JavaEnvUtils.JAVA_1_2;
110 /**
111 * Version constant for Java 1.3 .
112 *
113 * @deprecated since 1.5.x.
114 * Use {@link JavaEnvUtils#JAVA_1_3} instead.
115 */
116 public static final String JAVA_1_3 = JavaEnvUtils.JAVA_1_3;
117 /**
118 * Version constant for Java 1.4 .
119 *
120 * @deprecated since 1.5.x.
121 * Use {@link JavaEnvUtils#JAVA_1_4} instead.
122 */
123 public static final String JAVA_1_4 = JavaEnvUtils.JAVA_1_4;
124
125 /** Default filter start token. */
126 public static final String TOKEN_START = FilterSet.DEFAULT_TOKEN_START;
127 /** Default filter end token. */
128 public static final String TOKEN_END = FilterSet.DEFAULT_TOKEN_END;
129
130 /** Instance of a utility class to use for file operations. */
131 private static final FileUtils FILE_UTILS = FileUtils.getFileUtils();
132
133 /** Name of this project. */
134 private String name;
135 /** Description for this project (if any). */
136 private String description;
137
138
139 /** Map of references within the project (paths etc) (String to Object). */
140 private Hashtable references = new AntRefTable();
141
142 /** Map of id references - used for indicating broken build files */
143 private HashMap idReferences = new HashMap();
144
145 /** the parent project for old id resolution (if inheritreferences is set) */
146 private Project parentIdProject = null;
147
148 /** Name of the project's default target. */
149 private String defaultTarget;
150
151 /** Map from target names to targets (String to Target). */
152 private Hashtable targets = new Hashtable();
153 /** Set of global filters. */
154 private FilterSet globalFilterSet = new FilterSet();
155 {
156 // Initialize the globalFileSet's project
157 globalFilterSet.setProject(this);
158 }
159
160 /**
161 * Wrapper around globalFilterSet. This collection only ever
162 * contains one FilterSet, but the wrapper is needed in order to
163 * make it easier to use the FileUtils interface.
164 */
165 private FilterSetCollection globalFilters
166 = new FilterSetCollection(globalFilterSet);
167
168 /** Project base directory. */
169 private File baseDir;
170
171 /** List of listeners to notify of build events. */
172 private Vector listeners = new Vector();
173
174 /**
175 * The Ant core classloader--may be <code>null</code> if using
176 * parent classloader.
177 */
178 private ClassLoader coreLoader = null;
179
180 /** Records the latest task to be executed on a thread. */
181 private Map/*<Thread,Task>*/ threadTasks = Collections.synchronizedMap(new WeakHashMap());
182
183 /** Records the latest task to be executed on a thread group. */
184 private Map/*<ThreadGroup,Task>*/ threadGroupTasks
185 = Collections.synchronizedMap(new WeakHashMap());
186
187 /**
188 * Called to handle any input requests.
189 */
190 private InputHandler inputHandler = null;
191
192 /**
193 * The default input stream used to read any input.
194 */
195 private InputStream defaultInputStream = null;
196
197 /**
198 * Keep going flag.
199 */
200 private boolean keepGoingMode = false;
201
202 /**
203 * Flag which catches Listeners which try to use System.out or System.err .
204 */
205 private boolean loggingMessage = false;
206
207 /**
208 * Set the input handler.
209 *
210 * @param handler the InputHandler instance to use for gathering input.
211 */
212 public void setInputHandler(InputHandler handler) {
213 inputHandler = handler;
214 }
215
216 /**
217 * Set the default System input stream. Normally this stream is set to
218 * System.in. This inputStream is used when no task input redirection is
219 * being performed.
220 *
221 * @param defaultInputStream the default input stream to use when input
222 * is requested.
223 * @since Ant 1.6
224 */
225 public void setDefaultInputStream(InputStream defaultInputStream) {
226 this.defaultInputStream = defaultInputStream;
227 }
228
229 /**
230 * Get this project's input stream.
231 *
232 * @return the InputStream instance in use by this Project instance to
233 * read input.
234 */
235 public InputStream getDefaultInputStream() {
236 return defaultInputStream;
237 }
238
239 /**
240 * Retrieve the current input handler.
241 *
242 * @return the InputHandler instance currently in place for the project
243 * instance.
244 */
245 public InputHandler getInputHandler() {
246 return inputHandler;
247 }
248
249 /**
250 * Create a new Ant project.
251 */
252 public Project() {
253 inputHandler = new DefaultInputHandler();
254 }
255
256 /**
257 * Create and initialize a subproject. By default the subproject will be of
258 * the same type as its parent. If a no-arg constructor is unavailable, the
259 * <code>Project</code> class will be used.
260 * @return a Project instance configured as a subproject of this Project.
261 * @since Ant 1.7
262 */
263 public Project createSubProject() {
264 Project subProject = null;
265 try {
266 subProject = (Project) (getClass().newInstance());
267 } catch (Exception e) {
268 subProject = new Project();
269 }
270 initSubProject(subProject);
271 return subProject;
272 }
273
274 /**
275 * Initialize a subproject.
276 * @param subProject the subproject to initialize.
277 */
278 public void initSubProject(Project subProject) {
279 ComponentHelper.getComponentHelper(subProject)
280 .initSubProject(ComponentHelper.getComponentHelper(this));
281 subProject.setDefaultInputStream(getDefaultInputStream());
282 subProject.setKeepGoingMode(this.isKeepGoingMode());
283 subProject.setExecutor(getExecutor().getSubProjectExecutor());
284 }
285
286 /**
287 * Initialise the project.
288 *
289 * This involves setting the default task definitions and loading the
290 * system properties.
291 *
292 * @exception BuildException if the default task list cannot be loaded.
293 */
294 public void init() throws BuildException {
295 initProperties();
296
297 ComponentHelper.getComponentHelper(this).initDefaultDefinitions();
298 }
299
300 /**
301 * Initializes the properties.
302 * @exception BuildException if an vital property could not be set.
303 * @since Ant 1.7
304 */
305 public void initProperties() throws BuildException {
306 setJavaVersionProperty();
307 setSystemProperties();
308 setPropertyInternal(MagicNames.ANT_VERSION, Main.getAntVersion());
309 setAntLib();
310 }
311
312 /**
313 * Set a property to the location of ant.jar.
314 * Use the locator to find the location of the Project.class, and
315 * if this is not null, set the property {@link MagicNames#ANT_LIB}
316 * to the result
317 */
318 private void setAntLib() {
319 File antlib = org.apache.tools.ant.launch.Locator.getClassSource(
320 Project.class);
321 if (antlib != null) {
322 setPropertyInternal(MagicNames.ANT_LIB, antlib.getAbsolutePath());
323 }
324 }
325 /**
326 * Factory method to create a class loader for loading classes from
327 * a given path.
328 *
329 * @param path the path from which classes are to be loaded.
330 *
331 * @return an appropriate classloader.
332 */
333 public AntClassLoader createClassLoader(Path path) {
334 return new AntClassLoader(
335 getClass().getClassLoader(), this, path);
336 }
337
338 /**
339 * Factory method to create a class loader for loading classes from
340 * a given path.
341 *
342 * @param parent the parent classloader for the new loader.
343 * @param path the path from which classes are to be loaded.
344 *
345 * @return an appropriate classloader.
346 */
347 public AntClassLoader createClassLoader(
348 ClassLoader parent, Path path) {
349 return new AntClassLoader(parent, this, path);
350 }
351
352 /**
353 * Set the core classloader for the project. If a <code>null</code>
354 * classloader is specified, the parent classloader should be used.
355 *
356 * @param coreLoader The classloader to use for the project.
357 * May be <code>null</code>.
358 */
359 public void setCoreLoader(ClassLoader coreLoader) {
360 this.coreLoader = coreLoader;
361 }
362
363 /**
364 * Return the core classloader to use for this project.
365 * This may be <code>null</code>, indicating that
366 * the parent classloader should be used.
367 *
368 * @return the core classloader to use for this project.
369 *
370 */
371 public ClassLoader getCoreLoader() {
372 return coreLoader;
373 }
374
375 /**
376 * Add a build listener to the list. This listener will
377 * be notified of build events for this project.
378 *
379 * @param listener The listener to add to the list.
380 * Must not be <code>null</code>.
381 */
382 public synchronized void addBuildListener(BuildListener listener) {
383 // If the listeners already has this listener, do nothing
384 if (listeners.contains(listener)) {
385 return;
386 }
387 // create a new Vector to avoid ConcurrentModificationExc when
388 // the listeners get added/removed while we are in fire
389 Vector newListeners = getBuildListeners();
390 newListeners.addElement(listener);
391 listeners = newListeners;
392 }
393
394 /**
395 * Remove a build listener from the list. This listener
396 * will no longer be notified of build events for this project.
397 *
398 * @param listener The listener to remove from the list.
399 * Should not be <code>null</code>.
400 */
401 public synchronized void removeBuildListener(BuildListener listener) {
402 // create a new Vector to avoid ConcurrentModificationExc when
403 // the listeners get added/removed while we are in fire
404 Vector newListeners = getBuildListeners();
405 newListeners.removeElement(listener);
406 listeners = newListeners;
407 }
408
409 /**
410 * Return a copy of the list of build listeners for the project.
411 *
412 * @return a list of build listeners for the project
413 */
414 public Vector getBuildListeners() {
415 return (Vector) listeners.clone();
416 }
417
418 /**
419 * Write a message to the log with the default log level
420 * of MSG_INFO .
421 * @param message The text to log. Should not be <code>null</code>.
422 */
423
424 public void log(String message) {
425 log(message, MSG_INFO);
426 }
427
428 /**
429 * Write a project level message to the log with the given log level.
430 * @param message The text to log. Should not be <code>null</code>.
431 * @param msgLevel The log priority level to use.
432 */
433 public void log(String message, int msgLevel) {
434 log(message, null, msgLevel);
435 }
436
437 /**
438 * Write a project level message to the log with the given log level.
439 * @param message The text to log. Should not be <code>null</code>.
440 * @param throwable The exception causing this log, may be <code>null</code>.
441 * @param msgLevel The log priority level to use.
442 * @since 1.7
443 */
444 public void log(String message, Throwable throwable, int msgLevel) {
445 fireMessageLogged(this, message, throwable, msgLevel);
446 }
447
448 /**
449 * Write a task level message to the log with the given log level.
450 * @param task The task to use in the log. Must not be <code>null</code>.
451 * @param message The text to log. Should not be <code>null</code>.
452 * @param msgLevel The log priority level to use.
453 */
454 public void log(Task task, String message, int msgLevel) {
455 fireMessageLogged(task, message, null, msgLevel);
456 }
457
458 /**
459 * Write a task level message to the log with the given log level.
460 * @param task The task to use in the log. Must not be <code>null</code>.
461 * @param message The text to log. Should not be <code>null</code>.
462 * @param throwable The exception causing this log, may be <code>null</code>.
463 * @param msgLevel The log priority level to use.
464 * @since 1.7
465 */
466 public void log(Task task, String message, Throwable throwable, int msgLevel) {
467 fireMessageLogged(task, message, throwable, msgLevel);
468 }
469
470 /**
471 * Write a target level message to the log with the given log level.
472 * @param target The target to use in the log.
473 * Must not be <code>null</code>.
474 * @param message The text to log. Should not be <code>null</code>.
475 * @param msgLevel The log priority level to use.
476 */
477 public void log(Target target, String message, int msgLevel) {
478 log(target, message, null, msgLevel);
479 }
480
481 /**
482 * Write a target level message to the log with the given log level.
483 * @param target The target to use in the log.
484 * Must not be <code>null</code>.
485 * @param message The text to log. Should not be <code>null</code>.
486 * @param throwable The exception causing this log, may be <code>null</code>.
487 * @param msgLevel The log priority level to use.
488 * @since 1.7
489 */
490 public void log(Target target, String message, Throwable throwable,
491 int msgLevel) {
492 fireMessageLogged(target, message, throwable, msgLevel);
493 }
494
495 /**
496 * Return the set of global filters.
497 *
498 * @return the set of global filters.
499 */
500 public FilterSet getGlobalFilterSet() {
501 return globalFilterSet;
502 }
503
504 /**
505 * Set a property. Any existing property of the same name
506 * is overwritten, unless it is a user property.
507 * @param name The name of property to set.
508 * Must not be <code>null</code>.
509 * @param value The new value of the property.
510 * Must not be <code>null</code>.
511 */
512 public void setProperty(String name, String value) {
513 PropertyHelper.getPropertyHelper(this).
514 setProperty(null, name, value, true);
515 }
516
517 /**
518 * Set a property if no value currently exists. If the property
519 * exists already, a message is logged and the method returns with
520 * no other effect.
521 *
522 * @param name The name of property to set.
523 * Must not be <code>null</code>.
524 * @param value The new value of the property.
525 * Must not be <code>null</code>.
526 * @since 1.5
527 */
528 public void setNewProperty(String name, String value) {
529 PropertyHelper.getPropertyHelper(this).setNewProperty(null, name,
530 value);
531 }
532
533 /**
534 * Set a user property, which cannot be overwritten by
535 * set/unset property calls. Any previous value is overwritten.
536 * @param name The name of property to set.
537 * Must not be <code>null</code>.
538 * @param value The new value of the property.
539 * Must not be <code>null</code>.
540 * @see #setProperty(String,String)
541 */
542 public void setUserProperty(String name, String value) {
543 PropertyHelper.getPropertyHelper(this).setUserProperty(null, name,
544 value);
545 }
546
547 /**
548 * Set a user property, which cannot be overwritten by set/unset
549 * property calls. Any previous value is overwritten. Also marks
550 * these properties as properties that have not come from the
551 * command line.
552 *
553 * @param name The name of property to set.
554 * Must not be <code>null</code>.
555 * @param value The new value of the property.
556 * Must not be <code>null</code>.
557 * @see #setProperty(String,String)
558 */
559 public void setInheritedProperty(String name, String value) {
560 PropertyHelper ph = PropertyHelper.getPropertyHelper(this);
561 ph.setInheritedProperty(null, name, value);
562 }
563
564 /**
565 * Set a property unless it is already defined as a user property
566 * (in which case the method returns silently).
567 *
568 * @param name The name of the property.
569 * Must not be <code>null</code>.
570 * @param value The property value. Must not be <code>null</code>.
571 */
572 private void setPropertyInternal(String name, String value) {
573 PropertyHelper ph = PropertyHelper.getPropertyHelper(this);
574 ph.setProperty(null, name, value, false);
575 }
576
577 /**
578 * Return the value of a property, if it is set.
579 *
580 * @param propertyName The name of the property.
581 * May be <code>null</code>, in which case
582 * the return value is also <code>null</code>.
583 * @return the property value, or <code>null</code> for no match
584 * or if a <code>null</code> name is provided.
585 */
586 public String getProperty(String propertyName) {
587 PropertyHelper ph = PropertyHelper.getPropertyHelper(this);
588 return (String) ph.getProperty(null, propertyName);
589 }
590
591 /**
592 * Replace ${} style constructions in the given value with the
593 * string value of the corresponding data types.
594 *
595 * @param value The string to be scanned for property references.
596 * May be <code>null</code>.
597 *
598 * @return the given string with embedded property names replaced
599 * by values, or <code>null</code> if the given string is
600 * <code>null</code>.
601 *
602 * @exception BuildException if the given value has an unclosed
603 * property name, e.g. <code>${xxx</code>.
604 */
605 public String replaceProperties(String value)
606 throws BuildException {
607 PropertyHelper ph = PropertyHelper.getPropertyHelper(this);
608 return ph.replaceProperties(null, value, null);
609 }
610
611 /**
612 * Return the value of a user property, if it is set.
613 *
614 * @param propertyName The name of the property.
615 * May be <code>null</code>, in which case
616 * the return value is also <code>null</code>.
617 * @return the property value, or <code>null</code> for no match
618 * or if a <code>null</code> name is provided.
619 */
620 public String getUserProperty(String propertyName) {
621 PropertyHelper ph = PropertyHelper.getPropertyHelper(this);
622 return (String) ph.getUserProperty(null, propertyName);
623 }
624
625 /**
626 * Return a copy of the properties table.
627 * @return a hashtable containing all properties
628 * (including user properties).
629 */
630 public Hashtable getProperties() {
631 PropertyHelper ph = PropertyHelper.getPropertyHelper(this);
632 return ph.getProperties();
633 }
634
635 /**
636 * Return a copy of the user property hashtable.
637 * @return a hashtable containing just the user properties.
638 */
639 public Hashtable getUserProperties() {
640 PropertyHelper ph = PropertyHelper.getPropertyHelper(this);
641 return ph.getUserProperties();
642 }
643
644 /**
645 * Copy all user properties that have been set on the command
646 * line or a GUI tool from this instance to the Project instance
647 * given as the argument.
648 *
649 * <p>To copy all "user" properties, you will also have to call
650 * {@link #copyInheritedProperties copyInheritedProperties}.</p>
651 *
652 * @param other the project to copy the properties to. Must not be null.
653 *
654 * @since Ant 1.5
655 */
656 public void copyUserProperties(Project other) {
657 PropertyHelper ph = PropertyHelper.getPropertyHelper(this);
658 ph.copyUserProperties(other);
659 }
660
661 /**
662 * Copy all user properties that have not been set on the
663 * command line or a GUI tool from this instance to the Project
664 * instance given as the argument.
665 *
666 * <p>To copy all "user" properties, you will also have to call
667 * {@link #copyUserProperties copyUserProperties}.</p>
668 *
669 * @param other the project to copy the properties to. Must not be null.
670 *
671 * @since Ant 1.5
672 */
673 public void copyInheritedProperties(Project other) {
674 PropertyHelper ph = PropertyHelper.getPropertyHelper(this);
675 ph.copyInheritedProperties(other);
676 }
677
678 /**
679 * Set the default target of the project.
680 *
681 * @param defaultTarget The name of the default target for this project.
682 * May be <code>null</code>, indicating that there is
683 * no default target.
684 *
685 * @deprecated since 1.5.x.
686 * Use setDefault.
687 * @see #setDefault(String)
688 */
689 public void setDefaultTarget(String defaultTarget) {
690 this.defaultTarget = defaultTarget;
691 }
692
693 /**
694 * Return the name of the default target of the project.
695 * @return name of the default target or
696 * <code>null</code> if no default has been set.
697 */
698 public String getDefaultTarget() {
699 return defaultTarget;
700 }
701
702 /**
703 * Set the default target of the project.
704 *
705 * @param defaultTarget The name of the default target for this project.
706 * May be <code>null</code>, indicating that there is
707 * no default target.
708 */
709 public void setDefault(String defaultTarget) {
710 this.defaultTarget = defaultTarget;
711 }
712
713 /**
714 * Set the name of the project, also setting the user
715 * property <code>ant.project.name</code>.
716 *
717 * @param name The name of the project.
718 * Must not be <code>null</code>.
719 */
720 public void setName(String name) {
721 setUserProperty("ant.project.name", name);
722 this.name = name;
723 }
724
725 /**
726 * Return the project name, if one has been set.
727 *
728 * @return the project name, or <code>null</code> if it hasn't been set.
729 */
730 public String getName() {
731 return name;
732 }
733
734 /**
735 * Set the project description.
736 *
737 * @param description The description of the project.
738 * May be <code>null</code>.
739 */
740 public void setDescription(String description) {
741 this.description = description;
742 }
743
744 /**
745 * Return the project description, if one has been set.
746 *
747 * @return the project description, or <code>null</code> if it hasn't
748 * been set.
749 */
750 public String getDescription() {
751 if (description == null) {
752 description = Description.getDescription(this);
753 }
754 return description;
755 }
756
757 /**
758 * Add a filter to the set of global filters.
759 *
760 * @param token The token to filter.
761 * Must not be <code>null</code>.
762 * @param value The replacement value.
763 * Must not be <code>null</code>.
764 * @deprecated since 1.4.x.
765 * Use getGlobalFilterSet().addFilter(token,value)
766 *
767 * @see #getGlobalFilterSet()
768 * @see FilterSet#addFilter(String,String)
769 */
770 public void addFilter(String token, String value) {
771 if (token == null) {
772 return;
773 }
774 globalFilterSet.addFilter(new FilterSet.Filter(token, value));
775 }
776
777 /**
778 * Return a hashtable of global filters, mapping tokens to values.
779 *
780 * @return a hashtable of global filters, mapping tokens to values
781 * (String to String).
782 *
783 * @deprecated since 1.4.x
784 * Use getGlobalFilterSet().getFilterHash().
785 *
786 * @see #getGlobalFilterSet()
787 * @see FilterSet#getFilterHash()
788 */
789 public Hashtable getFilters() {
790 // we need to build the hashtable dynamically
791 return globalFilterSet.getFilterHash();
792 }
793
794 /**
795 * Set the base directory for the project, checking that
796 * the given filename exists and is a directory.
797 *
798 * @param baseD The project base directory.
799 * Must not be <code>null</code>.
800 *
801 * @exception BuildException if the directory if invalid.
802 */
803 public void setBasedir(String baseD) throws BuildException {
804 setBaseDir(new File(baseD));
805 }
806
807 /**
808 * Set the base directory for the project, checking that
809 * the given file exists and is a directory.
810 *
811 * @param baseDir The project base directory.
812 * Must not be <code>null</code>.
813 * @exception BuildException if the specified file doesn't exist or
814 * isn't a directory.
815 */
816 public void setBaseDir(File baseDir) throws BuildException {
817 baseDir = FILE_UTILS.normalize(baseDir.getAbsolutePath());
818 if (!baseDir.exists()) {
819 throw new BuildException("Basedir " + baseDir.getAbsolutePath()
820 + " does not exist");
821 }
822 if (!baseDir.isDirectory()) {
823 throw new BuildException("Basedir " + baseDir.getAbsolutePath()
824 + " is not a directory");
825 }
826 this.baseDir = baseDir;
827 setPropertyInternal(MagicNames.PROJECT_BASEDIR, this.baseDir.getPath());
828 String msg = "Project base dir set to: " + this.baseDir;
829 log(msg, MSG_VERBOSE);
830 }
831
832 /**
833 * Return the base directory of the project as a file object.
834 *
835 * @return the project base directory, or <code>null</code> if the
836 * base directory has not been successfully set to a valid value.
837 */
838 public File getBaseDir() {
839 if (baseDir == null) {
840 try {
841 setBasedir(".");
842 } catch (BuildException ex) {
843 ex.printStackTrace();
844 }
845 }
846 return baseDir;
847 }
848
849 /**
850 * Set "keep-going" mode. In this mode Ant will try to execute
851 * as many targets as possible. All targets that do not depend
852 * on failed target(s) will be executed. If the keepGoing settor/getter
853 * methods are used in conjunction with the <code>ant.executor.class</code>
854 * property, they will have no effect.
855 * @param keepGoingMode "keep-going" mode
856 * @since Ant 1.6
857 */
858 public void setKeepGoingMode(boolean keepGoingMode) {
859 this.keepGoingMode = keepGoingMode;
860 }
861
862 /**
863 * Return the keep-going mode. If the keepGoing settor/getter
864 * methods are used in conjunction with the <code>ant.executor.class</code>
865 * property, they will have no effect.
866 * @return "keep-going" mode
867 * @since Ant 1.6
868 */
869 public boolean isKeepGoingMode() {
870 return this.keepGoingMode;
871 }
872
873 /**
874 * Return the version of Java this class is running under.
875 * @return the version of Java as a String, e.g. "1.1" .
876 * @see org.apache.tools.ant.util.JavaEnvUtils#getJavaVersion
877 * @deprecated since 1.5.x.
878 * Use org.apache.tools.ant.util.JavaEnvUtils instead.
879 */
880 public static String getJavaVersion() {
881 return JavaEnvUtils.getJavaVersion();
882 }
883
884 /**
885 * Set the <code>ant.java.version</code> property and tests for
886 * unsupported JVM versions. If the version is supported,
887 * verbose log messages are generated to record the Java version
888 * and operating system name.
889 *
890 * @exception BuildException if this Java version is not supported.
891 *
892 * @see org.apache.tools.ant.util.JavaEnvUtils#getJavaVersion
893 */
894 public void setJavaVersionProperty() throws BuildException {
895 String javaVersion = JavaEnvUtils.getJavaVersion();
896 setPropertyInternal(MagicNames.ANT_JAVA_VERSION, javaVersion);
897
898 // sanity check
899 if (JavaEnvUtils.isJavaVersion(JavaEnvUtils.JAVA_1_0)
900 || JavaEnvUtils.isJavaVersion(JavaEnvUtils.JAVA_1_1)) {
901 throw new BuildException("Ant cannot work on Java 1.0 / 1.1");
902 }
903 log("Detected Java version: " + javaVersion + " in: "
904 + System.getProperty("java.home"), MSG_VERBOSE);
905
906 log("Detected OS: " + System.getProperty("os.name"), MSG_VERBOSE);
907 }
908
909 /**
910 * Add all system properties which aren't already defined as
911 * user properties to the project properties.
912 */
913 public void setSystemProperties() {
914 Properties systemP = System.getProperties();
915 Enumeration e = systemP.propertyNames();
916 while (e.hasMoreElements()) {
917 String propertyName = (String) e.nextElement();
918 String value = systemP.getProperty(propertyName);
919 if (value != null) {
920 this.setPropertyInternal(propertyName, value);
921 }
922 }
923 }
924
925 /**
926 * Add a new task definition to the project.
927 * Attempting to override an existing definition with an
928 * equivalent one (i.e. with the same classname) results in
929 * a verbose log message. Attempting to override an existing definition
930 * with a different one results in a warning log message and
931 * invalidates any tasks which have already been created with the
932 * old definition.
933 *
934 * @param taskName The name of the task to add.
935 * Must not be <code>null</code>.
936 * @param taskClass The full name of the class implementing the task.
937 * Must not be <code>null</code>.
938 *
939 * @exception BuildException if the class is unsuitable for being an Ant
940 * task. An error level message is logged before
941 * this exception is thrown.
942 *
943 * @see #checkTaskClass(Class)
944 */
945 public void addTaskDefinition(String taskName, Class taskClass)
946 throws BuildException {
947 ComponentHelper.getComponentHelper(this).addTaskDefinition(taskName,
948 taskClass);
949 }
950
951 /**
952 * Check whether or not a class is suitable for serving as Ant task.
953 * Ant task implementation classes must be public, concrete, and have
954 * a no-arg constructor.
955 *
956 * @param taskClass The class to be checked.
957 * Must not be <code>null</code>.
958 *
959 * @exception BuildException if the class is unsuitable for being an Ant
960 * task. An error level message is logged before
961 * this exception is thrown.
962 */
963 public void checkTaskClass(final Class taskClass) throws BuildException {
964 ComponentHelper.getComponentHelper(this).checkTaskClass(taskClass);
965
966 if (!Modifier.isPublic(taskClass.getModifiers())) {
967 final String message = taskClass + " is not public";
968 log(message, Project.MSG_ERR);
969 throw new BuildException(message);
970 }
971 if (Modifier.isAbstract(taskClass.getModifiers())) {
972 final String message = taskClass + " is abstract";
973 log(message, Project.MSG_ERR);
974 throw new BuildException(message);
975 }
976 try {
977 taskClass.getConstructor((Class[]) null);
978 // don't have to check for public, since
979 // getConstructor finds public constructors only.
980 } catch (NoSuchMethodException e) {
981 final String message = "No public no-arg constructor in "
982 + taskClass;
983 log(message, Project.MSG_ERR);
984 throw new BuildException(message);
985 } catch (LinkageError e) {
986 String message = "Could not load " + taskClass + ": " + e;
987 log(message, Project.MSG_ERR);
988 throw new BuildException(message, e);
989 }
990 if (!Task.class.isAssignableFrom(taskClass)) {
991 TaskAdapter.checkTaskClass(taskClass, this);
992 }
993 }
994
995 /**
996 * Return the current task definition hashtable. The returned hashtable is
997 * "live" and so should not be modified.
998 *
999 * @return a map of from task name to implementing class
1000 * (String to Class).
1001 */
1002 public Hashtable getTaskDefinitions() {
1003 return ComponentHelper.getComponentHelper(this).getTaskDefinitions();
1004 }
1005
1006 /**
1007 * Add a new datatype definition.
1008 * Attempting to override an existing definition with an
1009 * equivalent one (i.e. with the same classname) results in
1010 * a verbose log message. Attempting to override an existing definition
1011 * with a different one results in a warning log message, but the
1012 * definition is changed.
1013 *
1014 * @param typeName The name of the datatype.
1015 * Must not be <code>null</code>.
1016 * @param typeClass The full name of the class implementing the datatype.
1017 * Must not be <code>null</code>.
1018 */
1019 public void addDataTypeDefinition(String typeName, Class typeClass) {
1020 ComponentHelper.getComponentHelper(this).addDataTypeDefinition(typeName,
1021 typeClass);
1022 }
1023
1024 /**
1025 * Return the current datatype definition hashtable. The returned
1026 * hashtable is "live" and so should not be modified.
1027 *
1028 * @return a map of from datatype name to implementing class
1029 * (String to Class).
1030 */
1031 public Hashtable getDataTypeDefinitions() {
1032 return ComponentHelper.getComponentHelper(this).getDataTypeDefinitions();
1033 }
1034
1035 /**
1036 * Add a <em>new</em> target to the project.
1037 *
1038 * @param target The target to be added to the project.
1039 * Must not be <code>null</code>.
1040 *
1041 * @exception BuildException if the target already exists in the project
1042 *
1043 * @see Project#addOrReplaceTarget(Target)
1044 */
1045 public void addTarget(Target target) throws BuildException {
1046 addTarget(target.getName(), target);
1047 }
1048
1049 /**
1050 * Add a <em>new</em> target to the project.
1051 *
1052 * @param targetName The name to use for the target.
1053 * Must not be <code>null</code>.
1054 * @param target The target to be added to the project.
1055 * Must not be <code>null</code>.
1056 *
1057 * @exception BuildException if the target already exists in the project.
1058 *
1059 * @see Project#addOrReplaceTarget(String, Target)
1060 */
1061 public void addTarget(String targetName, Target target)
1062 throws BuildException {
1063 if (targets.get(targetName) != null) {
1064 throw new BuildException("Duplicate target: `" + targetName + "'");
1065 }
1066 addOrReplaceTarget(targetName, target);
1067 }
1068
1069 /**
1070 * Add a target to the project, or replaces one with the same
1071 * name.
1072 *
1073 * @param target The target to be added or replaced in the project.
1074 * Must not be <code>null</code>.
1075 */
1076 public void addOrReplaceTarget(Target target) {
1077 addOrReplaceTarget(target.getName(), target);
1078 }
1079
1080 /**
1081 * Add a target to the project, or replaces one with the same
1082 * name.
1083 *
1084 * @param targetName The name to use for the target.
1085 * Must not be <code>null</code>.
1086 * @param target The target to be added or replaced in the project.
1087 * Must not be <code>null</code>.
1088 */
1089 public void addOrReplaceTarget(String targetName, Target target) {
1090 String msg = " +Target: " + targetName;
1091 log(msg, MSG_DEBUG);
1092 target.setProject(this);
1093 targets.put(targetName, target);
1094 }
1095
1096 /**
1097 * Return the hashtable of targets. The returned hashtable
1098 * is "live" and so should not be modified.
1099 * @return a map from name to target (String to Target).
1100 */
1101 public Hashtable getTargets() {
1102 return targets;
1103 }
1104
1105 /**
1106 * Create a new instance of a task, adding it to a list of
1107 * created tasks for later invalidation. This causes all tasks
1108 * to be remembered until the containing project is removed
1109 * @param taskType The name of the task to create an instance of.
1110 * Must not be <code>null</code>.
1111 *
1112 * @return an instance of the specified task, or <code>null</code> if
1113 * the task name is not recognised.
1114 *
1115 * @exception BuildException if the task name is recognised but task
1116 * creation fails.
1117 */
1118 public Task createTask(String taskType) throws BuildException {
1119 return ComponentHelper.getComponentHelper(this).createTask(taskType);
1120 }
1121
1122 /**
1123 * Create a new instance of a data type.
1124 *
1125 * @param typeName The name of the data type to create an instance of.
1126 * Must not be <code>null</code>.
1127 *
1128 * @return an instance of the specified data type, or <code>null</code> if
1129 * the data type name is not recognised.
1130 *
1131 * @exception BuildException if the data type name is recognised but
1132 * instance creation fails.
1133 */
1134 public Object createDataType(String typeName) throws BuildException {
1135 return ComponentHelper.getComponentHelper(this).createDataType(typeName);
1136 }
1137
1138 /**
1139 * Set the Executor instance for this Project.
1140 * @param e the Executor to use.
1141 */
1142 public void setExecutor(Executor e) {
1143 addReference(MagicNames.ANT_EXECUTOR_REFERENCE, e);
1144 }
1145
1146 /**
1147 * Get this Project's Executor (setting it if necessary).
1148 * @return an Executor instance.
1149 */
1150 public Executor getExecutor() {
1151 Object o = getReference(MagicNames.ANT_EXECUTOR_REFERENCE);
1152 if (o == null) {
1153 String classname = getProperty(MagicNames.ANT_EXECUTOR_CLASSNAME);
1154 if (classname == null) {
1155 classname = DefaultExecutor.class.getName();
1156 }
1157 log("Attempting to create object of type " + classname, MSG_DEBUG);
1158 try {
1159 o = Class.forName(classname, true, coreLoader).newInstance();
1160 } catch (ClassNotFoundException seaEnEfEx) {
1161 //try the current classloader
1162 try {
1163 o = Class.forName(classname).newInstance();
1164 } catch (Exception ex) {
1165 log(ex.toString(), MSG_ERR);
1166 }
1167 } catch (Exception ex) {
1168 log(ex.toString(), MSG_ERR);
1169 }
1170 if (o == null) {
1171 throw new BuildException(
1172 "Unable to obtain a Target Executor instance.");
1173 }
1174 setExecutor((Executor) o);
1175 }
1176 return (Executor) o;
1177 }
1178
1179 /**
1180 * Execute the specified sequence of targets, and the targets
1181 * they depend on.
1182 *
1183 * @param names A vector of target name strings to execute.
1184 * Must not be <code>null</code>.
1185 *
1186 * @exception BuildException if the build failed.
1187 */
1188 public void executeTargets(Vector names) throws BuildException {
1189 getExecutor().executeTargets(this,
1190 (String[]) (names.toArray(new String[names.size()])));
1191 }
1192
1193 /**
1194 * Demultiplex output so that each task receives the appropriate
1195 * messages. If the current thread is not currently executing a task,
1196 * the message is logged directly.
1197 *
1198 * @param output Message to handle. Should not be <code>null</code>.
1199 * @param isWarning Whether the text represents an warning (<code>true</code>)
1200 * or information (<code>false</code>).
1201 */
1202 public void demuxOutput(String output, boolean isWarning) {
1203 Task task = getThreadTask(Thread.currentThread());
1204 if (task == null) {
1205 log(output, isWarning ? MSG_WARN : MSG_INFO);
1206 } else {
1207 if (isWarning) {
1208 task.handleErrorOutput(output);
1209 } else {
1210 task.handleOutput(output);
1211 }
1212 }
1213 }
1214
1215 /**
1216 * Read data from the default input stream. If no default has been
1217 * specified, System.in is used.
1218 *
1219 * @param buffer the buffer into which data is to be read.
1220 * @param offset the offset into the buffer at which data is stored.
1221 * @param length the amount of data to read.
1222 *
1223 * @return the number of bytes read.
1224 *
1225 * @exception IOException if the data cannot be read.
1226 * @since Ant 1.6
1227 */
1228 public int defaultInput(byte[] buffer, int offset, int length)
1229 throws IOException {
1230 if (defaultInputStream != null) {
1231 System.out.flush();
1232 return defaultInputStream.read(buffer, offset, length);
1233 } else {
1234 throw new EOFException("No input provided for project");
1235 }
1236 }
1237
1238 /**
1239 * Demux an input request to the correct task.
1240 *
1241 * @param buffer the buffer into which data is to be read.
1242 * @param offset the offset into the buffer at which data is stored.
1243 * @param length the amount of data to read.
1244 *
1245 * @return the number of bytes read.
1246 *
1247 * @exception IOException if the data cannot be read.
1248 * @since Ant 1.6
1249 */
1250 public int demuxInput(byte[] buffer, int offset, int length)
1251 throws IOException {
1252 Task task = getThreadTask(Thread.currentThread());
1253 if (task == null) {
1254 return defaultInput(buffer, offset, length);
1255 } else {
1256 return task.handleInput(buffer, offset, length);
1257 }
1258 }
1259
1260 /**
1261 * Demultiplex flush operations so that each task receives the appropriate
1262 * messages. If the current thread is not currently executing a task,
1263 * the message is logged directly.
1264 *
1265 * @since Ant 1.5.2
1266 *
1267 * @param output Message to handle. Should not be <code>null</code>.
1268 * @param isError Whether the text represents an error (<code>true</code>)
1269 * or information (<code>false</code>).
1270 */
1271 public void demuxFlush(String output, boolean isError) {
1272 Task task = getThreadTask(Thread.currentThread());
1273 if (task == null) {
1274 fireMessageLogged(this, output, isError ? MSG_ERR : MSG_INFO);
1275 } else {
1276 if (isError) {
1277 task.handleErrorFlush(output);
1278 } else {
1279 task.handleFlush(output);
1280 }
1281 }
1282 }
1283
1284 /**
1285 * Execute the specified target and any targets it depends on.
1286 *
1287 * @param targetName The name of the target to execute.
1288 * Must not be <code>null</code>.
1289 *
1290 * @exception BuildException if the build failed.
1291 */
1292 public void executeTarget(String targetName) throws BuildException {
1293
1294 // sanity check ourselves, if we've been asked to build nothing
1295 // then we should complain
1296
1297 if (targetName == null) {
1298 String msg = "No target specified";
1299 throw new BuildException(msg);
1300 }
1301
1302 // Sort and run the dependency tree.
1303 // Sorting checks if all the targets (and dependencies)
1304 // exist, and if there is any cycle in the dependency
1305 // graph.
1306 executeSortedTargets(topoSort(targetName, targets, false));
1307 }
1308
1309 /**
1310 * Execute a <code>Vector</code> of sorted targets.
1311 * @param sortedTargets the aforementioned <code>Vector</code>.
1312 * @throws BuildException on error.
1313 */
1314 public void executeSortedTargets(Vector sortedTargets)
1315 throws BuildException {
1316 Set succeededTargets = new HashSet();
1317 BuildException buildException = null; // first build exception
1318 for (Enumeration iter = sortedTargets.elements();
1319 iter.hasMoreElements();) {
1320 Target curtarget = (Target) iter.nextElement();
1321 boolean canExecute = true;
1322 for (Enumeration depIter = curtarget.getDependencies();
1323 depIter.hasMoreElements();) {
1324 String dependencyName = ((String) depIter.nextElement());
1325 if (!succeededTargets.contains(dependencyName)) {
1326 canExecute = false;
1327 log(curtarget,
1328 "Cannot execute '" + curtarget.getName() + "' - '"
1329 + dependencyName + "' failed or was not executed.",
1330 MSG_ERR);
1331 break;
1332 }
1333 }
1334 if (canExecute) {
1335 Throwable thrownException = null;
1336 try {
1337 curtarget.performTasks();
1338 succeededTargets.add(curtarget.getName());
1339 } catch (RuntimeException ex) {
1340 if (!(keepGoingMode)) {
1341 throw ex; // throw further
1342 }
1343 thrownException = ex;
1344 } catch (Throwable ex) {
1345 if (!(keepGoingMode)) {
1346 throw new BuildException(ex);
1347 }
1348 thrownException = ex;
1349 }
1350 if (thrownException != null) {
1351 if (thrownException instanceof BuildException) {
1352 log(curtarget,
1353 "Target '" + curtarget.getName()
1354 + "' failed with message '"
1355 + thrownException.getMessage() + "'.", MSG_ERR);
1356 // only the first build exception is reported
1357 if (buildException == null) {
1358 buildException = (BuildException) thrownException;
1359 }
1360 } else {
1361 log(curtarget,
1362 "Target '" + curtarget.getName()
1363 + "' failed with message '"
1364 + thrownException.getMessage() + "'.", MSG_ERR);
1365 thrownException.printStackTrace(System.err);
1366 if (buildException == null) {
1367 buildException =
1368 new BuildException(thrownException);
1369 }
1370 }
1371 }
1372 }
1373 }
1374 if (buildException != null) {
1375 throw buildException;
1376 }
1377 }
1378
1379 /**
1380 * Return the canonical form of a filename.
1381 * <p>
1382 * If the specified file name is relative it is resolved
1383 * with respect to the given root directory.
1384 *
1385 * @param fileName The name of the file to resolve.
1386 * Must not be <code>null</code>.
1387 *
1388 * @param rootDir The directory respective to which relative file names
1389 * are resolved. May be <code>null</code>, in which case
1390 * the current directory is used.
1391 *
1392 * @return the resolved File.
1393 *
1394 * @deprecated since 1.4.x
1395 */
1396 public File resolveFile(String fileName, File rootDir) {
1397 return FILE_UTILS.resolveFile(rootDir, fileName);
1398 }
1399
1400 /**
1401 * Return the canonical form of a filename.
1402 * <p>
1403 * If the specified file name is relative it is resolved
1404 * with respect to the project's base directory.
1405 *
1406 * @param fileName The name of the file to resolve.
1407 * Must not be <code>null</code>.
1408 *
1409 * @return the resolved File.
1410 *
1411 */
1412 public File resolveFile(String fileName) {
1413 return FILE_UTILS.resolveFile(baseDir, fileName);
1414 }
1415
1416 /**
1417 * Translate a path into its native (platform specific) format.
1418 * <p>
1419 * This method uses PathTokenizer to separate the input path
1420 * into its components. This handles DOS style paths in a relatively
1421 * sensible way. The file separators are then converted to their platform
1422 * specific versions.
1423 *
1424 * @param toProcess The path to be translated.
1425 * May be <code>null</code>.
1426 *
1427 * @return the native version of the specified path or
1428 * an empty string if the path is <code>null</code> or empty.
1429 *
1430 * @deprecated since 1.7
1431 * Use FileUtils.translatePath instead.
1432 *
1433 * @see PathTokenizer
1434 */
1435 public static String translatePath(String toProcess) {
1436 return FileUtils.translatePath(toProcess);
1437 }
1438
1439 /**
1440 * Convenience method to copy a file from a source to a destination.
1441 * No filtering is performed.
1442 *
1443 * @param sourceFile Name of file to copy from.
1444 * Must not be <code>null</code>.
1445 * @param destFile Name of file to copy to.
1446 * Must not be <code>null</code>.
1447 *
1448 * @exception IOException if the copying fails.
1449 *
1450 * @deprecated since 1.4.x
1451 */
1452 public void copyFile(String sourceFile, String destFile)
1453 throws IOException {
1454 FILE_UTILS.copyFile(sourceFile, destFile);
1455 }
1456
1457 /**
1458 * Convenience method to copy a file from a source to a destination
1459 * specifying if token filtering should be used.
1460 *
1461 * @param sourceFile Name of file to copy from.
1462 * Must not be <code>null</code>.
1463 * @param destFile Name of file to copy to.
1464 * Must not be <code>null</code>.
1465 * @param filtering Whether or not token filtering should be used during
1466 * the copy.
1467 *
1468 * @exception IOException if the copying fails.
1469 *
1470 * @deprecated since 1.4.x
1471 */
1472 public void copyFile(String sourceFile, String destFile, boolean filtering)
1473 throws IOException {
1474 FILE_UTILS.copyFile(sourceFile, destFile,
1475 filtering ? globalFilters : null);
1476 }
1477
1478 /**
1479 * Convenience method to copy a file from a source to a
1480 * destination specifying if token filtering should be used and if
1481 * source files may overwrite newer destination files.
1482 *
1483 * @param sourceFile Name of file to copy from.
1484 * Must not be <code>null</code>.
1485 * @param destFile Name of file to copy to.
1486 * Must not be <code>null</code>.
1487 * @param filtering Whether or not token filtering should be used during
1488 * the copy.
1489 * @param overwrite Whether or not the destination file should be
1490 * overwritten if it already exists.
1491 *
1492 * @exception IOException if the copying fails.
1493 *
1494 * @deprecated since 1.4.x
1495 */
1496 public void copyFile(String sourceFile, String destFile, boolean filtering,
1497 boolean overwrite) throws IOException {
1498 FILE_UTILS.copyFile(sourceFile, destFile,
1499 filtering ? globalFilters : null, overwrite);
1500 }
1501
1502 /**
1503 * Convenience method to copy a file from a source to a
1504 * destination specifying if token filtering should be used, if
1505 * source files may overwrite newer destination files, and if the
1506 * last modified time of the resulting file should be set to
1507 * that of the source file.
1508 *
1509 * @param sourceFile Name of file to copy from.
1510 * Must not be <code>null</code>.
1511 * @param destFile Name of file to copy to.
1512 * Must not be <code>null</code>.
1513 * @param filtering Whether or not token filtering should be used during
1514 * the copy.
1515 * @param overwrite Whether or not the destination file should be
1516 * overwritten if it already exists.
1517 * @param preserveLastModified Whether or not the last modified time of
1518 * the resulting file should be set to that
1519 * of the source file.
1520 *
1521 * @exception IOException if the copying fails.
1522 *
1523 * @deprecated since 1.4.x
1524 */
1525 public void copyFile(String sourceFile, String destFile, boolean filtering,
1526 boolean overwrite, boolean preserveLastModified)
1527 throws IOException {
1528 FILE_UTILS.copyFile(sourceFile, destFile,
1529 filtering ? globalFilters : null, overwrite, preserveLastModified);
1530 }
1531
1532 /**
1533 * Convenience method to copy a file from a source to a destination.
1534 * No filtering is performed.
1535 *
1536 * @param sourceFile File to copy from.
1537 * Must not be <code>null</code>.
1538 * @param destFile File to copy to.
1539 * Must not be <code>null</code>.
1540 *
1541 * @exception IOException if the copying fails.
1542 *
1543 * @deprecated since 1.4.x
1544 */
1545 public void copyFile(File sourceFile, File destFile) throws IOException {
1546 FILE_UTILS.copyFile(sourceFile, destFile);
1547 }
1548
1549 /**
1550 * Convenience method to copy a file from a source to a destination
1551 * specifying if token filtering should be used.
1552 *
1553 * @param sourceFile File to copy from.
1554 * Must not be <code>null</code>.
1555 * @param destFile File to copy to.
1556 * Must not be <code>null</code>.
1557 * @param filtering Whether or not token filtering should be used during
1558 * the copy.
1559 *
1560 * @exception IOException if the copying fails.
1561 *
1562 * @deprecated since 1.4.x
1563 */
1564 public void copyFile(File sourceFile, File destFile, boolean filtering)
1565 throws IOException {
1566 FILE_UTILS.copyFile(sourceFile, destFile,
1567 filtering ? globalFilters : null);
1568 }
1569
1570 /**
1571 * Convenience method to copy a file from a source to a
1572 * destination specifying if token filtering should be used and if
1573 * source files may overwrite newer destination files.
1574 *
1575 * @param sourceFile File to copy from.
1576 * Must not be <code>null</code>.
1577 * @param destFile File to copy to.
1578 * Must not be <code>null</code>.
1579 * @param filtering Whether or not token filtering should be used during
1580 * the copy.
1581 * @param overwrite Whether or not the destination file should be
1582 * overwritten if it already exists.
1583 *
1584 * @exception IOException if the file cannot be copied.
1585 *
1586 * @deprecated since 1.4.x
1587 */
1588 public void copyFile(File sourceFile, File destFile, boolean filtering,
1589 boolean overwrite) throws IOException {
1590 FILE_UTILS.copyFile(sourceFile, destFile,
1591 filtering ? globalFilters : null, overwrite);
1592 }
1593
1594 /**
1595 * Convenience method to copy a file from a source to a
1596 * destination specifying if token filtering should be used, if
1597 * source files may overwrite newer destination files, and if the
1598 * last modified time of the resulting file should be set to
1599 * that of the source file.
1600 *
1601 * @param sourceFile File to copy from.
1602 * Must not be <code>null</code>.
1603 * @param destFile File to copy to.
1604 * Must not be <code>null</code>.
1605 * @param filtering Whether or not token filtering should be used during
1606 * the copy.
1607 * @param overwrite Whether or not the destination file should be
1608 * overwritten if it already exists.
1609 * @param preserveLastModified Whether or not the last modified time of
1610 * the resulting file should be set to that
1611 * of the source file.
1612 *
1613 * @exception IOException if the file cannot be copied.
1614 *
1615 * @deprecated since 1.4.x
1616 */
1617 public void copyFile(File sourceFile, File destFile, boolean filtering,
1618 boolean overwrite, boolean preserveLastModified)
1619 throws IOException {
1620 FILE_UTILS.copyFile(sourceFile, destFile,
1621 filtering ? globalFilters : null, overwrite, preserveLastModified);
1622 }
1623
1624 /**
1625 * Call File.setLastModified(long time) on Java above 1.1, and logs
1626 * a warning on Java 1.1.
1627 *
1628 * @param file The file to set the last modified time on.
1629 * Must not be <code>null</code>.
1630 *
1631 * @param time the required modification time.
1632 *
1633 * @deprecated since 1.4.x
1634 *
1635 * @exception BuildException if the last modified time cannot be set
1636 * despite running on a platform with a version
1637 * above 1.1.
1638 */
1639 public void setFileLastModified(File file, long time)
1640 throws BuildException {
1641 FILE_UTILS.setFileLastModified(file, time);
1642 log("Setting modification time for " + file, MSG_VERBOSE);
1643 }
1644
1645 /**
1646 * Return the boolean equivalent of a string, which is considered
1647 * <code>true</code> if either <code>"on"</code>, <code>"true"</code>,
1648 * or <code>"yes"</code> is found, ignoring case.
1649 *
1650 * @param s The string to convert to a boolean value.
1651 *
1652 * @return <code>true</code> if the given string is <code>"on"</code>,
1653 * <code>"true"</code> or <code>"yes"</code>, or
1654 * <code>false</code> otherwise.
1655 */
1656 public static boolean toBoolean(String s) {
1657 return ("on".equalsIgnoreCase(s)
1658 || "true".equalsIgnoreCase(s)
1659 || "yes".equalsIgnoreCase(s));
1660 }
1661
1662 /**
1663 * Get the Project instance associated with the specified object.
1664 * @param o the object to query.
1665 * @return Project instance, if any.
1666 * @since Ant 1.7.1
1667 */
1668 public static Project getProject(Object o) {
1669 if (o instanceof ProjectComponent) {
1670 return ((ProjectComponent) o).getProject();
1671 }
1672 try {
1673 Method m = o.getClass().getMethod("getProject", (Class[]) null);
1674 if (Project.class == m.getReturnType()) {
1675 return (Project) m.invoke(o, (Object[]) null);
1676 }
1677 } catch (Exception e) {
1678 //too bad
1679 }
1680 return null;
1681 }
1682
1683 /**
1684 * Topologically sort a set of targets. Equivalent to calling
1685 * <code>topoSort(new String[] {root}, targets, true)</code>.
1686 *
1687 * @param root The name of the root target. The sort is created in such
1688 * a way that the sequence of Targets up to the root
1689 * target is the minimum possible such sequence.
1690 * Must not be <code>null</code>.
1691 * @param targetTable A Hashtable mapping names to Targets.
1692 * Must not be <code>null</code>.
1693 * @return a Vector of ALL Target objects in sorted order.
1694 * @exception BuildException if there is a cyclic dependency among the
1695 * targets, or if a named target does not exist.
1696 */
1697 public final Vector topoSort(String root, Hashtable targetTable)
1698 throws BuildException {
1699 return topoSort(new String[] {root}, targetTable, true);
1700 }
1701
1702 /**
1703 * Topologically sort a set of targets. Equivalent to calling
1704 * <code>topoSort(new String[] {root}, targets, returnAll)</code>.
1705 *
1706 * @param root The name of the root target. The sort is created in such
1707 * a way that the sequence of Targets up to the root
1708 * target is the minimum possible such sequence.
1709 * Must not be <code>null</code>.
1710 * @param targetTable A Hashtable mapping names to Targets.
1711 * Must not be <code>null</code>.
1712 * @param returnAll <code>boolean</code> indicating whether to return all
1713 * targets, or the execution sequence only.
1714 * @return a Vector of Target objects in sorted order.
1715 * @exception BuildException if there is a cyclic dependency among the
1716 * targets, or if a named target does not exist.
1717 * @since Ant 1.6.3
1718 */
1719 public final Vector topoSort(String root, Hashtable targetTable,
1720 boolean returnAll) throws BuildException {
1721 return topoSort(new String[] {root}, targetTable, returnAll);
1722 }
1723
1724 /**
1725 * Topologically sort a set of targets.
1726 *
1727 * @param root <code>String[]</code> containing the names of the root targets.
1728 * The sort is created in such a way that the ordered sequence of
1729 * Targets is the minimum possible such sequence to the specified
1730 * root targets.
1731 * Must not be <code>null</code>.
1732 * @param targetTable A map of names to targets (String to Target).
1733 * Must not be <code>null</code>.
1734 * @param returnAll <code>boolean</code> indicating whether to return all
1735 * targets, or the execution sequence only.
1736 * @return a Vector of Target objects in sorted order.
1737 * @exception BuildException if there is a cyclic dependency among the
1738 * targets, or if a named target does not exist.
1739 * @since Ant 1.6.3
1740 */
1741 public final Vector topoSort(String[] root, Hashtable targetTable,
1742 boolean returnAll) throws BuildException {
1743 Vector ret = new Vector();
1744 Hashtable state = new Hashtable();
1745 Stack visiting = new Stack();
1746
1747 // We first run a DFS based sort using each root as a starting node.
1748 // This creates the minimum sequence of Targets to the root node(s).
1749 // We then do a sort on any remaining unVISITED targets.
1750 // This is unnecessary for doing our build, but it catches
1751 // circular dependencies or missing Targets on the entire
1752 // dependency tree, not just on the Targets that depend on the
1753 // build Target.
1754
1755 for (int i = 0; i < root.length; i++) {
1756 String st = (String) (state.get(root[i]));
1757 if (st == null) {
1758 tsort(root[i], targetTable, state, visiting, ret);
1759 } else if (st == VISITING) {
1760 throw new RuntimeException("Unexpected node in visiting state: "
1761 + root[i]);
1762 }
1763 }
1764 StringBuffer buf = new StringBuffer("Build sequence for target(s)");
1765
1766 for (int j = 0; j < root.length; j++) {
1767 buf.append((j == 0) ? " `" : ", `").append(root[j]).append('\'');
1768 }
1769 buf.append(" is " + ret);
1770 log(buf.toString(), MSG_VERBOSE);
1771
1772 Vector complete = (returnAll) ? ret : new Vector(ret);
1773 for (Enumeration en = targetTable.keys(); en.hasMoreElements();) {
1774 String curTarget = (String) en.nextElement();
1775 String st = (String) state.get(curTarget);
1776 if (st == null) {
1777 tsort(curTarget, targetTable, state, visiting, complete);
1778 } else if (st == VISITING) {
1779 throw new RuntimeException("Unexpected node in visiting state: "
1780 + curTarget);
1781 }
1782 }
1783 log("Complete build sequence is " + complete, MSG_VERBOSE);
1784 return ret;
1785 }
1786
1787 /**
1788 * Perform a single step in a recursive depth-first-search traversal of
1789 * the target dependency tree.
1790 * <p>
1791 * The current target is first set to the "visiting" state, and
1792 * pushed onto the "visiting" stack.
1793 * <p>
1794 * An exception is then thrown if any child of the current node is in the
1795 * visiting state, as that implies a circular dependency. The exception
1796 * contains details of the cycle, using elements of the "visiting"
1797 * stack.
1798 * <p>
1799 * If any child has not already been "visited", this method is
1800 * called recursively on it.
1801 * <p>
1802 * The current target is then added to the ordered list of targets. Note
1803 * that this is performed after the children have been visited in order
1804 * to get the correct order. The current target is set to the
1805 * "visited" state.
1806 * <p>
1807 * By the time this method returns, the ordered list contains the sequence
1808 * of targets up to and including the current target.
1809 *
1810 * @param root The current target to inspect.
1811 * Must not be <code>null</code>.
1812 * @param targetTable A mapping from names to targets (String to Target).
1813 * Must not be <code>null</code>.
1814 * @param state A mapping from target names to states (String to String).
1815 * The states in question are "VISITING" and
1816 * "VISITED". Must not be <code>null</code>.
1817 * @param visiting A stack of targets which are currently being visited.
1818 * Must not be <code>null</code>.
1819 * @param ret The list to add target names to. This will end up
1820 * containing the complete list of dependencies in
1821 * dependency order.
1822 * Must not be <code>null</code>.
1823 *
1824 * @exception BuildException if a non-existent target is specified or if
1825 * a circular dependency is detected.
1826 */
1827 private void tsort(String root, Hashtable targetTable,
1828 Hashtable state, Stack visiting,
1829 Vector ret)
1830 throws BuildException {
1831 state.put(root, VISITING);
1832 visiting.push(root);
1833
1834 Target target = (Target) targetTable.get(root);
1835
1836 // Make sure we exist
1837 if (target == null) {
1838 StringBuffer sb = new StringBuffer("Target \"");
1839 sb.append(root);
1840 sb.append("\" does not exist in the project \"");
1841 sb.append(name);
1842 sb.append("\". ");
1843 visiting.pop();
1844 if (!visiting.empty()) {
1845 String parent = (String) visiting.peek();
1846 sb.append("It is used from target \"");
1847 sb.append(parent);
1848 sb.append("\".");
1849 }
1850 throw new BuildException(new String(sb));
1851 }
1852 for (Enumeration en = target.getDependencies(); en.hasMoreElements();) {
1853 String cur = (String) en.nextElement();
1854 String m = (String) state.get(cur);
1855 if (m == null) {
1856 // Not been visited
1857 tsort(cur, targetTable, state, visiting, ret);
1858 } else if (m == VISITING) {
1859 // Currently visiting this node, so have a cycle
1860 throw makeCircularException(cur, visiting);
1861 }
1862 }
1863 String p = (String) visiting.pop();
1864 if (root != p) {
1865 throw new RuntimeException("Unexpected internal error: expected to "
1866 + "pop " + root + " but got " + p);
1867 }
1868 state.put(root, VISITED);
1869 ret.addElement(target);
1870 }
1871
1872 /**
1873 * Build an appropriate exception detailing a specified circular
1874 * dependency.
1875 *
1876 * @param end The dependency to stop at. Must not be <code>null</code>.
1877 * @param stk A stack of dependencies. Must not be <code>null</code>.
1878 *
1879 * @return a BuildException detailing the specified circular dependency.
1880 */
1881 private static BuildException makeCircularException(String end, Stack stk) {
1882 StringBuffer sb = new StringBuffer("Circular dependency: ");
1883 sb.append(end);
1884 String c;
1885 do {
1886 c = (String) stk.pop();
1887 sb.append(" <- ");
1888 sb.append(c);
1889 } while (!c.equals(end));
1890 return new BuildException(new String(sb));
1891 }
1892
1893 /**
1894 * Inherit the id references.
1895 * @param parent the parent project of this project.
1896 */
1897 public void inheritIDReferences(Project parent) {
1898 parentIdProject = parent;
1899 }
1900
1901 /**
1902 * Attempt to resolve an Unknown Reference using the
1903 * parsed id's - for BC.
1904 */
1905 private Object resolveIdReference(String key, Project callerProject) {
1906 UnknownElement origUE = (UnknownElement) idReferences.get(key);
1907 if (origUE == null) {
1908 return parentIdProject == null
1909 ? null
1910 : parentIdProject.resolveIdReference(key, callerProject);
1911 }
1912 callerProject.log(
1913 "Warning: Reference " + key + " has not been set at runtime,"
1914 + " but was found during" + LINE_SEP
1915 + "build file parsing, attempting to resolve."
1916 + " Future versions of Ant may support" + LINE_SEP
1917 + " referencing ids defined in non-executed targets.", MSG_WARN);
1918 UnknownElement copyUE = origUE.copy(callerProject);
1919 copyUE.maybeConfigure();
1920 return copyUE.getRealThing();
1921 }
1922
1923 /**
1924 * Add an id reference.
1925 * Used for broken build files.
1926 * @param id the id to set.
1927 * @param value the value to set it to (Unknown element in this case.
1928 */
1929 public void addIdReference(String id, Object value) {
1930 idReferences.put(id, value);
1931 }
1932
1933 /**
1934 * Add a reference to the project.
1935 *
1936 * @param referenceName The name of the reference. Must not be <code>null</code>.
1937 * @param value The value of the reference.
1938 */
1939 public void addReference(String referenceName, Object value) {
1940 synchronized (references) {
1941 Object old = ((AntRefTable) references).getReal(referenceName);
1942 if (old == value) {
1943 // no warning, this is not changing anything
1944 return;
1945 }
1946 if (old != null && !(old instanceof UnknownElement)) {
1947 log("Overriding previous definition of reference to " + referenceName,
1948 MSG_VERBOSE);
1949 }
1950 log("Adding reference: " + referenceName, MSG_DEBUG);
1951 references.put(referenceName, value);
1952 }
1953 }
1954
1955 /**
1956 * Return a map of the references in the project (String to Object).
1957 * The returned hashtable is "live" and so must not be modified.
1958 *
1959 * @return a map of the references in the project (String to Object).
1960 */
1961 public Hashtable getReferences() {
1962 return references;
1963 }
1964
1965 /**
1966 * Look up a reference by its key (ID).
1967 *
1968 * @param key The key for the desired reference.
1969 * Must not be <code>null</code>.
1970 *
1971 * @return the reference with the specified ID, or <code>null</code> if
1972 * there is no such reference in the project.
1973 */
1974 public Object getReference(String key) {
1975 Object ret = references.get(key);
1976 if (ret != null) {
1977 return ret;
1978 }
1979 // Check for old id behaviour
1980 ret = resolveIdReference(key, this);
1981 if (ret == null && !key.equals(MagicNames.REFID_PROPERTY_HELPER)) {
1982 Vector p = new Vector();
1983 PropertyHelper.getPropertyHelper(this).parsePropertyString(
1984 key, new Vector(), p);
1985 if (p.size() == 1) {
1986 log("Unresolvable reference " + key
1987 + " might be a misuse of property expansion syntax.",
1988 MSG_WARN);
1989 }
1990 }
1991 return ret;
1992 }
1993
1994 /**
1995 * Return a description of the type of the given element, with
1996 * special handling for instances of tasks and data types.
1997 * <p>
1998 * This is useful for logging purposes.
1999 *
2000 * @param element The element to describe.
2001 * Must not be <code>null</code>.
2002 *
2003 * @return a description of the element type.
2004 *
2005 * @since 1.95, Ant 1.5
2006 */
2007 public String getElementName(Object element) {
2008 return ComponentHelper.getComponentHelper(this).getElementName(element);
2009 }
2010
2011 /**
2012 * Send a "build started" event
2013 * to the build listeners for this project.
2014 */
2015 public void fireBuildStarted() {
2016 BuildEvent event = new BuildEvent(this);
2017 Iterator iter = listeners.iterator();
2018 while (iter.hasNext()) {
2019 BuildListener listener = (BuildListener) iter.next();
2020 listener.buildStarted(event);
2021 }
2022 }
2023
2024 /**
2025 * Send a "build finished" event to the build listeners
2026 * for this project.
2027 * @param exception an exception indicating a reason for a build
2028 * failure. May be <code>null</code>, indicating
2029 * a successful build.
2030 */
2031 public void fireBuildFinished(Throwable exception) {
2032 BuildEvent event = new BuildEvent(this);
2033 event.setException(exception);
2034 Iterator iter = listeners.iterator();
2035 while (iter.hasNext()) {
2036 BuildListener listener = (BuildListener) iter.next();
2037 listener.buildFinished(event);
2038 }
2039 // Inform IH to clear the cache
2040 IntrospectionHelper.clearCache();
2041 }
2042
2043 /**
2044 * Send a "subbuild started" event to the build listeners for
2045 * this project.
2046 *
2047 * @since Ant 1.6.2
2048 */
2049 public void fireSubBuildStarted() {
2050 BuildEvent event = new BuildEvent(this);
2051 Iterator iter = listeners.iterator();
2052 while (iter.hasNext()) {
2053 Object listener = iter.next();
2054 if (listener instanceof SubBuildListener) {
2055 ((SubBuildListener) listener).subBuildStarted(event);
2056 }
2057 }
2058 }
2059
2060 /**
2061 * Send a "subbuild finished" event to the build listeners for
2062 * this project.
2063 * @param exception an exception indicating a reason for a build
2064 * failure. May be <code>null</code>, indicating
2065 * a successful build.
2066 *
2067 * @since Ant 1.6.2
2068 */
2069 public void fireSubBuildFinished(Throwable exception) {
2070 BuildEvent event = new BuildEvent(this);
2071 event.setException(exception);
2072 Iterator iter = listeners.iterator();
2073 while (iter.hasNext()) {
2074 Object listener = iter.next();
2075 if (listener instanceof SubBuildListener) {
2076 ((SubBuildListener) listener).subBuildFinished(event);
2077 }
2078 }
2079 }
2080
2081 /**
2082 * Send a "target started" event to the build listeners
2083 * for this project.
2084 *
2085 * @param target The target which is starting to build.
2086 * Must not be <code>null</code>.
2087 */
2088 protected void fireTargetStarted(Target target) {
2089 BuildEvent event = new BuildEvent(target);
2090 Iterator iter = listeners.iterator();
2091 while (iter.hasNext()) {
2092 BuildListener listener = (BuildListener) iter.next();
2093 listener.targetStarted(event);
2094 }
2095 }
2096
2097 /**
2098 * Send a "target finished" event to the build listeners
2099 * for this project.
2100 *
2101 * @param target The target which has finished building.
2102 * Must not be <code>null</code>.
2103 * @param exception an exception indicating a reason for a build
2104 * failure. May be <code>null</code>, indicating
2105 * a successful build.
2106 */
2107 protected void fireTargetFinished(Target target, Throwable exception) {
2108 BuildEvent event = new BuildEvent(target);
2109 event.setException(exception);
2110 Iterator iter = listeners.iterator();
2111 while (iter.hasNext()) {
2112 BuildListener listener = (BuildListener) iter.next();
2113 listener.targetFinished(event);
2114 }
2115 }
2116
2117 /**
2118 * Send a "task started" event to the build listeners
2119 * for this project.
2120 *
2121 * @param task The target which is starting to execute.
2122 * Must not be <code>null</code>.
2123 */
2124 protected void fireTaskStarted(Task task) {
2125 // register this as the current task on the current thread.
2126 registerThreadTask(Thread.currentThread(), task);
2127 BuildEvent event = new BuildEvent(task);
2128 Iterator iter = listeners.iterator();
2129 while (iter.hasNext()) {
2130 BuildListener listener = (BuildListener) iter.next();
2131 listener.taskStarted(event);
2132 }
2133 }
2134
2135 /**
2136 * Send a "task finished" event to the build listeners for this
2137 * project.
2138 *
2139 * @param task The task which has finished executing.
2140 * Must not be <code>null</code>.
2141 * @param exception an exception indicating a reason for a build
2142 * failure. May be <code>null</code>, indicating
2143 * a successful build.
2144 */
2145 protected void fireTaskFinished(Task task, Throwable exception) {
2146 registerThreadTask(Thread.currentThread(), null);
2147 System.out.flush();
2148 System.err.flush();
2149 BuildEvent event = new BuildEvent(task);
2150 event.setException(exception);
2151 Iterator iter = listeners.iterator();
2152 while (iter.hasNext()) {
2153 BuildListener listener = (BuildListener) iter.next();
2154 listener.taskFinished(event);
2155 }
2156 }
2157
2158 /**
2159 * Send a "message logged" event to the build listeners
2160 * for this project.
2161 *
2162 * @param event The event to send. This should be built up with the
2163 * appropriate task/target/project by the caller, so that
2164 * this method can set the message and priority, then send
2165 * the event. Must not be <code>null</code>.
2166 * @param message The message to send. Should not be <code>null</code>.
2167 * @param priority The priority of the message.
2168 */
2169 private void fireMessageLoggedEvent(BuildEvent event, String message,
2170 int priority) {
2171
2172 if (message.endsWith(StringUtils.LINE_SEP)) {
2173 int endIndex = message.length() - StringUtils.LINE_SEP.length();
2174 event.setMessage(message.substring(0, endIndex), priority);
2175 } else {
2176 event.setMessage(message, priority);
2177 }
2178 synchronized (this) {
2179 if (loggingMessage) {
2180 /*
2181 * One of the Listeners has attempted to access
2182 * System.err or System.out.
2183 *
2184 * We used to throw an exception in this case, but
2185 * sometimes Listeners can't prevent it(like our own
2186 * Log4jListener which invokes getLogger() which in
2187 * turn wants to write to the console).
2188 *
2189 * @see http://marc.theaimsgroup.com/?t=110538624200006&r=1&w=2
2190 *
2191 * We now (Ant 1.7 and 1.6.3) simply swallow the message.
2192 */
2193 return;
2194 }
2195 try {
2196 loggingMessage = true;
2197 Iterator iter = listeners.iterator();
2198 while (iter.hasNext()) {
2199 BuildListener listener = (BuildListener) iter.next();
2200 listener.messageLogged(event);
2201 }
2202 } finally {
2203 loggingMessage = false;
2204 }
2205 }
2206 }
2207
2208 /**
2209 * Send a "message logged" project level event
2210 * to the build listeners for this project.
2211 *
2212 * @param project The project generating the event.
2213 * Should not be <code>null</code>.
2214 * @param message The message to send. Should not be <code>null</code>.
2215 * @param priority The priority of the message.
2216 */
2217 protected void fireMessageLogged(Project project, String message,
2218 int priority) {
2219 fireMessageLogged(project, message, null, priority);
2220 }
2221
2222 /**
2223 * Send a "message logged" project level event
2224 * to the build listeners for this project.
2225 *
2226 * @param project The project generating the event.
2227 * Should not be <code>null</code>.
2228 * @param message The message to send. Should not be <code>null</code>.
2229 * @param throwable The exception that caused this message. May be <code>null</code>.
2230 * @param priority The priority of the message.
2231 * @since 1.7
2232 */
2233 protected void fireMessageLogged(Project project, String message,
2234 Throwable throwable, int priority) {
2235 BuildEvent event = new BuildEvent(project);
2236 event.setException(throwable);
2237 fireMessageLoggedEvent(event, message, priority);
2238 }
2239
2240 /**
2241 * Send a "message logged" target level event
2242 * to the build listeners for this project.
2243 *
2244 * @param target The target generating the event.
2245 * Must not be <code>null</code>.
2246 * @param message The message to send. Should not be <code>null</code>.
2247 * @param priority The priority of the message.
2248 */
2249 protected void fireMessageLogged(Target target, String message,
2250 int priority) {
2251 fireMessageLogged(target, message, null, priority);
2252 }
2253
2254 /**
2255 * Send a "message logged" target level event
2256 * to the build listeners for this project.
2257 *
2258 * @param target The target generating the event.
2259 * Must not be <code>null</code>.
2260 * @param message The message to send. Should not be <code>null</code>.
2261 * @param throwable The exception that caused this message. May be <code>null</code>.
2262 * @param priority The priority of the message.
2263 * @since 1.7
2264 */
2265 protected void fireMessageLogged(Target target, String message,
2266 Throwable throwable, int priority) {
2267 BuildEvent event = new BuildEvent(target);
2268 event.setException(throwable);
2269 fireMessageLoggedEvent(event, message, priority);
2270 }
2271
2272 /**
2273 * Send a "message logged" task level event
2274 * to the build listeners for this project.
2275 *
2276 * @param task The task generating the event.
2277 * Must not be <code>null</code>.
2278 * @param message The message to send. Should not be <code>null</code>.
2279 * @param priority The priority of the message.
2280 */
2281 protected void fireMessageLogged(Task task, String message, int priority) {
2282 fireMessageLogged(task, message, null, priority);
2283 }
2284
2285 /**
2286 * Send a "message logged" task level event
2287 * to the build listeners for this project.
2288 *
2289 * @param task The task generating the event.
2290 * Must not be <code>null</code>.
2291 * @param message The message to send. Should not be <code>null</code>.
2292 * @param throwable The exception that caused this message. May be <code>null</code>.
2293 * @param priority The priority of the message.
2294 * @since 1.7
2295 */
2296 protected void fireMessageLogged(Task task, String message,
2297 Throwable throwable, int priority) {
2298 BuildEvent event = new BuildEvent(task);
2299 event.setException(throwable);
2300 fireMessageLoggedEvent(event, message, priority);
2301 }
2302
2303 /**
2304 * Register a task as the current task for a thread.
2305 * If the task is null, the thread's entry is removed.
2306 *
2307 * @param thread the thread on which the task is registered.
2308 * @param task the task to be registered.
2309 * @since Ant 1.5
2310 */
2311 public synchronized void registerThreadTask(Thread thread, Task task) {
2312 if (task != null) {
2313 threadTasks.put(thread, task);
2314 threadGroupTasks.put(thread.getThreadGroup(), task);
2315 } else {
2316 threadTasks.remove(thread);
2317 threadGroupTasks.remove(thread.getThreadGroup());
2318 }
2319 }
2320
2321 /**
2322 * Get the current task associated with a thread, if any.
2323 *
2324 * @param thread the thread for which the task is required.
2325 * @return the task which is currently registered for the given thread or
2326 * null if no task is registered.
2327 */
2328 public Task getThreadTask(Thread thread) {
2329 Task task = (Task) threadTasks.get(thread);
2330 if (task == null) {
2331 ThreadGroup group = thread.getThreadGroup();
2332 while (task == null && group != null) {
2333 task = (Task) threadGroupTasks.get(group);
2334 group = group.getParent();
2335 }
2336 }
2337 return task;
2338 }
2339
2340
2341 // Should move to a separate public class - and have API to add
2342 // listeners, etc.
2343 private static class AntRefTable extends Hashtable {
2344
2345 AntRefTable() {
2346 super();
2347 }
2348
2349 /** Returns the unmodified original object.
2350 * This method should be called internally to
2351 * get the "real" object.
2352 * The normal get method will do the replacement
2353 * of UnknownElement (this is similar with the JDNI
2354 * refs behavior).
2355 */
2356 private Object getReal(Object key) {
2357 return super.get(key);
2358 }
2359
2360 /** Get method for the reference table.
2361 * It can be used to hook dynamic references and to modify
2362 * some references on the fly--for example for delayed
2363 * evaluation.
2364 *
2365 * It is important to make sure that the processing that is
2366 * done inside is not calling get indirectly.
2367 *
2368 * @param key lookup key.
2369 * @return mapped value.
2370 */
2371 public Object get(Object key) {
2372 //System.out.println("AntRefTable.get " + key);
2373 Object o = getReal(key);
2374 if (o instanceof UnknownElement) {
2375 //