diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index eb1d1315dc..ad12f961a0 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -64,6 +64,7 @@
Fix integer overflow in SetOperations.cosineSimilarity (#693).
Validate deserialized size in CircularFifoQueue.readObject (#678).
Do not mutate the input map in SwitchTransformer.switchTransformer (#696).
+ Do not mutate the input map in SwitchClosure.switchClosure, ClosureUtils.switchMapClosure and TransformerUtils.switchMapTransformer.
Add generics to UnmodifiableIterator for the wrapped type.
Add a Maven benchmark profile for JMH.
diff --git a/src/main/java/org/apache/commons/collections4/ClosureUtils.java b/src/main/java/org/apache/commons/collections4/ClosureUtils.java
index 4ad525c4dd..b90584a577 100644
--- a/src/main/java/org/apache/commons/collections4/ClosureUtils.java
+++ b/src/main/java/org/apache/commons/collections4/ClosureUtils.java
@@ -17,6 +17,7 @@
package org.apache.commons.collections4;
import java.util.Collection;
+import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
@@ -324,12 +325,14 @@ public static Closure switchClosure(final Predicate super E>[] predicat
@SuppressWarnings("unchecked")
public static Closure switchMapClosure(final Map extends E, Closure> objectsAndClosures) {
Objects.requireNonNull(objectsAndClosures, "objectsAndClosures");
- final Closure super E> def = objectsAndClosures.remove(null);
- final int size = objectsAndClosures.size();
+ // copy so the caller's map is not mutated
+ final Map extends E, Closure> objects = new LinkedHashMap<>(objectsAndClosures);
+ final Closure super E> def = objects.remove(null);
+ final int size = objects.size();
final Closure super E>[] trs = new Closure[size];
final Predicate[] preds = new Predicate[size];
int i = 0;
- for (final Map.Entry extends E, Closure> entry : objectsAndClosures.entrySet()) {
+ for (final Map.Entry extends E, Closure> entry : objects.entrySet()) {
preds[i] = EqualPredicate.equalPredicate(entry.getKey());
trs[i] = entry.getValue();
i++;
diff --git a/src/main/java/org/apache/commons/collections4/TransformerUtils.java b/src/main/java/org/apache/commons/collections4/TransformerUtils.java
index 3b1bdceabe..8376b78c2c 100644
--- a/src/main/java/org/apache/commons/collections4/TransformerUtils.java
+++ b/src/main/java/org/apache/commons/collections4/TransformerUtils.java
@@ -17,6 +17,7 @@
package org.apache.commons.collections4;
import java.util.Collection;
+import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
@@ -372,12 +373,14 @@ public static Transformer switchMapTransformer(
final Map> objectsAndTransformers) {
Objects.requireNonNull(objectsAndTransformers, "objectsAndTransformers");
- final Transformer super I, ? extends O> def = objectsAndTransformers.remove(null);
- final int size = objectsAndTransformers.size();
+ // copy so the caller's map is not mutated
+ final Map> objects = new LinkedHashMap<>(objectsAndTransformers);
+ final Transformer super I, ? extends O> def = objects.remove(null);
+ final int size = objects.size();
final Transformer super I, ? extends O>[] trs = new Transformer[size];
final Predicate[] preds = new Predicate[size];
int i = 0;
- for (final Map.Entry> entry : objectsAndTransformers.entrySet()) {
+ for (final Map.Entry> entry : objects.entrySet()) {
preds[i] = EqualPredicate.equalPredicate(entry.getKey());
trs[i++] = entry.getValue();
}
diff --git a/src/main/java/org/apache/commons/collections4/functors/SwitchClosure.java b/src/main/java/org/apache/commons/collections4/functors/SwitchClosure.java
index d3aff19856..12329473bd 100644
--- a/src/main/java/org/apache/commons/collections4/functors/SwitchClosure.java
+++ b/src/main/java/org/apache/commons/collections4/functors/SwitchClosure.java
@@ -17,6 +17,7 @@
package org.apache.commons.collections4.functors;
import java.io.Serializable;
+import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
@@ -57,16 +58,17 @@ public class SwitchClosure implements Closure, Serializable {
@SuppressWarnings("unchecked")
public static Closure switchClosure(final Map, Closure> predicatesAndClosures) {
Objects.requireNonNull(predicatesAndClosures, "predicatesAndClosures");
- // convert to array like this to guarantee iterator() ordering
- final Closure super E> defaultClosure = predicatesAndClosures.remove(null);
- final int size = predicatesAndClosures.size();
+ // copy so the caller's map is not mutated; LinkedHashMap preserves iterator() ordering
+ final Map, Closure> entries = new LinkedHashMap<>(predicatesAndClosures);
+ final Closure super E> defaultClosure = entries.remove(null);
+ final int size = entries.size();
if (size == 0) {
return (Closure) (defaultClosure == null ? NOPClosure.nopClosure() : defaultClosure);
}
final Closure[] closures = new Closure[size];
final Predicate[] preds = new Predicate[size];
int i = 0;
- for (final Map.Entry, Closure> entry : predicatesAndClosures.entrySet()) {
+ for (final Map.Entry, Closure> entry : entries.entrySet()) {
preds[i] = entry.getKey();
closures[i] = entry.getValue();
i++;
diff --git a/src/test/java/org/apache/commons/collections4/ClosureUtilsTest.java b/src/test/java/org/apache/commons/collections4/ClosureUtilsTest.java
index 546b2e0dd8..4a7ea5f193 100644
--- a/src/test/java/org/apache/commons/collections4/ClosureUtilsTest.java
+++ b/src/test/java/org/apache/commons/collections4/ClosureUtilsTest.java
@@ -20,6 +20,7 @@
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.util.ArrayList;
@@ -309,6 +310,33 @@ void testSwitchMapClosure() {
assertThrows(NullPointerException.class, () -> ClosureUtils.switchMapClosure(null));
}
+ @Test
+ void testSwitchClosureDoesNotMutateInputMap() {
+ final MockClosure def = new MockClosure<>();
+ final MockClosure match = new MockClosure<>();
+ final Map, Closure> predicateMap = new HashMap<>();
+ predicateMap.put(null, def);
+ predicateMap.put(EqualPredicate.equalPredicate("HELLO"), match);
+ final Closure closure = ClosureUtils.switchClosure(predicateMap);
+ assertTrue(predicateMap.containsKey(null));
+ closure.execute("HELLO");
+ closure.execute("WORLD");
+ assertEquals(1, match.count);
+ assertEquals(1, def.count);
+
+ final MockClosure mapDef = new MockClosure<>();
+ final MockClosure mapMatch = new MockClosure<>();
+ final Map> objectMap = new HashMap<>();
+ objectMap.put(null, mapDef);
+ objectMap.put("HELLO", mapMatch);
+ final Closure mapClosure = ClosureUtils.switchMapClosure(objectMap);
+ assertTrue(objectMap.containsKey(null));
+ mapClosure.execute("HELLO");
+ mapClosure.execute("WORLD");
+ assertEquals(1, mapMatch.count);
+ assertEquals(1, mapDef.count);
+ }
+
@Test
void testTransformerClosure() {
final MockTransformer