From 1c1e13821ef576ccf07bd6786307a71121d6d3d8 Mon Sep 17 00:00:00 2001 From: Matt Juntunen Date: Sat, 16 Jul 2022 07:57:04 -0400 Subject: [PATCH] Make default string lookups configurable via system property. Remove dns, url, and script lookups from defaults. --- src/changes/changes.xml | 1 + .../commons/text/StringSubstitutor.java | 94 +++++- .../text/lookup/DefaultStringLookup.java | 84 +++-- .../text/lookup/InterpolatorStringLookup.java | 9 +- .../text/lookup/StringLookupFactory.java | 301 +++++++++++++----- .../commons/text/lookup/defaults.properties | 38 --- src/site/xdoc/userguide.xml | 27 +- ...tutorWithInterpolatorStringLookupTest.java | 58 +++- .../text/lookup/StringLookupFactoryTest.java | 178 +++++++++-- .../commons/text/lookup/defaults.properties | 38 --- 10 files changed, 580 insertions(+), 248 deletions(-) delete mode 100644 src/main/resources/org/apache/commons/text/lookup/defaults.properties delete mode 100644 src/test/resources/org/apache/commons/text/lookup/defaults.properties diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 0dc1a37ff5..85907387ee 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -78,6 +78,7 @@ The type attribute can be add,update,fix,remove. Release Notes page hasn't been updated for 1.9 release yet. Add StrBuilder.isNotEmpty(). + Make default string lookups configurable via system property. Remove dns, url, and script lookups from defaults. If these lookups are required for use in StringSubstitutor.createInterpolator(), they must be enabled via system property. See StringLookupFactory for details. Bump actions/setup-java from v1.4.0 to 3 #147, #156, #155, #172, #215, #314. Bump actions/checkout from v1 to 3 #138, #146, #165, #183, #274, #279, #304. Bump actions/cache from v2 to v3.0.5 #205, #217, #234, #339. diff --git a/src/main/java/org/apache/commons/text/StringSubstitutor.java b/src/main/java/org/apache/commons/text/StringSubstitutor.java index 496727501d..3b1e4cb777 100644 --- a/src/main/java/org/apache/commons/text/StringSubstitutor.java +++ b/src/main/java/org/apache/commons/text/StringSubstitutor.java @@ -141,26 +141,28 @@ * *
  * final StringSubstitutor interpolator = StringSubstitutor.createInterpolator();
