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