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