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