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