Source code
Description: source: package: org.apache.commons.lang3.text, class: StrSubstitutor

    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}