1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache license, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the license for the specific language governing permissions and 15 * limitations under the license. 16 */ 17 package org.apache.logging.log4j.core.lookup; 18 19 import java.util.ArrayList; 20 import java.util.Enumeration; 21 import java.util.HashMap; 22 import java.util.Iterator; 23 import java.util.List; 24 import java.util.Map; 25 import java.util.Properties; 26 27 import org.apache.logging.log4j.core.LogEvent; 28 import org.apache.logging.log4j.core.config.Configuration; 29 import org.apache.logging.log4j.core.config.ConfigurationAware; 30 import org.apache.logging.log4j.util.Strings; 31 32 /** 33 * Substitutes variables within a string by values. 34 * <p> 35 * This class takes a piece of text and substitutes all the variables within it. 36 * The default definition of a variable is <code>${variableName}</code>. 37 * The prefix and suffix can be changed via constructors and set methods. 38 * </p> 39 * <p> 40 * Variable values are typically resolved from a map, but could also be resolved 41 * from system properties, or by supplying a custom variable resolver. 42 * </p> 43 * <p> 44 * The simplest example is to use this class to replace Java System properties. For example: 45 * </p> 46 * <pre> 47 * StrSubstitutor.replaceSystemProperties( 48 * "You are running with java.version = ${java.version} and os.name = ${os.name}."); 49 * </pre> 50 * <p> 51 * Typical usage of this class follows the following pattern: First an instance is created 52 * and initialized with the map that contains the values for the available variables. 53 * If a prefix and/or suffix for variables should be used other than the default ones, 54 * the appropriate settings can be performed. After that the <code>replace()</code> 55 * method can be called passing in the source text for interpolation. In the returned 56 * text all variable references (as long as their values are known) will be resolved. 57 * The following example demonstrates this: 58 * </p> 59 * <pre> 60 * Map valuesMap = HashMap(); 61 * valuesMap.put("animal", "quick brown fox"); 62 * valuesMap.put("target", "lazy dog"); 63 * String templateString = "The ${animal} jumped over the ${target}."; 64 * StrSubstitutor sub = new StrSubstitutor(valuesMap); 65 * String resolvedString = sub.replace(templateString); 66 * </pre> 67 * <p>yielding:</p> 68 * <pre> 69 * The quick brown fox jumped over the lazy dog. 70 * </pre> 71 * <p> 72 * Also, this class allows to set a default value for unresolved variables. 73 * The default value for a variable can be appended to the variable name after the variable 74 * default value delimiter. The default value of the variable default value delimiter is ':-', 75 * as in bash and other *nix shells, as those are arguably where the default ${} delimiter set originated. 76 * The variable default value delimiter can be manually set by calling {@link #setValueDelimiterMatcher(StrMatcher)}, 77 * {@link #setValueDelimiter(char)} or {@link #setValueDelimiter(String)}. 78 * The following shows an example with variable default value settings: 79 * </p> 80 * <pre> 81 * Map valuesMap = HashMap(); 82 * valuesMap.put("animal", "quick brown fox"); 83 * valuesMap.put("target", "lazy dog"); 84 * String templateString = "The ${animal} jumped over the ${target}. ${undefined.number:-1234567890}."; 85 * StrSubstitutor sub = new StrSubstitutor(valuesMap); 86 * String resolvedString = sub.replace(templateString); 87 * </pre> 88 * <p>yielding:</p> 89 * <pre> 90 * The quick brown fox jumped over the lazy dog. 1234567890. 91 * </pre> 92 * <p> 93 * In addition to this usage pattern there are some static convenience methods that 94 * cover the most common use cases. These methods can be used without the need of 95 * manually creating an instance. However if multiple replace operations are to be 96 * performed, creating and reusing an instance of this class will be more efficient. 97 * </p> 98 * <p> 99 * Variable replacement works in a recursive way. Thus, if a variable value contains 100 * a variable then that variable will also be replaced. Cyclic replacements are 101 * detected and will cause an exception to be thrown. 102 * </p> 103 * <p> 104 * Sometimes the interpolation's result must contain a variable prefix. As an example 105 * take the following source text: 106 * </p> 107 * <pre> 108 * The variable ${${name}} must be used. 109 * </pre> 110 * <p> 111 * Here only the variable's name referred to in the text should be replaced resulting 112 * in the text (assuming that the value of the <code>name</code> variable is <code>x</code>): 113 * </p> 114 * <pre> 115 * The variable ${x} must be used. 116 * </pre> 117 * <p> 118 * To achieve this effect there are two possibilities: Either set a different prefix 119 * and suffix for variables which do not conflict with the result text you want to 120 * produce. The other possibility is to use the escape character, by default '$'. 121 * If this character is placed before a variable reference, this reference is ignored 122 * and won't be replaced. For example: 123 * </p> 124 * <pre> 125 * The variable $${${name}} must be used. 126 * </pre> 127 * <p> 128 * In some complex scenarios you might even want to perform substitution in the 129 * names of variables, for instance 130 * </p> 131 * <pre> 132 * ${jre-${java.specification.version}} 133 * </pre> 134 * <p> 135 * <code>StrSubstitutor</code> supports this recursive substitution in variable 136 * names, but it has to be enabled explicitly by setting the 137 * {@link #setEnableSubstitutionInVariables(boolean) enableSubstitutionInVariables} 138 * property to <b>true</b>. 139 * </p> 140 */ 141 public class StrSubstitutor implements ConfigurationAware { 142 143 /** 144 * Constant for the default escape character. 145 */ 146 public static final char DEFAULT_ESCAPE = '$'; 147 148 /** 149 * Constant for the default variable prefix. 150 */ 151 public static final StrMatcher DEFAULT_PREFIX = StrMatcher.stringMatcher(DEFAULT_ESCAPE + "{"); 152 153 /** 154 * Constant for the default variable suffix. 155 */ 156 public static final StrMatcher DEFAULT_SUFFIX = StrMatcher.stringMatcher("}"); 157 158 /** 159 * Constant for the default value delimiter of a variable. 160 */ 161 public static final String DEFAULT_VALUE_DELIMITER_STRING = ":-"; 162 public static final StrMatcher DEFAULT_VALUE_DELIMITER = StrMatcher.stringMatcher(DEFAULT_VALUE_DELIMITER_STRING); 163 164 public static final String ESCAPE_DELIMITER_STRING = ":\\-"; 165 public static final StrMatcher DEFAULT_VALUE_ESCAPE_DELIMITER = StrMatcher.stringMatcher(ESCAPE_DELIMITER_STRING); 166 167 private static final int BUF_SIZE = 256; 168 169 /** 170 * Stores the escape character. 171 */ 172 private char escapeChar; 173 174 /** 175 * Stores the variable prefix. 176 */ 177 private StrMatcher prefixMatcher; 178 179 /** 180 * Stores the variable suffix. 181 */ 182 private StrMatcher suffixMatcher; 183 184 /** 185 * Stores the default variable value delimiter 186 */ 187 private String valueDelimiterString; 188 private StrMatcher valueDelimiterMatcher; 189 190 /** 191 * Escape string to avoid matching the value delimiter matcher; 192 */ 193 private StrMatcher valueEscapeDelimiterMatcher; 194 195 /** 196 * Variable resolution is delegated to an implementer of VariableResolver. 197 */ 198 private StrLookup variableResolver; 199 200 /** 201 * The flag whether substitution in variable names is enabled. 202 */ 203 private boolean enableSubstitutionInVariables = true; 204 205 /** 206 * The currently active Configuration for use by ConfigurationAware StrLookup implementations. 207 */ 208 private Configuration configuration; 209 210 //----------------------------------------------------------------------- 211 /** 212 * Creates a new instance with defaults for variable prefix and suffix 213 * and the escaping character. 214 */ 215 public StrSubstitutor() { 216 this(null, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); 217 } 218 219 /** 220 * Creates a new instance and initializes it. Uses defaults for variable 221 * prefix and suffix and the escaping character. 222 * 223 * @param valueMap the map with the variables' values, may be null 224 */ 225 public StrSubstitutor(final Map<String, String> valueMap) { 226 this(new MapLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); 227 } 228 229 /** 230 * Creates a new instance and initializes it. Uses a default escaping character. 231 * 232 * @param valueMap the map with the variables' values, may be null 233 * @param prefix the prefix for variables, not null 234 * @param suffix the suffix for variables, not null 235 * @throws IllegalArgumentException if the prefix or suffix is null 236 */ 237 public StrSubstitutor(final Map<String, String> valueMap, final String prefix, final String suffix) { 238 this(new MapLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE); 239 } 240 241 /** 242 * Creates a new instance and initializes it. 243 * 244 * @param valueMap the map with the variables' values, may be null 245 * @param prefix the prefix for variables, not null 246 * @param suffix the suffix for variables, not null 247 * @param escape the escape character 248 * @throws IllegalArgumentException if the prefix or suffix is null 249 */ 250 public StrSubstitutor(final Map<String, String> valueMap, final String prefix, final String suffix, 251 final char escape) { 252 this(new MapLookup(valueMap), prefix, suffix, escape); 253 } 254 255 /** 256 * Creates a new instance and initializes it. 257 * 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 * @param escape the escape character 262 * @param valueDelimiter the variable default value delimiter, may be null 263 * @throws IllegalArgumentException if the prefix or suffix is null 264 */ 265 public StrSubstitutor(final Map<String, String> valueMap, final String prefix, final String suffix, 266 final char escape, final String valueDelimiter) { 267 this(new MapLookup(valueMap), prefix, suffix, escape, valueDelimiter); 268 } 269 270 /** 271 * Creates a new instance and initializes it. Uses defaults for variable 272 * prefix and suffix and the escaping character. 273 * 274 * @param properties the map with the variables' values, may be null 275 */ 276 public StrSubstitutor(final Properties properties) { 277 this(toTypeSafeMap(properties)); 278 } 279 280 /** 281 * Creates a new instance and initializes it. 282 * 283 * @param variableResolver the variable resolver, may be null 284 */ 285 public StrSubstitutor(final StrLookup variableResolver) { 286 this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); 287 } 288 289 /** 290 * Creates a new instance and initializes it. 291 * 292 * @param variableResolver the variable resolver, may be null 293 * @param prefix the prefix for variables, not null 294 * @param suffix the suffix for variables, not null 295 * @param escape the escape character 296 * @throws IllegalArgumentException if the prefix or suffix is null 297 */ 298 public StrSubstitutor(final StrLookup variableResolver, final String prefix, final String suffix, 299 final char escape) { 300 this.setVariableResolver(variableResolver); 301 this.setVariablePrefix(prefix); 302 this.setVariableSuffix(suffix); 303 this.setEscapeChar(escape); 304 } 305 306 /** 307 * Creates a new instance and initializes it. 308 * 309 * @param variableResolver the variable resolver, may be null 310 * @param prefix the prefix for variables, not null 311 * @param suffix the suffix for variables, not null 312 * @param escape the escape character 313 * @param valueDelimiter the variable default value delimiter string, may be null 314 * @throws IllegalArgumentException if the prefix or suffix is null 315 */ 316 public StrSubstitutor(final StrLookup variableResolver, final String prefix, final String suffix, final char escape, final String valueDelimiter) { 317 this.setVariableResolver(variableResolver); 318 this.setVariablePrefix(prefix); 319 this.setVariableSuffix(suffix); 320 this.setEscapeChar(escape); 321 this.setValueDelimiter(valueDelimiter); 322 } 323 324 /** 325 * Creates a new instance and initializes it. 326 * 327 * @param variableResolver the variable resolver, may be null 328 * @param prefixMatcher the prefix for variables, not null 329 * @param suffixMatcher the suffix for variables, not null 330 * @param escape the escape character 331 * @throws IllegalArgumentException if the prefix or suffix is null 332 */ 333 public StrSubstitutor(final StrLookup variableResolver, final StrMatcher prefixMatcher, 334 final StrMatcher suffixMatcher, 335 final char escape) { 336 this(variableResolver, prefixMatcher, suffixMatcher, escape, DEFAULT_VALUE_DELIMITER, 337 DEFAULT_VALUE_ESCAPE_DELIMITER); 338 this.valueDelimiterString = DEFAULT_VALUE_DELIMITER_STRING; 339 } 340 341 /** 342 * Creates a new instance and initializes it. 343 * 344 * @param variableResolver the variable resolver, may be null 345 * @param prefixMatcher the prefix for variables, not null 346 * @param suffixMatcher the suffix for variables, not null 347 * @param escape the escape character 348 * @param valueDelimiterMatcher the variable default value delimiter matcher, may be null 349 * @throws IllegalArgumentException if the prefix or suffix is null 350 */ 351 public StrSubstitutor(final StrLookup variableResolver, final StrMatcher prefixMatcher, 352 final StrMatcher suffixMatcher, final char escape, final StrMatcher valueDelimiterMatcher) { 353 this.setVariableResolver(variableResolver); 354 this.setVariablePrefixMatcher(prefixMatcher); 355 this.setVariableSuffixMatcher(suffixMatcher); 356 this.setEscapeChar(escape); 357 this.setValueDelimiterMatcher(valueDelimiterMatcher); 358 } 359 360 /** 361 * Creates a new instance and initializes it. 362 * 363 * @param variableResolver the variable resolver, may be null 364 * @param prefixMatcher the prefix for variables, not null 365 * @param suffixMatcher the suffix for variables, not null 366 * @param escape the escape character 367 * @param valueDelimiterMatcher the variable default value delimiter matcher, may be null 368 * @param valueEscapeMatcher the matcher to escape defaulting, may be null. 369 * @throws IllegalArgumentException if the prefix or suffix is null 370 */ 371 public StrSubstitutor(final StrLookup variableResolver, final StrMatcher prefixMatcher, 372 final StrMatcher suffixMatcher, final char escape, final StrMatcher valueDelimiterMatcher, 373 final StrMatcher valueEscapeMatcher) { 374 this.setVariableResolver(variableResolver); 375 this.setVariablePrefixMatcher(prefixMatcher); 376 this.setVariableSuffixMatcher(suffixMatcher); 377 this.setEscapeChar(escape); 378 this.setValueDelimiterMatcher(valueDelimiterMatcher); 379 valueEscapeDelimiterMatcher = valueEscapeMatcher; 380 } 381 382 //----------------------------------------------------------------------- 383 /** 384 * Replaces all the occurrences of variables in the given source object with 385 * their matching values from the map. 386 * 387 * @param source the source text containing the variables to substitute, null returns null 388 * @param valueMap the map with the values, may be null 389 * @return the result of the replace operation 390 */ 391 public static String replace(final Object source, final Map<String, String> valueMap) { 392 return new StrSubstitutor(valueMap).replace(source); 393 } 394 395 /** 396 * Replaces all the occurrences of variables in the given source object with 397 * their matching values from the map. This method allows to specify a 398 * custom variable prefix and suffix 399 * 400 * @param source the source text containing the variables to substitute, null returns null 401 * @param valueMap the map with the values, may be null 402 * @param prefix the prefix of variables, not null 403 * @param suffix the suffix of variables, not null 404 * @return the result of the replace operation 405 * @throws IllegalArgumentException if the prefix or suffix is null 406 */ 407 public static String replace(final Object source, final Map<String, String> valueMap, final String prefix, 408 final String suffix) { 409 return new StrSubstitutor(valueMap, prefix, suffix).replace(source); 410 } 411 412 /** 413 * Replaces all the occurrences of variables in the given source object with their matching 414 * values from the properties. 415 * 416 * @param source the source text containing the variables to substitute, null returns null 417 * @param valueProperties the properties with values, may be null 418 * @return the result of the replace operation 419 */ 420 public static String replace(final Object source, final Properties valueProperties) { 421 if (valueProperties == null) { 422 return source.toString(); 423 } 424 final Map<String, String> valueMap = new HashMap<>(); 425 final Enumeration<?> propNames = valueProperties.propertyNames(); 426 while (propNames.hasMoreElements()) { 427 final String propName = (String) propNames.nextElement(); 428 final String propValue = valueProperties.getProperty(propName); 429 valueMap.put(propName, propValue); 430 } 431 return StrSubstitutor.replace(source, valueMap); 432 } 433 434 private static Map<String, String> toTypeSafeMap(final Properties properties) { 435 final Map<String, String> map = new HashMap<>(properties.size()); 436 for (final String name : properties.stringPropertyNames()) { 437 map.put(name, properties.getProperty(name)); 438 } 439 return map; 440 } 441 442 //----------------------------------------------------------------------- 443 /** 444 * Replaces all the occurrences of variables with their matching values 445 * from the resolver using the given source string as a template. 446 * 447 * @param source the string to replace in, null returns null 448 * @return the result of the replace operation 449 */ 450 public String replace(final String source) { 451 return replace(null, source); 452 } 453 //----------------------------------------------------------------------- 454 /** 455 * Replaces all the occurrences of variables with their matching values 456 * from the resolver using the given source string as a template. 457 * 458 * @param event The current LogEvent if there is one. 459 * @param source the string to replace in, null returns null 460 * @return the result of the replace operation 461 */ 462 public String replace(final LogEvent event, final String source) { 463 if (source == null) { 464 return null; 465 } 466 final StringBuilder buf = new StringBuilder(source); 467 if (!substitute(event, buf, 0, source.length())) { 468 return source; 469 } 470 return buf.toString(); 471 } 472 473 /** 474 * Replaces all the occurrences of variables with their matching values 475 * from the resolver using the given source string as a template. 476 * <p> 477 * Only the specified portion of the string will be processed. 478 * The rest of the string is not processed, and is not returned. 479 * </p> 480 * 481 * @param source the string to replace in, null returns null 482 * @param offset the start offset within the array, must be valid 483 * @param length the length within the array to be processed, must be valid 484 * @return the result of the replace operation 485 */ 486 public String replace(final String source, final int offset, final int length) { 487 return replace(null, source, offset, length); 488 } 489 490 /** 491 * Replaces all the occurrences of variables with their matching values 492 * from the resolver using the given source string as a template. 493 * <p> 494 * Only the specified portion of the string will be processed. 495 * The rest of the string is not processed, and is not returned. 496 * </p> 497 * 498 * @param event the current LogEvent, if one exists. 499 * @param source the string to replace in, null returns null 500 * @param offset the start offset within the array, must be valid 501 * @param length the length within the array to be processed, must be valid 502 * @return the result of the replace operation 503 */ 504 public String replace(final LogEvent event, final String source, final int offset, final int length) { 505 if (source == null) { 506 return null; 507 } 508 final StringBuilder buf = new StringBuilder(length).append(source, offset, length); 509 if (!substitute(event, buf, 0, length)) { 510 return source.substring(offset, offset + length); 511 } 512 return buf.toString(); 513 } 514 515 //----------------------------------------------------------------------- 516 /** 517 * Replaces all the occurrences of variables with their matching values 518 * from the resolver using the given source array as a template. 519 * The array is not altered by this method. 520 * 521 * @param source the character array to replace in, not altered, null returns null 522 * @return the result of the replace operation 523 */ 524 public String replace(final char[] source) { 525 return replace(null, source); 526 } 527 528 //----------------------------------------------------------------------- 529 /** 530 * Replaces all the occurrences of variables with their matching values 531 * from the resolver using the given source array as a template. 532 * The array is not altered by this method. 533 * 534 * @param event the current LogEvent, if one exists. 535 * @param source the character array to replace in, not altered, null returns null 536 * @return the result of the replace operation 537 */ 538 public String replace(final LogEvent event, final char[] source) { 539 if (source == null) { 540 return null; 541 } 542 final StringBuilder buf = new StringBuilder(source.length).append(source); 543 substitute(event, buf, 0, source.length); 544 return buf.toString(); 545 } 546 547 /** 548 * Replaces all the occurrences of variables with their matching values 549 * from the resolver using the given source array as a template. 550 * The array is not altered by this method. 551 * <p> 552 * Only the specified portion of the array will be processed. 553 * The rest of the array is not processed, and is not returned. 554 * </p> 555 * 556 * @param source the character array to replace in, not altered, null returns null 557 * @param offset the start offset within the array, must be valid 558 * @param length the length within the array to be processed, must be valid 559 * @return the result of the replace operation 560 */ 561 public String replace(final char[] source, final int offset, final int length) { 562 return replace(null, source, offset, length); 563 } 564 565 /** 566 * Replaces all the occurrences of variables with their matching values 567 * from the resolver using the given source array as a template. 568 * The array is not altered by this method. 569 * <p> 570 * Only the specified portion of the array will be processed. 571 * The rest of the array is not processed, and is not returned. 572 * </p> 573 * 574 * @param event the current LogEvent, if one exists. 575 * @param source the character array to replace in, not altered, null returns null 576 * @param offset the start offset within the array, must be valid 577 * @param length the length within the array to be processed, must be valid 578 * @return the result of the replace operation 579 */ 580 public String replace(final LogEvent event, final char[] source, final int offset, final int length) { 581 if (source == null) { 582 return null; 583 } 584 final StringBuilder buf = new StringBuilder(length).append(source, offset, length); 585 substitute(event, buf, 0, length); 586 return buf.toString(); 587 } 588 589 //----------------------------------------------------------------------- 590 /** 591 * Replaces all the occurrences of variables with their matching values 592 * from the resolver using the given source buffer as a template. 593 * The buffer is not altered by this method. 594 * 595 * @param source the buffer to use as a template, not changed, null returns null 596 * @return the result of the replace operation 597 */ 598 public String replace(final StringBuffer source) { 599 return replace(null, source); 600 } 601 602 //----------------------------------------------------------------------- 603 /** 604 * Replaces all the occurrences of variables with their matching values 605 * from the resolver using the given source buffer as a template. 606 * The buffer is not altered by this method. 607 * 608 * @param event the current LogEvent, if one exists. 609 * @param source the buffer to use as a template, not changed, null returns null 610 * @return the result of the replace operation 611 */ 612 public String replace(final LogEvent event, final StringBuffer source) { 613 if (source == null) { 614 return null; 615 } 616 final StringBuilder buf = new StringBuilder(source.length()).append(source); 617 substitute(event, buf, 0, buf.length()); 618 return buf.toString(); 619 } 620 621 /** 622 * Replaces all the occurrences of variables with their matching values 623 * from the resolver using the given source buffer as a template. 624 * The buffer is not altered by this method. 625 * <p> 626 * Only the specified portion of the buffer will be processed. 627 * The rest of the buffer is not processed, and is not returned. 628 * </p> 629 * 630 * @param source the buffer to use as a template, not changed, null returns null 631 * @param offset the start offset within the array, must be valid 632 * @param length the length within the array to be processed, must be valid 633 * @return the result of the replace operation 634 */ 635 public String replace(final StringBuffer source, final int offset, final int length) { 636 return replace(null, source, offset, length); 637 } 638 639 /** 640 * Replaces all the occurrences of variables with their matching values 641 * from the resolver using the given source buffer as a template. 642 * The buffer is not altered by this method. 643 * <p> 644 * Only the specified portion of the buffer will be processed. 645 * The rest of the buffer is not processed, and is not returned. 646 * </p> 647 * 648 * @param event the current LogEvent, if one exists. 649 * @param source the buffer to use as a template, not changed, null returns null 650 * @param offset the start offset within the array, must be valid 651 * @param length the length within the array to be processed, must be valid 652 * @return the result of the replace operation 653 */ 654 public String replace(final LogEvent event, final StringBuffer source, final int offset, final int length) { 655 if (source == null) { 656 return null; 657 } 658 final StringBuilder buf = new StringBuilder(length).append(source, offset, length); 659 substitute(event, buf, 0, length); 660 return buf.toString(); 661 } 662 663 //----------------------------------------------------------------------- 664 /** 665 * Replaces all the occurrences of variables with their matching values 666 * from the resolver using the given source builder as a template. 667 * The builder is not altered by this method. 668 * 669 * @param source the builder to use as a template, not changed, null returns null 670 * @return the result of the replace operation 671 */ 672 public String replace(final StringBuilder source) { 673 return replace(null, source); 674 } 675 676 //----------------------------------------------------------------------- 677 /** 678 * Replaces all the occurrences of variables with their matching values 679 * from the resolver using the given source builder as a template. 680 * The builder is not altered by this method. 681 * 682 * @param event The LogEvent. 683 * @param source the builder to use as a template, not changed, null returns null. 684 * @return the result of the replace operation. 685 */ 686 public String replace(final LogEvent event, final StringBuilder source) { 687 if (source == null) { 688 return null; 689 } 690 final StringBuilder buf = new StringBuilder(source.length()).append(source); 691 substitute(event, buf, 0, buf.length()); 692 return buf.toString(); 693 } 694 /** 695 * Replaces all the occurrences of variables with their matching values 696 * from the resolver using the given source builder as a template. 697 * The builder is not altered by this method. 698 * <p> 699 * Only the specified portion of the builder will be processed. 700 * The rest of the builder is not processed, and is not returned. 701 * </p> 702 * 703 * @param source the builder to use as a template, not changed, null returns null 704 * @param offset the start offset within the array, must be valid 705 * @param length the length within the array to be processed, must be valid 706 * @return the result of the replace operation 707 */ 708 public String replace(final StringBuilder source, final int offset, final int length) { 709 return replace(null, source, offset, length); 710 } 711 712 /** 713 * Replaces all the occurrences of variables with their matching values 714 * from the resolver using the given source builder as a template. 715 * The builder is not altered by this method. 716 * <p> 717 * Only the specified portion of the builder will be processed. 718 * The rest of the builder is not processed, and is not returned. 719 * </p> 720 * 721 * @param event the current LogEvent, if one exists. 722 * @param source the builder to use as a template, not changed, null returns null 723 * @param offset the start offset within the array, must be valid 724 * @param length the length within the array to be processed, must be valid 725 * @return the result of the replace operation 726 */ 727 public String replace(final LogEvent event, final StringBuilder source, final int offset, final int length) { 728 if (source == null) { 729 return null; 730 } 731 final StringBuilder buf = new StringBuilder(length).append(source, offset, length); 732 substitute(event, buf, 0, length); 733 return buf.toString(); 734 } 735 736 //----------------------------------------------------------------------- 737 /** 738 * Replaces all the occurrences of variables in the given source object with 739 * their matching values from the resolver. The input source object is 740 * converted to a string using <code>toString</code> and is not altered. 741 * 742 * @param source the source to replace in, null returns null 743 * @return the result of the replace operation 744 */ 745 public String replace(final Object source) { 746 return replace(null, source); 747 } 748 //----------------------------------------------------------------------- 749 /** 750 * Replaces all the occurrences of variables in the given source object with 751 * their matching values from the resolver. The input source object is 752 * converted to a string using <code>toString</code> and is not altered. 753 * 754 * @param event the current LogEvent, if one exists. 755 * @param source the source to replace in, null returns null 756 * @return the result of the replace operation 757 */ 758 public String replace(final LogEvent event, final Object source) { 759 if (source == null) { 760 return null; 761 } 762 final StringBuilder buf = new StringBuilder().append(source); 763 substitute(event, buf, 0, buf.length()); 764 return buf.toString(); 765 } 766 767 //----------------------------------------------------------------------- 768 /** 769 * Replaces all the occurrences of variables within the given source buffer 770 * with their matching values from the resolver. 771 * The buffer is updated with the result. 772 * 773 * @param source the buffer to replace in, updated, null returns zero 774 * @return true if altered 775 */ 776 public boolean replaceIn(final StringBuffer source) { 777 if (source == null) { 778 return false; 779 } 780 return replaceIn(source, 0, source.length()); 781 } 782 783 /** 784 * Replaces all the occurrences of variables within the given source buffer 785 * with their matching values from the resolver. 786 * The buffer is updated with the result. 787 * <p> 788 * Only the specified portion of the buffer will be processed. 789 * The rest of the buffer is not processed, but it is not deleted. 790 * </p> 791 * 792 * @param source the buffer to replace in, updated, null returns zero 793 * @param offset the start offset within the array, must be valid 794 * @param length the length within the buffer to be processed, must be valid 795 * @return true if altered 796 */ 797 public boolean replaceIn(final StringBuffer source, final int offset, final int length) { 798 return replaceIn(null, source, offset, length); 799 } 800 801 /** 802 * Replaces all the occurrences of variables within the given source buffer 803 * with their matching values from the resolver. 804 * The buffer is updated with the result. 805 * <p> 806 * Only the specified portion of the buffer will be processed. 807 * The rest of the buffer is not processed, but it is not deleted. 808 * </p> 809 * 810 * @param event the current LogEvent, if one exists. 811 * @param source the buffer to replace in, updated, null returns zero 812 * @param offset the start offset within the array, must be valid 813 * @param length the length within the buffer to be processed, must be valid 814 * @return true if altered 815 */ 816 public boolean replaceIn(final LogEvent event, final StringBuffer source, final int offset, final int length) { 817 if (source == null) { 818 return false; 819 } 820 final StringBuilder buf = new StringBuilder(length).append(source, offset, length); 821 if (!substitute(event, buf, 0, length)) { 822 return false; 823 } 824 source.replace(offset, offset + length, buf.toString()); 825 return true; 826 } 827 828 //----------------------------------------------------------------------- 829 /** 830 * Replaces all the occurrences of variables within the given source 831 * builder with their matching values from the resolver. 832 * 833 * @param source the builder to replace in, updated, null returns zero 834 * @return true if altered 835 */ 836 public boolean replaceIn(final StringBuilder source) { 837 return replaceIn(null, source); 838 } 839 840 //----------------------------------------------------------------------- 841 /** 842 * Replaces all the occurrences of variables within the given source 843 * builder with their matching values from the resolver. 844 * 845 * @param event the current LogEvent, if one exists. 846 * @param source the builder to replace in, updated, null returns zero 847 * @return true if altered 848 */ 849 public boolean replaceIn(final LogEvent event, final StringBuilder source) { 850 if (source == null) { 851 return false; 852 } 853 return substitute(event, source, 0, source.length()); 854 } 855 /** 856 * Replaces all the occurrences of variables within the given source 857 * builder with their matching values from the resolver. 858 * <p> 859 * Only the specified portion of the builder will be processed. 860 * The rest of the builder is not processed, but it is not deleted. 861 * </p> 862 * 863 * @param source the builder to replace in, null returns zero 864 * @param offset the start offset within the array, must be valid 865 * @param length the length within the builder to be processed, must be valid 866 * @return true if altered 867 */ 868 public boolean replaceIn(final StringBuilder source, final int offset, final int length) { 869 return replaceIn(null, source, offset, length); 870 } 871 872 /** 873 * Replaces all the occurrences of variables within the given source 874 * builder with their matching values from the resolver. 875 * <p> 876 * Only the specified portion of the builder will be processed. 877 * The rest of the builder is not processed, but it is not deleted. 878 * </p> 879 * 880 * @param event the current LogEvent, if one is present. 881 * @param source the builder to replace in, null returns zero 882 * @param offset the start offset within the array, must be valid 883 * @param length the length within the builder to be processed, must be valid 884 * @return true if altered 885 */ 886 public boolean replaceIn(final LogEvent event, final StringBuilder source, final int offset, final int length) { 887 if (source == null) { 888 return false; 889 } 890 return substitute(event, source, offset, length); 891 } 892 893 //----------------------------------------------------------------------- 894 /** 895 * Internal method that substitutes the variables. 896 * <p> 897 * Most users of this class do not need to call this method. This method will 898 * be called automatically by another (public) method. 899 * </p> 900 * <p> 901 * Writers of subclasses can override this method if they need access to 902 * the substitution process at the start or end. 903 * </p> 904 * 905 * @param event The current LogEvent, if there is one. 906 * @param buf the string builder to substitute into, not null 907 * @param offset the start offset within the builder, must be valid 908 * @param length the length within the builder to be processed, must be valid 909 * @return true if altered 910 */ 911 protected boolean substitute(final LogEvent event, final StringBuilder buf, final int offset, final int length) { 912 return substitute(event, buf, offset, length, null) > 0; 913 } 914 915 /** 916 * Recursive handler for multiple levels of interpolation. This is the main 917 * interpolation method, which resolves the values of all variable references 918 * contained in the passed in text. 919 * 920 * @param event The current LogEvent, if there is one. 921 * @param buf the string builder to substitute into, not null 922 * @param offset the start offset within the builder, must be valid 923 * @param length the length within the builder to be processed, must be valid 924 * @param priorVariables the stack keeping track of the replaced variables, may be null 925 * @return the length change that occurs, unless priorVariables is null when the int 926 * represents a boolean flag as to whether any change occurred. 927 */ 928 private int substitute(final LogEvent event, final StringBuilder buf, final int offset, final int length, 929 List<String> priorVariables) { 930 final StrMatcher prefixMatcher = getVariablePrefixMatcher(); 931 final StrMatcher suffixMatcher = getVariableSuffixMatcher(); 932 final char escape = getEscapeChar(); 933 final StrMatcher valueDelimiterMatcher = getValueDelimiterMatcher(); 934 final boolean substitutionInVariablesEnabled = isEnableSubstitutionInVariables(); 935 936 final boolean top = priorVariables == null; 937 boolean altered = false; 938 int lengthChange = 0; 939 char[] chars = getChars(buf); 940 int bufEnd = offset + length; 941 int pos = offset; 942 while (pos < bufEnd) { 943 final int startMatchLen = prefixMatcher.isMatch(chars, pos, offset, bufEnd); 944 if (startMatchLen == 0) { 945 pos++; 946 } else { 947 // found variable start marker 948 if (pos > offset && chars[pos - 1] == escape) { 949 // escaped 950 buf.deleteCharAt(pos - 1); 951 chars = getChars(buf); 952 lengthChange--; 953 altered = true; 954 bufEnd--; 955 } else { 956 // find suffix 957 final int startPos = pos; 958 pos += startMatchLen; 959 int endMatchLen = 0; 960 int nestedVarCount = 0; 961 while (pos < bufEnd) { 962 if (substitutionInVariablesEnabled 963 && (endMatchLen = prefixMatcher.isMatch(chars, pos, offset, bufEnd)) != 0) { 964 // found a nested variable start 965 nestedVarCount++; 966 pos += endMatchLen; 967 continue; 968 } 969 970 endMatchLen = suffixMatcher.isMatch(chars, pos, offset, bufEnd); 971 if (endMatchLen == 0) { 972 pos++; 973 } else { 974 // found variable end marker 975 if (nestedVarCount == 0) { 976 String varNameExpr = new String(chars, startPos + startMatchLen, pos - startPos - startMatchLen); 977 if (substitutionInVariablesEnabled) { 978 final StringBuilder bufName = new StringBuilder(varNameExpr); 979 substitute(event, bufName, 0, bufName.length()); 980 varNameExpr = bufName.toString(); 981 } 982 pos += endMatchLen; 983 final int endPos = pos; 984 985 String varName = varNameExpr; 986 String varDefaultValue = null; 987 988 if (valueDelimiterMatcher != null) { 989 final char [] varNameExprChars = varNameExpr.toCharArray(); 990 int valueDelimiterMatchLen = 0; 991 for (int i = 0; i < varNameExprChars.length; i++) { 992 // if there's any nested variable when nested variable substitution disabled, then stop resolving name and default value. 993 if (!substitutionInVariablesEnabled 994 && prefixMatcher.isMatch(varNameExprChars, i, i, varNameExprChars.length) != 0) { 995 break; 996 } 997 if (valueEscapeDelimiterMatcher != null) { 998 int matchLen = valueEscapeDelimiterMatcher.isMatch(varNameExprChars, i); 999 if (matchLen != 0) { 1000 String varNamePrefix = varNameExpr.substring(0, i) + Interpolator.PREFIX_SEPARATOR; 1001 varName = varNamePrefix + varNameExpr.substring(i + matchLen - 1); 1002 for (int j = i + matchLen; j < varNameExprChars.length; ++j){ 1003 if ((valueDelimiterMatchLen = valueDelimiterMatcher.isMatch(varNameExprChars, j)) != 0) { 1004 varName = varNamePrefix + varNameExpr.substring(i + matchLen, j); 1005 varDefaultValue = varNameExpr.substring(j + valueDelimiterMatchLen); 1006 break; 1007 } 1008 } 1009 break; 1010 } else { 1011 if ((valueDelimiterMatchLen = valueDelimiterMatcher.isMatch(varNameExprChars, i)) != 0) { 1012 varName = varNameExpr.substring(0, i); 1013 varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen); 1014 break; 1015 } 1016 } 1017 } else { 1018 if ((valueDelimiterMatchLen = valueDelimiterMatcher.isMatch(varNameExprChars, i)) != 0) { 1019 varName = varNameExpr.substring(0, i); 1020 varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen); 1021 break; 1022 } 1023 } 1024 } 1025 } 1026 1027 // on the first call initialize priorVariables 1028 if (priorVariables == null) { 1029 priorVariables = new ArrayList<>(); 1030 priorVariables.add(new String(chars, offset, length + lengthChange)); 1031 } 1032 1033 // handle cyclic substitution 1034 checkCyclicSubstitution(varName, priorVariables); 1035 priorVariables.add(varName); 1036 1037 // resolve the variable 1038 String varValue = resolveVariable(event, varName, buf, startPos, endPos); 1039 if (varValue == null) { 1040 varValue = varDefaultValue; 1041 } 1042 if (varValue != null) { 1043 // recursive replace 1044 final int varLen = varValue.length(); 1045 buf.replace(startPos, endPos, varValue); 1046 altered = true; 1047 int change = substitute(event, buf, startPos, varLen, priorVariables); 1048 change = change + (varLen - (endPos - startPos)); 1049 pos += change; 1050 bufEnd += change; 1051 lengthChange += change; 1052 chars = getChars(buf); // in case buffer was altered 1053 } 1054 1055 // remove variable from the cyclic stack 1056 priorVariables.remove(priorVariables.size() - 1); 1057 break; 1058 } 1059 nestedVarCount--; 1060 pos += endMatchLen; 1061 } 1062 } 1063 } 1064 } 1065 } 1066 if (top) { 1067 return altered ? 1 : 0; 1068 } 1069 return lengthChange; 1070 } 1071 1072 /** 1073 * Checks if the specified variable is already in the stack (list) of variables. 1074 * 1075 * @param varName the variable name to check 1076 * @param priorVariables the list of prior variables 1077 */ 1078 private void checkCyclicSubstitution(final String varName, final List<String> priorVariables) { 1079 if (!priorVariables.contains(varName)) { 1080 return; 1081 } 1082 final StringBuilder buf = new StringBuilder(BUF_SIZE); 1083 buf.append("Infinite loop in property interpolation of "); 1084 buf.append(priorVariables.remove(0)); 1085 buf.append(": "); 1086 appendWithSeparators(buf, priorVariables, "->"); 1087 throw new IllegalStateException(buf.toString()); 1088 } 1089 1090 /** 1091 * Internal method that resolves the value of a variable. 1092 * <p> 1093 * Most users of this class do not need to call this method. This method is 1094 * called automatically by the substitution process. 1095 * </p> 1096 * <p> 1097 * Writers of subclasses can override this method if they need to alter 1098 * how each substitution occurs. The method is passed the variable's name 1099 * and must return the corresponding value. This implementation uses the 1100 * {@link #getVariableResolver()} with the variable's name as the key. 1101 * </p> 1102 * 1103 * @param event The LogEvent, if there is one. 1104 * @param variableName the name of the variable, not null 1105 * @param buf the buffer where the substitution is occurring, not null 1106 * @param startPos the start position of the variable including the prefix, valid 1107 * @param endPos the end position of the variable including the suffix, valid 1108 * @return the variable's value or <b>null</b> if the variable is unknown 1109 */ 1110 protected String resolveVariable(final LogEvent event, final String variableName, final StringBuilder buf, 1111 final int startPos, final int endPos) { 1112 final StrLookup resolver = getVariableResolver(); 1113 if (resolver == null) { 1114 return null; 1115 } 1116 return resolver.lookup(event, variableName); 1117 } 1118 1119 // Escape 1120 //----------------------------------------------------------------------- 1121 /** 1122 * Returns the escape character. 1123 * 1124 * @return the character used for escaping variable references 1125 */ 1126 public char getEscapeChar() { 1127 return this.escapeChar; 1128 } 1129 1130 /** 1131 * Sets the escape character. 1132 * If this character is placed before a variable reference in the source 1133 * text, this variable will be ignored. 1134 * 1135 * @param escapeCharacter the escape character (0 for disabling escaping) 1136 */ 1137 public void setEscapeChar(final char escapeCharacter) { 1138 this.escapeChar = escapeCharacter; 1139 } 1140 1141 // Prefix 1142 //----------------------------------------------------------------------- 1143 /** 1144 * Gets the variable prefix matcher currently in use. 1145 * <p> 1146 * The variable prefix is the character or characters that identify the 1147 * start of a variable. This prefix is expressed in terms of a matcher 1148 * allowing advanced prefix matches. 1149 * </p> 1150 * 1151 * @return the prefix matcher in use 1152 */ 1153 public StrMatcher getVariablePrefixMatcher() { 1154 return prefixMatcher; 1155 } 1156 1157 /** 1158 * Sets the variable prefix matcher currently in use. 1159 * <p> 1160 * The variable prefix is the character or characters that identify the 1161 * start of a variable. This prefix is expressed in terms of a matcher 1162 * allowing advanced prefix matches. 1163 * </p> 1164 * 1165 * @param prefixMatcher the prefix matcher to use, null ignored 1166 * @return this, to enable chaining 1167 * @throws IllegalArgumentException if the prefix matcher is null 1168 */ 1169 public StrSubstitutor setVariablePrefixMatcher(final StrMatcher prefixMatcher) { 1170 if (prefixMatcher == null) { 1171 throw new IllegalArgumentException("Variable prefix matcher must not be null!"); 1172 } 1173 this.prefixMatcher = prefixMatcher; 1174 return this; 1175 } 1176 1177 /** 1178 * Sets the variable prefix to use. 1179 * <p> 1180 * The variable prefix is the character or characters that identify the 1181 * start of a variable. This method allows a single character prefix to 1182 * be easily set. 1183 * </p> 1184 * 1185 * @param prefix the prefix character to use 1186 * @return this, to enable chaining 1187 */ 1188 public StrSubstitutor setVariablePrefix(final char prefix) { 1189 return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix)); 1190 } 1191 1192 /** 1193 * Sets the variable prefix to use. 1194 * <p> 1195 * The variable prefix is the character or characters that identify the 1196 * start of a variable. This method allows a string prefix to be easily set. 1197 * </p> 1198 * 1199 * @param prefix the prefix for variables, not null 1200 * @return this, to enable chaining 1201 * @throws IllegalArgumentException if the prefix is null 1202 */ 1203 public StrSubstitutor setVariablePrefix(final String prefix) { 1204 if (prefix == null) { 1205 throw new IllegalArgumentException("Variable prefix must not be null!"); 1206 } 1207 return setVariablePrefixMatcher(StrMatcher.stringMatcher(prefix)); 1208 } 1209 1210 // Suffix 1211 //----------------------------------------------------------------------- 1212 /** 1213 * Gets the variable suffix matcher currently in use. 1214 * <p> 1215 * The variable suffix is the character or characters that identify the 1216 * end of a variable. This suffix is expressed in terms of a matcher 1217 * allowing advanced suffix matches. 1218 * </p> 1219 * 1220 * @return the suffix matcher in use 1221 */ 1222 public StrMatcher getVariableSuffixMatcher() { 1223 return suffixMatcher; 1224 } 1225 1226 /** 1227 * Sets the variable suffix matcher currently in use. 1228 * <p> 1229 * The variable suffix is the character or characters that identify the 1230 * end of a variable. This suffix is expressed in terms of a matcher 1231 * allowing advanced suffix matches. 1232 * </p> 1233 * 1234 * @param suffixMatcher the suffix matcher to use, null ignored 1235 * @return this, to enable chaining 1236 * @throws IllegalArgumentException if the suffix matcher is null 1237 */ 1238 public StrSubstitutor setVariableSuffixMatcher(final StrMatcher suffixMatcher) { 1239 if (suffixMatcher == null) { 1240 throw new IllegalArgumentException("Variable suffix matcher must not be null!"); 1241 } 1242 this.suffixMatcher = suffixMatcher; 1243 return this; 1244 } 1245 1246 /** 1247 * Sets the variable suffix to use. 1248 * <p> 1249 * The variable suffix is the character or characters that identify the 1250 * end of a variable. This method allows a single character suffix to 1251 * be easily set. 1252 * </p> 1253 * 1254 * @param suffix the suffix character to use 1255 * @return this, to enable chaining 1256 */ 1257 public StrSubstitutor setVariableSuffix(final char suffix) { 1258 return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix)); 1259 } 1260 1261 /** 1262 * Sets the variable suffix to use. 1263 * <p> 1264 * The variable suffix is the character or characters that identify the 1265 * end of a variable. This method allows a string suffix to be easily set. 1266 * </p> 1267 * 1268 * @param suffix the suffix for variables, not null 1269 * @return this, to enable chaining 1270 * @throws IllegalArgumentException if the suffix is null 1271 */ 1272 public StrSubstitutor setVariableSuffix(final String suffix) { 1273 if (suffix == null) { 1274 throw new IllegalArgumentException("Variable suffix must not be null!"); 1275 } 1276 return setVariableSuffixMatcher(StrMatcher.stringMatcher(suffix)); 1277 } 1278 1279 // Variable Default Value Delimiter 1280 //----------------------------------------------------------------------- 1281 /** 1282 * Gets the variable default value delimiter matcher currently in use. 1283 * <p> 1284 * The variable default value delimiter is the character or characters that delimit the 1285 * variable name and the variable default value. This delimiter is expressed in terms of a matcher 1286 * allowing advanced variable default value delimiter matches. 1287 * </p> 1288 * <p> 1289 * If it returns null, then the variable default value resolution is disabled. 1290 * </p> 1291 * 1292 * @return the variable default value delimiter matcher in use, may be null 1293 */ 1294 public StrMatcher getValueDelimiterMatcher() { 1295 return valueDelimiterMatcher; 1296 } 1297 1298 /** 1299 * Sets the variable default value delimiter matcher to use. 1300 * <p> 1301 * The variable default value delimiter is the character or characters that delimit the 1302 * variable name and the variable default value. This delimiter is expressed in terms of a matcher 1303 * allowing advanced variable default value delimiter matches. 1304 * </p> 1305 * <p> 1306 * If the <code>valueDelimiterMatcher</code> is null, then the variable default value resolution 1307 * becomes disabled. 1308 * </p> 1309 * 1310 * @param valueDelimiterMatcher variable default value delimiter matcher to use, may be null 1311 * @return this, to enable chaining 1312 */ 1313 public StrSubstitutor setValueDelimiterMatcher(final StrMatcher valueDelimiterMatcher) { 1314 this.valueDelimiterMatcher = valueDelimiterMatcher; 1315 return this; 1316 } 1317 1318 /** 1319 * Sets the variable default value delimiter to use. 1320 * <p> 1321 * The variable default value delimiter is the character or characters that delimit the 1322 * variable name and the variable default value. This method allows a single character 1323 * variable default value delimiter to be easily set. 1324 * </p> 1325 * 1326 * @param valueDelimiter the variable default value delimiter character to use 1327 * @return this, to enable chaining 1328 */ 1329 public StrSubstitutor setValueDelimiter(final char valueDelimiter) { 1330 return setValueDelimiterMatcher(StrMatcher.charMatcher(valueDelimiter)); 1331 } 1332 1333 /** 1334 * Sets the variable default value delimiter to use. 1335 * <p> 1336 * The variable default value delimiter is the character or characters that delimit the 1337 * variable name and the variable default value. This method allows a string 1338 * variable default value delimiter to be easily set. 1339 * </p> 1340 * <p> 1341 * If the <code>valueDelimiter</code> is null or empty string, then the variable default 1342 * value resolution becomes disabled. 1343 * </p> 1344 * 1345 * @param valueDelimiter the variable default value delimiter string to use, may be null or empty 1346 * @return this, to enable chaining 1347 */ 1348 public StrSubstitutor setValueDelimiter(final String valueDelimiter) { 1349 if (Strings.isEmpty(valueDelimiter)) { 1350 setValueDelimiterMatcher(null); 1351 return this; 1352 } 1353 String escapeValue = valueDelimiter.substring(0, valueDelimiter.length() - 1) + "\\" 1354 + valueDelimiter.substring(valueDelimiter.length() - 1); 1355 valueEscapeDelimiterMatcher = StrMatcher.stringMatcher(escapeValue); 1356 return setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter)); 1357 } 1358 1359 // Resolver 1360 //----------------------------------------------------------------------- 1361 /** 1362 * Gets the VariableResolver that is used to lookup variables. 1363 * 1364 * @return the VariableResolver 1365 */ 1366 public StrLookup getVariableResolver() { 1367 return this.variableResolver; 1368 } 1369 1370 /** 1371 * Sets the VariableResolver that is used to lookup variables. 1372 * 1373 * @param variableResolver the VariableResolver 1374 */ 1375 public void setVariableResolver(final StrLookup variableResolver) { 1376 if (variableResolver instanceof ConfigurationAware && this.configuration != null) { 1377 ((ConfigurationAware) variableResolver).setConfiguration(this.configuration); 1378 } 1379 this.variableResolver = variableResolver; 1380 } 1381 1382 // Substitution support in variable names 1383 //----------------------------------------------------------------------- 1384 /** 1385 * Returns a flag whether substitution is done in variable names. 1386 * 1387 * @return the substitution in variable names flag 1388 */ 1389 public boolean isEnableSubstitutionInVariables() { 1390 return enableSubstitutionInVariables; 1391 } 1392 1393 /** 1394 * Sets a flag whether substitution is done in variable names. If set to 1395 * <b>true</b>, the names of variables can contain other variables which are 1396 * processed first before the original variable is evaluated, e.g. 1397 * <code>${jre-${java.version}}</code>. The default value is <b>true</b>. 1398 * 1399 * @param enableSubstitutionInVariables the new value of the flag 1400 */ 1401 public void setEnableSubstitutionInVariables(final boolean enableSubstitutionInVariables) { 1402 this.enableSubstitutionInVariables = enableSubstitutionInVariables; 1403 } 1404 1405 private char[] getChars(final StringBuilder sb) { 1406 final char[] chars = new char[sb.length()]; 1407 sb.getChars(0, sb.length(), chars, 0); 1408 return chars; 1409 } 1410 1411 /** 1412 * Appends a iterable placing separators between each value, but 1413 * not before the first or after the last. 1414 * Appending a null iterable will have no effect.. 1415 * 1416 * @param sb StringBuilder that contains the String being constructed. 1417 * @param iterable the iterable to append 1418 * @param separator the separator to use, null means no separator 1419 */ 1420 public void appendWithSeparators(final StringBuilder sb, final Iterable<?> iterable, String separator) { 1421 if (iterable != null) { 1422 separator = separator == null ? Strings.EMPTY : separator; 1423 final Iterator<?> it = iterable.iterator(); 1424 while (it.hasNext()) { 1425 sb.append(it.next()); 1426 if (it.hasNext()) { 1427 sb.append(separator); 1428 } 1429 } 1430 } 1431 } 1432 1433 @Override 1434 public String toString() { 1435 return "StrSubstitutor(" + variableResolver.toString() + ')'; 1436 } 1437 1438 @Override 1439 public void setConfiguration(final Configuration configuration) { 1440 this.configuration = configuration; 1441 if (this.variableResolver instanceof ConfigurationAware) { 1442 ((ConfigurationAware) this.variableResolver).setConfiguration(this.configuration); 1443 } 1444 } 1445 }