This is an automated email from the ASF dual-hosted git repository. daim pushed a commit to branch OAK-11691 in repository https://gitbox.apache.org/repos/asf/jackrabbit-oak.git
commit fd152d6cf67b488cf3a71172a1e7452c3b872c38 Author: Rishabh Kumar <d...@adobe.com> AuthorDate: Sun May 4 13:35:10 2025 +0530 OAK-11691 : added Iterators.partition replacement in oak-commons --- .../oak/commons/collections/IterableUtils.java | 23 +---- .../oak/commons/collections/IteratorUtils.java | 57 +++++++++++ .../oak/commons/collections/IterableUtilsTest.java | 25 +++++ .../oak/commons/collections/IteratorUtilsTest.java | 111 +++++++++++++++++++++ 4 files changed, 194 insertions(+), 22 deletions(-) diff --git a/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/collections/IterableUtils.java b/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/collections/IterableUtils.java index 9f7b8d393a..4f93afc75f 100644 --- a/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/collections/IterableUtils.java +++ b/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/collections/IterableUtils.java @@ -238,28 +238,7 @@ public class IterableUtils { return new Iterable<>() { @Override public @NotNull Iterator<List<T>> iterator() { - return new Iterator<>() { - private final Iterator<T> iterator = itr.iterator(); - - @Override - public boolean hasNext() { - return iterator.hasNext(); - } - - @Override - public List<T> next() { - // check if there are elements left, throw an exception if not - if (!hasNext()) { - throw new NoSuchElementException(); - } - - List<T> currentPartition = new ArrayList<>(size); - for (int i = 0; i < size && iterator.hasNext(); i++) { - currentPartition.add(iterator.next()); - } - return currentPartition; - } - }; + return IteratorUtils.partition(itr.iterator(), size); } }; } diff --git a/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/collections/IteratorUtils.java b/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/collections/IteratorUtils.java index b6830593e4..7d98918b8c 100644 --- a/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/collections/IteratorUtils.java +++ b/oak-commons/src/main/java/org/apache/jackrabbit/oak/commons/collections/IteratorUtils.java @@ -20,12 +20,17 @@ package org.apache.jackrabbit.oak.commons.collections; import org.apache.commons.collections4.iterators.IteratorChain; import org.apache.commons.collections4.iterators.PeekingIterator; +import org.apache.commons.collections4.iterators.UnmodifiableIterator; +import org.apache.jackrabbit.oak.commons.conditions.Validate; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.Iterator; +import java.util.List; import java.util.NoSuchElementException; import java.util.Objects; import java.util.PriorityQueue; @@ -474,5 +479,57 @@ public class IteratorUtils { public static <E> Iterator<E> cycle(final Iterable<E> iterable) { return org.apache.commons.collections4.IteratorUtils.loopingIterator(CollectionUtils.toCollection(iterable)); } + + /** + * Returns an iterator that partitions the elements of another iterator into fixed-size lists. + * <p> + * This method creates a new iterator that will group elements from the source iterator + * into lists of the specified size. The final list may be smaller than the requested size + * if there are not enough elements remaining in the source iterator. + * <p> + * The returned lists are unmodifiable. The source iterator is consumed only as the + * returned iterator is advanced. + * <p> + * Example usage: + * <pre> + * Iterator<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5).iterator(); + * Iterator<List<Integer>> partitioned = IteratorUtils.partition(numbers, 2); + * // partitioned will iterate through [1, 2], [3, 4], [5] + * </pre> + * + * @param <T> the type of elements in the source iterator + * @param iterator the source iterator to partition, must not be null + * @param size the size of each partition, must be greater than 0 + * @return an iterator of fixed-size lists containing the elements of the source iterator + * @throws NullPointerException if the iterator is null + * @throws IllegalArgumentException if size is less than or equal to 0 + */ + public static <T> Iterator<List<T>> partition(final Iterator<T> iterator, final int size) { + + Objects.requireNonNull(iterator, "Iterator must not be null."); + Validate.checkArgument(size > 0, "Size must be greater than 0."); + + return UnmodifiableIterator.unmodifiableIterator(new Iterator<>() { + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public List<T> next() { + // check if there are elements left, throw an exception if not + if (!hasNext()) { + throw new NoSuchElementException(); + } + + final List<T> currentPartition = new ArrayList<>(size); + for (int i = 0; i < size && iterator.hasNext(); i++) { + currentPartition.add(iterator.next()); + } + return Collections.unmodifiableList(currentPartition); + } + }); + } } diff --git a/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/collections/IterableUtilsTest.java b/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/collections/IterableUtilsTest.java index 4acd80283c..7b8e17336b 100644 --- a/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/collections/IterableUtilsTest.java +++ b/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/collections/IterableUtilsTest.java @@ -400,6 +400,31 @@ public class IterableUtilsTest { Assert.assertThrows(NoSuchElementException.class, iterator::next); } + @Test + public void testPartitionReturnsUnmodifiableLists() { + List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c")); + Iterable<List<String>> partitioned = IterableUtils.partition(list, 2); + + List<String> partition = partitioned.iterator().next(); + Assert.assertThrows(UnsupportedOperationException.class, () -> partition.add("d")); // Should throw UnsupportedOperationException + } + + @Test + public void testPartitionWithRemovableIterable() { + List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d")); + Iterable<List<String>> partitioned = IterableUtils.partition(list, 2); + + // Get first partition + List<String> partition = partitioned.iterator().next(); + Assert.assertEquals(Arrays.asList("a", "b"), partition); + + // Original iterator shouldn't support removal through partition + Assert.assertThrows(UnsupportedOperationException.class, partitioned.iterator()::remove); + + // But original list should still have all elements + Assert.assertEquals(Arrays.asList("a", "b", "c", "d"), list); + } + @Test public void testFilterWithNonEmptyIterable() { Iterable<Integer> iterable = Arrays.asList(1, 2, 3, 4, 5); diff --git a/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/collections/IteratorUtilsTest.java b/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/collections/IteratorUtilsTest.java index ddcd9a8a7b..7abc2a8186 100644 --- a/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/collections/IteratorUtilsTest.java +++ b/oak-commons/src/test/java/org/apache/jackrabbit/oak/commons/collections/IteratorUtilsTest.java @@ -1135,4 +1135,115 @@ public class IteratorUtilsTest { Assert.assertEquals("x", cyclingIterator.next()); Assert.assertTrue(cyclingIterator.hasNext()); } + + @Test + public void testPartitionWithEvenDivision() { + List<String> list = Arrays.asList("a", "b", "c", "d"); + Iterator<List<String>> partitioned = IteratorUtils.partition(list.iterator(), 2); + + Assert.assertTrue(partitioned.hasNext()); + Assert.assertEquals(Arrays.asList("a", "b"), partitioned.next()); + Assert.assertTrue(partitioned.hasNext()); + Assert.assertEquals(Arrays.asList("c", "d"), partitioned.next()); + Assert.assertFalse(partitioned.hasNext()); + } + + @Test + public void testPartitionWithUnevenDivision() { + List<String> list = Arrays.asList("a", "b", "c", "d", "e"); + Iterator<List<String>> partitioned = IteratorUtils.partition(list.iterator(), 2); + + Assert.assertTrue(partitioned.hasNext()); + Assert.assertEquals(Arrays.asList("a", "b"), partitioned.next()); + Assert.assertTrue(partitioned.hasNext()); + Assert.assertEquals(Arrays.asList("c", "d"), partitioned.next()); + Assert.assertTrue(partitioned.hasNext()); + Assert.assertEquals(List.of("e"), partitioned.next()); + Assert.assertFalse(partitioned.hasNext()); + } + + @Test + public void testPartitionWithEmptyIterator() { + Iterator<List<String>> partitioned = IteratorUtils.partition(Collections.emptyIterator(), 3); + Assert.assertFalse(partitioned.hasNext()); + } + + @Test + public void testPartitionWithSizeOne() { + List<Integer> list = Arrays.asList(1, 2, 3); + Iterator<List<Integer>> partitioned = IteratorUtils.partition(list.iterator(), 1); + + Assert.assertTrue(partitioned.hasNext()); + Assert.assertEquals(Collections.singletonList(1), partitioned.next()); + Assert.assertTrue(partitioned.hasNext()); + Assert.assertEquals(Collections.singletonList(2), partitioned.next()); + Assert.assertTrue(partitioned.hasNext()); + Assert.assertEquals(Collections.singletonList(3), partitioned.next()); + Assert.assertFalse(partitioned.hasNext()); + } + + @Test + public void testPartitionWithLargeIterator() { + // Create a large list + List<Integer> list = new ArrayList<>(); + for (int i = 0; i < 1000; i++) { + list.add(i); + } + + Iterator<List<Integer>> partitioned = IteratorUtils.partition(list.iterator(), 100); + + // Verify we get 10 partitions of 100 elements each + int partitionCount = 0; + while (partitioned.hasNext()) { + List<Integer> partition = partitioned.next(); + if (partitionCount < 9) { + Assert.assertEquals(100, partition.size()); + } + partitionCount++; + } + Assert.assertEquals(10, partitionCount); + } + + @Test + public void testPartitionWithNullIterator() { + Assert.assertThrows(NullPointerException.class, () -> IteratorUtils.partition(null, 5)); + } + + @Test + public void testPartitionWithZeroSize() { + List<String> list = Arrays.asList("a", "b", "c"); + Assert.assertThrows(IllegalArgumentException.class, () -> IteratorUtils.partition(list.iterator(), 0)); + } + + @Test + public void testPartitionWithNegativeSize() { + List<String> list = Arrays.asList("a", "b", "c"); + Assert.assertThrows(IllegalArgumentException.class, () -> IteratorUtils.partition(list.iterator(), -1)); + } + + @Test + public void testPartitionReturnsUnmodifiableLists() { + List<String> list = Arrays.asList("a", "b", "c"); + Iterator<List<String>> partitioned = IteratorUtils.partition(list.iterator(), 2); + + List<String> partition = partitioned.next(); + Assert.assertThrows(UnsupportedOperationException.class, () -> partition.add("d")); // Should throw UnsupportedOperationException + } + + @Test + public void testPartitionWithRemovableIterator() { + List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c", "d")); + Iterator<String> iterator = list.iterator(); + Iterator<List<String>> partitioned = IteratorUtils.partition(iterator, 2); + + // Get first partition + List<String> partition = partitioned.next(); + Assert.assertEquals(Arrays.asList("a", "b"), partition); + + // Original iterator shouldn't support removal through partition + Assert.assertThrows(UnsupportedOperationException.class, partitioned::remove); + + // But original list should still have all elements + Assert.assertEquals(Arrays.asList("a", "b", "c", "d"), list); + } }