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 ca83f8699 [COLLECTIONS-892] Fix unique IndexedCollection add atomicity 
(#688)
ca83f8699 is described below

commit ca83f8699e10403be6987b92fb1a877743df8262
Author: OldTruckDriver <[email protected]>
AuthorDate: Sat Jun 20 03:26:26 2026 +1000

    [COLLECTIONS-892] Fix unique IndexedCollection add atomicity (#688)
    
    add(Object) called super.add() before addToIndex() checked the unique-key 
constraint,
    so a duplicate-key add mutated the decorated collection before throwing
    IllegalArgumentException. For a unique index, check the key up front and 
throw before
    adding. Add a test asserting the collection is unchanged after a rejected 
add.
    
    Reviewed-by: OpenAI Codex
    Reviewed-by: Anthropic Claude Code
---
 src/changes/changes.xml                                       |  1 +
 .../commons/collections4/collection/IndexedCollection.java    | 11 +++++++++++
 .../collections4/collection/IndexedCollectionTest.java        | 11 +++++++++++
 3 files changed, 23 insertions(+)

diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 4c334d631..1db10ee3f 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -52,6 +52,7 @@
     <action type="fix" dev="ggregory" due-to="Dexter.k, Gary 
Gregory">Re-validate entries in PredicatedMap/PredicatedCollection readObject 
(#682).</action>
     <action type="fix" dev="ggregory" due-to="Ruiqi Dong, Gary Gregory" 
issue="COLLECTIONS-889">IndexedCollection.remove(Object) removes all values for 
a non-unique index key.</action>
     <action type="fix" dev="ggregory" due-to="Ruiqi Dong, Gary Gregory" 
issue="COLLECTIONS-890">TransformIterator throws NullPointerException when its 
transformer is null.</action>
+    <action type="fix" dev="ggregory" due-to="Ruiqi Dong, Gary Gregory" 
issue="COLLECTIONS-892">IndexedCollection.add(Object) mutates unique indexed 
collections before rejecting duplicate keys.</action>
     <action type="fix" dev="ggregory" due-to="Maxim Safronov, Gary Gregory" 
issue="COLLECTIONS-878">MapUtils.invertMap() improves HashMap construction 
(#652).</action>
     <action type="fix" dev="ggregory" due-to="Dexter.k, Gary Gregory">Validate 
order and uniqueness invariants in decorator readObject() (#684).</action>
     <action type="fix" dev="ggregory" due-to="Gary Gregory">TransformIterator 
now fail-fast on a null Iterator (#686).</action>
diff --git 
a/src/main/java/org/apache/commons/collections4/collection/IndexedCollection.java
 
b/src/main/java/org/apache/commons/collections4/collection/IndexedCollection.java
index c534703a3..71d4b5ed3 100644
--- 
a/src/main/java/org/apache/commons/collections4/collection/IndexedCollection.java
+++ 
b/src/main/java/org/apache/commons/collections4/collection/IndexedCollection.java
@@ -113,6 +113,17 @@ public class IndexedCollection<K, C> extends 
AbstractCollectionDecorator<C> {
      */
     @Override
     public boolean add(final C object) {
+        if (uniqueIndex) {
+            final K key = keyTransformer.apply(object);
+            if (index.containsKey(key)) {
+                throw new IllegalArgumentException("Duplicate key in uniquely 
indexed collection.");
+            }
+            final boolean added = super.add(object);
+            if (added) {
+                index.put(key, object);
+            }
+            return added;
+        }
         final boolean added = super.add(object);
         if (added) {
             addToIndex(object);
diff --git 
a/src/test/java/org/apache/commons/collections4/collection/IndexedCollectionTest.java
 
b/src/test/java/org/apache/commons/collections4/collection/IndexedCollectionTest.java
index db427309d..c79810f17 100644
--- 
a/src/test/java/org/apache/commons/collections4/collection/IndexedCollectionTest.java
+++ 
b/src/test/java/org/apache/commons/collections4/collection/IndexedCollectionTest.java
@@ -135,6 +135,17 @@ class IndexedCollectionTest extends 
AbstractCollectionTest<String> {
         assertThrows(IllegalArgumentException.class, () -> coll.add("1"));
     }
 
+    @Test
+    void testFailedUniqueAddDoesNotPolluteDecoratedCollection() {
+        final IndexedCollection<Integer, String> indexed = 
decorateUniqueCollection(new ArrayList<>());
+        indexed.add("01");
+
+        assertThrows(IllegalArgumentException.class, () -> indexed.add("1"));
+        assertEquals(1, indexed.size());
+        assertEquals(asList("01"), new ArrayList<>(indexed));
+        assertEquals("01", indexed.get(1));
+    }
+
     @Test
     void testReindexUpdatesIndexWhenDecoratedCollectionIsModifiedSeparately() 
throws Exception {
         final Collection<String> original = new ArrayList<>();

Reply via email to