This is an automated email from the ASF dual-hosted git repository.

garydgregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-collections.git


The following commit(s) were added to refs/heads/master by this push:
     new 04b30be69 Fix nextKey skipping a key and throwing for keys not in the 
map (#728)
04b30be69 is described below

commit 04b30be69f4355f218d587703bc20b1a591c3e3c
Author: Naveed Khan <[email protected]>
AuthorDate: Sat Aug 29 21:38:18 2026 +0000

    Fix nextKey skipping a key and throwing for keys not in the map (#728)
    
    * fix nextKey skipping a key and throwing for keys not in the map
    
    * use NavigableMap.higherKey in nextKey and add absent-key tests
    
    Switch AbstractSortedMapDecorator.nextKey and DualTreeBidiMap.nextKey to
    NavigableMap.higherKey when the underlying map supports it. The containsKey
    guard stays: higherKey returns the strict successor whether or not the key
    is present, so an absent in-range key would otherwise get the successor
    instead of null. Hoist the guard in DualTreeBidiMap.nextKey above the
    OrderedMap delegation so both branches agree on absent keys. Add an
    explicit DualTreeBidiMap test with an absent key between existing keys and
    an explicit UnmodifiableSortedMap nextKey test.
---
 .../commons/collections4/bidimap/DualTreeBidiMap.java |  6 +++++-
 .../collections4/map/AbstractSortedMapDecorator.java  |  8 ++++++++
 .../bidimap/AbstractOrderedBidiMapTest.java           |  2 ++
 .../collections4/bidimap/DualTreeBidiMapTest.java     | 19 +++++++++++++++++++
 .../collections4/map/FixedSizeSortedMapTest.java      | 18 ++++++++++++++++++
 .../collections4/map/UnmodifiableSortedMapTest.java   | 16 ++++++++++++++++
 6 files changed, 68 insertions(+), 1 deletion(-)

diff --git 
a/src/main/java/org/apache/commons/collections4/bidimap/DualTreeBidiMap.java 
b/src/main/java/org/apache/commons/collections4/bidimap/DualTreeBidiMap.java
index d49d1a9e3..dbf80c8ce 100644
--- a/src/main/java/org/apache/commons/collections4/bidimap/DualTreeBidiMap.java
+++ b/src/main/java/org/apache/commons/collections4/bidimap/DualTreeBidiMap.java
@@ -25,6 +25,7 @@ import java.util.Comparator;
 import java.util.Iterator;
 import java.util.ListIterator;
 import java.util.Map;
+import java.util.NavigableMap;
 import java.util.SortedMap;
 import java.util.TreeMap;
 
@@ -364,13 +365,16 @@ public class DualTreeBidiMap<K, V> extends 
AbstractDualBidiMap<K, V>
 
     @Override
     public K nextKey(final K key) {
-        if (isEmpty()) {
+        if (isEmpty() || !normalMap.containsKey(key)) {
             return null;
         }
         if (normalMap instanceof OrderedMap) {
             return ((OrderedMap<K, ?>) normalMap).nextKey(key);
         }
         final SortedMap<K, V> sm = (SortedMap<K, V>) normalMap;
+        if (sm instanceof NavigableMap) {
+            return ((NavigableMap<K, V>) sm).higherKey(key);
+        }
         final Iterator<K> it = sm.tailMap(key).keySet().iterator();
         it.next();
         if (it.hasNext()) {
diff --git 
a/src/main/java/org/apache/commons/collections4/map/AbstractSortedMapDecorator.java
 
b/src/main/java/org/apache/commons/collections4/map/AbstractSortedMapDecorator.java
index 6f8d06a57..6ecb2f938 100644
--- 
a/src/main/java/org/apache/commons/collections4/map/AbstractSortedMapDecorator.java
+++ 
b/src/main/java/org/apache/commons/collections4/map/AbstractSortedMapDecorator.java
@@ -20,6 +20,7 @@ import java.util.Comparator;
 import java.util.Iterator;
 import java.util.ListIterator;
 import java.util.Map;
+import java.util.NavigableMap;
 import java.util.Set;
 import java.util.SortedMap;
 
@@ -151,6 +152,13 @@ public abstract class AbstractSortedMapDecorator<K, V> 
extends AbstractMapDecora
 
     @Override
     public K nextKey(final K key) {
+        if (!containsKey(key)) {
+            return null;
+        }
+        final SortedMap<K, V> map = decorated();
+        if (map instanceof NavigableMap) {
+            return ((NavigableMap<K, V>) map).higherKey(key);
+        }
         final Iterator<K> it = tailMap(key).keySet().iterator();
         it.next();
         return it.hasNext() ? it.next() : null;
diff --git 
a/src/test/java/org/apache/commons/collections4/bidimap/AbstractOrderedBidiMapTest.java
 
b/src/test/java/org/apache/commons/collections4/bidimap/AbstractOrderedBidiMapTest.java
index 585089436..a73055d65 100644
--- 
a/src/test/java/org/apache/commons/collections4/bidimap/AbstractOrderedBidiMapTest.java
+++ 
b/src/test/java/org/apache/commons/collections4/bidimap/AbstractOrderedBidiMapTest.java
@@ -157,6 +157,8 @@ public abstract class AbstractOrderedBidiMapTest<K, V> 
extends AbstractBidiMapTe
             confirmedLast = confirmedObject;
         }
         assertNull(bidi.nextKey(confirmedLast));
+        // a key that is not in the map has no next key
+        assertNull(bidi.nextKey(getOtherKeys()[0]));
 
         if (!isAllowNullKey()) {
             final OrderedBidiMap<K, V> finalBidi = bidi;
diff --git 
a/src/test/java/org/apache/commons/collections4/bidimap/DualTreeBidiMapTest.java
 
b/src/test/java/org/apache/commons/collections4/bidimap/DualTreeBidiMapTest.java
index c4a1f28e8..1628e841d 100644
--- 
a/src/test/java/org/apache/commons/collections4/bidimap/DualTreeBidiMapTest.java
+++ 
b/src/test/java/org/apache/commons/collections4/bidimap/DualTreeBidiMapTest.java
@@ -16,6 +16,11 @@
  */
 package org.apache.commons.collections4.bidimap;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.junit.jupiter.api.Test;
+
 /**
  * JUnit tests.
  */
@@ -29,6 +34,20 @@ public class DualTreeBidiMapTest<K extends Comparable<K>, V 
extends Comparable<V
         return new DualTreeBidiMap<>();
     }
 
+    @Test
+    void testNextKeyAbsentKey() {
+        final DualTreeBidiMap<String, Integer> map = new DualTreeBidiMap<>();
+        map.put("a", 1);
+        map.put("c", 3);
+        map.put("e", 5);
+        // an absent key inside the key range must not return the successor
+        assertNull(map.nextKey("b"));
+        // an absent key past the last key must not throw
+        assertNull(map.nextKey("z"));
+        assertEquals("c", map.nextKey("a"));
+        assertNull(map.nextKey("e"));
+    }
+
 //    void testCreate() throws Exception {
 //        resetEmpty();
 //        writeExternalFormToDisk((java.io.Serializable) map, 
"src/test/resources/data/test/DualTreeBidiMap.emptyCollection.version4.obj");
diff --git 
a/src/test/java/org/apache/commons/collections4/map/FixedSizeSortedMapTest.java 
b/src/test/java/org/apache/commons/collections4/map/FixedSizeSortedMapTest.java
index d6231339f..0573e6d62 100644
--- 
a/src/test/java/org/apache/commons/collections4/map/FixedSizeSortedMapTest.java
+++ 
b/src/test/java/org/apache/commons/collections4/map/FixedSizeSortedMapTest.java
@@ -18,6 +18,7 @@ package org.apache.commons.collections4.map;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
 import java.util.Collections;
@@ -68,6 +69,23 @@ public class FixedSizeSortedMapTest<K, V> extends 
AbstractSortedMapTest<K, V> {
         return FixedSizeSortedMap.fixedSizeSortedMap(new TreeMap<>());
     }
 
+    @Test
+    void testNextKey() {
+        final SortedMap<String, String> base = new TreeMap<>();
+        base.put("a", "1");
+        base.put("c", "3");
+        base.put("e", "5");
+        final FixedSizeSortedMap<String, String> fixed = 
FixedSizeSortedMap.fixedSizeSortedMap(base);
+        // a present key returns its successor, or null once at the end
+        assertEquals("c", fixed.nextKey("a"));
+        assertEquals("e", fixed.nextKey("c"));
+        assertNull(fixed.nextKey("e"));
+        // a key that is not in the map has no next key, whether it is in 
range or past the end
+        assertNull(fixed.nextKey("b"));
+        assertNull(fixed.nextKey("z"));
+        assertNull(FixedSizeSortedMap.fixedSizeSortedMap(new TreeMap<String, 
String>()).nextKey("a"));
+    }
+
     @Test
     void testPutAllAllowsUpdatesRejectsNewKeys() {
         final SortedMap<String, String> base = new TreeMap<>();
diff --git 
a/src/test/java/org/apache/commons/collections4/map/UnmodifiableSortedMapTest.java
 
b/src/test/java/org/apache/commons/collections4/map/UnmodifiableSortedMapTest.java
index f31c197cb..7ad336be9 100644
--- 
a/src/test/java/org/apache/commons/collections4/map/UnmodifiableSortedMapTest.java
+++ 
b/src/test/java/org/apache/commons/collections4/map/UnmodifiableSortedMapTest.java
@@ -16,6 +16,8 @@
  */
 package org.apache.commons.collections4.map;
 
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
 import static org.junit.jupiter.api.Assertions.assertSame;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -23,6 +25,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
 import java.util.SortedMap;
 import java.util.TreeMap;
 
+import org.apache.commons.collections4.OrderedMap;
 import org.apache.commons.collections4.Unmodifiable;
 import org.junit.jupiter.api.Test;
 
@@ -67,6 +70,19 @@ public class UnmodifiableSortedMapTest<K, V> extends 
AbstractSortedMapTest<K, V>
         return UnmodifiableSortedMap.unmodifiableSortedMap(new TreeMap<>());
     }
 
+    @Test
+    void testNextKey() {
+        final SortedMap<String, String> base = new TreeMap<>();
+        base.put("a", "1");
+        base.put("c", "3");
+        final OrderedMap<String, String> map = (OrderedMap<String, String>) 
UnmodifiableSortedMap.unmodifiableSortedMap(base);
+        assertEquals("c", map.nextKey("a"));
+        // an absent key has no next key, whether inside the key range or past 
the end
+        assertNull(map.nextKey("b"));
+        assertNull(map.nextKey("c"));
+        assertNull(map.nextKey("z"));
+    }
+
     @Test
     void testDecorateFactory() {
         final SortedMap<K, V> map = makeFullMap();

Reply via email to