@@ -183,9 +188,10 @@ private void checkLocked() {
@Override
public boolean hasNext() {
lockChain();
- updateCurrentIterator();
- lastUsedIterator = currentIterator;
- return currentIterator.hasNext();
+ if (cachedHasNextValue == null) {
+ updateCurrentIterator();
+ }
+ return cachedHasNextValue;
}
/**
@@ -219,9 +225,11 @@ private void lockChain() {
@Override
public E next() {
lockChain();
- updateCurrentIterator();
+ if (cachedHasNextValue == null) {
+ updateCurrentIterator();
+ }
lastUsedIterator = currentIterator;
-
+ cachedHasNextValue = null;
return currentIterator.next();
}
@@ -241,10 +249,11 @@ public E next() {
@Override
public void remove() {
lockChain();
- if (currentIterator == null) {
- updateCurrentIterator();
+ if (lastUsedIterator == null) {
+ throw new IllegalStateException("remove() has been invoked without next()");
}
lastUsedIterator.remove();
+ lastUsedIterator = null; // must never be used twice without next() being invoked
}
/**
@@ -267,13 +276,16 @@ protected void updateCurrentIterator() {
} else {
currentIterator = iteratorQueue.remove();
}
- // set last used iterator here, in case the user calls remove
- // before calling hasNext() or next() (although they shouldn't)
- lastUsedIterator = currentIterator;
}
- while (!currentIterator.hasNext() && !iteratorQueue.isEmpty()) {
+ while (true) {
+ cachedHasNextValue = currentIterator.hasNext();
+ if (cachedHasNextValue) {
+ break;
+ }
+ if (iteratorQueue.isEmpty()) {
+ break;
+ }
currentIterator = iteratorQueue.remove();
}
}
-
}
diff --git a/src/test/java/org/apache/commons/collections4/SetUtilsTest.java b/src/test/java/org/apache/commons/collections4/SetUtilsTest.java
index ffb7bc3a18..27a0965f78 100644
--- a/src/test/java/org/apache/commons/collections4/SetUtilsTest.java
+++ b/src/test/java/org/apache/commons/collections4/SetUtilsTest.java
@@ -23,17 +23,22 @@
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.assertTimeoutPreemptively;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
+import java.util.Iterator;
import java.util.Set;
import org.apache.commons.collections4.SetUtils.SetView;
import org.apache.commons.collections4.set.PredicatedSet;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
/**
* Tests for SetUtils.
@@ -218,6 +223,31 @@ void testUnion() {
assertThrows(NullPointerException.class, () -> SetUtils.union(null, setA));
}
+ @ParameterizedTest
+ @ValueSource(booleans = {true, false})
+ void testReverseNestedUnionPerfomWell(final boolean mergeLeft) {
+ Set