/** * Copyright 2016 Netflix, Inc. * * 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 rx.internal.operators; import java.util.Queue; import java.util.concurrent.atomic.*; import rx.*; import rx.Observable.OnSubscribe; import rx.exceptions.*; import rx.functions.Func1; import rx.internal.producers.ProducerArbiter; import rx.internal.util.*; import rx.internal.util.atomic.SpscAtomicArrayQueue; import rx.internal.util.unsafe.*; import rx.observers.SerializedSubscriber; import rx.plugins.RxJavaHooks; import rx.subscriptions.SerialSubscription; /** * Maps a source sequence into Observables and concatenates them in order, subscribing * to one at a time. * @param the source value type * @param the output value type * * @since 1.1.2 */ public final class OnSubscribeConcatMap implements OnSubscribe { final Observable source; final Func1> mapper; final int prefetch; /** * How to handle errors from the main and inner Observables. * See the constants below. */ final int delayErrorMode; /** Whenever any Observable fires an error, terminate with that error immediately. */ public static final int IMMEDIATE = 0; /** Whenever the main fires an error, wait until the inner terminates. */ public static final int BOUNDARY = 1; /** Delay all errors to the very end. */ public static final int END = 2; public OnSubscribeConcatMap(Observable source, Func1> mapper, int prefetch, int delayErrorMode) { this.source = source; this.mapper = mapper; this.prefetch = prefetch; this.delayErrorMode = delayErrorMode; } @Override public void call(Subscriber child) { Subscriber s; if (delayErrorMode == IMMEDIATE) { s = new SerializedSubscriber(child); } else { s = child; } final ConcatMapSubscriber parent = new ConcatMapSubscriber(s, mapper, prefetch, delayErrorMode); child.add(parent); child.add(parent.inner); child.setProducer(new Producer() { @Override public void request(long n) { parent.requestMore(n); } }); if (!child.isUnsubscribed()) { source.unsafeSubscribe(parent); } } static final class ConcatMapSubscriber extends Subscriber { final Subscriber actual; final Func1> mapper; final int delayErrorMode; final ProducerArbiter arbiter; final Queue queue; final AtomicInteger wip; final AtomicReference error; final SerialSubscription inner; volatile boolean done; volatile boolean active; public ConcatMapSubscriber(Subscriber actual, Func1> mapper, int prefetch, int delayErrorMode) { this.actual = actual; this.mapper = mapper; this.delayErrorMode = delayErrorMode; this.arbiter = new ProducerArbiter(); this.wip = new AtomicInteger(); this.error = new AtomicReference(); Queue q; if (UnsafeAccess.isUnsafeAvailable()) { q = new SpscArrayQueue(prefetch); } else { q = new SpscAtomicArrayQueue(prefetch); } this.queue = q; this.inner = new SerialSubscription(); this.request(prefetch); } @Override public void onNext(T t) { if (!queue.offer(NotificationLite.instance().next(t))) { unsubscribe(); onError(new MissingBackpressureException()); } else { drain(); } } @Override public void onError(Throwable mainError) { if (ExceptionsUtils.addThrowable(error, mainError)) { done = true; if (delayErrorMode == IMMEDIATE) { Throwable ex = ExceptionsUtils.terminate(error); if (!ExceptionsUtils.isTerminated(ex)) { actual.onError(ex); } inner.unsubscribe(); } else { drain(); } } else { pluginError(mainError); } } @Override public void onCompleted() { done = true; drain(); } void requestMore(long n) { if (n > 0) { arbiter.request(n); } else if (n < 0) { throw new IllegalArgumentException("n >= 0 required but it was " + n); } } void innerNext(R value) { actual.onNext(value); } void innerError(Throwable innerError, long produced) { if (!ExceptionsUtils.addThrowable(error, innerError)) { pluginError(innerError); } else if (delayErrorMode == IMMEDIATE) { Throwable ex = ExceptionsUtils.terminate(error); if (!ExceptionsUtils.isTerminated(ex)) { actual.onError(ex); } unsubscribe(); } else { if (produced != 0L) { arbiter.produced(produced); } active = false; drain(); } } void innerCompleted(long produced) { if (produced != 0L) { arbiter.produced(produced); } active = false; drain(); } void pluginError(Throwable e) { RxJavaHooks.onError(e); } void drain() { if (wip.getAndIncrement() != 0) { return; } final int delayErrorMode = this.delayErrorMode; for (;;) { if (actual.isUnsubscribed()) { return; } if (!active) { if (delayErrorMode == BOUNDARY) { if (error.get() != null) { Throwable ex = ExceptionsUtils.terminate(error); if (!ExceptionsUtils.isTerminated(ex)) { actual.onError(ex); } return; } } boolean mainDone = done; Object v = queue.poll(); boolean empty = v == null; if (mainDone && empty) { Throwable ex = ExceptionsUtils.terminate(error); if (ex == null) { actual.onCompleted(); } else if (!ExceptionsUtils.isTerminated(ex)) { actual.onError(ex); } return; } if (!empty) { Observable source; try { source = mapper.call(NotificationLite.instance().getValue(v)); } catch (Throwable mapperError) { Exceptions.throwIfFatal(mapperError); drainError(mapperError); return; } if (source == null) { drainError(new NullPointerException("The source returned by the mapper was null")); return; } if (source != Observable.empty()) { if (source instanceof ScalarSynchronousObservable) { ScalarSynchronousObservable scalarSource = (ScalarSynchronousObservable) source; active = true; arbiter.setProducer(new ConcatMapInnerScalarProducer(scalarSource.get(), this)); } else { ConcatMapInnerSubscriber innerSubscriber = new ConcatMapInnerSubscriber(this); inner.set(innerSubscriber); if (!innerSubscriber.isUnsubscribed()) { active = true; source.unsafeSubscribe(innerSubscriber); } else { return; } } request(1); } else { request(1); continue; } } } if (wip.decrementAndGet() == 0) { break; } } } void drainError(Throwable mapperError) { unsubscribe(); if (ExceptionsUtils.addThrowable(error, mapperError)) { Throwable ex = ExceptionsUtils.terminate(error); if (!ExceptionsUtils.isTerminated(ex)) { actual.onError(ex); } } else { pluginError(mapperError); } } } static final class ConcatMapInnerSubscriber extends Subscriber { final ConcatMapSubscriber parent; long produced; public ConcatMapInnerSubscriber(ConcatMapSubscriber parent) { this.parent = parent; } @Override public void setProducer(Producer p) { parent.arbiter.setProducer(p); } @Override public void onNext(R t) { produced++; parent.innerNext(t); } @Override public void onError(Throwable e) { parent.innerError(e, produced); } @Override public void onCompleted() { parent.innerCompleted(produced); } } static final class ConcatMapInnerScalarProducer implements Producer { final R value; final ConcatMapSubscriber parent; boolean once; public ConcatMapInnerScalarProducer(R value, ConcatMapSubscriber parent) { this.value = value; this.parent = parent; } @Override public void request(long n) { if (!once && n > 0L) { once = true; ConcatMapSubscriber p = parent; p.innerNext(value); p.innerCompleted(1); } } } }