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