[lang-482] Added support for substitution in variable names.

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/lang/trunk@1005974 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Oliver Heger 2010-10-08 19:17:31 +00:00
parent 790140111d
commit 6f6eddbf3a
2 changed files with 171 additions and 52 deletions

View File

@ -86,6 +86,16 @@
* <pre> * <pre>
* The variable $${${name}} must be used. * The variable $${${name}} must be used.
* </pre> * </pre>
* <p>
* In some complex scenarios you might even want to perform substitution in the
* names of variables, for instance
* <pre>
* ${jre-${java.specification.version}}
* </pre>
* <code>StrSubstitutor</code> supports this recursive substitution in variable
* names, but it has to be enabled explicitly by setting the
* {@link #setEnableSubstitutionInVariables(boolean) enableSubstitutionInVariables}
* property to <b>true</b>.
* *
* @author Apache Software Foundation * @author Apache Software Foundation
* @version $Id$ * @version $Id$
@ -122,6 +132,10 @@ public class StrSubstitutor {
* Variable resolution is delegated to an implementor of VariableResolver. * Variable resolution is delegated to an implementor of VariableResolver.
*/ */
private StrLookup<?> variableResolver; private StrLookup<?> variableResolver;
/**
* The flag whether substitution in variable names is enabled.
*/
private boolean enableSubstitutionInVariables;
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
/** /**
@ -571,7 +585,8 @@ private int substitute(StrBuilder buf, int offset, int length, List<String> prio
int bufEnd = offset + length; int bufEnd = offset + length;
int pos = offset; int pos = offset;
while (pos < bufEnd) { while (pos < bufEnd) {
int startMatchLen = prefixMatcher.isMatch(chars, pos, offset, bufEnd); int startMatchLen = prefixMatcher.isMatch(chars, pos, offset,
bufEnd);
if (startMatchLen == 0) { if (startMatchLen == 0) {
pos++; pos++;
} else { } else {
@ -579,7 +594,7 @@ private int substitute(StrBuilder buf, int offset, int length, List<String> prio
if (pos > offset && chars[pos - 1] == escape) { if (pos > offset && chars[pos - 1] == escape) {
// escaped // escaped
buf.deleteCharAt(pos - 1); buf.deleteCharAt(pos - 1);
chars = buf.buffer; // in case buffer was altered chars = buf.buffer; // in case buffer was altered
lengthChange--; lengthChange--;
altered = true; altered = true;
bufEnd--; bufEnd--;
@ -588,45 +603,73 @@ private int substitute(StrBuilder buf, int offset, int length, List<String> prio
int startPos = pos; int startPos = pos;
pos += startMatchLen; pos += startMatchLen;
int endMatchLen = 0; int endMatchLen = 0;
int nestedVarCount = 0;
while (pos < bufEnd) { while (pos < bufEnd) {
endMatchLen = suffixMatcher.isMatch(chars, pos, offset, bufEnd); if (isEnableSubstitutionInVariables()
&& (endMatchLen = prefixMatcher.isMatch(chars,
pos, offset, bufEnd)) != 0) {
// found a nested variable start
nestedVarCount++;
pos += endMatchLen;
continue;
}
endMatchLen = suffixMatcher.isMatch(chars, pos, offset,
bufEnd);
if (endMatchLen == 0) { if (endMatchLen == 0) {
pos++; pos++;
} else { } else {
// found variable end marker // found variable end marker
String varName = new String(chars, startPos + startMatchLen, if (nestedVarCount == 0) {
pos - startPos - startMatchLen); String varName = new String(chars, startPos
pos += endMatchLen; + startMatchLen, pos - startPos
int endPos = pos; - startMatchLen);
if (isEnableSubstitutionInVariables()) {
StrBuilder bufName = new StrBuilder(varName);
substitute(bufName, 0, bufName.length());
varName = bufName.toString();
}
pos += endMatchLen;
int endPos = pos;
// on the first call initialize priorVariables // on the first call initialize priorVariables
if (priorVariables == null) { if (priorVariables == null) {
priorVariables = new ArrayList<String>(); priorVariables = new ArrayList<String>();
priorVariables.add(new String(chars, offset, length)); priorVariables.add(new String(chars,
offset, length));
}
// handle cyclic substitution
checkCyclicSubstitution(varName, priorVariables);
priorVariables.add(varName);
// resolve the variable
String varValue = resolveVariable(varName, buf,
startPos, endPos);
if (varValue != null) {
// recursive replace
int varLen = varValue.length();
buf.replace(startPos, endPos, varValue);
altered = true;
int change = substitute(buf, startPos,
varLen, priorVariables);
change = change
+ (varLen - (endPos - startPos));
pos += change;
bufEnd += change;
lengthChange += change;
chars = buf.buffer; // in case buffer was
// altered
}
// remove variable from the cyclic stack
priorVariables
.remove(priorVariables.size() - 1);
break;
} else {
nestedVarCount--;
pos += endMatchLen;
} }
// handle cyclic substitution
checkCyclicSubstitution(varName, priorVariables);
priorVariables.add(varName);
// resolve the variable
String varValue = resolveVariable(varName, buf, startPos, endPos);
if (varValue != null) {
// recursive replace
int varLen = varValue.length();
buf.replace(startPos, endPos, varValue);
altered = true;
int change = substitute(buf, startPos, varLen, priorVariables);
change = change + (varLen - (endPos - startPos));
pos += change;
bufEnd += change;
lengthChange += change;
chars = buf.buffer; // in case buffer was altered
}
// remove variable from the cyclic stack
priorVariables.remove(priorVariables.size() - 1);
break;
} }
} }
} }
@ -740,7 +783,7 @@ public StrSubstitutor setVariablePrefixMatcher(StrMatcher prefixMatcher) {
/** /**
* Sets the variable prefix to use. * Sets the variable prefix to use.
* <p> * <p>
* The variable prefix is the characer or characters that identify the * The variable prefix is the character or characters that identify the
* start of a variable. This method allows a single character prefix to * start of a variable. This method allows a single character prefix to
* be easily set. * be easily set.
* *
@ -819,7 +862,7 @@ public StrSubstitutor setVariableSuffix(char suffix) {
/** /**
* Sets the variable suffix to use. * Sets the variable suffix to use.
* <p> * <p>
* The variable suffix is the characer or characters that identify the * The variable suffix is the character or characters that identify the
* end of a variable. This method allows a string suffix to be easily set. * end of a variable. This method allows a string suffix to be easily set.
* *
* @param suffix the suffix for variables, not null * @param suffix the suffix for variables, not null
@ -853,4 +896,29 @@ public void setVariableResolver(StrLookup<?> variableResolver) {
this.variableResolver = variableResolver; this.variableResolver = variableResolver;
} }
// Substitution support in variable names
//-----------------------------------------------------------------------
/**
* Returns a flag whether substitution is done in variable names.
*
* @return the substitution in variable names flag
* @since 3.0
*/
public boolean isEnableSubstitutionInVariables() {
return enableSubstitutionInVariables;
}
/**
* Sets a flag whether substitution is done in variable names. If set to
* <b>true</b>, the names of variables can contain other variables which are
* processed first before the original variable is evaluated, e.g.
* <code>${jre-${java.version}}</code>. The default value is <b>false</b>.
*
* @param enableSubstitutionInVariables the new value of the flag
* @since 3.0
*/
public void setEnableSubstitutionInVariables(
boolean enableSubstitutionInVariables) {
this.enableSubstitutionInVariables = enableSubstitutionInVariables;
}
} }

