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