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

Reply via email to