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.text; 018 019import java.util.ArrayList; 020import java.util.List; 021import java.util.Map; 022import java.util.Properties; 023import java.util.function.Function; 024import java.util.stream.Collectors; 025 026import org.apache.commons.lang3.Validate; 027 028/** 029 * Substitutes variables within a string by values. 030 * <p> 031 * This class takes a piece of text and substitutes all the variables within it. 032 * The default definition of a variable is {@code ${variableName}}. 033 * The prefix and suffix can be changed via constructors and set methods. 034 * <p> 035 * Variable values are typically resolved from a map, but could also be resolved 036 * from system properties, or by supplying a custom variable resolver. 037 * <p> 038 * The simplest example is to use this class to replace Java System properties. For example: 039 * <pre> 040 * StrSubstitutor.replaceSystemProperties( 041 * "You are running with java.version = ${java.version} and os.name = ${os.name}."); 042 * </pre> 043 * <p> 044 * Typical usage of this class follows the following pattern: First an instance is created 045 * and initialized with the map that contains the values for the available variables. 046 * If a prefix and/or suffix for variables should be used other than the default ones, 047 * the appropriate settings can be performed. After that the {@code replace()} 048 * method can be called passing in the source text for interpolation. In the returned 049 * text all variable references (as long as their values are known) will be resolved. 050 * The following example demonstrates this: 051 * <pre> 052 * Map<String, String> valuesMap = new HashMap<>(); 053 * valuesMap.put("animal", "quick brown fox"); 054 * valuesMap.put("target", "lazy dog"); 055 * String templateString = "The ${animal} jumped over the ${target}."; 056 * StrSubstitutor sub = new StrSubstitutor(valuesMap); 057 * String resolvedString = sub.replace(templateString); 058 * </pre> 059 * yielding: 060 * <pre> 061 * The quick brown fox jumped over the lazy dog. 062 * </pre> 063 * <p> 064 * Also, this class allows to set a default value for unresolved variables. 065 * The default value for a variable can be appended to the variable name after the variable 066 * default value delimiter. The default value of the variable default value delimiter is ':-', 067 * as in bash and other *nix shells, as those are arguably where the default ${} delimiter set originated. 068 * The variable default value delimiter can be manually set by calling {@link #setValueDelimiterMatcher(StrMatcher)}, 069 * {@link #setValueDelimiter(char)} or {@link #setValueDelimiter(String)}. 070 * The following shows an example with variable default value settings: 071 * <pre> 072 * Map<String, String> valuesMap = new HashMap<>(); 073 * valuesMap.put("animal", "quick brown fox"); 074 * valuesMap.put("target", "lazy dog"); 075 * String templateString = "The ${animal} jumped over the ${target}. ${undefined.number:-1234567890}."; 076 * StrSubstitutor sub = new StrSubstitutor(valuesMap); 077 * String resolvedString = sub.replace(templateString); 078 * </pre> 079 * yielding: 080 * <pre> 081 * The quick brown fox jumped over the lazy dog. 1234567890. 082 * </pre> 083 * <p> 084 * In addition to this usage pattern there are some static convenience methods that 085 * cover the most common use cases. These methods can be used without the need of 086 * manually creating an instance. However if multiple replace operations are to be 087 * performed, creating and reusing an instance of this class will be more efficient. 088 * <p> 089 * Variable replacement works in a recursive way. Thus, if a variable value contains 090 * a variable then that variable will also be replaced. Cyclic replacements are 091 * detected and will cause an exception to be thrown. 092 * <p> 093 * Sometimes the interpolation's result must contain a variable prefix. As an example 094 * take the following source text: 095 * <pre> 096 * The variable ${${name}} must be used. 097 * </pre> 098 * Here only the variable's name referred to in the text should be replaced resulting 099 * in the text (assuming that the value of the {@code name} variable is {@code x}): 100 * <pre> 101 * The variable ${x} must be used. 102 * </pre> 103 * To achieve this effect there are two possibilities: Either set a different prefix 104 * and suffix for variables which do not conflict with the result text you want to 105 * produce. The other possibility is to use the escape character, by default '$'. 106 * If this character is placed before a variable reference, this reference is ignored 107 * and won't be replaced. For example: 108 * <pre> 109 * The variable $${${name}} must be used. 110 * </pre> 111 * <p> 112 * In some complex scenarios you might even want to perform substitution in the 113 * names of variables, for instance 114 * <pre> 115 * ${jre-${java.specification.version}} 116 * </pre> 117 * {@code StrSubstitutor} supports this recursive substitution in variable 118 * names, but it has to be enabled explicitly by setting the 119 * {@link #setEnableSubstitutionInVariables(boolean) enableSubstitutionInVariables} 120 * property to <strong>true</strong>. 121 * <p>This class is <strong>not</strong> thread safe.</p> 122 * 123 * @since 1.0 124 * @deprecated Deprecated as of 1.3, use {@link StringSubstitutor} instead. This class will be removed in 2.0. 125 */ 126@Deprecated 127public class StrSubstitutor { 128 129 /** 130 * Constant for the default escape character. 131 */ 132 public static final char DEFAULT_ESCAPE = '$'; 133 134 /** 135 * Constant for the default variable prefix. 136 */ 137 public static final StrMatcher DEFAULT_PREFIX = StrMatcher.stringMatcher("${"); 138 139 /** 140 * Constant for the default variable suffix. 141 */ 142 public static final StrMatcher DEFAULT_SUFFIX = StrMatcher.stringMatcher("}"); 143 144 /** 145 * Constant for the default value delimiter of a variable. 146 */ 147 public static final StrMatcher DEFAULT_VALUE_DELIMITER = StrMatcher.stringMatcher(":-"); 148 149 /** 150 * Replaces all the occurrences of variables in the given source object with 151 * their matching values from the map. 152 * 153 * @param <V> the type of the values in the map. 154 * @param source the source text containing the variables to substitute, null returns null. 155 * @param valueMap the map with the values, may be null. 156 * @return The result of the replace operation. 157 */ 158 public static <V> String replace(final Object source, final Map<String, V> valueMap) { 159 return new StrSubstitutor(valueMap).replace(source); 160 } 161 162 /** 163 * Replaces all the occurrences of variables in the given source object with 164 * their matching values from the map. This method allows to specify a 165 * custom variable prefix and suffix 166 * 167 * @param <V> the type of the values in the map. 168 * @param source the source text containing the variables to substitute, null returns null. 169 * @param valueMap the map with the values, may be null. 170 * @param prefix the prefix of variables, not null. 171 * @param suffix the suffix of variables, not null. 172 * @return The result of the replace operation. 173 * @throws IllegalArgumentException if the prefix or suffix is null. 174 */ 175 public static <V> String replace(final Object source, 176 final Map<String, V> valueMap, 177 final String prefix, 178 final String suffix) { 179 return new StrSubstitutor(valueMap, prefix, suffix).replace(source); 180 } 181 182 /** 183 * Replaces all the occurrences of variables in the given source object with their matching 184 * values from the properties. 185 * 186 * @param source the source text containing the variables to substitute, null returns null. 187 * @param valueProperties the properties with values, may be null. 188 * @return The result of the replace operation. 189 */ 190 public static String replace(final Object source, final Properties valueProperties) { 191 if (valueProperties == null) { 192 return source.toString(); 193 } 194 return replace(source, valueProperties.stringPropertyNames().stream().collect(Collectors.toMap(Function.identity(), valueProperties::getProperty))); 195 } 196 197 /** 198 * Replaces all the occurrences of variables in the given source object with 199 * their matching values from the system properties. 200 * 201 * @param source the source text containing the variables to substitute, null returns null. 202 * @return The result of the replace operation. 203 */ 204 public static String replaceSystemProperties(final Object source) { 205 return new StrSubstitutor(StrLookup.systemPropertiesLookup()).replace(source); 206 } 207 208 /** 209 * Stores the escape character. 210 */ 211 private char escapeChar; 212 213 /** 214 * Stores the variable prefix. 215 */ 216 private StrMatcher prefixMatcher; 217 218 /** 219 * Stores the variable suffix. 220 */ 221 private StrMatcher suffixMatcher; 222 223 /** 224 * Stores the default variable value delimiter. 225 */ 226 private StrMatcher valueDelimiterMatcher; 227 228 /** 229 * Variable resolution is delegated to an implementor of VariableResolver. 230 */ 231 private StrLookup<?> variableResolver; 232 233 /** 234 * The flag whether substitution in variable names is enabled. 235 */ 236 private boolean enableSubstitutionInVariables; 237 238 /** 239 * Whether escapes should be preserved. Default is false; 240 */ 241 private boolean preserveEscapes; 242 243 /** 244 * The flag whether substitution in variable values is disabled. 245 */ 246 private boolean disableSubstitutionInValues; 247 248 /** 249 * Constructs a new instance with defaults for variable prefix and suffix 250 * and the escaping character. 251 */ 252 public StrSubstitutor() { 253 this(null, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); 254 } 255 256 /** 257 * Constructs a new instance and initializes it. Uses defaults for variable 258 * prefix and suffix and the escaping character. 259 * 260 * @param <V> the type of the values in the map. 261 * @param valueMap the map with the variables' values, may be null. 262 */ 263 public <V> StrSubstitutor(final Map<String, V> valueMap) { 264 this(StrLookup.mapLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); 265 } 266 267 /** 268 * Constructs a new instance and initializes it. Uses a default escaping character. 269 * 270 * @param <V> the type of the values in the map. 271 * @param valueMap the map with the variables' values, may be null. 272 * @param prefix the prefix for variables, not null. 273 * @param suffix the suffix for variables, not null. 274 * @throws IllegalArgumentException if the prefix or suffix is null. 275 */ 276 public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix) { 277 this(StrLookup.mapLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE); 278 } 279 280 /** 281 * Constructs a new instance and initializes it. 282 * 283 * @param <V> the type of the values in the map. 284 * @param valueMap the map with the variables' values, may be null. 285 * @param prefix the prefix for variables, not null. 286 * @param suffix the suffix for variables, not null. 287 * @param escape the escape character. 288 * @throws IllegalArgumentException if the prefix or suffix is null. 289 */ 290 public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix, 291 final char escape) { 292 this(StrLookup.mapLookup(valueMap), prefix, suffix, escape); 293 } 294 295 /** 296 * Constructs a new instance and initializes it. 297 * 298 * @param <V> the type of the values in the map. 299 * @param valueMap the map with the variables' values, may be null. 300 * @param prefix the prefix for variables, not null. 301 * @param suffix the suffix for variables, not null. 302 * @param escape the escape character. 303 * @param valueDelimiter the variable default value delimiter, may be null. 304 * @throws IllegalArgumentException if the prefix or suffix is null. 305 */ 306 public <V> StrSubstitutor(final Map<String, V> valueMap, final String prefix, final String suffix, 307 final char escape, final String valueDelimiter) { 308 this(StrLookup.mapLookup(valueMap), prefix, suffix, escape, valueDelimiter); 309 } 310 311 /** 312 * Constructs a new instance and initializes it. 313 * 314 * @param variableResolver the variable resolver, may be null. 315 */ 316 public StrSubstitutor(final StrLookup<?> variableResolver) { 317 this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); 318 } 319 320 /** 321 * Constructs a new instance and initializes it. 322 * 323 * @param variableResolver the variable resolver, may be null. 324 * @param prefix the prefix for variables, not null. 325 * @param suffix the suffix for variables, not null. 326 * @param escape the escape character. 327 * @throws IllegalArgumentException if the prefix or suffix is null. 328 */ 329 public StrSubstitutor(final StrLookup<?> variableResolver, final String prefix, final String suffix, 330 final char escape) { 331 setVariableResolver(variableResolver); 332 setVariablePrefix(prefix); 333 setVariableSuffix(suffix); 334 setEscapeChar(escape); 335 setValueDelimiterMatcher(DEFAULT_VALUE_DELIMITER); 336 } 337 338 /** 339 * Constructs a new instance and initializes it. 340 * 341 * @param variableResolver the variable resolver, may be null. 342 * @param prefix the prefix for variables, not null. 343 * @param suffix the suffix for variables, not null. 344 * @param escape the escape character. 345 * @param valueDelimiter the variable default value delimiter string, may be null. 346 * @throws IllegalArgumentException if the prefix or suffix is null. 347 */ 348 public StrSubstitutor(final StrLookup<?> variableResolver, final String prefix, final String suffix, 349 final char escape, final String valueDelimiter) { 350 setVariableResolver(variableResolver); 351 setVariablePrefix(prefix); 352 setVariableSuffix(suffix); 353 setEscapeChar(escape); 354 setValueDelimiter(valueDelimiter); 355 } 356 357 /** 358 * Constructs a new instance and initializes it. 359 * 360 * @param variableResolver the variable resolver, may be null. 361 * @param prefixMatcher the prefix for variables, not null. 362 * @param suffixMatcher the suffix for variables, not null. 363 * @param escape the escape character. 364 * @throws IllegalArgumentException if the prefix or suffix is null. 365 */ 366 public StrSubstitutor( 367 final StrLookup<?> variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher, 368 final char escape) { 369 this(variableResolver, prefixMatcher, suffixMatcher, escape, DEFAULT_VALUE_DELIMITER); 370 } 371 372 /** 373 * Constructs a new instance and initializes it. 374 * 375 * @param variableResolver the variable resolver, may be null. 376 * @param prefixMatcher the prefix for variables, not null. 377 * @param suffixMatcher the suffix for variables, not null. 378 * @param escape the escape character. 379 * @param valueDelimiterMatcher the variable default value delimiter matcher, may be null. 380 * @throws IllegalArgumentException if the prefix or suffix is null. 381 */ 382 public StrSubstitutor( 383 final StrLookup<?> variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher, 384 final char escape, final StrMatcher valueDelimiterMatcher) { 385 setVariableResolver(variableResolver); 386 setVariablePrefixMatcher(prefixMatcher); 387 setVariableSuffixMatcher(suffixMatcher); 388 setEscapeChar(escape); 389 setValueDelimiterMatcher(valueDelimiterMatcher); 390 } 391 392 /** 393 * Checks if the specified variable is already in the stack (list) of variables. 394 * 395 * @param varName the variable name to check. 396 * @param priorVariables the list of prior variables. 397 */ 398 private void checkCyclicSubstitution(final String varName, final List<String> priorVariables) { 399 if (!priorVariables.contains(varName)) { 400 return; 401 } 402 final StrBuilder buf = new StrBuilder(256); 403 buf.append("Infinite loop in property interpolation of "); 404 buf.append(priorVariables.remove(0)); 405 buf.append(": "); 406 buf.appendWithSeparators(priorVariables, "->"); 407 throw new IllegalStateException(buf.toString()); 408 } 409 410 /** 411 * Returns the escape character. 412 * 413 * @return The character used for escaping variable references. 414 */ 415 public char getEscapeChar() { 416 return this.escapeChar; 417 } 418 419 /** 420 * Gets the variable default value delimiter matcher currently in use. 421 * <p> 422 * The variable default value delimiter is the character or characters that delimit the variable name and the variable default value. This delimiter is 423 * expressed in terms of a matcher allowing advanced variable default value delimiter matches. 424 * </p> 425 * <p> 426 * If it returns null, then the variable default value resolution is disabled. 427 * </p> 428 * 429 * @return The variable default value delimiter matcher in use, may be null. 430 */ 431 public StrMatcher getValueDelimiterMatcher() { 432 return valueDelimiterMatcher; 433 } 434 435 /** 436 * Gets the variable prefix matcher currently in use. 437 * <p> 438 * The variable prefix is the character or characters that identify the start of a variable. This prefix is expressed in terms of a matcher allowing 439 * advanced prefix matches. 440 * </p> 441 * 442 * @return The prefix matcher in use. 443 */ 444 public StrMatcher getVariablePrefixMatcher() { 445 return prefixMatcher; 446 } 447 448 /** 449 * Gets the VariableResolver that is used to lookup variables. 450 * 451 * @return The VariableResolver. 452 */ 453 public StrLookup<?> getVariableResolver() { 454 return this.variableResolver; 455 } 456 457 /** 458 * Gets the variable suffix matcher currently in use. 459 * <p> 460 * The variable suffix is the character or characters that identify the end of a variable. This suffix is expressed in terms of a matcher allowing advanced 461 * suffix matches. 462 * </p> 463 * 464 * @return The suffix matcher in use. 465 */ 466 public StrMatcher getVariableSuffixMatcher() { 467 return suffixMatcher; 468 } 469 470 /** 471 * Returns a flag whether substitution is disabled in variable values.If set to 472 * <strong>true</strong>, the values of variables can contain other variables will not be 473 * processed and substituted original variable is evaluated, e.g. 474 * <pre> 475 * Map<String, String> valuesMap = new HashMap<>(); 476 * valuesMap.put("name", "Douglas ${surname}"); 477 * valuesMap.put("surname", "Crockford"); 478 * String templateString = "Hi ${name}"; 479 * StrSubstitutor sub = new StrSubstitutor(valuesMap); 480 * String resolvedString = sub.replace(templateString); 481 * </pre> 482 * <p> 483 * yielding: 484 * </p> 485 * <pre> 486 * Hi Douglas ${surname} 487 * </pre> 488 * 489 * @return The substitution in variable values flag. 490 * @since 1.2 491 */ 492 public boolean isDisableSubstitutionInValues() { 493 return disableSubstitutionInValues; 494 } 495 496 /** 497 * Returns a flag whether substitution is done in variable names. 498 * 499 * @return The substitution in variable names flag. 500 */ 501 public boolean isEnableSubstitutionInVariables() { 502 return enableSubstitutionInVariables; 503 } 504 505 /** 506 * Returns the flag controlling whether escapes are preserved during substitution. 507 * 508 * @return The preserve escape flag. 509 */ 510 public boolean isPreserveEscapes() { 511 return preserveEscapes; 512 } 513 514 /** 515 * Replaces all the occurrences of variables with their matching values from the resolver using the given source array as a template. The array is not 516 * 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 from the resolver using the given source array as a template. The array is not 532 * altered by this method. 533 * <p> 534 * Only the specified portion of the array will be processed. The rest of the array is not processed, and is not returned. 535 * </p> 536 * 537 * @param source the character array to replace in, not altered, null returns null. 538 * @param offset the start offset within the array, must be valid. 539 * @param length the length within the array to be processed, must be valid. 540 * @return The result of the replace operation. 541 */ 542 public String replace(final char[] source, final int offset, final int length) { 543 if (source == null) { 544 return null; 545 } 546 final StrBuilder buf = new StrBuilder(length).append(source, offset, length); 547 substitute(buf, 0, length); 548 return buf.toString(); 549 } 550 551 /** 552 * Replaces all the occurrences of variables with their matching values from the resolver using the given source as a template. The source is not altered by 553 * this method. 554 * 555 * @param source the buffer to use as a template, not changed, null returns null. 556 * @return The result of the replace operation. 557 */ 558 public String replace(final CharSequence source) { 559 if (source == null) { 560 return null; 561 } 562 return replace(source, 0, source.length()); 563 } 564 565 /** 566 * Replaces all the occurrences of variables with their matching values from the resolver using the given source as a template. The source is not altered by 567 * this method. 568 * <p> 569 * Only the specified portion of the buffer will be processed. The rest of the buffer is not processed, and is not returned. 570 * </p> 571 * 572 * @param source the buffer to use as a template, not changed, null returns null. 573 * @param offset the start offset within the array, must be valid. 574 * @param length the length within the array to be processed, must be valid. 575 * @return The result of the replace operation. 576 */ 577 public String replace(final CharSequence source, final int offset, final int length) { 578 if (source == null) { 579 return null; 580 } 581 final StrBuilder buf = new StrBuilder(length).append(source, offset, length); 582 substitute(buf, 0, length); 583 return buf.toString(); 584 } 585 586 /** 587 * Replaces all the occurrences of variables in the given source object with their matching values from the resolver. The input source object is converted 588 * to a string using {@code toString} and is not altered. 589 * 590 * @param source the source to replace in, null returns null. 591 * @return The result of the replace operation. 592 */ 593 public String replace(final Object source) { 594 if (source == null) { 595 return null; 596 } 597 final StrBuilder buf = new StrBuilder().append(source); 598 substitute(buf, 0, buf.length()); 599 return buf.toString(); 600 } 601 602 /** 603 * Replaces all the occurrences of variables with their matching values from the resolver using the given source builder as a template. The builder is not 604 * altered by this method. 605 * 606 * @param source the builder to use as a template, not changed, null returns null. 607 * @return The result of the replace operation. 608 */ 609 public String replace(final StrBuilder source) { 610 if (source == null) { 611 return null; 612 } 613 final StrBuilder buf = new StrBuilder(source.length()).append(source); 614 substitute(buf, 0, buf.length()); 615 return buf.toString(); 616 } 617 618 /** 619 * Replaces all the occurrences of variables with their matching values from the resolver using the given source builder as a template. The builder is not 620 * altered by this method. 621 * <p> 622 * Only the specified portion of the builder will be processed. The rest of the builder is not processed, and is not returned. 623 * </p> 624 * 625 * @param source the builder to use as a template, not changed, null returns null. 626 * @param offset the start offset within the array, must be valid. 627 * @param length the length within the array to be processed, must be valid. 628 * @return The result of the replace operation. 629 */ 630 public String replace(final StrBuilder source, final int offset, final int length) { 631 if (source == null) { 632 return null; 633 } 634 final StrBuilder buf = new StrBuilder(length).append(source, offset, length); 635 substitute(buf, 0, length); 636 return buf.toString(); 637 } 638 639 /** 640 * Replaces all the occurrences of variables with their matching values from the resolver using the given source string as a template. 641 * 642 * @param source the string to replace in, null returns null. 643 * @return The result of the replace operation. 644 */ 645 public String replace(final String source) { 646 if (source == null) { 647 return null; 648 } 649 final StrBuilder buf = new StrBuilder(source); 650 if (!substitute(buf, 0, source.length())) { 651 return source; 652 } 653 return buf.toString(); 654 } 655 656 /** 657 * Replaces all the occurrences of variables with their matching values from the resolver using the given source string as a template. 658 * <p> 659 * Only the specified portion of the string will be processed. The rest of the string is not processed, and is not returned. 660 * </p> 661 * 662 * @param source the string to replace in, null returns null. 663 * @param offset the start offset within the array, must be valid. 664 * @param length the length within the array to be processed, must be valid. 665 * @return The result of the replace operation. 666 */ 667 public String replace(final String source, final int offset, final int length) { 668 if (source == null) { 669 return null; 670 } 671 final StrBuilder buf = new StrBuilder(length).append(source, offset, length); 672 if (!substitute(buf, 0, length)) { 673 return source.substring(offset, offset + length); 674 } 675 return buf.toString(); 676 } 677 678 /** 679 * Replaces all the occurrences of variables with their matching values from the resolver using the given source buffer as a template. The buffer is not 680 * altered by this method. 681 * 682 * @param source the buffer to use as a template, not changed, null returns null. 683 * @return The result of the replace operation. 684 */ 685 public String replace(final StringBuffer source) { 686 if (source == null) { 687 return null; 688 } 689 final StrBuilder buf = new StrBuilder(source.length()).append(source); 690 substitute(buf, 0, buf.length()); 691 return buf.toString(); 692 } 693 694 /** 695 * Replaces all the occurrences of variables with their matching values from the resolver using the given source buffer as a template. The buffer is not 696 * altered by this method. 697 * <p> 698 * Only the specified portion of the buffer will be processed. The rest of the buffer is not processed, and is not returned. 699 * </p> 700 * 701 * @param source the buffer to use as a template, not changed, null returns null. 702 * @param offset the start offset within the array, must be valid. 703 * @param length the length within the array to be processed, must be valid. 704 * @return The result of the replace operation. 705 */ 706 public String replace(final StringBuffer source, final int offset, final int length) { 707 if (source == null) { 708 return null; 709 } 710 final StrBuilder buf = new StrBuilder(length).append(source, offset, length); 711 substitute(buf, 0, length); 712 return buf.toString(); 713 } 714 715 /** 716 * Replaces all the occurrences of variables within the given source builder with their matching values from the resolver. 717 * 718 * @param source the builder to replace in, updated, null returns zero. 719 * @return true if altered. 720 */ 721 public boolean replaceIn(final StrBuilder source) { 722 if (source == null) { 723 return false; 724 } 725 return substitute(source, 0, source.length()); 726 } 727 728 /** 729 * Replaces all the occurrences of variables within the given source builder with their matching values from the resolver. 730 * <p> 731 * Only the specified portion of the builder will be processed. The rest of the builder is not processed, but it is not deleted. 732 * </p> 733 * 734 * @param source the builder to replace in, null returns zero. 735 * @param offset the start offset within the array, must be valid. 736 * @param length the length within the builder to be processed, must be valid. 737 * @return true if altered. 738 */ 739 public boolean replaceIn(final StrBuilder source, final int offset, final int length) { 740 if (source == null) { 741 return false; 742 } 743 return substitute(source, offset, length); 744 } 745 746 /** 747 * Replaces all the occurrences of variables within the given source buffer with their matching values from the resolver. The buffer is updated with the 748 * result. 749 * 750 * @param source the buffer to replace in, updated, null returns zero. 751 * @return true if altered. 752 */ 753 public boolean replaceIn(final StringBuffer source) { 754 if (source == null) { 755 return false; 756 } 757 return replaceIn(source, 0, source.length()); 758 } 759 760 /** 761 * Replaces all the occurrences of variables within the given source buffer with their matching values from the resolver. The buffer is updated with the 762 * result. 763 * <p> 764 * Only the specified portion of the buffer will be processed. The rest of the buffer is not processed, but it is not deleted. 765 * </p> 766 * 767 * @param source the buffer to replace in, updated, null returns zero. 768 * @param offset the start offset within the array, must be valid. 769 * @param length the length within the buffer to be processed, must be valid. 770 * @return true if altered. 771 */ 772 public boolean replaceIn(final StringBuffer source, final int offset, final int length) { 773 if (source == null) { 774 return false; 775 } 776 final StrBuilder buf = new StrBuilder(length).append(source, offset, length); 777 if (!substitute(buf, 0, length)) { 778 return false; 779 } 780 source.replace(offset, offset + length, buf.toString()); 781 return true; 782 } 783 784 /** 785 * Replaces all the occurrences of variables within the given source buffer with their matching values from the resolver. The buffer is updated with the 786 * result. 787 * 788 * @param source the buffer to replace in, updated, null returns zero. 789 * @return true if altered. 790 */ 791 public boolean replaceIn(final StringBuilder source) { 792 if (source == null) { 793 return false; 794 } 795 return replaceIn(source, 0, source.length()); 796 } 797 798 /** 799 * Replaces all the occurrences of variables within the given source builder with their matching values from the resolver. The builder is updated with the 800 * result. 801 * <p> 802 * Only the specified portion of the buffer will be processed. The rest of the buffer is not processed, but it is not deleted. 803 * </p> 804 * 805 * @param source the buffer to replace in, updated, null returns zero. 806 * @param offset the start offset within the array, must be valid. 807 * @param length the length within the buffer to be processed, must be valid. 808 * @return true if altered. 809 */ 810 public boolean replaceIn(final StringBuilder source, final int offset, final int length) { 811 if (source == null) { 812 return false; 813 } 814 final StrBuilder buf = new StrBuilder(length).append(source, offset, length); 815 if (!substitute(buf, 0, length)) { 816 return false; 817 } 818 source.replace(offset, offset + length, buf.toString()); 819 return true; 820 } 821 822 /** 823 * Internal method that resolves the value of a variable. 824 * <p> 825 * Most users of this class do not need to call this method. This method is called automatically by the substitution process. 826 * </p> 827 * <p> 828 * Writers of subclasses can override this method if they need to alter how each substitution occurs. The method is passed the variable's name and must 829 * return the corresponding value. This implementation uses the {@link #getVariableResolver()} with the variable's name as the key. 830 * </p> 831 * 832 * @param variableName the name of the variable, not null. 833 * @param buf the buffer where the substitution is occurring, not null. 834 * @param startPos the start position of the variable including the prefix, valid. 835 * @param endPos the end position of the variable including the suffix, valid. 836 * @return The variable's value or <strong>null</strong> if the variable is unknown. 837 */ 838 protected String resolveVariable(final String variableName, 839 final StrBuilder buf, 840 final int startPos, 841 final int endPos) { 842 final StrLookup<?> resolver = getVariableResolver(); 843 if (resolver == null) { 844 return null; 845 } 846 return resolver.apply(variableName); 847 } 848 849 /** 850 * Sets a flag whether substitution is done in variable values (recursive). 851 * 852 * @param disableSubstitutionInValues true if substitution in variable value are disabled. 853 * @since 1.2 854 */ 855 public void setDisableSubstitutionInValues(final boolean disableSubstitutionInValues) { 856 this.disableSubstitutionInValues = disableSubstitutionInValues; 857 } 858 859 /** 860 * Sets a flag whether substitution is done in variable names. If set to <strong>true</strong>, the names of variables can contain other variables which are 861 * processed first before the original variable is evaluated, e.g. {@code ${jre-${java.version}}}. The default value is <strong>false</strong>. 862 * 863 * @param enableSubstitutionInVariables the new value of the flag. 864 */ 865 public void setEnableSubstitutionInVariables(final boolean enableSubstitutionInVariables) { 866 this.enableSubstitutionInVariables = enableSubstitutionInVariables; 867 } 868 869 /** 870 * Sets the escape character. If this character is placed before a variable reference in the source text, this variable will be ignored. 871 * 872 * @param escapeCharacter the escape character (0 for disabling escaping) 873 */ 874 public void setEscapeChar(final char escapeCharacter) { 875 this.escapeChar = escapeCharacter; 876 } 877 878 /** 879 * Sets a flag controlling whether escapes are preserved during substitution. If set to <strong>true</strong>, the escape character is retained during 880 * substitution (e.g. {@code $${this-is-escaped}} remains {@code $${this-is-escaped}}). If set to <strong>false</strong>, the escape character is removed 881 * during substitution (e.g. {@code $${this-is-escaped}} becomes {@code ${this-is-escaped}}). The default value is <strong>false</strong> 882 * 883 * @param preserveEscapes true if escapes are to be preserved 884 */ 885 public void setPreserveEscapes(final boolean preserveEscapes) { 886 this.preserveEscapes = preserveEscapes; 887 } 888 889 /** 890 * Sets the variable default value delimiter to use. 891 * <p> 892 * The variable default value delimiter is the character or characters that delimit the variable name and the variable default value. This method allows a 893 * single character variable default value delimiter to be easily set. 894 * </p> 895 * 896 * @param valueDelimiter the variable default value delimiter character to use. 897 * @return {@code this} instance. 898 */ 899 public StrSubstitutor setValueDelimiter(final char valueDelimiter) { 900 return setValueDelimiterMatcher(StrMatcher.charMatcher(valueDelimiter)); 901 } 902 903 /** 904 * Sets the variable default value delimiter to use. 905 * <p> 906 * The variable default value delimiter is the character or characters that delimit the variable name and the variable default value. This method allows a 907 * string variable default value delimiter to be easily set. 908 * </p> 909 * <p> 910 * If the {@code valueDelimiter} is null or empty string, then the variable default value resolution becomes disabled. 911 * </p> 912 * 913 * @param valueDelimiter the variable default value delimiter string to use, may be null or empty 914 * @return {@code this} instance. 915 */ 916 public StrSubstitutor setValueDelimiter(final String valueDelimiter) { 917 if (valueDelimiter == null || valueDelimiter.isEmpty()) { 918 setValueDelimiterMatcher(null); 919 return this; 920 } 921 return setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter)); 922 } 923 924 /** 925 * Sets the variable default value delimiter matcher to use. 926 * <p> 927 * The variable default value delimiter is the character or characters that delimit the variable name and the variable default value. This delimiter is 928 * expressed in terms of a matcher allowing advanced variable default value delimiter matches. 929 * </p> 930 * <p> 931 * If the {@code valueDelimiterMatcher} is null, then the variable default value resolution becomes disabled. 932 * </p> 933 * 934 * @param valueDelimiterMatcher variable default value delimiter matcher to use, may be null 935 * @return {@code this} instance. 936 */ 937 public StrSubstitutor setValueDelimiterMatcher(final StrMatcher valueDelimiterMatcher) { 938 this.valueDelimiterMatcher = valueDelimiterMatcher; 939 return this; 940 } 941 942 /** 943 * Sets the variable prefix to use. 944 * <p> 945 * The variable prefix is the character or characters that identify the start of a variable. This method allows a single character prefix to be easily set. 946 * </p> 947 * 948 * @param prefix the prefix character to use. 949 * @return {@code this} instance. 950 */ 951 public StrSubstitutor setVariablePrefix(final char prefix) { 952 return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix)); 953 } 954 955 /** 956 * Sets the variable prefix to use. 957 * <p> 958 * The variable prefix is the character or characters that identify the start of a variable. This method allows a string prefix to be easily set. 959 * </p> 960 * 961 * @param prefix the prefix for variables, not null. 962 * @return {@code this} instance. 963 * @throws IllegalArgumentException if the prefix is null. 964 */ 965 public StrSubstitutor setVariablePrefix(final String prefix) { 966 Validate.isTrue(prefix != null, "Variable prefix must not be null!"); 967 return setVariablePrefixMatcher(StrMatcher.stringMatcher(prefix)); 968 } 969 970 /** 971 * Sets the variable prefix matcher currently in use. 972 * <p> 973 * The variable prefix is the character or characters that identify the start of a variable. This prefix is expressed in terms of a matcher allowing 974 * advanced prefix matches. 975 * </p> 976 * 977 * @param prefixMatcher the prefix matcher to use, null ignored. 978 * @return {@code this} instance. 979 * @throws IllegalArgumentException if the prefix matcher is null. 980 */ 981 public StrSubstitutor setVariablePrefixMatcher(final StrMatcher prefixMatcher) { 982 Validate.isTrue(prefixMatcher != null, "Variable prefix matcher must not be null!"); 983 this.prefixMatcher = prefixMatcher; 984 return this; 985 } 986 987 /** 988 * Sets the VariableResolver that is used to lookup variables. 989 * 990 * @param variableResolver the VariableResolver. 991 */ 992 public void setVariableResolver(final StrLookup<?> variableResolver) { 993 this.variableResolver = variableResolver; 994 } 995 996 /** 997 * Sets the variable suffix to use. 998 * <p> 999 * The variable suffix is the character or characters that identify the end of a variable. This method allows a single character suffix to be easily set. 1000 * </p> 1001 * 1002 * @param suffix the suffix character to use. 1003 * @return {@code this} instance. 1004 */ 1005 public StrSubstitutor setVariableSuffix(final char suffix) { 1006 return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix)); 1007 } 1008 1009 /** 1010 * Sets the variable suffix to use. 1011 * <p> 1012 * The variable suffix is the character or characters that identify the end of a variable. This method allows a string suffix to be easily set. 1013 * </p> 1014 * 1015 * @param suffix the suffix for variables, not null. 1016 * @return {@code this} instance. 1017 * @throws IllegalArgumentException if the suffix is null. 1018 */ 1019 public StrSubstitutor setVariableSuffix(final String suffix) { 1020 Validate.isTrue(suffix != null, "Variable suffix must not be null!"); 1021 return setVariableSuffixMatcher(StrMatcher.stringMatcher(suffix)); 1022 } 1023 1024 /** 1025 * Sets the variable suffix matcher currently in use. 1026 * <p> 1027 * The variable suffix is the character or characters that identify the end of a variable. This suffix is expressed in terms of a matcher allowing advanced 1028 * suffix matches. 1029 * </p> 1030 * 1031 * @param suffixMatcher the suffix matcher to use, null ignored. 1032 * @return {@code this} instance. 1033 * @throws IllegalArgumentException if the suffix matcher is null. 1034 */ 1035 public StrSubstitutor setVariableSuffixMatcher(final StrMatcher suffixMatcher) { 1036 Validate.isTrue(suffixMatcher != null, "Variable suffix matcher must not be null!"); 1037 this.suffixMatcher = suffixMatcher; 1038 return this; 1039 } 1040 1041 /** 1042 * Internal method that substitutes the variables. 1043 * <p> 1044 * Most users of this class do not need to call this method. This method will be called automatically by another (public) method. 1045 * </p> 1046 * <p> 1047 * Writers of subclasses can override this method if they need access to the substitution process at the start or end. 1048 * </p> 1049 * 1050 * @param buf the string builder to substitute into, not null. 1051 * @param offset the start offset within the builder, must be valid. 1052 * @param length the length within the builder to be processed, must be valid. 1053 * @return true if altered. 1054 */ 1055 protected boolean substitute(final StrBuilder buf, final int offset, final int length) { 1056 return substitute(buf, offset, length, null) > 0; 1057 } 1058 1059 /** 1060 * Recursive handler for multiple levels of interpolation. This is the main interpolation method, which resolves the values of all variable references 1061 * contained in the passed in text. 1062 * 1063 * @param buf the string builder to substitute into, not null. 1064 * @param offset the start offset within the builder, must be valid. 1065 * @param length the length within the builder to be processed, must be valid. 1066 * @param priorVariables the stack keeping track of the replaced variables, may be null. 1067 * @return The length change that occurs, unless priorVariables is null when the int represents a boolean flag as to whether any change occurred. 1068 */ 1069 private int substitute(final StrBuilder buf, final int offset, final int length, List<String> priorVariables) { 1070 final StrMatcher pfxMatcher = getVariablePrefixMatcher(); 1071 final StrMatcher suffMatcher = getVariableSuffixMatcher(); 1072 final char escape = getEscapeChar(); 1073 final StrMatcher valueDelimMatcher = getValueDelimiterMatcher(); 1074 final boolean substitutionInVariablesEnabled = isEnableSubstitutionInVariables(); 1075 final boolean substitutionInValuesDisabled = isDisableSubstitutionInValues(); 1076 1077 final boolean top = priorVariables == null; 1078 boolean altered = false; 1079 int lengthChange = 0; 1080 char[] chars = buf.buffer; 1081 int bufEnd = offset + length; 1082 int pos = offset; 1083 while (pos < bufEnd) { 1084 final int startMatchLen = pfxMatcher.isMatch(chars, pos, offset, 1085 bufEnd); 1086 if (startMatchLen == 0) { 1087 pos++; 1088 } else // found variable start marker 1089 if (pos > offset && chars[pos - 1] == escape) { 1090 // escaped 1091 if (preserveEscapes) { 1092 pos++; 1093 continue; 1094 } 1095 buf.deleteCharAt(pos - 1); 1096 chars = buf.buffer; // in case buffer was altered 1097 lengthChange--; 1098 altered = true; 1099 bufEnd--; 1100 } else { 1101 // find suffix 1102 final int startPos = pos; 1103 pos += startMatchLen; 1104 int endMatchLen = 0; 1105 int nestedVarCount = 0; 1106 while (pos < bufEnd) { 1107 if (substitutionInVariablesEnabled 1108 && pfxMatcher.isMatch(chars, 1109 pos, offset, bufEnd) != 0) { 1110 // found a nested variable start 1111 endMatchLen = pfxMatcher.isMatch(chars, 1112 pos, offset, bufEnd); 1113 nestedVarCount++; 1114 pos += endMatchLen; 1115 continue; 1116 } 1117 1118 endMatchLen = suffMatcher.isMatch(chars, pos, offset, 1119 bufEnd); 1120 if (endMatchLen == 0) { 1121 pos++; 1122 } else { 1123 // found variable end marker 1124 if (nestedVarCount == 0) { 1125 String varNameExpr = new String(chars, startPos 1126 + startMatchLen, pos - startPos 1127 - startMatchLen); 1128 if (substitutionInVariablesEnabled) { 1129 final StrBuilder bufName = new StrBuilder(varNameExpr); 1130 substitute(bufName, 0, bufName.length()); 1131 varNameExpr = bufName.toString(); 1132 } 1133 pos += endMatchLen; 1134 final int endPos = pos; 1135 1136 String varName = varNameExpr; 1137 String varDefaultValue = null; 1138 1139 if (valueDelimMatcher != null) { 1140 final char[] varNameExprChars = varNameExpr.toCharArray(); 1141 int valueDelimiterMatchLen = 0; 1142 for (int i = 0; i < varNameExprChars.length; i++) { 1143 // if there's any nested variable when nested variable substitution disabled, 1144 // then stop resolving name and default value. 1145 if (!substitutionInVariablesEnabled 1146 && pfxMatcher.isMatch(varNameExprChars, 1147 i, 1148 i, 1149 varNameExprChars.length) != 0) { 1150 break; 1151 } 1152 if (valueDelimMatcher.isMatch(varNameExprChars, i) != 0) { 1153 valueDelimiterMatchLen = valueDelimMatcher.isMatch(varNameExprChars, i); 1154 varName = varNameExpr.substring(0, i); 1155 varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen); 1156 break; 1157 } 1158 } 1159 } 1160 1161 // on the first call initialize priorVariables 1162 if (priorVariables == null) { 1163 priorVariables = new ArrayList<>(); 1164 priorVariables.add(new String(chars, 1165 offset, length)); 1166 } 1167 1168 // handle cyclic substitution 1169 checkCyclicSubstitution(varName, priorVariables); 1170 priorVariables.add(varName); 1171 1172 // resolve the variable 1173 String varValue = resolveVariable(varName, buf, 1174 startPos, endPos); 1175 if (varValue == null) { 1176 varValue = varDefaultValue; 1177 } 1178 if (varValue != null) { 1179 final int varLen = varValue.length(); 1180 buf.replace(startPos, endPos, varValue); 1181 altered = true; 1182 int change = 0; 1183 if (!substitutionInValuesDisabled) { // recursive replace 1184 change = substitute(buf, startPos, 1185 varLen, priorVariables); 1186 } 1187 change = change 1188 + varLen - (endPos - startPos); 1189 pos += change; 1190 bufEnd += change; 1191 lengthChange += change; 1192 chars = buf.buffer; // in case buffer was 1193 // altered 1194 } 1195 1196 // remove variable from the cyclic stack 1197 priorVariables 1198 .remove(priorVariables.size() - 1); 1199 break; 1200 } 1201 nestedVarCount--; 1202 pos += endMatchLen; 1203 } 1204 } 1205 } 1206 } 1207 if (top) { 1208 return altered ? 1 : 0; 1209 } 1210 return lengthChange; 1211 } 1212}