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