1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.io.IOException;
28 import java.util.HashMap;
29 import java.util.Map;
30 import java.util.Properties;
31
32 import org.apache.commons.lang3.StringUtils;
33 import org.apache.commons.lang3.SystemProperties;
34 import org.apache.commons.lang3.mutable.MutableObject;
35 import org.apache.commons.text.lookup.StringLookup;
36 import org.apache.commons.text.lookup.StringLookupFactory;
37 import org.apache.commons.text.matcher.StringMatcher;
38 import org.apache.commons.text.matcher.StringMatcherFactory;
39 import org.junit.jupiter.api.AfterEach;
40 import org.junit.jupiter.api.BeforeEach;
41 import org.junit.jupiter.api.Disabled;
42 import org.junit.jupiter.api.MethodOrderer;
43 import org.junit.jupiter.api.Test;
44 import org.junit.jupiter.api.TestMethodOrder;
45
46
47
48
49 @TestMethodOrder(MethodOrderer.MethodName.class)
50 public class StringSubstitutorTest {
51
52 private static final String ACTUAL_ANIMAL = "quick brown fox";
53 private static final String ACTUAL_TARGET = "lazy dog";
54 private static final String CLASSIC_RESULT = "The quick brown fox jumps over the lazy dog.";
55 private static final String CLASSIC_TEMPLATE = "The ${animal} jumps over the ${target}.";
56 private static final String EMPTY_EXPR = "${}";
57
58 protected Map<String, String> values;
59
60 private void assertEqualsCharSeq(final CharSequence expected, final CharSequence actual) {
61 assertEquals(expected, actual,
62 () -> String.format("expected.length()=%,d, actual.length()=%,d", StringUtils.length(expected), StringUtils.length(actual)));
63 }
64
65 protected void doNotReplace(final String replaceTemplate) throws IOException {
66 doTestNoReplace(new StringSubstitutor(values), replaceTemplate);
67 }
68
69 protected void doReplace(final String expectedResult, final String replaceTemplate, final boolean substring) throws IOException {
70 doTestReplace(new StringSubstitutor(values), expectedResult, replaceTemplate, substring);
71 }
72
73 protected void doTestNoReplace(final StringSubstitutor substitutor, final String replaceTemplate) throws IOException {
74 if (replaceTemplate == null) {
75 assertNull(replace(substitutor, (String) null));
76 assertNull(substitutor.replace((String) null, 0, 100));
77 assertNull(substitutor.replace((char[]) null));
78 assertNull(substitutor.replace((char[]) null, 0, 100));
79 assertNull(substitutor.replace((StringBuffer) null));
80 assertNull(substitutor.replace((StringBuffer) null, 0, 100));
81 assertNull(substitutor.replace((TextStringBuilder) null));
82 assertNull(substitutor.replace((TextStringBuilder) null, 0, 100));
83 assertNull(substitutor.replace((Object) null));
84 assertFalse(substitutor.replaceIn((StringBuffer) null));
85 assertFalse(substitutor.replaceIn((StringBuffer) null, 0, 100));
86 assertFalse(substitutor.replaceIn((TextStringBuilder) null));
87 assertFalse(substitutor.replaceIn((TextStringBuilder) null, 0, 100));
88 } else {
89 assertEquals(replaceTemplate, replace(substitutor, replaceTemplate));
90 final TextStringBuilder builder = new TextStringBuilder(replaceTemplate);
91 assertFalse(substitutor.replaceIn(builder));
92 assertEquals(replaceTemplate, builder.toString());
93 }
94 }
95
96 protected void doTestReplace(final StringSubstitutor sub, final String expectedResult, final String replaceTemplate, final boolean substring)
97 throws IOException {
98 final String expectedShortResult = substring ? expectedResult.substring(1, expectedResult.length() - 1) : expectedResult;
99
100
101 final String actual = replace(sub, replaceTemplate);
102 assertEquals(expectedResult, actual, () -> String.format("Index of difference: %,d", StringUtils.indexOfDifference(expectedResult, actual)));
103 if (substring) {
104 assertEquals(expectedShortResult, sub.replace(replaceTemplate, 1, replaceTemplate.length() - 2));
105 }
106
107
108 final char[] chars = replaceTemplate.toCharArray();
109 assertEquals(expectedResult, sub.replace(chars));
110 if (substring) {
111 assertEquals(expectedShortResult, sub.replace(chars, 1, chars.length - 2));
112 }
113
114
115 StringBuffer buf = new StringBuffer(replaceTemplate);
116 assertEquals(expectedResult, sub.replace(buf));
117 if (substring) {
118 assertEquals(expectedShortResult, sub.replace(buf, 1, buf.length() - 2));
119 }
120
121
122 StringBuilder builder = new StringBuilder(replaceTemplate);
123 assertEquals(expectedResult, sub.replace(builder));
124 if (substring) {
125 assertEquals(expectedShortResult, sub.replace(builder, 1, builder.length() - 2));
126 }
127
128
129 TextStringBuilder bld = new TextStringBuilder(replaceTemplate);
130 assertEquals(expectedResult, sub.replace(bld));
131 if (substring) {
132 assertEquals(expectedShortResult, sub.replace(bld, 1, bld.length() - 2));
133 }
134
135
136 final MutableObject<String> obj = new MutableObject<>(replaceTemplate);
137 assertEquals(expectedResult, sub.replace(obj));
138
139
140 buf = new StringBuffer(replaceTemplate);
141 assertTrue(sub.replaceIn(buf), replaceTemplate);
142 assertEquals(expectedResult, buf.toString());
143 if (substring) {
144 buf = new StringBuffer(replaceTemplate);
145 assertTrue(sub.replaceIn(buf, 1, buf.length() - 2));
146 assertEquals(expectedResult, buf.toString());
147 }
148
149
150 builder = new StringBuilder(replaceTemplate);
151 assertTrue(sub.replaceIn(builder));
152 assertEquals(expectedResult, builder.toString());
153 if (substring) {
154 builder = new StringBuilder(replaceTemplate);
155 assertTrue(sub.replaceIn(builder, 1, builder.length() - 2));
156 assertEquals(expectedResult, builder.toString());
157 }
158
159
160 bld = new TextStringBuilder(replaceTemplate);
161 assertTrue(sub.replaceIn(bld));
162 assertEquals(expectedResult, bld.toString());
163 if (substring) {
164 bld = new TextStringBuilder(replaceTemplate);
165 assertTrue(sub.replaceIn(bld, 1, bld.length() - 2));
166 assertEquals(expectedResult, bld.toString());
167 }
168 }
169
170
171
172
173
174
175 protected String replace(final StringSubstitutor stringSubstitutor, final String template) throws IOException {
176 return stringSubstitutor.replace(template);
177 }
178
179 @BeforeEach
180 public void setUp() throws Exception {
181 values = new HashMap<>();
182
183 values.put("a", "1");
184 values.put("aa", "11");
185 values.put("aaa", "111");
186 values.put("b", "2");
187 values.put("bb", "22");
188 values.put("bbb", "222");
189 values.put("a2b", "b");
190
191 values.put("animal", ACTUAL_ANIMAL);
192 values.put("target", ACTUAL_TARGET);
193 }
194
195 @AfterEach
196 public void tearDown() throws Exception {
197 values = null;
198 }
199
200 @Test
201 void testConstructorNullMap() {
202 final Map<String, Object> parameters = null;
203 final StringSubstitutor s = new StringSubstitutor(parameters, "prefix", "suffix");
204 assertNull(s.getStringLookup().apply("X"));
205 assertNull(s.getStringLookup().lookup("X"));
206 }
207
208 @Test
209 void testConstructorStringSubstitutor() {
210 final StringSubstitutor source = new StringSubstitutor();
211 source.setDisableSubstitutionInValues(true);
212 source.setEnableSubstitutionInVariables(true);
213 source.setEnableUndefinedVariableException(true);
214 source.setEscapeChar('e');
215 source.setValueDelimiter('d');
216 source.setVariablePrefix('p');
217 source.setVariableResolver(StringLookupFactory.INSTANCE.nullStringLookup());
218 source.setVariableSuffix('s');
219
220 final StringSubstitutor target = new StringSubstitutor(source);
221
222 assertTrue(target.isDisableSubstitutionInValues());
223 assertTrue(target.isEnableSubstitutionInVariables());
224 assertTrue(target.isEnableUndefinedVariableException());
225 assertEquals('e', target.getEscapeChar());
226 assertTrue(target.getValueDelimiterMatcher().toString().endsWith("['d']"), target.getValueDelimiterMatcher().toString());
227 assertTrue(target.getVariablePrefixMatcher().toString().endsWith("['p']"), target.getValueDelimiterMatcher().toString());
228 assertTrue(target.getVariableSuffixMatcher().toString().endsWith("['s']"), target.getValueDelimiterMatcher().toString());
229 }
230
231 @Test
232 void testDetectsCyclicSubstitution() {
233 final Map<String, String> map = new HashMap<>();
234 map.put("name", "<name>");
235 assertThrows(IllegalStateException.class, () -> StringSubstitutor.replace("Hi <name>!", map, "<", ">"));
236 }
237
238
239
240
241 @Test
242 void testGetSetEscape() {
243 final StringSubstitutor sub = new StringSubstitutor();
244 assertEquals('$', sub.getEscapeChar());
245 sub.setEscapeChar('<');
246 assertEquals('<', sub.getEscapeChar());
247 }
248
249
250
251
252 @Test
253 void testLANG1055() {
254 System.setProperty("test_key", "test_value");
255
256 final String expected = StringSubstitutor.replace("test_key=${test_key}", System.getProperties());
257 final String actual = StringSubstitutor.replaceSystemProperties("test_key=${test_key}");
258 assertEquals(expected, actual);
259 }
260
261
262
263
264 @Test
265 void testReplace_JiraText178_WeirdPatterns1() throws IOException {
266 doNotReplace("$${");
267 doNotReplace("$${a");
268 doNotReplace("$$${");
269 doNotReplace("$$${a");
270 doNotReplace("$${${a");
271 doNotReplace("${${a}");
272 doNotReplace("${$${a}");
273 }
274
275
276
277
278 @Test
279 void testReplace_JiraText178_WeirdPatterns2() throws IOException {
280 doReplace("${1}", "$${${a}}", false);
281 }
282
283
284
285
286 @Test
287 @Disabled
288 void testReplace_JiraText178_WeirdPatterns3() throws IOException {
289 doReplace("${${a}", "$${${a}", false);
290 }
291
292
293
294
295 @Test
296 void testReplaceAdjacentAtEnd() throws IOException {
297 values.put("code", "GBP");
298 values.put("amount", "12.50");
299 final StringSubstitutor sub = new StringSubstitutor(values);
300 assertEqualsCharSeq("Amount is GBP12.50", replace(sub, "Amount is ${code}${amount}"));
301 }
302
303
304
305
306 @Test
307 void testReplaceAdjacentAtStart() throws IOException {
308 values.put("code", "GBP");
309 values.put("amount", "12.50");
310 final StringSubstitutor sub = new StringSubstitutor(values);
311 assertEqualsCharSeq("GBP12.50 charged", replace(sub, "${code}${amount} charged"));
312 }
313
314
315
316
317 @Test
318 void testReplaceChangedMap() throws IOException {
319 final StringSubstitutor sub = new StringSubstitutor(values);
320
321 final String template = CLASSIC_TEMPLATE;
322 assertEqualsCharSeq(CLASSIC_RESULT, replace(sub, template));
323
324 values.put("target", "moon");
325 assertEqualsCharSeq("The quick brown fox jumps over the moon.", replace(sub, template));
326 }
327
328
329
330
331 @Test
332 void testReplaceComplexEscaping() throws IOException {
333 doReplace("${1}", "$${${a}}", false);
334 doReplace("${11}", "$${${aa}}", false);
335 doReplace("${111}", "$${${aaa}}", false);
336 doReplace("${quick brown fox}", "$${${animal}}", false);
337 doReplace("The ${quick brown fox} jumps over the lazy dog.", "The $${${animal}} jumps over the ${target}.", true);
338 doReplace("${${a}}", "$${$${a}}", false);
339 doReplace("${${aa}}", "$${$${aa}}", false);
340 doReplace("${${aaa}}", "$${$${aaa}}", false);
341 doReplace("${${animal}}", "$${$${animal}}", false);
342 doReplace(".${${animal}}", ".$${$${animal}}", false);
343 doReplace("${${animal}}.", "$${$${animal}}.", false);
344 doReplace(".${${animal}}.", ".$${$${animal}}.", false);
345 doReplace("The ${${animal}} jumps over the lazy dog.", "The $${$${animal}} jumps over the ${target}.", true);
346 doReplace("The ${quick brown fox} jumps over the lazy dog. ${1234567890}.",
347 "The $${${animal}} jumps over the ${target}. $${${undefined.number:-1234567890}}.", true);
348 }
349
350
351
352
353 @Test
354 void testReplaceEmptyKey() throws IOException {
355 doReplace("The ${} jumps over the lazy dog.", "The ${} jumps over the ${target}.", true);
356 }
357
358
359
360
361 @Test
362 void testReplaceEmptyKeyExtraFirst() throws IOException {
363 assertEqualsCharSeq("." + EMPTY_EXPR, replace(new StringSubstitutor(values), "." + EMPTY_EXPR));
364 }
365
366
367
368
369 @Test
370 void testReplaceEmptyKeyExtraLast() throws IOException {
371 assertEqualsCharSeq(EMPTY_EXPR + ".", replace(new StringSubstitutor(values), EMPTY_EXPR + "."));
372 }
373
374
375
376
377 @Test
378 void testReplaceEmptyKeyOnly() throws IOException {
379 assertEquals(EMPTY_EXPR, replace(new StringSubstitutor(values), EMPTY_EXPR));
380 }
381
382
383
384
385 @Test
386 void testReplaceEmptyKeyShortest() throws IOException {
387 doNotReplace(EMPTY_EXPR);
388 }
389
390
391
392
393 @Test
394 void testReplaceEmptyKeyWithDefault() throws IOException {
395 doReplace("The animal jumps over the lazy dog.", "The ${:-animal} jumps over the ${target}.", true);
396 }
397
398
399
400
401 @Test
402 void testReplaceEmptyKeyWithDefaultOnly() throws IOException {
403 doReplace("animal", "${:-animal}", false);
404 }
405
406
407
408
409 @Test
410 void testReplaceEmptyKeyWithDefaultOnlyEmpty() throws IOException {
411 doReplace("", "${:-}", false);
412 }
413
414
415
416
417 @Test
418 void testReplaceEmptyKeyWithDefaultOnlyShortest() throws IOException {
419 doReplace("a", "${:-a}", false);
420 }
421
422
423
424
425 @Test
426 void testReplaceEmptyString() throws IOException {
427 doNotReplace(StringUtils.EMPTY);
428 }
429
430
431
432
433 @Test
434 void testReplaceEscaping() throws IOException {
435 doReplace("The ${animal} jumps over the lazy dog.", "The $${animal} jumps over the ${target}.", true);
436 doReplace("${a}", "$${a}", false);
437 doReplace("${a${a}}", "$${a$${a}}", false);
438 doReplace("${a${a${a}}}", "$${a$${a$${a}}}", false);
439 }
440
441
442
443
444 @Test
445 void testReplaceFailOnUndefinedVariable() throws IOException {
446 values.put("animal.1", "fox");
447 values.put("animal.2", "mouse");
448 values.put("species", "2");
449 final StringSubstitutor sub = new StringSubstitutor(values);
450 sub.setEnableUndefinedVariableException(true);
451
452 assertEquals("Cannot resolve variable 'animal.${species' (enableSubstitutionInVariables=false).",
453 assertThrows(IllegalArgumentException.class, () -> replace(sub, "The ${animal.${species}} jumps over the ${target}.")).getMessage());
454
455 assertEquals("Cannot resolve variable 'animal.${species:-1' (enableSubstitutionInVariables=false).",
456 assertThrows(IllegalArgumentException.class, () -> replace(sub, "The ${animal.${species:-1}} jumps over the ${target}.")).getMessage());
457
458 assertEquals("Cannot resolve variable 'unknown' (enableSubstitutionInVariables=false).",
459 assertThrows(IllegalArgumentException.class, () -> replace(sub, "The ${test:-statement} is a sample for missing ${unknown}.")).getMessage());
460
461
462 assertEqualsCharSeq("The statement is a sample for missing variable.",
463 replace(sub, "The ${test:-statement} is a sample for missing ${unknown:-variable}."));
464
465 assertEqualsCharSeq("The fox jumps over the lazy dog.", replace(sub, "The ${animal.1} jumps over the ${target}."));
466 }
467
468
469
470
471 @Test
472 void testReplaceFailOnUndefinedVariableWithReplaceInVariable() throws IOException {
473 values.put("animal.1", "fox");
474 values.put("animal.2", "mouse");
475 values.put("species", "2");
476 values.put("statement.1", "2");
477 values.put("recursive", "1");
478 values.put("word", "variable");
479 values.put("testok.2", "statement");
480 final StringSubstitutor sub = new StringSubstitutor(values);
481 sub.setEnableUndefinedVariableException(true);
482 sub.setEnableSubstitutionInVariables(true);
483
484 assertEqualsCharSeq("The mouse jumps over the lazy dog.", replace(sub, "The ${animal.${species}} jumps over the ${target}."));
485 values.put("species", "1");
486 assertEqualsCharSeq("The fox jumps over the lazy dog.", replace(sub, "The ${animal.${species}} jumps over the ${target}."));
487
488
489 assertEquals("Cannot resolve variable 'statement' (enableSubstitutionInVariables=true).",
490 assertThrows(IllegalArgumentException.class, () -> replace(sub, "The ${test.${statement}} is a sample for missing ${word}.")).getMessage());
491
492
493 assertEquals("Cannot resolve variable 'test.2' (enableSubstitutionInVariables=true).",
494 assertThrows(IllegalArgumentException.class, () -> replace(sub, "The ${test.${statement.${recursive}}} is a sample for missing ${word}."))
495 .getMessage());
496
497 assertEqualsCharSeq("statement", replace(sub, "${testok.${statement.${recursive}}}"));
498
499 assertEqualsCharSeq("${testok.2}", replace(sub, "$${testok.${statement.${recursive}}}"));
500
501 assertEqualsCharSeq("The statement is a sample for missing variable.",
502 replace(sub, "The ${testok.${statement.${recursive}}} is a sample for missing ${word}."));
503 }
504
505
506
507
508 @Test
509 void testReplaceIncompletePrefix() throws IOException {
510 doReplace("The {animal} jumps over the lazy dog.", "The {animal} jumps over the ${target}.", true);
511 }
512
513 @Test
514 void testReplaceInTakingStringBufferWithNonNull() {
515 final StringSubstitutor strSubstitutor = new StringSubstitutor(new HashMap<>(), "WV@i#y?N*[", "WV@i#y?N*[", '*');
516
517 assertFalse(strSubstitutor.isPreserveEscapes());
518 assertFalse(strSubstitutor.replaceIn(new StringBuffer("WV@i#y?N*[")));
519 assertEquals('*', strSubstitutor.getEscapeChar());
520 }
521
522 @Test
523 void testReplaceInTakingStringBuilderWithNonNull() {
524 final StringLookup strLookup = StringLookupFactory.INSTANCE.systemPropertyStringLookup();
525 final StringSubstitutor strSubstitutor = new StringSubstitutor(strLookup, "b<H", "b<H", '\'');
526 final StringBuilder stringBuilder = new StringBuilder((CharSequence) "b<H");
527
528 assertEquals('\'', strSubstitutor.getEscapeChar());
529 assertFalse(strSubstitutor.replaceIn(stringBuilder));
530 }
531
532 @Test
533 void testReplaceInTakingStringBuilderWithNull() {
534 final Map<String, Object> map = new HashMap<>();
535 final StringSubstitutor strSubstitutor = new StringSubstitutor(map, StringUtils.EMPTY, StringUtils.EMPTY, 'T', "K+<'f");
536
537 assertFalse(strSubstitutor.replaceIn((StringBuilder) null));
538 }
539
540 @Test
541 void testReplaceInTakingTwoAndThreeIntsReturningFalse() {
542 final Map<String, Object> hashMap = new HashMap<>();
543 final StringLookup mapStringLookup = StringLookupFactory.INSTANCE.mapStringLookup(hashMap);
544 final StringMatcher strMatcher = StringMatcherFactory.INSTANCE.tabMatcher();
545 final StringSubstitutor strSubstitutor = new StringSubstitutor(mapStringLookup, strMatcher, strMatcher, 'b', strMatcher);
546
547 assertFalse(strSubstitutor.replaceIn((StringBuilder) null, 1315, -1369));
548 assertEquals('b', strSubstitutor.getEscapeChar());
549 assertFalse(strSubstitutor.isPreserveEscapes());
550 }
551
552
553
554
555 @Test
556 void testReplaceInVariable() throws IOException {
557 values.put("animal.1", "fox");
558 values.put("animal.2", "mouse");
559 values.put("species", "2");
560 final StringSubstitutor sub = new StringSubstitutor(values);
561 sub.setEnableSubstitutionInVariables(true);
562 assertEqualsCharSeq("The mouse jumps over the lazy dog.", replace(sub, "The ${animal.${species}} jumps over the ${target}."));
563 values.put("species", "1");
564 assertEqualsCharSeq("The fox jumps over the lazy dog.", replace(sub, "The ${animal.${species}} jumps over the ${target}."));
565 assertEqualsCharSeq("The fox jumps over the lazy dog.",
566 replace(sub, "The ${unknown.animal.${unknown.species:-1}:-fox} jumps over the ${unknow.target:-lazy dog}."));
567 }
568
569
570
571
572 @Test
573 void testReplaceInVariableDisabled() throws IOException {
574 values.put("animal.1", "fox");
575 values.put("animal.2", "mouse");
576 values.put("species", "2");
577 final StringSubstitutor sub = new StringSubstitutor(values);
578 assertEqualsCharSeq("The ${animal.${species}} jumps over the lazy dog.", replace(sub, "The ${animal.${species}} jumps over the ${target}."));
579 assertEqualsCharSeq("The ${animal.${species:-1}} jumps over the lazy dog.", replace(sub, "The ${animal.${species:-1}} jumps over the ${target}."));
580 }
581
582
583
584
585 @Test
586 void testReplaceInVariableRecursive() throws IOException {
587 values.put("animal.2", "brown fox");
588 values.put("animal.1", "white mouse");
589 values.put("color", "white");
590 values.put("species.white", "1");
591 values.put("species.brown", "2");
592 final StringSubstitutor sub = new StringSubstitutor(values);
593 sub.setEnableSubstitutionInVariables(true);
594 assertEqualsCharSeq("white mouse", replace(sub, "${animal.${species.${color}}}"));
595 assertEqualsCharSeq("The white mouse jumps over the lazy dog.", replace(sub, "The ${animal.${species.${color}}} jumps over the ${target}."));
596 assertEqualsCharSeq("The brown fox jumps over the lazy dog.",
597 replace(sub, "The ${animal.${species.${unknownColor:-brown}}} jumps over the ${target}."));
598 }
599
600
601
602
603 @Test
604 void testReplaceKeyStartChars() throws IOException {
605 final String substring = StringSubstitutor.DEFAULT_VAR_START + "a";
606 assertEqualsCharSeq(substring, replace(new StringSubstitutor(values), substring));
607 }
608
609
610
611
612 @Test
613 void testReplaceKeyStartChars1Only() throws IOException {
614 final String substring = StringSubstitutor.DEFAULT_VAR_START.substring(0, 1);
615 assertEqualsCharSeq(substring, replace(new StringSubstitutor(values), substring));
616 }
617
618
619
620
621 @Test
622 void testReplaceKeyStartChars2Only() throws IOException {
623 final String substring = StringSubstitutor.DEFAULT_VAR_START.substring(0, 2);
624 assertEqualsCharSeq(substring, replace(new StringSubstitutor(values), substring));
625 }
626
627
628
629
630 @Test
631 void testReplaceNoPrefixNoSuffix() throws IOException {
632 doReplace("The animal jumps over the lazy dog.", "The animal jumps over the ${target}.", true);
633 }
634
635
636
637
638 @Test
639 void testReplaceNoPrefixSuffix() throws IOException {
640 doReplace("The animal} jumps over the lazy dog.", "The animal} jumps over the ${target}.", true);
641 }
642
643
644
645
646 @Test
647 void testReplaceNoVariables() throws IOException {
648 doNotReplace("The balloon arrived.");
649 }
650
651
652
653
654 @Test
655 void testReplaceNull() throws IOException {
656 doNotReplace(null);
657 }
658
659
660
661
662 @Test
663 void testReplacePartialString_noReplace() {
664 final StringSubstitutor sub = new StringSubstitutor();
665 assertEqualsCharSeq("${animal} jumps", sub.replace(CLASSIC_TEMPLATE, 4, 15));
666 }
667
668
669
670
671 @Test
672 void testReplacePrefixNoSuffix() throws IOException {
673 doReplace("The ${animal jumps over the ${target} lazy dog.", "The ${animal jumps over the ${target} ${target}.", true);
674 }
675
676
677
678
679 @Test
680 void testReplaceRecursive() throws IOException {
681 values.put("animal", "${critter}");
682 values.put("target", "${pet}");
683 values.put("pet", "${petCharacteristic} dog");
684 values.put("petCharacteristic", "lazy");
685 values.put("critter", "${critterSpeed} ${critterColor} ${critterType}");
686 values.put("critterSpeed", "quick");
687 values.put("critterColor", "brown");
688 values.put("critterType", "fox");
689 doReplace(CLASSIC_RESULT, CLASSIC_TEMPLATE, true);
690
691 values.put("pet", "${petCharacteristicUnknown:-lazy} dog");
692 doReplace(CLASSIC_RESULT, CLASSIC_TEMPLATE, true);
693 }
694
695
696
697
698 @Test
699 void testReplaceSimple() throws IOException {
700 doReplace(CLASSIC_RESULT, CLASSIC_TEMPLATE, true);
701 }
702
703
704
705
706 @Test
707 void testReplaceSimpleKeySize1() throws IOException {
708 doReplace("1", "${a}", false);
709 }
710
711
712
713
714 @Test
715 void testReplaceSimpleKeySize2() throws IOException {
716 doReplace("11", "${aa}", false);
717 }
718
719
720
721
722 @Test
723 void testReplaceSimpleKeySize3() throws IOException {
724 doReplace("111", "${aaa}", false);
725 }
726
727 @Test
728 void testReplaceTakingCharSequenceReturningNull() {
729 final StringSubstitutor strSubstitutor = new StringSubstitutor((StringLookup) null);
730
731 assertNull(strSubstitutor.replace((CharSequence) null));
732 assertFalse(strSubstitutor.isPreserveEscapes());
733 assertEquals('$', strSubstitutor.getEscapeChar());
734 }
735
736 @Test
737 void testReplaceTakingThreeArgumentsThrowsNullPointerException() {
738 assertThrows(NullPointerException.class, () -> StringSubstitutor.replace(null, (Properties) null));
739 }
740
741 @Test
742 void testReplaceThrowsStringIndexOutOfBoundsException() {
743 final StringSubstitutor sub = new StringSubstitutor();
744
745
746 final char[] emptyCharArray = {};
747
748 assertThrows(StringIndexOutOfBoundsException.class, () -> sub.replace(emptyCharArray, 0, 1));
749
750 assertThrows(StringIndexOutOfBoundsException.class, () -> sub.replace(emptyCharArray, 1, 0));
751
752
753
754 assertThrows(StringIndexOutOfBoundsException.class, () -> sub.replace("", 1, 1));
755
756 assertThrows(StringIndexOutOfBoundsException.class, () -> sub.replace("", 0, 1));
757 }
758
759
760
761
762 @Test
763 void testReplaceToIdentical() throws IOException {
764 values.put("animal", "$${${thing}}");
765 values.put("thing", "animal");
766 doReplace("The ${animal} jumps.", "The ${animal} jumps.", true);
767 }
768
769
770
771
772 @Test
773 void testReplaceUnknownKey() throws IOException {
774 doReplace("The ${person} jumps over the lazy dog.", "The ${person} jumps over the ${target}.", true);
775 }
776
777
778
779
780 @Test
781 void testReplaceUnknownKeyDefaultValue() throws IOException {
782 doReplace("The ${person} jumps over the lazy dog. 1234567890.", "The ${person} jumps over the ${target}. ${undefined.number:-1234567890}.", true);
783 }
784
785
786
787
788 @Test
789 void testReplaceUnknownKeyOnly() throws IOException {
790 final String expected = "${person}";
791 assertEqualsCharSeq(expected, replace(new StringSubstitutor(values), expected));
792 }
793
794
795
796
797 @Test
798 void testReplaceUnknownKeyOnlyExtraFirst() throws IOException {
799 final String expected = ".${person}";
800 assertEqualsCharSeq(expected, replace(new StringSubstitutor(values), expected));
801 }
802
803
804
805
806 @Test
807 void testReplaceUnknownKeyOnlyExtraLast() throws IOException {
808 final String expected = "${person}.";
809 assertEqualsCharSeq(expected, replace(new StringSubstitutor(values), expected));
810 }
811
812
813
814
815 @Test
816 void testReplaceUnknownShortestKeyOnly() throws IOException {
817 final String expected = "${U}";
818 assertEqualsCharSeq(expected, replace(new StringSubstitutor(values), expected));
819 }
820
821
822
823
824 @Test
825 void testReplaceUnknownShortestKeyOnlyExtraFirst() throws IOException {
826 final String expected = ".${U}";
827 assertEqualsCharSeq(expected, replace(new StringSubstitutor(values), expected));
828 }
829
830
831
832
833 @Test
834 void testReplaceUnknownShortestKeyOnlyExtraLast() throws IOException {
835 final String expected = "${U}.";
836 assertEqualsCharSeq(expected, replace(new StringSubstitutor(values), expected));
837 }
838
839
840
841
842 @Test
843 void testReplaceVariablesCount1() throws IOException {
844 doReplace(ACTUAL_ANIMAL, "${animal}", false);
845 }
846
847
848
849
850 @Test
851 void testReplaceVariablesCount1Escaping2To1() throws IOException {
852 doReplace("${a}", "$${a}", false);
853 doReplace("${animal}", "$${animal}", false);
854 }
855
856
857
858
859 @Test
860 void testReplaceVariablesCount1Escaping3To2() throws IOException {
861 doReplace("$${a}", "$$${a}", false);
862 doReplace("$${animal}", "$$${animal}", false);
863 }
864
865
866
867
868 @Test
869 void testReplaceVariablesCount1Escaping4To3() throws IOException {
870 doReplace("$$${a}", "$$$${a}", false);
871 doReplace("$$${animal}", "$$$${animal}", false);
872 }
873
874
875
876
877 @Test
878 void testReplaceVariablesCount1Escaping5To4() throws IOException {
879 doReplace("$$$${a}", "$$$$${a}", false);
880 doReplace("$$$${animal}", "$$$$${animal}", false);
881 }
882
883
884
885
886 @Test
887 void testReplaceVariablesCount1Escaping6To4() throws IOException {
888 doReplace("$$$$${a}", "$$$$$${a}", false);
889 doReplace("$$$$${animal}", "$$$$$${animal}", false);
890 }
891
892
893
894
895 @Test
896 void testReplaceVariablesCount2() throws IOException {
897
898 doReplace("1122", "${aa}${bb}", false);
899 doReplace(ACTUAL_ANIMAL + ACTUAL_ANIMAL, "${animal}${animal}", false);
900 doReplace(ACTUAL_TARGET + ACTUAL_TARGET, "${target}${target}", false);
901 doReplace(ACTUAL_ANIMAL + ACTUAL_TARGET, "${animal}${target}", false);
902 }
903
904
905
906
907 @Test
908 void testReplaceVariablesCount2NonAdjacent() throws IOException {
909 doReplace("1 2", "${a} ${b}", false);
910 doReplace("11 22", "${aa} ${bb}", false);
911 doReplace(ACTUAL_ANIMAL + " " + ACTUAL_ANIMAL, "${animal} ${animal}", false);
912 doReplace(ACTUAL_ANIMAL + " " + ACTUAL_ANIMAL, "${animal} ${animal}", false);
913 doReplace(ACTUAL_ANIMAL + " " + ACTUAL_ANIMAL, "${animal} ${animal}", false);
914 }
915
916
917
918
919 @Test
920 void testReplaceVariablesCount3() throws IOException {
921 doReplace("121", "${a}${b}${a}", false);
922 doReplace("112211", "${aa}${bb}${aa}", false);
923 doReplace(ACTUAL_ANIMAL + ACTUAL_ANIMAL + ACTUAL_ANIMAL, "${animal}${animal}${animal}", false);
924 doReplace(ACTUAL_TARGET + ACTUAL_TARGET + ACTUAL_TARGET, "${target}${target}${target}", false);
925 }
926
927
928
929
930 @Test
931 void testReplaceVariablesCount3NonAdjacent() throws IOException {
932 doReplace("1 2 1", "${a} ${b} ${a}", false);
933 doReplace("11 22 11", "${aa} ${bb} ${aa}", false);
934 doReplace(ACTUAL_ANIMAL + " " + ACTUAL_ANIMAL + " " + ACTUAL_ANIMAL, "${animal} ${animal} ${animal}", false);
935 doReplace(ACTUAL_TARGET + " " + ACTUAL_TARGET + " " + ACTUAL_TARGET, "${target} ${target} ${target}", false);
936 }
937
938
939
940
941 @Test
942 void testReplaceWeirdPattens() throws IOException {
943 doNotReplace(StringUtils.EMPTY);
944 doNotReplace(EMPTY_EXPR);
945 doNotReplace("${ }");
946 doNotReplace("${\t}");
947 doNotReplace("${\n}");
948 doNotReplace("${\b}");
949 doNotReplace("${");
950 doNotReplace("$}");
951 doNotReplace("$$}");
952 doNotReplace("}");
953 doNotReplace("${}$");
954 doNotReplace("${}$$");
955 doNotReplace("${${");
956 doNotReplace("${${}}");
957 doNotReplace("${$${}}");
958 doNotReplace("${$$${}}");
959 doNotReplace("${$$${$}}");
960 doNotReplace("${${}}");
961 doNotReplace("${${ }}");
962
963 doNotReplace("${$${a}}");
964 doNotReplace("${$$${a}}");
965 doNotReplace("${${a}}");
966 doNotReplace("${${${a}");
967 doNotReplace("${ ${a}");
968 doNotReplace("${ ${ ${a}");
969
970 doReplace("${1}", "$${${a}}", false);
971 doReplace("${ 1}", "$${ ${a}}", false);
972 doReplace("${12}", "$${${a}${b}}", false);
973 doReplace("${ 1 2 }", "$${ ${a} ${b} }", false);
974 doReplace("${${${a}2", "${${${a}${b}", false);
975 }
976
977
978
979
980 @Test
981 void testResolveVariable() {
982 final TextStringBuilder builder = new TextStringBuilder("Hi ${name}!");
983 final Map<String, String> map = new HashMap<>();
984 map.put("name", "commons");
985 final StringSubstitutor sub = new StringSubstitutor(map) {
986 @Override
987 protected String resolveVariable(final String variableName, final TextStringBuilder buf, final int startPos, final int endPos) {
988 assertEquals("name", variableName);
989 assertSame(builder, buf);
990 assertEquals(3, startPos);
991 assertEquals(10, endPos);
992 return "jakarta";
993 }
994 };
995 sub.replaceIn(builder);
996 assertEqualsCharSeq("Hi jakarta!", builder.toString());
997 }
998
999 @Test
1000 void testSamePrefixAndSuffix() {
1001 final Map<String, String> map = new HashMap<>();
1002 map.put("greeting", "Hello");
1003 map.put(" there ", "XXX");
1004 map.put("name", "commons");
1005 assertEqualsCharSeq("Hi commons!", StringSubstitutor.replace("Hi @name@!", map, "@", "@"));
1006 assertEqualsCharSeq("Hello there commons!", StringSubstitutor.replace("@greeting@ there @name@!", map, "@", "@"));
1007 }
1008
1009
1010
1011
1012 @Test
1013 void testStaticReplace() {
1014 final Map<String, String> map = new HashMap<>();
1015 map.put("name", "commons");
1016 assertEqualsCharSeq("Hi commons!", StringSubstitutor.replace("Hi ${name}!", map));
1017 }
1018
1019
1020
1021
1022 @Test
1023 void testStaticReplacePrefixSuffix() {
1024 final Map<String, String> map = new HashMap<>();
1025 map.put("name", "commons");
1026 assertEqualsCharSeq("Hi commons!", StringSubstitutor.replace("Hi <name>!", map, "<", ">"));
1027 }
1028
1029
1030
1031
1032 @Test
1033 void testStaticReplaceSystemProperties() {
1034 final TextStringBuilder buf = new TextStringBuilder();
1035 buf.append("Hi ").append(SystemProperties.getUserName());
1036 buf.append(", you are working with ");
1037 buf.append(SystemProperties.getOsName());
1038 buf.append(", your home directory is ");
1039 buf.append(SystemProperties.getUserHome()).append('.');
1040 assertEqualsCharSeq(buf.toString(),
1041 StringSubstitutor.replaceSystemProperties("Hi ${user.name}, you are " + "working with ${os.name}, your home directory is ${user.home}."));
1042 }
1043
1044
1045
1046
1047 @Test
1048 void testStaticReplaceSystemPropertiesWithUpdate() {
1049 System.setProperty("foo", "bar1");
1050 try {
1051 assertEqualsCharSeq("bar1", StringSubstitutor.replaceSystemProperties("${foo}"));
1052 System.setProperty("foo", "bar2");
1053 assertEqualsCharSeq("bar2", StringSubstitutor.replaceSystemProperties("${foo}"));
1054 } finally {
1055 System.getProperties().remove("foo");
1056 }
1057 }
1058
1059
1060
1061
1062 @Test
1063 void testSubstituteDefaultProperties() {
1064 final String org = "${doesnotwork}";
1065 System.setProperty("doesnotwork", "It works!");
1066
1067
1068 final Properties props = new Properties(System.getProperties());
1069
1070 assertEqualsCharSeq("It works!", StringSubstitutor.replace(org, props));
1071 }
1072
1073 @Test
1074 void testSubstitutePreserveEscape() throws IOException {
1075 final String org = "${not-escaped} $${escaped}";
1076 final Map<String, String> map = new HashMap<>();
1077 map.put("not-escaped", "value");
1078
1079 final StringSubstitutor sub = new StringSubstitutor(map, "${", "}", '$');
1080 assertFalse(sub.isPreserveEscapes());
1081 assertEqualsCharSeq("value ${escaped}", replace(sub, org));
1082
1083 sub.setPreserveEscapes(true);
1084 assertTrue(sub.isPreserveEscapes());
1085 assertEqualsCharSeq("value $${escaped}", replace(sub, org));
1086 }
1087
1088 }