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