View Javadoc
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(&quot;animal&quot;, &quot;quick brown fox&quot;);
59   * valuesMap.put(&quot;target&quot;, &quot;lazy dog&quot;);
60   * String templateString = &quot;The ${animal} jumps over the ${target}.&quot;;
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(&quot;animal&quot;, &quot;quick brown fox&quot;);
80   * valuesMap.put(&quot;target&quot;, &quot;lazy dog&quot;);
81   * String templateString = &quot;The ${animal} jumps over the ${target}. ${undefined.number:-1234567890}.&quot;;
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 }