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