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