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 */ 017package org.apache.logging.log4j.core.lookup; 018 019import java.util.ArrayList; 020import java.util.Enumeration; 021import java.util.HashMap; 022import java.util.Iterator; 023import java.util.List; 024import java.util.Map; 025import java.util.Objects; 026import java.util.Properties; 027 028import org.apache.logging.log4j.core.LogEvent; 029import org.apache.logging.log4j.core.config.Configuration; 030import org.apache.logging.log4j.core.config.ConfigurationAware; 031import org.apache.logging.log4j.status.StatusLogger; 032import org.apache.logging.log4j.util.Strings; 033 034/** 035 * Substitutes variables within a string by values. 036 * <p> 037 * This class takes a piece of text and substitutes all the variables within it. 038 * The default definition of a variable is <code>${variableName}</code>. 039 * The prefix and suffix can be changed via constructors and set methods. 040 * </p> 041 * <p> 042 * Variable values are typically resolved from a map, but could also be resolved 043 * from system properties, or by supplying a custom variable resolver. 044 * </p> 045 * <p> 046 * The simplest example is to use this class to replace Java System properties. For example: 047 * </p> 048 * <pre> 049 * StrSubstitutor.replaceSystemProperties( 050 * "You are running with java.version = ${java.version} and os.name = ${os.name}."); 051 * </pre> 052 * <p> 053 * Typical usage of this class follows the following pattern: First an instance is created 054 * and initialized with the map that contains the values for the available variables. 055 * If a prefix and/or suffix for variables should be used other than the default ones, 056 * the appropriate settings can be performed. After that the <code>replace()</code> 057 * method can be called passing in the source text for interpolation. In the returned 058 * text all variable references (as long as their values are known) will be resolved. 059 * The following example demonstrates this: 060 * </p> 061 * <pre> 062 * Map valuesMap = HashMap(); 063 * valuesMap.put("animal", "quick brown fox"); 064 * valuesMap.put("target", "lazy dog"); 065 * String templateString = "The ${animal} jumped over the ${target}."; 066 * StrSubstitutor sub = new StrSubstitutor(valuesMap); 067 * String resolvedString = sub.replace(templateString); 068 * </pre> 069 * <p>yielding:</p> 070 * <pre> 071 * The quick brown fox jumped over the lazy dog. 072 * </pre> 073 * <p> 074 * Also, this class allows to set a default value for unresolved variables. 075 * The default value for a variable can be appended to the variable name after the variable 076 * default value delimiter. The default value of the variable default value delimiter is ':-', 077 * as in bash and other *nix shells, as those are arguably where the default ${} delimiter set originated. 078 * The variable default value delimiter can be manually set by calling {@link #setValueDelimiterMatcher(StrMatcher)}, 079 * {@link #setValueDelimiter(char)} or {@link #setValueDelimiter(String)}. 080 * The following shows an example with variable default value settings: 081 * </p> 082 * <pre> 083 * Map valuesMap = HashMap(); 084 * valuesMap.put("animal", "quick brown fox"); 085 * valuesMap.put("target", "lazy dog"); 086 * String templateString = "The ${animal} jumped over the ${target}. ${undefined.number:-1234567890}."; 087 * StrSubstitutor sub = new StrSubstitutor(valuesMap); 088 * String resolvedString = sub.replace(templateString); 089 * </pre> 090 * <p>yielding:</p> 091 * <pre> 092 * The quick brown fox jumped over the lazy dog. 1234567890. 093 * </pre> 094 * <p> 095 * In addition to this usage pattern there are some static convenience methods that 096 * cover the most common use cases. These methods can be used without the need of 097 * manually creating an instance. However if multiple replace operations are to be 098 * performed, creating and reusing an instance of this class will be more efficient. 099 * </p> 100 * <p> 101 * Variable replacement works in a recursive way. Thus, if a variable value contains 102 * a variable then that variable will also be replaced. Cyclic replacements are 103 * detected and will cause an exception to be thrown. 104 * </p> 105 * <p> 106 * Sometimes the interpolation's result must contain a variable prefix. As an example 107 * take the following source text: 108 * </p> 109 * <pre> 110 * The variable ${${name}} must be used. 111 * </pre> 112 * <p> 113 * Here only the variable's name referred to in the text should be replaced resulting 114 * in the text (assuming that the value of the <code>name</code> variable is <code>x</code>): 115 * </p> 116 * <pre> 117 * The variable ${x} must be used. 118 * </pre> 119 * <p> 120 * To achieve this effect there are two possibilities: Either set a different prefix 121 * and suffix for variables which do not conflict with the result text you want to 122 * produce. The other possibility is to use the escape character, by default '$'. 123 * If this character is placed before a variable reference, this reference is ignored 124 * and won't be replaced. For example: 125 * </p> 126 * <pre> 127 * The variable $${${name}} must be used. 128 * </pre> 129 * <p> 130 * In some complex scenarios you might even want to perform substitution in the 131 * names of variables, for instance 132 * </p> 133 * <pre> 134 * ${jre-${java.specification.version}} 135 * </pre> 136 * <p> 137 * <code>StrSubstitutor</code> supports this recursive substitution in variable 138 * names, but it has to be enabled explicitly by setting the 139 * {@link #setEnableSubstitutionInVariables(boolean) enableSubstitutionInVariables} 140 * property to <b>true</b>. 141 * </p> 142 */ 143public class StrSubstitutor implements ConfigurationAware { 144 145 /** 146 * Constant for the default escape character. 147 */ 148 public static final char DEFAULT_ESCAPE = '$'; 149 150 /** 151 * Constant for the default variable prefix. 152 */ 153 public static final StrMatcher DEFAULT_PREFIX = StrMatcher.stringMatcher(DEFAULT_ESCAPE + "{"); 154 155 /** 156 * Constant for the default variable suffix. 157 */ 158 public static final StrMatcher DEFAULT_SUFFIX = StrMatcher.stringMatcher("}"); 159 160 /** 161 * Constant for the default value delimiter of a variable. 162 */ 163 public static final String DEFAULT_VALUE_DELIMITER_STRING = ":-"; 164 public static final StrMatcher DEFAULT_VALUE_DELIMITER = StrMatcher.stringMatcher(DEFAULT_VALUE_DELIMITER_STRING); 165 166 public static final String ESCAPE_DELIMITER_STRING = ":\\-"; 167 public static final StrMatcher DEFAULT_VALUE_ESCAPE_DELIMITER = StrMatcher.stringMatcher(ESCAPE_DELIMITER_STRING); 168 169 private static final int BUF_SIZE = 256; 170 171 /** 172 * Stores the escape character. 173 */ 174 private char escapeChar; 175 176 /** 177 * Stores the variable prefix. 178 */ 179 private StrMatcher prefixMatcher; 180 181 /** 182 * Stores the variable suffix. 183 */ 184 private StrMatcher suffixMatcher; 185 186 /** 187 * Stores the default variable value delimiter 188 */ 189 private String valueDelimiterString; 190 private StrMatcher valueDelimiterMatcher; 191 192 /** 193 * Escape string to avoid matching the value delimiter matcher; 194 */ 195 private StrMatcher valueEscapeDelimiterMatcher; 196 197 /** 198 * Variable resolution is delegated to an implementer of VariableResolver. 199 */ 200 private StrLookup variableResolver; 201 202 /** 203 * The flag whether substitution in variable names is enabled. 204 */ 205 private boolean enableSubstitutionInVariables = true; 206 207 /** 208 * The currently active Configuration for use by ConfigurationAware StrLookup implementations. 209 */ 210 private Configuration configuration; 211 212 private boolean recursiveEvaluationAllowed; 213 214 //----------------------------------------------------------------------- 215 /** 216 * Creates a new instance with defaults for variable prefix and suffix 217 * and the escaping character. 218 */ 219 public StrSubstitutor() { 220 this(null, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); 221 } 222 223 /** 224 * Creates a new instance and initializes it. Uses defaults for variable 225 * prefix and suffix and the escaping character. 226 * 227 * @param valueMap the map with the variables' values, may be null 228 */ 229 public StrSubstitutor(final Map<String, String> valueMap) { 230 this(new MapLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); 231 } 232 233 /** 234 * Creates a new instance and initializes it. Uses a default escaping character. 235 * 236 * @param valueMap the map with the variables' values, may be null 237 * @param prefix the prefix for variables, not null 238 * @param suffix the suffix for variables, not null 239 * @throws IllegalArgumentException if the prefix or suffix is null 240 */ 241 public StrSubstitutor(final Map<String, String> valueMap, final String prefix, final String suffix) { 242 this(new MapLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE); 243 } 244 245 /** 246 * Creates a new instance and initializes it. 247 * 248 * @param valueMap the map with the variables' values, may be null 249 * @param prefix the prefix for variables, not null 250 * @param suffix the suffix for variables, not null 251 * @param escape the escape character 252 * @throws IllegalArgumentException if the prefix or suffix is null 253 */ 254 public StrSubstitutor(final Map<String, String> valueMap, final String prefix, final String suffix, 255 final char escape) { 256 this(new MapLookup(valueMap), prefix, suffix, escape); 257 } 258 259 /** 260 * Creates a new instance and initializes it. 261 * 262 * @param valueMap the map with the variables' values, may be null 263 * @param prefix the prefix for variables, not null 264 * @param suffix the suffix for variables, not null 265 * @param escape the escape character 266 * @param valueDelimiter the variable default value delimiter, may be null 267 * @throws IllegalArgumentException if the prefix or suffix is null 268 */ 269 public StrSubstitutor(final Map<String, String> valueMap, final String prefix, final String suffix, 270 final char escape, final String valueDelimiter) { 271 this(new MapLookup(valueMap), prefix, suffix, escape, valueDelimiter); 272 } 273 274 /** 275 * Creates a new instance and initializes it. Uses defaults for variable 276 * prefix and suffix and the escaping character. 277 * 278 * @param properties the map with the variables' values, may be null 279 */ 280 public StrSubstitutor(final Properties properties) { 281 this(toTypeSafeMap(properties)); 282 } 283 284 /** 285 * Creates a new instance and initializes it. 286 * 287 * @param variableResolver the variable resolver, may be null 288 */ 289 public StrSubstitutor(final StrLookup variableResolver) { 290 this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE); 291 } 292 293 /** 294 * Creates a new instance and initializes it. 295 * 296 * @param variableResolver the variable resolver, may be null 297 * @param prefix the prefix for variables, not null 298 * @param suffix the suffix for variables, not null 299 * @param escape the escape character 300 * @throws IllegalArgumentException if the prefix or suffix is null 301 */ 302 public StrSubstitutor(final StrLookup variableResolver, final String prefix, final String suffix, 303 final char escape) { 304 this.setVariableResolver(variableResolver); 305 this.setVariablePrefix(prefix); 306 this.setVariableSuffix(suffix); 307 this.setEscapeChar(escape); 308 } 309 310 /** 311 * Creates a new instance and initializes it. 312 * 313 * @param variableResolver the variable resolver, may be null 314 * @param prefix the prefix for variables, not null 315 * @param suffix the suffix for variables, not null 316 * @param escape the escape character 317 * @param valueDelimiter the variable default value delimiter string, may be null 318 * @throws IllegalArgumentException if the prefix or suffix is null 319 */ 320 public StrSubstitutor(final StrLookup variableResolver, final String prefix, final String suffix, final char escape, final String valueDelimiter) { 321 this.setVariableResolver(variableResolver); 322 this.setVariablePrefix(prefix); 323 this.setVariableSuffix(suffix); 324 this.setEscapeChar(escape); 325 this.setValueDelimiter(valueDelimiter); 326 } 327 328 /** 329 * Creates a new instance and initializes it. 330 * 331 * @param variableResolver the variable resolver, may be null 332 * @param prefixMatcher the prefix for variables, not null 333 * @param suffixMatcher the suffix for variables, not null 334 * @param escape the escape character 335 * @throws IllegalArgumentException if the prefix or suffix is null 336 */ 337 public StrSubstitutor(final StrLookup variableResolver, final StrMatcher prefixMatcher, 338 final StrMatcher suffixMatcher, 339 final char escape) { 340 this(variableResolver, prefixMatcher, suffixMatcher, escape, DEFAULT_VALUE_DELIMITER, 341 DEFAULT_VALUE_ESCAPE_DELIMITER); 342 this.valueDelimiterString = DEFAULT_VALUE_DELIMITER_STRING; 343 } 344 345 /** 346 * Creates a new instance and initializes it. 347 * 348 * @param variableResolver the variable resolver, may be null 349 * @param prefixMatcher the prefix for variables, not null 350 * @param suffixMatcher the suffix for variables, not null 351 * @param escape the escape character 352 * @param valueDelimiterMatcher the variable default value delimiter matcher, may be null 353 * @throws IllegalArgumentException if the prefix or suffix is null 354 */ 355 public StrSubstitutor(final StrLookup variableResolver, final StrMatcher prefixMatcher, 356 final StrMatcher suffixMatcher, final char escape, final StrMatcher valueDelimiterMatcher) { 357 this.setVariableResolver(variableResolver); 358 this.setVariablePrefixMatcher(prefixMatcher); 359 this.setVariableSuffixMatcher(suffixMatcher); 360 this.setEscapeChar(escape); 361 this.setValueDelimiterMatcher(valueDelimiterMatcher); 362 } 363 364 /** 365 * Creates a new instance and initializes it. 366 * 367 * @param variableResolver the variable resolver, may be null 368 * @param prefixMatcher the prefix for variables, not null 369 * @param suffixMatcher the suffix for variables, not null 370 * @param escape the escape character 371 * @param valueDelimiterMatcher the variable default value delimiter matcher, may be null 372 * @param valueEscapeMatcher the matcher to escape defaulting, may be null. 373 * @throws IllegalArgumentException if the prefix or suffix is null 374 */ 375 public StrSubstitutor(final StrLookup variableResolver, final StrMatcher prefixMatcher, 376 final StrMatcher suffixMatcher, final char escape, final StrMatcher valueDelimiterMatcher, 377 final StrMatcher valueEscapeMatcher) { 378 this.setVariableResolver(variableResolver); 379 this.setVariablePrefixMatcher(prefixMatcher); 380 this.setVariableSuffixMatcher(suffixMatcher); 381 this.setEscapeChar(escape); 382 this.setValueDelimiterMatcher(valueDelimiterMatcher); 383 valueEscapeDelimiterMatcher = valueEscapeMatcher; 384 } 385 386 StrSubstitutor(final StrSubstitutor other) { 387 Objects.requireNonNull(other, "other"); 388 this.setVariableResolver(other.getVariableResolver()); 389 this.setVariablePrefixMatcher(other.getVariablePrefixMatcher()); 390 this.setVariableSuffixMatcher(other.getVariableSuffixMatcher()); 391 this.setEscapeChar(other.getEscapeChar()); 392 this.setValueDelimiterMatcher(other.valueDelimiterMatcher); 393 this.valueEscapeDelimiterMatcher = other.valueEscapeDelimiterMatcher; 394 this.configuration = other.configuration; 395 this.recursiveEvaluationAllowed = other.isRecursiveEvaluationAllowed(); 396 this.enableSubstitutionInVariables = other.isEnableSubstitutionInVariables(); 397 this.valueDelimiterString = other.valueDelimiterString; 398 } 399 400 //----------------------------------------------------------------------- 401 /** 402 * Replaces all the occurrences of variables in the given source object with 403 * their matching values from the map. 404 * 405 * @param source the source text containing the variables to substitute, null returns null 406 * @param valueMap the map with the values, may be null 407 * @return the result of the replace operation 408 */ 409 public static String replace(final Object source, final Map<String, String> valueMap) { 410 return new StrSubstitutor(valueMap).replace(source); 411 } 412 413 /** 414 * Replaces all the occurrences of variables in the given source object with 415 * their matching values from the map. This method allows to specify a 416 * custom variable prefix and suffix 417 * 418 * @param source the source text containing the variables to substitute, null returns null 419 * @param valueMap the map with the values, may be null 420 * @param prefix the prefix of variables, not null 421 * @param suffix the suffix of variables, not null 422 * @return the result of the replace operation 423 * @throws IllegalArgumentException if the prefix or suffix is null 424 */ 425 public static String replace(final Object source, final Map<String, String> valueMap, final String prefix, 426 final String suffix) { 427 return new StrSubstitutor(valueMap, prefix, suffix).replace(source); 428 } 429 430 /** 431 * Replaces all the occurrences of variables in the given source object with their matching 432 * values from the properties. 433 * 434 * @param source the source text containing the variables to substitute, null returns null 435 * @param valueProperties the properties with values, may be null 436 * @return the result of the replace operation 437 */ 438 public static String replace(final Object source, final Properties valueProperties) { 439 if (valueProperties == null) { 440 return source.toString(); 441 } 442 final Map<String, String> valueMap = new HashMap<>(); 443 final Enumeration<?> propNames = valueProperties.propertyNames(); 444 while (propNames.hasMoreElements()) { 445 final String propName = (String) propNames.nextElement(); 446 final String propValue = valueProperties.getProperty(propName); 447 valueMap.put(propName, propValue); 448 } 449 return StrSubstitutor.replace(source, valueMap); 450 } 451 452 private static Map<String, String> toTypeSafeMap(final Properties properties) { 453 final Map<String, String> map = new HashMap<>(properties.size()); 454 for (final String name : properties.stringPropertyNames()) { 455 map.put(name, properties.getProperty(name)); 456 } 457 return map; 458 } 459 460 private static String handleFailedReplacement(String input, Throwable throwable) { 461 StatusLogger.getLogger().error("Replacement failed on {}", input, throwable); 462 return input; 463 } 464 465 //----------------------------------------------------------------------- 466 /** 467 * Replaces all the occurrences of variables with their matching values 468 * from the resolver using the given source string as a template. 469 * 470 * @param source the string to replace in, null returns null 471 * @return the result of the replace operation 472 */ 473 public String replace(final String source) { 474 return replace(null, source); 475 } 476 //----------------------------------------------------------------------- 477 /** 478 * Replaces all the occurrences of variables with their matching values 479 * from the resolver using the given source string as a template. 480 * 481 * @param event The current LogEvent if there is one. 482 * @param source the string to replace in, null returns null 483 * @return the result of the replace operation 484 */ 485 public String replace(final LogEvent event, final String source) { 486 if (source == null) { 487 return null; 488 } 489 final StringBuilder buf = new StringBuilder(source); 490 try { 491 if (!substitute(event, buf, 0, source.length())) { 492 return source; 493 } 494 } catch (Throwable t) { 495 return handleFailedReplacement(source, t); 496 } 497 return buf.toString(); 498 } 499 500 /** 501 * Replaces all the occurrences of variables with their matching values 502 * from the resolver using the given source string as a template. 503 * <p> 504 * Only the specified portion of the string will be processed. 505 * The rest of the string is not processed, and is not returned. 506 * </p> 507 * 508 * @param source the string to replace in, null returns null 509 * @param offset the start offset within the array, must be valid 510 * @param length the length within the array to be processed, must be valid 511 * @return the result of the replace operation 512 */ 513 public String replace(final String source, final int offset, final int length) { 514 return replace(null, source, offset, length); 515 } 516 517 /** 518 * Replaces all the occurrences of variables with their matching values 519 * from the resolver using the given source string as a template. 520 * <p> 521 * Only the specified portion of the string will be processed. 522 * The rest of the string is not processed, and is not returned. 523 * </p> 524 * 525 * @param event the current LogEvent, if one exists. 526 * @param source the string to replace in, null returns null 527 * @param offset the start offset within the array, must be valid 528 * @param length the length within the array to be processed, must be valid 529 * @return the result of the replace operation 530 */ 531 public String replace(final LogEvent event, final String source, final int offset, final int length) { 532 if (source == null) { 533 return null; 534 } 535 final StringBuilder buf = new StringBuilder(length).append(source, offset, length); 536 try { 537 if (!substitute(event, buf, 0, length)) { 538 return source.substring(offset, offset + length); 539 } 540 } catch (Throwable t) { 541 return handleFailedReplacement(source, t); 542 } 543 return buf.toString(); 544 } 545 546 //----------------------------------------------------------------------- 547 /** 548 * Replaces all the occurrences of variables with their matching values 549 * from the resolver using the given source array as a template. 550 * The array is not altered by this method. 551 * 552 * @param source the character array to replace in, not altered, null returns null 553 * @return the result of the replace operation 554 */ 555 public String replace(final char[] source) { 556 return replace(null, source); 557 } 558 559 //----------------------------------------------------------------------- 560 /** 561 * Replaces all the occurrences of variables with their matching values 562 * from the resolver using the given source array as a template. 563 * The array is not altered by this method. 564 * 565 * @param event the current LogEvent, if one exists. 566 * @param source the character array to replace in, not altered, null returns null 567 * @return the result of the replace operation 568 */ 569 public String replace(final LogEvent event, final char[] source) { 570 if (source == null) { 571 return null; 572 } 573 final StringBuilder buf = new StringBuilder(source.length).append(source); 574 try { 575 substitute(event, buf, 0, source.length); 576 } catch (Throwable t) { 577 return handleFailedReplacement(new String(source), t); 578 } 579 return buf.toString(); 580 } 581 582 /** 583 * Replaces all the occurrences of variables with their matching values 584 * from the resolver using the given source array as a template. 585 * The array is not altered by this method. 586 * <p> 587 * Only the specified portion of the array will be processed. 588 * The rest of the array is not processed, and is not returned. 589 * </p> 590 * 591 * @param source the character array to replace in, not altered, null returns null 592 * @param offset the start offset within the array, must be valid 593 * @param length the length within the array to be processed, must be valid 594 * @return the result of the replace operation 595 */ 596 public String replace(final char[] source, final int offset, final int length) { 597 return replace(null, source, offset, length); 598 } 599 600 /** 601 * Replaces all the occurrences of variables with their matching values 602 * from the resolver using the given source array as a template. 603 * The array is not altered by this method. 604 * <p> 605 * Only the specified portion of the array will be processed. 606 * The rest of the array is not processed, and is not returned. 607 * </p> 608 * 609 * @param event the current LogEvent, if one exists. 610 * @param source the character array to replace in, not altered, null returns null 611 * @param offset the start offset within the array, must be valid 612 * @param length the length within the array to be processed, must be valid 613 * @return the result of the replace operation 614 */ 615 public String replace(final LogEvent event, final char[] source, final int offset, final int length) { 616 if (source == null) { 617 return null; 618 } 619 final StringBuilder buf = new StringBuilder(length).append(source, offset, length); 620 try { 621 substitute(event, buf, 0, length); 622 } catch (Throwable t) { 623 return handleFailedReplacement(new String(source, offset, length), t); 624 } 625 return buf.toString(); 626 } 627 628 //----------------------------------------------------------------------- 629 /** 630 * Replaces all the occurrences of variables with their matching values 631 * from the resolver using the given source buffer as a template. 632 * The buffer is not altered by this method. 633 * 634 * @param source the buffer to use as a template, not changed, null returns null 635 * @return the result of the replace operation 636 */ 637 public String replace(final StringBuffer source) { 638 return replace(null, source); 639 } 640 641 //----------------------------------------------------------------------- 642 /** 643 * Replaces all the occurrences of variables with their matching values 644 * from the resolver using the given source buffer as a template. 645 * The buffer is not altered by this method. 646 * 647 * @param event the current LogEvent, if one exists. 648 * @param source the buffer to use as a template, not changed, null returns null 649 * @return the result of the replace operation 650 */ 651 public String replace(final LogEvent event, final StringBuffer source) { 652 if (source == null) { 653 return null; 654 } 655 final StringBuilder buf = new StringBuilder(source.length()).append(source); 656 try { 657 substitute(event, buf, 0, buf.length()); 658 } catch (Throwable t) { 659 return handleFailedReplacement(source.toString(), t); 660 } 661 return buf.toString(); 662 } 663 664 /** 665 * Replaces all the occurrences of variables with their matching values 666 * from the resolver using the given source buffer as a template. 667 * The buffer is not altered by this method. 668 * <p> 669 * Only the specified portion of the buffer will be processed. 670 * The rest of the buffer is not processed, and is not returned. 671 * </p> 672 * 673 * @param source the buffer to use as a template, not changed, null returns null 674 * @param offset the start offset within the array, must be valid 675 * @param length the length within the array to be processed, must be valid 676 * @return the result of the replace operation 677 */ 678 public String replace(final StringBuffer source, final int offset, final int length) { 679 return replace(null, source, offset, length); 680 } 681 682 /** 683 * Replaces all the occurrences of variables with their matching values 684 * from the resolver using the given source buffer as a template. 685 * The buffer is not altered by this method. 686 * <p> 687 * Only the specified portion of the buffer will be processed. 688 * The rest of the buffer is not processed, and is not returned. 689 * </p> 690 * 691 * @param event the current LogEvent, if one exists. 692 * @param source the buffer to use as a template, not changed, null returns null 693 * @param offset the start offset within the array, must be valid 694 * @param length the length within the array to be processed, must be valid 695 * @return the result of the replace operation 696 */ 697 public String replace(final LogEvent event, final StringBuffer source, final int offset, final int length) { 698 if (source == null) { 699 return null; 700 } 701 final StringBuilder buf = new StringBuilder(length).append(source, offset, length); 702 try { 703 substitute(event, buf, 0, length); 704 } catch (Throwable t) { 705 return handleFailedReplacement(source.substring(offset, offset + length), t); 706 } 707 return buf.toString(); 708 } 709 710 //----------------------------------------------------------------------- 711 /** 712 * Replaces all the occurrences of variables with their matching values 713 * from the resolver using the given source builder as a template. 714 * The builder is not altered by this method. 715 * 716 * @param source the builder to use as a template, not changed, null returns null 717 * @return the result of the replace operation 718 */ 719 public String replace(final StringBuilder source) { 720 return replace(null, source); 721 } 722 723 //----------------------------------------------------------------------- 724 /** 725 * Replaces all the occurrences of variables with their matching values 726 * from the resolver using the given source builder as a template. 727 * The builder is not altered by this method. 728 * 729 * @param event The LogEvent. 730 * @param source the builder to use as a template, not changed, null returns null. 731 * @return the result of the replace operation. 732 */ 733 public String replace(final LogEvent event, final StringBuilder source) { 734 if (source == null) { 735 return null; 736 } 737 final StringBuilder buf = new StringBuilder(source.length()).append(source); 738 try { 739 substitute(event, buf, 0, buf.length()); 740 } catch (Throwable t) { 741 return handleFailedReplacement(source.toString(), t); 742 } 743 return buf.toString(); 744 } 745 /** 746 * Replaces all the occurrences of variables with their matching values 747 * from the resolver using the given source builder as a template. 748 * The builder is not altered by this method. 749 * <p> 750 * Only the specified portion of the builder will be processed. 751 * The rest of the builder is not processed, and is not returned. 752 * </p> 753 * 754 * @param source the builder to use as a template, not changed, null returns null 755 * @param offset the start offset within the array, must be valid 756 * @param length the length within the array to be processed, must be valid 757 * @return the result of the replace operation 758 */ 759 public String replace(final StringBuilder source, final int offset, final int length) { 760 return replace(null, source, offset, length); 761 } 762 763 /** 764 * Replaces all the occurrences of variables with their matching values 765 * from the resolver using the given source builder as a template. 766 * The builder is not altered by this method. 767 * <p> 768 * Only the specified portion of the builder will be processed. 769 * The rest of the builder is not processed, and is not returned. 770 * </p> 771 * 772 * @param event the current LogEvent, if one exists. 773 * @param source the builder to use as a template, not changed, null returns null 774 * @param offset the start offset within the array, must be valid 775 * @param length the length within the array to be processed, must be valid 776 * @return the result of the replace operation 777 */ 778 public String replace(final LogEvent event, final StringBuilder source, final int offset, final int length) { 779 if (source == null) { 780 return null; 781 } 782 final StringBuilder buf = new StringBuilder(length).append(source, offset, length); 783 try { 784 substitute(event, buf, 0, length); 785 } catch (Throwable t) { 786 return handleFailedReplacement(source.substring(offset, offset + length), t); 787 } 788 return buf.toString(); 789 } 790 791 //----------------------------------------------------------------------- 792 /** 793 * Replaces all the occurrences of variables in the given source object with 794 * their matching values from the resolver. The input source object is 795 * converted to a string using <code>toString</code> and is not altered. 796 * 797 * @param source the source to replace in, null returns null 798 * @return the result of the replace operation 799 */ 800 public String replace(final Object source) { 801 return replace(null, source); 802 } 803 //----------------------------------------------------------------------- 804 /** 805 * Replaces all the occurrences of variables in the given source object with 806 * their matching values from the resolver. The input source object is 807 * converted to a string using <code>toString</code> and is not altered. 808 * 809 * @param event the current LogEvent, if one exists. 810 * @param source the source to replace in, null returns null 811 * @return the result of the replace operation 812 */ 813 public String replace(final LogEvent event, final Object source) { 814 if (source == null) { 815 return null; 816 } 817 String stringValue = String.valueOf(source); 818 final StringBuilder buf = new StringBuilder(stringValue.length()).append(stringValue); 819 try { 820 substitute(event, buf, 0, buf.length()); 821 } catch (Throwable t) { 822 return handleFailedReplacement(stringValue, t); 823 } 824 return buf.toString(); 825 } 826 827 //----------------------------------------------------------------------- 828 /** 829 * Replaces all the occurrences of variables within the given source buffer 830 * with their matching values from the resolver. 831 * The buffer is updated with the result. 832 * 833 * @param source the buffer to replace in, updated, null returns zero 834 * @return true if altered 835 */ 836 public boolean replaceIn(final StringBuffer source) { 837 if (source == null) { 838 return false; 839 } 840 return replaceIn(source, 0, source.length()); 841 } 842 843 /** 844 * Replaces all the occurrences of variables within the given source buffer 845 * with their matching values from the resolver. 846 * The buffer is updated with the result. 847 * <p> 848 * Only the specified portion of the buffer will be processed. 849 * The rest of the buffer is not processed, but it is not deleted. 850 * </p> 851 * 852 * @param source the buffer to replace in, updated, null returns zero 853 * @param offset the start offset within the array, must be valid 854 * @param length the length within the buffer to be processed, must be valid 855 * @return true if altered 856 */ 857 public boolean replaceIn(final StringBuffer source, final int offset, final int length) { 858 return replaceIn(null, source, offset, length); 859 } 860 861 /** 862 * Replaces all the occurrences of variables within the given source buffer 863 * with their matching values from the resolver. 864 * The buffer is updated with the result. 865 * <p> 866 * Only the specified portion of the buffer will be processed. 867 * The rest of the buffer is not processed, but it is not deleted. 868 * </p> 869 * 870 * @param event the current LogEvent, if one exists. 871 * @param source the buffer to replace in, updated, null returns zero 872 * @param offset the start offset within the array, must be valid 873 * @param length the length within the buffer to be processed, must be valid 874 * @return true if altered 875 */ 876 public boolean replaceIn(final LogEvent event, final StringBuffer source, final int offset, final int length) { 877 if (source == null) { 878 return false; 879 } 880 final StringBuilder buf = new StringBuilder(length).append(source, offset, length); 881 try { 882 if (!substitute(event, buf, 0, length)) { 883 return false; 884 } 885 } catch (Throwable t) { 886 StatusLogger.getLogger().error("Replacement failed on {}", source, t); 887 return false; 888 } 889 source.replace(offset, offset + length, buf.toString()); 890 return true; 891 } 892 893 //----------------------------------------------------------------------- 894 /** 895 * Replaces all the occurrences of variables within the given source 896 * builder with their matching values from the resolver. 897 * 898 * @param source the builder to replace in, updated, null returns zero 899 * @return true if altered 900 */ 901 public boolean replaceIn(final StringBuilder source) { 902 return replaceIn(null, source); 903 } 904 905 //----------------------------------------------------------------------- 906 /** 907 * Replaces all the occurrences of variables within the given source 908 * builder with their matching values from the resolver. 909 * 910 * @param event the current LogEvent, if one exists. 911 * @param source the builder to replace in, updated, null returns zero 912 * @return true if altered 913 */ 914 public boolean replaceIn(final LogEvent event, final StringBuilder source) { 915 if (source == null) { 916 return false; 917 } 918 return substitute(event, source, 0, source.length()); 919 } 920 /** 921 * Replaces all the occurrences of variables within the given source 922 * builder with their matching values from the resolver. 923 * <p> 924 * Only the specified portion of the builder will be processed. 925 * The rest of the builder is not processed, but it is not deleted. 926 * </p> 927 * 928 * @param source the builder to replace in, null returns zero 929 * @param offset the start offset within the array, must be valid 930 * @param length the length within the builder to be processed, must be valid 931 * @return true if altered 932 */ 933 public boolean replaceIn(final StringBuilder source, final int offset, final int length) { 934 return replaceIn(null, source, offset, length); 935 } 936 937 /** 938 * Replaces all the occurrences of variables within the given source 939 * builder with their matching values from the resolver. 940 * <p> 941 * Only the specified portion of the builder will be processed. 942 * The rest of the builder is not processed, but it is not deleted. 943 * </p> 944 * 945 * @param event the current LogEvent, if one is present. 946 * @param source the builder to replace in, null returns zero 947 * @param offset the start offset within the array, must be valid 948 * @param length the length within the builder to be processed, must be valid 949 * @return true if altered 950 */ 951 public boolean replaceIn(final LogEvent event, final StringBuilder source, final int offset, final int length) { 952 if (source == null) { 953 return false; 954 } 955 return substitute(event, source, offset, length); 956 } 957 958 //----------------------------------------------------------------------- 959 /** 960 * Internal method that substitutes the variables. 961 * <p> 962 * Most users of this class do not need to call this method. This method will 963 * be called automatically by another (public) method. 964 * </p> 965 * <p> 966 * Writers of subclasses can override this method if they need access to 967 * the substitution process at the start or end. 968 * </p> 969 * 970 * @param event The current LogEvent, if there is one. 971 * @param buf the string builder to substitute into, not null 972 * @param offset the start offset within the builder, must be valid 973 * @param length the length within the builder to be processed, must be valid 974 * @return true if altered 975 */ 976 protected boolean substitute(final LogEvent event, final StringBuilder buf, final int offset, final int length) { 977 return substitute(event, buf, offset, length, null) > 0; 978 } 979 980 /** 981 * Recursive handler for multiple levels of interpolation. This is the main 982 * interpolation method, which resolves the values of all variable references 983 * contained in the passed in text. 984 * 985 * @param event The current LogEvent, if there is one. 986 * @param buf the string builder to substitute into, not null 987 * @param offset the start offset within the builder, must be valid 988 * @param length the length within the builder to be processed, must be valid 989 * @param priorVariables the stack keeping track of the replaced variables, may be null 990 * @return the length change that occurs, unless priorVariables is null when the int 991 * represents a boolean flag as to whether any change occurred. 992 */ 993 private int substitute(final LogEvent event, final StringBuilder buf, final int offset, final int length, 994 List<String> priorVariables) { 995 final StrMatcher prefixMatcher = getVariablePrefixMatcher(); 996 final StrMatcher suffixMatcher = getVariableSuffixMatcher(); 997 final char escape = getEscapeChar(); 998 final StrMatcher valueDelimiterMatcher = getValueDelimiterMatcher(); 999 final boolean substitutionInVariablesEnabled = isEnableSubstitutionInVariables(); 1000 1001 final boolean top = priorVariables == null; 1002 boolean altered = false; 1003 int lengthChange = 0; 1004 char[] chars = getChars(buf); 1005 int bufEnd = offset + length; 1006 int pos = offset; 1007 while (pos < bufEnd) { 1008 final int startMatchLen = prefixMatcher.isMatch(chars, pos, offset, bufEnd); 1009 if (startMatchLen == 0) { 1010 pos++; 1011 } else // found variable start marker 1012 if (pos > offset && chars[pos - 1] == escape) { 1013 // escaped 1014 buf.deleteCharAt(pos - 1); 1015 chars = getChars(buf); 1016 lengthChange--; 1017 altered = true; 1018 bufEnd--; 1019 } else { 1020 // find suffix 1021 final int startPos = pos; 1022 pos += startMatchLen; 1023 int endMatchLen = 0; 1024 int nestedVarCount = 0; 1025 while (pos < bufEnd) { 1026 if (substitutionInVariablesEnabled 1027 && (endMatchLen = prefixMatcher.isMatch(chars, pos, offset, bufEnd)) != 0) { 1028 // found a nested variable start 1029 nestedVarCount++; 1030 pos += endMatchLen; 1031 continue; 1032 } 1033 1034 endMatchLen = suffixMatcher.isMatch(chars, pos, offset, bufEnd); 1035 if (endMatchLen == 0) { 1036 pos++; 1037 } else { 1038 // found variable end marker 1039 if (nestedVarCount == 0) { 1040 String varNameExpr = new String(chars, startPos + startMatchLen, pos - startPos - startMatchLen); 1041 if (substitutionInVariablesEnabled) { 1042 // initialize priorVariables if they're not already set 1043 if (priorVariables == null) { 1044 priorVariables = new ArrayList<>(); 1045 } 1046 final StringBuilder bufName = new StringBuilder(varNameExpr); 1047 substitute(event, bufName, 0, bufName.length(), priorVariables); 1048 varNameExpr = bufName.toString(); 1049 } 1050 pos += endMatchLen; 1051 final int endPos = pos; 1052 1053 String varName = varNameExpr; 1054 String varDefaultValue = null; 1055 1056 if (valueDelimiterMatcher != null) { 1057 final char [] varNameExprChars = varNameExpr.toCharArray(); 1058 int valueDelimiterMatchLen = 0; 1059 for (int i = 0; i < varNameExprChars.length; i++) { 1060 // if there's any nested variable when nested variable substitution disabled, then stop resolving name and default value. 1061 if (!substitutionInVariablesEnabled 1062 && prefixMatcher.isMatch(varNameExprChars, i, i, varNameExprChars.length) != 0) { 1063 break; 1064 } 1065 if (valueEscapeDelimiterMatcher != null) { 1066 int matchLen = valueEscapeDelimiterMatcher.isMatch(varNameExprChars, i); 1067 if (matchLen != 0) { 1068 String varNamePrefix = varNameExpr.substring(0, i) + Interpolator.PREFIX_SEPARATOR; 1069 varName = varNamePrefix + varNameExpr.substring(i + matchLen - 1); 1070 for (int j = i + matchLen; j < varNameExprChars.length; ++j){ 1071 if ((valueDelimiterMatchLen = valueDelimiterMatcher.isMatch(varNameExprChars, j)) != 0) { 1072 varName = varNamePrefix + varNameExpr.substring(i + matchLen, j); 1073 varDefaultValue = varNameExpr.substring(j + valueDelimiterMatchLen); 1074 break; 1075 } 1076 } 1077 break; 1078 } else if ((valueDelimiterMatchLen = valueDelimiterMatcher.isMatch(varNameExprChars, i)) != 0) { 1079 varName = varNameExpr.substring(0, i); 1080 varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen); 1081 break; 1082 } 1083 } else if ((valueDelimiterMatchLen = valueDelimiterMatcher.isMatch(varNameExprChars, i)) != 0) { 1084 varName = varNameExpr.substring(0, i); 1085 varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen); 1086 break; 1087 } 1088 } 1089 } 1090 1091 // on the first call initialize priorVariables 1092 if (priorVariables == null) { 1093 priorVariables = new ArrayList<>(); 1094 priorVariables.add(new String(chars, offset, length + lengthChange)); 1095 } 1096 1097 // handle cyclic substitution 1098 boolean isCyclic = isCyclicSubstitution(varName, priorVariables); 1099 1100 // resolve the variable 1101 String varValue = isCyclic ? null : resolveVariable(event, varName, buf, startPos, endPos); 1102 if (varValue == null) { 1103 varValue = varDefaultValue; 1104 } 1105 if (varValue != null) { 1106 // recursive replace 1107 final int varLen = varValue.length(); 1108 buf.replace(startPos, endPos, varValue); 1109 altered = true; 1110 int change = isRecursiveEvaluationAllowed() 1111 ? substitute(event, buf, startPos, varLen, priorVariables) 1112 : 0; 1113 change = change + (varLen - (endPos - startPos)); 1114 pos += change; 1115 bufEnd += change; 1116 lengthChange += change; 1117 chars = getChars(buf); // in case buffer was altered 1118 } 1119 1120 // remove variable from the cyclic stack 1121 if (!isCyclic) { 1122 priorVariables.remove(priorVariables.size() - 1); 1123 } 1124 break; 1125 } 1126 nestedVarCount--; 1127 pos += endMatchLen; 1128 } 1129 } 1130 } 1131 } 1132 if (top) { 1133 return altered ? 1 : 0; 1134 } 1135 return lengthChange; 1136 } 1137 1138 /** 1139 * Checks if the specified variable is already in the stack (list) of variables, adding the value 1140 * if it's not already present. 1141 * 1142 * @param varName the variable name to check 1143 * @param priorVariables the list of prior variables 1144 * @return true if this is a cyclic substitution 1145 */ 1146 private boolean isCyclicSubstitution(final String varName, final List<String> priorVariables) { 1147 if (!priorVariables.contains(varName)) { 1148 priorVariables.add(varName); 1149 return false; 1150 } 1151 final StringBuilder buf = new StringBuilder(BUF_SIZE); 1152 buf.append("Infinite loop in property interpolation of "); 1153 appendWithSeparators(buf, priorVariables, "->"); 1154 StatusLogger.getLogger().warn(buf); 1155 return true; 1156 } 1157 1158 /** 1159 * Internal method that resolves the value of a variable. 1160 * <p> 1161 * Most users of this class do not need to call this method. This method is 1162 * called automatically by the substitution process. 1163 * </p> 1164 * <p> 1165 * Writers of subclasses can override this method if they need to alter 1166 * how each substitution occurs. The method is passed the variable's name 1167 * and must return the corresponding value. This implementation uses the 1168 * {@link #getVariableResolver()} with the variable's name as the key. 1169 * </p> 1170 * 1171 * @param event The LogEvent, if there is one. 1172 * @param variableName the name of the variable, not null 1173 * @param buf the buffer where the substitution is occurring, not null 1174 * @param startPos the start position of the variable including the prefix, valid 1175 * @param endPos the end position of the variable including the suffix, valid 1176 * @return the variable's value or <b>null</b> if the variable is unknown 1177 */ 1178 protected String resolveVariable(final LogEvent event, final String variableName, final StringBuilder buf, 1179 final int startPos, final int endPos) { 1180 final StrLookup resolver = getVariableResolver(); 1181 if (resolver == null) { 1182 return null; 1183 } 1184 try { 1185 return resolver.lookup(event, variableName); 1186 } catch (Throwable t) { 1187 StatusLogger.getLogger().error("Resolver failed to lookup {}", variableName, t); 1188 return null; 1189 } 1190 } 1191 1192 // Escape 1193 //----------------------------------------------------------------------- 1194 /** 1195 * Returns the escape character. 1196 * 1197 * @return the character used for escaping variable references 1198 */ 1199 public char getEscapeChar() { 1200 return this.escapeChar; 1201 } 1202 1203 /** 1204 * Sets the escape character. 1205 * If this character is placed before a variable reference in the source 1206 * text, this variable will be ignored. 1207 * 1208 * @param escapeCharacter the escape character (0 for disabling escaping) 1209 */ 1210 public void setEscapeChar(final char escapeCharacter) { 1211 this.escapeChar = escapeCharacter; 1212 } 1213 1214 // Prefix 1215 //----------------------------------------------------------------------- 1216 /** 1217 * Gets the variable prefix matcher currently in use. 1218 * <p> 1219 * The variable prefix is the character or characters that identify the 1220 * start of a variable. This prefix is expressed in terms of a matcher 1221 * allowing advanced prefix matches. 1222 * </p> 1223 * 1224 * @return the prefix matcher in use 1225 */ 1226 public StrMatcher getVariablePrefixMatcher() { 1227 return prefixMatcher; 1228 } 1229 1230 /** 1231 * Sets the variable prefix matcher currently in use. 1232 * <p> 1233 * The variable prefix is the character or characters that identify the 1234 * start of a variable. This prefix is expressed in terms of a matcher 1235 * allowing advanced prefix matches. 1236 * </p> 1237 * 1238 * @param prefixMatcher the prefix matcher to use, null ignored 1239 * @return this, to enable chaining 1240 * @throws IllegalArgumentException if the prefix matcher is null 1241 */ 1242 public StrSubstitutor setVariablePrefixMatcher(final StrMatcher prefixMatcher) { 1243 if (prefixMatcher == null) { 1244 throw new IllegalArgumentException("Variable prefix matcher must not be null!"); 1245 } 1246 this.prefixMatcher = prefixMatcher; 1247 return this; 1248 } 1249 1250 /** 1251 * Sets the variable prefix to use. 1252 * <p> 1253 * The variable prefix is the character or characters that identify the 1254 * start of a variable. This method allows a single character prefix to 1255 * be easily set. 1256 * </p> 1257 * 1258 * @param prefix the prefix character to use 1259 * @return this, to enable chaining 1260 */ 1261 public StrSubstitutor setVariablePrefix(final char prefix) { 1262 return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix)); 1263 } 1264 1265 /** 1266 * Sets the variable prefix to use. 1267 * <p> 1268 * The variable prefix is the character or characters that identify the 1269 * start of a variable. This method allows a string prefix to be easily set. 1270 * </p> 1271 * 1272 * @param prefix the prefix for variables, not null 1273 * @return this, to enable chaining 1274 * @throws IllegalArgumentException if the prefix is null 1275 */ 1276 public StrSubstitutor setVariablePrefix(final String prefix) { 1277 if (prefix == null) { 1278 throw new IllegalArgumentException("Variable prefix must not be null!"); 1279 } 1280 return setVariablePrefixMatcher(StrMatcher.stringMatcher(prefix)); 1281 } 1282 1283 // Suffix 1284 //----------------------------------------------------------------------- 1285 /** 1286 * Gets the variable suffix matcher currently in use. 1287 * <p> 1288 * The variable suffix is the character or characters that identify the 1289 * end of a variable. This suffix is expressed in terms of a matcher 1290 * allowing advanced suffix matches. 1291 * </p> 1292 * 1293 * @return the suffix matcher in use 1294 */ 1295 public StrMatcher getVariableSuffixMatcher() { 1296 return suffixMatcher; 1297 } 1298 1299 /** 1300 * Sets the variable suffix matcher currently in use. 1301 * <p> 1302 * The variable suffix is the character or characters that identify the 1303 * end of a variable. This suffix is expressed in terms of a matcher 1304 * allowing advanced suffix matches. 1305 * </p> 1306 * 1307 * @param suffixMatcher the suffix matcher to use, null ignored 1308 * @return this, to enable chaining 1309 * @throws IllegalArgumentException if the suffix matcher is null 1310 */ 1311 public StrSubstitutor setVariableSuffixMatcher(final StrMatcher suffixMatcher) { 1312 if (suffixMatcher == null) { 1313 throw new IllegalArgumentException("Variable suffix matcher must not be null!"); 1314 } 1315 this.suffixMatcher = suffixMatcher; 1316 return this; 1317 } 1318 1319 /** 1320 * Sets the variable suffix to use. 1321 * <p> 1322 * The variable suffix is the character or characters that identify the 1323 * end of a variable. This method allows a single character suffix to 1324 * be easily set. 1325 * </p> 1326 * 1327 * @param suffix the suffix character to use 1328 * @return this, to enable chaining 1329 */ 1330 public StrSubstitutor setVariableSuffix(final char suffix) { 1331 return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix)); 1332 } 1333 1334 /** 1335 * Sets the variable suffix to use. 1336 * <p> 1337 * The variable suffix is the character or characters that identify the 1338 * end of a variable. This method allows a string suffix to be easily set. 1339 * </p> 1340 * 1341 * @param suffix the suffix for variables, not null 1342 * @return this, to enable chaining 1343 * @throws IllegalArgumentException if the suffix is null 1344 */ 1345 public StrSubstitutor setVariableSuffix(final String suffix) { 1346 if (suffix == null) { 1347 throw new IllegalArgumentException("Variable suffix must not be null!"); 1348 } 1349 return setVariableSuffixMatcher(StrMatcher.stringMatcher(suffix)); 1350 } 1351 1352 // Variable Default Value Delimiter 1353 //----------------------------------------------------------------------- 1354 /** 1355 * Gets the variable default value delimiter matcher currently in use. 1356 * <p> 1357 * The variable default value delimiter is the character or characters that delimit the 1358 * variable name and the variable default value. This delimiter is expressed in terms of a matcher 1359 * allowing advanced variable default value delimiter matches. 1360 * </p> 1361 * <p> 1362 * If it returns null, then the variable default value resolution is disabled. 1363 * </p> 1364 * 1365 * @return the variable default value delimiter matcher in use, may be null 1366 */ 1367 public StrMatcher getValueDelimiterMatcher() { 1368 return valueDelimiterMatcher; 1369 } 1370 1371 /** 1372 * Sets the variable default value delimiter matcher to use. 1373 * <p> 1374 * The variable default value delimiter is the character or characters that delimit the 1375 * variable name and the variable default value. This delimiter is expressed in terms of a matcher 1376 * allowing advanced variable default value delimiter matches. 1377 * </p> 1378 * <p> 1379 * If the <code>valueDelimiterMatcher</code> is null, then the variable default value resolution 1380 * becomes disabled. 1381 * </p> 1382 * 1383 * @param valueDelimiterMatcher variable default value delimiter matcher to use, may be null 1384 * @return this, to enable chaining 1385 */ 1386 public StrSubstitutor setValueDelimiterMatcher(final StrMatcher valueDelimiterMatcher) { 1387 this.valueDelimiterMatcher = valueDelimiterMatcher; 1388 return this; 1389 } 1390 1391 /** 1392 * Sets the variable default value delimiter to use. 1393 * <p> 1394 * The variable default value delimiter is the character or characters that delimit the 1395 * variable name and the variable default value. This method allows a single character 1396 * variable default value delimiter to be easily set. 1397 * </p> 1398 * 1399 * @param valueDelimiter the variable default value delimiter character to use 1400 * @return this, to enable chaining 1401 */ 1402 public StrSubstitutor setValueDelimiter(final char valueDelimiter) { 1403 return setValueDelimiterMatcher(StrMatcher.charMatcher(valueDelimiter)); 1404 } 1405 1406 /** 1407 * Sets the variable default value delimiter to use. 1408 * <p> 1409 * The variable default value delimiter is the character or characters that delimit the 1410 * variable name and the variable default value. This method allows a string 1411 * variable default value delimiter to be easily set. 1412 * </p> 1413 * <p> 1414 * If the <code>valueDelimiter</code> is null or empty string, then the variable default 1415 * value resolution becomes disabled. 1416 * </p> 1417 * 1418 * @param valueDelimiter the variable default value delimiter string to use, may be null or empty 1419 * @return this, to enable chaining 1420 */ 1421 public StrSubstitutor setValueDelimiter(final String valueDelimiter) { 1422 if (Strings.isEmpty(valueDelimiter)) { 1423 setValueDelimiterMatcher(null); 1424 return this; 1425 } 1426 String escapeValue = valueDelimiter.substring(0, valueDelimiter.length() - 1) + "\\" 1427 + valueDelimiter.substring(valueDelimiter.length() - 1); 1428 valueEscapeDelimiterMatcher = StrMatcher.stringMatcher(escapeValue); 1429 return setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter)); 1430 } 1431 1432 // Resolver 1433 //----------------------------------------------------------------------- 1434 /** 1435 * Gets the VariableResolver that is used to lookup variables. 1436 * 1437 * @return the VariableResolver 1438 */ 1439 public StrLookup getVariableResolver() { 1440 return this.variableResolver; 1441 } 1442 1443 /** 1444 * Sets the VariableResolver that is used to lookup variables. 1445 * 1446 * @param variableResolver the VariableResolver 1447 */ 1448 public void setVariableResolver(final StrLookup variableResolver) { 1449 if (variableResolver instanceof ConfigurationAware && this.configuration != null) { 1450 ((ConfigurationAware) variableResolver).setConfiguration(this.configuration); 1451 } 1452 this.variableResolver = variableResolver; 1453 } 1454 1455 // Substitution support in variable names 1456 //----------------------------------------------------------------------- 1457 /** 1458 * Returns a flag whether substitution is done in variable names. 1459 * 1460 * @return the substitution in variable names flag 1461 */ 1462 public boolean isEnableSubstitutionInVariables() { 1463 return enableSubstitutionInVariables; 1464 } 1465 1466 /** 1467 * Sets a flag whether substitution is done in variable names. If set to 1468 * <b>true</b>, the names of variables can contain other variables which are 1469 * processed first before the original variable is evaluated, e.g. 1470 * <code>${jre-${java.version}}</code>. The default value is <b>true</b>. 1471 * 1472 * @param enableSubstitutionInVariables the new value of the flag 1473 */ 1474 public void setEnableSubstitutionInVariables(final boolean enableSubstitutionInVariables) { 1475 this.enableSubstitutionInVariables = enableSubstitutionInVariables; 1476 } 1477 1478 boolean isRecursiveEvaluationAllowed() { 1479 return recursiveEvaluationAllowed; 1480 } 1481 1482 void setRecursiveEvaluationAllowed(final boolean recursiveEvaluationAllowed) { 1483 this.recursiveEvaluationAllowed = recursiveEvaluationAllowed; 1484 } 1485 1486 private char[] getChars(final StringBuilder sb) { 1487 final char[] chars = new char[sb.length()]; 1488 sb.getChars(0, sb.length(), chars, 0); 1489 return chars; 1490 } 1491 1492 /** 1493 * Appends a iterable placing separators between each value, but 1494 * not before the first or after the last. 1495 * Appending a null iterable will have no effect.. 1496 * 1497 * @param sb StringBuilder that contains the String being constructed. 1498 * @param iterable the iterable to append 1499 * @param separator the separator to use, null means no separator 1500 */ 1501 public void appendWithSeparators(final StringBuilder sb, final Iterable<?> iterable, String separator) { 1502 if (iterable != null) { 1503 separator = separator == null ? Strings.EMPTY : separator; 1504 final Iterator<?> it = iterable.iterator(); 1505 while (it.hasNext()) { 1506 sb.append(it.next()); 1507 if (it.hasNext()) { 1508 sb.append(separator); 1509 } 1510 } 1511 } 1512 } 1513 1514 @Override 1515 public String toString() { 1516 return "StrSubstitutor(" + variableResolver.toString() + ')'; 1517 } 1518 1519 @Override 1520 public void setConfiguration(final Configuration configuration) { 1521 this.configuration = configuration; 1522 if (this.variableResolver instanceof ConfigurationAware) { 1523 ((ConfigurationAware) this.variableResolver).setConfiguration(this.configuration); 1524 } 1525 } 1526}