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