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  
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 implementer 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 valueMap  the map with the variables' values, may be null
155      */
156     public StrSubstitutor(final Map<String, String> valueMap) {
157         this(new MapLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
158     }
159 
160     /**
161      * Creates a new instance and initializes it. Uses a default escaping character.
162      *
163      * @param valueMap  the map with the variables' values, may be null
164      * @param prefix  the prefix for variables, not null
165      * @param suffix  the suffix for variables, not null
166      * @throws IllegalArgumentException if the prefix or suffix is null
167      */
168     public StrSubstitutor(final Map<String, String> valueMap, final String prefix, final String suffix) {
169         this(new MapLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE);
170     }
171 
172     /**
173      * Creates a new instance and initializes it.
174      *
175      * @param valueMap  the map with the variables' values, may be null
176      * @param prefix  the prefix for variables, not null
177      * @param suffix  the suffix for variables, not null
178      * @param escape  the escape character
179      * @throws IllegalArgumentException if the prefix or suffix is null
180      */
181     public StrSubstitutor(final Map<String, String> valueMap, final String prefix, final String suffix,
182                           final char escape) {
183         this(new MapLookup(valueMap), prefix, suffix, escape);
184     }
185 
186     /**
187      * Creates a new instance and initializes it.
188      *
189      * @param variableResolver  the variable resolver, may be null
190      */
191     public StrSubstitutor(final StrLookup variableResolver) {
192         this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
193     }
194 
195     /**
196      * Creates a new instance and initializes it.
197      *
198      * @param variableResolver  the variable resolver, may be null
199      * @param prefix  the prefix for variables, not null
200      * @param suffix  the suffix for variables, not null
201      * @param escape  the escape character
202      * @throws IllegalArgumentException if the prefix or suffix is null
203      */
204     public StrSubstitutor(final StrLookup variableResolver, final String prefix, final String suffix,
205                           final char escape) {
206         this.setVariableResolver(variableResolver);
207         this.setVariablePrefix(prefix);
208         this.setVariableSuffix(suffix);
209         this.setEscapeChar(escape);
210     }
211 
212     /**
213      * Creates a new instance and initializes it.
214      *
215      * @param variableResolver  the variable resolver, may be null
216      * @param prefixMatcher  the prefix for variables, not null
217      * @param suffixMatcher  the suffix for variables, not null
218      * @param escape  the escape character
219      * @throws IllegalArgumentException if the prefix or suffix is null
220      */
221     public StrSubstitutor(final StrLookup variableResolver, final StrMatcher prefixMatcher,
222                           final StrMatcher suffixMatcher,
223                           final 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 source  the source text containing the variables to substitute, null returns null
235      * @param valueMap  the map with the values, may be null
236      * @return the result of the replace operation
237      */
238     public static String replace(final Object source, final Map<String, String> valueMap) {
239         return new StrSubstitutor(valueMap).replace(source);
240     }
241 
242     /**
243      * Replaces all the occurrences of variables in the given source object with
244      * their matching values from the map. This method allows to specify a
245      * custom variable prefix and suffix
246      *
247      * @param source  the source text containing the variables to substitute, null returns null
248      * @param valueMap  the map with the values, may be null
249      * @param prefix  the prefix of variables, not null
250      * @param suffix  the suffix of variables, not null
251      * @return the result of the replace operation
252      * @throws IllegalArgumentException if the prefix or suffix is null
253      */
254     public static String replace(final Object source, final Map<String, String> valueMap, final String prefix,
255                                  final String suffix) {
256         return new StrSubstitutor(valueMap, prefix, suffix).replace(source);
257     }
258 
259     /**
260      * Replaces all the occurrences of variables in the given source object with their matching
261      * values from the properties.
262      *
263      * @param source the source text containing the variables to substitute, null returns null
264      * @param valueProperties the properties with values, may be null
265      * @return the result of the replace operation
266      */
267     public static String replace(final Object source, final Properties valueProperties) {
268         if (valueProperties == null) {
269             return source.toString();
270         }
271         final Map<String, String> valueMap = new HashMap<String, String>();
272         final Enumeration<?> propNames = valueProperties.propertyNames();
273         while (propNames.hasMoreElements()) {
274             final String propName = (String) propNames.nextElement();
275             final String propValue = valueProperties.getProperty(propName);
276             valueMap.put(propName, propValue);
277         }
278         return StrSubstitutor.replace(source, valueMap);
279     }
280 
281     //-----------------------------------------------------------------------
282     /**
283      * Replaces all the occurrences of variables with their matching values
284      * from the resolver using the given source string as a template.
285      *
286      * @param source  the string to replace in, null returns null
287      * @return the result of the replace operation
288      */
289     public String replace(final String source) {
290         return replace(null, source);
291     }
292     //-----------------------------------------------------------------------
293     /**
294      * Replaces all the occurrences of variables with their matching values
295      * from the resolver using the given source string as a template.
296      *
297      * @param event The current LogEvent if there is one.
298      * @param source  the string to replace in, null returns null
299      * @return the result of the replace operation
300      */
301     public String replace(final LogEvent event, final String source) {
302         if (source == null) {
303             return null;
304         }
305         final StringBuilder buf = new StringBuilder(source);
306         if (!substitute(event, buf, 0, source.length())) {
307             return source;
308         }
309         return buf.toString();
310     }
311 
312     /**
313      * Replaces all the occurrences of variables with their matching values
314      * from the resolver using the given source string as a template.
315      * <p>
316      * Only the specified portion of the string will be processed.
317      * The rest of the string is not processed, and is not returned.
318      *
319      * @param source  the string to replace in, null returns null
320      * @param offset  the start offset within the array, must be valid
321      * @param length  the length within the array to be processed, must be valid
322      * @return the result of the replace operation
323      */
324     public String replace(final String source, final int offset, final int length) {
325         return replace(null, source, offset, length);
326     }
327 
328     /**
329      * Replaces all the occurrences of variables with their matching values
330      * from the resolver using the given source string as a template.
331      * <p>
332      * Only the specified portion of the string will be processed.
333      * The rest of the string is not processed, and is not returned.
334      *
335      * @param event the current LogEvent, if one exists.
336      * @param source  the string to replace in, null returns null
337      * @param offset  the start offset within the array, must be valid
338      * @param length  the length within the array to be processed, must be valid
339      * @return the result of the replace operation
340      */
341     public String replace(final LogEvent event, final String source, final int offset, final int length) {
342         if (source == null) {
343             return null;
344         }
345         final StringBuilder buf = new StringBuilder(length).append(source, offset, length);
346         if (!substitute(event, buf, 0, length)) {
347             return source.substring(offset, offset + length);
348         }
349         return buf.toString();
350     }
351 
352     //-----------------------------------------------------------------------
353     /**
354      * Replaces all the occurrences of variables with their matching values
355      * from the resolver using the given source array as a template.
356      * The array is not altered by this method.
357      *
358      * @param source  the character array to replace in, not altered, null returns null
359      * @return the result of the replace operation
360      */
361     public String replace(final char[] source) {
362         return replace(null, source);
363     }
364 
365     //-----------------------------------------------------------------------
366     /**
367      * Replaces all the occurrences of variables with their matching values
368      * from the resolver using the given source array as a template.
369      * The array is not altered by this method.
370      *
371      * @param event the current LogEvent, if one exists.
372      * @param source  the character array to replace in, not altered, null returns null
373      * @return the result of the replace operation
374      */
375     public String replace(final LogEvent event, final char[] source) {
376         if (source == null) {
377             return null;
378         }
379         final StringBuilder buf = new StringBuilder(source.length).append(source);
380         substitute(event, buf, 0, source.length);
381         return buf.toString();
382     }
383 
384     /**
385      * Replaces all the occurrences of variables with their matching values
386      * from the resolver using the given source array as a template.
387      * The array is not altered by this method.
388      * <p>
389      * Only the specified portion of the array will be processed.
390      * The rest of the array is not processed, and is not returned.
391      *
392      * @param source  the character array to replace in, not altered, null returns null
393      * @param offset  the start offset within the array, must be valid
394      * @param length  the length within the array to be processed, must be valid
395      * @return the result of the replace operation
396      */
397     public String replace(final char[] source, final int offset, final int length) {
398         return replace(null, source, offset, length);
399     }
400 
401     /**
402      * Replaces all the occurrences of variables with their matching values
403      * from the resolver using the given source array as a template.
404      * The array is not altered by this method.
405      * <p>
406      * Only the specified portion of the array will be processed.
407      * The rest of the array is not processed, and is not returned.
408      *
409      * @param event the current LogEvent, if one exists.
410      * @param source  the character array to replace in, not altered, null returns null
411      * @param offset  the start offset within the array, must be valid
412      * @param length  the length within the array to be processed, must be valid
413      * @return the result of the replace operation
414      */
415     public String replace(final LogEvent event, final char[] source, final int offset, final int length) {
416         if (source == null) {
417             return null;
418         }
419         final StringBuilder buf = new StringBuilder(length).append(source, offset, length);
420         substitute(event, buf, 0, length);
421         return buf.toString();
422     }
423 
424     //-----------------------------------------------------------------------
425     /**
426      * Replaces all the occurrences of variables with their matching values
427      * from the resolver using the given source buffer as a template.
428      * The buffer is not altered by this method.
429      *
430      * @param source  the buffer to use as a template, not changed, null returns null
431      * @return the result of the replace operation
432      */
433     public String replace(final StringBuffer source) {
434         return replace(null, source);
435     }
436 
437     //-----------------------------------------------------------------------
438     /**
439      * Replaces all the occurrences of variables with their matching values
440      * from the resolver using the given source buffer as a template.
441      * The buffer is not altered by this method.
442      *
443      * @param event the current LogEvent, if one exists.
444      * @param source  the buffer to use as a template, not changed, null returns null
445      * @return the result of the replace operation
446      */
447     public String replace(final LogEvent event, final StringBuffer source) {
448         if (source == null) {
449             return null;
450         }
451         final StringBuilder buf = new StringBuilder(source.length()).append(source);
452         substitute(event, buf, 0, buf.length());
453         return buf.toString();
454     }
455 
456     /**
457      * Replaces all the occurrences of variables with their matching values
458      * from the resolver using the given source buffer as a template.
459      * The buffer is not altered by this method.
460      * <p>
461      * Only the specified portion of the buffer will be processed.
462      * The rest of the buffer is not processed, and is not returned.
463      *
464      * @param source  the buffer to use as a template, not changed, null returns null
465      * @param offset  the start offset within the array, must be valid
466      * @param length  the length within the array to be processed, must be valid
467      * @return the result of the replace operation
468      */
469     public String replace(final StringBuffer source, final int offset, final int length) {
470         return replace(null, source, offset, length);
471     }
472 
473     /**
474      * Replaces all the occurrences of variables with their matching values
475      * from the resolver using the given source buffer as a template.
476      * The buffer is not altered by this method.
477      * <p>
478      * Only the specified portion of the buffer will be processed.
479      * The rest of the buffer is not processed, and is not returned.
480      *
481      * @param event the current LogEvent, if one exists.
482      * @param source  the buffer to use as a template, not changed, null returns null
483      * @param offset  the start offset within the array, must be valid
484      * @param length  the length within the array to be processed, must be valid
485      * @return the result of the replace operation
486      */
487     public String replace(final LogEvent event, final StringBuffer source, final int offset, final int length) {
488         if (source == null) {
489             return null;
490         }
491         final StringBuilder buf = new StringBuilder(length).append(source, offset, length);
492         substitute(event, buf, 0, length);
493         return buf.toString();
494     }
495 
496     //-----------------------------------------------------------------------
497     /**
498      * Replaces all the occurrences of variables with their matching values
499      * from the resolver using the given source builder as a template.
500      * The builder is not altered by this method.
501      *
502      * @param source  the builder to use as a template, not changed, null returns null
503      * @return the result of the replace operation
504      */
505     public String replace(final StringBuilder source) {
506         return replace(null, source);
507     }
508 
509     //-----------------------------------------------------------------------
510     /**
511      * Replaces all the occurrences of variables with their matching values
512      * from the resolver using the given source builder as a template.
513      * The builder is not altered by this method.
514      *
515      * @param event The LogEvent.
516      * @param source  the builder to use as a template, not changed, null returns null.
517      * @return the result of the replace operation.
518      */
519     public String replace(final LogEvent event, final StringBuilder source) {
520         if (source == null) {
521             return null;
522         }
523         final StringBuilder buf = new StringBuilder(source.length()).append(source);
524         substitute(event, buf, 0, buf.length());
525         return buf.toString();
526     }
527     /**
528      * Replaces all the occurrences of variables with their matching values
529      * from the resolver using the given source builder as a template.
530      * The builder is not altered by this method.
531      * <p>
532      * Only the specified portion of the builder will be processed.
533      * The rest of the builder is not processed, and is not returned.
534      *
535      * @param source  the builder to use as a template, not changed, null returns null
536      * @param offset  the start offset within the array, must be valid
537      * @param length  the length within the array to be processed, must be valid
538      * @return the result of the replace operation
539      */
540     public String replace(final StringBuilder source, final int offset, final int length) {
541         return replace(null, source, offset, length);
542     }
543 
544     /**
545      * Replaces all the occurrences of variables with their matching values
546      * from the resolver using the given source builder as a template.
547      * The builder is not altered by this method.
548      * <p>
549      * Only the specified portion of the builder will be processed.
550      * The rest of the builder is not processed, and is not returned.
551      *
552      * @param event the current LogEvent, if one exists.
553      * @param source  the builder to use as a template, not changed, null returns null
554      * @param offset  the start offset within the array, must be valid
555      * @param length  the length within the array to be processed, must be valid
556      * @return the result of the replace operation
557      */
558     public String replace(final LogEvent event, final StringBuilder source, final int offset, final int length) {
559         if (source == null) {
560             return null;
561         }
562         final StringBuilder buf = new StringBuilder(length).append(source, offset, length);
563         substitute(event, buf, 0, length);
564         return buf.toString();
565     }
566 
567     //-----------------------------------------------------------------------
568     /**
569      * Replaces all the occurrences of variables in the given source object with
570      * their matching values from the resolver. The input source object is
571      * converted to a string using <code>toString</code> and is not altered.
572      *
573      * @param source  the source to replace in, null returns null
574      * @return the result of the replace operation
575      */
576     public String replace(final Object source) {
577         return replace(null, source);
578     }
579     //-----------------------------------------------------------------------
580     /**
581      * Replaces all the occurrences of variables in the given source object with
582      * their matching values from the resolver. The input source object is
583      * converted to a string using <code>toString</code> and is not altered.
584      *
585      * @param event the current LogEvent, if one exists.
586      * @param source  the source to replace in, null returns null
587      * @return the result of the replace operation
588      */
589     public String replace(final LogEvent event, final Object source) {
590         if (source == null) {
591             return null;
592         }
593         final StringBuilder buf = new StringBuilder().append(source);
594         substitute(event, buf, 0, buf.length());
595         return buf.toString();
596     }
597 
598     //-----------------------------------------------------------------------
599     /**
600      * Replaces all the occurrences of variables within the given source buffer
601      * with their matching values from the resolver.
602      * The buffer is updated with the result.
603      *
604      * @param source  the buffer to replace in, updated, null returns zero
605      * @return true if altered
606      */
607     public boolean replaceIn(final StringBuffer source) {
608         if (source == null) {
609             return false;
610         }
611         return replaceIn(source, 0, source.length());
612     }
613 
614     /**
615      * Replaces all the occurrences of variables within the given source buffer
616      * with their matching values from the resolver.
617      * The buffer is updated with the result.
618      * <p>
619      * Only the specified portion of the buffer will be processed.
620      * The rest of the buffer is not processed, but it is not deleted.
621      *
622      * @param source  the buffer to replace in, updated, null returns zero
623      * @param offset  the start offset within the array, must be valid
624      * @param length  the length within the buffer to be processed, must be valid
625      * @return true if altered
626      */
627     public boolean replaceIn(final StringBuffer source, final int offset, final int length) {
628         return replaceIn(null, source, offset, length);
629     }
630 
631     /**
632      * Replaces all the occurrences of variables within the given source buffer
633      * with their matching values from the resolver.
634      * The buffer is updated with the result.
635      * <p>
636      * Only the specified portion of the buffer will be processed.
637      * The rest of the buffer is not processed, but it is not deleted.
638      *
639      * @param event the current LogEvent, if one exists.
640      * @param source  the buffer to replace in, updated, null returns zero
641      * @param offset  the start offset within the array, must be valid
642      * @param length  the length within the buffer to be processed, must be valid
643      * @return true if altered
644      */
645     public boolean replaceIn(final LogEvent event, final StringBuffer source, final int offset, final int length) {
646         if (source == null) {
647             return false;
648         }
649         final StringBuilder buf = new StringBuilder(length).append(source, offset, length);
650         if (!substitute(event, buf, 0, length)) {
651             return false;
652         }
653         source.replace(offset, offset + length, buf.toString());
654         return true;
655     }
656 
657     //-----------------------------------------------------------------------
658     /**
659      * Replaces all the occurrences of variables within the given source
660      * builder with their matching values from the resolver.
661      *
662      * @param source  the builder to replace in, updated, null returns zero
663      * @return true if altered
664      */
665     public boolean replaceIn(final StringBuilder source) {
666         return replaceIn(null, source);
667     }
668 
669     //-----------------------------------------------------------------------
670     /**
671      * Replaces all the occurrences of variables within the given source
672      * builder with their matching values from the resolver.
673      *
674      * @param event the current LogEvent, if one exists.
675      * @param source  the builder to replace in, updated, null returns zero
676      * @return true if altered
677      */
678     public boolean replaceIn(final LogEvent event, final StringBuilder source) {
679         if (source == null) {
680             return false;
681         }
682         return substitute(event, source, 0, source.length());
683     }
684     /**
685      * Replaces all the occurrences of variables within the given source
686      * builder with their matching values from the resolver.
687      * <p>
688      * Only the specified portion of the builder will be processed.
689      * The rest of the builder is not processed, but it is not deleted.
690      *
691      * @param source  the builder to replace in, null returns zero
692      * @param offset  the start offset within the array, must be valid
693      * @param length  the length within the builder to be processed, must be valid
694      * @return true if altered
695      */
696     public boolean replaceIn(final StringBuilder source, final int offset, final int length) {
697         return replaceIn(null, source, offset, length);
698     }
699 
700     /**
701      * Replaces all the occurrences of variables within the given source
702      * builder with their matching values from the resolver.
703      * <p>
704      * Only the specified portion of the builder will be processed.
705      * The rest of the builder is not processed, but it is not deleted.
706      *
707      * @param event   the current LogEvent, if one is present.
708      * @param source  the builder to replace in, null returns zero
709      * @param offset  the start offset within the array, must be valid
710      * @param length  the length within the builder to be processed, must be valid
711      * @return true if altered
712      */
713     public boolean replaceIn(final LogEvent event, final StringBuilder source, final int offset, final int length) {
714         if (source == null) {
715             return false;
716         }
717         return substitute(event, source, offset, length);
718     }
719 
720     //-----------------------------------------------------------------------
721     /**
722      * Internal method that substitutes the variables.
723      * <p>
724      * Most users of this class do not need to call this method. This method will
725      * be called automatically by another (public) method.
726      * <p>
727      * Writers of subclasses can override this method if they need access to
728      * the substitution process at the start or end.
729      *
730      * @param event The current LogEvent, if there is one.
731      * @param buf  the string builder to substitute into, not null
732      * @param offset  the start offset within the builder, must be valid
733      * @param length  the length within the builder to be processed, must be valid
734      * @return true if altered
735      */
736     protected boolean substitute(final LogEvent event, final StringBuilder buf, final int offset, final int length) {
737         return substitute(event, buf, offset, length, null) > 0;
738     }
739 
740     /**
741      * Recursive handler for multiple levels of interpolation. This is the main
742      * interpolation method, which resolves the values of all variable references
743      * contained in the passed in text.
744      *
745      * @param event The current LogEvent, if there is one.
746      * @param buf  the string builder to substitute into, not null
747      * @param offset  the start offset within the builder, must be valid
748      * @param length  the length within the builder to be processed, must be valid
749      * @param priorVariables  the stack keeping track of the replaced variables, may be null
750      * @return the length change that occurs, unless priorVariables is null when the int
751      *  represents a boolean flag as to whether any change occurred.
752      */
753     private int substitute(final LogEvent event, final StringBuilder buf, final int offset, final int length,
754                            List<String> priorVariables) {
755         final StrMatcher prefixMatcher = getVariablePrefixMatcher();
756         final StrMatcher suffixMatcher = getVariableSuffixMatcher();
757         final char escape = getEscapeChar();
758 
759         final 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             final 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                     final 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                                     final StringBuilder bufName = new StringBuilder(varName);
807                                     substitute(event, bufName, 0, bufName.length());
808                                     varName = bufName.toString();
809                                 }
810                                 pos += endMatchLen;
811                                 final 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                                 final String varValue = resolveVariable(event, varName, buf,
826                                         startPos, endPos);
827                                 if (varValue != null) {
828                                     // recursive replace
829                                     final 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(final String varName, final List<String> priorVariables) {
869         if (!priorVariables.contains(varName)) {
870             return;
871         }
872         final 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(final LogEvent event, final String variableName, final StringBuilder buf,
899                                      final int startPos, final int endPos) {
900         final 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(final 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 character 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 character 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(final 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(final 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 character 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(final 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 character 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 character 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(final 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 character 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(final 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(final 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(final 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(final boolean enableSubstitutionInVariables) {
1099         this.enableSubstitutionInVariables = enableSubstitutionInVariables;
1100     }
1101 
1102     private char[] getChars(final StringBuilder sb) {
1103         final 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(final StringBuilder sb, final Iterable<?> iterable, String separator) {
1118         if (iterable != null) {
1119             separator = (separator == null ? "" : separator);
1120             final 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 }