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}