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<String, String>();
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<String>();
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                             } else {
983                                 nestedVarCount--;
984                                 pos += endMatchLen;
985                             }
986                         }
987                     }
988                 }
989             }
990         }
991         if (top) {
992             return altered ? 1 : 0;
993         }
994         return lengthChange;
995     }
996 
997     /**
998      * Checks if the specified variable is already in the stack (list) of variables.
999      *
1000      * @param varName  the variable name to check
1001      * @param priorVariables  the list of prior variables
1002      */
1003     private void checkCyclicSubstitution(final String varName, final List<String> priorVariables) {
1004         if (!priorVariables.contains(varName)) {
1005             return;
1006         }
1007         final StringBuilder buf = new StringBuilder(BUF_SIZE);
1008         buf.append("Infinite loop in property interpolation of ");
1009         buf.append(priorVariables.remove(0));
1010         buf.append(": ");
1011         appendWithSeparators(buf, priorVariables, "->");
1012         throw new IllegalStateException(buf.toString());
1013     }
1014 
1015     /**
1016      * Internal method that resolves the value of a variable.
1017      * <p>
1018      * Most users of this class do not need to call this method. This method is
1019      * called automatically by the substitution process.
1020      * </p>
1021      * <p>
1022      * Writers of subclasses can override this method if they need to alter
1023      * how each substitution occurs. The method is passed the variable's name
1024      * and must return the corresponding value. This implementation uses the
1025      * {@link #getVariableResolver()} with the variable's name as the key.
1026      * </p>
1027      *
1028      * @param event The LogEvent, if there is one.
1029      * @param variableName  the name of the variable, not null
1030      * @param buf  the buffer where the substitution is occurring, not null
1031      * @param startPos  the start position of the variable including the prefix, valid
1032      * @param endPos  the end position of the variable including the suffix, valid
1033      * @return the variable's value or <b>null</b> if the variable is unknown
1034      */
1035     protected String resolveVariable(final LogEvent event, final String variableName, final StringBuilder buf,
1036                                      final int startPos, final int endPos) {
1037         final StrLookup resolver = getVariableResolver();
1038         if (resolver == null) {
1039             return null;
1040         }
1041         return resolver.lookup(event, variableName);
1042     }
1043 
1044     // Escape
1045     //-----------------------------------------------------------------------
1046     /**
1047      * Returns the escape character.
1048      *
1049      * @return the character used for escaping variable references
1050      */
1051     public char getEscapeChar() {
1052         return this.escapeChar;
1053     }
1054 
1055     /**
1056      * Sets the escape character.
1057      * If this character is placed before a variable reference in the source
1058      * text, this variable will be ignored.
1059      *
1060      * @param escapeCharacter  the escape character (0 for disabling escaping)
1061      */
1062     public void setEscapeChar(final char escapeCharacter) {
1063         this.escapeChar = escapeCharacter;
1064     }
1065 
1066     // Prefix
1067     //-----------------------------------------------------------------------
1068     /**
1069      * Gets the variable prefix matcher currently in use.
1070      * <p>
1071      * The variable prefix is the character or characters that identify the
1072      * start of a variable. This prefix is expressed in terms of a matcher
1073      * allowing advanced prefix matches.
1074      * </p>
1075      *
1076      * @return the prefix matcher in use
1077      */
1078     public StrMatcher getVariablePrefixMatcher() {
1079         return prefixMatcher;
1080     }
1081 
1082     /**
1083      * Sets the variable prefix matcher currently in use.
1084      * <p>
1085      * The variable prefix is the character or characters that identify the
1086      * start of a variable. This prefix is expressed in terms of a matcher
1087      * allowing advanced prefix matches.
1088      * </p>
1089      *
1090      * @param prefixMatcher  the prefix matcher to use, null ignored
1091      * @return this, to enable chaining
1092      * @throws IllegalArgumentException if the prefix matcher is null
1093      */
1094     public StrSubstitutor setVariablePrefixMatcher(final StrMatcher prefixMatcher) {
1095         if (prefixMatcher == null) {
1096             throw new IllegalArgumentException("Variable prefix matcher must not be null!");
1097         }
1098         this.prefixMatcher = prefixMatcher;
1099         return this;
1100     }
1101 
1102     /**
1103      * Sets the variable prefix to use.
1104      * <p>
1105      * The variable prefix is the character or characters that identify the
1106      * start of a variable. This method allows a single character prefix to
1107      * be easily set.
1108      * </p>
1109      *
1110      * @param prefix  the prefix character to use
1111      * @return this, to enable chaining
1112      */
1113     public StrSubstitutor setVariablePrefix(final char prefix) {
1114         return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix));
1115     }
1116 
1117     /**
1118      * Sets the variable prefix to use.
1119      * <p>
1120      * The variable prefix is the character or characters that identify the
1121      * start of a variable. This method allows a string prefix to be easily set.
1122      * </p>
1123      *
1124      * @param prefix  the prefix for variables, not null
1125      * @return this, to enable chaining
1126      * @throws IllegalArgumentException if the prefix is null
1127      */
1128     public StrSubstitutor setVariablePrefix(final String prefix) {
1129        if (prefix == null) {
1130             throw new IllegalArgumentException("Variable prefix must not be null!");
1131         }
1132         return setVariablePrefixMatcher(StrMatcher.stringMatcher(prefix));
1133     }
1134 
1135     // Suffix
1136     //-----------------------------------------------------------------------
1137     /**
1138      * Gets the variable suffix matcher currently in use.
1139      * <p>
1140      * The variable suffix is the character or characters that identify the
1141      * end of a variable. This suffix is expressed in terms of a matcher
1142      * allowing advanced suffix matches.
1143      * </p>
1144      *
1145      * @return the suffix matcher in use
1146      */
1147     public StrMatcher getVariableSuffixMatcher() {
1148         return suffixMatcher;
1149     }
1150 
1151     /**
1152      * Sets the variable suffix matcher currently in use.
1153      * <p>
1154      * The variable suffix is the character or characters that identify the
1155      * end of a variable. This suffix is expressed in terms of a matcher
1156      * allowing advanced suffix matches.
1157      * </p>
1158      *
1159      * @param suffixMatcher  the suffix matcher to use, null ignored
1160      * @return this, to enable chaining
1161      * @throws IllegalArgumentException if the suffix matcher is null
1162      */
1163     public StrSubstitutor setVariableSuffixMatcher(final StrMatcher suffixMatcher) {
1164         if (suffixMatcher == null) {
1165             throw new IllegalArgumentException("Variable suffix matcher must not be null!");
1166         }
1167         this.suffixMatcher = suffixMatcher;
1168         return this;
1169     }
1170 
1171     /**
1172      * Sets the variable suffix to use.
1173      * <p>
1174      * The variable suffix is the character or characters that identify the
1175      * end of a variable. This method allows a single character suffix to
1176      * be easily set.
1177      * </p>
1178      *
1179      * @param suffix  the suffix character to use
1180      * @return this, to enable chaining
1181      */
1182     public StrSubstitutor setVariableSuffix(final char suffix) {
1183         return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix));
1184     }
1185 
1186     /**
1187      * Sets the variable suffix to use.
1188      * <p>
1189      * The variable suffix is the character or characters that identify the
1190      * end of a variable. This method allows a string suffix to be easily set.
1191      * </p>
1192      *
1193      * @param suffix  the suffix for variables, not null
1194      * @return this, to enable chaining
1195      * @throws IllegalArgumentException if the suffix is null
1196      */
1197     public StrSubstitutor setVariableSuffix(final String suffix) {
1198        if (suffix == null) {
1199             throw new IllegalArgumentException("Variable suffix must not be null!");
1200         }
1201         return setVariableSuffixMatcher(StrMatcher.stringMatcher(suffix));
1202     }
1203 
1204     // Variable Default Value Delimiter
1205     //-----------------------------------------------------------------------
1206     /**
1207      * Gets the variable default value delimiter matcher currently in use.
1208      * <p>
1209      * The variable default value delimiter is the character or characters that delimit the
1210      * variable name and the variable default value. This delimiter is expressed in terms of a matcher
1211      * allowing advanced variable default value delimiter matches.
1212      * </p>
1213      * <p>
1214      * If it returns null, then the variable default value resolution is disabled.
1215      * </p>
1216      *
1217      * @return the variable default value delimiter matcher in use, may be null
1218      */
1219     public StrMatcher getValueDelimiterMatcher() {
1220         return valueDelimiterMatcher;
1221     }
1222 
1223     /**
1224      * Sets the variable default value delimiter matcher to use.
1225      * <p>
1226      * The variable default value delimiter is the character or characters that delimit the
1227      * variable name and the variable default value. This delimiter is expressed in terms of a matcher
1228      * allowing advanced variable default value delimiter matches.
1229      * </p>
1230      * <p>
1231      * If the <code>valueDelimiterMatcher</code> is null, then the variable default value resolution
1232      * becomes disabled.
1233      * </p>
1234      *
1235      * @param valueDelimiterMatcher  variable default value delimiter matcher to use, may be null
1236      * @return this, to enable chaining
1237      */
1238     public StrSubstitutor setValueDelimiterMatcher(final StrMatcher valueDelimiterMatcher) {
1239         this.valueDelimiterMatcher = valueDelimiterMatcher;
1240         return this;
1241     }
1242 
1243     /**
1244      * Sets the variable default value delimiter to use.
1245      * <p>
1246      * The variable default value delimiter is the character or characters that delimit the
1247      * variable name and the variable default value. This method allows a single character
1248      * variable default value delimiter to be easily set.
1249      * </p>
1250      *
1251      * @param valueDelimiter  the variable default value delimiter character to use
1252      * @return this, to enable chaining
1253      */
1254     public StrSubstitutor setValueDelimiter(final char valueDelimiter) {
1255         return setValueDelimiterMatcher(StrMatcher.charMatcher(valueDelimiter));
1256     }
1257 
1258     /**
1259      * Sets the variable default value delimiter to use.
1260      * <p>
1261      * The variable default value delimiter is the character or characters that delimit the
1262      * variable name and the variable default value. This method allows a string
1263      * variable default value delimiter to be easily set.
1264      * </p>
1265      * <p>
1266      * If the <code>valueDelimiter</code> is null or empty string, then the variable default
1267      * value resolution becomes disabled.
1268      * </p>
1269      *
1270      * @param valueDelimiter  the variable default value delimiter string to use, may be null or empty
1271      * @return this, to enable chaining
1272      */
1273     public StrSubstitutor setValueDelimiter(final String valueDelimiter) {
1274         if (Strings.isEmpty(valueDelimiter)) {
1275             setValueDelimiterMatcher(null);
1276             return this;
1277         }
1278         return setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter));
1279     }
1280 
1281     // Resolver
1282     //-----------------------------------------------------------------------
1283     /**
1284      * Gets the VariableResolver that is used to lookup variables.
1285      *
1286      * @return the VariableResolver
1287      */
1288     public StrLookup getVariableResolver() {
1289         return this.variableResolver;
1290     }
1291 
1292     /**
1293      * Sets the VariableResolver that is used to lookup variables.
1294      *
1295      * @param variableResolver  the VariableResolver
1296      */
1297     public void setVariableResolver(final StrLookup variableResolver) {
1298         this.variableResolver = variableResolver;
1299     }
1300 
1301     // Substitution support in variable names
1302     //-----------------------------------------------------------------------
1303     /**
1304      * Returns a flag whether substitution is done in variable names.
1305      *
1306      * @return the substitution in variable names flag
1307      */
1308     public boolean isEnableSubstitutionInVariables() {
1309         return enableSubstitutionInVariables;
1310     }
1311 
1312     /**
1313      * Sets a flag whether substitution is done in variable names. If set to
1314      * <b>true</b>, the names of variables can contain other variables which are
1315      * processed first before the original variable is evaluated, e.g.
1316      * <code>${jre-${java.version}}</code>. The default value is <b>false</b>.
1317      *
1318      * @param enableSubstitutionInVariables the new value of the flag
1319      */
1320     public void setEnableSubstitutionInVariables(final boolean enableSubstitutionInVariables) {
1321         this.enableSubstitutionInVariables = enableSubstitutionInVariables;
1322     }
1323 
1324     private char[] getChars(final StringBuilder sb) {
1325         final char[] chars = new char[sb.length()];
1326         sb.getChars(0, sb.length(), chars, 0);
1327         return chars;
1328     }
1329 
1330     /**
1331      * Appends a iterable placing separators between each value, but
1332      * not before the first or after the last.
1333      * Appending a null iterable will have no effect..
1334      *
1335      * @param sb StringBuilder that contains the String being constructed.
1336      * @param iterable  the iterable to append
1337      * @param separator  the separator to use, null means no separator
1338      */
1339     public void appendWithSeparators(final StringBuilder sb, final Iterable<?> iterable, String separator) {
1340         if (iterable != null) {
1341             separator = separator == null ? Strings.EMPTY : separator;
1342             final Iterator<?> it = iterable.iterator();
1343             while (it.hasNext()) {
1344                 sb.append(it.next());
1345                 if (it.hasNext()) {
1346                     sb.append(separator);
1347                 }
1348             }
1349         }
1350     }
1351 
1352     @Override
1353     public String toString() {
1354         return "StrSubstitutor(" + variableResolver.toString() + ')';
1355     }
1356 }