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