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