/*
* 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.ArrayDeque;
import java.util.Spliterator;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.function.Supplier;
import static one.util.streamex.StreamExInternals.*;
/**
* @author Tagir Valeev
*/
/* package */final class OrderedCancellableSpliterator<T, A> extends CloneableSpliterator<A, OrderedCancellableSpliterator<T, A>> {
private Spliterator<T> source;
private final Object lock = new Object();
private final BiConsumer<A, ? super T> accumulator;
private final Predicate<A> cancelPredicate;
private final BinaryOperator<A> combiner;
private final Supplier<A> supplier;
private volatile boolean localCancelled;
private volatile OrderedCancellableSpliterator<T, A> prefix;
private volatile OrderedCancellableSpliterator<T, A> suffix;
private A payload;
OrderedCancellableSpliterator(Spliterator<T> source, Supplier<A> supplier, BiConsumer<A, ? super T> accumulator,
BinaryOperator<A> combiner, Predicate<A> cancelPredicate) {
this.source = source;
this.supplier = supplier;
this.accumulator = accumulator;
this.combiner = combiner;
this.cancelPredicate = cancelPredicate;
}
@Override
public boolean tryAdvance(Consumer<? super A> action) {
Spliterator<T> source = this.source;
if (source == null || localCancelled) {
this.source = null;
return false;
}
A acc = supplier.get();
try {
source.forEachRemaining(t -> {
accumulator.accept(acc, t);
if (cancelPredicate.test(acc)) {
cancelSuffix();
throw new CancelException();
}
if (localCancelled) {
throw new CancelException();
}
});
} catch (CancelException ex) {
if (localCancelled) {
return false;
}
}
this.source = null;
A result = acc;
while (true) {
if (prefix == null && suffix == null) {
action.accept(result);
return true;
}
ArrayDeque<A> res = new ArrayDeque<>();
res.offer(result);
synchronized (lock) {
if (localCancelled)
return false;
OrderedCancellableSpliterator<T, A> s = prefix;
while (s != null) {
if (s.payload == null)
break;
res.offerFirst(s.payload);
s = s.prefix;
}
prefix = s;
if (s != null) {
s.suffix = this;
}
s = suffix;
while (s != null) {
if (s.payload == null)
break;
res.offerLast(s.payload);
s = s.suffix;
}
suffix = s;
if (s != null) {
s.prefix = this;
}
if (res.size() == 1) {
if (prefix == null && suffix == null) {
action.accept(result);
return true;
}
this.payload = result;
break;
}
}
result = res.pollFirst();
while (!res.isEmpty()) {
result = combiner.apply(result, res.pollFirst());
if (cancelPredicate.test(result)) {
cancelSuffix();
}
}
}
return false;
}
private void cancelSuffix() {
if (this.suffix == null)
return;
synchronized (lock) {
OrderedCancellableSpliterator<T, A> suffix = this.suffix;
while (suffix != null && !suffix.localCancelled) {
suffix.prefix = null;
suffix.localCancelled = true;
suffix = suffix.suffix;
}
this.suffix = null;
}
}
@Override
public void forEachRemaining(Consumer<? super A> action) {
tryAdvance(action);
}
@Override
public Spliterator<A> trySplit() {
if (source == null || localCancelled) {
source = null;
return null;
}
Spliterator<T> prefix = source.trySplit();
if (prefix == null) {
return null;
}
synchronized (lock) {
OrderedCancellableSpliterator<T, A> result = doClone();
result.source = prefix;
this.prefix = result;
result.suffix = this;
OrderedCancellableSpliterator<T, A> prefixPrefix = result.prefix;
if (prefixPrefix != null)
prefixPrefix.suffix = result;
return result;
}
}
@Override
public long estimateSize() {
return source == null ? 0 : source.estimateSize();
}
@Override
public int characteristics() {
return source == null ? SIZED : ORDERED;
}
}