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 1552654 2013-12-20 13:25:37Z britter $
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(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(CharSequence source, int offset, int length) {
535        if (source == null) {
536            return null;
537        }
538        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(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(StringBuilder source, int offset, int length) {
674        if (source == null) {
675            return false;
676        }
677        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                            } else {
869                                nestedVarCount--;
870                                pos += endMatchLen;
871                            }
872                        }
873                    }
874                }
875            }
876        }
877        if (top) {
878            return altered ? 1 : 0;
879        }
880        return lengthChange;
881    }
882
883    /**
884     * Checks if the specified variable is already in the stack (list) of variables.
885     *
886     * @param varName  the variable name to check
887     * @param priorVariables  the list of prior variables
888     */
889    private void checkCyclicSubstitution(final String varName, final List<String> priorVariables) {
890        if (priorVariables.contains(varName) == false) {
891            return;
892        }
893        final StrBuilder buf = new StrBuilder(256);
894        buf.append("Infinite loop in property interpolation of ");
895        buf.append(priorVariables.remove(0));
896        buf.append(": ");
897        buf.appendWithSeparators(priorVariables, "->");
898        throw new IllegalStateException(buf.toString());
899    }
900
901    /**
902     * Internal method that resolves the value of a variable.
903     * <p>
904     * Most users of this class do not need to call this method. This method is
905     * called automatically by the substitution process.
906     * <p>
907     * Writers of subclasses can override this method if they need to alter
908     * how each substitution occurs. The method is passed the variable's name
909     * and must return the corresponding value. This implementation uses the
910     * {@link #getVariableResolver()} with the variable's name as the key.
911     *
912     * @param variableName  the name of the variable, not null
913     * @param buf  the buffer where the substitution is occurring, not null
914     * @param startPos  the start position of the variable including the prefix, valid
915     * @param endPos  the end position of the variable including the suffix, valid
916     * @return the variable's value or <b>null</b> if the variable is unknown
917     */
918    protected String resolveVariable(final String variableName, final StrBuilder buf, final int startPos, final int endPos) {
919        final StrLookup<?> resolver = getVariableResolver();
920        if (resolver == null) {
921            return null;
922        }
923        return resolver.lookup(variableName);
924    }
925
926    // Escape
927    //-----------------------------------------------------------------------
928    /**
929     * Returns the escape character.
930     *
931     * @return the character used for escaping variable references
932     */
933    public char getEscapeChar() {
934        return this.escapeChar;
935    }
936
937    /**
938     * Sets the escape character.
939     * If this character is placed before a variable reference in the source
940     * text, this variable will be ignored.
941     *
942     * @param escapeCharacter  the escape character (0 for disabling escaping)
943     */
944    public void setEscapeChar(final char escapeCharacter) {
945        this.escapeChar = escapeCharacter;
946    }
947
948    // Prefix
949    //-----------------------------------------------------------------------
950    /**
951     * Gets the variable prefix matcher currently in use.
952     * <p>
953     * The variable prefix is the characer or characters that identify the
954     * start of a variable. This prefix is expressed in terms of a matcher
955     * allowing advanced prefix matches.
956     *
957     * @return the prefix matcher in use
958     */
959    public StrMatcher getVariablePrefixMatcher() {
960        return prefixMatcher;
961    }
962
963    /**
964     * Sets the variable prefix matcher currently in use.
965     * <p>
966     * The variable prefix is the characer or characters that identify the
967     * start of a variable. This prefix is expressed in terms of a matcher
968     * allowing advanced prefix matches.
969     *
970     * @param prefixMatcher  the prefix matcher to use, null ignored
971     * @return this, to enable chaining
972     * @throws IllegalArgumentException if the prefix matcher is null
973     */
974    public StrSubstitutor setVariablePrefixMatcher(final StrMatcher prefixMatcher) {
975        if (prefixMatcher == null) {
976            throw new IllegalArgumentException("Variable prefix matcher must not be null!");
977        }
978        this.prefixMatcher = prefixMatcher;
979        return this;
980    }
981
982    /**
983     * Sets the variable prefix to use.
984     * <p>
985     * The variable prefix is the character or characters that identify the
986     * start of a variable. This method allows a single character prefix to
987     * be easily set.
988     *
989     * @param prefix  the prefix character to use
990     * @return this, to enable chaining
991     */
992    public StrSubstitutor setVariablePrefix(final char prefix) {
993        return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix));
994    }
995
996    /**
997     * Sets the variable prefix to use.
998     * <p>
999     * The variable prefix is the characer or characters that identify the
1000     * start of a variable. This method allows a string prefix to be easily set.
1001     *
1002     * @param prefix  the prefix for variables, not null
1003     * @return this, to enable chaining
1004     * @throws IllegalArgumentException if the prefix is null
1005     */
1006    public StrSubstitutor setVariablePrefix(final String prefix) {
1007       if (prefix == null) {
1008            throw new IllegalArgumentException("Variable prefix must not be null!");
1009        }
1010        return setVariablePrefixMatcher(StrMatcher.stringMatcher(prefix));
1011    }
1012
1013    // Suffix
1014    //-----------------------------------------------------------------------
1015    /**
1016     * Gets the variable suffix matcher currently in use.
1017     * <p>
1018     * The variable suffix is the characer or characters that identify the
1019     * end of a variable. This suffix is expressed in terms of a matcher
1020     * allowing advanced suffix matches.
1021     *
1022     * @return the suffix matcher in use
1023     */
1024    public StrMatcher getVariableSuffixMatcher() {
1025        return suffixMatcher;
1026    }
1027
1028    /**
1029     * Sets the variable suffix matcher currently in use.
1030     * <p>
1031     * The variable suffix is the characer or characters that identify the
1032     * end of a variable. This suffix is expressed in terms of a matcher
1033     * allowing advanced suffix matches.
1034     *
1035     * @param suffixMatcher  the suffix matcher to use, null ignored
1036     * @return this, to enable chaining
1037     * @throws IllegalArgumentException if the suffix matcher is null
1038     */
1039    public StrSubstitutor setVariableSuffixMatcher(final StrMatcher suffixMatcher) {
1040        if (suffixMatcher == null) {
1041            throw new IllegalArgumentException("Variable suffix matcher must not be null!");
1042        }
1043        this.suffixMatcher = suffixMatcher;
1044        return this;
1045    }
1046
1047    /**
1048     * Sets the variable suffix to use.
1049     * <p>
1050     * The variable suffix is the characer or characters that identify the
1051     * end of a variable. This method allows a single character suffix to
1052     * be easily set.
1053     *
1054     * @param suffix  the suffix character to use
1055     * @return this, to enable chaining
1056     */
1057    public StrSubstitutor setVariableSuffix(final char suffix) {
1058        return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix));
1059    }
1060
1061    /**
1062     * Sets the variable suffix to use.
1063     * <p>
1064     * The variable suffix is the character or characters that identify the
1065     * end of a variable. This method allows a string suffix to be easily set.
1066     *
1067     * @param suffix  the suffix for variables, not null
1068     * @return this, to enable chaining
1069     * @throws IllegalArgumentException if the suffix is null
1070     */
1071    public StrSubstitutor setVariableSuffix(final String suffix) {
1072       if (suffix == null) {
1073            throw new IllegalArgumentException("Variable suffix must not be null!");
1074        }
1075        return setVariableSuffixMatcher(StrMatcher.stringMatcher(suffix));
1076    }
1077
1078    // Variable Default Value Delimiter
1079    //-----------------------------------------------------------------------
1080    /**
1081     * Gets the variable default value delimiter matcher currently in use.
1082     * <p>
1083     * The variable default value delimiter is the characer or characters that delimite the
1084     * variable name and the variable default value. This delimiter is expressed in terms of a matcher
1085     * allowing advanced variable default value delimiter matches.
1086     * <p>
1087     * If it returns null, then the variable default value resolution is disabled.
1088     *
1089     * @return the variable default value delimiter matcher in use, may be null
1090     * @since 3.2
1091     */
1092    public StrMatcher getValueDelimiterMatcher() {
1093        return valueDelimiterMatcher;
1094    }
1095
1096    /**
1097     * Sets the variable default value delimiter matcher to use.
1098     * <p>
1099     * The variable default value delimiter is the characer or characters that delimite the
1100     * variable name and the variable default value. This delimiter is expressed in terms of a matcher
1101     * allowing advanced variable default value delimiter matches.
1102     * <p>
1103     * If the <code>valueDelimiterMatcher</code> is null, then the variable default value resolution
1104     * becomes disabled.
1105     *
1106     * @param valueDelimiterMatcher  variable default value delimiter matcher to use, may be null
1107     * @return this, to enable chaining
1108     * @since 3.2
1109     */
1110    public StrSubstitutor setValueDelimiterMatcher(final StrMatcher valueDelimiterMatcher) {
1111        this.valueDelimiterMatcher = valueDelimiterMatcher;
1112        return this;
1113    }
1114
1115    /**
1116     * Sets the variable default value delimiter to use.
1117     * <p>
1118     * The variable default value delimiter is the characer or characters that delimite the
1119     * variable name and the variable default value. This method allows a single character
1120     * variable default value delimiter to be easily set.
1121     *
1122     * @param valueDelimiter  the variable default value delimiter character to use
1123     * @return this, to enable chaining
1124     * @since 3.2
1125     */
1126    public StrSubstitutor setValueDelimiter(final char valueDelimiter) {
1127        return setValueDelimiterMatcher(StrMatcher.charMatcher(valueDelimiter));
1128    }
1129
1130    /**
1131     * Sets the variable default value delimiter to use.
1132     * <p>
1133     * The variable default value delimiter is the characer or characters that delimite the
1134     * variable name and the variable default value. This method allows a string
1135     * variable default value delimiter to be easily set.
1136     * <p>
1137     * If the <code>valueDelimiter</code> is null or empty string, then the variable default
1138     * value resolution becomes disabled.
1139     *
1140     * @param valueDelimiter  the variable default value delimiter string to use, may be null or empty
1141     * @return this, to enable chaining
1142     * @since 3.2
1143     */
1144    public StrSubstitutor setValueDelimiter(final String valueDelimiter) {
1145        if (StringUtils.isEmpty(valueDelimiter)) {
1146            setValueDelimiterMatcher(null);
1147            return this;
1148        }
1149        return setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter));
1150    }
1151
1152    // Resolver
1153    //-----------------------------------------------------------------------
1154    /**
1155     * Gets the VariableResolver that is used to lookup variables.
1156     *
1157     * @return the VariableResolver
1158     */
1159    public StrLookup<?> getVariableResolver() {
1160        return this.variableResolver;
1161    }
1162
1163    /**
1164     * Sets the VariableResolver that is used to lookup variables.
1165     *
1166     * @param variableResolver  the VariableResolver
1167     */
1168    public void setVariableResolver(final StrLookup<?> variableResolver) {
1169        this.variableResolver = variableResolver;
1170    }
1171
1172    // Substitution support in variable names
1173    //-----------------------------------------------------------------------
1174    /**
1175     * Returns a flag whether substitution is done in variable names.
1176     *
1177     * @return the substitution in variable names flag
1178     * @since 3.0
1179     */
1180    public boolean isEnableSubstitutionInVariables() {
1181        return enableSubstitutionInVariables;
1182    }
1183
1184    /**
1185     * Sets a flag whether substitution is done in variable names. If set to
1186     * <b>true</b>, the names of variables can contain other variables which are
1187     * processed first before the original variable is evaluated, e.g.
1188     * <code>${jre-${java.version}}</code>. The default value is <b>false</b>.
1189     *
1190     * @param enableSubstitutionInVariables the new value of the flag
1191     * @since 3.0
1192     */
1193    public void setEnableSubstitutionInVariables(
1194            final boolean enableSubstitutionInVariables) {
1195        this.enableSubstitutionInVariables = enableSubstitutionInVariables;
1196    }
1197}