Merge branch 'master' of https://github.com/nickwongwong/commons-text
diff --git a/pom.xml b/pom.xml
index 1c2c720..f79cfd3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -78,7 +78,7 @@
     <dependency>
       <groupId>org.apache.commons</groupId>
       <artifactId>commons-lang3</artifactId>
-      <version>3.8</version>
+      <version>3.8.1</version>
     </dependency>
     <!-- testing -->
     <dependency>
@@ -357,6 +357,9 @@
     <contributor>
       <name>Jan Martin Keil</name>
     </contributor>
+    <contributor>
+      <name>Nandor Kollar</name>
+    </contributor>
   </contributors>
 
   <scm>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 8ff0a07..91d507d 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -46,14 +46,19 @@
   <body>
 
   <release version="1.5" date="2018-MM-DD" description="Release 1.5">
+    <action issue="TEXT-118" type="fix" dev="chtompki" due-to="Nandor Kollar">JSON escaping incorrect for the delete control character</action>
     <action issue="TEXT-130" type="fix" dev="chtompki" due-to="Jan Martin Keil">Fixes JaroWinklerDistance: Wrong results due to precision of transpositions</action>
     <action issue="TEXT-131" type="fix" dev="chtompki" due-to="Jan Martin Keil">JaroWinklerDistance: Calculation deviates from definition</action>
-    <action issue="TEXT-132" type="update" dev="ggregory">Update Apache Commons Lang from 3.7 to 3.8.</action>
+    <action issue="TEXT-132" type="update" dev="ggregory">Update Apache Commons Lang from 3.7 to 3.8.1</action>
     <action issue="TEXT-133" type="add" dev="ggregory">Add a XML file XPath string lookup.</action>
     <action issue="TEXT-134" type="add" dev="ggregory">Add a Properties file string lookup.</action>
     <action issue="TEXT-135" type="add" dev="ggregory">Add a script string lookup.</action>
     <action issue="TEXT-136" type="add" dev="ggregory">Add a file string lookup.</action>
     <action issue="TEXT-137" type="add" dev="ggregory">Add a URL string lookup.</action>
+    <action issue="TEXT-140" type="add" dev="ggregory">Add a Base64 string lookup.</action>
+    <action issue="TEXT-141" type="add" dev="ggregory">Add org.apache.commons.text.lookup.StringLookupFactory.resourceBundleStringLookup(String).</action>
+    <action issue="TEXT-142" type="add" dev="ggregory">Add URL encoder and decoder string lookups.</action>
+    <action issue="TEXT-143" type="add" dev="ggregory">Add constant string lookup like the one in Apache Commons Configuration.</action>
   </release>
 
   <release version="1.4" date="2018-06-12" description="Release 1.4">
diff --git a/src/main/java/org/apache/commons/text/StrLookup.java b/src/main/java/org/apache/commons/text/StrLookup.java
index 8d3811a..b197e1d 100644
--- a/src/main/java/org/apache/commons/text/StrLookup.java
+++ b/src/main/java/org/apache/commons/text/StrLookup.java
@@ -1,212 +1,213 @@
-/*

- * 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.

- */

-package org.apache.commons.text;

-

-import java.util.Map;

-import java.util.ResourceBundle;

-

-import org.apache.commons.text.lookup.StringLookup;

-import org.apache.commons.text.lookup.StringLookupFactory;

-

-/**

- * Lookup a String key to a String value.

- * <p>

- * This class represents the simplest form of a string to string map. It has a benefit over a map in that it can create

- * the result on demand based on the key.

- * <p>

- * This class comes complete with various factory methods. If these do not suffice, you can subclass and implement your

- * own matcher.

- * <p>

- * For example, it would be possible to implement a lookup that used the key as a primary key, and looked up the value

- * on demand from the database

- *

- * @param <V> the type of the values supported by the lookup

- * @since 1.0

- * @deprecated Deprecated as of 1.3, use {@link StringLookupFactory} instead. This class will be removed in 2.0.

- */

-@Deprecated

-public abstract class StrLookup<V> implements StringLookup {

-

-    /**

-     * Lookup that always returns null.

-     */

-    private static final StrLookup<String> NONE_LOOKUP = new MapStrLookup<>(null);

-

-    /**

-     * Lookup based on system properties.

-     */

-    private static final StrLookup<String> SYSTEM_PROPERTIES_LOOKUP = new SystemPropertiesStrLookup();

-

-    // -----------------------------------------------------------------------

-    /**

-     * Returns a lookup which always returns null.

-     *

-     * @return a lookup that always returns null, not null

-     */

-    public static StrLookup<?> noneLookup() {

-        return NONE_LOOKUP;

-    }

-

-    /**

-     * Returns a new lookup which uses a copy of the current {@link System#getProperties() System properties}.

-     * <p>

-     * If a security manager blocked access to system properties, then null will be returned from every lookup.

-     * <p>

-     * If a null key is used, this lookup will throw a NullPointerException.

-     *

-     * @return a lookup using system properties, not null

-     */

-    public static StrLookup<String> systemPropertiesLookup() {

-        return SYSTEM_PROPERTIES_LOOKUP;

-    }

-

-    /**

-     * Returns a lookup which looks up values using a map.

-     * <p>

-     * If the map is null, then null will be returned from every lookup. The map result object is converted to a string

-     * using toString().

-     *

-     * @param <V> the type of the values supported by the lookup

-     * @param map the map of keys to values, may be null

-     * @return a lookup using the map, not null

-     */

-    public static <V> StrLookup<V> mapLookup(final Map<String, V> map) {

-        return new MapStrLookup<>(map);

-    }

-

-    /**

-     * Returns a lookup which looks up values using a ResourceBundle.

-     * <p>

-     * If the ResourceBundle is null, then null will be returned from every lookup. The map result object is converted

-     * to a string using toString().

-     *

-     * @param resourceBundle the map of keys to values, may be null

-     * @return a lookup using the map, not null

-     */

-    public static StrLookup<String> resourceBundleLookup(final ResourceBundle resourceBundle) {

-        return new ResourceBundleLookup(resourceBundle);

-    }

-

-    // -----------------------------------------------------------------------

-    /**

-     * Constructor.

-     */

-    protected StrLookup() {

-        super();

-    }

-

-    // -----------------------------------------------------------------------

-    /**

-     * Lookup implementation that uses a Map.

-     *

-     * @param <V> the type of the values supported by the lookup

-     */

-    static class MapStrLookup<V> extends StrLookup<V> {

-

-        /** Map keys are variable names and value. */

-        private final Map<String, V> map;

-

-        /**

-         * Creates a new instance backed by a Map.

-         *

-         * @param map the map of keys to values, may be null

-         */

-        MapStrLookup(final Map<String, V> map) {

-            this.map = map;

-        }

-

-        /**

-         * Looks up a String key to a String value using the map.

-         * <p>

-         * If the map is null, then null is returned. The map result object is converted to a string using toString().

-         *

-         * @param key the key to be looked up, may be null

-         * @return the matching value, null if no match

-         */

-        @Override

-        public String lookup(final String key) {

-            if (map == null) {

-                return null;

-            }

-            final Object obj = map.get(key);

-            if (obj == null) {

-                return null;

-            }

-            return obj.toString();

-        }

-

-        @Override

-        public String toString() {

-            return super.toString() + " [map=" + map + "]";

-        }

-    }

-

-    // -----------------------------------------------------------------------

-    /**

-     * Lookup implementation based on a ResourceBundle.

-     */

-    private static final class ResourceBundleLookup extends StrLookup<String> {

-

-        /** ResourceBundle keys are variable names and value. */

-        private final ResourceBundle resourceBundle;

-

-        /**

-         * Creates a new instance backed by a ResourceBundle.

-         *

-         * @param resourceBundle the ResourceBundle of keys to values, may be null

-         */

-        private ResourceBundleLookup(final ResourceBundle resourceBundle) {

-            this.resourceBundle = resourceBundle;

-        }

-

-        @Override

-        public String lookup(final String key) {

-            if (resourceBundle == null || key == null || !resourceBundle.containsKey(key)) {

-                return null;

-            }

-            return resourceBundle.getString(key);

-        }

-

-        @Override

-        public String toString() {

-            return super.toString() + " [resourceBundle=" + resourceBundle + "]";

-        }

-

-    }

-

-    // -----------------------------------------------------------------------

-    /**

-     * Lookup implementation based on system properties.

-     */

-    private static final class SystemPropertiesStrLookup extends StrLookup<String> {

-        /**

-         * {@inheritDoc} This implementation directly accesses system properties.

-         */

-        @Override

-        public String lookup(final String key) {

-            if (key.length() > 0) {

-                try {

-                    return System.getProperty(key);

-                } catch (final SecurityException scex) {

-                    // Squelched. All lookup(String) will return null.

-                    return null;

-                }

-            }

-            return null;

-        }

-    }

-}

