    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
    018package org.apache.commons.text.lookup;
    019
    020import java.nio.charset.StandardCharsets;
    021import java.nio.file.Path;
    022import java.util.Base64;
    023import java.util.Collections;
    024import java.util.HashMap;
    025import java.util.Locale;
    026import java.util.Map;
    027import java.util.Properties;
    028import java.util.function.BiFunction;
    029import java.util.function.Function;
    030import java.util.function.Supplier;
    031
    032import javax.xml.xpath.XPathFactory;
    033
    034import org.apache.commons.text.StringSubstitutor;
    035
    036/**
    037 * Create instances of string lookups or access singleton string lookups implemented in this package.
    038 * <p>
    039 * The "classic" look up is {@link #mapStringLookup(Map)}.
    040 * </p>
    041 * <p>
    042 * The methods for variable interpolation (A.K.A. variable substitution) are:
    043 * </p>
    044 * <ul>
    045 * <li>{@link #interpolatorStringLookup()}.</li>
    046 * <li>{@link #interpolatorStringLookup(Map)}.</li>
    047 * <li>{@link #interpolatorStringLookup(StringLookup)}.</li>
    048 * <li>{@link #interpolatorStringLookup(Map, StringLookup, boolean)}.</li>
    049 * </ul>
    050 * <p>
    051 * Unless explicitly requested otherwise, a set of default lookups are included for convenience with these variable interpolation methods. These defaults are
    052 * listed in the table below. However, the exact lookups included can be configured through the use of the {@value #DEFAULT_STRING_LOOKUPS_PROPERTY} system
    053 * property. If present, this system property will be parsed as a comma-separated list of lookup names, with the names being those defined by the
    054 * {@link DefaultStringLookup} enum. For example, setting this system property to {@code "BASE64_ENCODER,ENVIRONMENT"} will only include the
    055 * {@link DefaultStringLookup#BASE64_ENCODER BASE64_ENCODER} and {@link DefaultStringLookup#ENVIRONMENT ENVIRONMENT} lookups. Setting the property to the empty
    056 * string will cause no defaults to be configured. Note that not all lookups defined here and in {@link DefaultStringLookup} are included by default.
    057 * Specifically, lookups that can execute code (e.g., {@link DefaultStringLookup#SCRIPT SCRIPT}) and those that can result in contact with remote servers (e.g.,
    058 * {@link DefaultStringLookup#URL URL} and {@link DefaultStringLookup#DNS DNS}) are not included by default. The current set of default lookups can be accessed
    059 * directly with {@link #addDefaultStringLookups(Map)}.
    060 * </p>
    061 * <table>
    062 * <caption>Default String Lookups</caption>
    063 * <tr>
    064 * <th>Key</th>
    065 * <th>Interface</th>
    066 * <th>Factory Method</th>
    067 * <th>Since</th>
    068 * </tr>
    069 * <tr>
    070 * <td>{@value #KEY_BASE64_DECODER}</td>
    071 * <td>{@link StringLookup}</td>
    072 * <td>{@link #base64DecoderStringLookup()}</td>
    073 * <td>1.6</td>
    074 * </tr>
    075 * <tr>
    076 * <td>{@value #KEY_BASE64_ENCODER}</td>
    077 * <td>{@link StringLookup}</td>
    078 * <td>{@link #base64EncoderStringLookup()}</td>
    079 * <td>1.6</td>
    080 * </tr>
    081 * <tr>
    082 * <td>{@value #KEY_CONST}</td>
    083 * <td>{@link StringLookup}</td>
    084 * <td>{@link #constantStringLookup()}</td>
    085 * <td>1.5</td>
    086 * </tr>
    087 * <tr>
    088 * <td>{@value #KEY_DATE}</td>
    089 * <td>{@link StringLookup}</td>
    090 * <td>{@link #dateStringLookup()}</td>
    091 * <td>1.5</td>
    092 * </tr>
    093 * <tr>
    094 * <td>{@value #KEY_ENV}</td>
    095 * <td>{@link StringLookup}</td>
    096 * <td>{@link #environmentVariableStringLookup()}</td>
    097 * <td>1.3</td>
    098 * </tr>
    099 * <tr>
    100 * <td>{@value #KEY_FILE}</td>
    101 * <td>{@link StringLookup}</td>
    102 * <td>{@link #fileStringLookup(Path...)}</td>
    103 * <td>1.5</td>
    104 * </tr>
    105 * <tr>
    106 * <td>{@value #KEY_JAVA}</td>
    107 * <td>{@link StringLookup}</td>
    108 * <td>{@link #javaPlatformStringLookup()}</td>
    109 * <td>1.5</td>
    110 * </tr>
    111 * <tr>
    112 * <td>{@value #KEY_LOCALHOST}</td>
    113 * <td>{@link StringLookup}</td>
    114 * <td>{@link #localHostStringLookup()}</td>
    115 * <td>1.3</td>
    116 * </tr>
    117 * <tr>
    118 * <td>{@value #KEY_PROPERTIES}</td>
    119 * <td>{@link StringLookup}</td>
    120 * <td>{@link #propertiesStringLookup(Path...)}</td>
    121 * <td>1.5</td>
    122 * </tr>
    123 * <tr>
    124 * <td>{@value #KEY_RESOURCE_BUNDLE}</td>
    125 * <td>{@link StringLookup}</td>
    126 * <td>{@link #resourceBundleStringLookup()}</td>
    127 * <td>1.6</td>
    128 * </tr>
    129 * <tr>
    130 * <td>{@value #KEY_SYS}</td>
    131 * <td>{@link StringLookup}</td>
    132 * <td>{@link #systemPropertyStringLookup()}</td>
    133 * <td>1.3</td>
    134 * </tr>
    135 * <tr>
    136 * <td>{@value #KEY_URL_DECODER}</td>
    137 * <td>{@link StringLookup}</td>
    138 * <td>{@link #urlDecoderStringLookup()}</td>
    139 * <td>1.5</td>
    140 * </tr>
    141 * <tr>
    142 * <td>{@value #KEY_URL_ENCODER}</td>
    143 * <td>{@link StringLookup}</td>
    144 * <td>{@link #urlEncoderStringLookup()}</td>
    145 * <td>1.5</td>
    146 * </tr>
    147 * <tr>
    148 * <td>{@value #KEY_XML}</td>
    149 * <td>{@link StringLookup}</td>
    150 * <td>{@link #xmlStringLookup(Map, Path...)}</td>
    151 * <td>1.5</td>
    152 * </tr>
    153 * <tr>
    154 * <td>{@value #KEY_XML_DECODER}</td>
    155 * <td>{@link StringLookup}</td>
    156 * <td>{@link #xmlDecoderStringLookup()}</td>
    157 * <td>1.11.0</td>
    158 * </tr>
    159 * <tr>
    160 * <td>{@value #KEY_XML_ENCODER}</td>
    161 * <td>{@link StringLookup}</td>
    162 * <td>{@link #xmlEncoderStringLookup()}</td>
    163 * <td>1.11.0</td>
    164 * </tr>
    165 * </table>
    166 *
    167 * <table>
    168 * <caption>Additional String Lookups (not included by default)</caption>
    169 * <tr>
    170 * <th>Key</th>
    171 * <th>Interface</th>
    172 * <th>Factory Method</th>
    173 * <th>Since</th>
    174 * </tr>
    175 * <tr>
    176 * <td>{@value #KEY_DNS}</td>
    177 * <td>{@link StringLookup}</td>
    178 * <td>{@link #dnsStringLookup()}</td>
    179 * <td>1.8</td>
    180 * </tr>
    181 * <tr>
    182 * <td>{@value #KEY_URL}</td>
    183 * <td>{@link StringLookup}</td>
    184 * <td>{@link #urlStringLookup()}</td>
    185 * <td>1.5</td>
    186 * </tr>
    187 * <tr>
    188 * <td>{@value #KEY_SCRIPT}</td>
    189 * <td>{@link StringLookup}</td>
    190 * <td>{@link #scriptStringLookup()}</td>
    191 * <td>1.5</td>
    192 * </tr>
    193 * </table>
    194 *
    195 * <p>
    196 * This class also provides functional lookups used as building blocks for other lookups.
    197 * <table>
    198 * <caption>Functional String Lookups</caption>
    199 * <tr>
    200 * <th>Interface</th>
    201 * <th>Factory Method</th>
    202 * <th>Since</th>
    203 * </tr>
    204 * <tr>
    205 * <td>{@link BiStringLookup}</td>
    206 * <td>{@link #biFunctionStringLookup(BiFunction)}</td>
    207 * <td>1.9</td>
    208 * </tr>
    209 * <tr>
    210 * <td>{@link StringLookup}</td>
    211 * <td>{@link #functionStringLookup(Function)}</td>
    212 * <td>1.9</td>
    213 * </tr>
    214 * </table>
    215 *
    216 * @since 1.3
    217 */
    218public final class StringLookupFactory {
    219
    220    /**
    221     * Builds instance of {@link StringLookupFactory}.
    222     *
    223     * @since 1.12.0
    224     */
    225    public static final class Builder implements Supplier<StringLookupFactory> {
    226
    227        /**
    228         * Fences.
    229         */
    230        private Path[] fences;
    231
    232        @Override
    233        public StringLookupFactory get() {
    234            return new StringLookupFactory(fences);
    235        }
    236
    237        /**
    238         * Sets Path resolution fences.
    239         * <p>
    240         * Path Fences apply to the file, property, and XML string lookups.
    241         * </p>
    242         *
    243         * @param fences Path resolution fences.
    244         * @return this.
    245         */
    246        public Builder setFences(final Path... fences) {
    247            this.fences = fences;
    248            return this;
    249        }
    250
    251    }
    252
    253    /**
    254     * Internal class used to construct the default {@link StringLookup} map used by {@link StringLookupFactory#addDefaultStringLookups(Map)}.
    255     */
    256    static final class DefaultStringLookupsHolder {
    257
    258        /** Singleton instance, initialized with the system properties. */
    259        static final DefaultStringLookupsHolder INSTANCE = new DefaultStringLookupsHolder(System.getProperties());
    260
    261        /**
    262         * Adds the key and string lookup from {@code lookup} to {@code map}, also adding any additional key aliases if needed. Keys are normalized using the
    263         * {@link #toKey(String)} method.
    264         *
    265         * @param lookup lookup to add
    266         * @param map    map to add to
    267         */
    268        private static void addLookup(final DefaultStringLookup lookup, final Map<String, StringLookup> map) {
    269            map.put(toKey(lookup.getKey()), lookup.getStringLookup());
    270            if (DefaultStringLookup.BASE64_DECODER.equals(lookup)) {
    271                // "base64" is deprecated in favor of KEY_BASE64_DECODER.
    272                map.put(toKey("base64"), lookup.getStringLookup());
    273            }
    274        }
    275
    276        /**
    277         * Creates the lookup map used when the user has requested no customization.
    278         *
    279         * @return default lookup map
    280         */
    281        private static Map<String, StringLookup> createDefaultStringLookups() {
    282            final Map<String, StringLookup> lookupMap = new HashMap<>();
    283
    284            addLookup(DefaultStringLookup.BASE64_DECODER, lookupMap);
    285            addLookup(DefaultStringLookup.BASE64_ENCODER, lookupMap);
    286            addLookup(DefaultStringLookup.CONST, lookupMap);
    287            addLookup(DefaultStringLookup.DATE, lookupMap);
    288            addLookup(DefaultStringLookup.ENVIRONMENT, lookupMap);
    289            addLookup(DefaultStringLookup.FILE, lookupMap);
    290            addLookup(DefaultStringLookup.JAVA, lookupMap);
    291            addLookup(DefaultStringLookup.LOCAL_HOST, lookupMap);
    292            addLookup(DefaultStringLookup.PROPERTIES, lookupMap);
    293            addLookup(DefaultStringLookup.RESOURCE_BUNDLE, lookupMap);
    294            addLookup(DefaultStringLookup.SYSTEM_PROPERTIES, lookupMap);
    295            addLookup(DefaultStringLookup.URL_DECODER, lookupMap);
    296            addLookup(DefaultStringLookup.URL_ENCODER, lookupMap);
    297            addLookup(DefaultStringLookup.XML, lookupMap);
    298            addLookup(DefaultStringLookup.XML_DECODER, lookupMap);
    299            addLookup(DefaultStringLookup.XML_ENCODER, lookupMap);
    300
    301            return lookupMap;
    302        }
    303
    304        /**
    305         * Constructs a lookup map by parsing the given string. The string is expected to contain comma or space-separated names of values from the
    306         * {@link DefaultStringLookup} enum. If the given string is null or empty, an empty map is returned.
    307         *
    308         * @param str string to parse; may be null or empty
    309         * @return lookup map parsed from the given string
    310         */
    311        private static Map<String, StringLookup> parseStringLookups(final String str) {
    312            final Map<String, StringLookup> lookupMap = new HashMap<>();
    313            try {
    314                for (final String lookupName : str.split("[\\s,]+")) {
    315                    if (!lookupName.isEmpty()) {
    316                        addLookup(DefaultStringLookup.valueOf(lookupName.toUpperCase()), lookupMap);
    317                    }
    318                }
    319            } catch (final IllegalArgumentException exc) {
    320                throw new IllegalArgumentException("Invalid default string lookups definition: " + str, exc);
    321            }
    322            return lookupMap;
    323        }
    324
    325        /** Default string lookup map. */
    326        private final Map<String, StringLookup> defaultStringLookups;
    327
    328        /**
    329         * Constructs a new instance initialized with the given properties.
    330         *
    331         * @param props initialization properties
    332         */
    333        DefaultStringLookupsHolder(final Properties props) {
    334            final Map<String, StringLookup> lookups = props.containsKey(StringLookupFactory.DEFAULT_STRING_LOOKUPS_PROPERTY)
    335                    ? parseStringLookups(props.getProperty(StringLookupFactory.DEFAULT_STRING_LOOKUPS_PROPERTY))
    336                    : createDefaultStringLookups();
    337            defaultStringLookups = Collections.unmodifiableMap(lookups);
    338        }
    339
    340        /**
    341         * Gets the default string lookups map.
    342         *
    343         * @return default string lookups map
    344         */
    345        Map<String, StringLookup> getDefaultStringLookups() {
    346            return defaultStringLookups;
    347        }
    348    }
    349
    350    /**
    351     * Name of the system property used to determine the string lookups added by the {@link #addDefaultStringLookups(Map)} method. Use of this property is only
    352     * required in cases where the set of default lookups must be modified. (See the class documentation for details.)
    353     *
    354     * @since 1.10.0
    355     */
    356    public static final String DEFAULT_STRING_LOOKUPS_PROPERTY = "org.apache.commons.text.lookup.StringLookupFactory.defaultStringLookups";
    357
    358    /**
    359     * Defines the singleton for this class.
    360     */
    361    public static final StringLookupFactory INSTANCE = new StringLookupFactory();
    362
    363    /**
    364     * Decodes Base64 Strings.
    365     * <p>
    366     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    367     * </p>
    368     *
    369     * <pre>
    370     * StringLookupFactory.INSTANCE.base64DecoderStringLookup().lookup("SGVsbG9Xb3JsZCE=");
    371     * </pre>
    372     * <p>
    373     * Using a {@link StringSubstitutor}:
    374     * </p>
    375     *
    376     * <pre>
    377     * StringSubstitutor.createInterpolator().replace("... ${base64Decoder:SGVsbG9Xb3JsZCE=} ..."));
    378     * </pre>
    379     * <p>
    380     * The above examples convert {@code "SGVsbG9Xb3JsZCE="} to {@code "HelloWorld!"}.
    381     * </p>
    382     */
    383    static final FunctionStringLookup<String> INSTANCE_BASE64_DECODER = FunctionStringLookup
    384            .on(key -> new String(Base64.getDecoder().decode(key), StandardCharsets.ISO_8859_1));
    385
    386    /**
    387     * Encodes Base64 Strings.
    388     * <p>
    389     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    390     * </p>
    391     *
    392     * <pre>
    393     * StringLookupFactory.INSTANCE.base64EncoderStringLookup().lookup("HelloWorld!");
    394     * </pre>
    395     * <p>
    396     * Using a {@link StringSubstitutor}:
    397     * </p>
    398     *
    399     * <pre>
    400     * StringSubstitutor.createInterpolator().replace("... ${base64Encoder:HelloWorld!} ..."));
    401     * </pre>
    402     * <p>
    403     * The above examples convert {@code "HelloWorld!"} to {@code "SGVsbG9Xb3JsZCE="}.
    404     * </p>
    405     * Defines the singleton for this class.
    406     */
    407    static final FunctionStringLookup<String> INSTANCE_BASE64_ENCODER = FunctionStringLookup
    408            .on(key -> Base64.getEncoder().encodeToString(key.getBytes(StandardCharsets.ISO_8859_1)));
    409
    410    /**
    411     * Looks up keys from environment variables.
    412     * <p>
    413     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    414     * </p>
    415     *
    416     * <pre>
    417     * StringLookupFactory.INSTANCE.environmentVariableStringLookup().lookup("USER");
    418     * </pre>
    419     * <p>
    420     * Using a {@link StringSubstitutor}:
    421     * </p>
    422     *
    423     * <pre>
    424     * StringSubstitutor.createInterpolator().replace("... ${env:USER} ..."));
    425     * </pre>
    426     * <p>
    427     * The above examples convert (on Linux) {@code "USER"} to the current user name. On Windows 10, you would use {@code "USERNAME"} to the same effect.
    428     * </p>
    429     */
    430    static final FunctionStringLookup<String> INSTANCE_ENVIRONMENT_VARIABLES = FunctionStringLookup.on(System::getenv);
    431
    432    /**
    433     * Defines the FunctionStringLookup singleton that always returns null.
    434     */
    435    static final FunctionStringLookup<String> INSTANCE_NULL = FunctionStringLookup.on(key -> null);
    436
    437    /**
    438     * Defines the FunctionStringLookup singleton for looking up system properties.
    439     */
    440    static final FunctionStringLookup<String> INSTANCE_SYSTEM_PROPERTIES = FunctionStringLookup.on(System::getProperty);
    441
    442    /**
    443     * Default lookup key for interpolation {@value #KEY_BASE64_DECODER}.
    444     *
    445     * @since 1.6
    446     */
    447    public static final String KEY_BASE64_DECODER = "base64Decoder";
    448
    449    /**
    450     * Default lookup key for interpolation {@value #KEY_BASE64_ENCODER}.
    451     *
    452     * @since 1.6
    453     */
    454    public static final String KEY_BASE64_ENCODER = "base64Encoder";
    455
    456    /**
    457     * Default lookup key for interpolation {@value #KEY_CONST}.
    458     *
    459     * @since 1.6
    460     */
    461    public static final String KEY_CONST = "const";
    462
    463    /**
    464     * Default lookup key for interpolation {@value #KEY_DATE}.
    465     *
    466     * @since 1.6
    467     */
    468    public static final String KEY_DATE = "date";
    469
    470    /**
    471     * Default lookup key for interpolation {@value #KEY_DNS}.
    472     *
    473     * @since 1.8
    474     */
    475    public static final String KEY_DNS = "dns";
    476
    477    /**
    478     * Default lookup key for interpolation {@value #KEY_ENV}.
    479     *
    480     * @since 1.6
    481     */
    482    public static final String KEY_ENV = "env";
    483
    484    /**
    485     * Default lookup key for interpolation {@value #KEY_FILE}.
    486     *
    487     * @since 1.6
    488     */
    489    public static final String KEY_FILE = "file";
    490
    491    /**
    492     * Default lookup key for interpolation {@value #KEY_JAVA}.
    493     *
    494     * @since 1.6
    495     */
    496    public static final String KEY_JAVA = "java";
    497
    498    /**
    499     * Default lookup key for interpolation {@value #KEY_LOCALHOST}.
    500     *
    501     * @since 1.6
    502     */
    503    public static final String KEY_LOCALHOST = "localhost";
    504
    505    /**
    506     * Default lookup key for interpolation {@value #KEY_PROPERTIES}.
    507     *
    508     * @since 1.6
    509     */
    510    public static final String KEY_PROPERTIES = "properties";
    511
    512    /**
    513     * Default lookup key for interpolation {@value #KEY_RESOURCE_BUNDLE}.
    514     *
    515     * @since 1.6
    516     */
    517    public static final String KEY_RESOURCE_BUNDLE = "resourceBundle";
    518
    519    /**
    520     * Default lookup key for interpolation {@value #KEY_SCRIPT}.
    521     *
    522     * @since 1.6
    523     */
    524    public static final String KEY_SCRIPT = "script";
    525
    526    /**
    527     * Default lookup key for interpolation {@value #KEY_SYS}.
    528     *
    529     * @since 1.6
    530     */
    531    public static final String KEY_SYS = "sys";
    532
    533    /**
    534     * Default lookup key for interpolation {@value #KEY_URL}.
    535     *
    536     * @since 1.6
    537     */
    538    public static final String KEY_URL = "url";
    539
    540    /**
    541     * Default lookup key for interpolation {@value #KEY_URL_DECODER}.
    542     *
    543     * @since 1.6
    544     */
    545    public static final String KEY_URL_DECODER = "urlDecoder";
    546
    547    /**
    548     * Default lookup key for interpolation {@value #KEY_URL_ENCODER}.
    549     *
    550     * @since 1.6
    551     */
    552    public static final String KEY_URL_ENCODER = "urlEncoder";
    553
    554    /**
    555     * Default lookup key for interpolation {@value #KEY_XML}.
    556     *
    557     * @since 1.6
    558     */
    559    public static final String KEY_XML = "xml";
    560
    561    /**
    562     * Default lookup key for interpolation {@value #KEY_XML_DECODER}.
    563     *
    564     * @since 1.11.0
    565     */
    566    public static final String KEY_XML_DECODER = "xmlDecoder";
    567
    568    /**
    569     * Default lookup key for interpolation {@value #KEY_XML_ENCODER}.
    570     *
    571     * @since 1.11.0
    572     */
    573    public static final String KEY_XML_ENCODER = "xmlEncoder";
    574
    575    /**
    576     * Constructs a new {@link Builder}.
    577     *
    578     * @return a new {@link Builder}
    579     * @since 1.12.0
    580     */
    581    public static Builder builder() {
    582        return new Builder();
    583    }
    584
    585    /**
    586     * Clears any static resources.
    587     *
    588     * @since 1.5
    589     */
    590    public static void clear() {
    591        ConstantStringLookup.clear();
    592    }
    593
    594    /**
    595     * Gets a string suitable for use as a key in the string lookup map.
    596     *
    597     * @param key string to convert to a string lookup map key
    598     * @return string lookup map key
    599     */
    600    static String toKey(final String key) {
    601        return key.toLowerCase(Locale.ROOT);
    602    }
    603
    604    /**
    605     * Returns the given map if the input is non-null or an empty immutable map if the input is null.
    606     *
    607     * @param <K> the class of the map keys
    608     * @param <V> the class of the map values
    609     * @param map The map to test
    610     * @return the given map if the input is non-null or an empty immutable map if the input is null.
    611     */
    612    static <K, V> Map<K, V> toMap(final Map<K, V> map) {
    613        return map == null ? Collections.emptyMap() : map;
    614    }
    615
    616    /**
    617     * Fences.
    618     */
    619    private final Path[] fences;
    620
    621    /**
    622     * Constructs a new instance.
    623     */
    624    private StringLookupFactory() {
    625        this(null);
    626    }
    627
    628    /**
    629     * Constructs a new instance.
    630     */
    631    private StringLookupFactory(final Path[] fences) {
    632        this.fences = fences;
    633    }
    634
    635    /**
    636     * Adds the default string lookups for this class to {@code stringLookupMap}. The default string lookups are a set of built-in lookups added for convenience
    637     * during string interpolation. The defaults may be configured using the {@value #DEFAULT_STRING_LOOKUPS_PROPERTY} system property. See the class
    638     * documentation for details and a list of lookups.
    639     *
    640     * @param stringLookupMap the map of string lookups to edit.
    641     * @since 1.5
    642     */
    643    public void addDefaultStringLookups(final Map<String, StringLookup> stringLookupMap) {
    644        if (stringLookupMap != null) {
    645            stringLookupMap.putAll(DefaultStringLookupsHolder.INSTANCE.getDefaultStringLookups());
    646        }
    647    }
    648
    649    /**
    650     * Returns the Base64DecoderStringLookup singleton instance to decode Base64 strings.
    651     * <p>
    652     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    653     * </p>
    654     *
    655     * <pre>
    656     * StringLookupFactory.INSTANCE.base64DecoderStringLookup().lookup("SGVsbG9Xb3JsZCE=");
    657     * </pre>
    658     * <p>
    659     * Using a {@link StringSubstitutor}:
    660     * </p>
    661     *
    662     * <pre>
    663     * StringSubstitutor.createInterpolator().replace("... ${base64Decoder:SGVsbG9Xb3JsZCE=} ..."));
    664     * </pre>
    665     * <p>
    666     * The above examples convert {@code "SGVsbG9Xb3JsZCE="} to {@code "HelloWorld!"}.
    667     * </p>
    668     *
    669     * @return The Base64DecoderStringLookup singleton instance.
    670     * @since 1.5
    671     */
    672    public StringLookup base64DecoderStringLookup() {
    673        return StringLookupFactory.INSTANCE_BASE64_DECODER;
    674    }
    675
    676    /**
    677     * Returns the Base64EncoderStringLookup singleton instance to encode strings to Base64.
    678     * <p>
    679     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    680     * </p>
    681     *
    682     * <pre>
    683     * StringLookupFactory.INSTANCE.base64EncoderStringLookup().lookup("HelloWorld!");
    684     * </pre>
    685     * <p>
    686     * Using a {@link StringSubstitutor}:
    687     * </p>
    688     *
    689     * <pre>
    690     * StringSubstitutor.createInterpolator().replace("... ${base64Encoder:HelloWorld!} ..."));
    691     * </pre>
    692     * <p>
    693     * The above examples convert {@code } to {@code "SGVsbG9Xb3JsZCE="}.
    694     * </p>
    695     *
    696     * @return The Base64EncoderStringLookup singleton instance.
    697     * @since 1.6
    698     */
    699    public StringLookup base64EncoderStringLookup() {
    700        return StringLookupFactory.INSTANCE_BASE64_ENCODER;
    701    }
    702
    703    /**
    704     * Returns the Base64DecoderStringLookup singleton instance to decode Base64 strings.
    705     * <p>
    706     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    707     * </p>
    708     *
    709     * <pre>
    710     * StringLookupFactory.INSTANCE.base64DecoderStringLookup().lookup("SGVsbG9Xb3JsZCE=");
    711     * </pre>
    712     * <p>
    713     * Using a {@link StringSubstitutor}:
    714     * </p>
    715     *
    716     * <pre>
    717     * StringSubstitutor.createInterpolator().replace("... ${base64Decoder:SGVsbG9Xb3JsZCE=} ..."));
    718     * </pre>
    719     * <p>
    720     * The above examples convert {@code "SGVsbG9Xb3JsZCE="} to {@code "HelloWorld!"}.
    721     * </p>
    722     *
    723     * @return The Base64DecoderStringLookup singleton instance.
    724     * @since 1.5
    725     * @deprecated Use {@link #base64DecoderStringLookup()}.
    726     */
    727    @Deprecated
    728    public StringLookup base64StringLookup() {
    729        return StringLookupFactory.INSTANCE_BASE64_DECODER;
    730    }
    731
    732    /**
    733     * Returns a new function-based lookup where the request for a lookup is answered by applying the function with a lookup key.
    734     *
    735     * @param <R>        the function return type.
    736     * @param <U>        the function's second parameter type.
    737     * @param biFunction the function.
    738     * @return a new MapStringLookup.
    739     * @since 1.9
    740     */
    741    public <R, U> BiStringLookup<U> biFunctionStringLookup(final BiFunction<String, U, R> biFunction) {
    742        return BiFunctionStringLookup.on(biFunction);
    743    }
    744
    745    /**
    746     * Returns the ConstantStringLookup singleton instance to look up the value of a fully-qualified static final value.
    747     * <p>
    748     * Sometimes it is necessary in a configuration file to refer to a constant defined in a class. This can be done with this lookup implementation. Variable
    749     * names must be in the format {@code apackage.AClass.AFIELD}. The {@code lookup(String)} method will split the passed in string at the last dot, separating
    750     * the fully qualified class name and the name of the constant (i.e. <b>static final</b>) member field. Then the class is loaded and the field's value is
    751     * obtained using reflection.
    752     * </p>
    753     * <p>
    754     * Once retrieved values are cached for fast access. This class is thread-safe. It can be used as a standard (i.e. global) lookup object and serve multiple
    755     * clients concurrently.
    756     * </p>
    757     * <p>
    758     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    759     * </p>
    760     *
    761     * <pre>
    762     * StringLookupFactory.INSTANCE.constantStringLookup().lookup("java.awt.event.KeyEvent.VK_ESCAPE");
    763     * </pre>
    764     * <p>
    765     * Using a {@link StringSubstitutor}:
    766     * </p>
    767     *
    768     * <pre>
    769     * StringSubstitutor.createInterpolator().replace("... ${const:java.awt.event.KeyEvent.VK_ESCAPE} ..."));
    770     * </pre>
    771     * <p>
    772     * The above examples convert {@code java.awt.event.KeyEvent.VK_ESCAPE} to {@code "27"}.
    773     * </p>
    774     *
    775     * @return The ConstantStringLookup singleton instance.
    776     * @since 1.5
    777     */
    778    public StringLookup constantStringLookup() {
    779        return ConstantStringLookup.INSTANCE;
    780    }
    781
    782    /**
    783     * Returns the DateStringLookup singleton instance to format the current date with the format given in the key in a format compatible with
    784     * {@link java.text.SimpleDateFormat}.
    785     * <p>
    786     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    787     * </p>
    788     *
    789     * <pre>
    790     * StringLookupFactory.INSTANCE.dateStringLookup().lookup("yyyy-MM-dd");
    791     * </pre>
    792     * <p>
    793     * Using a {@link StringSubstitutor}:
    794     * </p>
    795     *
    796     * <pre>
    797     * StringSubstitutor.createInterpolator().replace("... ${date:yyyy-MM-dd} ..."));
    798     * </pre>
    799     * <p>
    800     * The above examples convert {@code "yyyy-MM-dd"} to todays's date, for example, {@code "2019-08-04"}.
    801     * </p>
    802     *
    803     * @return The DateStringLookup singleton instance.
    804     */
    805    public StringLookup dateStringLookup() {
    806        return DateStringLookup.INSTANCE;
    807    }
    808
    809    /**
    810     * Returns the DnsStringLookup singleton instance where the lookup key is one of:
    811     * <ul>
    812     * <li><b>name</b>: for the local host name, for example {@code EXAMPLE} but also {@code EXAMPLE.apache.org}.</li>
    813     * <li><b>canonical-name</b>: for the local canonical host name, for example {@code EXAMPLE.apache.org}.</li>
    814     * <li><b>address</b>: for the local host address, for example {@code 192.168.56.1}.</li>
    815     * </ul>
    816     *
    817     * <p>
    818     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    819     * </p>
    820     *
    821     * <pre>
    822     * StringLookupFactory.INSTANCE.dnsStringLookup().lookup("address|apache.org");
    823     * </pre>
    824     * <p>
    825     * When used through a {@link StringSubstitutor}, this lookup must either be added programmatically (as below) or enabled as a default lookup using the
    826     * {@value #DEFAULT_STRING_LOOKUPS_PROPERTY} system property (see class documentation).
    827     * </p>
    828     *
    829     * <pre>
    830     * Map&lt;String, StringLookup&gt; lookupMap = new HashMap&lt;&gt;();
    831     * lookupMap.put("dns", StringLookupFactory.INSTANCE.dnsStringLookup());
    832     *
    833     * StringLookup variableResolver = StringLookupFactory.INSTANCE.interpolatorStringLookup(lookupMap, null, false);
    834     *
    835     * new StringSubstitutor(variableResolver).replace("... ${dns:address|apache.org} ...");
    836     * </pre>
    837     * <p>
    838     * The above examples convert {@code "address|apache.org"} to the IP address of {@code apache.org}.
    839     * </p>
    840     *
    841     * @return the DnsStringLookup singleton instance.
    842     * @since 1.8
    843     */
    844    public StringLookup dnsStringLookup() {
    845        return DnsStringLookup.INSTANCE;
    846    }
    847
    848    /**
    849     * Returns the EnvironmentVariableStringLookup singleton instance where the lookup key is an environment variable name.
    850     * <p>
    851     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    852     * </p>
    853     *
    854     * <pre>
    855     * StringLookupFactory.INSTANCE.environmentVariableStringLookup().lookup("USER");
    856     * </pre>
    857     * <p>
    858     * Using a {@link StringSubstitutor}:
    859     * </p>
    860     *
    861     * <pre>
    862     * StringSubstitutor.createInterpolator().replace("... ${env:USER} ..."));
    863     * </pre>
    864     * <p>
    865     * The above examples convert (on Linux) {@code "USER"} to the current user name. On Windows 10, you would use {@code "USERNAME"} to the same effect.
    866     * </p>
    867     *
    868     * @return The EnvironmentVariableStringLookup singleton instance.
    869     */
    870    public StringLookup environmentVariableStringLookup() {
    871        return StringLookupFactory.INSTANCE_ENVIRONMENT_VARIABLES;
    872    }
    873
    874    /**
    875     * Returns a file StringLookup instance.
    876     * <p>
    877     * If this factory was built using {@link Builder#setFences(Path...)}, then the string lookup is fenced and will throw an {@link IllegalArgumentException}
    878     * if a lookup causes causes a path to resolve outside of these fences. Otherwise, the result is unfenced to preserved behavior from previous versions.
    879     * </p>
    880     * <em>Using a fenced StringLookup</em>
    881     * <p>
    882     * To use a fenced {@link StringLookup}, use {@link StringLookupFactory#builder()}:
    883     * </p>
    884     *
    885     * <pre>
    886     * // Make the fence the current directory
    887     * StringLookupFactory factory = StringLookupFactory.builder().setFences(Paths.get("")).get();
    888     * factory.fileStringLookup().lookup("UTF-8:com/domain/document.txt");
    889     *
    890     * // throws IllegalArgumentException
    891     * factory.fileStringLookup().lookup("UTF-8:/rootdir/foo/document.txt");
    892     *
    893     * // throws IllegalArgumentException
    894     * factory.fileStringLookup().lookup("UTF-8:../document.txt");
    895     * </pre>
    896     *
    897     * <em>Using an unfenced StringLookup</em>
    898     * <p>
    899     * To use an unfenced {@link StringLookup}, use {@link StringLookupFactory#INSTANCE}:
    900     * </p>
    901     *
    902     * <pre>
    903     * StringLookupFactory.INSTANCE.fileStringLookup().lookup("UTF-8:com/domain/document.properties");
    904     * </pre>
    905     *
    906     * <em>Using a StringLookup with StringSubstitutor</em>
    907     * <p>
    908     * To build a fenced StringSubstitutor, use:
    909     * </p>
    910     *
    911     * <pre>
    912     * // Make the fence the current directory
    913     * final StringLookupFactory factory = StringLookupFactory.builder().setFences(Paths.get("")).get();
    914     * final StringSubstitutor stringSubstitutor = new StringSubstitutor(factory.interpolatorStringLookup());
    915     * stringSubstitutor.replace("... ${file:UTF-8:com/domain/document.txt} ..."));
    916     *
    917     * // throws IllegalArgumentException
    918     * stringSubstitutor.replace("... ${file:UTF-8:/rootdir/foo/document.txt} ..."));
    919     * </pre>
    920     * <p>
    921     * Using an unfenced {@link StringSubstitutor}:
    922     * </p>
    923     *
    924     * <pre>
    925     * StringSubstitutor.createInterpolator().replace("... ${file:UTF-8:com/domain/document.txt} ..."));
    926     * </pre>
    927     * <p>
    928     * The above examples convert {@code "UTF-8:com/domain/document.txt"} to the contents of the file.
    929     * </p>
    930     *
    931     * @return a file StringLookup instance.
    932     * @since 1.5
    933     */
    934    public StringLookup fileStringLookup() {
    935        return fences != null ? fileStringLookup(fences) : FileStringLookup.INSTANCE;
    936    }
    937
    938    /**
    939     * Returns a fenced file StringLookup instance.
    940     * <p>
    941     * To use a {@link StringLookup} fenced by the current directory, use:
    942     * </p>
    943     *
    944     * <pre>
    945     * StringLookupFactory.INSTANCE.fileStringLookup(Paths.get("")).lookup("UTF-8:com/domain/document.txt");
    946     *
    947     * // throws IllegalArgumentException
    948     * StringLookupFactory.INSTANCE.fileStringLookup(Paths.get("")).lookup("UTF-8:/rootdir/foo/document.txt");
    949     *
    950     * // throws IllegalArgumentException
    951     * StringLookupFactory.INSTANCE.fileStringLookup(Paths.get("")).lookup("UTF-8:../com/domain/document.txt");
    952     * </pre>
    953     * <p>
    954     * The above example converts {@code "UTF-8:com/domain/document.txt"} to the contents of the file.
    955     * </p>
    956     * <p>
    957     * {@link StringSubstitutor} methods like {@link StringSubstitutor#replace(String)} will throw a {@link IllegalArgumentException} when a file doesn't
    958     * resolves in a fence.
    959     * </p>
    960     *
    961     * @param fences The fences guarding Path resolution.
    962     * @return a file StringLookup instance.
    963     * @since 1.12.0
    964     */
    965    public StringLookup fileStringLookup(final Path... fences) {
    966        return new FileStringLookup(fences);
    967    }
    968
    969    /**
    970     * Returns a new function-based lookup where the request for a lookup is answered by applying the function with a lookup key.
    971     *
    972     * @param <R>      the function return type.
    973     * @param function the function.
    974     * @return a new MapStringLookup.
    975     * @since 1.9
    976     */
    977    public <R> StringLookup functionStringLookup(final Function<String, R> function) {
    978        return FunctionStringLookup.on(function);
    979    }
    980
    981    /**
    982     * Returns a {@link InterpolatorStringLookup} containing the configured {@link #addDefaultStringLookups(Map) default lookups}. See the class documentation
    983     * for details on how these defaults are configured.
    984     * <p>
    985     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    986     * </p>
    987     *
    988     * <pre>
    989     * StringLookupFactory.INSTANCE.interpolatorStringLookup().lookup("${sys:os.name}, ${env:USER}");
    990     * </pre>
    991     * <p>
    992     * Using a {@link StringSubstitutor}:
    993     * </p>
    994     *
    995     * <pre>
    996     * StringSubstitutor.createInterpolator().replace("... ${sys:os.name}, ${env:USER} ..."));
    997     * </pre>
    998     * <p>
    999     * The above examples convert {@code "${sys:os.name}, ${env:USER}"} to the OS name and Linux user name.
    1000     * </p>
    1001     *
    1002     * @return the default {@link InterpolatorStringLookup}.
    1003     */
    1004    public StringLookup interpolatorStringLookup() {
    1005        return InterpolatorStringLookup.INSTANCE;
    1006    }
    1007
    1008    /**
    1009     * Returns a new InterpolatorStringLookup. If {@code addDefaultLookups} is {@code true}, the configured {@link #addDefaultStringLookups(Map) default
    1010     * lookups} are included in addition to the ones provided in {@code stringLookupMap}. (See the class documentation for details on how default lookups are
    1011     * configured.)
    1012     *
    1013     * @param stringLookupMap     the map of string lookups.
    1014     * @param defaultStringLookup the default string lookup; this lookup is used when a variable cannot be resolved using the lookups in {@code stringLookupMap}
    1015     *                            or the configured default lookups (if enabled)
    1016     * @param addDefaultLookups   whether to use default lookups as described above.
    1017     * @return a new InterpolatorStringLookup.
    1018     * @since 1.4
    1019     */
    1020    public StringLookup interpolatorStringLookup(final Map<String, StringLookup> stringLookupMap, final StringLookup defaultStringLookup,
    1021            final boolean addDefaultLookups) {
    1022        return new InterpolatorStringLookup(stringLookupMap, defaultStringLookup, addDefaultLookups);
    1023    }
    1024
    1025    /**
    1026     * Returns a new InterpolatorStringLookup using the given key-value pairs and the configured {@link #addDefaultStringLookups(Map) default lookups} to
    1027     * resolve variables. (See the class documentation for details on how default lookups are configured.)
    1028     *
    1029     * @param <V> the value type the default string lookup's map.
    1030     * @param map the default map for string lookups.
    1031     * @return a new InterpolatorStringLookup.
    1032     */
    1033    public <V> StringLookup interpolatorStringLookup(final Map<String, V> map) {
    1034        return new InterpolatorStringLookup(map);
    1035    }
    1036
    1037    /**
    1038     * Returns a new InterpolatorStringLookup using the given lookup and the configured {@link #addDefaultStringLookups(Map) default lookups} to resolve
    1039     * variables. (See the class documentation for details on how default lookups are configured.)
    1040     *
    1041     * @param defaultStringLookup the default string lookup.
    1042     * @return a new InterpolatorStringLookup.
    1043     */
    1044    public StringLookup interpolatorStringLookup(final StringLookup defaultStringLookup) {
    1045        return new InterpolatorStringLookup(defaultStringLookup);
    1046    }
    1047
    1048    /**
    1049     * Returns the JavaPlatformStringLookup singleton instance. Looks up keys related to Java: Java version, JRE version, VM version, and so on.
    1050     * <p>
    1051     * The lookup keys with examples are:
    1052     * </p>
    1053     * <ul>
    1054     * <li><b>version</b>: "Java version 1.8.0_181"</li>
    1055     * <li><b>runtime</b>: "Java(TM) SE Runtime Environment (build 1.8.0_181-b13) from Oracle Corporation"</li>
    1056     * <li><b>vm</b>: "Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)"</li>
    1057     * <li><b>os</b>: "Windows 10 10.0, architecture: amd64-64"</li>
    1058     * <li><b>hardware</b>: "processors: 4, architecture: amd64-64, instruction sets: amd64"</li>
    1059     * <li><b>locale</b>: "default locale: en_US, platform encoding: iso-8859-1"</li>
    1060     * </ul>
    1061     *
    1062     * <p>
    1063     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    1064     * </p>
    1065     *
    1066     * <pre>
    1067     * StringLookupFactory.INSTANCE.javaPlatformStringLookup().lookup("version");
    1068     * </pre>
    1069     * <p>
    1070     * Using a {@link StringSubstitutor}:
    1071     * </p>
    1072     *
    1073     * <pre>
    1074     * StringSubstitutor.createInterpolator().replace("... ${java:version} ..."));
    1075     * </pre>
    1076     * <p>
    1077     * The above examples convert {@code "version"} to the current VM version, for example, {@code "Java version 1.8.0_181"}.
    1078     * </p>
    1079     *
    1080     * @return The JavaPlatformStringLookup singleton instance.
    1081     */
    1082    public StringLookup javaPlatformStringLookup() {
    1083        return JavaPlatformStringLookup.INSTANCE;
    1084    }
    1085
    1086    /**
    1087     * Returns the LocalHostStringLookup singleton instance where the lookup key is one of:
    1088     * <ul>
    1089     * <li><b>name</b>: for the local host name, for example {@code EXAMPLE}.</li>
    1090     * <li><b>canonical-name</b>: for the local canonical host name, for example {@code EXAMPLE.apache.org}.</li>
    1091     * <li><b>address</b>: for the local host address, for example {@code 192.168.56.1}.</li>
    1092     * </ul>
    1093     *
    1094     * <p>
    1095     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    1096     * </p>
    1097     *
    1098     * <pre>
    1099     * StringLookupFactory.INSTANCE.localHostStringLookup().lookup("canonical-name");
    1100     * </pre>
    1101     * <p>
    1102     * Using a {@link StringSubstitutor}:
    1103     * </p>
    1104     *
    1105     * <pre>
    1106     * StringSubstitutor.createInterpolator().replace("... ${localhost:canonical-name} ..."));
    1107     * </pre>
    1108     * <p>
    1109     * The above examples convert {@code "canonical-name"} to the current host name, for example, {@code "EXAMPLE.apache.org"}.
    1110     * </p>
    1111     *
    1112     * @return The DateStringLookup singleton instance.
    1113     */
    1114    public StringLookup localHostStringLookup() {
    1115        return LocalHostStringLookup.INSTANCE;
    1116    }
    1117
    1118    /**
    1119     * Returns a new map-based lookup where the request for a lookup is answered with the value for that key.
    1120     *
    1121     * @param <V> the map value type.
    1122     * @param map the map.
    1123     * @return a new MapStringLookup.
    1124     */
    1125    public <V> StringLookup mapStringLookup(final Map<String, V> map) {
    1126        return FunctionStringLookup.on(map);
    1127    }
    1128
    1129    /**
    1130     * Returns the NullStringLookup singleton instance which always returns null.
    1131     *
    1132     * @return The NullStringLookup singleton instance.
    1133     */
    1134    public StringLookup nullStringLookup() {
    1135        return StringLookupFactory.INSTANCE_NULL;
    1136    }
    1137
    1138    /**
    1139     * Returns a Properties StringLookup instance.
    1140     * <p>
    1141     * If this factory was built using {@link Builder#setFences(Path...)}, then the string lookup is fenced and will throw an {@link IllegalArgumentException}
    1142     * if a lookup causes causes a path to resolve outside of these fences. Otherwise, the result is unfenced to preserved behavior from previous versions.
    1143     * </p>
    1144     * <p>
    1145     * We looks up a value for the key in the format "DocumentPath::MyKey".
    1146     * </p>
    1147     * <p>
    1148     * Note the use of "::" instead of ":" to allow for "C:" drive letters in paths.
    1149     * </p>
    1150     * <p>
    1151     * For example: "com/domain/document.properties::MyKey".
    1152     * </p>
    1153     * <em>Using a fenced StringLookup</em>
    1154     * <p>
    1155     * To use a fenced {@link StringLookup}, use {@link StringLookupFactory#builder()}:
    1156     * </p>
    1157     *
    1158     * <pre>
    1159     * // Make the fence the current directory
    1160     * StringLookupFactory factory = StringLookupFactory.builder().setFences(Paths.get("")).get();
    1161     * factory.propertiesStringLookup().lookup("com/domain/document.properties::MyKey");
    1162     *
    1163     * // throws IllegalArgumentException
    1164     * factory.propertiesStringLookup().lookup("/com/domain/document.properties::MyKey");
    1165     *
    1166     * // throws IllegalArgumentException
    1167     * factory.propertiesStringLookup().lookup("../com/domain/document.properties::MyKey");
    1168     * </pre>
    1169     *
    1170     * <em>Using an unfenced StringLookup</em>
    1171     * <p>
    1172     * To use an unfenced {@link StringLookup}, use {@link StringLookupFactory#INSTANCE}:
    1173     * </p>
    1174     *
    1175     * <pre>
    1176     * StringLookupFactory.INSTANCE.propertiesStringLookup().lookup("com/domain/document.properties::MyKey");
    1177     * </pre>
    1178     *
    1179     * <em>Using a StringLookup with StringSubstitutor</em>
    1180     * <p>
    1181     * To build a fenced StringSubstitutor, use:
    1182     * </p>
    1183     *
    1184     * <pre>
    1185     * // Make the fence the current directory
    1186     * final StringLookupFactory factory = StringLookupFactory.builder().setFences(Paths.get("")).get();
    1187     * final StringSubstitutor stringSubstitutor = new StringSubstitutor(factory.interpolatorStringLookup());
    1188     * stringSubstitutor.replace("... ${properties:com/domain/document.properties::MyKey} ..."));
    1189     *
    1190     * // throws IllegalArgumentException
    1191     * stringSubstitutor.replace("... ${properties:/rootdir/foo/document.properties::MyKey} ..."));
    1192     * </pre>
    1193     * <p>
    1194     * Using an unfenced {@link StringSubstitutor}:
    1195     * </p>
    1196     *
    1197     * <pre>
    1198     * StringSubstitutor.createInterpolator().replace("... ${properties:com/domain/document.properties::MyKey} ..."));
    1199     * </pre>
    1200     * <p>
    1201     * The above examples convert {@code "com/domain/document.properties::MyKey"} to the key value in the properties file at the path
    1202     * "com/domain/document.properties".
    1203     * </p>
    1204     *
    1205     * @return a Properties StringLookup instance.
    1206     * @since 1.5
    1207     */
    1208    public StringLookup propertiesStringLookup() {
    1209        return fences != null ? propertiesStringLookup(fences) : PropertiesStringLookup.INSTANCE;
    1210    }
    1211
    1212    /**
    1213     * Returns a fenced Properties StringLookup instance.
    1214     * <p>
    1215     * Looks up the value for the key in the format "DocumentPath::MyKey":.
    1216     * </p>
    1217     * <p>
    1218     * Note the use of "::" instead of ":" to allow for "C:" drive letters in paths.
    1219     * </p>
    1220     * <p>
    1221     * For example: "com/domain/document.properties::MyKey".
    1222     * </p>
    1223     * <p>
    1224     * To use a {@link StringLookup} fenced by the current directory, use:
    1225     * </p>
    1226     *
    1227     * <pre>
    1228     * StringLookupFactory.INSTANCE.fileStringLookup(Paths.get("")).lookup("com/domain/document.properties::MyKey");
    1229     *
    1230     * // throws IllegalArgumentException
    1231     * StringLookupFactory.INSTANCE.fileStringLookup(Paths.get("")).lookup("com/domain/document.properties::MyKey");
    1232     *
    1233     * // throws IllegalArgumentException
    1234     * StringLookupFactory.INSTANCE.fileStringLookup(Paths.get("")).lookup("com/domain/document.properties::MyKey");
    1235     * </pre>
    1236     * <p>
    1237     * The above example converts {@code "com/domain/document.properties::MyKey"} to the key value in the properties file at the path
    1238     * "com/domain/document.properties".
    1239     * </p>
    1240     * <p>
    1241     * {@link StringSubstitutor} methods like {@link StringSubstitutor#replace(String)} will throw a {@link IllegalArgumentException} when a file doesn't
    1242     * resolves in a fence.
    1243     * </p>
    1244     *
    1245     * @param fences The fences guarding Path resolution.
    1246     * @return a Properties StringLookup instance.
    1247     * @since 1.12.0
    1248     */
    1249    public StringLookup propertiesStringLookup(final Path... fences) {
    1250        return new PropertiesStringLookup(fences);
    1251    }
    1252
    1253    /**
    1254     * Returns the ResourceBundleStringLookup singleton instance.
    1255     * <p>
    1256     * Looks up the value for a given key in the format "BundleName:BundleKey".
    1257     * </p>
    1258     * <p>
    1259     * For example: "com.domain.messages:MyKey".
    1260     * </p>
    1261     * <p>
    1262     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    1263     * </p>
    1264     *
    1265     * <pre>
    1266     * StringLookupFactory.INSTANCE.resourceBundleStringLookup().lookup("com.domain.messages:MyKey");
    1267     * </pre>
    1268     * <p>
    1269     * Using a {@link StringSubstitutor}:
    1270     * </p>
    1271     *
    1272     * <pre>
    1273     * StringSubstitutor.createInterpolator().replace("... ${resourceBundle:com.domain.messages:MyKey} ..."));
    1274     * </pre>
    1275     * <p>
    1276     * The above examples convert {@code "com.domain.messages:MyKey"} to the key value in the resource bundle at {@code "com.domain.messages"}.
    1277     * </p>
    1278     *
    1279     * @return The ResourceBundleStringLookup singleton instance.
    1280     */
    1281    public StringLookup resourceBundleStringLookup() {
    1282        return ResourceBundleStringLookup.INSTANCE;
    1283    }
    1284
    1285    /**
    1286     * Returns a ResourceBundleStringLookup instance for the given bundle name.
    1287     * <p>
    1288     * Looks up the value for a given key in the format "MyKey".
    1289     * </p>
    1290     * <p>
    1291     * For example: "MyKey".
    1292     * </p>
    1293     * <p>
    1294     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    1295     * </p>
    1296     *
    1297     * <pre>
    1298     * StringLookupFactory.INSTANCE.resourceBundleStringLookup("com.domain.messages").lookup("MyKey");
    1299     * </pre>
    1300     * <p>
    1301     * The above example converts {@code "MyKey"} to the key value in the resource bundle at {@code "com.domain.messages"}.
    1302     * </p>
    1303     *
    1304     * @param bundleName Only lookup in this bundle.
    1305     * @return a ResourceBundleStringLookup instance for the given bundle name.
    1306     * @since 1.5
    1307     */
    1308    public StringLookup resourceBundleStringLookup(final String bundleName) {
    1309        return new ResourceBundleStringLookup(bundleName);
    1310    }
    1311
    1312    /**
    1313     * Returns the ScriptStringLookup singleton instance. NOTE: This lookup is not included as a {@link #addDefaultStringLookups(Map) default lookup} unless
    1314     * explicitly enabled. See the class level documentation for details.
    1315     * <p>
    1316     * Looks up the value for the key in the format "ScriptEngineName:Script".
    1317     * </p>
    1318     * <p>
    1319     * For example: "javascript:3 + 4".
    1320     * </p>
    1321     * <p>
    1322     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    1323     * </p>
    1324     *
    1325     * <pre>
    1326     * StringLookupFactory.INSTANCE.scriptStringLookup().lookup("javascript:3 + 4");
    1327     * </pre>
    1328     * <p>
    1329     * When used through a {@link StringSubstitutor}, this lookup must either be added programmatically (as below) or enabled as a default lookup using the
    1330     * {@value #DEFAULT_STRING_LOOKUPS_PROPERTY} system property (see class documentation).
    1331     * </p>
    1332     *
    1333     * <pre>
    1334     * Map&lt;String, StringLookup&gt; lookupMap = new HashMap&lt;&gt;();
    1335     * lookupMap.put("script", StringLookupFactory.INSTANCE.scriptStringLookup());
    1336     *
    1337     * StringLookup variableResolver = StringLookupFactory.INSTANCE.interpolatorStringLookup(lookupMap, null, false);
    1338     *
    1339     * String value = new StringSubstitutor(variableResolver).replace("${script:javascript:3 + 4}");
    1340     * </pre>
    1341     * <p>
    1342     * The above examples convert {@code "javascript:3 + 4"} to {@code "7"}.
    1343     * </p>
    1344     *
    1345     * @return The ScriptStringLookup singleton instance.
    1346     * @since 1.5
    1347     */
    1348    public StringLookup scriptStringLookup() {
    1349        return ScriptStringLookup.INSTANCE;
    1350    }
    1351
    1352    /**
    1353     * Returns the SystemPropertyStringLookup singleton instance where the lookup key is a system property name.
    1354     *
    1355     * <p>
    1356     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    1357     * </p>
    1358     *
    1359     * <pre>
    1360     * StringLookupFactory.INSTANCE.systemPropertyStringLookup().lookup("os.name");
    1361     * </pre>
    1362     * <p>
    1363     * Using a {@link StringSubstitutor}:
    1364     * </p>
    1365     *
    1366     * <pre>
    1367     * StringSubstitutor.createInterpolator().replace("... ${sys:os.name} ..."));
    1368     * </pre>
    1369     * <p>
    1370     * The above examples convert {@code "os.name"} to the operating system name.
    1371     * </p>
    1372     *
    1373     * @return The SystemPropertyStringLookup singleton instance.
    1374     */
    1375    public StringLookup systemPropertyStringLookup() {
    1376        return StringLookupFactory.INSTANCE_SYSTEM_PROPERTIES;
    1377    }
    1378
    1379    /**
    1380     * Returns the UrlDecoderStringLookup singleton instance.
    1381     * <p>
    1382     * Decodes URL Strings using the UTF-8 encoding.
    1383     * </p>
    1384     * <p>
    1385     * For example: "Hello%20World%21" becomes "Hello World!".
    1386     * </p>
    1387     * <p>
    1388     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    1389     * </p>
    1390     *
    1391     * <pre>
    1392     * StringLookupFactory.INSTANCE.urlDecoderStringLookup().lookup("Hello%20World%21");
    1393     * </pre>
    1394     * <p>
    1395     * Using a {@link StringSubstitutor}:
    1396     * </p>
    1397     *
    1398     * <pre>
    1399     * StringSubstitutor.createInterpolator().replace("... ${urlDecoder:Hello%20World%21} ..."));
    1400     * </pre>
    1401     * <p>
    1402     * The above examples convert {@code "Hello%20World%21"} to {@code "Hello World!"}.
    1403     * </p>
    1404     *
    1405     * @return The UrlStringLookup singleton instance.
    1406     * @since 1.6
    1407     */
    1408    public StringLookup urlDecoderStringLookup() {
    1409        return UrlDecoderStringLookup.INSTANCE;
    1410    }
    1411
    1412    /**
    1413     * Returns the UrlDecoderStringLookup singleton instance.
    1414     * <p>
    1415     * Decodes URL Strings using the UTF-8 encoding.
    1416     * </p>
    1417     * <p>
    1418     * For example: "Hello World!" becomes "Hello+World%21".
    1419     * </p>
    1420     * <p>
    1421     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    1422     * </p>
    1423     *
    1424     * <pre>
    1425     * StringLookupFactory.INSTANCE.urlEncoderStringLookup().lookup("Hello World!");
    1426     * </pre>
    1427     * <p>
    1428     * Using a {@link StringSubstitutor}:
    1429     * </p>
    1430     *
    1431     * <pre>
    1432     * StringSubstitutor.createInterpolator().replace("... ${urlEncoder:Hello World!} ..."));
    1433     * </pre>
    1434     * <p>
    1435     * The above examples convert {@code "Hello World!"} to {@code "Hello%20World%21"}.
    1436     * </p>
    1437     *
    1438     * @return The UrlStringLookup singleton instance.
    1439     * @since 1.6
    1440     */
    1441    public StringLookup urlEncoderStringLookup() {
    1442        return UrlEncoderStringLookup.INSTANCE;
    1443    }
    1444
    1445    /**
    1446     * Returns the UrlStringLookup singleton instance. This lookup is not included as a {@link #addDefaultStringLookups(Map) default lookup} unless explicitly
    1447     * enabled. See the class level documentation for details.
    1448     * <p>
    1449     * Looks up the value for the key in the format "CharsetName:URL".
    1450     * </p>
    1451     * <p>
    1452     * For example, using the HTTP scheme: "UTF-8:http://www.google.com"
    1453     * </p>
    1454     * <p>
    1455     * For example, using the file scheme: "UTF-8:file:///C:/somehome/commons/commons-text/src/test/resources/document.properties"
    1456     * </p>
    1457     * <p>
    1458     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    1459     * </p>
    1460     *
    1461     * <pre>
    1462     * StringLookupFactory.INSTANCE.urlStringLookup().lookup("UTF-8:https://www.apache.org");
    1463     * </pre>
    1464     * <p>
    1465     * When used through a {@link StringSubstitutor}, this lookup must either be added programmatically (as below) or enabled as a default lookup using the
    1466     * {@value #DEFAULT_STRING_LOOKUPS_PROPERTY} system property (see class documentation).
    1467     * </p>
    1468     *
    1469     * <pre>
    1470     * Map&lt;String, StringLookup&gt; lookupMap = new HashMap&lt;&gt;();
    1471     * lookupMap.put("url", StringLookupFactory.INSTANCE.urlStringLookup());
    1472     *
    1473     * StringLookup variableResolver = StringLookupFactory.INSTANCE.interpolatorStringLookup(lookupMap, null, false);
    1474     *
    1475     * String value = new StringSubstitutor(variableResolver).replace("${url:UTF-8:https://www.apache.org}");
    1476     * </pre>
    1477     * <p>
    1478     * The above examples convert {@code "UTF-8:https://www.apache.org"} to the contents of that page.
    1479     * </p>
    1480     *
    1481     * @return The UrlStringLookup singleton instance.
    1482     * @since 1.5
    1483     */
    1484    public StringLookup urlStringLookup() {
    1485        return UrlStringLookup.INSTANCE;
    1486    }
    1487
    1488    /**
    1489     * Returns the XmlDecoderStringLookup singleton instance.
    1490     * <p>
    1491     * Decodes strings according to the XML 1.0 specification.
    1492     * </p>
    1493     * <p>
    1494     * For example: "&amp;lt;element&amp;gt;" becomes "&lt;element&gt;".
    1495     * </p>
    1496     * <p>
    1497     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    1498     * </p>
    1499     *
    1500     * <pre>
    1501     * StringLookupFactory.INSTANCE.xmlDecoderStringLookup().lookup("&amp;lt;element&amp;gt;");
    1502     * </pre>
    1503     * <p>
    1504     * Using a {@link StringSubstitutor}:
    1505     * </p>
    1506     *
    1507     * <pre>
    1508     * StringSubstitutor.createInterpolator().replace("... ${xmlDecoder:&amp;lt;element&amp;gt;} ..."));
    1509     * </pre>
    1510     * <p>
    1511     * The above examples convert {@code "&lt;element&gt;"} to {@code "<element>"}.
    1512     * </p>
    1513     *
    1514     * @return The XmlDecoderStringLookup singleton instance.
    1515     * @since 1.11.0
    1516     */
    1517    public StringLookup xmlDecoderStringLookup() {
    1518        return XmlDecoderStringLookup.INSTANCE;
    1519    }
    1520
    1521    /**
    1522     * Returns the XmlEncoderStringLookup singleton instance.
    1523     * <p>
    1524     * Encodes strings according to the XML 1.0 specification.
    1525     * </p>
    1526     * <p>
    1527     * For example: "&lt;element&gt;" becomes "&amp;lt;element&amp;gt;".
    1528     * </p>
    1529     * <p>
    1530     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    1531     * </p>
    1532     *
    1533     * <pre>
    1534     * StringLookupFactory.INSTANCE.xmlEncoderStringLookup().lookup("&lt;element&gt;");
    1535     * </pre>
    1536     * <p>
    1537     * Using a {@link StringSubstitutor}:
    1538     * </p>
    1539     *
    1540     * <pre>
    1541     * StringSubstitutor.createInterpolator().replace("... ${xmlEncoder:&lt;element&gt;} ..."));
    1542     * </pre>
    1543     * <p>
    1544     * The above examples convert {@code "<element>"} to {@code "&lt;element&gt;"}.
    1545     * </p>
    1546     *
    1547     * @return The XmlEncoderStringLookup singleton instance.
    1548     * @since 1.11.0
    1549     */
    1550    public StringLookup xmlEncoderStringLookup() {
    1551        return XmlEncoderStringLookup.INSTANCE;
    1552    }
    1553
    1554    /**
    1555     * Returns an XML StringLookup instance.
    1556     * <p>
    1557     * If this factory was built using {@link Builder#setFences(Path...)}, then the string lookup is fenced and will throw an {@link IllegalArgumentException}
    1558     * if a lookup causes causes a path to resolve outside of these fences. Otherwise, the result is unfenced to preserved behavior from previous versions.
    1559     * </p>
    1560     * <p>
    1561     * We look up the value for the key in the format "DocumentPath:XPath".
    1562     * </p>
    1563     * <p>
    1564     * For example: "com/domain/document.xml:/path/to/node".
    1565     * </p>
    1566     * <p>
    1567     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    1568     * </p>
    1569     *
    1570     * <pre>
    1571     * StringLookupFactory.INSTANCE.xmlStringLookup().lookup("com/domain/document.xml:/path/to/node");
    1572     * </pre>
    1573     * <p>
    1574     * Using a {@link StringSubstitutor}:
    1575     * </p>
    1576     *
    1577     * <pre>
    1578     * StringSubstitutor.createInterpolator().replace("... ${xml:com/domain/document.xml:/path/to/node} ..."));
    1579     * </pre>
    1580     * <p>
    1581     * The above examples convert {@code "com/domain/document.xml:/path/to/node"} to the value of the XPath in the XML document.
    1582     * </p>
    1583     *
    1584     * @return An XML StringLookup instance.
    1585     * @since 1.5
    1586     */
    1587    public StringLookup xmlStringLookup() {
    1588        return fences != null ? xmlStringLookup(XmlStringLookup.DEFAULT_FEATURES, fences) : XmlStringLookup.INSTANCE;
    1589    }
    1590
    1591    /**
    1592     * Returns an XML StringLookup instance.
    1593     * <p>
    1594     * If this factory was built using {@link Builder#setFences(Path...)}, then the string lookup is fenced and will throw an {@link IllegalArgumentException}
    1595     * if a lookup causes causes a path to resolve outside of these fences. Otherwise, the result is unfenced to preserved behavior from previous versions.
    1596     * </p>
    1597     * <p>
    1598     * We look up the value for the key in the format "DocumentPath:XPath".
    1599     * </p>
    1600     * <p>
    1601     * For example: "com/domain/document.xml:/path/to/node".
    1602     * </p>
    1603     * <p>
    1604     * Using a {@link StringLookup} from the {@link StringLookupFactory}:
    1605     * </p>
    1606     *
    1607     * <pre>
    1608     * StringLookupFactory.INSTANCE.xmlStringLookup().lookup("com/domain/document.xml:/path/to/node");
    1609     * </pre>
    1610     * <p>
    1611     * Using a {@link StringSubstitutor}:
    1612     * </p>
    1613     *
    1614     * <pre>
    1615     * StringSubstitutor.createInterpolator().replace("... ${xml:com/domain/document.xml:/path/to/node} ..."));
    1616     * </pre>
    1617     * <p>
    1618     * The above examples convert {@code "com/domain/document.xml:/path/to/node"} to the value of the XPath in the XML document.
    1619     * </p>
    1620     *
    1621     * @param xPathFactoryFeatures XPathFactory features to set.
    1622     * @return An XML StringLookup instance.
    1623     * @see XPathFactory#setFeature(String, boolean)
    1624     * @since 1.11.0
    1625     */
    1626    public StringLookup xmlStringLookup(final Map<String, Boolean> xPathFactoryFeatures) {
    1627        return xmlStringLookup(xPathFactoryFeatures, fences);
    1628    }
    1629
    1630    /**
    1631     * Returns a fenced XML StringLookup instance.
    1632     * <p>
    1633     * If this factory was built using {@link Builder#setFences(Path...)}, then the string lookup is fenced and will throw an {@link IllegalArgumentException}
    1634     * if a lookup causes causes a path to resolve outside of these fences. Otherwise, the result is unfenced to preserved behavior from previous versions.
    1635     * </p>
    1636     * <p>
    1637     * We look up the value for the key in the format "DocumentPath:XPath".
    1638     * </p>
    1639     * <p>
    1640     * For example: "com/domain/document.xml:/path/to/node".
    1641     * </p>
    1642     * <p>
    1643     * Using a {@link StringLookup} from the {@link StringLookupFactory} fenced by the current directory ({@code Paths.get("")}):
    1644     * </p>
    1645     *
    1646     * <pre>
    1647     * StringLookupFactory.INSTANCE.xmlStringLookup(map, Pathe.get("")).lookup("com/domain/document.xml:/path/to/node");
    1648     * </pre>
    1649     * <p>
    1650     * To use a {@link StringLookup} fenced by the current directory, use:
    1651     * </p>
    1652     *
    1653     * <pre>
    1654     * StringLookupFactory.INSTANCE.xmlStringLookup(Paths.get("")).lookup("com/domain/document.xml:/path/to/node");
    1655     *
    1656     * // throws IllegalArgumentException
    1657     * StringLookupFactory.INSTANCE.xmlStringLookup(Paths.get("")).lookup("/rootdir/foo/document.xml:/path/to/node");
    1658     *
    1659     * // throws IllegalArgumentException
    1660     * StringLookupFactory.INSTANCE.xmlStringLookup(Paths.get("")).lookup("../com/domain/document.xml:/path/to/node");
    1661     * </pre>
    1662     * <p>
    1663     * The above examples convert {@code "com/domain/document.xml:/path/to/node"} to the value of the XPath in the XML document.
    1664     * </p>
    1665     * <p>
    1666     * {@link StringSubstitutor} methods like {@link StringSubstitutor#replace(String)} will throw a {@link IllegalArgumentException} when a file doesn't
    1667     * resolves in a fence.
    1668     * </p>
    1669     *
    1670     * @param xPathFactoryFeatures XPathFactory features to set.
    1671     * @param fences               The fences guarding Path resolution.
    1672     * @return An XML StringLookup instance.
    1673     * @since 1.12.0
    1674     */
    1675    public StringLookup xmlStringLookup(final Map<String, Boolean> xPathFactoryFeatures, final Path... fences) {
    1676        return new XmlStringLookup(xPathFactoryFeatures, fences);
    1677    }
    1678}