All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.tools.ant.PropertyHelper Maven / Gradle / Ivy

There is a newer version: 1.0-rc5
Show newest version
/*
 * Copyright  2002-2004 The Apache Software Foundation
 *
 *  Licensed 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.util.Hashtable;
import java.util.Vector;
import java.util.Enumeration;


/* 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.
 */

/** 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 {

    private Project project;
    private PropertyHelper next;

    /** Project properties map (usually String to String). */
    private 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.
     */
    private 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.
     */
    private Hashtable inheritedProperties = new Hashtable();

    /**
     * Default constructor.
     */
    protected PropertyHelper() {
    }

    // --------------------  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;
    }

    /** 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 the next property helper in the chain.
     */
    public void setNext(PropertyHelper next) {
        this.next = next;
    }

    /**
     * Get the next property helper in the chain.
     *
     * @return the next property helper.
     */
    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 fro which the property helper is required.
     *
     * @return the project's property helper.
     */
    public static synchronized
        PropertyHelper getPropertyHelper(Project project) {
        PropertyHelper helper
            = (PropertyHelper) project.getReference("ant.PropertyHelper");
        if (helper != null) {
            return helper;
        }
        helper = new PropertyHelper();
        helper.setProject(project);

        project.addReference("ant.PropertyHelper", helper);
        return helper;
    }

    // --------------------  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 ns   The namespace that the property is in (currently
     *             not used.
     * @param name The name of property to set.
     *             Must not be null.
     * @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).
     */
    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 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.
     */
    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 overridden 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 ns    The namespace for the property.
     * @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.
     *  @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.
     */
    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 = 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 ns   The namespace for the property (currently not used).
     * @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 = setPropertyHook(ns, name, value, false, false, true);
        if (done) {
            return;
        }

        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.
     * @param ns   The namespace for the property (currently not used).
     * @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 = setPropertyHook(ns, name, value, false, true, false);
        if (done) {
            return;
        }
        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.
     *
     * @param ns   The namespace for the property (currently not used).
     * @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 = 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 ns   The namespace for the property (currently not used).
     * @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 synchronized 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 ns   The namespace for the property (currently not used).
     * @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 synchronized 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() {
        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
     * @return a hashtable containing just the user properties
     */
    public Hashtable getUserProperties() {
        return new Hashtable(userProperties);
    }

    /**
     * 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 compatibility * 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)); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy