All Downloads are FREE. Search and download functionalities are using the official Maven repository.

rx.internal.operators.OperatorConcat Maven / Gradle / Ivy

There is a newer version: 1.3.8
Show newest version
/**
 * Copyright 2014 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.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

import rx.Observable;
import rx.Observable.Operator;
import rx.Producer;
import rx.Subscriber;
import rx.functions.Action0;
import rx.internal.producers.ProducerArbiter;
import rx.observers.SerializedSubscriber;
import rx.subscriptions.SerialSubscription;
import rx.subscriptions.Subscriptions;

/**
 * Returns an Observable that emits the items emitted by two or more Observables, one after the other.
 * 

* * * @param * the source and result value type */ public final class OperatorConcat implements Operator> { /** Lazy initialization via inner-class holder. */ private static final class Holder { /** A singleton instance. */ static final OperatorConcat INSTANCE = new OperatorConcat(); } /** * @return a singleton instance of this stateless operator. */ @SuppressWarnings("unchecked") public static OperatorConcat instance() { return (OperatorConcat)Holder.INSTANCE; } private OperatorConcat() { } @Override public Subscriber> call(final Subscriber child) { final SerializedSubscriber s = new SerializedSubscriber(child); final SerialSubscription current = new SerialSubscription(); child.add(current); ConcatSubscriber cs = new ConcatSubscriber(s, current); ConcatProducer cp = new ConcatProducer(cs); child.setProducer(cp); return cs; } static final class ConcatProducer implements Producer { final ConcatSubscriber cs; ConcatProducer(ConcatSubscriber cs) { this.cs = cs; } @Override public void request(long n) { cs.requestFromChild(n); } } static final class ConcatSubscriber extends Subscriber> { final NotificationLite> nl = NotificationLite.instance(); private final Subscriber child; private final SerialSubscription current; final ConcurrentLinkedQueue queue; volatile ConcatInnerSubscriber currentSubscriber; final AtomicInteger wip = new AtomicInteger(); // accessed by REQUESTED private final AtomicLong requested = new AtomicLong(); private final ProducerArbiter arbiter; public ConcatSubscriber(Subscriber s, SerialSubscription current) { super(s); this.child = s; this.current = current; this.arbiter = new ProducerArbiter(); this.queue = new ConcurrentLinkedQueue(); add(Subscriptions.create(new Action0() { @Override public void call() { queue.clear(); } })); } @Override public void onStart() { // no need for more than 1 at a time since we concat 1 at a time, so we'll request 2 to start ... // 1 to be subscribed to, 1 in the queue, then we'll keep requesting 1 at a time after that request(2); } private void requestFromChild(long n) { if (n <=0) return; // we track 'requested' so we know whether we should subscribe the next or not long previous = BackpressureUtils.getAndAddRequest(requested, n); arbiter.request(n); if (previous == 0) { if (currentSubscriber == null && wip.get() > 0) { // this means we may be moving from one subscriber to another after having stopped processing // so need to kick off the subscribe via this request notification subscribeNext(); } } } private void decrementRequested() { requested.decrementAndGet(); } @Override public void onNext(Observable t) { queue.add(nl.next(t)); if (wip.getAndIncrement() == 0) { subscribeNext(); } } @Override public void onError(Throwable e) { child.onError(e); unsubscribe(); } @Override public void onCompleted() { queue.add(nl.completed()); if (wip.getAndIncrement() == 0) { subscribeNext(); } } void completeInner() { currentSubscriber = null; if (wip.decrementAndGet() > 0) { subscribeNext(); } request(1); } void subscribeNext() { if (requested.get() > 0) { Object o = queue.poll(); if (nl.isCompleted(o)) { child.onCompleted(); } else if (o != null) { Observable obs = nl.getValue(o); currentSubscriber = new ConcatInnerSubscriber(this, child, arbiter); current.set(currentSubscriber); obs.unsafeSubscribe(currentSubscriber); } } else { // requested == 0, so we'll peek to see if we are completed, otherwise wait until another request Object o = queue.peek(); if (nl.isCompleted(o)) { child.onCompleted(); } } } } static class ConcatInnerSubscriber extends Subscriber { private final Subscriber child; private final ConcatSubscriber parent; private final AtomicInteger once = new AtomicInteger(); private final ProducerArbiter arbiter; public ConcatInnerSubscriber(ConcatSubscriber parent, Subscriber child, ProducerArbiter arbiter) { this.parent = parent; this.child = child; this.arbiter = arbiter; } @Override public void onNext(T t) { child.onNext(t); parent.decrementRequested(); arbiter.produced(1); } @Override public void onError(Throwable e) { if (once.compareAndSet(0, 1)) { // terminal error through parent so everything gets cleaned up, including this inner parent.onError(e); } } @Override public void onCompleted() { if (once.compareAndSet(0, 1)) { // terminal completion to parent so it continues to the next parent.completeInner(); } } @Override public void setProducer(Producer producer) { arbiter.setProducer(producer); } } }