View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.text;
19  
20  import static org.junit.jupiter.api.Assertions.assertEquals;
21  import static org.junit.jupiter.api.Assertions.assertFalse;
22  import static org.junit.jupiter.api.Assertions.assertNull;
23  import static org.junit.jupiter.api.Assertions.assertSame;
24  import static org.junit.jupiter.api.Assertions.assertThrows;
25  import static org.junit.jupiter.api.Assertions.assertTrue;
26  
27  import java.util.HashMap;
28  import java.util.Map;
29  import java.util.Properties;
30  
31  import org.apache.commons.lang3.SystemProperties;
32  import org.apache.commons.lang3.mutable.MutableObject;
33  import org.junit.jupiter.api.AfterEach;
34  import org.junit.jupiter.api.BeforeEach;
35  import org.junit.jupiter.api.Test;
36  
37  /**
38   * Test class for {@link StrSubstitutor}.
39   *
40   * @deprecated This class will be removed in 2.0.
41   */
42  @Deprecated
43  class StrSubstitutorTest {
44  
45      private Map<String, String> values;
46  
47      private void doTestNoReplace(final String replaceTemplate) {
48          final StrSubstitutor sub = new StrSubstitutor(values);
49  
50          if (replaceTemplate == null) {
51              assertNull(sub.replace((String) null));
52              assertNull(sub.replace((String) null, 0, 100));
53              assertNull(sub.replace((char[]) null));
54              assertNull(sub.replace((char[]) null, 0, 100));
55              assertNull(sub.replace((StringBuffer) null));
56              assertNull(sub.replace((StringBuffer) null, 0, 100));
57              assertNull(sub.replace((StrBuilder) null));
58              assertNull(sub.replace((StrBuilder) null, 0, 100));
59              assertNull(sub.replace((Object) null));
60              assertFalse(sub.replaceIn((StringBuffer) null));
61              assertFalse(sub.replaceIn((StringBuffer) null, 0, 100));
62              assertFalse(sub.replaceIn((StrBuilder) null));
63              assertFalse(sub.replaceIn((StrBuilder) null, 0, 100));
64          } else {
65              assertEquals(replaceTemplate, sub.replace(replaceTemplate));
66              final StrBuilder bld = new StrBuilder(replaceTemplate);
67              assertFalse(sub.replaceIn(bld));
68              assertEquals(replaceTemplate, bld.toString());
69          }
70      }
71  
72      private void doTestReplace(final String expectedResult, final String replaceTemplate, final boolean substring) {
73          final StrSubstitutor sub = new StrSubstitutor(values);
74          doTestReplace(sub, expectedResult, replaceTemplate, substring);
75      }
76  
77      private void doTestReplace(final StrSubstitutor sub, final String expectedResult, final String replaceTemplate,
78              final boolean substring) {
79          final String expectedShortResult = expectedResult.substring(1, expectedResult.length() - 1);
80  
81          // replace using String
82          assertEquals(expectedResult, sub.replace(replaceTemplate));
83          if (substring) {
84              assertEquals(expectedShortResult, sub.replace(replaceTemplate, 1, replaceTemplate.length() - 2));
85          }
86  
87          // replace using char[]
88          final char[] chars = replaceTemplate.toCharArray();
89          assertEquals(expectedResult, sub.replace(chars));
90          if (substring) {
91              assertEquals(expectedShortResult, sub.replace(chars, 1, chars.length - 2));
92          }
93  
94          // replace using StringBuffer
95          StringBuffer buf = new StringBuffer(replaceTemplate);
96          assertEquals(expectedResult, sub.replace(buf));
97          if (substring) {
98              assertEquals(expectedShortResult, sub.replace(buf, 1, buf.length() - 2));
99          }
100 
101         // replace using StringBuilder
102         StringBuilder builder = new StringBuilder(replaceTemplate);
103         assertEquals(expectedResult, sub.replace(builder));
104         if (substring) {
105             assertEquals(expectedShortResult, sub.replace(builder, 1, builder.length() - 2));
106         }
107 
108         // replace using StrBuilder
109         StrBuilder bld = new StrBuilder(replaceTemplate);
110         assertEquals(expectedResult, sub.replace(bld));
111         if (substring) {
112             assertEquals(expectedShortResult, sub.replace(bld, 1, bld.length() - 2));
113         }
114 
115         // replace using object
116         final MutableObject<String> obj = new MutableObject<>(replaceTemplate);  // toString returns template
117         assertEquals(expectedResult, sub.replace(obj));
118 
119         // replace in StringBuffer
120         buf = new StringBuffer(replaceTemplate);
121         assertTrue(sub.replaceIn(buf));
122         assertEquals(expectedResult, buf.toString());
123         if (substring) {
124             buf = new StringBuffer(replaceTemplate);
125             assertTrue(sub.replaceIn(buf, 1, buf.length() - 2));
126             assertEquals(expectedResult, buf.toString());  // expect full result as remainder is untouched
127         }
128 
129         // replace in StringBuilder
130         builder = new StringBuilder(replaceTemplate);
131         assertTrue(sub.replaceIn(builder));
132         assertEquals(expectedResult, builder.toString());
133         if (substring) {
134             builder = new StringBuilder(replaceTemplate);
135             assertTrue(sub.replaceIn(builder, 1, builder.length() - 2));
136             assertEquals(expectedResult, builder.toString());  // expect full result as remainder is untouched
137         }
138 
139         // replace in StrBuilder
140         bld = new StrBuilder(replaceTemplate);
141         assertTrue(sub.replaceIn(bld));
142         assertEquals(expectedResult, bld.toString());
143         if (substring) {
144             bld = new StrBuilder(replaceTemplate);
145             assertTrue(sub.replaceIn(bld, 1, bld.length() - 2));
146             assertEquals(expectedResult, bld.toString());  // expect full result as remainder is untouched
147         }
148     }
149 
150     @BeforeEach
151     public void setUp() throws Exception {
152         values = new HashMap<>();
153         values.put("animal", "quick brown fox");
154         values.put("target", "lazy dog");
155     }
156 
157     @AfterEach
158     public void tearDown() throws Exception {
159         values = null;
160     }
161 
162     /**
163      * Tests constructor.
164      */
165     @Test
166     void testConstructorMapFull() {
167         final Map<String, String> map = new HashMap<>();
168         map.put("name", "commons");
169         StrSubstitutor sub = new StrSubstitutor(map, "<", ">", '!');
170         assertEquals("Hi < commons", sub.replace("Hi !< <name>"));
171         sub = new StrSubstitutor(map, "<", ">", '!', "||");
172         assertEquals("Hi < commons", sub.replace("Hi !< <name2||commons>"));
173     }
174 
175     /**
176      * Tests constructor.
177      */
178     @Test
179     void testConstructorMapPrefixSuffix() {
180         final Map<String, String> map = new HashMap<>();
181         map.put("name", "commons");
182         final StrSubstitutor sub = new StrSubstitutor(map, "<", ">");
183         assertEquals("Hi < commons", sub.replace("Hi $< <name>"));
184     }
185 
186     /**
187      * Tests constructor.
188      */
189     @Test
190     void testConstructorNoArgs() {
191         final StrSubstitutor sub = new StrSubstitutor();
192         assertEquals("Hi ${name}", sub.replace("Hi ${name}"));
193     }
194 
195     @Test
196     void testCreatesStrSubstitutorTakingStrLookupAndCallsReplaceTakingTwoAndThreeInts() {
197         final Map<String, CharacterPredicates> map = new HashMap<>();
198         final StrLookup<CharacterPredicates> strLookupMapStrLookup = StrLookup.mapLookup(map);
199         final StrSubstitutor strSubstitutor = new StrSubstitutor(strLookupMapStrLookup);
200 
201         assertNull(strSubstitutor.replace((CharSequence) null, 0, 0));
202         assertEquals('$', strSubstitutor.getEscapeChar());
203     }
204 
205     /**
206      * Tests a cyclic replace operation.
207      * The cycle should be detected and cause an exception to be thrown.
208      */
209     @Test
210     void testCyclicReplacement() {
211         final Map<String, String> map = new HashMap<>();
212         map.put("animal", "${critter}");
213         map.put("target", "${pet}");
214         map.put("pet", "${petCharacteristic} dog");
215         map.put("petCharacteristic", "lazy");
216         map.put("critter", "${critterSpeed} ${critterColor} ${critterType}");
217         map.put("critterSpeed", "quick");
218         map.put("critterColor", "brown");
219         map.put("critterType", "${animal}");
220         final StrSubstitutor sub = new StrSubstitutor(map);
221         assertThrows(IllegalStateException.class, () -> sub.replace("The ${animal} jumps over the ${target}."));
222 
223         // also check even when default value is set.
224         map.put("critterType", "${animal:-fox}");
225         assertThrows(IllegalStateException.class,
226                 () -> new StrSubstitutor(map).replace("The ${animal} jumps over the ${target}."));
227     }
228 
229     @Test
230     void testDefaultValueDelimiters() {
231         final Map<String, String> map = new HashMap<>();
232         map.put("animal", "fox");
233         map.put("target", "dog");
234 
235         StrSubstitutor sub = new StrSubstitutor(map, "${", "}", '$');
236         assertEquals("The fox jumps over the lazy dog. 1234567890.",
237                 sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number:-1234567890}."));
238 
239         sub = new StrSubstitutor(map, "${", "}", '$', "?:");
240         assertEquals("The fox jumps over the lazy dog. 1234567890.",
241                 sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number?:1234567890}."));
242 
243         sub = new StrSubstitutor(map, "${", "}", '$', "||");
244         assertEquals("The fox jumps over the lazy dog. 1234567890.",
245                 sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number||1234567890}."));
246 
247         sub = new StrSubstitutor(map, "${", "}", '$', "!");
248         assertEquals("The fox jumps over the lazy dog. 1234567890.",
249                 sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number!1234567890}."));
250 
251         sub = new StrSubstitutor(map, "${", "}", '$', "");
252         sub.setValueDelimiterMatcher(null);
253         assertEquals("The fox jumps over the lazy dog. ${undefined.number!1234567890}.",
254                 sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number!1234567890}."));
255 
256         sub = new StrSubstitutor(map, "${", "}", '$');
257         sub.setValueDelimiterMatcher(null);
258         assertEquals("The fox jumps over the lazy dog. ${undefined.number!1234567890}.",
259                 sub.replace("The ${animal} jumps over the lazy ${target}. ${undefined.number!1234567890}."));
260     }
261 
262     @Test
263     void testDisableSubstitutionInValues() {
264         final StrSubstitutor sub = new StrSubstitutor(values);
265         sub.setDisableSubstitutionInValues(true);
266         values.put("animal", "${critter}");
267         values.put("target", "${pet}");
268         values.put("pet", "${petCharacteristic} dog");
269         values.put("petCharacteristic", "lazy");
270         values.put("critter", "${critterSpeed} ${critterColor} ${critterType}");
271         values.put("critterSpeed", "quick");
272         values.put("critterColor", "brown");
273         values.put("critterType", "fox");
274         doTestReplace(sub, "The ${critter} jumps over the ${pet}.", "The ${animal} jumps over the ${target}.", true);
275     }
276 
277     /**
278      * Tests get set.
279      */
280     @Test
281     void testGetSetEscape() {
282         final StrSubstitutor sub = new StrSubstitutor();
283         assertEquals('$', sub.getEscapeChar());
284         sub.setEscapeChar('<');
285         assertEquals('<', sub.getEscapeChar());
286     }
287 
288     /**
289      * Tests get set.
290      */
291     @Test
292     void testGetSetPrefix() {
293         final StrSubstitutor sub = new StrSubstitutor();
294         StrMatcherTest.assertStrMatcherPrefixImpl("StringMatcher", sub);
295         StrMatcherTest.assertStrMatcherSuffixImpl("StringMatcher", sub);
296         sub.setVariablePrefix('<');
297         StrMatcherTest.assertStrMatcherPrefixImpl("CharMatcher", sub);
298         StrMatcherTest.assertStrMatcherSuffixImpl("StringMatcher", sub);
299 
300         sub.setVariablePrefix("<<");
301         StrMatcherTest.assertStrMatcherPrefixImpl("StringMatcher", sub);
302         StrMatcherTest.assertStrMatcherSuffixImpl("StringMatcher", sub);
303         assertThrows(IllegalArgumentException.class, () -> sub.setVariablePrefix((String) null));
304         StrMatcherTest.assertStrMatcherPrefixImpl("StringMatcher", sub);
305         StrMatcherTest.assertStrMatcherSuffixImpl("StringMatcher", sub);
306 
307         final StrMatcher matcher = StrMatcher.commaMatcher();
308         sub.setVariablePrefixMatcher(matcher);
309         assertSame(matcher, sub.getVariablePrefixMatcher());
310         assertThrows(IllegalArgumentException.class, () -> sub.setVariablePrefixMatcher((StrMatcher) null));
311         assertSame(matcher, sub.getVariablePrefixMatcher());
312     }
313 
314     /**
315      * Tests get set.
316      */
317     @Test
318     void testGetSetSuffix() {
319         final StrSubstitutor sub = new StrSubstitutor();
320         StrMatcherTest.assertStrMatcherPrefixImpl("StringMatcher", sub);
321         StrMatcherTest.assertStrMatcherSuffixImpl("StringMatcher", sub);
322         sub.setVariableSuffix('<');
323         StrMatcherTest.assertStrMatcherPrefixImpl("StringMatcher", sub);
324         StrMatcherTest.assertStrMatcherSuffixImpl("CharMatcher", sub);
325 
326         sub.setVariableSuffix("<<");
327         StrMatcherTest.assertStrMatcherPrefixImpl("StringMatcher", sub);
328         StrMatcherTest.assertStrMatcherSuffixImpl("StringMatcher", sub);
329         assertThrows(IllegalArgumentException.class, () -> sub.setVariableSuffix((String) null));
330         StrMatcherTest.assertStrMatcherPrefixImpl("StringMatcher", sub);
331         StrMatcherTest.assertStrMatcherSuffixImpl("StringMatcher", sub);
332 
333         final StrMatcher matcher = StrMatcher.commaMatcher();
334         sub.setVariableSuffixMatcher(matcher);
335         assertSame(matcher, sub.getVariableSuffixMatcher());
336         assertThrows(IllegalArgumentException.class, () -> sub.setVariableSuffixMatcher((StrMatcher) null));
337         assertSame(matcher, sub.getVariableSuffixMatcher());
338     }
339 
340     /**
341      * Tests get set.
342      */
343     @Test
344     void testGetSetValueDelimiter() {
345         final StrSubstitutor sub = new StrSubstitutor();
346         StrMatcherTest.assertStrMatcherPrefixImpl("StringMatcher", sub);
347         StrMatcherTest.assertStrMatcherSuffixImpl("StringMatcher", sub);
348         sub.setValueDelimiter(':');
349         StrMatcherTest.assertStrMatcherPrefixImpl("StringMatcher", sub);
350         StrMatcherTest.assertStrMatcherSuffixImpl("StringMatcher", sub);
351 
352         sub.setValueDelimiter("||");
353         StrMatcherTest.assertStrMatcherPrefixImpl("StringMatcher", sub);
354         StrMatcherTest.assertStrMatcherSuffixImpl("StringMatcher", sub);
355         sub.setValueDelimiter((String) null);
356         assertNull(sub.getValueDelimiterMatcher());
357 
358         final StrMatcher matcher = StrMatcher.commaMatcher();
359         sub.setValueDelimiterMatcher(matcher);
360         assertSame(matcher, sub.getValueDelimiterMatcher());
361         sub.setValueDelimiterMatcher((StrMatcher) null);
362         assertNull(sub.getValueDelimiterMatcher());
363     }
364 
365     /**
366      * Test for LANG-1055: StrSubstitutor.replaceSystemProperties does not work consistently
367      */
368     @Test
369     void testLANG1055() {
370         System.setProperty("test_key", "test_value");
371 
372         final String expected = StrSubstitutor.replace("test_key=${test_key}", System.getProperties());
373         final String actual = StrSubstitutor.replaceSystemProperties("test_key=${test_key}");
374         assertEquals(expected, actual);
375     }
376 
377     /**
378      * Tests adjacent keys.
379      */
380     @Test
381     void testReplaceAdjacentAtEnd() {
382         values.put("code", "GBP");
383         values.put("amount", "12.50");
384         final StrSubstitutor sub = new StrSubstitutor(values);
385         assertEquals("Amount is GBP12.50", sub.replace("Amount is ${code}${amount}"));
386     }
387 
388     /**
389      * Tests adjacent keys.
390      */
391     @Test
392     void testReplaceAdjacentAtStart() {
393         values.put("code", "GBP");
394         values.put("amount", "12.50");
395         final StrSubstitutor sub = new StrSubstitutor(values);
396         assertEquals("GBP12.50 charged", sub.replace("${code}${amount} charged"));
397     }
398 
399     /**
400      * Tests key replace changing map after initialization (not recommended).
401      */
402     @Test
403     void testReplaceChangedMap() {
404         final StrSubstitutor sub = new StrSubstitutor(values);
405         values.put("target", "moon");
406         assertEquals("The quick brown fox jumps over the moon.",
407                 sub.replace("The ${animal} jumps over the ${target}."));
408     }
409 
410     /**
411      * Tests complex escaping.
412      */
413     @Test
414     void testReplaceComplexEscaping() {
415         doTestReplace("The ${quick brown fox} jumps over the lazy dog.",
416                 "The $${${animal}} jumps over the ${target}.", true);
417         doTestReplace("The ${quick brown fox} jumps over the lazy dog. ${1234567890}.",
418                 "The $${${animal}} jumps over the ${target}. $${${undefined.number:-1234567890}}.", true);
419     }
420 
421     /**
422      * Tests replace with null.
423      */
424     @Test
425     void testReplaceEmpty() {
426         doTestNoReplace("");
427     }
428 
429     /**
430      * Tests when no variable name.
431      */
432     @Test
433     void testReplaceEmptyKeys() {
434         doTestReplace("The ${} jumps over the lazy dog.", "The ${} jumps over the ${target}.", true);
435         doTestReplace("The animal jumps over the lazy dog.", "The ${:-animal} jumps over the ${target}.", true);
436     }
437 
438     /**
439      * Tests escaping.
440      */
441     @Test
442     void testReplaceEscaping() {
443         doTestReplace("The ${animal} jumps over the lazy dog.", "The $${animal} jumps over the ${target}.", true);
444     }
445 
446     /**
447      * Tests when no incomplete prefix.
448      */
449     @Test
450     void testReplaceIncompletePrefix() {
451         doTestReplace("The {animal} jumps over the lazy dog.", "The {animal} jumps over the ${target}.", true);
452     }
453 
454     @Test
455     void testReplaceInTakingStringBufferWithNonNull() {
456         final StrSubstitutor strSubstitutor =
457                 new StrSubstitutor(new HashMap<>(), "WV@i#y?N*[", "WV@i#y?N*[", '*');
458 
459         assertFalse(strSubstitutor.isPreserveEscapes());
460         assertFalse(strSubstitutor.replaceIn(new StringBuffer("WV@i#y?N*[")));
461         assertEquals('*', strSubstitutor.getEscapeChar());
462     }
463 
464     @Test
465     void testReplaceInTakingStringBuilderWithNonNull() {
466         final StrLookup<String> strLookup = StrLookup.systemPropertiesLookup();
467         final StrSubstitutor strSubstitutor = new StrSubstitutor(strLookup, "b<H", "b<H", '\'');
468         final StringBuilder stringBuilder = new StringBuilder((CharSequence) "b<H");
469 
470         assertEquals('\'', strSubstitutor.getEscapeChar());
471         assertFalse(strSubstitutor.replaceIn(stringBuilder));
472     }
473 
474     @Test
475     void testReplaceInTakingStringBuilderWithNull() {
476         final Map<String, Object> map = new HashMap<>();
477         final StrSubstitutor strSubstitutor = new StrSubstitutor(map, "", "", 'T', "K+<'f");
478 
479         assertFalse(strSubstitutor.replaceIn((StringBuilder) null));
480     }
481 
482     @Test
483     void testReplaceInTakingTwoAndThreeIntsReturningFalse() {
484         final Map<String, Object> hashMap = new HashMap<>();
485         final StrLookup<Object> strLookupMapStrLookup = StrLookup.mapLookup(hashMap);
486         final StrMatcher strMatcher = StrMatcher.tabMatcher();
487         final StrSubstitutor strSubstitutor =
488                 new StrSubstitutor(strLookupMapStrLookup, strMatcher, strMatcher, 'b', strMatcher);
489 
490         assertFalse(strSubstitutor.replaceIn((StringBuilder) null, 1315, -1369));
491         assertEquals('b', strSubstitutor.getEscapeChar());
492         assertFalse(strSubstitutor.isPreserveEscapes());
493     }
494 
495     /**
496      * Tests whether a variable can be replaced in a variable name.
497      */
498     @Test
499     void testReplaceInVariable() {
500         values.put("animal.1", "fox");
501         values.put("animal.2", "mouse");
502         values.put("species", "2");
503         final StrSubstitutor sub = new StrSubstitutor(values);
504         sub.setEnableSubstitutionInVariables(true);
505         assertEquals(
506                 "The mouse jumps over the lazy dog.",
507                 sub.replace("The ${animal.${species}} jumps over the ${target}."));
508         values.put("species", "1");
509         assertEquals(
510                 "The fox jumps over the lazy dog.",
511                 sub.replace("The ${animal.${species}} jumps over the ${target}."));
512         assertEquals(
513                 "The fox jumps over the lazy dog.",
514                 sub.replace("The ${unknown.animal.${unknown.species:-1}:-fox} "
515                         + "jumps over the ${unknow.target:-lazy dog}."));
516     }
517 
518     /**
519      * Tests whether substitution in variable names is disabled per default.
520      */
521     @Test
522     void testReplaceInVariableDisabled() {
523         values.put("animal.1", "fox");
524         values.put("animal.2", "mouse");
525         values.put("species", "2");
526         final StrSubstitutor sub = new StrSubstitutor(values);
527         assertEquals(
528                 "The ${animal.${species}} jumps over the lazy dog.",
529                 sub.replace("The ${animal.${species}} jumps over the ${target}."));
530         assertEquals(
531                 "The ${animal.${species:-1}} jumps over the lazy dog.",
532                 sub.replace("The ${animal.${species:-1}} jumps over the ${target}."));
533     }
534 
535     /**
536      * Tests complex and recursive substitution in variable names.
537      */
538     @Test
539     void testReplaceInVariableRecursive() {
540         values.put("animal.2", "brown fox");
541         values.put("animal.1", "white mouse");
542         values.put("color", "white");
543         values.put("species.white", "1");
544         values.put("species.brown", "2");
545         final StrSubstitutor sub = new StrSubstitutor(values);
546         sub.setEnableSubstitutionInVariables(true);
547         assertEquals(
548                 "The white mouse jumps over the lazy dog.",
549                 sub.replace("The ${animal.${species.${color}}} jumps over the ${target}."));
550         assertEquals(
551                 "The brown fox jumps over the lazy dog.",
552                 sub.replace("The ${animal.${species.${unknownColor:-brown}}} jumps over the ${target}."));
553     }
554 
555     /**
556      * Tests when no prefix or suffix.
557      */
558     @Test
559     void testReplaceNoPrefixNoSuffix() {
560         doTestReplace("The animal jumps over the lazy dog.", "The animal jumps over the ${target}.", true);
561     }
562 
563     /**
564      * Tests when suffix but no prefix.
565      */
566     @Test
567     void testReplaceNoPrefixSuffix() {
568         doTestReplace("The animal} jumps over the lazy dog.", "The animal} jumps over the ${target}.", true);
569     }
570 
571     /**
572      * Tests replace with no variables.
573      */
574     @Test
575     void testReplaceNoVariables() {
576         doTestNoReplace("The balloon arrived.");
577     }
578 
579     /**
580      * Tests replace with null.
581      */
582     @Test
583     void testReplaceNull() {
584         doTestNoReplace(null);
585     }
586 
587     /**
588      * Tests simple key replace.
589      */
590     @Test
591     void testReplacePartialString_noReplace() {
592         final StrSubstitutor sub = new StrSubstitutor();
593         assertEquals("${animal} jumps", sub.replace("The ${animal} jumps over the ${target}.", 4, 15));
594     }
595 
596     /**
597      * Tests when prefix but no suffix.
598      */
599     @Test
600     void testReplacePrefixNoSuffix() {
601         doTestReplace("The ${animal jumps over the ${target} lazy dog.",
602                 "The ${animal jumps over the ${target} ${target}.", true);
603     }
604 
605     /**
606      * Tests simple recursive replace.
607      */
608     @Test
609     void testReplaceRecursive() {
610         values.put("animal", "${critter}");
611         values.put("target", "${pet}");
612         values.put("pet", "${petCharacteristic} dog");
613         values.put("petCharacteristic", "lazy");
614         values.put("critter", "${critterSpeed} ${critterColor} ${critterType}");
615         values.put("critterSpeed", "quick");
616         values.put("critterColor", "brown");
617         values.put("critterType", "fox");
618         doTestReplace("The quick brown fox jumps over the lazy dog.", "The ${animal} jumps over the ${target}.", true);
619 
620         values.put("pet", "${petCharacteristicUnknown:-lazy} dog");
621         doTestReplace("The quick brown fox jumps over the lazy dog.", "The ${animal} jumps over the ${target}.", true);
622     }
623 
624     /**
625      * Tests simple key replace.
626      */
627     @Test
628     void testReplaceSimple() {
629         doTestReplace("The quick brown fox jumps over the lazy dog.", "The ${animal} jumps over the ${target}.", true);
630     }
631 
632     /**
633      * Tests simple key replace.
634      */
635     @Test
636     void testReplaceSolo() {
637         doTestReplace("quick brown fox", "${animal}", false);
638     }
639 
640     /**
641      * Tests escaping.
642      */
643     @Test
644     void testReplaceSoloEscaping() {
645         doTestReplace("${animal}", "$${animal}", false);
646     }
647 
648     @Test
649     void testReplaceTakingCharSequenceReturningNull() {
650         final StrSubstitutor strSubstitutor = new StrSubstitutor((StrLookup<?>) null);
651 
652         assertNull(strSubstitutor.replace((CharSequence) null));
653         assertFalse(strSubstitutor.isPreserveEscapes());
654         assertEquals('$', strSubstitutor.getEscapeChar());
655     }
656 
657     @Test
658     void testReplaceTakingThreeArgumentsThrowsNullPointerException() {
659         assertThrows(NullPointerException.class, () -> StrSubstitutor.replace(null, (Properties) null));
660     }
661 
662     /**
663      * Tests replace creates output same as input.
664      */
665     @Test
666     void testReplaceToIdentical() {
667         values.put("animal", "$${${thing}}");
668         values.put("thing", "animal");
669         doTestReplace("The ${animal} jumps.", "The ${animal} jumps.", true);
670     }
671 
672     /**
673      * Tests unknown key replace.
674      */
675     @Test
676     void testReplaceUnknownKey() {
677         doTestReplace("The ${person} jumps over the lazy dog.", "The ${person} jumps over the ${target}.", true);
678         doTestReplace("The ${person} jumps over the lazy dog. 1234567890.",
679                 "The ${person} jumps over the ${target}. ${undefined.number:-1234567890}.", true);
680     }
681 
682     /**
683      * Tests interpolation with weird boundary patterns.
684      */
685     @Test
686     void testReplaceWeirdPattens() {
687         doTestNoReplace("");
688         doTestNoReplace("${}");
689         doTestNoReplace("${ }");
690         doTestNoReplace("${\t}");
691         doTestNoReplace("${\n}");
692         doTestNoReplace("${\b}");
693         doTestNoReplace("${");
694         doTestNoReplace("$}");
695         doTestNoReplace("}");
696         doTestNoReplace("${}$");
697         doTestNoReplace("${${");
698         doTestNoReplace("${${}}");
699         doTestNoReplace("${$${}}");
700         doTestNoReplace("${$$${}}");
701         doTestNoReplace("${$$${$}}");
702         doTestNoReplace("${${}}");
703         doTestNoReplace("${${ }}");
704     }
705 
706     /**
707      * Tests protected.
708      */
709     @Test
710     void testResolveVariable() {
711         final StrBuilder builder = new StrBuilder("Hi ${name}!");
712         final Map<String, String> map = new HashMap<>();
713         map.put("name", "commons");
714         final StrSubstitutor sub = new StrSubstitutor(map) {
715             @Override
716             protected String resolveVariable(final String variableName, final StrBuilder buf, final int startPos,
717                     final int endPos) {
718                 assertEquals("name", variableName);
719                 assertSame(builder, buf);
720                 assertEquals(3, startPos);
721                 assertEquals(10, endPos);
722                 return "jakarta";
723             }
724         };
725         sub.replaceIn(builder);
726         assertEquals("Hi jakarta!", builder.toString());
727     }
728 
729     @Test
730     void testSamePrefixAndSuffix() {
731         final Map<String, String> map = new HashMap<>();
732         map.put("greeting", "Hello");
733         map.put(" there ", "XXX");
734         map.put("name", "commons");
735         assertEquals("Hi commons!", StrSubstitutor.replace("Hi @name@!", map, "@", "@"));
736         assertEquals("Hello there commons!", StrSubstitutor.replace("@greeting@ there @name@!", map, "@", "@"));
737     }
738 
739     /**
740      * Tests static.
741      */
742     @Test
743     void testStaticReplace() {
744         final Map<String, String> map = new HashMap<>();
745         map.put("name", "commons");
746         assertEquals("Hi commons!", StrSubstitutor.replace("Hi ${name}!", map));
747     }
748 
749     /**
750      * Tests static.
751      */
752     @Test
753     void testStaticReplacePrefixSuffix() {
754         final Map<String, String> map = new HashMap<>();
755         map.put("name", "commons");
756         assertEquals("Hi commons!", StrSubstitutor.replace("Hi <name>!", map, "<", ">"));
757     }
758 
759     /**
760      * Tests interpolation with system properties.
761      */
762     @Test
763     void testStaticReplaceSystemProperties() {
764         final StrBuilder buf = new StrBuilder();
765         buf.append("Hi ").append(SystemProperties.getUserName());
766         buf.append(", you are working with ");
767         buf.append(SystemProperties.getOsName());
768         buf.append(", your home directory is ");
769         buf.append(SystemProperties.getUserHome()).append('.');
770         assertEquals(buf.toString(), StrSubstitutor.replaceSystemProperties("Hi ${user.name}, you are "
771             + "working with ${os.name}, your home "
772             + "directory is ${user.home}."));
773     }
774 
775     /**
776      * Test the replace of a properties object
777      */
778     @Test
779     void testSubstituteDefaultProperties() {
780         final String org = "${doesnotwork}";
781         System.setProperty("doesnotwork", "It works!");
782 
783         // create a new Properties object with the System.getProperties as default
784         final Properties props = new Properties(System.getProperties());
785 
786         assertEquals("It works!", StrSubstitutor.replace(org, props));
787     }
788 
789     @Test
790     void testSubstitutePreserveEscape() {
791         final String org = "${not-escaped} $${escaped}";
792         final Map<String, String> map = new HashMap<>();
793         map.put("not-escaped", "value");
794 
795         final StrSubstitutor sub = new StrSubstitutor(map, "${", "}", '$');
796         assertFalse(sub.isPreserveEscapes());
797         assertEquals("value ${escaped}", sub.replace(org));
798 
799         sub.setPreserveEscapes(true);
800         assertTrue(sub.isPreserveEscapes());
801         assertEquals("value $${escaped}", sub.replace(org));
802     }
803 
804 }