View File

@ -5,9 +5,9 @@
* The ASF licenses this file to You under the Apache License, Version 2.0 * The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with * (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at * the License. You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -27,7 +27,7 @@
/** /**
* Test class for StrSubstitutor. * Test class for StrSubstitutor.
* *
* @author Oliver Heger * @author Oliver Heger
* @version $Id$ * @version $Id$
*/ */
@ -255,6 +255,57 @@ public void testReplacePartialString_noReplace() {
assertEquals("${animal} jumps", sub.replace("The ${animal} jumps over the ${target}.", 4, 15)); assertEquals("${animal} jumps", sub.replace("The ${animal} jumps over the ${target}.", 4, 15));
} }
/**
* Tests whether a variable can be replaced in a variable name.
*/
public void testReplaceInVariable() {
values.put("animal.1", "fox");
values.put("animal.2", "mouse");
values.put("species", "2");
StrSubstitutor sub = new StrSubstitutor(values);
sub.setEnableSubstitutionInVariables(true);
assertEquals(
"Wrong result (1)",
"The mouse jumps over the lazy dog.",
sub.replace("The ${animal.${species}} jumps over the ${target}."));
values.put("species", "1");
assertEquals(
"Wrong result (2)",
"The fox jumps over the lazy dog.",
sub.replace("The ${animal.${species}} jumps over the ${target}."));
}
/**
* Tests whether substitution in variable names is disabled per default.
*/
public void testReplaceInVariableDisabled() {
values.put("animal.1", "fox");
values.put("animal.2", "mouse");
values.put("species", "2");
StrSubstitutor sub = new StrSubstitutor(values);
assertEquals(
"Wrong result",
"The ${animal.${species}} jumps over the lazy dog.",
sub.replace("The ${animal.${species}} jumps over the ${target}."));
}
/**
* Tests complex and recursive substitution in variable names.
*/
public void testReplaceInVariableRecursive() {
values.put("animal.2", "brown fox");
values.put("animal.1", "white mouse");
values.put("color", "white");
values.put("species.white", "1");
values.put("species.brown", "2");
StrSubstitutor sub = new StrSubstitutor(values);
sub.setEnableSubstitutionInVariables(true);
assertEquals(
"Wrong result",
"The white mouse jumps over the lazy dog.",
sub.replace("The ${animal.${species.${color}}} jumps over the ${target}."));
}
//----------------------------------------------------------------------- //-----------------------------------------------------------------------
/** /**
* Tests protected. * Tests protected.
@ -325,7 +376,7 @@ public void testGetSetPrefix() {
assertEquals(true, sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher); assertEquals(true, sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher);
sub.setVariablePrefix('<'); sub.setVariablePrefix('<');
assertEquals(true, sub.getVariablePrefixMatcher() instanceof StrMatcher.CharMatcher); assertEquals(true, sub.getVariablePrefixMatcher() instanceof StrMatcher.CharMatcher);
sub.setVariablePrefix("<<"); sub.setVariablePrefix("<<");
assertEquals(true, sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher); assertEquals(true, sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher);
try { try {
@ -335,7 +386,7 @@ public void testGetSetPrefix() {
// expected // expected
} }
assertEquals(true, sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher); assertEquals(true, sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher);
StrMatcher matcher = StrMatcher.commaMatcher(); StrMatcher matcher = StrMatcher.commaMatcher();
sub.setVariablePrefixMatcher(matcher); sub.setVariablePrefixMatcher(matcher);
assertSame(matcher, sub.getVariablePrefixMatcher()); assertSame(matcher, sub.getVariablePrefixMatcher());
@ -356,7 +407,7 @@ public void testGetSetSuffix() {
assertEquals(true, sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher); assertEquals(true, sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher);
sub.setVariableSuffix('<'); sub.setVariableSuffix('<');
assertEquals(true, sub.getVariableSuffixMatcher() instanceof StrMatcher.CharMatcher); assertEquals(true, sub.getVariableSuffixMatcher() instanceof StrMatcher.CharMatcher);
sub.setVariableSuffix("<<"); sub.setVariableSuffix("<<");
assertEquals(true, sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher); assertEquals(true, sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher);
try { try {
@ -366,7 +417,7 @@ public void testGetSetSuffix() {
// expected // expected
} }
assertEquals(true, sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher); assertEquals(true, sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher);
StrMatcher matcher = StrMatcher.commaMatcher(); StrMatcher matcher = StrMatcher.commaMatcher();
sub.setVariableSuffixMatcher(matcher); sub.setVariableSuffixMatcher(matcher);
assertSame(matcher, sub.getVariableSuffixMatcher()); assertSame(matcher, sub.getVariableSuffixMatcher());
@ -412,7 +463,7 @@ public void testStaticReplaceSystemProperties() {
+ "working with ${os.name}, your home " + "working with ${os.name}, your home "
+ "directory is ${user.home}.")); + "directory is ${user.home}."));
} }
/** /**
* Test the replace of a properties object * Test the replace of a properties object
*/ */
@ -430,38 +481,38 @@ public void testSubstitutetDefaultProperties(){
private void doTestReplace(String expectedResult, String replaceTemplate, boolean substring) { private void doTestReplace(String expectedResult, String replaceTemplate, boolean substring) {
String expectedShortResult = expectedResult.substring(1, expectedResult.length() - 1); String expectedShortResult = expectedResult.substring(1, expectedResult.length() - 1);
StrSubstitutor sub = new StrSubstitutor(values); StrSubstitutor sub = new StrSubstitutor(values);
// replace using String // replace using String
assertEquals(expectedResult, sub.replace(replaceTemplate)); assertEquals(expectedResult, sub.replace(replaceTemplate));
if (substring) { if (substring) {
assertEquals(expectedShortResult, sub.replace(replaceTemplate, 1, replaceTemplate.length() - 2)); assertEquals(expectedShortResult, sub.replace(replaceTemplate, 1, replaceTemplate.length() - 2));
} }
// replace using char[] // replace using char[]
char[] chars = replaceTemplate.toCharArray(); char[] chars = replaceTemplate.toCharArray();
assertEquals(expectedResult, sub.replace(chars)); assertEquals(expectedResult, sub.replace(chars));
if (substring) { if (substring) {
assertEquals(expectedShortResult, sub.replace(chars, 1, chars.length - 2)); assertEquals(expectedShortResult, sub.replace(chars, 1, chars.length - 2));
} }
// replace using StringBuffer // replace using StringBuffer
StringBuffer buf = new StringBuffer(replaceTemplate); StringBuffer buf = new StringBuffer(replaceTemplate);
assertEquals(expectedResult, sub.replace(buf)); assertEquals(expectedResult, sub.replace(buf));
if (substring) { if (substring) {
assertEquals(expectedShortResult, sub.replace(buf, 1, buf.length() - 2)); assertEquals(expectedShortResult, sub.replace(buf, 1, buf.length() - 2));
} }
// replace using StrBuilder // replace using StrBuilder
StrBuilder bld = new StrBuilder(replaceTemplate); StrBuilder bld = new StrBuilder(replaceTemplate);
assertEquals(expectedResult, sub.replace(bld)); assertEquals(expectedResult, sub.replace(bld));
if (substring) { if (substring) {
assertEquals(expectedShortResult, sub.replace(bld, 1, bld.length() - 2)); assertEquals(expectedShortResult, sub.replace(bld, 1, bld.length() - 2));
} }
// replace using object // replace using object
MutableObject<String> obj = new MutableObject<String>(replaceTemplate); // toString returns template MutableObject<String> obj = new MutableObject<String>(replaceTemplate); // toString returns template
assertEquals(expectedResult, sub.replace(obj)); assertEquals(expectedResult, sub.replace(obj));
// replace in StringBuffer // replace in StringBuffer
buf = new StringBuffer(replaceTemplate); buf = new StringBuffer(replaceTemplate);
assertEquals(true, sub.replaceIn(buf)); assertEquals(true, sub.replaceIn(buf));
@ -471,7 +522,7 @@ private void doTestReplace(String expectedResult, String replaceTemplate, boolea
assertEquals(true, sub.replaceIn(buf, 1, buf.length() - 2)); assertEquals(true, sub.replaceIn(buf, 1, buf.length() - 2));
assertEquals(expectedResult, buf.toString()); // expect full result as remainder is untouched assertEquals(expectedResult, buf.toString()); // expect full result as remainder is untouched
} }
// replace in StrBuilder // replace in StrBuilder
bld = new StrBuilder(replaceTemplate); bld = new StrBuilder(replaceTemplate);
assertEquals(true, sub.replaceIn(bld)); assertEquals(true, sub.replaceIn(bld));
@ -485,7 +536,7 @@ private void doTestReplace(String expectedResult, String replaceTemplate, boolea
private void doTestNoReplace(String replaceTemplate) { private void doTestNoReplace(String replaceTemplate) {
StrSubstitutor sub = new StrSubstitutor(values); StrSubstitutor sub = new StrSubstitutor(values);
if (replaceTemplate == null) { if (replaceTemplate == null) {
assertEquals(null, sub.replace((String) null)); assertEquals(null, sub.replace((String) null));
assertEquals(null, sub.replace((String) null, 0, 100)); assertEquals(null, sub.replace((String) null, 0, 100));