You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2022/09/23 16:16:34 UTC

[commons-text] branch master updated: Make default string lookups configurable via system property. Remove dns, url, and script lookups from defaults. (#341)

This is an automated email from the ASF dual-hosted git repository.

ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-text.git


The following commit(s) were added to refs/heads/master by this push:
     new b9b40b90 Make default string lookups configurable via system property. Remove dns, url, and script lookups from defaults. (#341)
b9b40b90 is described below

commit b9b40b903e2d1f9935039803c9852439576780ea
Author: Matt Juntunen <da...@users.noreply.github.com>
AuthorDate: Fri Sep 23 12:16:27 2022 -0400

    Make default string lookups configurable via system property. Remove dns, url, and script lookups from defaults. (#341)
    
    Co-authored-by: Gary Gregory <ga...@users.noreply.github.com>
---
 src/changes/changes.xml                            |   3 +-
 .../org/apache/commons/text/StringSubstitutor.java |  94 ++++++-
 .../commons/text/lookup/DefaultStringLookup.java   |  84 ++++--
 .../text/lookup/InterpolatorStringLookup.java      |   9 +-
 .../commons/text/lookup/StringLookupFactory.java   | 301 +++++++++++++++------
 .../apache/commons/text/lookup/defaults.properties |  38 ---
 src/site/xdoc/userguide.xml                        |  27 +-
 ...ubstitutorWithInterpolatorStringLookupTest.java |  58 ++--
 .../text/lookup/StringLookupFactoryTest.java       | 178 ++++++++++--
 .../apache/commons/text/lookup/defaults.properties |  38 ---
 10 files changed, 580 insertions(+), 250 deletions(-)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 398cf42b..de2efd79 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -71,6 +71,7 @@ The <action> type attribute can be add,update,fix,remove.
     <action issue="TEXT-209" type="fix" dev="ggregory" due-to="Arturo Bernal">Use Math.min() call instead of doing it manually. #335.</action>
     <action                  type="fix" dev="ggregory" due-to="ValentijnvdBeek, Gary Gregory">TextStringBuilder: Throw OutOfMemoryError instead of NegativeArraySizeException.</action>
     <action                  type="fix" dev="ggregory" due-to="ValentijnvdBeek, Gary Gregory">TextStringBuilder: Can't grow to sizes up to Integer.MAX_VALUE.</action>
+    <action                  type="fix" dev="mattjuntunen">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.</action>
     <!-- ADD -->
     <action issue="TEXT-207" type="add" dev="mattjuntunen">Add DoubleFormat utility.</action>
     <action issue="TEXT-190" type="add" dev="kinow" due-to="Benjamin Bing">Document negative limit for WordUtils abbreviate method</action>
@@ -78,8 +79,6 @@ The <action> type attribute can be add,update,fix,remove.
     <action issue="TEXT-185" type="add" dev="ggregory" due-to="Larry West, Gary Gregory">Release Notes page hasn't been updated for 1.9 release yet.</action>
     <action                  type="add" dev="ggregory" due-to="Gary Gregory">Add StrBuilder.isNotEmpty().</action>
     <!-- UPDATE -->
-    <action                  type="update" dev="kinow" due-to="Dependabot">Bump actions/cache from v2 to v3.0.8 #205, #217, #234, #339.</action>
-    <action                  type="update" dev="kinow" due-to="Dependabot, Gary Gregory">Bump actions/checkout from 1 to 3.0.2 #138, #146, #165, #183, #274, #279, #304.</action>
     <action                  type="update" dev="kinow" due-to="Dependabot">Bump actions/setup-java from v1.4.0 to 3 #147, #156, #155, #172, #215, #314.</action>
     <action                  type="update" dev="kinow" due-to="Dependabot">Bump github/codeql-action from 1 to 2 #319.</action>
     <action                  type="update" dev="ggregory" due-to="Dependabot">Bump checkstyle from 8.34 to 9.3, #141, #168, #182, #188, #193, #201, #208, #211, #228, #235, #245, #253, #255, #262, #270, #280, #287, #299, #315, #321.</action>
diff --git a/src/main/java/org/apache/commons/text/StringSubstitutor.java b/src/main/java/org/apache/commons/text/StringSubstitutor.java
index 49672750..3b1e4cb7 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 @@ import org.apache.commons.text.matcher.StringMatcherFactory;
  *
  * <pre>
  * 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");
  * </pre>
  * <p>
- * For documentation of each lookup, see {@link StringLookupFactory}.
+ * For documentation and a full list of available lookups, see {@link StringLookupFactory}.
+ * </p>
+ * <p><strong>NOTE:</strong> 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.
  * </p>
  *
  * <h2>Using Recursive Variable Replacement</h2>
@@ -292,9 +294,81 @@ public class StringSubstitutor {
      *
      * <pre>
      * StringSubstitutor.createInterpolator().replace(
-     *   "OS name: ${sys:os.name}, " + "3 + 4 = ${script:javascript:3 + 4}");
+     *   "OS name: ${sys:os.name}, user: ${env:USER}");
      * </pre>
      *
+     * <p>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.</p>
+     *
+     * <p><strong>NOTE:</strong> 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.
+     * </p>
+     *
+     * <table>
+     * <caption>Default Lookups</caption>
+     * <tr>
+     * <th>Key</th>
+     * <th>Lookup</th>
+     * </tr>
+     * <tr>
+     * <td>{@value StringLookupFactory#KEY_BASE64_DECODER}</td>
+     * <td>{@link StringLookupFactory#base64DecoderStringLookup()}</td>
+     * </tr>
+     * <tr>
+     * <td>{@value StringLookupFactory#KEY_BASE64_ENCODER}</td>
+     * <td>{@link StringLookupFactory#base64EncoderStringLookup()}</td>
+     * </tr>
+     * <tr>
+     * <td>{@value StringLookupFactory#KEY_CONST}</td>
+     * <td>{@link StringLookupFactory#constantStringLookup()}</td>
+     * </tr>
+     * <tr>
+     * <td>{@value StringLookupFactory#KEY_DATE}</td>
+     * <td>{@link StringLookupFactory#dateStringLookup()}</td>
+     * </tr>
+     * <tr>
+     * <td>{@value StringLookupFactory#KEY_ENV}</td>
+     * <td>{@link StringLookupFactory#environmentVariableStringLookup()}</td>
+     * </tr>
+     * <tr>
+     * <td>{@value StringLookupFactory#KEY_FILE}</td>
+     * <td>{@link StringLookupFactory#fileStringLookup()}</td>
+     * </tr>
+     * <tr>
+     * <td>{@value StringLookupFactory#KEY_JAVA}</td>
+     * <td>{@link StringLookupFactory#javaPlatformStringLookup()}</td>
+     * </tr>
+     * <tr>
+     * <td>{@value StringLookupFactory#KEY_LOCALHOST}</td>
+     * <td>{@link StringLookupFactory#localHostStringLookup()}</td>
+     * </tr>
+     * <tr>
+     * <td>{@value StringLookupFactory#KEY_PROPERTIES}</td>
+     * <td>{@link StringLookupFactory#propertiesStringLookup()}</td>
+     * </tr>
+     * <tr>
+     * <td>{@value StringLookupFactory#KEY_RESOURCE_BUNDLE}</td>
+     * <td>{@link StringLookupFactory#resourceBundleStringLookup()}</td>
+     * </tr>
+     * <tr>
+     * <td>{@value StringLookupFactory#KEY_SYS}</td>
+     * <td>{@link StringLookupFactory#systemPropertyStringLookup()}</td>
+     * </tr>
+     * <tr>
+     * <td>{@value StringLookupFactory#KEY_URL_DECODER}</td>
+     * <td>{@link StringLookupFactory#urlDecoderStringLookup()}</td>
+     * </tr>
+     * <tr>
+     * <td>{@value StringLookupFactory#KEY_URL_ENCODER}</td>
+     * <td>{@link StringLookupFactory#urlEncoderStringLookup()}</td>
+     * </tr>
+     * <tr>
+     * <td>{@value StringLookupFactory#KEY_XML}</td>
+     * <td>{@link StringLookupFactory#xmlStringLookup()}</td>
+     * </tr>
+     * </table>
+     *
      * @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 ce01c81e..c23d97dd 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}.
  * <p>
  * This enum was adapted and expanded from Apache Commons Configuration 2.4.
  * </p>
+ * <p><strong>NOTE:</strong> 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.
+ * </p>
  *
  * @see StringLookupFactory
  * @see StringLookup
@@ -31,100 +34,125 @@ import java.util.stream.Stream;
 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 7a63ef61..72b36c31 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 @@ package org.apache.commons.text.lookup;
 
 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 @@ class InterpolatorStringLookup extends AbstractStringLookup {
         this.defaultStringLookup = defaultStringLookup;
         this.stringLookupMap = new HashMap<>(stringLookupMap.size());
         for (final Entry<String, StringLookup> 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 @@ class InterpolatorStringLookup extends AbstractStringLookup {
 
         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 54cd61de..bd397f87 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 @@ import org.apache.commons.text.StringSubstitutor;
  * <li>{@link #interpolatorStringLookup(Map, StringLookup, boolean)}.</li>
  * </ul>
  * <p>
- * 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)}.
  * </p>
  * <table>
  * <caption>Default String Lookups</caption>
@@ -81,12 +91,6 @@ import org.apache.commons.text.StringSubstitutor;
  * <td>1.5</td>
  * </tr>
  * <tr>
- * <td>{@value #KEY_DNS}</td>
- * <td>{@link StringLookup}</td>
- * <td>{@link #dnsStringLookup()}</td>
- * <td>1.8</td>
- * </tr>
- * <tr>
  * <td>{@value #KEY_ENV}</td>
  * <td>{@link StringLookup}</td>
  * <td>{@link #environmentVariableStringLookup()}</td>
@@ -123,24 +127,12 @@ import org.apache.commons.text.StringSubstitutor;
  * <td>1.6</td>
  * </tr>
  * <tr>
- * <td>{@value #KEY_SCRIPT}</td>
- * <td>{@link StringLookup}</td>
- * <td>{@link #scriptStringLookup()}</td>
- * <td>1.5</td>
- * </tr>
- * <tr>
  * <td>{@value #KEY_SYS}</td>
  * <td>{@link StringLookup}</td>
  * <td>{@link #systemPropertyStringLookup()}</td>
  * <td>1.3</td>
  * </tr>
  * <tr>
- * <td>{@value #KEY_URL}</td>
- * <td>{@link StringLookup}</td>
- * <td>{@link #urlStringLookup()}</td>
- * <td>1.5</td>
- * </tr>
- * <tr>
  * <td>{@value #KEY_URL_DECODER}</td>
  * <td>{@link StringLookup}</td>
  * <td>{@link #urlDecoderStringLookup()}</td>
@@ -159,8 +151,37 @@ import org.apache.commons.text.StringSubstitutor;
  * <td>1.5</td>
  * </tr>
  * </table>
+ *
+ * <table>
+ * <caption>Additional String Lookups (not included by default)</caption>
+ * <tr>
+ * <th>Key</th>
+ * <th>Interface</th>
+ * <th>Factory Method</th>
+ * <th>Since</th>
+ * </tr>
+ * <tr>
+ * <td>{@value #KEY_DNS}</td>
+ * <td>{@link StringLookup}</td>
+ * <td>{@link #dnsStringLookup()}</td>
+ * <td>1.8</td>
+ * </tr>
+ * <tr>
+ * <td>{@value #KEY_URL}</td>
+ * <td>{@link StringLookup}</td>
+ * <td>{@link #urlStringLookup()}</td>
+ * <td>1.5</td>
+ * </tr>
+ * <tr>
+ * <td>{@value #KEY_SCRIPT}</td>
+ * <td>{@link StringLookup}</td>
+ * <td>{@link #scriptStringLookup()}</td>
+ * <td>1.5</td>
+ * </tr>
+ * </table>
+ *
  * <p>
- * 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.
  * <table>
  * <caption>Functional String Lookups</caption>
  * <tr>
@@ -184,16 +205,6 @@ import org.apache.commons.text.StringSubstitutor;
  */
 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 {
      * </p>
      *
      * <pre>
-     * StringLookupFactory.INSTANCE.dateStringLookup().lookup("USER");
+     * StringLookupFactory.INSTANCE.environmentVariableStringLookup().lookup("USER");
      * </pre>
      * <p>
      * 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 final class StringLookupFactory {
         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 @@ public final class 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<String, StringLookup> 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 final class StringLookupFactory {
      * The above examples convert {@code "SGVsbG9Xb3JsZCE="} to {@code "HelloWorld!"}.
      * </p>
      *
-     * @return The DateStringLookup singleton instance.
+     * @return The Base64DecoderStringLookup singleton instance.
      * @since 1.5
      */
     public StringLookup base64DecoderStringLookup() {
@@ -498,7 +510,7 @@ public final class StringLookupFactory {
      * The above examples convert {@code } to {@code "SGVsbG9Xb3JsZCE="}.
      * </p>
      *
-     * @return The DateStringLookup singleton instance.
+     * @return The Base64EncoderStringLookup singleton instance.
      * @since 1.6
      */
     public StringLookup base64EncoderStringLookup() {
@@ -525,7 +537,7 @@ public final class StringLookupFactory {
      * The above examples convert {@code "SGVsbG9Xb3JsZCE="} to {@code "HelloWorld!"}.
      * </p>
      *
-     * @return The DateStringLookup singleton instance.
+     * @return The Base64DecoderStringLookup singleton instance.
      * @since 1.5
      * @deprecated Use {@link #base64DecoderStringLookup()}.
      */
@@ -579,7 +591,7 @@ public final class StringLookupFactory {
      * The above examples convert {@code java.awt.event.KeyEvent.VK_ESCAPE} to {@code "27"}.
      * </p>
      *
-     * @return The DateStringLookup singleton instance.
+     * @return The ConstantStringLookup singleton instance.
      * @since 1.5
      */
     public StringLookup constantStringLookup() {
@@ -629,17 +641,24 @@ public final class StringLookupFactory {
      * StringLookupFactory.INSTANCE.dnsStringLookup().lookup("address|apache.org");
      * </pre>
      * <p>
-     * 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).
      * </p>
      *
      * <pre>
-     * StringSubstitutor.createInterpolator().replace("... ${dns:address|apache.org} ..."));
+     * Map&lt;String, StringLookup&gt; lookupMap = new HashMap&lt;&gt;();
+     * lookupMap.put("dns", StringLookupFactory.INSTANCE.dnsStringLookup());
+     *
+     * StringLookup variableResolver = StringLookupFactory.INSTANCE.interpolatorStringLookup(lookupMap, null, false);
+     *
+     * new StringSubstitutor(variableResolver).replace("... ${dns:address|apache.org} ...");
      * </pre>
      * <p>
-     * 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}.
      * </p>
      *
-     * @return the DateStringLookup singleton instance.
+     * @return the DnsStringLookup singleton instance.
      * @since 1.8
      */
     public StringLookup dnsStringLookup() {
@@ -654,7 +673,7 @@ public final class StringLookupFactory {
      * </p>
      *
      * <pre>
-     * StringLookupFactory.INSTANCE.dateStringLookup().lookup("USER");
+     * StringLookupFactory.INSTANCE.environmentVariableStringLookup().lookup("USER");
      * </pre>
      * <p>
      * Using a {@link StringSubstitutor}:
@@ -715,10 +734,9 @@ public final class StringLookupFactory {
     }
 
     /**
-     * Returns the default {@link InterpolatorStringLookup} configured with the {@link StringLookupFactory default lookups}.
-     * <p>
-     * The lookups available to an interpolator are defined in
-     * </p>
+     * Returns a {@link InterpolatorStringLookup} containing the configured
+     * {@link #addDefaultStringLookups(Map) default lookups}. See the class documentation for
+     * details on how these defaults are configured.
      * <p>
      * Using a {@link StringLookup} from the {@link StringLookupFactory}:
      * </p>
@@ -744,15 +762,15 @@ public final class StringLookupFactory {
     }
 
     /**
-     * Returns a new InterpolatorStringLookup using the {@link StringLookupFactory default lookups}.
-     * <p>
-     * If {@code addDefaultLookups} is true, the following lookups are used in addition to the ones provided in
-     * {@code stringLookupMap}:
-     * </p>
+     * 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 final class StringLookupFactory {
     }
 
     /**
-     * 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 <V> the value type the default string lookup's map.
      * @param map the default map for string lookups.
@@ -773,7 +793,9 @@ public final class StringLookupFactory {
     }
 
     /**
-     * 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 final class StringLookupFactory {
     }
 
     /**
-     * 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.
      * <p>
      * Looks up the value for the key in the format "ScriptEngineName:Script".
      * </p>
@@ -990,11 +1014,18 @@ public final class StringLookupFactory {
      * StringLookupFactory.INSTANCE.scriptStringLookup().lookup("javascript:3 + 4");
      * </pre>
      * <p>
-     * 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).
      * </p>
      *
      * <pre>
-     * StringSubstitutor.createInterpolator().replace("... ${javascript:3 + 4} ..."));
+     * Map&lt;String, StringLookup&gt; lookupMap = new HashMap&lt;&gt;();
+     * lookupMap.put("script", StringLookupFactory.INSTANCE.scriptStringLookup());
+     *
+     * StringLookup variableResolver = StringLookupFactory.INSTANCE.interpolatorStringLookup(lookupMap, null, false);
+     *
+     * String value = new StringSubstitutor(variableResolver).replace("${script:javascript:3 + 4}");
      * </pre>
      * <p>
      * The above examples convert {@code "javascript:3 + 4"} to {@code "7"}.
@@ -1101,7 +1132,9 @@ public final class StringLookupFactory {
     }
 
     /**
-     * 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.
      * <p>
      * Looks up the value for the key in the format "CharsetName:URL".
      * </p>
@@ -1120,11 +1153,18 @@ public final class StringLookupFactory {
      * StringLookupFactory.INSTANCE.urlStringLookup().lookup("UTF-8:https://www.apache.org");
      * </pre>
      * <p>
-     * 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).
      * </p>
      *
      * <pre>
-     * StringSubstitutor.createInterpolator().replace("... ${url:UTF-8:https://www.apache.org} ..."));
+     * Map&lt;String, StringLookup&gt; lookupMap = new HashMap&lt;&gt;();
+     * 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}");
      * </pre>
      * <p>
      * The above examples convert {@code "UTF-8:https://www.apache.org"} to the contents of that page.
@@ -1171,4 +1211,109 @@ public final class StringLookupFactory {
         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<String, StringLookup> defaultStringLookups;
+
+        /**
+         * Construct a new instance initialized with the given properties.
+         * @param props initialization properties
+         */
+        DefaultStringLookupsHolder(final Properties props) {
+            final Map<String, StringLookup> 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<String, StringLookup> getDefaultStringLookups() {
+            return defaultStringLookups;
+        }
+
+        /**
+         * Create the lookup map used when the user has requested no customization.
+         * @return default lookup map
+         */
+        private static Map<String, StringLookup> createDefaultStringLookups() {
+            final Map<String, StringLookup> 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<String, StringLookup> parseStringLookups(final String str) {
+            final Map<String, StringLookup> 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<String, StringLookup> 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 ef2568bc..00000000
--- 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 a78902d5..db714712 100644
--- a/src/site/xdoc/userguide.xml
+++ b/src/site/xdoc/userguide.xml
@@ -105,7 +105,7 @@ limitations under the License.
       </ul>
       <p>
         The available substitutions are defined in 
-        <a href="http://commons.apache.org/proper/commons-text/apidocs/org/apache/commons/text/lookup/StringLookupFactory.html">org.apache.commons.text.lookup.StringLookupFactory</a>:
+        <a href="http://commons.apache.org/proper/commons-text/apidocs/org/apache/commons/text/lookup/StringLookupFactory.html">org.apache.commons.text.lookup.StringLookupFactory</a>.
       </p>
       </subsection>
 
@@ -181,36 +181,39 @@ limitations under the License.
     <section name="text.lookup">
       <p>Provides algorithms for looking up strings used by a 
         <a href="http://commons.apache.org/proper/commons-text/apidocs/org/apache/commons/text/StringSubstitutor.html">StringSubstitutor</a>.
-        where you can select which lookup are used from 
-        <a href="http://commons.apache.org/proper/commons-text/apidocs/org/apache/commons/text/lookup/StringLookupFactory.html">StringLookupFactory</a>.</p>
+        Standard lookups are defined in
+        <a href="http://commons.apache.org/proper/commons-text/apidocs/org/apache/commons/text/lookup/StringLookupFactory.html">StringLookupFactory</a>
+        and the associated
+        <a href="http://commons.apache.org/proper/commons-text/apidocs/org/apache/commons/text/lookup/DefaultStringLookup.html">DefaultStringLookup</a>
+        enum.
+        </p>
       <p>
-        The SS lets you build complex strings:
+        The example below demonstrates use of the default lookups for <code>StringSubstitutor</code> in order to
+        construct a complex string.
+      </p>
+      <p><strong>NOTE:</strong> The list of lookups available by default changed in version 1.10.0. See the documentation for
+        <a href="http://commons.apache.org/proper/commons-text/apidocs/org/apache/commons/text/lookup/StringLookupFactory.html">StringLookupFactory</a>
+        for details and instructions on how to reproduce the previous behavior.
       </p>
-      <code>
+      <source>
 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"
 );
-      </code>
+      </source>
     </section>
 
     <section name="text.similarity">
diff --git a/src/test/java/org/apache/commons/text/StringSubstitutorWithInterpolatorStringLookupTest.java b/src/test/java/org/apache/commons/text/StringSubstitutorWithInterpolatorStringLookupTest.java
index 62d0ae46..e19e7f0d 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.net.UnknownHostException;
 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 class StringSubstitutorWithInterpolatorStringLookupTest {
         // 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 class StringSubstitutorWithInterpolatorStringLookupTest {
         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 class StringSubstitutorWithInterpolatorStringLookupTest {
 
     @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 class StringSubstitutorWithInterpolatorStringLookupTest {
 
     @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 class StringSubstitutorWithInterpolatorStringLookupTest {
 
     @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 class StringSubstitutorWithInterpolatorStringLookupTest {
 
     @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 class StringSubstitutorWithInterpolatorStringLookupTest {
 
     @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 class StringSubstitutorWithInterpolatorStringLookupTest {
         Assertions.assertEquals(System.getProperty(spKey), strSubst.replace("${sys:" + spKey + "}"));
     }
 
+    private static StringLookup createInterpolatorWithLookups(final DefaultStringLookup... lookups) {
+        final Map<String, StringLookup> 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 71dcbc0a..20614bfb 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 @@ import org.junit.jupiter.api.Test;
 public class StringLookupFactoryTest {
 
     public static void assertDefaultKeys(final Map<String, StringLookup> 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 class StringLookupFactoryTest {
         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<String, StringLookup> lookupMap = holder.getDefaultStringLookups();
+
+        assertMappedLookups(lookupMap, keys);
+    }
+
+    private static void assertMappedLookups(final Map<String, StringLookup> lookupMap, final String... keys) {
+        final Set<String> 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<String, StringLookup> 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 ef2568bc..00000000
--- 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