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