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