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