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