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