001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.lang3.text; 018 019import java.util.ArrayList; 020import java.util.Enumeration; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Objects; 025import java.util.Properties; 026 027import org.apache.commons.lang3.StringUtils; 028 029/** 030 * Substitutes variables within a string by values. 031 * <p> 032 * This class takes a piece of text and substitutes all the variables within it. 033 * The default definition of a variable is {@code ${variableName}}. 034 * The prefix and suffix can be changed via constructors and set methods. 035 * </p> 036 * <p> 037 * Variable values are typically resolved from a map, but could also be resolved 038 * from system properties, or by supplying a custom variable resolver. 039 * </p> 040 * <p> 041 * The simplest example is to use this class to replace Java System properties. For example: 042 * </p> 043 * <pre> 044 * StrSubstitutor.replaceSystemProperties( 045 * "You are running with java.version = ${java.version} and os.name = ${os.name}."); 046 * </pre> 047 * <p> 048 * Typical usage of this class follows the following pattern: First an instance is created 049 * and initialized with the map that contains the values for the available variables. 050 * If a prefix and/or suffix for variables should be used other than the default ones, 051 * the appropriate settings can be performed. After that the {@code replace()} 052 * method can be called passing in the source text for interpolation. In the returned 053 * text all variable references (as long as their values are known) will be resolved. 054 * The following example demonstrates this: 055 * </p> 056 * <pre> 057 * Map valuesMap = HashMap(); 058 * valuesMap.put("animal", "quick brown fox"); 059 * valuesMap.put("target", "lazy dog"); 060 * String templateString = "The ${animal} jumps over the ${target}."; 061 * StrSubstitutor sub = new StrSubstitutor(valuesMap); 062 * String resolvedString = sub.replace(templateString); 063 * </pre> 064 * yielding: 065 * <pre> 066 * The quick brown fox jumps over the lazy dog. 067 * </pre> 068 * <p> 069 * Also, this class allows to set a default value for unresolved variables. 070 * The default value for a variable can be appended to the variable name after the variable 071 * default value delimiter. The default value of the variable default value delimiter is ':-', 072 * as in bash and other *nix shells, as those are arguably where the default ${} delimiter set originated. 073 * The variable default value delimiter can be manually set by calling {@link #setValueDelimiterMatcher(StrMatcher)}, 074 * {@link #setValueDelimiter(char)} or {@link #setValueDelimiter(String)}. 075 * The following shows an example with variable default value settings: 076 * </p> 077 * <pre> 078 * Map valuesMap = HashMap(); 079 * valuesMap.put("animal", "quick brown fox"); 080 * valuesMap.put("target", "lazy dog"); 081 * String templateString = "The ${animal} jumps over the ${target}. ${undefined.number:-1234567890}."; 082 * StrSubstitutor sub = new StrSubstitutor(valuesMap); 083 * String resolvedString = sub.replace(templateString); 084 * </pre> 085 * <p> 086 * yielding: 087 * </p> 088 * <pre> 089 * The quick brown fox jumps over the lazy dog. 1234567890. 090 * </pre> 091 * <p> 092 * In addition to this usage pattern there are some static convenience methods that 093 * cover the most common use cases. These methods can be used without the need of 094 * manually creating an instance. However if multiple replace operations are to be 095 * performed, creating and reusing an instance of this class will be more efficient. 096 * </p> 097 * <p> 098 * Variable replacement works in a recursive way. Thus, if a variable value contains 099 * 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 <strong>true</strong>. 138 * </p> 139 * <p> 140 * This class is <strong>not</strong> thread safe. 141 * </p> 142 * 143 * @since 2.2 144 * @deprecated As of <a href="https://commons.apache.org/proper/commons-lang/changes-report.html#a3.6">3.6</a>, 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>. 147 */ 148@Deprecated 149public 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, final char escape) { 304 this(StrLookup.mapLookup(valueMap), prefix, suffix, escape); 305 } 306 307 /** 308 * Creates a new instance and initializes it. 309 * 310 * @param <V> the type of the values in the map 311 * @param valueMap the map with the variables' values, may be null 312 * @param prefix the prefix for variables, not null 313 * @param suffix the suffix for variables, not null 314 * @param escape the escape character 315 * @param valueDelimiter the variable default value delimiter, may be null 316 * @throws IllegalArgumentException if the prefix or suffix is null 317 * @since 3.2 318 */ 319 public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix, final char escape, final String valueDelimiter) { 320 this(StrLookup.mapLookup(valueMap), prefix, suffix, escape, valueDelimiter); 321 } 322 323 /** 324 * Creates a new instance and initializes it. 325 * 326 * @param variableResolver the variable resolver, may be null 327 */ 328 public StrSubstitutor(final StrLookup<?> variableResolver) { 329 this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); 330 } 331 332 /** 333 * Creates a new instance and initializes it. 334 * 335 * @param variableResolver the variable resolver, may be null 336 * @param prefix the prefix for variables, not null 337 * @param suffix the suffix for variables, not null 338 * @param escape the escape character 339 * @throws IllegalArgumentException if the prefix or suffix is null 340 */ 341 public StrSubstitutor(final StrLookup<?> variableResolver, final String prefix, final String suffix, final char escape) { 342 setVariableResolver(variableResolver); 343 setVariablePrefix(prefix); 344 setVariableSuffix(suffix); 345 setEscapeChar(escape); 346 setValueDelimiterMatcher(DEFAULT_VALUE_DELIMITER); 347 } 348 349 /** 350 * Creates a new instance and initializes it. 351 * 352 * @param variableResolver the variable resolver, may be null 353 * @param prefix the prefix for variables, not null 354 * @param suffix the suffix for variables, not null 355 * @param escape the escape character 356 * @param valueDelimiter the variable default value delimiter string, may be null 357 * @throws IllegalArgumentException if the prefix or suffix is null 358 * @since 3.2 359 */ 360 public StrSubstitutor(final StrLookup<?> variableResolver, final String prefix, final String suffix, final char escape, final String valueDelimiter) { 361 setVariableResolver(variableResolver); 362 setVariablePrefix(prefix); 363 setVariableSuffix(suffix); 364 setEscapeChar(escape); 365 setValueDelimiter(valueDelimiter); 366 } 367 368 /** 369 * Creates a new instance and initializes it. 370 * 371 * @param variableResolver the variable resolver, may be null 372 * @param prefixMatcher the prefix for variables, not null 373 * @param suffixMatcher the suffix for variables, not null 374 * @param escape the escape character 375 * @throws IllegalArgumentException if the prefix or suffix is null 376 */ 377 public StrSubstitutor(final StrLookup<?> variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher, final char escape) { 378 this(variableResolver, prefixMatcher, suffixMatcher, escape, DEFAULT_VALUE_DELIMITER); 379 } 380 381 /** 382 * Creates a new instance and initializes it. 383 * 384 * @param variableResolver the variable resolver, may be null 385 * @param prefixMatcher the prefix for variables, not null 386 * @param suffixMatcher the suffix for variables, not null 387 * @param escape the escape character 388 * @param valueDelimiterMatcher the variable default value delimiter matcher, may be null 389 * @throws IllegalArgumentException if the prefix or suffix is null 390 * @since 3.2 391 */ 392 public StrSubstitutor(final StrLookup<?> variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher, final char escape, 393 final StrMatcher valueDelimiterMatcher) { 394 setVariableResolver(variableResolver); 395 setVariablePrefixMatcher(prefixMatcher); 396 setVariableSuffixMatcher(suffixMatcher); 397 setEscapeChar(escape); 398 setValueDelimiterMatcher(valueDelimiterMatcher); 399 } 400 401 /** 402 * Checks if the specified variable is already in the stack (list) of variables. 403 * 404 * @param varName the variable name to check 405 * @param priorVariables the list of prior variables 406 */ 407 private void checkCyclicSubstitution(final String varName, final List<String> priorVariables) { 408 if (!priorVariables.contains(varName)) { 409 return; 410 } 411 final StrBuilder buf = new StrBuilder(256); 412 buf.append("Infinite loop in property interpolation of "); 413 buf.append(priorVariables.remove(0)); 414 buf.append(": "); 415 buf.appendWithSeparators(priorVariables, "->"); 416 throw new IllegalStateException(buf.toString()); 417 } 418 419 /** 420 * Gets the escape character. 421 * 422 * @return the character used for escaping variable references 423 */ 424 public char getEscapeChar() { 425 return this.escapeChar; 426 } 427 428 /** 429 * Gets the variable default value delimiter matcher currently in use. 430 * <p> 431 * The variable default value delimiter is the character or characters that delimit the 432 * variable name and the variable default value. This delimiter is expressed in terms of a matcher 433 * allowing advanced variable default value delimiter matches. 434 * </p> 435 * <p> 436 * If it returns null, then the variable default value resolution is disabled. 437 * </p> 438 * 439 * @return the variable default value delimiter matcher in use, may be null 440 * @since 3.2 441 */ 442 public StrMatcher getValueDelimiterMatcher() { 443 return valueDelimiterMatcher; 444 } 445 446 /** 447 * Gets the variable prefix matcher currently in use. 448 * <p> 449 * The variable prefix is the character or characters that identify the 450 * start of a variable. This prefix is expressed in terms of a matcher 451 * allowing advanced prefix matches. 452 * </p> 453 * 454 * @return the prefix matcher in use 455 */ 456 public StrMatcher getVariablePrefixMatcher() { 457 return prefixMatcher; 458 } 459 460 /** 461 * Gets the VariableResolver that is used to lookup variables. 462 * 463 * @return the VariableResolver 464 */ 465 public StrLookup<?> getVariableResolver() { 466 return this.variableResolver; 467 } 468 469 /** 470 * Gets the variable suffix matcher currently in use. 471 * <p> 472 * The variable suffix is the character or characters that identify the 473 * end of a variable. This suffix is expressed in terms of a matcher 474 * allowing advanced suffix matches. 475 * </p> 476 * 477 * @return the suffix matcher in use 478 */ 479 public StrMatcher getVariableSuffixMatcher() { 480 return suffixMatcher; 481 } 482 483 /** 484 * Tests whether substitution is done in variable names. 485 * 486 * @return the substitution in variable names flag 487 * @since 3.0 488 */ 489 public boolean isEnableSubstitutionInVariables() { 490 return enableSubstitutionInVariables; 491 } 492 493 /** 494 * Tests whether escapes are preserved during substitution. 495 * 496 * @return the preserve escape flag 497 * @since 3.5 498 */ 499 public boolean isPreserveEscapes() { 500 return preserveEscapes; 501 } 502 503 /** 504 * Replaces all the occurrences of variables with their matching values 505 * from the resolver using the given source array as a template. 506 * The array is not altered by this method. 507 * 508 * @param source the character array to replace in, not altered, null returns null 509 * @return the result of the replace operation 510 */ 511 public String replace(final char[] source) { 512 if (source == null) { 513 return null; 514 } 515 final StrBuilder buf = new StrBuilder(source.length).append(source); 516 substitute(buf, 0, source.length); 517 return buf.toString(); 518 } 519 520 /** 521 * Replaces all the occurrences of variables with their matching values 522 * from the resolver using the given source array as a template. 523 * The array is not altered by this method. 524 * <p> 525 * Only the specified portion of the array will be processed. 526 * The rest of the array is not processed, and is not returned. 527 * </p> 528 * 529 * @param source the character array to replace in, not altered, null returns null 530 * @param offset the start offset within the array, must be valid 531 * @param length the length within the array to be processed, must be valid 532 * @return the result of the replace operation 533 */ 534 public String replace(final char[] source, final int offset, final int length) { 535 if (source == null) { 536 return null; 537 } 538 final StrBuilder buf = new StrBuilder(length).append(source, offset, length); 539 substitute(buf, 0, length); 540 return buf.toString(); 541 } 542 543 /** 544 * Replaces all the occurrences of variables with their matching values 545 * from the resolver using the given source as a template. 546 * The source is not altered by this method. 547 * 548 * @param source the buffer to use as a template, not changed, null returns null 549 * @return the result of the replace operation 550 * @since 3.2 551 */ 552 public String replace(final CharSequence source) { 553 if (source == null) { 554 return null; 555 } 556 return replace(source, 0, source.length()); 557 } 558 559 /** 560 * Replaces all the occurrences of variables with their matching values 561 * from the resolver using the given source as a template. 562 * The source is not altered by this method. 563 * <p> 564 * Only the specified portion of the buffer will be processed. 565 * The rest of the buffer is not processed, and is not returned. 566 * </p> 567 * 568 * @param source the buffer to use as a template, not changed, null returns null 569 * @param offset the start offset within the array, must be valid 570 * @param length the length within the array to be processed, must be valid 571 * @return the result of the replace operation 572 * @since 3.2 573 */ 574 public String replace(final CharSequence source, final int offset, final int length) { 575 if (source == null) { 576 return null; 577 } 578 final StrBuilder buf = new StrBuilder(length).append(source, offset, length); 579 substitute(buf, 0, length); 580 return buf.toString(); 581 } 582 583 /** 584 * Replaces all the occurrences of variables in the given source object with 585 * their matching values from the resolver. The input source object is 586 * converted to a string using {@code toString} and is not altered. 587 * 588 * @param source the source to replace in, null returns null 589 * @return the result of the replace operation 590 */ 591 public String replace(final Object source) { 592 if (source == null) { 593 return null; 594 } 595 final StrBuilder buf = new StrBuilder().append(source); 596 substitute(buf, 0, buf.length()); 597 return buf.toString(); 598 } 599 600 /** 601 * Replaces all the occurrences of variables with their matching values 602 * from the resolver using the given source builder as a template. 603 * The builder is not altered by this method. 604 * 605 * @param source the builder to use as a template, not changed, null returns null 606 * @return the result of the replace operation 607 */ 608 public String replace(final StrBuilder source) { 609 if (source == null) { 610 return null; 611 } 612 final StrBuilder buf = new StrBuilder(source.length()).append(source); 613 substitute(buf, 0, buf.length()); 614 return buf.toString(); 615 } 616 617 /** 618 * Replaces all the occurrences of variables with their matching values 619 * from the resolver using the given source builder as a template. 620 * The builder is not altered by this method. 621 * <p> 622 * Only the specified portion of the builder will be processed. 623 * The rest of the builder is not processed, and is not returned. 624 * </p> 625 * 626 * @param source the builder to use as a template, not changed, null returns null 627 * @param offset the start offset within the array, must be valid 628 * @param length the length within the array to be processed, must be valid 629 * @return the result of the replace operation 630 */ 631 public String replace(final StrBuilder source, final int offset, final int length) { 632 if (source == null) { 633 return null; 634 } 635 final StrBuilder buf = new StrBuilder(length).append(source, offset, length); 636 substitute(buf, 0, length); 637 return buf.toString(); 638 } 639 640 /** 641 * Replaces all the occurrences of variables with their matching values 642 * from the resolver using the given source string as a template. 643 * 644 * @param source the string to replace in, null returns null 645 * @return the result of the replace operation 646 */ 647 public String replace(final String source) { 648 if (source == null) { 649 return null; 650 } 651 final StrBuilder buf = new StrBuilder(source); 652 if (!substitute(buf, 0, source.length())) { 653 return source; 654 } 655 return buf.toString(); 656 } 657 658 /** 659 * Replaces all the occurrences of variables with their matching values 660 * from the resolver using the given source string as a template. 661 * <p> 662 * Only the specified portion of the string will be processed. 663 * The rest of the string is not processed, and is not returned. 664 * </p> 665 * 666 * @param source the string to replace in, null returns null 667 * @param offset the start offset within the array, must be valid 668 * @param length the length within the array to be processed, must be valid 669 * @return the result of the replace operation 670 */ 671 public String replace(final String source, final int offset, final int length) { 672 if (source == null) { 673 return null; 674 } 675 final StrBuilder buf = new StrBuilder(length).append(source, offset, length); 676 if (!substitute(buf, 0, length)) { 677 return source.substring(offset, offset + length); 678 } 679 return buf.toString(); 680 } 681 682 /** 683 * Replaces all the occurrences of variables with their matching values 684 * from the resolver using the given source buffer as a template. 685 * The buffer is not altered by this method. 686 * 687 * @param source the buffer to use as a template, not changed, null returns null 688 * @return the result of the replace operation 689 */ 690 public String replace(final StringBuffer source) { 691 if (source == null) { 692 return null; 693 } 694 final StrBuilder buf = new StrBuilder(source.length()).append(source); 695 substitute(buf, 0, buf.length()); 696 return buf.toString(); 697 } 698 699 /** 700 * Replaces all the occurrences of variables with their matching values 701 * from the resolver using the given source buffer as a template. 702 * The buffer is not altered by this method. 703 * <p> 704 * Only the specified portion of the buffer will be processed. 705 * The rest of the buffer is not processed, and is not returned. 706 * </p> 707 * 708 * @param source the buffer to use as a template, not changed, null returns null 709 * @param offset the start offset within the array, must be valid 710 * @param length the length within the array to be processed, must be valid 711 * @return the result of the replace operation 712 */ 713 public String replace(final StringBuffer source, final int offset, final int length) { 714 if (source == null) { 715 return null; 716 } 717 final StrBuilder buf = new StrBuilder(length).append(source, offset, length); 718 substitute(buf, 0, length); 719 return buf.toString(); 720 } 721 722 /** 723 * Replaces all the occurrences of variables within the given source 724 * builder with their matching values from the resolver. 725 * 726 * @param source the builder to replace in, updated, null returns zero 727 * @return true if altered 728 */ 729 public boolean replaceIn(final StrBuilder source) { 730 if (source == null) { 731 return false; 732 } 733 return substitute(source, 0, source.length()); 734 } 735 736 /** 737 * Replaces all the occurrences of variables within the given source 738 * builder with their matching values from the resolver. 739 * <p> 740 * Only the specified portion of the builder will be processed. 741 * The rest of the builder is not processed, but it is not deleted. 742 * </p> 743 * 744 * @param source the builder to replace in, null returns zero 745 * @param offset the start offset within the array, must be valid 746 * @param length the length within the builder to be processed, must be valid 747 * @return true if altered 748 */ 749 public boolean replaceIn(final StrBuilder source, final int offset, final int length) { 750 if (source == null) { 751 return false; 752 } 753 return substitute(source, offset, length); 754 } 755 756 /** 757 * Replaces all the occurrences of variables within the given source buffer 758 * with their matching values from the resolver. 759 * The buffer is updated with the result. 760 * 761 * @param source the buffer to replace in, updated, null returns zero 762 * @return true if altered 763 */ 764 public boolean replaceIn(final StringBuffer source) { 765 if (source == null) { 766 return false; 767 } 768 return replaceIn(source, 0, source.length()); 769 } 770 771 /** 772 * Replaces all the occurrences of variables within the given source buffer 773 * with their matching values from the resolver. 774 * The buffer is updated with the result. 775 * <p> 776 * Only the specified portion of the buffer will be processed. 777 * The rest of the buffer is not processed, but it is not deleted. 778 * </p> 779 * 780 * @param source the buffer to replace in, updated, null returns zero 781 * @param offset the start offset within the array, must be valid 782 * @param length the length within the buffer to be processed, must be valid 783 * @return true if altered 784 */ 785 public boolean replaceIn(final StringBuffer source, final int offset, final int length) { 786 if (source == null) { 787 return false; 788 } 789 final StrBuilder buf = new StrBuilder(length).append(source, offset, length); 790 if (!substitute(buf, 0, length)) { 791 return false; 792 } 793 source.replace(offset, offset + length, buf.toString()); 794 return true; 795 } 796 797 /** 798 * Replaces all the occurrences of variables within the given source buffer 799 * with their matching values from the resolver. 800 * The buffer is updated with the result. 801 * 802 * @param source the buffer to replace in, updated, null returns zero 803 * @return true if altered 804 * @since 3.2 805 */ 806 public boolean replaceIn(final StringBuilder source) { 807 if (source == null) { 808 return false; 809 } 810 return replaceIn(source, 0, source.length()); 811 } 812 813 /** 814 * Replaces all the occurrences of variables within the given source builder 815 * with their matching values from the resolver. 816 * The builder is updated with the result. 817 * <p> 818 * Only the specified portion of the buffer will be processed. 819 * The rest of the buffer is not processed, but it is not deleted. 820 * </p> 821 * 822 * @param source the buffer to replace in, updated, null returns zero 823 * @param offset the start offset within the array, must be valid 824 * @param length the length within the buffer to be processed, must be valid 825 * @return true if altered 826 * @since 3.2 827 */ 828 public boolean replaceIn(final StringBuilder source, final int offset, final int length) { 829 if (source == null) { 830 return false; 831 } 832 final StrBuilder buf = new StrBuilder(length).append(source, offset, length); 833 if (!substitute(buf, 0, length)) { 834 return false; 835 } 836 source.replace(offset, offset + length, buf.toString()); 837 return true; 838 } 839 840 /** 841 * Internal method that resolves the value of a variable. 842 * <p> 843 * Most users of this class do not need to call this method. This method is 844 * called automatically by the substitution process. 845 * </p> 846 * <p> 847 * Writers of subclasses can override this method if they need to alter 848 * how each substitution occurs. The method is passed the variable's name 849 * and must return the corresponding value. This implementation uses the 850 * {@link #getVariableResolver()} with the variable's name as the key. 851 * </p> 852 * 853 * @param variableName the name of the variable, not null 854 * @param buf the buffer where the substitution is occurring, not null 855 * @param startPos the start position of the variable including the prefix, valid 856 * @param endPos the end position of the variable including the suffix, valid 857 * @return the variable's value or <strong>null</strong> if the variable is unknown 858 */ 859 protected String resolveVariable(final String variableName, final StrBuilder buf, final int startPos, final int endPos) { 860 final StrLookup<?> resolver = getVariableResolver(); 861 if (resolver == null) { 862 return null; 863 } 864 return resolver.lookup(variableName); 865 } 866 867 /** 868 * Sets a flag whether substitution is done in variable names. If set to 869 * <strong>true</strong>, the names of variables can contain other variables which are 870 * processed first before the original variable is evaluated, e.g. 871 * {@code ${jre-${java.version}}}. The default value is <strong>false</strong>. 872 * 873 * @param enableSubstitutionInVariables the new value of the flag 874 * @since 3.0 875 */ 876 public void setEnableSubstitutionInVariables( 877 final boolean enableSubstitutionInVariables) { 878 this.enableSubstitutionInVariables = enableSubstitutionInVariables; 879 } 880 881 /** 882 * Sets the escape character. 883 * If this character is placed before a variable reference in the source 884 * text, this variable will be ignored. 885 * 886 * @param escapeCharacter the escape character (0 for disabling escaping) 887 */ 888 public void setEscapeChar(final char escapeCharacter) { 889 this.escapeChar = escapeCharacter; 890 } 891 892 /** 893 * Sets a flag controlling whether escapes are preserved during 894 * substitution. If set to <strong>true</strong>, the escape character is retained 895 * during substitution (e.g. {@code $${this-is-escaped}} remains 896 * {@code $${this-is-escaped}}). If set to <strong>false</strong>, the escape 897 * character is removed during substitution (e.g. 898 * {@code $${this-is-escaped}} becomes 899 * {@code ${this-is-escaped}}). The default value is <strong>false</strong> 900 * 901 * @param preserveEscapes true if escapes are to be preserved 902 * @since 3.5 903 */ 904 public void setPreserveEscapes(final boolean preserveEscapes) { 905 this.preserveEscapes = preserveEscapes; 906 } 907 908 /** 909 * Sets the variable default value delimiter to use. 910 * <p> 911 * The variable default value delimiter is the character or characters that delimit the 912 * variable name and the variable default value. This method allows a single character 913 * variable default value delimiter to be easily set. 914 * </p> 915 * 916 * @param valueDelimiter the variable default value delimiter character to use 917 * @return this, to enable chaining 918 * @since 3.2 919 */ 920 public StrSubstitutor setValueDelimiter(final char valueDelimiter) { 921 return setValueDelimiterMatcher(StrMatcher.charMatcher(valueDelimiter)); 922 } 923 924 /** 925 * Sets the variable default value delimiter to use. 926 * <p> 927 * The variable default value delimiter is the character or characters that delimit the 928 * variable name and the variable default value. This method allows a string 929 * variable default value delimiter to be easily set. 930 * </p> 931 * <p> 932 * If the {@code valueDelimiter} is null or empty string, then the variable default 933 * value resolution becomes disabled. 934 * </p> 935 * 936 * @param valueDelimiter the variable default value delimiter string to use, may be null or empty 937 * @return this, to enable chaining 938 * @since 3.2 939 */ 940 public StrSubstitutor setValueDelimiter(final String valueDelimiter) { 941 if (StringUtils.isEmpty(valueDelimiter)) { 942 setValueDelimiterMatcher(null); 943 return this; 944 } 945 return setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter)); 946 } 947 948 /** 949 * Sets the variable default value delimiter matcher to use. 950 * <p> 951 * The variable default value delimiter is the character or characters that delimit the 952 * variable name and the variable default value. This delimiter is expressed in terms of a matcher 953 * allowing advanced variable default value delimiter matches. 954 * </p> 955 * <p> 956 * If the {@code valueDelimiterMatcher} is null, then the variable default value resolution 957 * becomes disabled. 958 * </p> 959 * 960 * @param valueDelimiterMatcher variable default value delimiter matcher to use, may be null 961 * @return this, to enable chaining 962 * @since 3.2 963 */ 964 public StrSubstitutor setValueDelimiterMatcher(final StrMatcher valueDelimiterMatcher) { 965 this.valueDelimiterMatcher = valueDelimiterMatcher; 966 return this; 967 } 968 969 /** 970 * Sets the variable prefix to use. 971 * <p> 972 * The variable prefix is the character or characters that identify the 973 * start of a variable. This method allows a single character prefix to 974 * be easily set. 975 * </p> 976 * 977 * @param prefix the prefix character to use 978 * @return this, to enable chaining 979 */ 980 public StrSubstitutor setVariablePrefix(final char prefix) { 981 return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix)); 982 } 983 984 /** 985 * Sets the variable prefix to use. 986 * <p> 987 * The variable prefix is the character or characters that identify the 988 * start of a variable. This method allows a string prefix to be easily set. 989 * </p> 990 * 991 * @param prefix the prefix for variables, not null 992 * @return this, to enable chaining 993 * @throws NullPointerException if the prefix is null 994 */ 995 public StrSubstitutor setVariablePrefix(final String prefix) { 996 return setVariablePrefixMatcher(StrMatcher.stringMatcher(Objects.requireNonNull(prefix))); 997 } 998 999 /** 1000 * Sets the variable prefix matcher currently in use. 1001 * <p> 1002 * The variable prefix is the character or characters that identify the 1003 * start of a variable. This prefix is expressed in terms of a matcher 1004 * allowing advanced prefix matches. 1005 * </p> 1006 * 1007 * @param prefixMatcher the prefix matcher to use, null ignored 1008 * @return this, to enable chaining 1009 * @throws NullPointerException if the prefix matcher is null 1010 */ 1011 public StrSubstitutor setVariablePrefixMatcher(final StrMatcher prefixMatcher) { 1012 this.prefixMatcher = Objects.requireNonNull(prefixMatcher, "prefixMatcher"); 1013 return this; 1014 } 1015 1016 /** 1017 * Sets the VariableResolver that is used to lookup variables. 1018 * 1019 * @param variableResolver the VariableResolver 1020 */ 1021 public void setVariableResolver(final StrLookup<?> variableResolver) { 1022 this.variableResolver = variableResolver; 1023 } 1024 1025 /** 1026 * Sets the variable suffix to use. 1027 * <p> 1028 * The variable suffix is the character or characters that identify the 1029 * end of a variable. This method allows a single character suffix to 1030 * be easily set. 1031 * </p> 1032 * 1033 * @param suffix the suffix character to use 1034 * @return this, to enable chaining 1035 */ 1036 public StrSubstitutor setVariableSuffix(final char suffix) { 1037 return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix)); 1038 } 1039 1040 /** 1041 * Sets the variable suffix to use. 1042 * <p> 1043 * The variable suffix is the character or characters that identify the 1044 * end of a variable. This method allows a string suffix to be easily set. 1045 * </p> 1046 * 1047 * @param suffix the suffix for variables, not null 1048 * @return this, to enable chaining 1049 * @throws NullPointerException if the suffix is null 1050 */ 1051 public StrSubstitutor setVariableSuffix(final String suffix) { 1052 return setVariableSuffixMatcher(StrMatcher.stringMatcher(Objects.requireNonNull(suffix))); 1053 } 1054 1055 /** 1056 * Sets the variable suffix matcher currently in use. 1057 * <p> 1058 * The variable suffix is the character or characters that identify the 1059 * end of a variable. This suffix is expressed in terms of a matcher 1060 * allowing advanced suffix matches. 1061 * </p> 1062 * 1063 * @param suffixMatcher the suffix matcher to use, null ignored 1064 * @return this, to enable chaining 1065 * @throws NullPointerException if the suffix matcher is null 1066 */ 1067 public StrSubstitutor setVariableSuffixMatcher(final StrMatcher suffixMatcher) { 1068 this.suffixMatcher = Objects.requireNonNull(suffixMatcher); 1069 return this; 1070 } 1071 1072 /** 1073 * Internal method that substitutes the variables. 1074 * <p> 1075 * Most users of this class do not need to call this method. This method will 1076 * be called automatically by another (public) method. 1077 * </p> 1078 * <p> 1079 * Writers of subclasses can override this method if they need access to 1080 * the substitution process at the start or end. 1081 * </p> 1082 * 1083 * @param buf the string builder to substitute into, not null 1084 * @param offset the start offset within the builder, must be valid 1085 * @param length the length within the builder to be processed, must be valid 1086 * @return true if altered 1087 */ 1088 protected boolean substitute(final StrBuilder buf, final int offset, final int length) { 1089 return substitute(buf, offset, length, null) > 0; 1090 } 1091 1092 /** 1093 * Recursive handler for multiple levels of interpolation. This is the main 1094 * interpolation method, which resolves the values of all variable references 1095 * contained in the passed-in text. 1096 * 1097 * @param buf the string builder to substitute into, not null 1098 * @param offset the start offset within the builder, must be valid 1099 * @param length the length within the builder to be processed, must be valid 1100 * @param priorVariables the stack keeping track of the replaced variables, may be null 1101 * @return the length change that occurs, unless priorVariables is null when the int 1102 * represents a boolean flag as to whether any change occurred. 1103 */ 1104 private int substitute(final StrBuilder buf, final int offset, final int length, List<String> priorVariables) { 1105 final StrMatcher pfxMatcher = getVariablePrefixMatcher(); 1106 final StrMatcher suffMatcher = getVariableSuffixMatcher(); 1107 final char escape = getEscapeChar(); 1108 final StrMatcher valueDelimMatcher = getValueDelimiterMatcher(); 1109 final boolean substitutionInVariablesEnabled = isEnableSubstitutionInVariables(); 1110 final boolean top = priorVariables == null; 1111 boolean altered = false; 1112 int lengthChange = 0; 1113 char[] chars = buf.buffer; 1114 int bufEnd = offset + length; 1115 int pos = offset; 1116 while (pos < bufEnd) { 1117 final int startMatchLen = pfxMatcher.isMatch(chars, pos, offset, bufEnd); 1118 if (startMatchLen == 0) { 1119 pos++; 1120 } else // found variable start marker 1121 if (pos > offset && chars[pos - 1] == escape) { 1122 // escaped 1123 if (preserveEscapes) { 1124 pos++; 1125 continue; 1126 } 1127 buf.deleteCharAt(pos - 1); 1128 chars = buf.buffer; // in case buffer was altered 1129 lengthChange--; 1130 altered = true; 1131 bufEnd--; 1132 } else { 1133 // find suffix 1134 final int startPos = pos; 1135 pos += startMatchLen; 1136 int endMatchLen; 1137 int nestedVarCount = 0; 1138 while (pos < bufEnd) { 1139 if (substitutionInVariablesEnabled && (endMatchLen = pfxMatcher.isMatch(chars, pos, offset, bufEnd)) != 0) { 1140 // found a nested variable start 1141 nestedVarCount++; 1142 pos += endMatchLen; 1143 continue; 1144 } 1145 endMatchLen = suffMatcher.isMatch(chars, pos, offset, bufEnd); 1146 if (endMatchLen == 0) { 1147 pos++; 1148 } else { 1149 // found variable end marker 1150 if (nestedVarCount == 0) { 1151 String varNameExpr = new String(chars, startPos + startMatchLen, pos - startPos - startMatchLen); 1152 if (substitutionInVariablesEnabled) { 1153 final StrBuilder bufName = new StrBuilder(varNameExpr); 1154 substitute(bufName, 0, bufName.length()); 1155 varNameExpr = bufName.toString(); 1156 } 1157 pos += endMatchLen; 1158 final int endPos = pos; 1159 String varName = varNameExpr; 1160 String varDefaultValue = null; 1161 if (valueDelimMatcher != null) { 1162 final char[] varNameExprChars = varNameExpr.toCharArray(); 1163 int valueDelimiterMatchLen; 1164 for (int i = 0; i < varNameExprChars.length; i++) { 1165 // if there's any nested variable when nested variable substitution disabled, then stop resolving name and default value. 1166 if (!substitutionInVariablesEnabled && pfxMatcher.isMatch(varNameExprChars, i, i, varNameExprChars.length) != 0) { 1167 break; 1168 } 1169 if ((valueDelimiterMatchLen = valueDelimMatcher.isMatch(varNameExprChars, i)) != 0) { 1170 varName = varNameExpr.substring(0, i); 1171 varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen); 1172 break; 1173 } 1174 } 1175 } 1176 // on the first call initialize priorVariables 1177 if (priorVariables == null) { 1178 priorVariables = new ArrayList<>(); 1179 priorVariables.add(new String(chars, offset, length)); 1180 } 1181 // handle cyclic substitution 1182 checkCyclicSubstitution(varName, priorVariables); 1183 priorVariables.add(varName); 1184 // resolve the variable 1185 String varValue = resolveVariable(varName, buf, startPos, endPos); 1186 if (varValue == null) { 1187 varValue = varDefaultValue; 1188 } 1189 if (varValue != null) { 1190 // recursive replace 1191 final int varLen = varValue.length(); 1192 buf.replace(startPos, endPos, varValue); 1193 altered = true; 1194 int change = substitute(buf, startPos, varLen, priorVariables); 1195 change = change + varLen - (endPos - startPos); 1196 pos += change; 1197 bufEnd += change; 1198 lengthChange += change; 1199 chars = buf.buffer; // in case buffer was altered 1200 } 1201 // remove variable from the cyclic stack 1202 priorVariables.remove(priorVariables.size() - 1); 1203 break; 1204 } 1205 nestedVarCount--; 1206 pos += endMatchLen; 1207 } 1208 } 1209 } 1210 } 1211 if (top) { 1212 return altered ? 1 : 0; 1213 } 1214 return lengthChange; 1215 } 1216}