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