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