diff --git a/src/main/org/apache/tools/ant/PropertyHelper.java b/src/main/org/apache/tools/ant/PropertyHelper.java index 24465def6..55867a4f0 100644 --- a/src/main/org/apache/tools/ant/PropertyHelper.java +++ b/src/main/org/apache/tools/ant/PropertyHelper.java @@ -18,8 +18,13 @@ package org.apache.tools.ant; import java.util.Hashtable; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; import java.util.Vector; import java.util.Enumeration; +import java.util.List; +import java.util.ArrayList; /* ISSUES: @@ -35,7 +40,7 @@ import java.util.Enumeration; Need to discuss this and find if we need more. */ -/** NOT FINAL. API MAY CHANGE +/** * * Deals with properties - substitution, dynamic properties, etc. * @@ -46,11 +51,30 @@ import java.util.Enumeration; */ public class PropertyHelper { + /** + * Opaque interface for localproperties + * Allows a user to retrive, copy and replace + * the localproperties - currently used by the + * parallel task. + */ + public interface LocalProperties { + /** + * @return a copy of the local properties + */ + LocalProperties copy(); + } + + + /** Local Properties */ + private ThreadLocalProperties threadLocalProperties + = new ThreadLocalProperties(); + + private Project project; private PropertyHelper next; /** Project properties map (usually String to String). */ - private Hashtable properties = new Hashtable(); + private HashMap properties = new HashMap(); // Contains normal and user properties /** * Map of "user" properties (as created in the Ant task, for example). @@ -167,6 +191,14 @@ public class PropertyHelper { return true; } } + + // Check if this is a local property + LocalProperty l = threadLocalProperties.getLocalProperty(name); + if (l != null) { + l.setValue(value); + return true; + } + return false; } @@ -185,6 +217,11 @@ public class PropertyHelper { return o; } } + LocalProperty l = threadLocalProperties.getLocalProperty(name); + if (l != null) { + return l.getValue(); + } + // Experimental/Testing, will be removed if (name.startsWith("toString:")) { name = name.substring("toString:".length()); @@ -194,6 +231,72 @@ public class PropertyHelper { return null; } + /** + * @return the local properties + */ + public LocalProperties getLocalProperties() { + return (LocalProperties) threadLocalProperties.get(); + } + + /** + * Set the local properties + * @param localProperties the new local properties, may be null. + */ + public void setLocalProperties(LocalProperties localProperties) { + if (localProperties == null) { + localProperties = new LocalPropertyStack(); + } + threadLocalProperties.set(localProperties); + } + + /** + * Set the local properties without overriding the user props + * Used by ant.java to set the local properties, without + * modifing the user properties set in the param elements. + * @param localProperties the new local properties, may be null. + */ + public void setNotOverrideLocalProperties( + LocalProperties localProperties) { + if (localProperties == null) { + localProperties = new LocalPropertyStack(); + } + LocalPropertyStack s = (LocalPropertyStack) localProperties; + for (Iterator i = s.props.entrySet().iterator(); i.hasNext();) { + Map.Entry entry = (Map.Entry) i.next(); + if (userProperties.get(entry.getKey()) != null) { + i.remove(); + } + } + threadLocalProperties.set(localProperties); + } + + /** + * Add a local property, with an optional initial value + * + * @param name the name of the local property + * @param value the initial value of the localproperty, may be null + */ + public void addLocalProperty(String name, Object value) { + threadLocalProperties.addProperty(name, value); + } + + /** + * A new scope for local properties. + * + */ + public void enterLocalPropertyScope() { + threadLocalProperties.enterLocalPropertyScope(); + } + + /** + * Exit a scope of local properties, removing the + * local properties in the scope. + * + */ + public void exitLocalPropertyScope() { + threadLocalProperties.exitLocalPropertyScope(); + } + // -------------------- Optional methods -------------------- // You can override those methods if you want to optimize or // do advanced things (like support a special syntax). @@ -341,9 +444,15 @@ public class PropertyHelper { */ public synchronized void setNewProperty(String ns, String name, Object value) { - if (null != properties.get(name)) { + LocalProperty local = threadLocalProperties.getLocalProperty(name); + boolean localPropertySet = + local != null && local.getValue() != null; + boolean localProperty = local != null; + + if ((properties.get(name) != null && !localProperty) + || localPropertySet) { project.log("Override ignored for property \"" + name - + "\"", Project.MSG_VERBOSE); + + "\"", Project.MSG_VERBOSE); return; } @@ -427,7 +536,7 @@ public class PropertyHelper { } Object o = getPropertyHook(ns, name, false); - if (o != null) { + if (o != null || threadLocalProperties.getLocalProperty(name) != null) { return o; } @@ -451,6 +560,11 @@ public class PropertyHelper { if (o != null) { return o; } + // check if null local property + if (threadLocalProperties.getLocalProperty(name) != null) { + return null; + } + return userProperties.get(name); } @@ -460,15 +574,32 @@ public class PropertyHelper { // deprecated, it is possible to use a better (more efficient) // mechanism to preserve the context. - // TODO: do we need to delegate ? /** * Returns a copy of the properties table. * @return a hashtable containing all properties - * (including user properties). + * (including user properties and local properties). */ public Hashtable getProperties() { - return new Hashtable(properties); + System.out.println("GetProperties called"); + Hashtable ret = new Hashtable(properties); + Map locals = threadLocalProperties.getProps(); + for (Iterator i = locals.entrySet().iterator(); i.hasNext();) { + Map.Entry e = (Map.Entry) i.next(); + List l = (List) e.getValue(); + if (l != null && l.size() > 0) { + LocalProperty p = (LocalProperty) l.get(l.size() - 1); + if (p.getValue() == null) { + if (ret.get(e.getKey()) != null) { + ret.remove(e.getKey()); + } + } else { + ret.put(e.getKey(), p.getValue()); + } + } + } + return ret; + // There is a better way to save the context. This shouldn't // delegate to next, it's for backward compatibility only. } @@ -481,6 +612,24 @@ public class PropertyHelper { return new Hashtable(userProperties); } + /** + * Returns a copy of the local properties + * @return a map containing the local properties as string->string + */ + public Map getLocalPropertiesCopy() { + Map copy = new HashMap(); + Map locals = threadLocalProperties.getProps(); + for (Iterator i = locals.entrySet().iterator(); i.hasNext();) { + Map.Entry e = (Map.Entry) i.next(); + List l = (List) e.getValue(); + if (l != null && l.size() > 0) { + LocalProperty p = (LocalProperty) l.get(l.size() - 1); + copy.put(e.getKey(), p.getValue()); + } + } + return copy; + } + /** * Copies all user properties that have not been set on the * command line or a GUI tool from this instance to the Project @@ -594,4 +743,174 @@ public class PropertyHelper { } } + /** + * A holder class for a local property value + */ + private class LocalProperty { + private int level; + private Object value; + public LocalProperty(int level, Object value) { + this.level = level; + this.value = value; + } + + public LocalProperty copy() { + return new LocalProperty(level, value); + } + + public int getLevel() { + return level; + } + + public Object getValue() { + return value; + } + + void setValue(Object value) { + this.value = value; + } + } + + /** + * A class implementing a local property stack. + */ + private class LocalPropertyStack + implements LocalProperties { + private int level = 0; + // HashMap> + private HashMap props = new HashMap(); + + // ArrayList> + private List stack = new ArrayList(); + + public LocalProperties copy() { + LocalPropertyStack copy = new LocalPropertyStack(); + copy.stack = new ArrayList(); + copy.level = level; + for (int i = 0; i < stack.size(); ++i) { + copy.stack.add(((ArrayList) stack.get(i)).clone()); + } + copy.props = new HashMap(); + for (Iterator i = props.entrySet().iterator(); i.hasNext();) { + Map.Entry entry = (Map.Entry) i.next(); + ArrayList from = (ArrayList) entry.getValue(); + List l2 = new ArrayList(); + for (Iterator l = from.iterator(); l.hasNext();) { + LocalProperty v = (LocalProperty) l.next(); + l2.add(v.copy()); + } + copy.props.put(entry.getKey(), l2); + } + return copy; + } + + public LocalProperties shallowCopy() { + LocalPropertyStack copy = new LocalPropertyStack(); + copy.stack = new ArrayList(); + copy.level = level; + for (int i = 0; i < stack.size(); ++i) { + copy.stack.add(((ArrayList) stack.get(i)).clone()); + } + copy.props = new HashMap(); + for (Iterator i = props.entrySet().iterator(); i.hasNext();) { + Map.Entry entry = (Map.Entry) i.next(); + ArrayList from = (ArrayList) entry.getValue(); + List l2 = new ArrayList(); + for (Iterator l = from.iterator(); l.hasNext();) { + LocalProperty v = (LocalProperty) l.next(); + l2.add(v); + } + copy.props.put(entry.getKey(), l2); + } + return copy; + } + + public void enterLocalPropertyScope() { + stack.add(new ArrayList()); + level++; + } + + public void addProperty(String name, Object value) { + if (stack.size() == 0) { + return; + } + List list = (List) stack.get(stack.size() - 1); + list.add(name); + List local = (List) props.get(name); + if (local == null) { + local = new ArrayList(); + props.put(name, local); + } else { + LocalProperty l = (LocalProperty) local.get(local.size() - 1); + if (l.getLevel() == level) { + throw new BuildException( + "Attempt to add another local of the same name"); + } + } + LocalProperty l = new LocalProperty(level, value); + local.add(l); + } + + public void exitLocalPropertyScope() { + if (stack.size() == 0) { + return; + } + level--; + List list = (List) stack.remove(stack.size() - 1); + for (Iterator i = list.iterator(); i.hasNext();) { + String name = (String) i.next(); + List local = (List) props.get(name); + if (local != null && local.size() != 0) { + local.remove(local.size() - 1); + if (local.size() == 0) { + props.remove(name); + } + } + } + } + + public LocalProperty getLocalProperty(String name) { + List l = (List) props.get(name); + if (l != null && l.size() != 0) { + return (LocalProperty) l.get(l.size() - 1); + } + return null; + } + + public Map getProps() { + return props; + } + } + + /** + * A set of local properties stack for each thread + */ + + private class ThreadLocalProperties extends InheritableThreadLocal { + protected synchronized Object initialValue() { + return new LocalPropertyStack(); + } + protected synchronized Object childValue(Object obj) { + return ((LocalPropertyStack) obj).shallowCopy(); + } + public LocalProperty getLocalProperty(String name) { + return ((LocalPropertyStack) get()).getLocalProperty(name); + } + + public void enterLocalPropertyScope() { + ((LocalPropertyStack) get()).enterLocalPropertyScope(); + } + + public void addProperty(String name, Object value) { + ((LocalPropertyStack) get()).addProperty(name, value); + } + + public void exitLocalPropertyScope() { + ((LocalPropertyStack) get()).exitLocalPropertyScope(); + } + public Map getProps() { + return ((LocalPropertyStack) get()).getProps(); + } + } + } diff --git a/src/main/org/apache/tools/ant/taskdefs/Ant.java b/src/main/org/apache/tools/ant/taskdefs/Ant.java index f98594f64..cadd53539 100644 --- a/src/main/org/apache/tools/ant/taskdefs/Ant.java +++ b/src/main/org/apache/tools/ant/taskdefs/Ant.java @@ -35,6 +35,7 @@ import org.apache.tools.ant.Executor; import org.apache.tools.ant.Project; import org.apache.tools.ant.ProjectComponent; import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.PropertyHelper; import org.apache.tools.ant.Target; import org.apache.tools.ant.Task; import org.apache.tools.ant.helper.SingleCheckExecutor; @@ -442,6 +443,16 @@ public class Ant extends Task { p.setProject(newProject); p.execute(); } + // Do local properties second + if (inheritAll) { + // Only copy them if they have not been set + PropertyHelper newHelper = + PropertyHelper.getPropertyHelper(newProject); + PropertyHelper oldHelper = + PropertyHelper.getPropertyHelper(getProject()); + newHelper.setNotOverrideLocalProperties( + oldHelper.getLocalProperties().copy()); + } getProject().copyInheritedProperties(newProject); } diff --git a/src/main/org/apache/tools/ant/taskdefs/MacroDef.java b/src/main/org/apache/tools/ant/taskdefs/MacroDef.java index 331e21376..2a1e3f99e 100644 --- a/src/main/org/apache/tools/ant/taskdefs/MacroDef.java +++ b/src/main/org/apache/tools/ant/taskdefs/MacroDef.java @@ -44,6 +44,7 @@ public class MacroDef extends AntlibDefinition { private String name; private List attributes = new ArrayList(); private Map elements = new HashMap(); + private Map localProperties = new HashMap(); private String textName = null; private Text text = null; private boolean hasImplicitElement = false; @@ -294,6 +295,58 @@ public class MacroDef extends AntlibDefinition { elements.put(element.getName(), element); } + /** + * A localproperty nested element. + * @param el a localproperty nested element + * @throws BuildException if the name of the element is not set or if a + * duplicate name is used + */ + public void addConfiguredLocalProperty(LocalPropertyElement el) { + if (el.getName() == null) { + throw new BuildException( + "the 'localproperty' nested element needed a \"name\" attribute"); + } + if (localProperties.get(el.getName()) != null) { + throw new BuildException( + "the localproperty " + el.getName() + + " has already been specified"); + } + localProperties.put(el.getName(), el); + } + + /** + * Get the map of local properties specified by this macrodef. + * @return the localproperties map + */ + public Map getLocalProperties() { + return localProperties; + } + + /** + * A class to represent a local property nested element. + */ + public static class LocalPropertyElement { + + private String name; + + /** + * An attribute called "name". + * @param name the name value. + */ + public void setName(String name) { + this.name = name; + } + + /** + * Get the value of the "name" attribute. + * @return the name value + */ + public String getName() { + return name; + } + } + + /** * Create a new ant type based on the embedded tasks and types. * diff --git a/src/main/org/apache/tools/ant/taskdefs/MacroInstance.java b/src/main/org/apache/tools/ant/taskdefs/MacroInstance.java index 27f67afef..fbd0f2d9e 100644 --- a/src/main/org/apache/tools/ant/taskdefs/MacroInstance.java +++ b/src/main/org/apache/tools/ant/taskdefs/MacroInstance.java @@ -31,6 +31,7 @@ import java.util.Enumeration; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.DynamicAttribute; import org.apache.tools.ant.ProjectHelper; +import org.apache.tools.ant.PropertyHelper; import org.apache.tools.ant.RuntimeConfigurable; import org.apache.tools.ant.Target; import org.apache.tools.ant.Task; @@ -49,7 +50,7 @@ public class MacroInstance extends Task implements DynamicAttribute, TaskContain private Map map = new HashMap(); private Map nsElements = null; private Map presentElements; - private Hashtable localProperties; + private Hashtable localAttributes; private String text = null; private String implicitTag = null; private List unknownElements = new ArrayList(); @@ -262,10 +263,10 @@ public class MacroInstance extends Task implements DynamicAttribute, TaskContain Map.Entry entry = (Map.Entry) i.next(); rc.setAttribute( (String) entry.getKey(), - macroSubs((String) entry.getValue(), localProperties)); + macroSubs((String) entry.getValue(), localAttributes)); } rc.addText(macroSubs(ue.getWrapper().getText().toString(), - localProperties)); + localAttributes)); Enumeration e = ue.getWrapper().getChildren(); while (e.hasMoreElements()) { @@ -321,10 +322,19 @@ public class MacroInstance extends Task implements DynamicAttribute, TaskContain * */ public void execute() { + PropertyHelper propertyHelper = + PropertyHelper.getPropertyHelper(getProject()); + propertyHelper.enterLocalPropertyScope(); + for (Iterator i = macroDef.getLocalProperties().values().iterator(); + i.hasNext();) { + MacroDef.LocalPropertyElement el = (MacroDef.LocalPropertyElement) i.next(); + propertyHelper.addLocalProperty(el.getName(), null); + } + presentElements = new HashMap(); getNsElements(); processTasks(); - localProperties = new Hashtable(); + localAttributes = new Hashtable(); Set copyKeys = new HashSet(map.keySet()); for (Iterator i = macroDef.getAttributes().iterator(); i.hasNext();) { MacroDef.Attribute attribute = (MacroDef.Attribute) i.next(); @@ -334,7 +344,7 @@ public class MacroInstance extends Task implements DynamicAttribute, TaskContain } if (value == null) { value = attribute.getDefault(); - value = macroSubs(value, localProperties); + value = macroSubs(value, localAttributes); } else if (attribute instanceof MacroDef.DefineAttribute) { // Do not process given value, will fail as unknown attribute continue; @@ -343,7 +353,7 @@ public class MacroInstance extends Task implements DynamicAttribute, TaskContain throw new BuildException( "required attribute " + attribute.getName() + " not set"); } - localProperties.put(attribute.getName(), value); + localAttributes.put(attribute.getName(), value); copyKeys.remove(attribute.getName()); } if (copyKeys.contains("id")) { @@ -360,7 +370,7 @@ public class MacroInstance extends Task implements DynamicAttribute, TaskContain if (macroDef.getText().getTrim()) { text = text.trim(); } - localProperties.put(macroDef.getText().getName(), text); + localAttributes.put(macroDef.getText().getName(), text); } else { if (text != null && !text.trim().equals("")) { throw new BuildException( @@ -382,8 +392,10 @@ public class MacroInstance extends Task implements DynamicAttribute, TaskContain } catch (BuildException ex) { throw ProjectHelper.addLocationToBuildException( ex, getLocation()); + } finally { + presentElements = null; + localAttributes = null; + propertyHelper.exitLocalPropertyScope(); } - presentElements = null; - localProperties = null; } }