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.  *      http://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 <b>true</b>.
  135.  * </p>
  136.  * <p>
  137.  * This class is <b>not</b> thread safe.
  138.  * </p>
  139.  *
  140.  * @since 2.2
  141.  * @deprecated As of 3.6, 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> instead
  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,
  291.                               final char escape) {
  292.         this(StrLookup.mapLookup(valueMap), prefix, suffix, escape);
  293.     }

  294.     /**
  295.      * Creates a new instance and initializes it.
  296.      *
  297.      * @param <V> the type of the values in the map
  298.      * @param valueMap  the map with the variables' values, may be null
  299.      * @param prefix  the prefix for variables, not null
  300.      * @param suffix  the suffix for variables, not null
  301.      * @param escape  the escape character
  302.      * @param valueDelimiter  the variable default value delimiter, may be null
  303.      * @throws IllegalArgumentException if the prefix or suffix is null
  304.      * @since 3.2
  305.      */
  306.     public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix,
  307.                               final char escape, final String valueDelimiter) {
  308.         this(StrLookup.mapLookup(valueMap), prefix, suffix, escape, valueDelimiter);
  309.     }

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

  318.     /**
  319.      * Creates a new instance and initializes it.
  320.      *
  321.      * @param variableResolver  the variable resolver, may be null
  322.      * @param prefix  the prefix for variables, not null
  323.      * @param suffix  the suffix for variables, not null
  324.      * @param escape  the escape character
  325.      * @throws IllegalArgumentException if the prefix or suffix is null
  326.      */
  327.     public StrSubstitutor(final StrLookup<?> variableResolver, final String prefix, final String suffix,
  328.                           final char escape) {
  329.         this.setVariableResolver(variableResolver);
  330.         this.setVariablePrefix(prefix);
  331.         this.setVariableSuffix(suffix);
  332.         this.setEscapeChar(escape);
  333.         this.setValueDelimiterMatcher(DEFAULT_VALUE_DELIMITER);
  334.     }

  335.     /**
  336.      * Creates a new instance and initializes it.
  337.      *
  338.      * @param variableResolver  the variable resolver, may be null
  339.      * @param prefix  the prefix for variables, not null
  340.      * @param suffix  the suffix for variables, not null
  341.      * @param escape  the escape character
  342.      * @param valueDelimiter  the variable default value delimiter string, may be null
  343.      * @throws IllegalArgumentException if the prefix or suffix is null
  344.      * @since 3.2
  345.      */
  346.     public StrSubstitutor(final StrLookup<?> variableResolver, final String prefix, final String suffix,
  347.                           final char escape, final String valueDelimiter) {
  348.         this.setVariableResolver(variableResolver);
  349.         this.setVariablePrefix(prefix);
  350.         this.setVariableSuffix(suffix);
  351.         this.setEscapeChar(escape);
  352.         this.setValueDelimiter(valueDelimiter);
  353.     }

  354.     /**
  355.      * Creates a new instance and initializes it.
  356.      *
  357.      * @param variableResolver  the variable resolver, may be null
  358.      * @param prefixMatcher  the prefix for variables, not null
  359.      * @param suffixMatcher  the suffix for variables, not null
  360.      * @param escape  the escape character
  361.      * @throws IllegalArgumentException if the prefix or suffix is null
  362.      */
  363.     public StrSubstitutor(
  364.             final StrLookup<?> variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher,
  365.             final char escape) {
  366.         this(variableResolver, prefixMatcher, suffixMatcher, escape, DEFAULT_VALUE_DELIMITER);
  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.      * @param valueDelimiterMatcher  the variable default value delimiter matcher, may be null
  376.      * @throws IllegalArgumentException if the prefix or suffix is null
  377.      * @since 3.2
  378.      */
  379.     public StrSubstitutor(
  380.             final StrLookup<?> variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher,
  381.             final char escape, final StrMatcher valueDelimiterMatcher) {
  382.         this.setVariableResolver(variableResolver);
  383.         this.setVariablePrefixMatcher(prefixMatcher);
  384.         this.setVariableSuffixMatcher(suffixMatcher);
  385.         this.setEscapeChar(escape);
  386.         this.setValueDelimiterMatcher(valueDelimiterMatcher);
  387.     }

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

  405.     /**
  406.      * Returns the escape character.
  407.      *
  408.      * @return the character used for escaping variable references
  409.      */
  410.     public char getEscapeChar() {
  411.         return this.escapeChar;
  412.     }

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

  430.     /**
  431.      * Gets the variable prefix matcher currently in use.
  432.      * <p>
  433.      * The variable prefix is the character or characters that identify the
  434.      * start of a variable. This prefix is expressed in terms of a matcher
  435.      * allowing advanced prefix matches.
  436.      * </p>
  437.      *
  438.      * @return the prefix matcher in use
  439.      */
  440.     public StrMatcher getVariablePrefixMatcher() {
  441.         return prefixMatcher;
  442.     }

  443.     /**
  444.      * Gets the VariableResolver that is used to lookup variables.
  445.      *
  446.      * @return the VariableResolver
  447.      */
  448.     public StrLookup<?> getVariableResolver() {
  449.         return this.variableResolver;
  450.     }

  451.     /**
  452.      * Gets the variable suffix matcher currently in use.
  453.      * <p>
  454.      * The variable suffix is the character or characters that identify the
  455.      * end of a variable. This suffix is expressed in terms of a matcher
  456.      * allowing advanced suffix matches.
  457.      * </p>
  458.      *
  459.      * @return the suffix matcher in use
  460.      */
  461.     public StrMatcher getVariableSuffixMatcher() {
  462.         return suffixMatcher;
  463.     }

  464.     /**
  465.      * Returns a flag whether substitution is done in variable names.
  466.      *
  467.      * @return the substitution in variable names flag
  468.      * @since 3.0
  469.      */
  470.     public boolean isEnableSubstitutionInVariables() {
  471.         return enableSubstitutionInVariables;
  472.     }

  473.     /**
  474.      * Returns the flag controlling whether escapes are preserved during
  475.      * substitution.
  476.      *
  477.      * @return the preserve escape flag
  478.      * @since 3.5
  479.      */
  480.     public boolean isPreserveEscapes() {
  481.         return preserveEscapes;
  482.     }

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

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

  521.     /**
  522.      * Replaces all the occurrences of variables with their matching values
  523.      * from the resolver using the given source as a template.
  524.      * The source is not altered by this method.
  525.      *
  526.      * @param source  the buffer to use as a template, not changed, null returns null
  527.      * @return the result of the replace operation
  528.      * @since 3.2
  529.      */
  530.     public String replace(final CharSequence source) {
  531.         if (source == null) {
  532.             return null;
  533.         }
  534.         return replace(source, 0, source.length());
  535.     }

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

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

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

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

  613.     /**
  614.      * Replaces all the occurrences of variables with their matching values
  615.      * from the resolver using the given source string as a template.
  616.      *
  617.      * @param source  the string to replace in, null returns null
  618.      * @return the result of the replace operation
  619.      */
  620.     public String replace(final String source) {
  621.         if (source == null) {
  622.             return null;
  623.         }
  624.         final StrBuilder buf = new StrBuilder(source);
  625.         if (!substitute(buf, 0, source.length())) {
  626.             return source;
  627.         }
  628.         return buf.toString();
  629.     }

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

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

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

  691.     /**
  692.      * Replaces all the occurrences of variables within the given source
  693.      * builder with their matching values from the resolver.
  694.      *
  695.      * @param source  the builder to replace in, updated, null returns zero
  696.      * @return true if altered
  697.      */
  698.     public boolean replaceIn(final StrBuilder source) {
  699.         if (source == null) {
  700.             return false;
  701.         }
  702.         return substitute(source, 0, source.length());
  703.     }

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

  723.     /**
  724.      * Replaces all the occurrences of variables within the given source buffer
  725.      * with their matching values from the resolver.
  726.      * The buffer is updated with the result.
  727.      *
  728.      * @param source  the buffer to replace in, updated, null returns zero
  729.      * @return true if altered
  730.      */
  731.     public boolean replaceIn(final StringBuffer source) {
  732.         if (source == null) {
  733.             return false;
  734.         }
  735.         return replaceIn(source, 0, source.length());
  736.     }

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

  762.     /**
  763.      * Replaces all the occurrences of variables within the given source buffer
  764.      * with their matching values from the resolver.
  765.      * The buffer is updated with the result.
  766.      *
  767.      * @param source  the buffer to replace in, updated, null returns zero
  768.      * @return true if altered
  769.      * @since 3.2
  770.      */
  771.     public boolean replaceIn(final StringBuilder source) {
  772.         if (source == null) {
  773.             return false;
  774.         }
  775.         return replaceIn(source, 0, source.length());
  776.     }

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

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

  829.     /**
  830.      * Sets a flag whether substitution is done in variable names. If set to
  831.      * <b>true</b>, the names of variables can contain other variables which are
  832.      * processed first before the original variable is evaluated, e.g.
  833.      * {@code ${jre-${java.version}}}. The default value is <b>false</b>.
  834.      *
  835.      * @param enableSubstitutionInVariables the new value of the flag
  836.      * @since 3.0
  837.      */
  838.     public void setEnableSubstitutionInVariables(
  839.             final boolean enableSubstitutionInVariables) {
  840.         this.enableSubstitutionInVariables = enableSubstitutionInVariables;
  841.     }

  842.     /**
  843.      * Sets the escape character.
  844.      * If this character is placed before a variable reference in the source
  845.      * text, this variable will be ignored.
  846.      *
  847.      * @param escapeCharacter  the escape character (0 for disabling escaping)
  848.      */
  849.     public void setEscapeChar(final char escapeCharacter) {
  850.         this.escapeChar = escapeCharacter;
  851.     }

  852.     /**
  853.      * Sets a flag controlling whether escapes are preserved during
  854.      * substitution.  If set to <b>true</b>, the escape character is retained
  855.      * during substitution (e.g. {@code $${this-is-escaped}} remains
  856.      * {@code $${this-is-escaped}}).  If set to <b>false</b>, the escape
  857.      * character is removed during substitution (e.g.
  858.      * {@code $${this-is-escaped}} becomes
  859.      * {@code ${this-is-escaped}}).  The default value is <b>false</b>
  860.      *
  861.      * @param preserveEscapes true if escapes are to be preserved
  862.      * @since 3.5
  863.      */
  864.     public void setPreserveEscapes(final boolean preserveEscapes) {
  865.         this.preserveEscapes = preserveEscapes;
  866.     }

  867.     /**
  868.      * Sets the variable default value delimiter to use.
  869.      * <p>
  870.      * The variable default value delimiter is the character or characters that delimit the
  871.      * variable name and the variable default value. This method allows a single character
  872.      * variable default value delimiter to be easily set.
  873.      * </p>
  874.      *
  875.      * @param valueDelimiter  the variable default value delimiter character to use
  876.      * @return this, to enable chaining
  877.      * @since 3.2
  878.      */
  879.     public StrSubstitutor setValueDelimiter(final char valueDelimiter) {
  880.         return setValueDelimiterMatcher(StrMatcher.charMatcher(valueDelimiter));
  881.     }

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

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

  925.     /**
  926.      * Sets the variable prefix to use.
  927.      * <p>
  928.      * The variable prefix is the character or characters that identify the
  929.      * start of a variable. This method allows a single character prefix to
  930.      * be easily set.
  931.      * </p>
  932.      *
  933.      * @param prefix  the prefix character to use
  934.      * @return this, to enable chaining
  935.      */
  936.     public StrSubstitutor setVariablePrefix(final char prefix) {
  937.         return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix));
  938.     }

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

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

  969.     /**
  970.      * Sets the VariableResolver that is used to lookup variables.
  971.      *
  972.      * @param variableResolver  the VariableResolver
  973.      */
  974.     public void setVariableResolver(final StrLookup<?> variableResolver) {
  975.         this.variableResolver = variableResolver;
  976.     }

  977.     /**
  978.      * Sets the variable suffix to use.
  979.      * <p>
  980.      * The variable suffix is the character or characters that identify the
  981.      * end of a variable. This method allows a single character suffix to
  982.      * be easily set.
  983.      * </p>
  984.      *
  985.      * @param suffix  the suffix character to use
  986.      * @return this, to enable chaining
  987.      */
  988.     public StrSubstitutor setVariableSuffix(final char suffix) {
  989.         return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix));
  990.     }

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

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

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

  1040.     /**
  1041.      * Recursive handler for multiple levels of interpolation. This is the main
  1042.      * interpolation method, which resolves the values of all variable references
  1043.      * contained in the passed-in text.
  1044.      *
  1045.      * @param buf  the string builder to substitute into, not null
  1046.      * @param offset  the start offset within the builder, must be valid
  1047.      * @param length  the length within the builder to be processed, must be valid
  1048.      * @param priorVariables  the stack keeping track of the replaced variables, may be null
  1049.      * @return the length change that occurs, unless priorVariables is null when the int
  1050.      *  represents a boolean flag as to whether any change occurred.
  1051.      */
  1052.     private int substitute(final StrBuilder buf, final int offset, final int length, List<String> priorVariables) {
  1053.         final StrMatcher pfxMatcher = getVariablePrefixMatcher();
  1054.         final StrMatcher suffMatcher = getVariableSuffixMatcher();
  1055.         final char escape = getEscapeChar();
  1056.         final StrMatcher valueDelimMatcher = getValueDelimiterMatcher();
  1057.         final boolean substitutionInVariablesEnabled = isEnableSubstitutionInVariables();

  1058.         final boolean top = priorVariables == null;
  1059.         boolean altered = false;
  1060.         int lengthChange = 0;
  1061.         char[] chars = buf.buffer;
  1062.         int bufEnd = offset + length;
  1063.         int pos = offset;
  1064.         while (pos < bufEnd) {
  1065.             final int startMatchLen = pfxMatcher.isMatch(chars, pos, offset,
  1066.                     bufEnd);
  1067.             if (startMatchLen == 0) {
  1068.                 pos++;
  1069.             } else // found variable start marker
  1070.             if (pos > offset && chars[pos - 1] == escape) {
  1071.                 // escaped
  1072.                 if (preserveEscapes) {
  1073.                     pos++;
  1074.                     continue;
  1075.                 }
  1076.                 buf.deleteCharAt(pos - 1);
  1077.                 chars = buf.buffer; // in case buffer was altered
  1078.                 lengthChange--;
  1079.                 altered = true;
  1080.                 bufEnd--;
  1081.             } else {
  1082.                 // find suffix
  1083.                 final int startPos = pos;
  1084.                 pos += startMatchLen;
  1085.                 int endMatchLen;
  1086.                 int nestedVarCount = 0;
  1087.                 while (pos < bufEnd) {
  1088.                     if (substitutionInVariablesEnabled
  1089.                             && (endMatchLen = pfxMatcher.isMatch(chars,
  1090.                                     pos, offset, bufEnd)) != 0) {
  1091.                         // found a nested variable start
  1092.                         nestedVarCount++;
  1093.                         pos += endMatchLen;
  1094.                         continue;
  1095.                     }

  1096.                     endMatchLen = suffMatcher.isMatch(chars, pos, offset,
  1097.                             bufEnd);
  1098.                     if (endMatchLen == 0) {
  1099.                         pos++;
  1100.                     } else {
  1101.                         // found variable end marker
  1102.                         if (nestedVarCount == 0) {
  1103.                             String varNameExpr = new String(chars, startPos
  1104.                                     + startMatchLen, pos - startPos
  1105.                                     - startMatchLen);
  1106.                             if (substitutionInVariablesEnabled) {
  1107.                                 final StrBuilder bufName = new StrBuilder(varNameExpr);
  1108.                                 substitute(bufName, 0, bufName.length());
  1109.                                 varNameExpr = bufName.toString();
  1110.                             }
  1111.                             pos += endMatchLen;
  1112.                             final int endPos = pos;

  1113.                             String varName = varNameExpr;
  1114.                             String varDefaultValue = null;

  1115.                             if (valueDelimMatcher != null) {
  1116.                                 final char [] varNameExprChars = varNameExpr.toCharArray();
  1117.                                 int valueDelimiterMatchLen;
  1118.                                 for (int i = 0; i < varNameExprChars.length; i++) {
  1119.                                     // if there's any nested variable when nested variable substitution disabled, then stop resolving name and default value.
  1120.                                     if (!substitutionInVariablesEnabled
  1121.                                             && pfxMatcher.isMatch(varNameExprChars, i, i, varNameExprChars.length) != 0) {
  1122.                                         break;
  1123.                                     }
  1124.                                     if ((valueDelimiterMatchLen = valueDelimMatcher.isMatch(varNameExprChars, i)) != 0) {
  1125.                                         varName = varNameExpr.substring(0, i);
  1126.                                         varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen);
  1127.                                         break;
  1128.                                     }
  1129.                                 }
  1130.                             }

  1131.                             // on the first call initialize priorVariables
  1132.                             if (priorVariables == null) {
  1133.                                 priorVariables = new ArrayList<>();
  1134.                                 priorVariables.add(new String(chars,
  1135.                                         offset, length));
  1136.                             }

  1137.                             // handle cyclic substitution
  1138.                             checkCyclicSubstitution(varName, priorVariables);
  1139.                             priorVariables.add(varName);

  1140.                             // resolve the variable
  1141.                             String varValue = resolveVariable(varName, buf,
  1142.                                     startPos, endPos);
  1143.                             if (varValue == null) {
  1144.                                 varValue = varDefaultValue;
  1145.                             }
  1146.                             if (varValue != null) {
  1147.                                 // recursive replace
  1148.                                 final int varLen = varValue.length();
  1149.                                 buf.replace(startPos, endPos, varValue);
  1150.                                 altered = true;
  1151.                                 int change = substitute(buf, startPos,
  1152.                                         varLen, priorVariables);
  1153.                                 change = change
  1154.                                         + varLen - (endPos - startPos);
  1155.                                 pos += change;
  1156.                                 bufEnd += change;
  1157.                                 lengthChange += change;
  1158.                                 chars = buf.buffer; // in case buffer was
  1159.                                                     // altered
  1160.                             }

  1161.                             // remove variable from the cyclic stack
  1162.                             priorVariables
  1163.                                     .remove(priorVariables.size() - 1);
  1164.                             break;
  1165.                         }
  1166.                         nestedVarCount--;
  1167.                         pos += endMatchLen;
  1168.                     }
  1169.                 }
  1170.             }
  1171.         }
  1172.         if (top) {
  1173.             return altered ? 1 : 0;
  1174.         }
  1175.         return lengthChange;
  1176.     }
  1177. }