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