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