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