From e45d774ad4700c38461d812f694f09e2b8f89d04 Mon Sep 17 00:00:00 2001 From: Nikoleta Verbeck Date: Fri, 1 Nov 2019 13:12:48 -0600 Subject: [PATCH 1/2] Initial add of PrefixConfiguration --- .../CompositeConfiguration.java | 40 +++ .../configuration2/PrefixConfiguration.java | 232 +++++++++++++++ .../TestPrefixConfiguration.java | 280 ++++++++++++++++++ 3 files changed, 552 insertions(+) create mode 100644 src/main/java/org/apache/commons/configuration2/PrefixConfiguration.java create mode 100644 src/test/java/org/apache/commons/configuration2/TestPrefixConfiguration.java diff --git a/src/main/java/org/apache/commons/configuration2/CompositeConfiguration.java b/src/main/java/org/apache/commons/configuration2/CompositeConfiguration.java index b73714b578..415f7f89e5 100644 --- a/src/main/java/org/apache/commons/configuration2/CompositeConfiguration.java +++ b/src/main/java/org/apache/commons/configuration2/CompositeConfiguration.java @@ -152,6 +152,26 @@ public void addConfiguration(final Configuration config) { addConfiguration(config, false); } + + /** + * Add a configuration with a prefix. + * + * @param config the configuration to add + */ + public void addConfigurationWithPrefix(final Configuration config, final String prefix) + { + addConfigurationWithPrefix(config, prefix, "."); + } + + /** + * Add a configuration with a prefix and delimiter. + * + * @param config the configuration to add + */ + public void addConfigurationWithPrefix(final Configuration config, final String prefix, final String delimiter) + { + addConfiguration(new PrefixConfiguration(config, prefix, delimiter), false); + } /** * Adds a child configuration and optionally makes it the in-memory @@ -220,6 +240,26 @@ public void addConfigurationFirst(final Configuration config) { addConfigurationFirst(config, false); } + + /** + * Add a configuration with a prefix. + * + * @param config the configuration to add + */ + public void addConfigurationFirstWithPrefix(final Configuration config, final String prefix) + { + addConfigurationWithPrefix(config, prefix, "."); + } + + /** + * Add a configuration with a prefix and delimiter. + * + * @param config the configuration to add + */ + public void addConfigurationFirstWithPrefix(final Configuration config, final String prefix, final String delimiter) + { + addConfigurationFirst(new PrefixConfiguration(config, prefix, delimiter), false); + } /** * Adds a child configuration to the start of the collection and optionally diff --git a/src/main/java/org/apache/commons/configuration2/PrefixConfiguration.java b/src/main/java/org/apache/commons/configuration2/PrefixConfiguration.java new file mode 100644 index 0000000000..3c3b3e35cb --- /dev/null +++ b/src/main/java/org/apache/commons/configuration2/PrefixConfiguration.java @@ -0,0 +1,232 @@ +package org.apache.commons.configuration2; + +import java.util.Iterator; + +import org.apache.commons.configuration2.convert.ListDelimiterHandler; + +public class PrefixConfiguration extends AbstractConfiguration { + /** The parent configuration. */ + protected Configuration child; + + /** The prefix used to select the properties. */ + protected String prefix; + + /** The prefix delimiter */ + protected String delimiter; + + public PrefixConfiguration(Configuration child, String prefix) { + this(child, prefix, null); + } + + public PrefixConfiguration(Configuration child, String prefix, String delimiter) { + if (child == null) { + throw new IllegalArgumentException("Child configuration must not be null!"); + } + + this.child = child; + this.prefix = prefix; + this.delimiter = delimiter; + initInterpolator(); + } + + /** + * Initializes the {@code ConfigurationInterpolator} for this sub configuration. + * This is a standard {@code ConfigurationInterpolator} which also references + * the {@code ConfigurationInterpolator} of the parent configuration. + */ + private void initInterpolator() { + getInterpolator().setParentInterpolator(getChild().getInterpolator()); + } + + /** + * Return the key in the child configuration associated to the specified key in + * the configuration. + * + * @param key The key in the configuration. + * @return the key in the context of this child configuration + */ + protected String getChildKey(final String key) { + if (key.startsWith(prefix)) { + String modifiedKey = null; + if (key.length() == prefix.length()) { + modifiedKey = ""; + } else { + final int i = prefix.length() + (delimiter != null ? delimiter.length() : 0); + modifiedKey = key.substring(i); + } + + return modifiedKey; + } else { + return key; + } + } + + /** + * Return the key in the configuration associated to the specified key in this + * child. + * + * @param key The key in the child. + * @return the key as to be used by the configuration + */ + protected String getParentKey(final String key) { + if ("".equals(key) || key == null) { + return prefix; + } + return delimiter == null ? prefix + key : prefix + delimiter + key; + } + + /** + * Return the child configuration for this configuration. + * + * @return the child configuration + */ + public Configuration getChild() { + return child; + } + + /** + * Return the prefix used to select the properties in the child configuration. + * + * @return the prefix used by this configuration + */ + public String getPrefix() { + return prefix; + } + + /** + * Set the prefix used to select the properties in the child configuration. + * + * @param prefix the prefix + */ + public void setPrefix(final String prefix) { + this.prefix = prefix; + } + + @Override + public void addPropertyDirect(String key, Object value) { + this.child.addProperty(this.getChildKey(key), value); + } + + @Override + protected void clearPropertyDirect(String key) { + this.child.clearProperty(this.getChildKey(key)); + } + + @Override + protected Iterator getKeysInternal() { + return new PrefixIterator(this.child.getKeys()); + } + + @Override + protected Object getPropertyInternal(String key) { + return this.child.getProperty(this.getChildKey(key)); + } + + @Override + protected boolean isEmptyInternal() { + return this.child.isEmpty(); + } + + @Override + protected boolean containsKeyInternal(String key) { + return this.child.containsKey(this.getChildKey(key)); + } + + /** + * {@inheritDoc} + * + * Change the behavior of the child configuration if it supports this feature. + */ + @Override + public void setThrowExceptionOnMissing(final boolean throwExceptionOnMissing) { + if (child instanceof AbstractConfiguration) { + ((AbstractConfiguration) child).setThrowExceptionOnMissing(throwExceptionOnMissing); + } else { + super.setThrowExceptionOnMissing(throwExceptionOnMissing); + } + } + + /** + * {@inheritDoc} + * + * The prefix inherits this feature from its child if it supports this feature. + */ + @Override + public boolean isThrowExceptionOnMissing() { + if (child instanceof AbstractConfiguration) { + return ((AbstractConfiguration) child).isThrowExceptionOnMissing(); + } + return super.isThrowExceptionOnMissing(); + } + + /** + * {@inheritDoc} If the child configuration extends + * {@link AbstractConfiguration}, the list delimiter handler is obtained from + * there. + */ + @Override + public ListDelimiterHandler getListDelimiterHandler() { + return child instanceof AbstractConfiguration ? ((AbstractConfiguration) child).getListDelimiterHandler() + : super.getListDelimiterHandler(); + } + + /** + * {@inheritDoc} If the child configuration extends + * {@link AbstractConfiguration}, the list delimiter handler is passed to the + * child. + */ + @Override + public void setListDelimiterHandler(final ListDelimiterHandler listDelimiterHandler) { + if (child instanceof AbstractConfiguration) { + ((AbstractConfiguration) child).setListDelimiterHandler(listDelimiterHandler); + } else { + super.setListDelimiterHandler(listDelimiterHandler); + } + } + + private class PrefixIterator implements Iterator { + /** Stores the wrapped iterator. */ + private final Iterator parentIterator; + + /** + * Creates a new instance of {@code SubsetIterator} and initializes it with the + * parent iterator. + * + * @param it the iterator of the parent configuration + */ + public PrefixIterator(final Iterator it) { + parentIterator = it; + } + + /** + * Checks whether there are more elements. Delegates to the parent iterator. + * + * @return a flag whether there are more elements + */ + @Override + public boolean hasNext() { + return parentIterator.hasNext(); + } + + /** + * Returns the next element in the iteration. This is the next key from the + * parent configuration, transformed to correspond to the point of view of this + * subset configuration. + * + * @return the next element + */ + @Override + public String next() { + return PrefixConfiguration.this.getParentKey(parentIterator.next()); + } + + /** + * Removes the current element from the iteration. Delegates to the parent + * iterator. + */ + @Override + public void remove() { + parentIterator.remove(); + } + } +} diff --git a/src/test/java/org/apache/commons/configuration2/TestPrefixConfiguration.java b/src/test/java/org/apache/commons/configuration2/TestPrefixConfiguration.java new file mode 100644 index 0000000000..b619412c1c --- /dev/null +++ b/src/test/java/org/apache/commons/configuration2/TestPrefixConfiguration.java @@ -0,0 +1,280 @@ +/* + * 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.configuration2; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; + +import org.apache.commons.configuration2.convert.DefaultListDelimiterHandler; +import org.apache.commons.configuration2.convert.ListDelimiterHandler; +import org.apache.commons.configuration2.interpol.ConfigurationInterpolator; +import org.easymock.EasyMock; +import org.junit.Test; + +/** + * Test case for the {@link PrefixConfiguration} class. + * + */ +public class TestPrefixConfiguration { + + /** + * Tries to create an instance without a parent configuration. + */ + @Test(expected = IllegalArgumentException.class) + public void testInitNoParent() { + new PrefixConfiguration(null, ""); + } + + @Test + public void testGetProperty() { + final Configuration conf = new BaseConfiguration(); + conf.setProperty("key1", "value1"); + + final Configuration prefix = new PrefixConfiguration(conf, "testing", "."); + assertFalse("the prefix is empty", prefix.isEmpty()); + assertTrue("'key1' not found in the subset", prefix.containsKey("testing.key1")); + } + + @Test + public void testSetProperty() { + final Configuration conf = new BaseConfiguration(); + final Configuration prefix = new PrefixConfiguration(conf, "test", "."); + + // set a property in the prefix and check the parent + prefix.setProperty("test.key1", "value1"); + assertEquals("key1 in the prefix configuration", "value1", prefix.getProperty("test.key1")); + assertEquals("test.key1 in the parent configuration", "value1", conf.getProperty("key1")); + + // set a property in the parent and check in the subset + conf.setProperty("key2", "value2"); + assertEquals("test.key2 in the parent configuration", "value2", conf.getProperty("key2")); + assertEquals("key2 in the prefix configuration", "value2", prefix.getProperty("test.key2")); + } + + @Test + public void testGetParentKey() { + final Configuration conf = new BaseConfiguration(); + // prefix with delimiter + PrefixConfiguration prefix = new PrefixConfiguration(conf, "prefix", "."); + assertEquals("parent key for \"key\"", "prefix.key", prefix.getParentKey("key")); + assertEquals("parent key for \"\"", "prefix", prefix.getParentKey("")); + + // prefix without delimiter + prefix = new PrefixConfiguration(conf, "prefix", null); + assertEquals("parent key for \"key\"", "prefixkey", prefix.getParentKey("key")); + assertEquals("parent key for \"\"", "prefix", prefix.getParentKey("")); + } + + @Test + public void testGetChildKey() { + final Configuration conf = new BaseConfiguration(); + // prefix with delimiter + PrefixConfiguration prefix = new PrefixConfiguration(conf, "prefix", "."); + assertEquals("parent key for \"prefixkey\"", "key", prefix.getChildKey("prefix.key")); + assertEquals("parent key for \"prefix\"", "", prefix.getChildKey("prefix")); + + // prefix without delimiter + prefix = new PrefixConfiguration(conf, "prefix", null); + assertEquals("parent key for \"prefixkey\"", "key", prefix.getChildKey("prefixkey")); + assertEquals("parent key for \"prefix\"", "", prefix.getChildKey("prefix")); + } + + @Test + public void testGetKeys() { + final Configuration conf = new BaseConfiguration(); + conf.setProperty("key1", "value1"); + conf.setProperty("key2", "value1"); + + final Configuration prefix = new PrefixConfiguration(conf, "test", "."); + + final Iterator it = prefix.getKeys(); + assertEquals("1st key", "test.key1", it.next()); + assertEquals("2nd key", "test.key2", it.next()); + assertFalse("too many elements", it.hasNext()); + } + + @Test + public void testGetKeysWithPrefix() { + final Configuration conf = new BaseConfiguration(); + conf.setProperty("abc", "value0"); + conf.setProperty("abc.key1", "value1"); + + final Configuration prefix = new PrefixConfiguration(conf, "test", "."); + + final Iterator it = prefix.getKeys("test.abc"); + assertEquals("1st key", "test.abc", it.next()); + assertEquals("2nd key", "test.abc.key1", it.next()); + assertFalse("too many elements", it.hasNext()); + } + + @Test + public void testGetList() { + final BaseConfiguration conf = new BaseConfiguration(); + conf.setListDelimiterHandler(new DefaultListDelimiterHandler(',')); + conf.setProperty("abc", "value0,value1"); + conf.addProperty("abc", "value3"); + + final Configuration prefix = new PrefixConfiguration(conf, "test", "."); + final List list = prefix.getList("test.abc", new ArrayList<>()); + assertEquals(3, list.size()); + } + + @Test + public void testGetChild() { + final Configuration conf = new BaseConfiguration(); + final PrefixConfiguration prefix = new PrefixConfiguration(conf, "prefix", "."); + + assertEquals("parent", conf, prefix.getChild()); + } + + @Test + public void testGetPrefix() { + final Configuration conf = new BaseConfiguration(); + final PrefixConfiguration prefix = new PrefixConfiguration(conf, "prefix", "."); + + assertEquals("prefix", "prefix", prefix.getPrefix()); + } + + @Test + public void testSetPrefix() { + final Configuration conf = new BaseConfiguration(); + final PrefixConfiguration prefix = new PrefixConfiguration(conf, null, "."); + prefix.setPrefix("prefix"); + + assertEquals("prefix", "prefix", prefix.getPrefix()); + } + + @Test + public void testThrowExceptionOnMissing() { + final BaseConfiguration config = new BaseConfiguration(); + config.setThrowExceptionOnMissing(true); + + final PrefixConfiguration prefix = new PrefixConfiguration(config, "prefix"); + + try { + prefix.getString("prefix.foo"); + fail("NoSuchElementException expected"); + } catch (final NoSuchElementException e) { + // expected + } + + config.setThrowExceptionOnMissing(false); + assertNull(prefix.getString("prefix.foo")); + + prefix.setThrowExceptionOnMissing(true); + try { + config.getString("prefix.foo"); + fail("NoSuchElementException expected"); + } catch (final NoSuchElementException e) { + // expected + } + } + + @Test + public void testClear() { + final Configuration config = new BaseConfiguration(); + config.setProperty("key1", "value1"); + + final PrefixConfiguration prefix = new PrefixConfiguration(config, "prefix"); + prefix.clear(); + + assertTrue("the prefix is not empty", prefix.isEmpty()); + } + + /** + * Tests whether a list delimiter handler is used correctly. + */ + @Test + public void testListDelimiterHandling() { + final BaseConfiguration config = new BaseConfiguration(); + final PrefixConfiguration prefix = new PrefixConfiguration(config, "prefix"); + + config.setListDelimiterHandler(new DefaultListDelimiterHandler('/')); + prefix.addProperty("prefix.list", "a/b/c"); + assertEquals("Wrong size of list", 3, prefix.getList("prefix.list").size()); + + ((AbstractConfiguration) prefix).setListDelimiterHandler(new DefaultListDelimiterHandler(';')); + prefix.addProperty("prefix.list2", "a;b;c"); + assertEquals("Wrong size of list2", 3, prefix.getList("prefix.list2").size()); + } + + /** + * Tests whether the list delimiter handler from the parent configuration is + * used. + */ + @Test + public void testGetListDelimiterHandlerFromParent() { + final BaseConfiguration config = new BaseConfiguration(); + final PrefixConfiguration prefix = new PrefixConfiguration(config, "prefix"); + final ListDelimiterHandler listHandler = new DefaultListDelimiterHandler(','); + config.setListDelimiterHandler(listHandler); + assertSame("Not list handler from parent", listHandler, prefix.getListDelimiterHandler()); + } + + /** + * Tests the case that the parent configuration is not derived from + * AbstractConfiguration and thus does not support a list delimiter handler. + */ + @Test + public void testSetListDelimiterHandlerParentNotSupported() { + final Configuration config = EasyMock.createNiceMock(Configuration.class); + EasyMock.replay(config); + final PrefixConfiguration prefix = new PrefixConfiguration(config, "prefix"); + final ListDelimiterHandler listHandler = new DefaultListDelimiterHandler(','); + prefix.setListDelimiterHandler(listHandler); + assertSame("List delimiter handler not set", listHandler, prefix.getListDelimiterHandler()); + } + + /** + * Tests manipulating the interpolator. + */ + @Test + public void testInterpolator() { + final BaseConfiguration config = new BaseConfiguration(); + final PrefixConfiguration prefix = new PrefixConfiguration(config, "prefix"); + InterpolationTestHelper.testGetInterpolator(prefix); + } + + @Test + public void testLocalLookupsInInterpolatorAreInherited() { + final BaseConfiguration config = new BaseConfiguration(); + final ConfigurationInterpolator interpolator = config.getInterpolator(); + interpolator.registerLookup("brackets", key -> "(" + key + ")"); + config.setProperty("var", "${brackets:x}"); + final PrefixConfiguration prefix = new PrefixConfiguration(config, "prefix", "."); + assertEquals("Local lookup was not inherited", "(x)", prefix.getString("prefix.var", "")); + } + + @Test + public void testInterpolationForKeysOfTheParent() { + final BaseConfiguration config = new BaseConfiguration(); + config.setProperty("test", "junit"); + config.setProperty("key", "${test}"); + final PrefixConfiguration prefix = new PrefixConfiguration(config, "prefix", "."); + assertEquals("Interpolation does not resolve parent keys", "junit", prefix.getString("prefix.key", "")); + } +} From ece0d8d2b0085d1ad70ca3ad544d3b1e717301db Mon Sep 17 00:00:00 2001 From: Nikoleta Verbeck Date: Fri, 1 Nov 2019 13:28:20 -0600 Subject: [PATCH 2/2] Added license header --- .../configuration2/PrefixConfiguration.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/main/java/org/apache/commons/configuration2/PrefixConfiguration.java b/src/main/java/org/apache/commons/configuration2/PrefixConfiguration.java index 3c3b3e35cb..ae7250612c 100644 --- a/src/main/java/org/apache/commons/configuration2/PrefixConfiguration.java +++ b/src/main/java/org/apache/commons/configuration2/PrefixConfiguration.java @@ -1,3 +1,20 @@ +/* + * 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.configuration2; import java.util.Iterator;