blob: 177457376d26aa44e34f8ed07f8ea4552dfa6aed [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000-2002 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution, if
* any, must include the following acknowlegement:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowlegement may appear in the software itself,
* if and wherever such third-party acknowlegements normally appear.
*
* 4. The names "The Jakarta Project", "Ant", and "Apache Software
* Foundation" must not be used to endorse or promote products derived
* from this software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package org.apache.tools.ant;
import org.apache.tools.ant.helper.*;
import java.util.*;
import org.xml.sax.AttributeList;
import org.xml.sax.Attributes;
import org.xml.sax.helpers.AttributeListImpl;
import org.xml.sax.helpers.AttributesImpl;
/* 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 imutable - 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-&gt;String-&gt;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.
*/
/** NOT FINAL. API MAY CHANGE
*
* Deals with properties - substitution, dynamic properties, etc.
*
* This is the same code as in Ant1.5. The main addition is the ability
* to chain multiple PropertyHelpers and to replace the default.
*
* @since Ant 1.6
*/
public class PropertyHelper {
protected Project project;
protected PropertyHelper next;
/** Project properties map (usually String to String). */
protected Hashtable 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.
* Mapping is String to String.
*/
protected Hashtable 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.
* Mapping is String to String.
*/
protected Hashtable inheritedProperties = new Hashtable();
protected PropertyHelper() {
}
// -------------------- Hook management --------------------
public void setProject(Project p ) {
this.project=p;
}
/** There are 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 )
*
* @param next
*/
public void setNext( PropertyHelper next ) {
this.next=next;
}
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 themself to the chain, and provide
* dynamic properties.
*/
public static PropertyHelper getPropertyHelper(Project project) {
PropertyHelper ph=(PropertyHelper)project.getReference( "ant.PropertyHelper" );
if( ph!=null ) return ph;
ph=new PropertyHelper();
ph.setProject( project );
project.addReference( "ant.PropertyHelper",ph );
return ph;
}
// -------------------- Methods to override --------------------
/**
* Sets a property. Any existing property of the same name
* is overwritten, unless it is a user property. Will be called
* from setProperty().
*
* If all helpers return false, the property will be saved in
* the default properties table by setProperty.
*
* @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>.
* @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 ).
*/
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.
*
* @param ns
* @param name
* @return
*/
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( name.startsWith( "toString:" )) {
name=name.substring( "toString:".length());
Object v=project.getReference( name );
if( v==null ) return null;
return 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.
*
* It can be overriden with a more efficient or customized version.
*
* @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>
*/
public void parsePropertyString(String value, Vector fragments,
Vector 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.
*
* @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 String) 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>.
*/
public String replaceProperties(String ns, String value,
Hashtable keys)
throws BuildException
{
if (value == null) {
return null;
}
Vector fragments = new Vector();
Vector propertyRefs = new Vector();
parsePropertyString(value, fragments, propertyRefs);
StringBuffer sb = new StringBuffer();
Enumeration i = fragments.elements();
Enumeration j = propertyRefs.elements();
while (i.hasMoreElements()) {
String fragment = (String) i.nextElement();
if (fragment == null) {
String propertyName = (String) j.nextElement();
Object replacement=null;
// try to get it from the project or keys
// Backward compatibility
if( keys!=null ) {
replacement=keys.get(propertyName);
}
if( replacement==null ) {
replacement=getProperty(ns, propertyName);
}
if (replacement == null ) {
project.log("Property ${" + propertyName
+ "} has not been set", Project.MSG_VERBOSE);
}
fragment = (replacement!=null)
? replacement.toString()
: "${" + propertyName + "}";
}
sb.append(fragment);
}
return sb.toString();
}
// -------------------- 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.
*/
public synchronized boolean setProperty(String ns, String name,
Object value, boolean verbose)
{
// user ( CLI ) properties take precedence
if (null != userProperties.get(name)) {
if( verbose ) {
project.log("Override ignored for user property " + name,
Project.MSG_VERBOSE);
}
return false;
}
boolean done=this.setPropertyHook(ns, name, value, false, false, false);
if( done ) {
return true;
}
if (null != properties.get(name) && verbose) {
project.log("Overriding previous definition of property " + name,
Project.MSG_VERBOSE);
}
if( verbose ) {
project.log("Setting project property: " + name + " -> " +
value, Project.MSG_DEBUG);
}
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.
*
* @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
*/
public synchronized void setNewProperty(String ns, String name,
Object value)
{
if (null != properties.get(name)) {
project.log("Override ignored for property " + name,
Project.MSG_VERBOSE);
return;
}
boolean done=this.setPropertyHook(ns, name, value, false, true, false);
if( done ) {
return;
}
project.log("Setting project property: " + name + " -> " +
value, Project.MSG_DEBUG);
properties.put(name, value);
}
/**
* Sets a user property, which cannot be overwritten by
* set/unset property calls. Any previous value is overwritten.
* @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 synchronized void setUserProperty(String ns, String name,
Object value)
{
project.log("Setting ro project property: " + name + " -> " +
value, Project.MSG_DEBUG);
userProperties.put(name, value);
boolean done=this.setPropertyHook(ns, name, value, false, false, true);
if( done ) {
return;
}
properties.put(name, value);
}
/**
* Sets a 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.
*
* @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 synchronized void setInheritedProperty(String ns, String name,
Object value)
{
inheritedProperties.put(name, value);
project.log("Setting ro project property: " + name + " -> " +
value, Project.MSG_DEBUG);
userProperties.put(name, value);
boolean done=this.setPropertyHook(ns, name, value, true, false, false);
if( done ) {
return;
}
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.
*
* @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 ns, String name) {
if (name == null) {
return null;
}
Object o=getPropertyHook(ns, name, false);
if( o!= null ) {
return o;
}
return properties.get(name);
}
/**
* Returns the value of a user property, if it is set.
*
* @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 ns, String name) {
if (name == null) {
return null;
}
Object o=getPropertyHook(ns, name, true);
if( o!= null ) {
return o;
}
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.
// TODO: do we need to delegate ?
/**
* Returns a copy of the properties table.
* @return a hashtable containing all properties
* (including user properties).
*/
public Hashtable getProperties() {
Hashtable propertiesCopy = new Hashtable();
Enumeration e = properties.keys();
while (e.hasMoreElements()) {
Object name = e.nextElement();
Object value = properties.get(name);
propertiesCopy.put(name, value);
}
// There is a better way to save the context. This shouldn't
// delegate to next, it's for backward compat only.
return propertiesCopy;
}
/**
* Returns a copy of the user property hashtable
* @return a hashtable containing just the user properties
*/
public Hashtable getUserProperties() {
Hashtable propertiesCopy = new Hashtable();
Enumeration e = userProperties.keys();
while (e.hasMoreElements()) {
Object name = e.nextElement();
Object value = properties.get(name);
propertiesCopy.put(name, value);
}
return propertiesCopy;
}
/**
* 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>
*
* @param other the project to copy the properties to. Must not be null.
*
* @since Ant 1.6
*/
public void copyInheritedProperties(Project other) {
Enumeration 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>
*
* @param other the project to copy the properties to. Must not be null.
*
* @since Ant 1.6
*/
public void copyUserProperties(Project other) {
Enumeration 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 compat
* for the static ProjectHelper.parsePropertyString().
*/
static void parsePropertyStringDefault(String value, Vector fragments,
Vector 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));
}
}
}