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 2018/09/27 16:58:51 UTC

[text] [TEXT-143] Add constant string lookup like the one in Apache Commons Configuration.

Repository: commons-text
Updated Branches:
  refs/heads/master 682cf9571 -> 49d81114e


[TEXT-143] Add constant string lookup like the one in Apache Commons
Configuration.

Project: http://git-wip-us.apache.org/repos/asf/commons-text/repo
Commit: http://git-wip-us.apache.org/repos/asf/commons-text/commit/49d81114
Tree: http://git-wip-us.apache.org/repos/asf/commons-text/tree/49d81114
Diff: http://git-wip-us.apache.org/repos/asf/commons-text/diff/49d81114

Branch: refs/heads/master
Commit: 49d81114eb693eeb401bc18b4e6a263bf7071c4c
Parents: 682cf95
Author: Gary Gregory <gg...@rocketsoftware.com>
Authored: Thu Sep 27 10:58:49 2018 -0600
Committer: Gary Gregory <gg...@rocketsoftware.com>
Committed: Thu Sep 27 10:58:49 2018 -0600

----------------------------------------------------------------------
 src/changes/changes.xml                         |   1 +
 .../text/lookup/ConstantStringLookup.java       | 142 +++++++++++++++++++
 .../text/lookup/InterpolatorStringLookup.java   |   2 +
 .../text/lookup/StringLookupFactory.java        |  25 ++++
 .../lookup/ConstantStringLookupBasicTest.java   |  56 ++++++++
 .../text/lookup/ConstantStringLookupTest.java   | 142 +++++++++++++++++++
 6 files changed, 368 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/commons-text/blob/49d81114/src/changes/changes.xml
----------------------------------------------------------------------
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 85440dc..dce92bd 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -57,6 +57,7 @@ The <action> type attribute can be add,update,fix,remove.
     <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">

http://git-wip-us.apache.org/repos/asf/commons-text/blob/49d81114/src/main/java/org/apache/commons/text/lookup/ConstantStringLookup.java
----------------------------------------------------------------------
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);
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-text/blob/49d81114/src/main/java/org/apache/commons/text/lookup/InterpolatorStringLookup.java
----------------------------------------------------------------------
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 208b1d2..5f2d2fe 100644
--- a/src/main/java/org/apache/commons/text/lookup/InterpolatorStringLookup.java
+++ b/src/main/java/org/apache/commons/text/lookup/InterpolatorStringLookup.java
@@ -72,6 +72,7 @@ class InterpolatorStringLookup extends AbstractStringLookup {
      * <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() {
@@ -97,6 +98,7 @@ class InterpolatorStringLookup extends AbstractStringLookup {
      * <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>

http://git-wip-us.apache.org/repos/asf/commons-text/blob/49d81114/src/main/java/org/apache/commons/text/lookup/StringLookupFactory.java
----------------------------------------------------------------------
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 c866a4a..773608c 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 final class StringLookupFactory {
     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() {
@@ -54,6 +63,7 @@ public final class StringLookupFactory {
      * <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
@@ -74,6 +84,7 @@ public final class StringLookupFactory {
             stringLookupMap.put("base64", Base64StringLookup.INSTANCE);
             stringLookupMap.put("urlEncode", UrlEncoderStringLookup.INSTANCE);
             stringLookupMap.put("urlDecode", UrlDecoderStringLookup.INSTANCE);
+            stringLookupMap.put("const", ConstantStringLookup.INSTANCE);
         }
     }
 
@@ -88,6 +99,16 @@ public final class StringLookupFactory {
     }
 
     /**
+     * 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}.
      *
@@ -142,6 +163,7 @@ public final class StringLookupFactory {
      * <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.
@@ -170,6 +192,7 @@ public final class StringLookupFactory {
      * <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
@@ -205,6 +228,7 @@ public final class StringLookupFactory {
      * <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>
@@ -236,6 +260,7 @@ public final class StringLookupFactory {
      * <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

http://git-wip-us.apache.org/repos/asf/commons-text/blob/49d81114/src/test/java/org/apache/commons/text/lookup/ConstantStringLookupBasicTest.java
----------------------------------------------------------------------
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"));
+    }
+}

http://git-wip-us.apache.org/repos/asf/commons-text/blob/49d81114/src/test/java/org/apache/commons/text/lookup/ConstantStringLookupTest.java
----------------------------------------------------------------------
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;
+    }
+}