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