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