001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache license, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the license for the specific language governing permissions and 015 * limitations under the license. 016 */ 017 package org.apache.logging.log4j.core.lookup; 018 019 import org.apache.logging.log4j.core.LogEvent; 020 021 import java.util.ArrayList; 022 import java.util.Enumeration; 023 import java.util.HashMap; 024 import java.util.Iterator; 025 import java.util.List; 026 import java.util.Map; 027 import java.util.Properties; 028 029 /** 030 * Substitutes variables within a string by values. 031 * <p> 032 * This class takes a piece of text and substitutes all the variables within it. 033 * The default definition of a variable is <code>${variableName}</code>. 034 * The prefix and suffix can be changed via constructors and set methods. 035 * <p> 036 * Variable values are typically resolved from a map, but could also be resolved 037 * from system properties, or by supplying a custom variable resolver. 038 * <p> 039 * The simplest example is to use this class to replace Java System properties. For example: 040 * <pre> 041 * StrSubstitutor.replaceSystemProperties( 042 * "You are running with java.version = ${java.version} and os.name = ${os.name}."); 043 * </pre> 044 * <p> 045 * Typical usage of this class follows the following pattern: First an instance is created 046 * and initialized with the map that contains the values for the available variables. 047 * If a prefix and/or suffix for variables should be used other than the default ones, 048 * the appropriate settings can be performed. After that the <code>replace()</code> 049 * method can be called passing in the source text for interpolation. In the returned 050 * text all variable references (as long as their values are known) will be resolved. 051 * The following example demonstrates this: 052 * <pre> 053 * Map valuesMap = HashMap(); 054 * valuesMap.put("animal", "quick brown fox"); 055 * valuesMap.put("target", "lazy dog"); 056 * String templateString = "The ${animal} jumped over the ${target}."; 057 * StrSubstitutor sub = new StrSubstitutor(valuesMap); 058 * String resolvedString = sub.replace(templateString); 059 * </pre> 060 * yielding: 061 * <pre> 062 * The quick brown fox jumped over the lazy dog. 063 * </pre> 064 * <p> 065 * In addition to this usage pattern there are some static convenience methods that 066 * cover the most common use cases. These methods can be used without the need of 067 * manually creating an instance. However if multiple replace operations are to be 068 * performed, creating and reusing an instance of this class will be more efficient. 069 * <p> 070 * Variable replacement works in a recursive way. Thus, if a variable value contains 071 * a variable then that variable will also be replaced. Cyclic replacements are 072 * detected and will cause an exception to be thrown. 073 * <p> 074 * Sometimes the interpolation's result must contain a variable prefix. As an example 075 * take the following source text: 076 * <pre> 077 * The variable ${${name}} must be used. 078 * </pre> 079 * Here only the variable's name referred to in the text should be replaced resulting 080 * in the text (assuming that the value of the <code>name</code> variable is <code>x</code>): 081 * <pre> 082 * The variable ${x} must be used. 083 * </pre> 084 * To achieve this effect there are two possibilities: Either set a different prefix 085 * and suffix for variables which do not conflict with the result text you want to 086 * produce. The other possibility is to use the escape character, by default '$'. 087 * If this character is placed before a variable reference, this reference is ignored 088 * and won't be replaced. For example: 089 * <pre> 090 * The variable $${${name}} must be used. 091 * </pre> 092 * <p> 093 * In some complex scenarios you might even want to perform substitution in the 094 * names of variables, for instance 095 * <pre> 096 * ${jre-${java.specification.version}} 097 * </pre> 098 * <code>StrSubstitutor</code> supports this recursive substitution in variable 099 * names, but it has to be enabled explicitly by setting the 100 * {@link #setEnableSubstitutionInVariables(boolean) enableSubstitutionInVariables} 101 * property to <b>true</b>. 102 * 103 */ 104 public class StrSubstitutor { 105 106 /** 107 * Constant for the default escape character. 108 */ 109 public static final char DEFAULT_ESCAPE = '$'; 110 /** 111 * Constant for the default variable prefix. 112 */ 113 public static final StrMatcher DEFAULT_PREFIX = StrMatcher.stringMatcher("${"); 114 /** 115 * Constant for the default variable suffix. 116 */ 117 public static final StrMatcher DEFAULT_SUFFIX = StrMatcher.stringMatcher("}"); 118 119 private static final int BUF_SIZE = 256; 120 121 /** 122 * Stores the escape character. 123 */ 124 private char escapeChar; 125 /** 126 * Stores the variable prefix. 127 */ 128 private StrMatcher prefixMatcher; 129 /** 130 * Stores the variable suffix. 131 */ 132 private StrMatcher suffixMatcher; 133 /** 134 * Variable resolution is delegated to an implementor of VariableResolver. 135 */ 136 private StrLookup variableResolver; 137 /** 138 * The flag whether substitution in variable names is enabled. 139 */ 140 private boolean enableSubstitutionInVariables; 141 142 //----------------------------------------------------------------------- 143 /** 144 * Creates a new instance with defaults for variable prefix and suffix 145 * and the escaping character. 146 */ 147 public StrSubstitutor() { 148 this(null, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); 149 } 150 /** 151 * Creates a new instance and initializes it. Uses defaults for variable 152 * prefix and suffix and the escaping character. 153 * 154 * @param valueMap the map with the variables' values, may be null 155 */ 156 public StrSubstitutor(final Map<String, String> valueMap) { 157 this(new MapLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); 158 } 159 160 /** 161 * Creates a new instance and initializes it. Uses a default escaping character. 162 * 163 * @param valueMap the map with the variables' values, may be null 164 * @param prefix the prefix for variables, not null 165 * @param suffix the suffix for variables, not null 166 * @throws IllegalArgumentException if the prefix or suffix is null 167 */ 168 public StrSubstitutor(final Map<String, String> valueMap, final String prefix, final String suffix) { 169 this(new MapLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE); 170 } 171 172 /** 173 * Creates a new instance and initializes it. 174 * 175 * @param valueMap the map with the variables' values, may be null 176 * @param prefix the prefix for variables, not null 177 * @param suffix the suffix for variables, not null 178 * @param escape the escape character 179 * @throws IllegalArgumentException if the prefix or suffix is null 180 */ 181 public StrSubstitutor(final Map<String, String> valueMap, final String prefix, final String suffix, 182 final char escape) { 183 this(new MapLookup(valueMap), prefix, suffix, escape); 184 } 185 186 /** 187 * Creates a new instance and initializes it. 188 * 189 * @param variableResolver the variable resolver, may be null 190 */ 191 public StrSubstitutor(final StrLookup variableResolver) { 192 this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); 193 } 194 195 /** 196 * Creates a new instance and initializes it. 197 * 198 * @param variableResolver the variable resolver, may be null 199 * @param prefix the prefix for variables, not null 200 * @param suffix the suffix for variables, not null 201 * @param escape the escape character 202 * @throws IllegalArgumentException if the prefix or suffix is null 203 */ 204 public StrSubstitutor(final StrLookup variableResolver, final String prefix, final String suffix, 205 final char escape) { 206 this.setVariableResolver(variableResolver); 207 this.setVariablePrefix(prefix); 208 this.setVariableSuffix(suffix); 209 this.setEscapeChar(escape); 210 } 211 212 /** 213 * Creates a new instance and initializes it. 214 * 215 * @param variableResolver the variable resolver, may be null 216 * @param prefixMatcher the prefix for variables, not null 217 * @param suffixMatcher the suffix for variables, not null 218 * @param escape the escape character 219 * @throws IllegalArgumentException if the prefix or suffix is null 220 */ 221 public StrSubstitutor(final StrLookup variableResolver, final StrMatcher prefixMatcher, 222 final StrMatcher suffixMatcher, 223 final char escape) { 224 this.setVariableResolver(variableResolver); 225 this.setVariablePrefixMatcher(prefixMatcher); 226 this.setVariableSuffixMatcher(suffixMatcher); 227 this.setEscapeChar(escape); 228 } 229 //----------------------------------------------------------------------- 230 /** 231 * Replaces all the occurrences of variables in the given source object with 232 * their matching values from the map. 233 * 234 * @param source the source text containing the variables to substitute, null returns null 235 * @param valueMap the map with the values, may be null 236 * @return the result of the replace operation 237 */ 238 public static String replace(final Object source, final Map<String, String> valueMap) { 239 return new StrSubstitutor(valueMap).replace(source); 240 } 241 242 /** 243 * Replaces all the occurrences of variables in the given source object with 244 * their matching values from the map. This method allows to specifiy a 245 * custom variable prefix and suffix 246 * 247 * @param source the source text containing the variables to substitute, null returns null 248 * @param valueMap the map with the values, may be null 249 * @param prefix the prefix of variables, not null 250 * @param suffix the suffix of variables, not null 251 * @return the result of the replace operation 252 * @throws IllegalArgumentException if the prefix or suffix is null 253 */ 254 public static String replace(final Object source, final Map<String, String> valueMap, final String prefix, 255 final String suffix) { 256 return new StrSubstitutor(valueMap, prefix, suffix).replace(source); 257 } 258 259 /** 260 * Replaces all the occurrences of variables in the given source object with their matching 261 * values from the properties. 262 * 263 * @param source the source text containing the variables to substitute, null returns null 264 * @param valueProperties the properties with values, may be null 265 * @return the result of the replace operation 266 */ 267 public static String replace(final Object source, final Properties valueProperties) { 268 if (valueProperties == null) { 269 return source.toString(); 270 } 271 final Map<String, String> valueMap = new HashMap<String, String>(); 272 final Enumeration<?> propNames = valueProperties.propertyNames(); 273 while (propNames.hasMoreElements()) { 274 final String propName = (String) propNames.nextElement(); 275 final String propValue = valueProperties.getProperty(propName); 276 valueMap.put(propName, propValue); 277 } 278 return StrSubstitutor.replace(source, valueMap); 279 } 280 281 //----------------------------------------------------------------------- 282 /** 283 * Replaces all the occurrences of variables with their matching values 284 * from the resolver using the given source string as a template. 285 * 286 * @param source the string to replace in, null returns null 287 * @return the result of the replace operation 288 */ 289 public String replace(final String source) { 290 return replace(null, source); 291 } 292 //----------------------------------------------------------------------- 293 /** 294 * Replaces all the occurrences of variables with their matching values 295 * from the resolver using the given source string as a template. 296 * 297 * @param event The current LogEvent if there is one. 298 * @param source the string to replace in, null returns null 299 * @return the result of the replace operation 300 */ 301 public String replace(final LogEvent event, final String source) { 302 if (source == null) { 303 return null; 304 } 305 final StringBuilder buf = new StringBuilder(source); 306 if (!substitute(event, buf, 0, source.length())) { 307 return source; 308 } 309 return buf.toString(); 310 } 311 312 /** 313 * Replaces all the occurrences of variables with their matching values 314 * from the resolver using the given source string as a template. 315 * <p> 316 * Only the specified portion of the string will be processed. 317 * The rest of the string is not processed, and is not returned. 318 * 319 * @param source the string to replace in, null returns null 320 * @param offset the start offset within the array, must be valid 321 * @param length the length within the array to be processed, must be valid 322 * @return the result of the replace operation 323 */ 324 public String replace(final String source, final int offset, final int length) { 325 return replace(null, source, offset, length); 326 } 327 328 /** 329 * Replaces all the occurrences of variables with their matching values 330 * from the resolver using the given source string as a template. 331 * <p> 332 * Only the specified portion of the string will be processed. 333 * The rest of the string is not processed, and is not returned. 334 * 335 * @param event the current LogEvent, if one exists. 336 * @param source the string to replace in, null returns null 337 * @param offset the start offset within the array, must be valid 338 * @param length the length within the array to be processed, must be valid 339 * @return the result of the replace operation 340 */ 341 public String replace(final LogEvent event, final String source, final int offset, final int length) { 342 if (source == null) { 343 return null; 344 } 345 final StringBuilder buf = new StringBuilder(length).append(source, offset, length); 346 if (!substitute(event, buf, 0, length)) { 347 return source.substring(offset, offset + length); 348 } 349 return buf.toString(); 350 } 351 352 //----------------------------------------------------------------------- 353 /** 354 * Replaces all the occurrences of variables with their matching values 355 * from the resolver using the given source array as a template. 356 * The array is not altered by this method. 357 * 358 * @param source the character array to replace in, not altered, null returns null 359 * @return the result of the replace operation 360 */ 361 public String replace(final char[] source) { 362 return replace(null, source); 363 } 364 365 //----------------------------------------------------------------------- 366 /** 367 * Replaces all the occurrences of variables with their matching values 368 * from the resolver using the given source array as a template. 369 * The array is not altered by this method. 370 * 371 * @param event the current LogEvent, if one exists. 372 * @param source the character array to replace in, not altered, null returns null 373 * @return the result of the replace operation 374 */ 375 public String replace(final LogEvent event, final char[] source) { 376 if (source == null) { 377 return null; 378 } 379 final StringBuilder buf = new StringBuilder(source.length).append(source); 380 substitute(event, buf, 0, source.length); 381 return buf.toString(); 382 } 383 384 /** 385 * Replaces all the occurrences of variables with their matching values 386 * from the resolver using the given source array as a template. 387 * The array is not altered by this method. 388 * <p> 389 * Only the specified portion of the array will be processed. 390 * The rest of the array is not processed, and is not returned. 391 * 392 * @param source the character array to replace in, not altered, null returns null 393 * @param offset the start offset within the array, must be valid 394 * @param length the length within the array to be processed, must be valid 395 * @return the result of the replace operation 396 */ 397 public String replace(final char[] source, final int offset, final int length) { 398 return replace(null, source, offset, length); 399 } 400 401 /** 402 * Replaces all the occurrences of variables with their matching values 403 * from the resolver using the given source array as a template. 404 * The array is not altered by this method. 405 * <p> 406 * Only the specified portion of the array will be processed. 407 * The rest of the array is not processed, and is not returned. 408 * 409 * @param event the current LogEvent, if one exists. 410 * @param source the character array to replace in, not altered, null returns null 411 * @param offset the start offset within the array, must be valid 412 * @param length the length within the array to be processed, must be valid 413 * @return the result of the replace operation 414 */ 415 public String replace(final LogEvent event, final char[] source, final int offset, final int length) { 416 if (source == null) { 417 return null; 418 } 419 final StringBuilder buf = new StringBuilder(length).append(source, offset, length); 420 substitute(event, buf, 0, length); 421 return buf.toString(); 422 } 423 424 //----------------------------------------------------------------------- 425 /** 426 * Replaces all the occurrences of variables with their matching values 427 * from the resolver using the given source buffer as a template. 428 * The buffer is not altered by this method. 429 * 430 * @param source the buffer to use as a template, not changed, null returns null 431 * @return the result of the replace operation 432 */ 433 public String replace(final StringBuffer source) { 434 return replace(null, source); 435 } 436 437 //----------------------------------------------------------------------- 438 /** 439 * Replaces all the occurrences of variables with their matching values 440 * from the resolver using the given source buffer as a template. 441 * The buffer is not altered by this method. 442 * 443 * @param event the current LogEvent, if one exists. 444 * @param source the buffer to use as a template, not changed, null returns null 445 * @return the result of the replace operation 446 */ 447 public String replace(final LogEvent event, final StringBuffer source) { 448 if (source == null) { 449 return null; 450 } 451 final StringBuilder buf = new StringBuilder(source.length()).append(source); 452 substitute(event, buf, 0, buf.length()); 453 return buf.toString(); 454 } 455 456 /** 457 * Replaces all the occurrences of variables with their matching values 458 * from the resolver using the given source buffer as a template. 459 * The buffer is not altered by this method. 460 * <p> 461 * Only the specified portion of the buffer will be processed. 462 * The rest of the buffer is not processed, and is not returned. 463 * 464 * @param source the buffer to use as a template, not changed, null returns null 465 * @param offset the start offset within the array, must be valid 466 * @param length the length within the array to be processed, must be valid 467 * @return the result of the replace operation 468 */ 469 public String replace(final StringBuffer source, final int offset, final int length) { 470 return replace(null, source, offset, length); 471 } 472 473 /** 474 * Replaces all the occurrences of variables with their matching values 475 * from the resolver using the given source buffer as a template. 476 * The buffer is not altered by this method. 477 * <p> 478 * Only the specified portion of the buffer will be processed. 479 * The rest of the buffer is not processed, and is not returned. 480 * 481 * @param event the current LogEvent, if one exists. 482 * @param source the buffer to use as a template, not changed, null returns null 483 * @param offset the start offset within the array, must be valid 484 * @param length the length within the array to be processed, must be valid 485 * @return the result of the replace operation 486 */ 487 public String replace(final LogEvent event, final StringBuffer source, final int offset, final int length) { 488 if (source == null) { 489 return null; 490 } 491 final StringBuilder buf = new StringBuilder(length).append(source, offset, length); 492 substitute(event, buf, 0, length); 493 return buf.toString(); 494 } 495 496 //----------------------------------------------------------------------- 497 /** 498 * Replaces all the occurrences of variables with their matching values 499 * from the resolver using the given source builder as a template. 500 * The builder is not altered by this method. 501 * 502 * @param source the builder to use as a template, not changed, null returns null 503 * @return the result of the replace operation 504 */ 505 public String replace(final StringBuilder source) { 506 return replace(null, source); 507 } 508 509 //----------------------------------------------------------------------- 510 /** 511 * Replaces all the occurrences of variables with their matching values 512 * from the resolver using the given source builder as a template. 513 * The builder is not altered by this method. 514 * 515 * @param event The LogEvent. 516 * @param source the builder to use as a template, not changed, null returns null. 517 * @return the result of the replace operation. 518 */ 519 public String replace(final LogEvent event, final StringBuilder source) { 520 if (source == null) { 521 return null; 522 } 523 final StringBuilder buf = new StringBuilder(source.length()).append(source); 524 substitute(event, buf, 0, buf.length()); 525 return buf.toString(); 526 } 527 /** 528 * Replaces all the occurrences of variables with their matching values 529 * from the resolver using the given source builder as a template. 530 * The builder is not altered by this method. 531 * <p> 532 * Only the specified portion of the builder will be processed. 533 * The rest of the builder is not processed, and is not returned. 534 * 535 * @param source the builder to use as a template, not changed, 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 StringBuilder source, final int offset, final int length) { 541 return replace(null, source, offset, length); 542 } 543 544 /** 545 * Replaces all the occurrences of variables with their matching values 546 * from the resolver using the given source builder as a template. 547 * The builder is not altered by this method. 548 * <p> 549 * Only the specified portion of the builder will be processed. 550 * The rest of the builder is not processed, and is not returned. 551 * 552 * @param event the current LogEvent, if one exists. 553 * @param source the builder to use as a template, not changed, null returns null 554 * @param offset the start offset within the array, must be valid 555 * @param length the length within the array to be processed, must be valid 556 * @return the result of the replace operation 557 */ 558 public String replace(final LogEvent event, final StringBuilder source, final int offset, final int length) { 559 if (source == null) { 560 return null; 561 } 562 final StringBuilder buf = new StringBuilder(length).append(source, offset, length); 563 substitute(event, buf, 0, length); 564 return buf.toString(); 565 } 566 567 //----------------------------------------------------------------------- 568 /** 569 * Replaces all the occurrences of variables in the given source object with 570 * their matching values from the resolver. The input source object is 571 * converted to a string using <code>toString</code> and is not altered. 572 * 573 * @param source the source to replace in, null returns null 574 * @return the result of the replace operation 575 */ 576 public String replace(final Object source) { 577 return replace(null, source); 578 } 579 //----------------------------------------------------------------------- 580 /** 581 * Replaces all the occurrences of variables in the given source object with 582 * their matching values from the resolver. The input source object is 583 * converted to a string using <code>toString</code> and is not altered. 584 * 585 * @param event the current LogEvent, if one exists. 586 * @param source the source to replace in, null returns null 587 * @return the result of the replace operation 588 */ 589 public String replace(final LogEvent event, final Object source) { 590 if (source == null) { 591 return null; 592 } 593 final StringBuilder buf = new StringBuilder().append(source); 594 substitute(event, buf, 0, buf.length()); 595 return buf.toString(); 596 } 597 598 //----------------------------------------------------------------------- 599 /** 600 * Replaces all the occurrences of variables within the given source buffer 601 * with their matching values from the resolver. 602 * The buffer is updated with the result. 603 * 604 * @param source the buffer to replace in, updated, null returns zero 605 * @return true if altered 606 */ 607 public boolean replaceIn(final StringBuffer source) { 608 if (source == null) { 609 return false; 610 } 611 return replaceIn(source, 0, source.length()); 612 } 613 614 /** 615 * Replaces all the occurrences of variables within the given source buffer 616 * with their matching values from the resolver. 617 * The buffer is updated with the result. 618 * <p> 619 * Only the specified portion of the buffer will be processed. 620 * The rest of the buffer is not processed, but it is not deleted. 621 * 622 * @param source the buffer to replace in, updated, null returns zero 623 * @param offset the start offset within the array, must be valid 624 * @param length the length within the buffer to be processed, must be valid 625 * @return true if altered 626 */ 627 public boolean replaceIn(final StringBuffer source, final int offset, final int length) { 628 return replaceIn(null, source, offset, length); 629 } 630 631 /** 632 * Replaces all the occurrences of variables within the given source buffer 633 * with their matching values from the resolver. 634 * The buffer is updated with the result. 635 * <p> 636 * Only the specified portion of the buffer will be processed. 637 * The rest of the buffer is not processed, but it is not deleted. 638 * 639 * @param event the current LogEvent, if one exists. 640 * @param source the buffer to replace in, updated, null returns zero 641 * @param offset the start offset within the array, must be valid 642 * @param length the length within the buffer to be processed, must be valid 643 * @return true if altered 644 */ 645 public boolean replaceIn(final LogEvent event, final StringBuffer source, final int offset, final int length) { 646 if (source == null) { 647 return false; 648 } 649 final StringBuilder buf = new StringBuilder(length).append(source, offset, length); 650 if (!substitute(event, buf, 0, length)) { 651 return false; 652 } 653 source.replace(offset, offset + length, buf.toString()); 654 return true; 655 } 656 657 //----------------------------------------------------------------------- 658 /** 659 * Replaces all the occurrences of variables within the given source 660 * builder with their matching values from the resolver. 661 * 662 * @param source the builder to replace in, updated, null returns zero 663 * @return true if altered 664 */ 665 public boolean replaceIn(final StringBuilder source) { 666 return replaceIn(null, source); 667 } 668 669 //----------------------------------------------------------------------- 670 /** 671 * Replaces all the occurrences of variables within the given source 672 * builder with their matching values from the resolver. 673 * 674 * @param event the current LogEvent, if one exists. 675 * @param source the builder to replace in, updated, null returns zero 676 * @return true if altered 677 */ 678 public boolean replaceIn(final LogEvent event, final StringBuilder source) { 679 if (source == null) { 680 return false; 681 } 682 return substitute(event, source, 0, source.length()); 683 } 684 /** 685 * Replaces all the occurrences of variables within the given source 686 * builder with their matching values from the resolver. 687 * <p> 688 * Only the specified portion of the builder will be processed. 689 * The rest of the builder is not processed, but it is not deleted. 690 * 691 * @param source the builder to replace in, null returns zero 692 * @param offset the start offset within the array, must be valid 693 * @param length the length within the builder to be processed, must be valid 694 * @return true if altered 695 */ 696 public boolean replaceIn(final StringBuilder source, final int offset, final int length) { 697 return replaceIn(null, source, offset, length); 698 } 699 700 /** 701 * Replaces all the occurrences of variables within the given source 702 * builder with their matching values from the resolver. 703 * <p> 704 * Only the specified portion of the builder will be processed. 705 * The rest of the builder is not processed, but it is not deleted. 706 * 707 * @param event the current LogEvent, if one is present. 708 * @param source the builder to replace in, null returns zero 709 * @param offset the start offset within the array, must be valid 710 * @param length the length within the builder to be processed, must be valid 711 * @return true if altered 712 */ 713 public boolean replaceIn(final LogEvent event, final StringBuilder source, final int offset, final int length) { 714 if (source == null) { 715 return false; 716 } 717 return substitute(event, source, offset, length); 718 } 719 720 //----------------------------------------------------------------------- 721 /** 722 * Internal method that substitutes the variables. 723 * <p> 724 * Most users of this class do not need to call this method. This method will 725 * be called automatically by another (public) method. 726 * <p> 727 * Writers of subclasses can override this method if they need access to 728 * the substitution process at the start or end. 729 * 730 * @param event The current LogEvent, if there is one. 731 * @param buf the string builder to substitute into, not null 732 * @param offset the start offset within the builder, must be valid 733 * @param length the length within the builder to be processed, must be valid 734 * @return true if altered 735 */ 736 protected boolean substitute(final LogEvent event, final StringBuilder buf, final int offset, final int length) { 737 return substitute(event, buf, offset, length, null) > 0; 738 } 739 740 /** 741 * Recursive handler for multiple levels of interpolation. This is the main 742 * interpolation method, which resolves the values of all variable references 743 * contained in the passed in text. 744 * 745 * @param event The current LogEvent, if there is one. 746 * @param buf the string builder to substitute into, not null 747 * @param offset the start offset within the builder, must be valid 748 * @param length the length within the builder to be processed, must be valid 749 * @param priorVariables the stack keeping track of the replaced variables, may be null 750 * @return the length change that occurs, unless priorVariables is null when the int 751 * represents a boolean flag as to whether any change occurred. 752 */ 753 private int substitute(final LogEvent event, final StringBuilder buf, final int offset, final int length, 754 List<String> priorVariables) { 755 final StrMatcher prefixMatcher = getVariablePrefixMatcher(); 756 final StrMatcher suffixMatcher = getVariableSuffixMatcher(); 757 final char escape = getEscapeChar(); 758 759 final boolean top = (priorVariables == null); 760 boolean altered = false; 761 int lengthChange = 0; 762 char[] chars = getChars(buf); 763 int bufEnd = offset + length; 764 int pos = offset; 765 while (pos < bufEnd) { 766 final int startMatchLen = prefixMatcher.isMatch(chars, pos, offset, 767 bufEnd); 768 if (startMatchLen == 0) { 769 pos++; 770 } else { 771 // found variable start marker 772 if (pos > offset && chars[pos - 1] == escape) { 773 // escaped 774 buf.deleteCharAt(pos - 1); 775 chars = getChars(buf); 776 lengthChange--; 777 altered = true; 778 bufEnd--; 779 } else { 780 // find suffix 781 final int startPos = pos; 782 pos += startMatchLen; 783 int endMatchLen = 0; 784 int nestedVarCount = 0; 785 while (pos < bufEnd) { 786 if (isEnableSubstitutionInVariables() 787 && (endMatchLen = prefixMatcher.isMatch(chars, 788 pos, offset, bufEnd)) != 0) { 789 // found a nested variable start 790 nestedVarCount++; 791 pos += endMatchLen; 792 continue; 793 } 794 795 endMatchLen = suffixMatcher.isMatch(chars, pos, offset, 796 bufEnd); 797 if (endMatchLen == 0) { 798 pos++; 799 } else { 800 // found variable end marker 801 if (nestedVarCount == 0) { 802 String varName = new String(chars, startPos 803 + startMatchLen, pos - startPos 804 - startMatchLen); 805 if (isEnableSubstitutionInVariables()) { 806 final StringBuilder bufName = new StringBuilder(varName); 807 substitute(event, bufName, 0, bufName.length()); 808 varName = bufName.toString(); 809 } 810 pos += endMatchLen; 811 final int endPos = pos; 812 813 // on the first call initialize priorVariables 814 if (priorVariables == null) { 815 priorVariables = new ArrayList<String>(); 816 priorVariables.add(new String(chars, 817 offset, length)); 818 } 819 820 // handle cyclic substitution 821 checkCyclicSubstitution(varName, priorVariables); 822 priorVariables.add(varName); 823 824 // resolve the variable 825 final String varValue = resolveVariable(event, varName, buf, 826 startPos, endPos); 827 if (varValue != null) { 828 // recursive replace 829 final int varLen = varValue.length(); 830 buf.replace(startPos, endPos, varValue); 831 altered = true; 832 int change = substitute(event, buf, startPos, 833 varLen, priorVariables); 834 change = change 835 + (varLen - (endPos - startPos)); 836 pos += change; 837 bufEnd += change; 838 lengthChange += change; 839 chars = getChars(buf); // in case buffer was 840 // altered 841 } 842 843 // remove variable from the cyclic stack 844 priorVariables 845 .remove(priorVariables.size() - 1); 846 break; 847 } else { 848 nestedVarCount--; 849 pos += endMatchLen; 850 } 851 } 852 } 853 } 854 } 855 } 856 if (top) { 857 return altered ? 1 : 0; 858 } 859 return lengthChange; 860 } 861 862 /** 863 * Checks if the specified variable is already in the stack (list) of variables. 864 * 865 * @param varName the variable name to check 866 * @param priorVariables the list of prior variables 867 */ 868 private void checkCyclicSubstitution(final String varName, final List<String> priorVariables) { 869 if (!priorVariables.contains(varName)) { 870 return; 871 } 872 final StringBuilder buf = new StringBuilder(BUF_SIZE); 873 buf.append("Infinite loop in property interpolation of "); 874 buf.append(priorVariables.remove(0)); 875 buf.append(": "); 876 appendWithSeparators(buf, priorVariables, "->"); 877 throw new IllegalStateException(buf.toString()); 878 } 879 880 /** 881 * Internal method that resolves the value of a variable. 882 * <p> 883 * Most users of this class do not need to call this method. This method is 884 * called automatically by the substitution process. 885 * <p> 886 * Writers of subclasses can override this method if they need to alter 887 * how each substitution occurs. The method is passed the variable's name 888 * and must return the corresponding value. This implementation uses the 889 * {@link #getVariableResolver()} with the variable's name as the key. 890 * 891 * @param event The LogEvent, if there is one. 892 * @param variableName the name of the variable, not null 893 * @param buf the buffer where the substitution is occurring, not null 894 * @param startPos the start position of the variable including the prefix, valid 895 * @param endPos the end position of the variable including the suffix, valid 896 * @return the variable's value or <b>null</b> if the variable is unknown 897 */ 898 protected String resolveVariable(final LogEvent event, final String variableName, final StringBuilder buf, 899 final int startPos, final int endPos) { 900 final StrLookup resolver = getVariableResolver(); 901 if (resolver == null) { 902 return null; 903 } 904 return resolver.lookup(event, variableName); 905 } 906 907 // Escape 908 //----------------------------------------------------------------------- 909 /** 910 * Returns the escape character. 911 * 912 * @return the character used for escaping variable references 913 */ 914 public char getEscapeChar() { 915 return this.escapeChar; 916 } 917 918 /** 919 * Sets the escape character. 920 * If this character is placed before a variable reference in the source 921 * text, this variable will be ignored. 922 * 923 * @param escapeCharacter the escape character (0 for disabling escaping) 924 */ 925 public void setEscapeChar(final char escapeCharacter) { 926 this.escapeChar = escapeCharacter; 927 } 928 929 // Prefix 930 //----------------------------------------------------------------------- 931 /** 932 * Gets the variable prefix matcher currently in use. 933 * <p> 934 * The variable prefix is the characer or characters that identify the 935 * start of a variable. This prefix is expressed in terms of a matcher 936 * allowing advanced prefix matches. 937 * 938 * @return the prefix matcher in use 939 */ 940 public StrMatcher getVariablePrefixMatcher() { 941 return prefixMatcher; 942 } 943 944 /** 945 * Sets the variable prefix matcher currently in use. 946 * <p> 947 * The variable prefix is the characer or characters that identify the 948 * start of a variable. This prefix is expressed in terms of a matcher 949 * allowing advanced prefix matches. 950 * 951 * @param prefixMatcher the prefix matcher to use, null ignored 952 * @return this, to enable chaining 953 * @throws IllegalArgumentException if the prefix matcher is null 954 */ 955 public StrSubstitutor setVariablePrefixMatcher(final StrMatcher prefixMatcher) { 956 if (prefixMatcher == null) { 957 throw new IllegalArgumentException("Variable prefix matcher must not be null!"); 958 } 959 this.prefixMatcher = prefixMatcher; 960 return this; 961 } 962 963 /** 964 * Sets the variable prefix to use. 965 * <p> 966 * The variable prefix is the character or characters that identify the 967 * start of a variable. This method allows a single character prefix to 968 * be easily set. 969 * 970 * @param prefix the prefix character to use 971 * @return this, to enable chaining 972 */ 973 public StrSubstitutor setVariablePrefix(final char prefix) { 974 return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix)); 975 } 976 977 /** 978 * Sets the variable prefix to use. 979 * <p> 980 * The variable prefix is the characer or characters that identify the 981 * start of a variable. This method allows a string prefix to be easily set. 982 * 983 * @param prefix the prefix for variables, not null 984 * @return this, to enable chaining 985 * @throws IllegalArgumentException if the prefix is null 986 */ 987 public StrSubstitutor setVariablePrefix(final String prefix) { 988 if (prefix == null) { 989 throw new IllegalArgumentException("Variable prefix must not be null!"); 990 } 991 return setVariablePrefixMatcher(StrMatcher.stringMatcher(prefix)); 992 } 993 994 // Suffix 995 //----------------------------------------------------------------------- 996 /** 997 * Gets the variable suffix matcher currently in use. 998 * <p> 999 * The variable suffix is the characer or characters that identify the 1000 * end of a variable. This suffix is expressed in terms of a matcher 1001 * allowing advanced suffix matches. 1002 * 1003 * @return the suffix matcher in use 1004 */ 1005 public StrMatcher getVariableSuffixMatcher() { 1006 return suffixMatcher; 1007 } 1008 1009 /** 1010 * Sets the variable suffix matcher currently in use. 1011 * <p> 1012 * The variable suffix is the characer or characters that identify the 1013 * end of a variable. This suffix is expressed in terms of a matcher 1014 * allowing advanced suffix matches. 1015 * 1016 * @param suffixMatcher the suffix matcher to use, null ignored 1017 * @return this, to enable chaining 1018 * @throws IllegalArgumentException if the suffix matcher is null 1019 */ 1020 public StrSubstitutor setVariableSuffixMatcher(final StrMatcher suffixMatcher) { 1021 if (suffixMatcher == null) { 1022 throw new IllegalArgumentException("Variable suffix matcher must not be null!"); 1023 } 1024 this.suffixMatcher = suffixMatcher; 1025 return this; 1026 } 1027 1028 /** 1029 * Sets the variable suffix to use. 1030 * <p> 1031 * The variable suffix is the characer or characters that identify the 1032 * end of a variable. This method allows a single character suffix to 1033 * be easily set. 1034 * 1035 * @param suffix the suffix character to use 1036 * @return this, to enable chaining 1037 */ 1038 public StrSubstitutor setVariableSuffix(final char suffix) { 1039 return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix)); 1040 } 1041 1042 /** 1043 * Sets the variable suffix to use. 1044 * <p> 1045 * The variable suffix is the character or characters that identify the 1046 * end of a variable. This method allows a string suffix to be easily set. 1047 * 1048 * @param suffix the suffix for variables, not null 1049 * @return this, to enable chaining 1050 * @throws IllegalArgumentException if the suffix is null 1051 */ 1052 public StrSubstitutor setVariableSuffix(final String suffix) { 1053 if (suffix == null) { 1054 throw new IllegalArgumentException("Variable suffix must not be null!"); 1055 } 1056 return setVariableSuffixMatcher(StrMatcher.stringMatcher(suffix)); 1057 } 1058 1059 // Resolver 1060 //----------------------------------------------------------------------- 1061 /** 1062 * Gets the VariableResolver that is used to lookup variables. 1063 * 1064 * @return the VariableResolver 1065 */ 1066 public StrLookup getVariableResolver() { 1067 return this.variableResolver; 1068 } 1069 1070 /** 1071 * Sets the VariableResolver that is used to lookup variables. 1072 * 1073 * @param variableResolver the VariableResolver 1074 */ 1075 public void setVariableResolver(final StrLookup variableResolver) { 1076 this.variableResolver = variableResolver; 1077 } 1078 1079 // Substitution support in variable names 1080 //----------------------------------------------------------------------- 1081 /** 1082 * Returns a flag whether substitution is done in variable names. 1083 * 1084 * @return the substitution in variable names flag 1085 */ 1086 public boolean isEnableSubstitutionInVariables() { 1087 return enableSubstitutionInVariables; 1088 } 1089 1090 /** 1091 * Sets a flag whether substitution is done in variable names. If set to 1092 * <b>true</b>, the names of variables can contain other variables which are 1093 * processed first before the original variable is evaluated, e.g. 1094 * <code>${jre-${java.version}}</code>. The default value is <b>false</b>. 1095 * 1096 * @param enableSubstitutionInVariables the new value of the flag 1097 */ 1098 public void setEnableSubstitutionInVariables(final boolean enableSubstitutionInVariables) { 1099 this.enableSubstitutionInVariables = enableSubstitutionInVariables; 1100 } 1101 1102 private char[] getChars(final StringBuilder sb) { 1103 final char[] chars = new char[sb.length()]; 1104 sb.getChars(0, sb.length(), chars, 0); 1105 return chars; 1106 } 1107 1108 /** 1109 * Appends a iterable placing separators between each value, but 1110 * not before the first or after the last. 1111 * Appending a null iterable will have no effect.. 1112 * 1113 * @param sb StringBuilder that contains the String being constructed. 1114 * @param iterable the iterable to append 1115 * @param separator the separator to use, null means no separator 1116 */ 1117 public void appendWithSeparators(final StringBuilder sb, final Iterable<?> iterable, String separator) { 1118 if (iterable != null) { 1119 separator = (separator == null ? "" : separator); 1120 final Iterator<?> it = iterable.iterator(); 1121 while (it.hasNext()) { 1122 sb.append(it.next()); 1123 if (it.hasNext()) { 1124 sb.append(separator); 1125 } 1126 } 1127 } 1128 } 1129 1130 @Override 1131 public String toString() { 1132 return "StrSubstitutor(" + variableResolver.toString() + ")"; 1133 } 1134 }