diff --git a/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java b/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java index f472087ea..b836c7df0 100644 --- a/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java +++ b/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java @@ -86,6 +86,16 @@ import java.util.Properties; *
* The variable $${${name}} must be used. *+ *
+ * In some complex scenarios you might even want to perform substitution in the + * names of variables, for instance + *
+ * ${jre-${java.specification.version}} + *+ *
StrSubstitutor
supports this recursive substitution in variable
+ * names, but it has to be enabled explicitly by setting the
+ * {@link #setEnableSubstitutionInVariables(boolean) enableSubstitutionInVariables}
+ * property to true.
*
* @author Apache Software Foundation
* @version $Id$
@@ -122,6 +132,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;
//-----------------------------------------------------------------------
/**
@@ -571,7 +585,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 {
@@ -579,7 +594,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--;
@@ -588,45 +603,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;
+ 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- * 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 * be easily set. * @@ -819,7 +862,7 @@ public class StrSubstitutor { /** * Sets the variable suffix to use. *
- * 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.
*
* @param suffix the suffix for variables, not null
@@ -853,4 +896,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 3.0
+ */
+ public boolean isEnableSubstitutionInVariables() {
+ return enableSubstitutionInVariables;
+ }
+
+ /**
+ * Sets a flag whether substitution is done in variable names. If set to
+ * true, the names of variables can contain other variables which are
+ * processed first before the original variable is evaluated, e.g.
+ * ${jre-${java.version}}
. The default value is false.
+ *
+ * @param enableSubstitutionInVariables the new value of the flag
+ * @since 3.0
+ */
+ public void setEnableSubstitutionInVariables(
+ boolean enableSubstitutionInVariables) {
+ this.enableSubstitutionInVariables = enableSubstitutionInVariables;
+ }
}
diff --git a/src/test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java b/src/test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java
index edb15d65d..4b2a62e46 100644
--- a/src/test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java
+++ b/src/test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java
@@ -5,9 +5,9 @@
* 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 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.
@@ -27,7 +27,7 @@ import org.apache.commons.lang3.mutable.MutableObject;
/**
* Test class for StrSubstitutor.
- *
+ *
* @author Oliver Heger
* @version $Id$
*/
@@ -255,6 +255,57 @@ public class StrSubstitutorTest extends TestCase {
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.
@@ -325,7 +376,7 @@ public class StrSubstitutorTest extends TestCase {
assertEquals(true, sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher);
sub.setVariablePrefix('<');
assertEquals(true, sub.getVariablePrefixMatcher() instanceof StrMatcher.CharMatcher);
-
+
sub.setVariablePrefix("<<");
assertEquals(true, sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher);
try {
@@ -335,7 +386,7 @@ public class StrSubstitutorTest extends TestCase {
// expected
}
assertEquals(true, sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher);
-
+
StrMatcher matcher = StrMatcher.commaMatcher();
sub.setVariablePrefixMatcher(matcher);
assertSame(matcher, sub.getVariablePrefixMatcher());
@@ -356,7 +407,7 @@ public class StrSubstitutorTest extends TestCase {
assertEquals(true, sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher);
sub.setVariableSuffix('<');
assertEquals(true, sub.getVariableSuffixMatcher() instanceof StrMatcher.CharMatcher);
-
+
sub.setVariableSuffix("<<");
assertEquals(true, sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher);
try {
@@ -366,7 +417,7 @@ public class StrSubstitutorTest extends TestCase {
// expected
}
assertEquals(true, sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher);
-
+
StrMatcher matcher = StrMatcher.commaMatcher();
sub.setVariableSuffixMatcher(matcher);
assertSame(matcher, sub.getVariableSuffixMatcher());
@@ -412,7 +463,7 @@ public class StrSubstitutorTest extends TestCase {
+ "working with ${os.name}, your home "
+ "directory is ${user.home}."));
}
-
+
/**
* Test the replace of a properties object
*/
@@ -430,38 +481,38 @@ public class StrSubstitutorTest extends TestCase {
private void doTestReplace(String expectedResult, String replaceTemplate, boolean substring) {
String expectedShortResult = expectedResult.substring(1, expectedResult.length() - 1);
StrSubstitutor sub = new StrSubstitutor(values);
-
+
// replace using String
assertEquals(expectedResult, sub.replace(replaceTemplate));
if (substring) {
assertEquals(expectedShortResult, sub.replace(replaceTemplate, 1, replaceTemplate.length() - 2));
}
-
+
// replace using char[]
char[] chars = replaceTemplate.toCharArray();
assertEquals(expectedResult, sub.replace(chars));
if (substring) {
assertEquals(expectedShortResult, sub.replace(chars, 1, chars.length - 2));
}
-
+
// replace using StringBuffer
StringBuffer buf = new StringBuffer(replaceTemplate);
assertEquals(expectedResult, sub.replace(buf));
if (substring) {
assertEquals(expectedShortResult, sub.replace(buf, 1, buf.length() - 2));
}
-
+
// replace using StrBuilder
StrBuilder bld = new StrBuilder(replaceTemplate);
assertEquals(expectedResult, sub.replace(bld));
if (substring) {
assertEquals(expectedShortResult, sub.replace(bld, 1, bld.length() - 2));
}
-
+
// replace using object
MutableObject