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