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