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