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