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