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 <V> The type of object contained in the Map.
155         * @param valueMap  the map with the variables' values, may be null
156         */
157        public <V> StrSubstitutor(Map<String, V> valueMap) {
158            this(new MapLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
159        }
160    
161        /**
162         * Creates a new instance and initializes it. Uses a default escaping character.
163         *
164         * @param <V> The type of object contained in the Map.
165         * @param valueMap  the map with the variables' values, may be null
166         * @param prefix  the prefix for variables, not null
167         * @param suffix  the suffix for variables, not null
168         * @throws IllegalArgumentException if the prefix or suffix is null
169         */
170        public <V> StrSubstitutor(Map<String, V> valueMap, String prefix, String suffix) {
171            this(new MapLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE);
172        }
173    
174        /**
175         * Creates a new instance and initializes it.
176         *
177         * @param <V> The type of object contained in the Map.
178         * @param valueMap  the map with the variables' values, may be null
179         * @param prefix  the prefix for variables, not null
180         * @param suffix  the suffix for variables, not null
181         * @param escape  the escape character
182         * @throws IllegalArgumentException if the prefix or suffix is null
183         */
184        public <V> StrSubstitutor(Map<String, V> valueMap, String prefix, String suffix, char escape) {
185            this(new MapLookup(valueMap), prefix, suffix, escape);
186        }
187    
188        /**
189         * Creates a new instance and initializes it.
190         *
191         * @param variableResolver  the variable resolver, may be null
192         */
193        public StrSubstitutor(StrLookup<?> variableResolver) {
194            this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
195        }
196    
197        /**
198         * Creates a new instance and initializes it.
199         *
200         * @param variableResolver  the variable resolver, may be null
201         * @param prefix  the prefix for variables, not null
202         * @param suffix  the suffix for variables, not null
203         * @param escape  the escape character
204         * @throws IllegalArgumentException if the prefix or suffix is null
205         */
206        public StrSubstitutor(StrLookup<?> variableResolver, String prefix, String suffix, char escape) {
207            this.setVariableResolver(variableResolver);
208            this.setVariablePrefix(prefix);
209            this.setVariableSuffix(suffix);
210            this.setEscapeChar(escape);
211        }
212    
213        /**
214         * Creates a new instance and initializes it.
215         *
216         * @param variableResolver  the variable resolver, may be null
217         * @param prefixMatcher  the prefix for variables, not null
218         * @param suffixMatcher  the suffix for variables, not null
219         * @param escape  the escape character
220         * @throws IllegalArgumentException if the prefix or suffix is null
221         */
222        public StrSubstitutor(StrLookup<?> variableResolver, StrMatcher prefixMatcher, StrMatcher suffixMatcher,
223                              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 <V> The type of object contained in the Map.
235         * @param source  the source text containing the variables to substitute, null returns null
236         * @param valueMap  the map with the values, may be null
237         * @return the result of the replace operation
238         */
239        public static <V> String replace(Object source, Map<String, V> valueMap) {
240            return new StrSubstitutor(valueMap).replace(source);
241        }
242    
243        /**
244         * Replaces all the occurrences of variables in the given source object with
245         * their matching values from the map. This method allows to specifiy a
246         * custom variable prefix and suffix
247         *
248         * @param <V> The type of object contained in the Map.
249         * @param source  the source text containing the variables to substitute, null returns null
250         * @param valueMap  the map with the values, may be null
251         * @param prefix  the prefix of variables, not null
252         * @param suffix  the suffix of variables, not null
253         * @return the result of the replace operation
254         * @throws IllegalArgumentException if the prefix or suffix is null
255         */
256        public static <V> String replace(Object source, Map<String, V> valueMap, String prefix, String suffix) {
257            return new StrSubstitutor(valueMap, prefix, suffix).replace(source);
258        }
259    
260        /**
261         * Replaces all the occurrences of variables in the given source object with their matching
262         * values from the properties.
263         *
264         * @param source the source text containing the variables to substitute, null returns null
265         * @param valueProperties the properties with values, may be null
266         * @return the result of the replace operation
267         */
268        public static String replace(Object source, Properties valueProperties) {
269            if (valueProperties == null) {
270                return source.toString();
271            }
272            Map<String, String> valueMap = new HashMap<String, String>();
273            Enumeration<?> propNames = valueProperties.propertyNames();
274            while (propNames.hasMoreElements()) {
275                String propName = (String) propNames.nextElement();
276                String propValue = valueProperties.getProperty(propName);
277                valueMap.put(propName, propValue);
278            }
279            return StrSubstitutor.replace(source, valueMap);
280        }
281    
282        //-----------------------------------------------------------------------
283        /**
284         * Replaces all the occurrences of variables with their matching values
285         * from the resolver using the given source string as a template.
286         *
287         * @param source  the string to replace in, null returns null
288         * @return the result of the replace operation
289         */
290        public String replace(String source) {
291            return replace(null, source);
292        }
293        //-----------------------------------------------------------------------
294        /**
295         * Replaces all the occurrences of variables with their matching values
296         * from the resolver using the given source string as a template.
297         *
298         * @param event The current LogEvent if there is one.
299         * @param source  the string to replace in, null returns null
300         * @return the result of the replace operation
301         */
302        public String replace(LogEvent event, String source) {
303            if (source == null) {
304                return null;
305            }
306            StringBuilder buf = new StringBuilder(source);
307            if (!substitute(event, buf, 0, source.length())) {
308                return source;
309            }
310            return buf.toString();
311        }
312    
313        /**
314         * Replaces all the occurrences of variables with their matching values
315         * from the resolver using the given source string as a template.
316         * <p>
317         * Only the specified portion of the string will be processed.
318         * The rest of the string is not processed, and is not returned.
319         *
320         * @param source  the string to replace in, null returns null
321         * @param offset  the start offset within the array, must be valid
322         * @param length  the length within the array to be processed, must be valid
323         * @return the result of the replace operation
324         */
325        public String replace(String source, int offset, int length) {
326            return replace(null, source, offset, length);
327        }
328    
329        /**
330         * Replaces all the occurrences of variables with their matching values
331         * from the resolver using the given source string as a template.
332         * <p>
333         * Only the specified portion of the string will be processed.
334         * The rest of the string is not processed, and is not returned.
335         *
336         * @param event the current LogEvent, if one exists.
337         * @param source  the string to replace in, null returns null
338         * @param offset  the start offset within the array, must be valid
339         * @param length  the length within the array to be processed, must be valid
340         * @return the result of the replace operation
341         */
342        public String replace(LogEvent event, String source, int offset, int length) {
343            if (source == null) {
344                return null;
345            }
346            StringBuilder buf = new StringBuilder(length).append(source, offset, length);
347            if (!substitute(event, buf, 0, length)) {
348                return source.substring(offset, offset + length);
349            }
350            return buf.toString();
351        }
352    
353        //-----------------------------------------------------------------------
354        /**
355         * Replaces all the occurrences of variables with their matching values
356         * from the resolver using the given source array as a template.
357         * The array is not altered by this method.
358         *
359         * @param source  the character array to replace in, not altered, null returns null
360         * @return the result of the replace operation
361         */
362        public String replace(char[] source) {
363            return replace(null, source);
364        }
365    
366        //-----------------------------------------------------------------------
367        /**
368         * Replaces all the occurrences of variables with their matching values
369         * from the resolver using the given source array as a template.
370         * The array is not altered by this method.
371         *
372         * @param event the current LogEvent, if one exists.
373         * @param source  the character array to replace in, not altered, null returns null
374         * @return the result of the replace operation
375         */
376        public String replace(LogEvent event, char[] source) {
377            if (source == null) {
378                return null;
379            }
380            StringBuilder buf = new StringBuilder(source.length).append(source);
381            substitute(event, buf, 0, source.length);
382            return buf.toString();
383        }
384    
385        /**
386         * Replaces all the occurrences of variables with their matching values
387         * from the resolver using the given source array as a template.
388         * The array is not altered by this method.
389         * <p>
390         * Only the specified portion of the array will be processed.
391         * The rest of the array is not processed, and is not returned.
392         *
393         * @param source  the character array to replace in, not altered, null returns null
394         * @param offset  the start offset within the array, must be valid
395         * @param length  the length within the array to be processed, must be valid
396         * @return the result of the replace operation
397         */
398        public String replace(char[] source, int offset, int length) {
399            return replace(null, source, offset, length);
400        }
401    
402        /**
403         * Replaces all the occurrences of variables with their matching values
404         * from the resolver using the given source array as a template.
405         * The array is not altered by this method.
406         * <p>
407         * Only the specified portion of the array will be processed.
408         * The rest of the array is not processed, and is not returned.
409         *
410         * @param event the current LogEvent, if one exists.
411         * @param source  the character array to replace in, not altered, null returns null
412         * @param offset  the start offset within the array, must be valid
413         * @param length  the length within the array to be processed, must be valid
414         * @return the result of the replace operation
415         */
416        public String replace(LogEvent event, char[] source, int offset, int length) {
417            if (source == null) {
418                return null;
419            }
420            StringBuilder buf = new StringBuilder(length).append(source, offset, length);
421            substitute(event, buf, 0, length);
422            return buf.toString();
423        }
424    
425        //-----------------------------------------------------------------------
426        /**
427         * Replaces all the occurrences of variables with their matching values
428         * from the resolver using the given source buffer as a template.
429         * The buffer is not altered by this method.
430         *
431         * @param source  the buffer to use as a template, not changed, null returns null
432         * @return the result of the replace operation
433         */
434        public String replace(StringBuffer source) {
435            return replace(null, source);
436        }
437    
438        //-----------------------------------------------------------------------
439        /**
440         * Replaces all the occurrences of variables with their matching values
441         * from the resolver using the given source buffer as a template.
442         * The buffer is not altered by this method.
443         *
444         * @param event the current LogEvent, if one exists.
445         * @param source  the buffer to use as a template, not changed, null returns null
446         * @return the result of the replace operation
447         */
448        public String replace(LogEvent event, StringBuffer source) {
449            if (source == null) {
450                return null;
451            }
452            StringBuilder buf = new StringBuilder(source.length()).append(source);
453            substitute(event, buf, 0, buf.length());
454            return buf.toString();
455        }
456    
457        /**
458         * Replaces all the occurrences of variables with their matching values
459         * from the resolver using the given source buffer as a template.
460         * The buffer is not altered by this method.
461         * <p>
462         * Only the specified portion of the buffer will be processed.
463         * The rest of the buffer is not processed, and is not returned.
464         *
465         * @param source  the buffer to use as a template, not changed, null returns null
466         * @param offset  the start offset within the array, must be valid
467         * @param length  the length within the array to be processed, must be valid
468         * @return the result of the replace operation
469         */
470        public String replace(StringBuffer source, int offset, int length) {
471            return replace(null, source, offset, length);
472        }
473    
474        /**
475         * Replaces all the occurrences of variables with their matching values
476         * from the resolver using the given source buffer as a template.
477         * The buffer is not altered by this method.
478         * <p>
479         * Only the specified portion of the buffer will be processed.
480         * The rest of the buffer is not processed, and is not returned.
481         *
482         * @param event the current LogEvent, if one exists.
483         * @param source  the buffer to use as a template, not changed, null returns null
484         * @param offset  the start offset within the array, must be valid
485         * @param length  the length within the array to be processed, must be valid
486         * @return the result of the replace operation
487         */
488        public String replace(LogEvent event, StringBuffer source, int offset, int length) {
489            if (source == null) {
490                return null;
491            }
492            StringBuilder buf = new StringBuilder(length).append(source, offset, length);
493            substitute(event, buf, 0, length);
494            return buf.toString();
495        }
496    
497        //-----------------------------------------------------------------------
498        /**
499         * Replaces all the occurrences of variables with their matching values
500         * from the resolver using the given source builder as a template.
501         * The builder is not altered by this method.
502         *
503         * @param source  the builder to use as a template, not changed, null returns null
504         * @return the result of the replace operation
505         */
506        public String replace(StringBuilder source) {
507            return replace(null, source);
508        }
509    
510        //-----------------------------------------------------------------------
511        /**
512         * Replaces all the occurrences of variables with their matching values
513         * from the resolver using the given source builder as a template.
514         * The builder is not altered by this method.
515         *
516         * @param event The LogEvent.
517         * @param source  the builder to use as a template, not changed, null returns null.
518         * @return the result of the replace operation.
519         */
520        public String replace(LogEvent event, StringBuilder source) {
521            if (source == null) {
522                return null;
523            }
524            StringBuilder buf = new StringBuilder(source.length()).append(source);
525            substitute(event, buf, 0, buf.length());
526            return buf.toString();
527        }
528        /**
529         * Replaces all the occurrences of variables with their matching values
530         * from the resolver using the given source builder as a template.
531         * The builder is not altered by this method.
532         * <p>
533         * Only the specified portion of the builder will be processed.
534         * The rest of the builder is not processed, and is not returned.
535         *
536         * @param source  the builder to use as a template, not changed, null returns null
537         * @param offset  the start offset within the array, must be valid
538         * @param length  the length within the array to be processed, must be valid
539         * @return the result of the replace operation
540         */
541        public String replace(StringBuilder source, int offset, int length) {
542            return replace(null, source, offset, length);
543        }
544    
545        /**
546         * Replaces all the occurrences of variables with their matching values
547         * from the resolver using the given source builder as a template.
548         * The builder is not altered by this method.
549         * <p>
550         * Only the specified portion of the builder will be processed.
551         * The rest of the builder is not processed, and is not returned.
552         *
553         * @param event the current LogEvent, if one exists.
554         * @param source  the builder to use as a template, not changed, null returns null
555         * @param offset  the start offset within the array, must be valid
556         * @param length  the length within the array to be processed, must be valid
557         * @return the result of the replace operation
558         */
559        public String replace(LogEvent event, StringBuilder source, int offset, int length) {
560            if (source == null) {
561                return null;
562            }
563            StringBuilder buf = new StringBuilder(length).append(source, offset, length);
564            substitute(event, buf, 0, length);
565            return buf.toString();
566        }
567    
568        //-----------------------------------------------------------------------
569        /**
570         * Replaces all the occurrences of variables in the given source object with
571         * their matching values from the resolver. The input source object is
572         * converted to a string using <code>toString</code> and is not altered.
573         *
574         * @param source  the source to replace in, null returns null
575         * @return the result of the replace operation
576         */
577        public String replace(Object source) {
578            return replace(null, source);
579        }
580        //-----------------------------------------------------------------------
581        /**
582         * Replaces all the occurrences of variables in the given source object with
583         * their matching values from the resolver. The input source object is
584         * converted to a string using <code>toString</code> and is not altered.
585         *
586         * @param event the current LogEvent, if one exists.
587         * @param source  the source to replace in, null returns null
588         * @return the result of the replace operation
589         */
590        public String replace(LogEvent event, Object source) {
591            if (source == null) {
592                return null;
593            }
594            StringBuilder buf = new StringBuilder().append(source);
595            substitute(event, buf, 0, buf.length());
596            return buf.toString();
597        }
598    
599        //-----------------------------------------------------------------------
600        /**
601         * Replaces all the occurrences of variables within the given source buffer
602         * with their matching values from the resolver.
603         * The buffer is updated with the result.
604         *
605         * @param source  the buffer to replace in, updated, null returns zero
606         * @return true if altered
607         */
608        public boolean replaceIn(StringBuffer source) {
609            if (source == null) {
610                return false;
611            }
612            return replaceIn(source, 0, source.length());
613        }
614    
615        /**
616         * Replaces all the occurrences of variables within the given source buffer
617         * with their matching values from the resolver.
618         * The buffer is updated with the result.
619         * <p>
620         * Only the specified portion of the buffer will be processed.
621         * The rest of the buffer is not processed, but it is not deleted.
622         *
623         * @param source  the buffer to replace in, updated, null returns zero
624         * @param offset  the start offset within the array, must be valid
625         * @param length  the length within the buffer to be processed, must be valid
626         * @return true if altered
627         */
628        public boolean replaceIn(StringBuffer source, int offset, int length) {
629            return replaceIn(null, source, offset, length);
630        }
631    
632        /**
633         * Replaces all the occurrences of variables within the given source buffer
634         * with their matching values from the resolver.
635         * The buffer is updated with the result.
636         * <p>
637         * Only the specified portion of the buffer will be processed.
638         * The rest of the buffer is not processed, but it is not deleted.
639         *
640         * @param event the current LogEvent, if one exists.
641         * @param source  the buffer to replace in, updated, null returns zero
642         * @param offset  the start offset within the array, must be valid
643         * @param length  the length within the buffer to be processed, must be valid
644         * @return true if altered
645         */
646        public boolean replaceIn(LogEvent event, StringBuffer source, int offset, int length) {
647            if (source == null) {
648                return false;
649            }
650            StringBuilder buf = new StringBuilder(length).append(source, offset, length);
651            if (!substitute(event, buf, 0, length)) {
652                return false;
653            }
654            source.replace(offset, offset + length, buf.toString());
655            return true;
656        }
657    
658        //-----------------------------------------------------------------------
659        /**
660         * Replaces all the occurrences of variables within the given source
661         * builder with their matching values from the resolver.
662         *
663         * @param source  the builder to replace in, updated, null returns zero
664         * @return true if altered
665         */
666        public boolean replaceIn(StringBuilder source) {
667            return replaceIn(null, source);
668        }
669    
670        //-----------------------------------------------------------------------
671        /**
672         * Replaces all the occurrences of variables within the given source
673         * builder with their matching values from the resolver.
674         *
675         * @param event the current LogEvent, if one exists.
676         * @param source  the builder to replace in, updated, null returns zero
677         * @return true if altered
678         */
679        public boolean replaceIn(LogEvent event, StringBuilder source) {
680            if (source == null) {
681                return false;
682            }
683            return substitute(event, source, 0, source.length());
684        }
685        /**
686         * Replaces all the occurrences of variables within the given source
687         * builder with their matching values from the resolver.
688         * <p>
689         * Only the specified portion of the builder will be processed.
690         * The rest of the builder is not processed, but it is not deleted.
691         *
692         * @param source  the builder to replace in, null returns zero
693         * @param offset  the start offset within the array, must be valid
694         * @param length  the length within the builder to be processed, must be valid
695         * @return true if altered
696         */
697        public boolean replaceIn(StringBuilder source, int offset, int length) {
698            return replaceIn(null, source, offset, length);
699        }
700    
701        /**
702         * Replaces all the occurrences of variables within the given source
703         * builder with their matching values from the resolver.
704         * <p>
705         * Only the specified portion of the builder will be processed.
706         * The rest of the builder is not processed, but it is not deleted.
707         *
708         * @param event   the current LogEvent, if one is present.
709         * @param source  the builder to replace in, null returns zero
710         * @param offset  the start offset within the array, must be valid
711         * @param length  the length within the builder to be processed, must be valid
712         * @return true if altered
713         */
714        public boolean replaceIn(LogEvent event, StringBuilder source, int offset, int length) {
715            if (source == null) {
716                return false;
717            }
718            return substitute(event, source, offset, length);
719        }
720    
721        //-----------------------------------------------------------------------
722        /**
723         * Internal method that substitutes the variables.
724         * <p>
725         * Most users of this class do not need to call this method. This method will
726         * be called automatically by another (public) method.
727         * <p>
728         * Writers of subclasses can override this method if they need access to
729         * the substitution process at the start or end.
730         *
731         * @param event The current LogEvent, if there is one.
732         * @param buf  the string builder to substitute into, not null
733         * @param offset  the start offset within the builder, must be valid
734         * @param length  the length within the builder to be processed, must be valid
735         * @return true if altered
736         */
737        protected boolean substitute(LogEvent event, StringBuilder buf, int offset, int length) {
738            return substitute(event, buf, offset, length, null) > 0;
739        }
740    
741        /**
742         * Recursive handler for multiple levels of interpolation. This is the main
743         * interpolation method, which resolves the values of all variable references
744         * contained in the passed in text.
745         *
746         * @param event The current LogEvent, if there is one.
747         * @param buf  the string builder to substitute into, not null
748         * @param offset  the start offset within the builder, must be valid
749         * @param length  the length within the builder to be processed, must be valid
750         * @param priorVariables  the stack keeping track of the replaced variables, may be null
751         * @return the length change that occurs, unless priorVariables is null when the int
752         *  represents a boolean flag as to whether any change occurred.
753         */
754        private int substitute(LogEvent event, StringBuilder buf, int offset, int length, List<String> priorVariables) {
755            StrMatcher prefixMatcher = getVariablePrefixMatcher();
756            StrMatcher suffixMatcher = getVariableSuffixMatcher();
757            char escape = getEscapeChar();
758    
759            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                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                        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                                        StringBuilder bufName = new StringBuilder(varName);
807                                        substitute(event, bufName, 0, bufName.length());
808                                        varName = bufName.toString();
809                                    }
810                                    pos += endMatchLen;
811                                    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                                    String varValue = resolveVariable(event, varName, buf,
826                                            startPos, endPos);
827                                    if (varValue != null) {
828                                        // recursive replace
829                                        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(String varName, List<String> priorVariables) {
869            if (!priorVariables.contains(varName)) {
870                return;
871            }
872            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(LogEvent event, String variableName, StringBuilder buf, int startPos,
899                                         int endPos) {
900            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(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(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(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(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(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(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(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(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(boolean enableSubstitutionInVariables) {
1099            this.enableSubstitutionInVariables = enableSubstitutionInVariables;
1100        }
1101    
1102        private char[] getChars(StringBuilder sb) {
1103            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(StringBuilder sb, Iterable<?> iterable, String separator) {
1118            if (iterable != null) {
1119                separator = (separator == null ? "" : separator);
1120                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    }