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