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;
028
029/**
030 * Substitutes variables within a string by values.
031 * <p>
032 * This class takes a piece of text and substitutes all the variables within it.
033 * The default definition of a variable is <code>${variableName}</code>.
034 * The prefix and suffix can be changed via constructors and set methods.
035 * <p>
036 * Variable values are typically resolved from a map, but could also be resolved
037 * from system properties, or by supplying a custom variable resolver.
038 * <p>
039 * The simplest example is to use this class to replace Java System properties. For example:
040 * <pre>
041 * StrSubstitutor.replaceSystemProperties(
042 *      "You are running with java.version = ${java.version} and os.name = ${os.name}.");
043 * </pre>
044 * <p>
045 * Typical usage of this class follows the following pattern: First an instance is created
046 * and initialized with the map that contains the values for the available variables.
047 * If a prefix and/or suffix for variables should be used other than the default ones,
048 * the appropriate settings can be performed. After that the <code>replace()</code>
049 * method can be called passing in the source text for interpolation. In the returned
050 * text all variable references (as long as their values are known) will be resolved.
051 * The following example demonstrates this:
052 * <pre>
053 * Map valuesMap = HashMap();
054 * valuesMap.put(&quot;animal&quot;, &quot;quick brown fox&quot;);
055 * valuesMap.put(&quot;target&quot;, &quot;lazy dog&quot;);
056 * String templateString = &quot;The ${animal} jumped over the ${target}.&quot;;
057 * StrSubstitutor sub = new StrSubstitutor(valuesMap);
058 * String resolvedString = sub.replace(templateString);
059 * </pre>
060 * yielding:
061 * <pre>
062 *      The quick brown fox jumped over the lazy dog.
063 * </pre>
064 * <p>
065 * In addition to this usage pattern there are some static convenience methods that
066 * cover the most common use cases. These methods can be used without the need of
067 * manually creating an instance. However if multiple replace operations are to be
068 * performed, creating and reusing an instance of this class will be more efficient.
069 * <p>
070 * Variable replacement works in a recursive way. Thus, if a variable value contains
071 * a variable then that variable will also be replaced. Cyclic replacements are
072 * detected and will cause an exception to be thrown.
073 * <p>
074 * Sometimes the interpolation's result must contain a variable prefix. As an example
075 * take the following source text:
076 * <pre>
077 *   The variable ${${name}} must be used.
078 * </pre>
079 * Here only the variable's name referred to in the text should be replaced resulting
080 * in the text (assuming that the value of the <code>name</code> variable is <code>x</code>):
081 * <pre>
082 *   The variable ${x} must be used.
083 * </pre>
084 * To achieve this effect there are two possibilities: Either set a different prefix
085 * and suffix for variables which do not conflict with the result text you want to
086 * produce. The other possibility is to use the escape character, by default '$'.
087 * If this character is placed before a variable reference, this reference is ignored
088 * and won't be replaced. For example:
089 * <pre>
090 *   The variable $${${name}} must be used.
091 * </pre>
092 * <p>
093 * In some complex scenarios you might even want to perform substitution in the
094 * names of variables, for instance
095 * <pre>
096 * ${jre-${java.specification.version}}
097 * </pre>
098 * <code>StrSubstitutor</code> supports this recursive substitution in variable
099 * names, but it has to be enabled explicitly by setting the
100 * {@link #setEnableSubstitutionInVariables(boolean) enableSubstitutionInVariables}
101 * property to <b>true</b>.
102 *
103 */
104public 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}