View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.lookup;
18  
19  import java.util.ArrayList;
20  import java.util.Enumeration;
21  import java.util.HashMap;
22  import java.util.Iterator;
23  import java.util.List;
24  import java.util.Map;
25  import java.util.Properties;
26  
27  import org.apache.logging.log4j.core.LogEvent;
28  import org.apache.logging.log4j.core.config.Configuration;
29  import org.apache.logging.log4j.core.config.ConfigurationAware;
30  import org.apache.logging.log4j.util.Strings;
31  
32  /**
33   * Substitutes variables within a string by values.
34   * <p>
35   * This class takes a piece of text and substitutes all the variables within it.
36   * The default definition of a variable is <code>${variableName}</code>.
37   * The prefix and suffix can be changed via constructors and set methods.
38   * </p>
39   * <p>
40   * Variable values are typically resolved from a map, but could also be resolved
41   * from system properties, or by supplying a custom variable resolver.
42   * </p>
43   * <p>
44   * The simplest example is to use this class to replace Java System properties. For example:
45   * </p>
46   * <pre>
47   * StrSubstitutor.replaceSystemProperties(
48   *      "You are running with java.version = ${java.version} and os.name = ${os.name}.");
49   * </pre>
50   * <p>
51   * Typical usage of this class follows the following pattern: First an instance is created
52   * and initialized with the map that contains the values for the available variables.
53   * If a prefix and/or suffix for variables should be used other than the default ones,
54   * the appropriate settings can be performed. After that the <code>replace()</code>
55   * method can be called passing in the source text for interpolation. In the returned
56   * text all variable references (as long as their values are known) will be resolved.
57   * The following example demonstrates this:
58   * </p>
59   * <pre>
60   * Map valuesMap = HashMap();
61   * valuesMap.put(&quot;animal&quot;, &quot;quick brown fox&quot;);
62   * valuesMap.put(&quot;target&quot;, &quot;lazy dog&quot;);
63   * String templateString = &quot;The ${animal} jumped over the ${target}.&quot;;
64   * StrSubstitutor sub = new StrSubstitutor(valuesMap);
65   * String resolvedString = sub.replace(templateString);
66   * </pre>
67   * <p>yielding:</p>
68   * <pre>
69   *      The quick brown fox jumped over the lazy dog.
70   * </pre>
71   * <p>
72   * Also, this class allows to set a default value for unresolved variables.
73   * The default value for a variable can be appended to the variable name after the variable
74   * default value delimiter. The default value of the variable default value delimiter is ':-',
75   * as in bash and other *nix shells, as those are arguably where the default ${} delimiter set originated.
76   * The variable default value delimiter can be manually set by calling {@link #setValueDelimiterMatcher(StrMatcher)},
77   * {@link #setValueDelimiter(char)} or {@link #setValueDelimiter(String)}.
78   * The following shows an example with variable default value settings:
79   * </p>
80   * <pre>
81   * Map valuesMap = HashMap();
82   * valuesMap.put(&quot;animal&quot;, &quot;quick brown fox&quot;);
83   * valuesMap.put(&quot;target&quot;, &quot;lazy dog&quot;);
84   * String templateString = &quot;The ${animal} jumped over the ${target}. ${undefined.number:-1234567890}.&quot;;
85   * StrSubstitutor sub = new StrSubstitutor(valuesMap);
86   * String resolvedString = sub.replace(templateString);
87   * </pre>
88   * <p>yielding:</p>
89   * <pre>
90   *      The quick brown fox jumped over the lazy dog. 1234567890.
91   * </pre>
92   * <p>
93   * In addition to this usage pattern there are some static convenience methods that
94   * cover the most common use cases. These methods can be used without the need of
95   * manually creating an instance. However if multiple replace operations are to be
96   * performed, creating and reusing an instance of this class will be more efficient.
97   * </p>
98   * <p>
99   * 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  */
141 public 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      * Creates a new instance and initializes it. Uses defaults for variable
204      * prefix and suffix and the escaping character.
205      *
206      * @param valueMap  the map with the variables' values, may be null
207      */
208     public StrSubstitutor(final Map<String, String> valueMap) {
209         this(new MapLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
210     }
211 
212     /**
213      * Creates a new instance and initializes it. Uses a default escaping character.
214      *
215      * @param valueMap  the map with the variables' values, may be null
216      * @param prefix  the prefix for variables, not null
217      * @param suffix  the suffix for variables, not null
218      * @throws IllegalArgumentException if the prefix or suffix is null
219      */
220     public StrSubstitutor(final Map<String, String> valueMap, final String prefix, final String suffix) {
221         this(new MapLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE);
222     }
223 
224     /**
225      * Creates a new instance and initializes it.
226      *
227      * @param valueMap  the map with the variables' values, may be null
228      * @param prefix  the prefix for variables, not null
229      * @param suffix  the suffix for variables, not null
230      * @param escape  the escape character
231      * @throws IllegalArgumentException if the prefix or suffix is null
232      */
233     public StrSubstitutor(final Map<String, String> valueMap, final String prefix, final String suffix,
234                           final char escape) {
235         this(new MapLookup(valueMap), prefix, suffix, escape);
236     }
237 
238     /**
239      * Creates a new instance and initializes it.
240      *
241      * @param valueMap  the map with the variables' values, may be null
242      * @param prefix  the prefix for variables, not null
243      * @param suffix  the suffix for variables, not null
244      * @param escape  the escape character
245      * @param valueDelimiter  the variable default value delimiter, may be null
246      * @throws IllegalArgumentException if the prefix or suffix is null
247      */
248     public StrSubstitutor(final Map<String, String> valueMap, final String prefix, final String suffix,
249                               final char escape, final String valueDelimiter) {
250         this(new MapLookup(valueMap), prefix, suffix, escape, valueDelimiter);
251     }
252 
253     /**
254      * Creates a new instance and initializes it.
255      *
256      * @param variableResolver  the variable resolver, may be null
257      */
258     public StrSubstitutor(final StrLookup variableResolver) {
259         this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
260     }
261 
262     /**
263      * Creates a new instance and initializes it.
264      *
265      * @param variableResolver  the variable resolver, may be null
266      * @param prefix  the prefix for variables, not null
267      * @param suffix  the suffix for variables, not null
268      * @param escape  the escape character
269      * @throws IllegalArgumentException if the prefix or suffix is null
270      */
271     public StrSubstitutor(final StrLookup variableResolver, final String prefix, final String suffix,
272                           final char escape) {
273         this.setVariableResolver(variableResolver);
274         this.setVariablePrefix(prefix);
275         this.setVariableSuffix(suffix);
276         this.setEscapeChar(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      * @param valueDelimiter  the variable default value delimiter string, may be null
287      * @throws IllegalArgumentException if the prefix or suffix is null
288      */
289     public StrSubstitutor(final StrLookup variableResolver, final String prefix, final String suffix, final char escape, final String valueDelimiter) {
290         this.setVariableResolver(variableResolver);
291         this.setVariablePrefix(prefix);
292         this.setVariableSuffix(suffix);
293         this.setEscapeChar(escape);
294         this.setValueDelimiter(valueDelimiter);
295     }
296 
297     /**
298      * Creates a new instance and initializes it.
299      *
300      * @param variableResolver  the variable resolver, may be null
301      * @param prefixMatcher  the prefix for variables, not null
302      * @param suffixMatcher  the suffix for variables, not null
303      * @param escape  the escape character
304      * @throws IllegalArgumentException if the prefix or suffix is null
305      */
306     public StrSubstitutor(final StrLookup variableResolver, final StrMatcher prefixMatcher,
307                           final StrMatcher suffixMatcher,
308                           final char escape) {
309         this(variableResolver, prefixMatcher, suffixMatcher, escape, DEFAULT_VALUE_DELIMITER);
310     }
311 
312     /**
313      * Creates a new instance and initializes it.
314      *
315      * @param variableResolver  the variable resolver, may be null
316      * @param prefixMatcher  the prefix for variables, not null
317      * @param suffixMatcher  the suffix for variables, not null
318      * @param escape  the escape character
319      * @param valueDelimiterMatcher  the variable default value delimiter matcher, may be null
320      * @throws IllegalArgumentException if the prefix or suffix is null
321      */
322     public StrSubstitutor(
323             final StrLookup variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher, final char escape, final StrMatcher valueDelimiterMatcher) {
324         this.setVariableResolver(variableResolver);
325         this.setVariablePrefixMatcher(prefixMatcher);
326         this.setVariableSuffixMatcher(suffixMatcher);
327         this.setEscapeChar(escape);
328         this.setValueDelimiterMatcher(valueDelimiterMatcher);
329     }
330 
331     //-----------------------------------------------------------------------
332     /**
333      * Replaces all the occurrences of variables in the given source object with
334      * their matching values from the map.
335      *
336      * @param source  the source text containing the variables to substitute, null returns null
337      * @param valueMap  the map with the values, may be null
338      * @return the result of the replace operation
339      */
340     public static String replace(final Object source, final Map<String, String> valueMap) {
341         return new StrSubstitutor(valueMap).replace(source);
342     }
343 
344     /**
345      * Replaces all the occurrences of variables in the given source object with
346      * their matching values from the map. This method allows to specify a
347      * custom variable prefix and suffix
348      *
349      * @param source  the source text containing the variables to substitute, null returns null
350      * @param valueMap  the map with the values, may be null
351      * @param prefix  the prefix of variables, not null
352      * @param suffix  the suffix of variables, not null
353      * @return the result of the replace operation
354      * @throws IllegalArgumentException if the prefix or suffix is null
355      */
356     public static String replace(final Object source, final Map<String, String> valueMap, final String prefix,
357                                  final String suffix) {
358         return new StrSubstitutor(valueMap, prefix, suffix).replace(source);
359     }
360 
361     /**
362      * Replaces all the occurrences of variables in the given source object with their matching
363      * values from the properties.
364      *
365      * @param source the source text containing the variables to substitute, null returns null
366      * @param valueProperties the properties with values, may be null
367      * @return the result of the replace operation
368      */
369     public static String replace(final Object source, final Properties valueProperties) {
370         if (valueProperties == null) {
371             return source.toString();
372         }
373         final Map<String, String> valueMap = new HashMap<>();
374         final Enumeration<?> propNames = valueProperties.propertyNames();
375         while (propNames.hasMoreElements()) {
376             final String propName = (String) propNames.nextElement();
377             final String propValue = valueProperties.getProperty(propName);
378             valueMap.put(propName, propValue);
379         }
380         return StrSubstitutor.replace(source, valueMap);
381     }
382 
383     //-----------------------------------------------------------------------
384     /**
385      * Replaces all the occurrences of variables with their matching values
386      * from the resolver using the given source string as a template.
387      *
388      * @param source  the string to replace in, null returns null
389      * @return the result of the replace operation
390      */
391     public String replace(final String source) {
392         return replace(null, source);
393     }
394     //-----------------------------------------------------------------------
395     /**
396      * Replaces all the occurrences of variables with their matching values
397      * from the resolver using the given source string as a template.
398      *
399      * @param event The current LogEvent if there is one.
400      * @param source  the string to replace in, null returns null
401      * @return the result of the replace operation
402      */
403     public String replace(final LogEvent event, final String source) {
404         if (source == null) {
405             return null;
406         }
407         final StringBuilder buf = new StringBuilder(source);
408         if (!substitute(event, buf, 0, source.length())) {
409             return source;
410         }
411         return buf.toString();
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      * <p>
418      * Only the specified portion of the string will be processed.
419      * The rest of the string is not processed, and is not returned.
420      * </p>
421      *
422      * @param source  the string to replace in, null returns null
423      * @param offset  the start offset within the array, must be valid
424      * @param length  the length within the array to be processed, must be valid
425      * @return the result of the replace operation
426      */
427     public String replace(final String source, final int offset, final int length) {
428         return replace(null, source, offset, length);
429     }
430 
431     /**
432      * Replaces all the occurrences of variables with their matching values
433      * from the resolver using the given source string as a template.
434      * <p>
435      * Only the specified portion of the string will be processed.
436      * The rest of the string is not processed, and is not returned.
437      * </p>
438      *
439      * @param event the current LogEvent, if one exists.
440      * @param source  the string to replace in, null returns null
441      * @param offset  the start offset within the array, must be valid
442      * @param length  the length within the array to be processed, must be valid
443      * @return the result of the replace operation
444      */
445     public String replace(final LogEvent event, final String source, final int offset, final int length) {
446         if (source == null) {
447             return null;
448         }
449         final StringBuilder buf = new StringBuilder(length).append(source, offset, length);
450         if (!substitute(event, buf, 0, length)) {
451             return source.substring(offset, offset + length);
452         }
453         return buf.toString();
454     }
455 
456     //-----------------------------------------------------------------------
457     /**
458      * Replaces all the occurrences of variables with their matching values
459      * from the resolver using the given source array as a template.
460      * The array is not altered by this method.
461      *
462      * @param source  the character array to replace in, not altered, null returns null
463      * @return the result of the replace operation
464      */
465     public String replace(final char[] source) {
466         return replace(null, source);
467     }
468 
469     //-----------------------------------------------------------------------
470     /**
471      * Replaces all the occurrences of variables with their matching values
472      * from the resolver using the given source array as a template.
473      * The array is not altered by this method.
474      *
475      * @param event the current LogEvent, if one exists.
476      * @param source  the character array to replace in, not altered, null returns null
477      * @return the result of the replace operation
478      */
479     public String replace(final LogEvent event, final char[] source) {
480         if (source == null) {
481             return null;
482         }
483         final StringBuilder buf = new StringBuilder(source.length).append(source);
484         substitute(event, buf, 0, source.length);
485         return buf.toString();
486     }
487 
488     /**
489      * Replaces all the occurrences of variables with their matching values
490      * from the resolver using the given source array as a template.
491      * The array is not altered by this method.
492      * <p>
493      * Only the specified portion of the array will be processed.
494      * The rest of the array is not processed, and is not returned.
495      * </p>
496      *
497      * @param source  the character array to replace in, not altered, null returns null
498      * @param offset  the start offset within the array, must be valid
499      * @param length  the length within the array to be processed, must be valid
500      * @return the result of the replace operation
501      */
502     public String replace(final char[] source, final int offset, final int length) {
503         return replace(null, source, offset, length);
504     }
505 
506     /**
507      * Replaces all the occurrences of variables with their matching values
508      * from the resolver using the given source array as a template.
509      * The array is not altered by this method.
510      * <p>
511      * Only the specified portion of the array will be processed.
512      * The rest of the array is not processed, and is not returned.
513      * </p>
514      *
515      * @param event the current LogEvent, if one exists.
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 LogEvent event, final char[] source, final int offset, final int length) {
522         if (source == null) {
523             return null;
524         }
525         final StringBuilder buf = new StringBuilder(length).append(source, offset, length);
526         substitute(event, buf, 0, length);
527         return buf.toString();
528     }
529 
530     //-----------------------------------------------------------------------
531     /**
532      * Replaces all the occurrences of variables with their matching values
533      * from the resolver using the given source buffer as a template.
534      * The buffer is not altered by this method.
535      *
536      * @param source  the buffer to use as a template, not changed, null returns null
537      * @return the result of the replace operation
538      */
539     public String replace(final StringBuffer source) {
540         return replace(null, source);
541     }
542 
543     //-----------------------------------------------------------------------
544     /**
545      * Replaces all the occurrences of variables with their matching values
546      * from the resolver using the given source buffer as a template.
547      * The buffer is not altered by this method.
548      *
549      * @param event the current LogEvent, if one exists.
550      * @param source  the buffer to use as a template, not changed, null returns null
551      * @return the result of the replace operation
552      */
553     public String replace(final LogEvent event, final StringBuffer source) {
554         if (source == null) {
555             return null;
556         }
557         final StringBuilder buf = new StringBuilder(source.length()).append(source);
558         substitute(event, buf, 0, buf.length());
559         return buf.toString();
560     }
561 
562     /**
563      * Replaces all the occurrences of variables with their matching values
564      * from the resolver using the given source buffer as a template.
565      * The buffer is not altered by this method.
566      * <p>
567      * Only the specified portion of the buffer will be processed.
568      * The rest of the buffer is not processed, and is not returned.
569      * </p>
570      *
571      * @param source  the buffer to use as a template, not changed, null returns null
572      * @param offset  the start offset within the array, must be valid
573      * @param length  the length within the array to be processed, must be valid
574      * @return the result of the replace operation
575      */
576     public String replace(final StringBuffer source, final int offset, final int length) {
577         return replace(null, source, offset, length);
578     }
579 
580     /**
581      * Replaces all the occurrences of variables with their matching values
582      * from the resolver using the given source buffer as a template.
583      * The buffer is not altered by this method.
584      * <p>
585      * Only the specified portion of the buffer will be processed.
586      * The rest of the buffer is not processed, and is not returned.
587      * </p>
588      *
589      * @param event the current LogEvent, if one exists.
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 LogEvent event, final StringBuffer source, final int offset, final int length) {
596         if (source == null) {
597             return null;
598         }
599         final StringBuilder buf = new StringBuilder(length).append(source, offset, length);
600         substitute(event, buf, 0, length);
601         return buf.toString();
602     }
603 
604     //-----------------------------------------------------------------------
605     /**
606      * Replaces all the occurrences of variables with their matching values
607      * from the resolver using the given source builder as a template.
608      * The builder is not altered by this method.
609      *
610      * @param source  the builder to use as a template, not changed, null returns null
611      * @return the result of the replace operation
612      */
613     public String replace(final StringBuilder source) {
614         return replace(null, source);
615     }
616 
617     //-----------------------------------------------------------------------
618     /**
619      * Replaces all the occurrences of variables with their matching values
620      * from the resolver using the given source builder as a template.
621      * The builder is not altered by this method.
622      *
623      * @param event The LogEvent.
624      * @param source  the builder to use as a template, not changed, null returns null.
625      * @return the result of the replace operation.
626      */
627     public String replace(final LogEvent event, final StringBuilder source) {
628         if (source == null) {
629             return null;
630         }
631         final StringBuilder buf = new StringBuilder(source.length()).append(source);
632         substitute(event, buf, 0, buf.length());
633         return buf.toString();
634     }
635     /**
636      * Replaces all the occurrences of variables with their matching values
637      * from the resolver using the given source builder as a template.
638      * The builder is not altered by this method.
639      * <p>
640      * Only the specified portion of the builder will be processed.
641      * The rest of the builder is not processed, and is not returned.
642      * </p>
643      *
644      * @param source  the builder to use as a template, not changed, null returns null
645      * @param offset  the start offset within the array, must be valid
646      * @param length  the length within the array to be processed, must be valid
647      * @return the result of the replace operation
648      */
649     public String replace(final StringBuilder source, final int offset, final int length) {
650         return replace(null, source, offset, length);
651     }
652 
653     /**
654      * Replaces all the occurrences of variables with their matching values
655      * from the resolver using the given source builder as a template.
656      * The builder is not altered by this method.
657      * <p>
658      * Only the specified portion of the builder will be processed.
659      * The rest of the builder is not processed, and is not returned.
660      * </p>
661      *
662      * @param event the current LogEvent, if one exists.
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 LogEvent event, final StringBuilder source, final int offset, final int length) {
669         if (source == null) {
670             return null;
671         }
672         final StringBuilder buf = new StringBuilder(length).append(source, offset, length);
673         substitute(event, buf, 0, length);
674         return buf.toString();
675     }
676 
677     //-----------------------------------------------------------------------
678     /**
679      * Replaces all the occurrences of variables in the given source object with
680      * their matching values from the resolver. The input source object is
681      * converted to a string using <code>toString</code> and is not altered.
682      *
683      * @param source  the source to replace in, null returns null
684      * @return the result of the replace operation
685      */
686     public String replace(final Object source) {
687         return replace(null, source);
688     }
689     //-----------------------------------------------------------------------
690     /**
691      * Replaces all the occurrences of variables in the given source object with
692      * their matching values from the resolver. The input source object is
693      * converted to a string using <code>toString</code> and is not altered.
694      *
695      * @param event the current LogEvent, if one exists.
696      * @param source  the source to replace in, null returns null
697      * @return the result of the replace operation
698      */
699     public String replace(final LogEvent event, final Object source) {
700         if (source == null) {
701             return null;
702         }
703         final StringBuilder buf = new StringBuilder().append(source);
704         substitute(event, buf, 0, buf.length());
705         return buf.toString();
706     }
707 
708     //-----------------------------------------------------------------------
709     /**
710      * Replaces all the occurrences of variables within the given source buffer
711      * with their matching values from the resolver.
712      * The buffer is updated with the result.
713      *
714      * @param source  the buffer to replace in, updated, null returns zero
715      * @return true if altered
716      */
717     public boolean replaceIn(final StringBuffer source) {
718         if (source == null) {
719             return false;
720         }
721         return replaceIn(source, 0, source.length());
722     }
723 
724     /**
725      * Replaces all the occurrences of variables within the given source buffer
726      * with their matching values from the resolver.
727      * The buffer is updated with the result.
728      * <p>
729      * Only the specified portion of the buffer will be processed.
730      * The rest of the buffer is not processed, but it is not deleted.
731      * </p>
732      *
733      * @param source  the buffer to replace in, updated, null returns zero
734      * @param offset  the start offset within the array, must be valid
735      * @param length  the length within the buffer to be processed, must be valid
736      * @return true if altered
737      */
738     public boolean replaceIn(final StringBuffer source, final int offset, final int length) {
739         return replaceIn(null, source, offset, length);
740     }
741 
742     /**
743      * Replaces all the occurrences of variables within the given source buffer
744      * with their matching values from the resolver.
745      * The buffer is updated with the result.
746      * <p>
747      * Only the specified portion of the buffer will be processed.
748      * The rest of the buffer is not processed, but it is not deleted.
749      * </p>
750      *
751      * @param event the current LogEvent, if one exists.
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 LogEvent event, final StringBuffer source, final int offset, final int length) {
758         if (source == null) {
759             return false;
760         }
761         final StringBuilder buf = new StringBuilder(length).append(source, offset, length);
762         if (!substitute(event, buf, 0, length)) {
763             return false;
764         }
765         source.replace(offset, offset + length, buf.toString());
766         return true;
767     }
768 
769     //-----------------------------------------------------------------------
770     /**
771      * Replaces all the occurrences of variables within the given source
772      * builder with their matching values from the resolver.
773      *
774      * @param source  the builder to replace in, updated, null returns zero
775      * @return true if altered
776      */
777     public boolean replaceIn(final StringBuilder source) {
778         return replaceIn(null, source);
779     }
780 
781     //-----------------------------------------------------------------------
782     /**
783      * Replaces all the occurrences of variables within the given source
784      * builder with their matching values from the resolver.
785      *
786      * @param event the current LogEvent, if one exists.
787      * @param source  the builder to replace in, updated, null returns zero
788      * @return true if altered
789      */
790     public boolean replaceIn(final LogEvent event, final StringBuilder source) {
791         if (source == null) {
792             return false;
793         }
794         return substitute(event, source, 0, source.length());
795     }
796     /**
797      * Replaces all the occurrences of variables within the given source
798      * builder with their matching values from the resolver.
799      * <p>
800      * Only the specified portion of the builder will be processed.
801      * The rest of the builder is not processed, but it is not deleted.
802      * </p>
803      *
804      * @param source  the builder to replace in, null returns zero
805      * @param offset  the start offset within the array, must be valid
806      * @param length  the length within the builder to be processed, must be valid
807      * @return true if altered
808      */
809     public boolean replaceIn(final StringBuilder source, final int offset, final int length) {
810         return replaceIn(null, source, offset, length);
811     }
812 
813     /**
814      * Replaces all the occurrences of variables within the given source
815      * builder with their matching values from the resolver.
816      * <p>
817      * Only the specified portion of the builder will be processed.
818      * The rest of the builder is not processed, but it is not deleted.
819      * </p>
820      *
821      * @param event   the current LogEvent, if one is present.
822      * @param source  the builder to replace in, null returns zero
823      * @param offset  the start offset within the array, must be valid
824      * @param length  the length within the builder to be processed, must be valid
825      * @return true if altered
826      */
827     public boolean replaceIn(final LogEvent event, final StringBuilder source, final int offset, final int length) {
828         if (source == null) {
829             return false;
830         }
831         return substitute(event, source, offset, length);
832     }
833 
834     //-----------------------------------------------------------------------
835     /**
836      * Internal method that substitutes the variables.
837      * <p>
838      * Most users of this class do not need to call this method. This method will
839      * be called automatically by another (public) method.
840      * </p>
841      * <p>
842      * Writers of subclasses can override this method if they need access to
843      * the substitution process at the start or end.
844      * </p>
845      *
846      * @param event The current LogEvent, if there is one.
847      * @param buf  the string builder to substitute into, not null
848      * @param offset  the start offset within the builder, must be valid
849      * @param length  the length within the builder to be processed, must be valid
850      * @return true if altered
851      */
852     protected boolean substitute(final LogEvent event, final StringBuilder buf, final int offset, final int length) {
853         return substitute(event, buf, offset, length, null) > 0;
854     }
855 
856     /**
857      * Recursive handler for multiple levels of interpolation. This is the main
858      * interpolation method, which resolves the values of all variable references
859      * contained in the passed in text.
860      *
861      * @param event The current LogEvent, if there is one.
862      * @param buf  the string builder to substitute into, not null
863      * @param offset  the start offset within the builder, must be valid
864      * @param length  the length within the builder to be processed, must be valid
865      * @param priorVariables  the stack keeping track of the replaced variables, may be null
866      * @return the length change that occurs, unless priorVariables is null when the int
867      *  represents a boolean flag as to whether any change occurred.
868      */
869     private int substitute(final LogEvent event, final StringBuilder buf, final int offset, final int length,
870                            List<String> priorVariables) {
871         final StrMatcher prefixMatcher = getVariablePrefixMatcher();
872         final StrMatcher suffixMatcher = getVariableSuffixMatcher();
873         final char escape = getEscapeChar();
874         final StrMatcher valueDelimiterMatcher = getValueDelimiterMatcher();
875         final boolean substitutionInVariablesEnabled = isEnableSubstitutionInVariables();
876 
877         final boolean top = priorVariables == null;
878         boolean altered = false;
879         int lengthChange = 0;
880         char[] chars = getChars(buf);
881         int bufEnd = offset + length;
882         int pos = offset;
883         while (pos < bufEnd) {
884             final int startMatchLen = prefixMatcher.isMatch(chars, pos, offset,
885                     bufEnd);
886             if (startMatchLen == 0) {
887                 pos++;
888             } else {
889                 // found variable start marker
890                 if (pos > offset && chars[pos - 1] == escape) {
891                     // escaped
892                     buf.deleteCharAt(pos - 1);
893                     chars = getChars(buf);
894                     lengthChange--;
895                     altered = true;
896                     bufEnd--;
897                 } else {
898                     // find suffix
899                     final int startPos = pos;
900                     pos += startMatchLen;
901                     int endMatchLen = 0;
902                     int nestedVarCount = 0;
903                     while (pos < bufEnd) {
904                         if (substitutionInVariablesEnabled
905                                 && (endMatchLen = prefixMatcher.isMatch(chars,
906                                         pos, offset, bufEnd)) != 0) {
907                             // found a nested variable start
908                             nestedVarCount++;
909                             pos += endMatchLen;
910                             continue;
911                         }
912 
913                         endMatchLen = suffixMatcher.isMatch(chars, pos, offset,
914                                 bufEnd);
915                         if (endMatchLen == 0) {
916                             pos++;
917                         } else {
918                             // found variable end marker
919                             if (nestedVarCount == 0) {
920                                 String varNameExpr = new String(chars, startPos
921                                         + startMatchLen, pos - startPos
922                                         - startMatchLen);
923                                 if (substitutionInVariablesEnabled) {
924                                     final StringBuilder bufName = new StringBuilder(varNameExpr);
925                                     substitute(event, bufName, 0, bufName.length());
926                                     varNameExpr = bufName.toString();
927                                 }
928                                 pos += endMatchLen;
929                                 final int endPos = pos;
930 
931                                 String varName = varNameExpr;
932                                 String varDefaultValue = null;
933 
934                                 if (valueDelimiterMatcher != null) {
935                                     final char [] varNameExprChars = varNameExpr.toCharArray();
936                                     int valueDelimiterMatchLen = 0;
937                                     for (int i = 0; i < varNameExprChars.length; i++) {
938                                         // if there's any nested variable when nested variable substitution disabled, then stop resolving name and default value.
939                                         if (!substitutionInVariablesEnabled
940                                                 && prefixMatcher.isMatch(varNameExprChars, i, i, varNameExprChars.length) != 0) {
941                                             break;
942                                         }
943                                         if ((valueDelimiterMatchLen = valueDelimiterMatcher.isMatch(varNameExprChars, i)) != 0) {
944                                             varName = varNameExpr.substring(0, i);
945                                             varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen);
946                                             break;
947                                         }
948                                     }
949                                 }
950 
951                                 // on the first call initialize priorVariables
952                                 if (priorVariables == null) {
953                                     priorVariables = new ArrayList<>();
954                                     priorVariables.add(new String(chars,
955                                             offset, length + lengthChange));
956                                 }
957 
958                                 // handle cyclic substitution
959                                 checkCyclicSubstitution(varName, priorVariables);
960                                 priorVariables.add(varName);
961 
962                                 // resolve the variable
963                                 String varValue = resolveVariable(event, varName, buf,
964                                         startPos, endPos);
965                                 if (varValue == null) {
966                                     varValue = varDefaultValue;
967                                 }
968                                 if (varValue != null) {
969                                     // recursive replace
970                                     final int varLen = varValue.length();
971                                     buf.replace(startPos, endPos, varValue);
972                                     altered = true;
973                                     int change = substitute(event, buf, startPos,
974                                             varLen, priorVariables);
975                                     change = change
976                                             + (varLen - (endPos - startPos));
977                                     pos += change;
978                                     bufEnd += change;
979                                     lengthChange += change;
980                                     chars = getChars(buf); // in case buffer was
981                                                         // altered
982                                 }
983 
984                                 // remove variable from the cyclic stack
985                                 priorVariables
986                                         .remove(priorVariables.size() - 1);
987                                 break;
988                             }
989                             nestedVarCount--;
990                             pos += endMatchLen;
991                         }
992                     }
993                 }
994             }
995         }
996         if (top) {
997             return altered ? 1 : 0;
998         }
999         return lengthChange;
1000     }
1001 
1002     /**
1003      * Checks if the specified variable is already in the stack (list) of variables.
1004      *
1005      * @param varName  the variable name to check
1006      * @param priorVariables  the list of prior variables
1007      */
1008     private void checkCyclicSubstitution(final String varName, final List<String> priorVariables) {
1009         if (!priorVariables.contains(varName)) {
1010             return;
1011         }
1012         final StringBuilder buf = new StringBuilder(BUF_SIZE);
1013         buf.append("Infinite loop in property interpolation of ");
1014         buf.append(priorVariables.remove(0));
1015         buf.append(": ");
1016         appendWithSeparators(buf, priorVariables, "->");
1017         throw new IllegalStateException(buf.toString());
1018     }
1019 
1020     /**
1021      * Internal method that resolves the value of a variable.
1022      * <p>
1023      * Most users of this class do not need to call this method. This method is
1024      * called automatically by the substitution process.
1025      * </p>
1026      * <p>
1027      * Writers of subclasses can override this method if they need to alter
1028      * how each substitution occurs. The method is passed the variable's name
1029      * and must return the corresponding value. This implementation uses the
1030      * {@link #getVariableResolver()} with the variable's name as the key.
1031      * </p>
1032      *
1033      * @param event The LogEvent, if there is one.
1034      * @param variableName  the name of the variable, not null
1035      * @param buf  the buffer where the substitution is occurring, not null
1036      * @param startPos  the start position of the variable including the prefix, valid
1037      * @param endPos  the end position of the variable including the suffix, valid
1038      * @return the variable's value or <b>null</b> if the variable is unknown
1039      */
1040     protected String resolveVariable(final LogEvent event, final String variableName, final StringBuilder buf,
1041                                      final int startPos, final int endPos) {
1042         final StrLookup resolver = getVariableResolver();
1043         if (resolver == null) {
1044             return null;
1045         }
1046         return resolver.lookup(event, variableName);
1047     }
1048 
1049     // Escape
1050     //-----------------------------------------------------------------------
1051     /**
1052      * Returns the escape character.
1053      *
1054      * @return the character used for escaping variable references
1055      */
1056     public char getEscapeChar() {
1057         return this.escapeChar;
1058     }
1059 
1060     /**
1061      * Sets the escape character.
1062      * If this character is placed before a variable reference in the source
1063      * text, this variable will be ignored.
1064      *
1065      * @param escapeCharacter  the escape character (0 for disabling escaping)
1066      */
1067     public void setEscapeChar(final char escapeCharacter) {
1068         this.escapeChar = escapeCharacter;
1069     }
1070 
1071     // Prefix
1072     //-----------------------------------------------------------------------
1073     /**
1074      * Gets the variable prefix matcher currently in use.
1075      * <p>
1076      * The variable prefix is the character or characters that identify the
1077      * start of a variable. This prefix is expressed in terms of a matcher
1078      * allowing advanced prefix matches.
1079      * </p>
1080      *
1081      * @return the prefix matcher in use
1082      */
1083     public StrMatcher getVariablePrefixMatcher() {
1084         return prefixMatcher;
1085     }
1086 
1087     /**
1088      * Sets 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      * @param prefixMatcher  the prefix matcher to use, null ignored
1096      * @return this, to enable chaining
1097      * @throws IllegalArgumentException if the prefix matcher is null
1098      */
1099     public StrSubstitutor setVariablePrefixMatcher(final StrMatcher prefixMatcher) {
1100         if (prefixMatcher == null) {
1101             throw new IllegalArgumentException("Variable prefix matcher must not be null!");
1102         }
1103         this.prefixMatcher = prefixMatcher;
1104         return this;
1105     }
1106 
1107     /**
1108      * Sets the variable prefix to use.
1109      * <p>
1110      * The variable prefix is the character or characters that identify the
1111      * start of a variable. This method allows a single character prefix to
1112      * be easily set.
1113      * </p>
1114      *
1115      * @param prefix  the prefix character to use
1116      * @return this, to enable chaining
1117      */
1118     public StrSubstitutor setVariablePrefix(final char prefix) {
1119         return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix));
1120     }
1121 
1122     /**
1123      * Sets the variable prefix to use.
1124      * <p>
1125      * The variable prefix is the character or characters that identify the
1126      * start of a variable. This method allows a string prefix to be easily set.
1127      * </p>
1128      *
1129      * @param prefix  the prefix for variables, not null
1130      * @return this, to enable chaining
1131      * @throws IllegalArgumentException if the prefix is null
1132      */
1133     public StrSubstitutor setVariablePrefix(final String prefix) {
1134        if (prefix == null) {
1135             throw new IllegalArgumentException("Variable prefix must not be null!");
1136         }
1137         return setVariablePrefixMatcher(StrMatcher.stringMatcher(prefix));
1138     }
1139 
1140     // Suffix
1141     //-----------------------------------------------------------------------
1142     /**
1143      * Gets the variable suffix matcher currently in use.
1144      * <p>
1145      * The variable suffix is the character or characters that identify the
1146      * end of a variable. This suffix is expressed in terms of a matcher
1147      * allowing advanced suffix matches.
1148      * </p>
1149      *
1150      * @return the suffix matcher in use
1151      */
1152     public StrMatcher getVariableSuffixMatcher() {
1153         return suffixMatcher;
1154     }
1155 
1156     /**
1157      * Sets 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      * @param suffixMatcher  the suffix matcher to use, null ignored
1165      * @return this, to enable chaining
1166      * @throws IllegalArgumentException if the suffix matcher is null
1167      */
1168     public StrSubstitutor setVariableSuffixMatcher(final StrMatcher suffixMatcher) {
1169         if (suffixMatcher == null) {
1170             throw new IllegalArgumentException("Variable suffix matcher must not be null!");
1171         }
1172         this.suffixMatcher = suffixMatcher;
1173         return this;
1174     }
1175 
1176     /**
1177      * Sets the variable suffix to use.
1178      * <p>
1179      * The variable suffix is the character or characters that identify the
1180      * end of a variable. This method allows a single character suffix to
1181      * be easily set.
1182      * </p>
1183      *
1184      * @param suffix  the suffix character to use
1185      * @return this, to enable chaining
1186      */
1187     public StrSubstitutor setVariableSuffix(final char suffix) {
1188         return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix));
1189     }
1190 
1191     /**
1192      * Sets the variable suffix to use.
1193      * <p>
1194      * The variable suffix is the character or characters that identify the
1195      * end of a variable. This method allows a string suffix to be easily set.
1196      * </p>
1197      *
1198      * @param suffix  the suffix for variables, not null
1199      * @return this, to enable chaining
1200      * @throws IllegalArgumentException if the suffix is null
1201      */
1202     public StrSubstitutor setVariableSuffix(final String suffix) {
1203        if (suffix == null) {
1204             throw new IllegalArgumentException("Variable suffix must not be null!");
1205         }
1206         return setVariableSuffixMatcher(StrMatcher.stringMatcher(suffix));
1207     }
1208 
1209     // Variable Default Value Delimiter
1210     //-----------------------------------------------------------------------
1211     /**
1212      * Gets the variable default value delimiter matcher currently in use.
1213      * <p>
1214      * The variable default value delimiter is the character or characters that delimit the
1215      * variable name and the variable default value. This delimiter is expressed in terms of a matcher
1216      * allowing advanced variable default value delimiter matches.
1217      * </p>
1218      * <p>
1219      * If it returns null, then the variable default value resolution is disabled.
1220      * </p>
1221      *
1222      * @return the variable default value delimiter matcher in use, may be null
1223      */
1224     public StrMatcher getValueDelimiterMatcher() {
1225         return valueDelimiterMatcher;
1226     }
1227 
1228     /**
1229      * Sets the variable default value delimiter matcher to use.
1230      * <p>
1231      * The variable default value delimiter is the character or characters that delimit the
1232      * variable name and the variable default value. This delimiter is expressed in terms of a matcher
1233      * allowing advanced variable default value delimiter matches.
1234      * </p>
1235      * <p>
1236      * If the <code>valueDelimiterMatcher</code> is null, then the variable default value resolution
1237      * becomes disabled.
1238      * </p>
1239      *
1240      * @param valueDelimiterMatcher  variable default value delimiter matcher to use, may be null
1241      * @return this, to enable chaining
1242      */
1243     public StrSubstitutor setValueDelimiterMatcher(final StrMatcher valueDelimiterMatcher) {
1244         this.valueDelimiterMatcher = valueDelimiterMatcher;
1245         return this;
1246     }
1247 
1248     /**
1249      * Sets the variable default value delimiter to use.
1250      * <p>
1251      * The variable default value delimiter is the character or characters that delimit the
1252      * variable name and the variable default value. This method allows a single character
1253      * variable default value delimiter to be easily set.
1254      * </p>
1255      *
1256      * @param valueDelimiter  the variable default value delimiter character to use
1257      * @return this, to enable chaining
1258      */
1259     public StrSubstitutor setValueDelimiter(final char valueDelimiter) {
1260         return setValueDelimiterMatcher(StrMatcher.charMatcher(valueDelimiter));
1261     }
1262 
1263     /**
1264      * Sets the variable default value delimiter to use.
1265      * <p>
1266      * The variable default value delimiter is the character or characters that delimit the
1267      * variable name and the variable default value. This method allows a string
1268      * variable default value delimiter to be easily set.
1269      * </p>
1270      * <p>
1271      * If the <code>valueDelimiter</code> is null or empty string, then the variable default
1272      * value resolution becomes disabled.
1273      * </p>
1274      *
1275      * @param valueDelimiter  the variable default value delimiter string to use, may be null or empty
1276      * @return this, to enable chaining
1277      */
1278     public StrSubstitutor setValueDelimiter(final String valueDelimiter) {
1279         if (Strings.isEmpty(valueDelimiter)) {
1280             setValueDelimiterMatcher(null);
1281             return this;
1282         }
1283         return setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter));
1284     }
1285 
1286     // Resolver
1287     //-----------------------------------------------------------------------
1288     /**
1289      * Gets the VariableResolver that is used to lookup variables.
1290      *
1291      * @return the VariableResolver
1292      */
1293     public StrLookup getVariableResolver() {
1294         return this.variableResolver;
1295     }
1296 
1297     /**
1298      * Sets the VariableResolver that is used to lookup variables.
1299      *
1300      * @param variableResolver  the VariableResolver
1301      */
1302     public void setVariableResolver(final StrLookup variableResolver) {
1303         if (variableResolver instanceof ConfigurationAware && this.configuration != null) {
1304             ((ConfigurationAware) variableResolver).setConfiguration(this.configuration);
1305         }
1306         this.variableResolver = variableResolver;
1307     }
1308 
1309     // Substitution support in variable names
1310     //-----------------------------------------------------------------------
1311     /**
1312      * Returns a flag whether substitution is done in variable names.
1313      *
1314      * @return the substitution in variable names flag
1315      */
1316     public boolean isEnableSubstitutionInVariables() {
1317         return enableSubstitutionInVariables;
1318     }
1319 
1320     /**
1321      * Sets a flag whether substitution is done in variable names. If set to
1322      * <b>true</b>, the names of variables can contain other variables which are
1323      * processed first before the original variable is evaluated, e.g.
1324      * <code>${jre-${java.version}}</code>. The default value is <b>false</b>.
1325      *
1326      * @param enableSubstitutionInVariables the new value of the flag
1327      */
1328     public void setEnableSubstitutionInVariables(final boolean enableSubstitutionInVariables) {
1329         this.enableSubstitutionInVariables = enableSubstitutionInVariables;
1330     }
1331 
1332     private char[] getChars(final StringBuilder sb) {
1333         final char[] chars = new char[sb.length()];
1334         sb.getChars(0, sb.length(), chars, 0);
1335         return chars;
1336     }
1337 
1338     /**
1339      * Appends a iterable placing separators between each value, but
1340      * not before the first or after the last.
1341      * Appending a null iterable will have no effect..
1342      *
1343      * @param sb StringBuilder that contains the String being constructed.
1344      * @param iterable  the iterable to append
1345      * @param separator  the separator to use, null means no separator
1346      */
1347     public void appendWithSeparators(final StringBuilder sb, final Iterable<?> iterable, String separator) {
1348         if (iterable != null) {
1349             separator = separator == null ? Strings.EMPTY : separator;
1350             final Iterator<?> it = iterable.iterator();
1351             while (it.hasNext()) {
1352                 sb.append(it.next());
1353                 if (it.hasNext()) {
1354                     sb.append(separator);
1355                 }
1356             }
1357         }
1358     }
1359 
1360     @Override
1361     public String toString() {
1362         return "StrSubstitutor(" + variableResolver.toString() + ')';
1363     }
1364 
1365     @Override
1366     public void setConfiguration(final Configuration configuration) {
1367         this.configuration = configuration;
1368         if (this.variableResolver instanceof ConfigurationAware) {
1369             ((ConfigurationAware) this.variableResolver).setConfiguration(this.configuration);
1370         }
1371     }
1372 }