StrSubstitutor.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      https://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.lang3.text;

  18. import java.util.ArrayList;
  19. import java.util.Enumeration;
  20. import java.util.HashMap;
  21. import java.util.List;
  22. import java.util.Map;
  23. import java.util.Objects;
  24. import java.util.Properties;

  25. import org.apache.commons.lang3.StringUtils;

  26. /**
  27.  * Substitutes variables within a string by values.
  28.  * <p>
  29.  * This class takes a piece of text and substitutes all the variables within it.
  30.  * The default definition of a variable is {@code ${variableName}}.
  31.  * The prefix and suffix can be changed via constructors and set methods.
  32.  * </p>
  33.  * <p>
  34.  * Variable values are typically resolved from a map, but could also be resolved
  35.  * from system properties, or by supplying a custom variable resolver.
  36.  * </p>
  37.  * <p>
  38.  * The simplest example is to use this class to replace Java System properties. For example:
  39.  * </p>
  40.  * <pre>
  41.  * StrSubstitutor.replaceSystemProperties(
  42.  *      "You are running with java.version = ${java.version} and os.name = ${os.name}.");
  43.  * </pre>
  44.  * <p>
  45.  * Typical usage of this class follows the following pattern: First an instance is created
  46.  * and initialized with the map that contains the values for the available variables.
  47.  * If a prefix and/or suffix for variables should be used other than the default ones,
  48.  * the appropriate settings can be performed. After that the {@code replace()}
  49.  * method can be called passing in the source text for interpolation. In the returned
  50.  * text all variable references (as long as their values are known) will be resolved.
  51.  * The following example demonstrates this:
  52.  * </p>
  53.  * <pre>
  54.  * Map valuesMap = HashMap();
  55.  * valuesMap.put(&quot;animal&quot;, &quot;quick brown fox&quot;);
  56.  * valuesMap.put(&quot;target&quot;, &quot;lazy dog&quot;);
  57.  * String templateString = &quot;The ${animal} jumps over the ${target}.&quot;;
  58.  * StrSubstitutor sub = new StrSubstitutor(valuesMap);
  59.  * String resolvedString = sub.replace(templateString);
  60.  * </pre>
  61.  * yielding:
  62.  * <pre>
  63.  *      The quick brown fox jumps over the lazy dog.
  64.  * </pre>
  65.  * <p>
  66.  * Also, this class allows to set a default value for unresolved variables.
  67.  * The default value for a variable can be appended to the variable name after the variable
  68.  * default value delimiter. The default value of the variable default value delimiter is ':-',
  69.  * as in bash and other *nix shells, as those are arguably where the default ${} delimiter set originated.
  70.  * The variable default value delimiter can be manually set by calling {@link #setValueDelimiterMatcher(StrMatcher)},
  71.  * {@link #setValueDelimiter(char)} or {@link #setValueDelimiter(String)}.
  72.  * The following shows an example with variable default value settings:
  73.  * </p>
  74.  * <pre>
  75.  * Map valuesMap = HashMap();
  76.  * valuesMap.put(&quot;animal&quot;, &quot;quick brown fox&quot;);
  77.  * valuesMap.put(&quot;target&quot;, &quot;lazy dog&quot;);
  78.  * String templateString = &quot;The ${animal} jumps over the ${target}. ${undefined.number:-1234567890}.&quot;;
  79.  * StrSubstitutor sub = new StrSubstitutor(valuesMap);
  80.  * String resolvedString = sub.replace(templateString);
  81.  * </pre>
  82.  * <p>
  83.  * yielding:
  84.  * </p>
  85.  * <pre>
  86.  *      The quick brown fox jumps over the lazy dog. 1234567890.
  87.  * </pre>
  88.  * <p>
  89.  * In addition to this usage pattern there are some static convenience methods that
  90.  * cover the most common use cases. These methods can be used without the need of
  91.  * manually creating an instance. However if multiple replace operations are to be
  92.  * performed, creating and reusing an instance of this class will be more efficient.
  93.  * </p>
  94.  * <p>
  95.  * Variable replacement works in a recursive way. Thus, if a variable value contains
  96.  * a variable then that variable will also be replaced. Cyclic replacements are
  97.  * detected and will cause an exception to be thrown.
  98.  * </p>
  99.  * <p>
  100.  * Sometimes the interpolation's result must contain a variable prefix. As an example
  101.  * take the following source text:
  102.  * </p>
  103.  * <pre>
  104.  *   The variable ${${name}} must be used.
  105.  * </pre>
  106.  * <p>
  107.  * Here only the variable's name referred to in the text should be replaced resulting
  108.  * in the text (assuming that the value of the {@code name} variable is {@code x}):
  109.  * </p>
  110.  * <pre>
  111.  *   The variable ${x} must be used.
  112.  * </pre>
  113.  * <p>
  114.  * To achieve this effect there are two possibilities: Either set a different prefix
  115.  * and suffix for variables which do not conflict with the result text you want to
  116.  * produce. The other possibility is to use the escape character, by default '$'.
  117.  * If this character is placed before a variable reference, this reference is ignored
  118.  * and won't be replaced. For example:
  119.  * </p>
  120.  * <pre>
  121.  *   The variable $${${name}} must be used.
  122.  * </pre>
  123.  * <p>
  124.  * In some complex scenarios you might even want to perform substitution in the
  125.  * names of variables, for instance:
  126.  * </p>
  127.  * <pre>
  128.  * ${jre-${java.specification.version}}
  129.  * </pre>
  130.  * <p>
  131.  * {@link StrSubstitutor} supports this recursive substitution in variable
  132.  * names, but it has to be enabled explicitly by setting the
  133.  * {@link #setEnableSubstitutionInVariables(boolean) enableSubstitutionInVariables}
  134.  * property to <strong>true</strong>.
  135.  * </p>
  136.  * <p>
  137.  * This class is <strong>not</strong> thread safe.
  138.  * </p>
  139.  *
  140.  * @since 2.2
  141.  * @deprecated As of <a href="https://commons.apache.org/proper/commons-lang/changes-report.html#a3.6">3.6</a>, use Apache Commons Text
  142.  * <a href="https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/StringSubstitutor.html">
  143.  * StringSubstitutor</a>.
  144.  */
  145. @Deprecated
  146. public class StrSubstitutor {

  147.     /**
  148.      * Constant for the default escape character.
  149.      */
  150.     public static final char DEFAULT_ESCAPE = '$';
  151.     /**
  152.      * Constant for the default variable prefix.
  153.      */
  154.     public static final StrMatcher DEFAULT_PREFIX = StrMatcher.stringMatcher("${");
  155.     /**
  156.      * Constant for the default variable suffix.
  157.      */
  158.     public static final StrMatcher DEFAULT_SUFFIX = StrMatcher.stringMatcher("}");
  159.     /**
  160.      * Constant for the default value delimiter of a variable.
  161.      * @since 3.2
  162.      */
  163.     public static final StrMatcher DEFAULT_VALUE_DELIMITER = StrMatcher.stringMatcher(":-");

  164.     /**
  165.      * Replaces all the occurrences of variables in the given source object with
  166.      * their matching values from the map.
  167.      *
  168.      * @param <V> the type of the values in the map
  169.      * @param source  the source text containing the variables to substitute, null returns null
  170.      * @param valueMap  the map with the values, may be null
  171.      * @return the result of the replace operation
  172.      */
  173.     public static <V> String replace(final Object source, final Map<String, V> valueMap) {
  174.         return new StrSubstitutor(valueMap).replace(source);
  175.     }
  176.     /**
  177.      * Replaces all the occurrences of variables in the given source object with
  178.      * their matching values from the map. This method allows to specify a
  179.      * custom variable prefix and suffix
  180.      *
  181.      * @param <V> the type of the values in the map
  182.      * @param source  the source text containing the variables to substitute, null returns null
  183.      * @param valueMap  the map with the values, may be null
  184.      * @param prefix  the prefix of variables, not null
  185.      * @param suffix  the suffix of variables, not null
  186.      * @return the result of the replace operation
  187.      * @throws IllegalArgumentException if the prefix or suffix is null
  188.      */
  189.     public static <V> String replace(final Object source, final Map<String, V> valueMap, final String prefix, final String suffix) {
  190.         return new StrSubstitutor(valueMap, prefix, suffix).replace(source);
  191.     }
  192.     /**
  193.      * Replaces all the occurrences of variables in the given source object with their matching
  194.      * values from the properties.
  195.      *
  196.      * @param source the source text containing the variables to substitute, null returns null
  197.      * @param valueProperties the properties with values, may be null
  198.      * @return the result of the replace operation
  199.      */
  200.     public static String replace(final Object source, final Properties valueProperties) {
  201.         if (valueProperties == null) {
  202.             return source.toString();
  203.         }
  204.         final Map<String, String> valueMap = new HashMap<>();
  205.         final Enumeration<?> propNames = valueProperties.propertyNames();
  206.         while (propNames.hasMoreElements()) {
  207.             final String propName = String.valueOf(propNames.nextElement());
  208.             final String propValue = valueProperties.getProperty(propName);
  209.             valueMap.put(propName, propValue);
  210.         }
  211.         return replace(source, valueMap);
  212.     }
  213.     /**
  214.      * Replaces all the occurrences of variables in the given source object with
  215.      * their matching values from the system properties.
  216.      *
  217.      * @param source  the source text containing the variables to substitute, null returns null
  218.      * @return the result of the replace operation
  219.      */
  220.     public static String replaceSystemProperties(final Object source) {
  221.         return new StrSubstitutor(StrLookup.systemPropertiesLookup()).replace(source);
  222.     }
  223.     /**
  224.      * Stores the escape character.
  225.      */
  226.     private char escapeChar;
  227.     /**
  228.      * Stores the variable prefix.
  229.      */
  230.     private StrMatcher prefixMatcher;
  231.     /**
  232.      * Stores the variable suffix.
  233.      */
  234.     private StrMatcher suffixMatcher;

  235.     /**
  236.      * Stores the default variable value delimiter
  237.      */
  238.     private StrMatcher valueDelimiterMatcher;

  239.     /**
  240.      * Variable resolution is delegated to an implementor of VariableResolver.
  241.      */
  242.     private StrLookup<?> variableResolver;

  243.     /**
  244.      * The flag whether substitution in variable names is enabled.
  245.      */
  246.     private boolean enableSubstitutionInVariables;

  247.     /**
  248.      * Whether escapes should be preserved.  Default is false;
  249.      */
  250.     private boolean preserveEscapes;

  251.     /**
  252.      * Creates a new instance with defaults for variable prefix and suffix
  253.      * and the escaping character.
  254.      */
  255.     public StrSubstitutor() {
  256.         this(null, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
  257.     }

  258.     /**
  259.      * Creates a new instance and initializes it. Uses defaults for variable
  260.      * prefix and suffix and the escaping character.
  261.      *
  262.      * @param <V> the type of the values in the map
  263.      * @param valueMap  the map with the variables' values, may be null
  264.      */
  265.     public <V> StrSubstitutor(final Map<String, V> valueMap) {
  266.         this(StrLookup.mapLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
  267.     }

  268.     /**
  269.      * Creates a new instance and initializes it. Uses a default escaping character.
  270.      *
  271.      * @param <V> the type of the values in the map
  272.      * @param valueMap  the map with the variables' values, may be null
  273.      * @param prefix  the prefix for variables, not null
  274.      * @param suffix  the suffix for variables, not null
  275.      * @throws IllegalArgumentException if the prefix or suffix is null
  276.      */
  277.     public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix) {
  278.         this(StrLookup.mapLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE);
  279.     }

  280.     /**
  281.      * Creates a new instance and initializes it.
  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.      * @param escape  the escape character
  288.      * @throws IllegalArgumentException if the prefix or suffix is null
  289.      */
  290.     public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix, final char escape) {
  291.         this(StrLookup.mapLookup(valueMap), prefix, suffix, escape);
  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.      * @param valueDelimiter  the variable default value delimiter, may be null
  302.      * @throws IllegalArgumentException if the prefix or suffix is null
  303.      * @since 3.2
  304.      */
  305.     public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix, final char escape, final String valueDelimiter) {
  306.         this(StrLookup.mapLookup(valueMap), prefix, suffix, escape, valueDelimiter);
  307.     }

  308.     /**
  309.      * Creates a new instance and initializes it.
  310.      *
  311.      * @param variableResolver  the variable resolver, may be null
  312.      */
  313.     public StrSubstitutor(final StrLookup<?> variableResolver) {
  314.         this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
  315.     }

  316.     /**
  317.      * Creates a new instance and initializes it.
  318.      *
  319.      * @param variableResolver  the variable resolver, may be null
  320.      * @param prefix  the prefix for variables, not null
  321.      * @param suffix  the suffix for variables, not null
  322.      * @param escape  the escape character
  323.      * @throws IllegalArgumentException if the prefix or suffix is null
  324.      */
  325.     public StrSubstitutor(final StrLookup<?> variableResolver, final String prefix, final String suffix, final char escape) {
  326.         setVariableResolver(variableResolver);
  327.         setVariablePrefix(prefix);
  328.         setVariableSuffix(suffix);
  329.         setEscapeChar(escape);
  330.         setValueDelimiterMatcher(DEFAULT_VALUE_DELIMITER);
  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.      * @param valueDelimiter  the variable default value delimiter string, may be null
  340.      * @throws IllegalArgumentException if the prefix or suffix is null
  341.      * @since 3.2
  342.      */
  343.     public StrSubstitutor(final StrLookup<?> variableResolver, final String prefix, final String suffix, final char escape, final String valueDelimiter) {
  344.         setVariableResolver(variableResolver);
  345.         setVariablePrefix(prefix);
  346.         setVariableSuffix(suffix);
  347.         setEscapeChar(escape);
  348.         setValueDelimiter(valueDelimiter);
  349.     }

  350.     /**
  351.      * Creates a new instance and initializes it.
  352.      *
  353.      * @param variableResolver  the variable resolver, may be null
  354.      * @param prefixMatcher  the prefix for variables, not null
  355.      * @param suffixMatcher  the suffix for variables, not null
  356.      * @param escape  the escape character
  357.      * @throws IllegalArgumentException if the prefix or suffix is null
  358.      */
  359.     public StrSubstitutor(final StrLookup<?> variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher, final char escape) {
  360.         this(variableResolver, prefixMatcher, suffixMatcher, escape, DEFAULT_VALUE_DELIMITER);
  361.     }

  362.     /**
  363.      * Creates a new instance and initializes it.
  364.      *
  365.      * @param variableResolver  the variable resolver, may be null
  366.      * @param prefixMatcher  the prefix for variables, not null
  367.      * @param suffixMatcher  the suffix for variables, not null
  368.      * @param escape  the escape character
  369.      * @param valueDelimiterMatcher  the variable default value delimiter matcher, may be null
  370.      * @throws IllegalArgumentException if the prefix or suffix is null
  371.      * @since 3.2
  372.      */
  373.     public StrSubstitutor(final StrLookup<?> variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher, final char escape,
  374.             final StrMatcher valueDelimiterMatcher) {
  375.         setVariableResolver(variableResolver);
  376.         setVariablePrefixMatcher(prefixMatcher);
  377.         setVariableSuffixMatcher(suffixMatcher);
  378.         setEscapeChar(escape);
  379.         setValueDelimiterMatcher(valueDelimiterMatcher);
  380.     }

  381.     /**
  382.      * Checks if the specified variable is already in the stack (list) of variables.
  383.      *
  384.      * @param varName  the variable name to check
  385.      * @param priorVariables  the list of prior variables
  386.      */
  387.     private void checkCyclicSubstitution(final String varName, final List<String> priorVariables) {
  388.         if (!priorVariables.contains(varName)) {
  389.             return;
  390.         }
  391.         final StrBuilder buf = new StrBuilder(256);
  392.         buf.append("Infinite loop in property interpolation of ");
  393.         buf.append(priorVariables.remove(0));
  394.         buf.append(": ");
  395.         buf.appendWithSeparators(priorVariables, "->");
  396.         throw new IllegalStateException(buf.toString());
  397.     }

  398.     /**
  399.      * Gets the escape character.
  400.      *
  401.      * @return the character used for escaping variable references
  402.      */
  403.     public char getEscapeChar() {
  404.         return this.escapeChar;
  405.     }

  406.     /**
  407.      * Gets the variable default value delimiter matcher currently in use.
  408.      * <p>
  409.      * The variable default value delimiter is the character or characters that delimit the
  410.      * variable name and the variable default value. This delimiter is expressed in terms of a matcher
  411.      * allowing advanced variable default value delimiter matches.
  412.      * </p>
  413.      * <p>
  414.      * If it returns null, then the variable default value resolution is disabled.
  415.      * </p>
  416.      *
  417.      * @return the variable default value delimiter matcher in use, may be null
  418.      * @since 3.2
  419.      */
  420.     public StrMatcher getValueDelimiterMatcher() {
  421.         return valueDelimiterMatcher;
  422.     }

  423.     /**
  424.      * Gets the variable prefix matcher currently in use.
  425.      * <p>
  426.      * The variable prefix is the character or characters that identify the
  427.      * start of a variable. This prefix is expressed in terms of a matcher
  428.      * allowing advanced prefix matches.
  429.      * </p>
  430.      *
  431.      * @return the prefix matcher in use
  432.      */
  433.     public StrMatcher getVariablePrefixMatcher() {
  434.         return prefixMatcher;
  435.     }

  436.     /**
  437.      * Gets the VariableResolver that is used to lookup variables.
  438.      *
  439.      * @return the VariableResolver
  440.      */
  441.     public StrLookup<?> getVariableResolver() {
  442.         return this.variableResolver;
  443.     }

  444.     /**
  445.      * Gets the variable suffix matcher currently in use.
  446.      * <p>
  447.      * The variable suffix is the character or characters that identify the
  448.      * end of a variable. This suffix is expressed in terms of a matcher
  449.      * allowing advanced suffix matches.
  450.      * </p>
  451.      *
  452.      * @return the suffix matcher in use
  453.      */
  454.     public StrMatcher getVariableSuffixMatcher() {
  455.         return suffixMatcher;
  456.     }

  457.     /**
  458.      * Tests whether substitution is done in variable names.
  459.      *
  460.      * @return the substitution in variable names flag
  461.      * @since 3.0
  462.      */
  463.     public boolean isEnableSubstitutionInVariables() {
  464.         return enableSubstitutionInVariables;
  465.     }

  466.     /**
  467.      * Tests whether escapes are preserved during substitution.
  468.      *
  469.      * @return the preserve escape flag
  470.      * @since 3.5
  471.      */
  472.     public boolean isPreserveEscapes() {
  473.         return preserveEscapes;
  474.     }

  475.     /**
  476.      * Replaces all the occurrences of variables with their matching values
  477.      * from the resolver using the given source array as a template.
  478.      * The array is not altered by this method.
  479.      *
  480.      * @param source  the character array to replace in, not altered, null returns null
  481.      * @return the result of the replace operation
  482.      */
  483.     public String replace(final char[] source) {
  484.         if (source == null) {
  485.             return null;
  486.         }
  487.         final StrBuilder buf = new StrBuilder(source.length).append(source);
  488.         substitute(buf, 0, source.length);
  489.         return buf.toString();
  490.     }

  491.     /**
  492.      * Replaces all the occurrences of variables with their matching values
  493.      * from the resolver using the given source array as a template.
  494.      * The array is not altered by this method.
  495.      * <p>
  496.      * Only the specified portion of the array will be processed.
  497.      * The rest of the array is not processed, and is not returned.
  498.      * </p>
  499.      *
  500.      * @param source  the character array to replace in, not altered, null returns null
  501.      * @param offset  the start offset within the array, must be valid
  502.      * @param length  the length within the array to be processed, must be valid
  503.      * @return the result of the replace operation
  504.      */
  505.     public String replace(final char[] source, final int offset, final int length) {
  506.         if (source == null) {
  507.             return null;
  508.         }
  509.         final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
  510.         substitute(buf, 0, length);
  511.         return buf.toString();
  512.     }

  513.     /**
  514.      * Replaces all the occurrences of variables with their matching values
  515.      * from the resolver using the given source as a template.
  516.      * The source is not altered by this method.
  517.      *
  518.      * @param source  the buffer to use as a template, not changed, null returns null
  519.      * @return the result of the replace operation
  520.      * @since 3.2
  521.      */
  522.     public String replace(final CharSequence source) {
  523.         if (source == null) {
  524.             return null;
  525.         }
  526.         return replace(source, 0, source.length());
  527.     }

  528.     /**
  529.      * Replaces all the occurrences of variables with their matching values
  530.      * from the resolver using the given source as a template.
  531.      * The source is not altered by this method.
  532.      * <p>
  533.      * Only the specified portion of the buffer will be processed.
  534.      * The rest of the buffer is not processed, and is not returned.
  535.      * </p>
  536.      *
  537.      * @param source  the buffer to use as a template, not changed, null returns null
  538.      * @param offset  the start offset within the array, must be valid
  539.      * @param length  the length within the array to be processed, must be valid
  540.      * @return the result of the replace operation
  541.      * @since 3.2
  542.      */
  543.     public String replace(final CharSequence source, final int offset, final int length) {
  544.         if (source == null) {
  545.             return null;
  546.         }
  547.         final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
  548.         substitute(buf, 0, length);
  549.         return buf.toString();
  550.     }

  551.     /**
  552.      * Replaces all the occurrences of variables in the given source object with
  553.      * their matching values from the resolver. The input source object is
  554.      * converted to a string using {@code toString} and is not altered.
  555.      *
  556.      * @param source  the source to replace in, null returns null
  557.      * @return the result of the replace operation
  558.      */
  559.     public String replace(final Object source) {
  560.         if (source == null) {
  561.             return null;
  562.         }
  563.         final StrBuilder buf = new StrBuilder().append(source);
  564.         substitute(buf, 0, buf.length());
  565.         return buf.toString();
  566.     }

  567.     /**
  568.      * Replaces all the occurrences of variables with their matching values
  569.      * from the resolver using the given source builder as a template.
  570.      * The builder is not altered by this method.
  571.      *
  572.      * @param source  the builder to use as a template, not changed, null returns null
  573.      * @return the result of the replace operation
  574.      */
  575.     public String replace(final StrBuilder source) {
  576.         if (source == null) {
  577.             return null;
  578.         }
  579.         final StrBuilder buf = new StrBuilder(source.length()).append(source);
  580.         substitute(buf, 0, buf.length());
  581.         return buf.toString();
  582.     }

  583.     /**
  584.      * Replaces all the occurrences of variables with their matching values
  585.      * from the resolver using the given source builder as a template.
  586.      * The builder is not altered by this method.
  587.      * <p>
  588.      * Only the specified portion of the builder will be processed.
  589.      * The rest of the builder is not processed, and is not returned.
  590.      * </p>
  591.      *
  592.      * @param source  the builder to use as a template, not changed, null returns null
  593.      * @param offset  the start offset within the array, must be valid
  594.      * @param length  the length within the array to be processed, must be valid
  595.      * @return the result of the replace operation
  596.      */
  597.     public String replace(final StrBuilder source, final int offset, final int length) {
  598.         if (source == null) {
  599.             return null;
  600.         }
  601.         final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
  602.         substitute(buf, 0, length);
  603.         return buf.toString();
  604.     }

  605.     /**
  606.      * Replaces all the occurrences of variables with their matching values
  607.      * from the resolver using the given source string as a template.
  608.      *
  609.      * @param source  the string to replace in, null returns null
  610.      * @return the result of the replace operation
  611.      */
  612.     public String replace(final String source) {
  613.         if (source == null) {
  614.             return null;
  615.         }
  616.         final StrBuilder buf = new StrBuilder(source);
  617.         if (!substitute(buf, 0, source.length())) {
  618.             return source;
  619.         }
  620.         return buf.toString();
  621.     }

  622.     /**
  623.      * Replaces all the occurrences of variables with their matching values
  624.      * from the resolver using the given source string as a template.
  625.      * <p>
  626.      * Only the specified portion of the string will be processed.
  627.      * The rest of the string is not processed, and is not returned.
  628.      * </p>
  629.      *
  630.      * @param source  the string to replace in, null returns null
  631.      * @param offset  the start offset within the array, must be valid
  632.      * @param length  the length within the array to be processed, must be valid
  633.      * @return the result of the replace operation
  634.      */
  635.     public String replace(final String source, final int offset, final int length) {
  636.         if (source == null) {
  637.             return null;
  638.         }
  639.         final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
  640.         if (!substitute(buf, 0, length)) {
  641.             return source.substring(offset, offset + length);
  642.         }
  643.         return buf.toString();
  644.     }

  645.     /**
  646.      * Replaces all the occurrences of variables with their matching values
  647.      * from the resolver using the given source buffer as a template.
  648.      * The buffer is not altered by this method.
  649.      *
  650.      * @param source  the buffer to use as a template, not changed, null returns null
  651.      * @return the result of the replace operation
  652.      */
  653.     public String replace(final StringBuffer source) {
  654.         if (source == null) {
  655.             return null;
  656.         }
  657.         final StrBuilder buf = new StrBuilder(source.length()).append(source);
  658.         substitute(buf, 0, buf.length());
  659.         return buf.toString();
  660.     }

  661.     /**
  662.      * Replaces all the occurrences of variables with their matching values
  663.      * from the resolver using the given source buffer as a template.
  664.      * The buffer is not altered by this method.
  665.      * <p>
  666.      * Only the specified portion of the buffer will be processed.
  667.      * The rest of the buffer is not processed, and is not returned.
  668.      * </p>
  669.      *
  670.      * @param source  the buffer to use as a template, not changed, null returns null
  671.      * @param offset  the start offset within the array, must be valid
  672.      * @param length  the length within the array to be processed, must be valid
  673.      * @return the result of the replace operation
  674.      */
  675.     public String replace(final StringBuffer source, final int offset, final int length) {
  676.         if (source == null) {
  677.             return null;
  678.         }
  679.         final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
  680.         substitute(buf, 0, length);
  681.         return buf.toString();
  682.     }

  683.     /**
  684.      * Replaces all the occurrences of variables within the given source
  685.      * builder with their matching values from the resolver.
  686.      *
  687.      * @param source  the builder to replace in, updated, null returns zero
  688.      * @return true if altered
  689.      */
  690.     public boolean replaceIn(final StrBuilder source) {
  691.         if (source == null) {
  692.             return false;
  693.         }
  694.         return substitute(source, 0, source.length());
  695.     }

  696.     /**
  697.      * Replaces all the occurrences of variables within the given source
  698.      * builder with their matching values from the resolver.
  699.      * <p>
  700.      * Only the specified portion of the builder will be processed.
  701.      * The rest of the builder is not processed, but it is not deleted.
  702.      * </p>
  703.      *
  704.      * @param source  the builder to replace in, null returns zero
  705.      * @param offset  the start offset within the array, must be valid
  706.      * @param length  the length within the builder to be processed, must be valid
  707.      * @return true if altered
  708.      */
  709.     public boolean replaceIn(final StrBuilder source, final int offset, final int length) {
  710.         if (source == null) {
  711.             return false;
  712.         }
  713.         return substitute(source, offset, length);
  714.     }

  715.     /**
  716.      * Replaces all the occurrences of variables within the given source buffer
  717.      * with their matching values from the resolver.
  718.      * The buffer is updated with the result.
  719.      *
  720.      * @param source  the buffer to replace in, updated, null returns zero
  721.      * @return true if altered
  722.      */
  723.     public boolean replaceIn(final StringBuffer source) {
  724.         if (source == null) {
  725.             return false;
  726.         }
  727.         return replaceIn(source, 0, source.length());
  728.     }

  729.     /**
  730.      * Replaces all the occurrences of variables within the given source buffer
  731.      * with their matching values from the resolver.
  732.      * The buffer is updated with the result.
  733.      * <p>
  734.      * Only the specified portion of the buffer will be processed.
  735.      * The rest of the buffer is not processed, but it is not deleted.
  736.      * </p>
  737.      *
  738.      * @param source  the buffer to replace in, updated, null returns zero
  739.      * @param offset  the start offset within the array, must be valid
  740.      * @param length  the length within the buffer to be processed, must be valid
  741.      * @return true if altered
  742.      */
  743.     public boolean replaceIn(final StringBuffer source, final int offset, final int length) {
  744.         if (source == null) {
  745.             return false;
  746.         }
  747.         final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
  748.         if (!substitute(buf, 0, length)) {
  749.             return false;
  750.         }
  751.         source.replace(offset, offset + length, buf.toString());
  752.         return true;
  753.     }

  754.     /**
  755.      * Replaces all the occurrences of variables within the given source buffer
  756.      * with their matching values from the resolver.
  757.      * The buffer is updated with the result.
  758.      *
  759.      * @param source  the buffer to replace in, updated, null returns zero
  760.      * @return true if altered
  761.      * @since 3.2
  762.      */
  763.     public boolean replaceIn(final StringBuilder source) {
  764.         if (source == null) {
  765.             return false;
  766.         }
  767.         return replaceIn(source, 0, source.length());
  768.     }

  769.     /**
  770.      * Replaces all the occurrences of variables within the given source builder
  771.      * with their matching values from the resolver.
  772.      * The builder is updated with the result.
  773.      * <p>
  774.      * Only the specified portion of the buffer will be processed.
  775.      * The rest of the buffer is not processed, but it is not deleted.
  776.      * </p>
  777.      *
  778.      * @param source  the buffer to replace in, updated, null returns zero
  779.      * @param offset  the start offset within the array, must be valid
  780.      * @param length  the length within the buffer to be processed, must be valid
  781.      * @return true if altered
  782.      * @since 3.2
  783.      */
  784.     public boolean replaceIn(final StringBuilder source, final int offset, final int length) {
  785.         if (source == null) {
  786.             return false;
  787.         }
  788.         final StrBuilder buf = new StrBuilder(length).append(source, offset, length);
  789.         if (!substitute(buf, 0, length)) {
  790.             return false;
  791.         }
  792.         source.replace(offset, offset + length, buf.toString());
  793.         return true;
  794.     }

  795.     /**
  796.      * Internal method that resolves the value of a variable.
  797.      * <p>
  798.      * Most users of this class do not need to call this method. This method is
  799.      * called automatically by the substitution process.
  800.      * </p>
  801.      * <p>
  802.      * Writers of subclasses can override this method if they need to alter
  803.      * how each substitution occurs. The method is passed the variable's name
  804.      * and must return the corresponding value. This implementation uses the
  805.      * {@link #getVariableResolver()} with the variable's name as the key.
  806.      * </p>
  807.      *
  808.      * @param variableName  the name of the variable, not null
  809.      * @param buf  the buffer where the substitution is occurring, not null
  810.      * @param startPos  the start position of the variable including the prefix, valid
  811.      * @param endPos  the end position of the variable including the suffix, valid
  812.      * @return the variable's value or <strong>null</strong> if the variable is unknown
  813.      */
  814.     protected String resolveVariable(final String variableName, final StrBuilder buf, final int startPos, final int endPos) {
  815.         final StrLookup<?> resolver = getVariableResolver();
  816.         if (resolver == null) {
  817.             return null;
  818.         }
  819.         return resolver.lookup(variableName);
  820.     }

  821.     /**
  822.      * Sets a flag whether substitution is done in variable names. If set to
  823.      * <strong>true</strong>, the names of variables can contain other variables which are
  824.      * processed first before the original variable is evaluated, e.g.
  825.      * {@code ${jre-${java.version}}}. The default value is <strong>false</strong>.
  826.      *
  827.      * @param enableSubstitutionInVariables the new value of the flag
  828.      * @since 3.0
  829.      */
  830.     public void setEnableSubstitutionInVariables(
  831.             final boolean enableSubstitutionInVariables) {
  832.         this.enableSubstitutionInVariables = enableSubstitutionInVariables;
  833.     }

  834.     /**
  835.      * Sets the escape character.
  836.      * If this character is placed before a variable reference in the source
  837.      * text, this variable will be ignored.
  838.      *
  839.      * @param escapeCharacter  the escape character (0 for disabling escaping)
  840.      */
  841.     public void setEscapeChar(final char escapeCharacter) {
  842.         this.escapeChar = escapeCharacter;
  843.     }

  844.     /**
  845.      * Sets a flag controlling whether escapes are preserved during
  846.      * substitution.  If set to <strong>true</strong>, the escape character is retained
  847.      * during substitution (e.g. {@code $${this-is-escaped}} remains
  848.      * {@code $${this-is-escaped}}).  If set to <strong>false</strong>, the escape
  849.      * character is removed during substitution (e.g.
  850.      * {@code $${this-is-escaped}} becomes
  851.      * {@code ${this-is-escaped}}).  The default value is <strong>false</strong>
  852.      *
  853.      * @param preserveEscapes true if escapes are to be preserved
  854.      * @since 3.5
  855.      */
  856.     public void setPreserveEscapes(final boolean preserveEscapes) {
  857.         this.preserveEscapes = preserveEscapes;
  858.     }

  859.     /**
  860.      * Sets the variable default value delimiter to use.
  861.      * <p>
  862.      * The variable default value delimiter is the character or characters that delimit the
  863.      * variable name and the variable default value. This method allows a single character
  864.      * variable default value delimiter to be easily set.
  865.      * </p>
  866.      *
  867.      * @param valueDelimiter  the variable default value delimiter character to use
  868.      * @return this, to enable chaining
  869.      * @since 3.2
  870.      */
  871.     public StrSubstitutor setValueDelimiter(final char valueDelimiter) {
  872.         return setValueDelimiterMatcher(StrMatcher.charMatcher(valueDelimiter));
  873.     }

  874.     /**
  875.      * Sets the variable default value delimiter to use.
  876.      * <p>
  877.      * The variable default value delimiter is the character or characters that delimit the
  878.      * variable name and the variable default value. This method allows a string
  879.      * variable default value delimiter to be easily set.
  880.      * </p>
  881.      * <p>
  882.      * If the {@code valueDelimiter} is null or empty string, then the variable default
  883.      * value resolution becomes disabled.
  884.      * </p>
  885.      *
  886.      * @param valueDelimiter  the variable default value delimiter string to use, may be null or empty
  887.      * @return this, to enable chaining
  888.      * @since 3.2
  889.      */
  890.     public StrSubstitutor setValueDelimiter(final String valueDelimiter) {
  891.         if (StringUtils.isEmpty(valueDelimiter)) {
  892.             setValueDelimiterMatcher(null);
  893.             return this;
  894.         }
  895.         return setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter));
  896.     }

  897.     /**
  898.      * Sets the variable default value delimiter matcher to use.
  899.      * <p>
  900.      * The variable default value delimiter is the character or characters that delimit the
  901.      * variable name and the variable default value. This delimiter is expressed in terms of a matcher
  902.      * allowing advanced variable default value delimiter matches.
  903.      * </p>
  904.      * <p>
  905.      * If the {@code valueDelimiterMatcher} is null, then the variable default value resolution
  906.      * becomes disabled.
  907.      * </p>
  908.      *
  909.      * @param valueDelimiterMatcher  variable default value delimiter matcher to use, may be null
  910.      * @return this, to enable chaining
  911.      * @since 3.2
  912.      */
  913.     public StrSubstitutor setValueDelimiterMatcher(final StrMatcher valueDelimiterMatcher) {
  914.         this.valueDelimiterMatcher = valueDelimiterMatcher;
  915.         return this;
  916.     }

  917.     /**
  918.      * Sets the variable prefix to use.
  919.      * <p>
  920.      * The variable prefix is the character or characters that identify the
  921.      * start of a variable. This method allows a single character prefix to
  922.      * be easily set.
  923.      * </p>
  924.      *
  925.      * @param prefix  the prefix character to use
  926.      * @return this, to enable chaining
  927.      */
  928.     public StrSubstitutor setVariablePrefix(final char prefix) {
  929.         return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix));
  930.     }

  931.     /**
  932.      * Sets the variable prefix to use.
  933.      * <p>
  934.      * The variable prefix is the character or characters that identify the
  935.      * start of a variable. This method allows a string prefix to be easily set.
  936.      * </p>
  937.      *
  938.      * @param prefix  the prefix for variables, not null
  939.      * @return this, to enable chaining
  940.      * @throws NullPointerException if the prefix is null
  941.      */
  942.     public StrSubstitutor setVariablePrefix(final String prefix) {
  943.         return setVariablePrefixMatcher(StrMatcher.stringMatcher(Objects.requireNonNull(prefix)));
  944.     }

  945.     /**
  946.      * Sets the variable prefix matcher currently in use.
  947.      * <p>
  948.      * The variable prefix is the character or characters that identify the
  949.      * start of a variable. This prefix is expressed in terms of a matcher
  950.      * allowing advanced prefix matches.
  951.      * </p>
  952.      *
  953.      * @param prefixMatcher  the prefix matcher to use, null ignored
  954.      * @return this, to enable chaining
  955.      * @throws NullPointerException if the prefix matcher is null
  956.      */
  957.     public StrSubstitutor setVariablePrefixMatcher(final StrMatcher prefixMatcher) {
  958.         this.prefixMatcher = Objects.requireNonNull(prefixMatcher, "prefixMatcher");
  959.         return this;
  960.     }

  961.     /**
  962.      * Sets the VariableResolver that is used to lookup variables.
  963.      *
  964.      * @param variableResolver  the VariableResolver
  965.      */
  966.     public void setVariableResolver(final StrLookup<?> variableResolver) {
  967.         this.variableResolver = variableResolver;
  968.     }

  969.     /**
  970.      * Sets the variable suffix to use.
  971.      * <p>
  972.      * The variable suffix is the character or characters that identify the
  973.      * end of a variable. This method allows a single character suffix to
  974.      * be easily set.
  975.      * </p>
  976.      *
  977.      * @param suffix  the suffix character to use
  978.      * @return this, to enable chaining
  979.      */
  980.     public StrSubstitutor setVariableSuffix(final char suffix) {
  981.         return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix));
  982.     }

  983.     /**
  984.      * Sets the variable suffix to use.
  985.      * <p>
  986.      * The variable suffix is the character or characters that identify the
  987.      * end of a variable. This method allows a string suffix to be easily set.
  988.      * </p>
  989.      *
  990.      * @param suffix  the suffix for variables, not null
  991.      * @return this, to enable chaining
  992.      * @throws NullPointerException if the suffix is null
  993.      */
  994.     public StrSubstitutor setVariableSuffix(final String suffix) {
  995.         return setVariableSuffixMatcher(StrMatcher.stringMatcher(Objects.requireNonNull(suffix)));
  996.     }

  997.     /**
  998.      * Sets the variable suffix matcher currently in use.
  999.      * <p>
  1000.      * The variable suffix is the character or characters that identify the
  1001.      * end of a variable. This suffix is expressed in terms of a matcher
  1002.      * allowing advanced suffix matches.
  1003.      * </p>
  1004.      *
  1005.      * @param suffixMatcher  the suffix matcher to use, null ignored
  1006.      * @return this, to enable chaining
  1007.      * @throws NullPointerException if the suffix matcher is null
  1008.      */
  1009.     public StrSubstitutor setVariableSuffixMatcher(final StrMatcher suffixMatcher) {
  1010.         this.suffixMatcher = Objects.requireNonNull(suffixMatcher);
  1011.         return this;
  1012.     }

  1013.     /**
  1014.      * Internal method that substitutes the variables.
  1015.      * <p>
  1016.      * Most users of this class do not need to call this method. This method will
  1017.      * be called automatically by another (public) method.
  1018.      * </p>
  1019.      * <p>
  1020.      * Writers of subclasses can override this method if they need access to
  1021.      * the substitution process at the start or end.
  1022.      * </p>
  1023.      *
  1024.      * @param buf  the string builder to substitute into, not null
  1025.      * @param offset  the start offset within the builder, must be valid
  1026.      * @param length  the length within the builder to be processed, must be valid
  1027.      * @return true if altered
  1028.      */
  1029.     protected boolean substitute(final StrBuilder buf, final int offset, final int length) {
  1030.         return substitute(buf, offset, length, null) > 0;
  1031.     }

  1032.     /**
  1033.      * Recursive handler for multiple levels of interpolation. This is the main
  1034.      * interpolation method, which resolves the values of all variable references
  1035.      * contained in the passed-in text.
  1036.      *
  1037.      * @param buf  the string builder to substitute into, not null
  1038.      * @param offset  the start offset within the builder, must be valid
  1039.      * @param length  the length within the builder to be processed, must be valid
  1040.      * @param priorVariables  the stack keeping track of the replaced variables, may be null
  1041.      * @return the length change that occurs, unless priorVariables is null when the int
  1042.      *  represents a boolean flag as to whether any change occurred.
  1043.      */
  1044.     private int substitute(final StrBuilder buf, final int offset, final int length, List<String> priorVariables) {
  1045.         final StrMatcher pfxMatcher = getVariablePrefixMatcher();
  1046.         final StrMatcher suffMatcher = getVariableSuffixMatcher();
  1047.         final char escape = getEscapeChar();
  1048.         final StrMatcher valueDelimMatcher = getValueDelimiterMatcher();
  1049.         final boolean substitutionInVariablesEnabled = isEnableSubstitutionInVariables();
  1050.         final boolean top = priorVariables == null;
  1051.         boolean altered = false;
  1052.         int lengthChange = 0;
  1053.         char[] chars = buf.buffer;
  1054.         int bufEnd = offset + length;
  1055.         int pos = offset;
  1056.         while (pos < bufEnd) {
  1057.             final int startMatchLen = pfxMatcher.isMatch(chars, pos, offset, bufEnd);
  1058.             if (startMatchLen == 0) {
  1059.                 pos++;
  1060.             } else // found variable start marker
  1061.             if (pos > offset && chars[pos - 1] == escape) {
  1062.                 // escaped
  1063.                 if (preserveEscapes) {
  1064.                     pos++;
  1065.                     continue;
  1066.                 }
  1067.                 buf.deleteCharAt(pos - 1);
  1068.                 chars = buf.buffer; // in case buffer was altered
  1069.                 lengthChange--;
  1070.                 altered = true;
  1071.                 bufEnd--;
  1072.             } else {
  1073.                 // find suffix
  1074.                 final int startPos = pos;
  1075.                 pos += startMatchLen;
  1076.                 int endMatchLen;
  1077.                 int nestedVarCount = 0;
  1078.                 while (pos < bufEnd) {
  1079.                     if (substitutionInVariablesEnabled && (endMatchLen = pfxMatcher.isMatch(chars, pos, offset, bufEnd)) != 0) {
  1080.                         // found a nested variable start
  1081.                         nestedVarCount++;
  1082.                         pos += endMatchLen;
  1083.                         continue;
  1084.                     }
  1085.                     endMatchLen = suffMatcher.isMatch(chars, pos, offset, bufEnd);
  1086.                     if (endMatchLen == 0) {
  1087.                         pos++;
  1088.                     } else {
  1089.                         // found variable end marker
  1090.                         if (nestedVarCount == 0) {
  1091.                             String varNameExpr = new String(chars, startPos + startMatchLen, pos - startPos - startMatchLen);
  1092.                             if (substitutionInVariablesEnabled) {
  1093.                                 final StrBuilder bufName = new StrBuilder(varNameExpr);
  1094.                                 substitute(bufName, 0, bufName.length());
  1095.                                 varNameExpr = bufName.toString();
  1096.                             }
  1097.                             pos += endMatchLen;
  1098.                             final int endPos = pos;
  1099.                             String varName = varNameExpr;
  1100.                             String varDefaultValue = null;
  1101.                             if (valueDelimMatcher != null) {
  1102.                                 final char[] varNameExprChars = varNameExpr.toCharArray();
  1103.                                 int valueDelimiterMatchLen;
  1104.                                 for (int i = 0; i < varNameExprChars.length; i++) {
  1105.                                     // if there's any nested variable when nested variable substitution disabled, then stop resolving name and default value.
  1106.                                     if (!substitutionInVariablesEnabled && pfxMatcher.isMatch(varNameExprChars, i, i, varNameExprChars.length) != 0) {
  1107.                                         break;
  1108.                                     }
  1109.                                     if ((valueDelimiterMatchLen = valueDelimMatcher.isMatch(varNameExprChars, i)) != 0) {
  1110.                                         varName = varNameExpr.substring(0, i);
  1111.                                         varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen);
  1112.                                         break;
  1113.                                     }
  1114.                                 }
  1115.                             }
  1116.                             // on the first call initialize priorVariables
  1117.                             if (priorVariables == null) {
  1118.                                 priorVariables = new ArrayList<>();
  1119.                                 priorVariables.add(new String(chars, offset, length));
  1120.                             }
  1121.                             // handle cyclic substitution
  1122.                             checkCyclicSubstitution(varName, priorVariables);
  1123.                             priorVariables.add(varName);
  1124.                             // resolve the variable
  1125.                             String varValue = resolveVariable(varName, buf, startPos, endPos);
  1126.                             if (varValue == null) {
  1127.                                 varValue = varDefaultValue;
  1128.                             }
  1129.                             if (varValue != null) {
  1130.                                 // recursive replace
  1131.                                 final int varLen = varValue.length();
  1132.                                 buf.replace(startPos, endPos, varValue);
  1133.                                 altered = true;
  1134.                                 int change = substitute(buf, startPos, varLen, priorVariables);
  1135.                                 change = change + varLen - (endPos - startPos);
  1136.                                 pos += change;
  1137.                                 bufEnd += change;
  1138.                                 lengthChange += change;
  1139.                                 chars = buf.buffer; // in case buffer was altered
  1140.                             }
  1141.                             // remove variable from the cyclic stack
  1142.                             priorVariables.remove(priorVariables.size() - 1);
  1143.                             break;
  1144.                         }
  1145.                         nestedVarCount--;
  1146.                         pos += endMatchLen;
  1147.                     }
  1148.                 }
  1149.             }
  1150.         }
  1151.         if (top) {
  1152.             return altered ? 1 : 0;
  1153.         }
  1154.         return lengthChange;
  1155.     }
  1156. }