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