diff options
Diffstat (limited to 'framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/Project.java')
-rw-r--r-- | framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/Project.java | 2494 |
1 files changed, 2494 insertions, 0 deletions
diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/Project.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/Project.java new file mode 100644 index 00000000..3a0c4b82 --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/Project.java @@ -0,0 +1,2494 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package org.apache.tools.ant; + +import java.io.EOFException; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.Stack; +import java.util.Vector; +import java.util.WeakHashMap; + +import org.apache.tools.ant.helper.DefaultExecutor; +import org.apache.tools.ant.input.DefaultInputHandler; +import org.apache.tools.ant.input.InputHandler; +import org.apache.tools.ant.types.Description; +import org.apache.tools.ant.types.FilterSet; +import org.apache.tools.ant.types.FilterSetCollection; +import org.apache.tools.ant.types.Path; +import org.apache.tools.ant.types.Resource; +import org.apache.tools.ant.types.ResourceFactory; +import org.apache.tools.ant.types.resources.FileResource; +import org.apache.tools.ant.util.CollectionUtils; +import org.apache.tools.ant.util.FileUtils; +import org.apache.tools.ant.util.JavaEnvUtils; +import org.apache.tools.ant.util.StringUtils; +import org.apache.tools.ant.util.VectorSet; + +/** + * Central representation of an Ant project. This class defines an + * Ant project with all of its targets, tasks and various other + * properties. It also provides the mechanism to kick off a build using + * a particular target name. + * <p> + * This class also encapsulates methods which allow files to be referred + * to using abstract path names which are translated to native system + * file paths at runtime. + * + */ +public class Project implements ResourceFactory { + /** Message priority of "error". */ + public static final int MSG_ERR = 0; + /** Message priority of "warning". */ + public static final int MSG_WARN = 1; + /** Message priority of "information". */ + public static final int MSG_INFO = 2; + /** Message priority of "verbose". */ + public static final int MSG_VERBOSE = 3; + /** Message priority of "debug". */ + public static final int MSG_DEBUG = 4; + + /** + * Constant for the "visiting" state, used when + * traversing a DFS of target dependencies. + */ + private static final String VISITING = "VISITING"; + /** + * Constant for the "visited" state, used when + * traversing a DFS of target dependencies. + */ + private static final String VISITED = "VISITED"; + + /** + * Version constant for Java 1.0 . + * + * @deprecated since 1.5.x. + * Use {@link JavaEnvUtils#JAVA_1_0} instead. + */ + @Deprecated + public static final String JAVA_1_0 = JavaEnvUtils.JAVA_1_0; + /** + * Version constant for Java 1.1 . + * + * @deprecated since 1.5.x. + * Use {@link JavaEnvUtils#JAVA_1_1} instead. + */ + @Deprecated + public static final String JAVA_1_1 = JavaEnvUtils.JAVA_1_1; + /** + * Version constant for Java 1.2 . + * + * @deprecated since 1.5.x. + * Use {@link JavaEnvUtils#JAVA_1_2} instead. + */ + @Deprecated + public static final String JAVA_1_2 = JavaEnvUtils.JAVA_1_2; + /** + * Version constant for Java 1.3 . + * + * @deprecated since 1.5.x. + * Use {@link JavaEnvUtils#JAVA_1_3} instead. + */ + @Deprecated + public static final String JAVA_1_3 = JavaEnvUtils.JAVA_1_3; + /** + * Version constant for Java 1.4 . + * + * @deprecated since 1.5.x. + * Use {@link JavaEnvUtils#JAVA_1_4} instead. + */ + @Deprecated + public static final String JAVA_1_4 = JavaEnvUtils.JAVA_1_4; + + /** Default filter start token. */ + public static final String TOKEN_START = FilterSet.DEFAULT_TOKEN_START; + /** Default filter end token. */ + public static final String TOKEN_END = FilterSet.DEFAULT_TOKEN_END; + + /** Instance of a utility class to use for file operations. */ + private static final FileUtils FILE_UTILS = FileUtils.getFileUtils(); + + /** Name of this project. */ + private String name; + /** Description for this project (if any). */ + private String description; + + + /** Map of references within the project (paths etc) (String to Object). */ + private final Hashtable<String, Object> references = new AntRefTable(); + + /** Map of id references - used for indicating broken build files */ + private final HashMap<String, Object> idReferences = new HashMap<String, Object>(); + + /** Name of the project's default target. */ + private String defaultTarget; + + /** Map from target names to targets (String to Target). */ + private final Hashtable<String, Target> targets = new Hashtable<String, Target>(); + + /** Set of global filters. */ + private final FilterSet globalFilterSet = new FilterSet(); + { + // Initialize the globalFileSet's project + globalFilterSet.setProject(this); + } + + /** + * Wrapper around globalFilterSet. This collection only ever + * contains one FilterSet, but the wrapper is needed in order to + * make it easier to use the FileUtils interface. + */ + private final FilterSetCollection globalFilters + = new FilterSetCollection(globalFilterSet); + + /** Project base directory. */ + private File baseDir; + + /** lock object used when adding/removing listeners */ + private final Object listenersLock = new Object(); + + /** List of listeners to notify of build events. */ + private volatile BuildListener[] listeners = new BuildListener[0]; + + /** for each thread, record whether it is currently executing + messageLogged */ + private final ThreadLocal<Boolean> isLoggingMessage = new ThreadLocal<Boolean>() { + @Override + protected Boolean initialValue() { + return Boolean.FALSE; + } + }; + + /** + * The Ant core classloader--may be <code>null</code> if using + * parent classloader. + */ + private ClassLoader coreLoader = null; + + /** Records the latest task to be executed on a thread. */ + private final Map<Thread,Task> threadTasks = + Collections.synchronizedMap(new WeakHashMap<Thread, Task>()); + + /** Records the latest task to be executed on a thread group. */ + private final Map<ThreadGroup,Task> threadGroupTasks + = Collections.synchronizedMap(new WeakHashMap<ThreadGroup,Task>()); + + /** + * Called to handle any input requests. + */ + private InputHandler inputHandler = null; + + /** + * The default input stream used to read any input. + */ + private InputStream defaultInputStream = null; + + /** + * Keep going flag. + */ + private boolean keepGoingMode = false; + + /** + * Set the input handler. + * + * @param handler the InputHandler instance to use for gathering input. + */ + public void setInputHandler(final InputHandler handler) { + inputHandler = handler; + } + + /** + * Set the default System input stream. Normally this stream is set to + * System.in. This inputStream is used when no task input redirection is + * being performed. + * + * @param defaultInputStream the default input stream to use when input + * is requested. + * @since Ant 1.6 + */ + public void setDefaultInputStream(final InputStream defaultInputStream) { + this.defaultInputStream = defaultInputStream; + } + + /** + * Get this project's input stream. + * + * @return the InputStream instance in use by this Project instance to + * read input. + */ + public InputStream getDefaultInputStream() { + return defaultInputStream; + } + + /** + * Retrieve the current input handler. + * + * @return the InputHandler instance currently in place for the project + * instance. + */ + public InputHandler getInputHandler() { + return inputHandler; + } + + /** + * Create a new Ant project. + */ + public Project() { + inputHandler = new DefaultInputHandler(); + } + + /** + * Create and initialize a subproject. By default the subproject will be of + * the same type as its parent. If a no-arg constructor is unavailable, the + * <code>Project</code> class will be used. + * @return a Project instance configured as a subproject of this Project. + * @since Ant 1.7 + */ + public Project createSubProject() { + Project subProject = null; + try { + subProject = (getClass().newInstance()); + } catch (final Exception e) { + subProject = new Project(); + } + initSubProject(subProject); + return subProject; + } + + /** + * Initialize a subproject. + * @param subProject the subproject to initialize. + */ + public void initSubProject(final Project subProject) { + ComponentHelper.getComponentHelper(subProject) + .initSubProject(ComponentHelper.getComponentHelper(this)); + subProject.setDefaultInputStream(getDefaultInputStream()); + subProject.setKeepGoingMode(this.isKeepGoingMode()); + subProject.setExecutor(getExecutor().getSubProjectExecutor()); + } + + /** + * Initialise the project. + * + * This involves setting the default task definitions and loading the + * system properties. + * + * @exception BuildException if the default task list cannot be loaded. + */ + public void init() throws BuildException { + initProperties(); + + ComponentHelper.getComponentHelper(this).initDefaultDefinitions(); + } + + /** + * Initializes the properties. + * @exception BuildException if an vital property could not be set. + * @since Ant 1.7 + */ + public void initProperties() throws BuildException { + setJavaVersionProperty(); + setSystemProperties(); + setPropertyInternal(MagicNames.ANT_VERSION, Main.getAntVersion()); + setAntLib(); + } + + /** + * Set a property to the location of ant.jar. + * Use the locator to find the location of the Project.class, and + * if this is not null, set the property {@link MagicNames#ANT_LIB} + * to the result + */ + private void setAntLib() { + final File antlib = org.apache.tools.ant.launch.Locator.getClassSource( + Project.class); + if (antlib != null) { + setPropertyInternal(MagicNames.ANT_LIB, antlib.getAbsolutePath()); + } + } + /** + * Factory method to create a class loader for loading classes from + * a given path. + * + * @param path the path from which classes are to be loaded. + * + * @return an appropriate classloader. + */ + public AntClassLoader createClassLoader(final Path path) { + return AntClassLoader + .newAntClassLoader(getClass().getClassLoader(), this, path, true); + } + + /** + * Factory method to create a class loader for loading classes from + * a given path. + * + * @param parent the parent classloader for the new loader. + * @param path the path from which classes are to be loaded. + * + * @return an appropriate classloader. + */ + public AntClassLoader createClassLoader( + final ClassLoader parent, final Path path) { + return AntClassLoader.newAntClassLoader(parent, this, path, true); + } + + /** + * Set the core classloader for the project. If a <code>null</code> + * classloader is specified, the parent classloader should be used. + * + * @param coreLoader The classloader to use for the project. + * May be <code>null</code>. + */ + public void setCoreLoader(final ClassLoader coreLoader) { + this.coreLoader = coreLoader; + } + + /** + * Return the core classloader to use for this project. + * This may be <code>null</code>, indicating that + * the parent classloader should be used. + * + * @return the core classloader to use for this project. + * + */ + public ClassLoader getCoreLoader() { + return coreLoader; + } + + /** + * Add a build listener to the list. This listener will + * be notified of build events for this project. + * + * @param listener The listener to add to the list. + * Must not be <code>null</code>. + */ + public void addBuildListener(final BuildListener listener) { + synchronized (listenersLock) { + // If the listeners already has this listener, do nothing + for (int i = 0; i < listeners.length; i++) { + if (listeners[i] == listener) { + return; + } + } + // copy on write semantics + final BuildListener[] newListeners = + new BuildListener[listeners.length + 1]; + System.arraycopy(listeners, 0, newListeners, 0, listeners.length); + newListeners[listeners.length] = listener; + listeners = newListeners; + } + } + + /** + * Remove a build listener from the list. This listener + * will no longer be notified of build events for this project. + * + * @param listener The listener to remove from the list. + * Should not be <code>null</code>. + */ + public void removeBuildListener(final BuildListener listener) { + synchronized (listenersLock) { + // copy on write semantics + for (int i = 0; i < listeners.length; i++) { + if (listeners[i] == listener) { + final BuildListener[] newListeners = + new BuildListener[listeners.length - 1]; + System.arraycopy(listeners, 0, newListeners, 0, i); + System.arraycopy(listeners, i + 1, newListeners, i, + listeners.length - i - 1); + listeners = newListeners; + break; + } + } + } + } + + /** + * Return a copy of the list of build listeners for the project. + * + * @return a list of build listeners for the project + */ + public Vector<BuildListener> getBuildListeners() { + synchronized (listenersLock) { + final Vector<BuildListener> r = new Vector<BuildListener>(listeners.length); + for (int i = 0; i < listeners.length; i++) { + r.add(listeners[i]); + } + return r; + } + } + + /** + * Write a message to the log with the default log level + * of MSG_INFO . + * @param message The text to log. Should not be <code>null</code>. + */ + + public void log(final String message) { + log(message, MSG_INFO); + } + + /** + * Write a project level message to the log with the given log level. + * @param message The text to log. Should not be <code>null</code>. + * @param msgLevel The log priority level to use. + */ + public void log(final String message, final int msgLevel) { + log(message, null, msgLevel); + } + + /** + * Write a project level message to the log with the given log level. + * @param message The text to log. Should not be <code>null</code>. + * @param throwable The exception causing this log, may be <code>null</code>. + * @param msgLevel The log priority level to use. + * @since 1.7 + */ + public void log(final String message, final Throwable throwable, final int msgLevel) { + fireMessageLogged(this, message, throwable, msgLevel); + } + + /** + * Write a task level message to the log with the given log level. + * @param task The task to use in the log. Must not be <code>null</code>. + * @param message The text to log. Should not be <code>null</code>. + * @param msgLevel The log priority level to use. + */ + public void log(final Task task, final String message, final int msgLevel) { + fireMessageLogged(task, message, null, msgLevel); + } + + /** + * Write a task level message to the log with the given log level. + * @param task The task to use in the log. Must not be <code>null</code>. + * @param message The text to log. Should not be <code>null</code>. + * @param throwable The exception causing this log, may be <code>null</code>. + * @param msgLevel The log priority level to use. + * @since 1.7 + */ + public void log(final Task task, final String message, final Throwable throwable, final int msgLevel) { + fireMessageLogged(task, message, throwable, msgLevel); + } + + /** + * Write a target level message to the log with the given log level. + * @param target The target to use in the log. + * Must not be <code>null</code>. + * @param message The text to log. Should not be <code>null</code>. + * @param msgLevel The log priority level to use. + */ + public void log(final Target target, final String message, final int msgLevel) { + log(target, message, null, msgLevel); + } + + /** + * Write a target level message to the log with the given log level. + * @param target The target to use in the log. + * Must not be <code>null</code>. + * @param message The text to log. Should not be <code>null</code>. + * @param throwable The exception causing this log, may be <code>null</code>. + * @param msgLevel The log priority level to use. + * @since 1.7 + */ + public void log(final Target target, final String message, final Throwable throwable, + final int msgLevel) { + fireMessageLogged(target, message, throwable, msgLevel); + } + + /** + * Return the set of global filters. + * + * @return the set of global filters. + */ + public FilterSet getGlobalFilterSet() { + return globalFilterSet; + } + + /** + * Set a property. Any existing property of the same name + * is overwritten, unless it is a user property. + * @param name The name of property to set. + * Must not be <code>null</code>. + * @param value The new value of the property. + * Must not be <code>null</code>. + */ + public void setProperty(final String name, final String value) { + PropertyHelper.getPropertyHelper(this).setProperty(name, value, true); + } + + /** + * Set a property if no value currently exists. If the property + * exists already, a message is logged and the method returns with + * no other effect. + * + * @param name The name of property to set. + * Must not be <code>null</code>. + * @param value The new value of the property. + * Must not be <code>null</code>. + * @since 1.5 + */ + public void setNewProperty(final String name, final String value) { + PropertyHelper.getPropertyHelper(this).setNewProperty(name, value); + } + + /** + * Set a user property, which cannot be overwritten by + * set/unset property calls. Any previous value is overwritten. + * @param name The name of property to set. + * Must not be <code>null</code>. + * @param value The new value of the property. + * Must not be <code>null</code>. + * @see #setProperty(String,String) + */ + public void setUserProperty(final String name, final String value) { + PropertyHelper.getPropertyHelper(this).setUserProperty(name, value); + } + + /** + * Set a user property, which cannot be overwritten by set/unset + * property calls. Any previous value is overwritten. Also marks + * these properties as properties that have not come from the + * command line. + * + * @param name The name of property to set. + * Must not be <code>null</code>. + * @param value The new value of the property. + * Must not be <code>null</code>. + * @see #setProperty(String,String) + */ + public void setInheritedProperty(final String name, final String value) { + PropertyHelper.getPropertyHelper(this).setInheritedProperty(name, value); + } + + /** + * Set a property unless it is already defined as a user property + * (in which case the method returns silently). + * + * @param name The name of the property. + * Must not be <code>null</code>. + * @param value The property value. Must not be <code>null</code>. + */ + private void setPropertyInternal(final String name, final String value) { + PropertyHelper.getPropertyHelper(this).setProperty(name, value, false); + } + + /** + * Return the value of a property, if it is set. + * + * @param propertyName The name of the property. + * May be <code>null</code>, in which case + * the return value is also <code>null</code>. + * @return the property value, or <code>null</code> for no match + * or if a <code>null</code> name is provided. + */ + public String getProperty(final String propertyName) { + final Object value = PropertyHelper.getPropertyHelper(this).getProperty(propertyName); + return value == null ? null : String.valueOf(value); + } + + /** + * Replace ${} style constructions in the given value with the + * string value of the corresponding data types. + * + * @param value The string to be scanned for property references. + * May be <code>null</code>. + * + * @return the given string with embedded property names replaced + * by values, or <code>null</code> if the given string is + * <code>null</code>. + * + * @exception BuildException if the given value has an unclosed + * property name, e.g. <code>${xxx</code>. + */ + public String replaceProperties(final String value) throws BuildException { + return PropertyHelper.getPropertyHelper(this).replaceProperties(null, value, null); + } + + /** + * Return the value of a user property, if it is set. + * + * @param propertyName The name of the property. + * May be <code>null</code>, in which case + * the return value is also <code>null</code>. + * @return the property value, or <code>null</code> for no match + * or if a <code>null</code> name is provided. + */ + public String getUserProperty(final String propertyName) { + return (String) PropertyHelper.getPropertyHelper(this).getUserProperty(propertyName); + } + + /** + * Return a copy of the properties table. + * @return a hashtable containing all properties + * (including user properties). + */ + public Hashtable<String, Object> getProperties() { + return PropertyHelper.getPropertyHelper(this).getProperties(); + } + + /** + * Return a copy of the user property hashtable. + * @return a hashtable containing just the user properties. + */ + public Hashtable<String, Object> getUserProperties() { + return PropertyHelper.getPropertyHelper(this).getUserProperties(); + } + + /** + * Return a copy of the inherited property hashtable. + * @return a hashtable containing just the inherited properties. + * @since Ant 1.8.0 + */ + public Hashtable<String, Object> getInheritedProperties() { + return PropertyHelper.getPropertyHelper(this).getInheritedProperties(); + } + + /** + * Copy all user properties that have been set on the command + * line or a GUI tool from this instance to the Project instance + * given as the argument. + * + * <p>To copy all "user" properties, you will also have to call + * {@link #copyInheritedProperties copyInheritedProperties}.</p> + * + * @param other the project to copy the properties to. Must not be null. + * + * @since Ant 1.5 + */ + public void copyUserProperties(final Project other) { + PropertyHelper.getPropertyHelper(this).copyUserProperties(other); + } + + /** + * Copy all user properties that have not been set on the + * command line or a GUI tool from this instance to the Project + * instance given as the argument. + * + * <p>To copy all "user" properties, you will also have to call + * {@link #copyUserProperties copyUserProperties}.</p> + * + * @param other the project to copy the properties to. Must not be null. + * + * @since Ant 1.5 + */ + public void copyInheritedProperties(final Project other) { + PropertyHelper.getPropertyHelper(this).copyInheritedProperties(other); + } + + /** + * Set the default target of the project. + * + * @param defaultTarget The name of the default target for this project. + * May be <code>null</code>, indicating that there is + * no default target. + * + * @deprecated since 1.5.x. + * Use setDefault. + * @see #setDefault(String) + */ + @Deprecated + public void setDefaultTarget(final String defaultTarget) { + setDefault(defaultTarget); + } + + /** + * Return the name of the default target of the project. + * @return name of the default target or + * <code>null</code> if no default has been set. + */ + public String getDefaultTarget() { + return defaultTarget; + } + + /** + * Set the default target of the project. + * + * @param defaultTarget The name of the default target for this project. + * May be <code>null</code>, indicating that there is + * no default target. + */ + public void setDefault(final String defaultTarget) { + if (defaultTarget != null) { + setUserProperty(MagicNames.PROJECT_DEFAULT_TARGET, defaultTarget); + } + this.defaultTarget = defaultTarget; + } + + /** + * Set the name of the project, also setting the user + * property <code>ant.project.name</code>. + * + * @param name The name of the project. + * Must not be <code>null</code>. + */ + public void setName(final String name) { + setUserProperty(MagicNames.PROJECT_NAME, name); + this.name = name; + } + + /** + * Return the project name, if one has been set. + * + * @return the project name, or <code>null</code> if it hasn't been set. + */ + public String getName() { + return name; + } + + /** + * Set the project description. + * + * @param description The description of the project. + * May be <code>null</code>. + */ + public void setDescription(final String description) { + this.description = description; + } + + /** + * Return the project description, if one has been set. + * + * @return the project description, or <code>null</code> if it hasn't + * been set. + */ + public String getDescription() { + if (description == null) { + description = Description.getDescription(this); + } + return description; + } + + /** + * Add a filter to the set of global filters. + * + * @param token The token to filter. + * Must not be <code>null</code>. + * @param value The replacement value. + * Must not be <code>null</code>. + * @deprecated since 1.4.x. + * Use getGlobalFilterSet().addFilter(token,value) + * + * @see #getGlobalFilterSet() + * @see FilterSet#addFilter(String,String) + */ + @Deprecated + public void addFilter(final String token, final String value) { + if (token == null) { + return; + } + globalFilterSet.addFilter(new FilterSet.Filter(token, value)); + } + + /** + * Return a hashtable of global filters, mapping tokens to values. + * + * @return a hashtable of global filters, mapping tokens to values + * (String to String). + * + * @deprecated since 1.4.x + * Use getGlobalFilterSet().getFilterHash(). + * + * @see #getGlobalFilterSet() + * @see FilterSet#getFilterHash() + */ + @Deprecated + public Hashtable<String, String> getFilters() { + // we need to build the hashtable dynamically + return globalFilterSet.getFilterHash(); + } + + /** + * Set the base directory for the project, checking that + * the given filename exists and is a directory. + * + * @param baseD The project base directory. + * Must not be <code>null</code>. + * + * @exception BuildException if the directory if invalid. + */ + public void setBasedir(final String baseD) throws BuildException { + setBaseDir(new File(baseD)); + } + + /** + * Set the base directory for the project, checking that + * the given file exists and is a directory. + * + * @param baseDir The project base directory. + * Must not be <code>null</code>. + * @exception BuildException if the specified file doesn't exist or + * isn't a directory. + */ + public void setBaseDir(File baseDir) throws BuildException { + baseDir = FILE_UTILS.normalize(baseDir.getAbsolutePath()); + if (!baseDir.exists()) { + throw new BuildException("Basedir " + baseDir.getAbsolutePath() + + " does not exist"); + } + if (!baseDir.isDirectory()) { + throw new BuildException("Basedir " + baseDir.getAbsolutePath() + + " is not a directory"); + } + this.baseDir = baseDir; + setPropertyInternal(MagicNames.PROJECT_BASEDIR, this.baseDir.getPath()); + final String msg = "Project base dir set to: " + this.baseDir; + log(msg, MSG_VERBOSE); + } + + /** + * Return the base directory of the project as a file object. + * + * @return the project base directory, or <code>null</code> if the + * base directory has not been successfully set to a valid value. + */ + public File getBaseDir() { + if (baseDir == null) { + try { + setBasedir("."); + } catch (final BuildException ex) { + ex.printStackTrace(); + } + } + return baseDir; + } + + /** + * Set "keep-going" mode. In this mode Ant will try to execute + * as many targets as possible. All targets that do not depend + * on failed target(s) will be executed. If the keepGoing settor/getter + * methods are used in conjunction with the <code>ant.executor.class</code> + * property, they will have no effect. + * @param keepGoingMode "keep-going" mode + * @since Ant 1.6 + */ + public void setKeepGoingMode(final boolean keepGoingMode) { + this.keepGoingMode = keepGoingMode; + } + + /** + * Return the keep-going mode. If the keepGoing settor/getter + * methods are used in conjunction with the <code>ant.executor.class</code> + * property, they will have no effect. + * @return "keep-going" mode + * @since Ant 1.6 + */ + public boolean isKeepGoingMode() { + return this.keepGoingMode; + } + + /** + * Return the version of Java this class is running under. + * @return the version of Java as a String, e.g. "1.1" . + * @see org.apache.tools.ant.util.JavaEnvUtils#getJavaVersion + * @deprecated since 1.5.x. + * Use org.apache.tools.ant.util.JavaEnvUtils instead. + */ + @Deprecated + public static String getJavaVersion() { + return JavaEnvUtils.getJavaVersion(); + } + + /** + * Set the <code>ant.java.version</code> property and tests for + * unsupported JVM versions. If the version is supported, + * verbose log messages are generated to record the Java version + * and operating system name. + * + * @exception BuildException if this Java version is not supported. + * + * @see org.apache.tools.ant.util.JavaEnvUtils#getJavaVersion + */ + public void setJavaVersionProperty() throws BuildException { + final String javaVersion = JavaEnvUtils.getJavaVersion(); + setPropertyInternal(MagicNames.ANT_JAVA_VERSION, javaVersion); + + // sanity check + if (!JavaEnvUtils.isAtLeastJavaVersion(JavaEnvUtils.JAVA_1_5)) { + throw new BuildException("Ant cannot work on Java prior to 1.5"); + } + log("Detected Java version: " + javaVersion + " in: " + + System.getProperty("java.home"), MSG_VERBOSE); + + log("Detected OS: " + System.getProperty("os.name"), MSG_VERBOSE); + } + + /** + * Add all system properties which aren't already defined as + * user properties to the project properties. + */ + public void setSystemProperties() { + final Properties systemP = System.getProperties(); + final Enumeration<?> e = systemP.propertyNames(); + while (e.hasMoreElements()) { + final String propertyName = (String) e.nextElement(); + final String value = systemP.getProperty(propertyName); + if (value != null) { + this.setPropertyInternal(propertyName, value); + } + } + } + + /** + * Add a new task definition to the project. + * Attempting to override an existing definition with an + * equivalent one (i.e. with the same classname) results in + * a verbose log message. Attempting to override an existing definition + * with a different one results in a warning log message and + * invalidates any tasks which have already been created with the + * old definition. + * + * @param taskName The name of the task to add. + * Must not be <code>null</code>. + * @param taskClass The full name of the class implementing the task. + * Must not be <code>null</code>. + * + * @exception BuildException if the class is unsuitable for being an Ant + * task. An error level message is logged before + * this exception is thrown. + * + * @see #checkTaskClass(Class) + */ + public void addTaskDefinition(final String taskName, final Class<?> taskClass) + throws BuildException { + ComponentHelper.getComponentHelper(this).addTaskDefinition(taskName, + taskClass); + } + + /** + * Check whether or not a class is suitable for serving as Ant task. + * Ant task implementation classes must be public, concrete, and have + * a no-arg constructor. + * + * @param taskClass The class to be checked. + * Must not be <code>null</code>. + * + * @exception BuildException if the class is unsuitable for being an Ant + * task. An error level message is logged before + * this exception is thrown. + */ + public void checkTaskClass(final Class<?> taskClass) throws BuildException { + ComponentHelper.getComponentHelper(this).checkTaskClass(taskClass); + + if (!Modifier.isPublic(taskClass.getModifiers())) { + final String message = taskClass + " is not public"; + log(message, Project.MSG_ERR); + throw new BuildException(message); + } + if (Modifier.isAbstract(taskClass.getModifiers())) { + final String message = taskClass + " is abstract"; + log(message, Project.MSG_ERR); + throw new BuildException(message); + } + try { + taskClass.getConstructor(); + // don't have to check for public, since + // getConstructor finds public constructors only. + } catch (final NoSuchMethodException e) { + final String message = "No public no-arg constructor in " + + taskClass; + log(message, Project.MSG_ERR); + throw new BuildException(message); + } catch (final LinkageError e) { + final String message = "Could not load " + taskClass + ": " + e; + log(message, Project.MSG_ERR); + throw new BuildException(message, e); + } + if (!Task.class.isAssignableFrom(taskClass)) { + TaskAdapter.checkTaskClass(taskClass, this); + } + } + + /** + * Return the current task definition hashtable. The returned hashtable is + * "live" and so should not be modified. + * + * @return a map of from task name to implementing class + * (String to Class). + */ + public Hashtable<String, Class<?>> getTaskDefinitions() { + return ComponentHelper.getComponentHelper(this).getTaskDefinitions(); + } + + /** + * Return the current task definition map. The returned map is a + * copy of the "live" definitions. + * + * @return a map of from task name to implementing class + * (String to Class). + * + * @since Ant 1.8.1 + */ + public Map<String, Class<?>> getCopyOfTaskDefinitions() { + return new HashMap<String, Class<?>>(getTaskDefinitions()); + } + + /** + * Add a new datatype definition. + * Attempting to override an existing definition with an + * equivalent one (i.e. with the same classname) results in + * a verbose log message. Attempting to override an existing definition + * with a different one results in a warning log message, but the + * definition is changed. + * + * @param typeName The name of the datatype. + * Must not be <code>null</code>. + * @param typeClass The full name of the class implementing the datatype. + * Must not be <code>null</code>. + */ + public void addDataTypeDefinition(final String typeName, final Class<?> typeClass) { + ComponentHelper.getComponentHelper(this).addDataTypeDefinition(typeName, + typeClass); + } + + /** + * Return the current datatype definition hashtable. The returned + * hashtable is "live" and so should not be modified. + * + * @return a map of from datatype name to implementing class + * (String to Class). + */ + public Hashtable<String, Class<?>> getDataTypeDefinitions() { + return ComponentHelper.getComponentHelper(this).getDataTypeDefinitions(); + } + + /** + * Return the current datatype definition map. The returned + * map is a copy pf the "live" definitions. + * + * @return a map of from datatype name to implementing class + * (String to Class). + * + * @since Ant 1.8.1 + */ + public Map<String, Class<?>> getCopyOfDataTypeDefinitions() { + return new HashMap<String, Class<?>>(getDataTypeDefinitions()); + } + + /** + * Add a <em>new</em> target to the project. + * + * @param target The target to be added to the project. + * Must not be <code>null</code>. + * + * @exception BuildException if the target already exists in the project + * + * @see Project#addOrReplaceTarget(Target) + */ + public void addTarget(final Target target) throws BuildException { + addTarget(target.getName(), target); + } + + /** + * Add a <em>new</em> target to the project. + * + * @param targetName The name to use for the target. + * Must not be <code>null</code>. + * @param target The target to be added to the project. + * Must not be <code>null</code>. + * + * @exception BuildException if the target already exists in the project. + * + * @see Project#addOrReplaceTarget(String, Target) + */ + public void addTarget(final String targetName, final Target target) + throws BuildException { + if (targets.get(targetName) != null) { + throw new BuildException("Duplicate target: `" + targetName + "'"); + } + addOrReplaceTarget(targetName, target); + } + + /** + * Add a target to the project, or replaces one with the same + * name. + * + * @param target The target to be added or replaced in the project. + * Must not be <code>null</code>. + */ + public void addOrReplaceTarget(final Target target) { + addOrReplaceTarget(target.getName(), target); + } + + /** + * Add a target to the project, or replaces one with the same + * name. + * + * @param targetName The name to use for the target. + * Must not be <code>null</code>. + * @param target The target to be added or replaced in the project. + * Must not be <code>null</code>. + */ + public void addOrReplaceTarget(final String targetName, final Target target) { + final String msg = " +Target: " + targetName; + log(msg, MSG_DEBUG); + target.setProject(this); + targets.put(targetName, target); + } + + /** + * Return the hashtable of targets. The returned hashtable + * is "live" and so should not be modified. + * @return a map from name to target (String to Target). + */ + public Hashtable<String, Target> getTargets() { + return targets; + } + + /** + * Return the map of targets. The returned map + * is a copy of the "live" targets. + * @return a map from name to target (String to Target). + * @since Ant 1.8.1 + */ + public Map<String, Target> getCopyOfTargets() { + return new HashMap<String, Target>(targets); + } + + /** + * Create a new instance of a task, adding it to a list of + * created tasks for later invalidation. This causes all tasks + * to be remembered until the containing project is removed + * @param taskType The name of the task to create an instance of. + * Must not be <code>null</code>. + * + * @return an instance of the specified task, or <code>null</code> if + * the task name is not recognised. + * + * @exception BuildException if the task name is recognised but task + * creation fails. + */ + public Task createTask(final String taskType) throws BuildException { + return ComponentHelper.getComponentHelper(this).createTask(taskType); + } + + /** + * Create a new instance of a data type. + * + * @param typeName The name of the data type to create an instance of. + * Must not be <code>null</code>. + * + * @return an instance of the specified data type, or <code>null</code> if + * the data type name is not recognised. + * + * @exception BuildException if the data type name is recognised but + * instance creation fails. + */ + public Object createDataType(final String typeName) throws BuildException { + return ComponentHelper.getComponentHelper(this).createDataType(typeName); + } + + /** + * Set the Executor instance for this Project. + * @param e the Executor to use. + */ + public void setExecutor(final Executor e) { + addReference(MagicNames.ANT_EXECUTOR_REFERENCE, e); + } + + /** + * Get this Project's Executor (setting it if necessary). + * @return an Executor instance. + */ + public Executor getExecutor() { + Object o = getReference(MagicNames.ANT_EXECUTOR_REFERENCE); + if (o == null) { + String classname = getProperty(MagicNames.ANT_EXECUTOR_CLASSNAME); + if (classname == null) { + classname = DefaultExecutor.class.getName(); + } + log("Attempting to create object of type " + classname, MSG_DEBUG); + try { + o = Class.forName(classname, true, coreLoader).newInstance(); + } catch (final ClassNotFoundException seaEnEfEx) { + //try the current classloader + try { + o = Class.forName(classname).newInstance(); + } catch (final Exception ex) { + log(ex.toString(), MSG_ERR); + } + } catch (final Exception ex) { + log(ex.toString(), MSG_ERR); + } + if (o == null) { + throw new BuildException( + "Unable to obtain a Target Executor instance."); + } + setExecutor((Executor) o); + } + return (Executor) o; + } + + /** + * Execute the specified sequence of targets, and the targets + * they depend on. + * + * @param names A vector of target name strings to execute. + * Must not be <code>null</code>. + * + * @exception BuildException if the build failed. + */ + public void executeTargets(final Vector<String> names) throws BuildException { + setUserProperty(MagicNames.PROJECT_INVOKED_TARGETS, + CollectionUtils.flattenToString(names)); + getExecutor().executeTargets(this, names.toArray(new String[names.size()])); + } + + /** + * Demultiplex output so that each task receives the appropriate + * messages. If the current thread is not currently executing a task, + * the message is logged directly. + * + * @param output Message to handle. Should not be <code>null</code>. + * @param isWarning Whether the text represents an warning (<code>true</code>) + * or information (<code>false</code>). + */ + public void demuxOutput(final String output, final boolean isWarning) { + final Task task = getThreadTask(Thread.currentThread()); + if (task == null) { + log(output, isWarning ? MSG_WARN : MSG_INFO); + } else { + if (isWarning) { + task.handleErrorOutput(output); + } else { + task.handleOutput(output); + } + } + } + + /** + * Read data from the default input stream. If no default has been + * specified, System.in is used. + * + * @param buffer the buffer into which data is to be read. + * @param offset the offset into the buffer at which data is stored. + * @param length the amount of data to read. + * + * @return the number of bytes read. + * + * @exception IOException if the data cannot be read. + * @since Ant 1.6 + */ + public int defaultInput(final byte[] buffer, final int offset, final int length) + throws IOException { + if (defaultInputStream != null) { + System.out.flush(); + return defaultInputStream.read(buffer, offset, length); + } else { + throw new EOFException("No input provided for project"); + } + } + + /** + * Demux an input request to the correct task. + * + * @param buffer the buffer into which data is to be read. + * @param offset the offset into the buffer at which data is stored. + * @param length the amount of data to read. + * + * @return the number of bytes read. + * + * @exception IOException if the data cannot be read. + * @since Ant 1.6 + */ + public int demuxInput(final byte[] buffer, final int offset, final int length) + throws IOException { + final Task task = getThreadTask(Thread.currentThread()); + if (task == null) { + return defaultInput(buffer, offset, length); + } else { + return task.handleInput(buffer, offset, length); + } + } + + /** + * Demultiplex flush operations so that each task receives the appropriate + * messages. If the current thread is not currently executing a task, + * the message is logged directly. + * + * @since Ant 1.5.2 + * + * @param output Message to handle. Should not be <code>null</code>. + * @param isError Whether the text represents an error (<code>true</code>) + * or information (<code>false</code>). + */ + public void demuxFlush(final String output, final boolean isError) { + final Task task = getThreadTask(Thread.currentThread()); + if (task == null) { + fireMessageLogged(this, output, isError ? MSG_ERR : MSG_INFO); + } else { + if (isError) { + task.handleErrorFlush(output); + } else { + task.handleFlush(output); + } + } + } + + /** + * Execute the specified target and any targets it depends on. + * + * @param targetName The name of the target to execute. + * Must not be <code>null</code>. + * + * @exception BuildException if the build failed. + */ + public void executeTarget(final String targetName) throws BuildException { + + // sanity check ourselves, if we've been asked to build nothing + // then we should complain + + if (targetName == null) { + final String msg = "No target specified"; + throw new BuildException(msg); + } + + // Sort and run the dependency tree. + // Sorting checks if all the targets (and dependencies) + // exist, and if there is any cycle in the dependency + // graph. + executeSortedTargets(topoSort(targetName, targets, false)); + } + + /** + * Execute a <code>Vector</code> of sorted targets. + * @param sortedTargets the aforementioned <code>Vector</code>. + * @throws BuildException on error. + */ + public void executeSortedTargets(final Vector<Target> sortedTargets) + throws BuildException { + final Set<String> succeededTargets = new HashSet<String>(); + BuildException buildException = null; // first build exception + for (final Target curtarget : sortedTargets) { + boolean canExecute = true; + for (final Enumeration<String> depIter = curtarget.getDependencies(); + depIter.hasMoreElements();) { + final String dependencyName = depIter.nextElement(); + if (!succeededTargets.contains(dependencyName)) { + canExecute = false; + log(curtarget, + "Cannot execute '" + curtarget.getName() + "' - '" + + dependencyName + "' failed or was not executed.", + MSG_ERR); + break; + } + } + if (canExecute) { + Throwable thrownException = null; + try { + curtarget.performTasks(); + succeededTargets.add(curtarget.getName()); + } catch (final RuntimeException ex) { + if (!(keepGoingMode)) { + throw ex; // throw further + } + thrownException = ex; + } catch (final Throwable ex) { + if (!(keepGoingMode)) { + throw new BuildException(ex); + } + thrownException = ex; + } + if (thrownException != null) { + if (thrownException instanceof BuildException) { + log(curtarget, + "Target '" + curtarget.getName() + + "' failed with message '" + + thrownException.getMessage() + "'.", MSG_ERR); + // only the first build exception is reported + if (buildException == null) { + buildException = (BuildException) thrownException; + } + } else { + log(curtarget, + "Target '" + curtarget.getName() + + "' failed with message '" + + thrownException.getMessage() + "'.", MSG_ERR); + thrownException.printStackTrace(System.err); + if (buildException == null) { + buildException = + new BuildException(thrownException); + } + } + } + } + } + if (buildException != null) { + throw buildException; + } + } + + /** + * Return the canonical form of a filename. + * <p> + * If the specified file name is relative it is resolved + * with respect to the given root directory. + * + * @param fileName The name of the file to resolve. + * Must not be <code>null</code>. + * + * @param rootDir The directory respective to which relative file names + * are resolved. May be <code>null</code>, in which case + * the current directory is used. + * + * @return the resolved File. + * + * @deprecated since 1.4.x + */ + @Deprecated + public File resolveFile(final String fileName, final File rootDir) { + return FILE_UTILS.resolveFile(rootDir, fileName); + } + + /** + * Return the canonical form of a filename. + * <p> + * If the specified file name is relative it is resolved + * with respect to the project's base directory. + * + * @param fileName The name of the file to resolve. + * Must not be <code>null</code>. + * + * @return the resolved File. + * + */ + public File resolveFile(final String fileName) { + return FILE_UTILS.resolveFile(baseDir, fileName); + } + + /** + * Translate a path into its native (platform specific) format. + * <p> + * This method uses PathTokenizer to separate the input path + * into its components. This handles DOS style paths in a relatively + * sensible way. The file separators are then converted to their platform + * specific versions. + * + * @param toProcess The path to be translated. + * May be <code>null</code>. + * + * @return the native version of the specified path or + * an empty string if the path is <code>null</code> or empty. + * + * @deprecated since 1.7 + * Use FileUtils.translatePath instead. + * + * @see PathTokenizer + */ + @Deprecated + public static String translatePath(final String toProcess) { + return FileUtils.translatePath(toProcess); + } + + /** + * Convenience method to copy a file from a source to a destination. + * No filtering is performed. + * + * @param sourceFile Name of file to copy from. + * Must not be <code>null</code>. + * @param destFile Name of file to copy to. + * Must not be <code>null</code>. + * + * @exception IOException if the copying fails. + * + * @deprecated since 1.4.x + */ + @Deprecated + public void copyFile(final String sourceFile, final String destFile) + throws IOException { + FILE_UTILS.copyFile(sourceFile, destFile); + } + + /** + * Convenience method to copy a file from a source to a destination + * specifying if token filtering should be used. + * + * @param sourceFile Name of file to copy from. + * Must not be <code>null</code>. + * @param destFile Name of file to copy to. + * Must not be <code>null</code>. + * @param filtering Whether or not token filtering should be used during + * the copy. + * + * @exception IOException if the copying fails. + * + * @deprecated since 1.4.x + */ + @Deprecated + public void copyFile(final String sourceFile, final String destFile, final boolean filtering) + throws IOException { + FILE_UTILS.copyFile(sourceFile, destFile, + filtering ? globalFilters : null); + } + + /** + * Convenience method to copy a file from a source to a + * destination specifying if token filtering should be used and if + * source files may overwrite newer destination files. + * + * @param sourceFile Name of file to copy from. + * Must not be <code>null</code>. + * @param destFile Name of file to copy to. + * Must not be <code>null</code>. + * @param filtering Whether or not token filtering should be used during + * the copy. + * @param overwrite Whether or not the destination file should be + * overwritten if it already exists. + * + * @exception IOException if the copying fails. + * + * @deprecated since 1.4.x + */ + @Deprecated + public void copyFile(final String sourceFile, final String destFile, final boolean filtering, + final boolean overwrite) throws IOException { + FILE_UTILS.copyFile(sourceFile, destFile, + filtering ? globalFilters : null, overwrite); + } + + /** + * Convenience method to copy a file from a source to a + * destination specifying if token filtering should be used, if + * source files may overwrite newer destination files, and if the + * last modified time of the resulting file should be set to + * that of the source file. + * + * @param sourceFile Name of file to copy from. + * Must not be <code>null</code>. + * @param destFile Name of file to copy to. + * Must not be <code>null</code>. + * @param filtering Whether or not token filtering should be used during + * the copy. + * @param overwrite Whether or not the destination file should be + * overwritten if it already exists. + * @param preserveLastModified Whether or not the last modified time of + * the resulting file should be set to that + * of the source file. + * + * @exception IOException if the copying fails. + * + * @deprecated since 1.4.x + */ + @Deprecated + public void copyFile(final String sourceFile, final String destFile, final boolean filtering, + final boolean overwrite, final boolean preserveLastModified) + throws IOException { + FILE_UTILS.copyFile(sourceFile, destFile, + filtering ? globalFilters : null, overwrite, preserveLastModified); + } + + /** + * Convenience method to copy a file from a source to a destination. + * No filtering is performed. + * + * @param sourceFile File to copy from. + * Must not be <code>null</code>. + * @param destFile File to copy to. + * Must not be <code>null</code>. + * + * @exception IOException if the copying fails. + * + * @deprecated since 1.4.x + */ + @Deprecated + public void copyFile(final File sourceFile, final File destFile) throws IOException { + FILE_UTILS.copyFile(sourceFile, destFile); + } + + /** + * Convenience method to copy a file from a source to a destination + * specifying if token filtering should be used. + * + * @param sourceFile File to copy from. + * Must not be <code>null</code>. + * @param destFile File to copy to. + * Must not be <code>null</code>. + * @param filtering Whether or not token filtering should be used during + * the copy. + * + * @exception IOException if the copying fails. + * + * @deprecated since 1.4.x + */ + @Deprecated + public void copyFile(final File sourceFile, final File destFile, final boolean filtering) + throws IOException { + FILE_UTILS.copyFile(sourceFile, destFile, + filtering ? globalFilters : null); + } + + /** + * Convenience method to copy a file from a source to a + * destination specifying if token filtering should be used and if + * source files may overwrite newer destination files. + * + * @param sourceFile File to copy from. + * Must not be <code>null</code>. + * @param destFile File to copy to. + * Must not be <code>null</code>. + * @param filtering Whether or not token filtering should be used during + * the copy. + * @param overwrite Whether or not the destination file should be + * overwritten if it already exists. + * + * @exception IOException if the file cannot be copied. + * + * @deprecated since 1.4.x + */ + @Deprecated + public void copyFile(final File sourceFile, final File destFile, final boolean filtering, + final boolean overwrite) throws IOException { + FILE_UTILS.copyFile(sourceFile, destFile, + filtering ? globalFilters : null, overwrite); + } + + /** + * Convenience method to copy a file from a source to a + * destination specifying if token filtering should be used, if + * source files may overwrite newer destination files, and if the + * last modified time of the resulting file should be set to + * that of the source file. + * + * @param sourceFile File to copy from. + * Must not be <code>null</code>. + * @param destFile File to copy to. + * Must not be <code>null</code>. + * @param filtering Whether or not token filtering should be used during + * the copy. + * @param overwrite Whether or not the destination file should be + * overwritten if it already exists. + * @param preserveLastModified Whether or not the last modified time of + * the resulting file should be set to that + * of the source file. + * + * @exception IOException if the file cannot be copied. + * + * @deprecated since 1.4.x + */ + @Deprecated + public void copyFile(final File sourceFile, final File destFile, final boolean filtering, + final boolean overwrite, final boolean preserveLastModified) + throws IOException { + FILE_UTILS.copyFile(sourceFile, destFile, + filtering ? globalFilters : null, overwrite, preserveLastModified); + } + + /** + * Call File.setLastModified(long time) on Java above 1.1, and logs + * a warning on Java 1.1. + * + * @param file The file to set the last modified time on. + * Must not be <code>null</code>. + * + * @param time the required modification time. + * + * @deprecated since 1.4.x + * + * @exception BuildException if the last modified time cannot be set + * despite running on a platform with a version + * above 1.1. + */ + @Deprecated + public void setFileLastModified(final File file, final long time) + throws BuildException { + FILE_UTILS.setFileLastModified(file, time); + log("Setting modification time for " + file, MSG_VERBOSE); + } + + /** + * Return the boolean equivalent of a string, which is considered + * <code>true</code> if either <code>"on"</code>, <code>"true"</code>, + * or <code>"yes"</code> is found, ignoring case. + * + * @param s The string to convert to a boolean value. + * + * @return <code>true</code> if the given string is <code>"on"</code>, + * <code>"true"</code> or <code>"yes"</code>, or + * <code>false</code> otherwise. + */ + public static boolean toBoolean(final String s) { + return ("on".equalsIgnoreCase(s) + || "true".equalsIgnoreCase(s) + || "yes".equalsIgnoreCase(s)); + } + + /** + * Get the Project instance associated with the specified object. + * @param o the object to query. + * @return Project instance, if any. + * @since Ant 1.7.1 + */ + public static Project getProject(final Object o) { + if (o instanceof ProjectComponent) { + return ((ProjectComponent) o).getProject(); + } + try { + final Method m = o.getClass().getMethod("getProject", (Class[]) null); + if (Project.class == m.getReturnType()) { + return (Project) m.invoke(o, (Object[]) null); + } + } catch (final Exception e) { + //too bad + } + return null; + } + + /** + * Topologically sort a set of targets. Equivalent to calling + * <code>topoSort(new String[] {root}, targets, true)</code>. + * + * @param root The name of the root target. The sort is created in such + * a way that the sequence of Targets up to the root + * target is the minimum possible such sequence. + * Must not be <code>null</code>. + * @param targetTable A Hashtable mapping names to Targets. + * Must not be <code>null</code>. + * @return a Vector of ALL Target objects in sorted order. + * @exception BuildException if there is a cyclic dependency among the + * targets, or if a named target does not exist. + */ + public final Vector<Target> topoSort(final String root, final Hashtable<String, Target> targetTable) + throws BuildException { + return topoSort(new String[] {root}, targetTable, true); + } + + /** + * Topologically sort a set of targets. Equivalent to calling + * <code>topoSort(new String[] {root}, targets, returnAll)</code>. + * + * @param root The name of the root target. The sort is created in such + * a way that the sequence of Targets up to the root + * target is the minimum possible such sequence. + * Must not be <code>null</code>. + * @param targetTable A Hashtable mapping names to Targets. + * Must not be <code>null</code>. + * @param returnAll <code>boolean</code> indicating whether to return all + * targets, or the execution sequence only. + * @return a Vector of Target objects in sorted order. + * @exception BuildException if there is a cyclic dependency among the + * targets, or if a named target does not exist. + * @since Ant 1.6.3 + */ + public final Vector<Target> topoSort(final String root, final Hashtable<String, Target> targetTable, + final boolean returnAll) throws BuildException { + return topoSort(new String[] {root}, targetTable, returnAll); + } + + /** + * Topologically sort a set of targets. + * + * @param root <code>String[]</code> containing the names of the root targets. + * The sort is created in such a way that the ordered sequence of + * Targets is the minimum possible such sequence to the specified + * root targets. + * Must not be <code>null</code>. + * @param targetTable A map of names to targets (String to Target). + * Must not be <code>null</code>. + * @param returnAll <code>boolean</code> indicating whether to return all + * targets, or the execution sequence only. + * @return a Vector of Target objects in sorted order. + * @exception BuildException if there is a cyclic dependency among the + * targets, or if a named target does not exist. + * @since Ant 1.6.3 + */ + public final Vector<Target> topoSort(final String[] root, final Hashtable<String, Target> targetTable, + final boolean returnAll) throws BuildException { + final Vector<Target> ret = new VectorSet<Target>(); + final Hashtable<String, String> state = new Hashtable<String, String>(); + final Stack<String> visiting = new Stack<String>(); + + // We first run a DFS based sort using each root as a starting node. + // This creates the minimum sequence of Targets to the root node(s). + // We then do a sort on any remaining unVISITED targets. + // This is unnecessary for doing our build, but it catches + // circular dependencies or missing Targets on the entire + // dependency tree, not just on the Targets that depend on the + // build Target. + + for (int i = 0; i < root.length; i++) { + final String st = (state.get(root[i])); + if (st == null) { + tsort(root[i], targetTable, state, visiting, ret); + } else if (st == VISITING) { + throw new RuntimeException("Unexpected node in visiting state: " + + root[i]); + } + } + final StringBuffer buf = new StringBuffer("Build sequence for target(s)"); + + for (int j = 0; j < root.length; j++) { + buf.append((j == 0) ? " `" : ", `").append(root[j]).append('\''); + } + buf.append(" is " + ret); + log(buf.toString(), MSG_VERBOSE); + + final Vector<Target> complete = (returnAll) ? ret : new Vector<Target>(ret); + for (final Enumeration<String> en = targetTable.keys(); en.hasMoreElements();) { + final String curTarget = en.nextElement(); + final String st = state.get(curTarget); + if (st == null) { + tsort(curTarget, targetTable, state, visiting, complete); + } else if (st == VISITING) { + throw new RuntimeException("Unexpected node in visiting state: " + + curTarget); + } + } + log("Complete build sequence is " + complete, MSG_VERBOSE); + return ret; + } + + /** + * Perform a single step in a recursive depth-first-search traversal of + * the target dependency tree. + * <p> + * The current target is first set to the "visiting" state, and + * pushed onto the "visiting" stack. + * <p> + * An exception is then thrown if any child of the current node is in the + * visiting state, as that implies a circular dependency. The exception + * contains details of the cycle, using elements of the "visiting" + * stack. + * <p> + * If any child has not already been "visited", this method is + * called recursively on it. + * <p> + * The current target is then added to the ordered list of targets. Note + * that this is performed after the children have been visited in order + * to get the correct order. The current target is set to the + * "visited" state. + * <p> + * By the time this method returns, the ordered list contains the sequence + * of targets up to and including the current target. + * + * @param root The current target to inspect. + * Must not be <code>null</code>. + * @param targetTable A mapping from names to targets (String to Target). + * Must not be <code>null</code>. + * @param state A mapping from target names to states (String to String). + * The states in question are "VISITING" and + * "VISITED". Must not be <code>null</code>. + * @param visiting A stack of targets which are currently being visited. + * Must not be <code>null</code>. + * @param ret The list to add target names to. This will end up + * containing the complete list of dependencies in + * dependency order. + * Must not be <code>null</code>. + * + * @exception BuildException if a non-existent target is specified or if + * a circular dependency is detected. + */ + private void tsort(final String root, final Hashtable<String, Target> targetTable, + final Hashtable<String, String> state, final Stack<String> visiting, + final Vector<Target> ret) + throws BuildException { + state.put(root, VISITING); + visiting.push(root); + + final Target target = targetTable.get(root); + + // Make sure we exist + if (target == null) { + final StringBuilder sb = new StringBuilder("Target \""); + sb.append(root); + sb.append("\" does not exist in the project \""); + sb.append(name); + sb.append("\". "); + visiting.pop(); + if (!visiting.empty()) { + final String parent = visiting.peek(); + sb.append("It is used from target \""); + sb.append(parent); + sb.append("\"."); + } + throw new BuildException(new String(sb)); + } + for (final Enumeration<String> en = target.getDependencies(); en.hasMoreElements();) { + final String cur = en.nextElement(); + final String m = state.get(cur); + if (m == null) { + // Not been visited + tsort(cur, targetTable, state, visiting, ret); + } else if (m == VISITING) { + // Currently visiting this node, so have a cycle + throw makeCircularException(cur, visiting); + } + } + final String p = visiting.pop(); + if (root != p) { + throw new RuntimeException("Unexpected internal error: expected to " + + "pop " + root + " but got " + p); + } + state.put(root, VISITED); + ret.addElement(target); + } + + /** + * Build an appropriate exception detailing a specified circular + * dependency. + * + * @param end The dependency to stop at. Must not be <code>null</code>. + * @param stk A stack of dependencies. Must not be <code>null</code>. + * + * @return a BuildException detailing the specified circular dependency. + */ + private static BuildException makeCircularException(final String end, final Stack<String> stk) { + final StringBuilder sb = new StringBuilder("Circular dependency: "); + sb.append(end); + String c; + do { + c = stk.pop(); + sb.append(" <- "); + sb.append(c); + } while (!c.equals(end)); + return new BuildException(sb.toString()); + } + + /** + * Inherit the id references. + * @param parent the parent project of this project. + */ + public void inheritIDReferences(final Project parent) { + } + + /** + * Add an id reference. + * Used for broken build files. + * @param id the id to set. + * @param value the value to set it to (Unknown element in this case. + */ + public void addIdReference(final String id, final Object value) { + idReferences.put(id, value); + } + + /** + * Add a reference to the project. + * + * @param referenceName The name of the reference. Must not be <code>null</code>. + * @param value The value of the reference. + */ + public void addReference(final String referenceName, final Object value) { + final Object old = ((AntRefTable) references).getReal(referenceName); + if (old == value) { + // no warning, this is not changing anything + return; + } + if (old != null && !(old instanceof UnknownElement)) { + log("Overriding previous definition of reference to " + referenceName, + MSG_VERBOSE); + } + log("Adding reference: " + referenceName, MSG_DEBUG); + references.put(referenceName, value); + } + + /** + * Return a map of the references in the project (String to Object). + * The returned hashtable is "live" and so must not be modified. + * + * @return a map of the references in the project (String to Object). + */ + public Hashtable<String, Object> getReferences() { + return references; + } + + /** + * Does the project know this reference? + * + * @since Ant 1.8.0 + */ + public boolean hasReference(final String key) { + return references.containsKey(key); + } + + /** + * Return a map of the references in the project (String to + * Object). The returned hashtable is a copy of the + * "live" references. + * + * @return a map of the references in the project (String to Object). + * + * @since Ant 1.8.1 + */ + public Map<String, Object> getCopyOfReferences() { + return new HashMap<String, Object>(references); + } + + /** + * Look up a reference by its key (ID). + * + * @param key The key for the desired reference. + * Must not be <code>null</code>. + * + * @return the reference with the specified ID, or <code>null</code> if + * there is no such reference in the project, with type inference. + */ + public <T> T getReference(final String key) { + @SuppressWarnings("unchecked") + final T ret = (T) references.get(key); + if (ret != null) { + return ret; + } + if (!key.equals(MagicNames.REFID_PROPERTY_HELPER)) { + try { + if (PropertyHelper.getPropertyHelper(this).containsProperties(key)) { + log("Unresolvable reference " + key + + " might be a misuse of property expansion syntax.", MSG_WARN); + } + } catch (final Exception e) { + //ignore + } + } + return null; + } + + /** + * Return a description of the type of the given element, with + * special handling for instances of tasks and data types. + * <p> + * This is useful for logging purposes. + * + * @param element The element to describe. + * Must not be <code>null</code>. + * + * @return a description of the element type. + * + * @since 1.95, Ant 1.5 + */ + public String getElementName(final Object element) { + return ComponentHelper.getComponentHelper(this).getElementName(element); + } + + /** + * Send a "build started" event + * to the build listeners for this project. + */ + public void fireBuildStarted() { + final BuildEvent event = new BuildEvent(this); + final BuildListener[] currListeners = listeners; + for (int i = 0; i < currListeners.length; i++) { + currListeners[i].buildStarted(event); + } + } + + /** + * Send a "build finished" event to the build listeners + * for this project. + * @param exception an exception indicating a reason for a build + * failure. May be <code>null</code>, indicating + * a successful build. + */ + public void fireBuildFinished(final Throwable exception) { + final BuildEvent event = new BuildEvent(this); + event.setException(exception); + final BuildListener[] currListeners = listeners; + for (int i = 0; i < currListeners.length; i++) { + currListeners[i].buildFinished(event); + } + // Inform IH to clear the cache + IntrospectionHelper.clearCache(); + } + + /** + * Send a "subbuild started" event to the build listeners for + * this project. + * + * @since Ant 1.6.2 + */ + public void fireSubBuildStarted() { + final BuildEvent event = new BuildEvent(this); + final BuildListener[] currListeners = listeners; + for (int i = 0; i < currListeners.length; i++) { + if (currListeners[i] instanceof SubBuildListener) { + ((SubBuildListener) currListeners[i]).subBuildStarted(event); + } + } + } + + /** + * Send a "subbuild finished" event to the build listeners for + * this project. + * @param exception an exception indicating a reason for a build + * failure. May be <code>null</code>, indicating + * a successful build. + * + * @since Ant 1.6.2 + */ + public void fireSubBuildFinished(final Throwable exception) { + final BuildEvent event = new BuildEvent(this); + event.setException(exception); + final BuildListener[] currListeners = listeners; + for (int i = 0; i < currListeners.length; i++) { + if (currListeners[i] instanceof SubBuildListener) { + ((SubBuildListener) currListeners[i]).subBuildFinished(event); + } + } + } + + /** + * Send a "target started" event to the build listeners + * for this project. + * + * @param target The target which is starting to build. + * Must not be <code>null</code>. + */ + protected void fireTargetStarted(final Target target) { + final BuildEvent event = new BuildEvent(target); + final BuildListener[] currListeners = listeners; + for (int i = 0; i < currListeners.length; i++) { + currListeners[i].targetStarted(event); + } + + } + + /** + * Send a "target finished" event to the build listeners + * for this project. + * + * @param target The target which has finished building. + * Must not be <code>null</code>. + * @param exception an exception indicating a reason for a build + * failure. May be <code>null</code>, indicating + * a successful build. + */ + protected void fireTargetFinished(final Target target, final Throwable exception) { + final BuildEvent event = new BuildEvent(target); + event.setException(exception); + final BuildListener[] currListeners = listeners; + for (int i = 0; i < currListeners.length; i++) { + currListeners[i].targetFinished(event); + } + + } + + /** + * Send a "task started" event to the build listeners + * for this project. + * + * @param task The target which is starting to execute. + * Must not be <code>null</code>. + */ + protected void fireTaskStarted(final Task task) { + // register this as the current task on the current thread. + registerThreadTask(Thread.currentThread(), task); + final BuildEvent event = new BuildEvent(task); + final BuildListener[] currListeners = listeners; + for (int i = 0; i < currListeners.length; i++) { + currListeners[i].taskStarted(event); + } + } + + /** + * Send a "task finished" event to the build listeners for this + * project. + * + * @param task The task which has finished executing. + * Must not be <code>null</code>. + * @param exception an exception indicating a reason for a build + * failure. May be <code>null</code>, indicating + * a successful build. + */ + protected void fireTaskFinished(final Task task, final Throwable exception) { + registerThreadTask(Thread.currentThread(), null); + System.out.flush(); + System.err.flush(); + final BuildEvent event = new BuildEvent(task); + event.setException(exception); + final BuildListener[] currListeners = listeners; + for (int i = 0; i < currListeners.length; i++) { + currListeners[i].taskFinished(event); + } + + } + + /** + * Send a "message logged" event to the build listeners + * for this project. + * + * @param event The event to send. This should be built up with the + * appropriate task/target/project by the caller, so that + * this method can set the message and priority, then send + * the event. Must not be <code>null</code>. + * @param message The message to send. Should not be <code>null</code>. + * @param priority The priority of the message. + */ + private void fireMessageLoggedEvent(final BuildEvent event, String message, + final int priority) { + + if (message == null) { + message = String.valueOf(message); + } + if (message.endsWith(StringUtils.LINE_SEP)) { + final int endIndex = message.length() - StringUtils.LINE_SEP.length(); + event.setMessage(message.substring(0, endIndex), priority); + } else { + event.setMessage(message, priority); + } + if (isLoggingMessage.get() != Boolean.FALSE) { + /* + * One of the Listeners has attempted to access + * System.err or System.out. + * + * We used to throw an exception in this case, but + * sometimes Listeners can't prevent it(like our own + * Log4jListener which invokes getLogger() which in + * turn wants to write to the console). + * + * @see http://marc.theaimsgroup.com/?t=110538624200006&r=1&w=2 + * + * We now (Ant 1.6.3 and later) simply swallow the message. + */ + return; + } + try { + isLoggingMessage.set(Boolean.TRUE); + final BuildListener[] currListeners = listeners; + for (int i = 0; i < currListeners.length; i++) { + currListeners[i].messageLogged(event); + } + } finally { + isLoggingMessage.set(Boolean.FALSE); + } + } + + /** + * Send a "message logged" project level event + * to the build listeners for this project. + * + * @param project The project generating the event. + * Should not be <code>null</code>. + * @param message The message to send. Should not be <code>null</code>. + * @param priority The priority of the message. + */ + protected void fireMessageLogged(final Project project, final String message, + final int priority) { + fireMessageLogged(project, message, null, priority); + } + + /** + * Send a "message logged" project level event + * to the build listeners for this project. + * + * @param project The project generating the event. + * Should not be <code>null</code>. + * @param message The message to send. Should not be <code>null</code>. + * @param throwable The exception that caused this message. May be <code>null</code>. + * @param priority The priority of the message. + * @since 1.7 + */ + protected void fireMessageLogged(final Project project, final String message, + final Throwable throwable, final int priority) { + final BuildEvent event = new BuildEvent(project); + event.setException(throwable); + fireMessageLoggedEvent(event, message, priority); + } + + /** + * Send a "message logged" target level event + * to the build listeners for this project. + * + * @param target The target generating the event. + * Must not be <code>null</code>. + * @param message The message to send. Should not be <code>null</code>. + * @param priority The priority of the message. + */ + protected void fireMessageLogged(final Target target, final String message, + final int priority) { + fireMessageLogged(target, message, null, priority); + } + + /** + * Send a "message logged" target level event + * to the build listeners for this project. + * + * @param target The target generating the event. + * Must not be <code>null</code>. + * @param message The message to send. Should not be <code>null</code>. + * @param throwable The exception that caused this message. May be <code>null</code>. + * @param priority The priority of the message. + * @since 1.7 + */ + protected void fireMessageLogged(final Target target, final String message, + final Throwable throwable, final int priority) { + final BuildEvent event = new BuildEvent(target); + event.setException(throwable); + fireMessageLoggedEvent(event, message, priority); + } + + /** + * Send a "message logged" task level event + * to the build listeners for this project. + * + * @param task The task generating the event. + * Must not be <code>null</code>. + * @param message The message to send. Should not be <code>null</code>. + * @param priority The priority of the message. + */ + protected void fireMessageLogged(final Task task, final String message, final int priority) { + fireMessageLogged(task, message, null, priority); + } + + /** + * Send a "message logged" task level event + * to the build listeners for this project. + * + * @param task The task generating the event. + * Must not be <code>null</code>. + * @param message The message to send. Should not be <code>null</code>. + * @param throwable The exception that caused this message. May be <code>null</code>. + * @param priority The priority of the message. + * @since 1.7 + */ + protected void fireMessageLogged(final Task task, final String message, + final Throwable throwable, final int priority) { + final BuildEvent event = new BuildEvent(task); + event.setException(throwable); + fireMessageLoggedEvent(event, message, priority); + } + + /** + * Register a task as the current task for a thread. + * If the task is null, the thread's entry is removed. + * + * @param thread the thread on which the task is registered. + * @param task the task to be registered. + * @since Ant 1.5 + */ + public void registerThreadTask(final Thread thread, final Task task) { + synchronized (threadTasks) { + if (task != null) { + threadTasks.put(thread, task); + threadGroupTasks.put(thread.getThreadGroup(), task); + } else { + threadTasks.remove(thread); + threadGroupTasks.remove(thread.getThreadGroup()); + } + } + } + + /** + * Get the current task associated with a thread, if any. + * + * @param thread the thread for which the task is required. + * @return the task which is currently registered for the given thread or + * null if no task is registered. + */ + public Task getThreadTask(final Thread thread) { + synchronized (threadTasks) { + Task task = threadTasks.get(thread); + if (task == null) { + ThreadGroup group = thread.getThreadGroup(); + while (task == null && group != null) { + task = threadGroupTasks.get(group); + group = group.getParent(); + } + } + return task; + } + } + + + // Should move to a separate public class - and have API to add + // listeners, etc. + private static class AntRefTable extends Hashtable<String, Object> { + private static final long serialVersionUID = 1L; + + AntRefTable() { + super(); + } + + /** Returns the unmodified original object. + * This method should be called internally to + * get the "real" object. + * The normal get method will do the replacement + * of UnknownElement (this is similar with the JDNI + * refs behavior). + */ + private Object getReal(final Object key) { + return super.get(key); + } + + /** Get method for the reference table. + * It can be used to hook dynamic references and to modify + * some references on the fly--for example for delayed + * evaluation. + * + * It is important to make sure that the processing that is + * done inside is not calling get indirectly. + * + * @param key lookup key. + * @return mapped value. + */ + @Override + public Object get(final Object key) { + Object o = getReal(key); + if (o instanceof UnknownElement) { + // Make sure that + final UnknownElement ue = (UnknownElement) o; + ue.maybeConfigure(); + o = ue.getRealThing(); + } + return o; + } + } + + /** + * Set a reference to this Project on the parameterized object. + * Need to set the project before other set/add elements + * are called. + * @param obj the object to invoke setProject(this) on. + */ + public final void setProjectReference(final Object obj) { + if (obj instanceof ProjectComponent) { + ((ProjectComponent) obj).setProject(this); + return; + } + try { + final Method method = + obj.getClass().getMethod( + "setProject", new Class[] {Project.class}); + if (method != null) { + method.invoke(obj, new Object[] {this}); + } + } catch (final Throwable e) { + // ignore this if the object does not have + // a set project method or the method + // is private/protected. + } + } + + /** + * Resolve the file relative to the project's basedir and return it as a + * FileResource. + * @param name the name of the file to resolve. + * @return the file resource. + * @since Ant 1.7 + */ + public Resource getResource(final String name) { + return new FileResource(getBaseDir(), name); + } +} |