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