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