package com.github.davidmoten.rx.internal.operators;
import java.util.*;
import java.util.concurrent.atomic.*;
import rx.*;
import rx.Observable;
import rx.Observable.*;
import rx.exceptions.*;
import rx.functions.Func1;
import rx.internal.operators.*;
import rx.internal.util.atomic.SpscAtomicArrayQueue;
import rx.internal.util.unsafe.*;
/**
* Buffers values into a continuous, non-overlapping Lists where the boundary is determined
* by a predicate returning true.
*
* @param <T> the source and List element type
*/
public final class OperatorBufferPredicateBoundary<T> implements Transformer<T, List<T>> {
final Func1<? super T, Boolean> predicate;
final int prefetch;
final int capacityHint;
final boolean after;
public OperatorBufferPredicateBoundary(Func1<? super T, Boolean> predicate, int prefetch, int capacityHint, boolean after) {
if (predicate == null) {
throw new NullPointerException("predicate");
}
if (prefetch <= 0) {
throw new IllegalArgumentException("prefetch > 0 required but it was " + prefetch);
}
if (capacityHint <= 0) {
throw new IllegalArgumentException("capacityHint > 0 required but it was " + capacityHint);
}
this.predicate = predicate;
this.prefetch = prefetch;
this.capacityHint = capacityHint;
this.after = after;
}
@Override
public Observable<List<T>> call(Observable<T> source) {
return source.lift(new Operator<List<T>, T>() {
@Override
public Subscriber<? super T> call(Subscriber<? super List<T>> child) {
final BoundedSubscriber<T> parent = after
? new BoundedAfterSubscriber<T>(child, capacityHint, predicate, prefetch)
: new BoundedBeforeSubscriber<T>(child, capacityHint, predicate, prefetch);
child.add(parent);
child.setProducer(new Producer() {
@Override
public void request(long n) {
parent.requestMore(n);
}
});
return parent;
}
});
}
static abstract class BoundedSubscriber<T> extends Subscriber<T> {
final Subscriber<? super List<T>> actual;
final int capacityHint;
final Func1<? super T, Boolean> predicate;
final Queue<Object> queue;
final AtomicLong requested;
final AtomicInteger wip;
final int limit;
List<T> buffer;
long upstreamConsumed;
volatile boolean done;
Throwable error;
public BoundedSubscriber(Subscriber<? super List<T>> actual, int capacityHint,
Func1<? super T, Boolean> predicate, int prefetch) {
this.actual = actual;
this.capacityHint = capacityHint;
this.predicate = predicate;
Queue<Object> q;
if (UnsafeAccess.isUnsafeAvailable()) {
q = new SpscArrayQueue<Object>(prefetch);
} else {
q = new SpscAtomicArrayQueue<Object>(prefetch);
}
queue = q;
buffer = new ArrayList<T>(capacityHint);
requested = new AtomicLong();
wip = new AtomicInteger();
limit = prefetch - (prefetch >> 2);
if (prefetch == Integer.MAX_VALUE) {
request(Long.MAX_VALUE);
} else {
request(prefetch);
}
}
@Override
public void onNext(T t) {
if (!queue.offer(NotificationLite.next(t))) {
unsubscribe();
onError(new MissingBackpressureException());
} else {
drain();
}
}
@Override
public void onError(Throwable e) {
error = e;
done = true;
drain();
}
@Override
public void onCompleted() {
done = true;
drain();
}
void requestMore(long n) {
if (n > 0) {
BackpressureUtils.getAndAddRequest(requested, n);
drain();
} else
if (n < 0) {
throw new IllegalArgumentException("n >= 0 required but it was " + n);
}
}
abstract void drain();
}
static final class BoundedAfterSubscriber<T> extends BoundedSubscriber<T> {
public BoundedAfterSubscriber(Subscriber<? super List<T>> actual, int capacityHint,
Func1<? super T, Boolean> predicate, int prefetch) {
super(actual, capacityHint, predicate, prefetch);
}
@Override
void drain() {
if (wip.getAndIncrement() != 0) {
return;
}
final Subscriber<? super List<T>> localSubscriber = actual;
final Queue<Object> localQueue = queue;
int missed = 1;
for (;;) {
long localRequested = requested.get();
long localEmission = 0L;
long localConsumption = 0L;
List<T> localBuffer = buffer;
while (localEmission != localRequested) {
if (localSubscriber.isUnsubscribed()) {
return;
}
boolean mainDone = done;
if (mainDone) {
Throwable exception = error;
if (exception != null) {
buffer = null;
localSubscriber.onError(exception);
return;
}
}
Object notification = localQueue.poll();
boolean empty = notification == null;
if (mainDone && empty) {
buffer = null;
if (!localBuffer.isEmpty()) {
localSubscriber.onNext(localBuffer);
}
localSubscriber.onCompleted();
return;
}
if (empty) {
break;
}
T value = NotificationLite.getValue(notification);
localBuffer.add(value);
localConsumption++;
boolean emit;
try {
emit = predicate.call(value);
} catch (Throwable ex) {
unsubscribe();
buffer = null;
Exceptions.throwOrReport(ex, localSubscriber, value);
return;
}
if (emit) {
localSubscriber.onNext(localBuffer);
localBuffer = new ArrayList<T>(capacityHint);
buffer = localBuffer;
localEmission++;
}
}
if (localEmission == localRequested) {
if (localSubscriber.isUnsubscribed()) {
return;
}
boolean mainDone = done;
if (mainDone) {
Throwable exception = error;
if (exception != null) {
buffer = null;
localSubscriber.onError(exception);
return;
} else
if (localQueue.isEmpty() && localBuffer.isEmpty()) {
buffer = null;
localSubscriber.onCompleted();
return;
}
}
}
if (localEmission != 0L) {
BackpressureUtils.produced(requested, localEmission);
}
if (localConsumption != 0L) {
long p = upstreamConsumed + localConsumption;
if (p >= limit) {
upstreamConsumed = 0L;
request(p);
} else {
upstreamConsumed = p;
}
}
missed = wip.addAndGet(-missed);
if (missed == 0) {
break;
}
}
}
}
static final class BoundedBeforeSubscriber<T> extends BoundedSubscriber<T> {
public BoundedBeforeSubscriber(Subscriber<? super List<T>> actual, int capacityHint,
Func1<? super T, Boolean> predicate, int prefetch) {
super(actual, capacityHint, predicate, prefetch);
}
@Override
void drain() {
if (wip.getAndIncrement() != 0) {
return;
}
final Subscriber<? super List<T>> localSubscriber = actual;
final Queue<Object> localQueue = queue;
int missed = 1;
for (;;) {
long localRequested = requested.get();
long localEmission = 0L;
long localConsumption = 0L;
List<T> localBuffer = buffer;
while (localEmission != localRequested) {
if (localSubscriber.isUnsubscribed()) {
return;
}
boolean mainDone = done;
if (mainDone) {
Throwable exception = error;
if (exception != null) {
buffer = null;
localSubscriber.onError(exception);
return;
}
}
Object o = localQueue.poll();
boolean empty = o == null;
if (mainDone && empty) {
buffer = null;
if (!localBuffer.isEmpty()) {
localSubscriber.onNext(localBuffer);
}
localSubscriber.onCompleted();
return;
}
if (empty) {
break;
}
T value = NotificationLite.getValue(o);
boolean emit;
try {
emit = predicate.call(value);
} catch (Throwable ex) {
unsubscribe();
buffer = null;
Exceptions.throwOrReport(ex, localSubscriber, value);
return;
}
if (emit && !localBuffer.isEmpty()) {
localSubscriber.onNext(localBuffer);
localBuffer = new ArrayList<T>(capacityHint);
buffer = localBuffer;
localEmission++;
}
localBuffer.add(value);
localConsumption++;
}
if (localEmission == localRequested) {
if (localSubscriber.isUnsubscribed()) {
return;
}
boolean mainDone = done;
if (mainDone) {
Throwable exception = error;
if (exception != null) {
buffer = null;
localSubscriber.onError(exception);
return;
} else
if (localQueue.isEmpty() && localBuffer.isEmpty()) {
buffer = null;
localSubscriber.onCompleted();
return;
}
}
}
if (localEmission != 0L) {
BackpressureUtils.produced(requested, localEmission);
}
if (localConsumption != 0L) {
long produced = upstreamConsumed + localConsumption;
if (produced >= limit) {
upstreamConsumed = 0L;
request(produced);
} else {
upstreamConsumed = produced;
}
}
missed = wip.addAndGet(-missed);
if (missed == 0) {
break;
}
}
}
}
}