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