/* * 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. * *
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.
* *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.
* *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.
* *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.
* * @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. * *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
* ${toString:refid}
syntax.
null
and the property's
* value otherwise.
*/
Object evaluate(String property, PropertyHelper propertyHelper);
}
/**
* Sets or overrides a property.
*
* Can be used to store properties in a different storage than * the project instance (like local properties for example).
* * @since Ant 1.8.0 */ public interface PropertySetter extends Delegate { /** * Set a *new" property. * *Should not replace the value of an existing 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. */ boolean setNew( String property, Object value, PropertyHelper propertyHelper); /** * Set a property. * *May replace the value of an existing 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. */ 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 HashtableAs of Ant 1.8.0 this method is never invoked by any code * inside of Ant itself.
* * @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. * *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.
* * @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 CollectionAs of Ant 1.8.0 this method is never invoked by any code * inside of Ant itself.
* * @param ns The namespace that the property is in (currently * not used. * @param name The name of property to set. * Must not benull
.
* @param value The new value of the property.
* Must not be null
.
* @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.
*
* As of Ant 1.8.0 this method is never invoked by any code * inside of Ant itself.
* * @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${xxx}
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.
* null
entries in the first list indicate a property
* reference from the second list.
*
* Delegates to {@link #parsePropertyStringDefault * parsePropertyStringDefault}.
* *As of Ant 1.8.0 this method is never invoked by any code * inside of Ant itself except {ProjectHelper#parsePropertyString * ProjectHelper.parsePropertyString}.
* * @param value Text to parse. Must not benull
.
* @param fragments List to add text fragments to.
* Must not be null
.
* @param propertyRefs List to add property names to.
* Must not be null
.
*
* @exception BuildException if the string contains an opening
* ${
without a closing
* }
* @deprecated use the other mechanisms of this class instead
*/
public void parsePropertyString(String value, Vector${xxx}
style constructions in the given value
* with the string value of the corresponding data types.
*
* Delegates to the one-arg version, completely ignoring the ns * and keys parameters.
* * @param ns The namespace for the property. * @param value The string to be scanned for property references. * May benull
, in which case this
* method returns immediately with no effect.
* @param keys Mapping (String to Object) of property names to their
* values. If null
, only project properties will
* be used.
*
* @exception BuildException if the string contains an opening
* ${
without a closing
* }
* @return the original string with the properties replaced, or
* null
if the original string is null
.
*/
//TODO deprecate? Recall why no longer using ns/keys params
public String replaceProperties(String ns, String value, Hashtable${xxx}
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 null
, in which case this
* method returns immediately with no effect.
*
* @exception BuildException if the string contains an opening
* ${
without a closing
* }
* @return the original string with the properties replaced, or
* null
if the original string is null
.
*/
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 null
, in which case this
* method returns immediately with no effect.
*
* @exception BuildException if the string contains an opening
* ${
without a closing
* }
* @return the original string with the properties replaced, or
* null
if the original string is null
.
*/
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 true
if value
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.
*
* Delegates to the three-arg version, completely ignoring the * ns parameter.
* * @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. * *Delegates to the two-arg version, completely ignoring the * ns parameter.
* * @param ns The namespace for the property (currently not used). * @param name The name of property to set. * Must not benull
.
* @param value The new value of the property.
* Must not be null
.
* @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 null
.
* @param value The new value of the property.
* Must not be null
.
* @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.
*
* Delegates to the two-arg version, completely ignoring the * ns parameter.
* * @param ns The namespace for the property (currently not used). * @param name The name of property to set. * Must not benull
.
* @param value The new value of the property.
* Must not be null
.
* @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.
*
* Does not
consult any delegates.
null
.
* @param value The new value of the property.
* Must not be null
.
*/
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.
*
* Delegates to the two-arg version, completely ignoring the * ns parameter.
* * @param ns The namespace for the property (currently not used). * @param name The name of property to set. * Must not benull
.
* @param value The new value of the property.
* Must not be null
.
* @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.
*
* Does not
consult any delegates.
null
.
* @param value The new value of the property.
* Must not be null
.
*/
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.
*
* Delegates to the one-arg version ignoring the ns parameter.
* * @param ns The namespace for the property (currently not used). * @param name The name of the property. * May benull
, in which case
* the return value is also null
.
* @return the property value, or null
for no match
* or if a null
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.
*
* This is the method that is invoked by {Project#getProperty * Project.getProperty}.
* *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.
* * @param name The name of the property. * May benull
, in which case
* the return value is also null
.
* @return the property value, or null
for no match
* or if a null
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.
*
* Delegates to the one-arg version ignoring the ns parameter.
* * @param ns The namespace for the property (currently not used). * @param name The name of the property. * May benull
, in which case
* the return value is also null
.
* @return the property value, or null
for no match
* or if a null
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.
*
* Does not
consult any delegates.
null
, in which case
* the return value is also null
.
* @return the property value, or null
for no match
* or if a null
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.
*
* Does not contain properties held by implementations of * delegates (like local properties).
* * @return a hashtable containing all properties (including user properties). */ public HashtableDoes not contain properties held by implementations of * delegates (like local properties).
* * @return a hashtable containing just the user properties */ public HashtableDoes not contain properties held by implementations of * delegates (like local properties).
* * @return a hashtable containing just the inherited properties */ public HashtableTo copy all "user" properties, you will also have to call * {@link #copyUserProperties copyUserProperties}.
* *Does not copy properties held by implementations of * delegates (like local properties).
* * @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) { EnumerationTo copy all "user" properties, you will also have to call * {@link #copyInheritedProperties copyInheritedProperties}.
* *Does not copy properties held by implementations of * delegates (like local properties).
* * @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