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