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