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     */
017    package org.apache.logging.log4j.core.lookup;
018    
019    import java.util.ArrayList;
020    import java.util.Enumeration;
021    import java.util.HashMap;
022    import java.util.Iterator;
023    import java.util.List;
024    import java.util.Map;
025    import java.util.Properties;
026    
027    import org.apache.logging.log4j.core.LogEvent;
028    import 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     */
139    public 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<String, String>();
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<String>();
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                                } else {
983                                    nestedVarCount--;
984                                    pos += endMatchLen;
985                                }
986                            }
987                        }
988                    }
989                }
990            }
991            if (top) {
992                return altered ? 1 : 0;
993            }
994            return lengthChange;
995        }
996    
997        /**
998         * Checks if the specified variable is already in the stack (list) of variables.
999         *
1000         * @param varName  the variable name to check
1001         * @param priorVariables  the list of prior variables
1002         */
1003        private void checkCyclicSubstitution(final String varName, final List<String> priorVariables) {
1004            if (!priorVariables.contains(varName)) {
1005                return;
1006            }
1007            final StringBuilder buf = new StringBuilder(BUF_SIZE);
1008            buf.append("Infinite loop in property interpolation of ");
1009            buf.append(priorVariables.remove(0));
1010            buf.append(": ");
1011            appendWithSeparators(buf, priorVariables, "->");
1012            throw new IllegalStateException(buf.toString());
1013        }
1014    
1015        /**
1016         * Internal method that resolves the value of a variable.
1017         * <p>
1018         * Most users of this class do not need to call this method. This method is
1019         * called automatically by the substitution process.
1020         * </p>
1021         * <p>
1022         * Writers of subclasses can override this method if they need to alter
1023         * how each substitution occurs. The method is passed the variable's name
1024         * and must return the corresponding value. This implementation uses the
1025         * {@link #getVariableResolver()} with the variable's name as the key.
1026         * </p>
1027         *
1028         * @param event The LogEvent, if there is one.
1029         * @param variableName  the name of the variable, not null
1030         * @param buf  the buffer where the substitution is occurring, not null
1031         * @param startPos  the start position of the variable including the prefix, valid
1032         * @param endPos  the end position of the variable including the suffix, valid
1033         * @return the variable's value or <b>null</b> if the variable is unknown
1034         */
1035        protected String resolveVariable(final LogEvent event, final String variableName, final StringBuilder buf,
1036                                         final int startPos, final int endPos) {
1037            final StrLookup resolver = getVariableResolver();
1038            if (resolver == null) {
1039                return null;
1040            }
1041            return resolver.lookup(event, variableName);
1042        }
1043    
1044        // Escape
1045        //-----------------------------------------------------------------------
1046        /**
1047         * Returns the escape character.
1048         *
1049         * @return the character used for escaping variable references
1050         */
1051        public char getEscapeChar() {
1052            return this.escapeChar;
1053        }
1054    
1055        /**
1056         * Sets the escape character.
1057         * If this character is placed before a variable reference in the source
1058         * text, this variable will be ignored.
1059         *
1060         * @param escapeCharacter  the escape character (0 for disabling escaping)
1061         */
1062        public void setEscapeChar(final char escapeCharacter) {
1063            this.escapeChar = escapeCharacter;
1064        }
1065    
1066        // Prefix
1067        //-----------------------------------------------------------------------
1068        /**
1069         * Gets the variable prefix matcher currently in use.
1070         * <p>
1071         * The variable prefix is the character or characters that identify the
1072         * start of a variable. This prefix is expressed in terms of a matcher
1073         * allowing advanced prefix matches.
1074         * </p>
1075         *
1076         * @return the prefix matcher in use
1077         */
1078        public StrMatcher getVariablePrefixMatcher() {
1079            return prefixMatcher;
1080        }
1081    
1082        /**
1083         * Sets the variable prefix matcher currently in use.
1084         * <p>
1085         * The variable prefix is the character or characters that identify the
1086         * start of a variable. This prefix is expressed in terms of a matcher
1087         * allowing advanced prefix matches.
1088         * </p>
1089         *
1090         * @param prefixMatcher  the prefix matcher to use, null ignored
1091         * @return this, to enable chaining
1092         * @throws IllegalArgumentException if the prefix matcher is null
1093         */
1094        public StrSubstitutor setVariablePrefixMatcher(final StrMatcher prefixMatcher) {
1095            if (prefixMatcher == null) {
1096                throw new IllegalArgumentException("Variable prefix matcher must not be null!");
1097            }
1098            this.prefixMatcher = prefixMatcher;
1099            return this;
1100        }
1101    
1102        /**
1103         * Sets the variable prefix to use.
1104         * <p>
1105         * The variable prefix is the character or characters that identify the
1106         * start of a variable. This method allows a single character prefix to
1107         * be easily set.
1108         * </p>
1109         *
1110         * @param prefix  the prefix character to use
1111         * @return this, to enable chaining
1112         */
1113        public StrSubstitutor setVariablePrefix(final char prefix) {
1114            return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix));
1115        }
1116    
1117        /**
1118         * Sets the variable prefix to use.
1119         * <p>
1120         * The variable prefix is the character or characters that identify the
1121         * start of a variable. This method allows a string prefix to be easily set.
1122         * </p>
1123         *
1124         * @param prefix  the prefix for variables, not null
1125         * @return this, to enable chaining
1126         * @throws IllegalArgumentException if the prefix is null
1127         */
1128        public StrSubstitutor setVariablePrefix(final String prefix) {
1129           if (prefix == null) {
1130                throw new IllegalArgumentException("Variable prefix must not be null!");
1131            }
1132            return setVariablePrefixMatcher(StrMatcher.stringMatcher(prefix));
1133        }
1134    
1135        // Suffix
1136        //-----------------------------------------------------------------------
1137        /**
1138         * Gets the variable suffix matcher currently in use.
1139         * <p>
1140         * The variable suffix is the character or characters that identify the
1141         * end of a variable. This suffix is expressed in terms of a matcher
1142         * allowing advanced suffix matches.
1143         * </p>
1144         *
1145         * @return the suffix matcher in use
1146         */
1147        public StrMatcher getVariableSuffixMatcher() {
1148            return suffixMatcher;
1149        }
1150    
1151        /**
1152         * Sets the variable suffix matcher currently in use.
1153         * <p>
1154         * The variable suffix is the character or characters that identify the
1155         * end of a variable. This suffix is expressed in terms of a matcher
1156         * allowing advanced suffix matches.
1157         * </p>
1158         *
1159         * @param suffixMatcher  the suffix matcher to use, null ignored
1160         * @return this, to enable chaining
1161         * @throws IllegalArgumentException if the suffix matcher is null
1162         */
1163        public StrSubstitutor setVariableSuffixMatcher(final StrMatcher suffixMatcher) {
1164            if (suffixMatcher == null) {
1165                throw new IllegalArgumentException("Variable suffix matcher must not be null!");
1166            }
1167            this.suffixMatcher = suffixMatcher;
1168            return this;
1169        }
1170    
1171        /**
1172         * Sets the variable suffix to use.
1173         * <p>
1174         * The variable suffix is the character or characters that identify the
1175         * end of a variable. This method allows a single character suffix to
1176         * be easily set.
1177         * </p>
1178         *
1179         * @param suffix  the suffix character to use
1180         * @return this, to enable chaining
1181         */
1182        public StrSubstitutor setVariableSuffix(final char suffix) {
1183            return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix));
1184        }
1185    
1186        /**
1187         * Sets the variable suffix to use.
1188         * <p>
1189         * The variable suffix is the character or characters that identify the
1190         * end of a variable. This method allows a string suffix to be easily set.
1191         * </p>
1192         *
1193         * @param suffix  the suffix for variables, not null
1194         * @return this, to enable chaining
1195         * @throws IllegalArgumentException if the suffix is null
1196         */
1197        public StrSubstitutor setVariableSuffix(final String suffix) {
1198           if (suffix == null) {
1199                throw new IllegalArgumentException("Variable suffix must not be null!");
1200            }
1201            return setVariableSuffixMatcher(StrMatcher.stringMatcher(suffix));
1202        }
1203    
1204        // Variable Default Value Delimiter
1205        //-----------------------------------------------------------------------
1206        /**
1207         * Gets the variable default value delimiter matcher currently in use.
1208         * <p>
1209         * The variable default value delimiter is the character or characters that delimit the
1210         * variable name and the variable default value. This delimiter is expressed in terms of a matcher
1211         * allowing advanced variable default value delimiter matches.
1212         * </p>
1213         * <p>
1214         * If it returns null, then the variable default value resolution is disabled.
1215         * </p>
1216         *
1217         * @return the variable default value delimiter matcher in use, may be null
1218         */
1219        public StrMatcher getValueDelimiterMatcher() {
1220            return valueDelimiterMatcher;
1221        }
1222    
1223        /**
1224         * Sets the variable default value delimiter matcher to use.
1225         * <p>
1226         * The variable default value delimiter is the character or characters that delimit the
1227         * variable name and the variable default value. This delimiter is expressed in terms of a matcher
1228         * allowing advanced variable default value delimiter matches.
1229         * </p>
1230         * <p>
1231         * If the <code>valueDelimiterMatcher</code> is null, then the variable default value resolution
1232         * becomes disabled.
1233         * </p>
1234         *
1235         * @param valueDelimiterMatcher  variable default value delimiter matcher to use, may be null
1236         * @return this, to enable chaining
1237         */
1238        public StrSubstitutor setValueDelimiterMatcher(final StrMatcher valueDelimiterMatcher) {
1239            this.valueDelimiterMatcher = valueDelimiterMatcher;
1240            return this;
1241        }
1242    
1243        /**
1244         * Sets the variable default value delimiter to use.
1245         * <p>
1246         * The variable default value delimiter is the character or characters that delimit the
1247         * variable name and the variable default value. This method allows a single character
1248         * variable default value delimiter to be easily set.
1249         * </p>
1250         *
1251         * @param valueDelimiter  the variable default value delimiter character to use
1252         * @return this, to enable chaining
1253         */
1254        public StrSubstitutor setValueDelimiter(final char valueDelimiter) {
1255            return setValueDelimiterMatcher(StrMatcher.charMatcher(valueDelimiter));
1256        }
1257    
1258        /**
1259         * Sets the variable default value delimiter to use.
1260         * <p>
1261         * The variable default value delimiter is the character or characters that delimit the
1262         * variable name and the variable default value. This method allows a string
1263         * variable default value delimiter to be easily set.
1264         * </p>
1265         * <p>
1266         * If the <code>valueDelimiter</code> is null or empty string, then the variable default
1267         * value resolution becomes disabled.
1268         * </p>
1269         *
1270         * @param valueDelimiter  the variable default value delimiter string to use, may be null or empty
1271         * @return this, to enable chaining
1272         */
1273        public StrSubstitutor setValueDelimiter(final String valueDelimiter) {
1274            if (Strings.isEmpty(valueDelimiter)) {
1275                setValueDelimiterMatcher(null);
1276                return this;
1277            }
1278            return setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter));
1279        }
1280    
1281        // Resolver
1282        //-----------------------------------------------------------------------
1283        /**
1284         * Gets the VariableResolver that is used to lookup variables.
1285         *
1286         * @return the VariableResolver
1287         */
1288        public StrLookup getVariableResolver() {
1289            return this.variableResolver;
1290        }
1291    
1292        /**
1293         * Sets the VariableResolver that is used to lookup variables.
1294         *
1295         * @param variableResolver  the VariableResolver
1296         */
1297        public void setVariableResolver(final StrLookup variableResolver) {
1298            this.variableResolver = variableResolver;
1299        }
1300    
1301        // Substitution support in variable names
1302        //-----------------------------------------------------------------------
1303        /**
1304         * Returns a flag whether substitution is done in variable names.
1305         *
1306         * @return the substitution in variable names flag
1307         */
1308        public boolean isEnableSubstitutionInVariables() {
1309            return enableSubstitutionInVariables;
1310        }
1311    
1312        /**
1313         * Sets a flag whether substitution is done in variable names. If set to
1314         * <b>true</b>, the names of variables can contain other variables which are
1315         * processed first before the original variable is evaluated, e.g.
1316         * <code>${jre-${java.version}}</code>. The default value is <b>false</b>.
1317         *
1318         * @param enableSubstitutionInVariables the new value of the flag
1319         */
1320        public void setEnableSubstitutionInVariables(final boolean enableSubstitutionInVariables) {
1321            this.enableSubstitutionInVariables = enableSubstitutionInVariables;
1322        }
1323    
1324        private char[] getChars(final StringBuilder sb) {
1325            final char[] chars = new char[sb.length()];
1326            sb.getChars(0, sb.length(), chars, 0);
1327            return chars;
1328        }
1329    
1330        /**
1331         * Appends a iterable placing separators between each value, but
1332         * not before the first or after the last.
1333         * Appending a null iterable will have no effect..
1334         *
1335         * @param sb StringBuilder that contains the String being constructed.
1336         * @param iterable  the iterable to append
1337         * @param separator  the separator to use, null means no separator
1338         */
1339        public void appendWithSeparators(final StringBuilder sb, final Iterable<?> iterable, String separator) {
1340            if (iterable != null) {
1341                separator = separator == null ? Strings.EMPTY : separator;
1342                final Iterator<?> it = iterable.iterator();
1343                while (it.hasNext()) {
1344                    sb.append(it.next());
1345                    if (it.hasNext()) {
1346                        sb.append(separator);
1347                    }
1348                }
1349            }
1350        }
1351    
1352        @Override
1353        public String toString() {
1354            return "StrSubstitutor(" + variableResolver.toString() + ')';
1355        }
1356    }