/*
* Copyright 2015, 2016 Tagir Valeev
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package one.util.streamex;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import static one.util.streamex.UnknownSizeSpliterator.*;
import static one.util.streamex.StreamExInternals.*;
/**
* @author Tagir Valeev
*/
/* package */class ZipSpliterator<U, V, R> implements Spliterator<R> {
private Spliterator<U> left;
private Spliterator<V> right;
private final BiFunction<? super U, ? super V, ? extends R> mapper;
private boolean trySplit;
private int batch = 0;
private final Box<U> l = new Box<>();
private final Box<V> r = new Box<>();
ZipSpliterator(Spliterator<U> left, Spliterator<V> right, BiFunction<? super U, ? super V, ? extends R> mapper, boolean trySplit) {
this.left = left;
this.right = right;
this.mapper = mapper;
this.trySplit = trySplit;
}
@Override
public boolean tryAdvance(Consumer<? super R> action) {
if (left.tryAdvance(l) && right.tryAdvance(r)) {
action.accept(mapper.apply(l.a, r.a));
return true;
}
return false;
}
@Override
public void forEachRemaining(Consumer<? super R> action) {
if(!hasCharacteristics(SIZED)) {
Spliterator.super.forEachRemaining(action);
return;
}
long leftSize = left.getExactSizeIfKnown();
long rightSize = right.getExactSizeIfKnown();
if(leftSize <= rightSize) {
left.forEachRemaining(u -> {
if(right.tryAdvance(r)) {
action.accept(mapper.apply(u, r.a));
}
});
} else {
right.forEachRemaining(v -> {
if(left.tryAdvance(l)) {
action.accept(mapper.apply(l.a, v));
}
});
}
}
@Override
public Spliterator<R> trySplit() {
if(trySplit && hasCharacteristics(SIZED | SUBSIZED))
{
Spliterator<U> leftPrefix = left.trySplit();
if(leftPrefix == null)
return arraySplit();
Spliterator<V> rightPrefix = right.trySplit();
if(rightPrefix == null)
{
left = new TailConcatSpliterator<>(leftPrefix, left);
return arraySplit();
}
long leftSize = leftPrefix.getExactSizeIfKnown();
long rightSize = rightPrefix.getExactSizeIfKnown();
if(leftSize >= 0 && rightSize >= 0)
{
if(leftSize == rightSize)
{
return new ZipSpliterator<>(leftPrefix, rightPrefix, mapper, true);
}
if(Math.abs(leftSize-rightSize) < Math.min(BATCH_UNIT, Math.max(leftSize, rightSize)/8))
{
if(leftSize < rightSize)
{
@SuppressWarnings("unchecked")
U[] array = (U[]) new Object[(int) (rightSize-leftSize)];
drainTo(array, left);
leftPrefix = new TailConcatSpliterator<>(leftPrefix, Spliterators.spliterator(array, characteristics()));
} else
{
@SuppressWarnings("unchecked")
V[] array = (V[]) new Object[(int) (leftSize-rightSize)];
drainTo(array, right);
rightPrefix = new TailConcatSpliterator<>(rightPrefix, Spliterators.spliterator(array, characteristics()));
}
this.trySplit = false;
return new ZipSpliterator<>(leftPrefix, rightPrefix, mapper, false);
}
}
left = new TailConcatSpliterator<>(leftPrefix, left);
right = new TailConcatSpliterator<>(rightPrefix, right);
}
return arraySplit();
}
private Spliterator<R> arraySplit() {
long s = estimateSize();
if (s <= 1) return null;
int n = batch + BATCH_UNIT;
if (n > s)
n = (int) s;
if (n > MAX_BATCH)
n = MAX_BATCH;
@SuppressWarnings("unchecked")
R[] array = (R[]) new Object[n];
int index = drainTo(array, this);
if((batch = index) == 0)
return null;
long s2 = estimateSize();
USOfRef<R> prefix = new UnknownSizeSpliterator.USOfRef<>(array, 0, index);
if(hasCharacteristics(SUBSIZED))
prefix.est = index;
else if(s == s2)
prefix.est = Math.max(index, s/2);
else
prefix.est = Math.max(index, s2 - s);
return prefix;
}
@Override
public long estimateSize() {
return Math.min(left.estimateSize(), right.estimateSize());
}
@Override
public int characteristics() {
// Remove SORTED, NONNULL, DISTINCT
return left.characteristics() & right.characteristics() & (SIZED | SUBSIZED | ORDERED | IMMUTABLE | CONCURRENT);
}
}