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