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 <V> The type of object contained in the Map. 155 * @param valueMap the map with the variables' values, may be null 156 */ 157 public <V> StrSubstitutor(Map<String, V> valueMap) { 158 this(new MapLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); 159 } 160 161 /** 162 * Creates a new instance and initializes it. Uses a default escaping character. 163 * 164 * @param <V> The type of object contained in the Map. 165 * @param valueMap the map with the variables' values, may be null 166 * @param prefix the prefix for variables, not null 167 * @param suffix the suffix for variables, not null 168 * @throws IllegalArgumentException if the prefix or suffix is null 169 */ 170 public <V> StrSubstitutor(Map<String, V> valueMap, String prefix, String suffix) { 171 this(new MapLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE); 172 } 173 174 /** 175 * Creates a new instance and initializes it. 176 * 177 * @param <V> The type of object contained in the Map. 178 * @param valueMap the map with the variables' values, may be null 179 * @param prefix the prefix for variables, not null 180 * @param suffix the suffix for variables, not null 181 * @param escape the escape character 182 * @throws IllegalArgumentException if the prefix or suffix is null 183 */ 184 public <V> StrSubstitutor(Map<String, V> valueMap, String prefix, String suffix, char escape) { 185 this(new MapLookup(valueMap), prefix, suffix, escape); 186 } 187 188 /** 189 * Creates a new instance and initializes it. 190 * 191 * @param variableResolver the variable resolver, may be null 192 */ 193 public StrSubstitutor(StrLookup<?> variableResolver) { 194 this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); 195 } 196 197 /** 198 * Creates a new instance and initializes it. 199 * 200 * @param variableResolver the variable resolver, may be null 201 * @param prefix the prefix for variables, not null 202 * @param suffix the suffix for variables, not null 203 * @param escape the escape character 204 * @throws IllegalArgumentException if the prefix or suffix is null 205 */ 206 public StrSubstitutor(StrLookup<?> variableResolver, String prefix, String suffix, char escape) { 207 this.setVariableResolver(variableResolver); 208 this.setVariablePrefix(prefix); 209 this.setVariableSuffix(suffix); 210 this.setEscapeChar(escape); 211 } 212 213 /** 214 * Creates a new instance and initializes it. 215 * 216 * @param variableResolver the variable resolver, may be null 217 * @param prefixMatcher the prefix for variables, not null 218 * @param suffixMatcher the suffix for variables, not null 219 * @param escape the escape character 220 * @throws IllegalArgumentException if the prefix or suffix is null 221 */ 222 public StrSubstitutor(StrLookup<?> variableResolver, StrMatcher prefixMatcher, StrMatcher suffixMatcher, 223 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 <V> The type of object contained in the Map. 235 * @param source the source text containing the variables to substitute, null returns null 236 * @param valueMap the map with the values, may be null 237 * @return the result of the replace operation 238 */ 239 public static <V> String replace(Object source, Map<String, V> valueMap) { 240 return new StrSubstitutor(valueMap).replace(source); 241 } 242 243 /** 244 * Replaces all the occurrences of variables in the given source object with 245 * their matching values from the map. This method allows to specifiy a 246 * custom variable prefix and suffix 247 * 248 * @param <V> The type of object contained in the Map. 249 * @param source the source text containing the variables to substitute, null returns null 250 * @param valueMap the map with the values, may be null 251 * @param prefix the prefix of variables, not null 252 * @param suffix the suffix of variables, not null 253 * @return the result of the replace operation 254 * @throws IllegalArgumentException if the prefix or suffix is null 255 */ 256 public static <V> String replace(Object source, Map<String, V> valueMap, String prefix, String suffix) { 257 return new StrSubstitutor(valueMap, prefix, suffix).replace(source); 258 } 259 260 /** 261 * Replaces all the occurrences of variables in the given source object with their matching 262 * values from the properties. 263 * 264 * @param source the source text containing the variables to substitute, null returns null 265 * @param valueProperties the properties with values, may be null 266 * @return the result of the replace operation 267 */ 268 public static String replace(Object source, Properties valueProperties) { 269 if (valueProperties == null) { 270 return source.toString(); 271 } 272 Map<String, String> valueMap = new HashMap<String, String>(); 273 Enumeration<?> propNames = valueProperties.propertyNames(); 274 while (propNames.hasMoreElements()) { 275 String propName = (String) propNames.nextElement(); 276 String propValue = valueProperties.getProperty(propName); 277 valueMap.put(propName, propValue); 278 } 279 return StrSubstitutor.replace(source, valueMap); 280 } 281 282 //----------------------------------------------------------------------- 283 /** 284 * Replaces all the occurrences of variables with their matching values 285 * from the resolver using the given source string as a template. 286 * 287 * @param source the string to replace in, null returns null 288 * @return the result of the replace operation 289 */ 290 public String replace(String source) { 291 return replace(null, source); 292 } 293 //----------------------------------------------------------------------- 294 /** 295 * Replaces all the occurrences of variables with their matching values 296 * from the resolver using the given source string as a template. 297 * 298 * @param event The current LogEvent if there is one. 299 * @param source the string to replace in, null returns null 300 * @return the result of the replace operation 301 */ 302 public String replace(LogEvent event, String source) { 303 if (source == null) { 304 return null; 305 } 306 StringBuilder buf = new StringBuilder(source); 307 if (!substitute(event, buf, 0, source.length())) { 308 return source; 309 } 310 return buf.toString(); 311 } 312 313 /** 314 * Replaces all the occurrences of variables with their matching values 315 * from the resolver using the given source string as a template. 316 * <p> 317 * Only the specified portion of the string will be processed. 318 * The rest of the string is not processed, and is not returned. 319 * 320 * @param source the string to replace in, null returns null 321 * @param offset the start offset within the array, must be valid 322 * @param length the length within the array to be processed, must be valid 323 * @return the result of the replace operation 324 */ 325 public String replace(String source, int offset, int length) { 326 return replace(null, source, offset, length); 327 } 328 329 /** 330 * Replaces all the occurrences of variables with their matching values 331 * from the resolver using the given source string as a template. 332 * <p> 333 * Only the specified portion of the string will be processed. 334 * The rest of the string is not processed, and is not returned. 335 * 336 * @param event the current LogEvent, if one exists. 337 * @param source the string to replace in, null returns null 338 * @param offset the start offset within the array, must be valid 339 * @param length the length within the array to be processed, must be valid 340 * @return the result of the replace operation 341 */ 342 public String replace(LogEvent event, String source, int offset, int length) { 343 if (source == null) { 344 return null; 345 } 346 StringBuilder buf = new StringBuilder(length).append(source, offset, length); 347 if (!substitute(event, buf, 0, length)) { 348 return source.substring(offset, offset + length); 349 } 350 return buf.toString(); 351 } 352 353 //----------------------------------------------------------------------- 354 /** 355 * Replaces all the occurrences of variables with their matching values 356 * from the resolver using the given source array as a template. 357 * The array is not altered by this method. 358 * 359 * @param source the character array to replace in, not altered, null returns null 360 * @return the result of the replace operation 361 */ 362 public String replace(char[] source) { 363 return replace(null, source); 364 } 365 366 //----------------------------------------------------------------------- 367 /** 368 * Replaces all the occurrences of variables with their matching values 369 * from the resolver using the given source array as a template. 370 * The array is not altered by this method. 371 * 372 * @param event the current LogEvent, if one exists. 373 * @param source the character array to replace in, not altered, null returns null 374 * @return the result of the replace operation 375 */ 376 public String replace(LogEvent event, char[] source) { 377 if (source == null) { 378 return null; 379 } 380 StringBuilder buf = new StringBuilder(source.length).append(source); 381 substitute(event, buf, 0, source.length); 382 return buf.toString(); 383 } 384 385 /** 386 * Replaces all the occurrences of variables with their matching values 387 * from the resolver using the given source array as a template. 388 * The array is not altered by this method. 389 * <p> 390 * Only the specified portion of the array will be processed. 391 * The rest of the array is not processed, and is not returned. 392 * 393 * @param source the character array to replace in, not altered, null returns null 394 * @param offset the start offset within the array, must be valid 395 * @param length the length within the array to be processed, must be valid 396 * @return the result of the replace operation 397 */ 398 public String replace(char[] source, int offset, int length) { 399 return replace(null, source, offset, length); 400 } 401 402 /** 403 * Replaces all the occurrences of variables with their matching values 404 * from the resolver using the given source array as a template. 405 * The array is not altered by this method. 406 * <p> 407 * Only the specified portion of the array will be processed. 408 * The rest of the array is not processed, and is not returned. 409 * 410 * @param event the current LogEvent, if one exists. 411 * @param source the character array to replace in, not altered, null returns null 412 * @param offset the start offset within the array, must be valid 413 * @param length the length within the array to be processed, must be valid 414 * @return the result of the replace operation 415 */ 416 public String replace(LogEvent event, char[] source, int offset, int length) { 417 if (source == null) { 418 return null; 419 } 420 StringBuilder buf = new StringBuilder(length).append(source, offset, length); 421 substitute(event, buf, 0, length); 422 return buf.toString(); 423 } 424 425 //----------------------------------------------------------------------- 426 /** 427 * Replaces all the occurrences of variables with their matching values 428 * from the resolver using the given source buffer as a template. 429 * The buffer is not altered by this method. 430 * 431 * @param source the buffer to use as a template, not changed, null returns null 432 * @return the result of the replace operation 433 */ 434 public String replace(StringBuffer source) { 435 return replace(null, source); 436 } 437 438 //----------------------------------------------------------------------- 439 /** 440 * Replaces all the occurrences of variables with their matching values 441 * from the resolver using the given source buffer as a template. 442 * The buffer is not altered by this method. 443 * 444 * @param event the current LogEvent, if one exists. 445 * @param source the buffer to use as a template, not changed, null returns null 446 * @return the result of the replace operation 447 */ 448 public String replace(LogEvent event, StringBuffer source) { 449 if (source == null) { 450 return null; 451 } 452 StringBuilder buf = new StringBuilder(source.length()).append(source); 453 substitute(event, buf, 0, buf.length()); 454 return buf.toString(); 455 } 456 457 /** 458 * Replaces all the occurrences of variables with their matching values 459 * from the resolver using the given source buffer as a template. 460 * The buffer is not altered by this method. 461 * <p> 462 * Only the specified portion of the buffer will be processed. 463 * The rest of the buffer is not processed, and is not returned. 464 * 465 * @param source the buffer to use as a template, not changed, null returns null 466 * @param offset the start offset within the array, must be valid 467 * @param length the length within the array to be processed, must be valid 468 * @return the result of the replace operation 469 */ 470 public String replace(StringBuffer source, int offset, int length) { 471 return replace(null, source, offset, length); 472 } 473 474 /** 475 * Replaces all the occurrences of variables with their matching values 476 * from the resolver using the given source buffer as a template. 477 * The buffer is not altered by this method. 478 * <p> 479 * Only the specified portion of the buffer will be processed. 480 * The rest of the buffer is not processed, and is not returned. 481 * 482 * @param event the current LogEvent, if one exists. 483 * @param source the buffer to use as a template, not changed, null returns null 484 * @param offset the start offset within the array, must be valid 485 * @param length the length within the array to be processed, must be valid 486 * @return the result of the replace operation 487 */ 488 public String replace(LogEvent event, StringBuffer source, int offset, int length) { 489 if (source == null) { 490 return null; 491 } 492 StringBuilder buf = new StringBuilder(length).append(source, offset, length); 493 substitute(event, buf, 0, length); 494 return buf.toString(); 495 } 496 497 //----------------------------------------------------------------------- 498 /** 499 * Replaces all the occurrences of variables with their matching values 500 * from the resolver using the given source builder as a template. 501 * The builder is not altered by this method. 502 * 503 * @param source the builder to use as a template, not changed, null returns null 504 * @return the result of the replace operation 505 */ 506 public String replace(StringBuilder source) { 507 return replace(null, source); 508 } 509 510 //----------------------------------------------------------------------- 511 /** 512 * Replaces all the occurrences of variables with their matching values 513 * from the resolver using the given source builder as a template. 514 * The builder is not altered by this method. 515 * 516 * @param event The LogEvent. 517 * @param source the builder to use as a template, not changed, null returns null. 518 * @return the result of the replace operation. 519 */ 520 public String replace(LogEvent event, StringBuilder source) { 521 if (source == null) { 522 return null; 523 } 524 StringBuilder buf = new StringBuilder(source.length()).append(source); 525 substitute(event, buf, 0, buf.length()); 526 return buf.toString(); 527 } 528 /** 529 * Replaces all the occurrences of variables with their matching values 530 * from the resolver using the given source builder as a template. 531 * The builder is not altered by this method. 532 * <p> 533 * Only the specified portion of the builder will be processed. 534 * The rest of the builder is not processed, and is not returned. 535 * 536 * @param source the builder to use as a template, not changed, null returns null 537 * @param offset the start offset within the array, must be valid 538 * @param length the length within the array to be processed, must be valid 539 * @return the result of the replace operation 540 */ 541 public String replace(StringBuilder source, int offset, int length) { 542 return replace(null, source, offset, length); 543 } 544 545 /** 546 * Replaces all the occurrences of variables with their matching values 547 * from the resolver using the given source builder as a template. 548 * The builder is not altered by this method. 549 * <p> 550 * Only the specified portion of the builder will be processed. 551 * The rest of the builder is not processed, and is not returned. 552 * 553 * @param event the current LogEvent, if one exists. 554 * @param source the builder to use as a template, not changed, null returns null 555 * @param offset the start offset within the array, must be valid 556 * @param length the length within the array to be processed, must be valid 557 * @return the result of the replace operation 558 */ 559 public String replace(LogEvent event, StringBuilder source, int offset, int length) { 560 if (source == null) { 561 return null; 562 } 563 StringBuilder buf = new StringBuilder(length).append(source, offset, length); 564 substitute(event, buf, 0, length); 565 return buf.toString(); 566 } 567 568 //----------------------------------------------------------------------- 569 /** 570 * Replaces all the occurrences of variables in the given source object with 571 * their matching values from the resolver. The input source object is 572 * converted to a string using <code>toString</code> and is not altered. 573 * 574 * @param source the source to replace in, null returns null 575 * @return the result of the replace operation 576 */ 577 public String replace(Object source) { 578 return replace(null, source); 579 } 580 //----------------------------------------------------------------------- 581 /** 582 * Replaces all the occurrences of variables in the given source object with 583 * their matching values from the resolver. The input source object is 584 * converted to a string using <code>toString</code> and is not altered. 585 * 586 * @param event the current LogEvent, if one exists. 587 * @param source the source to replace in, null returns null 588 * @return the result of the replace operation 589 */ 590 public String replace(LogEvent event, Object source) { 591 if (source == null) { 592 return null; 593 } 594 StringBuilder buf = new StringBuilder().append(source); 595 substitute(event, buf, 0, buf.length()); 596 return buf.toString(); 597 } 598 599 //----------------------------------------------------------------------- 600 /** 601 * Replaces all the occurrences of variables within the given source buffer 602 * with their matching values from the resolver. 603 * The buffer is updated with the result. 604 * 605 * @param source the buffer to replace in, updated, null returns zero 606 * @return true if altered 607 */ 608 public boolean replaceIn(StringBuffer source) { 609 if (source == null) { 610 return false; 611 } 612 return replaceIn(source, 0, source.length()); 613 } 614 615 /** 616 * Replaces all the occurrences of variables within the given source buffer 617 * with their matching values from the resolver. 618 * The buffer is updated with the result. 619 * <p> 620 * Only the specified portion of the buffer will be processed. 621 * The rest of the buffer is not processed, but it is not deleted. 622 * 623 * @param source the buffer to replace in, updated, null returns zero 624 * @param offset the start offset within the array, must be valid 625 * @param length the length within the buffer to be processed, must be valid 626 * @return true if altered 627 */ 628 public boolean replaceIn(StringBuffer source, int offset, int length) { 629 return replaceIn(null, source, offset, length); 630 } 631 632 /** 633 * Replaces all the occurrences of variables within the given source buffer 634 * with their matching values from the resolver. 635 * The buffer is updated with the result. 636 * <p> 637 * Only the specified portion of the buffer will be processed. 638 * The rest of the buffer is not processed, but it is not deleted. 639 * 640 * @param event the current LogEvent, if one exists. 641 * @param source the buffer to replace in, updated, null returns zero 642 * @param offset the start offset within the array, must be valid 643 * @param length the length within the buffer to be processed, must be valid 644 * @return true if altered 645 */ 646 public boolean replaceIn(LogEvent event, StringBuffer source, int offset, int length) { 647 if (source == null) { 648 return false; 649 } 650 StringBuilder buf = new StringBuilder(length).append(source, offset, length); 651 if (!substitute(event, buf, 0, length)) { 652 return false; 653 } 654 source.replace(offset, offset + length, buf.toString()); 655 return true; 656 } 657 658 //----------------------------------------------------------------------- 659 /** 660 * Replaces all the occurrences of variables within the given source 661 * builder with their matching values from the resolver. 662 * 663 * @param source the builder to replace in, updated, null returns zero 664 * @return true if altered 665 */ 666 public boolean replaceIn(StringBuilder source) { 667 return replaceIn(null, source); 668 } 669 670 //----------------------------------------------------------------------- 671 /** 672 * Replaces all the occurrences of variables within the given source 673 * builder with their matching values from the resolver. 674 * 675 * @param event the current LogEvent, if one exists. 676 * @param source the builder to replace in, updated, null returns zero 677 * @return true if altered 678 */ 679 public boolean replaceIn(LogEvent event, StringBuilder source) { 680 if (source == null) { 681 return false; 682 } 683 return substitute(event, source, 0, source.length()); 684 } 685 /** 686 * Replaces all the occurrences of variables within the given source 687 * builder with their matching values from the resolver. 688 * <p> 689 * Only the specified portion of the builder will be processed. 690 * The rest of the builder is not processed, but it is not deleted. 691 * 692 * @param source the builder to replace in, null returns zero 693 * @param offset the start offset within the array, must be valid 694 * @param length the length within the builder to be processed, must be valid 695 * @return true if altered 696 */ 697 public boolean replaceIn(StringBuilder source, int offset, int length) { 698 return replaceIn(null, source, offset, length); 699 } 700 701 /** 702 * Replaces all the occurrences of variables within the given source 703 * builder with their matching values from the resolver. 704 * <p> 705 * Only the specified portion of the builder will be processed. 706 * The rest of the builder is not processed, but it is not deleted. 707 * 708 * @param event the current LogEvent, if one is present. 709 * @param source the builder to replace in, null returns zero 710 * @param offset the start offset within the array, must be valid 711 * @param length the length within the builder to be processed, must be valid 712 * @return true if altered 713 */ 714 public boolean replaceIn(LogEvent event, StringBuilder source, int offset, int length) { 715 if (source == null) { 716 return false; 717 } 718 return substitute(event, source, offset, length); 719 } 720 721 //----------------------------------------------------------------------- 722 /** 723 * Internal method that substitutes the variables. 724 * <p> 725 * Most users of this class do not need to call this method. This method will 726 * be called automatically by another (public) method. 727 * <p> 728 * Writers of subclasses can override this method if they need access to 729 * the substitution process at the start or end. 730 * 731 * @param event The current LogEvent, if there is one. 732 * @param buf the string builder to substitute into, not null 733 * @param offset the start offset within the builder, must be valid 734 * @param length the length within the builder to be processed, must be valid 735 * @return true if altered 736 */ 737 protected boolean substitute(LogEvent event, StringBuilder buf, int offset, int length) { 738 return substitute(event, buf, offset, length, null) > 0; 739 } 740 741 /** 742 * Recursive handler for multiple levels of interpolation. This is the main 743 * interpolation method, which resolves the values of all variable references 744 * contained in the passed in text. 745 * 746 * @param event The current LogEvent, if there is one. 747 * @param buf the string builder to substitute into, not null 748 * @param offset the start offset within the builder, must be valid 749 * @param length the length within the builder to be processed, must be valid 750 * @param priorVariables the stack keeping track of the replaced variables, may be null 751 * @return the length change that occurs, unless priorVariables is null when the int 752 * represents a boolean flag as to whether any change occurred. 753 */ 754 private int substitute(LogEvent event, StringBuilder buf, int offset, int length, List<String> priorVariables) { 755 StrMatcher prefixMatcher = getVariablePrefixMatcher(); 756 StrMatcher suffixMatcher = getVariableSuffixMatcher(); 757 char escape = getEscapeChar(); 758 759 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 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 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 StringBuilder bufName = new StringBuilder(varName); 807 substitute(event, bufName, 0, bufName.length()); 808 varName = bufName.toString(); 809 } 810 pos += endMatchLen; 811 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 String varValue = resolveVariable(event, varName, buf, 826 startPos, endPos); 827 if (varValue != null) { 828 // recursive replace 829 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(String varName, List<String> priorVariables) { 869 if (!priorVariables.contains(varName)) { 870 return; 871 } 872 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(LogEvent event, String variableName, StringBuilder buf, int startPos, 899 int endPos) { 900 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(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(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(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(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(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(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(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(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(boolean enableSubstitutionInVariables) { 1099 this.enableSubstitutionInVariables = enableSubstitutionInVariables; 1100 } 1101 1102 private char[] getChars(StringBuilder sb) { 1103 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(StringBuilder sb, Iterable<?> iterable, String separator) { 1118 if (iterable != null) { 1119 separator = (separator == null ? "" : separator); 1120 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 }