1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache license, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the license for the specific language governing permissions and
15 * limitations under the license.
16 */
17 package org.apache.logging.log4j.core.lookup;
18
19 import java.util.ArrayList;
20 import java.util.Enumeration;
21 import java.util.HashMap;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Objects;
26 import java.util.Properties;
27
28 import org.apache.logging.log4j.core.LogEvent;
29 import org.apache.logging.log4j.core.config.Configuration;
30 import org.apache.logging.log4j.core.config.ConfigurationAware;
31 import org.apache.logging.log4j.status.StatusLogger;
32 import org.apache.logging.log4j.util.Strings;
33
34 /**
35 * Substitutes variables within a string by values.
36 * <p>
37 * This class takes a piece of text and substitutes all the variables within it.
38 * The default definition of a variable is <code>${variableName}</code>.
39 * The prefix and suffix can be changed via constructors and set methods.
40 * </p>
41 * <p>
42 * Variable values are typically resolved from a map, but could also be resolved
43 * from system properties, or by supplying a custom variable resolver.
44 * </p>
45 * <p>
46 * The simplest example is to use this class to replace Java System properties. For example:
47 * </p>
48 * <pre>
49 * StrSubstitutor.replaceSystemProperties(
50 * "You are running with java.version = ${java.version} and os.name = ${os.name}.");
51 * </pre>
52 * <p>
53 * Typical usage of this class follows the following pattern: First an instance is created
54 * and initialized with the map that contains the values for the available variables.
55 * If a prefix and/or suffix for variables should be used other than the default ones,
56 * the appropriate settings can be performed. After that the <code>replace()</code>
57 * method can be called passing in the source text for interpolation. In the returned
58 * text all variable references (as long as their values are known) will be resolved.
59 * The following example demonstrates this:
60 * </p>
61 * <pre>
62 * Map valuesMap = HashMap();
63 * valuesMap.put("animal", "quick brown fox");
64 * valuesMap.put("target", "lazy dog");
65 * String templateString = "The ${animal} jumped over the ${target}.";
66 * StrSubstitutor sub = new StrSubstitutor(valuesMap);
67 * String resolvedString = sub.replace(templateString);
68 * </pre>
69 * <p>yielding:</p>
70 * <pre>
71 * The quick brown fox jumped over the lazy dog.
72 * </pre>
73 * <p>
74 * Also, this class allows to set a default value for unresolved variables.
75 * The default value for a variable can be appended to the variable name after the variable
76 * default value delimiter. The default value of the variable default value delimiter is ':-',
77 * as in bash and other *nix shells, as those are arguably where the default ${} delimiter set originated.
78 * The variable default value delimiter can be manually set by calling {@link #setValueDelimiterMatcher(StrMatcher)},
79 * {@link #setValueDelimiter(char)} or {@link #setValueDelimiter(String)}.
80 * The following shows an example with variable default value settings:
81 * </p>
82 * <pre>
83 * Map valuesMap = HashMap();
84 * valuesMap.put("animal", "quick brown fox");
85 * valuesMap.put("target", "lazy dog");
86 * String templateString = "The ${animal} jumped over the ${target}. ${undefined.number:-1234567890}.";
87 * StrSubstitutor sub = new StrSubstitutor(valuesMap);
88 * String resolvedString = sub.replace(templateString);
89 * </pre>
90 * <p>yielding:</p>
91 * <pre>
92 * The quick brown fox jumped over the lazy dog. 1234567890.
93 * </pre>
94 * <p>
95 * In addition to this usage pattern there are some static convenience methods that
96 * cover the most common use cases. These methods can be used without the need of
97 * manually creating an instance. However if multiple replace operations are to be
98 * performed, creating and reusing an instance of this class will be more efficient.
99 * </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 */
143 public 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 }