- * interpolator.setEnableSubstitutionInVariables(true); // Allows for nested $'s.
- * final String text = interpolator.replace("Base64 Decoder:        ${base64Decoder:SGVsbG9Xb3JsZCE=}\n"
+ * final String text = interpolator.replace(
+ *       "Base64 Decoder:        ${base64Decoder:SGVsbG9Xb3JsZCE=}\n"
  *     + "Base64 Encoder:        ${base64Encoder:HelloWorld!}\n"
  *     + "Java Constant:         ${const:java.awt.event.KeyEvent.VK_ESCAPE}\n"
- *     + "Date:                  ${date:yyyy-MM-dd}\n" + "DNS:                   ${dns:address|apache.org}\n"
+ *     + "Date:                  ${date:yyyy-MM-dd}\n"
  *     + "Environment Variable:  ${env:USERNAME}\n"
  *     + "File Content:          ${file:UTF-8:src/test/resources/document.properties}\n"
- *     + "Java:                  ${java:version}\n" + "Localhost:             ${localhost:canonical-name}\n"
+ *     + "Java:                  ${java:version}\n"
+ *     + "Localhost:             ${localhost:canonical-name}\n"
  *     + "Properties File:       ${properties:src/test/resources/document.properties::mykey}\n"
  *     + "Resource Bundle:       ${resourceBundle:org.apache.commons.text.example.testResourceBundleLookup:mykey}\n"
- *     + "Script:                ${script:javascript:3 + 4}\n" + "System Property:       ${sys:user.dir}\n"
+ *     + "System Property:       ${sys:user.dir}\n"
  *     + "URL Decoder:           ${urlDecoder:Hello%20World%21}\n"
  *     + "URL Encoder:           ${urlEncoder:Hello World!}\n"
- *     + "URL Content (HTTP):    ${url:UTF-8:http://www.apache.org}\n"
- *     + "URL Content (HTTPS):   ${url:UTF-8:https://www.apache.org}\n"
- *     + "URL Content (File):    ${url:UTF-8:file:///${sys:user.dir}/src/test/resources/document.properties}\n"
  *     + "XML XPath:             ${xml:src/test/resources/document.xml:/root/path/to/node}\n");
  * 
*

- * For documentation of each lookup, see {@link StringLookupFactory}. + * For documentation and a full list of available lookups, see {@link StringLookupFactory}. + *

+ *

NOTE: The list of lookups available by default in {@link #createInterpolator()} changed + * in version {@code 1.10.0}. See the {@link StringLookupFactory} documentation for details and an explanation + * on how to reproduce the previous functionality. *

* *

Using Recursive Variable Replacement

@@ -292,9 +294,81 @@ public String toString() { * *
      * StringSubstitutor.createInterpolator().replace(
-     *   "OS name: ${sys:os.name}, " + "3 + 4 = ${script:javascript:3 + 4}");
+     *   "OS name: ${sys:os.name}, user: ${env:USER}");
      * 
* + *

The table below lists the lookups available by default in the returned instance. These + * may be modified through the use of the {@value StringLookupFactory#DEFAULT_STRING_LOOKUPS_PROPERTY} + * system property, as described in the {@link StringLookupFactory} documentation.

+ * + *

NOTE: The list of lookups available by default changed in version {@code 1.10.0}. + * Configuration via system property (as mentioned above) may be necessary to reproduce previous functionality. + *

+ * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
Default Lookups
KeyLookup
{@value StringLookupFactory#KEY_BASE64_DECODER}{@link StringLookupFactory#base64DecoderStringLookup()}
{@value StringLookupFactory#KEY_BASE64_ENCODER}{@link StringLookupFactory#base64EncoderStringLookup()}
{@value StringLookupFactory#KEY_CONST}{@link StringLookupFactory#constantStringLookup()}
{@value StringLookupFactory#KEY_DATE}{@link StringLookupFactory#dateStringLookup()}
{@value StringLookupFactory#KEY_ENV}{@link StringLookupFactory#environmentVariableStringLookup()}
{@value StringLookupFactory#KEY_FILE}{@link StringLookupFactory#fileStringLookup()}
{@value StringLookupFactory#KEY_JAVA}{@link StringLookupFactory#javaPlatformStringLookup()}
{@value StringLookupFactory#KEY_LOCALHOST}{@link StringLookupFactory#localHostStringLookup()}
{@value StringLookupFactory#KEY_PROPERTIES}{@link StringLookupFactory#propertiesStringLookup()}
{@value StringLookupFactory#KEY_RESOURCE_BUNDLE}{@link StringLookupFactory#resourceBundleStringLookup()}
{@value StringLookupFactory#KEY_SYS}{@link StringLookupFactory#systemPropertyStringLookup()}
{@value StringLookupFactory#KEY_URL_DECODER}{@link StringLookupFactory#urlDecoderStringLookup()}
{@value StringLookupFactory#KEY_URL_ENCODER}{@link StringLookupFactory#urlEncoderStringLookup()}
{@value StringLookupFactory#KEY_XML}{@link StringLookupFactory#xmlStringLookup()}
+ * * @return a new instance using the interpolator string lookup. * @see StringLookupFactory#interpolatorStringLookup() * @since 1.8 diff --git a/src/main/java/org/apache/commons/text/lookup/DefaultStringLookup.java b/src/main/java/org/apache/commons/text/lookup/DefaultStringLookup.java index ce01c81ea3..c23d97dd4e 100644 --- a/src/main/java/org/apache/commons/text/lookup/DefaultStringLookup.java +++ b/src/main/java/org/apache/commons/text/lookup/DefaultStringLookup.java @@ -16,13 +16,16 @@ */ package org.apache.commons.text.lookup; -import java.util.stream.Stream; - /** * An enumeration defining {@link StringLookup} objects available through {@link StringLookupFactory}. *

* This enum was adapted and expanded from Apache Commons Configuration 2.4. *

+ *

NOTE: Starting in version 1.10.0, not all lookups defined in this class are + * included by default in the + * {@link StringLookupFactory#addDefaultStringLookups(java.util.Map) StringLookupFactory.addDefaultStringLookups} + * method. See the {@link StringLookupFactory} class documentation for details. + *

* * @see StringLookupFactory * @see StringLookup @@ -31,100 +34,125 @@ public enum DefaultStringLookup { /** - * The lookup for Base64 decoding using the key {@value StringLookupFactory#KEY_BASE64_DECODER}. + * The lookup for Base64 decoding using the key {@code "base64Decoder"}. + * @see StringLookupFactory#KEY_BASE64_DECODER + * @see StringLookupFactory#base64DecoderStringLookup() */ BASE64_DECODER(StringLookupFactory.KEY_BASE64_DECODER, StringLookupFactory.INSTANCE.base64DecoderStringLookup()), /** - * The lookup for Base64 decoding using the key {@value StringLookupFactory#KEY_BASE64_ENCODER}. + * The lookup for Base64 decoding using the key {@code "base64Encoder"}. + * @see StringLookupFactory#KEY_BASE64_ENCODER + * @see StringLookupFactory#base64EncoderStringLookup() */ BASE64_ENCODER(StringLookupFactory.KEY_BASE64_ENCODER, StringLookupFactory.INSTANCE.base64EncoderStringLookup()), /** - * The lookup for constants using the key {@value StringLookupFactory#KEY_CONST}. + * The lookup for Java static class member constants using the key {@code "const"}. + * @see StringLookupFactory#KEY_CONST + * @see StringLookupFactory#constantStringLookup() */ CONST(StringLookupFactory.KEY_CONST, StringLookupFactory.INSTANCE.constantStringLookup()), /** - * The lookup for dates using the key {@value StringLookupFactory#KEY_DATE}. + * The lookup for formatting the current date using the key {@code "date"}. + * @see StringLookupFactory#KEY_DATE + * @see StringLookupFactory#dateStringLookup() */ DATE(StringLookupFactory.KEY_DATE, StringLookupFactory.INSTANCE.dateStringLookup()), /** - * The lookup for DNS using the key {@value StringLookupFactory#KEY_DNS}. - * + * The lookup for DNS using the key {@code "dns"}. + * @see StringLookupFactory#KEY_DNS + * @see StringLookupFactory#dnsStringLookup() * @since 1.8 */ DNS(StringLookupFactory.KEY_DNS, StringLookupFactory.INSTANCE.dnsStringLookup()), /** - * The lookup for environment properties using the key {@value StringLookupFactory#KEY_ENV}. + * The lookup for environment properties using the key {@code "env"}. + * @see StringLookupFactory#KEY_ENV + * @see StringLookupFactory#environmentVariableStringLookup() */ ENVIRONMENT(StringLookupFactory.KEY_ENV, StringLookupFactory.INSTANCE.environmentVariableStringLookup()), /** - * The lookup for files using the key {@value StringLookupFactory#KEY_FILE}. + * The lookup for files using the key {@code "file"}. + * @see StringLookupFactory#KEY_FILE + * @see StringLookupFactory#fileStringLookup() */ FILE(StringLookupFactory.KEY_FILE, StringLookupFactory.INSTANCE.fileStringLookup()), /** - * The lookup for Java platform information using the key {@value StringLookupFactory#KEY_JAVA}. + * The lookup for Java platform information using the key {@code "java"}. + * @see StringLookupFactory#KEY_JAVA + * @see StringLookupFactory#javaPlatformStringLookup() */ JAVA(StringLookupFactory.KEY_JAVA, StringLookupFactory.INSTANCE.javaPlatformStringLookup()), /** - * The lookup for localhost information using the key {@value StringLookupFactory#KEY_LOCALHOST}. + * The lookup for localhost information using the key {@code "localhost"}. + * @see StringLookupFactory#KEY_LOCALHOST + * @see StringLookupFactory#localHostStringLookup() */ LOCAL_HOST(StringLookupFactory.KEY_LOCALHOST, StringLookupFactory.INSTANCE.localHostStringLookup()), /** - * The lookup for properties using the key {@value StringLookupFactory#KEY_PROPERTIES}. + * The lookup for properties using the key {@code "properties"}. + * @see StringLookupFactory#KEY_PROPERTIES + * @see StringLookupFactory#propertiesStringLookup() */ PROPERTIES(StringLookupFactory.KEY_PROPERTIES, StringLookupFactory.INSTANCE.propertiesStringLookup()), /** - * The lookup for resource bundles using the key {@value StringLookupFactory#KEY_RESOURCE_BUNDLE}. + * The lookup for resource bundles using the key {@code "resourceBundle"}. + * @see StringLookupFactory#KEY_RESOURCE_BUNDLE + * @see StringLookupFactory#resourceBundleStringLookup() */ RESOURCE_BUNDLE(StringLookupFactory.KEY_RESOURCE_BUNDLE, StringLookupFactory.INSTANCE.resourceBundleStringLookup()), /** - * The lookup for scripts using the key {@value StringLookupFactory#KEY_SCRIPT}. + * The lookup for scripts using the key {@code "script"}. + * @see StringLookupFactory#KEY_SCRIPT + * @see StringLookupFactory#scriptStringLookup() */ SCRIPT(StringLookupFactory.KEY_SCRIPT, StringLookupFactory.INSTANCE.scriptStringLookup()), /** - * The lookup for system properties using the key {@value StringLookupFactory#KEY_SYS}. + * The lookup for system properties using the key {@code "sys"}. + * @see StringLookupFactory#KEY_SYS + * @see StringLookupFactory#systemPropertyStringLookup() */ SYSTEM_PROPERTIES(StringLookupFactory.KEY_SYS, StringLookupFactory.INSTANCE.systemPropertyStringLookup()), /** - * The lookup for URLs using the key {@value StringLookupFactory#KEY_URL}. + * The lookup for URLs using the key {@code "url"}. + * @see StringLookupFactory#KEY_URL + * @see StringLookupFactory#urlStringLookup() */ URL(StringLookupFactory.KEY_URL, StringLookupFactory.INSTANCE.urlStringLookup()), /** - * The lookup for URL decoding using the key {@value StringLookupFactory#KEY_URL_DECODER}. + * The lookup for URL decoding using the key {@code "urlDecoder"}. + * @see StringLookupFactory#KEY_URL_DECODER + * @see StringLookupFactory#urlDecoderStringLookup() */ URL_DECODER(StringLookupFactory.KEY_URL_DECODER, StringLookupFactory.INSTANCE.urlDecoderStringLookup()), /** - * The lookup for URL decoding using the key {@value StringLookupFactory#KEY_URL_ENCODER}. + * The lookup for URL decoding using the key {@code "urlEncoder"}. + * @see StringLookupFactory#KEY_URL_ENCODER + * @see StringLookupFactory#urlEncoderStringLookup() */ URL_ENCODER(StringLookupFactory.KEY_URL_ENCODER, StringLookupFactory.INSTANCE.urlEncoderStringLookup()), /** - * The lookup for URL decoding using the key {@value StringLookupFactory#KEY_XML}. + * The lookup for URL decoding using the key {@code "xml"}. + * @see StringLookupFactory#KEY_XML + * @see StringLookupFactory#xmlStringLookup() */ XML(StringLookupFactory.KEY_XML, StringLookupFactory.INSTANCE.xmlStringLookup()); - /** - * Prints out to the console the mapping from enum keys to enum name. - * @param args ignored. - */ - public static void main(final String[] args) { - Stream.of(values()).forEach(e -> System.out.println(e.getKey() + "=" + e.name())); - } - /** The prefix under which the associated lookup object is registered. */ private final String key; diff --git a/src/main/java/org/apache/commons/text/lookup/InterpolatorStringLookup.java b/src/main/java/org/apache/commons/text/lookup/InterpolatorStringLookup.java index 7a63ef6158..72b36c31fe 100644 --- a/src/main/java/org/apache/commons/text/lookup/InterpolatorStringLookup.java +++ b/src/main/java/org/apache/commons/text/lookup/InterpolatorStringLookup.java @@ -18,7 +18,6 @@ import java.util.Collections; import java.util.HashMap; -import java.util.Locale; import java.util.Map; import java.util.Map.Entry; @@ -40,10 +39,6 @@ class InterpolatorStringLookup extends AbstractStringLookup { /** Constant for the prefix separator. */ private static final char PREFIX_SEPARATOR = ':'; - static String toKey(final String key) { - return key.toLowerCase(Locale.ROOT); - } - /** The default string lookup. */ private final StringLookup defaultStringLookup; @@ -72,7 +67,7 @@ static String toKey(final String key) { this.defaultStringLookup = defaultStringLookup; this.stringLookupMap = new HashMap<>(stringLookupMap.size()); for (final Entry entry : stringLookupMap.entrySet()) { - this.stringLookupMap.put(toKey(entry.getKey()), entry.getValue()); + this.stringLookupMap.put(StringLookupFactory.toKey(entry.getKey()), entry.getValue()); } if (addDefaultLookups) { StringLookupFactory.INSTANCE.addDefaultStringLookups(this.stringLookupMap); @@ -127,7 +122,7 @@ public String lookup(String key) { final int prefixPos = key.indexOf(PREFIX_SEPARATOR); if (prefixPos >= 0) { - final String prefix = toKey(key.substring(0, prefixPos)); + final String prefix = StringLookupFactory.toKey(key.substring(0, prefixPos)); final String name = key.substring(prefixPos + 1); final StringLookup lookup = stringLookupMap.get(prefix); String value = null; diff --git a/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java b/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java index 54cd61de15..bd397f8773 100644 --- a/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java +++ b/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java @@ -17,14 +17,12 @@ package org.apache.commons.text.lookup; -import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; import java.util.Map; -import java.util.Objects; import java.util.Properties; import java.util.function.BiFunction; import java.util.function.Function; @@ -46,7 +44,19 @@ *
  • {@link #interpolatorStringLookup(Map, StringLookup, boolean)}.
  • * *

    - * The default lookups are: + * Unless explicitly requested otherwise, a set of default lookups are included for convenience with these + * variable interpolation methods. These defaults are listed in the table below. However, the exact lookups + * included can be configured through the use of the {@value #DEFAULT_STRING_LOOKUPS_PROPERTY} system 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 {@link DefaultStringLookup} enum. For example, setting this system property to + * {@code "BASE64_ENCODER,ENVIRONMENT"} will only include the + * {@link DefaultStringLookup#BASE64_ENCODER BASE64_ENCODER} and {@link DefaultStringLookup#ENVIRONMENT ENVIRONMENT} + * lookups. Setting the property to the empty string will cause no defaults to be configured. + * Note that not all lookups defined here and in {@link DefaultStringLookup} are included by default. + * Specifically, lookups that can execute code (e.g., {@link DefaultStringLookup#SCRIPT SCRIPT}) and those + * that can result in contact with remote servers (e.g., {@link DefaultStringLookup#URL URL} and + * {@link DefaultStringLookup#DNS DNS}) are not included by default. The current set of default lookups can + * be accessed directly with {@link #addDefaultStringLookups(Map)}. *

    * * @@ -81,12 +91,6 @@ * * * - * - * - * - * - * - * * * * @@ -123,24 +127,12 @@ * * * - * - * - * - * - * - * * * * * * * - * - * - * - * - * - * * * * @@ -159,8 +151,37 @@ * * *
    Default String Lookups
    1.5
    {@value #KEY_DNS}{@link StringLookup}{@link #dnsStringLookup()}1.8
    {@value #KEY_ENV}{@link StringLookup}{@link #environmentVariableStringLookup()}1.6
    {@value #KEY_SCRIPT}{@link StringLookup}{@link #scriptStringLookup()}1.5
    {@value #KEY_SYS}{@link StringLookup}{@link #systemPropertyStringLookup()}1.3
    {@value #KEY_URL}{@link StringLookup}{@link #urlStringLookup()}1.5
    {@value #KEY_URL_DECODER}{@link StringLookup}{@link #urlDecoderStringLookup()}1.5
    + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    Additional String Lookups (not included by default)
    KeyInterfaceFactory MethodSince
    {@value #KEY_DNS}{@link StringLookup}{@link #dnsStringLookup()}1.8
    {@value #KEY_URL}{@link StringLookup}{@link #urlStringLookup()}1.5
    {@value #KEY_SCRIPT}{@link StringLookup}{@link #scriptStringLookup()}1.5
    + * *

    - * We also provide functional lookups used as building blocks for other lookups. + * This class also provides functional lookups used as building blocks for other lookups. * * * @@ -184,16 +205,6 @@ */ public final class StringLookupFactory { - /** - * Default properties file classpath location. - */ - private static final String DEFAULT_RESOURCE = "org/apache/commons/text/lookup/defaults.properties"; - - /** - * Default mapping. - */ - private static final Properties DEFAULTS = loadProperties(); - /** * Defines the singleton for this class. */ @@ -253,7 +264,7 @@ public final class StringLookupFactory { *

    * *
    -     * StringLookupFactory.INSTANCE.dateStringLookup().lookup("USER");
    +     * StringLookupFactory.INSTANCE.environmentVariableStringLookup().lookup("USER");
          * 
    *

    * Using a {@link StringSubstitutor}: @@ -398,6 +409,17 @@ public final class StringLookupFactory { */ public static final String KEY_XML = "xml"; + /** + * Name of the system property used to determine the string lookups added by the + * {@link #addDefaultStringLookups(Map)} method. Use of this property is only required + * in cases where the set of default lookups must be modified. (See the class documentation + * for details.) + * + * @since 1.10 + */ + public static final String DEFAULT_STRING_LOOKUPS_PROPERTY = + "org.apache.commons.text.lookup.StringLookupFactory.defaultStringLookups"; + /** * Clears any static resources. * @@ -407,16 +429,6 @@ public static void clear() { ConstantStringLookup.clear(); } - private static Properties loadProperties() { - final Properties properties = new Properties(); - try (InputStream inputStream = ClassLoader.getSystemResourceAsStream(DEFAULT_RESOURCE)) { - properties.load(inputStream); - } catch (final IOException e) { - throw new UncheckedIOException(e); - } - return properties; - } - /** * Returns the given map if the input is non-null or an empty immutable map if the input is null. * @@ -437,17 +449,17 @@ private StringLookupFactory() { } /** - * Adds the {@link StringLookupFactory default lookups} as defined in {@value #DEFAULT_RESOURCE}. + * 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 during string interpolation. The + * defaults may be configured using the {@value #DEFAULT_STRING_LOOKUPS_PROPERTY} system property. + * See the class documentation for details and a list of lookups. * * @param stringLookupMap the map of string lookups to edit. * @since 1.5 */ public void addDefaultStringLookups(final Map stringLookupMap) { if (stringLookupMap != null) { - DEFAULTS.forEach((k, v) -> { - final DefaultStringLookup stringLookup = DefaultStringLookup.valueOf(Objects.toString(v)); - stringLookupMap.put(InterpolatorStringLookup.toKey(Objects.toString(k)), stringLookup.getStringLookup()); - }); + stringLookupMap.putAll(DefaultStringLookupsHolder.INSTANCE.getDefaultStringLookups()); } } @@ -471,7 +483,7 @@ public void addDefaultStringLookups(final Map stringLookup * The above examples convert {@code "SGVsbG9Xb3JsZCE="} to {@code "HelloWorld!"}. *

    * - * @return The DateStringLookup singleton instance. + * @return The Base64DecoderStringLookup singleton instance. * @since 1.5 */ public StringLookup base64DecoderStringLookup() { @@ -498,7 +510,7 @@ public StringLookup base64DecoderStringLookup() { * The above examples convert {@code } to {@code "SGVsbG9Xb3JsZCE="}. *

    * - * @return The DateStringLookup singleton instance. + * @return The Base64EncoderStringLookup singleton instance. * @since 1.6 */ public StringLookup base64EncoderStringLookup() { @@ -525,7 +537,7 @@ public StringLookup base64EncoderStringLookup() { * The above examples convert {@code "SGVsbG9Xb3JsZCE="} to {@code "HelloWorld!"}. *

    * - * @return The DateStringLookup singleton instance. + * @return The Base64DecoderStringLookup singleton instance. * @since 1.5 * @deprecated Use {@link #base64DecoderStringLookup()}. */ @@ -579,7 +591,7 @@ public BiStringLookup biFunctionStringLookup(final BiFunction * - * @return The DateStringLookup singleton instance. + * @return The ConstantStringLookup singleton instance. * @since 1.5 */ public StringLookup constantStringLookup() { @@ -629,17 +641,24 @@ public StringLookup dateStringLookup() { * StringLookupFactory.INSTANCE.dnsStringLookup().lookup("address|apache.org"); * *

    - * Using a {@link StringSubstitutor}: + * When used through a {@link StringSubstitutor}, this lookup must either be added programmatically + * (as below) or enabled as a default lookup using the {@value #DEFAULT_STRING_LOOKUPS_PROPERTY} system property + * (see class documentation). *

    * *
    -     * StringSubstitutor.createInterpolator().replace("... ${dns:address|apache.org} ..."));
    +     * Map<String, StringLookup> lookupMap = new HashMap<>();
    +     * lookupMap.put("dns", StringLookupFactory.INSTANCE.dnsStringLookup());
    +     *
    +     * StringLookup variableResolver = StringLookupFactory.INSTANCE.interpolatorStringLookup(lookupMap, null, false);
    +     *
    +     * new StringSubstitutor(variableResolver).replace("... ${dns:address|apache.org} ...");
          * 
    *

    - * The above examples convert {@code "address|apache.org"} to {@code "95.216.24.32} (or {@code "40.79.78.1"}). + * The above examples convert {@code "address|apache.org"} to the IP address of {@code apache.org}. *

    * - * @return the DateStringLookup singleton instance. + * @return the DnsStringLookup singleton instance. * @since 1.8 */ public StringLookup dnsStringLookup() { @@ -654,7 +673,7 @@ public StringLookup dnsStringLookup() { *

    * *
    -     * StringLookupFactory.INSTANCE.dateStringLookup().lookup("USER");
    +     * StringLookupFactory.INSTANCE.environmentVariableStringLookup().lookup("USER");
          * 
    *

    * Using a {@link StringSubstitutor}: @@ -715,10 +734,9 @@ public StringLookup functionStringLookup(final Function function) } /** - * Returns the default {@link InterpolatorStringLookup} configured with the {@link StringLookupFactory default lookups}. - *

    - * The lookups available to an interpolator are defined in - *

    + * Returns a {@link InterpolatorStringLookup} containing the configured + * {@link #addDefaultStringLookups(Map) default lookups}. See the class documentation for + * details on how these defaults are configured. *

    * Using a {@link StringLookup} from the {@link StringLookupFactory}: *

    @@ -744,15 +762,15 @@ public StringLookup interpolatorStringLookup() { } /** - * Returns a new InterpolatorStringLookup using the {@link StringLookupFactory default lookups}. - *

    - * If {@code addDefaultLookups} is true, the following lookups are used in addition to the ones provided in - * {@code stringLookupMap}: - *

    + * Returns a new InterpolatorStringLookup. If {@code addDefaultLookups} is {@code true}, the configured + * {@link #addDefaultStringLookups(Map) default lookups} are included in addition to the ones + * provided in {@code stringLookupMap}. (See the class documentation for details on how default lookups + * are configured.) * * @param stringLookupMap the map of string lookups. - * @param defaultStringLookup the default string lookup. - * @param addDefaultLookups whether to use lookups as described above. + * @param defaultStringLookup the default string lookup; this lookup is used when a variable cannot be + * resolved using the lookups in {@code stringLookupMap} or the configured default lookups (if enabled) + * @param addDefaultLookups whether to use default lookups as described above. * @return a new InterpolatorStringLookup. * @since 1.4 */ @@ -762,7 +780,9 @@ public StringLookup interpolatorStringLookup(final Map str } /** - * Returns a new InterpolatorStringLookup using the {@link StringLookupFactory default lookups}. + * Returns a new InterpolatorStringLookup using the given key-value pairs and the configured + * {@link #addDefaultStringLookups(Map) default lookups} to resolve variables. (See the class + * documentation for details on how default lookups are configured.) * * @param the value type the default string lookup's map. * @param map the default map for string lookups. @@ -773,7 +793,9 @@ public StringLookup interpolatorStringLookup(final Map map) { } /** - * Returns a new InterpolatorStringLookup using the {@link StringLookupFactory default lookups}. + * Returns a new InterpolatorStringLookup using the given lookup and the configured + * {@link #addDefaultStringLookups(Map) default lookups} to resolve variables. (See the class + * documentation for details on how default lookups are configured.) * * @param defaultStringLookup the default string lookup. * @return a new InterpolatorStringLookup. @@ -975,7 +997,9 @@ public StringLookup resourceBundleStringLookup(final String bundleName) { } /** - * Returns the ScriptStringLookup singleton instance. + * Returns the ScriptStringLookup singleton instance. NOTE: This lookup is not included + * as a {@link #addDefaultStringLookups(Map) default lookup} unless explicitly enabled. See + * the class level documentation for details. *

    * Looks up the value for the key in the format "ScriptEngineName:Script". *

    @@ -990,11 +1014,18 @@ public StringLookup resourceBundleStringLookup(final String bundleName) { * StringLookupFactory.INSTANCE.scriptStringLookup().lookup("javascript:3 + 4"); * *

    - * Using a {@link StringSubstitutor}: + * When used through a {@link StringSubstitutor}, this lookup must either be added programmatically + * (as below) or enabled as a default lookup using the {@value #DEFAULT_STRING_LOOKUPS_PROPERTY} system property + * (see class documentation). *

    * *
    -     * StringSubstitutor.createInterpolator().replace("... ${javascript:3 + 4} ..."));
    +     * Map<String, StringLookup> lookupMap = new HashMap<>();
    +     * lookupMap.put("script", StringLookupFactory.INSTANCE.scriptStringLookup());
    +     *
    +     * StringLookup variableResolver = StringLookupFactory.INSTANCE.interpolatorStringLookup(lookupMap, null, false);
    +     *
    +     * String value = new StringSubstitutor(variableResolver).replace("${script:javascript:3 + 4}");
          * 
    *

    * The above examples convert {@code "javascript:3 + 4"} to {@code "7"}. @@ -1101,7 +1132,9 @@ public StringLookup urlEncoderStringLookup() { } /** - * Returns the UrlStringLookup singleton instance. + * Returns the UrlStringLookup singleton instance. This lookup is not included + * as a {@link #addDefaultStringLookups(Map) default lookup} unless explicitly enabled. See + * the class level documentation for details. *

    * Looks up the value for the key in the format "CharsetName:URL". *

    @@ -1120,11 +1153,18 @@ public StringLookup urlEncoderStringLookup() { * StringLookupFactory.INSTANCE.urlStringLookup().lookup("UTF-8:https://www.apache.org"); * *

    - * Using a {@link StringSubstitutor}: + * When used through a {@link StringSubstitutor}, this lookup must either be added programmatically + * (as below) or enabled as a default lookup using the {@value #DEFAULT_STRING_LOOKUPS_PROPERTY} system property + * (see class documentation). *

    * *
    -     * StringSubstitutor.createInterpolator().replace("... ${url:UTF-8:https://www.apache.org} ..."));
    +     * Map<String, StringLookup> lookupMap = new HashMap<>();
    +     * lookupMap.put("url", StringLookupFactory.INSTANCE.urlStringLookup());
    +     *
    +     * StringLookup variableResolver = StringLookupFactory.INSTANCE.interpolatorStringLookup(lookupMap, null, false);
    +     *
    +     * String value = new StringSubstitutor(variableResolver).replace("${url:UTF-8:https://www.apache.org}");
          * 
    *

    * The above examples convert {@code "UTF-8:https://www.apache.org"} to the contents of that page. @@ -1171,4 +1211,109 @@ public StringLookup xmlStringLookup() { return XmlStringLookup.INSTANCE; } + /** + * Get a string suitable for use as a key in the string lookup map. + * @param key string to convert to a string lookup map key + * @return string lookup map key + */ + static String toKey(final String key) { + return key.toLowerCase(Locale.ROOT); + } + + /** + * Internal class used to construct the default {@link StringLookup} map used by + * {@link StringLookupFactory#addDefaultStringLookups(Map)}. + */ + static final class DefaultStringLookupsHolder { + + /** Singleton instance, initialized with the system properties. */ + static final DefaultStringLookupsHolder INSTANCE = new DefaultStringLookupsHolder(System.getProperties()); + + /** Default string lookup map. */ + private final Map defaultStringLookups; + + /** + * Construct a new instance initialized with the given properties. + * @param props initialization properties + */ + DefaultStringLookupsHolder(final Properties props) { + final Map lookups = + props.containsKey(StringLookupFactory.DEFAULT_STRING_LOOKUPS_PROPERTY) + ? parseStringLookups(props.getProperty(StringLookupFactory.DEFAULT_STRING_LOOKUPS_PROPERTY)) + : createDefaultStringLookups(); + + defaultStringLookups = Collections.unmodifiableMap(lookups); + } + + /** + * Get the default string lookups map. + * @return default string lookups map + */ + Map getDefaultStringLookups() { + return defaultStringLookups; + } + + /** + * Create the lookup map used when the user has requested no customization. + * @return default lookup map + */ + private static Map createDefaultStringLookups() { + final Map lookupMap = new HashMap<>(); + + addLookup(DefaultStringLookup.BASE64_DECODER, lookupMap); + addLookup(DefaultStringLookup.BASE64_ENCODER, lookupMap); + addLookup(DefaultStringLookup.CONST, lookupMap); + addLookup(DefaultStringLookup.DATE, lookupMap); + addLookup(DefaultStringLookup.ENVIRONMENT, lookupMap); + addLookup(DefaultStringLookup.FILE, lookupMap); + addLookup(DefaultStringLookup.JAVA, lookupMap); + addLookup(DefaultStringLookup.LOCAL_HOST, lookupMap); + addLookup(DefaultStringLookup.PROPERTIES, lookupMap); + addLookup(DefaultStringLookup.RESOURCE_BUNDLE, lookupMap); + addLookup(DefaultStringLookup.SYSTEM_PROPERTIES, lookupMap); + addLookup(DefaultStringLookup.URL_DECODER, lookupMap); + addLookup(DefaultStringLookup.URL_ENCODER, lookupMap); + addLookup(DefaultStringLookup.XML, lookupMap); + + return lookupMap; + } + + /** + * Construct a lookup map by parsing the given string. The string is expected to contain + * comma or space-separated names of values from the {@link DefaultStringLookup} enum. If + * the given string is null or empty, an empty map is returned. + * @param str string to parse; may be null or empty + * @return lookup map parsed from the given string + */ + private static Map parseStringLookups(final String str) { + final Map lookupMap = new HashMap<>(); + + try { + for (final String lookupName : str.split("[\\s,]+")) { + if (!lookupName.isEmpty()) { + addLookup(DefaultStringLookup.valueOf(lookupName.toUpperCase()), lookupMap); + } + } + } catch (IllegalArgumentException exc) { + throw new IllegalArgumentException("Invalid default string lookups definition: " + str, exc); + } + + return lookupMap; + } + + /** + * Add the key and string lookup from {@code lookup} to {@code map}, also adding any additional + * key aliases if needed. Keys are normalized using the {@link #toKey(String)} method. + * @param lookup lookup to add + * @param map map to add to + */ + private static void addLookup(final DefaultStringLookup lookup, final Map map) { + map.put(toKey(lookup.getKey()), lookup.getStringLookup()); + + if (DefaultStringLookup.BASE64_DECODER.equals(lookup)) { + // "base64" is deprecated in favor of KEY_BASE64_DECODER. + map.put(toKey("base64"), lookup.getStringLookup()); + } + } + } } diff --git a/src/main/resources/org/apache/commons/text/lookup/defaults.properties b/src/main/resources/org/apache/commons/text/lookup/defaults.properties deleted file mode 100644 index ef2568bc49..0000000000 --- a/src/main/resources/org/apache/commons/text/lookup/defaults.properties +++ /dev/null @@ -1,38 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Note: -# base64 and base64Decoder are aliases. -# base64 is deprecated - -base64=BASE64_DECODER -base64Decoder=BASE64_DECODER - -base64Encoder=BASE64_ENCODER -const=CONST -date=DATE -dns=DNS -env=ENVIRONMENT -file=FILE -java=JAVA -localhost=LOCAL_HOST -properties=PROPERTIES -resourceBundle=RESOURCE_BUNDLE -script=SCRIPT -sys=SYSTEM_PROPERTIES -url=URL -urlDecoder=URL_DECODER -urlEncoder=URL_ENCODER -xml=XML diff --git a/src/site/xdoc/userguide.xml b/src/site/xdoc/userguide.xml index a78902d573..db7147129c 100644 --- a/src/site/xdoc/userguide.xml +++ b/src/site/xdoc/userguide.xml @@ -105,7 +105,7 @@ limitations under the License.

    The available substitutions are defined in - org.apache.commons.text.lookup.StringLookupFactory: + org.apache.commons.text.lookup.StringLookupFactory.

    @@ -181,36 +181,39 @@ limitations under the License.

    Provides algorithms for looking up strings used by a StringSubstitutor. - where you can select which lookup are used from - StringLookupFactory.

    + Standard lookups are defined in + StringLookupFactory + and the associated + DefaultStringLookup + enum. +

    - The SS lets you build complex strings: + The example below demonstrates use of the default lookups for StringSubstitutor in order to + construct a complex string. +

    +

    NOTE: The list of lookups available by default changed in version 1.10.0. See the documentation for + StringLookupFactory + for details and instructions on how to reproduce the previous behavior.

    - + final StringSubstitutor interpolator = StringSubstitutor.createInterpolator(); -interpolator.setEnableSubstitutionInVariables(true); // Allows for nested $'s. final String text = interpolator.replace( "Base64 Decoder: ${base64Decoder:SGVsbG9Xb3JsZCE=}\n" + "Base64 Encoder: ${base64Encoder:HelloWorld!}\n" + "Java Constant: ${const:java.awt.event.KeyEvent.VK_ESCAPE}\n" + "Date: ${date:yyyy-MM-dd}\n" + - "DNS: ${dns:address|apache.org}\n" + "Environment Variable: ${env:USERNAME}\n" + "File Content: ${file:UTF-8:src/test/resources/document.properties}\n" + "Java: ${java:version}\n" + "Localhost: ${localhost:canonical-name}\n" + "Properties File: ${properties:src/test/resources/document.properties::mykey}\n" + "Resource Bundle: ${resourceBundle:org.apache.commons.text.example.testResourceBundleLookup:mykey}\n" + - "Script: ${script:javascript:3 + 4}\n" + "System Property: ${sys:user.dir}\n" + "URL Decoder: ${urlDecoder:Hello%20World%21}\n" + "URL Encoder: ${urlEncoder:Hello World!}\n" + - "URL Content (HTTP): ${url:UTF-8:http://www.apache.org}\n" + - "URL Content (HTTPS): ${url:UTF-8:https://www.apache.org}\n" + - "URL Content (File): ${url:UTF-8:file:///${sys:user.dir}/src/test/resources/document.properties}\n" + "XML XPath: ${xml:src/test/resources/document.xml:/root/path/to/node}\n" ); - +
    diff --git a/src/test/java/org/apache/commons/text/StringSubstitutorWithInterpolatorStringLookupTest.java b/src/test/java/org/apache/commons/text/StringSubstitutorWithInterpolatorStringLookupTest.java index 62d0ae46f2..e19e7f0dd3 100644 --- a/src/test/java/org/apache/commons/text/StringSubstitutorWithInterpolatorStringLookupTest.java +++ b/src/test/java/org/apache/commons/text/StringSubstitutorWithInterpolatorStringLookupTest.java @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.Map; +import org.apache.commons.text.lookup.DefaultStringLookup; import org.apache.commons.text.lookup.StringLookup; import org.apache.commons.text.lookup.StringLookupFactory; import org.junit.jupiter.api.Assertions; @@ -91,26 +92,20 @@ public void testDefaultInterpolator() { // Used to cut and paste into the docs. // @formatter:off final StringSubstitutor interpolator = StringSubstitutor.createInterpolator(); - interpolator.setEnableSubstitutionInVariables(true); // Allows for nested $'s. final String text = interpolator.replace( "Base64 Decoder: ${base64Decoder:SGVsbG9Xb3JsZCE=}\n" + "Base64 Encoder: ${base64Encoder:HelloWorld!}\n" + "Java Constant: ${const:java.awt.event.KeyEvent.VK_ESCAPE}\n" + "Date: ${date:yyyy-MM-dd}\n" - + "DNS: ${dns:address|apache.org}\n" + "Environment Variable: ${env:USERNAME}\n" + "File Content: ${file:UTF-8:src/test/resources/org/apache/commons/text/document.properties}\n" + "Java: ${java:version}\n" + "Localhost: ${localhost:canonical-name}\n" + "Properties File: ${properties:src/test/resources/org/apache/commons/text/document.properties::mykey}\n" + "Resource Bundle: ${resourceBundle:org.apache.commons.text.example.testResourceBundleLookup:mykey}\n" - + "Script: ${script:javascript:3 + 4}\n" + "System Property: ${sys:user.dir}\n" + "URL Decoder: ${urlDecoder:Hello%20World%21}\n" + "URL Encoder: ${urlEncoder:Hello World!}\n" - + "URL Content (HTTP): ${url:UTF-8:http://www.apache.org}\n" - + "URL Content (HTTPS): ${url:UTF-8:https://www.apache.org}\n" - + "URL Content (File): ${url:UTF-8:file:///${sys:user.dir}/src/test/resources/org/apache/commons/text/document.properties}\n" + "XML XPath: ${xml:src/test/resources/org/apache/commons/text/document.xml:/root/path/to/node}\n" ); // @formatter:on @@ -121,7 +116,6 @@ public void testDefaultInterpolator() { Assertions.assertFalse(text.contains("${urlDecoder:Hello%20World%21}")); Assertions.assertFalse(text.contains("${urlEncoder:Hello World!}")); Assertions.assertFalse(text.contains("${resourceBundle:org.apache.commons.text.example.testResourceBundleLookup:mykey}")); - // System.out.println(text); } @Test public void testDefaultValueForMissingKeyInResourceBundle() { @@ -135,7 +129,8 @@ public void testDefaultValueForMissingKeyInResourceBundle() { @Test public void testDnsLookup() throws UnknownHostException { - final StringSubstitutor strSubst = StringSubstitutor.createInterpolator(); + final StringSubstitutor strSubst = + new StringSubstitutor(createInterpolatorWithLookups(DefaultStringLookup.DNS)); final String hostName = InetAddress.getLocalHost().getHostName(); Assertions.assertEquals(InetAddress.getByName(hostName).getHostAddress(), strSubst.replace("${dns:" + hostName + "}")); @@ -143,14 +138,16 @@ public void testDnsLookup() throws UnknownHostException { @Test public void testDnsLookupAddress() throws UnknownHostException { - final StringSubstitutor strSubst = StringSubstitutor.createInterpolator(); + final StringSubstitutor strSubst = + new StringSubstitutor(createInterpolatorWithLookups(DefaultStringLookup.DNS)); Assertions.assertEquals(InetAddress.getByName("apache.org").getHostAddress(), strSubst.replace("${dns:address|apache.org}")); } @Test public void testDnsLookupCanonicalName() throws UnknownHostException { - final StringSubstitutor strSubst = StringSubstitutor.createInterpolator(); + final StringSubstitutor strSubst = + new StringSubstitutor(createInterpolatorWithLookups(DefaultStringLookup.DNS)); final String address = InetAddress.getLocalHost().getHostAddress(); final InetAddress inetAddress = InetAddress.getByName(address); Assertions.assertEquals(inetAddress.getCanonicalHostName(), @@ -159,7 +156,8 @@ public void testDnsLookupCanonicalName() throws UnknownHostException { @Test public void testDnsLookupName() throws UnknownHostException { - final StringSubstitutor strSubst = StringSubstitutor.createInterpolator(); + final StringSubstitutor strSubst = + new StringSubstitutor(createInterpolatorWithLookups(DefaultStringLookup.DNS)); final String address = InetAddress.getLocalHost().getHostAddress(); final InetAddress inetAddress = InetAddress.getByName(address); Assertions.assertEquals(inetAddress.getHostName(), strSubst.replace("${dns:name|" + address + "}")); @@ -167,7 +165,8 @@ public void testDnsLookupName() throws UnknownHostException { @Test public void testDnsLookupNameUntrimmed() throws UnknownHostException { - final StringSubstitutor strSubst = StringSubstitutor.createInterpolator(); + final StringSubstitutor strSubst = + new StringSubstitutor(createInterpolatorWithLookups(DefaultStringLookup.DNS)); final String address = InetAddress.getLocalHost().getHostAddress(); final InetAddress inetAddress = InetAddress.getByName(address); Assertions.assertEquals(inetAddress.getHostName(), strSubst.replace("${dns:name| " + address + " }")); @@ -175,17 +174,34 @@ public void testDnsLookupNameUntrimmed() throws UnknownHostException { @Test public void testDnsLookupUnknown() { - final StringSubstitutor strSubst = StringSubstitutor.createInterpolator(); + final StringSubstitutor strSubst = + new StringSubstitutor(createInterpolatorWithLookups(DefaultStringLookup.DNS)); final String unknown = "${dns: u n k n o w n}"; Assertions.assertEquals(unknown, strSubst.replace(unknown)); } + @Test + public void testDnsLookup_disabledByDefault() throws UnknownHostException { + final StringSubstitutor strSubst = StringSubstitutor.createInterpolator(); + final String hostName = InetAddress.getLocalHost().getHostName(); + final String input = "${dns:" + hostName + "}"; + Assertions.assertEquals(input, strSubst.replace(input)); + } + @Test public void testJavaScript() { - Assertions.assertEquals("Hello World!", - StringSubstitutor.createInterpolator().replace("${script:javascript:\"Hello World!\"}")); - Assertions.assertEquals("7", - StringSubstitutor.createInterpolator().replace("${script:javascript:3 + 4}")); + final StringSubstitutor strSubst = + new StringSubstitutor(createInterpolatorWithLookups(DefaultStringLookup.SCRIPT)); + + Assertions.assertEquals("Hello World!", strSubst.replace("${script:javascript:\"Hello World!\"}")); + Assertions.assertEquals("7", strSubst.replace("${script:javascript:3 + 4}")); + } + + @Test + public void testJavaScript_disabledByDefault() { + final StringSubstitutor strSubst = StringSubstitutor.createInterpolator(); + + Assertions.assertEquals("${script:javascript:3 + 4}", strSubst.replace("${script:javascript:3 + 4}")); } @Test @@ -236,4 +252,12 @@ public void testSystemPropertyDefaultStringLookup() { Assertions.assertEquals(System.getProperty(spKey), strSubst.replace("${sys:" + spKey + "}")); } + private static StringLookup createInterpolatorWithLookups(final DefaultStringLookup... lookups) { + final Map lookupMap = new HashMap<>(); + for (final DefaultStringLookup lookup : lookups) { + lookupMap.put(lookup.getKey().toLowerCase(), lookup.getStringLookup()); + } + + return StringLookupFactory.INSTANCE.interpolatorStringLookup(lookupMap, null, false); + } } diff --git a/src/test/java/org/apache/commons/text/lookup/StringLookupFactoryTest.java b/src/test/java/org/apache/commons/text/lookup/StringLookupFactoryTest.java index 71dcbc0ae8..20614bfb74 100644 --- a/src/test/java/org/apache/commons/text/lookup/StringLookupFactoryTest.java +++ b/src/test/java/org/apache/commons/text/lookup/StringLookupFactoryTest.java @@ -16,10 +16,11 @@ */ package org.apache.commons.text.lookup; -import static org.junit.jupiter.api.Assertions.assertTrue; - import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Properties; +import java.util.Set; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -30,24 +31,23 @@ public class StringLookupFactoryTest { public static void assertDefaultKeys(final Map stringLookupMap) { - assertTrue(stringLookupMap.containsKey(InterpolatorStringLookup.toKey(StringLookupFactory.KEY_BASE64_DECODER))); - assertTrue(stringLookupMap.containsKey(InterpolatorStringLookup.toKey(StringLookupFactory.KEY_BASE64_ENCODER))); - assertTrue(stringLookupMap.containsKey(InterpolatorStringLookup.toKey(StringLookupFactory.KEY_CONST))); - assertTrue(stringLookupMap.containsKey(InterpolatorStringLookup.toKey(StringLookupFactory.KEY_DATE))); - assertTrue(stringLookupMap.containsKey(InterpolatorStringLookup.toKey(StringLookupFactory.KEY_DNS))); - assertTrue(stringLookupMap.containsKey(InterpolatorStringLookup.toKey(StringLookupFactory.KEY_ENV))); - assertTrue(stringLookupMap.containsKey(InterpolatorStringLookup.toKey(StringLookupFactory.KEY_FILE))); - assertTrue(stringLookupMap.containsKey(InterpolatorStringLookup.toKey(StringLookupFactory.KEY_JAVA))); - assertTrue(stringLookupMap.containsKey(InterpolatorStringLookup.toKey(StringLookupFactory.KEY_LOCALHOST))); - assertTrue(stringLookupMap.containsKey(InterpolatorStringLookup.toKey(StringLookupFactory.KEY_PROPERTIES))); - assertTrue( - stringLookupMap.containsKey(InterpolatorStringLookup.toKey(StringLookupFactory.KEY_RESOURCE_BUNDLE))); - assertTrue(stringLookupMap.containsKey(InterpolatorStringLookup.toKey(StringLookupFactory.KEY_SCRIPT))); - assertTrue(stringLookupMap.containsKey(InterpolatorStringLookup.toKey(StringLookupFactory.KEY_SYS))); - assertTrue(stringLookupMap.containsKey(InterpolatorStringLookup.toKey(StringLookupFactory.KEY_URL))); - assertTrue(stringLookupMap.containsKey(InterpolatorStringLookup.toKey(StringLookupFactory.KEY_URL_DECODER))); - assertTrue(stringLookupMap.containsKey(InterpolatorStringLookup.toKey(StringLookupFactory.KEY_URL_ENCODER))); - assertTrue(stringLookupMap.containsKey(InterpolatorStringLookup.toKey(StringLookupFactory.KEY_XML))); + // included + assertMappedLookups(stringLookupMap, + "base64", + StringLookupFactory.KEY_BASE64_DECODER, + StringLookupFactory.KEY_BASE64_ENCODER, + StringLookupFactory.KEY_CONST, + StringLookupFactory.KEY_DATE, + StringLookupFactory.KEY_ENV, + StringLookupFactory.KEY_FILE, + StringLookupFactory.KEY_JAVA, + StringLookupFactory.KEY_LOCALHOST, + StringLookupFactory.KEY_PROPERTIES, + StringLookupFactory.KEY_RESOURCE_BUNDLE, + StringLookupFactory.KEY_SYS, + StringLookupFactory.KEY_URL_DECODER, + StringLookupFactory.KEY_URL_ENCODER, + StringLookupFactory.KEY_XML); } @Test @@ -90,4 +90,142 @@ public void testSingletons() { Assertions.assertSame(UrlStringLookup.INSTANCE, stringLookupFactory.urlStringLookup()); Assertions.assertSame(XmlStringLookup.INSTANCE, stringLookupFactory.xmlStringLookup()); } + + @Test + public void testDefaultStringLookupsHolder_lookupsPropertyNotPresent() { + checkDefaultStringLookupsHolder(new Properties(), + "base64", + StringLookupFactory.KEY_BASE64_DECODER, + StringLookupFactory.KEY_BASE64_ENCODER, + StringLookupFactory.KEY_CONST, + StringLookupFactory.KEY_DATE, + StringLookupFactory.KEY_ENV, + StringLookupFactory.KEY_FILE, + StringLookupFactory.KEY_JAVA, + StringLookupFactory.KEY_LOCALHOST, + StringLookupFactory.KEY_PROPERTIES, + StringLookupFactory.KEY_RESOURCE_BUNDLE, + StringLookupFactory.KEY_SYS, + StringLookupFactory.KEY_URL_DECODER, + StringLookupFactory.KEY_URL_ENCODER, + StringLookupFactory.KEY_XML); + } + + @Test + public void testDefaultStringLookupsHolder_lookupsPropertyEmptyAndBlank() { + final Properties propsWithNull = new Properties(); + propsWithNull.setProperty(StringLookupFactory.DEFAULT_STRING_LOOKUPS_PROPERTY, ""); + + checkDefaultStringLookupsHolder(propsWithNull); + + final Properties propsWithBlank = new Properties(); + propsWithBlank.setProperty(StringLookupFactory.DEFAULT_STRING_LOOKUPS_PROPERTY, " "); + + checkDefaultStringLookupsHolder(propsWithBlank); + } + + @Test + public void testDefaultStringLookupsHolder_givenSingleLookup() { + final Properties props = new Properties(); + props.setProperty(StringLookupFactory.DEFAULT_STRING_LOOKUPS_PROPERTY, "base64_encoder"); + + checkDefaultStringLookupsHolder(props, + "base64", + StringLookupFactory.KEY_BASE64_ENCODER); + } + + @Test + public void testDefaultStringLookupsHolder_givenSingleLookup_weirdString() { + final Properties props = new Properties(); + props.setProperty(StringLookupFactory.DEFAULT_STRING_LOOKUPS_PROPERTY, " \n \t ,, DnS , , "); + + checkDefaultStringLookupsHolder(props, StringLookupFactory.KEY_DNS); + } + + @Test + public void testDefaultStringLookupsHolder_multipleLookups() { + final Properties props = new Properties(); + props.setProperty(StringLookupFactory.DEFAULT_STRING_LOOKUPS_PROPERTY, "dns, url script "); + + checkDefaultStringLookupsHolder(props, + StringLookupFactory.KEY_DNS, + StringLookupFactory.KEY_URL, + StringLookupFactory.KEY_SCRIPT); + } + + @Test + public void testDefaultStringLookupsHolder_allLookups() { + final Properties props = new Properties(); + props.setProperty(StringLookupFactory.DEFAULT_STRING_LOOKUPS_PROPERTY, + "BASE64_DECODER BASE64_ENCODER const, date, dns, environment " + + "file ,java, local_host properties, resource_bundle,script,system_properties " + + "url url_decoder , url_encoder, xml"); + + checkDefaultStringLookupsHolder(props, + "base64", + StringLookupFactory.KEY_BASE64_DECODER, + StringLookupFactory.KEY_BASE64_ENCODER, + StringLookupFactory.KEY_CONST, + StringLookupFactory.KEY_DATE, + StringLookupFactory.KEY_ENV, + StringLookupFactory.KEY_FILE, + StringLookupFactory.KEY_JAVA, + StringLookupFactory.KEY_LOCALHOST, + StringLookupFactory.KEY_PROPERTIES, + StringLookupFactory.KEY_RESOURCE_BUNDLE, + StringLookupFactory.KEY_SYS, + StringLookupFactory.KEY_URL_DECODER, + StringLookupFactory.KEY_URL_ENCODER, + StringLookupFactory.KEY_XML, + + StringLookupFactory.KEY_DNS, + StringLookupFactory.KEY_URL, + StringLookupFactory.KEY_SCRIPT); + } + + @Test + public void testDefaultStringLookupsHolder_invalidLookupsDefinition() { + final Properties props = new Properties(); + props.setProperty(StringLookupFactory.DEFAULT_STRING_LOOKUPS_PROPERTY, "base64_encoder nope"); + + final Exception exc = Assertions.assertThrows(IllegalArgumentException.class, + () -> new StringLookupFactory.DefaultStringLookupsHolder(props)); + Assertions.assertEquals("Invalid default string lookups definition: base64_encoder nope", exc.getMessage()); + } + + private static void checkDefaultStringLookupsHolder(final Properties props, final String... keys) { + final StringLookupFactory.DefaultStringLookupsHolder holder = + new StringLookupFactory.DefaultStringLookupsHolder(props); + + final Map lookupMap = holder.getDefaultStringLookups(); + + assertMappedLookups(lookupMap, keys); + } + + private static void assertMappedLookups(final Map lookupMap, final String... keys) { + final Set remainingKeys = new HashSet<>(lookupMap.keySet()); + + for (final String key : keys) { + final String normalizedKey = StringLookupFactory.toKey(key); + Assertions.assertNotNull(normalizedKey, () -> "Expected map to contain string lookup for key " + key); + + remainingKeys.remove(normalizedKey); + } + + Assertions.assertTrue(remainingKeys.isEmpty(), () -> "Unexpected keys in lookup map: " + remainingKeys); + } + + /** + * Main method used to verify the default string lookups resolved during JVM execution. + * @param args + */ + public static void main(final String[] args) { + final Map lookupMap = new HashMap<>(); + StringLookupFactory.INSTANCE.addDefaultStringLookups(lookupMap); + + System.out.println("Default string lookups"); + for (final String key : lookupMap.keySet()) { + System.out.println("- " + key); + } + } } diff --git a/src/test/resources/org/apache/commons/text/lookup/defaults.properties b/src/test/resources/org/apache/commons/text/lookup/defaults.properties deleted file mode 100644 index ef2568bc49..0000000000 --- a/src/test/resources/org/apache/commons/text/lookup/defaults.properties +++ /dev/null @@ -1,38 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# Note: -# base64 and base64Decoder are aliases. -# base64 is deprecated - -base64=BASE64_DECODER -base64Decoder=BASE64_DECODER - -base64Encoder=BASE64_ENCODER -const=CONST -date=DATE -dns=DNS -env=ENVIRONMENT -file=FILE -java=JAVA -localhost=LOCAL_HOST -properties=PROPERTIES -resourceBundle=RESOURCE_BUNDLE -script=SCRIPT -sys=SYSTEM_PROPERTIES -url=URL -urlDecoder=URL_DECODER -urlEncoder=URL_ENCODER -xml=XML
    Functional String Lookups