1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.lang3.text;
18
19 import java.util.ArrayList;
20 import java.util.Enumeration;
21 import java.util.HashMap;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Objects;
25 import java.util.Properties;
26
27 import org.apache.commons.lang3.StringUtils;
28
29 /**
30 * Substitutes variables within a string by values.
31 * <p>
32 * This class takes a piece of text and substitutes all the variables within it.
33 * The default definition of a variable is {@code ${variableName}}.
34 * The prefix and suffix can be changed via constructors and set methods.
35 * </p>
36 * <p>
37 * Variable values are typically resolved from a map, but could also be resolved
38 * from system properties, or by supplying a custom variable resolver.
39 * </p>
40 * <p>
41 * The simplest example is to use this class to replace Java System properties. For example:
42 * </p>
43 * <pre>
44 * StrSubstitutor.replaceSystemProperties(
45 * "You are running with java.version = ${java.version} and os.name = ${os.name}.");
46 * </pre>
47 * <p>
48 * Typical usage of this class follows the following pattern: First an instance is created
49 * and initialized with the map that contains the values for the available variables.
50 * If a prefix and/or suffix for variables should be used other than the default ones,
51 * the appropriate settings can be performed. After that the {@code replace()}
52 * method can be called passing in the source text for interpolation. In the returned
53 * text all variable references (as long as their values are known) will be resolved.
54 * The following example demonstrates this:
55 * </p>
56 * <pre>
57 * Map valuesMap = HashMap();
58 * valuesMap.put("animal", "quick brown fox");
59 * valuesMap.put("target", "lazy dog");
60 * String templateString = "The ${animal} jumps over the ${target}.";
61 * StrSubstitutor sub = new StrSubstitutor(valuesMap);
62 * String resolvedString = sub.replace(templateString);
63 * </pre>
64 * yielding:
65 * <pre>
66 * The quick brown fox jumps over the lazy dog.
67 * </pre>
68 * <p>
69 * Also, this class allows to set a default value for unresolved variables.
70 * The default value for a variable can be appended to the variable name after the variable
71 * default value delimiter. The default value of the variable default value delimiter is ':-',
72 * as in bash and other *nix shells, as those are arguably where the default ${} delimiter set originated.
73 * The variable default value delimiter can be manually set by calling {@link #setValueDelimiterMatcher(StrMatcher)},
74 * {@link #setValueDelimiter(char)} or {@link #setValueDelimiter(String)}.
75 * The following shows an example with variable default value settings:
76 * </p>
77 * <pre>
78 * Map valuesMap = HashMap();
79 * valuesMap.put("animal", "quick brown fox");
80 * valuesMap.put("target", "lazy dog");
81 * String templateString = "The ${animal} jumps over the ${target}. ${undefined.number:-1234567890}.";
82 * StrSubstitutor sub = new StrSubstitutor(valuesMap);
83 * String resolvedString = sub.replace(templateString);
84 * </pre>
85 * <p>
86 * yielding:
87 * </p>
88 * <pre>
89 * The quick brown fox jumps over the lazy dog. 1234567890.
90 * </pre>
91 * <p>
92 * In addition to this usage pattern there are some static convenience methods that
93 * cover the most common use cases. These methods can be used without the need of
94 * manually creating an instance. However if multiple replace operations are to be
95 * performed, creating and reusing an instance of this class will be more efficient.
96 * </p>
97 * <p>
98 * Variable replacement works in a recursive way. Thus, if a variable value contains
99 * a variable then that variable will also be replaced. Cyclic replacements are
100 * detected and will cause an exception to be thrown.
101 * </p>
102 * <p>
103 * Sometimes the interpolation's result must contain a variable prefix. As an example
104 * take the following source text:
105 * </p>
106 * <pre>
107 * The variable ${${name}} must be used.
108 * </pre>
109 * <p>
110 * Here only the variable's name referred to in the text should be replaced resulting
111 * in the text (assuming that the value of the {@code name} variable is {@code x}):
112 * </p>
113 * <pre>
114 * The variable ${x} must be used.
115 * </pre>
116 * <p>
117 * To achieve this effect there are two possibilities: Either set a different prefix
118 * and suffix for variables which do not conflict with the result text you want to
119 * produce. The other possibility is to use the escape character, by default '$'.
120 * If this character is placed before a variable reference, this reference is ignored
121 * and won't be replaced. For example:
122 * </p>
123 * <pre>
124 * The variable $${${name}} must be used.
125 * </pre>
126 * <p>
127 * In some complex scenarios you might even want to perform substitution in the
128 * names of variables, for instance:
129 * </p>
130 * <pre>
131 * ${jre-${java.specification.version}}
132 * </pre>
133 * <p>
134 * {@link StrSubstitutor} supports this recursive substitution in variable
135 * names, but it has to be enabled explicitly by setting the
136 * {@link #setEnableSubstitutionInVariables(boolean) enableSubstitutionInVariables}
137 * property to <strong>true</strong>.
138 * </p>
139 * <p>
140 * This class is <strong>not</strong> thread safe.
141 * </p>
142 *
143 * @since 2.2
144 * @deprecated As of <a href="https://commons.apache.org/proper/commons-lang/changes-report.html#a3.6">3.6</a>, use Apache Commons Text
145 * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/StringSubstitutor.html">
146 * StringSubstitutor</a>.
147 */
148 @Deprecated
149 public class StrSubstitutor {
150
151 /**
152 * Constant for the default escape character.
153 */
154 public static final char DEFAULT_ESCAPE = '$';
155
156 /**
157 * Constant for the default variable prefix.
158 */
159 public static final StrMatcher DEFAULT_PREFIX = StrMatcher.stringMatcher("${");
160
161 /**
162 * Constant for the default variable suffix.
163 */
164 public static final StrMatcher DEFAULT_SUFFIX = StrMatcher.stringMatcher("}");
165
166 /**
167 * Constant for the default value delimiter of a variable.
168 *
169 * @since 3.2
170 */
171 public static final StrMatcher DEFAULT_VALUE_DELIMITER = StrMatcher.stringMatcher(":-");
172
173 /**
174 * The maximum nesting depth of variable interpolation. The cyclic-substitution check only rejects a variable
175 * already on the current substitution stack; without a depth bound, deeply nested (acyclic) references and
176 * nested variable names (when {@link #isEnableSubstitutionInVariables()} is on) recurse once per level and
177 * can end in {@link StackOverflowError}.
178 */
179 private static final int MAX_SUBSTITUTION_DEPTH = 256;
180
181 /**
182 * The maximum total number of characters that variable replacement may emit during one top-level substitution.
183 * Bounds exponential acyclic fan-out (each of N references expanding to N more), which the cyclic-substitution
184 * check cannot see.
185 */
186 private static final int MAX_SUBSTITUTION_LENGTH = 16 * 1024 * 1024;
187
188 /**
189 * Replaces all the occurrences of variables in the given source object with
190 * their matching values from the map.
191 *
192 * @param <V> The type of the values in the map.
193 * @param source The source text containing the variables to substitute, null returns null.
194 * @param valueMap The map with the values, may be null.
195 * @return The result of the replace operation.
196 */
197 public static <V> String replace(final Object source, final Map<String, V> valueMap) {
198 return new StrSubstitutor(valueMap).replace(source);
199 }
200
201 /**
202 * Replaces all the occurrences of variables in the given source object with
203 * their matching values from the map. This method allows to specify a
204 * custom variable prefix and suffix.
205 *
206 * @param <V> The type of the values in the map.
207 * @param source The source text containing the variables to substitute, null returns null.
208 * @param valueMap The map with the values, may be null.
209 * @param prefix The prefix of variables, not null.
210 * @param suffix The suffix of variables, not null.
211 * @return The result of the replace operation.
212 * @throws IllegalArgumentException Thrown if the prefix or suffix is null.
213 */
214 public static <V> String replace(final Object source, final Map<String, V> valueMap, final String prefix, final String suffix) {
215 return new StrSubstitutor(valueMap, prefix, suffix).replace(source);
216 }
217
218 /**
219 * Replaces all the occurrences of variables in the given source object with their matching
220 * values from the properties.
221 *
222 * @param source The source text containing the variables to substitute, null returns null.
223 * @param valueProperties The properties with values, may be null.
224 * @return The result of the replace operation.
225 */
226 public static String replace(final Object source, final Properties valueProperties) {
227 if (valueProperties == null) {
228 return source.toString();
229 }
230 final Map<String, String> valueMap = new HashMap<>();
231 final Enumeration<?> propNames = valueProperties.propertyNames();
232 while (propNames.hasMoreElements()) {
233 final String propName = String.valueOf(propNames.nextElement());
234 final String propValue = valueProperties.getProperty(propName);
235 valueMap.put(propName, propValue);
236 }
237 return replace(source, valueMap);
238 }
239
240 /**
241 * Replaces all the occurrences of variables in the given source object with
242 * their matching values from the system properties.
243 *
244 * @param source The source text containing the variables to substitute, null returns null.
245 * @return The result of the replace operation.
246 */
247 public static String replaceSystemProperties(final Object source) {
248 return new StrSubstitutor(StrLookup.systemPropertiesLookup()).replace(source);
249 }
250
251 /**
252 * Stores the escape character.
253 */
254 private char escapeChar;
255
256 /**
257 * Stores the variable prefix.
258 */
259 private StrMatcher prefixMatcher;
260
261 /**
262 * Stores the variable suffix.
263 */
264 private StrMatcher suffixMatcher;
265
266 /**
267 * Stores the default variable value delimiter
268 */
269 private StrMatcher valueDelimiterMatcher;
270
271 /**
272 * Variable resolution is delegated to an implementor of VariableResolver.
273 */
274 private StrLookup<?> variableResolver;
275
276 /**
277 * The flag whether substitution in variable names is enabled.
278 */
279 private boolean enableSubstitutionInVariables;
280
281 /**
282 * Whether escapes should be preserved. Default is false;
283 */
284 private boolean preserveEscapes;
285
286 /**
287 * Current recursion depth of {@link #substitute(StrBuilder, int, int, List)}. Like the rest of this class,
288 * not thread safe.
289 */
290 private int substitutionDepth;
291
292 /**
293 * Total number of characters emitted by variable replacement in the current top-level substitution.
294 */
295 private long substitutionLength;
296
297 /**
298 * Creates a new instance with defaults for variable prefix and suffix
299 * and the escaping character.
300 */
301 public StrSubstitutor() {
302 this(null, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
303 }
304
305 /**
306 * Creates a new instance and initializes it. Uses defaults for variable
307 * prefix and suffix and the escaping character.
308 *
309 * @param <V> The type of the values in the map.
310 * @param valueMap The map with the variables' values, may be null.
311 */
312 public <V> StrSubstitutor(final Map<String, V> valueMap) {
313 this(StrLookup.mapLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
314 }
315
316 /**
317 * Creates a new instance and initializes it. Uses a default escaping character.
318 *
319 * @param <V> The type of the values in the map.
320 * @param valueMap The map with the variables' values, may be null.
321 * @param prefix The prefix for variables, not null.
322 * @param suffix The suffix for variables, not null.
323 * @throws IllegalArgumentException Thrown if the prefix or suffix is null.
324 */
325 public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix) {
326 this(StrLookup.mapLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE);
327 }
328
329 /**
330 * Creates a new instance and initializes it.
331 *
332 * @param <V> The type of the values in the map.
333 * @param valueMap The map with the variables' values, may be null.
334 * @param prefix The prefix for variables, not null.
335 * @param suffix The suffix for variables, not null.
336 * @param escape The escape character.
337 * @throws IllegalArgumentException Thrown if the prefix or suffix is null.
338 */
339 public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix, final char escape) {
340 this(StrLookup.mapLookup(valueMap), prefix, suffix, escape);
341 }
342
343 /**
344 * Creates a new instance and initializes it.
345 *
346 * @param <V> The type of the values in the map.
347 * @param valueMap The map with the variables' values, may be null.
348 * @param prefix The prefix for variables, not null.
349 * @param suffix The suffix for variables, not null.
350 * @param escape The escape character.
351 * @param valueDelimiter The variable default value delimiter, may be null.
352 * @throws IllegalArgumentException Thrown if the prefix or suffix is null.
353 * @since 3.2
354 */
355 public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix, final char escape, final String valueDelimiter) {
356 this(StrLookup.mapLookup(valueMap), prefix, suffix, escape, valueDelimiter);
357 }
358
359 /**
360 * Creates a new instance and initializes it.
361 *
362 * @param variableResolver The variable resolver, may be null
363 */
364 public StrSubstitutor(final StrLookup<?> variableResolver) {
365 this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
366 }
367
368 /**
369 * Creates a new instance and initializes it.
370 *
371 * @param variableResolver The variable resolver, may be null.
372 * @param prefix The prefix for variables, not null.
373 * @param suffix The suffix for variables, not null.
374 * @param escape The escape character.
375 * @throws IllegalArgumentException Thrown if the prefix or suffix is null.
376 */
377 public StrSubstitutor(final StrLookup<?> variableResolver, final String prefix, final String suffix, final char escape) {
378 setVariableResolver(variableResolver);
379 setVariablePrefix(prefix);
380 setVariableSuffix(suffix);
381 setEscapeChar(escape);
382 setValueDelimiterMatcher(DEFAULT_VALUE_DELIMITER);
383 }
384
385 /**
386 * Creates a new instance and initializes it.
387 *
388 * @param variableResolver The variable resolver, may be null.
389 * @param prefix The prefix for variables, not null.
390 * @param suffix The suffix for variables, not null.
391 * @param escape The escape character.
392 * @param valueDelimiter The variable default value delimiter string, may be null.
393 * @throws IllegalArgumentException Thrown if the prefix or suffix is null.
394 * @since 3.2
395 */
396 public StrSubstitutor(final StrLookup<?> variableResolver, final String prefix, final String suffix, final char escape, final String valueDelimiter) {
397 setVariableResolver(variableResolver);
398 setVariablePrefix(prefix);
399 setVariableSuffix(suffix);
400 setEscapeChar(escape);
401 setValueDelimiter(valueDelimiter);
402 }
403
404 /**
405 * Creates a new instance and initializes it.
406 *
407 * @param variableResolver The variable resolver, may be null.
408 * @param prefixMatcher The prefix for variables, not null.
409 * @param suffixMatcher The suffix for variables, not null.
410 * @param escape The escape character.
411 * @throws IllegalArgumentException Thrown if the prefix or suffix is null.
412 */
413 public StrSubstitutor(final StrLookup<?> variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher, final char escape) {
414 this(variableResolver, prefixMatcher, suffixMatcher, escape, DEFAULT_VALUE_DELIMITER);
415 }
416
417 /**
418 * Creates a new instance and initializes it.
419 *
420 * @param variableResolver The variable resolver, may be null.
421 * @param prefixMatcher The prefix for variables, not null.
422 * @param suffixMatcher The suffix for variables, not null.
423 * @param escape The escape character.
424 * @param valueDelimiterMatcher The variable default value delimiter matcher, may be null.
425 * @throws IllegalArgumentException Thrown if the prefix or suffix is null.
426 * @since 3.2
427 */
428 public StrSubstitutor(final StrLookup<?> variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher, final char escape,
429 final StrMatcher valueDelimiterMatcher) {
430 setVariableResolver(variableResolver);
431 setVariablePrefixMatcher(prefixMatcher);
432 setVariableSuffixMatcher(suffixMatcher);
433 setEscapeChar(escape);
434 setValueDelimiterMatcher(valueDelimiterMatcher);
435 }
436
437 /**
438 * Checks if the specified variable is already in the stack (list) of variables.
439 *
440 * @param varName The variable name to check.
441 * @param priorVariables The list of prior variables.
442 */
443 private void checkCyclicSubstitution(final String varName, final List<String> priorVariables) {
444 if (!priorVariables.contains(varName)) {
445 return;
446 }
447 final StrBuilder buf = new StrBuilder(256);
448 buf.append("Infinite loop in property interpolation of ");
449 buf.append(priorVariables.remove(0));
450 buf.append(": ");
451 buf.appendWithSeparators(priorVariables, "->");
452 throw new IllegalStateException(buf.toString());
453 }
454
455 /**
456 * Gets the escape character.
457 *
458 * @return The character used for escaping variable references
459 */
460 public char getEscapeChar() {
461 return this.escapeChar;
462 }
463
464 /**
465 * Gets the variable default value delimiter matcher currently in use.
466 * <p>
467 * The variable default value delimiter is the character or characters that delimit the
468 * variable name and the variable default value. This delimiter is expressed in terms of a matcher
469 * allowing advanced variable default value delimiter matches.
470 * </p>
471 * <p>
472 * If it returns null, then the variable default value resolution is disabled.
473 * </p>
474 *
475 * @return The variable default value delimiter matcher in use, may be null.
476 * @since 3.2
477 */
478 public StrMatcher getValueDelimiterMatcher() {
479 return valueDelimiterMatcher;
480 }
481
482 /**
483 * Gets the variable prefix matcher currently in use.
484 * <p>
485 * The variable prefix is the character or characters that identify the
486 * start of a variable. This prefix is expressed in terms of a matcher
487 * allowing advanced prefix matches.
488 * </p>
489 *
490 * @return The prefix matcher in use.
491 */
492 public StrMatcher getVariablePrefixMatcher() {
493 return prefixMatcher;
494 }
495
496 /**
497 * Gets the VariableResolver that is used to lookup variables.
498 *
499 * @return The VariableResolver.
500 */
501 public StrLookup<?> getVariableResolver() {
502 return this.variableResolver;
503 }
504
505 /**
506 * Gets the variable suffix matcher currently in use.
507 * <p>
508 * The variable suffix is the character or characters that identify the
509 * end of a variable. This suffix is expressed in terms of a matcher
510 * allowing advanced suffix matches.
511 * </p>
512 *
513 * @return The suffix matcher in use.
514 */
515 public StrMatcher getVariableSuffixMatcher() {
516 return suffixMatcher;
517 }
518
519 /**
520 * Tests whether substitution is done in variable names.
521 *
522 * @return The substitution in variable names flag.
523 * @since 3.0
524 */
525 public boolean isEnableSubstitutionInVariables() {
526 return enableSubstitutionInVariables;
527 }
528
529 /**
530 * Tests whether escapes are preserved during substitution.
531 *
532 * @return The preserve escape flag.
533 * @since 3.5
534 */
535 public boolean isPreserveEscapes() {
536 return preserveEscapes;
537 }
538
539 /**
540 * Replaces all the occurrences of variables with their matching values
541 * from the resolver using the given source array as a template.
542 * The array is not altered by this method.
543 *
544 * @param source The character array to replace in, not altered, null returns null.
545 * @return The result of the replace operation.
546 */
547 public String replace(final char[] source) {
548 if (source == null) {
549 return null;
550 }
551 final StrBuilder buf = new StrBuilder(source.length).append(source);
552 substitute(buf, 0, source.length);
553 return buf.toString();
554 }
555
556 /**
557 * Replaces all the occurrences of variables with their matching values
558 * from the resolver using the given source array as a template.
559 * The array is not altered by this method.
560 * <p>
561 * Only the specified portion of the array will be processed.
562 * The rest of the array is not processed, and is not returned.
563 * </p>
564 *
565 * @param source The character array to replace in, not altered, null returns null.
566 * @param offset The start offset within the array, must be valid.
567 * @param length The length within the array to be processed, must be valid.
568 * @return The result of the replace operation.
569 */
570 public String replace(final char[] source, final int offset, final int length) {
571 if (source == null) {
572 return null;
573 }
574 final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
575 substitute(buf, 0, length);
576 return buf.toString();
577 }
578
579 /**
580 * Replaces all the occurrences of variables with their matching values
581 * from the resolver using the given source as a template.
582 * The source is not altered by this method.
583 *
584 * @param source The buffer to use as a template, not changed, null returns null.
585 * @return The result of the replace operation.
586 * @since 3.2
587 */
588 public String replace(final CharSequence source) {
589 if (source == null) {
590 return null;
591 }
592 return replace(source, 0, source.length());
593 }
594
595 /**
596 * Replaces all the occurrences of variables with their matching values
597 * from the resolver using the given source as a template.
598 * The source is not altered by this method.
599 * <p>
600 * Only the specified portion of the buffer will be processed.
601 * The rest of the buffer is not processed, and is not returned.
602 * </p>
603 *
604 * @param source The buffer to use as a template, not changed, null returns null.
605 * @param offset The start offset within the array, must be valid.
606 * @param length The length within the array to be processed, must be valid.
607 * @return The result of the replace operation.
608 * @since 3.2
609 */
610 public String replace(final CharSequence source, final int offset, final int length) {
611 if (source == null) {
612 return null;
613 }
614 final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
615 substitute(buf, 0, length);
616 return buf.toString();
617 }
618
619 /**
620 * Replaces all the occurrences of variables in the given source object with
621 * their matching values from the resolver. The input source object is
622 * converted to a string using {@code toString} and is not altered.
623 *
624 * @param source The source to replace in, null returns null.
625 * @return The result of the replace operation.
626 */
627 public String replace(final Object source) {
628 if (source == null) {
629 return null;
630 }
631 final StrBuilder buf = new StrBuilder().append(source);
632 substitute(buf, 0, buf.length());
633 return buf.toString();
634 }
635
636 /**
637 * Replaces all the occurrences of variables with their matching values
638 * from the resolver using the given source builder as a template.
639 * The builder is not altered by this method.
640 *
641 * @param source The builder to use as a template, not changed, null returns null.
642 * @return The result of the replace operation.
643 */
644 public String replace(final StrBuilder source) {
645 if (source == null) {
646 return null;
647 }
648 final StrBuilder buf = new StrBuilder(source.length()).append(source);
649 substitute(buf, 0, buf.length());
650 return buf.toString();
651 }
652
653 /**
654 * Replaces all the occurrences of variables with their matching values
655 * from the resolver using the given source builder as a template.
656 * The builder is not altered by this method.
657 * <p>
658 * Only the specified portion of the builder will be processed.
659 * The rest of the builder is not processed, and is not returned.
660 * </p>
661 *
662 * @param source The builder to use as a template, not changed, null returns null.
663 * @param offset The start offset within the array, must be valid.
664 * @param length The length within the array to be processed, must be valid.
665 * @return The result of the replace operation.
666 */
667 public String replace(final StrBuilder source, final int offset, final int length) {
668 if (source == null) {
669 return null;
670 }
671 final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
672 substitute(buf, 0, length);
673 return buf.toString();
674 }
675
676 /**
677 * Replaces all the occurrences of variables with their matching values
678 * from the resolver using the given source string as a template.
679 *
680 * @param source The string to replace in, null returns null.
681 * @return The result of the replace operation.
682 */
683 public String replace(final String source) {
684 if (source == null) {
685 return null;
686 }
687 final StrBuilder buf = new StrBuilder(source);
688 if (!substitute(buf, 0, source.length())) {
689 return source;
690 }
691 return buf.toString();
692 }
693
694 /**
695 * Replaces all the occurrences of variables with their matching values
696 * from the resolver using the given source string as a template.
697 * <p>
698 * Only the specified portion of the string will be processed.
699 * The rest of the string is not processed, and is not returned.
700 * </p>
701 *
702 * @param source The string to replace in, null returns null.
703 * @param offset The start offset within the array, must be valid.
704 * @param length The length within the array to be processed, must be valid.
705 * @return The result of the replace operation.
706 */
707 public String replace(final String source, final int offset, final int length) {
708 if (source == null) {
709 return null;
710 }
711 final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
712 if (!substitute(buf, 0, length)) {
713 return source.substring(offset, offset + length);
714 }
715 return buf.toString();
716 }
717
718 /**
719 * Replaces all the occurrences of variables with their matching values
720 * from the resolver using the given source buffer as a template.
721 * The buffer is not altered by this method.
722 *
723 * @param source The buffer to use as a template, not changed, null returns null.
724 * @return The result of the replace operation.
725 */
726 public String replace(final StringBuffer source) {
727 if (source == null) {
728 return null;
729 }
730 final StrBuilder buf = new StrBuilder(source.length()).append(source);
731 substitute(buf, 0, buf.length());
732 return buf.toString();
733 }
734
735 /**
736 * Replaces all the occurrences of variables with their matching values
737 * from the resolver using the given source buffer as a template.
738 * The buffer is not altered by this method.
739 * <p>
740 * Only the specified portion of the buffer will be processed.
741 * The rest of the buffer is not processed, and is not returned.
742 * </p>
743 *
744 * @param source The buffer to use as a template, not changed, null returns null.
745 * @param offset The start offset within the array, must be valid.
746 * @param length The length within the array to be processed, must be valid.
747 * @return The result of the replace operation.
748 */
749 public String replace(final StringBuffer source, final int offset, final int length) {
750 if (source == null) {
751 return null;
752 }
753 final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
754 substitute(buf, 0, length);
755 return buf.toString();
756 }
757
758 /**
759 * Replaces all the occurrences of variables within the given source
760 * builder with their matching values from the resolver.
761 *
762 * @param source The builder to replace in, updated, null returns zero.
763 * @return true if altered.
764 */
765 public boolean replaceIn(final StrBuilder source) {
766 if (source == null) {
767 return false;
768 }
769 return substitute(source, 0, source.length());
770 }
771
772 /**
773 * Replaces all the occurrences of variables within the given source
774 * builder with their matching values from the resolver.
775 * <p>
776 * Only the specified portion of the builder will be processed.
777 * The rest of the builder is not processed, but it is not deleted.
778 * </p>
779 *
780 * @param source The builder to replace in, null returns zero.
781 * @param offset The start offset within the array, must be valid.
782 * @param length The length within the builder to be processed, must be valid.
783 * @return true if altered.
784 */
785 public boolean replaceIn(final StrBuilder source, final int offset, final int length) {
786 if (source == null) {
787 return false;
788 }
789 return substitute(source, offset, length);
790 }
791
792 /**
793 * Replaces all the occurrences of variables within the given source buffer
794 * with their matching values from the resolver.
795 * The buffer is updated with the result.
796 *
797 * @param source The buffer to replace in, updated, null returns zero.
798 * @return true if altered.
799 */
800 public boolean replaceIn(final StringBuffer source) {
801 if (source == null) {
802 return false;
803 }
804 return replaceIn(source, 0, source.length());
805 }
806
807 /**
808 * Replaces all the occurrences of variables within the given source buffer
809 * with their matching values from the resolver.
810 * The buffer is updated with the result.
811 * <p>
812 * Only the specified portion of the buffer will be processed.
813 * The rest of the buffer is not processed, but it is not deleted.
814 * </p>
815 *
816 * @param source The buffer to replace in, updated, null returns zero.
817 * @param offset The start offset within the array, must be valid.
818 * @param length The length within the buffer to be processed, must be valid.
819 * @return true if altered.
820 */
821 public boolean replaceIn(final StringBuffer source, final int offset, final int length) {
822 if (source == null) {
823 return false;
824 }
825 final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
826 if (!substitute(buf, 0, length)) {
827 return false;
828 }
829 source.replace(offset, offset + length, buf.toString());
830 return true;
831 }
832
833 /**
834 * Replaces all the occurrences of variables within the given source buffer
835 * with their matching values from the resolver.
836 * The buffer is updated with the result.
837 *
838 * @param source The buffer to replace in, updated, null returns zero.
839 * @return true if altered.
840 * @since 3.2
841 */
842 public boolean replaceIn(final StringBuilder source) {
843 if (source == null) {
844 return false;
845 }
846 return replaceIn(source, 0, source.length());
847 }
848
849 /**
850 * Replaces all the occurrences of variables within the given source builder
851 * with their matching values from the resolver.
852 * The builder is updated with the result.
853 * <p>
854 * Only the specified portion of the buffer will be processed.
855 * The rest of the buffer is not processed, but it is not deleted.
856 * </p>
857 *
858 * @param source The buffer to replace in, updated, null returns zero.
859 * @param offset The start offset within the array, must be valid.
860 * @param length The length within the buffer to be processed, must be valid.
861 * @return true if altered.
862 * @since 3.2
863 */
864 public boolean replaceIn(final StringBuilder source, final int offset, final int length) {
865 if (source == null) {
866 return false;
867 }
868 final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
869 if (!substitute(buf, 0, length)) {
870 return false;
871 }
872 source.replace(offset, offset + length, buf.toString());
873 return true;
874 }
875
876 /**
877 * Internal method that resolves the value of a variable.
878 * <p>
879 * Most users of this class do not need to call this method. This method is
880 * called automatically by the substitution process.
881 * </p>
882 * <p>
883 * Writers of subclasses can override this method if they need to alter
884 * how each substitution occurs. The method is passed the variable's name
885 * and must return the corresponding value. This implementation uses the
886 * {@link #getVariableResolver()} with the variable's name as the key.
887 * </p>
888 *
889 * @param variableName The name of the variable, not null.
890 * @param buf The buffer where the substitution is occurring, not null.
891 * @param startPos The start position of the variable including the prefix, valid.
892 * @param endPos The end position of the variable including the suffix, valid.
893 * @return The variable's value or {@code null} if the variable is unknown.
894 */
895 protected String resolveVariable(final String variableName, final StrBuilder buf, final int startPos, final int endPos) {
896 final StrLookup<?> resolver = getVariableResolver();
897 if (resolver == null) {
898 return null;
899 }
900 return resolver.lookup(variableName);
901 }
902
903 /**
904 * Sets a flag whether substitution is done in variable names. If set to
905 * <strong>true</strong>, the names of variables can contain other variables which are
906 * processed first before the original variable is evaluated, e.g.
907 * {@code ${jre-${java.version}}}. The default value is <strong>false</strong>.
908 *
909 * @param enableSubstitutionInVariables The new value of the flag.
910 * @since 3.0
911 */
912 public void setEnableSubstitutionInVariables(
913 final boolean enableSubstitutionInVariables) {
914 this.enableSubstitutionInVariables = enableSubstitutionInVariables;
915 }
916
917 /**
918 * Sets the escape character.
919 * If this character is placed before a variable reference in the source
920 * text, this variable will be ignored.
921 *
922 * @param escapeCharacter The escape character (0 for disabling escaping)
923 */
924 public void setEscapeChar(final char escapeCharacter) {
925 this.escapeChar = escapeCharacter;
926 }
927
928 /**
929 * Sets a flag controlling whether escapes are preserved during
930 * substitution. If set to <strong>true</strong>, the escape character is retained
931 * during substitution (e.g. {@code $${this-is-escaped}} remains
932 * {@code $${this-is-escaped}}). If set to <strong>false</strong>, the escape
933 * character is removed during substitution (e.g.
934 * {@code $${this-is-escaped}} becomes
935 * {@code ${this-is-escaped}}). The default value is <strong>false</strong>
936 *
937 * @param preserveEscapes true if escapes are to be preserved.
938 * @since 3.5
939 */
940 public void setPreserveEscapes(final boolean preserveEscapes) {
941 this.preserveEscapes = preserveEscapes;
942 }
943
944 /**
945 * Sets the variable default value delimiter to use.
946 * <p>
947 * The variable default value delimiter is the character or characters that delimit the
948 * variable name and the variable default value. This method allows a single character
949 * variable default value delimiter to be easily set.
950 * </p>
951 *
952 * @param valueDelimiter The variable default value delimiter character to use.
953 * @return {@code this} instance.
954 * @since 3.2
955 */
956 public StrSubstitutor setValueDelimiter(final char valueDelimiter) {
957 return setValueDelimiterMatcher(StrMatcher.charMatcher(valueDelimiter));
958 }
959
960 /**
961 * Sets the variable default value delimiter to use.
962 * <p>
963 * The variable default value delimiter is the character or characters that delimit the
964 * variable name and the variable default value. This method allows a string
965 * variable default value delimiter to be easily set.
966 * </p>
967 * <p>
968 * If the {@code valueDelimiter} is null or empty string, then the variable default
969 * value resolution becomes disabled.
970 * </p>
971 *
972 * @param valueDelimiter The variable default value delimiter string to use, may be null or empty.
973 * @return {@code this} instance.
974 * @since 3.2
975 */
976 public StrSubstitutor setValueDelimiter(final String valueDelimiter) {
977 if (StringUtils.isEmpty(valueDelimiter)) {
978 setValueDelimiterMatcher(null);
979 return this;
980 }
981 return setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter));
982 }
983
984 /**
985 * Sets the variable default value delimiter matcher to use.
986 * <p>
987 * The variable default value delimiter is the character or characters that delimit the
988 * variable name and the variable default value. This delimiter is expressed in terms of a matcher
989 * allowing advanced variable default value delimiter matches.
990 * </p>
991 * <p>
992 * If the {@code valueDelimiterMatcher} is null, then the variable default value resolution
993 * becomes disabled.
994 * </p>
995 *
996 * @param valueDelimiterMatcher variable default value delimiter matcher to use, may be null.
997 * @return {@code this} instance.
998 * @since 3.2
999 */
1000 public StrSubstitutor setValueDelimiterMatcher(final StrMatcher valueDelimiterMatcher) {
1001 this.valueDelimiterMatcher = valueDelimiterMatcher;
1002 return this;
1003 }
1004
1005 /**
1006 * Sets the variable prefix to use.
1007 * <p>
1008 * The variable prefix is the character or characters that identify the
1009 * start of a variable. This method allows a single character prefix to
1010 * be easily set.
1011 * </p>
1012 *
1013 * @param prefix The prefix character to use.
1014 * @return {@code this} instance.
1015 */
1016 public StrSubstitutor setVariablePrefix(final char prefix) {
1017 return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix));
1018 }
1019
1020 /**
1021 * Sets the variable prefix to use.
1022 * <p>
1023 * The variable prefix is the character or characters that identify the
1024 * start of a variable. This method allows a string prefix to be easily set.
1025 * </p>
1026 *
1027 * @param prefix The prefix for variables, not null.
1028 * @return {@code this} instance.
1029 * @throws NullPointerException Thrown if the prefix is null.
1030 */
1031 public StrSubstitutor setVariablePrefix(final String prefix) {
1032 return setVariablePrefixMatcher(StrMatcher.stringMatcher(Objects.requireNonNull(prefix, "prefix")));
1033 }
1034
1035 /**
1036 * Sets the variable prefix matcher currently in use.
1037 * <p>
1038 * The variable prefix is the character or characters that identify the
1039 * start of a variable. This prefix is expressed in terms of a matcher
1040 * allowing advanced prefix matches.
1041 * </p>
1042 *
1043 * @param prefixMatcher The prefix matcher to use, null ignored.
1044 * @return {@code this} instance.
1045 * @throws NullPointerException Thrown if the prefix matcher is null.
1046 */
1047 public StrSubstitutor setVariablePrefixMatcher(final StrMatcher prefixMatcher) {
1048 this.prefixMatcher = Objects.requireNonNull(prefixMatcher, "prefixMatcher");
1049 return this;
1050 }
1051
1052 /**
1053 * Sets the VariableResolver that is used to lookup variables.
1054 *
1055 * @param variableResolver The VariableResolver
1056 */
1057 public void setVariableResolver(final StrLookup<?> variableResolver) {
1058 this.variableResolver = variableResolver;
1059 }
1060
1061 /**
1062 * Sets the variable suffix to use.
1063 * <p>
1064 * The variable suffix is the character or characters that identify the
1065 * end of a variable. This method allows a single character suffix to
1066 * be easily set.
1067 * </p>
1068 *
1069 * @param suffix The suffix character to use.
1070 * @return {@code this} instance.
1071 */
1072 public StrSubstitutor setVariableSuffix(final char suffix) {
1073 return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix));
1074 }
1075
1076 /**
1077 * Sets the variable suffix to use.
1078 * <p>
1079 * The variable suffix is the character or characters that identify the
1080 * end of a variable. This method allows a string suffix to be easily set.
1081 * </p>
1082 *
1083 * @param suffix The suffix for variables, not null.
1084 * @return {@code this} instance.
1085 * @throws NullPointerException Thrown if the suffix is null.
1086 */
1087 public StrSubstitutor setVariableSuffix(final String suffix) {
1088 return setVariableSuffixMatcher(StrMatcher.stringMatcher(Objects.requireNonNull(suffix, "suffix")));
1089 }
1090
1091 /**
1092 * Sets the variable suffix matcher currently in use.
1093 * <p>
1094 * The variable suffix is the character or characters that identify the
1095 * end of a variable. This suffix is expressed in terms of a matcher
1096 * allowing advanced suffix matches.
1097 * </p>
1098 *
1099 * @param suffixMatcher The suffix matcher to use, null ignored.
1100 * @return {@code this} instance.
1101 * @throws NullPointerException Thrown if the suffix matcher is null.
1102 */
1103 public StrSubstitutor setVariableSuffixMatcher(final StrMatcher suffixMatcher) {
1104 this.suffixMatcher = Objects.requireNonNull(suffixMatcher, "suffixMatcher");
1105 return this;
1106 }
1107
1108 /**
1109 * Internal method that substitutes the variables.
1110 * <p>
1111 * Most users of this class do not need to call this method. This method will
1112 * be called automatically by another (public) method.
1113 * </p>
1114 * <p>
1115 * Writers of subclasses can override this method if they need access to
1116 * the substitution process at the start or end.
1117 * </p>
1118 *
1119 * @param buf The string builder to substitute into, not null.
1120 * @param offset The start offset within the builder, must be valid.
1121 * @param length The length within the builder to be processed, must be valid.
1122 * @return true if altered.
1123 */
1124 protected boolean substitute(final StrBuilder buf, final int offset, final int length) {
1125 return substitute(buf, offset, length, null) > 0;
1126 }
1127
1128 /**
1129 * Recursive handler for multiple levels of interpolation. This is the main
1130 * interpolation method, which resolves the values of all variable references
1131 * contained in the passed-in text.
1132 *
1133 * @param buf The string builder to substitute into, not null.
1134 * @param offset The start offset within the builder, must be valid.
1135 * @param length The length within the builder to be processed, must be valid.
1136 * @param priorVariables The stack keeping track of the replaced variables, may be null.
1137 * @return The length change that occurs, unless priorVariables is null when the int
1138 * represents a boolean flag as to whether any change occurred.
1139 * @throws IllegalStateException Thrown if the interpolation exceeds {@value #MAX_SUBSTITUTION_DEPTH} nesting levels or
1140 * emits more than {@value #MAX_SUBSTITUTION_LENGTH} characters. These budgets bound recursive expansion that the
1141 * cyclic-substitution check cannot detect (acyclic fan-out, deep nesting). This class is deprecated; the
1142 * Apache Commons Text successor {@code StringSubstitutor} should receive any richer treatment.
1143 */
1144 private int substitute(final StrBuilder buf, final int offset, final int length, final List<String> priorVariables) {
1145 if (substitutionDepth == 0) {
1146 substitutionLength = 0;
1147 }
1148 if (substitutionDepth >= MAX_SUBSTITUTION_DEPTH) {
1149 throw new IllegalStateException("Maximum interpolation depth (" + MAX_SUBSTITUTION_DEPTH + ") exceeded in variable substitution");
1150 }
1151 substitutionDepth++;
1152 try {
1153 return substituteRecursive(buf, offset, length, priorVariables);
1154 } finally {
1155 substitutionDepth--;
1156 }
1157 }
1158
1159 /**
1160 * Implements {@link #substitute(StrBuilder, int, int, List)}; only that budget-enforcing wrapper may call this.
1161 *
1162 * @param buf The string builder to substitute into, not null.
1163 * @param offset The start offset within the builder, must be valid.
1164 * @param length The length within the builder to be processed, must be valid.
1165 * @param priorVariables The stack keeping track of the replaced variables, may be null.
1166 * @return The length change that occurs, unless priorVariables is null when the int
1167 * represents a boolean flag as to whether any change occurred.
1168 */
1169 private int substituteRecursive(final StrBuilder buf, final int offset, final int length, List<String> priorVariables) {
1170 final StrMatcher pfxMatcher = getVariablePrefixMatcher();
1171 final StrMatcher suffMatcher = getVariableSuffixMatcher();
1172 final char escape = getEscapeChar();
1173 final StrMatcher valueDelimMatcher = getValueDelimiterMatcher();
1174 final boolean substitutionInVariablesEnabled = isEnableSubstitutionInVariables();
1175 final boolean top = priorVariables == null;
1176 boolean altered = false;
1177 int lengthChange = 0;
1178 char[] chars = buf.buffer;
1179 int bufEnd = offset + length;
1180 int pos = offset;
1181 while (pos < bufEnd) {
1182 final int startMatchLen = pfxMatcher.isMatch(chars, pos, offset, bufEnd);
1183 if (startMatchLen == 0) {
1184 pos++;
1185 } else // found variable start marker
1186 if (pos > offset && chars[pos - 1] == escape) {
1187 // escaped
1188 if (preserveEscapes) {
1189 pos++;
1190 continue;
1191 }
1192 buf.deleteCharAt(pos - 1);
1193 chars = buf.buffer; // in case buffer was altered
1194 lengthChange--;
1195 altered = true;
1196 bufEnd--;
1197 } else {
1198 // find suffix
1199 final int startPos = pos;
1200 pos += startMatchLen;
1201 int endMatchLen;
1202 int nestedVarCount = 0;
1203 while (pos < bufEnd) {
1204 if (substitutionInVariablesEnabled && (endMatchLen = pfxMatcher.isMatch(chars, pos, offset, bufEnd)) != 0) {
1205 // found a nested variable start
1206 nestedVarCount++;
1207 pos += endMatchLen;
1208 continue;
1209 }
1210 endMatchLen = suffMatcher.isMatch(chars, pos, offset, bufEnd);
1211 if (endMatchLen == 0) {
1212 pos++;
1213 } else {
1214 // found variable end marker
1215 if (nestedVarCount == 0) {
1216 String varNameExpr = new String(chars, startPos + startMatchLen, pos - startPos - startMatchLen);
1217 if (substitutionInVariablesEnabled) {
1218 final StrBuilder bufName = new StrBuilder(varNameExpr);
1219 substitute(bufName, 0, bufName.length());
1220 varNameExpr = bufName.toString();
1221 }
1222 pos += endMatchLen;
1223 final int endPos = pos;
1224 String varName = varNameExpr;
1225 String varDefaultValue = null;
1226 if (valueDelimMatcher != null) {
1227 final char[] varNameExprChars = varNameExpr.toCharArray();
1228 int valueDelimiterMatchLen;
1229 for (int i = 0; i < varNameExprChars.length; i++) {
1230 // if there's any nested variable when nested variable substitution disabled, then stop resolving name and default value.
1231 if (!substitutionInVariablesEnabled && pfxMatcher.isMatch(varNameExprChars, i, i, varNameExprChars.length) != 0) {
1232 break;
1233 }
1234 if ((valueDelimiterMatchLen = valueDelimMatcher.isMatch(varNameExprChars, i)) != 0) {
1235 varName = varNameExpr.substring(0, i);
1236 varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen);
1237 break;
1238 }
1239 }
1240 }
1241 // on the first call initialize priorVariables
1242 if (priorVariables == null) {
1243 priorVariables = new ArrayList<>();
1244 priorVariables.add(new String(chars, offset, length));
1245 }
1246 // handle cyclic substitution
1247 checkCyclicSubstitution(varName, priorVariables);
1248 priorVariables.add(varName);
1249 // resolve the variable
1250 String varValue = resolveVariable(varName, buf, startPos, endPos);
1251 if (varValue == null) {
1252 varValue = varDefaultValue;
1253 }
1254 if (varValue != null) {
1255 // recursive replace
1256 final int varLen = varValue.length();
1257 buf.replace(startPos, endPos, varValue);
1258 altered = true;
1259 substitutionLength += varLen;
1260 if (substitutionLength > MAX_SUBSTITUTION_LENGTH) {
1261 throw new IllegalStateException("Maximum interpolation size (" + MAX_SUBSTITUTION_LENGTH
1262 + " characters) exceeded in variable substitution");
1263 }
1264 int change = substitute(buf, startPos, varLen, priorVariables);
1265 change = change + varLen - (endPos - startPos);
1266 pos += change;
1267 bufEnd += change;
1268 lengthChange += change;
1269 chars = buf.buffer; // in case buffer was altered
1270 }
1271 // remove variable from the cyclic stack
1272 priorVariables.remove(priorVariables.size() - 1);
1273 break;
1274 }
1275 nestedVarCount--;
1276 pos += endMatchLen;
1277 }
1278 }
1279 }
1280 }
1281 if (top) {
1282 return altered ? 1 : 0;
1283 }
1284 return lengthChange;
1285 }
1286 }