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