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}