Fun with Regular Expressions: ANT-style variable replacing in strings

I recently felt the need to write a piece of code that resolves ANT-style variables in a string. Suppose you have a property file similar to this one:

propertyA=SomeValue
propertyB=${propertyA}.SomeOtherValue
listofThings=${propertyA}, ${propertyB}, constantValue

Let's further assume you want to read property listofThings, and resolve the variables. Isn't that a perfect job for regular expressions?

Using Regex Tester, I came up with the following regular expression to find occurrence of ${variable}:

\$\{(.+?)\}

Using java.util.regex.Pattern and java.util.regex.Matcher to find all occurrences is rather trivial:

Pattern re = Pattern.compile("\\$\\{(.+?)\\}");
Matcher m = re.matcher(sourcestring);
while (m.find()) {
  String variable = m.group(1);
  System.out.println(variable);
}

Replacing the variables with their concrete values is not so trivial. You might be tempted to use String.substring():

value = sourcestring.substring(0, m.start()) + replacement + (sourcestring.substring(m.end()));

But this will modify the source string, basically throwing the matcher out of the curve.

Looking at the matcher API, I found java.util.regex.Matcher.appendReplacement(StringBuffer, String) and java.util.regex.Matcher.appendTail(StringBuffer). These two little gems to the trick:

private String resolve(String sourcestring, Properties props) {
  Pattern re = Pattern.compile("\\$\\{(.+?)\\}");
  Matcher m = re.matcher(sourcestring);
  StringBuffer result = new StringBuffer();
  while (m.find()) {
    String variable = m.group(1);
    String resolved = resolve(props.getProperty(variable), props);
    m.appendReplacement(result, resolved);
  }
  m.appendTail(result);
  return result.toString();
}

Regular Expressions do save the day!

Thanks for reading this post. You should follow me on twitter here

  1. Your version do have a bug which will not correctly replace “$1″.

    – quote from JavaDoc –
    The replacement string may contain references to subsequences captured during the previous match: Each occurrence of $g will be replaced by the result of evaluating group(g). The first number after the $ is always treated as part of the group reference. Subsequent numbers are incorporated into g if they would form a legal group reference. Only the numerals ‘0′ through ‘9′ are considered as potential components of the group reference. If the second group matched the string “foo”, for example, then passing the replacement string “$2bar” would cause “foobar” to be appended to the string buffer. A dollar sign ($) may be included as a literal in the replacement string by preceding it with a backslash (\$).

    The missing piece is that the replacement string has to be replaced “$” ->”\$” before the appendReplacement.

    It is stupid…. I have to do another replace in order to do a replace.
    Why don’t JDK provide a nice and friendly API?

  2. @Dennis
    Dennis, I tried to reproduce your issue. Here are the properties:
    propertyA=SomeValue[$1]
    propertyB=${propertyA}.SomeOtherValue: $1
    listofThings=${propertyA}, ${propertyB}, constantValue”

    and here is the output:

    SomeValue[propertyA], SomeValue[propertyA].SomeOtherValue: propertyB, constantValue

    Looks as intended, or am I missing something? If so, please feel free to post a complete code example :-)

  3. Have you tried Ant-Contrib? With that you can just use the task, and basically achieve this:

    if (string1.matches(regex))
    {
    string2 = string1.replace(regex,replacement)
    }
    else
    {
    string2 = someOtherString; // or string1, whatever
    }

    You can also override an existing variable w/ the results of the regex replace so you don’t need to create a new var to hold string2, if you only need the resulting replaced one.

    Combine that with Ant-Contrib’s support for and and you can loop through lists (strings, files, dirs), select what you want to manipulate, regex ‘em, and be a happy camper… at least as happy as one can ever be writing code with XML syntax, anyway.

    For example, see http://dev.eclipse.org/viewcvs/index.cgi/org.eclipse.dash/athena/org.eclipse.dash.commonbuilder/org.eclipse.dash.common.releng/promote.xml?view=annotate&root=Technology_Project, line 130.

  4. Oh, and for resolving variables you can use Ant’s built-in support for expanding properties…

    http://ant.apache.org/manual/CoreTypes/filterchain.html#expandproperties

  5. @Nick Boldt
    Nick, thanks for commenting. Had I been writing ANT scripts, I no doubt had used the approaches you mentioned. However, I wasn’t writing ANT scripts (and writing ANT scripts wasn’t an option in the particular case).

    So, it’s great that we now have both approaches (ANT and non-ANT) listed here so people can google them and decide whichever they need or like :-)

  1. No trackbacks yet.

Additional comments powered by BackType