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