View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements. See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache license, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License. You may obtain a copy of the License at
    8    *
    9    *      http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the license for the specific language governing permissions and
    15   * limitations under the license.
    16   */
    17  package org.apache.logging.log4j.core.lookup;
    18
    19  import java.util.ArrayList;
    20  import java.util.Enumeration;
    21  import java.util.HashMap;
    22  import java.util.Iterator;
    23  import java.util.List;
    24  import java.util.Map;
    25  import java.util.Properties;
    26
    27  import org.apache.logging.log4j.core.LogEvent;
    28  import org.apache.logging.log4j.util.Strings;
    29
    30  /**
    31   * Substitutes variables within a string by values.
    32   * <p>
    33   * This class takes a piece of text and substitutes all the variables within it.
    34   * The default definition of a variable is <code>${variableName}</code>.
    35   * The prefix and suffix can be changed via constructors and set methods.
    36   * </p>
    37   * <p>
    38   * Variable values are typically resolved from a map, but could also be resolved
    39   * from system properties, or by supplying a custom variable resolver.
    40   * </p>
    41   * <p>
    42   * The simplest example is to use this class to replace Java System properties. For example:
    43   * </p>
    44   * <pre>
    45   * StrSubstitutor.replaceSystemProperties(
    46   *      "You are running with java.version = ${java.version} and os.name = ${os.name}.");
    47   * </pre>
    48   * <p>
    49   * Typical usage of this class follows the following pattern: First an instance is created
    50   * and initialized with the map that contains the values for the available variables.
    51   * If a prefix and/or suffix for variables should be used other than the default ones,
    52   * the appropriate settings can be performed. After that the <code>replace()</code>
    53   * method can be called passing in the source text for interpolation. In the returned
    54   * text all variable references (as long as their values are known) will be resolved.
    55   * The following example demonstrates this:
    56   * </p>
    57   * <pre>
    58   * Map valuesMap = HashMap();
    59   * valuesMap.put(&quot;animal&quot;, &quot;quick brown fox&quot;);
    60   * valuesMap.put(&quot;target&quot;, &quot;lazy dog&quot;);
    61   * String templateString = &quot;The ${animal} jumped over the ${target}.&quot;;
    62   * StrSubstitutor sub = new StrSubstitutor(valuesMap);
    63   * String resolvedString = sub.replace(templateString);
    64   * </pre>
    65   * <p>yielding:</p>
    66   * <pre>
    67   *      The quick brown fox jumped over the lazy dog.
    68   * </pre>
    69   * <p>
    70   * Also, this class allows to set a default value for unresolved variables.
    71   * The default value for a variable can be appended to the variable name after the variable
    72   * default value delimiter. The default value of the variable default value delimiter is ':-',
    73   * as in bash and other *nix shells, as those are arguably where the default ${} delimiter set originated.
    74   * The variable default value delimiter can be manually set by calling {@link #setValueDelimiterMatcher(StrMatcher)},
    75   * {@link #setValueDelimiter(char)} or {@link #setValueDelimiter(String)}.
    76   * The following shows an example with variable default value settings:
    77   * </p>
    78   * <pre>
    79   * Map valuesMap = HashMap();
    80   * valuesMap.put(&quot;animal&quot;, &quot;quick brown fox&quot;);
    81   * valuesMap.put(&quot;target&quot;, &quot;lazy dog&quot;);
    82   * String templateString = &quot;The ${animal} jumped over the ${target}. ${undefined.number:-1234567890}.&quot;;
    83   * StrSubstitutor sub = new StrSubstitutor(valuesMap);
    84   * String resolvedString = sub.replace(templateString);
    85   * </pre>
    86   * <p>yielding:</p>
    87   * <pre>
    88   *      The quick brown fox jumped over the lazy dog. 1234567890.
    89   * </pre>
    90   * <p>
    91   * In addition to this usage pattern there are some static convenience methods that
    92   * cover the most common use cases. These methods can be used without the need of
    93   * manually creating an instance. However if multiple replace operations are to be
    94   * performed, creating and reusing an instance of this class will be more efficient.
    95   * </p>
    96   * <p>
    97   * Variable replacement works in a recursive way. Thus, if a variable value contains
    98   * a variable then that variable will also be replaced. Cyclic replacements are
    99   * 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 }