001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.lang3.text;
018
019import java.util.ArrayList;
020import java.util.Enumeration;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Objects;
025import java.util.Properties;
026
027import org.apache.commons.lang3.StringUtils;
028
029/**
030 * Substitutes variables within a string by values.
031 * <p>
032 * This class takes a piece of text and substitutes all the variables within it.
033 * The default definition of a variable is {@code ${variableName}}.
034 * The prefix and suffix can be changed via constructors and set methods.
035 * </p>
036 * <p>
037 * Variable values are typically resolved from a map, but could also be resolved
038 * from system properties, or by supplying a custom variable resolver.
039 * </p>
040 * <p>
041 * The simplest example is to use this class to replace Java System properties. For example:
042 * </p>
043 * <pre>
044 * StrSubstitutor.replaceSystemProperties(
045 *      "You are running with java.version = ${java.version} and os.name = ${os.name}.");
046 * </pre>
047 * <p>
048 * Typical usage of this class follows the following pattern: First an instance is created
049 * and initialized with the map that contains the values for the available variables.
050 * If a prefix and/or suffix for variables should be used other than the default ones,
051 * the appropriate settings can be performed. After that the {@code replace()}
052 * method can be called passing in the source text for interpolation. In the returned
053 * text all variable references (as long as their values are known) will be resolved.
054 * The following example demonstrates this:
055 * </p>
056 * <pre>
057 * Map valuesMap = HashMap();
058 * valuesMap.put(&quot;animal&quot;, &quot;quick brown fox&quot;);
059 * valuesMap.put(&quot;target&quot;, &quot;lazy dog&quot;);
060 * String templateString = &quot;The ${animal} jumps over the ${target}.&quot;;
061 * StrSubstitutor sub = new StrSubstitutor(valuesMap);
062 * String resolvedString = sub.replace(templateString);
063 * </pre>
064 * yielding:
065 * <pre>
066 *      The quick brown fox jumps over the lazy dog.
067 * </pre>
068 * <p>
069 * Also, this class allows to set a default value for unresolved variables.
070 * The default value for a variable can be appended to the variable name after the variable
071 * default value delimiter. The default value of the variable default value delimiter is ':-',
072 * as in bash and other *nix shells, as those are arguably where the default ${} delimiter set originated.
073 * The variable default value delimiter can be manually set by calling {@link #setValueDelimiterMatcher(StrMatcher)},
074 * {@link #setValueDelimiter(char)} or {@link #setValueDelimiter(String)}.
075 * The following shows an example with variable default value settings:
076 * </p>
077 * <pre>
078 * Map valuesMap = HashMap();
079 * valuesMap.put(&quot;animal&quot;, &quot;quick brown fox&quot;);
080 * valuesMap.put(&quot;target&quot;, &quot;lazy dog&quot;);
081 * String templateString = &quot;The ${animal} jumps over the ${target}. ${undefined.number:-1234567890}.&quot;;
082 * StrSubstitutor sub = new StrSubstitutor(valuesMap);
083 * String resolvedString = sub.replace(templateString);
084 * </pre>
085 * <p>
086 * yielding:
087 * </p>
088 * <pre>
089 *      The quick brown fox jumps over the lazy dog. 1234567890.
090 * </pre>
091 * <p>
092 * In addition to this usage pattern there are some static convenience methods that
093 * cover the most common use cases. These methods can be used without the need of
094 * manually creating an instance. However if multiple replace operations are to be
095 * performed, creating and reusing an instance of this class will be more efficient.
096 * </p>
097 * <p>
098 * Variable replacement works in a recursive way. Thus, if a variable value contains
099 * 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 <b>true</b>.
138 * </p>
139 * <p>
140 * This class is <b>not</b> thread safe.
141 * </p>
142 *
143 * @since 2.2
144 * @deprecated As of 3.6, 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> instead
147 */
148@Deprecated
149public 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,
304                              final char escape) {
305        this(StrLookup.mapLookup(valueMap), prefix, suffix, escape);
306    }
307
308    /**
309     * Creates a new instance and initializes it.
310     *
311     * @param <V> the type of the values in the map
312     * @param valueMap  the map with the variables' values, may be null
313     * @param prefix  the prefix for variables, not null
314     * @param suffix  the suffix for variables, not null
315     * @param escape  the escape character
316     * @param valueDelimiter  the variable default value delimiter, may be null
317     * @throws IllegalArgumentException if the prefix or suffix is null
318     * @since 3.2
319     */
320    public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix,
321                              final char escape, final String valueDelimiter) {
322        this(StrLookup.mapLookup(valueMap), prefix, suffix, escape, valueDelimiter);
323    }
324
325    /**
326     * Creates a new instance and initializes it.
327     *
328     * @param variableResolver  the variable resolver, may be null
329     */
330    public StrSubstitutor(final StrLookup<?> variableResolver) {
331        this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
332    }
333
334    /**
335     * Creates a new instance and initializes it.
336     *
337     * @param variableResolver  the variable resolver, may be null
338     * @param prefix  the prefix for variables, not null
339     * @param suffix  the suffix for variables, not null
340     * @param escape  the escape character
341     * @throws IllegalArgumentException if the prefix or suffix is null
342     */
343    public StrSubstitutor(final StrLookup<?> variableResolver, final String prefix, final String suffix,
344                          final char escape) {
345        this.setVariableResolver(variableResolver);
346        this.setVariablePrefix(prefix);
347        this.setVariableSuffix(suffix);
348        this.setEscapeChar(escape);
349        this.setValueDelimiterMatcher(DEFAULT_VALUE_DELIMITER);
350    }
351
352    /**
353     * Creates a new instance and initializes it.
354     *
355     * @param variableResolver  the variable resolver, may be null
356     * @param prefix  the prefix for variables, not null
357     * @param suffix  the suffix for variables, not null
358     * @param escape  the escape character
359     * @param valueDelimiter  the variable default value delimiter string, may be null
360     * @throws IllegalArgumentException if the prefix or suffix is null
361     * @since 3.2
362     */
363    public StrSubstitutor(final StrLookup<?> variableResolver, final String prefix, final String suffix,
364                          final char escape, final String valueDelimiter) {
365        this.setVariableResolver(variableResolver);
366        this.setVariablePrefix(prefix);
367        this.setVariableSuffix(suffix);
368        this.setEscapeChar(escape);
369        this.setValueDelimiter(valueDelimiter);
370    }
371
372    /**
373     * Creates a new instance and initializes it.
374     *
375     * @param variableResolver  the variable resolver, may be null
376     * @param prefixMatcher  the prefix for variables, not null
377     * @param suffixMatcher  the suffix for variables, not null
378     * @param escape  the escape character
379     * @throws IllegalArgumentException if the prefix or suffix is null
380     */
381    public StrSubstitutor(
382            final StrLookup<?> variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher,
383            final char escape) {
384        this(variableResolver, prefixMatcher, suffixMatcher, escape, DEFAULT_VALUE_DELIMITER);
385    }
386
387    /**
388     * Creates a new instance and initializes it.
389     *
390     * @param variableResolver  the variable resolver, may be null
391     * @param prefixMatcher  the prefix for variables, not null
392     * @param suffixMatcher  the suffix for variables, not null
393     * @param escape  the escape character
394     * @param valueDelimiterMatcher  the variable default value delimiter matcher, may be null
395     * @throws IllegalArgumentException if the prefix or suffix is null
396     * @since 3.2
397     */
398    public StrSubstitutor(
399            final StrLookup<?> variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher,
400            final char escape, final StrMatcher valueDelimiterMatcher) {
401        this.setVariableResolver(variableResolver);
402        this.setVariablePrefixMatcher(prefixMatcher);
403        this.setVariableSuffixMatcher(suffixMatcher);
404        this.setEscapeChar(escape);
405        this.setValueDelimiterMatcher(valueDelimiterMatcher);
406    }
407
408    /**
409     * Checks if the specified variable is already in the stack (list) of variables.
410     *
411     * @param varName  the variable name to check
412     * @param priorVariables  the list of prior variables
413     */
414    private void checkCyclicSubstitution(final String varName, final List<String> priorVariables) {
415        if (!priorVariables.contains(varName)) {
416            return;
417        }
418        final StrBuilder buf = new StrBuilder(256);
419        buf.append("Infinite loop in property interpolation of ");
420        buf.append(priorVariables.remove(0));
421        buf.append(": ");
422        buf.appendWithSeparators(priorVariables, "->");
423        throw new IllegalStateException(buf.toString());
424    }
425
426    /**
427     * Returns the escape character.
428     *
429     * @return the character used for escaping variable references
430     */
431    public char getEscapeChar() {
432        return this.escapeChar;
433    }
434
435    /**
436     * Gets the variable default value delimiter matcher currently in use.
437     * <p>
438     * The variable default value delimiter is the character or characters that delimit the
439     * variable name and the variable default value. This delimiter is expressed in terms of a matcher
440     * allowing advanced variable default value delimiter matches.
441     * </p>
442     * <p>
443     * If it returns null, then the variable default value resolution is disabled.
444     * </p>
445     *
446     * @return the variable default value delimiter matcher in use, may be null
447     * @since 3.2
448     */
449    public StrMatcher getValueDelimiterMatcher() {
450        return valueDelimiterMatcher;
451    }
452
453    /**
454     * Gets the variable prefix matcher currently in use.
455     * <p>
456     * The variable prefix is the character or characters that identify the
457     * start of a variable. This prefix is expressed in terms of a matcher
458     * allowing advanced prefix matches.
459     * </p>
460     *
461     * @return the prefix matcher in use
462     */
463    public StrMatcher getVariablePrefixMatcher() {
464        return prefixMatcher;
465    }
466
467    /**
468     * Gets the VariableResolver that is used to lookup variables.
469     *
470     * @return the VariableResolver
471     */
472    public StrLookup<?> getVariableResolver() {
473        return this.variableResolver;
474    }
475
476    /**
477     * Gets the variable suffix matcher currently in use.
478     * <p>
479     * The variable suffix is the character or characters that identify the
480     * end of a variable. This suffix is expressed in terms of a matcher
481     * allowing advanced suffix matches.
482     * </p>
483     *
484     * @return the suffix matcher in use
485     */
486    public StrMatcher getVariableSuffixMatcher() {
487        return suffixMatcher;
488    }
489
490    /**
491     * Returns a flag whether substitution is done in variable names.
492     *
493     * @return the substitution in variable names flag
494     * @since 3.0
495     */
496    public boolean isEnableSubstitutionInVariables() {
497        return enableSubstitutionInVariables;
498    }
499
500    /**
501     * Returns the flag controlling whether escapes are preserved during
502     * substitution.
503     *
504     * @return the preserve escape flag
505     * @since 3.5
506     */
507    public boolean isPreserveEscapes() {
508        return preserveEscapes;
509    }
510
511    /**
512     * Replaces all the occurrences of variables with their matching values
513     * from the resolver using the given source array as a template.
514     * The array is not altered by this method.
515     *
516     * @param source  the character array to replace in, not altered, null returns null
517     * @return the result of the replace operation
518     */
519    public String replace(final char[] source) {
520        if (source == null) {
521            return null;
522        }
523        final StrBuilder buf = new StrBuilder(source.length).append(source);
524        substitute(buf, 0, source.length);
525        return buf.toString();
526    }
527
528    /**
529     * Replaces all the occurrences of variables with their matching values
530     * from the resolver using the given source array as a template.
531     * The array is not altered by this method.
532     * <p>
533     * Only the specified portion of the array will be processed.
534     * The rest of the array is not processed, and is not returned.
535     * </p>
536     *
537     * @param source  the character array to replace in, not altered, null returns null
538     * @param offset  the start offset within the array, must be valid
539     * @param length  the length within the array to be processed, must be valid
540     * @return the result of the replace operation
541     */
542    public String replace(final char[] source, final int offset, final int length) {
543        if (source == null) {
544            return null;
545        }
546        final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
547        substitute(buf, 0, length);
548        return buf.toString();
549    }
550
551    /**
552     * Replaces all the occurrences of variables with their matching values
553     * from the resolver using the given source as a template.
554     * The source is not altered by this method.
555     *
556     * @param source  the buffer to use as a template, not changed, null returns null
557     * @return the result of the replace operation
558     * @since 3.2
559     */
560    public String replace(final CharSequence source) {
561        if (source == null) {
562            return null;
563        }
564        return replace(source, 0, source.length());
565    }
566
567    /**
568     * Replaces all the occurrences of variables with their matching values
569     * from the resolver using the given source as a template.
570     * The source is not altered by this method.
571     * <p>
572     * Only the specified portion of the buffer will be processed.
573     * The rest of the buffer is not processed, and is not returned.
574     * </p>
575     *
576     * @param source  the buffer to use as a template, not changed, null returns null
577     * @param offset  the start offset within the array, must be valid
578     * @param length  the length within the array to be processed, must be valid
579     * @return the result of the replace operation
580     * @since 3.2
581     */
582    public String replace(final CharSequence source, final int offset, final int length) {
583        if (source == null) {
584            return null;
585        }
586        final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
587        substitute(buf, 0, length);
588        return buf.toString();
589    }
590
591    /**
592     * Replaces all the occurrences of variables in the given source object with
593     * their matching values from the resolver. The input source object is
594     * converted to a string using {@code toString} and is not altered.
595     *
596     * @param source  the source to replace in, null returns null
597     * @return the result of the replace operation
598     */
599    public String replace(final Object source) {
600        if (source == null) {
601            return null;
602        }
603        final StrBuilder buf = new StrBuilder().append(source);
604        substitute(buf, 0, buf.length());
605        return buf.toString();
606    }
607
608    /**
609     * Replaces all the occurrences of variables with their matching values
610     * from the resolver using the given source builder as a template.
611     * The builder is not altered by this method.
612     *
613     * @param source  the builder to use as a template, not changed, null returns null
614     * @return the result of the replace operation
615     */
616    public String replace(final StrBuilder source) {
617        if (source == null) {
618            return null;
619        }
620        final StrBuilder buf = new StrBuilder(source.length()).append(source);
621        substitute(buf, 0, buf.length());
622        return buf.toString();
623    }
624
625    /**
626     * Replaces all the occurrences of variables with their matching values
627     * from the resolver using the given source builder as a template.
628     * The builder is not altered by this method.
629     * <p>
630     * Only the specified portion of the builder will be processed.
631     * The rest of the builder is not processed, and is not returned.
632     * </p>
633     *
634     * @param source  the builder to use as a template, not changed, null returns null
635     * @param offset  the start offset within the array, must be valid
636     * @param length  the length within the array to be processed, must be valid
637     * @return the result of the replace operation
638     */
639    public String replace(final StrBuilder source, final int offset, final int length) {
640        if (source == null) {
641            return null;
642        }
643        final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
644        substitute(buf, 0, length);
645        return buf.toString();
646    }
647
648    /**
649     * Replaces all the occurrences of variables with their matching values
650     * from the resolver using the given source string as a template.
651     *
652     * @param source  the string to replace in, null returns null
653     * @return the result of the replace operation
654     */
655    public String replace(final String source) {
656        if (source == null) {
657            return null;
658        }
659        final StrBuilder buf = new StrBuilder(source);
660        if (!substitute(buf, 0, source.length())) {
661            return source;
662        }
663        return buf.toString();
664    }
665
666    /**
667     * Replaces all the occurrences of variables with their matching values
668     * from the resolver using the given source string as a template.
669     * <p>
670     * Only the specified portion of the string will be processed.
671     * The rest of the string is not processed, and is not returned.
672     * </p>
673     *
674     * @param source  the string to replace in, null returns null
675     * @param offset  the start offset within the array, must be valid
676     * @param length  the length within the array to be processed, must be valid
677     * @return the result of the replace operation
678     */
679    public String replace(final String source, final int offset, final int length) {
680        if (source == null) {
681            return null;
682        }
683        final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
684        if (!substitute(buf, 0, length)) {
685            return source.substring(offset, offset + length);
686        }
687        return buf.toString();
688    }
689
690    /**
691     * Replaces all the occurrences of variables with their matching values
692     * from the resolver using the given source buffer as a template.
693     * The buffer is not altered by this method.
694     *
695     * @param source  the buffer to use as a template, not changed, null returns null
696     * @return the result of the replace operation
697     */
698    public String replace(final StringBuffer source) {
699        if (source == null) {
700            return null;
701        }
702        final StrBuilder buf = new StrBuilder(source.length()).append(source);
703        substitute(buf, 0, buf.length());
704        return buf.toString();
705    }
706
707    /**
708     * Replaces all the occurrences of variables with their matching values
709     * from the resolver using the given source buffer as a template.
710     * The buffer is not altered by this method.
711     * <p>
712     * Only the specified portion of the buffer will be processed.
713     * The rest of the buffer is not processed, and is not returned.
714     * </p>
715     *
716     * @param source  the buffer to use as a template, not changed, null returns null
717     * @param offset  the start offset within the array, must be valid
718     * @param length  the length within the array to be processed, must be valid
719     * @return the result of the replace operation
720     */
721    public String replace(final StringBuffer source, final int offset, final int length) {
722        if (source == null) {
723            return null;
724        }
725        final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
726        substitute(buf, 0, length);
727        return buf.toString();
728    }
729
730    /**
731     * Replaces all the occurrences of variables within the given source
732     * builder with their matching values from the resolver.
733     *
734     * @param source  the builder to replace in, updated, null returns zero
735     * @return true if altered
736     */
737    public boolean replaceIn(final StrBuilder source) {
738        if (source == null) {
739            return false;
740        }
741        return substitute(source, 0, source.length());
742    }
743
744    /**
745     * Replaces all the occurrences of variables within the given source
746     * builder with their matching values from the resolver.
747     * <p>
748     * Only the specified portion of the builder will be processed.
749     * The rest of the builder is not processed, but it is not deleted.
750     * </p>
751     *
752     * @param source  the builder to replace in, null returns zero
753     * @param offset  the start offset within the array, must be valid
754     * @param length  the length within the builder to be processed, must be valid
755     * @return true if altered
756     */
757    public boolean replaceIn(final StrBuilder source, final int offset, final int length) {
758        if (source == null) {
759            return false;
760        }
761        return substitute(source, offset, length);
762    }
763
764    /**
765     * Replaces all the occurrences of variables within the given source buffer
766     * with their matching values from the resolver.
767     * The buffer is updated with the result.
768     *
769     * @param source  the buffer to replace in, updated, null returns zero
770     * @return true if altered
771     */
772    public boolean replaceIn(final StringBuffer source) {
773        if (source == null) {
774            return false;
775        }
776        return replaceIn(source, 0, source.length());
777    }
778
779    /**
780     * Replaces all the occurrences of variables within the given source buffer
781     * with their matching values from the resolver.
782     * The buffer is updated with the result.
783     * <p>
784     * Only the specified portion of the buffer will be processed.
785     * The rest of the buffer is not processed, but it is not deleted.
786     * </p>
787     *
788     * @param source  the buffer to replace in, updated, null returns zero
789     * @param offset  the start offset within the array, must be valid
790     * @param length  the length within the buffer to be processed, must be valid
791     * @return true if altered
792     */
793    public boolean replaceIn(final StringBuffer source, final int offset, final int length) {
794        if (source == null) {
795            return false;
796        }
797        final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
798        if (!substitute(buf, 0, length)) {
799            return false;
800        }
801        source.replace(offset, offset + length, buf.toString());
802        return true;
803    }
804
805    /**
806     * Replaces all the occurrences of variables within the given source buffer
807     * with their matching values from the resolver.
808     * The buffer is updated with the result.
809     *
810     * @param source  the buffer to replace in, updated, null returns zero
811     * @return true if altered
812     * @since 3.2
813     */
814    public boolean replaceIn(final StringBuilder source) {
815        if (source == null) {
816            return false;
817        }
818        return replaceIn(source, 0, source.length());
819    }
820
821    /**
822     * Replaces all the occurrences of variables within the given source builder
823     * with their matching values from the resolver.
824     * The builder is updated with the result.
825     * <p>
826     * Only the specified portion of the buffer will be processed.
827     * The rest of the buffer is not processed, but it is not deleted.
828     * </p>
829     *
830     * @param source  the buffer to replace in, updated, null returns zero
831     * @param offset  the start offset within the array, must be valid
832     * @param length  the length within the buffer to be processed, must be valid
833     * @return true if altered
834     * @since 3.2
835     */
836    public boolean replaceIn(final StringBuilder source, final int offset, final int length) {
837        if (source == null) {
838            return false;
839        }
840        final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
841        if (!substitute(buf, 0, length)) {
842            return false;
843        }
844        source.replace(offset, offset + length, buf.toString());
845        return true;
846    }
847
848    /**
849     * Internal method that resolves the value of a variable.
850     * <p>
851     * Most users of this class do not need to call this method. This method is
852     * called automatically by the substitution process.
853     * </p>
854     * <p>
855     * Writers of subclasses can override this method if they need to alter
856     * how each substitution occurs. The method is passed the variable's name
857     * and must return the corresponding value. This implementation uses the
858     * {@link #getVariableResolver()} with the variable's name as the key.
859     * </p>
860     *
861     * @param variableName  the name of the variable, not null
862     * @param buf  the buffer where the substitution is occurring, not null
863     * @param startPos  the start position of the variable including the prefix, valid
864     * @param endPos  the end position of the variable including the suffix, valid
865     * @return the variable's value or <b>null</b> if the variable is unknown
866     */
867    protected String resolveVariable(final String variableName, final StrBuilder buf, final int startPos, final int endPos) {
868        final StrLookup<?> resolver = getVariableResolver();
869        if (resolver == null) {
870            return null;
871        }
872        return resolver.lookup(variableName);
873    }
874
875    /**
876     * Sets a flag whether substitution is done in variable names. If set to
877     * <b>true</b>, the names of variables can contain other variables which are
878     * processed first before the original variable is evaluated, e.g.
879     * {@code ${jre-${java.version}}}. The default value is <b>false</b>.
880     *
881     * @param enableSubstitutionInVariables the new value of the flag
882     * @since 3.0
883     */
884    public void setEnableSubstitutionInVariables(
885            final boolean enableSubstitutionInVariables) {
886        this.enableSubstitutionInVariables = enableSubstitutionInVariables;
887    }
888
889    /**
890     * Sets the escape character.
891     * If this character is placed before a variable reference in the source
892     * text, this variable will be ignored.
893     *
894     * @param escapeCharacter  the escape character (0 for disabling escaping)
895     */
896    public void setEscapeChar(final char escapeCharacter) {
897        this.escapeChar = escapeCharacter;
898    }
899
900    /**
901     * Sets a flag controlling whether escapes are preserved during
902     * substitution.  If set to <b>true</b>, the escape character is retained
903     * during substitution (e.g. {@code $${this-is-escaped}} remains
904     * {@code $${this-is-escaped}}).  If set to <b>false</b>, the escape
905     * character is removed during substitution (e.g.
906     * {@code $${this-is-escaped}} becomes
907     * {@code ${this-is-escaped}}).  The default value is <b>false</b>
908     *
909     * @param preserveEscapes true if escapes are to be preserved
910     * @since 3.5
911     */
912    public void setPreserveEscapes(final boolean preserveEscapes) {
913        this.preserveEscapes = preserveEscapes;
914    }
915
916    /**
917     * Sets the variable default value delimiter to use.
918     * <p>
919     * The variable default value delimiter is the character or characters that delimit the
920     * variable name and the variable default value. This method allows a single character
921     * variable default value delimiter to be easily set.
922     * </p>
923     *
924     * @param valueDelimiter  the variable default value delimiter character to use
925     * @return this, to enable chaining
926     * @since 3.2
927     */
928    public StrSubstitutor setValueDelimiter(final char valueDelimiter) {
929        return setValueDelimiterMatcher(StrMatcher.charMatcher(valueDelimiter));
930    }
931
932    /**
933     * Sets the variable default value delimiter to use.
934     * <p>
935     * The variable default value delimiter is the character or characters that delimit the
936     * variable name and the variable default value. This method allows a string
937     * variable default value delimiter to be easily set.
938     * </p>
939     * <p>
940     * If the {@code valueDelimiter} is null or empty string, then the variable default
941     * value resolution becomes disabled.
942     * </p>
943     *
944     * @param valueDelimiter  the variable default value delimiter string to use, may be null or empty
945     * @return this, to enable chaining
946     * @since 3.2
947     */
948    public StrSubstitutor setValueDelimiter(final String valueDelimiter) {
949        if (StringUtils.isEmpty(valueDelimiter)) {
950            setValueDelimiterMatcher(null);
951            return this;
952        }
953        return setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter));
954    }
955
956    /**
957     * Sets the variable default value delimiter matcher to use.
958     * <p>
959     * The variable default value delimiter is the character or characters that delimit the
960     * variable name and the variable default value. This delimiter is expressed in terms of a matcher
961     * allowing advanced variable default value delimiter matches.
962     * </p>
963     * <p>
964     * If the {@code valueDelimiterMatcher} is null, then the variable default value resolution
965     * becomes disabled.
966     * </p>
967     *
968     * @param valueDelimiterMatcher  variable default value delimiter matcher to use, may be null
969     * @return this, to enable chaining
970     * @since 3.2
971     */
972    public StrSubstitutor setValueDelimiterMatcher(final StrMatcher valueDelimiterMatcher) {
973        this.valueDelimiterMatcher = valueDelimiterMatcher;
974        return this;
975    }
976
977    /**
978     * Sets the variable prefix to use.
979     * <p>
980     * The variable prefix is the character or characters that identify the
981     * start of a variable. This method allows a single character prefix to
982     * be easily set.
983     * </p>
984     *
985     * @param prefix  the prefix character to use
986     * @return this, to enable chaining
987     */
988    public StrSubstitutor setVariablePrefix(final char prefix) {
989        return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix));
990    }
991
992    /**
993     * Sets the variable prefix to use.
994     * <p>
995     * The variable prefix is the character or characters that identify the
996     * start of a variable. This method allows a string prefix to be easily set.
997     * </p>
998     *
999     * @param prefix  the prefix for variables, not null
1000     * @return this, to enable chaining
1001     * @throws NullPointerException if the prefix is null
1002     */
1003    public StrSubstitutor setVariablePrefix(final String prefix) {
1004        return setVariablePrefixMatcher(StrMatcher.stringMatcher(Objects.requireNonNull(prefix)));
1005    }
1006
1007    /**
1008     * Sets the variable prefix matcher currently in use.
1009     * <p>
1010     * The variable prefix is the character or characters that identify the
1011     * start of a variable. This prefix is expressed in terms of a matcher
1012     * allowing advanced prefix matches.
1013     * </p>
1014     *
1015     * @param prefixMatcher  the prefix matcher to use, null ignored
1016     * @return this, to enable chaining
1017     * @throws NullPointerException if the prefix matcher is null
1018     */
1019    public StrSubstitutor setVariablePrefixMatcher(final StrMatcher prefixMatcher) {
1020        this.prefixMatcher = Objects.requireNonNull(prefixMatcher, "prefixMatcher");
1021        return this;
1022    }
1023
1024    /**
1025     * Sets the VariableResolver that is used to lookup variables.
1026     *
1027     * @param variableResolver  the VariableResolver
1028     */
1029    public void setVariableResolver(final StrLookup<?> variableResolver) {
1030        this.variableResolver = variableResolver;
1031    }
1032
1033    /**
1034     * Sets the variable suffix to use.
1035     * <p>
1036     * The variable suffix is the character or characters that identify the
1037     * end of a variable. This method allows a single character suffix to
1038     * be easily set.
1039     * </p>
1040     *
1041     * @param suffix  the suffix character to use
1042     * @return this, to enable chaining
1043     */
1044    public StrSubstitutor setVariableSuffix(final char suffix) {
1045        return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix));
1046    }
1047
1048    /**
1049     * Sets the variable suffix to use.
1050     * <p>
1051     * The variable suffix is the character or characters that identify the
1052     * end of a variable. This method allows a string suffix to be easily set.
1053     * </p>
1054     *
1055     * @param suffix  the suffix for variables, not null
1056     * @return this, to enable chaining
1057     * @throws NullPointerException if the suffix is null
1058     */
1059    public StrSubstitutor setVariableSuffix(final String suffix) {
1060        return setVariableSuffixMatcher(StrMatcher.stringMatcher(Objects.requireNonNull(suffix)));
1061    }
1062
1063    /**
1064     * Sets the variable suffix matcher currently in use.
1065     * <p>
1066     * The variable suffix is the character or characters that identify the
1067     * end of a variable. This suffix is expressed in terms of a matcher
1068     * allowing advanced suffix matches.
1069     * </p>
1070     *
1071     * @param suffixMatcher  the suffix matcher to use, null ignored
1072     * @return this, to enable chaining
1073     * @throws NullPointerException if the suffix matcher is null
1074     */
1075    public StrSubstitutor setVariableSuffixMatcher(final StrMatcher suffixMatcher) {
1076        this.suffixMatcher = Objects.requireNonNull(suffixMatcher);
1077        return this;
1078    }
1079
1080    /**
1081     * Internal method that substitutes the variables.
1082     * <p>
1083     * Most users of this class do not need to call this method. This method will
1084     * be called automatically by another (public) method.
1085     * </p>
1086     * <p>
1087     * Writers of subclasses can override this method if they need access to
1088     * the substitution process at the start or end.
1089     * </p>
1090     *
1091     * @param buf  the string builder to substitute into, not null
1092     * @param offset  the start offset within the builder, must be valid
1093     * @param length  the length within the builder to be processed, must be valid
1094     * @return true if altered
1095     */
1096    protected boolean substitute(final StrBuilder buf, final int offset, final int length) {
1097        return substitute(buf, offset, length, null) > 0;
1098    }
1099
1100    /**
1101     * Recursive handler for multiple levels of interpolation. This is the main
1102     * interpolation method, which resolves the values of all variable references
1103     * contained in the passed-in text.
1104     *
1105     * @param buf  the string builder to substitute into, not null
1106     * @param offset  the start offset within the builder, must be valid
1107     * @param length  the length within the builder to be processed, must be valid
1108     * @param priorVariables  the stack keeping track of the replaced variables, may be null
1109     * @return the length change that occurs, unless priorVariables is null when the int
1110     *  represents a boolean flag as to whether any change occurred.
1111     */
1112    private int substitute(final StrBuilder buf, final int offset, final int length, List<String> priorVariables) {
1113        final StrMatcher pfxMatcher = getVariablePrefixMatcher();
1114        final StrMatcher suffMatcher = getVariableSuffixMatcher();
1115        final char escape = getEscapeChar();
1116        final StrMatcher valueDelimMatcher = getValueDelimiterMatcher();
1117        final boolean substitutionInVariablesEnabled = isEnableSubstitutionInVariables();
1118
1119        final boolean top = priorVariables == null;
1120        boolean altered = false;
1121        int lengthChange = 0;
1122        char[] chars = buf.buffer;
1123        int bufEnd = offset + length;
1124        int pos = offset;
1125        while (pos < bufEnd) {
1126            final int startMatchLen = pfxMatcher.isMatch(chars, pos, offset,
1127                    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
1150                            && (endMatchLen = pfxMatcher.isMatch(chars,
1151                                    pos, offset, bufEnd)) != 0) {
1152                        // found a nested variable start
1153                        nestedVarCount++;
1154                        pos += endMatchLen;
1155                        continue;
1156                    }
1157
1158                    endMatchLen = suffMatcher.isMatch(chars, pos, offset,
1159                            bufEnd);
1160                    if (endMatchLen == 0) {
1161                        pos++;
1162                    } else {
1163                        // found variable end marker
1164                        if (nestedVarCount == 0) {
1165                            String varNameExpr = new String(chars, startPos
1166                                    + startMatchLen, pos - startPos
1167                                    - startMatchLen);
1168                            if (substitutionInVariablesEnabled) {
1169                                final StrBuilder bufName = new StrBuilder(varNameExpr);
1170                                substitute(bufName, 0, bufName.length());
1171                                varNameExpr = bufName.toString();
1172                            }
1173                            pos += endMatchLen;
1174                            final int endPos = pos;
1175
1176                            String varName = varNameExpr;
1177                            String varDefaultValue = null;
1178
1179                            if (valueDelimMatcher != null) {
1180                                final char [] varNameExprChars = varNameExpr.toCharArray();
1181                                int valueDelimiterMatchLen;
1182                                for (int i = 0; i < varNameExprChars.length; i++) {
1183                                    // if there's any nested variable when nested variable substitution disabled, then stop resolving name and default value.
1184                                    if (!substitutionInVariablesEnabled
1185                                            && pfxMatcher.isMatch(varNameExprChars, i, i, varNameExprChars.length) != 0) {
1186                                        break;
1187                                    }
1188                                    if ((valueDelimiterMatchLen = valueDelimMatcher.isMatch(varNameExprChars, i)) != 0) {
1189                                        varName = varNameExpr.substring(0, i);
1190                                        varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen);
1191                                        break;
1192                                    }
1193                                }
1194                            }
1195
1196                            // on the first call initialize priorVariables
1197                            if (priorVariables == null) {
1198                                priorVariables = new ArrayList<>();
1199                                priorVariables.add(new String(chars,
1200                                        offset, length));
1201                            }
1202
1203                            // handle cyclic substitution
1204                            checkCyclicSubstitution(varName, priorVariables);
1205                            priorVariables.add(varName);
1206
1207                            // resolve the variable
1208                            String varValue = resolveVariable(varName, buf,
1209                                    startPos, endPos);
1210                            if (varValue == null) {
1211                                varValue = varDefaultValue;
1212                            }
1213                            if (varValue != null) {
1214                                // recursive replace
1215                                final int varLen = varValue.length();
1216                                buf.replace(startPos, endPos, varValue);
1217                                altered = true;
1218                                int change = substitute(buf, startPos,
1219                                        varLen, priorVariables);
1220                                change = change
1221                                        + varLen - (endPos - startPos);
1222                                pos += change;
1223                                bufEnd += change;
1224                                lengthChange += change;
1225                                chars = buf.buffer; // in case buffer was
1226                                                    // altered
1227                            }
1228
1229                            // remove variable from the cyclic stack
1230                            priorVariables
1231                                    .remove(priorVariables.size() - 1);
1232                            break;
1233                        }
1234                        nestedVarCount--;
1235                        pos += endMatchLen;
1236                    }
1237                }
1238            }
1239        }
1240        if (top) {
1241            return altered ? 1 : 0;
1242        }
1243        return lengthChange;
1244    }
1245}