You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by oh...@apache.org on 2013/09/18 21:35:16 UTC
svn commit: r1524541 - in /commons/proper/lang/trunk/src:
main/java/org/apache/commons/lang3/text/StrSubstitutor.java
test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java
Author: oheger
Date: Wed Sep 18 19:35:16 2013
New Revision: 1524541
URL: http://svn.apache.org/r1524541
Log:
[LANG-893] StrSubstitutor now supports the declaration of default values for the variables to be replaced. Thanks to Woonsan Ko for the patch.
Modified:
commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java
commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java
Modified: commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java?rev=1524541&r1=1524540&r2=1524541&view=diff
==============================================================================
--- commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java (original)
+++ commons/proper/lang/trunk/src/main/java/org/apache/commons/lang3/text/StrSubstitutor.java Wed Sep 18 19:35:16 2013
@@ -59,6 +59,26 @@ import java.util.Properties;
* The quick brown fox jumped over the lazy dog.
* </pre>
* <p>
+ * Also, this class allows to set a default value for unresolved variables.
+ * The default value for a variable can be appended to the variable name after the variable
+ * default value delimiter. The default value of the variable default value delimiter is ':-',
+ * as in bash and other *nix shells, as those are arguably where the default ${} delimiter set originated.
+ * The variable default value delimiter can be manually set by calling {@link #setValueDelimiterMatcher(StrMatcher)},
+ * {@link #setValueDelimiter(char)} or {@link #setValueDelimiter(String)}.
+ * The following shows an example with varialbe default value settings:
+ * <pre>
+ * Map valuesMap = HashMap();
+ * valuesMap.put("animal", "quick brown fox");
+ * valuesMap.put("target", "lazy dog");
+ * String templateString = "The ${animal} jumped over the ${target}. ${undefined.number:-1234567890}.";
+ * StrSubstitutor sub = new StrSubstitutor(valuesMap);
+ * String resolvedString = sub.replace(templateString);
+ * </pre>
+ * yielding:
+ * <pre>
+ * The quick brown fox jumped over the lazy dog. 1234567890.
+ * </pre>
+ * <p>
* In addition to this usage pattern there are some static convenience methods that
* cover the most common use cases. These methods can be used without the need of
* manually creating an instance. However if multiple replace operations are to be
@@ -114,6 +134,10 @@ public class StrSubstitutor {
* Constant for the default variable suffix.
*/
public static final StrMatcher DEFAULT_SUFFIX = StrMatcher.stringMatcher("}");
+ /**
+ * Constant for the default value delimiter of a variable.
+ */
+ public static final StrMatcher DEFAULT_VALUE_DELIMITER = StrMatcher.stringMatcher(":-");
/**
* Stores the escape character.
@@ -128,6 +152,10 @@ public class StrSubstitutor {
*/
private StrMatcher suffixMatcher;
/**
+ * Stores the default variable value delimiter
+ */
+ private StrMatcher valueDelimiterMatcher;
+ /**
* Variable resolution is delegated to an implementor of VariableResolver.
*/
private StrLookup<?> variableResolver;
@@ -250,6 +278,21 @@ public class StrSubstitutor {
/**
* Creates a new instance and initializes it.
*
+ * @param <V> the type of the values in the map
+ * @param valueMap the map with the variables' values, may be null
+ * @param prefix the prefix for variables, not null
+ * @param suffix the suffix for variables, not null
+ * @param escape the escape character
+ * @param valueDelimiter the variable default value delimiter, may be null
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ */
+ public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix, final char escape, final String valueDelimiter) {
+ this(StrLookup.mapLookup(valueMap), prefix, suffix, escape, valueDelimiter);
+ }
+
+ /**
+ * Creates a new instance and initializes it.
+ *
* @param variableResolver the variable resolver, may be null
*/
public StrSubstitutor(final StrLookup<?> variableResolver) {
@@ -270,6 +313,25 @@ public class StrSubstitutor {
this.setVariablePrefix(prefix);
this.setVariableSuffix(suffix);
this.setEscapeChar(escape);
+ this.setValueDelimiterMatcher(DEFAULT_VALUE_DELIMITER);
+ }
+
+ /**
+ * Creates a new instance and initializes it.
+ *
+ * @param variableResolver the variable resolver, may be null
+ * @param prefix the prefix for variables, not null
+ * @param suffix the suffix for variables, not null
+ * @param escape the escape character
+ * @param valueDelimiter the variable default value delimiter string, may be null
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ */
+ public StrSubstitutor(final StrLookup<?> variableResolver, final String prefix, final String suffix, final char escape, final String valueDelimiter) {
+ this.setVariableResolver(variableResolver);
+ this.setVariablePrefix(prefix);
+ this.setVariableSuffix(suffix);
+ this.setEscapeChar(escape);
+ this.setValueDelimiter(valueDelimiter);
}
/**
@@ -283,10 +345,26 @@ public class StrSubstitutor {
*/
public StrSubstitutor(
final StrLookup<?> variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher, final char escape) {
+ this(variableResolver, prefixMatcher, suffixMatcher, escape, DEFAULT_VALUE_DELIMITER);
+ }
+
+ /**
+ * Creates a new instance and initializes it.
+ *
+ * @param variableResolver the variable resolver, may be null
+ * @param prefixMatcher the prefix for variables, not null
+ * @param suffixMatcher the suffix for variables, not null
+ * @param escape the escape character
+ * @param valueDelimiterMatcher the variable default value delimiter matcher, may be null
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ */
+ public StrSubstitutor(
+ final StrLookup<?> variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher, final char escape, final StrMatcher valueDelimiterMatcher) {
this.setVariableResolver(variableResolver);
this.setVariablePrefixMatcher(prefixMatcher);
this.setVariableSuffixMatcher(suffixMatcher);
this.setEscapeChar(escape);
+ this.setValueDelimiterMatcher(valueDelimiterMatcher);
}
//-----------------------------------------------------------------------
@@ -410,7 +488,7 @@ public class StrSubstitutor {
substitute(buf, 0, length);
return buf.toString();
}
-
+
/**
* Replaces all the occurrences of variables with their matching values
* from the resolver using the given source as a template.
@@ -591,7 +669,7 @@ public class StrSubstitutor {
source.replace(offset, offset + length, buf.toString());
return true;
}
-
+
//-----------------------------------------------------------------------
/**
* Replaces all the occurrences of variables within the given source
@@ -661,6 +739,8 @@ public class StrSubstitutor {
final StrMatcher prefixMatcher = getVariablePrefixMatcher();
final StrMatcher suffixMatcher = getVariableSuffixMatcher();
final char escape = getEscapeChar();
+ final StrMatcher valueDelimiterMatcher = getValueDelimiterMatcher();
+ final boolean substitutionInVariablesEnabled = isEnableSubstitutionInVariables();
final boolean top = priorVariables == null;
boolean altered = false;
@@ -689,7 +769,7 @@ public class StrSubstitutor {
int endMatchLen = 0;
int nestedVarCount = 0;
while (pos < bufEnd) {
- if (isEnableSubstitutionInVariables()
+ if (substitutionInVariablesEnabled
&& (endMatchLen = prefixMatcher.isMatch(chars,
pos, offset, bufEnd)) != 0) {
// found a nested variable start
@@ -705,17 +785,37 @@ public class StrSubstitutor {
} else {
// found variable end marker
if (nestedVarCount == 0) {
- String varName = new String(chars, startPos
+ String varNameExpr = new String(chars, startPos
+ startMatchLen, pos - startPos
- startMatchLen);
- if (isEnableSubstitutionInVariables()) {
- final StrBuilder bufName = new StrBuilder(varName);
+ if (substitutionInVariablesEnabled) {
+ final StrBuilder bufName = new StrBuilder(varNameExpr);
substitute(bufName, 0, bufName.length());
- varName = bufName.toString();
+ varNameExpr = bufName.toString();
}
pos += endMatchLen;
final int endPos = pos;
+ String varName = varNameExpr;
+ String varDefaultValue = null;
+
+ if (valueDelimiterMatcher != null) {
+ final char [] varNameExprChars = varNameExpr.toCharArray();
+ int valueDelimiterMatchLen = 0;
+ for (int i = 0; i < varNameExprChars.length; i++) {
+ // if there's any nested variable when nested variable substitution disabled, then stop resolving name and default value.
+ if (!substitutionInVariablesEnabled
+ && prefixMatcher.isMatch(varNameExprChars, i, i, varNameExprChars.length) != 0) {
+ break;
+ }
+ if ((valueDelimiterMatchLen = valueDelimiterMatcher.isMatch(varNameExprChars, i)) != 0) {
+ varName = varNameExpr.substring(0, i);
+ varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen);
+ break;
+ }
+ }
+ }
+
// on the first call initialize priorVariables
if (priorVariables == null) {
priorVariables = new ArrayList<String>();
@@ -728,8 +828,11 @@ public class StrSubstitutor {
priorVariables.add(varName);
// resolve the variable
- final String varValue = resolveVariable(varName, buf,
+ String varValue = resolveVariable(varName, buf,
startPos, endPos);
+ if (varValue == null) {
+ varValue = varDefaultValue;
+ }
if (varValue != null) {
// recursive replace
final int varLen = varValue.length();
@@ -960,6 +1063,76 @@ public class StrSubstitutor {
return setVariableSuffixMatcher(StrMatcher.stringMatcher(suffix));
}
+ // Variable Default Value Delimiter
+ //-----------------------------------------------------------------------
+ /**
+ * Gets the variable default value delimiter matcher currently in use.
+ * <p>
+ * The variable default value delimiter is the characer or characters that delimite the
+ * variable name and the variable default value. This delimiter is expressed in terms of a matcher
+ * allowing advanced variable default value delimiter matches.
+ * <p>
+ * If it returns null, then the variable default value resolution is disabled.
+ *
+ * @return the variable default value delimiter matcher in use, may be null
+ */
+ public StrMatcher getValueDelimiterMatcher() {
+ return valueDelimiterMatcher;
+ }
+
+ /**
+ * Sets the variable default value delimiter matcher to use.
+ * <p>
+ * The variable default value delimiter is the characer or characters that delimite the
+ * variable name and the variable default value. This delimiter is expressed in terms of a matcher
+ * allowing advanced variable default value delimiter matches.
+ * <p>
+ * If the <code>valueDelimiterMatcher</code> is null, then the variable default value resolution
+ * becomes disabled.
+ *
+ * @param valueDelimiterMatcher variable default value delimiter matcher to use, may be null
+ * @return this, to enable chaining
+ */
+ public StrSubstitutor setValueDelimiterMatcher(final StrMatcher valueDelimiterMatcher) {
+ this.valueDelimiterMatcher = valueDelimiterMatcher;
+ return this;
+ }
+
+ /**
+ * Sets the variable default value delimiter to use.
+ * <p>
+ * The variable default value delimiter is the characer or characters that delimite the
+ * variable name and the variable default value. This method allows a single character
+ * variable default value delimiter to be easily set.
+ *
+ * @param valueDelimiter the variable default value delimiter character to use
+ * @return this, to enable chaining
+ */
+ public StrSubstitutor setValueDelimiter(final char valueDelimiter) {
+ return setValueDelimiterMatcher(StrMatcher.charMatcher(valueDelimiter));
+ }
+
+ /**
+ * Sets the variable default value delimiter to use.
+ * <p>
+ * The variable default value delimiter is the characer or characters that delimite the
+ * variable name and the variable default value. This method allows a string
+ * variable default value delimiter to be easily set.
+ * <p>
+ * If the <code>valueDelimiter</code> is null or empty string, then the variable default
+ * value resolution becomes disabled.
+ *
+ * @param valueDelimiter the variable default value delimiter string to use, may be null or empty
+ * @return this, to enable chaining
+ */
+ public StrSubstitutor setValueDelimiter(final String valueDelimiter) {
+ if (valueDelimiter == null || valueDelimiter.length() == 0) {
+ setValueDelimiterMatcher(null);
+ return this;
+ }
+ return setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter));
+ }
+
// Resolver
//-----------------------------------------------------------------------
/**
Modified: commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java
URL: http://svn.apache.org/viewvc/commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java?rev=1524541&r1=1524540&r2=1524541&view=diff
==============================================================================
--- commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java (original)
+++ commons/proper/lang/trunk/src/test/java/org/apache/commons/lang3/text/StrSubstitutorTest.java Wed Sep 18 19:35:16 2013
@@ -17,15 +17,21 @@
package org.apache.commons.lang3.text;
-import org.junit.After;
-import org.junit.Test;
-import org.junit.Before;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.lang3.mutable.MutableObject;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
/**
* Test class for StrSubstitutor.
@@ -105,6 +111,7 @@ public class StrSubstitutorTest {
@Test
public void testReplaceUnknownKey() {
doTestReplace("The ${person} jumps over the lazy dog.", "The ${person} jumps over the ${target}.", true);
+ doTestReplace("The ${person} jumps over the lazy dog. 1234567890.", "The ${person} jumps over the ${target}. ${undefined.number:-1234567890}.", true);
}
/**
@@ -143,6 +150,9 @@ public class StrSubstitutorTest {
values.put("critterColor", "brown");
values.put("critterType", "fox");
doTestReplace("The quick brown fox jumps over the lazy dog.", "The ${animal} jumps over the ${target}.", true);
+
+ values.put("pet", "${petCharacteristicUnknown:-lazy} dog");
+ doTestReplace("The quick brown fox jumps over the lazy dog.", "The ${animal} jumps over the ${target}.", true);
}
/**
@@ -167,6 +177,7 @@ public class StrSubstitutorTest {
@Test
public void testReplaceComplexEscaping() {
doTestReplace("The ${quick brown fox} jumps over the lazy dog.", "The $${${animal}} jumps over the ${target}.", true);
+ doTestReplace("The ${quick brown fox} jumps over the lazy dog. ${1234567890}.", "The $${${animal}} jumps over the ${target}. $${${undefined.number:-1234567890}}.", true);
}
/**
@@ -207,6 +218,7 @@ public class StrSubstitutorTest {
@Test
public void testReplaceEmptyKeys() {
doTestReplace("The ${} jumps over the lazy dog.", "The ${} jumps over the ${target}.", true);
+ doTestReplace("The animal jumps over the lazy dog.", "The ${:-animal} jumps over the ${target}.", true);
}
/**
@@ -234,7 +246,17 @@ public class StrSubstitutorTest {
map.put("critterSpeed", "quick");
map.put("critterColor", "brown");
map.put("critterType", "${animal}");
- final StrSubstitutor sub = new StrSubstitutor(map);
+ StrSubstitutor sub = new StrSubstitutor(map);
+ try {
+ sub.replace("The ${animal} jumps over the ${target}.");
+ fail("Cyclic replacement was not detected!");
+ } catch (final IllegalStateException ex) {
+ // expected
+ }
+
+ // also check even when default value is set.
+ map.put("critterType", "${animal:-fox}");
+ sub = new StrSubstitutor(map);
try {
sub.replace("The ${animal} jumps over the ${target}.");
fail("Cyclic replacement was not detected!");
@@ -295,6 +317,10 @@ public class StrSubstitutorTest {
"Wrong result (2)",
"The fox jumps over the lazy dog.",
sub.replace("The ${animal.${species}} jumps over the ${target}."));
+ assertEquals(
+ "Wrong result (3)",
+ "The fox jumps over the lazy dog.",
+ sub.replace("The ${unknown.animal.${unknown.species:-1}:-fox} jumps over the ${unknow.target:-lazy dog}."));
}
/**
@@ -307,9 +333,13 @@ public class StrSubstitutorTest {
values.put("species", "2");
final StrSubstitutor sub = new StrSubstitutor(values);
assertEquals(
- "Wrong result",
+ "Wrong result (1)",
"The ${animal.${species}} jumps over the lazy dog.",
sub.replace("The ${animal.${species}} jumps over the ${target}."));
+ assertEquals(
+ "Wrong result (2)",
+ "The ${animal.${species:-1}} jumps over the lazy dog.",
+ sub.replace("The ${animal.${species:-1}} jumps over the ${target}."));
}
/**
@@ -325,9 +355,46 @@ public class StrSubstitutorTest {
final StrSubstitutor sub = new StrSubstitutor(values);
sub.setEnableSubstitutionInVariables(true);
assertEquals(
- "Wrong result",
+ "Wrong result (1)",
"The white mouse jumps over the lazy dog.",
sub.replace("The ${animal.${species.${color}}} jumps over the ${target}."));
+ assertEquals(
+ "Wrong result (2)",
+ "The brown fox jumps over the lazy dog.",
+ sub.replace("The ${animal.${species.${unknownColor:-brown}}} jumps over the ${target}."));
+ }
+
+ @Test
+ public void testDefaultValueDelimiters() {
+ final Map<String, String> map = new HashMap<String, String>();
+ map.put("animal", "fox");
+ map.put("target", "dog");
+
+ StrSubstitutor sub = new StrSubstitutor(map, "${", "}", '$');
+ assertEquals("The fox jumps over the lazy dog. 1234567890.",
+ sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number:-1234567890}."));
+
+ sub = new StrSubstitutor(map, "${", "}", '$', "?:");
+ assertEquals("The fox jumps over the lazy dog. 1234567890.",
+ sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number?:1234567890}."));
+
+ sub = new StrSubstitutor(map, "${", "}", '$', "||");
+ assertEquals("The fox jumps over the lazy dog. 1234567890.",
+ sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number||1234567890}."));
+
+ sub = new StrSubstitutor(map, "${", "}", '$', "!");
+ assertEquals("The fox jumps over the lazy dog. 1234567890.",
+ sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number!1234567890}."));
+
+ sub = new StrSubstitutor(map, "${", "}", '$', "");
+ sub.setValueDelimiterMatcher(null);
+ assertEquals("The fox jumps over the lazy dog. ${undefined.number!1234567890}.",
+ sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number!1234567890}."));
+
+ sub = new StrSubstitutor(map, "${", "}", '$');
+ sub.setValueDelimiterMatcher(null);
+ assertEquals("The fox jumps over the lazy dog. ${undefined.number!1234567890}.",
+ sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number!1234567890}."));
}
//-----------------------------------------------------------------------
@@ -381,8 +448,10 @@ public class StrSubstitutorTest {
public void testConstructorMapFull() {
final Map<String, String> map = new HashMap<String, String>();
map.put("name", "commons");
- final StrSubstitutor sub = new StrSubstitutor(map, "<", ">", '!');
+ StrSubstitutor sub = new StrSubstitutor(map, "<", ">", '!');
assertEquals("Hi < commons", sub.replace("Hi !< <name>"));
+ sub = new StrSubstitutor(map, "<", ">", '!', "||");
+ assertEquals("Hi < commons", sub.replace("Hi !< <name2||commons>"));
}
//-----------------------------------------------------------------------
@@ -461,6 +530,28 @@ public class StrSubstitutorTest {
assertSame(matcher, sub.getVariableSuffixMatcher());
}
+ /**
+ * Tests get set.
+ */
+ @Test
+ public void testGetSetValueDelimiter() {
+ final StrSubstitutor sub = new StrSubstitutor();
+ assertTrue(sub.getValueDelimiterMatcher() instanceof StrMatcher.StringMatcher);
+ sub.setValueDelimiter(':');
+ assertTrue(sub.getValueDelimiterMatcher() instanceof StrMatcher.CharMatcher);
+
+ sub.setValueDelimiter("||");
+ assertTrue(sub.getValueDelimiterMatcher() instanceof StrMatcher.StringMatcher);
+ sub.setValueDelimiter((String) null);
+ assertNull(sub.getValueDelimiterMatcher());
+
+ final StrMatcher matcher = StrMatcher.commaMatcher();
+ sub.setValueDelimiterMatcher(matcher);
+ assertSame(matcher, sub.getValueDelimiterMatcher());
+ sub.setValueDelimiterMatcher((StrMatcher) null);
+ assertNull(sub.getValueDelimiterMatcher());
+ }
+
//-----------------------------------------------------------------------
/**
* Tests static.