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