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<>(); 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<>(); 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 } 983 nestedVarCount--; 984 pos += endMatchLen; 985 } 986 } 987 } 988 } 989 } 990 if (top) { 991 return altered ? 1 : 0; 992 } 993 return lengthChange; 994 } 995 996 /** 997 * Checks if the specified variable is already in the stack (list) of variables. 998 * 999 * @param varName the variable name to check 1000 * @param priorVariables the list of prior variables 1001 */ 1002 private void checkCyclicSubstitution(final String varName, final List<String> priorVariables) { 1003 if (!priorVariables.contains(varName)) { 1004 return; 1005 } 1006 final StringBuilder buf = new StringBuilder(BUF_SIZE); 1007 buf.append("Infinite loop in property interpolation of "); 1008 buf.append(priorVariables.remove(0)); 1009 buf.append(": "); 1010 appendWithSeparators(buf, priorVariables, "->"); 1011 throw new IllegalStateException(buf.toString()); 1012 } 1013 1014 /** 1015 * Internal method that resolves the value of a variable. 1016 * <p> 1017 * Most users of this class do not need to call this method. This method is 1018 * called automatically by the substitution process. 1019 * </p> 1020 * <p> 1021 * Writers of subclasses can override this method if they need to alter 1022 * how each substitution occurs. The method is passed the variable's name 1023 * and must return the corresponding value. This implementation uses the 1024 * {@link #getVariableResolver()} with the variable's name as the key. 1025 * </p> 1026 * 1027 * @param event The LogEvent, if there is one. 1028 * @param variableName the name of the variable, not null 1029 * @param buf the buffer where the substitution is occurring, not null 1030 * @param startPos the start position of the variable including the prefix, valid 1031 * @param endPos the end position of the variable including the suffix, valid 1032 * @return the variable's value or <b>null</b> if the variable is unknown 1033 */ 1034 protected String resolveVariable(final LogEvent event, final String variableName, final StringBuilder buf, 1035 final int startPos, final int endPos) { 1036 final StrLookup resolver = getVariableResolver(); 1037 if (resolver == null) { 1038 return null; 1039 } 1040 return resolver.lookup(event, variableName); 1041 } 1042 1043 // Escape 1044 //----------------------------------------------------------------------- 1045 /** 1046 * Returns the escape character. 1047 * 1048 * @return the character used for escaping variable references 1049 */ 1050 public char getEscapeChar() { 1051 return this.escapeChar; 1052 } 1053 1054 /** 1055 * Sets the escape character. 1056 * If this character is placed before a variable reference in the source 1057 * text, this variable will be ignored. 1058 * 1059 * @param escapeCharacter the escape character (0 for disabling escaping) 1060 */ 1061 public void setEscapeChar(final char escapeCharacter) { 1062 this.escapeChar = escapeCharacter; 1063 } 1064 1065 // Prefix 1066 //----------------------------------------------------------------------- 1067 /** 1068 * Gets the variable prefix matcher currently in use. 1069 * <p> 1070 * The variable prefix is the character or characters that identify the 1071 * start of a variable. This prefix is expressed in terms of a matcher 1072 * allowing advanced prefix matches. 1073 * </p> 1074 * 1075 * @return the prefix matcher in use 1076 */ 1077 public StrMatcher getVariablePrefixMatcher() { 1078 return prefixMatcher; 1079 } 1080 1081 /** 1082 * Sets the variable prefix matcher currently in use. 1083 * <p> 1084 * The variable prefix is the character or characters that identify the 1085 * start of a variable. This prefix is expressed in terms of a matcher 1086 * allowing advanced prefix matches. 1087 * </p> 1088 * 1089 * @param prefixMatcher the prefix matcher to use, null ignored 1090 * @return this, to enable chaining 1091 * @throws IllegalArgumentException if the prefix matcher is null 1092 */ 1093 public StrSubstitutor setVariablePrefixMatcher(final StrMatcher prefixMatcher) { 1094 if (prefixMatcher == null) { 1095 throw new IllegalArgumentException("Variable prefix matcher must not be null!"); 1096 } 1097 this.prefixMatcher = prefixMatcher; 1098 return this; 1099 } 1100 1101 /** 1102 * Sets the variable prefix to use. 1103 * <p> 1104 * The variable prefix is the character or characters that identify the 1105 * start of a variable. This method allows a single character prefix to 1106 * be easily set. 1107 * </p> 1108 * 1109 * @param prefix the prefix character to use 1110 * @return this, to enable chaining 1111 */ 1112 public StrSubstitutor setVariablePrefix(final char prefix) { 1113 return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix)); 1114 } 1115 1116 /** 1117 * Sets the variable prefix to use. 1118 * <p> 1119 * The variable prefix is the character or characters that identify the 1120 * start of a variable. This method allows a string prefix to be easily set. 1121 * </p> 1122 * 1123 * @param prefix the prefix for variables, not null 1124 * @return this, to enable chaining 1125 * @throws IllegalArgumentException if the prefix is null 1126 */ 1127 public StrSubstitutor setVariablePrefix(final String prefix) { 1128 if (prefix == null) { 1129 throw new IllegalArgumentException("Variable prefix must not be null!"); 1130 } 1131 return setVariablePrefixMatcher(StrMatcher.stringMatcher(prefix)); 1132 } 1133 1134 // Suffix 1135 //----------------------------------------------------------------------- 1136 /** 1137 * Gets the variable suffix matcher currently in use. 1138 * <p> 1139 * The variable suffix is the character or characters that identify the 1140 * end of a variable. This suffix is expressed in terms of a matcher 1141 * allowing advanced suffix matches. 1142 * </p> 1143 * 1144 * @return the suffix matcher in use 1145 */ 1146 public StrMatcher getVariableSuffixMatcher() { 1147 return suffixMatcher; 1148 } 1149 1150 /** 1151 * Sets the variable suffix matcher currently in use. 1152 * <p> 1153 * The variable suffix is the character or characters that identify the 1154 * end of a variable. This suffix is expressed in terms of a matcher 1155 * allowing advanced suffix matches. 1156 * </p> 1157 * 1158 * @param suffixMatcher the suffix matcher to use, null ignored 1159 * @return this, to enable chaining 1160 * @throws IllegalArgumentException if the suffix matcher is null 1161 */ 1162 public StrSubstitutor setVariableSuffixMatcher(final StrMatcher suffixMatcher) { 1163 if (suffixMatcher == null) { 1164 throw new IllegalArgumentException("Variable suffix matcher must not be null!"); 1165 } 1166 this.suffixMatcher = suffixMatcher; 1167 return this; 1168 } 1169 1170 /** 1171 * Sets the variable suffix to use. 1172 * <p> 1173 * The variable suffix is the character or characters that identify the 1174 * end of a variable. This method allows a single character suffix to 1175 * be easily set. 1176 * </p> 1177 * 1178 * @param suffix the suffix character to use 1179 * @return this, to enable chaining 1180 */ 1181 public StrSubstitutor setVariableSuffix(final char suffix) { 1182 return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix)); 1183 } 1184 1185 /** 1186 * Sets the variable suffix to use. 1187 * <p> 1188 * The variable suffix is the character or characters that identify the 1189 * end of a variable. This method allows a string suffix to be easily set. 1190 * </p> 1191 * 1192 * @param suffix the suffix for variables, not null 1193 * @return this, to enable chaining 1194 * @throws IllegalArgumentException if the suffix is null 1195 */ 1196 public StrSubstitutor setVariableSuffix(final String suffix) { 1197 if (suffix == null) { 1198 throw new IllegalArgumentException("Variable suffix must not be null!"); 1199 } 1200 return setVariableSuffixMatcher(StrMatcher.stringMatcher(suffix)); 1201 } 1202 1203 // Variable Default Value Delimiter 1204 //----------------------------------------------------------------------- 1205 /** 1206 * Gets the variable default value delimiter matcher currently in use. 1207 * <p> 1208 * The variable default value delimiter is the character or characters that delimit the 1209 * variable name and the variable default value. This delimiter is expressed in terms of a matcher 1210 * allowing advanced variable default value delimiter matches. 1211 * </p> 1212 * <p> 1213 * If it returns null, then the variable default value resolution is disabled. 1214 * </p> 1215 * 1216 * @return the variable default value delimiter matcher in use, may be null 1217 */ 1218 public StrMatcher getValueDelimiterMatcher() { 1219 return valueDelimiterMatcher; 1220 } 1221 1222 /** 1223 * Sets the variable default value delimiter matcher to use. 1224 * <p> 1225 * The variable default value delimiter is the character or characters that delimit the 1226 * variable name and the variable default value. This delimiter is expressed in terms of a matcher 1227 * allowing advanced variable default value delimiter matches. 1228 * </p> 1229 * <p> 1230 * If the <code>valueDelimiterMatcher</code> is null, then the variable default value resolution 1231 * becomes disabled. 1232 * </p> 1233 * 1234 * @param valueDelimiterMatcher variable default value delimiter matcher to use, may be null 1235 * @return this, to enable chaining 1236 */ 1237 public StrSubstitutor setValueDelimiterMatcher(final StrMatcher valueDelimiterMatcher) { 1238 this.valueDelimiterMatcher = valueDelimiterMatcher; 1239 return this; 1240 } 1241 1242 /** 1243 * Sets the variable default value delimiter to use. 1244 * <p> 1245 * The variable default value delimiter is the character or characters that delimit the 1246 * variable name and the variable default value. This method allows a single character 1247 * variable default value delimiter to be easily set. 1248 * </p> 1249 * 1250 * @param valueDelimiter the variable default value delimiter character to use 1251 * @return this, to enable chaining 1252 */ 1253 public StrSubstitutor setValueDelimiter(final char valueDelimiter) { 1254 return setValueDelimiterMatcher(StrMatcher.charMatcher(valueDelimiter)); 1255 } 1256 1257 /** 1258 * Sets the variable default value delimiter to use. 1259 * <p> 1260 * The variable default value delimiter is the character or characters that delimit the 1261 * variable name and the variable default value. This method allows a string 1262 * variable default value delimiter to be easily set. 1263 * </p> 1264 * <p> 1265 * If the <code>valueDelimiter</code> is null or empty string, then the variable default 1266 * value resolution becomes disabled. 1267 * </p> 1268 * 1269 * @param valueDelimiter the variable default value delimiter string to use, may be null or empty 1270 * @return this, to enable chaining 1271 */ 1272 public StrSubstitutor setValueDelimiter(final String valueDelimiter) { 1273 if (Strings.isEmpty(valueDelimiter)) { 1274 setValueDelimiterMatcher(null); 1275 return this; 1276 } 1277 return setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter)); 1278 } 1279 1280 // Resolver 1281 //----------------------------------------------------------------------- 1282 /** 1283 * Gets the VariableResolver that is used to lookup variables. 1284 * 1285 * @return the VariableResolver 1286 */ 1287 public StrLookup getVariableResolver() { 1288 return this.variableResolver; 1289 } 1290 1291 /** 1292 * Sets the VariableResolver that is used to lookup variables. 1293 * 1294 * @param variableResolver the VariableResolver 1295 */ 1296 public void setVariableResolver(final StrLookup variableResolver) { 1297 this.variableResolver = variableResolver; 1298 } 1299 1300 // Substitution support in variable names 1301 //----------------------------------------------------------------------- 1302 /** 1303 * Returns a flag whether substitution is done in variable names. 1304 * 1305 * @return the substitution in variable names flag 1306 */ 1307 public boolean isEnableSubstitutionInVariables() { 1308 return enableSubstitutionInVariables; 1309 } 1310 1311 /** 1312 * Sets a flag whether substitution is done in variable names. If set to 1313 * <b>true</b>, the names of variables can contain other variables which are 1314 * processed first before the original variable is evaluated, e.g. 1315 * <code>${jre-${java.version}}</code>. The default value is <b>false</b>. 1316 * 1317 * @param enableSubstitutionInVariables the new value of the flag 1318 */ 1319 public void setEnableSubstitutionInVariables(final boolean enableSubstitutionInVariables) { 1320 this.enableSubstitutionInVariables = enableSubstitutionInVariables; 1321 } 1322 1323 private char[] getChars(final StringBuilder sb) { 1324 final char[] chars = new char[sb.length()]; 1325 sb.getChars(0, sb.length(), chars, 0); 1326 return chars; 1327 } 1328 1329 /** 1330 * Appends a iterable placing separators between each value, but 1331 * not before the first or after the last. 1332 * Appending a null iterable will have no effect.. 1333 * 1334 * @param sb StringBuilder that contains the String being constructed. 1335 * @param iterable the iterable to append 1336 * @param separator the separator to use, null means no separator 1337 */ 1338 public void appendWithSeparators(final StringBuilder sb, final Iterable<?> iterable, String separator) { 1339 if (iterable != null) { 1340 separator = separator == null ? Strings.EMPTY : separator; 1341 final Iterator<?> it = iterable.iterator(); 1342 while (it.hasNext()) { 1343 sb.append(it.next()); 1344 if (it.hasNext()) { 1345 sb.append(separator); 1346 } 1347 } 1348 } 1349 } 1350 1351 @Override 1352 public String toString() { 1353 return "StrSubstitutor(" + variableResolver.toString() + ')'; 1354 } 1355 }