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 * Replaces all the occurrences of variables in the given source object with
175 * their matching values from the map.
176 *
177 * @param <V> the type of the values in the map.
178 * @param source the source text containing the variables to substitute, null returns null.
179 * @param valueMap the map with the values, may be null.
180 * @return the result of the replace operation.
181 */
182 public static <V> String replace(final Object source, final Map<String, V> valueMap) {
183 return new StrSubstitutor(valueMap).replace(source);
184 }
185
186 /**
187 * Replaces all the occurrences of variables in the given source object with
188 * their matching values from the map. This method allows to specify a
189 * custom variable prefix and suffix.
190 *
191 * @param <V> the type of the values in the map.
192 * @param source the source text containing the variables to substitute, null returns null.
193 * @param valueMap the map with the values, may be null.
194 * @param prefix the prefix of variables, not null.
195 * @param suffix the suffix of variables, not null.
196 * @return the result of the replace operation.
197 * @throws IllegalArgumentException if the prefix or suffix is null.
198 */
199 public static <V> String replace(final Object source, final Map<String, V> valueMap, final String prefix, final String suffix) {
200 return new StrSubstitutor(valueMap, prefix, suffix).replace(source);
201 }
202
203 /**
204 * Replaces all the occurrences of variables in the given source object with their matching
205 * values from the properties.
206 *
207 * @param source the source text containing the variables to substitute, null returns null.
208 * @param valueProperties the properties with values, may be null.
209 * @return the result of the replace operation.
210 */
211 public static String replace(final Object source, final Properties valueProperties) {
212 if (valueProperties == null) {
213 return source.toString();
214 }
215 final Map<String, String> valueMap = new HashMap<>();
216 final Enumeration<?> propNames = valueProperties.propertyNames();
217 while (propNames.hasMoreElements()) {
218 final String propName = String.valueOf(propNames.nextElement());
219 final String propValue = valueProperties.getProperty(propName);
220 valueMap.put(propName, propValue);
221 }
222 return replace(source, valueMap);
223 }
224
225 /**
226 * Replaces all the occurrences of variables in the given source object with
227 * their matching values from the system properties.
228 *
229 * @param source the source text containing the variables to substitute, null returns null.
230 * @return the result of the replace operation.
231 */
232 public static String replaceSystemProperties(final Object source) {
233 return new StrSubstitutor(StrLookup.systemPropertiesLookup()).replace(source);
234 }
235
236 /**
237 * Stores the escape character.
238 */
239 private char escapeChar;
240
241 /**
242 * Stores the variable prefix.
243 */
244 private StrMatcher prefixMatcher;
245
246 /**
247 * Stores the variable suffix.
248 */
249 private StrMatcher suffixMatcher;
250
251 /**
252 * Stores the default variable value delimiter
253 */
254 private StrMatcher valueDelimiterMatcher;
255
256 /**
257 * Variable resolution is delegated to an implementor of VariableResolver.
258 */
259 private StrLookup<?> variableResolver;
260
261 /**
262 * The flag whether substitution in variable names is enabled.
263 */
264 private boolean enableSubstitutionInVariables;
265
266 /**
267 * Whether escapes should be preserved. Default is false;
268 */
269 private boolean preserveEscapes;
270
271 /**
272 * Creates a new instance with defaults for variable prefix and suffix
273 * and the escaping character.
274 */
275 public StrSubstitutor() {
276 this(null, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
277 }
278
279 /**
280 * Creates a new instance and initializes it. Uses defaults for variable
281 * prefix and suffix and the escaping character.
282 *
283 * @param <V> the type of the values in the map.
284 * @param valueMap the map with the variables' values, may be null.
285 */
286 public <V> StrSubstitutor(final Map<String, V> valueMap) {
287 this(StrLookup.mapLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
288 }
289
290 /**
291 * Creates a new instance and initializes it. Uses a default escaping character.
292 *
293 * @param <V> the type of the values in the map.
294 * @param valueMap the map with the variables' values, may be null.
295 * @param prefix the prefix for variables, not null.
296 * @param suffix the suffix for variables, not null.
297 * @throws IllegalArgumentException if the prefix or suffix is null.
298 */
299 public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix) {
300 this(StrLookup.mapLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE);
301 }
302
303 /**
304 * Creates a new instance and initializes it.
305 *
306 * @param <V> the type of the values in the map.
307 * @param valueMap the map with the variables' values, may be null.
308 * @param prefix the prefix for variables, not null.
309 * @param suffix the suffix for variables, not null.
310 * @param escape the escape character.
311 * @throws IllegalArgumentException if the prefix or suffix is null.
312 */
313 public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix, final char escape) {
314 this(StrLookup.mapLookup(valueMap), prefix, suffix, escape);
315 }
316
317 /**
318 * Creates a new instance and initializes it.
319 *
320 * @param <V> the type of the values in the map.
321 * @param valueMap the map with the variables' values, may be null.
322 * @param prefix the prefix for variables, not null.
323 * @param suffix the suffix for variables, not null.
324 * @param escape the escape character.
325 * @param valueDelimiter the variable default value delimiter, may be null.
326 * @throws IllegalArgumentException if the prefix or suffix is null.
327 * @since 3.2
328 */
329 public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix, final char escape, final String valueDelimiter) {
330 this(StrLookup.mapLookup(valueMap), prefix, suffix, escape, valueDelimiter);
331 }
332
333 /**
334 * Creates a new instance and initializes it.
335 *
336 * @param variableResolver the variable resolver, may be null
337 */
338 public StrSubstitutor(final StrLookup<?> variableResolver) {
339 this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
340 }
341
342 /**
343 * Creates a new instance and initializes it.
344 *
345 * @param variableResolver the variable resolver, may be null.
346 * @param prefix the prefix for variables, not null.
347 * @param suffix the suffix for variables, not null.
348 * @param escape the escape character.
349 * @throws IllegalArgumentException if the prefix or suffix is null.
350 */
351 public StrSubstitutor(final StrLookup<?> variableResolver, final String prefix, final String suffix, final char escape) {
352 setVariableResolver(variableResolver);
353 setVariablePrefix(prefix);
354 setVariableSuffix(suffix);
355 setEscapeChar(escape);
356 setValueDelimiterMatcher(DEFAULT_VALUE_DELIMITER);
357 }
358
359 /**
360 * Creates a new instance and initializes it.
361 *
362 * @param variableResolver the variable resolver, may be null.
363 * @param prefix the prefix for variables, not null.
364 * @param suffix the suffix for variables, not null.
365 * @param escape the escape character.
366 * @param valueDelimiter the variable default value delimiter string, may be null.
367 * @throws IllegalArgumentException if the prefix or suffix is null.
368 * @since 3.2
369 */
370 public StrSubstitutor(final StrLookup<?> variableResolver, final String prefix, final String suffix, final char escape, final String valueDelimiter) {
371 setVariableResolver(variableResolver);
372 setVariablePrefix(prefix);
373 setVariableSuffix(suffix);
374 setEscapeChar(escape);
375 setValueDelimiter(valueDelimiter);
376 }
377
378 /**
379 * Creates a new instance and initializes it.
380 *
381 * @param variableResolver the variable resolver, may be null.
382 * @param prefixMatcher the prefix for variables, not null.
383 * @param suffixMatcher the suffix for variables, not null.
384 * @param escape the escape character.
385 * @throws IllegalArgumentException if the prefix or suffix is null.
386 */
387 public StrSubstitutor(final StrLookup<?> variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher, final char escape) {
388 this(variableResolver, prefixMatcher, suffixMatcher, escape, DEFAULT_VALUE_DELIMITER);
389 }
390
391 /**
392 * Creates a new instance and initializes it.
393 *
394 * @param variableResolver the variable resolver, may be null.
395 * @param prefixMatcher the prefix for variables, not null.
396 * @param suffixMatcher the suffix for variables, not null.
397 * @param escape the escape character.
398 * @param valueDelimiterMatcher the variable default value delimiter matcher, may be null.
399 * @throws IllegalArgumentException if the prefix or suffix is null.
400 * @since 3.2
401 */
402 public StrSubstitutor(final StrLookup<?> variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher, final char escape,
403 final StrMatcher valueDelimiterMatcher) {
404 setVariableResolver(variableResolver);
405 setVariablePrefixMatcher(prefixMatcher);
406 setVariableSuffixMatcher(suffixMatcher);
407 setEscapeChar(escape);
408 setValueDelimiterMatcher(valueDelimiterMatcher);
409 }
410
411 /**
412 * Checks if the specified variable is already in the stack (list) of variables.
413 *
414 * @param varName the variable name to check.
415 * @param priorVariables the list of prior variables.
416 */
417 private void checkCyclicSubstitution(final String varName, final List<String> priorVariables) {
418 if (!priorVariables.contains(varName)) {
419 return;
420 }
421 final StrBuilder buf = new StrBuilder(256);
422 buf.append("Infinite loop in property interpolation of ");
423 buf.append(priorVariables.remove(0));
424 buf.append(": ");
425 buf.appendWithSeparators(priorVariables, "->");
426 throw new IllegalStateException(buf.toString());
427 }
428
429 /**
430 * Gets the escape character.
431 *
432 * @return the character used for escaping variable references
433 */
434 public char getEscapeChar() {
435 return this.escapeChar;
436 }
437
438 /**
439 * Gets the variable default value delimiter matcher currently in use.
440 * <p>
441 * The variable default value delimiter is the character or characters that delimit the
442 * variable name and the variable default value. This delimiter is expressed in terms of a matcher
443 * allowing advanced variable default value delimiter matches.
444 * </p>
445 * <p>
446 * If it returns null, then the variable default value resolution is disabled.
447 * </p>
448 *
449 * @return the variable default value delimiter matcher in use, may be null.
450 * @since 3.2
451 */
452 public StrMatcher getValueDelimiterMatcher() {
453 return valueDelimiterMatcher;
454 }
455
456 /**
457 * Gets the variable prefix matcher currently in use.
458 * <p>
459 * The variable prefix is the character or characters that identify the
460 * start of a variable. This prefix is expressed in terms of a matcher
461 * allowing advanced prefix matches.
462 * </p>
463 *
464 * @return the prefix matcher in use.
465 */
466 public StrMatcher getVariablePrefixMatcher() {
467 return prefixMatcher;
468 }
469
470 /**
471 * Gets the VariableResolver that is used to lookup variables.
472 *
473 * @return the VariableResolver.
474 */
475 public StrLookup<?> getVariableResolver() {
476 return this.variableResolver;
477 }
478
479 /**
480 * Gets the variable suffix matcher currently in use.
481 * <p>
482 * The variable suffix is the character or characters that identify the
483 * end of a variable. This suffix is expressed in terms of a matcher
484 * allowing advanced suffix matches.
485 * </p>
486 *
487 * @return the suffix matcher in use.
488 */
489 public StrMatcher getVariableSuffixMatcher() {
490 return suffixMatcher;
491 }
492
493 /**
494 * Tests whether substitution is done in variable names.
495 *
496 * @return the substitution in variable names flag.
497 * @since 3.0
498 */
499 public boolean isEnableSubstitutionInVariables() {
500 return enableSubstitutionInVariables;
501 }
502
503 /**
504 * Tests whether escapes are preserved during substitution.
505 *
506 * @return the preserve escape flag.
507 * @since 3.5
508 */
509 public boolean isPreserveEscapes() {
510 return preserveEscapes;
511 }
512
513 /**
514 * Replaces all the occurrences of variables with their matching values
515 * from the resolver using the given source array as a template.
516 * The array is not altered by this method.
517 *
518 * @param source the character array to replace in, not altered, null returns null.
519 * @return the result of the replace operation.
520 */
521 public String replace(final char[] source) {
522 if (source == null) {
523 return null;
524 }
525 final StrBuilder buf = new StrBuilder(source.length).append(source);
526 substitute(buf, 0, source.length);
527 return buf.toString();
528 }
529
530 /**
531 * Replaces all the occurrences of variables with their matching values
532 * from the resolver using the given source array as a template.
533 * The array is not altered by this method.
534 * <p>
535 * Only the specified portion of the array will be processed.
536 * The rest of the array is not processed, and is not returned.
537 * </p>
538 *
539 * @param source the character array to replace in, not altered, null returns null.
540 * @param offset the start offset within the array, must be valid.
541 * @param length the length within the array to be processed, must be valid.
542 * @return the result of the replace operation.
543 */
544 public String replace(final char[] source, final int offset, final int length) {
545 if (source == null) {
546 return null;
547 }
548 final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
549 substitute(buf, 0, length);
550 return buf.toString();
551 }
552
553 /**
554 * Replaces all the occurrences of variables with their matching values
555 * from the resolver using the given source as a template.
556 * The source is not altered by this method.
557 *
558 * @param source the buffer to use as a template, not changed, null returns null.
559 * @return the result of the replace operation.
560 * @since 3.2
561 */
562 public String replace(final CharSequence source) {
563 if (source == null) {
564 return null;
565 }
566 return replace(source, 0, source.length());
567 }
568
569 /**
570 * Replaces all the occurrences of variables with their matching values
571 * from the resolver using the given source as a template.
572 * The source is not altered by this method.
573 * <p>
574 * Only the specified portion of the buffer will be processed.
575 * The rest of the buffer is not processed, and is not returned.
576 * </p>
577 *
578 * @param source the buffer to use as a template, not changed, null returns null.
579 * @param offset the start offset within the array, must be valid.
580 * @param length the length within the array to be processed, must be valid.
581 * @return the result of the replace operation.
582 * @since 3.2
583 */
584 public String replace(final CharSequence source, final int offset, final int length) {
585 if (source == null) {
586 return null;
587 }
588 final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
589 substitute(buf, 0, length);
590 return buf.toString();
591 }
592
593 /**
594 * Replaces all the occurrences of variables in the given source object with
595 * their matching values from the resolver. The input source object is
596 * converted to a string using {@code toString} and is not altered.
597 *
598 * @param source the source to replace in, null returns null.
599 * @return the result of the replace operation.
600 */
601 public String replace(final Object source) {
602 if (source == null) {
603 return null;
604 }
605 final StrBuilder buf = new StrBuilder().append(source);
606 substitute(buf, 0, buf.length());
607 return buf.toString();
608 }
609
610 /**
611 * Replaces all the occurrences of variables with their matching values
612 * from the resolver using the given source builder as a template.
613 * The builder is not altered by this method.
614 *
615 * @param source the builder to use as a template, not changed, null returns null.
616 * @return the result of the replace operation.
617 */
618 public String replace(final StrBuilder source) {
619 if (source == null) {
620 return null;
621 }
622 final StrBuilder buf = new StrBuilder(source.length()).append(source);
623 substitute(buf, 0, buf.length());
624 return buf.toString();
625 }
626
627 /**
628 * Replaces all the occurrences of variables with their matching values
629 * from the resolver using the given source builder as a template.
630 * The builder is not altered by this method.
631 * <p>
632 * Only the specified portion of the builder will be processed.
633 * The rest of the builder is not processed, and is not returned.
634 * </p>
635 *
636 * @param source the builder to use as a template, not changed, null returns null.
637 * @param offset the start offset within the array, must be valid.
638 * @param length the length within the array to be processed, must be valid.
639 * @return the result of the replace operation.
640 */
641 public String replace(final StrBuilder source, final int offset, final int length) {
642 if (source == null) {
643 return null;
644 }
645 final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
646 substitute(buf, 0, length);
647 return buf.toString();
648 }
649
650 /**
651 * Replaces all the occurrences of variables with their matching values
652 * from the resolver using the given source string as a template.
653 *
654 * @param source the string to replace in, null returns null.
655 * @return the result of the replace operation.
656 */
657 public String replace(final String source) {
658 if (source == null) {
659 return null;
660 }
661 final StrBuilder buf = new StrBuilder(source);
662 if (!substitute(buf, 0, source.length())) {
663 return source;
664 }
665 return buf.toString();
666 }
667
668 /**
669 * Replaces all the occurrences of variables with their matching values
670 * from the resolver using the given source string as a template.
671 * <p>
672 * Only the specified portion of the string will be processed.
673 * The rest of the string is not processed, and is not returned.
674 * </p>
675 *
676 * @param source the string to replace in, null returns null.
677 * @param offset the start offset within the array, must be valid.
678 * @param length the length within the array to be processed, must be valid.
679 * @return the result of the replace operation.
680 */
681 public String replace(final String source, final int offset, final int length) {
682 if (source == null) {
683 return null;
684 }
685 final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
686 if (!substitute(buf, 0, length)) {
687 return source.substring(offset, offset + length);
688 }
689 return buf.toString();
690 }
691
692 /**
693 * Replaces all the occurrences of variables with their matching values
694 * from the resolver using the given source buffer as a template.
695 * The buffer is not altered by this method.
696 *
697 * @param source the buffer to use as a template, not changed, null returns null.
698 * @return the result of the replace operation.
699 */
700 public String replace(final StringBuffer source) {
701 if (source == null) {
702 return null;
703 }
704 final StrBuilder buf = new StrBuilder(source.length()).append(source);
705 substitute(buf, 0, buf.length());
706 return buf.toString();
707 }
708
709 /**
710 * Replaces all the occurrences of variables with their matching values
711 * from the resolver using the given source buffer as a template.
712 * The buffer is not altered by this method.
713 * <p>
714 * Only the specified portion of the buffer will be processed.
715 * The rest of the buffer is not processed, and is not returned.
716 * </p>
717 *
718 * @param source the buffer to use as a template, not changed, null returns null.
719 * @param offset the start offset within the array, must be valid.
720 * @param length the length within the array to be processed, must be valid.
721 * @return the result of the replace operation.
722 */
723 public String replace(final StringBuffer source, final int offset, final int length) {
724 if (source == null) {
725 return null;
726 }
727 final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
728 substitute(buf, 0, length);
729 return buf.toString();
730 }
731
732 /**
733 * Replaces all the occurrences of variables within the given source
734 * builder with their matching values from the resolver.
735 *
736 * @param source the builder to replace in, updated, null returns zero.
737 * @return true if altered.
738 */
739 public boolean replaceIn(final StrBuilder source) {
740 if (source == null) {
741 return false;
742 }
743 return substitute(source, 0, source.length());
744 }
745
746 /**
747 * Replaces all the occurrences of variables within the given source
748 * builder with their matching values from the resolver.
749 * <p>
750 * Only the specified portion of the builder will be processed.
751 * The rest of the builder is not processed, but it is not deleted.
752 * </p>
753 *
754 * @param source the builder to replace in, null returns zero.
755 * @param offset the start offset within the array, must be valid.
756 * @param length the length within the builder to be processed, must be valid.
757 * @return true if altered.
758 */
759 public boolean replaceIn(final StrBuilder source, final int offset, final int length) {
760 if (source == null) {
761 return false;
762 }
763 return substitute(source, offset, length);
764 }
765
766 /**
767 * Replaces all the occurrences of variables within the given source buffer
768 * with their matching values from the resolver.
769 * The buffer is updated with the result.
770 *
771 * @param source the buffer to replace in, updated, null returns zero.
772 * @return true if altered.
773 */
774 public boolean replaceIn(final StringBuffer source) {
775 if (source == null) {
776 return false;
777 }
778 return replaceIn(source, 0, source.length());
779 }
780
781 /**
782 * Replaces all the occurrences of variables within the given source buffer
783 * with their matching values from the resolver.
784 * The buffer is updated with the result.
785 * <p>
786 * Only the specified portion of the buffer will be processed.
787 * The rest of the buffer is not processed, but it is not deleted.
788 * </p>
789 *
790 * @param source the buffer to replace in, updated, null returns zero.
791 * @param offset the start offset within the array, must be valid.
792 * @param length the length within the buffer to be processed, must be valid.
793 * @return true if altered.
794 */
795 public boolean replaceIn(final StringBuffer source, final int offset, final int length) {
796 if (source == null) {
797 return false;
798 }
799 final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
800 if (!substitute(buf, 0, length)) {
801 return false;
802 }
803 source.replace(offset, offset + length, buf.toString());
804 return true;
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 *
812 * @param source the buffer to replace in, updated, null returns zero.
813 * @return true if altered.
814 * @since 3.2
815 */
816 public boolean replaceIn(final StringBuilder source) {
817 if (source == null) {
818 return false;
819 }
820 return replaceIn(source, 0, source.length());
821 }
822
823 /**
824 * Replaces all the occurrences of variables within the given source builder
825 * with their matching values from the resolver.
826 * The builder is updated with the result.
827 * <p>
828 * Only the specified portion of the buffer will be processed.
829 * The rest of the buffer is not processed, but it is not deleted.
830 * </p>
831 *
832 * @param source the buffer to replace in, updated, null returns zero.
833 * @param offset the start offset within the array, must be valid.
834 * @param length the length within the buffer to be processed, must be valid.
835 * @return true if altered.
836 * @since 3.2
837 */
838 public boolean replaceIn(final StringBuilder source, final int offset, final int length) {
839 if (source == null) {
840 return false;
841 }
842 final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
843 if (!substitute(buf, 0, length)) {
844 return false;
845 }
846 source.replace(offset, offset + length, buf.toString());
847 return true;
848 }
849
850 /**
851 * Internal method that resolves the value of a variable.
852 * <p>
853 * Most users of this class do not need to call this method. This method is
854 * called automatically by the substitution process.
855 * </p>
856 * <p>
857 * Writers of subclasses can override this method if they need to alter
858 * how each substitution occurs. The method is passed the variable's name
859 * and must return the corresponding value. This implementation uses the
860 * {@link #getVariableResolver()} with the variable's name as the key.
861 * </p>
862 *
863 * @param variableName the name of the variable, not null.
864 * @param buf the buffer where the substitution is occurring, not null.
865 * @param startPos the start position of the variable including the prefix, valid.
866 * @param endPos the end position of the variable including the suffix, valid.
867 * @return the variable's value or <strong>null</strong> if the variable is unknown.
868 */
869 protected String resolveVariable(final String variableName, final StrBuilder buf, final int startPos, final int endPos) {
870 final StrLookup<?> resolver = getVariableResolver();
871 if (resolver == null) {
872 return null;
873 }
874 return resolver.lookup(variableName);
875 }
876
877 /**
878 * Sets a flag whether substitution is done in variable names. If set to
879 * <strong>true</strong>, the names of variables can contain other variables which are
880 * processed first before the original variable is evaluated, e.g.
881 * {@code ${jre-${java.version}}}. The default value is <strong>false</strong>.
882 *
883 * @param enableSubstitutionInVariables the new value of the flag.
884 * @since 3.0
885 */
886 public void setEnableSubstitutionInVariables(
887 final boolean enableSubstitutionInVariables) {
888 this.enableSubstitutionInVariables = enableSubstitutionInVariables;
889 }
890
891 /**
892 * Sets the escape character.
893 * If this character is placed before a variable reference in the source
894 * text, this variable will be ignored.
895 *
896 * @param escapeCharacter the escape character (0 for disabling escaping)
897 */
898 public void setEscapeChar(final char escapeCharacter) {
899 this.escapeChar = escapeCharacter;
900 }
901
902 /**
903 * Sets a flag controlling whether escapes are preserved during
904 * substitution. If set to <strong>true</strong>, the escape character is retained
905 * during substitution (e.g. {@code $${this-is-escaped}} remains
906 * {@code $${this-is-escaped}}). If set to <strong>false</strong>, the escape
907 * character is removed during substitution (e.g.
908 * {@code $${this-is-escaped}} becomes
909 * {@code ${this-is-escaped}}). The default value is <strong>false</strong>
910 *
911 * @param preserveEscapes true if escapes are to be preserved.
912 * @since 3.5
913 */
914 public void setPreserveEscapes(final boolean preserveEscapes) {
915 this.preserveEscapes = preserveEscapes;
916 }
917
918 /**
919 * Sets the variable default value delimiter to use.
920 * <p>
921 * The variable default value delimiter is the character or characters that delimit the
922 * variable name and the variable default value. This method allows a single character
923 * variable default value delimiter to be easily set.
924 * </p>
925 *
926 * @param valueDelimiter the variable default value delimiter character to use.
927 * @return {@code this} instance.
928 * @since 3.2
929 */
930 public StrSubstitutor setValueDelimiter(final char valueDelimiter) {
931 return setValueDelimiterMatcher(StrMatcher.charMatcher(valueDelimiter));
932 }
933
934 /**
935 * Sets the variable default value delimiter to use.
936 * <p>
937 * The variable default value delimiter is the character or characters that delimit the
938 * variable name and the variable default value. This method allows a string
939 * variable default value delimiter to be easily set.
940 * </p>
941 * <p>
942 * If the {@code valueDelimiter} is null or empty string, then the variable default
943 * value resolution becomes disabled.
944 * </p>
945 *
946 * @param valueDelimiter the variable default value delimiter string to use, may be null or empty.
947 * @return {@code this} instance.
948 * @since 3.2
949 */
950 public StrSubstitutor setValueDelimiter(final String valueDelimiter) {
951 if (StringUtils.isEmpty(valueDelimiter)) {
952 setValueDelimiterMatcher(null);
953 return this;
954 }
955 return setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter));
956 }
957
958 /**
959 * Sets the variable default value delimiter matcher to use.
960 * <p>
961 * The variable default value delimiter is the character or characters that delimit the
962 * variable name and the variable default value. This delimiter is expressed in terms of a matcher
963 * allowing advanced variable default value delimiter matches.
964 * </p>
965 * <p>
966 * If the {@code valueDelimiterMatcher} is null, then the variable default value resolution
967 * becomes disabled.
968 * </p>
969 *
970 * @param valueDelimiterMatcher variable default value delimiter matcher to use, may be null.
971 * @return {@code this} instance.
972 * @since 3.2
973 */
974 public StrSubstitutor setValueDelimiterMatcher(final StrMatcher valueDelimiterMatcher) {
975 this.valueDelimiterMatcher = valueDelimiterMatcher;
976 return this;
977 }
978
979 /**
980 * Sets the variable prefix to use.
981 * <p>
982 * The variable prefix is the character or characters that identify the
983 * start of a variable. This method allows a single character prefix to
984 * be easily set.
985 * </p>
986 *
987 * @param prefix the prefix character to use.
988 * @return {@code this} instance.
989 */
990 public StrSubstitutor setVariablePrefix(final char prefix) {
991 return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix));
992 }
993
994 /**
995 * Sets the variable prefix to use.
996 * <p>
997 * The variable prefix is the character or characters that identify the
998 * start of a variable. This method allows a string prefix to be easily set.
999 * </p>
1000 *
1001 * @param prefix the prefix for variables, not null.
1002 * @return {@code this} instance.
1003 * @throws NullPointerException if the prefix is null.
1004 */
1005 public StrSubstitutor setVariablePrefix(final String prefix) {
1006 return setVariablePrefixMatcher(StrMatcher.stringMatcher(Objects.requireNonNull(prefix)));
1007 }
1008
1009 /**
1010 * Sets the variable prefix matcher currently in use.
1011 * <p>
1012 * The variable prefix is the character or characters that identify the
1013 * start of a variable. This prefix is expressed in terms of a matcher
1014 * allowing advanced prefix matches.
1015 * </p>
1016 *
1017 * @param prefixMatcher the prefix matcher to use, null ignored.
1018 * @return {@code this} instance.
1019 * @throws NullPointerException if the prefix matcher is null.
1020 */
1021 public StrSubstitutor setVariablePrefixMatcher(final StrMatcher prefixMatcher) {
1022 this.prefixMatcher = Objects.requireNonNull(prefixMatcher, "prefixMatcher");
1023 return this;
1024 }
1025
1026 /**
1027 * Sets the VariableResolver that is used to lookup variables.
1028 *
1029 * @param variableResolver the VariableResolver
1030 */
1031 public void setVariableResolver(final StrLookup<?> variableResolver) {
1032 this.variableResolver = variableResolver;
1033 }
1034
1035 /**
1036 * Sets the variable suffix to use.
1037 * <p>
1038 * The variable suffix is the character or characters that identify the
1039 * end of a variable. This method allows a single character suffix to
1040 * be easily set.
1041 * </p>
1042 *
1043 * @param suffix the suffix character to use.
1044 * @return {@code this} instance.
1045 */
1046 public StrSubstitutor setVariableSuffix(final char suffix) {
1047 return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix));
1048 }
1049
1050 /**
1051 * Sets the variable suffix to use.
1052 * <p>
1053 * The variable suffix is the character or characters that identify the
1054 * end of a variable. This method allows a string suffix to be easily set.
1055 * </p>
1056 *
1057 * @param suffix the suffix for variables, not null.
1058 * @return {@code this} instance.
1059 * @throws NullPointerException if the suffix is null.
1060 */
1061 public StrSubstitutor setVariableSuffix(final String suffix) {
1062 return setVariableSuffixMatcher(StrMatcher.stringMatcher(Objects.requireNonNull(suffix)));
1063 }
1064
1065 /**
1066 * Sets the variable suffix matcher currently in use.
1067 * <p>
1068 * The variable suffix is the character or characters that identify the
1069 * end of a variable. This suffix is expressed in terms of a matcher
1070 * allowing advanced suffix matches.
1071 * </p>
1072 *
1073 * @param suffixMatcher the suffix matcher to use, null ignored.
1074 * @return {@code this} instance.
1075 * @throws NullPointerException if the suffix matcher is null.
1076 */
1077 public StrSubstitutor setVariableSuffixMatcher(final StrMatcher suffixMatcher) {
1078 this.suffixMatcher = Objects.requireNonNull(suffixMatcher);
1079 return this;
1080 }
1081
1082 /**
1083 * Internal method that substitutes the variables.
1084 * <p>
1085 * Most users of this class do not need to call this method. This method will
1086 * be called automatically by another (public) method.
1087 * </p>
1088 * <p>
1089 * Writers of subclasses can override this method if they need access to
1090 * the substitution process at the start or end.
1091 * </p>
1092 *
1093 * @param buf the string builder to substitute into, not null.
1094 * @param offset the start offset within the builder, must be valid.
1095 * @param length the length within the builder to be processed, must be valid.
1096 * @return true if altered.
1097 */
1098 protected boolean substitute(final StrBuilder buf, final int offset, final int length) {
1099 return substitute(buf, offset, length, null) > 0;
1100 }
1101
1102 /**
1103 * Recursive handler for multiple levels of interpolation. This is the main
1104 * interpolation method, which resolves the values of all variable references
1105 * contained in the passed-in text.
1106 *
1107 * @param buf the string builder to substitute into, not null.
1108 * @param offset the start offset within the builder, must be valid.
1109 * @param length the length within the builder to be processed, must be valid.
1110 * @param priorVariables the stack keeping track of the replaced variables, may be null.
1111 * @return the length change that occurs, unless priorVariables is null when the int
1112 * represents a boolean flag as to whether any change occurred.
1113 */
1114 private int substitute(final StrBuilder buf, final int offset, final int length, List<String> priorVariables) {
1115 final StrMatcher pfxMatcher = getVariablePrefixMatcher();
1116 final StrMatcher suffMatcher = getVariableSuffixMatcher();
1117 final char escape = getEscapeChar();
1118 final StrMatcher valueDelimMatcher = getValueDelimiterMatcher();
1119 final boolean substitutionInVariablesEnabled = isEnableSubstitutionInVariables();
1120 final boolean top = priorVariables == null;
1121 boolean altered = false;
1122 int lengthChange = 0;
1123 char[] chars = buf.buffer;
1124 int bufEnd = offset + length;
1125 int pos = offset;
1126 while (pos < bufEnd) {
1127 final int startMatchLen = pfxMatcher.isMatch(chars, pos, offset, bufEnd);
1128 if (startMatchLen == 0) {
1129 pos++;
1130 } else // found variable start marker
1131 if (pos > offset && chars[pos - 1] == escape) {
1132 // escaped
1133 if (preserveEscapes) {
1134 pos++;
1135 continue;
1136 }
1137 buf.deleteCharAt(pos - 1);
1138 chars = buf.buffer; // in case buffer was altered
1139 lengthChange--;
1140 altered = true;
1141 bufEnd--;
1142 } else {
1143 // find suffix
1144 final int startPos = pos;
1145 pos += startMatchLen;
1146 int endMatchLen;
1147 int nestedVarCount = 0;
1148 while (pos < bufEnd) {
1149 if (substitutionInVariablesEnabled && (endMatchLen = pfxMatcher.isMatch(chars, pos, offset, bufEnd)) != 0) {
1150 // found a nested variable start
1151 nestedVarCount++;
1152 pos += endMatchLen;
1153 continue;
1154 }
1155 endMatchLen = suffMatcher.isMatch(chars, pos, offset, bufEnd);
1156 if (endMatchLen == 0) {
1157 pos++;
1158 } else {
1159 // found variable end marker
1160 if (nestedVarCount == 0) {
1161 String varNameExpr = new String(chars, startPos + startMatchLen, pos - startPos - startMatchLen);
1162 if (substitutionInVariablesEnabled) {
1163 final StrBuilder bufName = new StrBuilder(varNameExpr);
1164 substitute(bufName, 0, bufName.length());
1165 varNameExpr = bufName.toString();
1166 }
1167 pos += endMatchLen;
1168 final int endPos = pos;
1169 String varName = varNameExpr;
1170 String varDefaultValue = null;
1171 if (valueDelimMatcher != null) {
1172 final char[] varNameExprChars = varNameExpr.toCharArray();
1173 int valueDelimiterMatchLen;
1174 for (int i = 0; i < varNameExprChars.length; i++) {
1175 // if there's any nested variable when nested variable substitution disabled, then stop resolving name and default value.
1176 if (!substitutionInVariablesEnabled && pfxMatcher.isMatch(varNameExprChars, i, i, varNameExprChars.length) != 0) {
1177 break;
1178 }
1179 if ((valueDelimiterMatchLen = valueDelimMatcher.isMatch(varNameExprChars, i)) != 0) {
1180 varName = varNameExpr.substring(0, i);
1181 varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen);
1182 break;
1183 }
1184 }
1185 }
1186 // on the first call initialize priorVariables
1187 if (priorVariables == null) {
1188 priorVariables = new ArrayList<>();
1189 priorVariables.add(new String(chars, offset, length));
1190 }
1191 // handle cyclic substitution
1192 checkCyclicSubstitution(varName, priorVariables);
1193 priorVariables.add(varName);
1194 // resolve the variable
1195 String varValue = resolveVariable(varName, buf, startPos, endPos);
1196 if (varValue == null) {
1197 varValue = varDefaultValue;
1198 }
1199 if (varValue != null) {
1200 // recursive replace
1201 final int varLen = varValue.length();
1202 buf.replace(startPos, endPos, varValue);
1203 altered = true;
1204 int change = substitute(buf, startPos, varLen, priorVariables);
1205 change = change + varLen - (endPos - startPos);
1206 pos += change;
1207 bufEnd += change;
1208 lengthChange += change;
1209 chars = buf.buffer; // in case buffer was altered
1210 }
1211 // remove variable from the cyclic stack
1212 priorVariables.remove(priorVariables.size() - 1);
1213 break;
1214 }
1215 nestedVarCount--;
1216 pos += endMatchLen;
1217 }
1218 }
1219 }
1220 }
1221 if (top) {
1222 return altered ? 1 : 0;
1223 }
1224 return lengthChange;
1225 }
1226 }