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 */ 017 package org.apache.commons.lang3.text; 018 019 import java.util.ArrayList; 020 import java.util.Enumeration; 021 import java.util.HashMap; 022 import java.util.List; 023 import java.util.Map; 024 import java.util.Properties; 025 026 /** 027 * Substitutes variables within a string by values. 028 * <p> 029 * This class takes a piece of text and substitutes all the variables within it. 030 * The default definition of a variable is <code>${variableName}</code>. 031 * The prefix and suffix can be changed via constructors and set methods. 032 * <p> 033 * Variable values are typically resolved from a map, but could also be resolved 034 * from system properties, or by supplying a custom variable resolver. 035 * <p> 036 * The simplest example is to use this class to replace Java System properties. For example: 037 * <pre> 038 * StrSubstitutor.replaceSystemProperties( 039 * "You are running with java.version = ${java.version} and os.name = ${os.name}."); 040 * </pre> 041 * <p> 042 * Typical usage of this class follows the following pattern: First an instance is created 043 * and initialized with the map that contains the values for the available variables. 044 * If a prefix and/or suffix for variables should be used other than the default ones, 045 * the appropriate settings can be performed. After that the <code>replace()</code> 046 * method can be called passing in the source text for interpolation. In the returned 047 * text all variable references (as long as their values are known) will be resolved. 048 * The following example demonstrates this: 049 * <pre> 050 * Map valuesMap = HashMap(); 051 * valuesMap.put("animal", "quick brown fox"); 052 * valuesMap.put("target", "lazy dog"); 053 * String templateString = "The ${animal} jumped over the ${target}."; 054 * StrSubstitutor sub = new StrSubstitutor(valuesMap); 055 * String resolvedString = sub.replace(templateString); 056 * </pre> 057 * yielding: 058 * <pre> 059 * The quick brown fox jumped over the lazy dog. 060 * </pre> 061 * <p> 062 * In addition to this usage pattern there are some static convenience methods that 063 * cover the most common use cases. These methods can be used without the need of 064 * manually creating an instance. However if multiple replace operations are to be 065 * performed, creating and reusing an instance of this class will be more efficient. 066 * <p> 067 * Variable replacement works in a recursive way. Thus, if a variable value contains 068 * a variable then that variable will also be replaced. Cyclic replacements are 069 * detected and will cause an exception to be thrown. 070 * <p> 071 * Sometimes the interpolation's result must contain a variable prefix. As an example 072 * take the following source text: 073 * <pre> 074 * The variable ${${name}} must be used. 075 * </pre> 076 * Here only the variable's name referred to in the text should be replaced resulting 077 * in the text (assuming that the value of the <code>name</code> variable is <code>x</code>): 078 * <pre> 079 * The variable ${x} must be used. 080 * </pre> 081 * To achieve this effect there are two possibilities: Either set a different prefix 082 * and suffix for variables which do not conflict with the result text you want to 083 * produce. The other possibility is to use the escape character, by default '$'. 084 * If this character is placed before a variable reference, this reference is ignored 085 * and won't be replaced. For example: 086 * <pre> 087 * The variable $${${name}} must be used. 088 * </pre> 089 * <p> 090 * In some complex scenarios you might even want to perform substitution in the 091 * names of variables, for instance 092 * <pre> 093 * ${jre-${java.specification.version}} 094 * </pre> 095 * <code>StrSubstitutor</code> supports this recursive substitution in variable 096 * names, but it has to be enabled explicitly by setting the 097 * {@link #setEnableSubstitutionInVariables(boolean) enableSubstitutionInVariables} 098 * property to <b>true</b>. 099 * 100 * @version $Id: StrSubstitutor.java 1088899 2011-04-05 05:31:27Z bayard $ 101 * @since 2.2 102 */ 103 public class StrSubstitutor { 104 105 /** 106 * Constant for the default escape character. 107 */ 108 public static final char DEFAULT_ESCAPE = '$'; 109 /** 110 * Constant for the default variable prefix. 111 */ 112 public static final StrMatcher DEFAULT_PREFIX = StrMatcher.stringMatcher("${"); 113 /** 114 * Constant for the default variable suffix. 115 */ 116 public static final StrMatcher DEFAULT_SUFFIX = StrMatcher.stringMatcher("}"); 117 118 /** 119 * Stores the escape character. 120 */ 121 private char escapeChar; 122 /** 123 * Stores the variable prefix. 124 */ 125 private StrMatcher prefixMatcher; 126 /** 127 * Stores the variable suffix. 128 */ 129 private StrMatcher suffixMatcher; 130 /** 131 * Variable resolution is delegated to an implementor of VariableResolver. 132 */ 133 private StrLookup<?> variableResolver; 134 /** 135 * The flag whether substitution in variable names is enabled. 136 */ 137 private boolean enableSubstitutionInVariables; 138 139 //----------------------------------------------------------------------- 140 /** 141 * Replaces all the occurrences of variables in the given source object with 142 * their matching values from the map. 143 * 144 * @param <V> the type of the values in the map 145 * @param source the source text containing the variables to substitute, null returns null 146 * @param valueMap the map with the values, may be null 147 * @return the result of the replace operation 148 */ 149 public static <V> String replace(Object source, Map<String, V> valueMap) { 150 return new StrSubstitutor(valueMap).replace(source); 151 } 152 153 /** 154 * Replaces all the occurrences of variables in the given source object with 155 * their matching values from the map. This method allows to specifiy a 156 * custom variable prefix and suffix 157 * 158 * @param <V> the type of the values in the map 159 * @param source the source text containing the variables to substitute, null returns null 160 * @param valueMap the map with the values, may be null 161 * @param prefix the prefix of variables, not null 162 * @param suffix the suffix of variables, not null 163 * @return the result of the replace operation 164 * @throws IllegalArgumentException if the prefix or suffix is null 165 */ 166 public static <V> String replace(Object source, Map<String, V> valueMap, String prefix, String suffix) { 167 return new StrSubstitutor(valueMap, prefix, suffix).replace(source); 168 } 169 170 /** 171 * Replaces all the occurrences of variables in the given source object with their matching 172 * values from the properties. 173 * 174 * @param source the source text containing the variables to substitute, null returns null 175 * @param valueProperties the properties with values, may be null 176 * @return the result of the replace operation 177 */ 178 public static String replace(Object source, Properties valueProperties) { 179 if (valueProperties == null) { 180 return source.toString(); 181 } 182 Map<String,String> valueMap = new HashMap<String,String>(); 183 Enumeration<?> propNames = valueProperties.propertyNames(); 184 while (propNames.hasMoreElements()) { 185 String propName = (String)propNames.nextElement(); 186 String propValue = valueProperties.getProperty(propName); 187 valueMap.put(propName, propValue); 188 } 189 return StrSubstitutor.replace(source, valueMap); 190 } 191 192 /** 193 * Replaces all the occurrences of variables in the given source object with 194 * their matching values from the system properties. 195 * 196 * @param source the source text containing the variables to substitute, null returns null 197 * @return the result of the replace operation 198 */ 199 public static String replaceSystemProperties(Object source) { 200 return new StrSubstitutor(StrLookup.systemPropertiesLookup()).replace(source); 201 } 202 203 //----------------------------------------------------------------------- 204 /** 205 * Creates a new instance with defaults for variable prefix and suffix 206 * and the escaping character. 207 */ 208 public StrSubstitutor() { 209 this((StrLookup<?>) null, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); 210 } 211 212 /** 213 * Creates a new instance and initializes it. Uses defaults for variable 214 * prefix and suffix and the escaping character. 215 * 216 * @param <V> the type of the values in the map 217 * @param valueMap the map with the variables' values, may be null 218 */ 219 public <V> StrSubstitutor(Map<String, V> valueMap) { 220 this(StrLookup.mapLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); 221 } 222 223 /** 224 * Creates a new instance and initializes it. Uses a default escaping character. 225 * 226 * @param <V> the type of the values in the map 227 * @param valueMap the map with the variables' values, may be null 228 * @param prefix the prefix for variables, not null 229 * @param suffix the suffix for variables, not null 230 * @throws IllegalArgumentException if the prefix or suffix is null 231 */ 232 public <V> StrSubstitutor(Map<String, V> valueMap, String prefix, String suffix) { 233 this(StrLookup.mapLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE); 234 } 235 236 /** 237 * Creates a new instance and initializes it. 238 * 239 * @param <V> the type of the values in the map 240 * @param valueMap the map with the variables' values, may be null 241 * @param prefix the prefix for variables, not null 242 * @param suffix the suffix for variables, not null 243 * @param escape the escape character 244 * @throws IllegalArgumentException if the prefix or suffix is null 245 */ 246 public <V> StrSubstitutor(Map<String, V> valueMap, String prefix, String suffix, char escape) { 247 this(StrLookup.mapLookup(valueMap), prefix, suffix, escape); 248 } 249 250 /** 251 * Creates a new instance and initializes it. 252 * 253 * @param variableResolver the variable resolver, may be null 254 */ 255 public StrSubstitutor(StrLookup<?> variableResolver) { 256 this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); 257 } 258 259 /** 260 * Creates a new instance and initializes it. 261 * 262 * @param variableResolver the variable resolver, may be null 263 * @param prefix the prefix for variables, not null 264 * @param suffix the suffix for variables, not null 265 * @param escape the escape character 266 * @throws IllegalArgumentException if the prefix or suffix is null 267 */ 268 public StrSubstitutor(StrLookup<?> variableResolver, String prefix, String suffix, char escape) { 269 this.setVariableResolver(variableResolver); 270 this.setVariablePrefix(prefix); 271 this.setVariableSuffix(suffix); 272 this.setEscapeChar(escape); 273 } 274 275 /** 276 * Creates a new instance and initializes it. 277 * 278 * @param variableResolver the variable resolver, may be null 279 * @param prefixMatcher the prefix for variables, not null 280 * @param suffixMatcher the suffix for variables, not null 281 * @param escape the escape character 282 * @throws IllegalArgumentException if the prefix or suffix is null 283 */ 284 public StrSubstitutor( 285 StrLookup<?> variableResolver, StrMatcher prefixMatcher, StrMatcher suffixMatcher, char escape) { 286 this.setVariableResolver(variableResolver); 287 this.setVariablePrefixMatcher(prefixMatcher); 288 this.setVariableSuffixMatcher(suffixMatcher); 289 this.setEscapeChar(escape); 290 } 291 292 //----------------------------------------------------------------------- 293 /** 294 * Replaces all the occurrences of variables with their matching values 295 * from the resolver using the given source string as a template. 296 * 297 * @param source the string to replace in, null returns null 298 * @return the result of the replace operation 299 */ 300 public String replace(String source) { 301 if (source == null) { 302 return null; 303 } 304 StrBuilder buf = new StrBuilder(source); 305 if (substitute(buf, 0, source.length()) == false) { 306 return source; 307 } 308 return buf.toString(); 309 } 310 311 /** 312 * Replaces all the occurrences of variables with their matching values 313 * from the resolver using the given source string as a template. 314 * <p> 315 * Only the specified portion of the string will be processed. 316 * The rest of the string is not processed, and is not returned. 317 * 318 * @param source the string to replace in, null returns null 319 * @param offset the start offset within the array, must be valid 320 * @param length the length within the array to be processed, must be valid 321 * @return the result of the replace operation 322 */ 323 public String replace(String source, int offset, int length) { 324 if (source == null) { 325 return null; 326 } 327 StrBuilder buf = new StrBuilder(length).append(source, offset, length); 328 if (substitute(buf, 0, length) == false) { 329 return source.substring(offset, offset + length); 330 } 331 return buf.toString(); 332 } 333 334 //----------------------------------------------------------------------- 335 /** 336 * Replaces all the occurrences of variables with their matching values 337 * from the resolver using the given source array as a template. 338 * The array is not altered by this method. 339 * 340 * @param source the character array to replace in, not altered, null returns null 341 * @return the result of the replace operation 342 */ 343 public String replace(char[] source) { 344 if (source == null) { 345 return null; 346 } 347 StrBuilder buf = new StrBuilder(source.length).append(source); 348 substitute(buf, 0, source.length); 349 return buf.toString(); 350 } 351 352 /** 353 * Replaces all the occurrences of variables with their matching values 354 * from the resolver using the given source array as a template. 355 * The array is not altered by this method. 356 * <p> 357 * Only the specified portion of the array will be processed. 358 * The rest of the array is not processed, and is not returned. 359 * 360 * @param source the character array to replace in, not altered, null returns null 361 * @param offset the start offset within the array, must be valid 362 * @param length the length within the array to be processed, must be valid 363 * @return the result of the replace operation 364 */ 365 public String replace(char[] source, int offset, int length) { 366 if (source == null) { 367 return null; 368 } 369 StrBuilder buf = new StrBuilder(length).append(source, offset, length); 370 substitute(buf, 0, length); 371 return buf.toString(); 372 } 373 374 //----------------------------------------------------------------------- 375 /** 376 * Replaces all the occurrences of variables with their matching values 377 * from the resolver using the given source buffer as a template. 378 * The buffer is not altered by this method. 379 * 380 * @param source the buffer to use as a template, not changed, null returns null 381 * @return the result of the replace operation 382 */ 383 public String replace(StringBuffer source) { 384 if (source == null) { 385 return null; 386 } 387 StrBuilder buf = new StrBuilder(source.length()).append(source); 388 substitute(buf, 0, buf.length()); 389 return buf.toString(); 390 } 391 392 /** 393 * Replaces all the occurrences of variables with their matching values 394 * from the resolver using the given source buffer as a template. 395 * The buffer is not altered by this method. 396 * <p> 397 * Only the specified portion of the buffer will be processed. 398 * The rest of the buffer is not processed, and is not returned. 399 * 400 * @param source the buffer to use as a template, not changed, null returns null 401 * @param offset the start offset within the array, must be valid 402 * @param length the length within the array to be processed, must be valid 403 * @return the result of the replace operation 404 */ 405 public String replace(StringBuffer source, int offset, int length) { 406 if (source == null) { 407 return null; 408 } 409 StrBuilder buf = new StrBuilder(length).append(source, offset, length); 410 substitute(buf, 0, length); 411 return buf.toString(); 412 } 413 414 //----------------------------------------------------------------------- 415 /** 416 * Replaces all the occurrences of variables with their matching values 417 * from the resolver using the given source builder as a template. 418 * The builder is not altered by this method. 419 * 420 * @param source the builder to use as a template, not changed, null returns null 421 * @return the result of the replace operation 422 */ 423 public String replace(StrBuilder source) { 424 if (source == null) { 425 return null; 426 } 427 StrBuilder buf = new StrBuilder(source.length()).append(source); 428 substitute(buf, 0, buf.length()); 429 return buf.toString(); 430 } 431 432 /** 433 * Replaces all the occurrences of variables with their matching values 434 * from the resolver using the given source builder as a template. 435 * The builder is not altered by this method. 436 * <p> 437 * Only the specified portion of the builder will be processed. 438 * The rest of the builder is not processed, and is not returned. 439 * 440 * @param source the builder to use as a template, not changed, null returns null 441 * @param offset the start offset within the array, must be valid 442 * @param length the length within the array to be processed, must be valid 443 * @return the result of the replace operation 444 */ 445 public String replace(StrBuilder source, int offset, int length) { 446 if (source == null) { 447 return null; 448 } 449 StrBuilder buf = new StrBuilder(length).append(source, offset, length); 450 substitute(buf, 0, length); 451 return buf.toString(); 452 } 453 454 //----------------------------------------------------------------------- 455 /** 456 * Replaces all the occurrences of variables in the given source object with 457 * their matching values from the resolver. The input source object is 458 * converted to a string using <code>toString</code> and is not altered. 459 * 460 * @param source the source to replace in, null returns null 461 * @return the result of the replace operation 462 */ 463 public String replace(Object source) { 464 if (source == null) { 465 return null; 466 } 467 StrBuilder buf = new StrBuilder().append(source); 468 substitute(buf, 0, buf.length()); 469 return buf.toString(); 470 } 471 472 //----------------------------------------------------------------------- 473 /** 474 * Replaces all the occurrences of variables within the given source buffer 475 * with their matching values from the resolver. 476 * The buffer is updated with the result. 477 * 478 * @param source the buffer to replace in, updated, null returns zero 479 * @return true if altered 480 */ 481 public boolean replaceIn(StringBuffer source) { 482 if (source == null) { 483 return false; 484 } 485 return replaceIn(source, 0, source.length()); 486 } 487 488 /** 489 * Replaces all the occurrences of variables within the given source buffer 490 * with their matching values from the resolver. 491 * The buffer is updated with the result. 492 * <p> 493 * Only the specified portion of the buffer will be processed. 494 * The rest of the buffer is not processed, but it is not deleted. 495 * 496 * @param source the buffer to replace in, updated, null returns zero 497 * @param offset the start offset within the array, must be valid 498 * @param length the length within the buffer to be processed, must be valid 499 * @return true if altered 500 */ 501 public boolean replaceIn(StringBuffer source, int offset, int length) { 502 if (source == null) { 503 return false; 504 } 505 StrBuilder buf = new StrBuilder(length).append(source, offset, length); 506 if (substitute(buf, 0, length) == false) { 507 return false; 508 } 509 source.replace(offset, offset + length, buf.toString()); 510 return true; 511 } 512 513 //----------------------------------------------------------------------- 514 /** 515 * Replaces all the occurrences of variables within the given source 516 * builder with their matching values from the resolver. 517 * 518 * @param source the builder to replace in, updated, null returns zero 519 * @return true if altered 520 */ 521 public boolean replaceIn(StrBuilder source) { 522 if (source == null) { 523 return false; 524 } 525 return substitute(source, 0, source.length()); 526 } 527 528 /** 529 * Replaces all the occurrences of variables within the given source 530 * builder with their matching values from the resolver. 531 * <p> 532 * Only the specified portion of the builder will be processed. 533 * The rest of the builder is not processed, but it is not deleted. 534 * 535 * @param source the builder to replace in, null returns zero 536 * @param offset the start offset within the array, must be valid 537 * @param length the length within the builder to be processed, must be valid 538 * @return true if altered 539 */ 540 public boolean replaceIn(StrBuilder source, int offset, int length) { 541 if (source == null) { 542 return false; 543 } 544 return substitute(source, offset, length); 545 } 546 547 //----------------------------------------------------------------------- 548 /** 549 * Internal method that substitutes the variables. 550 * <p> 551 * Most users of this class do not need to call this method. This method will 552 * be called automatically by another (public) method. 553 * <p> 554 * Writers of subclasses can override this method if they need access to 555 * the substitution process at the start or end. 556 * 557 * @param buf the string builder to substitute into, not null 558 * @param offset the start offset within the builder, must be valid 559 * @param length the length within the builder to be processed, must be valid 560 * @return true if altered 561 */ 562 protected boolean substitute(StrBuilder buf, int offset, int length) { 563 return substitute(buf, offset, length, null) > 0; 564 } 565 566 /** 567 * Recursive handler for multiple levels of interpolation. This is the main 568 * interpolation method, which resolves the values of all variable references 569 * contained in the passed in text. 570 * 571 * @param buf the string builder to substitute into, not null 572 * @param offset the start offset within the builder, must be valid 573 * @param length the length within the builder to be processed, must be valid 574 * @param priorVariables the stack keeping track of the replaced variables, may be null 575 * @return the length change that occurs, unless priorVariables is null when the int 576 * represents a boolean flag as to whether any change occurred. 577 */ 578 private int substitute(StrBuilder buf, int offset, int length, List<String> priorVariables) { 579 StrMatcher prefixMatcher = getVariablePrefixMatcher(); 580 StrMatcher suffixMatcher = getVariableSuffixMatcher(); 581 char escape = getEscapeChar(); 582 583 boolean top = (priorVariables == null); 584 boolean altered = false; 585 int lengthChange = 0; 586 char[] chars = buf.buffer; 587 int bufEnd = offset + length; 588 int pos = offset; 589 while (pos < bufEnd) { 590 int startMatchLen = prefixMatcher.isMatch(chars, pos, offset, 591 bufEnd); 592 if (startMatchLen == 0) { 593 pos++; 594 } else { 595 // found variable start marker 596 if (pos > offset && chars[pos - 1] == escape) { 597 // escaped 598 buf.deleteCharAt(pos - 1); 599 chars = buf.buffer; // in case buffer was altered 600 lengthChange--; 601 altered = true; 602 bufEnd--; 603 } else { 604 // find suffix 605 int startPos = pos; 606 pos += startMatchLen; 607 int endMatchLen = 0; 608 int nestedVarCount = 0; 609 while (pos < bufEnd) { 610 if (isEnableSubstitutionInVariables() 611 && (endMatchLen = prefixMatcher.isMatch(chars, 612 pos, offset, bufEnd)) != 0) { 613 // found a nested variable start 614 nestedVarCount++; 615 pos += endMatchLen; 616 continue; 617 } 618 619 endMatchLen = suffixMatcher.isMatch(chars, pos, offset, 620 bufEnd); 621 if (endMatchLen == 0) { 622 pos++; 623 } else { 624 // found variable end marker 625 if (nestedVarCount == 0) { 626 String varName = new String(chars, startPos 627 + startMatchLen, pos - startPos 628 - startMatchLen); 629 if (isEnableSubstitutionInVariables()) { 630 StrBuilder bufName = new StrBuilder(varName); 631 substitute(bufName, 0, bufName.length()); 632 varName = bufName.toString(); 633 } 634 pos += endMatchLen; 635 int endPos = pos; 636 637 // on the first call initialize priorVariables 638 if (priorVariables == null) { 639 priorVariables = new ArrayList<String>(); 640 priorVariables.add(new String(chars, 641 offset, length)); 642 } 643 644 // handle cyclic substitution 645 checkCyclicSubstitution(varName, priorVariables); 646 priorVariables.add(varName); 647 648 // resolve the variable 649 String varValue = resolveVariable(varName, buf, 650 startPos, endPos); 651 if (varValue != null) { 652 // recursive replace 653 int varLen = varValue.length(); 654 buf.replace(startPos, endPos, varValue); 655 altered = true; 656 int change = substitute(buf, startPos, 657 varLen, priorVariables); 658 change = change 659 + (varLen - (endPos - startPos)); 660 pos += change; 661 bufEnd += change; 662 lengthChange += change; 663 chars = buf.buffer; // in case buffer was 664 // altered 665 } 666 667 // remove variable from the cyclic stack 668 priorVariables 669 .remove(priorVariables.size() - 1); 670 break; 671 } else { 672 nestedVarCount--; 673 pos += endMatchLen; 674 } 675 } 676 } 677 } 678 } 679 } 680 if (top) { 681 return (altered ? 1 : 0); 682 } 683 return lengthChange; 684 } 685 686 /** 687 * Checks if the specified variable is already in the stack (list) of variables. 688 * 689 * @param varName the variable name to check 690 * @param priorVariables the list of prior variables 691 */ 692 private void checkCyclicSubstitution(String varName, List<String> priorVariables) { 693 if (priorVariables.contains(varName) == false) { 694 return; 695 } 696 StrBuilder buf = new StrBuilder(256); 697 buf.append("Infinite loop in property interpolation of "); 698 buf.append(priorVariables.remove(0)); 699 buf.append(": "); 700 buf.appendWithSeparators(priorVariables, "->"); 701 throw new IllegalStateException(buf.toString()); 702 } 703 704 /** 705 * Internal method that resolves the value of a variable. 706 * <p> 707 * Most users of this class do not need to call this method. This method is 708 * called automatically by the substitution process. 709 * <p> 710 * Writers of subclasses can override this method if they need to alter 711 * how each substitution occurs. The method is passed the variable's name 712 * and must return the corresponding value. This implementation uses the 713 * {@link #getVariableResolver()} with the variable's name as the key. 714 * 715 * @param variableName the name of the variable, not null 716 * @param buf the buffer where the substitution is occurring, not null 717 * @param startPos the start position of the variable including the prefix, valid 718 * @param endPos the end position of the variable including the suffix, valid 719 * @return the variable's value or <b>null</b> if the variable is unknown 720 */ 721 protected String resolveVariable(String variableName, StrBuilder buf, int startPos, int endPos) { 722 StrLookup<?> resolver = getVariableResolver(); 723 if (resolver == null) { 724 return null; 725 } 726 return resolver.lookup(variableName); 727 } 728 729 // Escape 730 //----------------------------------------------------------------------- 731 /** 732 * Returns the escape character. 733 * 734 * @return the character used for escaping variable references 735 */ 736 public char getEscapeChar() { 737 return this.escapeChar; 738 } 739 740 /** 741 * Sets the escape character. 742 * If this character is placed before a variable reference in the source 743 * text, this variable will be ignored. 744 * 745 * @param escapeCharacter the escape character (0 for disabling escaping) 746 */ 747 public void setEscapeChar(char escapeCharacter) { 748 this.escapeChar = escapeCharacter; 749 } 750 751 // Prefix 752 //----------------------------------------------------------------------- 753 /** 754 * Gets the variable prefix matcher currently in use. 755 * <p> 756 * The variable prefix is the characer or characters that identify the 757 * start of a variable. This prefix is expressed in terms of a matcher 758 * allowing advanced prefix matches. 759 * 760 * @return the prefix matcher in use 761 */ 762 public StrMatcher getVariablePrefixMatcher() { 763 return prefixMatcher; 764 } 765 766 /** 767 * Sets the variable prefix matcher currently in use. 768 * <p> 769 * The variable prefix is the characer or characters that identify the 770 * start of a variable. This prefix is expressed in terms of a matcher 771 * allowing advanced prefix matches. 772 * 773 * @param prefixMatcher the prefix matcher to use, null ignored 774 * @return this, to enable chaining 775 * @throws IllegalArgumentException if the prefix matcher is null 776 */ 777 public StrSubstitutor setVariablePrefixMatcher(StrMatcher prefixMatcher) { 778 if (prefixMatcher == null) { 779 throw new IllegalArgumentException("Variable prefix matcher must not be null!"); 780 } 781 this.prefixMatcher = prefixMatcher; 782 return this; 783 } 784 785 /** 786 * Sets the variable prefix to use. 787 * <p> 788 * The variable prefix is the character or characters that identify the 789 * start of a variable. This method allows a single character prefix to 790 * be easily set. 791 * 792 * @param prefix the prefix character to use 793 * @return this, to enable chaining 794 */ 795 public StrSubstitutor setVariablePrefix(char prefix) { 796 return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix)); 797 } 798 799 /** 800 * Sets the variable prefix to use. 801 * <p> 802 * The variable prefix is the characer or characters that identify the 803 * start of a variable. This method allows a string prefix to be easily set. 804 * 805 * @param prefix the prefix for variables, not null 806 * @return this, to enable chaining 807 * @throws IllegalArgumentException if the prefix is null 808 */ 809 public StrSubstitutor setVariablePrefix(String prefix) { 810 if (prefix == null) { 811 throw new IllegalArgumentException("Variable prefix must not be null!"); 812 } 813 return setVariablePrefixMatcher(StrMatcher.stringMatcher(prefix)); 814 } 815 816 // Suffix 817 //----------------------------------------------------------------------- 818 /** 819 * Gets the variable suffix matcher currently in use. 820 * <p> 821 * The variable suffix is the characer or characters that identify the 822 * end of a variable. This suffix is expressed in terms of a matcher 823 * allowing advanced suffix matches. 824 * 825 * @return the suffix matcher in use 826 */ 827 public StrMatcher getVariableSuffixMatcher() { 828 return suffixMatcher; 829 } 830 831 /** 832 * Sets the variable suffix matcher currently in use. 833 * <p> 834 * The variable suffix is the characer or characters that identify the 835 * end of a variable. This suffix is expressed in terms of a matcher 836 * allowing advanced suffix matches. 837 * 838 * @param suffixMatcher the suffix matcher to use, null ignored 839 * @return this, to enable chaining 840 * @throws IllegalArgumentException if the suffix matcher is null 841 */ 842 public StrSubstitutor setVariableSuffixMatcher(StrMatcher suffixMatcher) { 843 if (suffixMatcher == null) { 844 throw new IllegalArgumentException("Variable suffix matcher must not be null!"); 845 } 846 this.suffixMatcher = suffixMatcher; 847 return this; 848 } 849 850 /** 851 * Sets the variable suffix to use. 852 * <p> 853 * The variable suffix is the characer or characters that identify the 854 * end of a variable. This method allows a single character suffix to 855 * be easily set. 856 * 857 * @param suffix the suffix character to use 858 * @return this, to enable chaining 859 */ 860 public StrSubstitutor setVariableSuffix(char suffix) { 861 return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix)); 862 } 863 864 /** 865 * Sets the variable suffix to use. 866 * <p> 867 * The variable suffix is the character or characters that identify the 868 * end of a variable. This method allows a string suffix to be easily set. 869 * 870 * @param suffix the suffix for variables, not null 871 * @return this, to enable chaining 872 * @throws IllegalArgumentException if the suffix is null 873 */ 874 public StrSubstitutor setVariableSuffix(String suffix) { 875 if (suffix == null) { 876 throw new IllegalArgumentException("Variable suffix must not be null!"); 877 } 878 return setVariableSuffixMatcher(StrMatcher.stringMatcher(suffix)); 879 } 880 881 // Resolver 882 //----------------------------------------------------------------------- 883 /** 884 * Gets the VariableResolver that is used to lookup variables. 885 * 886 * @return the VariableResolver 887 */ 888 public StrLookup<?> getVariableResolver() { 889 return this.variableResolver; 890 } 891 892 /** 893 * Sets the VariableResolver that is used to lookup variables. 894 * 895 * @param variableResolver the VariableResolver 896 */ 897 public void setVariableResolver(StrLookup<?> variableResolver) { 898 this.variableResolver = variableResolver; 899 } 900 901 // Substitution support in variable names 902 //----------------------------------------------------------------------- 903 /** 904 * Returns a flag whether substitution is done in variable names. 905 * 906 * @return the substitution in variable names flag 907 * @since 3.0 908 */ 909 public boolean isEnableSubstitutionInVariables() { 910 return enableSubstitutionInVariables; 911 } 912 913 /** 914 * Sets a flag whether substitution is done in variable names. If set to 915 * <b>true</b>, the names of variables can contain other variables which are 916 * processed first before the original variable is evaluated, e.g. 917 * <code>${jre-${java.version}}</code>. The default value is <b>false</b>. 918 * 919 * @param enableSubstitutionInVariables the new value of the flag 920 * @since 3.0 921 */ 922 public void setEnableSubstitutionInVariables( 923 boolean enableSubstitutionInVariables) { 924 this.enableSubstitutionInVariables = enableSubstitutionInVariables; 925 } 926 }