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.status.StatusLogger; 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 { 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 = false; 204 205 /** 206 * The currently active Configuration for use by ConfigurationAware StrLookup implementations. 207 */ 208 private Configuration configuration; 209 210 private boolean recursiveEvaluationAllowed; 211 212 //----------------------------------------------------------------------- 213 /** 214 * Creates a new instance with defaults for variable prefix and suffix 215 * and the escaping character. 216 */ 217 public StrSubstitutor() { 218 this(null, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); 219 } 220 221 /** 222 * Creates a new instance and initializes it. Uses defaults for variable 223 * prefix and suffix and the escaping character. 224 * 225 * @param valueMap the map with the variables' values, may be null 226 */ 227 public StrSubstitutor(final Map<String, String> valueMap) { 228 this(new MapLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); 229 } 230 231 /** 232 * Creates a new instance and initializes it. Uses a default escaping character. 233 * 234 * @param valueMap the map with the variables' values, may be null 235 * @param prefix the prefix for variables, not null 236 * @param suffix the suffix for variables, not null 237 * @throws IllegalArgumentException if the prefix or suffix is null 238 */ 239 public StrSubstitutor(final Map<String, String> valueMap, final String prefix, final String suffix) { 240 this(new MapLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE); 241 } 242 243 /** 244 * Creates a new instance and initializes it. 245 * 246 * @param valueMap the map with the variables' values, may be null 247 * @param prefix the prefix for variables, not null 248 * @param suffix the suffix for variables, not null 249 * @param escape the escape character 250 * @throws IllegalArgumentException if the prefix or suffix is null 251 */ 252 public StrSubstitutor(final Map<String, String> valueMap, final String prefix, final String suffix, 253 final char escape) { 254 this(new MapLookup(valueMap), prefix, suffix, escape); 255 } 256 257 /** 258 * Creates a new instance and initializes it. 259 * 260 * @param valueMap the map with the variables' values, may be null 261 * @param prefix the prefix for variables, not null 262 * @param suffix the suffix for variables, not null 263 * @param escape the escape character 264 * @param valueDelimiter the variable default value delimiter, may be null 265 * @throws IllegalArgumentException if the prefix or suffix is null 266 */ 267 public StrSubstitutor(final Map<String, String> valueMap, final String prefix, final String suffix, 268 final char escape, final String valueDelimiter) { 269 this(new MapLookup(valueMap), prefix, suffix, escape, valueDelimiter); 270 } 271 272 /** 273 * Creates a new instance and initializes it. Uses defaults for variable 274 * prefix and suffix and the escaping character. 275 * 276 * @param properties the map with the variables' values, may be null 277 */ 278 public StrSubstitutor(final Properties properties) { 279 this(toTypeSafeMap(properties)); 280 } 281 282 /** 283 * Creates a new instance and initializes it. 284 * 285 * @param variableResolver the variable resolver, may be null 286 */ 287 public StrSubstitutor(final StrLookup variableResolver) { 288 this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); 289 } 290 291 /** 292 * Creates a new instance and initializes it. 293 * 294 * @param variableResolver the variable resolver, may be null 295 * @param prefix the prefix for variables, not null 296 * @param suffix the suffix for variables, not null 297 * @param escape the escape character 298 * @throws IllegalArgumentException if the prefix or suffix is null 299 */ 300 public StrSubstitutor(final StrLookup variableResolver, final String prefix, final String suffix, 301 final char escape) { 302 this.setVariableResolver(variableResolver); 303 this.setVariablePrefix(prefix); 304 this.setVariableSuffix(suffix); 305 this.setEscapeChar(escape); 306 } 307 308 /** 309 * Creates a new instance and initializes it. 310 * 311 * @param variableResolver the variable resolver, may be null 312 * @param prefix the prefix for variables, not null 313 * @param suffix the suffix for variables, not null 314 * @param escape the escape character 315 * @param valueDelimiter the variable default value delimiter string, may be null 316 * @throws IllegalArgumentException if the prefix or suffix is null 317 */ 318 public StrSubstitutor(final StrLookup variableResolver, final String prefix, final String suffix, final char escape, final String valueDelimiter) { 319 this.setVariableResolver(variableResolver); 320 this.setVariablePrefix(prefix); 321 this.setVariableSuffix(suffix); 322 this.setEscapeChar(escape); 323 this.setValueDelimiter(valueDelimiter); 324 } 325 326 /** 327 * Creates a new instance and initializes it. 328 * 329 * @param variableResolver the variable resolver, may be null 330 * @param prefixMatcher the prefix for variables, not null 331 * @param suffixMatcher the suffix for variables, not null 332 * @param escape the escape character 333 * @throws IllegalArgumentException if the prefix or suffix is null 334 */ 335 public StrSubstitutor(final StrLookup variableResolver, final StrMatcher prefixMatcher, 336 final StrMatcher suffixMatcher, 337 final char escape) { 338 this(variableResolver, prefixMatcher, suffixMatcher, escape, DEFAULT_VALUE_DELIMITER, 339 DEFAULT_VALUE_ESCAPE_DELIMITER); 340 this.valueDelimiterString = DEFAULT_VALUE_DELIMITER_STRING; 341 } 342 343 /** 344 * Creates a new instance and initializes it. 345 * 346 * @param variableResolver the variable resolver, may be null 347 * @param prefixMatcher the prefix for variables, not null 348 * @param suffixMatcher the suffix for variables, not null 349 * @param escape the escape character 350 * @param valueDelimiterMatcher the variable default value delimiter matcher, may be null 351 * @throws IllegalArgumentException if the prefix or suffix is null 352 */ 353 public StrSubstitutor(final StrLookup variableResolver, final StrMatcher prefixMatcher, 354 final StrMatcher suffixMatcher, final char escape, final StrMatcher valueDelimiterMatcher) { 355 this.setVariableResolver(variableResolver); 356 this.setVariablePrefixMatcher(prefixMatcher); 357 this.setVariableSuffixMatcher(suffixMatcher); 358 this.setEscapeChar(escape); 359 this.setValueDelimiterMatcher(valueDelimiterMatcher); 360 } 361 362 /** 363 * Creates a new instance and initializes it. 364 * 365 * @param variableResolver the variable resolver, may be null 366 * @param prefixMatcher the prefix for variables, not null 367 * @param suffixMatcher the suffix for variables, not null 368 * @param escape the escape character 369 * @param valueDelimiterMatcher the variable default value delimiter matcher, may be null 370 * @param valueEscapeMatcher the matcher to escape defaulting, may be null. 371 * @throws IllegalArgumentException if the prefix or suffix is null 372 */ 373 public StrSubstitutor(final StrLookup variableResolver, final StrMatcher prefixMatcher, 374 final StrMatcher suffixMatcher, final char escape, final StrMatcher valueDelimiterMatcher, 375 final StrMatcher valueEscapeMatcher) { 376 this.setVariableResolver(variableResolver); 377 this.setVariablePrefixMatcher(prefixMatcher); 378 this.setVariableSuffixMatcher(suffixMatcher); 379 this.setEscapeChar(escape); 380 this.setValueDelimiterMatcher(valueDelimiterMatcher); 381 valueEscapeDelimiterMatcher = valueEscapeMatcher; 382 } 383 384 StrSubstitutor(final StrSubstitutor other) { 385 this.setVariableResolver(other.getVariableResolver()); 386 this.setVariablePrefixMatcher(other.getVariablePrefixMatcher()); 387 this.setVariableSuffixMatcher(other.getVariableSuffixMatcher()); 388 this.setEscapeChar(other.getEscapeChar()); 389 this.setValueDelimiterMatcher(other.valueDelimiterMatcher); 390 this.valueEscapeDelimiterMatcher = other.valueEscapeDelimiterMatcher; 391 this.configuration = other.configuration; 392 this.recursiveEvaluationAllowed = other.isRecursiveEvaluationAllowed(); 393 this.enableSubstitutionInVariables = other.isEnableSubstitutionInVariables(); 394 this.valueDelimiterString = other.valueDelimiterString; 395 } 396 397 //----------------------------------------------------------------------- 398 /** 399 * Replaces all the occurrences of variables in the given source object with 400 * their matching values from the map. 401 * 402 * @param source the source text containing the variables to substitute, null returns null 403 * @param valueMap the map with the values, may be null 404 * @return the result of the replace operation 405 */ 406 public static String replace(final Object source, final Map<String, String> valueMap) { 407 return new StrSubstitutor(valueMap).replace(source); 408 } 409 410 /** 411 * Replaces all the occurrences of variables in the given source object with 412 * their matching values from the map. This method allows to specify a 413 * custom variable prefix and suffix 414 * 415 * @param source the source text containing the variables to substitute, null returns null 416 * @param valueMap the map with the values, may be null 417 * @param prefix the prefix of variables, not null 418 * @param suffix the suffix of variables, not null 419 * @return the result of the replace operation 420 * @throws IllegalArgumentException if the prefix or suffix is null 421 */ 422 public static String replace(final Object source, final Map<String, String> valueMap, final String prefix, 423 final String suffix) { 424 return new StrSubstitutor(valueMap, prefix, suffix).replace(source); 425 } 426 427 /** 428 * Replaces all the occurrences of variables in the given source object with their matching 429 * values from the properties. 430 * 431 * @param source the source text containing the variables to substitute, null returns null 432 * @param valueProperties the properties with values, may be null 433 * @return the result of the replace operation 434 */ 435 public static String replace(final Object source, final Properties valueProperties) { 436 if (valueProperties == null) { 437 return source.toString(); 438 } 439 final Map<String, String> valueMap = new HashMap<String, String>(); 440 final Enumeration<?> propNames = valueProperties.propertyNames(); 441 while (propNames.hasMoreElements()) { 442 final String propName = (String) propNames.nextElement(); 443 final String propValue = valueProperties.getProperty(propName); 444 valueMap.put(propName, propValue); 445 } 446 return StrSubstitutor.replace(source, valueMap); 447 } 448 449 private static Map<String, String> toTypeSafeMap(final Properties properties) { 450 final Map<String, String> map = new HashMap<String, String>(properties.size()); 451 for (final String name : properties.stringPropertyNames()) { 452 map.put(name, properties.getProperty(name)); 453 } 454 return map; 455 } 456 457 private static String handleFailedReplacement(String input, Throwable throwable) { 458 StatusLogger.getLogger().error("Replacement failed on {}", input, throwable); 459 return input; 460 } 461 462 //----------------------------------------------------------------------- 463 /** 464 * Replaces all the occurrences of variables with their matching values 465 * from the resolver using the given source string as a template. 466 * 467 * @param source the string to replace in, null returns null 468 * @return the result of the replace operation 469 */ 470 public String replace(final String source) { 471 return replace(null, source); 472 } 473 //----------------------------------------------------------------------- 474 /** 475 * Replaces all the occurrences of variables with their matching values 476 * from the resolver using the given source string as a template. 477 * 478 * @param event The current LogEvent if there is one. 479 * @param source the string to replace in, null returns null 480 * @return the result of the replace operation 481 */ 482 public String replace(final LogEvent event, final String source) { 483 if (source == null) { 484 return null; 485 } 486 final StringBuilder buf = new StringBuilder(source); 487 try { 488 if (!substitute(event, buf, 0, source.length())) { 489 return source; 490 } 491 } catch (Throwable t) { 492 return handleFailedReplacement(source, t); 493 } 494 return buf.toString(); 495 } 496 497 /** 498 * Replaces all the occurrences of variables with their matching values 499 * from the resolver using the given source string as a template. 500 * <p> 501 * Only the specified portion of the string will be processed. 502 * The rest of the string is not processed, and is not returned. 503 * </p> 504 * 505 * @param source the string to replace in, null returns null 506 * @param offset the start offset within the array, must be valid 507 * @param length the length within the array to be processed, must be valid 508 * @return the result of the replace operation 509 */ 510 public String replace(final String source, final int offset, final int length) { 511 return replace(null, source, offset, length); 512 } 513 514 /** 515 * Replaces all the occurrences of variables with their matching values 516 * from the resolver using the given source string as a template. 517 * <p> 518 * Only the specified portion of the string will be processed. 519 * The rest of the string is not processed, and is not returned. 520 * </p> 521 * 522 * @param event the current LogEvent, if one exists. 523 * @param source the string to replace in, null returns null 524 * @param offset the start offset within the array, must be valid 525 * @param length the length within the array to be processed, must be valid 526 * @return the result of the replace operation 527 */ 528 public String replace(final LogEvent event, final String source, final int offset, final int length) { 529 if (source == null) { 530 return null; 531 } 532 final StringBuilder buf = new StringBuilder(length).append(source, offset, length); 533 try { 534 if (!substitute(event, buf, 0, length)) { 535 return source.substring(offset, offset + length); 536 } 537 } catch (Throwable t) { 538 return handleFailedReplacement(source, t); 539 } 540 return buf.toString(); 541 } 542 543 //----------------------------------------------------------------------- 544 /** 545 * Replaces all the occurrences of variables with their matching values 546 * from the resolver using the given source array as a template. 547 * The array is not altered by this method. 548 * 549 * @param source the character array to replace in, not altered, null returns null 550 * @return the result of the replace operation 551 */ 552 public String replace(final char[] source) { 553 return replace(null, source); 554 } 555 556 //----------------------------------------------------------------------- 557 /** 558 * Replaces all the occurrences of variables with their matching values 559 * from the resolver using the given source array as a template. 560 * The array is not altered by this method. 561 * 562 * @param event the current LogEvent, if one exists. 563 * @param source the character array to replace in, not altered, null returns null 564 * @return the result of the replace operation 565 */ 566 public String replace(final LogEvent event, final char[] source) { 567 if (source == null) { 568 return null; 569 } 570 final StringBuilder buf = new StringBuilder(source.length).append(source); 571 try { 572 substitute(event, buf, 0, source.length); 573 } catch (Throwable t) { 574 return handleFailedReplacement(new String(source), t); 575 } 576 return buf.toString(); 577 } 578 579 /** 580 * Replaces all the occurrences of variables with their matching values 581 * from the resolver using the given source array as a template. 582 * The array is not altered by this method. 583 * <p> 584 * Only the specified portion of the array will be processed. 585 * The rest of the array is not processed, and is not returned. 586 * </p> 587 * 588 * @param source the character array to replace in, not altered, null returns null 589 * @param offset the start offset within the array, must be valid 590 * @param length the length within the array to be processed, must be valid 591 * @return the result of the replace operation 592 */ 593 public String replace(final char[] source, final int offset, final int length) { 594 return replace(null, source, offset, length); 595 } 596 597 /** 598 * Replaces all the occurrences of variables with their matching values 599 * from the resolver using the given source array as a template. 600 * The array is not altered by this method. 601 * <p> 602 * Only the specified portion of the array will be processed. 603 * The rest of the array is not processed, and is not returned. 604 * </p> 605 * 606 * @param event the current LogEvent, if one exists. 607 * @param source the character array to replace in, not altered, null returns null 608 * @param offset the start offset within the array, must be valid 609 * @param length the length within the array to be processed, must be valid 610 * @return the result of the replace operation 611 */ 612 public String replace(final LogEvent event, final char[] source, final int offset, final int length) { 613 if (source == null) { 614 return null; 615 } 616 final StringBuilder buf = new StringBuilder(length).append(source, offset, length); 617 try { 618 substitute(event, buf, 0, length); 619 } catch (Throwable t) { 620 return handleFailedReplacement(new String(source, offset, length), t); 621 } 622 return buf.toString(); 623 } 624 625 //----------------------------------------------------------------------- 626 /** 627 * Replaces all the occurrences of variables with their matching values 628 * from the resolver using the given source buffer as a template. 629 * The buffer is not altered by this method. 630 * 631 * @param source the buffer to use as a template, not changed, null returns null 632 * @return the result of the replace operation 633 */ 634 public String replace(final StringBuffer source) { 635 return replace(null, source); 636 } 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 * 644 * @param event the current LogEvent, if one exists. 645 * @param source the buffer to use as a template, not changed, null returns null 646 * @return the result of the replace operation 647 */ 648 public String replace(final LogEvent event, final StringBuffer source) { 649 if (source == null) { 650 return null; 651 } 652 final StringBuilder buf = new StringBuilder(source.length()).append(source); 653 try { 654 substitute(event, buf, 0, buf.length()); 655 } catch (Throwable t) { 656 return handleFailedReplacement(source.toString(), t); 657 } 658 return buf.toString(); 659 } 660 661 /** 662 * Replaces all the occurrences of variables with their matching values 663 * from the resolver using the given source buffer as a template. 664 * The buffer is not altered by this method. 665 * <p> 666 * Only the specified portion of the buffer will be processed. 667 * The rest of the buffer is not processed, and is not returned. 668 * </p> 669 * 670 * @param source the buffer to use as a template, not changed, null returns null 671 * @param offset the start offset within the array, must be valid 672 * @param length the length within the array to be processed, must be valid 673 * @return the result of the replace operation 674 */ 675 public String replace(final StringBuffer source, final int offset, final int length) { 676 return replace(null, source, offset, length); 677 } 678 679 /** 680 * Replaces all the occurrences of variables with their matching values 681 * from the resolver using the given source buffer as a template. 682 * The buffer is not altered by this method. 683 * <p> 684 * Only the specified portion of the buffer will be processed. 685 * The rest of the buffer is not processed, and is not returned. 686 * </p> 687 * 688 * @param event the current LogEvent, if one exists. 689 * @param source the buffer to use as a template, not changed, null returns null 690 * @param offset the start offset within the array, must be valid 691 * @param length the length within the array to be processed, must be valid 692 * @return the result of the replace operation 693 */ 694 public String replace(final LogEvent event, final StringBuffer source, final int offset, final int length) { 695 if (source == null) { 696 return null; 697 } 698 final StringBuilder buf = new StringBuilder(length).append(source, offset, length); 699 try { 700 substitute(event, buf, 0, length); 701 } catch (Throwable t) { 702 return handleFailedReplacement(source.substring(offset, offset + length), t); 703 } 704 return buf.toString(); 705 } 706 707 //----------------------------------------------------------------------- 708 /** 709 * Replaces all the occurrences of variables with their matching values 710 * from the resolver using the given source builder as a template. 711 * The builder is not altered by this method. 712 * 713 * @param source the builder to use as a template, not changed, null returns null 714 * @return the result of the replace operation 715 */ 716 public String replace(final StringBuilder source) { 717 return replace(null, source); 718 } 719 720 //----------------------------------------------------------------------- 721 /** 722 * Replaces all the occurrences of variables with their matching values 723 * from the resolver using the given source builder as a template. 724 * The builder is not altered by this method. 725 * 726 * @param event The LogEvent. 727 * @param source the builder to use as a template, not changed, null returns null. 728 * @return the result of the replace operation. 729 */ 730 public String replace(final LogEvent event, final StringBuilder source) { 731 if (source == null) { 732 return null; 733 } 734 final StringBuilder buf = new StringBuilder(source.length()).append(source); 735 try { 736 substitute(event, buf, 0, buf.length()); 737 } catch (Throwable t) { 738 return handleFailedReplacement(source.toString(), t); 739 } 740 return buf.toString(); 741 } 742 /** 743 * Replaces all the occurrences of variables with their matching values 744 * from the resolver using the given source builder as a template. 745 * The builder is not altered by this method. 746 * <p> 747 * Only the specified portion of the builder will be processed. 748 * The rest of the builder is not processed, and is not returned. 749 * </p> 750 * 751 * @param source the builder to use as a template, not changed, null returns null 752 * @param offset the start offset within the array, must be valid 753 * @param length the length within the array to be processed, must be valid 754 * @return the result of the replace operation 755 */ 756 public String replace(final StringBuilder source, final int offset, final int length) { 757 return replace(null, source, offset, length); 758 } 759 760 /** 761 * Replaces all the occurrences of variables with their matching values 762 * from the resolver using the given source builder as a template. 763 * The builder is not altered by this method. 764 * <p> 765 * Only the specified portion of the builder will be processed. 766 * The rest of the builder is not processed, and is not returned. 767 * </p> 768 * 769 * @param event the current LogEvent, if one exists. 770 * @param source the builder to use as a template, not changed, null returns null 771 * @param offset the start offset within the array, must be valid 772 * @param length the length within the array to be processed, must be valid 773 * @return the result of the replace operation 774 */ 775 public String replace(final LogEvent event, final StringBuilder source, final int offset, final int length) { 776 if (source == null) { 777 return null; 778 } 779 final StringBuilder buf = new StringBuilder(length).append(source, offset, length); 780 try { 781 substitute(event, buf, 0, length); 782 } catch (Throwable t) { 783 return handleFailedReplacement(source.substring(offset, offset + length), t); 784 } 785 return buf.toString(); 786 } 787 788 //----------------------------------------------------------------------- 789 /** 790 * Replaces all the occurrences of variables in the given source object with 791 * their matching values from the resolver. The input source object is 792 * converted to a string using <code>toString</code> and is not altered. 793 * 794 * @param source the source to replace in, null returns null 795 * @return the result of the replace operation 796 */ 797 public String replace(final Object source) { 798 return replace(null, source); 799 } 800 //----------------------------------------------------------------------- 801 /** 802 * Replaces all the occurrences of variables in the given source object with 803 * their matching values from the resolver. The input source object is 804 * converted to a string using <code>toString</code> and is not altered. 805 * 806 * @param event the current LogEvent, if one exists. 807 * @param source the source to replace in, null returns null 808 * @return the result of the replace operation 809 */ 810 public String replace(final LogEvent event, final Object source) { 811 if (source == null) { 812 return null; 813 } 814 String stringValue = String.valueOf(source); 815 final StringBuilder buf = new StringBuilder(stringValue.length()).append(stringValue); 816 try { 817 substitute(event, buf, 0, buf.length()); 818 } catch (Throwable t) { 819 return handleFailedReplacement(stringValue, t); 820 } 821 return buf.toString(); 822 } 823 824 //----------------------------------------------------------------------- 825 /** 826 * Replaces all the occurrences of variables within the given source buffer 827 * with their matching values from the resolver. 828 * The buffer is updated with the result. 829 * 830 * @param source the buffer to replace in, updated, null returns zero 831 * @return true if altered 832 */ 833 public boolean replaceIn(final StringBuffer source) { 834 if (source == null) { 835 return false; 836 } 837 return replaceIn(source, 0, source.length()); 838 } 839 840 /** 841 * Replaces all the occurrences of variables within the given source buffer 842 * with their matching values from the resolver. 843 * The buffer is updated with the result. 844 * <p> 845 * Only the specified portion of the buffer will be processed. 846 * The rest of the buffer is not processed, but it is not deleted. 847 * </p> 848 * 849 * @param source the buffer to replace in, updated, null returns zero 850 * @param offset the start offset within the array, must be valid 851 * @param length the length within the buffer to be processed, must be valid 852 * @return true if altered 853 */ 854 public boolean replaceIn(final StringBuffer source, final int offset, final int length) { 855 return replaceIn(null, source, offset, length); 856 } 857 858 /** 859 * Replaces all the occurrences of variables within the given source buffer 860 * with their matching values from the resolver. 861 * The buffer is updated with the result. 862 * <p> 863 * Only the specified portion of the buffer will be processed. 864 * The rest of the buffer is not processed, but it is not deleted. 865 * </p> 866 * 867 * @param event the current LogEvent, if one exists. 868 * @param source the buffer to replace in, updated, null returns zero 869 * @param offset the start offset within the array, must be valid 870 * @param length the length within the buffer to be processed, must be valid 871 * @return true if altered 872 */ 873 public boolean replaceIn(final LogEvent event, final StringBuffer source, final int offset, final int length) { 874 if (source == null) { 875 return false; 876 } 877 final StringBuilder buf = new StringBuilder(length).append(source, offset, length); 878 try { 879 if (!substitute(event, buf, 0, length)) { 880 return false; 881 } 882 } catch (Throwable t) { 883 StatusLogger.getLogger().error("Replacement failed on {}", source, t); 884 return false; 885 } 886 source.replace(offset, offset + length, buf.toString()); 887 return true; 888 } 889 890 //----------------------------------------------------------------------- 891 /** 892 * Replaces all the occurrences of variables within the given source 893 * builder with their matching values from the resolver. 894 * 895 * @param source the builder to replace in, updated, null returns zero 896 * @return true if altered 897 */ 898 public boolean replaceIn(final StringBuilder source) { 899 return replaceIn(null, source); 900 } 901 902 //----------------------------------------------------------------------- 903 /** 904 * Replaces all the occurrences of variables within the given source 905 * builder with their matching values from the resolver. 906 * 907 * @param event the current LogEvent, if one exists. 908 * @param source the builder to replace in, updated, null returns zero 909 * @return true if altered 910 */ 911 public boolean replaceIn(final LogEvent event, final StringBuilder source) { 912 if (source == null) { 913 return false; 914 } 915 return substitute(event, source, 0, source.length()); 916 } 917 /** 918 * Replaces all the occurrences of variables within the given source 919 * builder with their matching values from the resolver. 920 * <p> 921 * Only the specified portion of the builder will be processed. 922 * The rest of the builder is not processed, but it is not deleted. 923 * </p> 924 * 925 * @param source the builder to replace in, null returns zero 926 * @param offset the start offset within the array, must be valid 927 * @param length the length within the builder to be processed, must be valid 928 * @return true if altered 929 */ 930 public boolean replaceIn(final StringBuilder source, final int offset, final int length) { 931 return replaceIn(null, source, offset, length); 932 } 933 934 /** 935 * Replaces all the occurrences of variables within the given source 936 * builder with their matching values from the resolver. 937 * <p> 938 * Only the specified portion of the builder will be processed. 939 * The rest of the builder is not processed, but it is not deleted. 940 * </p> 941 * 942 * @param event the current LogEvent, if one is present. 943 * @param source the builder to replace in, null returns zero 944 * @param offset the start offset within the array, must be valid 945 * @param length the length within the builder to be processed, must be valid 946 * @return true if altered 947 */ 948 public boolean replaceIn(final LogEvent event, final StringBuilder source, final int offset, final int length) { 949 if (source == null) { 950 return false; 951 } 952 return substitute(event, source, offset, length); 953 } 954 955 //----------------------------------------------------------------------- 956 /** 957 * Internal method that substitutes the variables. 958 * <p> 959 * Most users of this class do not need to call this method. This method will 960 * be called automatically by another (public) method. 961 * </p> 962 * <p> 963 * Writers of subclasses can override this method if they need access to 964 * the substitution process at the start or end. 965 * </p> 966 * 967 * @param event The current LogEvent, if there is one. 968 * @param buf the string builder to substitute into, not null 969 * @param offset the start offset within the builder, must be valid 970 * @param length the length within the builder to be processed, must be valid 971 * @return true if altered 972 */ 973 protected boolean substitute(final LogEvent event, final StringBuilder buf, final int offset, final int length) { 974 return substitute(event, buf, offset, length, null) > 0; 975 } 976 977 /** 978 * Recursive handler for multiple levels of interpolation. This is the main 979 * interpolation method, which resolves the values of all variable references 980 * contained in the passed in text. 981 * 982 * @param event The current LogEvent, if there is one. 983 * @param buf the string builder to substitute into, not null 984 * @param offset the start offset within the builder, must be valid 985 * @param length the length within the builder to be processed, must be valid 986 * @param priorVariables the stack keeping track of the replaced variables, may be null 987 * @return the length change that occurs, unless priorVariables is null when the int 988 * represents a boolean flag as to whether any change occurred. 989 */ 990 private int substitute(final LogEvent event, final StringBuilder buf, final int offset, final int length, 991 List<String> priorVariables) { 992 final StrMatcher prefixMatcher = getVariablePrefixMatcher(); 993 final StrMatcher suffixMatcher = getVariableSuffixMatcher(); 994 final char escape = getEscapeChar(); 995 final StrMatcher valueDelimiterMatcher = getValueDelimiterMatcher(); 996 final boolean substitutionInVariablesEnabled = isEnableSubstitutionInVariables(); 997 998 final boolean top = priorVariables == null; 999 boolean altered = false; 1000 int lengthChange = 0; 1001 char[] chars = getChars(buf); 1002 int bufEnd = offset + length; 1003 int pos = offset; 1004 while (pos < bufEnd) { 1005 final int startMatchLen = prefixMatcher.isMatch(chars, pos, offset, bufEnd); 1006 if (startMatchLen == 0) { 1007 pos++; 1008 } else // found variable start marker 1009 if (pos > offset && chars[pos - 1] == escape) { 1010 // escaped 1011 buf.deleteCharAt(pos - 1); 1012 chars = getChars(buf); 1013 lengthChange--; 1014 altered = true; 1015 bufEnd--; 1016 } else { 1017 // find suffix 1018 final int startPos = pos; 1019 pos += startMatchLen; 1020 int endMatchLen = 0; 1021 int nestedVarCount = 0; 1022 while (pos < bufEnd) { 1023 if (substitutionInVariablesEnabled 1024 && (endMatchLen = prefixMatcher.isMatch(chars, pos, offset, bufEnd)) != 0) { 1025 // found a nested variable start 1026 nestedVarCount++; 1027 pos += endMatchLen; 1028 continue; 1029 } 1030 1031 endMatchLen = suffixMatcher.isMatch(chars, pos, offset, bufEnd); 1032 if (endMatchLen == 0) { 1033 pos++; 1034 } else { 1035 // found variable end marker 1036 if (nestedVarCount == 0) { 1037 String varNameExpr = new String(chars, startPos + startMatchLen, pos - startPos - startMatchLen); 1038 if (substitutionInVariablesEnabled) { 1039 // initialize priorVariables if they're not already set 1040 if (priorVariables == null) { 1041 priorVariables = new ArrayList<String>(); 1042 } 1043 final StringBuilder bufName = new StringBuilder(varNameExpr); 1044 substitute(event, bufName, 0, bufName.length(), priorVariables); 1045 varNameExpr = bufName.toString(); 1046 } 1047 pos += endMatchLen; 1048 final int endPos = pos; 1049 1050 String varName = varNameExpr; 1051 String varDefaultValue = null; 1052 1053 if (valueDelimiterMatcher != null) { 1054 final char [] varNameExprChars = varNameExpr.toCharArray(); 1055 int valueDelimiterMatchLen = 0; 1056 for (int i = 0; i < varNameExprChars.length; i++) { 1057 // if there's any nested variable when nested variable substitution disabled, then stop resolving name and default value. 1058 if (!substitutionInVariablesEnabled 1059 && prefixMatcher.isMatch(varNameExprChars, i, i, varNameExprChars.length) != 0) { 1060 break; 1061 } 1062 if (valueEscapeDelimiterMatcher != null) { 1063 int matchLen = valueEscapeDelimiterMatcher.isMatch(varNameExprChars, i); 1064 if (matchLen != 0) { 1065 String varNamePrefix = varNameExpr.substring(0, i) + Interpolator.PREFIX_SEPARATOR; 1066 varName = varNamePrefix + varNameExpr.substring(i + matchLen - 1); 1067 for (int j = i + matchLen; j < varNameExprChars.length; ++j){ 1068 if ((valueDelimiterMatchLen = valueDelimiterMatcher.isMatch(varNameExprChars, j)) != 0) { 1069 varName = varNamePrefix + varNameExpr.substring(i + matchLen, j); 1070 varDefaultValue = varNameExpr.substring(j + valueDelimiterMatchLen); 1071 break; 1072 } 1073 } 1074 break; 1075 } else if ((valueDelimiterMatchLen = valueDelimiterMatcher.isMatch(varNameExprChars, i)) != 0) { 1076 varName = varNameExpr.substring(0, i); 1077 varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen); 1078 break; 1079 } 1080 } else if ((valueDelimiterMatchLen = valueDelimiterMatcher.isMatch(varNameExprChars, i)) != 0) { 1081 varName = varNameExpr.substring(0, i); 1082 varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen); 1083 break; 1084 } 1085 } 1086 } 1087 1088 // on the first call initialize priorVariables 1089 if (priorVariables == null) { 1090 priorVariables = new ArrayList<String>(); 1091 priorVariables.add(new String(chars, offset, length + lengthChange)); 1092 } 1093 1094 // handle cyclic substitution 1095 boolean isCyclic = isCyclicSubstitution(varName, priorVariables); 1096 1097 // resolve the variable 1098 String varValue = isCyclic ? null : resolveVariable(event, varName, buf, startPos, endPos); 1099 if (varValue == null) { 1100 varValue = varDefaultValue; 1101 } 1102 if (varValue != null) { 1103 // recursive replace 1104 final int varLen = varValue.length(); 1105 buf.replace(startPos, endPos, varValue); 1106 altered = true; 1107 int change = isRecursiveEvaluationAllowed() 1108 ? substitute(event, buf, startPos, varLen, priorVariables) 1109 : 0; 1110 change = change + (varLen - (endPos - startPos)); 1111 pos += change; 1112 bufEnd += change; 1113 lengthChange += change; 1114 chars = getChars(buf); // in case buffer was altered 1115 } 1116 1117 // remove variable from the cyclic stack 1118 if (!isCyclic) { 1119 priorVariables.remove(priorVariables.size() - 1); 1120 } 1121 break; 1122 } 1123 nestedVarCount--; 1124 pos += endMatchLen; 1125 } 1126 } 1127 } 1128 } 1129 if (top) { 1130 return altered ? 1 : 0; 1131 } 1132 return lengthChange; 1133 } 1134 1135 /** 1136 * Checks if the specified variable is already in the stack (list) of variables, adding the value 1137 * if it's not already present. 1138 * 1139 * @param varName the variable name to check 1140 * @param priorVariables the list of prior variables 1141 * @return true if this is a cyclic substitution 1142 */ 1143 private boolean isCyclicSubstitution(final String varName, final List<String> priorVariables) { 1144 if (!priorVariables.contains(varName)) { 1145 priorVariables.add(varName); 1146 return false; 1147 } 1148 final StringBuilder buf = new StringBuilder(BUF_SIZE); 1149 buf.append("Infinite loop in property interpolation of "); 1150 appendWithSeparators(buf, priorVariables, "->"); 1151 StatusLogger.getLogger().warn(buf); 1152 return true; 1153 } 1154 1155 /** 1156 * Internal method that resolves the value of a variable. 1157 * <p> 1158 * Most users of this class do not need to call this method. This method is 1159 * called automatically by the substitution process. 1160 * </p> 1161 * <p> 1162 * Writers of subclasses can override this method if they need to alter 1163 * how each substitution occurs. The method is passed the variable's name 1164 * and must return the corresponding value. This implementation uses the 1165 * {@link #getVariableResolver()} with the variable's name as the key. 1166 * </p> 1167 * 1168 * @param event The LogEvent, if there is one. 1169 * @param variableName the name of the variable, not null 1170 * @param buf the buffer where the substitution is occurring, not null 1171 * @param startPos the start position of the variable including the prefix, valid 1172 * @param endPos the end position of the variable including the suffix, valid 1173 * @return the variable's value or <b>null</b> if the variable is unknown 1174 */ 1175 protected String resolveVariable(final LogEvent event, final String variableName, final StringBuilder buf, 1176 final int startPos, final int endPos) { 1177 final StrLookup resolver = getVariableResolver(); 1178 if (resolver == null) { 1179 return null; 1180 } 1181 try { 1182 return resolver.lookup(event, variableName); 1183 } catch (Throwable t) { 1184 StatusLogger.getLogger().error("Resolver failed to lookup {}", variableName, t); 1185 return null; 1186 } 1187 } 1188 1189 // Escape 1190 //----------------------------------------------------------------------- 1191 /** 1192 * Returns the escape character. 1193 * 1194 * @return the character used for escaping variable references 1195 */ 1196 public char getEscapeChar() { 1197 return this.escapeChar; 1198 } 1199 1200 /** 1201 * Sets the escape character. 1202 * If this character is placed before a variable reference in the source 1203 * text, this variable will be ignored. 1204 * 1205 * @param escapeCharacter the escape character (0 for disabling escaping) 1206 */ 1207 public void setEscapeChar(final char escapeCharacter) { 1208 this.escapeChar = escapeCharacter; 1209 } 1210 1211 // Prefix 1212 //----------------------------------------------------------------------- 1213 /** 1214 * Gets the variable prefix matcher currently in use. 1215 * <p> 1216 * The variable prefix is the character or characters that identify the 1217 * start of a variable. This prefix is expressed in terms of a matcher 1218 * allowing advanced prefix matches. 1219 * </p> 1220 * 1221 * @return the prefix matcher in use 1222 */ 1223 public StrMatcher getVariablePrefixMatcher() { 1224 return prefixMatcher; 1225 } 1226 1227 /** 1228 * Sets the variable prefix matcher currently in use. 1229 * <p> 1230 * The variable prefix is the character or characters that identify the 1231 * start of a variable. This prefix is expressed in terms of a matcher 1232 * allowing advanced prefix matches. 1233 * </p> 1234 * 1235 * @param prefixMatcher the prefix matcher to use, null ignored 1236 * @return this, to enable chaining 1237 * @throws IllegalArgumentException if the prefix matcher is null 1238 */ 1239 public StrSubstitutor setVariablePrefixMatcher(final StrMatcher prefixMatcher) { 1240 if (prefixMatcher == null) { 1241 throw new IllegalArgumentException("Variable prefix matcher must not be null!"); 1242 } 1243 this.prefixMatcher = prefixMatcher; 1244 return this; 1245 } 1246 1247 /** 1248 * Sets the variable prefix to use. 1249 * <p> 1250 * The variable prefix is the character or characters that identify the 1251 * start of a variable. This method allows a single character prefix to 1252 * be easily set. 1253 * </p> 1254 * 1255 * @param prefix the prefix character to use 1256 * @return this, to enable chaining 1257 */ 1258 public StrSubstitutor setVariablePrefix(final char prefix) { 1259 return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix)); 1260 } 1261 1262 /** 1263 * Sets the variable prefix to use. 1264 * <p> 1265 * The variable prefix is the character or characters that identify the 1266 * start of a variable. This method allows a string prefix to be easily set. 1267 * </p> 1268 * 1269 * @param prefix the prefix for variables, not null 1270 * @return this, to enable chaining 1271 * @throws IllegalArgumentException if the prefix is null 1272 */ 1273 public StrSubstitutor setVariablePrefix(final String prefix) { 1274 if (prefix == null) { 1275 throw new IllegalArgumentException("Variable prefix must not be null!"); 1276 } 1277 return setVariablePrefixMatcher(StrMatcher.stringMatcher(prefix)); 1278 } 1279 1280 // Suffix 1281 //----------------------------------------------------------------------- 1282 /** 1283 * Gets the variable suffix matcher currently in use. 1284 * <p> 1285 * The variable suffix is the character or characters that identify the 1286 * end of a variable. This suffix is expressed in terms of a matcher 1287 * allowing advanced suffix matches. 1288 * </p> 1289 * 1290 * @return the suffix matcher in use 1291 */ 1292 public StrMatcher getVariableSuffixMatcher() { 1293 return suffixMatcher; 1294 } 1295 1296 /** 1297 * Sets the variable suffix matcher currently in use. 1298 * <p> 1299 * The variable suffix is the character or characters that identify the 1300 * end of a variable. This suffix is expressed in terms of a matcher 1301 * allowing advanced suffix matches. 1302 * </p> 1303 * 1304 * @param suffixMatcher the suffix matcher to use, null ignored 1305 * @return this, to enable chaining 1306 * @throws IllegalArgumentException if the suffix matcher is null 1307 */ 1308 public StrSubstitutor setVariableSuffixMatcher(final StrMatcher suffixMatcher) { 1309 if (suffixMatcher == null) { 1310 throw new IllegalArgumentException("Variable suffix matcher must not be null!"); 1311 } 1312 this.suffixMatcher = suffixMatcher; 1313 return this; 1314 } 1315 1316 /** 1317 * Sets the variable suffix to use. 1318 * <p> 1319 * The variable suffix is the character or characters that identify the 1320 * end of a variable. This method allows a single character suffix to 1321 * be easily set. 1322 * </p> 1323 * 1324 * @param suffix the suffix character to use 1325 * @return this, to enable chaining 1326 */ 1327 public StrSubstitutor setVariableSuffix(final char suffix) { 1328 return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix)); 1329 } 1330 1331 /** 1332 * Sets the variable suffix to use. 1333 * <p> 1334 * The variable suffix is the character or characters that identify the 1335 * end of a variable. This method allows a string suffix to be easily set. 1336 * </p> 1337 * 1338 * @param suffix the suffix for variables, not null 1339 * @return this, to enable chaining 1340 * @throws IllegalArgumentException if the suffix is null 1341 */ 1342 public StrSubstitutor setVariableSuffix(final String suffix) { 1343 if (suffix == null) { 1344 throw new IllegalArgumentException("Variable suffix must not be null!"); 1345 } 1346 return setVariableSuffixMatcher(StrMatcher.stringMatcher(suffix)); 1347 } 1348 1349 // Variable Default Value Delimiter 1350 //----------------------------------------------------------------------- 1351 /** 1352 * Gets the variable default value delimiter matcher currently in use. 1353 * <p> 1354 * The variable default value delimiter is the character or characters that delimit the 1355 * variable name and the variable default value. This delimiter is expressed in terms of a matcher 1356 * allowing advanced variable default value delimiter matches. 1357 * </p> 1358 * <p> 1359 * If it returns null, then the variable default value resolution is disabled. 1360 * </p> 1361 * 1362 * @return the variable default value delimiter matcher in use, may be null 1363 */ 1364 public StrMatcher getValueDelimiterMatcher() { 1365 return valueDelimiterMatcher; 1366 } 1367 1368 /** 1369 * Sets the variable default value delimiter matcher to use. 1370 * <p> 1371 * The variable default value delimiter is the character or characters that delimit the 1372 * variable name and the variable default value. This delimiter is expressed in terms of a matcher 1373 * allowing advanced variable default value delimiter matches. 1374 * </p> 1375 * <p> 1376 * If the <code>valueDelimiterMatcher</code> is null, then the variable default value resolution 1377 * becomes disabled. 1378 * </p> 1379 * 1380 * @param valueDelimiterMatcher variable default value delimiter matcher to use, may be null 1381 * @return this, to enable chaining 1382 */ 1383 public StrSubstitutor setValueDelimiterMatcher(final StrMatcher valueDelimiterMatcher) { 1384 this.valueDelimiterMatcher = valueDelimiterMatcher; 1385 return this; 1386 } 1387 1388 /** 1389 * Sets the variable default value delimiter to use. 1390 * <p> 1391 * The variable default value delimiter is the character or characters that delimit the 1392 * variable name and the variable default value. This method allows a single character 1393 * variable default value delimiter to be easily set. 1394 * </p> 1395 * 1396 * @param valueDelimiter the variable default value delimiter character to use 1397 * @return this, to enable chaining 1398 */ 1399 public StrSubstitutor setValueDelimiter(final char valueDelimiter) { 1400 return setValueDelimiterMatcher(StrMatcher.charMatcher(valueDelimiter)); 1401 } 1402 1403 /** 1404 * Sets the variable default value delimiter to use. 1405 * <p> 1406 * The variable default value delimiter is the character or characters that delimit the 1407 * variable name and the variable default value. This method allows a string 1408 * variable default value delimiter to be easily set. 1409 * </p> 1410 * <p> 1411 * If the <code>valueDelimiter</code> is null or empty string, then the variable default 1412 * value resolution becomes disabled. 1413 * </p> 1414 * 1415 * @param valueDelimiter the variable default value delimiter string to use, may be null or empty 1416 * @return this, to enable chaining 1417 */ 1418 public StrSubstitutor setValueDelimiter(final String valueDelimiter) { 1419 if (Strings.isEmpty(valueDelimiter)) { 1420 setValueDelimiterMatcher(null); 1421 return this; 1422 } 1423 String escapeValue = valueDelimiter.substring(0, valueDelimiter.length() - 1) + "\\" 1424 + valueDelimiter.substring(valueDelimiter.length() - 1); 1425 valueEscapeDelimiterMatcher = StrMatcher.stringMatcher(escapeValue); 1426 return setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter)); 1427 } 1428 1429 // Resolver 1430 //----------------------------------------------------------------------- 1431 /** 1432 * Gets the VariableResolver that is used to lookup variables. 1433 * 1434 * @return the VariableResolver 1435 */ 1436 public StrLookup getVariableResolver() { 1437 return this.variableResolver; 1438 } 1439 1440 /** 1441 * Sets the VariableResolver that is used to lookup variables. 1442 * 1443 * @param variableResolver the VariableResolver 1444 */ 1445 public void setVariableResolver(final StrLookup variableResolver) { 1446 this.variableResolver = variableResolver; 1447 } 1448 1449 // Substitution support in variable names 1450 //----------------------------------------------------------------------- 1451 /** 1452 * Returns a flag whether substitution is done in variable names. 1453 * 1454 * @return the substitution in variable names flag 1455 */ 1456 public boolean isEnableSubstitutionInVariables() { 1457 return enableSubstitutionInVariables; 1458 } 1459 1460 /** 1461 * Sets a flag whether substitution is done in variable names. If set to 1462 * <b>true</b>, the names of variables can contain other variables which are 1463 * processed first before the original variable is evaluated, e.g. 1464 * <code>${jre-${java.version}}</code>. The default value is <b>false</b>. 1465 * 1466 * @param enableSubstitutionInVariables the new value of the flag 1467 */ 1468 public void setEnableSubstitutionInVariables(final boolean enableSubstitutionInVariables) { 1469 this.enableSubstitutionInVariables = enableSubstitutionInVariables; 1470 } 1471 1472 boolean isRecursiveEvaluationAllowed() { 1473 return recursiveEvaluationAllowed; 1474 } 1475 1476 void setRecursiveEvaluationAllowed(final boolean recursiveEvaluationAllowed) { 1477 this.recursiveEvaluationAllowed = recursiveEvaluationAllowed; 1478 } 1479 1480 private char[] getChars(final StringBuilder sb) { 1481 final char[] chars = new char[sb.length()]; 1482 sb.getChars(0, sb.length(), chars, 0); 1483 return chars; 1484 } 1485 1486 /** 1487 * Appends a iterable placing separators between each value, but 1488 * not before the first or after the last. 1489 * Appending a null iterable will have no effect.. 1490 * 1491 * @param sb StringBuilder that contains the String being constructed. 1492 * @param iterable the iterable to append 1493 * @param separator the separator to use, null means no separator 1494 */ 1495 public void appendWithSeparators(final StringBuilder sb, final Iterable<?> iterable, String separator) { 1496 if (iterable != null) { 1497 separator = separator == null ? Strings.EMPTY : separator; 1498 final Iterator<?> it = iterable.iterator(); 1499 while (it.hasNext()) { 1500 sb.append(it.next()); 1501 if (it.hasNext()) { 1502 sb.append(separator); 1503 } 1504 } 1505 } 1506 } 1507 1508 @Override 1509 public String toString() { 1510 return "StrSubstitutor(" + variableResolver.toString() + ')'; 1511 } 1512 }