This is an automated email from the ASF dual-hosted git repository.
pkarwasz 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 845c0e540 [collections-722] deeply nested chainedIterator performance
problems fixed (#628)
845c0e540 is described below
commit 845c0e5400c5a804c972bdd9d60e6852f0c268f9
Author: Joerg Budischewski <[email protected]>
AuthorDate: Tue Aug 19 15:07:35 2025 +0200
[collections-722] deeply nested chainedIterator performance problems fixed
(#628)
* [collections-722] basically 'renovated' stale PR
commons-collections/pull/308 from collections-770, ensures that rechaining
chainedIterators multiple times now result in a single IteratorChain instance
with all picked up underlying iterators, thus avoiding deeply nested
imperformant IteratorChain instances. Additionally the UnmodifiableIterator
gets a special treatment, so that SetUtils.SetView.iterator() also benefits
from this solution. Raised test coverage.
* [collections-722] added 722 to changes.xml
---
src/changes/changes.xml | 1 +
.../collections4/iterators/IteratorChain.java | 24 ++++++-
.../iterators/UnmodifiableIterator.java | 1 -
.../collections4/iterators/IteratorChainTest.java | 81 ++++++++++++++++++++++
4 files changed, 105 insertions(+), 2 deletions(-)
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 70ef6bb2e..42830e30a 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -33,6 +33,7 @@
<action type="fix" dev="ggregory" due-to="Gary Gregory">Fix exception
message in
org.apache.commons.collections4.functors.FunctorUtils.validate(Consumer...)</action>
<action type="fix" dev="ggregory" due-to="Gary Gregory">Fix exception
message in
org.apache.commons.collections4.iterators.UnmodifiableIterator.remove() to
match java.util.Iterator.remove().</action>
<action type="fix" dev="pkarwasz" due-to="Piotr P. Karwasz, Joerg
Budischewski" issue="COLLECTIONS-838">Calling SetUtils.union on multiple
instances of SetView causes JVM to hang</action>
+ <action type="fix" dev="pkarwasz" due-to="Piotr P. Karwasz, Joerg
Budischewski" issue="COLLECTIONS-722">Improve IteratorUtils.chainedIterator()
performance.</action>
<!-- ADD -->
<action type="add" dev="ggregory" due-to="Gary Gregory">Add generics to
UnmodifiableIterator for the wrapped type.</action>
<!-- UPDATE -->
diff --git
a/src/main/java/org/apache/commons/collections4/iterators/IteratorChain.java
b/src/main/java/org/apache/commons/collections4/iterators/IteratorChain.java
index 97373b13c..433f60d46 100644
--- a/src/main/java/org/apache/commons/collections4/iterators/IteratorChain.java
+++ b/src/main/java/org/apache/commons/collections4/iterators/IteratorChain.java
@@ -168,7 +168,29 @@ public class IteratorChain<E> implements Iterator<E> {
*/
public void addIterator(final Iterator<? extends E> iterator) {
checkLocked();
- iteratorQueue.add(Objects.requireNonNull(iterator, "iterator"));
+ Objects.requireNonNull(iterator, "iterator");
+ if (iterator instanceof UnmodifiableIterator) {
+ final Iterator<? extends E> underlyingIterator =
((UnmodifiableIterator) iterator).unwrap();
+ if (underlyingIterator instanceof IteratorChain) {
+ // in case it is an IteratorChain, wrap every underlying
iterators as unmodifiable
+ // multiple rechainings would otherwise lead to exponential
growing number of function calls
+ // when the iteratorChain gets used.
+ for (Iterator<? extends E> nestedIterator : ((IteratorChain<?
extends E>) underlyingIterator).iteratorQueue) {
+
iteratorQueue.add(UnmodifiableIterator.unmodifiableIterator(nestedIterator));
+ }
+ } else {
+ // we don't know anything about the underlying iterator,
simply add it here
+ iteratorQueue.add(iterator);
+ }
+ } else if (iterator instanceof IteratorChain) {
+ // add the wrapped iterators directly instead of reusing the given
instance
+ // multiple rechainings would otherwise lead to exponential
growing number of function calls
+ // when the iteratorChain gets used.
+ iteratorQueue.addAll(((IteratorChain) iterator).iteratorQueue);
+ } else {
+ // arbitrary other iterator
+ iteratorQueue.add(iterator);
+ }
}
/**
diff --git
a/src/main/java/org/apache/commons/collections4/iterators/UnmodifiableIterator.java
b/src/main/java/org/apache/commons/collections4/iterators/UnmodifiableIterator.java
index 42b11b41a..1fb56b5cc 100644
---
a/src/main/java/org/apache/commons/collections4/iterators/UnmodifiableIterator.java
+++
b/src/main/java/org/apache/commons/collections4/iterators/UnmodifiableIterator.java
@@ -91,5 +91,4 @@ public final class UnmodifiableIterator<E, T extends
Iterator<? extends E>> impl
T unwrap() {
return iterator;
}
-
}
diff --git
a/src/test/java/org/apache/commons/collections4/iterators/IteratorChainTest.java
b/src/test/java/org/apache/commons/collections4/iterators/IteratorChainTest.java
index be17dac08..8d642e774 100644
---
a/src/test/java/org/apache/commons/collections4/iterators/IteratorChainTest.java
+++
b/src/test/java/org/apache/commons/collections4/iterators/IteratorChainTest.java
@@ -25,9 +25,13 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
import java.util.NoSuchElementException;
+import java.util.Set;
import org.apache.commons.collections4.IteratorUtils;
import org.apache.commons.collections4.Predicate;
@@ -42,10 +46,14 @@ public class IteratorChainTest extends
AbstractIteratorTest<String> {
protected String[] testArray = {
"One", "Two", "Three", "Four", "Five", "Six"
};
+ protected String[] testArray1234 = {
+ "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight"
+ };
protected List<String> list1;
protected List<String> list2;
protected List<String> list3;
+ protected List<String> list4;
public List<String> getList1() {
return list1;
@@ -89,6 +97,9 @@ public class IteratorChainTest extends
AbstractIteratorTest<String> {
list3 = new ArrayList<>();
list3.add("Five");
list3.add("Six");
+ list4 = new ArrayList<>();
+ list4.add("Seven");
+ list4.add("Eight");
}
@Test
@@ -149,9 +160,14 @@ public class IteratorChainTest extends
AbstractIteratorTest<String> {
expected.addAll(list2);
expected.addAll(list3);
final IteratorChain<String> iter = new IteratorChain<>(list);
+ assertEquals(iter.size(), list.size());
+ assertFalse(iter.isLocked());
final List<String> actual = new ArrayList<>();
iter.forEachRemaining(actual::add);
assertEquals(actual, expected);
+ assertTrue(iter.isLocked());
+ assertThrows(UnsupportedOperationException.class, () ->
iter.addIterator(list1.iterator()),
+ "adding iterators after iteratorChain has been traversed
must fail");
}
@Test
@@ -263,4 +279,69 @@ public class IteratorChainTest extends
AbstractIteratorTest<String> {
assertEquals(1, list2.size());
}
+ @Test
+ public void testChainOfChains() {
+ final Iterator<String> iteratorChain1 = new
IteratorChain<>(list1.iterator(), list2.iterator());
+ final Iterator<String> iteratorChain2 = new
IteratorChain<>(list3.iterator(), list4.iterator());
+ final Iterator<String> iteratorChainOfChains = new
IteratorChain<>(iteratorChain1, iteratorChain2);
+
+ for (final String testValue : testArray1234) {
+ final String iterValue = (String) iteratorChainOfChains.next();
+ assertEquals(testValue, iterValue, "Iteration value is correct");
+ }
+
+ assertFalse(iteratorChainOfChains.hasNext(), "Iterator should now be
empty");
+ assertThrows(NoSuchElementException.class,
iteratorChainOfChains::next, "NoSuchElementException must be thrown");
+ }
+
+ @Test
+ public void testChainOfUnmodifiableChains() {
+ final Iterator<String> iteratorChain1 = new
IteratorChain<>(list1.iterator(), list2.iterator());
+ final Iterator<String> unmodifiableChain1 =
IteratorUtils.unmodifiableIterator(iteratorChain1);
+ final Iterator<String> iteratorChain2 = new
IteratorChain<>(list3.iterator(), list4.iterator());
+ final Iterator<String> unmodifiableChain2 =
IteratorUtils.unmodifiableIterator(iteratorChain2);
+ final Iterator<String> iteratorChainOfChains = new
IteratorChain<>(unmodifiableChain1, unmodifiableChain2);
+
+ for (final String testValue : testArray1234) {
+ final String iterValue = (String) iteratorChainOfChains.next();
+ assertEquals(testValue, iterValue, "Iteration value is correct");
+ }
+
+ assertFalse(iteratorChainOfChains.hasNext(), "Iterator should now be
empty");
+ assertThrows(NoSuchElementException.class,
iteratorChainOfChains::next, "NoSuchElementException must be thrown");
+ }
+
+ @Test
+ public void
testChainOfUnmodifiableChainsRetainsUnmodifiableBehaviourOfNestedIterator() {
+ final Iterator<String> iteratorChain1 = new
IteratorChain<>(list1.iterator(), list2.iterator());
+ final Iterator<String> unmodifiableChain1 =
IteratorUtils.unmodifiableIterator(iteratorChain1);
+ final Iterator<String> iteratorChain2 = new
IteratorChain<>(list3.iterator(), list4.iterator());
+ final Iterator<String> unmodifiableChain2 =
IteratorUtils.unmodifiableIterator(iteratorChain2);
+ final Iterator<String> iteratorChainOfChains = new
IteratorChain<>(unmodifiableChain1, unmodifiableChain2);
+
+ iteratorChainOfChains.next();
+ assertThrows(UnsupportedOperationException.class,
iteratorChainOfChains::remove,
+ "Calling remove must fail when nested iterator is
unmodifiable");
+ }
+
+ @Test
+ public void testMultipleChainedIteratorPerformWellCollections722() {
+ final Map<Integer, List<Integer>> source = new HashMap<>();
+ for (int i = 0; i < 50; i++) {
+ source.put(i, Arrays.asList(1, 2, 3));
+ }
+
+ Iterator<Integer> iterator = IteratorUtils.emptyIterator();
+ final Set<Entry<Integer, List<Integer>>> entries = source.entrySet();
+ for (final Entry<Integer, List<Integer>> entry : entries) {
+ final Iterator<Integer> next = entry.getValue().iterator();
+ iterator = IteratorUtils.chainedIterator(iterator, next);
+ }
+ final Iterator<Integer> lastIterator = iterator;
+ assertTimeoutPreemptively(Duration.ofSeconds(2), () -> {
+ while (lastIterator.hasNext()) {
+ lastIterator.next().toString();
+ }
+ });
+ }
}