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;
+ }
+}