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