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