+/*
+ * 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.
+ */
+package org.apache.commons.text;
+
+import java.util.Map;
+import java.util.ResourceBundle;
+
+import org.apache.commons.text.lookup.StringLookup;
+import org.apache.commons.text.lookup.StringLookupFactory;
+
+/**
+ * Lookup a String key to a String value.
+ * <p>
+ * This class represents the simplest form of a string to string map. It has a benefit over a map in that it can create
+ * the result on demand based on the key.
+ * <p>
+ * This class comes complete with various factory methods. If these do not suffice, you can subclass and implement your
+ * own matcher.
+ * <p>
+ * For example, it would be possible to implement a lookup that used the key as a primary key, and looked up the value
+ * on demand from the database
+ *
+ * @param <V> the type of the values supported by the lookup
+ * @since 1.0
+ * @deprecated Deprecated as of 1.3, use {@link StringLookupFactory} instead. This class will be removed in 2.0.
+ */
+@Deprecated
+public abstract class StrLookup<V> implements StringLookup {
+
+    /**
+     * Lookup that always returns null.
+     */
+    private static final StrLookup<String> NONE_LOOKUP = new MapStrLookup<>(null);
+
+    /**
+     * Lookup based on system properties.
+     */
+    private static final StrLookup<String> SYSTEM_PROPERTIES_LOOKUP = new SystemPropertiesStrLookup();
+
+    // -----------------------------------------------------------------------
+    /**
+     * Returns a lookup which always returns null.
+     *
+     * @return a lookup that always returns null, not null
+     */
+    public static StrLookup<?> noneLookup() {
+        return NONE_LOOKUP;
+    }
+
+    /**
+     * Returns a new lookup which uses a copy of the current {@link System#getProperties() System properties}.
+     * <p>
+     * If a security manager blocked access to system properties, then null will be returned from every lookup.
+     * <p>
+     * If a null key is used, this lookup will throw a NullPointerException.
+     *
+     * @return a lookup using system properties, not null
+     */
+    public static StrLookup<String> systemPropertiesLookup() {
+        return SYSTEM_PROPERTIES_LOOKUP;
+    }
+
+    /**
+     * Returns a lookup which looks up values using a map.
+     * <p>
+     * If the map is null, then null will be returned from every lookup. The map result object is converted to a string
+     * using toString().
+     *
+     * @param <V> the type of the values supported by the lookup
+     * @param map the map of keys to values, may be null
+     * @return a lookup using the map, not null
+     */
+    public static <V> StrLookup<V> mapLookup(final Map<String, V> map) {
+        return new MapStrLookup<>(map);
+    }
+
+    /**
+     * Returns a lookup which looks up values using a ResourceBundle.
+     * <p>
+     * If the ResourceBundle is null, then null will be returned from every lookup. The map result object is converted
+     * to a string using toString().
+     *
+     * @param resourceBundle the map of keys to values, may be null
+     * @return a lookup using the map, not null
+     * @see StringLookupFactory#resourceBundleStringLookup(String)
+     */
+    public static StrLookup<String> resourceBundleLookup(final ResourceBundle resourceBundle) {
+        return new ResourceBundleLookup(resourceBundle);
+    }
+
+    // -----------------------------------------------------------------------
+    /**
+     * Constructor.
+     */
+    protected StrLookup() {
+        super();
+    }
+
+    // -----------------------------------------------------------------------
+    /**
+     * Lookup implementation that uses a Map.
+     *
+     * @param <V> the type of the values supported by the lookup
+     */
+    static class MapStrLookup<V> extends StrLookup<V> {
+
+        /** Map keys are variable names and value. */
+        private final Map<String, V> map;
+
+        /**
+         * Creates a new instance backed by a Map.
+         *
+         * @param map the map of keys to values, may be null
+         */
+        MapStrLookup(final Map<String, V> map) {
+            this.map = map;
+        }
+
+        /**
+         * Looks up a String key to a String value using the map.
+         * <p>
+         * If the map is null, then null is returned. The map result object is converted to a string using toString().
+         *
+         * @param key the key to be looked up, may be null
+         * @return the matching value, null if no match
+         */
+        @Override
+        public String lookup(final String key) {
+            if (map == null) {
+                return null;
+            }
+            final Object obj = map.get(key);
+            if (obj == null) {
+                return null;
+            }
+            return obj.toString();
+        }
+
+        @Override
+        public String toString() {
+            return super.toString() + " [map=" + map + "]";
+        }
+    }
+
+    // -----------------------------------------------------------------------
+    /**
+     * Lookup implementation based on a ResourceBundle.
+     */
+    private static final class ResourceBundleLookup extends StrLookup<String> {
+
+        /** ResourceBundle keys are variable names and value. */
+        private final ResourceBundle resourceBundle;
+
+        /**
+         * Creates a new instance backed by a ResourceBundle.
+         *
+         * @param resourceBundle the ResourceBundle of keys to values, may be null
+         */
+        private ResourceBundleLookup(final ResourceBundle resourceBundle) {
+            this.resourceBundle = resourceBundle;
+        }
+
+        @Override
+        public String lookup(final String key) {
+            if (resourceBundle == null || key == null || !resourceBundle.containsKey(key)) {
+                return null;
+            }
+            return resourceBundle.getString(key);
+        }
+
+        @Override
+        public String toString() {
+            return super.toString() + " [resourceBundle=" + resourceBundle + "]";
+        }
+
+    }
+
+    // -----------------------------------------------------------------------
+    /**
+     * Lookup implementation based on system properties.
+     */
+    private static final class SystemPropertiesStrLookup extends StrLookup<String> {
+        /**
+         * {@inheritDoc} This implementation directly accesses system properties.
+         */
+        @Override
+        public String lookup(final String key) {
+            if (key.length() > 0) {
+                try {
+                    return System.getProperty(key);
+                } catch (final SecurityException scex) {
+                    // Squelched. All lookup(String) will return null.
+                    return null;
+                }
+            }
+            return null;
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/text/StringEscapeUtils.java b/src/main/java/org/apache/commons/text/StringEscapeUtils.java
index 3ac66ff..b183233 100644
--- a/src/main/java/org/apache/commons/text/StringEscapeUtils.java
+++ b/src/main/java/org/apache/commons/text/StringEscapeUtils.java
@@ -108,7 +108,7 @@
         ESCAPE_JSON = new AggregateTranslator(
                 new LookupTranslator(Collections.unmodifiableMap(escapeJsonMap)),
                 new LookupTranslator(EntityArrays.JAVA_CTRL_CHARS_ESCAPE),
-                JavaUnicodeEscaper.outsideOf(32, 0x7f)
+                JavaUnicodeEscaper.outsideOf(32, 0x7e)
         );
     }
 
diff --git a/src/main/java/org/apache/commons/text/StringSubstitutor.java b/src/main/java/org/apache/commons/text/StringSubstitutor.java
index 4fcbc4b..58f6e82 100644
--- a/src/main/java/org/apache/commons/text/StringSubstitutor.java
+++ b/src/main/java/org/apache/commons/text/StringSubstitutor.java
@@ -135,6 +135,27 @@
 public class StringSubstitutor {
 
     /**
+     * The default variable default separator.
+     *
+     * @since 1.5.
+     */
+    public static final String DEFAULT_VAR_DEFAULT = ":-";
+
+    /**
+     * The default variable end separator.
+     *
+     * @since 1.5.
+     */
+    public static final String DEFAULT_VAR_END = "}";
+
+    /**
+     * The default variable start separator.
+     *
+     * @since 1.5.
+     */
+    public static final String DEFAULT_VAR_START = "${";
+
+    /**
      * Constant for the default escape character.
      */
     public static final char DEFAULT_ESCAPE = '$';
@@ -142,17 +163,17 @@
     /**
      * Constant for the default variable prefix.
      */
-    public static final StringMatcher DEFAULT_PREFIX = StringMatcherFactory.INSTANCE.stringMatcher("${");
+    public static final StringMatcher DEFAULT_PREFIX = StringMatcherFactory.INSTANCE.stringMatcher(DEFAULT_VAR_START);
 
     /**
      * Constant for the default variable suffix.
      */
-    public static final StringMatcher DEFAULT_SUFFIX = StringMatcherFactory.INSTANCE.stringMatcher("}");
+    public static final StringMatcher DEFAULT_SUFFIX = StringMatcherFactory.INSTANCE.stringMatcher(DEFAULT_VAR_END);
 
     /**
      * Constant for the default value delimiter of a variable.
      */
-    public static final StringMatcher DEFAULT_VALUE_DELIMITER = StringMatcherFactory.INSTANCE.stringMatcher(":-");
+    public static final StringMatcher DEFAULT_VALUE_DELIMITER = StringMatcherFactory.INSTANCE.stringMatcher(DEFAULT_VAR_DEFAULT);
 
     // -----------------------------------------------------------------------
     /**
diff --git a/src/main/java/org/apache/commons/text/lookup/AbstractStringLookup.java b/src/main/java/org/apache/commons/text/lookup/AbstractStringLookup.java
index 404bbdc..4dbdb65 100644
--- a/src/main/java/org/apache/commons/text/lookup/AbstractStringLookup.java
+++ b/src/main/java/org/apache/commons/text/lookup/AbstractStringLookup.java
@@ -24,6 +24,12 @@
  */
 abstract class AbstractStringLookup implements StringLookup {
 
+
+    /**
+     * The empty string.
+     */
+    private static final String EMPTY = "";
+
     /**
      * The default split char.
      */
@@ -45,7 +51,21 @@
      */
     protected String substringAfter(final String value, final char ch) {
         final int indexOf = value.indexOf(ch);
-        return indexOf > -1 ? value.substring(indexOf + 1) : "";
+        return indexOf > -1 ? value.substring(indexOf + 1) : EMPTY;
+    }
+
+    /**
+     * Returns the substring after the first occurrence of <code>ch</code> in <code>value</code>.
+     *
+     * @param value
+     *            The source string.
+     * @param ch
+     *            The character to search.
+     * @return a new string.
+     */
+    protected String substringAfterLast(final String value, final char ch) {
+        final int indexOf = value.lastIndexOf(ch);
+        return indexOf > -1 ? value.substring(indexOf + 1) : EMPTY;
     }
 
     /**
@@ -59,6 +79,6 @@
      */
     protected String substringAfter(final String value, final String str) {
         final int indexOf = value.indexOf(str);
-        return indexOf > -1 ? value.substring(indexOf + str.length()) : "";
+        return indexOf > -1 ? value.substring(indexOf + str.length()) : EMPTY;
     }
 }
diff --git a/src/main/java/org/apache/commons/text/lookup/Base64StringLookup.java b/src/main/java/org/apache/commons/text/lookup/Base64StringLookup.java
new file mode 100644
index 0000000..a4651bd
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/lookup/Base64StringLookup.java
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+package org.apache.commons.text.lookup;
+
+import java.util.Base64;
+
+/**
+ * Decodes Base64 Strings.
+ *
+ * @since 1.5
+ */
+final class Base64StringLookup extends AbstractStringLookup {
+
+    /**
+     * Defines the singleton for this class.
+     */
+    static final Base64StringLookup INSTANCE = new Base64StringLookup();
+
+    /**
+     * No need to build instances for now.
+     */
+    private Base64StringLookup() {
+        // empty
+    }
+
+    @Override
+    public String lookup(final String key) {
+        return new String(Base64.getDecoder().decode(key));
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/text/lookup/ConstantStringLookup.java b/src/main/java/org/apache/commons/text/lookup/ConstantStringLookup.java
new file mode 100644
index 0000000..96e7855
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/lookup/ConstantStringLookup.java
@@ -0,0 +1,142 @@
+/*
+ * 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.
+ */
+
+package org.apache.commons.text.lookup;
+
+import java.lang.reflect.Field;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.commons.lang3.ClassUtils;
+
+/**
+ * <p>
+ * A specialized lookup implementation that allows access to constant fields of classes.
+ * </p>
+ * <p>
+ * Sometimes it is necessary in a configuration file to refer to a constant defined in a class. This can be done with
+ * this lookup implementation. Variable names passed in must be of the form {@code apackage.AClass.AFIELD}. The
+ * {@code lookup(String)} method will split the passed in string at the last dot, separating the fully qualified class
+ * name and the name of the constant (i.e. <b>static final</b>) member field. Then the class is loaded and the field's
+ * value is obtained using reflection.
+ * </p>
+ * <p>
+ * Once retrieved values are cached for fast access. This class is thread-safe. It can be used as a standard (i.e.
+ * global) lookup object and serve multiple clients concurrently.
+ * </p>
+ * <p>
+ * This class was adapted from Apache Commons Configuration.
+ * </p>
+ *
+ * @since 1.5
+ */
+class ConstantStringLookup extends AbstractStringLookup {
+
+    /** Constant for the field separator. */
+    private static final char FIELD_SEPRATOR = '.';
+
+    /**
+     * Defines the singleton for this class.
+     */
+    static final ConstantStringLookup INSTANCE = new ConstantStringLookup();
+
+    /** An internally used cache for already retrieved values. */
+    private static ConcurrentHashMap<String, String> constantCache = new ConcurrentHashMap<>();
+
+    /**
+     * Clears the shared cache with the so far resolved constants.
+     */
+    static void clear() {
+        constantCache.clear();
+    }
+
+    /**
+     * Tries to resolve the specified variable. The passed in variable name is interpreted as the name of a <b>static
+     * final</b> member field of a class. If the value has already been obtained, it can be retrieved from an internal
+     * cache. Otherwise this method will invoke the {@code resolveField()} method and pass in the name of the class and
+     * the field.
+     *
+     * @param key
+     *            the name of the variable to be resolved
+     * @return the value of this variable or <b>null</b> if it cannot be resolved
+     */
+    @Override
+    public synchronized String lookup(final String key) {
+        if (key == null) {
+            return null;
+        }
+        String result;
+        result = constantCache.get(key);
+        if (result != null) {
+            return result;
+        }
+        final int fieldPos = key.lastIndexOf(FIELD_SEPRATOR);
+        if (fieldPos < 0) {
+            return null;
+        }
+        try {
+            final Object value = resolveField(key.substring(0, fieldPos), key.substring(fieldPos + 1));
+            if (value != null) {
+                final String string = Objects.toString(value, null);
+                constantCache.put(key, string);
+                result = string;
+            }
+        } catch (final Exception ex) {
+            // TODO it would be nice to log
+            return null;
+        }
+        return result;
+    }
+
+    /**
+     * Determines the value of the specified constant member field of a class. This implementation will call
+     * {@code fetchClass()} to obtain the {@code java.lang.Class} object for the target class. Then it will use
+     * reflection to obtain the field's value. For this to work the field must be accessable.
+     *
+     * @param className
+     *            the name of the class
+     * @param fieldName
+     *            the name of the member field of that class to read
+     * @return the field's value
+     * @throws Exception
+     *             if an error occurs
+     */
+    protected Object resolveField(final String className, final String fieldName) throws Exception {
+        final Class<?> clazz = fetchClass(className);
+        if (clazz == null) {
+            return null;
+        }
+        final Field field = clazz.getField(fieldName);
+        return field == null ? null : field.get(null);
+    }
+
+    /**
+     * Loads the class with the specified name. If an application has special needs regarding the class loaders to be
+     * used, it can hook in here. This implementation delegates to the {@code getClass()} method of Commons Lang's
+     * <code><a href="http://commons.apache.org/lang/api-release/org/apache/commons/lang/ClassUtils.html">
+     * ClassUtils</a></code>.
+     *
+     * @param className
+     *            the name of the class to be loaded
+     * @return the corresponding class object
+     * @throws ClassNotFoundException
+     *             if the class cannot be loaded
+     */
+    protected Class<?> fetchClass(final String className) throws ClassNotFoundException {
+        return ClassUtils.getClass(className);
+    }
+}
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 068ac0a..8f996cb 100644
--- a/src/main/java/org/apache/commons/text/lookup/InterpolatorStringLookup.java
+++ b/src/main/java/org/apache/commons/text/lookup/InterpolatorStringLookup.java
@@ -32,10 +32,14 @@
  * <li>"java" for the {@link JavaPlatformStringLookup}.</li>
  * <li>"date" for the {@link DateStringLookup}.</li>
  * <li>"localhost" for the {@link LocalHostStringLookup}.</li>
- * <li>"xml" for the {@link XmlStringLookup}.</li>
- * <li>"properties" for the {@link PropertiesStringLookup}.</li>
- * <li>"file" for the {@link FileStringLookup}.</li>
- * <li>"url" for the {@link UrlStringLookup}.</li>
+ * <li>"xml" for the {@link XmlStringLookup} since 1.5.</li>
+ * <li>"properties" for the {@link PropertiesStringLookup} since 1.5.</li>
+ * <li>"script" for the {@link ScriptStringLookup} since 1.5.</li>
+ * <li>"file" for the {@link FileStringLookup} since 1.5.</li>
+ * <li>"url" for the {@link UrlStringLookup} since 1.5.</li>
+ * <li>"base64" for the {@link Base64StringLookup} since 1.5.</li>
+ * <li>"urlEncode" for the {@link UrlEncoderStringLookup} since 1.5.</li>
+ * <li>"urlDecode" for the {@link UrlDecoderStringLookup} since 1.5.</li>
  * </ul>
  */
 class InterpolatorStringLookup extends AbstractStringLookup {
@@ -59,12 +63,16 @@
      * <li>"env" for the {@link EnvironmentVariableStringLookup}.</li>
      * <li>"java" for the {@link JavaPlatformStringLookup}.</li>
      * <li>"date" for the {@link DateStringLookup}.</li>
-     * <li>"localhost" for the {@link LocalHostStringLookup}.</li>
-     * <li>"xml" for the {@link XmlStringLookup}.</li>
-     * <li>"properties" for the {@link PropertiesStringLookup}.</li>
-     * <li>"script" for the {@link ScriptStringLookup}.</li>
-     * <li>"file" for the {@link FileStringLookup}.</li>
-     * <li>"url" for the {@link UrlStringLookup}.</li>
+     * <li>"localhost" for the {@link LocalHostStringLookup} since 1.3.</li>
+     * <li>"xml" for the {@link XmlStringLookup} since 1.5.</li>
+     * <li>"properties" for the {@link PropertiesStringLookup} since 1.5.</li>
+     * <li>"script" for the {@link ScriptStringLookup} since 1.5.</li>
+     * <li>"file" for the {@link FileStringLookup} since 1.5.</li>
+     * <li>"url" for the {@link UrlStringLookup} since 1.5.</li>
+     * <li>"base64" for the {@link Base64StringLookup} since 1.5.</li>
+     * <li>"urlEncode" for the {@link UrlEncoderStringLookup} since 1.5.</li>
+     * <li>"urlDecode" for the {@link UrlDecoderStringLookup} since 1.5.</li>
+     * <li>"const" for the {@link ConstantStringLookup} since 1.5.</li>
      * </ul>
      */
     InterpolatorStringLookup() {
@@ -81,12 +89,16 @@
      * <li>"env" for the {@link EnvironmentVariableStringLookup}.</li>
      * <li>"java" for the {@link JavaPlatformStringLookup}.</li>
      * <li>"date" for the {@link DateStringLookup}.</li>
-     * <li>"localhost" for the {@link LocalHostStringLookup}.</li>
-     * <li>"xml" for the {@link XmlStringLookup}.</li>
-     * <li>"properties" for the {@link PropertiesStringLookup}.</li>
-     * <li>"script" for the {@link ScriptStringLookup}.</li>
-     * <li>"file" for the {@link FileStringLookup}.</li>
-     * <li>"url" for the {@link UrlStringLookup}.</li>
+     * <li>"localhost" for the {@link LocalHostStringLookup} since 1.3.</li>
+     * <li>"xml" for the {@link XmlStringLookup} since 1.5.</li>
+     * <li>"properties" for the {@link PropertiesStringLookup} since 1.5.</li>
+     * <li>"script" for the {@link ScriptStringLookup} since 1.5.</li>
+     * <li>"file" for the {@link FileStringLookup} since 1.5.</li>
+     * <li>"url" for the {@link UrlStringLookup} since 1.5.</li>
+     * <li>"base64" for the {@link Base64StringLookup} since 1.5.</li>
+     * <li>"urlEncode" for the {@link UrlEncoderStringLookup} since 1.5.</li>
+     * <li>"urlDecode" for the {@link UrlDecoderStringLookup} since 1.5.</li>
+     * <li>"const" for the {@link ConstantStringLookup} since 1.5.</li>
      * </ul>
      *
      * @param <V>
diff --git a/src/main/java/org/apache/commons/text/lookup/JavaPlatformStringLookup.java b/src/main/java/org/apache/commons/text/lookup/JavaPlatformStringLookup.java
index ee7f556..383c10a 100644
--- a/src/main/java/org/apache/commons/text/lookup/JavaPlatformStringLookup.java
+++ b/src/main/java/org/apache/commons/text/lookup/JavaPlatformStringLookup.java
@@ -1,147 +1,186 @@
-/*

- * 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.

- */

-package org.apache.commons.text.lookup;

-

-import java.util.Locale;

-

-import org.apache.commons.lang3.StringUtils;

-

-/**

- * Looks up keys related to Java: Java version, JRE version, VM version, and so on.

- *

- * @since 1.3

- */

-final class JavaPlatformStringLookup extends AbstractStringLookup {

-

-    /**

-     * Defines the singleton for this class.

-     */

-    static final JavaPlatformStringLookup INSTANCE = new JavaPlatformStringLookup();

-

-    /**

-     * No need to build instances for now.

-     */

-    private JavaPlatformStringLookup() {

-        // empty

-    }

-

-    /**

-     * Accessible through the Lookup key {@code hardware}.

-     *

-     * @return hardware processor information.

-     */

-    String getHardware() {

-        return "processors: " + Runtime.getRuntime().availableProcessors() + ", architecture: "

-                + getSystemProperty("os.arch") + this.getSystemProperty("-", "sun.arch.data.model")

-                + this.getSystemProperty(", instruction sets: ", "sun.cpu.isalist");

-    }

-

-    /**

-     * Accessible through the Lookup key {@code locale}.

-     *

-     * @return system locale and file encoding information.

-     */

-    String getLocale() {

-        return "default locale: " + Locale.getDefault() + ", platform encoding: " + getSystemProperty("file.encoding");

-    }

-

-    /**

-     * Accessible through the Lookup key {@code os}.

-     *

-     * @return operating system information.

-     */

-    String getOperatingSystem() {

-        return getSystemProperty("os.name") + " " + getSystemProperty("os.version")

-                + getSystemProperty(" ", "sun.os.patch.level") + ", architecture: " + getSystemProperty("os.arch")

-                + getSystemProperty("-", "sun.arch.data.model");

-    }

-

-    /**

-     * Accessible through the Lookup key {@code runtime}.

-     *

-     * @return Java Runtime Environment information.

-     */

-    String getRuntime() {

-        return getSystemProperty("java.runtime.name") + " (build " + getSystemProperty("java.runtime.version")

-                + ") from " + getSystemProperty("java.vendor");

-    }

-

-    /**

-     * Gets the given system property.

-     *

-     * @param name

-     *            a system property name.

-     * @return a system property value.

-     */

-    private String getSystemProperty(final String name) {

-        return SystemPropertyStringLookup.INSTANCE.lookup(name);

-    }

-

-    /**

-     * Gets the given system property.

-     *

-     * @param prefix

-     *            the prefix to use for the result string

-     * @param name

-     *            a system property name.

-     * @return the prefix + a system property value.

-     */

-    private String getSystemProperty(final String prefix, final String name) {

-        final String value = getSystemProperty(name);

-        if (StringUtils.isEmpty(value)) {

-            return StringUtils.EMPTY;

-        }

-        return prefix + value;

-    }

-

-    /**

-     * Accessible through the Lookup key {@code vm}.

-     *

-     * @return Java Virtual Machine information.

-     */

-    String getVirtualMachine() {

-        return getSystemProperty("java.vm.name") + " (build " + getSystemProperty("java.vm.version") + ", "

-                + getSystemProperty("java.vm.info") + ")";

-    }

-

-    /**

-     * Looks up the value of the Java platform key.

-     *

-     * @param key

-     *            the key to be looked up, may be null

-     * @return The value of the environment variable.

-     */

-    @Override

-    public String lookup(final String key) {

-        switch (key) {

-        case "version":

-            return "Java version " + getSystemProperty("java.version");

-        case "runtime":

-            return getRuntime();

-        case "vm":

-            return getVirtualMachine();

-        case "os":

-            return getOperatingSystem();

-        case "hardware":

-            return getHardware();

-        case "locale":

-            return getLocale();

-        default:

-            throw new IllegalArgumentException(key);

-        }

-    }

-}

+/*
+ * 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.
+ */
+package org.apache.commons.text.lookup;
+
+import java.util.Locale;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Looks up keys related to Java: Java version, JRE version, VM version, and so on.
+ * <p>
+ * The lookup keys with examples are:
+ * </p>
+ * <ul>
+ * <li><b>version</b>: "Java version 1.8.0_181"</li>
+ * <li><b>runtime</b>: "Java(TM) SE Runtime Environment (build 1.8.0_181-b13) from Oracle Corporation"</li>
+ * <li><b>vm</b>: "Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)"</li>
+ * <li><b>os</b>: "Windows 10 10.0, architecture: amd64-64"</li>
+ * <li><b>hardware</b>: "processors: 4, architecture: amd64-64, instruction sets: amd64"</li>
+ * <li><b>locale</b>: "default locale: en_US, platform encoding: iso-8859-1"</li>
+ * </ul>
+ *
+ * @since 1.3
+ */
+final class JavaPlatformStringLookup extends AbstractStringLookup {
+
+    private static final String KEY_LOCALE = "locale";
+    private static final String KEY_HARDWARE = "hardware";
+    private static final String KEY_OS = "os";
+    private static final String KEY_VM = "vm";
+    private static final String KEY_RUNTIME = "runtime";
+    private static final String KEY_VERSION = "version";
+
+    /**
+     * Defines the singleton for this class.
+     */
+    static final JavaPlatformStringLookup INSTANCE = new JavaPlatformStringLookup();
+
+    public static void main(String[] args) {
+        System.out.println(JavaPlatformStringLookup.class);
+        System.out.printf("%s = %s\n", KEY_VERSION, JavaPlatformStringLookup.INSTANCE.lookup(KEY_VERSION));
+        System.out.printf("%s = %s\n", KEY_RUNTIME, JavaPlatformStringLookup.INSTANCE.lookup(KEY_RUNTIME));
+        System.out.printf("%s = %s\n", KEY_VM, JavaPlatformStringLookup.INSTANCE.lookup(KEY_VM));
+        System.out.printf("%s = %s\n", KEY_OS, JavaPlatformStringLookup.INSTANCE.lookup(KEY_OS));
+        System.out.printf("%s = %s\n", KEY_HARDWARE, JavaPlatformStringLookup.INSTANCE.lookup(KEY_HARDWARE));
+        System.out.printf("%s = %s\n", KEY_LOCALE, JavaPlatformStringLookup.INSTANCE.lookup(KEY_LOCALE));
+    }
+
+    /**
+     * No need to build instances for now.
+     */
+    private JavaPlatformStringLookup() {
+        // empty
+    }
+
+    /**
+     * Accessible through the Lookup key {@code hardware}.
+     *
+     * @return hardware processor information.
+     */
+    String getHardware() {
+        return "processors: " + Runtime.getRuntime().availableProcessors() + ", architecture: "
+                + getSystemProperty("os.arch") + this.getSystemProperty("-", "sun.arch.data.model")
+                + this.getSystemProperty(", instruction sets: ", "sun.cpu.isalist");
+    }
+
+    /**
+     * Accessible through the Lookup key {@code locale}.
+     *
+     * @return system locale and file encoding information.
+     */
+    String getLocale() {
+        return "default locale: " + Locale.getDefault() + ", platform encoding: " + getSystemProperty("file.encoding");
+    }
+
+    /**
+     * Accessible through the Lookup key {@code os}.
+     *
+     * @return operating system information.
+     */
+    String getOperatingSystem() {
+        return getSystemProperty("os.name") + " " + getSystemProperty("os.version")
+                + getSystemProperty(" ", "sun.os.patch.level") + ", architecture: " + getSystemProperty("os.arch")
+                + getSystemProperty("-", "sun.arch.data.model");
+    }
+
+    /**
+     * Accessible through the Lookup key {@code runtime}.
+     *
+     * @return Java Runtime Environment information.
+     */
+    String getRuntime() {
+        return getSystemProperty("java.runtime.name") + " (build " + getSystemProperty("java.runtime.version")
+                + ") from " + getSystemProperty("java.vendor");
+    }
+
+    /**
+     * Gets the given system property.
+     *
+     * @param name
+     *            a system property name.
+     * @return a system property value.
+     */
+    private String getSystemProperty(final String name) {
+        return SystemPropertyStringLookup.INSTANCE.lookup(name);
+    }
+
+    /**
+     * Gets the given system property.
+     *
+     * @param prefix
+     *            the prefix to use for the result string
+     * @param name
+     *            a system property name.
+     * @return the prefix + a system property value.
+     */
+    private String getSystemProperty(final String prefix, final String name) {
+        final String value = getSystemProperty(name);
+        if (StringUtils.isEmpty(value)) {
+            return StringUtils.EMPTY;
+        }
+        return prefix + value;
+    }
+
+    /**
+     * Accessible through the Lookup key {@code vm}.
+     *
+     * @return Java Virtual Machine information.
+     */
+    String getVirtualMachine() {
+        return getSystemProperty("java.vm.name") + " (build " + getSystemProperty("java.vm.version") + ", "
+                + getSystemProperty("java.vm.info") + ")";
+    }
+
+    /**
+     * Looks up the value of the Java platform key.
+     * <p>
+     * The lookup keys with examples are:
+     * </p>
+     * <ul>
+     * <li><b>version</b>: "Java version 1.8.0_181"</li>
+     * <li><b>runtime</b>: "Java(TM) SE Runtime Environment (build 1.8.0_181-b13) from Oracle Corporation"</li>
+     * <li><b>vm</b>: "Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)"</li>
+     * <li><b>os</b>: "Windows 10 10.0, architecture: amd64-64"</li>
+     * <li><b>hardware</b>: "processors: 4, architecture: amd64-64, instruction sets: amd64"</li>
+     * <li><b>locale</b>: "default locale: en_US, platform encoding: iso-8859-1"</li>
+     * </ul>
+     *
+     * @param key
+     *            the key to be looked up, may be null
+     * @return The value of the environment variable.
+     */
+    @Override
+    public String lookup(final String key) {
+        switch (key) {
+        case KEY_VERSION:
+            return "Java version " + getSystemProperty("java.version");
+        case KEY_RUNTIME:
+            return getRuntime();
+        case KEY_VM:
+            return getVirtualMachine();
+        case KEY_OS:
+            return getOperatingSystem();
+        case KEY_HARDWARE:
+            return getHardware();
+        case KEY_LOCALE:
+            return getLocale();
+        default:
+            throw new IllegalArgumentException(key);
+        }
+    }
+}
diff --git a/src/main/java/org/apache/commons/text/lookup/ResourceBundleStringLookup.java b/src/main/java/org/apache/commons/text/lookup/ResourceBundleStringLookup.java
index 9e3e6d5..8a14c1b 100644
--- a/src/main/java/org/apache/commons/text/lookup/ResourceBundleStringLookup.java
+++ b/src/main/java/org/apache/commons/text/lookup/ResourceBundleStringLookup.java
@@ -33,6 +33,8 @@
  */
 final class ResourceBundleStringLookup extends AbstractStringLookup {
 
+    private final String bundleName;
+
     /**
      * Defines the singleton for this class.
      */
@@ -42,7 +44,16 @@
      * No need to build instances for now.
      */
     private ResourceBundleStringLookup() {
-        // empty
+        this(null);
+    }
+
+    /**
+     * Constructs an instance that only works for the given bundle.
+     *
+     * @since 1.5
+     */
+    ResourceBundleStringLookup(final String bundleName) {
+        this.bundleName = bundleName;
     }
 
     /**
@@ -62,20 +73,24 @@
         if (key == null) {
             return null;
         }
-        final String[] keys = key.split(":");
+        final String[] keys = key.split(SPLIT_STR);
         final int keyLen = keys.length;
-        if (keyLen != 2) {
+        final boolean anyBundle = bundleName == null;
+        if (anyBundle && keyLen != 2) {
             throw IllegalArgumentExceptions
                     .format("Bad resource bundle key format [%s]; expected format is BundleName:KeyName.", key);
+        } else if (bundleName != null && keyLen != 1) {
+            throw IllegalArgumentExceptions.format("Bad resource bundle key format [%s]; expected format is KeyName.",
+                    key);
         }
-        final String bundleName = keys[0];
-        final String bundleKey = keys[1];
+        final String keyBundleName = anyBundle ? keys[0] : bundleName;
+        final String bundleKey = anyBundle ? keys[1] : keys[0];
         try {
             // The ResourceBundle class caches bundles, no need to cache here.
-            return ResourceBundle.getBundle(bundleName).getString(bundleKey);
+            return ResourceBundle.getBundle(keyBundleName).getString(bundleKey);
         } catch (final Exception e) {
-            throw IllegalArgumentExceptions.format(e, "Error looking up resource bundle [%s] and key [%s].", bundleName,
-                    bundleKey);
+            throw IllegalArgumentExceptions.format(e, "Error looking up resource bundle [%s] and key [%s].",
+                    keyBundleName, bundleKey);
         }
     }
 
diff --git a/src/main/java/org/apache/commons/text/lookup/ScriptStringLookup.java b/src/main/java/org/apache/commons/text/lookup/ScriptStringLookup.java
index 99f1be9..c80e88c 100644
--- a/src/main/java/org/apache/commons/text/lookup/ScriptStringLookup.java
+++ b/src/main/java/org/apache/commons/text/lookup/ScriptStringLookup.java
@@ -62,7 +62,7 @@
         if (key == null) {
             return null;
         }
-        final String[] keys = key.split(":");
+        final String[] keys = key.split(SPLIT_STR);
         final int keyLen = keys.length;
         if (keyLen != 2) {
             throw IllegalArgumentExceptions
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 25f6596..dcf599d 100644
--- a/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java
+++ b/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java
@@ -32,6 +32,15 @@
     public static final StringLookupFactory INSTANCE = new StringLookupFactory();
 
     /**
+     * Clears any static resources.
+     *
+     * @since 1.5
+     */
+    public static void clear() {
+        ConstantStringLookup.clear();
+    }
+
+    /**
      * No need to build instances for now.
      */
     private StringLookupFactory() {
@@ -45,12 +54,16 @@
      * <li>"env" for the {@link EnvironmentVariableStringLookup}.</li>
      * <li>"java" for the {@link JavaPlatformStringLookup}.</li>
      * <li>"date" for the {@link DateStringLookup}.</li>
-     * <li>"localhost" for the {@link LocalHostStringLookup}.</li>
-     * <li>"xml" for the {@link XmlStringLookup}.</li>
-     * <li>"properties" for the {@link PropertiesStringLookup}.</li>
-     * <li>"script" for the {@link ScriptStringLookup}.</li>
-     * <li>"file" for the {@link FileStringLookup}.</li>
-     * <li>"url" for the {@link UrlStringLookup}.</li>
+     * <li>"localhost" for the {@link LocalHostStringLookup} since 1.3.</li>
+     * <li>"xml" for the {@link XmlStringLookup} since 1.5.</li>
+     * <li>"properties" for the {@link PropertiesStringLookup} since 1.5.</li>
+     * <li>"script" for the {@link ScriptStringLookup} since 1.5.</li>
+     * <li>"file" for the {@link FileStringLookup} since 1.5.</li>
+     * <li>"url" for the {@link UrlStringLookup} since 1.5.</li>
+     * <li>"base64" for the {@link Base64StringLookup} since 1.5.</li>
+     * <li>"urlEncode" for the {@link UrlEncoderStringLookup} since 1.5.</li>
+     * <li>"urlDecode" for the {@link UrlDecoderStringLookup} since 1.5.</li>
+     * <li>"const" for the {@link ConstantStringLookup} since 1.5.</li>
      * </ul>
      *
      * @param stringLookupMap
@@ -69,6 +82,10 @@
             stringLookupMap.put("script", ScriptStringLookup.INSTANCE);
             stringLookupMap.put("file", FileStringLookup.INSTANCE);
             stringLookupMap.put("url", UrlStringLookup.INSTANCE);
+            stringLookupMap.put("base64", Base64StringLookup.INSTANCE);
+            stringLookupMap.put("urlEncode", UrlEncoderStringLookup.INSTANCE);
+            stringLookupMap.put("urlDecode", UrlDecoderStringLookup.INSTANCE);
+            stringLookupMap.put("const", ConstantStringLookup.INSTANCE);
         }
     }
 
@@ -78,6 +95,26 @@
      *
      * @return the DateStringLookup singleton instance.
      */
+    public StringLookup base64StringLookup() {
+        return Base64StringLookup.INSTANCE;
+    }
+
+    /**
+     * Returns the ConstantStringLookup singleton instance to get the value of a fully-qualified static final value.
+     *
+     * @return the DateStringLookup singleton instance.
+     * @since 1.5
+     */
+    public StringLookup constantStringLookup() {
+        return ConstantStringLookup.INSTANCE;
+    }
+
+    /**
+     * Returns the DateStringLookup singleton instance to format the current date with the format given in the key in a
+     * format compatible with {@link java.text.SimpleDateFormat}.
+     *
+     * @return the DateStringLookup singleton instance.
+     */
     public StringLookup dateStringLookup() {
         return DateStringLookup.INSTANCE;
     }
@@ -118,12 +155,17 @@
      * <li>"env" for the {@link EnvironmentVariableStringLookup}.</li>
      * <li>"java" for the {@link JavaPlatformStringLookup}.</li>
      * <li>"date" for the {@link DateStringLookup}.</li>
-     * <li>"localhost" for the {@link LocalHostStringLookup}, see {@link #localHostStringLookup()} for key names.</li>
-     * <li>"xml" for the {@link XmlStringLookup}.</li>
-     * <li>"properties" for the {@link PropertiesStringLookup}.</li>
-     * <li>"script" for the {@link ScriptStringLookup}.</li>
-     * <li>"file" for the {@link FileStringLookup}.</li>
-     * <li>"url" for the {@link UrlStringLookup}.</li>
+     * <li>"localhost" for the {@link LocalHostStringLookup}, see {@link #localHostStringLookup()} for key names; since
+     * 1.3.</li>
+     * <li>"xml" for the {@link XmlStringLookup} since 1.5.</li>
+     * <li>"properties" for the {@link PropertiesStringLookup} since 1.5.</li>
+     * <li>"script" for the {@link ScriptStringLookup} since 1.5.</li>
+     * <li>"file" for the {@link FileStringLookup} since 1.5.</li>
+     * <li>"url" for the {@link UrlStringLookup} since 1.5.</li>
+     * <li>"base64" for the {@link Base64StringLookup} since 1.5.</li>
+     * <li>"urlEncode" for the {@link UrlEncoderStringLookup} since 1.5.</li>
+     * <li>"urlDecode" for the {@link UrlDecoderStringLookup} since 1.5.</li>
+     * <li>"const" for the {@link ConstantStringLookup} since 1.5.</li>
      * </ul>
      *
      * @return a new InterpolatorStringLookup.
@@ -143,12 +185,17 @@
      * <li>"env" for the {@link EnvironmentVariableStringLookup}.</li>
      * <li>"java" for the {@link JavaPlatformStringLookup}.</li>
      * <li>"date" for the {@link DateStringLookup}.</li>
-     * <li>"localhost" for the {@link LocalHostStringLookup}, see {@link #localHostStringLookup()} for key names.</li>
-     * <li>"xml" for the {@link XmlStringLookup}.</li>
-     * <li>"properties" for the {@link PropertiesStringLookup}.</li>
-     * <li>"script" for the {@link ScriptStringLookup}.</li>
-     * <li>"file" for the {@link FileStringLookup}.</li>
-     * <li>"url" for the {@link UrlStringLookup}.</li>
+     * <li>"localhost" for the {@link LocalHostStringLookup}, see {@link #localHostStringLookup()} for key names; since
+     * 1.3.</li>
+     * <li>"xml" for the {@link XmlStringLookup} since 1.5.</li>
+     * <li>"properties" for the {@link PropertiesStringLookup} since 1.5.</li>
+     * <li>"script" for the {@link ScriptStringLookup} since 1.5.</li>
+     * <li>"file" for the {@link FileStringLookup} since 1.5.</li>
+     * <li>"url" for the {@link UrlStringLookup} since 1.5.</li>
+     * <li>"base64" for the {@link Base64StringLookup} since 1.5.</li>
+     * <li>"urlEncode" for the {@link UrlEncoderStringLookup} since 1.5.</li>
+     * <li>"urlDecode" for the {@link UrlDecoderStringLookup} since 1.5.</li>
+     * <li>"const" for the {@link ConstantStringLookup} since 1.5.</li>
      * </ul>
      *
      * @param stringLookupMap
@@ -175,12 +222,17 @@
      * <li>"env" for the {@link EnvironmentVariableStringLookup}.</li>
      * <li>"java" for the {@link JavaPlatformStringLookup}.</li>
      * <li>"date" for the {@link DateStringLookup}.</li>
-     * <li>"localhost" for the {@link LocalHostStringLookup}, see {@link #localHostStringLookup()} for key names.</li>
-     * <li>"xml" for the {@link XmlStringLookup}.</li>
-     * <li>"properties" for the {@link PropertiesStringLookup}.</li>
-     * <li>"script" for the {@link ScriptStringLookup}.</li>
-     * <li>"file" for the {@link FileStringLookup}.</li>
-     * <li>"url" for the {@link UrlStringLookup}.</li>
+     * <li>"localhost" for the {@link LocalHostStringLookup}, see {@link #localHostStringLookup()} for key names; since
+     * 1.3.</li>
+     * <li>"xml" for the {@link XmlStringLookup} since 1.5.</li>
+     * <li>"properties" for the {@link PropertiesStringLookup} since 1.5.</li>
+     * <li>"script" for the {@link ScriptStringLookup} since 1.5.</li>
+     * <li>"file" for the {@link FileStringLookup} since 1.5.</li>
+     * <li>"url" for the {@link UrlStringLookup} since 1.5.</li>
+     * <li>"base64" for the {@link Base64StringLookup} since 1.5.</li>
+     * <li>"urlEncode" for the {@link UrlEncoderStringLookup} since 1.5.</li>
+     * <li>"urlDecode" for the {@link UrlDecoderStringLookup} since 1.5.</li>
+     * <li>"const" for the {@link ConstantStringLookup} since 1.5.</li>
      * </ul>
      *
      * @param <V>
@@ -203,12 +255,17 @@
      * <li>"env" for the {@link EnvironmentVariableStringLookup}.</li>
      * <li>"java" for the {@link JavaPlatformStringLookup}.</li>
      * <li>"date" for the {@link DateStringLookup}.</li>
-     * <li>"localhost" for the {@link LocalHostStringLookup}, see {@link #localHostStringLookup()} for key names.</li>
-     * <li>"xml" for the {@link XmlStringLookup}.</li>
-     * <li>"properties" for the {@link PropertiesStringLookup}.</li>
-     * <li>"script" for the {@link ScriptStringLookup}.</li>
-     * <li>"file" for the {@link FileStringLookup}.</li>
-     * <li>"url" for the {@link UrlStringLookup}.</li>
+     * <li>"localhost" for the {@link LocalHostStringLookup}, see {@link #localHostStringLookup()} for key names; since
+     * 1.3.</li>
+     * <li>"xml" for the {@link XmlStringLookup} since 1.5.</li>
+     * <li>"properties" for the {@link PropertiesStringLookup} since 1.5.</li>
+     * <li>"script" for the {@link ScriptStringLookup} since 1.5.</li>
+     * <li>"file" for the {@link FileStringLookup} since 1.5.</li>
+     * <li>"url" for the {@link UrlStringLookup} since 1.5.</li>
+     * <li>"base64" for the {@link Base64StringLookup} since 1.5.</li>
+     * <li>"urlEncode" for the {@link UrlEncoderStringLookup} since 1.5.</li>
+     * <li>"urlDecode" for the {@link UrlDecoderStringLookup} since 1.5.</li>
+     * <li>"const" for the {@link ConstantStringLookup} since 1.5.</li>
      * </ul>
      *
      * @param defaultStringLookup
@@ -220,7 +277,19 @@
     }
 
     /**
-     * Returns the JavaPlatformStringLookup singleton instance.
+     * Returns the JavaPlatformStringLookup singleton instance. Looks up keys related to Java: Java version, JRE
+     * version, VM version, and so on.
+     * <p>
+     * The lookup keys with examples are:
+     * </p>
+     * <ul>
+     * <li><b>version</b>: "Java version 1.8.0_181"</li>
+     * <li><b>runtime</b>: "Java(TM) SE Runtime Environment (build 1.8.0_181-b13) from Oracle Corporation"</li>
+     * <li><b>vm</b>: "Java HotSpot(TM) 64-Bit Server VM (build 25.181-b13, mixed mode)"</li>
+     * <li><b>os</b>: "Windows 10 10.0, architecture: amd64-64"</li>
+     * <li><b>hardware</b>: "processors: 4, architecture: amd64-64, instruction sets: amd64"</li>
+     * <li><b>locale</b>: "default locale: en_US, platform encoding: iso-8859-1"</li>
+     * </ul>
      *
      * @return the JavaPlatformStringLookup singleton instance.
      */
@@ -296,6 +365,24 @@
     }
 
     /**
+     * Returns a ResourceBundleStringLookup instance for the given bundle name.
+     * <p>
+     * Looks up the value for a given key in the format "BundleKey".
+     * </p>
+     * <p>
+     * For example: "MyKey".
+     * </p>
+     *
+     * @param bundleName
+     *            Only lookup in this bundle.
+     * @return a ResourceBundleStringLookup instance for the given bundle name.
+     * @since 1.5
+     */
+    public StringLookup resourceBundleStringLookup(final String bundleName) {
+        return new ResourceBundleStringLookup(bundleName);
+    }
+
+    /**
      * Returns the ScriptStringLookup singleton instance.
      * <p>
      * Looks up the value for the key in the format "ScriptEngineName:Script".
diff --git a/src/main/java/org/apache/commons/text/lookup/UrlDecoderStringLookup.java b/src/main/java/org/apache/commons/text/lookup/UrlDecoderStringLookup.java
new file mode 100644
index 0000000..55a2807
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/lookup/UrlDecoderStringLookup.java
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+package org.apache.commons.text.lookup;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Decodes URL Strings using the UTF-8 encoding.
+ *
+ * @see URLEncoder
+ * @since 1.5
+ */
+final class UrlDecoderStringLookup extends AbstractStringLookup {
+
+    /**
+     * Defines the singleton for this class.
+     */
+    static final UrlDecoderStringLookup INSTANCE = new UrlDecoderStringLookup();
+
+    /**
+     * No need to build instances for now.
+     */
+    private UrlDecoderStringLookup() {
+        // empty
+    }
+
+    @Override
+    public String lookup(final String key) {
+        final String enc = StandardCharsets.UTF_8.name();
+        try {
+            return new String(URLDecoder.decode(key, enc));
+        } catch (final UnsupportedEncodingException e) {
+            throw IllegalArgumentExceptions.format(e, "%s: source=%s, encoding=%s", e, key, enc);
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/text/lookup/UrlEncoderStringLookup.java b/src/main/java/org/apache/commons/text/lookup/UrlEncoderStringLookup.java
new file mode 100644
index 0000000..fdc2c1f
--- /dev/null
+++ b/src/main/java/org/apache/commons/text/lookup/UrlEncoderStringLookup.java
@@ -0,0 +1,54 @@
+/*
+ * 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.
+ */
+
+package org.apache.commons.text.lookup;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+
+/**
+ * Encodes URL Strings using the UTF-8 encoding.
+ *
+ * @see URLEncoder
+ * @since 1.5
+ */
+final class UrlEncoderStringLookup extends AbstractStringLookup {
+
+    /**
+     * Defines the singleton for this class.
+     */
+    static final UrlEncoderStringLookup INSTANCE = new UrlEncoderStringLookup();
+
+    /**
+     * No need to build instances for now.
+     */
+    private UrlEncoderStringLookup() {
+        // empty
+    }
+
+    @Override
+    public String lookup(final String key) {
+        final String enc = StandardCharsets.UTF_8.name();
+        try {
+            return new String(URLEncoder.encode(key, enc));
+        } catch (final UnsupportedEncodingException e) {
+            throw IllegalArgumentExceptions.format(e, "%s: source=%s, encoding=%s", e, key, enc);
+        }
+    }
+
+}
diff --git a/src/main/java/org/apache/commons/text/lookup/UrlStringLookup.java b/src/main/java/org/apache/commons/text/lookup/UrlStringLookup.java
index 6e0d4d4..1c27736 100644
--- a/src/main/java/org/apache/commons/text/lookup/UrlStringLookup.java
+++ b/src/main/java/org/apache/commons/text/lookup/UrlStringLookup.java
@@ -62,7 +62,7 @@
         if (key == null) {
             return null;
         }
-        final String[] keys = key.split(":");
+        final String[] keys = key.split(SPLIT_STR);
         final int keyLen = keys.length;
         if (keyLen < 2) {
             throw IllegalArgumentExceptions.format("Bad URL key format [%s]; expected format is DocumentPath:Key.",
diff --git a/src/test/java/org/apache/commons/text/StringEscapeUtilsTest.java b/src/test/java/org/apache/commons/text/StringEscapeUtilsTest.java
index 2d36c13..e9abe7b 100644
--- a/src/test/java/org/apache/commons/text/StringEscapeUtilsTest.java
+++ b/src/test/java/org/apache/commons/text/StringEscapeUtilsTest.java
@@ -633,4 +633,10 @@
 
       assertEquals(jsonString, StringEscapeUtils.unescapeJson(escapedJsonString));
     }
+
+    @Test
+    public void testDeleteCharacter() {
+      String deleteString = "Delete: \u007F";
+      assertEquals("Delete: \\u007F", StringEscapeUtils.escapeJson(deleteString));
+    }
 }
diff --git a/src/test/java/org/apache/commons/text/lookup/Base64StringLookupTest.java b/src/test/java/org/apache/commons/text/lookup/Base64StringLookupTest.java
new file mode 100644
index 0000000..19b61db
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/lookup/Base64StringLookupTest.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+package org.apache.commons.text.lookup;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class Base64StringLookupTest {
+
+    @Test
+    public void test() {
+        Assertions.assertEquals("HelloWorld!", Base64StringLookup.INSTANCE.lookup("SGVsbG9Xb3JsZCE="));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/text/lookup/ConstantStringLookupBasicTest.java b/src/test/java/org/apache/commons/text/lookup/ConstantStringLookupBasicTest.java
new file mode 100644
index 0000000..ded4c3b
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/lookup/ConstantStringLookupBasicTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+package org.apache.commons.text.lookup;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link ConstantStringLookup}.
+ */
+public class ConstantStringLookupBasicTest {
+
+    /**
+     * Test fixture.
+     */
+    public static final String STRING_FIXTURE = "Hello World!";
+
+    /**
+     * Clears the test environment. Here the static cache of the constant lookup class is wiped out.
+     */
+    @AfterEach
+    public void afterEach() {
+        ConstantStringLookup.clear();
+    }
+
+    /**
+     * Clears the test environment. Here the static cache of the constant lookup class is wiped out.
+     */
+    @BeforeEach
+    public void beforeEach() {
+        ConstantStringLookup.clear();
+    }
+
+    @Test
+    public void testOne() {
+        Assertions.assertEquals(STRING_FIXTURE, ConstantStringLookup.INSTANCE
+                .lookup(ConstantStringLookupBasicTest.class.getName() + ".STRING_FIXTURE"));
+    }
+}
diff --git a/src/test/java/org/apache/commons/text/lookup/ConstantStringLookupTest.java b/src/test/java/org/apache/commons/text/lookup/ConstantStringLookupTest.java
new file mode 100644
index 0000000..fb1c4ca
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/lookup/ConstantStringLookupTest.java
@@ -0,0 +1,142 @@
+/*
+ * 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.
+ */
+package org.apache.commons.text.lookup;
+
+import java.awt.event.KeyEvent;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link ConstantStringLookup}.
+ * <p>
+ * This class was adapted from Apache Commons Configuration.
+ * </p>
+ */
+public class ConstantStringLookupTest {
+
+    /** A public field that can be read by the lookup. */
+    public static final String FIELD = "Field that can be read";
+
+    /** A private field that cannot be read by the lookup. */
+    @SuppressWarnings("unused")
+    private static final String PRIVATE_FIELD = "PRIVATE";
+
+    /** The lookup object to be tested. */
+    private ConstantStringLookup stringLookup;
+
+    /**
+     * Clears the test environment. Here the static cache of the constant lookup class is wiped out.
+     */
+    @AfterEach
+    public void afterEach() {
+        ConstantStringLookup.clear();
+    }
+
+    /**
+     * Clears the test environment. Here the static cache of the constant lookup class is wiped out.
+     */
+    @BeforeEach
+    public void beforeEach() {
+        stringLookup = ConstantStringLookup.INSTANCE;
+    }
+
+    /**
+     * Tests accessing the cache by querying a variable twice.
+     */
+    @Test
+    public void testLookupCache() {
+        testLookupConstant();
+        testLookupConstant();
+    }
+
+    /**
+     * Tests resolving a valid constant.
+     */
+    @Test
+    public void testLookupConstant() {
+        Assertions.assertEquals(FIELD, stringLookup.lookup(variable("FIELD")), "Wrong value of constant");
+    }
+
+    /**
+     * Tries to resolve a variable with an invalid syntax: The name does not contain a dot as a field separator.
+     */
+    @Test
+    public void testLookupInvalidSyntax() {
+        Assertions.assertNull(stringLookup.lookup("InvalidVariableName"),
+                "Non null return value for invalid variable name");
+    }
+
+    /**
+     * Tests resolving a non existing constant. Result should be null.
+     */
+    @Test
+    public void testLookupNonExisting() {
+        Assertions.assertNull(stringLookup.lookup(variable("NO_FIELD")),
+                "Non null return value for non existing constant");
+    }
+
+    /**
+     * Tests resolving a non string constant. Then looks the same variable up from the cache.
+     */
+    @Test
+    public void testLookupNonString() {
+        final String ref = KeyEvent.class.getName() + ".VK_ESCAPE";
+        final String expected = Integer.toString(KeyEvent.VK_ESCAPE);
+        Assertions.assertEquals(expected, stringLookup.lookup(ref), "Wrong result of first lookup");
+        Assertions.assertEquals(expected, stringLookup.lookup(ref), "Wrong result of 2nd lookup");
+    }
+
+    /**
+     * Tests looking up a null variable.
+     */
+    @Test
+    public void testLookupNull() {
+        Assertions.assertNull(stringLookup.lookup(null), "Non null return value for null variable");
+    }
+
+    /**
+     * Tests resolving a private constant. Because a private field cannot be accessed this should again yield null.
+     */
+    @Test
+    public void testLookupPrivate() {
+        Assertions.assertNull(stringLookup.lookup(variable("PRIVATE_FIELD")),
+                "Non null return value for non accessible field");
+    }
+
+    /**
+     * Tests resolving a field from an unknown class.
+     */
+    @Test
+    public void testLookupUnknownClass() {
+        Assertions.assertNull(stringLookup.lookup("org.apache.commons.configuration.NonExistingConfig." + FIELD),
+                "Non null return value for unknown class");
+    }
+
+    /**
+     * Generates the name of a variable for a lookup operation based on the given field name of this class.
+     *
+     * @param field
+     *            the field name
+     * @return the variable for looking up this field
+     */
+    private String variable(final String field) {
+        return getClass().getName() + '.' + field;
+    }
+}
diff --git a/src/test/java/org/apache/commons/text/lookup/ResourceBundleStringLookupTest.java b/src/test/java/org/apache/commons/text/lookup/ResourceBundleStringLookupTest.java
index 37415b5..0b5c4d9 100644
--- a/src/test/java/org/apache/commons/text/lookup/ResourceBundleStringLookupTest.java
+++ b/src/test/java/org/apache/commons/text/lookup/ResourceBundleStringLookupTest.java
@@ -25,11 +25,19 @@
 public class ResourceBundleStringLookupTest {
 
     @Test
-    public void testOne() {
+    public void testAny() {
         final String bundleName = "testResourceBundleLookup";
         final String bundleKey = "key";
         Assertions.assertEquals(ResourceBundle.getBundle(bundleName).getString(bundleKey),
                 ResourceBundleStringLookup.INSTANCE.lookup(bundleName + ":" + bundleKey));
     }
 
+    @Test
+    public void testOne() {
+        final String bundleName = "testResourceBundleLookup";
+        final String bundleKey = "key";
+        Assertions.assertEquals(ResourceBundle.getBundle(bundleName).getString(bundleKey),
+                new ResourceBundleStringLookup(bundleName).lookup(bundleKey));
+    }
+
 }
diff --git a/src/test/java/org/apache/commons/text/lookup/UrlDecoderStringLookupTest.java b/src/test/java/org/apache/commons/text/lookup/UrlDecoderStringLookupTest.java
new file mode 100644
index 0000000..9fc628c
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/lookup/UrlDecoderStringLookupTest.java
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+package org.apache.commons.text.lookup;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link UrlDecoderStringLookup}.
+ */
+public class UrlDecoderStringLookupTest {
+
+    @Test
+    public void testAllPercent() {
+        Assertions.assertEquals("Hello World!", UrlDecoderStringLookup.INSTANCE.lookup("Hello%20World%21"));
+    }
+
+    @Test
+    public void testExclamation() {
+        Assertions.assertEquals("Hello World!", UrlDecoderStringLookup.INSTANCE.lookup("Hello%20World!"));
+    }
+
+    @Test
+    public void testPlus() {
+        Assertions.assertEquals("Hello World!", UrlDecoderStringLookup.INSTANCE.lookup("Hello+World!"));
+    }
+
+}
diff --git a/src/test/java/org/apache/commons/text/lookup/UrlEncoderStringLookupTest.java b/src/test/java/org/apache/commons/text/lookup/UrlEncoderStringLookupTest.java
new file mode 100644
index 0000000..c73c591
--- /dev/null
+++ b/src/test/java/org/apache/commons/text/lookup/UrlEncoderStringLookupTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+package org.apache.commons.text.lookup;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Tests {@link UrlEncoderStringLookup}.
+ */
+public class UrlEncoderStringLookupTest {
+
+    @Test
+    public void test() {
+        Assertions.assertEquals("Hello+World%21", UrlEncoderStringLookup.INSTANCE.lookup("Hello World!"));
+    }
+
+}