package org.infinispan.stream.impl.spliterators; import java.util.Comparator; import java.util.Iterator; import java.util.Objects; import java.util.Spliterator; import java.util.Spliterators; import java.util.function.Consumer; import java.util.function.Supplier; import org.infinispan.commons.util.CloseableIterator; import org.infinispan.commons.util.CloseableSpliterator; import org.infinispan.commons.util.Closeables; /** * A Spliterator using the provided iterator for supplying values. * Splits occur start at the batch size. Each split gets subsequently bigger by increasing by the original * split size. The batch size will never become higher than the configured max batch size */ public class IteratorAsSpliterator<T> implements CloseableSpliterator<T> { private CloseableIterator<? extends T> iterator; private final int characteristics; private final int batchIncrease; private final int maxBatchSize; private long estimateRemaining; private int currentBatchSize; public static class Builder<T> implements Supplier<IteratorAsSpliterator<T>> { private final CloseableIterator<? extends T> iterator; private int characteristics; private int batchIncrease = 1024; private int maxBatchSize = 51200; private long estimateRemaining = Long.MAX_VALUE; public Builder(Iterator<? extends T> iterator) { Objects.nonNull(iterator); this.iterator = Closeables.iterator(iterator); } public Builder(CloseableIterator<? extends T> closeableIterator) { Objects.nonNull(closeableIterator); this.iterator = closeableIterator; } /** * Sets the characteristics the subsequent spliterator will have. * @param characteristics * @return */ public Builder<T> setCharacteristics(int characteristics) { this.characteristics = characteristics; return this; } /** * Sets the batch increase size. This controls how much larger subsequent splits are. * The default value is 1024; * @param batchIncrease * @return this */ public Builder<T> setBatchIncrease(int batchIncrease) { if (batchIncrease <= 0) { throw new IllegalArgumentException("The batchIncrease " + batchIncrease + " must be greater than 0"); } this.batchIncrease = batchIncrease; return this; } /** * Sets the max batch size for a thread to use - This defaults to 51200 * @param maxBatchSize * @return this */ public Builder setMaxBatchSize(int maxBatchSize) { if (maxBatchSize <= 0) { throw new IllegalArgumentException("The maxBatchSize " + maxBatchSize + " must be greater than 0"); } this.maxBatchSize = maxBatchSize; return this; } /** * Sets how many estimated elements are remaining for this iterator * This defaults to Long.MAX_VALUE. It is heavily recommended to provide an exact or estimate value * to help with controlling parallelism * @param estimateRemaining * @return this */ public Builder<T> setEstimateRemaining(long estimateRemaining) { this.estimateRemaining = estimateRemaining; return this; } @Override public IteratorAsSpliterator<T> get() { if (iterator == null) { throw new IllegalArgumentException("Iterator cannot be null"); } if (batchIncrease > maxBatchSize) { throw new IllegalArgumentException("Max batch size " + maxBatchSize + " cannot be larger than batchIncrease" + batchIncrease); } return new IteratorAsSpliterator<>(this); } } /** * * @param builder */ private IteratorAsSpliterator(Builder<T> builder) { this.iterator = builder.iterator; this.characteristics = builder.characteristics; this.batchIncrease = builder.batchIncrease; this.maxBatchSize = builder.maxBatchSize; this.estimateRemaining = builder.estimateRemaining; } @Override public Spliterator<T> trySplit() { if (estimateRemaining > 1 && iterator.hasNext()) { int batch = currentBatchSize + batchIncrease; if (batch > estimateRemaining) { batch = (int) estimateRemaining; } if (batch > maxBatchSize) { batch = maxBatchSize; } Object[] array = new Object[batch]; int i = 0; while (iterator.hasNext() && i < batch) { array[i] = iterator.next(); i++; } currentBatchSize = batch; estimateRemaining -= i; return Spliterators.spliterator(array, 0, i, characteristics); } return null; } @Override public void forEachRemaining(Consumer<? super T> action) { if (action == null) { throw new NullPointerException(); } iterator.forEachRemaining(action); } @Override public boolean tryAdvance(Consumer<? super T> action) { if (action == null) { throw new NullPointerException(); } if (iterator.hasNext()) { action.accept(iterator.next()); return true; } return false; } @Override public long estimateSize() { return estimateRemaining; } @Override public int characteristics() { return characteristics; } @Override public Comparator<? super T> getComparator() { if (hasCharacteristics(Spliterator.SORTED)) { return null; } throw new IllegalStateException(); } @Override public void close() { iterator.close(); } }