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