hu.akarnokd.rxjava2.operators.FlowableCoalesce Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rxjava2-extensions Show documentation
Show all versions of rxjava2-extensions Show documentation
rxjava2-extensions developed by David Karnok
/*
* Copyright 2016-2017 David Karnok
*
* 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 hu.akarnokd.rxjava2.operators;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.*;
import org.reactivestreams.*;
import io.reactivex.*;
import io.reactivex.exceptions.Exceptions;
import io.reactivex.functions.BiConsumer;
import io.reactivex.internal.fuseable.SimplePlainQueue;
import io.reactivex.internal.queue.SpscLinkedArrayQueue;
import io.reactivex.internal.subscriptions.SubscriptionHelper;
import io.reactivex.internal.util.BackpressureHelper;
/**
* Coalesces items into a container if the downstream is not ready to receive items.
*
* @param the upstream element type
* @param the container type emitted to downstream
*
* @since 0.17.3
*/
final class FlowableCoalesce extends Flowable implements FlowableTransformer {
final Publisher source;
final Callable containerSupplier;
final BiConsumer coalescer;
final int bufferSize;
FlowableCoalesce(Publisher source, Callable containerSupplier, BiConsumer coalescer, int bufferSize) {
this.source = source;
this.containerSupplier = containerSupplier;
this.coalescer = coalescer;
this.bufferSize = bufferSize;
}
@Override
public Publisher apply(Flowable upstream) {
return new FlowableCoalesce(upstream, containerSupplier, coalescer, bufferSize);
}
@Override
protected void subscribeActual(Subscriber super R> s) {
source.subscribe(new CoalesceSubscriber(s, containerSupplier, coalescer, bufferSize));
}
static final class CoalesceSubscriber extends AtomicInteger
implements FlowableSubscriber, Subscription {
private static final long serialVersionUID = -6157179110480235565L;
final Subscriber super R> actual;
final Callable containerSupplier;
final BiConsumer coalescer;
final AtomicLong requested;
final int bufferSize;
volatile SimplePlainQueue queue;
Subscription upstream;
R container;
volatile boolean done;
volatile boolean cancelled;
Throwable error;
long emitted;
CoalesceSubscriber(Subscriber super R> actual, Callable containerSupplier,
BiConsumer coalescer, int bufferSize) {
this.actual = actual;
this.containerSupplier = containerSupplier;
this.coalescer = coalescer;
this.requested = new AtomicLong();
this.bufferSize = bufferSize;
}
@Override
public void onSubscribe(Subscription s) {
if (SubscriptionHelper.validate(upstream, s)) {
upstream = s;
actual.onSubscribe(this);
s.request(Long.MAX_VALUE);
}
}
@Override
public void onNext(T t) {
if (get() == 0 && compareAndSet(0, 1)) {
SimplePlainQueue q = queue;
if (q == null || q.isEmpty()) {
R c = container;
try {
if (c == null) {
c = containerSupplier.call();
container = c;
}
coalescer.accept(c, t);
} catch (Throwable ex) {
Exceptions.throwIfFatal(ex);
upstream.cancel();
container = null;
actual.onError(ex);
return;
}
long r = requested.get();
long e = emitted;
if (e != r) {
container = null;
actual.onNext(c);
emitted = e + 1;
}
if (decrementAndGet() == 0) {
return;
}
}
} else {
SimplePlainQueue q = queue;
if (q == null) {
q = new SpscLinkedArrayQueue(bufferSize);
queue = q;
}
q.offer(t);
if (getAndIncrement() != 0) {
return;
}
}
drainLoop();
}
@Override
public void onError(Throwable t) {
error = t;
done = true;
drain();
}
@Override
public void onComplete() {
done = true;
drain();
}
@Override
public void request(long n) {
if (SubscriptionHelper.validate(n)) {
BackpressureHelper.add(requested, n);
drain();
}
}
@Override
public void cancel() {
cancelled = true;
upstream.cancel();
if (getAndIncrement() == 0) {
container = null;
}
}
void drain() {
if (getAndIncrement() != 0) {
return;
}
drainLoop();
}
void drainLoop() {
int missed = 1;
long e = emitted;
R c = container;
Subscriber super R> a = actual;
for (;;) {
if (cancelled) {
container = null;
return;
}
boolean d = done;
SimplePlainQueue q = queue;
boolean empty = q == null || q.isEmpty();
if (!empty) {
try {
if (c == null) {
c = containerSupplier.call();
container = c;
}
for (;;) {
T v = q.poll();
if (v == null) {
break;
}
coalescer.accept(c, v);
}
} catch (Throwable ex) {
Exceptions.throwIfFatal(ex);
container = null;
a.onError(ex);
return;
}
}
if (c != null && e != requested.get()) {
a.onNext(c);
c = null;
container = null;
e++;
}
if (d && c == null) {
Throwable ex = error;
container = null;
if (ex != null) {
a.onError(ex);
} else {
a.onComplete();
}
return;
}
emitted = e;
missed = addAndGet(-missed);
if (missed == 0) {
break;
}
}
}
}
}