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