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