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