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