This is an automated email from the ASF dual-hosted git repository. ggregory pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/commons-configuration.git
The following commit(s) were added to refs/heads/master by this push: new 77ad6773 Take prefix delimiter into account when SubsetConfiguration.getKeysInternal() is called (#300) 77ad6773 is described below commit 77ad67731e495f7aa0db1823d58b631fc04d8657 Author: KeijoB <keijo-b...@web.de> AuthorDate: Mon Oct 23 14:04:24 2023 +0200 Take prefix delimiter into account when SubsetConfiguration.getKeysInternal() is called (#300) * Take prefix delimiter into account when SubsetConfiguration.getKeysInternal() is called * Take prefix delimiter into account when SubsetConfiguration.getKeysInternal() is called - added JUnit test * Take prefix delimiter into account when SubsetConfiguration.getKeysInternal() is called - added JUnit test for negative example * Add Javadoc since tag * Update Javadoc since tag * Add Javadoc since tag --------- Co-authored-by: Gary Gregory <garydgreg...@users.noreply.github.com> --- .../configuration2/AbstractConfiguration.java | 30 +++++++++++ .../configuration2/ImmutableConfiguration.java | 20 ++++++++ .../configuration2/PrefixedKeysIterator.java | 19 ++++++- .../configuration2/SubsetConfiguration.java | 2 +- .../configuration2/TestSubsetConfiguration.java | 58 ++++++++++++++++++++++ 5 files changed, 127 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/apache/commons/configuration2/AbstractConfiguration.java b/src/main/java/org/apache/commons/configuration2/AbstractConfiguration.java index 8125a57e..e6fdd6b7 100644 --- a/src/main/java/org/apache/commons/configuration2/AbstractConfiguration.java +++ b/src/main/java/org/apache/commons/configuration2/AbstractConfiguration.java @@ -770,6 +770,21 @@ public abstract class AbstractConfiguration extends BaseEventSource implements C } } + /** + * {@inheritDoc} This implementation returns keys that either match the prefix or start with the prefix followed by the delimiter. + * So the call {@code getKeys("db");} will find the keys {@code db}, {@code db@user}, or {@code db@password}, + * but not the key {@code dbdriver}. + */ + @Override + public final Iterator<String> getKeys(final String prefix, final String delimiter) { + beginRead(false); + try { + return getKeysInternal(prefix, delimiter); + } finally { + endRead(); + } + } + /** * Actually creates an iterator for iterating over the keys in this configuration. This method is called by * {@code getKeys()}, it has to be defined by concrete subclasses. @@ -793,6 +808,21 @@ public abstract class AbstractConfiguration extends BaseEventSource implements C return new PrefixedKeysIterator(getKeysInternal(), prefix); } + /** + * Gets an {@code Iterator} with all property keys starting with the specified prefix and specified delimiter. This method is called by + * {@link #getKeys(String)}. It is fully implemented by delegating to {@code getKeysInternal()} and returning a special + * iterator which filters for the passed in prefix. Subclasses can override it if they can provide a more efficient way + * to iterate over specific keys only. + * + * @param prefix the prefix for the keys to be taken into account + * @param delimiter the prefix delimiter + * @return an {@code Iterator} returning the filtered keys + * @since 2.10.0 + */ + protected Iterator<String> getKeysInternal(final String prefix, final String delimiter) { + return new PrefixedKeysIterator(getKeysInternal(), prefix, delimiter); + } + /** * {@inheritDoc} This implementation ensures proper synchronization. Subclasses have to define the abstract * {@code getPropertyInternal()} method which is called from here. diff --git a/src/main/java/org/apache/commons/configuration2/ImmutableConfiguration.java b/src/main/java/org/apache/commons/configuration2/ImmutableConfiguration.java index b6c8d7f2..06af63e5 100644 --- a/src/main/java/org/apache/commons/configuration2/ImmutableConfiguration.java +++ b/src/main/java/org/apache/commons/configuration2/ImmutableConfiguration.java @@ -508,6 +508,26 @@ public interface ImmutableConfiguration { */ Iterator<String> getKeys(String prefix); + /** + * Gets the list of the keys contained in the configuration that match the specified prefix. For instance, if the + * configuration contains the following keys:<br> + * {@code db@user, db@pwd, db@url, window.xpos, window.ypos},<br> + * an invocation of {@code getKeys("db","@");}<br> + * will return the keys below:<br> + * {@code db@user, db@pwd, db@url}.<br> + * Note that the prefix itself is included in the result set if there is a matching key. The exact behavior - how the + * prefix is actually interpreted - depends on a concrete implementation. + * + * @param prefix The prefix to test against. + * @param delimiter The prefix delimiter. + * @return An Iterator of keys that match the prefix. + * @see #getKeys() + * @since 2.10.0 + */ + default Iterator<String> getKeys(String prefix, String delimiter) { + return null; + } + /** * Gets a list of typed objects associated with the given configuration key returning a null if the key doesn't map to * an existing object. diff --git a/src/main/java/org/apache/commons/configuration2/PrefixedKeysIterator.java b/src/main/java/org/apache/commons/configuration2/PrefixedKeysIterator.java index 07b7c216..bb365f68 100644 --- a/src/main/java/org/apache/commons/configuration2/PrefixedKeysIterator.java +++ b/src/main/java/org/apache/commons/configuration2/PrefixedKeysIterator.java @@ -35,6 +35,9 @@ class PrefixedKeysIterator implements Iterator<String> { /** Stores the prefix. */ private final String prefix; + /** Stores the prefix delimiter. Default delimiter is "." */ + private final String delimiter; + /** Stores the next element in the iteration. */ private String nextElement; @@ -49,8 +52,22 @@ class PrefixedKeysIterator implements Iterator<String> { * @param keyPrefix the prefix of the allowed keys */ public PrefixedKeysIterator(final Iterator<String> wrappedIterator, final String keyPrefix) { + this(wrappedIterator, keyPrefix, "."); + } + + /** + * Creates a new instance of {@code PrefixedKeysIterator} and sets the wrapped iterator and the prefix as well as + * the delimiter for the preix for the accepted keys. + * + * @param wrappedIterator the wrapped iterator + * @param keyPrefix the prefix of the allowed keys + * @param prefixDelimiter the prefix delimiter + * @since 2.10.0 + */ + public PrefixedKeysIterator(final Iterator<String> wrappedIterator, final String keyPrefix, final String prefixDelimiter) { iterator = wrappedIterator; prefix = keyPrefix; + delimiter = prefixDelimiter; } /** @@ -101,7 +118,7 @@ class PrefixedKeysIterator implements Iterator<String> { private boolean setNextElement() { while (iterator.hasNext()) { final String key = iterator.next(); - if (key.startsWith(prefix + ".") || key.equals(prefix)) { + if (key.startsWith(prefix + delimiter) || key.equals(prefix)) { nextElement = key; nextElementSet = true; return true; diff --git a/src/main/java/org/apache/commons/configuration2/SubsetConfiguration.java b/src/main/java/org/apache/commons/configuration2/SubsetConfiguration.java index 3646b5ff..cf6a645a 100644 --- a/src/main/java/org/apache/commons/configuration2/SubsetConfiguration.java +++ b/src/main/java/org/apache/commons/configuration2/SubsetConfiguration.java @@ -170,7 +170,7 @@ public class SubsetConfiguration extends AbstractConfiguration { @Override protected Iterator<String> getKeysInternal() { - return new SubsetIterator(parent.getKeys(prefix)); + return new SubsetIterator(parent.getKeys(prefix, delimiter)); } /** diff --git a/src/test/java/org/apache/commons/configuration2/TestSubsetConfiguration.java b/src/test/java/org/apache/commons/configuration2/TestSubsetConfiguration.java index 3c5918f3..962dd43e 100644 --- a/src/test/java/org/apache/commons/configuration2/TestSubsetConfiguration.java +++ b/src/test/java/org/apache/commons/configuration2/TestSubsetConfiguration.java @@ -25,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; +import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; @@ -307,4 +308,61 @@ public class TestSubsetConfiguration { subset.setThrowExceptionOnMissing(true); assertThrows(NoSuchElementException.class, () -> config.getString("foo")); } + + @Test + public void testPrefixDelimiter(){ + final BaseConfiguration config = new BaseConfiguration(); + config.setProperty("part1.part2@test.key1", "value1"); + config.setProperty("part1.part2", "value2"); + config.setProperty("part3.part4@testing.key2", "value3"); + + final SubsetConfiguration subset = new SubsetConfiguration(config, "part1.part2", "@"); + // Check subset properties + assertEquals("value1", subset.getString("test.key1")); + assertEquals("value2", subset.getString("")); + assertNull(subset.getString("testing.key2")); + + // Check for empty subset configuration and iterator + assertEquals(2, subset.size()); + assertFalse(subset.isEmpty()); + assertTrue(subset.getKeys().hasNext()); + } + + @Test + public void testPrefixDelimiterNegativeTest(){ + final BaseConfiguration config = new BaseConfiguration(); + config.setProperty("part1.part2@test.key1", "value1"); + config.setProperty("part3.part4@testing.key2", "value2"); + + final SubsetConfiguration subset = new SubsetConfiguration(config, "part1.part2", "@") { + // Anonymous inner class declaration to override SubsetConfiguration.getKeysInternal() - Call + // ImutableConfiguration.getKeys(String) on the parent configuration of the SubsetConfiguration in order to + // not consequently pass the prefix delimiter + @Override + protected Iterator<String> getKeysInternal() { + Class<?> subsetIteratorClass; + try { + subsetIteratorClass = Class + .forName("org.apache.commons.configuration2.SubsetConfiguration$SubsetIterator"); + Constructor<?> ctor = subsetIteratorClass.getDeclaredConstructor(SubsetConfiguration.class, + Iterator.class); + ctor.setAccessible(true); + + return (Iterator<String>) ctor.newInstance(this, parent.getKeys("part1.part2")); + } catch (Exception ex) { + throw new IllegalArgumentException(ex); + } + } + }; + + // Check subset properties - contains one property + assertEquals("value1", subset.getString("test.key1")); + assertNull(subset.getString("testing.key2")); + + // Check for empty subset configuration and iterator - even if the SubsetConfiguration contains properties, like + // checked previously its states that it is empty + assertEquals(0, subset.size()); + assertTrue(subset.isEmpty()); + assertFalse(subset.getKeys().hasNext()); + } }