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