blob: 53ec6ee7a0b99b4c23a8487f0ee7623c01e856e4 [file] [log] [blame]
/*
* 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package org.apache.tools.ant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
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.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 property 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);
}
/**
* Obtains the names of all known properties.
*
* @since 1.10.9
*/
public interface PropertyEnumerator extends Delegate {
/**
* Returns the names of all properties known to this delegate.
*
* @return the names of all properties known to this delegate.
*/
Set<String> getPropertyNames();
}
// --------------------------------------------------------
//
// 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 =
(s, pos, 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 =
(s, pos, 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<>();
/** Project properties map (usually String to String). */
private final Hashtable<String, Object> properties = new Hashtable<>();
/**
* 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 final Hashtable<String, Object> userProperties = new Hashtable<>();
/**
* 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 final Hashtable<String, Object> inheritedProperties = new Hashtable<>();
/**
* 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
*/
@Deprecated
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
*/
@Deprecated
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 = 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.
*/
@Deprecated
public boolean setPropertyHook(String ns, String name,
Object value,
boolean inherited, boolean user,
boolean isNew) {
if (getNext() != null) {
// If next has handled the property
return getNext().setPropertyHook(ns, name, value, inherited, user, isNew);
}
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.
*/
@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
*/
@Deprecated
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.
*/
@Deprecated
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.
*/
@Deprecated
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.
*/
@Deprecated
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.
*/
@Deprecated
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.
*/
@Deprecated
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 names of all known properties.
* @since 1.10.9
* @return the names of all known properties.
*/
public Set<String> getPropertyNames() {
final Set<String> names = new HashSet<>(properties.keySet());
getDelegates(PropertyEnumerator.class)
.forEach(e -> names.addAll(e.getPropertyNames()));
return Collections.unmodifiableSet(names);
}
/**
* 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.
*/
@Deprecated
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<>(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<>(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<>(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) {
for (Map.Entry<String, Object> entry : inheritedProperties.entrySet()) {
String arg = entry.getKey();
if (other.getUserProperty(arg) == null) {
other.setInheritedProperty(arg, entry.getValue().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) {
for (Map.Entry<String, Object> entry : userProperties.entrySet()) {
String arg = entry.getKey();
if (!inheritedProperties.containsKey(arg)) {
other.setUserProperty(arg, entry.getValue().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("$");
} 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<>();
} else {
//copy on write, top priority
list = new ArrayList<>(list);
list.remove(delegate);
}
list.add(0, delegate);
delegates.put(key, Collections.unmodifiableList(list));
}
}
}
/**
* Get the Collection of delegates of the specified type.
*
* @param <D> desired 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.emptyList() : result;
}
/**
* Get all Delegate interfaces (excluding Delegate itself) from the specified Delegate.
* @param d the Delegate to inspect.
* @return Set&lt;Class&gt;
* @since Ant 1.8.0
*/
@SuppressWarnings("unchecked")
protected static Set<Class<? extends Delegate>> getDelegateInterfaces(Delegate d) {
final HashSet<Class<? extends Delegate>> result = new HashSet<>();
Class<?> c = d.getClass();
while (c != null) {
for (Class<?> ifc : c.getInterfaces()) {
if (Delegate.class.isAssignableFrom(ifc)) {
result.add((Class<? extends Delegate>) ifc);
}
}
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.
* @param value Object
* @return Boolean
* @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.
*
* @param value Object
* @return boolean
* @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.
* @param value Object
* @return boolean
* @since Ant 1.8.0
*/
private boolean evalAsBooleanOrPropertyName(Object value) {
Boolean b = toBoolean(value);
if (b != null) {
return b;
}
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.
* @param value Object
* @return boolean
* @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.
* @param value Object
* @return boolean
* @since Ant 1.8.0
*/
public boolean testUnlessCondition(Object value) {
return nullOrEmpty(value) || !evalAsBooleanOrPropertyName(value);
}
}