diff options
Diffstat (limited to 'framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/PropertyHelper.java')
-rw-r--r-- | framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/PropertyHelper.java | 1215 |
1 files changed, 1215 insertions, 0 deletions
diff --git a/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/PropertyHelper.java b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/PropertyHelper.java new file mode 100644 index 00000000..1dbb280c --- /dev/null +++ b/framework/src/ant/apache-ant-1.9.6/src/main/org/apache/tools/ant/PropertyHelper.java @@ -0,0 +1,1215 @@ +/* + * 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.text.ParsePosition; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.List; +import java.util.Set; +import java.util.Vector; + +import org.apache.tools.ant.property.GetProperty; +import org.apache.tools.ant.property.NullReturn; +import org.apache.tools.ant.property.ParseNextProperty; +import org.apache.tools.ant.property.ParseProperties; +import org.apache.tools.ant.property.PropertyExpander; + +/* ISSUES: + - ns param. It could be used to provide "namespaces" for properties, which + may be more flexible. + - Object value. In ant1.5 String is used for Properties - but it would be nice + to support generic Objects (the property remains immutable - you can't change + the associated object). This will also allow JSP-EL style setting using the + Object if an attribute contains only the property (name="${property}" could + avoid Object->String->Object conversion) + - Currently we "chain" only for get and set property (probably most users + will only need that - if they need more they can replace the top helper). + Need to discuss this and find if we need more. + */ + +/* update for impending Ant 1.8.0: + + - I can't see any reason for ns and would like to deprecate it. + - Replacing chaining with delegates for certain behavioral aspects. + - Object value seems valuable as outlined. + + */ + +/** + * Deals with properties - substitution, dynamic properties, etc. + * + * <p>This code has been heavily restructured for Ant 1.8.0. It is + * expected that custom PropertyHelper implementation that used the + * older chaining mechanism of Ant 1.6 won't work in all cases, and + * its usage is deprecated. The preferred way to customize Ant's + * property handling is by {@link #add adding} {@link + * PropertyHelper.Delegate delegates} of the appropriate subinterface + * and have this implementation use them.</p> + * + * <p>When {@link #parseProperties expanding a string that may contain + * properties} this class will delegate the actual parsing to {@link + * org.apache.tools.ant.property.ParseProperties#parseProperties + * parseProperties} inside the ParseProperties class which in turn + * uses the {@link org.apache.tools.ant.property.PropertyExpander + * PropertyExpander delegates} to find properties inside the string + * and this class to expand the propertiy names found into the + * corresponding values.</p> + * + * <p>When {@link #getProperty looking up a property value} this class + * will first consult all {@link PropertyHelper.PropertyEvaluator + * PropertyEvaluator} delegates and fall back to an internal map of + * "project properties" if no evaluator matched the property name.</p> + * + * <p>When {@link #setProperty setting a property value} this class + * will first consult all {@link PropertyHelper.PropertySetter + * PropertySetter} delegates and fall back to an internal map of + * "project properties" if no setter matched the property name.</p> + * + * @since Ant 1.6 + */ +public class PropertyHelper implements GetProperty { + + // -------------------------------------------------------- + // + // The property delegate interfaces + // + // -------------------------------------------------------- + + /** + * Marker interface for a PropertyHelper delegate. + * @since Ant 1.8.0 + */ + public interface Delegate { + } + + /** + * Looks up a property's value based on its name. + * + * <p>Can be used to look up properties in a different storage + * than the project instance (like local properties for example) + * or to implement custom "protocols" like Ant's + * <code>${toString:refid}</code> syntax.</p> + * + * @since Ant 1.8.0 + */ + public interface PropertyEvaluator extends Delegate { + /** + * Evaluate a property. + * + * @param property the property's String "identifier". + * @param propertyHelper the invoking PropertyHelper. + * @return null if the property name could not be found, an + * instance of {@link org.apache.tools.ant.property.NullReturn + * NullReturn} to indicate a property with a name that can be + * matched but a value of <code>null</code> and the property's + * value otherwise. + */ + Object evaluate(String property, PropertyHelper propertyHelper); + } + + /** + * Sets or overrides a property. + * + * <p>Can be used to store properties in a different storage than + * the project instance (like local properties for example).</p> + * + * @since Ant 1.8.0 + */ + public interface PropertySetter extends Delegate { + /** + * Set a *new" property. + * + * <p>Should not replace the value of an existing property.</p> + * + * @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. + */ + boolean setNew( + String property, Object value, PropertyHelper propertyHelper); + + /** + * Set a property. + * + * <p>May replace the value of an existing property.</p> + * + * @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. + */ + boolean set( + String property, Object value, PropertyHelper propertyHelper); + } + + //TODO PropertyEnumerator Delegate type, would improve PropertySet + + // -------------------------------------------------------- + // + // The predefined property delegates + // + // -------------------------------------------------------- + + private static final PropertyEvaluator TO_STRING = new PropertyEvaluator() { + private final String PREFIX = "toString:"; + private final int PREFIX_LEN = PREFIX.length(); + + public Object evaluate(String property, PropertyHelper propertyHelper) { + Object o = null; + if (property.startsWith(PREFIX) && propertyHelper.getProject() != null) { + o = propertyHelper.getProject().getReference(property.substring(PREFIX_LEN)); + } + return o == null ? null : o.toString(); + } + }; + + private static final PropertyExpander DEFAULT_EXPANDER = new PropertyExpander() { + public String parsePropertyName( + String s, ParsePosition pos, ParseNextProperty notUsed) { + int index = pos.getIndex(); + //directly check near, triggering characters: + if (s.length() - index >= 3 + && '$' == s.charAt(index) && '{' == s.charAt(index + 1)) { + int start = index + 2; + //defer to String.indexOf() for protracted check: + int end = s.indexOf('}', start); + if (end < 0) { + throw new BuildException("Syntax error in property: " + + s.substring(index)); + } + pos.setIndex(end + 1); + return start == end ? "" : s.substring(start, end); + } + return null; + } + }; + + /** dummy */ + private static final PropertyExpander SKIP_DOUBLE_DOLLAR + = new PropertyExpander() { + // CheckStyle:LineLengthCheck OFF see too long + /** + * {@inheritDoc} + * @see org.apache.tools.ant.property.PropertyExpander#parsePropertyName(java.lang.String, java.text.ParsePosition, org.apache.tools.ant.PropertyHelper) + */ + // CheckStyle:LineLengthCheck ON + public String parsePropertyName( + String s, ParsePosition pos, ParseNextProperty notUsed) { + int index = pos.getIndex(); + if (s.length() - index >= 2) { + /* check for $$; if found, advance by one-- + * this expander is at the bottom of the stack + * and will thus be the last consulted, + * so the next thing that ParseProperties will do + * is advance the parse position beyond the second $ + */ + if ('$' == s.charAt(index) && '$' == s.charAt(++index)) { + pos.setIndex(index); + } + } + return null; + } + }; + + /** + * @since Ant 1.8.0 + */ + private static final PropertyEvaluator FROM_REF = new PropertyEvaluator() { + private final String PREFIX = "ant.refid:"; + private final int PREFIX_LEN = PREFIX.length(); + + public Object evaluate(String prop, PropertyHelper helper) { + return prop.startsWith(PREFIX) && helper.getProject() != null + ? helper.getProject().getReference(prop.substring(PREFIX_LEN)) + : null; + } + }; + + private Project project; + private PropertyHelper next; + private final Hashtable<Class<? extends Delegate>, List<Delegate>> delegates = new Hashtable<Class<? extends Delegate>, List<Delegate>>(); + + /** Project properties map (usually String to String). */ + private Hashtable<String, Object> properties = new Hashtable<String, Object>(); + + /** + * Map of "user" properties (as created in the Ant task, for example). + * Note that these key/value pairs are also always put into the + * project properties, so only the project properties need to be queried. + */ + private Hashtable<String, Object> userProperties = new Hashtable<String, Object>(); + + /** + * Map of inherited "user" properties - that are those "user" + * properties that have been created by tasks and not been set + * from the command line or a GUI tool. + */ + private Hashtable<String, Object> inheritedProperties = new Hashtable<String, Object>(); + + /** + * Default constructor. + */ + protected PropertyHelper() { + add(FROM_REF); + add(TO_STRING); + add(SKIP_DOUBLE_DOLLAR); + add(DEFAULT_EXPANDER); + } + + // -------------------------------------------------------- + // + // Some helper static methods to get and set properties + // + // -------------------------------------------------------- + + /** + * A helper static method to get a property + * from a particular project. + * @param project the project in question. + * @param name the property name + * @return the value of the property if present, null otherwise. + * @since Ant 1.8.0 + */ + public static Object getProperty(Project project, String name) { + return PropertyHelper.getPropertyHelper(project) + .getProperty(name); + } + + /** + * A helper static method to set a property + * from a particular project. + * @param project the project in question. + * @param name the property name + * @param value the value to use. + * @since Ant 1.8.0 + */ + public static void setProperty(Project project, String name, Object value) { + PropertyHelper.getPropertyHelper(project) + .setProperty(name, value, true); + } + + /** + * A helper static method to set a new property + * from a particular project. + * @param project the project in question. + * @param name the property name + * @param value the value to use. + * @since Ant 1.8.0 + */ + public static void setNewProperty( + Project project, String name, Object value) { + PropertyHelper.getPropertyHelper(project) + .setNewProperty(name, value); + } + + //override facility for subclasses to put custom hashtables in + + // -------------------- Hook management -------------------- + + /** + * Set the project for which this helper is performing property resolution. + * + * @param p the project instance. + */ + public void setProject(Project p) { + this.project = p; + } + + /** + * Get this PropertyHelper's Project. + * @return Project + */ + public Project getProject() { + return project; + } + + /** + * Prior to Ant 1.8.0 there have been 2 ways to hook into property handling: + * + * - you can replace the main PropertyHelper. The replacement is required + * to support the same semantics (of course :-) + * + * - you can chain a property helper capable of storing some properties. + * Again, you are required to respect the immutability semantics (at + * least for non-dynamic properties) + * + * <p>As of Ant 1.8.0 this method is never invoked by any code + * inside of Ant itself.</p> + * + * @param next the next property helper in the chain. + * @deprecated use the delegate mechanism instead + */ + public void setNext(PropertyHelper next) { + this.next = next; + } + + /** + * Get the next property helper in the chain. + * + * <p>As of Ant 1.8.0 this method is never invoked by any code + * inside of Ant itself except the {@link #setPropertyHook + * setPropertyHook} and {@link #getPropertyHook getPropertyHook} + * methods in this class.</p> + * + * @return the next property helper. + * @deprecated use the delegate mechanism instead + */ + public PropertyHelper getNext() { + return next; + } + + /** + * Factory method to create a property processor. + * Users can provide their own or replace it using "ant.PropertyHelper" + * reference. User tasks can also add themselves to the chain, and provide + * dynamic properties. + * + * @param project the project for which the property helper is required. + * + * @return the project's property helper. + */ + public static synchronized PropertyHelper getPropertyHelper(Project project) { + PropertyHelper helper = null; + if (project != null) { + helper = (PropertyHelper) project.getReference(MagicNames + .REFID_PROPERTY_HELPER); + } + if (helper != null) { + return helper; + } + + helper = new PropertyHelper(); + helper.setProject(project); + + if (project != null) { + project.addReference(MagicNames.REFID_PROPERTY_HELPER, helper); + } + + return helper; + } + + /** + * Get the {@link PropertyExpander expanders}. + * @since Ant 1.8.0 + * @return the expanders. + */ + public Collection<PropertyExpander> getExpanders() { + return getDelegates(PropertyExpander.class); + } + + + // -------------------- Methods to override -------------------- + + /** + * Sets a property. Any existing property of the same name + * is overwritten, unless it is a user property. + * + * If all helpers return false, the property will be saved in + * the default properties table by setProperty. + * + * <p>As of Ant 1.8.0 this method is never invoked by any code + * inside of Ant itself.</p> + * + * @param ns The namespace that the property is in (currently + * not used. + * @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>. + * @param inherited True if this property is inherited (an [sub]ant[call] property). + * @param user True if this property is a user property. + * @param isNew True is this is a new property. + * @return true if this helper has stored the property, false if it + * couldn't. Each helper should delegate to the next one (unless it + * has a good reason not to). + * @deprecated PropertyHelper chaining is deprecated. + */ + public boolean setPropertyHook(String ns, String name, + Object value, + boolean inherited, boolean user, + boolean isNew) { + if (getNext() != null) { + boolean subst = getNext().setPropertyHook(ns, name, value, inherited, user, isNew); + // If next has handled the property + if (subst) { + return true; + } + } + return false; + } + + /** + * Get a property. If all hooks return null, the default + * tables will be used. + * + * <p>As of Ant 1.8.0 this method is never invoked by any code + * inside of Ant itself.</p> + * + * @param ns namespace of the sought property. + * @param name name of the sought property. + * @param user True if this is a user property. + * @return The property, if returned by a hook, or null if none. + * @deprecated PropertyHelper chaining is deprecated. + */ + public Object getPropertyHook(String ns, String name, boolean user) { + if (getNext() != null) { + Object o = getNext().getPropertyHook(ns, name, user); + if (o != null) { + return o; + } + } + // Experimental/Testing, will be removed + if (project != null && name.startsWith("toString:")) { + name = name.substring("toString:".length()); + Object v = project.getReference(name); + return (v == null) ? null : v.toString(); + } + return null; + } + + // -------------------- Optional methods -------------------- + // You can override those methods if you want to optimize or + // do advanced things (like support a special syntax). + // The methods do not chain - you should use them when embedding ant + // (by replacing the main helper) + + /** + * Parses a string containing <code>${xxx}</code> style property + * references into two lists. The first list is a collection + * of text fragments, while the other is a set of string property names. + * <code>null</code> entries in the first list indicate a property + * reference from the second list. + * + * <p>Delegates to {@link #parsePropertyStringDefault + * parsePropertyStringDefault}.</p> + * + * <p>As of Ant 1.8.0 this method is never invoked by any code + * inside of Ant itself except {ProjectHelper#parsePropertyString + * ProjectHelper.parsePropertyString}.</p> + * + * @param value Text to parse. Must not be <code>null</code>. + * @param fragments List to add text fragments to. + * Must not be <code>null</code>. + * @param propertyRefs List to add property names to. + * Must not be <code>null</code>. + * + * @exception BuildException if the string contains an opening + * <code>${</code> without a closing + * <code>}</code> + * @deprecated use the other mechanisms of this class instead + */ + public void parsePropertyString(String value, Vector<String> fragments, + Vector<String> propertyRefs) throws BuildException { + parsePropertyStringDefault(value, fragments, propertyRefs); + } + + /** + * Replaces <code>${xxx}</code> style constructions in the given value + * with the string value of the corresponding data types. + * + * <p>Delegates to the one-arg version, completely ignoring the ns + * and keys parameters.</p> + * + * @param ns The namespace for the property. + * @param value The string to be scanned for property references. + * May be <code>null</code>, in which case this + * method returns immediately with no effect. + * @param keys Mapping (String to Object) of property names to their + * values. If <code>null</code>, only project properties will + * be used. + * + * @exception BuildException if the string contains an opening + * <code>${</code> without a closing + * <code>}</code> + * @return the original string with the properties replaced, or + * <code>null</code> if the original string is <code>null</code>. + */ + //TODO deprecate? Recall why no longer using ns/keys params + public String replaceProperties(String ns, String value, Hashtable<String, Object> keys) throws BuildException { + return replaceProperties(value); + } + + /** + * Replaces <code>${xxx}</code> 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>, in which case this + * method returns immediately with no effect. + * + * @exception BuildException if the string contains an opening + * <code>${</code> without a closing + * <code>}</code> + * @return the original string with the properties replaced, or + * <code>null</code> if the original string is <code>null</code>. + */ + public String replaceProperties(String value) throws BuildException { + Object o = parseProperties(value); + return o == null || o instanceof String ? (String) o : o.toString(); + } + + /** + * Decode properties from a String representation. If the entire + * contents of the String resolve to a single property, that value + * is returned. Otherwise a String is returned. + * + * @param value The string to be scanned for property references. + * May be <code>null</code>, in which case this + * method returns immediately with no effect. + * + * @exception BuildException if the string contains an opening + * <code>${</code> without a closing + * <code>}</code> + * @return the original string with the properties replaced, or + * <code>null</code> if the original string is <code>null</code>. + */ + public Object parseProperties(String value) throws BuildException { + return new ParseProperties(getProject(), getExpanders(), this) + .parseProperties(value); + } + + /** + * Learn whether a String contains replaceable properties. + * @param value the String to check. + * @return <code>true</code> if <code>value</code> contains property notation. + */ + public boolean containsProperties(String value) { + return new ParseProperties(getProject(), getExpanders(), this) + .containsProperties(value); + } + + // -------------------- Default implementation -------------------- + // Methods used to support the default behavior and provide backward + // compatibility. Some will be deprecated, you should avoid calling them. + + /** + * Default implementation of setProperty. Will be called from Project. + * This is the original 1.5 implementation, with calls to the hook + * added. + * + * <p>Delegates to the three-arg version, completely ignoring the + * ns parameter.</p> + * + * @param ns The namespace for the property (currently not used). + * @param name The name of the property. + * @param value The value to set the property to. + * @param verbose If this is true output extra log messages. + * @return true if the property is set. + * @deprecated namespaces are unnecessary. + */ + public boolean setProperty(String ns, String name, Object value, boolean verbose) { + return setProperty(name, value, verbose); + } + + /** + * Default implementation of setProperty. Will be called from Project. + * @param name The name of the property. + * @param value The value to set the property to. + * @param verbose If this is true output extra log messages. + * @return true if the property is set. + */ + public boolean setProperty(String name, Object value, boolean verbose) { + for (PropertySetter setter : getDelegates(PropertySetter.class)) { + if (setter.set(name, value, this)) { + return true; + } + } + synchronized (this) { + // user (CLI) properties take precedence + if (userProperties.containsKey(name)) { + if (project != null && verbose) { + project.log("Override ignored for user property \"" + + name + "\"", Project.MSG_VERBOSE); + } + return false; + } + if (project != null && verbose) { + if (properties.containsKey(name)) { + project.log("Overriding previous definition of property \"" + + name + "\"", Project.MSG_VERBOSE); + } + project.log("Setting project property: " + name + " -> " + + value, Project.MSG_DEBUG); + } + if (name != null && value != null) { + properties.put(name, value); + } + return true; + } + } + + /** + * Sets a property if no value currently exists. If the property + * exists already, a message is logged and the method returns with + * no other effect. + * + * <p>Delegates to the two-arg version, completely ignoring the + * ns parameter.</p> + * + * @param ns The namespace for the property (currently not used). + * @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 Ant 1.6 + * @deprecated namespaces are unnecessary. + */ + public void setNewProperty(String ns, String name, Object value) { + setNewProperty(name, value); + } + + /** + * Sets 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 Ant 1.8.0 + */ + public void setNewProperty(String name, Object value) { + for (PropertySetter setter : getDelegates(PropertySetter.class)) { + if (setter.setNew(name, value, this)) { + return; + } + } + synchronized (this) { + if (project != null && properties.containsKey(name)) { + project.log("Override ignored for property \"" + name + + "\"", Project.MSG_VERBOSE); + return; + } + if (project != null) { + project.log("Setting project property: " + name + + " -> " + value, Project.MSG_DEBUG); + } + if (name != null && value != null) { + properties.put(name, value); + } + } + } + + /** + * Sets a user property, which cannot be overwritten by + * set/unset property calls. Any previous value is overwritten. + * + * <p>Delegates to the two-arg version, completely ignoring the + * ns parameter.</p> + * + * @param ns The namespace for the property (currently not used). + * @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>. + * @deprecated namespaces are unnecessary. + */ + public void setUserProperty(String ns, String name, Object value) { + setUserProperty(name, value); + } + + /** + * Sets a user property, which cannot be overwritten by + * set/unset property calls. Any previous value is overwritten. + * + * <p>Does <code>not</code> consult any delegates.</p> + * + * @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 setUserProperty(String name, Object value) { + if (project != null) { + project.log("Setting ro project property: " + + name + " -> " + value, Project.MSG_DEBUG); + } + synchronized (this) { + userProperties.put(name, value); + properties.put(name, value); + } + } + + /** + * Sets an inherited 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. + * + * <p>Delegates to the two-arg version, completely ignoring the + * ns parameter.</p> + * + * @param ns The namespace for the property (currently not used). + * @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>. + * @deprecated namespaces are unnecessary. + */ + public void setInheritedProperty(String ns, String name, Object value) { + setInheritedProperty(name, value); + } + + /** + * Sets an inherited 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. + * + * <p>Does <code>not</code> consult any delegates.</p> + * + * @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 setInheritedProperty(String name, Object value) { + if (project != null) { + project.log("Setting ro project property: " + name + " -> " + + value, Project.MSG_DEBUG); + } + + synchronized (this) { + inheritedProperties.put(name, value); + userProperties.put(name, value); + properties.put(name, value); + } + } + + // -------------------- Getting properties -------------------- + + /** + * Returns the value of a property, if it is set. You can override + * this method in order to plug your own storage. + * + * <p>Delegates to the one-arg version ignoring the ns parameter.</p> + * + * @param ns The namespace for the property (currently not used). + * @param name 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. + * @deprecated namespaces are unnecessary. + */ + public Object getProperty(String ns, String name) { + return getProperty(name); + } + + /** + * Returns the value of a property, if it is set. + * + * <p>This is the method that is invoked by {Project#getProperty + * Project.getProperty}.</p> + * + * <p>You can override this method in order to plug your own + * storage but the recommended approach is to add your own + * implementation of {@link PropertyEvaluator PropertyEvaluator} + * instead.</p> + * + * @param name 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 Object getProperty(String name) { + if (name == null) { + return null; + } + for (PropertyEvaluator evaluator : getDelegates(PropertyEvaluator.class)) { + final Object o = evaluator.evaluate(name, this); + if (o == null) { + continue; + } + return o instanceof NullReturn ? null : o; + } + return properties.get(name); + } + + /** + * Returns the value of a user property, if it is set. + * + * <p>Delegates to the one-arg version ignoring the ns parameter.</p> + * + * @param ns The namespace for the property (currently not used). + * @param name 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. + * @deprecated namespaces are unnecessary. + */ + public Object getUserProperty(String ns, String name) { + return getUserProperty(name); + } + + /** + * Returns the value of a user property, if it is set. + * + * <p>Does <code>not</code> consult any delegates.</p> + * + * @param name 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 Object getUserProperty(String name) { + if (name == null) { + return null; + } + return userProperties.get(name); + } + + // -------------------- Access to property tables -------------------- + // This is used to support ant call and similar tasks. It should be + // deprecated, it is possible to use a better (more efficient) + // mechanism to preserve the context. + + /** + * Returns a copy of the properties table. + * + * <p>Does not contain properties held by implementations of + * delegates (like local properties).</p> + * + * @return a hashtable containing all properties (including user properties). + */ + public Hashtable<String, Object> getProperties() { + //avoid concurrent modification: + synchronized (properties) { + return new Hashtable<String, Object>(properties); + } + // There is a better way to save the context. This shouldn't + // delegate to next, it's for backward compatibility only. + } + + /** + * Returns a copy of the user property hashtable + * + * <p>Does not contain properties held by implementations of + * delegates (like local properties).</p> + * + * @return a hashtable containing just the user properties + */ + public Hashtable<String, Object> getUserProperties() { + //avoid concurrent modification: + synchronized (userProperties) { + return new Hashtable<String, Object>(userProperties); + } + } + + /** + * Returns a copy of the inherited property hashtable + * + * <p>Does not contain properties held by implementations of + * delegates (like local properties).</p> + * + * @return a hashtable containing just the inherited properties + */ + public Hashtable<String, Object> getInheritedProperties() { + //avoid concurrent modification: + synchronized (inheritedProperties) { + return new Hashtable<String, Object>(inheritedProperties); + } + } + + /** + * special back door for subclasses, internal access to the hashtables + * @return the live hashtable of all properties + */ + protected Hashtable<String, Object> getInternalProperties() { + return properties; + } + + /** + * special back door for subclasses, internal access to the hashtables + * + * @return the live hashtable of user properties + */ + protected Hashtable<String, Object> getInternalUserProperties() { + return userProperties; + } + + /** + * special back door for subclasses, internal access to the hashtables + * + * @return the live hashtable inherited properties + */ + protected Hashtable<String, Object> getInternalInheritedProperties() { + return inheritedProperties; + } + + /** + * Copies 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> + * + * <p>Does not copy properties held by implementations of + * delegates (like local properties).</p> + * + * @param other the project to copy the properties to. Must not be null. + * + * @since Ant 1.6 + */ + public void copyInheritedProperties(Project other) { + //avoid concurrent modification: + synchronized (inheritedProperties) { + Enumeration<String> e = inheritedProperties.keys(); + while (e.hasMoreElements()) { + String arg = e.nextElement().toString(); + if (other.getUserProperty(arg) != null) { + continue; + } + Object value = inheritedProperties.get(arg); + other.setInheritedProperty(arg, value.toString()); + } + } + } + + /** + * Copies 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> + * + * <p>Does not copy properties held by implementations of + * delegates (like local properties).</p> + * + * @param other the project to copy the properties to. Must not be null. + * + * @since Ant 1.6 + */ + public void copyUserProperties(Project other) { + //avoid concurrent modification: + synchronized (userProperties) { + Enumeration<String> e = userProperties.keys(); + while (e.hasMoreElements()) { + Object arg = e.nextElement(); + if (inheritedProperties.containsKey(arg)) { + continue; + } + Object value = userProperties.get(arg); + other.setUserProperty(arg.toString(), value.toString()); + } + } + } + + // -------------------- Property parsing -------------------- + // Moved from ProjectHelper. You can override the static method - + // this is used for backward compatibility (for code that calls + // the parse method in ProjectHelper). + + /** + * Default parsing method. It is here only to support backward compatibility + * for the static ProjectHelper.parsePropertyString(). + */ + static void parsePropertyStringDefault(String value, Vector<String> fragments, Vector<String> propertyRefs) + throws BuildException { + int prev = 0; + int pos; + //search for the next instance of $ from the 'prev' position + while ((pos = value.indexOf("$", prev)) >= 0) { + + //if there was any text before this, add it as a fragment + //TODO, this check could be modified to go if pos>prev; + //seems like this current version could stick empty strings + //into the list + if (pos > 0) { + fragments.addElement(value.substring(prev, pos)); + } + //if we are at the end of the string, we tack on a $ + //then move past it + if (pos == (value.length() - 1)) { + fragments.addElement("$"); + prev = pos + 1; + } else if (value.charAt(pos + 1) != '{') { + //peek ahead to see if the next char is a property or not + //not a property: insert the char as a literal + /* + fragments.addElement(value.substring(pos + 1, pos + 2)); + prev = pos + 2; + */ + if (value.charAt(pos + 1) == '$') { + //backwards compatibility two $ map to one mode + fragments.addElement("$"); + prev = pos + 2; + } else { + //new behaviour: $X maps to $X for all values of X!='$' + fragments.addElement(value.substring(pos, pos + 2)); + prev = pos + 2; + } + } else { + //property found, extract its name or bail on a typo + int endName = value.indexOf('}', pos); + if (endName < 0) { + throw new BuildException("Syntax error in property: " + value); + } + String propertyName = value.substring(pos + 2, endName); + fragments.addElement(null); + propertyRefs.addElement(propertyName); + prev = endName + 1; + } + } + //no more $ signs found + //if there is any tail to the file, append it + if (prev < value.length()) { + fragments.addElement(value.substring(prev)); + } + } + + /** + * Add the specified delegate object to this PropertyHelper. + * Delegates are processed in LIFO order. + * @param delegate the delegate to add. + * @since Ant 1.8.0 + */ + public void add(Delegate delegate) { + synchronized (delegates) { + for (Class<? extends Delegate> key : getDelegateInterfaces(delegate)) { + List<Delegate> list = delegates.get(key); + if (list == null) { + list = new ArrayList<Delegate>(); + } else { + //copy on write, top priority + list = new ArrayList<Delegate>(list); + list.remove(delegate); + } + list.add(0, delegate); + delegates.put(key, Collections.unmodifiableList(list)); + } + } + } + + /** + * Get the Collection of delegates of the specified type. + * + * @param type + * delegate type. + * @return Collection. + * @since Ant 1.8.0 + */ + protected <D extends Delegate> List<D> getDelegates(Class<D> type) { + @SuppressWarnings("unchecked") + final List<D> result = (List<D>) delegates.get(type); + return result == null ? Collections.<D> emptyList() : result; + } + + /** + * Get all Delegate interfaces (excluding Delegate itself) from the specified Delegate. + * @param d the Delegate to inspect. + * @return Set<Class> + * @since Ant 1.8.0 + */ + protected static Set<Class<? extends Delegate>> getDelegateInterfaces(Delegate d) { + final HashSet<Class<? extends Delegate>> result = new HashSet<Class<? extends Delegate>>(); + Class<?> c = d.getClass(); + while (c != null) { + Class<?>[] ifs = c.getInterfaces(); + for (int i = 0; i < ifs.length; i++) { + if (Delegate.class.isAssignableFrom(ifs[i])) { + @SuppressWarnings("unchecked") + final Class<? extends Delegate> delegateInterface = (Class<? extends Delegate>) ifs[i]; + result.add(delegateInterface); + } + } + c = c.getSuperclass(); + } + result.remove(Delegate.class); + return result; + } + + /** + * If the given object can be interpreted as a true/false value, + * turn it into a matching Boolean - otherwise return null. + * @since Ant 1.8.0 + */ + public static Boolean toBoolean(Object value) { + if (value instanceof Boolean) { + return (Boolean) value; + } + if (value instanceof String) { + String s = (String) value; + if (Project.toBoolean(s)) { + return Boolean.TRUE; + } + if ("off".equalsIgnoreCase(s) + || "false".equalsIgnoreCase(s) + || "no".equalsIgnoreCase(s)) { + return Boolean.FALSE; + } + } + return null; + } + + /** + * Returns true if the object is null or an empty string. + * + * @since Ant 1.8.0 + */ + private static boolean nullOrEmpty(Object value) { + return value == null || "".equals(value); + + } + + /** + * Returns true if the value can be interpreted as a true value or + * cannot be interpreted as a false value and a property of the + * value's name exists. + * @since Ant 1.8.0 + */ + private boolean evalAsBooleanOrPropertyName(Object value) { + Boolean b = toBoolean(value); + if (b != null) { + return b.booleanValue(); + } + return getProperty(String.valueOf(value)) != null; + } + + /** + * Returns true if the value is null or an empty string, can be + * interpreted as a true value or cannot be interpreted as a false + * value and a property of the value's name exists. + * @since Ant 1.8.0 + */ + public boolean testIfCondition(Object value) { + return nullOrEmpty(value) || evalAsBooleanOrPropertyName(value); + } + + /** + * Returns true if the value is null or an empty string, can be + * interpreted as a false value or cannot be interpreted as a true + * value and a property of the value's name doesn't exist. + * @since Ant 1.8.0 + */ + public boolean testUnlessCondition(Object value) { + return nullOrEmpty(value) || !evalAsBooleanOrPropertyName(value); + } +} |