package io.smallrye.mutiny.operators.multi; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import org.reactivestreams.Publisher; import io.smallrye.mutiny.helpers.ParameterValidation; import io.smallrye.mutiny.helpers.Subscriptions; import io.smallrye.mutiny.infrastructure.Infrastructure; import io.smallrye.mutiny.operators.AbstractMulti; import io.smallrye.mutiny.subscription.MultiSubscriber; import io.smallrye.mutiny.subscription.SwitchableSubscriptionSubscriber; /** * Concatenates a fixed set of Publishers. * Items from each publisher are emitted in order. * All the items from one publisher must be consumed before items from another publisher are emitted. * * @param <T> the type of item */ public class MultiConcatOp<T> extends AbstractMulti<T> { private final Publisher<? extends T>[] publishers; private final boolean postponeFailurePropagation; @SafeVarargs public MultiConcatOp(boolean postponeFailurePropagation, Publisher<? extends T>... publishers) { this.publishers = ParameterValidation.doesNotContainNull(publishers, "publishers"); this.postponeFailurePropagation = postponeFailurePropagation; } @Override public void subscribe(MultiSubscriber<? super T> actual) { if (actual == null) { throw new NullPointerException("The subscriber must not be `null`"); } if (publishers.length == 0) { Subscriptions.complete(actual); return; } if (publishers.length == 1) { publishers[0].subscribe(Infrastructure.onMultiSubscription(publishers[0], actual)); return; } if (postponeFailurePropagation) { ConcatArrayAndPostponeFailureSubscriber<T> parent = new ConcatArrayAndPostponeFailureSubscriber<>(actual, publishers); actual.onSubscribe(parent); if (!parent.isCancelled()) { parent.onCompletion(); } } else { ConcatArraySubscriber<T> parent = new ConcatArraySubscriber<>(actual, publishers); actual.onSubscribe(parent); if (!parent.isCancelled()) { parent.onCompletion(); } } } static final class ConcatArraySubscriber<T> extends SwitchableSubscriptionSubscriber<T> { private final Publisher<? extends T>[] upstreams; private int currentIndex; private long emitted; private final AtomicInteger wip = new AtomicInteger(); ConcatArraySubscriber(MultiSubscriber<? super T> actual, Publisher<? extends T>[] upstreams) { super(actual); this.upstreams = upstreams; } @Override public void onItem(T t) { emitted++; downstream.onItem(t); } @Override public void onCompletion() { if (wip.getAndIncrement() == 0) { Publisher<? extends T>[] a = upstreams; do { if (isCancelled()) { return; } int i = currentIndex; if (i == a.length) { downstream.onCompletion(); return; } Publisher<? extends T> p = a[i]; long c = emitted; if (c != 0L) { emitted = 0L; emitted(c); } p.subscribe(Infrastructure.onMultiSubscription(p, this)); if (isCancelled()) { return; } currentIndex = ++i; } while (wip.decrementAndGet() != 0); } } } static final class ConcatArrayAndPostponeFailureSubscriber<T> extends SwitchableSubscriptionSubscriber<T> { final Publisher<? extends T>[] upstreams; int index; long produced; private final AtomicInteger wip = new AtomicInteger(); private final AtomicReference<Throwable> failure = new AtomicReference<>(); ConcatArrayAndPostponeFailureSubscriber(MultiSubscriber<? super T> actual, Publisher<? extends T>[] upstreams) { super(actual); this.upstreams = upstreams; } @Override public void onItem(T t) { produced++; downstream.onItem(t); } @Override public void onFailure(Throwable t) { if (Subscriptions.addFailure(failure, t)) { onCompletion(); } } @Override public void onCompletion() { if (wip.getAndIncrement() == 0) { Publisher<? extends T>[] a = upstreams; do { if (isCancelled()) { return; } int i = index; if (i == a.length) { Throwable last = Subscriptions.markFailureAsTerminated(failure); if (last != null) { downstream.onFailure(last); } else { downstream.onCompletion(); } return; } Publisher<? extends T> p = a[i]; if (p == null) { downstream.onFailure( new NullPointerException("Source Publisher at currentIndex " + i + " is null")); return; } long c = produced; if (c != 0L) { produced = 0L; emitted(c); } p.subscribe(Infrastructure.onMultiSubscription(p, this)); if (isCancelled()) { return; } index = ++i; } while (wip.decrementAndGet() != 0); } } } }