diff --git a/WHATSNEW b/WHATSNEW index 350789623..c7708d9ab 100644 --- a/WHATSNEW +++ b/WHATSNEW @@ -153,6 +153,8 @@ Other changes: a build if a warning occurs. Bugzilla Report 41836. + * Ant now supports local properties. Bugzilla report 23942. + Changes from Ant 1.7.0 TO Ant 1.7.1 ============================================= diff --git a/docs/manual/CoreTasks/local.html b/docs/manual/CoreTasks/local.html new file mode 100644 index 000000000..5abc87789 --- /dev/null +++ b/docs/manual/CoreTasks/local.html @@ -0,0 +1,53 @@ + + + + + + +Local Task + + + + +

Local

+

Description

+

Adds a local property to the current scope. Property scopes exist at Ant's +various "block" levels. These include targets as well as the +Parallel and Sequential +task containers (including Macrodef bodies). A local +property at a given scope "shadows" properties of the same name at higher scopes, +including the global scope (declaring a local property at the global level, i.e. +outside of any scope block, has no effect). Since Ant 1.8

+ +

Parameters

+ + + + + + + + + + + +
AttributeDescriptionRequired
nameThe property to declare in the current scopeYes
+ + + + diff --git a/docs/manual/coretasklist.html b/docs/manual/coretasklist.html index 670e724d4..aa47dd623 100644 --- a/docs/manual/coretasklist.html +++ b/docs/manual/coretasklist.html @@ -86,6 +86,7 @@ LoadFile
LoadProperties
LoadResource
+Local
MakeURL
Mail
MacroDef
diff --git a/src/main/org/apache/tools/ant/MagicNames.java b/src/main/org/apache/tools/ant/MagicNames.java index 36efb3570..46e2a59aa 100644 --- a/src/main/org/apache/tools/ant/MagicNames.java +++ b/src/main/org/apache/tools/ant/MagicNames.java @@ -174,6 +174,12 @@ public final class MagicNames { */ public static final String REFID_PROPERTY_HELPER = "ant.PropertyHelper"; + /** + * Reference used to store the local properties. + * Value: {@value} + */ + public static final String REFID_LOCAL_PROPERTIES = "ant.LocalProperties"; + /** * Name of JVM system property which provides the name of the ProjectHelper class to use. * Value: {@value} diff --git a/src/main/org/apache/tools/ant/Target.java b/src/main/org/apache/tools/ant/Target.java index cf0993442..bf5bcd049 100644 --- a/src/main/org/apache/tools/ant/Target.java +++ b/src/main/org/apache/tools/ant/Target.java @@ -25,6 +25,8 @@ import java.util.Iterator; import java.util.List; import java.util.StringTokenizer; +import org.apache.tools.ant.property.LocalProperties; + /** * Class to implement a target object with required parameters. * @@ -347,14 +349,22 @@ public class Target implements TaskContainer { */ public void execute() throws BuildException { if (testIfCondition() && testUnlessCondition()) { - for (int taskPosition = 0; taskPosition < children.size(); ++taskPosition) { - Object o = children.get(taskPosition); - if (o instanceof Task) { - Task task = (Task) o; - task.perform(); - } else { - ((RuntimeConfigurable) o).maybeConfigure(project); + LocalProperties localProperties + = LocalProperties.get(getProject()); + localProperties.enterScope(); + try { + for (int taskPosition = 0; taskPosition < children.size(); + ++taskPosition) { + Object o = children.get(taskPosition); + if (o instanceof Task) { + Task task = (Task) o; + task.perform(); + } else { + ((RuntimeConfigurable) o).maybeConfigure(project); + } } + } finally { + localProperties.exitScope(); } } else if (!testIfCondition()) { project.log(this, "Skipped because property '" + project.replaceProperties(ifCondition) diff --git a/src/main/org/apache/tools/ant/property/LocalProperties.java b/src/main/org/apache/tools/ant/property/LocalProperties.java new file mode 100644 index 000000000..367261637 --- /dev/null +++ b/src/main/org/apache/tools/ant/property/LocalProperties.java @@ -0,0 +1,152 @@ +/* + * 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.property; + +import org.apache.tools.ant.PropertyHelper; +import org.apache.tools.ant.Project; +import org.apache.tools.ant.MagicNames; + +/** + * Thread local class containing local properties. + * @since Ant 1.8.0 + */ +public class LocalProperties + extends InheritableThreadLocal + implements PropertyHelper.PropertyEvaluator, + PropertyHelper.PropertySetter { + + /** + * Get a localproperties for the given project. + * @param project the project to retieve the localproperties for. + * @return the localproperties. + */ + public static synchronized LocalProperties get(Project project) { + LocalProperties l = (LocalProperties) project.getReference( + MagicNames.REFID_LOCAL_PROPERTIES); + if (l == null) { + l = new LocalProperties(); + project.addReference(MagicNames.REFID_LOCAL_PROPERTIES, l); + PropertyHelper.getPropertyHelper(project).add(l); + } + return l; + } + + // -------------------------------------------------- + // + // Thread stuff + // + // -------------------------------------------------- + + /** + * Construct a new LocalProperties object. + */ + private LocalProperties() { + } + + /** + * Get the initial value. + * @return a new localproperties stack. + */ + protected synchronized Object initialValue() { + return new LocalPropertyStack(); + } + + private LocalPropertyStack current() { + return (LocalPropertyStack) get(); + } + + // -------------------------------------------------- + // + // Local property adding and scoping + // + // -------------------------------------------------- + + /** + * Add a local property to the current scope. + * @param property the property name to add. + */ + public void addLocal(String property) { + current().addLocal(property); + } + + /** enter the scope */ + public void enterScope() { + current().enterScope(); + } + + /** exit the scope */ + public void exitScope() { + current().exitScope(); + } + + // -------------------------------------------------- + // + // Copy - used in parallel to make a new stack + // + // -------------------------------------------------- + + /** + * Copy the stack for a parallel thread. + * To be called from the parallel thread itself. + */ + public void copy() { + set(current().copy()); + } + + // -------------------------------------------------- + // + // PropertyHelper delegate methods + // + // -------------------------------------------------- + + /** + * Evaluate a property. + * @param property the property's String "identifier". + * @param helper the invoking PropertyHelper. + * @return Object value. + */ + public Object evaluate(String property, PropertyHelper helper) { + return current().evaluate(property, helper); + } + + /** + * Set a *new" property. + * @param property the property's String "identifier". + * @param value the value to set. + * @param propertyHelper the invoking PropertyHelper. + * @return true if this entity 'owns' the property. + */ + public boolean setNew( + String property, Object value, PropertyHelper propertyHelper) { + return current().setNew(property, value, propertyHelper); + } + + /** + * Set a property. + * @param property the property's String "identifier". + * @param value the value to set. + * @param propertyHelper the invoking PropertyHelper. + * @return true if this entity 'owns' the property. + */ + public boolean set( + String property, Object value, PropertyHelper propertyHelper) { + return current().set(property, value, propertyHelper); + } +} + + diff --git a/src/main/org/apache/tools/ant/property/LocalPropertyStack.java b/src/main/org/apache/tools/ant/property/LocalPropertyStack.java new file mode 100644 index 000000000..dd6f97adc --- /dev/null +++ b/src/main/org/apache/tools/ant/property/LocalPropertyStack.java @@ -0,0 +1,151 @@ +/* + * 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.property; + + +import java.util.LinkedList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.apache.tools.ant.PropertyHelper; + +/** + * A stack of local property maps. + * There is a map for each scope (target, sequential, macro). + * @since Ant 1.8.0 + */ +public class LocalPropertyStack { + private LinkedList stack = new LinkedList(); + + // -------------------------------------------------- + // + // Local property adding and scoping + // + // -------------------------------------------------- + + /** + * Add a local property. + * @param property the name of the local proeprty. + */ + public void addLocal(String property) { + if (!stack.isEmpty()) { + ((Map) stack.getFirst()).put(property, NullReturn.NULL); + } + } + + /** + * Enter the local scope. + */ + public void enterScope() { + stack.addFirst(new HashMap()); + } + + /** + * Exit the local scope. + */ + public void exitScope() { + ((HashMap) stack.removeFirst()).clear(); + } + + // -------------------------------------------------- + // + // Copy - used in parallel to make a new stack + // + // -------------------------------------------------- + + /** + * Copy the stack for a parallel thread. + * @return a copy. + */ + public LocalPropertyStack copy() { + LocalPropertyStack ret = new LocalPropertyStack(); + ret.stack.addAll(stack); + return ret; + } + + // -------------------------------------------------- + // + // PropertyHelper delegate methods + // + // -------------------------------------------------- + + /** + * Evaluate a property. + * @param property the property's String "identifier". + * @param helper the invoking PropertyHelper. + * @return Object value. + */ + public Object evaluate(String property, PropertyHelper helper) { + for (Iterator i = stack.iterator(); i.hasNext();) { + Map map = (Map) i.next(); + Object ret = map.get(property); + if (ret != null) { + return ret; + } + } + return null; + } + + /** + * Set a *new" property. + * @param property the property's String "identifier". + * @param value the value to set. + * @param propertyHelper the invoking PropertyHelper. + * @return true if this entity 'owns' the property. + */ + public boolean setNew( + String property, Object value, PropertyHelper propertyHelper) { + Map map = getMapForProperty(property); + if (map == null) { + return false; + } + Object currValue = map.get(property); + if (currValue == NullReturn.NULL) { + map.put(property, value); + } + return true; + } + + /** + * Set a property. + * @param property the property's String "identifier". + * @param value the value to set. + * @param propertyHelper the invoking PropertyHelper. + * @return true if this entity 'owns' the property. + */ + public boolean set(String property, Object value, PropertyHelper propertyHelper) { + Map map = getMapForProperty(property); + if (map == null) { + return false; + } + map.put(property, value); + return true; + } + + private Map getMapForProperty(String property) { + for (Iterator i = stack.iterator(); i.hasNext();) { + Map map = (Map) i.next(); + if (map.get(property) != null) { + return map; + } + } + return null; + } +} + diff --git a/src/main/org/apache/tools/ant/taskdefs/Local.java b/src/main/org/apache/tools/ant/taskdefs/Local.java new file mode 100644 index 000000000..3fdf9b59b --- /dev/null +++ b/src/main/org/apache/tools/ant/taskdefs/Local.java @@ -0,0 +1,47 @@ +/* + * 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.taskdefs; + +import org.apache.tools.ant.BuildException; +import org.apache.tools.ant.Task; +import org.apache.tools.ant.property.LocalProperties; + +/** + * Task to create a local property in the current scope. + */ +public class Local extends Task { + private String name; + + /** + * Set the name attribute. + * @param name the name of the local property. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Run the task. + */ + public void execute() { + if (name == null) { + throw new BuildException("Missing attribute name"); + } + LocalProperties.get(getProject()).addLocal(name); + } +} diff --git a/src/main/org/apache/tools/ant/taskdefs/MacroInstance.java b/src/main/org/apache/tools/ant/taskdefs/MacroInstance.java index 3917101ca..8814eba1e 100644 --- a/src/main/org/apache/tools/ant/taskdefs/MacroInstance.java +++ b/src/main/org/apache/tools/ant/taskdefs/MacroInstance.java @@ -37,6 +37,7 @@ import org.apache.tools.ant.Target; import org.apache.tools.ant.Task; import org.apache.tools.ant.TaskContainer; import org.apache.tools.ant.UnknownElement; +import org.apache.tools.ant.property.LocalProperties; /** * The class to be placed in the ant type definition. @@ -390,6 +391,9 @@ public class MacroInstance extends Task implements DynamicAttribute, TaskContain // need to set the project on unknown element UnknownElement c = copy(macroDef.getNestedTask(), false); c.init(); + LocalProperties localProperties + = LocalProperties.get(getProject()); + localProperties.enterScope(); try { c.perform(); } catch (BuildException ex) { @@ -403,6 +407,7 @@ public class MacroInstance extends Task implements DynamicAttribute, TaskContain } finally { presentElements = null; localAttributes = null; + localProperties.exitScope(); } } } diff --git a/src/main/org/apache/tools/ant/taskdefs/Parallel.java b/src/main/org/apache/tools/ant/taskdefs/Parallel.java index 9ebdf9c9d..750071cc9 100644 --- a/src/main/org/apache/tools/ant/taskdefs/Parallel.java +++ b/src/main/org/apache/tools/ant/taskdefs/Parallel.java @@ -26,6 +26,7 @@ import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Location; import org.apache.tools.ant.Task; import org.apache.tools.ant.TaskContainer; +import org.apache.tools.ant.property.LocalProperties; import org.apache.tools.ant.util.StringUtils; /** @@ -451,6 +452,7 @@ public class Parallel extends Task */ public void run() { try { + LocalProperties.get(getProject()).copy(); thread = Thread.currentThread(); task.perform(); } catch (Throwable t) { diff --git a/src/main/org/apache/tools/ant/taskdefs/Sequential.java b/src/main/org/apache/tools/ant/taskdefs/Sequential.java index 6ef641bf8..2fa9388ab 100644 --- a/src/main/org/apache/tools/ant/taskdefs/Sequential.java +++ b/src/main/org/apache/tools/ant/taskdefs/Sequential.java @@ -23,6 +23,8 @@ import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Task; import org.apache.tools.ant.TaskContainer; +import org.apache.tools.ant.property.LocalProperties; + /** * Sequential is a container task - it can contain other Ant tasks. The nested * tasks are simply executed in sequence. Sequential's primary use is to support @@ -57,9 +59,16 @@ public class Sequential extends Task implements TaskContainer { * @throws BuildException if one of the nested tasks fails. */ public void execute() throws BuildException { - for (Iterator i = nestedTasks.iterator(); i.hasNext();) { - Task nestedTask = (Task) i.next(); - nestedTask.perform(); + LocalProperties localProperties + = LocalProperties.get(getProject()); + localProperties.enterScope(); + try { + for (Iterator i = nestedTasks.iterator(); i.hasNext();) { + Task nestedTask = (Task) i.next(); + nestedTask.perform(); + } + } finally { + localProperties.exitScope(); } } } diff --git a/src/main/org/apache/tools/ant/taskdefs/defaults.properties b/src/main/org/apache/tools/ant/taskdefs/defaults.properties index f0488dc90..6b923e680 100644 --- a/src/main/org/apache/tools/ant/taskdefs/defaults.properties +++ b/src/main/org/apache/tools/ant/taskdefs/defaults.properties @@ -61,6 +61,7 @@ length=org.apache.tools.ant.taskdefs.Length loadfile=org.apache.tools.ant.taskdefs.LoadFile loadproperties=org.apache.tools.ant.taskdefs.LoadProperties loadresource=org.apache.tools.ant.taskdefs.LoadResource +local=org.apache.tools.ant.taskdefs.Local macrodef=org.apache.tools.ant.taskdefs.MacroDef mail=org.apache.tools.ant.taskdefs.email.EmailTask manifest=org.apache.tools.ant.taskdefs.ManifestTask diff --git a/src/tests/antunit/taskdefs/local-test.xml b/src/tests/antunit/taskdefs/local-test.xml new file mode 100644 index 000000000..49b01193e --- /dev/null +++ b/src/tests/antunit/taskdefs/local-test.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

+ +

+ +

+ + + + + + + + + + + + + + + + + + + + + +