Home » apache-ant-1.7.1-src » org.apache.tools » ant » [javadoc | source]
    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 &quot;error&quot;. */
   68       public static final int MSG_ERR = 0;
   69       /** Message priority of &quot;warning&quot;. */
   70       public static final int MSG_WARN = 1;
   71       /** Message priority of &quot;information&quot;. */
   72       public static final int MSG_INFO = 2;
   73       /** Message priority of &quot;verbose&quot;. */
   74       public static final int MSG_VERBOSE = 3;
   75       /** Message priority of &quot;debug&quot;. */
   76       public static final int MSG_DEBUG = 4;
   77   
   78       /**
   79        * Constant for the &quot;visiting&quot; state, used when
   80        * traversing a DFS of target dependencies.
   81        */
   82       private static final String VISITING = "VISITING";
   83       /**
   84        * Constant for the &quot;visited&quot; 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 &quot;user&quot; 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 &quot;user&quot; 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 &quot;keep-going&quot; 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 &quot;keep-going&quot; 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 &quot;keep-going&quot; 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        * &quot;live&quot; 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 &quot;live&quot; 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 &quot;live&quot; 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 &quot;visiting&quot; state, and
 1792        * pushed onto the &quot;visiting&quot; 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 &quot;visiting&quot;
 1797        * stack.
 1798        * <p>
 1799        * If any child has not already been &quot;visited&quot;, 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        * &quot;visited&quot; 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 &quot;VISITING&quot; and
 1816        *                &quot;VISITED&quot;. 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 &quot;live&quot; 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 &quot;build started&quot; 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 &quot;build finished&quot; 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 &quot;subbuild started&quot; 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 &quot;subbuild finished&quot; 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 &quot;target started&quot; 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 &quot;target finished&quot; 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 &quot;task started&quot; 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 &quot;task finished&quot; 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 &quot;message logged&quot; 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 &quot;message logged&quot; 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 &quot;message logged&quot; 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 &quot;message logged&quot; 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 &quot;message logged&quot; 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 &quot;message logged&quot; 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 &quot;message logged&quot; 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 &quot;real&quot; 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                   //