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