    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements.  See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache License, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License.  You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the License for the specific language governing permissions and
    015     * limitations under the License.
    016     */
    017    package org.apache.commons.lang.text;
    018
    019    import java.util.ArrayList;
    020    import java.util.List;
    021    import java.util.Map;
    022
    023    /**
    024     * Substitutes variables within a string by values.
    025     * <p>
    026     * This class takes a piece of text and substitutes all the variables within it.
    027     * The default definition of a variable is <code>${variableName}</code>.
    028     * The prefix and suffix can be changed via constructors and set methods.
    029     * <p>
    030     * Variable values are typically resolved from a map, but could also be resolved
    031     * from system properties, or by supplying a custom variable resolver.
    032     * <p>
    033     * The simplest example is to use this class to replace Java System properties. For example:
    034     * <pre>
    035     * StrSubstitutor.replaceSystemProperties(
    036     *      "You are running with java.version = ${java.version} and os.name = ${os.name}.");
    037     * </pre>
    038     * <p>
    039     * Typical usage of this class follows the following pattern: First an instance is created
    040     * and initialized with the map that contains the values for the available variables.
    041     * If a prefix and/or suffix for variables should be used other than the default ones,
    042     * the appropriate settings can be performed. After that the <code>replace()</code>
    043     * method can be called passing in the source text for interpolation. In the returned
    044     * text all variable references (as long as their values are known) will be resolved.
    045     * The following example demonstrates this:
    046     * <pre>
    047     * Map valuesMap = HashMap();
    048     * valuesMap.put(&quot;animal&quot;, &quot;quick brown fox&quot;);
    049     * valuesMap.put(&quot;target&quot;, &quot;lazy dog&quot;);
    050     * String templateString = &quot;The ${animal} jumped over the ${target}.&quot;;
    051     * StrSubstitutor sub = new StrSubstitutor(valuesMap);
    052     * String resolvedString = sub.replace(templateString);
    053     * </pre>
    054     * yielding:
    055     * <pre>
    056     *      The quick brown fox jumped over the lazy dog.
    057     * </pre>
    058     * <p>
    059     * In addition to this usage pattern there are some static convenience methods that
    060     * cover the most common use cases. These methods can be used without the need of
    061     * manually creating an instance. However if multiple replace operations are to be
    062     * performed, creating and reusing an instance of this class will be more efficient.
    063     * <p>
    064     * Variable replacement works in a recursive way. Thus, if a variable value contains
    065     * a variable then that variable will also be replaced. Cyclic replacements are
    066     * detected and will cause an exception to be thrown.
    067     * <p>
    068     * Sometimes the interpolation's result must contain a variable prefix. As an example
    069     * take the following source text:
    070     * <pre>
    071     *   The variable ${${name}} must be used.
    072     * </pre>
    073     * Here only the variable's name refered to in the text should be replaced resulting
    074     * in the text (assuming that the value of the <code>name</code> variable is <code>x</code>):
    075     * <pre>
    076     *   The variable ${x} must be used.
    077     * </pre>
    078     * To achieve this effect there are two possibilities: Either set a different prefix
    079     * and suffix for variables which do not conflict with the result text you want to
    080     * produce. The other possibility is to use the escape character, by default '$'.
    081     * If this character is placed before a variable reference, this reference is ignored
    082     * and won't be replaced. For example:
    083     * <pre>
    084     *   The variable $${${name}} must be used.
    085     * </pre>
    086     *
    087     * @author Apache Software Foundation
    088     * @author Oliver Heger
    089     * @version $Id: StrSubstitutor.java 905636 2010-02-02 14:03:32Z niallp $
    090     * @since 2.2
    091     */
    092    public class StrSubstitutor {
    093
    094        /**
    095         * Constant for the default escape character.
    096         */
    097        public static final char DEFAULT_ESCAPE = '$';
    098        /**
    099         * Constant for the default variable prefix.
    100         */
    101        public static final StrMatcher DEFAULT_PREFIX = StrMatcher.stringMatcher("${");
    102        /**
    103         * Constant for the default variable suffix.
    104         */
    105        public static final StrMatcher DEFAULT_SUFFIX = StrMatcher.stringMatcher("}");
    106
    107        /**
    108         * Stores the escape character.
    109         */
    110        private char escapeChar;
    111        /**
    112         * Stores the variable prefix.
    113         */
    114        private StrMatcher prefixMatcher;
    115        /**
    116         * Stores the variable suffix.
    117         */
    118        private StrMatcher suffixMatcher;
    119        /**
    120         * Variable resolution is delegated to an implementor of VariableResolver.
    121         */
    122        private StrLookup variableResolver;
    123
    124        //-----------------------------------------------------------------------
    125        /**
    126         * Replaces all the occurrences of variables in the given source object with
    127         * their matching values from the map.
    128         *
    129         * @param source  the source text containing the variables to substitute, null returns null
    130         * @param valueMap  the map with the values, may be null
    131         * @return the result of the replace operation
    132         */
    133        public static String replace(Object source, Map valueMap) {
    134            return new StrSubstitutor(valueMap).replace(source);
    135        }
    136
    137        /**
    138         * Replaces all the occurrences of variables in the given source object with
    139         * their matching values from the map. This method allows to specifiy a
    140         * custom variable prefix and suffix
    141         *
    142         * @param source  the source text containing the variables to substitute, null returns null
    143         * @param valueMap  the map with the values, may be null
    144         * @param prefix  the prefix of variables, not null
    145         * @param suffix  the suffix of variables, not null
    146         * @return the result of the replace operation
    147         * @throws IllegalArgumentException if the prefix or suffix is null
    148         */
    149        public static String replace(Object source, Map valueMap, String prefix, String suffix) {
    150            return new StrSubstitutor(valueMap, prefix, suffix).replace(source);
    151        }
    152
    153        /**
    154         * Replaces all the occurrences of variables in the given source object with
    155         * their matching values from the system properties.
    156         *
    157         * @param source  the source text containing the variables to substitute, null returns null
    158         * @return the result of the replace operation
    159         */
    160        public static String replaceSystemProperties(Object source) {
    161            return new StrSubstitutor(StrLookup.systemPropertiesLookup()).replace(source);
    162        }
    163
    164        //-----------------------------------------------------------------------
    165        /**
    166         * Creates a new instance with defaults for variable prefix and suffix
    167         * and the escaping character.
    168         */
    169        public StrSubstitutor() {
    170            this((StrLookup) null, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
    171        }
    172
    173        /**
    174         * Creates a new instance and initializes it. Uses defaults for variable
    175         * prefix and suffix and the escaping character.
    176         *
    177         * @param valueMap  the map with the variables' values, may be null
    178         */
    179        public StrSubstitutor(Map valueMap) {
    180            this(StrLookup.mapLookup(valueMap), DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
    181        }
    182
    183        /**
    184         * Creates a new instance and initializes it. Uses a default escaping character.
    185         *
    186         * @param valueMap  the map with the variables' values, may be null
    187         * @param prefix  the prefix for variables, not null
    188         * @param suffix  the suffix for variables, not null
    189         * @throws IllegalArgumentException if the prefix or suffix is null
    190         */
    191        public StrSubstitutor(Map valueMap, String prefix, String suffix) {
    192            this(StrLookup.mapLookup(valueMap), prefix, suffix, DEFAULT_ESCAPE);
    193        }
    194
    195        /**
    196         * Creates a new instance and initializes it.
    197         *
    198         * @param valueMap  the map with the variables' values, may be null
    199         * @param prefix  the prefix for variables, not null
    200         * @param suffix  the suffix for variables, not null
    201         * @param escape  the escape character
    202         * @throws IllegalArgumentException if the prefix or suffix is null
    203         */
    204        public StrSubstitutor(Map valueMap, String prefix, String suffix, char escape) {
    205            this(StrLookup.mapLookup(valueMap), prefix, suffix, escape);
    206        }
    207
    208        /**
    209         * Creates a new instance and initializes it.
    210         *
    211         * @param variableResolver  the variable resolver, may be null
    212         */
    213        public StrSubstitutor(StrLookup variableResolver) {
    214            this(variableResolver, DEFAULT_PREFIX, DEFAULT_SUFFIX, DEFAULT_ESCAPE);
    215        }
    216
    217        /**
    218         * Creates a new instance and initializes it.
    219         *
    220         * @param variableResolver  the variable resolver, may be null
    221         * @param prefix  the prefix for variables, not null
    222         * @param suffix  the suffix for variables, not null
    223         * @param escape  the escape character
    224         * @throws IllegalArgumentException if the prefix or suffix is null
    225         */
    226        public StrSubstitutor(StrLookup variableResolver, String prefix, String suffix, char escape) {
    227            this.setVariableResolver(variableResolver);
    228            this.setVariablePrefix(prefix);
    229            this.setVariableSuffix(suffix);
    230            this.setEscapeChar(escape);
    231        }
    232
    233        /**
    234         * Creates a new instance and initializes it.
    235         *
    236         * @param variableResolver  the variable resolver, may be null
    237         * @param prefixMatcher  the prefix for variables, not null
    238         * @param suffixMatcher  the suffix for variables, not null
    239         * @param escape  the escape character
    240         * @throws IllegalArgumentException if the prefix or suffix is null
    241         */
    242        public StrSubstitutor(
    243                StrLookup variableResolver, StrMatcher prefixMatcher, StrMatcher suffixMatcher, char escape) {
    244            this.setVariableResolver(variableResolver);
    245            this.setVariablePrefixMatcher(prefixMatcher);
    246            this.setVariableSuffixMatcher(suffixMatcher);
    247            this.setEscapeChar(escape);
    248        }
    249
    250        //-----------------------------------------------------------------------
    251        /**
    252         * Replaces all the occurrences of variables with their matching values
    253         * from the resolver using the given source string as a template.
    254         *
    255         * @param source  the string to replace in, null returns null
    256         * @return the result of the replace operation
    257         */
    258        public String replace(String source) {
    259            if (source == null) {
    260                return null;
    261            }
    262            StrBuilder buf = new StrBuilder(source);
    263            if (substitute(buf, 0, source.length()) == false) {
    264                return source;
    265            }
    266            return buf.toString();
    267        }
    268
    269        /**
    270         * Replaces all the occurrences of variables with their matching values
    271         * from the resolver using the given source string as a template.
    272         * <p>
    273         * Only the specified portion of the string will be processed.
    274         * The rest of the string is not processed, and is not returned.
    275         *
    276         * @param source  the string to replace in, null returns null
    277         * @param offset  the start offset within the array, must be valid
    278         * @param length  the length within the array to be processed, must be valid
    279         * @return the result of the replace operation
    280         */
    281        public String replace(String source, int offset, int length) {
    282            if (source == null) {
    283                return null;
    284            }
    285            StrBuilder buf = new StrBuilder(length).append(source, offset, length);
    286            if (substitute(buf, 0, length) == false) {
    287                return source.substring(offset, offset + length);
    288            }
    289            return buf.toString();
    290        }
    291
    292        //-----------------------------------------------------------------------
    293        /**
    294         * Replaces all the occurrences of variables with their matching values
    295         * from the resolver using the given source array as a template.
    296         * The array is not altered by this method.
    297         *
    298         * @param source  the character array to replace in, not altered, null returns null
    299         * @return the result of the replace operation
    300         */
    301        public String replace(char[] source) {
    302            if (source == null) {
    303                return null;
    304            }
    305            StrBuilder buf = new StrBuilder(source.length).append(source);
    306            substitute(buf, 0, source.length);
    307            return buf.toString();
    308        }
    309
    310        /**
    311         * Replaces all the occurrences of variables with their matching values
    312         * from the resolver using the given source array as a template.
    313         * The array is not altered by this method.
    314         * <p>
    315         * Only the specified portion of the array will be processed.
    316         * The rest of the array is not processed, and is not returned.
    317         *
    318         * @param source  the character array to replace in, not altered, null returns null
    319         * @param offset  the start offset within the array, must be valid
    320         * @param length  the length within the array to be processed, must be valid
    321         * @return the result of the replace operation
    322         */
    323        public String replace(char[] source, int offset, int length) {
    324            if (source == null) {
    325                return null;
    326            }
    327            StrBuilder buf = new StrBuilder(length).append(source, offset, length);
    328            substitute(buf, 0, length);
    329            return buf.toString();
    330        }
    331
    332        //-----------------------------------------------------------------------
    333        /**
    334         * Replaces all the occurrences of variables with their matching values
    335         * from the resolver using the given source buffer as a template.
    336         * The buffer is not altered by this method.
    337         *
    338         * @param source  the buffer to use as a template, not changed, null returns null
    339         * @return the result of the replace operation
    340         */
    341        public String replace(StringBuffer source) {
    342            if (source == null) {
    343                return null;
    344            }
    345            StrBuilder buf = new StrBuilder(source.length()).append(source);
    346            substitute(buf, 0, buf.length());
    347            return buf.toString();
    348        }
    349
    350        /**
    351         * Replaces all the occurrences of variables with their matching values
    352         * from the resolver using the given source buffer as a template.
    353         * The buffer is not altered by this method.
    354         * <p>
    355         * Only the specified portion of the buffer will be processed.
    356         * The rest of the buffer is not processed, and is not returned.
    357         *
    358         * @param source  the buffer to use as a template, not changed, null returns null
    359         * @param offset  the start offset within the array, must be valid
    360         * @param length  the length within the array to be processed, must be valid
    361         * @return the result of the replace operation
    362         */
    363        public String replace(StringBuffer source, int offset, int length) {
    364            if (source == null) {
    365                return null;
    366            }
    367            StrBuilder buf = new StrBuilder(length).append(source, offset, length);
    368            substitute(buf, 0, length);
    369            return buf.toString();
    370        }
    371
    372        //-----------------------------------------------------------------------
    373        /**
    374         * Replaces all the occurrences of variables with their matching values
    375         * from the resolver using the given source builder as a template.
    376         * The builder is not altered by this method.
    377         *
    378         * @param source  the builder to use as a template, not changed, null returns null
    379         * @return the result of the replace operation
    380         */
    381        public String replace(StrBuilder source) {
    382            if (source == null) {
    383                return null;
    384            }
    385            StrBuilder buf = new StrBuilder(source.length()).append(source);
    386            substitute(buf, 0, buf.length());
    387            return buf.toString();
    388        }
    389
    390        /**
    391         * Replaces all the occurrences of variables with their matching values
    392         * from the resolver using the given source builder as a template.
    393         * The builder is not altered by this method.
    394         * <p>
    395         * Only the specified portion of the builder will be processed.
    396         * The rest of the builder is not processed, and is not returned.
    397         *
    398         * @param source  the builder to use as a template, not changed, null returns null
    399         * @param offset  the start offset within the array, must be valid
    400         * @param length  the length within the array to be processed, must be valid
    401         * @return the result of the replace operation
    402         */
    403        public String replace(StrBuilder source, int offset, int length) {
    404            if (source == null) {
    405                return null;
    406            }
    407            StrBuilder buf = new StrBuilder(length).append(source, offset, length);
    408            substitute(buf, 0, length);
    409            return buf.toString();
    410        }
    411
    412        //-----------------------------------------------------------------------
    413        /**
    414         * Replaces all the occurrences of variables in the given source object with
    415         * their matching values from the resolver. The input source object is
    416         * converted to a string using <code>toString</code> and is not altered.
    417         *
    418         * @param source  the source to replace in, null returns null
    419         * @return the result of the replace operation
    420         */
    421        public String replace(Object source) {
    422            if (source == null) {
    423                return null;
    424            }
    425            StrBuilder buf = new StrBuilder().append(source);
    426            substitute(buf, 0, buf.length());
    427            return buf.toString();
    428        }
    429
    430        //-----------------------------------------------------------------------
    431        /**
    432         * Replaces all the occurrences of variables within the given source buffer
    433         * with their matching values from the resolver.
    434         * The buffer is updated with the result.
    435         *
    436         * @param source  the buffer to replace in, updated, null returns zero
    437         * @return true if altered
    438         */
    439        public boolean replaceIn(StringBuffer source) {
    440            if (source == null) {
    441                return false;
    442            }
    443            return replaceIn(source, 0, source.length());
    444        }
    445
    446        /**
    447         * Replaces all the occurrences of variables within the given source buffer
    448         * with their matching values from the resolver.
    449         * The buffer is updated with the result.
    450         * <p>
    451         * Only the specified portion of the buffer will be processed.
    452         * The rest of the buffer is not processed, but it is not deleted.
    453         *
    454         * @param source  the buffer to replace in, updated, null returns zero
    455         * @param offset  the start offset within the array, must be valid
    456         * @param length  the length within the buffer to be processed, must be valid
    457         * @return true if altered
    458         */
    459        public boolean replaceIn(StringBuffer source, int offset, int length) {
    460            if (source == null) {
    461                return false;
    462            }
    463            StrBuilder buf = new StrBuilder(length).append(source, offset, length);
    464            if (substitute(buf, 0, length) == false) {
    465                return false;
    466            }
    467            source.replace(offset, offset + length, buf.toString());
    468            return true;
    469        }
    470
    471        //-----------------------------------------------------------------------
    472        /**
    473         * Replaces all the occurrences of variables within the given source
    474         * builder with their matching values from the resolver.
    475         *
    476         * @param source  the builder to replace in, updated, null returns zero
    477         * @return true if altered
    478         */
    479        public boolean replaceIn(StrBuilder source) {
    480            if (source == null) {
    481                return false;
    482            }
    483            return substitute(source, 0, source.length());
    484        }
    485
    486        /**
    487         * Replaces all the occurrences of variables within the given source
    488         * builder with their matching values from the resolver.
    489         * <p>
    490         * Only the specified portion of the builder will be processed.
    491         * The rest of the builder is not processed, but it is not deleted.
    492         *
    493         * @param source  the builder to replace in, null returns zero
    494         * @param offset  the start offset within the array, must be valid
    495         * @param length  the length within the builder to be processed, must be valid
    496         * @return true if altered
    497         */
    498        public boolean replaceIn(StrBuilder source, int offset, int length) {
    499            if (source == null) {
    500                return false;
    501            }
    502            return substitute(source, offset, length);
    503        }
    504
    505        //-----------------------------------------------------------------------
    506        /**
    507         * Internal method that substitutes the variables.
    508         * <p>
    509         * Most users of this class do not need to call this method. This method will
    510         * be called automatically by another (public) method.
    511         * <p>
    512         * Writers of subclasses can override this method if they need access to
    513         * the substitution process at the start or end.
    514         *
    515         * @param buf  the string builder to substitute into, not null
    516         * @param offset  the start offset within the builder, must be valid
    517         * @param length  the length within the builder to be processed, must be valid
    518         * @return true if altered
    519         */
    520        protected boolean substitute(StrBuilder buf, int offset, int length) {
    521            return substitute(buf, offset, length, null) > 0;
    522        }
    523
    524        /**
    525         * Recursive handler for multiple levels of interpolation. This is the main
    526         * interpolation method, which resolves the values of all variable references
    527         * contained in the passed in text.
    528         *
    529         * @param buf  the string builder to substitute into, not null
    530         * @param offset  the start offset within the builder, must be valid
    531         * @param length  the length within the builder to be processed, must be valid
    532         * @param priorVariables  the stack keeping track of the replaced variables, may be null
    533         * @return the length change that occurs, unless priorVariables is null when the int
    534         *  represents a boolean flag as to whether any change occurred.
    535         */
    536        private int substitute(StrBuilder buf, int offset, int length, List priorVariables) {
    537            StrMatcher prefixMatcher = getVariablePrefixMatcher();
    538            StrMatcher suffixMatcher = getVariableSuffixMatcher();
    539            char escape = getEscapeChar();
    540
    541            boolean top = (priorVariables == null);
    542            boolean altered = false;
    543            int lengthChange = 0;
    544            char[] chars = buf.buffer;
    545            int bufEnd = offset + length;
    546            int pos = offset;
    547            while (pos < bufEnd) {
    548                int startMatchLen = prefixMatcher.isMatch(chars, pos, offset, bufEnd);
    549                if (startMatchLen == 0) {
    550                    pos++;
    551                } else {
    552                    // found variable start marker
    553                    if (pos > offset && chars[pos - 1] == escape) {
    554                        // escaped
    555                        buf.deleteCharAt(pos - 1);
    556                        chars = buf.buffer;  // in case buffer was altered
    557                        lengthChange--;
    558                        altered = true;
    559                        bufEnd--;
    560                    } else {
    561                        // find suffix
    562                        int startPos = pos;
    563                        pos += startMatchLen;
    564                        int endMatchLen = 0;
    565                        while (pos < bufEnd) {
    566                            endMatchLen = suffixMatcher.isMatch(chars, pos, offset, bufEnd);
    567                            if (endMatchLen == 0) {
    568                                pos++;
    569                            } else {
    570                                // found variable end marker
    571                                String varName = new String(chars, startPos + startMatchLen,
    572                                                            pos - startPos - startMatchLen);
    573                                pos += endMatchLen;
    574                                int endPos = pos;
    575
    576                                // on the first call initialize priorVariables
    577                                if (priorVariables == null) {
    578                                    priorVariables = new ArrayList();
    579                                    priorVariables.add(new String(chars, offset, length));
    580                                }
    581
    582                                // handle cyclic substitution
    583                                checkCyclicSubstitution(varName, priorVariables);
    584                                priorVariables.add(varName);
    585
    586                                // resolve the variable
    587                                String varValue = resolveVariable(varName, buf, startPos, endPos);
    588                                if (varValue != null) {
    589                                    // recursive replace
    590                                    int varLen = varValue.length();
    591                                    buf.replace(startPos, endPos, varValue);
    592                                    altered = true;
    593                                    int change = substitute(buf, startPos, varLen, priorVariables);
    594                                    change = change + (varLen - (endPos - startPos));
    595                                    pos += change;
    596                                    bufEnd += change;
    597                                    lengthChange += change;
    598                                    chars = buf.buffer;  // in case buffer was altered
    599                                }
    600
    601                                // remove variable from the cyclic stack
    602                                priorVariables.remove(priorVariables.size() - 1);
    603                                break;
    604                            }
    605                        }
    606                    }
    607                }
    608            }
    609            if (top) {
    610                return (altered ? 1 : 0);
    611            }
    612            return lengthChange;
    613        }
    614
    615        /**
    616         * Checks if the specified variable is already in the stack (list) of variables.
    617         *
    618         * @param varName  the variable name to check
    619         * @param priorVariables  the list of prior variables
    620         */
    621        private void checkCyclicSubstitution(String varName, List priorVariables) {
    622            if (priorVariables.contains(varName) == false) {
    623                return;
    624            }
    625            StrBuilder buf = new StrBuilder(256);
    626            buf.append("Infinite loop in property interpolation of ");
    627            buf.append(priorVariables.remove(0));
    628            buf.append(": ");
    629            buf.appendWithSeparators(priorVariables, "->");
    630            throw new IllegalStateException(buf.toString());
    631        }
    632
    633        /**
    634         * Internal method that resolves the value of a variable.
    635         * <p>
    636         * Most users of this class do not need to call this method. This method is
    637         * called automatically by the substitution process.
    638         * <p>
    639         * Writers of subclasses can override this method if they need to alter
    640         * how each substitution occurs. The method is passed the variable's name
    641         * and must return the corresponding value. This implementation uses the
    642         * {@link #getVariableResolver()} with the variable's name as the key.
    643         *
    644         * @param variableName  the name of the variable, not null
    645         * @param buf  the buffer where the substitution is occurring, not null
    646         * @param startPos  the start position of the variable including the prefix, valid
    647         * @param endPos  the end position of the variable including the suffix, valid
    648         * @return the variable's value or <b>null</b> if the variable is unknown
    649         */
    650        protected String resolveVariable(String variableName, StrBuilder buf, int startPos, int endPos) {
    651            StrLookup resolver = getVariableResolver();
    652            if (resolver == null) {
    653                return null;
    654            }
    655            return resolver.lookup(variableName);
    656        }
    657
    658        // Escape
    659        //-----------------------------------------------------------------------
    660        /**
    661         * Returns the escape character.
    662         *
    663         * @return the character used for escaping variable references
    664         */
    665        public char getEscapeChar() {
    666            return this.escapeChar;
    667        }
    668
    669        /**
    670         * Sets the escape character.
    671         * If this character is placed before a variable reference in the source
    672         * text, this variable will be ignored.
    673         *
    674         * @param escapeCharacter  the escape character (0 for disabling escaping)
    675         */
    676        public void setEscapeChar(char escapeCharacter) {
    677            this.escapeChar = escapeCharacter;
    678        }
    679
    680        // Prefix
    681        //-----------------------------------------------------------------------
    682        /**
    683         * Gets the variable prefix matcher currently in use.
    684         * <p>
    685         * The variable prefix is the characer or characters that identify the
    686         * start of a variable. This prefix is expressed in terms of a matcher
    687         * allowing advanced prefix matches.
    688         *
    689         * @return the prefix matcher in use
    690         */
    691        public StrMatcher getVariablePrefixMatcher() {
    692            return prefixMatcher;
    693        }
    694
    695        /**
    696         * Sets the variable prefix matcher currently in use.
    697         * <p>
    698         * The variable prefix is the characer or characters that identify the
    699         * start of a variable. This prefix is expressed in terms of a matcher
    700         * allowing advanced prefix matches.
    701         *
    702         * @param prefixMatcher  the prefix matcher to use, null ignored
    703         * @return this, to enable chaining
    704         * @throws IllegalArgumentException if the prefix matcher is null
    705         */
    706        public StrSubstitutor setVariablePrefixMatcher(StrMatcher prefixMatcher) {
    707            if (prefixMatcher == null) {
    708                throw new IllegalArgumentException("Variable prefix matcher must not be null!");
    709            }
    710            this.prefixMatcher = prefixMatcher;
    711            return this;
    712        }
    713
    714        /**
    715         * Sets the variable prefix to use.
    716         * <p>
    717         * The variable prefix is the characer or characters that identify the
    718         * start of a variable. This method allows a single character prefix to
    719         * be easily set.
    720         *
    721         * @param prefix  the prefix character to use
    722         * @return this, to enable chaining
    723         */
    724        public StrSubstitutor setVariablePrefix(char prefix) {
    725            return setVariablePrefixMatcher(StrMatcher.charMatcher(prefix));
    726        }
    727
    728        /**
    729         * Sets the variable prefix to use.
    730         * <p>
    731         * The variable prefix is the characer or characters that identify the
    732         * start of a variable. This method allows a string prefix to be easily set.
    733         *
    734         * @param prefix  the prefix for variables, not null
    735         * @return this, to enable chaining
    736         * @throws IllegalArgumentException if the prefix is null
    737         */
    738        public StrSubstitutor setVariablePrefix(String prefix) {
    739           if (prefix == null) {
    740                throw new IllegalArgumentException("Variable prefix must not be null!");
    741            }
    742            return setVariablePrefixMatcher(StrMatcher.stringMatcher(prefix));
    743        }
    744
    745        // Suffix
    746        //-----------------------------------------------------------------------
    747        /**
    748         * Gets the variable suffix matcher currently in use.
    749         * <p>
    750         * The variable suffix is the characer or characters that identify the
    751         * end of a variable. This suffix is expressed in terms of a matcher
    752         * allowing advanced suffix matches.
    753         *
    754         * @return the suffix matcher in use
    755         */
    756        public StrMatcher getVariableSuffixMatcher() {
    757            return suffixMatcher;
    758        }
    759
    760        /**
    761         * Sets the variable suffix matcher currently in use.
    762         * <p>
    763         * The variable suffix is the characer or characters that identify the
    764         * end of a variable. This suffix is expressed in terms of a matcher
    765         * allowing advanced suffix matches.
    766         *
    767         * @param suffixMatcher  the suffix matcher to use, null ignored
    768         * @return this, to enable chaining
    769         * @throws IllegalArgumentException if the suffix matcher is null
    770         */
    771        public StrSubstitutor setVariableSuffixMatcher(StrMatcher suffixMatcher) {
    772            if (suffixMatcher == null) {
    773                throw new IllegalArgumentException("Variable suffix matcher must not be null!");
    774            }
    775            this.suffixMatcher = suffixMatcher;
    776            return this;
    777        }
    778
    779        /**
    780         * Sets the variable suffix to use.
    781         * <p>
    782         * The variable suffix is the characer or characters that identify the
    783         * end of a variable. This method allows a single character suffix to
    784         * be easily set.
    785         *
    786         * @param suffix  the suffix character to use
    787         * @return this, to enable chaining
    788         */
    789        public StrSubstitutor setVariableSuffix(char suffix) {
    790            return setVariableSuffixMatcher(StrMatcher.charMatcher(suffix));
    791        }
    792
    793        /**
    794         * Sets the variable suffix to use.
    795         * <p>
    796         * The variable suffix is the characer or characters that identify the
    797         * end of a variable. This method allows a string suffix to be easily set.
    798         *
    799         * @param suffix  the suffix for variables, not null
    800         * @return this, to enable chaining
    801         * @throws IllegalArgumentException if the suffix is null
    802         */
    803        public StrSubstitutor setVariableSuffix(String suffix) {
    804           if (suffix == null) {
    805                throw new IllegalArgumentException("Variable suffix must not be null!");
    806            }
    807            return setVariableSuffixMatcher(StrMatcher.stringMatcher(suffix));
    808        }
    809
    810        // Resolver
    811        //-----------------------------------------------------------------------
    812        /**
    813         * Gets the VariableResolver that is used to lookup variables.
    814         *
    815         * @return the VariableResolver
    816         */
    817        public StrLookup getVariableResolver() {
    818            return this.variableResolver;
    819        }
    820
    821        /**
    822         * Sets the VariableResolver that is used to lookup variables.
    823         *
    824         * @param variableResolver  the VariableResolver
    825         */
    826        public void setVariableResolver(StrLookup variableResolver) {
    827            this.variableResolver = variableResolver;
    828        }
    829
    830    }