diff --git a/proposal/embed/src/java/org/apache/tools/ant/PropertyHelper.java b/proposal/embed/src/java/org/apache/tools/ant/PropertyHelper.java new file mode 100644 index 000000000..6026b32b8 --- /dev/null +++ b/proposal/embed/src/java/org/apache/tools/ant/PropertyHelper.java @@ -0,0 +1,628 @@ +/* + * 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 + * . + */ + +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->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. + */ + +/** 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 + * @author Costin Manolache + */ +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 null. + * @param value The new value of the property. + * Must not be null. + * @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 ${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. + * + * It can be overriden with a more efficient or customized version. + * + * @param value Text to parse. Must not be null. + * @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 + * } + */ + public void parsePropertyString(String value, Vector fragments, + Vector propertyRefs) + throws BuildException + { + parsePropertyStringDefault(value, fragments, propertyRefs); + } + + /** + * Replaces ${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. + * @param keys Mapping (String to String) 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. + */ + 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 null. + * @param value The new value of the property. + * Must not be null. + * @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 null. + * @param value The new value of the property. + * Must not be null. + */ + 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 null. + * @param value The new value of the property. + * Must not be null. + */ + 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 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 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 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 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. + * + *

To copy all "user" properties, you will also have to call + * {@link #copyUserProperties copyUserProperties}.

+ * + * @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. + * + *

To copy all "user" properties, you will also have to call + * {@link #copyInheritedProperties copyInheritedProperties}.

+ * + * @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)); + } + } + +}