/* * 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.util.ArrayList; import java.util.Collections; import java.util.Enumeration; import java.util.Hashtable; import java.util.List; import java.util.StringTokenizer; import org.apache.tools.ant.property.LocalProperties; import org.apache.tools.ant.taskdefs.condition.And; import org.apache.tools.ant.taskdefs.condition.Condition; import org.apache.tools.ant.taskdefs.condition.Or; /** * Class to implement a target object with required parameters. * * <p>If you are creating Targets programmatically, make sure you set * the Location to a useful value. In particular all targets should * have different location values.</p> */ public class Target implements TaskContainer { /** Name of this target. */ private String name; /** The "if" condition to test on execution. */ private String ifString = ""; /** The "unless" condition to test on execution. */ private String unlessString = ""; private Condition ifCondition; private Condition unlessCondition; /** List of targets this target is dependent on. */ private List<String> dependencies = null; /** Children of this target (tasks and data types). */ private List<Object> children = new ArrayList<>(); /** Since Ant 1.6.2 */ private Location location = Location.UNKNOWN_LOCATION; /** Project this target belongs to. */ private Project project; /** Description of this target, if any. */ private String description = null; /** Default constructor. */ public Target() { //empty } /** * Cloning constructor. * @param other the Target to clone. */ public Target(Target other) { this.name = other.name; this.ifString = other.ifString; this.unlessString = other.unlessString; this.ifCondition = other.ifCondition; this.unlessCondition = other.unlessCondition; this.dependencies = other.dependencies; this.location = other.location; this.project = other.project; this.description = other.description; // The children are added to after this cloning this.children = other.children; } /** * Sets the project this target belongs to. * * @param project The project this target belongs to. * Must not be <code>null</code>. */ public void setProject(Project project) { this.project = project; } /** * Returns the project this target belongs to. * * @return The project this target belongs to, or <code>null</code> if * the project has not been set yet. */ public Project getProject() { return project; } /** * Sets the location of this target's definition. * * @param location <code>Location</code> * @since 1.6.2 */ public void setLocation(Location location) { this.location = location; } /** * Get the location of this target's definition. * * @return <code>Location</code> * @since 1.6.2 */ public Location getLocation() { return location; } /** * Sets the list of targets this target is dependent on. * The targets themselves are not resolved at this time. * * @param depS A comma-separated list of targets this target * depends on. Must not be <code>null</code>. */ public void setDepends(String depS) { for (String dep : parseDepends(depS, getName(), "depends")) { addDependency(dep); } } public static List<String> parseDepends(String depends, String targetName, String attributeName) { List<String> list = new ArrayList<>(); if (depends.length() > 0) { StringTokenizer tok = new StringTokenizer(depends, ",", true); while (tok.hasMoreTokens()) { String token = tok.nextToken().trim(); // Make sure the dependency is not empty string if ("".equals(token) || ",".equals(token)) { throw new BuildException("Syntax Error: " + attributeName + " attribute of target \"" + targetName + "\" contains an empty string."); } list.add(token); // Make sure that depends attribute does not // end in a , if (tok.hasMoreTokens()) { token = tok.nextToken(); if (!tok.hasMoreTokens() || !",".equals(token)) { throw new BuildException("Syntax Error: " + attributeName + " attribute for target \"" + targetName + "\" ends with a \",\" " + "character"); } } } } return list; } /** * Sets the name of this target. * * @param name The name of this target. Should not be <code>null</code>. */ public void setName(String name) { this.name = name; } /** * Returns the name of this target. * * @return the name of this target, or <code>null</code> if the * name has not been set yet. */ public String getName() { return name; } /** * Adds a task to this target. * * @param task The task to be added. Must not be <code>null</code>. */ public void addTask(Task task) { children.add(task); } /** * Adds the wrapper for a data type element to this target. * * @param r The wrapper for the data type element to be added. * Must not be <code>null</code>. */ public void addDataType(RuntimeConfigurable r) { children.add(r); } /** * Returns the current set of tasks to be executed by this target. * * @return an array of the tasks currently within this target */ public Task[] getTasks() { List<Task> tasks = new ArrayList<>(children.size()); for (Object o : children) { if (o instanceof Task) { tasks.add((Task) o); } } return tasks.toArray(new Task[tasks.size()]); } /** * Adds a dependency to this target. * * @param dependency The name of a target this target is dependent on. * Must not be <code>null</code>. */ public void addDependency(String dependency) { if (dependencies == null) { dependencies = new ArrayList<>(2); } dependencies.add(dependency); } /** * Returns an enumeration of the dependencies of this target. * * @return an enumeration of the dependencies of this target (enumeration of String) */ public Enumeration<String> getDependencies() { return dependencies == null ? Collections.emptyEnumeration() : Collections.enumeration(dependencies); } /** * Does this target depend on the named target? * @param other the other named target. * @return true if the target does depend on the named target * @since Ant 1.6 */ public boolean dependsOn(String other) { Project p = getProject(); Hashtable<String, Target> t = p == null ? null : p.getTargets(); return p != null && p.topoSort(getName(), t, false).contains(t.get(other)); } /** * Sets the "if" condition to test on execution. This is the * name of a property to test for existence - if the property * is not set, the task will not execute. The property goes * through property substitution once before testing, so if * property <code>foo</code> has value <code>bar</code>, setting * the "if" condition to <code>${foo}_x</code> will mean that the * task will only execute if property <code>bar_x</code> is set. * * @param property The property condition to test on execution. * May be <code>null</code>, in which case * no "if" test is performed. */ public void setIf(String property) { ifString = property == null ? "" : property; setIf(() -> { PropertyHelper propertyHelper = PropertyHelper.getPropertyHelper(getProject()); Object o = propertyHelper.parseProperties(ifString); return propertyHelper.testIfCondition(o); }); } /** * Returns the "if" property condition of this target. * * @return the "if" property condition or <code>null</code> if no * "if" condition had been defined. * @since 1.6.2 */ public String getIf() { return "".equals(ifString) ? null : ifString; } /** * Same as {@link #setIf(String)} but requires a {@link Condition} instance * * @since 1.9 */ public void setIf(Condition condition) { if (ifCondition == null) { ifCondition = condition; } else { And andCondition = new And(); andCondition.setProject(getProject()); andCondition.setLocation(getLocation()); andCondition.add(ifCondition); andCondition.add(condition); ifCondition = andCondition; } } /** * Sets the "unless" condition to test on execution. This is the * name of a property to test for existence - if the property * is set, the task will not execute. The property goes * through property substitution once before testing, so if * property <code>foo</code> has value <code>bar</code>, setting * the "unless" condition to <code>${foo}_x</code> will mean that the * task will only execute if property <code>bar_x</code> isn't set. * * @param property The property condition to test on execution. * May be <code>null</code>, in which case * no "unless" test is performed. */ public void setUnless(String property) { unlessString = property == null ? "" : property; setUnless(() -> { PropertyHelper propertyHelper = PropertyHelper.getPropertyHelper(getProject()); Object o = propertyHelper.parseProperties(unlessString); return !propertyHelper.testUnlessCondition(o); }); } /** * Returns the "unless" property condition of this target. * * @return the "unless" property condition or <code>null</code> * if no "unless" condition had been defined. * @since 1.6.2 */ public String getUnless() { return "".equals(unlessString) ? null : unlessString; } /** * Same as {@link #setUnless(String)} but requires a {@link Condition} instance * * @since 1.9 */ public void setUnless(Condition condition) { if (unlessCondition == null) { unlessCondition = condition; } else { Or orCondition = new Or(); orCondition.setProject(getProject()); orCondition.setLocation(getLocation()); orCondition.add(unlessCondition); orCondition.add(condition); unlessCondition = orCondition; } } /** * Sets the description of this target. * * @param description The description for this target. * May be <code>null</code>, indicating that no * description is available. */ public void setDescription(String description) { this.description = description; } /** * Returns the description of this target. * * @return the description of this target, or <code>null</code> if no * description is available. */ public String getDescription() { return description; } /** * Returns the name of this target. * * @return the name of this target, or <code>null</code> if the * name has not been set yet. */ @Override public String toString() { return name; } /** * Executes the target if the "if" and "unless" conditions are * satisfied. Dependency checking should be done before calling this * method, as it does no checking of its own. If either the "if" * or "unless" test prevents this target from being executed, a verbose * message is logged giving the reason. It is recommended that clients * of this class call performTasks rather than this method so that * appropriate build events are fired. * * @exception BuildException if any of the tasks fail or if a data type * configuration fails. * * @see #performTasks() * @see #setIf(String) * @see #setUnless(String) */ public void execute() throws BuildException { if (ifCondition != null && !ifCondition.eval()) { project.log(this, "Skipped because property '" + project.replaceProperties(ifString) + "' not set.", Project.MSG_VERBOSE); return; } if (unlessCondition != null && unlessCondition.eval()) { project.log(this, "Skipped because property '" + project.replaceProperties(unlessString) + "' set.", Project.MSG_VERBOSE); return; } LocalProperties localProperties = LocalProperties.get(getProject()); localProperties.enterScope(); try { // use index-based approach to avoid ConcurrentModificationExceptions; // also account for growing target children // do not optimize this loop by replacing children.size() by a variable // as children can be added dynamically as in RhinoScriptTest where a target is adding work for itself for (int i = 0; i < children.size(); i++) { Object o = children.get(i); if (o instanceof Task) { Task task = (Task) o; task.perform(); } else { ((RuntimeConfigurable) o).maybeConfigure(project); } } } finally { localProperties.exitScope(); } } /** * Performs the tasks within this target (if the conditions are met), * firing target started/target finished messages around a call to * execute. * * @see #execute() */ public final void performTasks() { RuntimeException thrown = null; project.fireTargetStarted(this); try { execute(); } catch (RuntimeException exc) { thrown = exc; throw exc; } finally { project.fireTargetFinished(this, thrown); } } /** * Replaces all occurrences of the given task in the list * of children with the replacement data type wrapper. * * @param el The task to replace. * Must not be <code>null</code>. * @param o The data type wrapper to replace <code>el</code> with. */ void replaceChild(Task el, RuntimeConfigurable o) { int index; while ((index = children.indexOf(el)) >= 0) { children.set(index, o); } } /** * Replaces all occurrences of the given task in the list * of children with the replacement task. * * @param el The task to replace. * Must not be <code>null</code>. * @param o The task to replace <code>el</code> with. */ void replaceChild(Task el, Task o) { int index; while ((index = children.indexOf(el)) >= 0) { children.set(index, o); } } }