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