package org.infinispan.stream.impl.termop.object; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.function.Supplier; import java.util.stream.BaseStream; import java.util.stream.Stream; import org.infinispan.commons.util.ByRef; import org.infinispan.container.entries.CacheEntry; import org.infinispan.container.entries.ImmortalCacheEntry; import org.infinispan.stream.impl.KeyTrackingTerminalOperation; import org.infinispan.stream.impl.intops.IntermediateOperation; import org.infinispan.stream.impl.termop.BaseTerminalOperation; /** * Terminal rehash aware operation that handles an iterator when a flat map intermediate operation was performed on * the stream. This is important due to the fact that we need to track what keys have been processed for the iterator. * Since flat map can produce multiple values for the same key we need to handle that special so we can guarantee * we are returning the keys properly and a rehash wouldn't lose some. * This class assumes the stream is composed of {@link java.util.Map.Entry} instances where the key is typed the same * as defined K type. * @param <K> key type * @param <V> resulting value type */ public class FlatMapIteratorOperation<K, V> extends BaseTerminalOperation implements KeyTrackingTerminalOperation<K, V, Collection<V>> { private final int batchSize; public FlatMapIteratorOperation(Iterable<IntermediateOperation> intermediateOperations, Supplier<Stream<CacheEntry>> supplier, int batchSize) { super(intermediateOperations, supplier); this.batchSize = batchSize; } @Override public boolean lostSegment(boolean stopIfLost) { // TODO: stop this early return true; } @Override public List<V> performOperation(IntermediateCollector<Collection<V>> response) { /** * This is for rehash only! {@link NoMapIteratorOperation} should always be used for non rehash */ throw new UnsupportedOperationException(); } @Override public Collection<CacheEntry<K, Collection<V>>> performOperationRehashAware( IntermediateCollector<Collection<CacheEntry<K, Collection<V>>>> response) { // We only support sequential streams for iterator rehash aware BaseStream<?, ?> stream = supplier.get().sequential(); List<CacheEntry<K, Collection<V>>> collectedValues = new ArrayList(batchSize); ByRef<List<V>> currentList = new ByRef<>(new ArrayList<>()); ByRef<K> currentKey = new ByRef<>(null); stream = ((Stream<Map.Entry<K, ?>>) stream).peek(e -> { List<V> list = currentList.get(); if (!list.isEmpty()) { collectedValues.add(new ImmortalCacheEntry(currentKey.get(), list)); if (collectedValues.size() >= batchSize) { response.sendDataResonse(collectedValues); collectedValues.clear(); list.clear(); } else { currentList.set(new ArrayList<V>(list.size())); } } currentKey.set(e.getKey()); }); for (IntermediateOperation intermediateOperation : intermediateOperations) { stream = intermediateOperation.perform(stream); } Stream<V> convertedStream = ((Stream<V>)stream); // We rely on the fact that iterator processes 1 entry at a time convertedStream.forEach(v -> { currentList.get().add(v); }); List<V> lastList = currentList.get(); if (lastList != null && !lastList.isEmpty()) { collectedValues.add(new ImmortalCacheEntry(currentKey.get(), lastList)); } return collectedValues; } public int getBatchSize() { return batchSize; } }