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