You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2018/02/12 17:22:07 UTC
[1/5] [text] [TEXT-114] Add a replacement for StrSubstitutor based on
interfaces: StringSubstitutor.
Repository: commons-text
Updated Branches:
refs/heads/master 3e649a798 -> 4b67dd516
http://git-wip-us.apache.org/repos/asf/commons-text/blob/4b67dd51/src/test/java/org/apache/commons/text/StringSubstitutorTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/StringSubstitutorTest.java b/src/test/java/org/apache/commons/text/StringSubstitutorTest.java
new file mode 100644
index 0000000..6f62fe8
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/StringSubstitutorTest.java
@@ -0,0 +1,622 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.text;
+
+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 java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.commons.lang3.mutable.MutableObject;
+import org.apache.commons.text.lookup.StringLookup;
+import org.apache.commons.text.lookup.StringLookupFactory;
+import org.apache.commons.text.matcher.StringMatcher;
+import org.apache.commons.text.matcher.StringMatcherFactory;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Test class for {@link StringSubstitutor}.
+ */
+public class StringSubstitutorTest {
+
+ private Map<String, String> values;
+
+ private void doTestNoReplace(final String replaceTemplate) {
+ final StringSubstitutor sub = new StringSubstitutor(values);
+
+ if (replaceTemplate == null) {
+ assertNull(sub.replace((String) null));
+ assertNull(sub.replace((String) null, 0, 100));
+ assertNull(sub.replace((char[]) null));
+ assertNull(sub.replace((char[]) null, 0, 100));
+ assertNull(sub.replace((StringBuffer) null));
+ assertNull(sub.replace((StringBuffer) null, 0, 100));
+ assertNull(sub.replace((StrBuilder) null));
+ assertNull(sub.replace((StrBuilder) null, 0, 100));
+ assertNull(sub.replace((Object) null));
+ assertFalse(sub.replaceIn((StringBuffer) null));
+ assertFalse(sub.replaceIn((StringBuffer) null, 0, 100));
+ assertFalse(sub.replaceIn((StrBuilder) null));
+ assertFalse(sub.replaceIn((StrBuilder) null, 0, 100));
+ } else {
+ assertEquals(replaceTemplate, sub.replace(replaceTemplate));
+ final StrBuilder bld = new StrBuilder(replaceTemplate);
+ assertFalse(sub.replaceIn(bld));
+ assertEquals(replaceTemplate, bld.toString());
+ }
+ }
+
+ private void doTestReplace(final String expectedResult, final String replaceTemplate, final boolean substring) {
+ final StringSubstitutor sub = new StringSubstitutor(values);
+ doTestReplace(sub, expectedResult, replaceTemplate, substring);
+ }
+
+ //-----------------------------------------------------------------------
+ private void doTestReplace(final StringSubstitutor sub, final String expectedResult, final String replaceTemplate,
+ final boolean substring) {
+ final String expectedShortResult = expectedResult.substring(1, expectedResult.length() - 1);
+
+ // replace using String
+ assertEquals(expectedResult, sub.replace(replaceTemplate));
+ if (substring) {
+ assertEquals(expectedShortResult, sub.replace(replaceTemplate, 1, replaceTemplate.length() - 2));
+ }
+
+ // replace using char[]
+ final 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 StringBuilder
+ StringBuilder builder = new StringBuilder(replaceTemplate);
+ assertEquals(expectedResult, sub.replace(builder));
+ if (substring) {
+ assertEquals(expectedShortResult, sub.replace(builder, 1, builder.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
+ final MutableObject<String> obj = new MutableObject<>(replaceTemplate); // toString returns template
+ assertEquals(expectedResult, sub.replace(obj));
+
+ // replace in StringBuffer
+ buf = new StringBuffer(replaceTemplate);
+ assertTrue(sub.replaceIn(buf));
+ assertEquals(expectedResult, buf.toString());
+ if (substring) {
+ buf = new StringBuffer(replaceTemplate);
+ assertTrue(sub.replaceIn(buf, 1, buf.length() - 2));
+ assertEquals(expectedResult, buf.toString()); // expect full result as remainder is untouched
+ }
+
+ // replace in StringBuilder
+ builder = new StringBuilder(replaceTemplate);
+ assertTrue(sub.replaceIn(builder));
+ assertEquals(expectedResult, builder.toString());
+ if (substring) {
+ builder = new StringBuilder(replaceTemplate);
+ assertTrue(sub.replaceIn(builder, 1, builder.length() - 2));
+ assertEquals(expectedResult, builder.toString()); // expect full result as remainder is untouched
+ }
+
+ // replace in StrBuilder
+ bld = new StrBuilder(replaceTemplate);
+ assertTrue(sub.replaceIn(bld));
+ assertEquals(expectedResult, bld.toString());
+ if (substring) {
+ bld = new StrBuilder(replaceTemplate);
+ assertTrue(sub.replaceIn(bld, 1, bld.length() - 2));
+ assertEquals(expectedResult, bld.toString()); // expect full result as remainder is untouched
+ }
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ values = new HashMap<>();
+ values.put("animal", "quick brown fox");
+ values.put("target", "lazy dog");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ values = null;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Tests get set.
+ */
+ @Test
+ public void testGetSetEscape() {
+ final StringSubstitutor sub = new StringSubstitutor();
+ assertEquals('$', sub.getEscapeChar());
+ sub.setEscapeChar('<');
+ assertEquals('<', sub.getEscapeChar());
+ }
+
+ /**
+ * Test for LANG-1055: StringSubstitutor.replaceSystemProperties does not work consistently
+ */
+ @Test
+ public void testLANG1055() {
+ System.setProperty("test_key", "test_value");
+
+ final String expected = StringSubstitutor.replace("test_key=${test_key}", System.getProperties());
+ final String actual = StringSubstitutor.replaceSystemProperties("test_key=${test_key}");
+ assertEquals(expected, actual);
+ }
+
+ /**
+ * Tests adjacent keys.
+ */
+ @Test
+ public void testReplaceAdjacentAtEnd() {
+ values.put("code", "GBP");
+ values.put("amount", "12.50");
+ final StringSubstitutor sub = new StringSubstitutor(values);
+ assertEquals("Amount is GBP12.50", sub.replace("Amount is ${code}${amount}"));
+ }
+
+ /**
+ * Tests adjacent keys.
+ */
+ @Test
+ public void testReplaceAdjacentAtStart() {
+ values.put("code", "GBP");
+ values.put("amount", "12.50");
+ final StringSubstitutor sub = new StringSubstitutor(values);
+ assertEquals("GBP12.50 charged", sub.replace("${code}${amount} charged"));
+ }
+
+ /**
+ * Tests key replace changing map after initialization (not recommended).
+ */
+ @Test
+ public void testReplaceChangedMap() {
+ final StringSubstitutor sub = new StringSubstitutor(values);
+ values.put("target", "moon");
+ assertEquals("The quick brown fox jumps over the moon.",
+ sub.replace("The ${animal} jumps over the ${target}."));
+ }
+
+ /**
+ * Tests complex escaping.
+ */
+ @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);
+ }
+
+ /**
+ * Tests replace with null.
+ */
+ @Test
+ public void testReplaceEmpty() {
+ doTestNoReplace("");
+ }
+
+ /**
+ * Tests when no variable name.
+ */
+ @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);
+ }
+
+ /**
+ * Tests escaping.
+ */
+ @Test
+ public void testReplaceEscaping() {
+ doTestReplace("The ${animal} jumps over the lazy dog.", "The $${animal} jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests when no incomplete prefix.
+ */
+ @Test
+ public void testReplaceIncompletePrefix() {
+ doTestReplace("The {animal} jumps over the lazy dog.", "The {animal} jumps over the ${target}.", true);
+ }
+
+ @Test
+ public void testReplaceInTakingStringBufferWithNonNull() {
+ final StringSubstitutor strSubstitutor =
+ new StringSubstitutor(new HashMap<String, String>(), "WV@i#y?N*[", "WV@i#y?N*[", '*');
+
+ assertFalse(strSubstitutor.isPreserveEscapes());
+ assertFalse(strSubstitutor.replaceIn(new StringBuffer("WV@i#y?N*[")));
+ assertEquals('*', strSubstitutor.getEscapeChar());
+ }
+
+ @Test
+ public void testReplaceInTakingStringBuilderWithNonNull() {
+ final StringLookup strLookup = StringLookupFactory.INSTANCE.systemPropertyStringLookup();
+ final StringSubstitutor strSubstitutor = new StringSubstitutor(strLookup, "b<H", "b<H", '\'');
+ final StringBuilder stringBuilder = new StringBuilder((CharSequence) "b<H");
+
+ assertEquals('\'', strSubstitutor.getEscapeChar());
+ assertFalse(strSubstitutor.replaceIn(stringBuilder));
+ }
+
+ @Test
+ public void testReplaceInTakingStringBuilderWithNull() {
+ final Map<String, Object> map = new HashMap<>();
+ final StringSubstitutor strSubstitutor = new StringSubstitutor(map, "", "", 'T', "K+<'f");
+
+ assertFalse(strSubstitutor.replaceIn((StringBuilder) null));
+ }
+
+ @Test
+ public void testReplaceInTakingTwoAndThreeIntsReturningFalse() {
+ final Map<String, Object> hashMap = new HashMap<>();
+ final StringLookup mapStringLookup = StringLookupFactory.INSTANCE.mapStringLookup(hashMap);
+ final StringMatcher strMatcher = StringMatcherFactory.INSTANCE.tabMatcher();
+ final StringSubstitutor strSubstitutor =
+ new StringSubstitutor(mapStringLookup, strMatcher, strMatcher, 'b', strMatcher);
+
+ assertFalse(strSubstitutor.replaceIn((StringBuilder) null, 1315, (-1369)));
+ assertEquals('b', strSubstitutor.getEscapeChar());
+ assertFalse(strSubstitutor.isPreserveEscapes());
+ }
+
+ /**
+ * Tests whether a variable can be replaced in a variable name.
+ */
+ @Test
+ public void testReplaceInVariable() {
+ values.put("animal.1", "fox");
+ values.put("animal.2", "mouse");
+ values.put("species", "2");
+ final StringSubstitutor sub = new StringSubstitutor(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}."));
+ 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}."));
+ }
+
+ /**
+ * Tests whether substitution in variable names is disabled per default.
+ */
+ @Test
+ public void testReplaceInVariableDisabled() {
+ values.put("animal.1", "fox");
+ values.put("animal.2", "mouse");
+ values.put("species", "2");
+ final StringSubstitutor sub = new StringSubstitutor(values);
+ assertEquals(
+ "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}."));
+ }
+
+ /**
+ * Tests complex and recursive substitution in variable names.
+ */
+ @Test
+ 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");
+ final StringSubstitutor sub = new StringSubstitutor(values);
+ sub.setEnableSubstitutionInVariables(true);
+ assertEquals(
+ "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}."));
+ }
+
+ /**
+ * Tests when no prefix or suffix.
+ */
+ @Test
+ public void testReplaceNoPrefixNoSuffix() {
+ doTestReplace("The animal jumps over the lazy dog.", "The animal jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests when suffix but no prefix.
+ */
+ @Test
+ public void testReplaceNoPrefixSuffix() {
+ doTestReplace("The animal} jumps over the lazy dog.", "The animal} jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests replace with no variables.
+ */
+ @Test
+ public void testReplaceNoVariables() {
+ doTestNoReplace("The balloon arrived.");
+ }
+
+ /**
+ * Tests replace with null.
+ */
+ @Test
+ public void testReplaceNull() {
+ doTestNoReplace(null);
+ }
+
+ /**
+ * Tests simple key replace.
+ */
+ @Test
+ public void testReplacePartialString_noReplace() {
+ final StringSubstitutor sub = new StringSubstitutor();
+ assertEquals("${animal} jumps", sub.replace("The ${animal} jumps over the ${target}.", 4, 15));
+ }
+
+ /**
+ * Tests when prefix but no suffix.
+ */
+ @Test
+ public void testReplacePrefixNoSuffix() {
+ doTestReplace("The ${animal jumps over the ${target} lazy dog.",
+ "The ${animal jumps over the ${target} ${target}.", true);
+ }
+
+ /**
+ * Tests simple recursive replace.
+ */
+ @Test
+ public void testReplaceRecursive() {
+ values.put("animal", "${critter}");
+ values.put("target", "${pet}");
+ values.put("pet", "${petCharacteristic} dog");
+ values.put("petCharacteristic", "lazy");
+ values.put("critter", "${critterSpeed} ${critterColor} ${critterType}");
+ values.put("critterSpeed", "quick");
+ 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);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Tests simple key replace.
+ */
+ @Test
+ public void testReplaceSimple() {
+ doTestReplace("The quick brown fox jumps over the lazy dog.", "The ${animal} jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests simple key replace.
+ */
+ @Test
+ public void testReplaceSolo() {
+ doTestReplace("quick brown fox", "${animal}", false);
+ }
+
+ /**
+ * Tests escaping.
+ */
+ @Test
+ public void testReplaceSoloEscaping() {
+ doTestReplace("${animal}", "$${animal}", false);
+ }
+
+ @Test
+ public void testReplaceTakingCharSequenceReturningNull() {
+ final StringSubstitutor strSubstitutor = new StringSubstitutor((StringLookup) null);
+
+ assertNull(strSubstitutor.replace((CharSequence) null));
+ assertFalse(strSubstitutor.isPreserveEscapes());
+ assertEquals('$', strSubstitutor.getEscapeChar());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testReplaceTakingThreeArgumentsThrowsNullPointerException() {
+ StringSubstitutor.replace(null, (Properties) null);
+ }
+
+ /**
+ * Tests replace creates output same as input.
+ */
+ @Test
+ public void testReplaceToIdentical() {
+ values.put("animal", "$${${thing}}");
+ values.put("thing", "animal");
+ doTestReplace("The ${animal} jumps.", "The ${animal} jumps.", true);
+ }
+
+ /**
+ * Tests unknown key replace.
+ */
+ @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);
+ }
+
+ /**
+ * Tests interpolation with weird boundary patterns.
+ */
+ @Test
+ public void testReplaceWeirdPattens() {
+ doTestNoReplace("");
+ doTestNoReplace("${}");
+ doTestNoReplace("${ }");
+ doTestNoReplace("${\t}");
+ doTestNoReplace("${\n}");
+ doTestNoReplace("${\b}");
+ doTestNoReplace("${");
+ doTestNoReplace("$}");
+ doTestNoReplace("}");
+ doTestNoReplace("${}$");
+ doTestNoReplace("${${");
+ doTestNoReplace("${${}}");
+ doTestNoReplace("${$${}}");
+ doTestNoReplace("${$$${}}");
+ doTestNoReplace("${$$${$}}");
+ doTestNoReplace("${${}}");
+ doTestNoReplace("${${ }}");
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Tests protected.
+ */
+ @Test
+ public void testResolveVariable() {
+ final StrBuilder builder = new StrBuilder("Hi ${name}!");
+ final Map<String, String> map = new HashMap<>();
+ map.put("name", "commons");
+ final StringSubstitutor sub = new StringSubstitutor(map) {
+ @Override
+ protected String resolveVariable(final String variableName, final StrBuilder buf, final int startPos,
+ final int endPos) {
+ assertEquals("name", variableName);
+ assertSame(builder, buf);
+ assertEquals(3, startPos);
+ assertEquals(10, endPos);
+ return "jakarta";
+ }
+ };
+ sub.replaceIn(builder);
+ assertEquals("Hi jakarta!", builder.toString());
+ }
+
+ @Test
+ public void testSamePrefixAndSuffix() {
+ final Map<String, String> map = new HashMap<>();
+ map.put("greeting", "Hello");
+ map.put(" there ", "XXX");
+ map.put("name", "commons");
+ assertEquals("Hi commons!", StringSubstitutor.replace("Hi @name@!", map, "@", "@"));
+ assertEquals("Hello there commons!", StringSubstitutor.replace("@greeting@ there @name@!", map, "@", "@"));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Tests static.
+ */
+ @Test
+ public void testStaticReplace() {
+ final Map<String, String> map = new HashMap<>();
+ map.put("name", "commons");
+ assertEquals("Hi commons!", StringSubstitutor.replace("Hi ${name}!", map));
+ }
+
+ /**
+ * Tests static.
+ */
+ @Test
+ public void testStaticReplacePrefixSuffix() {
+ final Map<String, String> map = new HashMap<>();
+ map.put("name", "commons");
+ assertEquals("Hi commons!", StringSubstitutor.replace("Hi <name>!", map, "<", ">"));
+ }
+
+ /**
+ * Tests interpolation with system properties.
+ */
+ @Test
+ public void testStaticReplaceSystemProperties() {
+ final StrBuilder buf = new StrBuilder();
+ buf.append("Hi ").append(System.getProperty("user.name"));
+ buf.append(", you are working with ");
+ buf.append(System.getProperty("os.name"));
+ buf.append(", your home directory is ");
+ buf.append(System.getProperty("user.home")).append('.');
+ assertEquals(buf.toString(), StringSubstitutor.replaceSystemProperties("Hi ${user.name}, you are "
+ + "working with ${os.name}, your home "
+ + "directory is ${user.home}."));
+ }
+
+ /**
+ * Test the replace of a properties object
+ */
+ @Test
+ public void testSubstituteDefaultProperties() {
+ final String org = "${doesnotwork}";
+ System.setProperty("doesnotwork", "It works!");
+
+ // create a new Properties object with the System.getProperties as default
+ final Properties props = new Properties(System.getProperties());
+
+ assertEquals("It works!", StringSubstitutor.replace(org, props));
+ }
+
+ @Test
+ public void testSubstitutePreserveEscape() {
+ final String org = "${not-escaped} $${escaped}";
+ final Map<String, String> map = new HashMap<>();
+ map.put("not-escaped", "value");
+
+ final StringSubstitutor sub = new StringSubstitutor(map, "${", "}", '$');
+ assertFalse(sub.isPreserveEscapes());
+ assertEquals("value ${escaped}", sub.replace(org));
+
+ sub.setPreserveEscapes(true);
+ assertTrue(sub.isPreserveEscapes());
+ assertEquals("value $${escaped}", sub.replace(org));
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/commons-text/blob/4b67dd51/src/test/java/org/apache/commons/text/StringSubstitutorWithInterpolatorStringLookupTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/StringSubstitutorWithInterpolatorStringLookupTest.java b/src/test/java/org/apache/commons/text/StringSubstitutorWithInterpolatorStringLookupTest.java
new file mode 100644
index 0000000..e8bca78
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/StringSubstitutorWithInterpolatorStringLookupTest.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.text;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.commons.text.lookup.StringLookupFactory;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class StringSubstitutorWithInterpolatorStringLookupTest {
+
+ @Test
+ public void testMapAndSystemProperty() {
+ final String key = "key";
+ final String value = "value";
+ final Map<String, String> map = new HashMap<>();
+ map.put(key, value);
+ final StringSubstitutor strSubst = new StringSubstitutor(
+ StringLookupFactory.INSTANCE.interpolatorStringLookup(map));
+ final String spKey = "user.name";
+ Assert.assertEquals(System.getProperty(spKey), strSubst.replace("${sys:" + spKey + "}"));
+ Assert.assertEquals(value, strSubst.replace("${" + key + "}"));
+ }
+
+ @Test
+ public void testSystemProperty() {
+ final StringSubstitutor strSubst = new StringSubstitutor(
+ StringLookupFactory.INSTANCE.interpolatorStringLookup());
+ final String spKey = "user.name";
+ Assert.assertEquals(System.getProperty(spKey), strSubst.replace("${sys:" + spKey + "}"));
+ }
+}
http://git-wip-us.apache.org/repos/asf/commons-text/blob/4b67dd51/src/test/java/org/apache/commons/text/matcher/StringMatcherTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/matcher/StringMatcherTest.java b/src/test/java/org/apache/commons/text/matcher/StringMatcherTest.java
new file mode 100644
index 0000000..6dcd792
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/matcher/StringMatcherTest.java
@@ -0,0 +1,219 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.text.matcher;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link StringMatcher}.
+ */
+public class StringMatcherTest {
+
+ private static final char[] BUFFER1 = "0,1\t2 3\n\r\f\u0000'\"".toCharArray();
+
+ private static final char[] BUFFER2 = "abcdef".toCharArray();
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testCommaMatcher() {
+ final StringMatcher matcher = StringMatcherFactory.INSTANCE.commaMatcher();
+ assertThat(StringMatcherFactory.INSTANCE.commaMatcher()).isSameAs(matcher);
+ assertThat(matcher.isMatch(BUFFER1, 0, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 1, 0, BUFFER1.length)).isEqualTo(1);
+ assertThat(matcher.isMatch(BUFFER1, 2, 0, BUFFER1.length)).isEqualTo(0);
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testTabMatcher() {
+ final StringMatcher matcher = StringMatcherFactory.INSTANCE.tabMatcher();
+ assertThat(StringMatcherFactory.INSTANCE.tabMatcher()).isSameAs(matcher);
+ assertThat(matcher.isMatch(BUFFER1, 2, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 3, 0, BUFFER1.length)).isEqualTo(1);
+ assertThat(matcher.isMatch(BUFFER1, 4, 0, BUFFER1.length)).isEqualTo(0);
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testSpaceMatcher() {
+ final StringMatcher matcher = StringMatcherFactory.INSTANCE.spaceMatcher();
+ assertThat(StringMatcherFactory.INSTANCE.spaceMatcher()).isSameAs(matcher);
+ assertThat(matcher.isMatch(BUFFER1, 4, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 5, 0, BUFFER1.length)).isEqualTo(1);
+ assertThat(matcher.isMatch(BUFFER1, 6, 0, BUFFER1.length)).isEqualTo(0);
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testSplitMatcher() {
+ final StringMatcher matcher = StringMatcherFactory.INSTANCE.splitMatcher();
+ assertThat(StringMatcherFactory.INSTANCE.splitMatcher()).isSameAs(matcher);
+ assertThat(matcher.isMatch(BUFFER1, 2, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 3, 0, BUFFER1.length)).isEqualTo(1);
+ assertThat(matcher.isMatch(BUFFER1, 4, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 5, 0, BUFFER1.length)).isEqualTo(1);
+ assertThat(matcher.isMatch(BUFFER1, 6, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 7, 0, BUFFER1.length)).isEqualTo(1);
+ assertThat(matcher.isMatch(BUFFER1, 8, 0, BUFFER1.length)).isEqualTo(1);
+ assertThat(matcher.isMatch(BUFFER1, 9, 0, BUFFER1.length)).isEqualTo(1);
+ assertThat(matcher.isMatch(BUFFER1, 10, 0, BUFFER1.length)).isEqualTo(0);
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testTrimMatcher() {
+ final StringMatcher matcher = StringMatcherFactory.INSTANCE.trimMatcher();
+ assertThat(StringMatcherFactory.INSTANCE.trimMatcher()).isSameAs(matcher);
+ assertThat(matcher.isMatch(BUFFER1, 2, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 3, 0, BUFFER1.length)).isEqualTo(1);
+ assertThat(matcher.isMatch(BUFFER1, 4, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 5, 0, BUFFER1.length)).isEqualTo(1);
+ assertThat(matcher.isMatch(BUFFER1, 6, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 7, 0, BUFFER1.length)).isEqualTo(1);
+ assertThat(matcher.isMatch(BUFFER1, 8, 0, BUFFER1.length)).isEqualTo(1);
+ assertThat(matcher.isMatch(BUFFER1, 9, 0, BUFFER1.length)).isEqualTo(1);
+ assertThat(matcher.isMatch(BUFFER1, 10, 0, BUFFER1.length)).isEqualTo(1);
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testSingleQuoteMatcher() {
+ final StringMatcher matcher = StringMatcherFactory.INSTANCE.singleQuoteMatcher();
+ assertThat(StringMatcherFactory.INSTANCE.singleQuoteMatcher()).isSameAs(matcher);
+ assertThat(matcher.isMatch(BUFFER1, 10, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 11, 0, BUFFER1.length)).isEqualTo(1);
+ assertThat(matcher.isMatch(BUFFER1, 12, 0, BUFFER1.length)).isEqualTo(0);
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testDoubleQuoteMatcher() {
+ final StringMatcher matcher = StringMatcherFactory.INSTANCE.doubleQuoteMatcher();
+ assertThat(StringMatcherFactory.INSTANCE.doubleQuoteMatcher()).isSameAs(matcher);
+ assertThat(matcher.isMatch(BUFFER1, 11, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 12, 0, BUFFER1.length)).isEqualTo(1);
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testQuoteMatcher() {
+ final StringMatcher matcher = StringMatcherFactory.INSTANCE.quoteMatcher();
+ assertThat(StringMatcherFactory.INSTANCE.quoteMatcher()).isSameAs(matcher);
+ assertThat(matcher.isMatch(BUFFER1, 10, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 11, 0, BUFFER1.length)).isEqualTo(1);
+ assertThat(matcher.isMatch(BUFFER1, 12, 0, BUFFER1.length)).isEqualTo(1);
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testNoneMatcher() {
+ final StringMatcher matcher = StringMatcherFactory.INSTANCE.noneMatcher();
+ assertThat(StringMatcherFactory.INSTANCE.noneMatcher()).isSameAs(matcher);
+ assertThat(matcher.isMatch(BUFFER1, 0, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 1, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 2, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 3, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 4, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 5, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 6, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 7, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 8, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 9, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 10, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 11, 0, BUFFER1.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER1, 12, 0, BUFFER1.length)).isEqualTo(0);
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testCharMatcher_char() {
+ final StringMatcher matcher = StringMatcherFactory.INSTANCE.charMatcher('c');
+ assertThat(matcher.isMatch(BUFFER2, 0, 0, BUFFER2.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER2, 1, 0, BUFFER2.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER2, 2, 0, BUFFER2.length)).isEqualTo(1);
+ assertThat(matcher.isMatch(BUFFER2, 3, 0, BUFFER2.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER2, 4, 0, BUFFER2.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER2, 5, 0, BUFFER2.length)).isEqualTo(0);
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testCharSetMatcher_String() {
+ final StringMatcher matcher = StringMatcherFactory.INSTANCE.charSetMatcher("ace");
+ assertThat(matcher.isMatch(BUFFER2, 0, 0, BUFFER2.length)).isEqualTo(1);
+ assertThat(matcher.isMatch(BUFFER2, 1, 0, BUFFER2.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER2, 2, 0, BUFFER2.length)).isEqualTo(1);
+ assertThat(matcher.isMatch(BUFFER2, 3, 0, BUFFER2.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER2, 4, 0, BUFFER2.length)).isEqualTo(1);
+ assertThat(matcher.isMatch(BUFFER2, 5, 0, BUFFER2.length)).isEqualTo(0);
+ assertThat(StringMatcherFactory.INSTANCE.charSetMatcher(""))
+ .isSameAs(StringMatcherFactory.INSTANCE.noneMatcher());
+ assertThat(StringMatcherFactory.INSTANCE.charSetMatcher((String) null))
+ .isSameAs(StringMatcherFactory.INSTANCE.noneMatcher());
+ assertThat(StringMatcherFactory.INSTANCE.charSetMatcher("a") instanceof AbstractStringMatcher.CharMatcher)
+ .isTrue();
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testCharSetMatcher_charArray() {
+ final StringMatcher matcher = StringMatcherFactory.INSTANCE.charSetMatcher("ace".toCharArray());
+ assertThat(matcher.isMatch(BUFFER2, 0, 0, BUFFER2.length)).isEqualTo(1);
+ assertThat(matcher.isMatch(BUFFER2, 1, 0, BUFFER2.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER2, 2, 0, BUFFER2.length)).isEqualTo(1);
+ assertThat(matcher.isMatch(BUFFER2, 3, 0, BUFFER2.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER2, 4, 0, BUFFER2.length)).isEqualTo(1);
+ assertThat(matcher.isMatch(BUFFER2, 5, 0, BUFFER2.length)).isEqualTo(0);
+ assertThat(StringMatcherFactory.INSTANCE.charSetMatcher(new char[0]))
+ .isSameAs(StringMatcherFactory.INSTANCE.noneMatcher());
+ assertThat(StringMatcherFactory.INSTANCE.charSetMatcher((char[]) null))
+ .isSameAs(StringMatcherFactory.INSTANCE.noneMatcher());
+ assertThat(StringMatcherFactory.INSTANCE
+ .charSetMatcher("a".toCharArray()) instanceof AbstractStringMatcher.CharMatcher).isTrue();
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testStringMatcher_String() {
+ final StringMatcher matcher = StringMatcherFactory.INSTANCE.stringMatcher("bc");
+ assertThat(matcher.isMatch(BUFFER2, 0, 0, BUFFER2.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER2, 1, 0, BUFFER2.length)).isEqualTo(2);
+ assertThat(matcher.isMatch(BUFFER2, 2, 0, BUFFER2.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER2, 3, 0, BUFFER2.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER2, 4, 0, BUFFER2.length)).isEqualTo(0);
+ assertThat(matcher.isMatch(BUFFER2, 5, 0, BUFFER2.length)).isEqualTo(0);
+ assertThat(StringMatcherFactory.INSTANCE.stringMatcher(""))
+ .isSameAs(StringMatcherFactory.INSTANCE.noneMatcher());
+ assertThat(StringMatcherFactory.INSTANCE.stringMatcher((String) null))
+ .isSameAs(StringMatcherFactory.INSTANCE.noneMatcher());
+ }
+
+ // -----------------------------------------------------------------------
+ @Test
+ public void testMatcherIndices() {
+ // remember that the API contract is tight for the isMatch() method
+ // all the onus is on the caller, so invalid inputs are not
+ // the concern of StringMatcher, and are not bugs
+ final StringMatcher matcher = StringMatcherFactory.INSTANCE.stringMatcher("bc");
+ assertThat(matcher.isMatch(BUFFER2, 1, 1, BUFFER2.length)).isEqualTo(2);
+ assertThat(matcher.isMatch(BUFFER2, 1, 0, 3)).isEqualTo(2);
+ assertThat(matcher.isMatch(BUFFER2, 1, 0, 2)).isEqualTo(0);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/commons-text/blob/4b67dd51/src/test/java/org/apache/commons/text/matcher/StringSubstitutorGetSetTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/matcher/StringSubstitutorGetSetTest.java b/src/test/java/org/apache/commons/text/matcher/StringSubstitutorGetSetTest.java
new file mode 100644
index 0000000..733c853
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/matcher/StringSubstitutorGetSetTest.java
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.text.matcher;
+
+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 org.apache.commons.text.StringSubstitutor;
+import org.junit.Test;
+
+/**
+ * Test class for {@link StringSubstitutor}.
+ */
+public class StringSubstitutorGetSetTest {
+
+ /**
+ * Tests get set.
+ */
+ @Test
+ public void testGetSetPrefix() {
+ final StringSubstitutor sub = new StringSubstitutor();
+ assertTrue(sub.getVariablePrefixMatcher() instanceof AbstractStringMatcher.StringMatcher);
+ sub.setVariablePrefix('<');
+ assertTrue(sub.getVariablePrefixMatcher() instanceof AbstractStringMatcher.CharMatcher);
+
+ sub.setVariablePrefix("<<");
+ assertTrue(sub.getVariablePrefixMatcher() instanceof AbstractStringMatcher.StringMatcher);
+ try {
+ sub.setVariablePrefix((String) null);
+ fail();
+ } catch (final IllegalArgumentException ex) {
+ // expected
+ }
+ assertTrue(sub.getVariablePrefixMatcher() instanceof AbstractStringMatcher.StringMatcher);
+
+ final StringMatcher matcher = StringMatcherFactory.INSTANCE.commaMatcher();
+ sub.setVariablePrefixMatcher(matcher);
+ assertSame(matcher, sub.getVariablePrefixMatcher());
+ try {
+ sub.setVariablePrefixMatcher((StringMatcher) null);
+ fail();
+ } catch (final IllegalArgumentException ex) {
+ // expected
+ }
+ assertSame(matcher, sub.getVariablePrefixMatcher());
+ }
+
+ /**
+ * Tests get set.
+ */
+ @Test
+ public void testGetSetSuffix() {
+ final StringSubstitutor sub = new StringSubstitutor();
+ assertTrue(sub.getVariableSuffixMatcher() instanceof AbstractStringMatcher.StringMatcher);
+ sub.setVariableSuffix('<');
+ assertTrue(sub.getVariableSuffixMatcher() instanceof AbstractStringMatcher.CharMatcher);
+
+ sub.setVariableSuffix("<<");
+ assertTrue(sub.getVariableSuffixMatcher() instanceof AbstractStringMatcher.StringMatcher);
+ try {
+ sub.setVariableSuffix((String) null);
+ fail();
+ } catch (final IllegalArgumentException ex) {
+ // expected
+ }
+ assertTrue(sub.getVariableSuffixMatcher() instanceof AbstractStringMatcher.StringMatcher);
+
+ final StringMatcher matcher = StringMatcherFactory.INSTANCE.commaMatcher();
+ sub.setVariableSuffixMatcher(matcher);
+ assertSame(matcher, sub.getVariableSuffixMatcher());
+ try {
+ sub.setVariableSuffixMatcher((StringMatcher) null);
+ fail();
+ } catch (final IllegalArgumentException ex) {
+ // expected
+ }
+ assertSame(matcher, sub.getVariableSuffixMatcher());
+ }
+
+ /**
+ * Tests get set.
+ */
+ @Test
+ public void testGetSetValueDelimiter() {
+ final StringSubstitutor sub = new StringSubstitutor();
+ assertTrue(sub.getValueDelimiterMatcher() instanceof AbstractStringMatcher.StringMatcher);
+ sub.setValueDelimiter(':');
+ assertTrue(sub.getValueDelimiterMatcher() instanceof AbstractStringMatcher.CharMatcher);
+
+ sub.setValueDelimiter("||");
+ assertTrue(sub.getValueDelimiterMatcher() instanceof AbstractStringMatcher.StringMatcher);
+ sub.setValueDelimiter((String) null);
+ assertNull(sub.getValueDelimiterMatcher());
+
+ final StringMatcher matcher = StringMatcherFactory.INSTANCE.commaMatcher();
+ sub.setValueDelimiterMatcher(matcher);
+ assertSame(matcher, sub.getValueDelimiterMatcher());
+ sub.setValueDelimiterMatcher((StringMatcher) null);
+ assertNull(sub.getValueDelimiterMatcher());
+ }
+
+}
[3/5] [text] [TEXT-114] Add a replacement for StrSubstitutor based on
interfaces: StringSubstitutor.
Posted by gg...@apache.org.
http://git-wip-us.apache.org/repos/asf/commons-text/blob/4b67dd51/src/main/java/org/apache/commons/text/StringSubstitutor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/StringSubstitutor.java b/src/main/java/org/apache/commons/text/StringSubstitutor.java
new file mode 100644
index 0000000..583cd37
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/StringSubstitutor.java
@@ -0,0 +1,1359 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.text;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.commons.lang3.Validate;
+import org.apache.commons.text.lookup.StringLookup;
+import org.apache.commons.text.lookup.StringLookupFactory;
+import org.apache.commons.text.matcher.StringMatcher;
+import org.apache.commons.text.matcher.StringMatcherFactory;
+
+/**
+ * Substitutes variables within a string by values.
+ * <p>
+ * This class takes a piece of text and substitutes all the variables within it. The default definition of a variable is
+ * <code>${variableName}</code>. The prefix and suffix can be changed via constructors and set methods.
+ * <p>
+ * Variable values are typically resolved from a map, but could also be resolved from system properties, or by supplying
+ * a custom variable resolver.
+ * <p>
+ * The simplest example is to use this class to replace Java System properties. For example:
+ *
+ * <pre>
+ * StrSubstitutor
+ * .replaceSystemProperties("You are running with java.version = ${java.version} and os.name = ${os.name}.");
+ * </pre>
+ * <p>
+ * Typical usage of this class follows the following pattern: First an instance is created and initialized with the map
+ * that contains the values for the available variables. If a prefix and/or suffix for variables should be used other
+ * than the default ones, the appropriate settings can be performed. After that the <code>replace()</code> method can be
+ * called passing in the source text for interpolation. In the returned text all variable references (as long as their
+ * values are known) will be resolved. The following example demonstrates this:
+ *
+ * <pre>
+ * Map valuesMap = HashMap();
+ * valuesMap.put("animal", "quick brown fox");
+ * valuesMap.put("target", "lazy dog");
+ * String templateString = "The ${animal} jumped over the ${target}.";
+ * StrSubstitutor sub = new StrSubstitutor(valuesMap);
+ * String resolvedString = sub.replace(templateString);
+ * </pre>
+ *
+ * yielding:
+ *
+ * <pre>
+ * 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(StringMatcher)}, {@link #setValueDelimiter(char)} or
+ * {@link #setValueDelimiter(String)}. The following shows an example with variable 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 performed, creating and reusing an instance of this class will be more efficient.
+ * <p>
+ * Variable replacement works in a recursive way. Thus, if a variable value contains a variable then that variable will
+ * also be replaced. Cyclic replacements are detected and will cause an exception to be thrown.
+ * <p>
+ * Sometimes the interpolation's result must contain a variable prefix. As an example take the following source text:
+ *
+ * <pre>
+ * The variable ${${name}} must be used.
+ * </pre>
+ *
+ * Here only the variable's name referred to in the text should be replaced resulting in the text (assuming that the
+ * value of the <code>name</code> variable is <code>x</code>):
+ *
+ * <pre>
+ * The variable ${x} must be used.
+ * </pre>
+ *
+ * To achieve this effect there are two possibilities: Either set a different prefix and suffix for variables which do
+ * not conflict with the result text you want to produce. The other possibility is to use the escape character, by
+ * default '$'. If this character is placed before a variable reference, this reference is ignored and won't be
+ * replaced. For example:
+ *
+ * <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>.
+ * <p>
+ * This class is <b>not</b> thread safe.
+ * </p>
+ *
+ * @since 1.3
+ */
+public class StringSubstitutor {
+
+ /**
+ * Constant for the default escape character.
+ */
+ public static final char DEFAULT_ESCAPE = '$';
+
+ /**
+ * Constant for the default variable prefix.
+ */
+ public static final StringMatcher DEFAULT_PREFIX = StringMatcherFactory.INSTANCE.stringMatcher("${");
+
+ /**
+ * Constant for the default variable suffix.
+ */
+ public static final StringMatcher DEFAULT_SUFFIX = StringMatcherFactory.INSTANCE.stringMatcher("}");
+
+ /**
+ * Constant for the default value delimiter of a variable.
+ */
+ public static final StringMatcher DEFAULT_VALUE_DELIMITER = StringMatcherFactory.INSTANCE.stringMatcher(":-");
+
+ // -----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables in the given source object with their matching values from the map.
+ *
+ * @param <V>
+ * the type of the values in the map
+ * @param source
+ * the source text containing the variables to substitute, null returns null
+ * @param valueMap
+ * the map with the values, may be null
+ * @return the result of the replace operation
+ */
+ public static <V> String replace(final Object source, final Map<String, V> valueMap) {
+ return new StringSubstitutor(valueMap).replace(source);
+ }
+
+ /**
+ * Replaces all the occurrences of variables in the given source object with their matching values from the map.
+ * This method allows to specify a custom variable prefix and suffix
+ *
+ * @param <V>
+ * the type of the values in the map
+ * @param source
+ * the source text containing the variables to substitute, null returns null
+ * @param valueMap
+ * the map with the values, may be null
+ * @param prefix
+ * the prefix of variables, not null
+ * @param suffix
+ * the suffix of variables, not null
+ * @return the result of the replace operation
+ * @throws IllegalArgumentException
+ * if the prefix or suffix is null
+ */
+ public static <V> String replace(final Object source, final Map<String, V> valueMap, final String prefix,
+ final String suffix) {
+ return new StringSubstitutor(valueMap, prefix, suffix).replace(source);
+ }
+
+ /**
+ * Replaces all the occurrences of variables in the given source object with their matching values from the
+ * properties.
+ *
+ * @param source
+ * the source text containing the variables to substitute, null returns null
+ * @param valueProperties
+ * the properties with values, may be null
+ * @return the result of the replace operation
+ */
+ public static String replace(final Object source, final Properties valueProperties) {
+ if (valueProperties == null) {
+ return source.toString();
+ }
+ final Map<String, String> valueMap = new HashMap<>();
+ final Enumeration<?> propNames = valueProperties.propertyNames();
+ while (propNames.hasMoreElements()) {
+ final String propName = (String) propNames.nextElement();
+ final String propValue = valueProperties.getProperty(propName);
+ valueMap.put(propName, propValue);
+ }
+ return StringSubstitutor.replace(source, valueMap);
+ }
+
+ /**
+ * Replaces all the occurrences of variables in the given source object with their matching values from the system
+ * properties.
+ *
+ * @param source
+ * the source text containing the variables to substitute, null returns null
+ * @return the result of the replace operation
+ */
+ public static String replaceSystemProperties(final Object source) {
+ return new StringSubstitutor(StringLookupFactory.INSTANCE.systemPropertyStringLookup()).replace(source);
+ }
+
+ /**
+ * Stores the escape character.
+ */
+ private char escapeChar;
+
+ /**
+ * Stores the variable prefix.
+ */
+ private StringMatcher prefixMatcher;
+
+ /**
+ * Stores the variable suffix.
+ */
+ private StringMatcher suffixMatcher;
+
+ /**
+ * Stores the default variable value delimiter.
+ */
+ private StringMatcher valueDelimiterMatcher;
+
+ /**
+ * Variable resolution is delegated to an implementor of {@link StringLookup}.
+ */
+ private StringLookup variableResolver;
+
+ /**
+ * The flag whether substitution in variable names is enabled.
+ */
+ private boolean enableSubstitutionInVariables;
+
+ /**
+ * Whether escapes should be preserved. Default is false;
+ */
+ private boolean preserveEscapes = false;
+
+ /**
+ * The flag whether substitution in variable values is disabled.
+ */
+ private boolean disableSubstitutionInValues;
+
+ // -----------------------------------------------------------------------
+ /**
+ * Creates a new instance with defaults for variable prefix and suffix and the escaping character.
+ */
+ public StringSubstitutor() {
+ this((StringLookup) null, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
+ }
+
+ /**
+ * Creates a new instance and initializes it. Uses defaults for variable prefix and suffix and the escaping
+ * character.
+ *
+ * @param <V>
+ * the type of the values in the map
+ * @param valueMap
+ * the map with the variables' values, may be null
+ */
+ public <V> StringSubstitutor(final Map<String, V> valueMap) {
+ this(StringLookupFactory.INSTANCE.mapStringLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
+ }
+
+ /**
+ * Creates a new instance and initializes it. Uses a default escaping character.
+ *
+ * @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
+ * @throws IllegalArgumentException
+ * if the prefix or suffix is null
+ */
+ public <V> StringSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix) {
+ this(StringLookupFactory.INSTANCE.mapStringLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE);
+ }
+
+ /**
+ * 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
+ * @throws IllegalArgumentException
+ * if the prefix or suffix is null
+ */
+ public <V> StringSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix,
+ final char escape) {
+ this(StringLookupFactory.INSTANCE.mapStringLookup(valueMap), prefix, suffix, escape);
+ }
+
+ /**
+ * 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> StringSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix,
+ final char escape, final String valueDelimiter) {
+ this(StringLookupFactory.INSTANCE.mapStringLookup(valueMap), prefix, suffix, escape, valueDelimiter);
+ }
+
+ /**
+ * Creates a new instance and initializes it.
+ *
+ * @param variableResolver
+ * the variable resolver, may be null
+ */
+ public StringSubstitutor(final StringLookup variableResolver) {
+ this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
+ }
+
+ /**
+ * 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
+ * @throws IllegalArgumentException
+ * if the prefix or suffix is null
+ */
+ public StringSubstitutor(final StringLookup variableResolver, final String prefix, final String suffix,
+ final char escape) {
+ this.setVariableResolver(variableResolver);
+ 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 StringSubstitutor(final StringLookup 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);
+ }
+
+ /**
+ * 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
+ * @throws IllegalArgumentException
+ * if the prefix or suffix is null
+ */
+ public StringSubstitutor(final StringLookup variableResolver, final StringMatcher prefixMatcher,
+ final StringMatcher 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 StringSubstitutor(final StringLookup variableResolver, final StringMatcher prefixMatcher,
+ final StringMatcher suffixMatcher, final char escape, final StringMatcher valueDelimiterMatcher) {
+ this.setVariableResolver(variableResolver);
+ this.setVariablePrefixMatcher(prefixMatcher);
+ this.setVariableSuffixMatcher(suffixMatcher);
+ this.setEscapeChar(escape);
+ this.setValueDelimiterMatcher(valueDelimiterMatcher);
+ }
+
+ /**
+ * Checks if the specified variable is already in the stack (list) of variables.
+ *
+ * @param varName
+ * the variable name to check
+ * @param priorVariables
+ * the list of prior variables
+ */
+ private void checkCyclicSubstitution(final String varName, final List<String> priorVariables) {
+ if (!priorVariables.contains(varName)) {
+ return;
+ }
+ final StrBuilder buf = new StrBuilder(256);
+ buf.append("Infinite loop in property interpolation of ");
+ buf.append(priorVariables.remove(0));
+ buf.append(": ");
+ buf.appendWithSeparators(priorVariables, "->");
+ throw new IllegalStateException(buf.toString());
+ }
+
+ // Escape
+ // -----------------------------------------------------------------------
+ /**
+ * Returns the escape character.
+ *
+ * @return the character used for escaping variable references
+ */
+ public char getEscapeChar() {
+ return this.escapeChar;
+ }
+
+ // Resolver
+ // -----------------------------------------------------------------------
+ /**
+ * Gets the StringLookup that is used to lookup variables.
+ *
+ * @return the StringLookup
+ */
+ public StringLookup getStringLookup() {
+ return this.variableResolver;
+ }
+
+ // Variable Default Value Delimiter
+ // -----------------------------------------------------------------------
+ /**
+ * Gets the variable default value delimiter matcher currently in use.
+ * <p>
+ * The variable default value delimiter is the character 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 StringMatcher getValueDelimiterMatcher() {
+ return valueDelimiterMatcher;
+ }
+
+ // Prefix
+ // -----------------------------------------------------------------------
+ /**
+ * Gets the variable prefix matcher currently in use.
+ * <p>
+ * The variable prefix is the character or characters that identify the start of a variable. This prefix is
+ * expressed in terms of a matcher allowing advanced prefix matches.
+ *
+ * @return the prefix matcher in use
+ */
+ public StringMatcher getVariablePrefixMatcher() {
+ return prefixMatcher;
+ }
+
+ // Suffix
+ // -----------------------------------------------------------------------
+ /**
+ * Gets the variable suffix matcher currently in use.
+ * <p>
+ * The variable suffix is the character or characters that identify the end of a variable. This suffix is expressed
+ * in terms of a matcher allowing advanced suffix matches.
+ *
+ * @return the suffix matcher in use
+ */
+ public StringMatcher getVariableSuffixMatcher() {
+ return suffixMatcher;
+ }
+
+ /**
+ * Returns a flag whether substitution is disabled in variable values.If set to <b>true</b>, the values of variables
+ * can contain other variables will not be processed and substituted original variable is evaluated, e.g.
+ *
+ * <pre>
+ * Map valuesMap = HashMap();
+ * valuesMap.put("name", "Douglas ${surname}");
+ * valuesMap.put("surname", "Crockford");
+ * String templateString = "Hi ${name}";
+ * StrSubstitutor sub = new StrSubstitutor(valuesMap);
+ * String resolvedString = sub.replace(templateString);
+ * </pre>
+ *
+ * yielding:
+ *
+ * <pre>
+ * Hi Douglas ${surname}
+ * </pre>
+ *
+ * @return the substitution in variable values flag
+ */
+ public boolean isDisableSubstitutionInValues() {
+ return disableSubstitutionInValues;
+ }
+
+ // Substitution support in variable names
+ // -----------------------------------------------------------------------
+ /**
+ * Returns a flag whether substitution is done in variable names.
+ *
+ * @return the substitution in variable names flag
+ */
+ public boolean isEnableSubstitutionInVariables() {
+ return enableSubstitutionInVariables;
+ }
+
+ /**
+ * Returns the flag controlling whether escapes are preserved during substitution.
+ *
+ * @return the preserve escape flag
+ */
+ public boolean isPreserveEscapes() {
+ return preserveEscapes;
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables with their matching values from the resolver using the given source
+ * array as a template. The array is not altered by this method.
+ *
+ * @param source
+ * the character array to replace in, not altered, null returns null
+ * @return the result of the replace operation
+ */
+ public String replace(final char[] source) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(source.length).append(source);
+ substitute(buf, 0, source.length);
+ return buf.toString();
+ }
+
+ /**
+ * Replaces all the occurrences of variables with their matching values from the resolver using the given source
+ * array as a template. The array is not altered by this method.
+ * <p>
+ * Only the specified portion of the array will be processed. The rest of the array is not processed, and is not
+ * returned.
+ *
+ * @param source
+ * the character array to replace in, not altered, null returns null
+ * @param offset
+ * the start offset within the array, must be valid
+ * @param length
+ * the length within the array to be processed, must be valid
+ * @return the result of the replace operation
+ */
+ public String replace(final char[] source, final int offset, final int length) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+ 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. The source is not altered by this method.
+ *
+ * @param source
+ * the buffer to use as a template, not changed, null returns null
+ * @return the result of the replace operation
+ */
+ public String replace(final CharSequence source) {
+ if (source == null) {
+ return null;
+ }
+ return replace(source, 0, source.length());
+ }
+
+ /**
+ * Replaces all the occurrences of variables with their matching values from the resolver using the given source as
+ * a template. The source is not altered by this method.
+ * <p>
+ * Only the specified portion of the buffer will be processed. The rest of the buffer is not processed, and is not
+ * returned.
+ *
+ * @param source
+ * the buffer to use as a template, not changed, null returns null
+ * @param offset
+ * the start offset within the array, must be valid
+ * @param length
+ * the length within the array to be processed, must be valid
+ * @return the result of the replace operation
+ */
+ public String replace(final CharSequence source, final int offset, final int length) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+ substitute(buf, 0, length);
+ return buf.toString();
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables in the given source object with their matching values from the
+ * resolver. The input source object is converted to a string using <code>toString</code> and is not altered.
+ *
+ * @param source
+ * the source to replace in, null returns null
+ * @return the result of the replace operation
+ */
+ public String replace(final Object source) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder().append(source);
+ substitute(buf, 0, buf.length());
+ return buf.toString();
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables with their matching values from the resolver using the given source
+ * builder as a template. The builder is not altered by this method.
+ *
+ * @param source
+ * the builder to use as a template, not changed, null returns null
+ * @return the result of the replace operation
+ */
+ public String replace(final StrBuilder source) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(source.length()).append(source);
+ substitute(buf, 0, buf.length());
+ return buf.toString();
+ }
+
+ /**
+ * Replaces all the occurrences of variables with their matching values from the resolver using the given source
+ * builder as a template. The builder is not altered by this method.
+ * <p>
+ * Only the specified portion of the builder will be processed. The rest of the builder is not processed, and is not
+ * returned.
+ *
+ * @param source
+ * the builder to use as a template, not changed, null returns null
+ * @param offset
+ * the start offset within the array, must be valid
+ * @param length
+ * the length within the array to be processed, must be valid
+ * @return the result of the replace operation
+ */
+ public String replace(final StrBuilder source, final int offset, final int length) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+ substitute(buf, 0, length);
+ return buf.toString();
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables with their matching values from the resolver using the given source
+ * string as a template.
+ *
+ * @param source
+ * the string to replace in, null returns null
+ * @return the result of the replace operation
+ */
+ public String replace(final String source) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(source);
+ if (!substitute(buf, 0, source.length())) {
+ return source;
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Replaces all the occurrences of variables with their matching values from the resolver using the given source
+ * string as a template.
+ * <p>
+ * Only the specified portion of the string will be processed. The rest of the string is not processed, and is not
+ * returned.
+ *
+ * @param source
+ * the string to replace in, null returns null
+ * @param offset
+ * the start offset within the array, must be valid
+ * @param length
+ * the length within the array to be processed, must be valid
+ * @return the result of the replace operation
+ */
+ public String replace(final String source, final int offset, final int length) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+ if (!substitute(buf, 0, length)) {
+ return source.substring(offset, offset + length);
+ }
+ return buf.toString();
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables with their matching values from the resolver using the given source
+ * buffer as a template. The buffer is not altered by this method.
+ *
+ * @param source
+ * the buffer to use as a template, not changed, null returns null
+ * @return the result of the replace operation
+ */
+ public String replace(final StringBuffer source) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(source.length()).append(source);
+ substitute(buf, 0, buf.length());
+ return buf.toString();
+ }
+
+ /**
+ * Replaces all the occurrences of variables with their matching values from the resolver using the given source
+ * buffer as a template. The buffer is not altered by this method.
+ * <p>
+ * Only the specified portion of the buffer will be processed. The rest of the buffer is not processed, and is not
+ * returned.
+ *
+ * @param source
+ * the buffer to use as a template, not changed, null returns null
+ * @param offset
+ * the start offset within the array, must be valid
+ * @param length
+ * the length within the array to be processed, must be valid
+ * @return the result of the replace operation
+ */
+ public String replace(final StringBuffer source, final int offset, final int length) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+ substitute(buf, 0, length);
+ return buf.toString();
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables within the given source builder with their matching values from the
+ * resolver.
+ *
+ * @param source
+ * the builder to replace in, updated, null returns zero
+ * @return true if altered
+ */
+ public boolean replaceIn(final StrBuilder source) {
+ if (source == null) {
+ return false;
+ }
+ return substitute(source, 0, source.length());
+ }
+
+ /**
+ * Replaces all the occurrences of variables within the given source builder with their matching values from the
+ * resolver.
+ * <p>
+ * Only the specified portion of the builder will be processed. The rest of the builder is not processed, but it is
+ * not deleted.
+ *
+ * @param source
+ * the builder to replace in, null returns zero
+ * @param offset
+ * the start offset within the array, must be valid
+ * @param length
+ * the length within the builder to be processed, must be valid
+ * @return true if altered
+ */
+ public boolean replaceIn(final StrBuilder source, final int offset, final int length) {
+ if (source == null) {
+ return false;
+ }
+ return substitute(source, offset, length);
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables within the given source buffer with their matching values from the
+ * resolver. The buffer is updated with the result.
+ *
+ * @param source
+ * the buffer to replace in, updated, null returns zero
+ * @return true if altered
+ */
+ public boolean replaceIn(final StringBuffer source) {
+ if (source == null) {
+ return false;
+ }
+ return replaceIn(source, 0, source.length());
+ }
+
+ /**
+ * Replaces all the occurrences of variables within the given source buffer with their matching values from the
+ * resolver. The buffer is updated with the result.
+ * <p>
+ * Only the specified portion of the buffer will be processed. The rest of the buffer is not processed, but it is
+ * not deleted.
+ *
+ * @param source
+ * the buffer to replace in, updated, null returns zero
+ * @param offset
+ * the start offset within the array, must be valid
+ * @param length
+ * the length within the buffer to be processed, must be valid
+ * @return true if altered
+ */
+ public boolean replaceIn(final StringBuffer source, final int offset, final int length) {
+ if (source == null) {
+ return false;
+ }
+ final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+ if (!substitute(buf, 0, length)) {
+ return false;
+ }
+ source.replace(offset, offset + length, buf.toString());
+ return true;
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables within the given source buffer with their matching values from the
+ * resolver. The buffer is updated with the result.
+ *
+ * @param source
+ * the buffer to replace in, updated, null returns zero
+ * @return true if altered
+ */
+ public boolean replaceIn(final StringBuilder source) {
+ if (source == null) {
+ return false;
+ }
+ return replaceIn(source, 0, source.length());
+ }
+
+ /**
+ * Replaces all the occurrences of variables within the given source builder with their matching values from the
+ * resolver. The builder is updated with the result.
+ * <p>
+ * Only the specified portion of the buffer will be processed. The rest of the buffer is not processed, but it is
+ * not deleted.
+ *
+ * @param source
+ * the buffer to replace in, updated, null returns zero
+ * @param offset
+ * the start offset within the array, must be valid
+ * @param length
+ * the length within the buffer to be processed, must be valid
+ * @return true if altered
+ */
+ public boolean replaceIn(final StringBuilder source, final int offset, final int length) {
+ if (source == null) {
+ return false;
+ }
+ final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+ if (!substitute(buf, 0, length)) {
+ return false;
+ }
+ source.replace(offset, offset + length, buf.toString());
+ return true;
+ }
+
+ /**
+ * Internal method that resolves the value of a variable.
+ * <p>
+ * Most users of this class do not need to call this method. This method is called automatically by the substitution
+ * process.
+ * <p>
+ * Writers of subclasses can override this method if they need to alter how each substitution occurs. The method is
+ * passed the variable's name and must return the corresponding value. This implementation uses the
+ * {@link #getStringLookup()} with the variable's name as the key.
+ *
+ * @param variableName
+ * the name of the variable, not null
+ * @param buf
+ * the buffer where the substitution is occurring, not null
+ * @param startPos
+ * the start position of the variable including the prefix, valid
+ * @param endPos
+ * the end position of the variable including the suffix, valid
+ * @return the variable's value or <b>null</b> if the variable is unknown
+ */
+ protected String resolveVariable(final String variableName, final StrBuilder buf, final int startPos,
+ final int endPos) {
+ final StringLookup resolver = getStringLookup();
+ if (resolver == null) {
+ return null;
+ }
+ return resolver.lookup(variableName);
+ }
+
+ /**
+ * Sets a flag whether substitution is done in variable values (recursive).
+ *
+ * @param disableSubstitutionInValues
+ * true if substitution in variable value are disabled
+ * @return this, to enable chaining
+ */
+ public StringSubstitutor setDisableSubstitutionInValues(final boolean disableSubstitutionInValues) {
+ this.disableSubstitutionInValues = disableSubstitutionInValues;
+ return this;
+ }
+
+ /**
+ * 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
+ * @return this, to enable chaining
+ */
+ public StringSubstitutor setEnableSubstitutionInVariables(final boolean enableSubstitutionInVariables) {
+ this.enableSubstitutionInVariables = enableSubstitutionInVariables;
+ return this;
+ }
+
+ /**
+ * Sets the escape character. If this character is placed before a variable reference in the source text, this
+ * variable will be ignored.
+ *
+ * @param escapeCharacter
+ * the escape character (0 for disabling escaping)
+ * @return this, to enable chaining
+ */
+ public StringSubstitutor setEscapeChar(final char escapeCharacter) {
+ this.escapeChar = escapeCharacter;
+ return this;
+ }
+
+ /**
+ * Sets a flag controlling whether escapes are preserved during substitution. If set to <b>true</b>, the escape
+ * character is retained during substitution (e.g. <code>$${this-is-escaped}</code> remains
+ * <code>$${this-is-escaped}</code>). If set to <b>false</b>, the escape character is removed during substitution
+ * (e.g. <code>$${this-is-escaped}</code> becomes <code>${this-is-escaped}</code>). The default value is
+ * <b>false</b>
+ *
+ * @param preserveEscapes
+ * true if escapes are to be preserved
+ * @return this, to enable chaining
+ */
+ public StringSubstitutor setPreserveEscapes(final boolean preserveEscapes) {
+ this.preserveEscapes = preserveEscapes;
+ return this;
+ }
+
+ /**
+ * Sets the variable default value delimiter to use.
+ * <p>
+ * The variable default value delimiter is the character 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 StringSubstitutor setValueDelimiter(final char valueDelimiter) {
+ return setValueDelimiterMatcher(StringMatcherFactory.INSTANCE.charMatcher(valueDelimiter));
+ }
+
+ /**
+ * Sets the variable default value delimiter to use.
+ * <p>
+ * The variable default value delimiter is the character 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 StringSubstitutor setValueDelimiter(final String valueDelimiter) {
+ if (valueDelimiter == null || valueDelimiter.length() == 0) {
+ setValueDelimiterMatcher(null);
+ return this;
+ }
+ return setValueDelimiterMatcher(StringMatcherFactory.INSTANCE.stringMatcher(valueDelimiter));
+ }
+
+ /**
+ * Sets the variable default value delimiter matcher to use.
+ * <p>
+ * The variable default value delimiter is the character 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 StringSubstitutor setValueDelimiterMatcher(final StringMatcher valueDelimiterMatcher) {
+ this.valueDelimiterMatcher = valueDelimiterMatcher;
+ return this;
+ }
+
+ /**
+ * Sets the variable prefix to use.
+ * <p>
+ * 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.
+ *
+ * @param prefix
+ * the prefix character to use
+ * @return this, to enable chaining
+ */
+ public StringSubstitutor setVariablePrefix(final char prefix) {
+ return setVariablePrefixMatcher(StringMatcherFactory.INSTANCE.charMatcher(prefix));
+ }
+
+ /**
+ * Sets the variable prefix to use.
+ * <p>
+ * The variable prefix is the character or characters that identify the start of a variable. This method allows a
+ * string prefix to be easily set.
+ *
+ * @param prefix
+ * the prefix for variables, not null
+ * @return this, to enable chaining
+ * @throws IllegalArgumentException
+ * if the prefix is null
+ */
+ public StringSubstitutor setVariablePrefix(final String prefix) {
+ Validate.isTrue(prefix != null, "Variable prefix must not be null!");
+ return setVariablePrefixMatcher(StringMatcherFactory.INSTANCE.stringMatcher(prefix));
+ }
+
+ /**
+ * Sets the variable prefix matcher currently in use.
+ * <p>
+ * The variable prefix is the character or characters that identify the start of a variable. This prefix is
+ * expressed in terms of a matcher allowing advanced prefix matches.
+ *
+ * @param prefixMatcher
+ * the prefix matcher to use, null ignored
+ * @return this, to enable chaining
+ * @throws IllegalArgumentException
+ * if the prefix matcher is null
+ */
+ public StringSubstitutor setVariablePrefixMatcher(final StringMatcher prefixMatcher) {
+ Validate.isTrue(prefixMatcher != null, "Variable prefix matcher must not be null!");
+ this.prefixMatcher = prefixMatcher;
+ return this;
+ }
+
+ /**
+ * Sets the VariableResolver that is used to lookup variables.
+ *
+ * @param variableResolver
+ * the VariableResolver
+ * @return this, to enable chaining
+ */
+ public StringSubstitutor setVariableResolver(final StringLookup variableResolver) {
+ this.variableResolver = variableResolver;
+ return this;
+ }
+
+ /**
+ * Sets the variable suffix to use.
+ * <p>
+ * The variable suffix is the character or characters that identify the end of a variable. This method allows a
+ * single character suffix to be easily set.
+ *
+ * @param suffix
+ * the suffix character to use
+ * @return this, to enable chaining
+ */
+ public StringSubstitutor setVariableSuffix(final char suffix) {
+ return setVariableSuffixMatcher(StringMatcherFactory.INSTANCE.charMatcher(suffix));
+ }
+
+ /**
+ * Sets the variable suffix to use.
+ * <p>
+ * 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
+ * @return this, to enable chaining
+ * @throws IllegalArgumentException
+ * if the suffix is null
+ */
+ public StringSubstitutor setVariableSuffix(final String suffix) {
+ Validate.isTrue(suffix != null, "Variable suffix must not be null!");
+ return setVariableSuffixMatcher(StringMatcherFactory.INSTANCE.stringMatcher(suffix));
+ }
+
+ /**
+ * Sets the variable suffix matcher currently in use.
+ * <p>
+ * The variable suffix is the character or characters that identify the end of a variable. This suffix is expressed
+ * in terms of a matcher allowing advanced suffix matches.
+ *
+ * @param suffixMatcher
+ * the suffix matcher to use, null ignored
+ * @return this, to enable chaining
+ * @throws IllegalArgumentException
+ * if the suffix matcher is null
+ */
+ public StringSubstitutor setVariableSuffixMatcher(final StringMatcher suffixMatcher) {
+ Validate.isTrue(suffixMatcher != null, "Variable suffix matcher must not be null!");
+ this.suffixMatcher = suffixMatcher;
+ return this;
+ }
+
+ // -----------------------------------------------------------------------
+ /**
+ * Internal method that substitutes the variables.
+ * <p>
+ * Most users of this class do not need to call this method. This method will be called automatically by another
+ * (public) method.
+ * <p>
+ * Writers of subclasses can override this method if they need access to the substitution process at the start or
+ * end.
+ *
+ * @param buf
+ * the string builder to substitute into, not null
+ * @param offset
+ * the start offset within the builder, must be valid
+ * @param length
+ * the length within the builder to be processed, must be valid
+ * @return true if altered
+ */
+ protected boolean substitute(final StrBuilder buf, final int offset, final int length) {
+ return substitute(buf, offset, length, null) > 0;
+ }
+
+ /**
+ * Recursive handler for multiple levels of interpolation. This is the main interpolation method, which resolves the
+ * values of all variable references contained in the passed in text.
+ *
+ * @param buf
+ * the string builder to substitute into, not null
+ * @param offset
+ * the start offset within the builder, must be valid
+ * @param length
+ * the length within the builder to be processed, must be valid
+ * @param priorVariables
+ * the stack keeping track of the replaced variables, may be null
+ * @return the length change that occurs, unless priorVariables is null when the int represents a boolean flag as to
+ * whether any change occurred.
+ */
+ private int substitute(final StrBuilder buf, final int offset, final int length, List<String> priorVariables) {
+ final StringMatcher pfxMatcher = getVariablePrefixMatcher();
+ final StringMatcher suffMatcher = getVariableSuffixMatcher();
+ final char escape = getEscapeChar();
+ final StringMatcher valueDelimMatcher = getValueDelimiterMatcher();
+ final boolean substitutionInVariablesEnabled = isEnableSubstitutionInVariables();
+ final boolean substitutionInValuesDisabled = isDisableSubstitutionInValues();
+
+ final boolean top = priorVariables == null;
+ boolean altered = false;
+ int lengthChange = 0;
+ char[] chars = buf.buffer;
+ int bufEnd = offset + length;
+ int pos = offset;
+ while (pos < bufEnd) {
+ final int startMatchLen = pfxMatcher.isMatch(chars, pos, offset, bufEnd);
+ if (startMatchLen == 0) {
+ pos++;
+ } else {
+ // found variable start marker
+ if (pos > offset && chars[pos - 1] == escape) {
+ // escaped
+ if (preserveEscapes) {
+ pos++;
+ continue;
+ }
+ buf.deleteCharAt(pos - 1);
+ chars = buf.buffer; // in case buffer was altered
+ lengthChange--;
+ altered = true;
+ bufEnd--;
+ } else {
+ // find suffix
+ final int startPos = pos;
+ pos += startMatchLen;
+ int endMatchLen = 0;
+ int nestedVarCount = 0;
+ while (pos < bufEnd) {
+ if (substitutionInVariablesEnabled && pfxMatcher.isMatch(chars, pos, offset, bufEnd) != 0) {
+ // found a nested variable start
+ endMatchLen = pfxMatcher.isMatch(chars, pos, offset, bufEnd);
+ nestedVarCount++;
+ pos += endMatchLen;
+ continue;
+ }
+
+ endMatchLen = suffMatcher.isMatch(chars, pos, offset, bufEnd);
+ if (endMatchLen == 0) {
+ pos++;
+ } else {
+ // found variable end marker
+ if (nestedVarCount == 0) {
+ String varNameExpr = new String(chars, startPos + startMatchLen,
+ pos - startPos - startMatchLen);
+ if (substitutionInVariablesEnabled) {
+ final StrBuilder bufName = new StrBuilder(varNameExpr);
+ substitute(bufName, 0, bufName.length());
+ varNameExpr = bufName.toString();
+ }
+ pos += endMatchLen;
+ final int endPos = pos;
+
+ String varName = varNameExpr;
+ String varDefaultValue = null;
+
+ if (valueDelimMatcher != 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 && pfxMatcher.isMatch(varNameExprChars, i,
+ i, varNameExprChars.length) != 0) {
+ break;
+ }
+ if (valueDelimMatcher.isMatch(varNameExprChars, i, 0,
+ varNameExprChars.length) != 0) {
+ valueDelimiterMatchLen = valueDelimMatcher.isMatch(varNameExprChars, i, 0,
+ varNameExprChars.length);
+ varName = varNameExpr.substring(0, i);
+ varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen);
+ break;
+ }
+ }
+ }
+
+ // 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) {
+ varValue = varDefaultValue;
+ }
+ if (varValue != null) {
+ final int varLen = varValue.length();
+ buf.replace(startPos, endPos, varValue);
+ altered = true;
+ int change = 0;
+ if (!substitutionInValuesDisabled) { // recursive replace
+ 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;
+ }
+ nestedVarCount--;
+ pos += endMatchLen;
+ }
+ }
+ }
+ }
+ }
+ if (top) {
+ return altered ? 1 : 0;
+ }
+ return lengthChange;
+ }
+}
http://git-wip-us.apache.org/repos/asf/commons-text/blob/4b67dd51/src/main/java/org/apache/commons/text/lookup/AbstractStringLookup.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/lookup/AbstractStringLookup.java b/src/main/java/org/apache/commons/text/lookup/AbstractStringLookup.java
index bb43e96..3cbbaa3 100644
--- a/src/main/java/org/apache/commons/text/lookup/AbstractStringLookup.java
+++ b/src/main/java/org/apache/commons/text/lookup/AbstractStringLookup.java
@@ -19,10 +19,6 @@ package org.apache.commons.text.lookup;
/**
* A default lookup for others to extend in this package.
- * <p>
- * Unfortunately, the type {@link org.apache.commons.text.StrLookup} was defined as class and not an interface, which is
- * why this package introduces the interface {@link StringLookup}.
- * </p>
*
* @since 1.3
*/
http://git-wip-us.apache.org/repos/asf/commons-text/blob/4b67dd51/src/main/java/org/apache/commons/text/lookup/StringLookup.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/lookup/StringLookup.java b/src/main/java/org/apache/commons/text/lookup/StringLookup.java
index 4ced2c4..f3098e7 100644
--- a/src/main/java/org/apache/commons/text/lookup/StringLookup.java
+++ b/src/main/java/org/apache/commons/text/lookup/StringLookup.java
@@ -27,10 +27,6 @@ package org.apache.commons.text.lookup;
* For example, it would be possible to implement a lookup that used the key as a primary key, and looked up the value
* on demand from the database
* </p>
- * <p>
- * Unfortunately, the type {@link org.apache.commons.text.StrLookup} was defined as class and not an interface, which is
- * why this package introduces the interface {@link StringLookup}.
- * </p>
*
* @since 1.3
*/
http://git-wip-us.apache.org/repos/asf/commons-text/blob/4b67dd51/src/main/java/org/apache/commons/text/lookup/package-info.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/lookup/package-info.java b/src/main/java/org/apache/commons/text/lookup/package-info.java
index b53399a..528211b 100644
--- a/src/main/java/org/apache/commons/text/lookup/package-info.java
+++ b/src/main/java/org/apache/commons/text/lookup/package-info.java
@@ -16,7 +16,7 @@
*/
/**
* <p>
- * Provides algorithms for looking up strings for use with a {@link org.apache.commons.text.StrSubstitutor}. The main
+ * Provides algorithms for looking up strings for use with a {@link org.apache.commons.text.StringSubstitutor}. The main
* class here is {@link org.apache.commons.text.lookup.StringLookupFactory}
* </p>
*
http://git-wip-us.apache.org/repos/asf/commons-text/blob/4b67dd51/src/main/java/org/apache/commons/text/matcher/AbstractStringMatcher.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/matcher/AbstractStringMatcher.java b/src/main/java/org/apache/commons/text/matcher/AbstractStringMatcher.java
new file mode 100644
index 0000000..34f9771
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/matcher/AbstractStringMatcher.java
@@ -0,0 +1,256 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.text.matcher;
+
+import java.util.Arrays;
+
+/**
+ * A matcher class that can be queried to determine if a character array portion matches.
+ * <p>
+ * This class comes complete with various factory methods. If these do not suffice, you can subclass and implement your
+ * own matcher.
+ *
+ * @since 1.3
+ */
+abstract class AbstractStringMatcher implements StringMatcher {
+
+ /**
+ * Class used to define a character for matching purposes.
+ */
+ static final class CharMatcher extends AbstractStringMatcher {
+ /** The character to match. */
+ private final char ch;
+
+ /**
+ * Constructor that creates a matcher that matches a single character.
+ *
+ * @param ch
+ * the character to match
+ */
+ CharMatcher(final char ch) {
+ super();
+ this.ch = ch;
+ }
+
+ /**
+ * Returns whether or not the given character matches.
+ *
+ * @param buffer
+ * the text content to match against, do not change
+ * @param pos
+ * the starting position for the match, valid for buffer
+ * @param bufferStart
+ * the first active index in the buffer, valid for buffer
+ * @param bufferEnd
+ * the end index of the active buffer, valid for buffer
+ * @return the number of matching characters, zero for no match
+ */
+ @Override
+ public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
+ return ch == buffer[pos] ? 1 : 0;
+ }
+ }
+
+ /**
+ * Class used to define a set of characters for matching purposes.
+ */
+ static final class CharSetMatcher extends AbstractStringMatcher {
+ /** The set of characters to match. */
+ private final char[] chars;
+
+ /**
+ * Constructor that creates a matcher from a character array.
+ *
+ * @param chars
+ * the characters to match, must not be null
+ */
+ CharSetMatcher(final char[] chars) {
+ super();
+ this.chars = chars.clone();
+ Arrays.sort(this.chars);
+ }
+
+ /**
+ * Returns whether or not the given character matches.
+ *
+ * @param buffer
+ * the text content to match against, do not change
+ * @param pos
+ * the starting position for the match, valid for buffer
+ * @param bufferStart
+ * the first active index in the buffer, valid for buffer
+ * @param bufferEnd
+ * the end index of the active buffer, valid for buffer
+ * @return the number of matching characters, zero for no match
+ */
+ @Override
+ public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
+ return Arrays.binarySearch(chars, buffer[pos]) >= 0 ? 1 : 0;
+ }
+ }
+
+ /**
+ * Class used to match no characters.
+ */
+ static final class NoMatcher extends AbstractStringMatcher {
+
+ /**
+ * Constructs a new instance of <code>NoMatcher</code>.
+ */
+ NoMatcher() {
+ super();
+ }
+
+ /**
+ * Always returns <code>false</code>.
+ *
+ * @param buffer
+ * the text content to match against, do not change
+ * @param pos
+ * the starting position for the match, valid for buffer
+ * @param bufferStart
+ * the first active index in the buffer, valid for buffer
+ * @param bufferEnd
+ * the end index of the active buffer, valid for buffer
+ * @return the number of matching characters, zero for no match
+ */
+ @Override
+ public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
+ return 0;
+ }
+ }
+
+ /**
+ * Class used to define a set of characters for matching purposes.
+ */
+ static final class StringMatcher extends AbstractStringMatcher {
+ /** The string to match, as a character array. */
+ private final char[] chars;
+
+ /**
+ * Constructor that creates a matcher from a String.
+ *
+ * @param str
+ * the string to match, must not be null
+ */
+ StringMatcher(final String str) {
+ super();
+ chars = str.toCharArray();
+ }
+
+ /**
+ * Returns whether or not the given text matches the stored string.
+ *
+ * @param buffer
+ * the text content to match against, do not change
+ * @param pos
+ * the starting position for the match, valid for buffer
+ * @param bufferStart
+ * the first active index in the buffer, valid for buffer
+ * @param bufferEnd
+ * the end index of the active buffer, valid for buffer
+ * @return the number of matching characters, zero for no match
+ */
+ @Override
+ public int isMatch(final char[] buffer, int pos, final int bufferStart, final int bufferEnd) {
+ final int len = chars.length;
+ if (pos + len > bufferEnd) {
+ return 0;
+ }
+ for (int i = 0; i < chars.length; i++, pos++) {
+ if (chars[i] != buffer[pos]) {
+ return 0;
+ }
+ }
+ return len;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + ' ' + Arrays.toString(chars);
+ }
+
+ }
+
+ /**
+ * Class used to match whitespace as per trim().
+ */
+ static final class TrimMatcher extends AbstractStringMatcher {
+
+ /**
+ * The space character.
+ */
+ private static final int SPACE_INT = 32;
+
+ /**
+ * Constructs a new instance of <code>TrimMatcher</code>.
+ */
+ TrimMatcher() {
+ super();
+ }
+
+ /**
+ * Returns whether or not the given character matches.
+ *
+ * @param buffer
+ * the text content to match against, do not change
+ * @param pos
+ * the starting position for the match, valid for buffer
+ * @param bufferStart
+ * the first active index in the buffer, valid for buffer
+ * @param bufferEnd
+ * the end index of the active buffer, valid for buffer
+ * @return the number of matching characters, zero for no match
+ */
+ @Override
+ public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
+ return buffer[pos] <= SPACE_INT ? 1 : 0;
+ }
+ }
+
+ /**
+ * Constructor.
+ */
+ protected AbstractStringMatcher() {
+ super();
+ }
+
+ /**
+ * Returns the number of matching characters, zero for no match.
+ * <p>
+ * This method is called to check for a match. The parameter <code>pos</code> represents the current position to be
+ * checked in the string <code>buffer</code> (a character array which must not be changed). The API guarantees that
+ * <code>pos</code> is a valid index for <code>buffer</code>.
+ * <p>
+ * The matching code may check one character or many. It may check characters preceding <code>pos</code> as well as
+ * those after.
+ * <p>
+ * It must return zero for no match, or a positive number if a match was found. The number indicates the number of
+ * characters that matched.
+ *
+ * @param buffer
+ * the text content to match against, do not change
+ * @param pos
+ * the starting position for the match, valid for buffer
+ * @return the number of matching characters, zero for no match
+ */
+ public int isMatch(final char[] buffer, final int pos) {
+ return isMatch(buffer, pos, 0, buffer.length);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/commons-text/blob/4b67dd51/src/main/java/org/apache/commons/text/matcher/StringMatcher.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/matcher/StringMatcher.java b/src/main/java/org/apache/commons/text/matcher/StringMatcher.java
new file mode 100644
index 0000000..4133955
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/matcher/StringMatcher.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.text.matcher;
+
+/**
+ * Determines if a character array portion matches.
+ *
+ * @since 1.3
+ */
+public interface StringMatcher {
+
+ /**
+ * Returns the number of matching characters, zero for no match.
+ * <p>
+ * This method is called to check for a match. The parameter <code>pos</code> represents the current position to be
+ * checked in the string <code>buffer</code> (a character array which must not be changed). The API guarantees that
+ * <code>pos</code> is a valid index for <code>buffer</code>.
+ * <p>
+ * The character array may be larger than the active area to be matched. Only values in the buffer between the
+ * specified indices may be accessed.
+ * <p>
+ * The matching code may check one character or many. It may check characters preceding <code>pos</code> as well as
+ * those after, so long as no checks exceed the bounds specified.
+ * <p>
+ * It must return zero for no match, or a positive number if a match was found. The number indicates the number of
+ * characters that matched.
+ *
+ * @param buffer
+ * the text content to match against, do not change
+ * @param pos
+ * the starting position for the match, valid for buffer
+ * @param bufferStart
+ * the first active index in the buffer, valid for buffer
+ * @param bufferEnd
+ * the end index (exclusive) of the active buffer, valid for buffer
+ * @return the number of matching characters, or zero if there is no match
+ */
+ int isMatch(char[] buffer, int pos, int bufferStart, int bufferEnd);
+
+}
http://git-wip-us.apache.org/repos/asf/commons-text/blob/4b67dd51/src/main/java/org/apache/commons/text/matcher/StringMatcherFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/matcher/StringMatcherFactory.java b/src/main/java/org/apache/commons/text/matcher/StringMatcherFactory.java
new file mode 100644
index 0000000..988d150
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/matcher/StringMatcherFactory.java
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.text.matcher;
+
+/**
+ * Provides access to matchers defined in this package.
+ *
+ * @since 1.3
+ */
+public final class StringMatcherFactory {
+
+ /**
+ * Defines the singleton for this class.
+ */
+ public static final StringMatcherFactory INSTANCE = new StringMatcherFactory();
+
+ /**
+ * Matches the same characters as StringTokenizer, namely space, tab, newline, form feed.
+ */
+ private static final AbstractStringMatcher.CharSetMatcher SPLIT_MATCHER = new AbstractStringMatcher.CharSetMatcher(
+ " \t\n\r\f".toCharArray());
+
+ /**
+ * Matches the comma character.
+ */
+ private static final AbstractStringMatcher.CharMatcher COMMA_MATCHER = new AbstractStringMatcher.CharMatcher(',');
+
+ /**
+ * Matches the tab character.
+ */
+ private static final AbstractStringMatcher.CharMatcher TAB_MATCHER = new AbstractStringMatcher.CharMatcher('\t');
+
+ /**
+ * Matches the space character.
+ */
+ private static final AbstractStringMatcher.CharMatcher SPACE_MATCHER = new AbstractStringMatcher.CharMatcher(' ');
+
+ /**
+ * Matches the String trim() whitespace characters.
+ */
+ private static final AbstractStringMatcher.TrimMatcher TRIM_MATCHER = new AbstractStringMatcher.TrimMatcher();
+
+ /**
+ * Matches the double quote character.
+ */
+ private static final AbstractStringMatcher.CharMatcher SINGLE_QUOTE_MATCHER = new AbstractStringMatcher.CharMatcher(
+ '\'');
+
+ /**
+ * Matches the double quote character.
+ */
+ private static final AbstractStringMatcher.CharMatcher DOUBLE_QUOTE_MATCHER = new AbstractStringMatcher.CharMatcher(
+ '"');
+
+ /**
+ * Matches the single or double quote character.
+ */
+ private static final AbstractStringMatcher.CharSetMatcher QUOTE_MATCHER = new AbstractStringMatcher.CharSetMatcher(
+ "'\"".toCharArray());
+
+ /**
+ * Matches no characters.
+ */
+ private static final AbstractStringMatcher.NoMatcher NONE_MATCHER = new AbstractStringMatcher.NoMatcher();
+
+ /**
+ * No need to build instances for now.
+ */
+ private StringMatcherFactory() {
+ // empty
+ }
+
+ /**
+ * Constructor that creates a matcher from a character.
+ *
+ * @param ch
+ * the character to match, must not be null
+ * @return a new Matcher for the given char
+ */
+ public StringMatcher charMatcher(final char ch) {
+ return new AbstractStringMatcher.CharMatcher(ch);
+ }
+
+ /**
+ * Constructor that creates a matcher from a set of characters.
+ *
+ * @param chars
+ * the characters to match, null or empty matches nothing
+ * @return a new matcher for the given char[]
+ */
+ public StringMatcher charSetMatcher(final char... chars) {
+ if (chars == null || chars.length == 0) {
+ return NONE_MATCHER;
+ }
+ if (chars.length == 1) {
+ return new AbstractStringMatcher.CharMatcher(chars[0]);
+ }
+ return new AbstractStringMatcher.CharSetMatcher(chars);
+ }
+
+ /**
+ * Creates a matcher from a string representing a set of characters.
+ *
+ * @param chars
+ * the characters to match, null or empty matches nothing
+ * @return a new Matcher for the given characters
+ */
+ public StringMatcher charSetMatcher(final String chars) {
+ if (chars == null || chars.length() == 0) {
+ return NONE_MATCHER;
+ }
+ if (chars.length() == 1) {
+ return new AbstractStringMatcher.CharMatcher(chars.charAt(0));
+ }
+ return new AbstractStringMatcher.CharSetMatcher(chars.toCharArray());
+ }
+
+ /**
+ * Returns a matcher which matches the comma character.
+ *
+ * @return a matcher for a comma
+ */
+ public StringMatcher commaMatcher() {
+ return COMMA_MATCHER;
+ }
+
+ /**
+ * Returns a matcher which matches the double quote character.
+ *
+ * @return a matcher for a double quote
+ */
+ public StringMatcher doubleQuoteMatcher() {
+ return DOUBLE_QUOTE_MATCHER;
+ }
+
+ /**
+ * Matches no characters.
+ *
+ * @return a matcher that matches nothing
+ */
+ public StringMatcher noneMatcher() {
+ return NONE_MATCHER;
+ }
+
+ /**
+ * Returns a matcher which matches the single or double quote character.
+ *
+ * @return a matcher for a single or double quote
+ */
+ public StringMatcher quoteMatcher() {
+ return QUOTE_MATCHER;
+ }
+
+ /**
+ * Returns a matcher which matches the single quote character.
+ *
+ * @return a matcher for a single quote
+ */
+ public StringMatcher singleQuoteMatcher() {
+ return SINGLE_QUOTE_MATCHER;
+ }
+
+ /**
+ * Returns a matcher which matches the space character.
+ *
+ * @return a matcher for a space
+ */
+ public StringMatcher spaceMatcher() {
+ return SPACE_MATCHER;
+ }
+
+ /**
+ * Matches the same characters as StringTokenizer, namely space, tab, newline and form feed.
+ *
+ * @return the split matcher
+ */
+ public StringMatcher splitMatcher() {
+ return SPLIT_MATCHER;
+ }
+
+ /**
+ * Creates a matcher from a string.
+ *
+ * @param str
+ * the string to match, null or empty matches nothing
+ * @return a new Matcher for the given String
+ */
+ public StringMatcher stringMatcher(final String str) {
+ if (str == null || str.length() == 0) {
+ return NONE_MATCHER;
+ }
+ return new AbstractStringMatcher.StringMatcher(str);
+ }
+
+ /**
+ * Returns a matcher which matches the tab character.
+ *
+ * @return a matcher for a tab
+ */
+ public StringMatcher tabMatcher() {
+ return TAB_MATCHER;
+ }
+
+ /**
+ * Matches the String trim() whitespace characters.
+ *
+ * @return the trim matcher
+ */
+ public StringMatcher trimMatcher() {
+ return TRIM_MATCHER;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/commons-text/blob/4b67dd51/src/main/java/org/apache/commons/text/matcher/package-info.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/matcher/package-info.java b/src/main/java/org/apache/commons/text/matcher/package-info.java
new file mode 100644
index 0000000..a7a3aae
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/matcher/package-info.java
@@ -0,0 +1,25 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/**
+ * <p>
+ * Provides algorithms for matching up strings for use with a {@link org.apache.commons.text.StringSubstitutor}. The
+ * main class here is {@link org.apache.commons.text.matcher.StringMatcherFactory}
+ * </p>
+ *
+ * @since 1.3
+ */
+package org.apache.commons.text.matcher;
[5/5] [text] [TEXT-114] Add a replacement for StrSubstitutor based on
interfaces: StringSubstitutor.
Posted by gg...@apache.org.
[TEXT-114] Add a replacement for StrSubstitutor based on interfaces:
StringSubstitutor.
Project: http://git-wip-us.apache.org/repos/asf/commons-text/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-text/commit/4b67dd51
Tree: http://git-wip-us.apache.org/repos/asf/commons-text/tree/4b67dd51
Diff: http://git-wip-us.apache.org/repos/asf/commons-text/diff/4b67dd51
Branch: refs/heads/master
Commit: 4b67dd516dbb23b31d4b35d3f737f31de86a1cda
Parents: 3e649a7
Author: Gary Gregory <ga...@gmail.com>
Authored: Mon Feb 12 10:22:04 2018 -0700
Committer: Gary Gregory <ga...@gmail.com>
Committed: Mon Feb 12 10:22:04 2018 -0700
----------------------------------------------------------------------
src/changes/changes.xml | 1 +
.../commons/text/ExtendedMessageFormat.java | 4 +-
.../java/org/apache/commons/text/StrLookup.java | 2 +-
.../org/apache/commons/text/StrMatcher.java | 22 +-
.../org/apache/commons/text/StrSubstitutor.java | 2663 +++++++++---------
.../apache/commons/text/StringSubstitutor.java | 1359 +++++++++
.../text/lookup/AbstractStringLookup.java | 4 -
.../commons/text/lookup/StringLookup.java | 4 -
.../commons/text/lookup/package-info.java | 2 +-
.../text/matcher/AbstractStringMatcher.java | 256 ++
.../commons/text/matcher/StringMatcher.java | 55 +
.../text/matcher/StringMatcherFactory.java | 228 ++
.../commons/text/matcher/package-info.java | 25 +
.../apache/commons/text/StrSubstitutorTest.java | 1708 ++++++-----
...titutorWithInterpolatorStringLookupTest.java | 47 -
.../commons/text/StringSubstitutorTest.java | 622 ++++
...titutorWithInterpolatorStringLookupTest.java | 49 +
.../commons/text/matcher/StringMatcherTest.java | 219 ++
.../matcher/StringSubstitutorGetSetTest.java | 119 +
19 files changed, 5058 insertions(+), 2331 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/commons-text/blob/4b67dd51/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index eedd7ac..778d77a 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -49,6 +49,7 @@ The <action> type attribute can be add,update,fix,remove.
<action issue="TEXT-110" type="add" dev="pschumacher">Add Automatic-Module-Name MANIFEST entry for Java 9 compatibility</action>
<action issue="TEXT-70" type="fix" dev="pschumacher">Build failure with java 9-ea+159</action>
<action issue="TEXT-113" type="add" dev="ggregory">Add an interpolator string lookup</action>
+ <action issue="TEXT-114" type="add" dev="ggregory">Add a replacement for StrSubstitutor based on interfaces: StringSubstitutor</action>
</release>
<release version="1.2" date="2017-12-12" description="Release 1.2">
http://git-wip-us.apache.org/repos/asf/commons-text/blob/4b67dd51/src/main/java/org/apache/commons/text/ExtendedMessageFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/ExtendedMessageFormat.java b/src/main/java/org/apache/commons/text/ExtendedMessageFormat.java
index cfe1bf4..612845f 100644
--- a/src/main/java/org/apache/commons/text/ExtendedMessageFormat.java
+++ b/src/main/java/org/apache/commons/text/ExtendedMessageFormat.java
@@ -27,6 +27,8 @@ import java.util.Locale.Category;
import java.util.Map;
import java.util.Objects;
+import org.apache.commons.text.matcher.StringMatcherFactory;
+
/**
* Extends <code>java.text.MessageFormat</code> to allow pluggable/additional formatting
* options for embedded format elements. Client code should specify a registry
@@ -490,7 +492,7 @@ public class ExtendedMessageFormat extends MessageFormat {
int len = 0;
final char[] buffer = pattern.toCharArray();
do {
- len = StrMatcher.splitMatcher().isMatch(buffer, pos.getIndex());
+ len = StringMatcherFactory.INSTANCE.splitMatcher().isMatch(buffer, pos.getIndex(), 0, buffer.length);
pos.setIndex(pos.getIndex() + len);
} while (len > 0 && pos.getIndex() < pattern.length());
}
http://git-wip-us.apache.org/repos/asf/commons-text/blob/4b67dd51/src/main/java/org/apache/commons/text/StrLookup.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/StrLookup.java b/src/main/java/org/apache/commons/text/StrLookup.java
index 320e097..6357869 100644
--- a/src/main/java/org/apache/commons/text/StrLookup.java
+++ b/src/main/java/org/apache/commons/text/StrLookup.java
@@ -36,7 +36,7 @@ import org.apache.commons.text.lookup.StringLookupFactory;
*
* @param <V> the type of the values supported by the lookup
* @since 1.0
- * @deprecated Use {@link StringLookupFactory}.
+ * @deprecated Use {@link StringLookupFactory}. This class will be removed in 2.0.
*/
@Deprecated
public abstract class StrLookup<V> implements StringLookup {
http://git-wip-us.apache.org/repos/asf/commons-text/blob/4b67dd51/src/main/java/org/apache/commons/text/StrMatcher.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/StrMatcher.java b/src/main/java/org/apache/commons/text/StrMatcher.java
index 161d9a1..466eab4 100644
--- a/src/main/java/org/apache/commons/text/StrMatcher.java
+++ b/src/main/java/org/apache/commons/text/StrMatcher.java
@@ -18,6 +18,8 @@ package org.apache.commons.text;
import java.util.Arrays;
+import org.apache.commons.text.matcher.StringMatcherFactory;
+
/**
* A matcher class that can be queried to determine if a character array
* portion matches.
@@ -26,7 +28,9 @@ import java.util.Arrays;
* If these do not suffice, you can subclass and implement your own matcher.
*
* @since 1.0
+ * @deprecated Use {@link StringMatcherFactory}. This class will be removed in 2.0.
*/
+@Deprecated
public abstract class StrMatcher {
/**
@@ -223,7 +227,7 @@ public abstract class StrMatcher {
}
/**
- * Returns the number of matching characters, zero for no match.
+ * Returns the number of matching characters, or zero if there is no match.
* <p>
* This method is called to check for a match.
* The parameter <code>pos</code> represents the current position to be
@@ -245,12 +249,12 @@ public abstract class StrMatcher {
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index (exclusive) of the active buffer, valid for buffer
- * @return the number of matching characters, zero for no match
+ * @return the number of matching characters, or zero if there is no match
*/
public abstract int isMatch(char[] buffer, int pos, int bufferStart, int bufferEnd);
/**
- * Returns the number of matching characters, zero for no match.
+ * Returns the number of matching characters, or zero if there is no match.
* <p>
* This method is called to check for a match.
* The parameter <code>pos</code> represents the current position to be
@@ -266,7 +270,7 @@ public abstract class StrMatcher {
*
* @param buffer the text content to match against, do not change
* @param pos the starting position for the match, valid for buffer
- * @return the number of matching characters, zero for no match
+ * @return the number of matching characters, or zero if there is no match
*/
public int isMatch(final char[] buffer, final int pos) {
return isMatch(buffer, pos, 0, buffer.length);
@@ -298,7 +302,7 @@ public abstract class StrMatcher {
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index of the active buffer, valid for buffer
- * @return the number of matching characters, zero for no match
+ * @return the number of matching characters, or zero if there is no match
*/
@Override
public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
@@ -331,7 +335,7 @@ public abstract class StrMatcher {
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index of the active buffer, valid for buffer
- * @return the number of matching characters, zero for no match
+ * @return the number of matching characters, or zero if there is no match
*/
@Override
public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
@@ -364,7 +368,7 @@ public abstract class StrMatcher {
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index of the active buffer, valid for buffer
- * @return the number of matching characters, zero for no match
+ * @return the number of matching characters, or zero if there is no match
*/
@Override
public int isMatch(final char[] buffer, int pos, final int bufferStart, final int bufferEnd) {
@@ -407,7 +411,7 @@ public abstract class StrMatcher {
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index of the active buffer, valid for buffer
- * @return the number of matching characters, zero for no match
+ * @return the number of matching characters, or zero if there is no match
*/
@Override
public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
@@ -435,7 +439,7 @@ public abstract class StrMatcher {
* @param pos the starting position for the match, valid for buffer
* @param bufferStart the first active index in the buffer, valid for buffer
* @param bufferEnd the end index of the active buffer, valid for buffer
- * @return the number of matching characters, zero for no match
+ * @return the number of matching characters, or zero if there is no match
*/
@Override
public int isMatch(final char[] buffer, final int pos, final int bufferStart, final int bufferEnd) {
[2/5] [text] [TEXT-114] Add a replacement for StrSubstitutor based on
interfaces: StringSubstitutor.
Posted by gg...@apache.org.
http://git-wip-us.apache.org/repos/asf/commons-text/blob/4b67dd51/src/test/java/org/apache/commons/text/StrSubstitutorTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/StrSubstitutorTest.java b/src/test/java/org/apache/commons/text/StrSubstitutorTest.java
index 4ab52ed..cbd95c3 100644
--- a/src/test/java/org/apache/commons/text/StrSubstitutorTest.java
+++ b/src/test/java/org/apache/commons/text/StrSubstitutorTest.java
@@ -1,876 +1,832 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * 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.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.commons.text;
-
-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.apache.commons.text.lookup.StringLookup;
-import org.apache.commons.text.lookup.StringLookupFactory;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Test class for {@link StrSubstitutor}.
- */
-public class StrSubstitutorTest {
-
- private Map<String, String> values;
-
- @Before
- public void setUp() throws Exception {
- values = new HashMap<>();
- values.put("animal", "quick brown fox");
- values.put("target", "lazy dog");
- }
-
- @After
- public void tearDown() throws Exception {
- values = null;
- }
-
- //-----------------------------------------------------------------------
- /**
- * Tests simple key replace.
- */
- @Test
- public void testReplaceSimple() {
- doTestReplace("The quick brown fox jumps over the lazy dog.", "The ${animal} jumps over the ${target}.", true);
- }
-
- /**
- * Tests simple key replace.
- */
- @Test
- public void testReplaceSolo() {
- doTestReplace("quick brown fox", "${animal}", false);
- }
-
- /**
- * Tests replace with no variables.
- */
- @Test
- public void testReplaceNoVariables() {
- doTestNoReplace("The balloon arrived.");
- }
-
- /**
- * Tests replace with null.
- */
- @Test
- public void testReplaceNull() {
- doTestNoReplace(null);
- }
-
- /**
- * Tests replace with null.
- */
- @Test
- public void testReplaceEmpty() {
- doTestNoReplace("");
- }
-
- /**
- * Tests key replace changing map after initialization (not recommended).
- */
- @Test
- public void testReplaceChangedMap() {
- final StrSubstitutor sub = new StrSubstitutor(values);
- values.put("target", "moon");
- assertEquals("The quick brown fox jumps over the moon.",
- sub.replace("The ${animal} jumps over the ${target}."));
- }
-
- /**
- * Tests unknown key replace.
- */
- @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);
- }
-
- /**
- * Tests adjacent keys.
- */
- @Test
- public void testReplaceAdjacentAtStart() {
- values.put("code", "GBP");
- values.put("amount", "12.50");
- final StrSubstitutor sub = new StrSubstitutor(values);
- assertEquals("GBP12.50 charged", sub.replace("${code}${amount} charged"));
- }
-
- /**
- * Tests adjacent keys.
- */
- @Test
- public void testReplaceAdjacentAtEnd() {
- values.put("code", "GBP");
- values.put("amount", "12.50");
- final StrSubstitutor sub = new StrSubstitutor(values);
- assertEquals("Amount is GBP12.50", sub.replace("Amount is ${code}${amount}"));
- }
-
- /**
- * Tests simple recursive replace.
- */
- @Test
- public void testReplaceRecursive() {
- values.put("animal", "${critter}");
- values.put("target", "${pet}");
- values.put("pet", "${petCharacteristic} dog");
- values.put("petCharacteristic", "lazy");
- values.put("critter", "${critterSpeed} ${critterColor} ${critterType}");
- values.put("critterSpeed", "quick");
- 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);
- }
-
- @Test
- public void testDisableSubstitutionInValues() {
- final StrSubstitutor sub = new StrSubstitutor(values);
- sub.setDisableSubstitutionInValues(true);
- values.put("animal", "${critter}");
- values.put("target", "${pet}");
- values.put("pet", "${petCharacteristic} dog");
- values.put("petCharacteristic", "lazy");
- values.put("critter", "${critterSpeed} ${critterColor} ${critterType}");
- values.put("critterSpeed", "quick");
- values.put("critterColor", "brown");
- values.put("critterType", "fox");
- doTestReplace(sub, "The ${critter} jumps over the ${pet}.", "The ${animal} jumps over the ${target}.", true);
- }
-
- /**
- * Tests escaping.
- */
- @Test
- public void testReplaceEscaping() {
- doTestReplace("The ${animal} jumps over the lazy dog.", "The $${animal} jumps over the ${target}.", true);
- }
-
- /**
- * Tests escaping.
- */
- @Test
- public void testReplaceSoloEscaping() {
- doTestReplace("${animal}", "$${animal}", false);
- }
-
- /**
- * Tests complex escaping.
- */
- @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);
- }
-
- /**
- * Tests when no prefix or suffix.
- */
- @Test
- public void testReplaceNoPrefixNoSuffix() {
- doTestReplace("The animal jumps over the lazy dog.", "The animal jumps over the ${target}.", true);
- }
-
- /**
- * Tests when no incomplete prefix.
- */
- @Test
- public void testReplaceIncompletePrefix() {
- doTestReplace("The {animal} jumps over the lazy dog.", "The {animal} jumps over the ${target}.", true);
- }
-
- /**
- * Tests when prefix but no suffix.
- */
- @Test
- public void testReplacePrefixNoSuffix() {
- doTestReplace("The ${animal jumps over the ${target} lazy dog.",
- "The ${animal jumps over the ${target} ${target}.", true);
- }
-
- /**
- * Tests when suffix but no prefix.
- */
- @Test
- public void testReplaceNoPrefixSuffix() {
- doTestReplace("The animal} jumps over the lazy dog.", "The animal} jumps over the ${target}.", true);
- }
-
- /**
- * Tests when no variable name.
- */
- @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);
- }
-
- /**
- * Tests replace creates output same as input.
- */
- @Test
- public void testReplaceToIdentical() {
- values.put("animal", "$${${thing}}");
- values.put("thing", "animal");
- doTestReplace("The ${animal} jumps.", "The ${animal} jumps.", true);
- }
-
- /**
- * Tests a cyclic replace operation.
- * The cycle should be detected and cause an exception to be thrown.
- */
- @Test
- public void testCyclicReplacement() {
- final Map<String, String> map = new HashMap<>();
- map.put("animal", "${critter}");
- map.put("target", "${pet}");
- map.put("pet", "${petCharacteristic} dog");
- map.put("petCharacteristic", "lazy");
- map.put("critter", "${critterSpeed} ${critterColor} ${critterType}");
- map.put("critterSpeed", "quick");
- map.put("critterColor", "brown");
- map.put("critterType", "${animal}");
- 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!");
- } catch (final IllegalStateException ex) {
- // expected
- }
- }
-
- /**
- * Tests interpolation with weird boundary patterns.
- */
- @Test
- public void testReplaceWeirdPattens() {
- doTestNoReplace("");
- doTestNoReplace("${}");
- doTestNoReplace("${ }");
- doTestNoReplace("${\t}");
- doTestNoReplace("${\n}");
- doTestNoReplace("${\b}");
- doTestNoReplace("${");
- doTestNoReplace("$}");
- doTestNoReplace("}");
- doTestNoReplace("${}$");
- doTestNoReplace("${${");
- doTestNoReplace("${${}}");
- doTestNoReplace("${$${}}");
- doTestNoReplace("${$$${}}");
- doTestNoReplace("${$$${$}}");
- doTestNoReplace("${${}}");
- doTestNoReplace("${${ }}");
- }
-
- /**
- * Tests simple key replace.
- */
- @Test
- public void testReplacePartialString_noReplace() {
- final StrSubstitutor sub = new StrSubstitutor();
- assertEquals("${animal} jumps", sub.replace("The ${animal} jumps over the ${target}.", 4, 15));
- }
-
- /**
- * Tests whether a variable can be replaced in a variable name.
- */
- @Test
- public void testReplaceInVariable() {
- values.put("animal.1", "fox");
- values.put("animal.2", "mouse");
- values.put("species", "2");
- final 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}."));
- 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}."));
- }
-
- /**
- * Tests whether substitution in variable names is disabled per default.
- */
- @Test
- public void testReplaceInVariableDisabled() {
- values.put("animal.1", "fox");
- values.put("animal.2", "mouse");
- values.put("species", "2");
- final StrSubstitutor sub = new StrSubstitutor(values);
- assertEquals(
- "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}."));
- }
-
- /**
- * Tests complex and recursive substitution in variable names.
- */
- @Test
- 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");
- final StrSubstitutor sub = new StrSubstitutor(values);
- sub.setEnableSubstitutionInVariables(true);
- assertEquals(
- "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<>();
- 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}."));
- }
-
- //-----------------------------------------------------------------------
- /**
- * Tests protected.
- */
- @Test
- public void testResolveVariable() {
- final StrBuilder builder = new StrBuilder("Hi ${name}!");
- final Map<String, String> map = new HashMap<>();
- map.put("name", "commons");
- final StrSubstitutor sub = new StrSubstitutor(map) {
- @Override
- protected String resolveVariable(final String variableName, final StrBuilder buf, final int startPos,
- final int endPos) {
- assertEquals("name", variableName);
- assertSame(builder, buf);
- assertEquals(3, startPos);
- assertEquals(10, endPos);
- return "jakarta";
- }
- };
- sub.replaceIn(builder);
- assertEquals("Hi jakarta!", builder.toString());
- }
-
- //-----------------------------------------------------------------------
- /**
- * Tests constructor.
- */
- @Test
- public void testConstructorNoArgs() {
- final StrSubstitutor sub = new StrSubstitutor();
- assertEquals("Hi ${name}", sub.replace("Hi ${name}"));
- }
-
- /**
- * Tests constructor.
- */
- @Test
- public void testConstructorMapPrefixSuffix() {
- final Map<String, String> map = new HashMap<>();
- map.put("name", "commons");
- final StrSubstitutor sub = new StrSubstitutor(map, "<", ">");
- assertEquals("Hi < commons", sub.replace("Hi $< <name>"));
- }
-
- /**
- * Tests constructor.
- */
- @Test
- public void testConstructorMapFull() {
- final Map<String, String> map = new HashMap<>();
- map.put("name", "commons");
- StrSubstitutor sub = new StrSubstitutor(map, "<", ">", '!');
- assertEquals("Hi < commons", sub.replace("Hi !< <name>"));
- sub = new StrSubstitutor(map, "<", ">", '!', "||");
- assertEquals("Hi < commons", sub.replace("Hi !< <name2||commons>"));
- }
-
- //-----------------------------------------------------------------------
- /**
- * Tests get set.
- */
- @Test
- public void testGetSetEscape() {
- final StrSubstitutor sub = new StrSubstitutor();
- assertEquals('$', sub.getEscapeChar());
- sub.setEscapeChar('<');
- assertEquals('<', sub.getEscapeChar());
- }
-
- /**
- * Tests get set.
- */
- @Test
- public void testGetSetPrefix() {
- final StrSubstitutor sub = new StrSubstitutor();
- assertTrue(sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher);
- sub.setVariablePrefix('<');
- assertTrue(sub.getVariablePrefixMatcher() instanceof StrMatcher.CharMatcher);
-
- sub.setVariablePrefix("<<");
- assertTrue(sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher);
- try {
- sub.setVariablePrefix((String) null);
- fail();
- } catch (final IllegalArgumentException ex) {
- // expected
- }
- assertTrue(sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher);
-
- final StrMatcher matcher = StrMatcher.commaMatcher();
- sub.setVariablePrefixMatcher(matcher);
- assertSame(matcher, sub.getVariablePrefixMatcher());
- try {
- sub.setVariablePrefixMatcher((StrMatcher) null);
- fail();
- } catch (final IllegalArgumentException ex) {
- // expected
- }
- assertSame(matcher, sub.getVariablePrefixMatcher());
- }
-
- /**
- * Tests get set.
- */
- @Test
- public void testGetSetSuffix() {
- final StrSubstitutor sub = new StrSubstitutor();
- assertTrue(sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher);
- sub.setVariableSuffix('<');
- assertTrue(sub.getVariableSuffixMatcher() instanceof StrMatcher.CharMatcher);
-
- sub.setVariableSuffix("<<");
- assertTrue(sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher);
- try {
- sub.setVariableSuffix((String) null);
- fail();
- } catch (final IllegalArgumentException ex) {
- // expected
- }
- assertTrue(sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher);
-
- final StrMatcher matcher = StrMatcher.commaMatcher();
- sub.setVariableSuffixMatcher(matcher);
- assertSame(matcher, sub.getVariableSuffixMatcher());
- try {
- sub.setVariableSuffixMatcher((StrMatcher) null);
- fail();
- } catch (final IllegalArgumentException ex) {
- // expected
- }
- 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.
- */
- @Test
- public void testStaticReplace() {
- final Map<String, String> map = new HashMap<>();
- map.put("name", "commons");
- assertEquals("Hi commons!", StrSubstitutor.replace("Hi ${name}!", map));
- }
-
- /**
- * Tests static.
- */
- @Test
- public void testStaticReplacePrefixSuffix() {
- final Map<String, String> map = new HashMap<>();
- map.put("name", "commons");
- assertEquals("Hi commons!", StrSubstitutor.replace("Hi <name>!", map, "<", ">"));
- }
-
- /**
- * Tests interpolation with system properties.
- */
- @Test
- public void testStaticReplaceSystemProperties() {
- final StrBuilder buf = new StrBuilder();
- buf.append("Hi ").append(System.getProperty("user.name"));
- buf.append(", you are working with ");
- buf.append(System.getProperty("os.name"));
- buf.append(", your home directory is ");
- buf.append(System.getProperty("user.home")).append('.');
- assertEquals(buf.toString(), StrSubstitutor.replaceSystemProperties("Hi ${user.name}, you are "
- + "working with ${os.name}, your home "
- + "directory is ${user.home}."));
- }
-
- /**
- * Test for LANG-1055: StrSubstitutor.replaceSystemProperties does not work consistently
- */
- @Test
- public void testLANG1055() {
- System.setProperty("test_key", "test_value");
-
- final String expected = StrSubstitutor.replace("test_key=${test_key}", System.getProperties());
- final String actual = StrSubstitutor.replaceSystemProperties("test_key=${test_key}");
- assertEquals(expected, actual);
- }
-
- /**
- * Test the replace of a properties object
- */
- @Test
- public void testSubstituteDefaultProperties() {
- final String org = "${doesnotwork}";
- System.setProperty("doesnotwork", "It works!");
-
- // create a new Properties object with the System.getProperties as default
- final Properties props = new Properties(System.getProperties());
-
- assertEquals("It works!", StrSubstitutor.replace(org, props));
- }
-
- @Test
- public void testSamePrefixAndSuffix() {
- final Map<String, String> map = new HashMap<>();
- map.put("greeting", "Hello");
- map.put(" there ", "XXX");
- map.put("name", "commons");
- assertEquals("Hi commons!", StrSubstitutor.replace("Hi @name@!", map, "@", "@"));
- assertEquals("Hello there commons!", StrSubstitutor.replace("@greeting@ there @name@!", map, "@", "@"));
- }
-
- @Test
- public void testSubstitutePreserveEscape() {
- final String org = "${not-escaped} $${escaped}";
- final Map<String, String> map = new HashMap<>();
- map.put("not-escaped", "value");
-
- final StrSubstitutor sub = new StrSubstitutor(map, "${", "}", '$');
- assertFalse(sub.isPreserveEscapes());
- assertEquals("value ${escaped}", sub.replace(org));
-
- sub.setPreserveEscapes(true);
- assertTrue(sub.isPreserveEscapes());
- assertEquals("value $${escaped}", sub.replace(org));
- }
-
- private void doTestReplace(final String expectedResult, final String replaceTemplate, final boolean substring) {
- final StrSubstitutor sub = new StrSubstitutor(values);
- doTestReplace(sub, expectedResult, replaceTemplate, substring);
- }
-
- //-----------------------------------------------------------------------
- private void doTestReplace(final StrSubstitutor sub, final String expectedResult, final String replaceTemplate,
- final boolean substring) {
- final String expectedShortResult = expectedResult.substring(1, expectedResult.length() - 1);
-
- // replace using String
- assertEquals(expectedResult, sub.replace(replaceTemplate));
- if (substring) {
- assertEquals(expectedShortResult, sub.replace(replaceTemplate, 1, replaceTemplate.length() - 2));
- }
-
- // replace using char[]
- final 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 StringBuilder
- StringBuilder builder = new StringBuilder(replaceTemplate);
- assertEquals(expectedResult, sub.replace(builder));
- if (substring) {
- assertEquals(expectedShortResult, sub.replace(builder, 1, builder.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
- final MutableObject<String> obj = new MutableObject<>(replaceTemplate); // toString returns template
- assertEquals(expectedResult, sub.replace(obj));
-
- // replace in StringBuffer
- buf = new StringBuffer(replaceTemplate);
- assertTrue(sub.replaceIn(buf));
- assertEquals(expectedResult, buf.toString());
- if (substring) {
- buf = new StringBuffer(replaceTemplate);
- assertTrue(sub.replaceIn(buf, 1, buf.length() - 2));
- assertEquals(expectedResult, buf.toString()); // expect full result as remainder is untouched
- }
-
- // replace in StringBuilder
- builder = new StringBuilder(replaceTemplate);
- assertTrue(sub.replaceIn(builder));
- assertEquals(expectedResult, builder.toString());
- if (substring) {
- builder = new StringBuilder(replaceTemplate);
- assertTrue(sub.replaceIn(builder, 1, builder.length() - 2));
- assertEquals(expectedResult, builder.toString()); // expect full result as remainder is untouched
- }
-
- // replace in StrBuilder
- bld = new StrBuilder(replaceTemplate);
- assertTrue(sub.replaceIn(bld));
- assertEquals(expectedResult, bld.toString());
- if (substring) {
- bld = new StrBuilder(replaceTemplate);
- assertTrue(sub.replaceIn(bld, 1, bld.length() - 2));
- assertEquals(expectedResult, bld.toString()); // expect full result as remainder is untouched
- }
- }
-
- private void doTestNoReplace(final String replaceTemplate) {
- final StrSubstitutor sub = new StrSubstitutor(values);
-
- if (replaceTemplate == null) {
- assertNull(sub.replace((String) null));
- assertNull(sub.replace((String) null, 0, 100));
- assertNull(sub.replace((char[]) null));
- assertNull(sub.replace((char[]) null, 0, 100));
- assertNull(sub.replace((StringBuffer) null));
- assertNull(sub.replace((StringBuffer) null, 0, 100));
- assertNull(sub.replace((StrBuilder) null));
- assertNull(sub.replace((StrBuilder) null, 0, 100));
- assertNull(sub.replace((Object) null));
- assertFalse(sub.replaceIn((StringBuffer) null));
- assertFalse(sub.replaceIn((StringBuffer) null, 0, 100));
- assertFalse(sub.replaceIn((StrBuilder) null));
- assertFalse(sub.replaceIn((StrBuilder) null, 0, 100));
- } else {
- assertEquals(replaceTemplate, sub.replace(replaceTemplate));
- final StrBuilder bld = new StrBuilder(replaceTemplate);
- assertFalse(sub.replaceIn(bld));
- assertEquals(replaceTemplate, bld.toString());
- }
- }
-
- @Test
- public void testReplaceInTakingTwoAndThreeIntsReturningFalse() {
- final Map<String, Object> hashMap = new HashMap<>();
- final StringLookup mapStringLookup = StringLookupFactory.INSTANCE.mapStringLookup(hashMap);
- final StrMatcher strMatcher = StrMatcher.tabMatcher();
- final StrSubstitutor strSubstitutor =
- new StrSubstitutor(mapStringLookup, strMatcher, strMatcher, 'b', strMatcher);
-
- assertFalse(strSubstitutor.replaceIn((StringBuilder) null, 1315, (-1369)));
- assertEquals('b', strSubstitutor.getEscapeChar());
- assertFalse(strSubstitutor.isPreserveEscapes());
- }
-
- @Test
- public void testReplaceInTakingTwoAndThreeIntsReturningFalse_deprecated() {
- final Map<String, Object> hashMap = new HashMap<>();
- final StrLookup.MapStrLookup<Object> mapStrLookup = new StrLookup.MapStrLookup<>(hashMap);
- final StrMatcher strMatcher = StrMatcher.tabMatcher();
- final StrSubstitutor strSubstitutor =
- new StrSubstitutor(mapStrLookup, strMatcher, strMatcher, 'b', strMatcher);
-
- assertFalse(strSubstitutor.replaceIn((StringBuilder) null, 1315, (-1369)));
- assertEquals('b', strSubstitutor.getEscapeChar());
- assertFalse(strSubstitutor.isPreserveEscapes());
- }
-
- @Test
- public void testReplaceInTakingStringBuilderWithNonNull() {
- final StringLookup strLookup = StringLookupFactory.INSTANCE.systemPropertyStringLookup();
- final StrSubstitutor strSubstitutor = new StrSubstitutor(strLookup, "b<H", "b<H", '\'');
- final StringBuilder stringBuilder = new StringBuilder((CharSequence) "b<H");
-
- assertEquals('\'', strSubstitutor.getEscapeChar());
- assertFalse(strSubstitutor.replaceIn(stringBuilder));
- }
-
- @Test
- public void testReplaceInTakingStringBuilderWithNonNull_deprecated() {
- final StrLookup<String> strLookup = StrLookup.systemPropertiesLookup();
- final StrSubstitutor strSubstitutor = new StrSubstitutor(strLookup, "b<H", "b<H", '\'');
- final StringBuilder stringBuilder = new StringBuilder((CharSequence) "b<H");
-
- assertEquals('\'', strSubstitutor.getEscapeChar());
- assertFalse(strSubstitutor.replaceIn(stringBuilder));
- }
-
- @Test
- public void testReplaceInTakingStringBufferWithNonNull() {
- final StrSubstitutor strSubstitutor =
- new StrSubstitutor(new HashMap<String, String>(), "WV@i#y?N*[", "WV@i#y?N*[", '*');
-
- assertFalse(strSubstitutor.isPreserveEscapes());
- assertFalse(strSubstitutor.replaceIn(new StringBuffer("WV@i#y?N*[")));
- assertEquals('*', strSubstitutor.getEscapeChar());
- }
-
- @Test
- public void testCreatesStrSubstitutorTakingStrLookupAndCallsReplaceTakingTwoAndThreeInts() {
- final Map<String, CharacterPredicates> map = new HashMap<>();
- final StringLookup mapStringLookup = StringLookupFactory.INSTANCE.mapStringLookup(map);
- final StrSubstitutor strSubstitutor = new StrSubstitutor(mapStringLookup);
-
- assertNull(strSubstitutor.replace((CharSequence) null, 0, 0));
- assertEquals('$', strSubstitutor.getEscapeChar());
- }
-
- @Test
- public void testCreatesStrSubstitutorTakingStrLookupAndCallsReplaceTakingTwoAndThreeInts_deprecated() {
- final Map<String, CharacterPredicates> map = new HashMap<>();
- final StrLookup.MapStrLookup<CharacterPredicates> mapStrLookup = new StrLookup.MapStrLookup<>(map);
- final StrSubstitutor strSubstitutor = new StrSubstitutor(mapStrLookup);
-
- assertNull(strSubstitutor.replace((CharSequence) null, 0, 0));
- assertEquals('$', strSubstitutor.getEscapeChar());
- }
-
- @Test
- public void testReplaceTakingCharSequenceReturningNull() {
- final StrSubstitutor strSubstitutor = new StrSubstitutor((StringLookup) null);
-
- assertNull(strSubstitutor.replace((CharSequence) null));
- assertFalse(strSubstitutor.isPreserveEscapes());
- assertEquals('$', strSubstitutor.getEscapeChar());
- }
-
- @Test
- public void testReplaceTakingCharSequenceReturningNull_deprecated() {
- final StrSubstitutor strSubstitutor = new StrSubstitutor((StrLookup<?>) null);
-
- assertNull(strSubstitutor.replace((CharSequence) null));
- assertFalse(strSubstitutor.isPreserveEscapes());
- assertEquals('$', strSubstitutor.getEscapeChar());
- }
-
- @Test(expected = NullPointerException.class)
- public void testReplaceTakingThreeArgumentsThrowsNullPointerException() {
- StrSubstitutor.replace(null, (Properties) null);
- }
-
- @Test
- public void testReplaceInTakingStringBuilderWithNull() {
- final Map<String, Object> map = new HashMap<>();
- final StrSubstitutor strSubstitutor = new StrSubstitutor(map, "", "", 'T', "K+<'f");
-
- assertFalse(strSubstitutor.replaceIn((StringBuilder) null));
- }
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.text;
+
+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 {@link StrSubstitutor}.
+ */
+public class StrSubstitutorTest {
+
+ private Map<String, String> values;
+
+ @Before
+ public void setUp() throws Exception {
+ values = new HashMap<>();
+ values.put("animal", "quick brown fox");
+ values.put("target", "lazy dog");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ values = null;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Tests simple key replace.
+ */
+ @Test
+ public void testReplaceSimple() {
+ doTestReplace("The quick brown fox jumps over the lazy dog.", "The ${animal} jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests simple key replace.
+ */
+ @Test
+ public void testReplaceSolo() {
+ doTestReplace("quick brown fox", "${animal}", false);
+ }
+
+ /**
+ * Tests replace with no variables.
+ */
+ @Test
+ public void testReplaceNoVariables() {
+ doTestNoReplace("The balloon arrived.");
+ }
+
+ /**
+ * Tests replace with null.
+ */
+ @Test
+ public void testReplaceNull() {
+ doTestNoReplace(null);
+ }
+
+ /**
+ * Tests replace with null.
+ */
+ @Test
+ public void testReplaceEmpty() {
+ doTestNoReplace("");
+ }
+
+ /**
+ * Tests key replace changing map after initialization (not recommended).
+ */
+ @Test
+ public void testReplaceChangedMap() {
+ final StrSubstitutor sub = new StrSubstitutor(values);
+ values.put("target", "moon");
+ assertEquals("The quick brown fox jumps over the moon.",
+ sub.replace("The ${animal} jumps over the ${target}."));
+ }
+
+ /**
+ * Tests unknown key replace.
+ */
+ @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);
+ }
+
+ /**
+ * Tests adjacent keys.
+ */
+ @Test
+ public void testReplaceAdjacentAtStart() {
+ values.put("code", "GBP");
+ values.put("amount", "12.50");
+ final StrSubstitutor sub = new StrSubstitutor(values);
+ assertEquals("GBP12.50 charged", sub.replace("${code}${amount} charged"));
+ }
+
+ /**
+ * Tests adjacent keys.
+ */
+ @Test
+ public void testReplaceAdjacentAtEnd() {
+ values.put("code", "GBP");
+ values.put("amount", "12.50");
+ final StrSubstitutor sub = new StrSubstitutor(values);
+ assertEquals("Amount is GBP12.50", sub.replace("Amount is ${code}${amount}"));
+ }
+
+ /**
+ * Tests simple recursive replace.
+ */
+ @Test
+ public void testReplaceRecursive() {
+ values.put("animal", "${critter}");
+ values.put("target", "${pet}");
+ values.put("pet", "${petCharacteristic} dog");
+ values.put("petCharacteristic", "lazy");
+ values.put("critter", "${critterSpeed} ${critterColor} ${critterType}");
+ values.put("critterSpeed", "quick");
+ 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);
+ }
+
+ @Test
+ public void testDisableSubstitutionInValues() {
+ final StrSubstitutor sub = new StrSubstitutor(values);
+ sub.setDisableSubstitutionInValues(true);
+ values.put("animal", "${critter}");
+ values.put("target", "${pet}");
+ values.put("pet", "${petCharacteristic} dog");
+ values.put("petCharacteristic", "lazy");
+ values.put("critter", "${critterSpeed} ${critterColor} ${critterType}");
+ values.put("critterSpeed", "quick");
+ values.put("critterColor", "brown");
+ values.put("critterType", "fox");
+ doTestReplace(sub, "The ${critter} jumps over the ${pet}.", "The ${animal} jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests escaping.
+ */
+ @Test
+ public void testReplaceEscaping() {
+ doTestReplace("The ${animal} jumps over the lazy dog.", "The $${animal} jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests escaping.
+ */
+ @Test
+ public void testReplaceSoloEscaping() {
+ doTestReplace("${animal}", "$${animal}", false);
+ }
+
+ /**
+ * Tests complex escaping.
+ */
+ @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);
+ }
+
+ /**
+ * Tests when no prefix or suffix.
+ */
+ @Test
+ public void testReplaceNoPrefixNoSuffix() {
+ doTestReplace("The animal jumps over the lazy dog.", "The animal jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests when no incomplete prefix.
+ */
+ @Test
+ public void testReplaceIncompletePrefix() {
+ doTestReplace("The {animal} jumps over the lazy dog.", "The {animal} jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests when prefix but no suffix.
+ */
+ @Test
+ public void testReplacePrefixNoSuffix() {
+ doTestReplace("The ${animal jumps over the ${target} lazy dog.",
+ "The ${animal jumps over the ${target} ${target}.", true);
+ }
+
+ /**
+ * Tests when suffix but no prefix.
+ */
+ @Test
+ public void testReplaceNoPrefixSuffix() {
+ doTestReplace("The animal} jumps over the lazy dog.", "The animal} jumps over the ${target}.", true);
+ }
+
+ /**
+ * Tests when no variable name.
+ */
+ @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);
+ }
+
+ /**
+ * Tests replace creates output same as input.
+ */
+ @Test
+ public void testReplaceToIdentical() {
+ values.put("animal", "$${${thing}}");
+ values.put("thing", "animal");
+ doTestReplace("The ${animal} jumps.", "The ${animal} jumps.", true);
+ }
+
+ /**
+ * Tests a cyclic replace operation.
+ * The cycle should be detected and cause an exception to be thrown.
+ */
+ @Test
+ public void testCyclicReplacement() {
+ final Map<String, String> map = new HashMap<>();
+ map.put("animal", "${critter}");
+ map.put("target", "${pet}");
+ map.put("pet", "${petCharacteristic} dog");
+ map.put("petCharacteristic", "lazy");
+ map.put("critter", "${critterSpeed} ${critterColor} ${critterType}");
+ map.put("critterSpeed", "quick");
+ map.put("critterColor", "brown");
+ map.put("critterType", "${animal}");
+ 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!");
+ } catch (final IllegalStateException ex) {
+ // expected
+ }
+ }
+
+ /**
+ * Tests interpolation with weird boundary patterns.
+ */
+ @Test
+ public void testReplaceWeirdPattens() {
+ doTestNoReplace("");
+ doTestNoReplace("${}");
+ doTestNoReplace("${ }");
+ doTestNoReplace("${\t}");
+ doTestNoReplace("${\n}");
+ doTestNoReplace("${\b}");
+ doTestNoReplace("${");
+ doTestNoReplace("$}");
+ doTestNoReplace("}");
+ doTestNoReplace("${}$");
+ doTestNoReplace("${${");
+ doTestNoReplace("${${}}");
+ doTestNoReplace("${$${}}");
+ doTestNoReplace("${$$${}}");
+ doTestNoReplace("${$$${$}}");
+ doTestNoReplace("${${}}");
+ doTestNoReplace("${${ }}");
+ }
+
+ /**
+ * Tests simple key replace.
+ */
+ @Test
+ public void testReplacePartialString_noReplace() {
+ final StrSubstitutor sub = new StrSubstitutor();
+ assertEquals("${animal} jumps", sub.replace("The ${animal} jumps over the ${target}.", 4, 15));
+ }
+
+ /**
+ * Tests whether a variable can be replaced in a variable name.
+ */
+ @Test
+ public void testReplaceInVariable() {
+ values.put("animal.1", "fox");
+ values.put("animal.2", "mouse");
+ values.put("species", "2");
+ final 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}."));
+ 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}."));
+ }
+
+ /**
+ * Tests whether substitution in variable names is disabled per default.
+ */
+ @Test
+ public void testReplaceInVariableDisabled() {
+ values.put("animal.1", "fox");
+ values.put("animal.2", "mouse");
+ values.put("species", "2");
+ final StrSubstitutor sub = new StrSubstitutor(values);
+ assertEquals(
+ "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}."));
+ }
+
+ /**
+ * Tests complex and recursive substitution in variable names.
+ */
+ @Test
+ 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");
+ final StrSubstitutor sub = new StrSubstitutor(values);
+ sub.setEnableSubstitutionInVariables(true);
+ assertEquals(
+ "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<>();
+ 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}."));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Tests protected.
+ */
+ @Test
+ public void testResolveVariable() {
+ final StrBuilder builder = new StrBuilder("Hi ${name}!");
+ final Map<String, String> map = new HashMap<>();
+ map.put("name", "commons");
+ final StrSubstitutor sub = new StrSubstitutor(map) {
+ @Override
+ protected String resolveVariable(final String variableName, final StrBuilder buf, final int startPos,
+ final int endPos) {
+ assertEquals("name", variableName);
+ assertSame(builder, buf);
+ assertEquals(3, startPos);
+ assertEquals(10, endPos);
+ return "jakarta";
+ }
+ };
+ sub.replaceIn(builder);
+ assertEquals("Hi jakarta!", builder.toString());
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Tests constructor.
+ */
+ @Test
+ public void testConstructorNoArgs() {
+ final StrSubstitutor sub = new StrSubstitutor();
+ assertEquals("Hi ${name}", sub.replace("Hi ${name}"));
+ }
+
+ /**
+ * Tests constructor.
+ */
+ @Test
+ public void testConstructorMapPrefixSuffix() {
+ final Map<String, String> map = new HashMap<>();
+ map.put("name", "commons");
+ final StrSubstitutor sub = new StrSubstitutor(map, "<", ">");
+ assertEquals("Hi < commons", sub.replace("Hi $< <name>"));
+ }
+
+ /**
+ * Tests constructor.
+ */
+ @Test
+ public void testConstructorMapFull() {
+ final Map<String, String> map = new HashMap<>();
+ map.put("name", "commons");
+ StrSubstitutor sub = new StrSubstitutor(map, "<", ">", '!');
+ assertEquals("Hi < commons", sub.replace("Hi !< <name>"));
+ sub = new StrSubstitutor(map, "<", ">", '!', "||");
+ assertEquals("Hi < commons", sub.replace("Hi !< <name2||commons>"));
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Tests get set.
+ */
+ @Test
+ public void testGetSetEscape() {
+ final StrSubstitutor sub = new StrSubstitutor();
+ assertEquals('$', sub.getEscapeChar());
+ sub.setEscapeChar('<');
+ assertEquals('<', sub.getEscapeChar());
+ }
+
+ /**
+ * Tests get set.
+ */
+ @Test
+ public void testGetSetPrefix() {
+ final StrSubstitutor sub = new StrSubstitutor();
+ assertTrue(sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher);
+ sub.setVariablePrefix('<');
+ assertTrue(sub.getVariablePrefixMatcher() instanceof StrMatcher.CharMatcher);
+
+ sub.setVariablePrefix("<<");
+ assertTrue(sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher);
+ try {
+ sub.setVariablePrefix((String) null);
+ fail();
+ } catch (final IllegalArgumentException ex) {
+ // expected
+ }
+ assertTrue(sub.getVariablePrefixMatcher() instanceof StrMatcher.StringMatcher);
+
+ final StrMatcher matcher = StrMatcher.commaMatcher();
+ sub.setVariablePrefixMatcher(matcher);
+ assertSame(matcher, sub.getVariablePrefixMatcher());
+ try {
+ sub.setVariablePrefixMatcher((StrMatcher) null);
+ fail();
+ } catch (final IllegalArgumentException ex) {
+ // expected
+ }
+ assertSame(matcher, sub.getVariablePrefixMatcher());
+ }
+
+ /**
+ * Tests get set.
+ */
+ @Test
+ public void testGetSetSuffix() {
+ final StrSubstitutor sub = new StrSubstitutor();
+ assertTrue(sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher);
+ sub.setVariableSuffix('<');
+ assertTrue(sub.getVariableSuffixMatcher() instanceof StrMatcher.CharMatcher);
+
+ sub.setVariableSuffix("<<");
+ assertTrue(sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher);
+ try {
+ sub.setVariableSuffix((String) null);
+ fail();
+ } catch (final IllegalArgumentException ex) {
+ // expected
+ }
+ assertTrue(sub.getVariableSuffixMatcher() instanceof StrMatcher.StringMatcher);
+
+ final StrMatcher matcher = StrMatcher.commaMatcher();
+ sub.setVariableSuffixMatcher(matcher);
+ assertSame(matcher, sub.getVariableSuffixMatcher());
+ try {
+ sub.setVariableSuffixMatcher((StrMatcher) null);
+ fail();
+ } catch (final IllegalArgumentException ex) {
+ // expected
+ }
+ 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.
+ */
+ @Test
+ public void testStaticReplace() {
+ final Map<String, String> map = new HashMap<>();
+ map.put("name", "commons");
+ assertEquals("Hi commons!", StrSubstitutor.replace("Hi ${name}!", map));
+ }
+
+ /**
+ * Tests static.
+ */
+ @Test
+ public void testStaticReplacePrefixSuffix() {
+ final Map<String, String> map = new HashMap<>();
+ map.put("name", "commons");
+ assertEquals("Hi commons!", StrSubstitutor.replace("Hi <name>!", map, "<", ">"));
+ }
+
+ /**
+ * Tests interpolation with system properties.
+ */
+ @Test
+ public void testStaticReplaceSystemProperties() {
+ final StrBuilder buf = new StrBuilder();
+ buf.append("Hi ").append(System.getProperty("user.name"));
+ buf.append(", you are working with ");
+ buf.append(System.getProperty("os.name"));
+ buf.append(", your home directory is ");
+ buf.append(System.getProperty("user.home")).append('.');
+ assertEquals(buf.toString(), StrSubstitutor.replaceSystemProperties("Hi ${user.name}, you are "
+ + "working with ${os.name}, your home "
+ + "directory is ${user.home}."));
+ }
+
+ /**
+ * Test for LANG-1055: StrSubstitutor.replaceSystemProperties does not work consistently
+ */
+ @Test
+ public void testLANG1055() {
+ System.setProperty("test_key", "test_value");
+
+ final String expected = StrSubstitutor.replace("test_key=${test_key}", System.getProperties());
+ final String actual = StrSubstitutor.replaceSystemProperties("test_key=${test_key}");
+ assertEquals(expected, actual);
+ }
+
+ /**
+ * Test the replace of a properties object
+ */
+ @Test
+ public void testSubstituteDefaultProperties() {
+ final String org = "${doesnotwork}";
+ System.setProperty("doesnotwork", "It works!");
+
+ // create a new Properties object with the System.getProperties as default
+ final Properties props = new Properties(System.getProperties());
+
+ assertEquals("It works!", StrSubstitutor.replace(org, props));
+ }
+
+ @Test
+ public void testSamePrefixAndSuffix() {
+ final Map<String, String> map = new HashMap<>();
+ map.put("greeting", "Hello");
+ map.put(" there ", "XXX");
+ map.put("name", "commons");
+ assertEquals("Hi commons!", StrSubstitutor.replace("Hi @name@!", map, "@", "@"));
+ assertEquals("Hello there commons!", StrSubstitutor.replace("@greeting@ there @name@!", map, "@", "@"));
+ }
+
+ @Test
+ public void testSubstitutePreserveEscape() {
+ final String org = "${not-escaped} $${escaped}";
+ final Map<String, String> map = new HashMap<>();
+ map.put("not-escaped", "value");
+
+ final StrSubstitutor sub = new StrSubstitutor(map, "${", "}", '$');
+ assertFalse(sub.isPreserveEscapes());
+ assertEquals("value ${escaped}", sub.replace(org));
+
+ sub.setPreserveEscapes(true);
+ assertTrue(sub.isPreserveEscapes());
+ assertEquals("value $${escaped}", sub.replace(org));
+ }
+
+ private void doTestReplace(final String expectedResult, final String replaceTemplate, final boolean substring) {
+ final StrSubstitutor sub = new StrSubstitutor(values);
+ doTestReplace(sub, expectedResult, replaceTemplate, substring);
+ }
+
+ //-----------------------------------------------------------------------
+ private void doTestReplace(final StrSubstitutor sub, final String expectedResult, final String replaceTemplate,
+ final boolean substring) {
+ final String expectedShortResult = expectedResult.substring(1, expectedResult.length() - 1);
+
+ // replace using String
+ assertEquals(expectedResult, sub.replace(replaceTemplate));
+ if (substring) {
+ assertEquals(expectedShortResult, sub.replace(replaceTemplate, 1, replaceTemplate.length() - 2));
+ }
+
+ // replace using char[]
+ final 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 StringBuilder
+ StringBuilder builder = new StringBuilder(replaceTemplate);
+ assertEquals(expectedResult, sub.replace(builder));
+ if (substring) {
+ assertEquals(expectedShortResult, sub.replace(builder, 1, builder.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
+ final MutableObject<String> obj = new MutableObject<>(replaceTemplate); // toString returns template
+ assertEquals(expectedResult, sub.replace(obj));
+
+ // replace in StringBuffer
+ buf = new StringBuffer(replaceTemplate);
+ assertTrue(sub.replaceIn(buf));
+ assertEquals(expectedResult, buf.toString());
+ if (substring) {
+ buf = new StringBuffer(replaceTemplate);
+ assertTrue(sub.replaceIn(buf, 1, buf.length() - 2));
+ assertEquals(expectedResult, buf.toString()); // expect full result as remainder is untouched
+ }
+
+ // replace in StringBuilder
+ builder = new StringBuilder(replaceTemplate);
+ assertTrue(sub.replaceIn(builder));
+ assertEquals(expectedResult, builder.toString());
+ if (substring) {
+ builder = new StringBuilder(replaceTemplate);
+ assertTrue(sub.replaceIn(builder, 1, builder.length() - 2));
+ assertEquals(expectedResult, builder.toString()); // expect full result as remainder is untouched
+ }
+
+ // replace in StrBuilder
+ bld = new StrBuilder(replaceTemplate);
+ assertTrue(sub.replaceIn(bld));
+ assertEquals(expectedResult, bld.toString());
+ if (substring) {
+ bld = new StrBuilder(replaceTemplate);
+ assertTrue(sub.replaceIn(bld, 1, bld.length() - 2));
+ assertEquals(expectedResult, bld.toString()); // expect full result as remainder is untouched
+ }
+ }
+
+ private void doTestNoReplace(final String replaceTemplate) {
+ final StrSubstitutor sub = new StrSubstitutor(values);
+
+ if (replaceTemplate == null) {
+ assertNull(sub.replace((String) null));
+ assertNull(sub.replace((String) null, 0, 100));
+ assertNull(sub.replace((char[]) null));
+ assertNull(sub.replace((char[]) null, 0, 100));
+ assertNull(sub.replace((StringBuffer) null));
+ assertNull(sub.replace((StringBuffer) null, 0, 100));
+ assertNull(sub.replace((StrBuilder) null));
+ assertNull(sub.replace((StrBuilder) null, 0, 100));
+ assertNull(sub.replace((Object) null));
+ assertFalse(sub.replaceIn((StringBuffer) null));
+ assertFalse(sub.replaceIn((StringBuffer) null, 0, 100));
+ assertFalse(sub.replaceIn((StrBuilder) null));
+ assertFalse(sub.replaceIn((StrBuilder) null, 0, 100));
+ } else {
+ assertEquals(replaceTemplate, sub.replace(replaceTemplate));
+ final StrBuilder bld = new StrBuilder(replaceTemplate);
+ assertFalse(sub.replaceIn(bld));
+ assertEquals(replaceTemplate, bld.toString());
+ }
+ }
+
+ @Test
+ public void testReplaceInTakingTwoAndThreeIntsReturningFalse() {
+ final Map<String, Object> hashMap = new HashMap<>();
+ final StrLookup.MapStrLookup<Object> strLookupMapStrLookup = new StrLookup.MapStrLookup<>(hashMap);
+ final StrMatcher strMatcher = StrMatcher.tabMatcher();
+ final StrSubstitutor strSubstitutor =
+ new StrSubstitutor(strLookupMapStrLookup, strMatcher, strMatcher, 'b', strMatcher);
+
+ assertFalse(strSubstitutor.replaceIn((StringBuilder) null, 1315, (-1369)));
+ assertEquals('b', strSubstitutor.getEscapeChar());
+ assertFalse(strSubstitutor.isPreserveEscapes());
+ }
+
+ @Test
+ public void testReplaceInTakingStringBuilderWithNonNull() {
+ final StrLookup<String> strLookup = StrLookup.systemPropertiesLookup();
+ final StrSubstitutor strSubstitutor = new StrSubstitutor(strLookup, "b<H", "b<H", '\'');
+ final StringBuilder stringBuilder = new StringBuilder((CharSequence) "b<H");
+
+ assertEquals('\'', strSubstitutor.getEscapeChar());
+ assertFalse(strSubstitutor.replaceIn(stringBuilder));
+ }
+
+ @Test
+ public void testReplaceInTakingStringBufferWithNonNull() {
+ final StrSubstitutor strSubstitutor =
+ new StrSubstitutor(new HashMap<String, String>(), "WV@i#y?N*[", "WV@i#y?N*[", '*');
+
+ assertFalse(strSubstitutor.isPreserveEscapes());
+ assertFalse(strSubstitutor.replaceIn(new StringBuffer("WV@i#y?N*[")));
+ assertEquals('*', strSubstitutor.getEscapeChar());
+ }
+
+ @Test
+ public void testCreatesStrSubstitutorTakingStrLookupAndCallsReplaceTakingTwoAndThreeInts() {
+ final Map<String, CharacterPredicates> map = new HashMap<>();
+ final StrLookup.MapStrLookup<CharacterPredicates> strLookupMapStrLookup = new StrLookup.MapStrLookup<>(map);
+ final StrSubstitutor strSubstitutor = new StrSubstitutor(strLookupMapStrLookup);
+
+ assertNull(strSubstitutor.replace((CharSequence) null, 0, 0));
+ assertEquals('$', strSubstitutor.getEscapeChar());
+ }
+
+ @Test
+ public void testReplaceTakingCharSequenceReturningNull() {
+ final StrSubstitutor strSubstitutor = new StrSubstitutor((StrLookup<?>) null);
+
+ assertNull(strSubstitutor.replace((CharSequence) null));
+ assertFalse(strSubstitutor.isPreserveEscapes());
+ assertEquals('$', strSubstitutor.getEscapeChar());
+ }
+
+ @Test(expected = NullPointerException.class)
+ public void testReplaceTakingThreeArgumentsThrowsNullPointerException() {
+ StrSubstitutor.replace(null, (Properties) null);
+ }
+
+ @Test
+ public void testReplaceInTakingStringBuilderWithNull() {
+ final Map<String, Object> map = new HashMap<>();
+ final StrSubstitutor strSubstitutor = new StrSubstitutor(map, "", "", 'T', "K+<'f");
+
+ assertFalse(strSubstitutor.replaceIn((StringBuilder) null));
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/commons-text/blob/4b67dd51/src/test/java/org/apache/commons/text/StrSubstitutorWithInterpolatorStringLookupTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/org/apache/commons/text/StrSubstitutorWithInterpolatorStringLookupTest.java b/src/test/java/org/apache/commons/text/StrSubstitutorWithInterpolatorStringLookupTest.java
deleted file mode 100644
index d50d3b7..0000000
--- a/src/test/java/org/apache/commons/text/StrSubstitutorWithInterpolatorStringLookupTest.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * 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.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.apache.commons.text;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.commons.text.lookup.StringLookupFactory;
-import org.junit.Assert;
-import org.junit.Test;
-
-public class StrSubstitutorWithInterpolatorStringLookupTest {
-
- @Test
- public void testMapAndSystemProperty() {
- final String key = "key";
- final String value = "value";
- final Map<String, String> map = new HashMap<>();
- map.put(key, value);
- final StrSubstitutor strSubst = new StrSubstitutor(StringLookupFactory.INSTANCE.interpolatorStringLookup(map));
- final String spKey = "user.name";
- Assert.assertEquals(System.getProperty(spKey), strSubst.replace("${sys:" + spKey + "}"));
- Assert.assertEquals(value, strSubst.replace("${" + key + "}"));
- }
-
- @Test
- public void testSystemProperty() {
- final StrSubstitutor strSubst = new StrSubstitutor(StringLookupFactory.INSTANCE.interpolatorStringLookup());
- final String spKey = "user.name";
- Assert.assertEquals(System.getProperty(spKey), strSubst.replace("${sys:" + spKey + "}"));
- }
-}
[4/5] [text] [TEXT-114] Add a replacement for StrSubstitutor based on
interfaces: StringSubstitutor.
Posted by gg...@apache.org.
http://git-wip-us.apache.org/repos/asf/commons-text/blob/4b67dd51/src/main/java/org/apache/commons/text/StrSubstitutor.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/commons/text/StrSubstitutor.java b/src/main/java/org/apache/commons/text/StrSubstitutor.java
index 34fa61f..44adbd9 100644
--- a/src/main/java/org/apache/commons/text/StrSubstitutor.java
+++ b/src/main/java/org/apache/commons/text/StrSubstitutor.java
@@ -1,1388 +1,1275 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * 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.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.commons.text;
-
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-
-import org.apache.commons.lang3.Validate;
-import org.apache.commons.text.lookup.StringLookup;
-import org.apache.commons.text.lookup.StringLookupFactory;
-
-/**
- * Substitutes variables within a string by values.
- * <p>
- * This class takes a piece of text and substitutes all the variables within it.
- * The default definition of a variable is <code>${variableName}</code>.
- * The prefix and suffix can be changed via constructors and set methods.
- * <p>
- * Variable values are typically resolved from a map, but could also be resolved
- * from system properties, or by supplying a custom variable resolver.
- * <p>
- * The simplest example is to use this class to replace Java System properties. For example:
- * <pre>
- * StrSubstitutor.replaceSystemProperties(
- * "You are running with java.version = ${java.version} and os.name = ${os.name}.");
- * </pre>
- * <p>
- * Typical usage of this class follows the following pattern: First an instance is created
- * and initialized with the map that contains the values for the available variables.
- * If a prefix and/or suffix for variables should be used other than the default ones,
- * the appropriate settings can be performed. After that the <code>replace()</code>
- * method can be called passing in the source text for interpolation. In the returned
- * text all variable references (as long as their values are known) will be resolved.
- * The following example demonstrates this:
- * <pre>
- * Map valuesMap = HashMap();
- * valuesMap.put("animal", "quick brown fox");
- * valuesMap.put("target", "lazy dog");
- * String templateString = "The ${animal} jumped over the ${target}.";
- * StrSubstitutor sub = new StrSubstitutor(valuesMap);
- * String resolvedString = sub.replace(templateString);
- * </pre>
- * yielding:
- * <pre>
- * 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 variable 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
- * performed, creating and reusing an instance of this class will be more efficient.
- * <p>
- * Variable replacement works in a recursive way. Thus, if a variable value contains
- * a variable then that variable will also be replaced. Cyclic replacements are
- * detected and will cause an exception to be thrown.
- * <p>
- * Sometimes the interpolation's result must contain a variable prefix. As an example
- * take the following source text:
- * <pre>
- * The variable ${${name}} must be used.
- * </pre>
- * Here only the variable's name referred to in the text should be replaced resulting
- * in the text (assuming that the value of the <code>name</code> variable is <code>x</code>):
- * <pre>
- * The variable ${x} must be used.
- * </pre>
- * To achieve this effect there are two possibilities: Either set a different prefix
- * and suffix for variables which do not conflict with the result text you want to
- * produce. The other possibility is to use the escape character, by default '$'.
- * If this character is placed before a variable reference, this reference is ignored
- * and won't be replaced. For example:
- * <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>.
- * <p>This class is <b>not</b> thread safe.</p>
- *
- * @since 1.0
- */
-public class StrSubstitutor {
-
- /**
- * Constant for the default escape character.
- */
- public static final char DEFAULT_ESCAPE = '$';
-
- /**
- * Constant for the default variable prefix.
- */
- public static final StrMatcher DEFAULT_PREFIX = StrMatcher.stringMatcher("${");
-
- /**
- * 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.
- */
- private char escapeChar;
-
- /**
- * Stores the variable prefix.
- */
- private StrMatcher prefixMatcher;
-
- /**
- * Stores the variable suffix.
- */
- private StrMatcher suffixMatcher;
-
- /**
- * Stores the default variable value delimiter.
- */
- private StrMatcher valueDelimiterMatcher;
-
- /**
- * Variable resolution is delegated to an implementor of {@link StringLookup}.
- */
- private StringLookup variableResolver;
-
- /**
- * The flag whether substitution in variable names is enabled.
- */
- private boolean enableSubstitutionInVariables;
-
- /**
- * Whether escapes should be preserved. Default is false;
- */
- private boolean preserveEscapes = false;
-
- /**
- * The flag whether substitution in variable values is disabled.
- */
- private boolean disableSubstitutionInValues;
-
- //-----------------------------------------------------------------------
- /**
- * Replaces all the occurrences of variables in the given source object with
- * their matching values from the map.
- *
- * @param <V> the type of the values in the map
- * @param source the source text containing the variables to substitute, null returns null
- * @param valueMap the map with the values, may be null
- * @return the result of the replace operation
- */
- public static <V> String replace(final Object source, final Map<String, V> valueMap) {
- return new StrSubstitutor(valueMap).replace(source);
- }
-
- /**
- * Replaces all the occurrences of variables in the given source object with
- * their matching values from the map. This method allows to specify a
- * custom variable prefix and suffix
- *
- * @param <V> the type of the values in the map
- * @param source the source text containing the variables to substitute, null returns null
- * @param valueMap the map with the values, may be null
- * @param prefix the prefix of variables, not null
- * @param suffix the suffix of variables, not null
- * @return the result of the replace operation
- * @throws IllegalArgumentException if the prefix or suffix is null
- */
- public static <V> String replace(final Object source,
- final Map<String, V> valueMap,
- final String prefix,
- final String suffix) {
- return new StrSubstitutor(valueMap, prefix, suffix).replace(source);
- }
-
- /**
- * Replaces all the occurrences of variables in the given source object with their matching
- * values from the properties.
- *
- * @param source the source text containing the variables to substitute, null returns null
- * @param valueProperties the properties with values, may be null
- * @return the result of the replace operation
- */
- public static String replace(final Object source, final Properties valueProperties) {
- if (valueProperties == null) {
- return source.toString();
- }
- final Map<String, String> valueMap = new HashMap<>();
- final Enumeration<?> propNames = valueProperties.propertyNames();
- while (propNames.hasMoreElements()) {
- final String propName = (String) propNames.nextElement();
- final String propValue = valueProperties.getProperty(propName);
- valueMap.put(propName, propValue);
- }
- return StrSubstitutor.replace(source, valueMap);
- }
-
- /**
- * Replaces all the occurrences of variables in the given source object with
- * their matching values from the system properties.
- *
- * @param source the source text containing the variables to substitute, null returns null
- * @return the result of the replace operation
- */
- public static String replaceSystemProperties(final Object source) {
- return new StrSubstitutor(StringLookupFactory.INSTANCE.systemPropertyStringLookup()).replace(source);
- }
-
- //-----------------------------------------------------------------------
- /**
- * Creates a new instance with defaults for variable prefix and suffix
- * and the escaping character.
- */
- public StrSubstitutor() {
- this((StringLookup) null, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
- }
-
- /**
- * Creates a new instance and initializes it. Uses defaults for variable
- * prefix and suffix and the escaping character.
- *
- * @param <V> the type of the values in the map
- * @param valueMap the map with the variables' values, may be null
- */
- public <V> StrSubstitutor(final Map<String, V> valueMap) {
- this(StringLookupFactory.INSTANCE.mapStringLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
- }
-
- /**
- * Creates a new instance and initializes it. Uses a default escaping character.
- *
- * @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
- * @throws IllegalArgumentException if the prefix or suffix is null
- */
- public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix) {
- this(StringLookupFactory.INSTANCE.mapStringLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE);
- }
-
- /**
- * 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
- * @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) {
- this(StringLookupFactory.INSTANCE.mapStringLookup(valueMap), prefix, suffix, escape);
- }
-
- /**
- * 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(StringLookupFactory.INSTANCE.mapStringLookup(valueMap), prefix, suffix, escape, valueDelimiter);
- }
-
- /**
- * Creates a new instance and initializes it.
- *
- * @param variableResolver the variable resolver, may be null
- * @deprecated Use the StringLookup version of this constructor.
- */
- @Deprecated
- public StrSubstitutor(final StrLookup<?> variableResolver) {
- this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
- }
-
- /**
- * 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
- * @throws IllegalArgumentException if the prefix or suffix is null
- * @deprecated Use the StringLookup version of this constructor.
- */
- @Deprecated
- public StrSubstitutor(final StrLookup<?> variableResolver, final String prefix, final String suffix,
- final char escape) {
- this.setVariableResolver(variableResolver);
- 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
- * @deprecated Use the StringLookup version of this constructor.
- */
- @Deprecated
- 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);
- }
-
- /**
- * 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
- * @throws IllegalArgumentException if the prefix or suffix is null
- * @deprecated Use the StringLookup version of this constructor.
- */
- @Deprecated
- 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
- * @deprecated Use the StringLookup version of this constructor.
- */
- @Deprecated
- 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);
- }
-
- /**
- * Creates a new instance and initializes it.
- *
- * @param variableResolver the variable resolver, may be null
- */
- public StrSubstitutor(final StringLookup variableResolver) {
- this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
- }
-
- /**
- * 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
- * @throws IllegalArgumentException if the prefix or suffix is null
- */
- public StrSubstitutor(final StringLookup variableResolver, final String prefix, final String suffix,
- final char escape) {
- this.setVariableResolver(variableResolver);
- 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 StringLookup 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);
- }
-
- /**
- * 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
- * @throws IllegalArgumentException if the prefix or suffix is null
- */
- public StrSubstitutor(
- final StringLookup 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 StringLookup 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);
- }
-
- //-----------------------------------------------------------------------
- /**
- * Replaces all the occurrences of variables with their matching values
- * from the resolver using the given source string as a template.
- *
- * @param source the string to replace in, null returns null
- * @return the result of the replace operation
- */
- public String replace(final String source) {
- if (source == null) {
- return null;
- }
- final StrBuilder buf = new StrBuilder(source);
- if (!substitute(buf, 0, source.length())) {
- return source;
- }
- return buf.toString();
- }
-
- /**
- * Replaces all the occurrences of variables with their matching values
- * from the resolver using the given source string as a template.
- * <p>
- * Only the specified portion of the string will be processed.
- * The rest of the string is not processed, and is not returned.
- *
- * @param source the string to replace in, null returns null
- * @param offset the start offset within the array, must be valid
- * @param length the length within the array to be processed, must be valid
- * @return the result of the replace operation
- */
- public String replace(final String source, final int offset, final int length) {
- if (source == null) {
- return null;
- }
- final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
- if (!substitute(buf, 0, length)) {
- return source.substring(offset, offset + length);
- }
- return buf.toString();
- }
-
- //-----------------------------------------------------------------------
- /**
- * Replaces all the occurrences of variables with their matching values
- * from the resolver using the given source array as a template.
- * The array is not altered by this method.
- *
- * @param source the character array to replace in, not altered, null returns null
- * @return the result of the replace operation
- */
- public String replace(final char[] source) {
- if (source == null) {
- return null;
- }
- final StrBuilder buf = new StrBuilder(source.length).append(source);
- substitute(buf, 0, source.length);
- return buf.toString();
- }
-
- /**
- * Replaces all the occurrences of variables with their matching values
- * from the resolver using the given source array as a template.
- * The array is not altered by this method.
- * <p>
- * Only the specified portion of the array will be processed.
- * The rest of the array is not processed, and is not returned.
- *
- * @param source the character array to replace in, not altered, null returns null
- * @param offset the start offset within the array, must be valid
- * @param length the length within the array to be processed, must be valid
- * @return the result of the replace operation
- */
- public String replace(final char[] source, final int offset, final int length) {
- if (source == null) {
- return null;
- }
- final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
- substitute(buf, 0, length);
- return buf.toString();
- }
-
- //-----------------------------------------------------------------------
- /**
- * Replaces all the occurrences of variables with their matching values
- * from the resolver using the given source buffer as a template.
- * The buffer is not altered by this method.
- *
- * @param source the buffer to use as a template, not changed, null returns null
- * @return the result of the replace operation
- */
- public String replace(final StringBuffer source) {
- if (source == null) {
- return null;
- }
- final StrBuilder buf = new StrBuilder(source.length()).append(source);
- substitute(buf, 0, buf.length());
- return buf.toString();
- }
-
- /**
- * Replaces all the occurrences of variables with their matching values
- * from the resolver using the given source buffer as a template.
- * The buffer is not altered by this method.
- * <p>
- * Only the specified portion of the buffer will be processed.
- * The rest of the buffer is not processed, and is not returned.
- *
- * @param source the buffer to use as a template, not changed, null returns null
- * @param offset the start offset within the array, must be valid
- * @param length the length within the array to be processed, must be valid
- * @return the result of the replace operation
- */
- public String replace(final StringBuffer source, final int offset, final int length) {
- if (source == null) {
- return null;
- }
- final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
- 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.
- * The source is not altered by this method.
- *
- * @param source the buffer to use as a template, not changed, null returns null
- * @return the result of the replace operation
- */
- public String replace(final CharSequence source) {
- if (source == null) {
- return null;
- }
- return replace(source, 0, source.length());
- }
-
- /**
- * Replaces all the occurrences of variables with their matching values
- * from the resolver using the given source as a template.
- * The source is not altered by this method.
- * <p>
- * Only the specified portion of the buffer will be processed.
- * The rest of the buffer is not processed, and is not returned.
- *
- * @param source the buffer to use as a template, not changed, null returns null
- * @param offset the start offset within the array, must be valid
- * @param length the length within the array to be processed, must be valid
- * @return the result of the replace operation
- */
- public String replace(final CharSequence source, final int offset, final int length) {
- if (source == null) {
- return null;
- }
- final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
- substitute(buf, 0, length);
- return buf.toString();
- }
-
- //-----------------------------------------------------------------------
- /**
- * Replaces all the occurrences of variables with their matching values
- * from the resolver using the given source builder as a template.
- * The builder is not altered by this method.
- *
- * @param source the builder to use as a template, not changed, null returns null
- * @return the result of the replace operation
- */
- public String replace(final StrBuilder source) {
- if (source == null) {
- return null;
- }
- final StrBuilder buf = new StrBuilder(source.length()).append(source);
- substitute(buf, 0, buf.length());
- return buf.toString();
- }
-
- /**
- * Replaces all the occurrences of variables with their matching values
- * from the resolver using the given source builder as a template.
- * The builder is not altered by this method.
- * <p>
- * Only the specified portion of the builder will be processed.
- * The rest of the builder is not processed, and is not returned.
- *
- * @param source the builder to use as a template, not changed, null returns null
- * @param offset the start offset within the array, must be valid
- * @param length the length within the array to be processed, must be valid
- * @return the result of the replace operation
- */
- public String replace(final StrBuilder source, final int offset, final int length) {
- if (source == null) {
- return null;
- }
- final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
- substitute(buf, 0, length);
- return buf.toString();
- }
-
- //-----------------------------------------------------------------------
- /**
- * Replaces all the occurrences of variables in the given source object with
- * their matching values from the resolver. The input source object is
- * converted to a string using <code>toString</code> and is not altered.
- *
- * @param source the source to replace in, null returns null
- * @return the result of the replace operation
- */
- public String replace(final Object source) {
- if (source == null) {
- return null;
- }
- final StrBuilder buf = new StrBuilder().append(source);
- substitute(buf, 0, buf.length());
- return buf.toString();
- }
-
- //-----------------------------------------------------------------------
- /**
- * Replaces all the occurrences of variables within the given source buffer
- * with their matching values from the resolver.
- * The buffer is updated with the result.
- *
- * @param source the buffer to replace in, updated, null returns zero
- * @return true if altered
- */
- public boolean replaceIn(final StringBuffer source) {
- if (source == null) {
- return false;
- }
- return replaceIn(source, 0, source.length());
- }
-
- /**
- * Replaces all the occurrences of variables within the given source buffer
- * with their matching values from the resolver.
- * The buffer is updated with the result.
- * <p>
- * Only the specified portion of the buffer will be processed.
- * The rest of the buffer is not processed, but it is not deleted.
- *
- * @param source the buffer to replace in, updated, null returns zero
- * @param offset the start offset within the array, must be valid
- * @param length the length within the buffer to be processed, must be valid
- * @return true if altered
- */
- public boolean replaceIn(final StringBuffer source, final int offset, final int length) {
- if (source == null) {
- return false;
- }
- final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
- if (!substitute(buf, 0, length)) {
- return false;
- }
- source.replace(offset, offset + length, buf.toString());
- return true;
- }
-
- //-----------------------------------------------------------------------
- /**
- * Replaces all the occurrences of variables within the given source buffer
- * with their matching values from the resolver.
- * The buffer is updated with the result.
- *
- * @param source the buffer to replace in, updated, null returns zero
- * @return true if altered
- */
- public boolean replaceIn(final StringBuilder source) {
- if (source == null) {
- return false;
- }
- return replaceIn(source, 0, source.length());
- }
-
- /**
- * Replaces all the occurrences of variables within the given source builder
- * with their matching values from the resolver.
- * The builder is updated with the result.
- * <p>
- * Only the specified portion of the buffer will be processed.
- * The rest of the buffer is not processed, but it is not deleted.
- *
- * @param source the buffer to replace in, updated, null returns zero
- * @param offset the start offset within the array, must be valid
- * @param length the length within the buffer to be processed, must be valid
- * @return true if altered
- */
- public boolean replaceIn(final StringBuilder source, final int offset, final int length) {
- if (source == null) {
- return false;
- }
- final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
- if (!substitute(buf, 0, length)) {
- return false;
- }
- source.replace(offset, offset + length, buf.toString());
- return true;
- }
-
- //-----------------------------------------------------------------------
- /**
- * Replaces all the occurrences of variables within the given source
- * builder with their matching values from the resolver.
- *
- * @param source the builder to replace in, updated, null returns zero
- * @return true if altered
- */
- public boolean replaceIn(final StrBuilder source) {
- if (source == null) {
- return false;
- }
- return substitute(source, 0, source.length());
- }
-
- /**
- * Replaces all the occurrences of variables within the given source
- * builder with their matching values from the resolver.
- * <p>
- * Only the specified portion of the builder will be processed.
- * The rest of the builder is not processed, but it is not deleted.
- *
- * @param source the builder to replace in, null returns zero
- * @param offset the start offset within the array, must be valid
- * @param length the length within the builder to be processed, must be valid
- * @return true if altered
- */
- public boolean replaceIn(final StrBuilder source, final int offset, final int length) {
- if (source == null) {
- return false;
- }
- return substitute(source, offset, length);
- }
-
- //-----------------------------------------------------------------------
- /**
- * Internal method that substitutes the variables.
- * <p>
- * Most users of this class do not need to call this method. This method will
- * be called automatically by another (public) method.
- * <p>
- * Writers of subclasses can override this method if they need access to
- * the substitution process at the start or end.
- *
- * @param buf the string builder to substitute into, not null
- * @param offset the start offset within the builder, must be valid
- * @param length the length within the builder to be processed, must be valid
- * @return true if altered
- */
- protected boolean substitute(final StrBuilder buf, final int offset, final int length) {
- return substitute(buf, offset, length, null) > 0;
- }
-
- /**
- * Recursive handler for multiple levels of interpolation. This is the main
- * interpolation method, which resolves the values of all variable references
- * contained in the passed in text.
- *
- * @param buf the string builder to substitute into, not null
- * @param offset the start offset within the builder, must be valid
- * @param length the length within the builder to be processed, must be valid
- * @param priorVariables the stack keeping track of the replaced variables, may be null
- * @return the length change that occurs, unless priorVariables is null when the int
- * represents a boolean flag as to whether any change occurred.
- */
- private int substitute(final StrBuilder buf, final int offset, final int length, List<String> priorVariables) {
- final StrMatcher pfxMatcher = getVariablePrefixMatcher();
- final StrMatcher suffMatcher = getVariableSuffixMatcher();
- final char escape = getEscapeChar();
- final StrMatcher valueDelimMatcher = getValueDelimiterMatcher();
- final boolean substitutionInVariablesEnabled = isEnableSubstitutionInVariables();
- final boolean substitutionInValuesDisabled = isDisableSubstitutionInValues();
-
- final boolean top = priorVariables == null;
- boolean altered = false;
- int lengthChange = 0;
- char[] chars = buf.buffer;
- int bufEnd = offset + length;
- int pos = offset;
- while (pos < bufEnd) {
- final int startMatchLen = pfxMatcher.isMatch(chars, pos, offset,
- bufEnd);
- if (startMatchLen == 0) {
- pos++;
- } else {
- // found variable start marker
- if (pos > offset && chars[pos - 1] == escape) {
- // escaped
- if (preserveEscapes) {
- pos++;
- continue;
- }
- buf.deleteCharAt(pos - 1);
- chars = buf.buffer; // in case buffer was altered
- lengthChange--;
- altered = true;
- bufEnd--;
- } else {
- // find suffix
- final int startPos = pos;
- pos += startMatchLen;
- int endMatchLen = 0;
- int nestedVarCount = 0;
- while (pos < bufEnd) {
- if (substitutionInVariablesEnabled
- && pfxMatcher.isMatch(chars,
- pos, offset, bufEnd) != 0) {
- // found a nested variable start
- endMatchLen = pfxMatcher.isMatch(chars,
- pos, offset, bufEnd);
- nestedVarCount++;
- pos += endMatchLen;
- continue;
- }
-
- endMatchLen = suffMatcher.isMatch(chars, pos, offset,
- bufEnd);
- if (endMatchLen == 0) {
- pos++;
- } else {
- // found variable end marker
- if (nestedVarCount == 0) {
- String varNameExpr = new String(chars, startPos
- + startMatchLen, pos - startPos
- - startMatchLen);
- if (substitutionInVariablesEnabled) {
- final StrBuilder bufName = new StrBuilder(varNameExpr);
- substitute(bufName, 0, bufName.length());
- varNameExpr = bufName.toString();
- }
- pos += endMatchLen;
- final int endPos = pos;
-
- String varName = varNameExpr;
- String varDefaultValue = null;
-
- if (valueDelimMatcher != 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
- && pfxMatcher.isMatch(varNameExprChars,
- i,
- i,
- varNameExprChars.length) != 0) {
- break;
- }
- if (valueDelimMatcher.isMatch(varNameExprChars, i) != 0) {
- valueDelimiterMatchLen = valueDelimMatcher.isMatch(varNameExprChars, i);
- varName = varNameExpr.substring(0, i);
- varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen);
- break;
- }
- }
- }
-
- // 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) {
- varValue = varDefaultValue;
- }
- if (varValue != null) {
- final int varLen = varValue.length();
- buf.replace(startPos, endPos, varValue);
- altered = true;
- int change = 0;
- if (!substitutionInValuesDisabled) { // recursive replace
- 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;
- }
- nestedVarCount--;
- pos += endMatchLen;
- }
- }
- }
- }
- }
- if (top) {
- return altered ? 1 : 0;
- }
- return lengthChange;
- }
-
- /**
- * Checks if the specified variable is already in the stack (list) of variables.
- *
- * @param varName the variable name to check
- * @param priorVariables the list of prior variables
- */
- private void checkCyclicSubstitution(final String varName, final List<String> priorVariables) {
- if (!priorVariables.contains(varName)) {
- return;
- }
- final StrBuilder buf = new StrBuilder(256);
- buf.append("Infinite loop in property interpolation of ");
- buf.append(priorVariables.remove(0));
- buf.append(": ");
- buf.appendWithSeparators(priorVariables, "->");
- throw new IllegalStateException(buf.toString());
- }
-
- /**
- * Internal method that resolves the value of a variable.
- * <p>
- * Most users of this class do not need to call this method. This method is
- * called automatically by the substitution process.
- * <p>
- * Writers of subclasses can override this method if they need to alter
- * how each substitution occurs. The method is passed the variable's name
- * and must return the corresponding value. This implementation uses the
- * {@link #getStringLookup()} with the variable's name as the key.
- *
- * @param variableName the name of the variable, not null
- * @param buf the buffer where the substitution is occurring, not null
- * @param startPos the start position of the variable including the prefix, valid
- * @param endPos the end position of the variable including the suffix, valid
- * @return the variable's value or <b>null</b> if the variable is unknown
- */
- protected String resolveVariable(final String variableName,
- final StrBuilder buf,
- final int startPos,
- final int endPos) {
- final StringLookup resolver = getStringLookup();
- if (resolver == null) {
- return null;
- }
- return resolver.lookup(variableName);
- }
-
- // Escape
- //-----------------------------------------------------------------------
- /**
- * Returns the escape character.
- *
- * @return the character used for escaping variable references
- */
- public char getEscapeChar() {
- return this.escapeChar;
- }
-
- /**
- * Sets the escape character.
- * If this character is placed before a variable reference in the source
- * text, this variable will be ignored.
- *
- * @param escapeCharacter the escape character (0 for disabling escaping)
- */
- public void setEscapeChar(final char escapeCharacter) {
- this.escapeChar = escapeCharacter;
- }
-
- // Prefix
- //-----------------------------------------------------------------------
- /**
- * Gets the variable prefix matcher currently in use.
- * <p>
- * The variable prefix is the character or characters that identify the
- * start of a variable. This prefix is expressed in terms of a matcher
- * allowing advanced prefix matches.
- *
- * @return the prefix matcher in use
- */
- public StrMatcher getVariablePrefixMatcher() {
- return prefixMatcher;
- }
-
- /**
- * Sets the variable prefix matcher currently in use.
- * <p>
- * The variable prefix is the character or characters that identify the
- * start of a variable. This prefix is expressed in terms of a matcher
- * allowing advanced prefix matches.
- *
- * @param prefixMatcher the prefix matcher to use, null ignored
- * @return this, to enable chaining
- * @throws IllegalArgumentException if the prefix matcher is null
- */
- public StrSubstitutor setVariablePrefixMatcher(final StrMatcher prefixMatcher) {
- Validate.isTrue(prefixMatcher != null, "Variable prefix matcher must not be null!");
- this.prefixMatcher = prefixMatcher;
- return this;
- }
-
- /**
- * Sets the variable prefix to use.
- * <p>
- * 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.
- *
- * @param prefix the prefix character to use
- * @return this, to enable chaining
- */
- public StrSubstitutor setVariablePrefix(final char prefix) {
- return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix));
- }
-
- /**
- * Sets the variable prefix to use.
- * <p>
- * The variable prefix is the character or characters that identify the
- * start of a variable. This method allows a string prefix to be easily set.
- *
- * @param prefix the prefix for variables, not null
- * @return this, to enable chaining
- * @throws IllegalArgumentException if the prefix is null
- */
- public StrSubstitutor setVariablePrefix(final String prefix) {
- Validate.isTrue(prefix != null, "Variable prefix must not be null!");
- return setVariablePrefixMatcher(StrMatcher.stringMatcher(prefix));
- }
-
- // Suffix
- //-----------------------------------------------------------------------
- /**
- * Gets the variable suffix matcher currently in use.
- * <p>
- * The variable suffix is the character or characters that identify the
- * end of a variable. This suffix is expressed in terms of a matcher
- * allowing advanced suffix matches.
- *
- * @return the suffix matcher in use
- */
- public StrMatcher getVariableSuffixMatcher() {
- return suffixMatcher;
- }
-
- /**
- * Sets the variable suffix matcher currently in use.
- * <p>
- * The variable suffix is the character or characters that identify the
- * end of a variable. This suffix is expressed in terms of a matcher
- * allowing advanced suffix matches.
- *
- * @param suffixMatcher the suffix matcher to use, null ignored
- * @return this, to enable chaining
- * @throws IllegalArgumentException if the suffix matcher is null
- */
- public StrSubstitutor setVariableSuffixMatcher(final StrMatcher suffixMatcher) {
- Validate.isTrue(suffixMatcher != null, "Variable suffix matcher must not be null!");
- this.suffixMatcher = suffixMatcher;
- return this;
- }
-
- /**
- * Sets the variable suffix to use.
- * <p>
- * The variable suffix is the character or characters that identify the
- * end of a variable. This method allows a single character suffix to
- * be easily set.
- *
- * @param suffix the suffix character to use
- * @return this, to enable chaining
- */
- public StrSubstitutor setVariableSuffix(final char suffix) {
- return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix));
- }
-
- /**
- * Sets the variable suffix to use.
- * <p>
- * 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
- * @return this, to enable chaining
- * @throws IllegalArgumentException if the suffix is null
- */
- public StrSubstitutor setVariableSuffix(final String suffix) {
- Validate.isTrue(suffix != null, "Variable suffix must not be null!");
- 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 character 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 character 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 character 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 character 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
- //-----------------------------------------------------------------------
- /**
- * Gets the StringLookup that is used to lookup variables.
- *
- * @return the StringLookup
- */
- public StringLookup getStringLookup() {
- return this.variableResolver;
- }
-
- /**
- * Gets the VariableResolver that is used to lookup variables.
- *
- * @return the VariableResolver
- * @deprecated Use {@link #getStringLookup()}.
- */
- @Deprecated
- public StrLookup<?> getVariableResolver() {
- return (StrLookup<?>) this.variableResolver;
- }
-
- /**
- * Sets the VariableResolver that is used to lookup variables.
- *
- * @param variableResolver the VariableResolver
- * @deprecated Use {@link #StrSubstitutor(StringLookup)}.
- */
- @Deprecated
- public void setVariableResolver(final StrLookup<?> variableResolver) {
- this.variableResolver = variableResolver;
- }
-
- /**
- * Sets the VariableResolver that is used to lookup variables.
- *
- * @param variableResolver the VariableResolver
- */
- public void setVariableResolver(final StringLookup 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
- */
- 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
- */
- public void setEnableSubstitutionInVariables(
- final boolean enableSubstitutionInVariables) {
- this.enableSubstitutionInVariables = enableSubstitutionInVariables;
- }
-
- /**
- * Returns a flag whether substitution is disabled in variable values.If set to
- * <b>true</b>, the values of variables can contain other variables will not be
- * processed and substituted original variable is evaluated, e.g.
- * <pre>
- * Map valuesMap = HashMap();
- * valuesMap.put("name", "Douglas ${surname}");
- * valuesMap.put("surname", "Crockford");
- * String templateString = "Hi ${name}";
- * StrSubstitutor sub = new StrSubstitutor(valuesMap);
- * String resolvedString = sub.replace(templateString);
- * </pre>
- * yielding:
- * <pre>
- * Hi Douglas ${surname}
- * </pre>
- *
- * @return the substitution in variable values flag
- *
- * @since 1.2
- */
- public boolean isDisableSubstitutionInValues() {
- return disableSubstitutionInValues;
- }
-
- /**
- * Sets a flag whether substitution is done in variable values (recursive).
- *
- * @param disableSubstitutionInValues true if substitution in variable value are disabled
- *
- * @since 1.2
- */
- public void setDisableSubstitutionInValues(boolean disableSubstitutionInValues) {
- this.disableSubstitutionInValues = disableSubstitutionInValues;
- }
-
- /**
- * Returns the flag controlling whether escapes are preserved during
- * substitution.
- *
- * @return the preserve escape flag
- */
- public boolean isPreserveEscapes() {
- return preserveEscapes;
- }
-
- /**
- * Sets a flag controlling whether escapes are preserved during
- * substitution. If set to <b>true</b>, the escape character is retained
- * during substitution (e.g. <code>$${this-is-escaped}</code> remains
- * <code>$${this-is-escaped}</code>). If set to <b>false</b>, the escape
- * character is removed during substitution (e.g.
- * <code>$${this-is-escaped}</code> becomes
- * <code>${this-is-escaped}</code>). The default value is <b>false</b>
- *
- * @param preserveEscapes true if escapes are to be preserved
- */
- public void setPreserveEscapes(final boolean preserveEscapes) {
- this.preserveEscapes = preserveEscapes;
- }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * 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.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.text;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+
+import org.apache.commons.lang3.Validate;
+
+/**
+ * Substitutes variables within a string by values.
+ * <p>
+ * This class takes a piece of text and substitutes all the variables within it.
+ * The default definition of a variable is <code>${variableName}</code>.
+ * The prefix and suffix can be changed via constructors and set methods.
+ * <p>
+ * Variable values are typically resolved from a map, but could also be resolved
+ * from system properties, or by supplying a custom variable resolver.
+ * <p>
+ * The simplest example is to use this class to replace Java System properties. For example:
+ * <pre>
+ * StrSubstitutor.replaceSystemProperties(
+ * "You are running with java.version = ${java.version} and os.name = ${os.name}.");
+ * </pre>
+ * <p>
+ * Typical usage of this class follows the following pattern: First an instance is created
+ * and initialized with the map that contains the values for the available variables.
+ * If a prefix and/or suffix for variables should be used other than the default ones,
+ * the appropriate settings can be performed. After that the <code>replace()</code>
+ * method can be called passing in the source text for interpolation. In the returned
+ * text all variable references (as long as their values are known) will be resolved.
+ * The following example demonstrates this:
+ * <pre>
+ * Map valuesMap = HashMap();
+ * valuesMap.put("animal", "quick brown fox");
+ * valuesMap.put("target", "lazy dog");
+ * String templateString = "The ${animal} jumped over the ${target}.";
+ * StrSubstitutor sub = new StrSubstitutor(valuesMap);
+ * String resolvedString = sub.replace(templateString);
+ * </pre>
+ * yielding:
+ * <pre>
+ * 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 variable 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
+ * performed, creating and reusing an instance of this class will be more efficient.
+ * <p>
+ * Variable replacement works in a recursive way. Thus, if a variable value contains
+ * a variable then that variable will also be replaced. Cyclic replacements are
+ * detected and will cause an exception to be thrown.
+ * <p>
+ * Sometimes the interpolation's result must contain a variable prefix. As an example
+ * take the following source text:
+ * <pre>
+ * The variable ${${name}} must be used.
+ * </pre>
+ * Here only the variable's name referred to in the text should be replaced resulting
+ * in the text (assuming that the value of the <code>name</code> variable is <code>x</code>):
+ * <pre>
+ * The variable ${x} must be used.
+ * </pre>
+ * To achieve this effect there are two possibilities: Either set a different prefix
+ * and suffix for variables which do not conflict with the result text you want to
+ * produce. The other possibility is to use the escape character, by default '$'.
+ * If this character is placed before a variable reference, this reference is ignored
+ * and won't be replaced. For example:
+ * <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>.
+ * <p>This class is <b>not</b> thread safe.</p>
+ *
+ * @since 1.0
+ * @deprecated Use {@link StringSubstitutor}. This class will be removed in 2.0.
+ */
+@Deprecated
+public class StrSubstitutor {
+
+ /**
+ * Constant for the default escape character.
+ */
+ public static final char DEFAULT_ESCAPE = '$';
+
+ /**
+ * Constant for the default variable prefix.
+ */
+ public static final StrMatcher DEFAULT_PREFIX = StrMatcher.stringMatcher("${");
+
+ /**
+ * 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.
+ */
+ private char escapeChar;
+
+ /**
+ * Stores the variable prefix.
+ */
+ private StrMatcher prefixMatcher;
+
+ /**
+ * Stores the variable suffix.
+ */
+ private StrMatcher suffixMatcher;
+
+ /**
+ * Stores the default variable value delimiter.
+ */
+ private StrMatcher valueDelimiterMatcher;
+
+ /**
+ * Variable resolution is delegated to an implementor of VariableResolver.
+ */
+ private StrLookup<?> variableResolver;
+
+ /**
+ * The flag whether substitution in variable names is enabled.
+ */
+ private boolean enableSubstitutionInVariables;
+
+ /**
+ * Whether escapes should be preserved. Default is false;
+ */
+ private boolean preserveEscapes = false;
+
+ /**
+ * The flag whether substitution in variable values is disabled.
+ */
+ private boolean disableSubstitutionInValues;
+
+ //-----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables in the given source object with
+ * their matching values from the map.
+ *
+ * @param <V> the type of the values in the map
+ * @param source the source text containing the variables to substitute, null returns null
+ * @param valueMap the map with the values, may be null
+ * @return the result of the replace operation
+ */
+ public static <V> String replace(final Object source, final Map<String, V> valueMap) {
+ return new StrSubstitutor(valueMap).replace(source);
+ }
+
+ /**
+ * Replaces all the occurrences of variables in the given source object with
+ * their matching values from the map. This method allows to specify a
+ * custom variable prefix and suffix
+ *
+ * @param <V> the type of the values in the map
+ * @param source the source text containing the variables to substitute, null returns null
+ * @param valueMap the map with the values, may be null
+ * @param prefix the prefix of variables, not null
+ * @param suffix the suffix of variables, not null
+ * @return the result of the replace operation
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ */
+ public static <V> String replace(final Object source,
+ final Map<String, V> valueMap,
+ final String prefix,
+ final String suffix) {
+ return new StrSubstitutor(valueMap, prefix, suffix).replace(source);
+ }
+
+ /**
+ * Replaces all the occurrences of variables in the given source object with their matching
+ * values from the properties.
+ *
+ * @param source the source text containing the variables to substitute, null returns null
+ * @param valueProperties the properties with values, may be null
+ * @return the result of the replace operation
+ */
+ public static String replace(final Object source, final Properties valueProperties) {
+ if (valueProperties == null) {
+ return source.toString();
+ }
+ final Map<String, String> valueMap = new HashMap<>();
+ final Enumeration<?> propNames = valueProperties.propertyNames();
+ while (propNames.hasMoreElements()) {
+ final String propName = (String) propNames.nextElement();
+ final String propValue = valueProperties.getProperty(propName);
+ valueMap.put(propName, propValue);
+ }
+ return StrSubstitutor.replace(source, valueMap);
+ }
+
+ /**
+ * Replaces all the occurrences of variables in the given source object with
+ * their matching values from the system properties.
+ *
+ * @param source the source text containing the variables to substitute, null returns null
+ * @return the result of the replace operation
+ */
+ public static String replaceSystemProperties(final Object source) {
+ return new StrSubstitutor(StrLookup.systemPropertiesLookup()).replace(source);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Creates a new instance with defaults for variable prefix and suffix
+ * and the escaping character.
+ */
+ public StrSubstitutor() {
+ this((StrLookup<?>) null, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
+ }
+
+ /**
+ * Creates a new instance and initializes it. Uses defaults for variable
+ * prefix and suffix and the escaping character.
+ *
+ * @param <V> the type of the values in the map
+ * @param valueMap the map with the variables' values, may be null
+ */
+ public <V> StrSubstitutor(final Map<String, V> valueMap) {
+ this(StrLookup.mapLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
+ }
+
+ /**
+ * Creates a new instance and initializes it. Uses a default escaping character.
+ *
+ * @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
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ */
+ public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix) {
+ this(StrLookup.mapLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE);
+ }
+
+ /**
+ * 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
+ * @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) {
+ this(StrLookup.mapLookup(valueMap), prefix, suffix, escape);
+ }
+
+ /**
+ * 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) {
+ this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
+ }
+
+ /**
+ * 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
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ */
+ public StrSubstitutor(final StrLookup<?> variableResolver, final String prefix, final String suffix,
+ final char escape) {
+ this.setVariableResolver(variableResolver);
+ 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);
+ }
+
+ /**
+ * 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
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ */
+ 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);
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables with their matching values
+ * from the resolver using the given source string as a template.
+ *
+ * @param source the string to replace in, null returns null
+ * @return the result of the replace operation
+ */
+ public String replace(final String source) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(source);
+ if (!substitute(buf, 0, source.length())) {
+ return source;
+ }
+ return buf.toString();
+ }
+
+ /**
+ * Replaces all the occurrences of variables with their matching values
+ * from the resolver using the given source string as a template.
+ * <p>
+ * Only the specified portion of the string will be processed.
+ * The rest of the string is not processed, and is not returned.
+ *
+ * @param source the string to replace in, null returns null
+ * @param offset the start offset within the array, must be valid
+ * @param length the length within the array to be processed, must be valid
+ * @return the result of the replace operation
+ */
+ public String replace(final String source, final int offset, final int length) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+ if (!substitute(buf, 0, length)) {
+ return source.substring(offset, offset + length);
+ }
+ return buf.toString();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables with their matching values
+ * from the resolver using the given source array as a template.
+ * The array is not altered by this method.
+ *
+ * @param source the character array to replace in, not altered, null returns null
+ * @return the result of the replace operation
+ */
+ public String replace(final char[] source) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(source.length).append(source);
+ substitute(buf, 0, source.length);
+ return buf.toString();
+ }
+
+ /**
+ * Replaces all the occurrences of variables with their matching values
+ * from the resolver using the given source array as a template.
+ * The array is not altered by this method.
+ * <p>
+ * Only the specified portion of the array will be processed.
+ * The rest of the array is not processed, and is not returned.
+ *
+ * @param source the character array to replace in, not altered, null returns null
+ * @param offset the start offset within the array, must be valid
+ * @param length the length within the array to be processed, must be valid
+ * @return the result of the replace operation
+ */
+ public String replace(final char[] source, final int offset, final int length) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+ substitute(buf, 0, length);
+ return buf.toString();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables with their matching values
+ * from the resolver using the given source buffer as a template.
+ * The buffer is not altered by this method.
+ *
+ * @param source the buffer to use as a template, not changed, null returns null
+ * @return the result of the replace operation
+ */
+ public String replace(final StringBuffer source) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(source.length()).append(source);
+ substitute(buf, 0, buf.length());
+ return buf.toString();
+ }
+
+ /**
+ * Replaces all the occurrences of variables with their matching values
+ * from the resolver using the given source buffer as a template.
+ * The buffer is not altered by this method.
+ * <p>
+ * Only the specified portion of the buffer will be processed.
+ * The rest of the buffer is not processed, and is not returned.
+ *
+ * @param source the buffer to use as a template, not changed, null returns null
+ * @param offset the start offset within the array, must be valid
+ * @param length the length within the array to be processed, must be valid
+ * @return the result of the replace operation
+ */
+ public String replace(final StringBuffer source, final int offset, final int length) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+ 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.
+ * The source is not altered by this method.
+ *
+ * @param source the buffer to use as a template, not changed, null returns null
+ * @return the result of the replace operation
+ */
+ public String replace(final CharSequence source) {
+ if (source == null) {
+ return null;
+ }
+ return replace(source, 0, source.length());
+ }
+
+ /**
+ * Replaces all the occurrences of variables with their matching values
+ * from the resolver using the given source as a template.
+ * The source is not altered by this method.
+ * <p>
+ * Only the specified portion of the buffer will be processed.
+ * The rest of the buffer is not processed, and is not returned.
+ *
+ * @param source the buffer to use as a template, not changed, null returns null
+ * @param offset the start offset within the array, must be valid
+ * @param length the length within the array to be processed, must be valid
+ * @return the result of the replace operation
+ */
+ public String replace(final CharSequence source, final int offset, final int length) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+ substitute(buf, 0, length);
+ return buf.toString();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables with their matching values
+ * from the resolver using the given source builder as a template.
+ * The builder is not altered by this method.
+ *
+ * @param source the builder to use as a template, not changed, null returns null
+ * @return the result of the replace operation
+ */
+ public String replace(final StrBuilder source) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(source.length()).append(source);
+ substitute(buf, 0, buf.length());
+ return buf.toString();
+ }
+
+ /**
+ * Replaces all the occurrences of variables with their matching values
+ * from the resolver using the given source builder as a template.
+ * The builder is not altered by this method.
+ * <p>
+ * Only the specified portion of the builder will be processed.
+ * The rest of the builder is not processed, and is not returned.
+ *
+ * @param source the builder to use as a template, not changed, null returns null
+ * @param offset the start offset within the array, must be valid
+ * @param length the length within the array to be processed, must be valid
+ * @return the result of the replace operation
+ */
+ public String replace(final StrBuilder source, final int offset, final int length) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+ substitute(buf, 0, length);
+ return buf.toString();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables in the given source object with
+ * their matching values from the resolver. The input source object is
+ * converted to a string using <code>toString</code> and is not altered.
+ *
+ * @param source the source to replace in, null returns null
+ * @return the result of the replace operation
+ */
+ public String replace(final Object source) {
+ if (source == null) {
+ return null;
+ }
+ final StrBuilder buf = new StrBuilder().append(source);
+ substitute(buf, 0, buf.length());
+ return buf.toString();
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables within the given source buffer
+ * with their matching values from the resolver.
+ * The buffer is updated with the result.
+ *
+ * @param source the buffer to replace in, updated, null returns zero
+ * @return true if altered
+ */
+ public boolean replaceIn(final StringBuffer source) {
+ if (source == null) {
+ return false;
+ }
+ return replaceIn(source, 0, source.length());
+ }
+
+ /**
+ * Replaces all the occurrences of variables within the given source buffer
+ * with their matching values from the resolver.
+ * The buffer is updated with the result.
+ * <p>
+ * Only the specified portion of the buffer will be processed.
+ * The rest of the buffer is not processed, but it is not deleted.
+ *
+ * @param source the buffer to replace in, updated, null returns zero
+ * @param offset the start offset within the array, must be valid
+ * @param length the length within the buffer to be processed, must be valid
+ * @return true if altered
+ */
+ public boolean replaceIn(final StringBuffer source, final int offset, final int length) {
+ if (source == null) {
+ return false;
+ }
+ final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
+ if (!substitute(buf, 0, length)) {
+ return false;
+ }
+ source.replace(offset, offset + length, buf.toString());
+ return true;
+ }
+
+ //-----------------------------------------------------------------------
+ /**
+ * Replaces all the occurrences of variables within the given source buffer
+ * with their matching values from the resolver.
+ * The buffer is updated with the result.
+ *
+ * @param source the buffer to replace in, updated, null returns zero
+ * @return true if altered
+ */
+ public boolean replaceIn(final StringBuilder source) {
+ if (source == null) {
+ return false;
+
<TRUNCATED>