package rsc.publisher; import java.util.Collection; import java.util.Objects; import java.util.Queue; import java.util.concurrent.atomic.AtomicIntegerFieldUpdater; import java.util.concurrent.atomic.AtomicLongFieldUpdater; import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.function.Supplier; import org.reactivestreams.Publisher; import org.reactivestreams.Subscriber; import org.reactivestreams.Subscription; import rsc.util.BackpressureHelper; import rsc.subscriber.DeferredSubscription; import rsc.util.ExceptionHelper; import rsc.subscriber.SubscriptionHelper; import rsc.util.UnsignalledExceptions; /** * Buffers elements into custom collections where the buffer boundary is signalled * by another publisher or the buffer reaches a size limit. * * @param <T> the source value type * @param <U> the element type of the boundary publisher (irrelevant) * @param <C> the output collection type */ public final class PublisherBufferBoundaryAndSize<T, U, C extends Collection<? super T>> extends PublisherSource<T, C> { final Publisher<U> other; final Supplier<C> bufferSupplier; final int maxSize; final Supplier<? extends Queue<C>> queueSupplier; public PublisherBufferBoundaryAndSize(Publisher<? extends T> source, Publisher<U> other, Supplier<C> bufferSupplier, int maxSize, Supplier<? extends Queue<C>> queueSupplier) { super(source); if (maxSize < 1) { throw new IllegalArgumentException("maxSize > 0 required but it was " + maxSize); } this.other = Objects.requireNonNull(other, "other"); this.bufferSupplier = Objects.requireNonNull(bufferSupplier, "bufferSupplier"); this.maxSize = maxSize; this.queueSupplier = Objects.requireNonNull(queueSupplier, "queueSupplier"); } @Override public long getPrefetch() { return Long.MAX_VALUE; } @Override public void subscribe(Subscriber<? super C> s) { C buffer; try { buffer = bufferSupplier.get(); } catch (Throwable e) { SubscriptionHelper.error(s, e); return; } if (buffer == null) { SubscriptionHelper.error(s, new NullPointerException("The bufferSupplier returned a null buffer")); return; } Queue<C> q; try { q = queueSupplier.get(); } catch (Throwable e) { SubscriptionHelper.error(s, e); return; } if (q == null) { SubscriptionHelper.error(s, new NullPointerException("The queueSupplier returned a null queue")); return; } PublisherBufferBoundaryAndSizeMain<T, U, C> parent = new PublisherBufferBoundaryAndSizeMain<>( s, buffer, bufferSupplier, maxSize, q); PublisherBufferBoundaryAndSizeOther<U> boundary = new PublisherBufferBoundaryAndSizeOther<>(parent); parent.other = boundary; s.onSubscribe(parent); other.subscribe(boundary); source.subscribe(parent); } static final class PublisherBufferBoundaryAndSizeMain<T, U, C extends Collection<? super T>> implements Subscriber<T>, Subscription { final Subscriber<? super C> actual; final Supplier<C> bufferSupplier; PublisherBufferBoundaryAndSizeOther<U> other; C buffer; final Queue<C> queue; final int maxSize; volatile Subscription s; @SuppressWarnings("rawtypes") static final AtomicReferenceFieldUpdater<PublisherBufferBoundaryAndSizeMain, Subscription> S = AtomicReferenceFieldUpdater.newUpdater(PublisherBufferBoundaryAndSizeMain.class, Subscription.class, "s"); volatile long requested; @SuppressWarnings("rawtypes") static final AtomicLongFieldUpdater<PublisherBufferBoundaryAndSizeMain> REQUESTED = AtomicLongFieldUpdater.newUpdater(PublisherBufferBoundaryAndSizeMain.class, "requested"); volatile int wip; @SuppressWarnings("rawtypes") static final AtomicIntegerFieldUpdater<PublisherBufferBoundaryAndSizeMain> WIP = AtomicIntegerFieldUpdater.newUpdater(PublisherBufferBoundaryAndSizeMain.class, "wip"); volatile boolean done; Throwable error; volatile boolean cancelled; public PublisherBufferBoundaryAndSizeMain(Subscriber<? super C> actual, C buffer, Supplier<C> bufferSupplier, int maxSize, Queue<C> queue) { this.actual = actual; this.buffer = buffer; this.bufferSupplier = bufferSupplier; this.queue = queue; this.maxSize = maxSize; } @Override public void request(long n) { if (SubscriptionHelper.validate(n)) { BackpressureHelper.getAndAddCap(REQUESTED, this, n); } } void cancelMain() { SubscriptionHelper.terminate(S, this); } @Override public void cancel() { if (!cancelled) { cancelled = true; cancelMain(); other.cancel(); } } @Override public void onSubscribe(Subscription s) { if (SubscriptionHelper.setOnce(S, this, s)) { s.request(Long.MAX_VALUE); } } @Override public void onNext(T t) { Throwable e = null; synchronized (this) { C b = buffer; if (b != null) { b.add(t); if (Integer.MAX_VALUE != maxSize && b.size() == maxSize) { queue.offer(b); try { b = bufferSupplier.get(); if (b == null) { e = new NullPointerException("The bufferSupplier returned a null value"); } else { buffer = b; return; } } catch (Throwable ex) { ExceptionHelper.throwIfFatal(e); e = ExceptionHelper.unwrap(ex); } } else { return; } } } UnsignalledExceptions.onNextDropped(t); if (e != null) { onError(e); } } @Override public void onError(Throwable t) { other.cancel(); boolean report; synchronized (this) { if (buffer == null) { report = true; } else { buffer = null; report = false; } } if (report) { UnsignalledExceptions.onErrorDropped(t); } else { error = t; done = true; drain(); } } @Override public void onComplete() { other.cancel(); C b; synchronized (this) { b = buffer; if (b == null) { return; } buffer = null; queue.offer(b); } done = true; drain(); } void otherNext() { C c; C b = buffer; if(b == null || b.isEmpty()){ return; } try { c = bufferSupplier.get(); } catch (Throwable e) { other.cancel(); otherError(e); return; } if (c == null) { other.cancel(); otherError(new NullPointerException("The bufferSupplier returned a null buffer")); return; } synchronized (this) { b = buffer; if (b == null) { return; } buffer = c; queue.offer(b); } drain(); } void otherError(Throwable e) { cancelMain(); boolean report; synchronized (this) { if (buffer == null) { report = true; } else { buffer = null; report = false; } } if (report) { UnsignalledExceptions.onErrorDropped(e); } else { error = e; done = true; drain(); } } void otherComplete() { cancelMain(); C b; synchronized (this) { b = buffer; if (b == null) { return; } buffer = null; queue.offer(b); } done = true; drain(); } void drain() { if (WIP.getAndIncrement(this) != 0) { return; } int missed = 1; final Queue<C> q = queue; final Subscriber<? super C> a = actual; for (;;) { for (;;) { boolean d = done; C b = q.poll(); boolean empty = b == null; if (cancelled) { q.clear(); return; } if (d) { Throwable e = error; if (e != null) { q.clear(); a.onError(e); return; } else if (empty) { a.onComplete(); return; } } if (empty) { break; } long r = requested; if (r != 0L) { a.onNext(b); if (r != Long.MAX_VALUE) { REQUESTED.decrementAndGet(this); } } else { cancel(); q.clear(); a.onError(new IllegalStateException("Could not emit value due to lack of requests")); return; } } missed = WIP.addAndGet(this, -missed); if (missed == 0) { break; } } } } static final class PublisherBufferBoundaryAndSizeOther<U> extends DeferredSubscription implements Subscriber<U> { final PublisherBufferBoundaryAndSizeMain<?, U, ?> main; public PublisherBufferBoundaryAndSizeOther(PublisherBufferBoundaryAndSizeMain<?, U, ?> main) { this.main = main; } @Override public void onSubscribe(Subscription s) { if (set(s)) { s.request(Long.MAX_VALUE); } } @Override public void onNext(U t) { main.otherNext(); } @Override public void onError(Throwable t) { main.otherError(t); } @Override public void onComplete() { main.otherComplete(); } } }