Author: niallp Date: Sun Jan 9 03:17:24 2011 New Revision: 1056876 URL: http://svn.apache.org/viewvc?rev=1056876&view=rev Log: Port LANG-482 to LANG 2.x Branch - Enhance StrSubstitutor to support substitution in variable names
Modified: commons/proper/lang/branches/LANG_2_X/src/main/java/org/apache/commons/lang/text/StrSubstitutor.java commons/proper/lang/branches/LANG_2_X/src/test/java/org/apache/commons/lang/text/StrSubstitutorTest.java Modified: commons/proper/lang/branches/LANG_2_X/src/main/java/org/apache/commons/lang/text/StrSubstitutor.java URL: http://svn.apache.org/viewvc/commons/proper/lang/branches/LANG_2_X/src/main/java/org/apache/commons/lang/text/StrSubstitutor.java?rev=1056876&r1=1056875&r2=1056876&view=diff ============================================================================== --- commons/proper/lang/branches/LANG_2_X/src/main/java/org/apache/commons/lang/text/StrSubstitutor.java (original) +++ commons/proper/lang/branches/LANG_2_X/src/main/java/org/apache/commons/lang/text/StrSubstitutor.java Sun Jan 9 03:17:24 2011 @@ -86,6 +86,16 @@ import java.util.Properties; * <pre> * The variable $${${name}} must be used. * </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 Oliver Heger @@ -123,6 +133,10 @@ public class StrSubstitutor { * Variable resolution is delegated to an implementor of VariableResolver. */ private StrLookup variableResolver; + /** + * The flag whether substitution in variable names is enabled. + */ + private boolean enableSubstitutionInVariables; //----------------------------------------------------------------------- /** @@ -564,7 +578,7 @@ public class StrSubstitutor { StrMatcher prefixMatcher = getVariablePrefixMatcher(); StrMatcher suffixMatcher = getVariableSuffixMatcher(); char escape = getEscapeChar(); - + boolean top = (priorVariables == null); boolean altered = false; int lengthChange = 0; @@ -572,7 +586,8 @@ public class StrSubstitutor { int bufEnd = offset + length; int pos = offset; while (pos < bufEnd) { - int startMatchLen = prefixMatcher.isMatch(chars, pos, offset, bufEnd); + int startMatchLen = prefixMatcher.isMatch(chars, pos, offset, + bufEnd); if (startMatchLen == 0) { pos++; } else { @@ -580,7 +595,7 @@ public class StrSubstitutor { if (pos > offset && chars[pos - 1] == escape) { // escaped buf.deleteCharAt(pos - 1); - chars = buf.buffer; // in case buffer was altered + chars = buf.buffer; // in case buffer was altered lengthChange--; altered = true; bufEnd--; @@ -589,45 +604,73 @@ public class StrSubstitutor { int startPos = pos; pos += startMatchLen; int endMatchLen = 0; + int nestedVarCount = 0; 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) { pos++; } else { // found variable end marker - String varName = new String(chars, startPos + startMatchLen, - pos - startPos - startMatchLen); - pos += endMatchLen; - int endPos = pos; - - // on the first call initialize priorVariables - if (priorVariables == null) { - priorVariables = new ArrayList(); - 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 + if (nestedVarCount == 0) { + String varName = new String(chars, startPos + + startMatchLen, pos - startPos + - 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 + if (priorVariables == null) { + priorVariables = new ArrayList(); + 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; } - - // remove variable from the cyclic stack - priorVariables.remove(priorVariables.size() - 1); - break; } } } @@ -854,4 +897,29 @@ public class StrSubstitutor { 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 2.6 + */ + 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 2.6 + */ + public void setEnableSubstitutionInVariables( + boolean enableSubstitutionInVariables) { + this.enableSubstitutionInVariables = enableSubstitutionInVariables; + } } Modified: commons/proper/lang/branches/LANG_2_X/src/test/java/org/apache/commons/lang/text/StrSubstitutorTest.java URL: http://svn.apache.org/viewvc/commons/proper/lang/branches/LANG_2_X/src/test/java/org/apache/commons/lang/text/StrSubstitutorTest.java?rev=1056876&r1=1056875&r2=1056876&view=diff ============================================================================== --- commons/proper/lang/branches/LANG_2_X/src/test/java/org/apache/commons/lang/text/StrSubstitutorTest.java (original) +++ commons/proper/lang/branches/LANG_2_X/src/test/java/org/apache/commons/lang/text/StrSubstitutorTest.java Sun Jan 9 03:17:24 2011 @@ -254,6 +254,57 @@ public class StrSubstitutorTest extends 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.