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