To aid discussion, here is a patch to make property expansion recursive.
Notes:
* I rejustified PropertyHelper.java in NetBeans so the diff looks more
monstrous than it really is.
* PropertyHelper.replacePropertiesRecursively() is substituted
replaceProperties
o For BC this should be done differently
* Put the file RecursivePropertyParser in ..ant/util
* The ant-test-expansion.xml file is a simple example with three test cases.
The last one should fail.
--
Jack J. Woehr # We have gone from the horse and buggy
Senior Consultant # to the moon rocket in one lifetime, but
Purematrix, Inc. # there has not been a corresponding moral
www.purematrix.com # growth in mankind. - Dwight D. Eisenhower
? recursive-properties-patch.txt
? .nbintdb
? src/main/.nbintdb
? src/main/org/apache/tools/ant/.nbattrs
? src/main/org/apache/tools/ant/util/RecursivePropertyParser.java
Index: src/main/org/apache/tools/ant/PropertyHelper.java
===================================================================
RCS file:
/home/cvspublic/ant/src/main/org/apache/tools/ant/PropertyHelper.java,v
retrieving revision 1.15
diff -c -r1.15 PropertyHelper.java
*** src/main/org/apache/tools/ant/PropertyHelper.java 14 Apr 2004 15:42:06
-0000 1.15
--- src/main/org/apache/tools/ant/PropertyHelper.java 3 Jun 2004 17:40:32
-0000
***************
*** 20,25 ****
--- 20,26 ----
import java.util.Hashtable;
import java.util.Vector;
import java.util.Enumeration;
+ import org.apache.tools.ant.util.RecursivePropertyParser;
/* ISSUES:
***************
*** 45,57 ****
* @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
--- 46,58 ----
* @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
***************
*** 59,65 ****
* 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
--- 60,66 ----
* 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
***************
*** 67,81 ****
* 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
*
--- 68,82 ----
* 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
*
***************
*** 84,90 ****
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 :-)
--- 85,91 ----
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 :-)
***************
*** 98,104 ****
public void setNext(PropertyHelper next) {
this.next = next;
}
!
/**
* Get the next property helper in the chain.
*
--- 99,105 ----
public void setNext(PropertyHelper next) {
this.next = next;
}
!
/**
* Get the next property helper in the chain.
*
***************
*** 107,113 ****
public PropertyHelper getNext() {
return next;
}
!
/**
* Factory method to create a property processor.
* Users can provide their own or replace it using "ant.PropertyHelper"
--- 108,114 ----
public PropertyHelper getNext() {
return next;
}
!
/**
* Factory method to create a property processor.
* Users can provide their own or replace it using "ant.PropertyHelper"
***************
*** 119,139 ****
* @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
--- 120,140 ----
* @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
***************
*** 151,171 ****
* 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.
*
--- 152,172 ----
* 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.
*
***************
*** 189,205 ****
}
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
--- 190,206 ----
}
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
***************
*** 220,230 ****
* <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.
--- 221,237 ----
* <code>}</code>
*/
public void parsePropertyString(String value, Vector fragments,
! Vector propertyRefs)
! throws BuildException {
parsePropertyStringDefault(value, fragments, propertyRefs);
}
!
! public String replaceProperties(String ns, String value,
! Hashtable keys)
! throws BuildException {
! return replacePropertiesRecursively(ns, value, keys);
! }
!
/**
* Replaces <code>${xxx}</code> style constructions in the given value
* with the string value of the corresponding data types.
***************
*** 242,268 ****
* @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) {
--- 249,275 ----
* @return the original string with the properties replaced, or
* <code>null</code> if the original string is <code>null</code>.
*/
! public String replacePropertiesBC(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) {
***************
*** 271,329 ****
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 = 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
--- 278,403 ----
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();
}
!
! /**
! * Replaces <code>${xxx}</code> style constructions RECURSIVELY in the
given value
! * with the string value of the corresponding data types. That is, this
function
! * keeps finding the next leftmost outermost expression and expands it
recursively until no
! * instantiable expressions remain.
! *
! *
! * @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 replacePropertiesRecursively(String ns, String value,
! Hashtable keys)
! throws BuildException {
! if (value == null) {
! return null;
! }
! StringBuffer sb = new StringBuffer(value);
!
! int nextOpenerIndex = RecursivePropertyParser.nextOpener(value, 0);
!
! while (nextOpenerIndex != RecursivePropertyParser.NO_MARKER) { //
More inner expressions, recurse.
! // /* debug */ System.out.println("Head of
replacePropertiesRecursively while loop, nextOpenerIndex = " + nextOpenerIndex
+ " sb=" + sb.toString());
! int balancingCloserIndex =
RecursivePropertyParser.balancingCloser(sb.toString(), nextOpenerIndex);
! if (balancingCloserIndex == RecursivePropertyParser.NO_MARKER) {
! // ??? Should this be logged first?
! throw new BuildException("Unbalanced property expression: " +
sb.substring(nextOpenerIndex).toString());
! }
! final String innerExpression = sb.substring(nextOpenerIndex +
RecursivePropertyParser.OPENER_LENGTH , balancingCloserIndex);
!
! final String expansion = replacePropertiesRecursively(ns,
innerExpression, keys);
! // /* debug */ System.out.println("replacePropertiesRecursively()
innerExpression=" + innerExpression);
! Object replacement = null;
!
! // try to get it from the project or keys
! // Backward compatibility
! if (keys != null) {
! replacement = keys.get(expansion);
! }
! if (replacement == null) {
! replacement = getProperty(ns, expansion);
! }
!
! if (replacement == null) {
! project.log("Property ${" + expansion
! + "} has not been set", Project.MSG_VERBOSE);
! }
! final String fragment = (replacement != null)
! ? replacement.toString()
! : "${" + expansion + "}";
!
! sb.replace(nextOpenerIndex, balancingCloserIndex +
RecursivePropertyParser.CLOSER_LENGTH, fragment);
! nextOpenerIndex =
RecursivePropertyParser.nextOpener(sb.toString(), balancingCloserIndex+1);
! }
! 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 = 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
***************
*** 336,360 ****
* @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.
--- 410,434 ----
* @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.
***************
*** 364,381 ****
* 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 = setPropertyHook(ns, name, value, false, true, false);
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
--- 438,455 ----
* 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 = setPropertyHook(ns, name, value, false, true, false);
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
***************
*** 388,409 ****
* 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 = 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.
--- 462,483 ----
* 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 = 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.
***************
*** 418,429 ****
if (name == null) {
return null;
}
!
Object o = getPropertyHook(ns, name, false);
if (o != null) {
return o;
}
!
return properties.get(name);
}
/**
--- 492,503 ----
if (name == null) {
return null;
}
!
Object o = getPropertyHook(ns, name, false);
if (o != null) {
return o;
}
!
return properties.get(name);
}
/**
***************
*** 445,459 ****
}
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
--- 519,533 ----
}
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
***************
*** 461,497 ****
*/
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 compatibility 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
--- 535,571 ----
*/
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 compatibility 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
***************
*** 515,521 ****
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
--- 589,595 ----
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
***************
*** 539,561 ****
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
--- 613,635 ----
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
***************
*** 574,580 ****
/*
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("$");
--- 648,654 ----
/*
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("$");
***************
*** 584,596 ****
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);
--- 658,670 ----
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);
***************
*** 604,608 ****
fragments.addElement(value.substring(prev));
}
}
!
}
--- 678,682 ----
fragments.addElement(value.substring(prev));
}
}
!
}
/*
* RecursivePropertyParser.java
*
* Created on June 2, 2004, 3:29 PM
*/
package org.apache.tools.ant.util;
/**
* Class of static methods to parse property expressions
* @author jax
*/
public class RecursivePropertyParser {
/** No marker found in a string search */
public final static int NO_MARKER = -1;
public final static String OPENER = "${";
public final static String CLOSER = "}";
public final static int OPENER_LENGTH = OPENER.length();
public final static int CLOSER_LENGTH = CLOSER.length();
/** Never creates a new instance of RecursivePropertyParser */
protected RecursivePropertyParser() {
}
/**
* Find the next expression opener
*
* @param value The source string
* @return The index of the start of the opener, NO_MARKER if not found
*/
public static int nextOpener(final String value, int index) {
int result = NO_MARKER;
if (value != null && !(index > value.length())) {
result = value.indexOf(OPENER, index);
}
return result;
}
/**
* Find the next expression closer
*
* @param index offsetr in the source string from which to start looking
for a closer
* @param value The source string
* @return The index of the closer, NO_MARKER if not found
*/
public static int nextCloser(final String value, int index) {
int result = NO_MARKER;
if (value != null && !(index > value.length())) {
result = value.indexOf(CLOSER, index);
// /* debug */ System.out.println("nextCloser: result = " + result);
}
return result;
}
/**
* Finds the balancing expression closer.
*
* @param index offset in the source string from which to start looking for
a closer
* @param value The source string
* @return The index of the closer, NO_MARKER if not found
*/
public static int balancingCloser(final String value, int index) {
int result = NO_MARKER;
int lastOpener = index;
if (value != null) {
int unbalancedOpeners = 1;
int currentOpener = nextOpener(value, lastOpener+1);
int currentCloser = nextCloser(value, lastOpener);
while (unbalancedOpeners > 0) {
// /* debug */ System.out.println("balancingCloser():
unbalancedOpeners=" + unbalancedOpeners +"currentOpener=" + currentOpener + "
currentCloser=" + currentCloser);
// We can't find a matching closing '}'
if (currentCloser == NO_MARKER) {
break; // doom
}
// We found another '${' before finding a '}'
if (currentOpener != NO_MARKER && currentOpener <
currentCloser) {
unbalancedOpeners++;
lastOpener = currentOpener;
currentOpener = nextOpener(value, lastOpener+1);
}
// We found a closer '}' before any new openers '${'
else {
unbalancedOpeners--;
if (unbalancedOpeners == 0) { // We're done if we are
balanced
result = currentCloser;
}
else { // We have to go round again trying to find closers
to balance
currentCloser=nextCloser(value, currentCloser+1);
}
}
}
}
return result;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project basedir="." default="all" name="changeme">
<target name="all">
<property name="a" value="arf"/>
<property name="b" value="bark"/>
<property name="arf.bark" value="woof woof woof"/>
<echo message="The expansion comes out to ${${a}.${b}}"/>
<echo message="The expansion comes out to ${${a}.${${a}.${b}}}"/>
<!-- this one will fail with a build exception -->
<echo message="${${a}.${b}${"/>
</target>
</project>
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]