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