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

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

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

import rx.*;
import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.exceptions.*;
import rx.functions.Func1;
import rx.internal.util.*;
import rx.internal.util.atomic.*;
import rx.internal.util.unsafe.*;
import rx.plugins.RxJavaHooks;

/**
 * Flattens a sequence if Iterable sources, generated via a function, into a single sequence.
 *
 * @param  the input value type
 * @param  the output value type
 */
public final class OnSubscribeFlattenIterable implements OnSubscribe {

    final Observable source;

    final Func1> mapper;

    final int prefetch;

    /** Protected: use createFrom to handle source-dependent optimizations. */
    protected OnSubscribeFlattenIterable(Observable source,
            Func1> mapper, int prefetch) {
        this.source = source;
        this.mapper = mapper;
        this.prefetch = prefetch;
    }

    @Override
    public void call(Subscriber t) {
        final FlattenIterableSubscriber parent = new FlattenIterableSubscriber(t, mapper, prefetch);

        t.add(parent);
        t.setProducer(new Producer() {
            @Override
            public void request(long n) {
                parent.requestMore(n);
            }
        });

        source.unsafeSubscribe(parent);
    }

    public static  Observable createFrom(Observable source,
            Func1> mapper, int prefetch) {
        if (source instanceof ScalarSynchronousObservable) {
            T scalar = ((ScalarSynchronousObservable) source).get();
            return Observable.create(new OnSubscribeScalarFlattenIterable(scalar, mapper));
        }
        return Observable.create(new OnSubscribeFlattenIterable(source, mapper, prefetch));
    }

    static final class FlattenIterableSubscriber extends Subscriber {
        final Subscriber actual;

        final Func1> mapper;

        final long limit;

        final Queue queue;

        final AtomicReference error;

        final AtomicLong requested;

        final AtomicInteger wip;

        volatile boolean done;

        long produced;

        Iterator active;

        public FlattenIterableSubscriber(Subscriber actual,
                Func1> mapper, int prefetch) {
            this.actual = actual;
            this.mapper = mapper;
            this.error = new AtomicReference();
            this.wip = new AtomicInteger();
            this.requested = new AtomicLong();
            if (prefetch == Integer.MAX_VALUE) {
                this.limit = Long.MAX_VALUE;
                this.queue = new SpscLinkedArrayQueue(RxRingBuffer.SIZE);
            } else {
                // limit = prefetch * 75% rounded up
                this.limit = prefetch - (prefetch >> 2);
                if (UnsafeAccess.isUnsafeAvailable()) {
                    this.queue = new SpscArrayQueue(prefetch);
                } else {
                    this.queue = new SpscAtomicArrayQueue(prefetch);
                }
            }
            request(prefetch);
        }

        @Override
        public void onNext(T t) {
            if (!queue.offer(NotificationLite.next(t))) {
                unsubscribe();
                onError(new MissingBackpressureException());
                return;
            }
            drain();
        }

        @Override
        public void onError(Throwable e) {
            if (ExceptionsUtils.addThrowable(error, e)) {
                done = true;
                drain();
            } else {
                RxJavaHooks.onError(e);
            }
        }

        @Override
        public void onCompleted() {
            done = true;
            drain();
        }

        void requestMore(long n) {
            if (n > 0) {
                BackpressureUtils.getAndAddRequest(requested, n);
                drain();
            } else if (n < 0) {
                throw new IllegalStateException("n >= 0 required but it was " + n);
            }
        }

        void drain() {
            if (wip.getAndIncrement() != 0) {
                return;
            }

            final Subscriber actual = this.actual;
            final Queue queue = this.queue;

            int missed = 1;

            for (;;) {

                Iterator it = active;

                if (it == null) {
                    boolean d = done;

                    Object v = queue.poll();

                    boolean empty = v == null;

                    if (checkTerminated(d, empty, actual, queue)) {
                        return;
                    }

                    if (!empty) {

                        long p = produced + 1;
                        if (p == limit) {
                            produced = 0L;
                            request(p);
                        } else {
                            produced = p;
                        }

                        boolean b;

                        try {
                            Iterable iterable = mapper.call(NotificationLite.getValue(v));

                            it = iterable.iterator();

                            b = it.hasNext();
                        } catch (Throwable ex) {
                            Exceptions.throwIfFatal(ex);

                            onError(ex);

                            continue;
                        }

                        if (!b) {
                            continue;
                        }

                        active = it;
                    }
                }

                if (it != null) {
                    long r = requested.get();
                    long e = 0L;

                    while (e != r) {
                        if (checkTerminated(done, false, actual, queue)) {
                            return;
                        }

                        R v;

                        try {
                            v = it.next();
                        } catch (Throwable ex) {
                            Exceptions.throwIfFatal(ex);
                            it = null;
                            active = null;
                            onError(ex);
                            break;
                        }

                        actual.onNext(v);

                        if (checkTerminated(done, false, actual, queue)) {
                            return;
                        }

                        e++;

                        boolean b;

                        try {
                            b = it.hasNext();
                        } catch (Throwable ex) {
                            Exceptions.throwIfFatal(ex);
                            it = null;
                            active = null;
                            onError(ex);
                            break;
                        }

                        if (!b) {
                            it = null;
                            active = null;
                            break;
                        }
                    }

                    if (e == r) {
                        if (checkTerminated(done, queue.isEmpty() && it == null, actual, queue)) {
                            return;
                        }
                    }

                    if (e != 0L) {
                        BackpressureUtils.produced(requested, e);
                    }

                    if (it == null) {
                        continue;
                    }
                }

                missed = wip.addAndGet(-missed);
                if (missed == 0) {
                    break;
                }
            }
        }

        boolean checkTerminated(boolean d, boolean empty, Subscriber a, Queue q) {
            if (a.isUnsubscribed()) {
                q.clear();
                active = null;
                return true;
            }

            if (d) {
                Throwable ex = error.get();
                if (ex != null) {
                    ex = ExceptionsUtils.terminate(error);
                    unsubscribe();
                    q.clear();
                    active = null;

                    a.onError(ex);
                    return true;
                } else
                if (empty) {

                    a.onCompleted();
                    return true;
                }
            }

            return false;
        }
    }

    /**
     * A custom flattening operator that works from a scalar value and computes the iterable
     * during subscription time.
     *
     * @param  the scalar's value type
     * @param  the result value type
     */
    static final class OnSubscribeScalarFlattenIterable implements OnSubscribe {
        final T value;

        final Func1> mapper;

        public OnSubscribeScalarFlattenIterable(T value, Func1> mapper) {
            this.value = value;
            this.mapper = mapper;
        }

        @Override
        public void call(Subscriber t) {
            Iterator iterator;
            boolean b;
            try {
                Iterable it = mapper.call(value);

                iterator = it.iterator();

                b = iterator.hasNext();
            } catch (Throwable ex) {
                Exceptions.throwOrReport(ex, t, value);
                return;
            }

            if (!b) {
                t.onCompleted();
                return;
            }

            t.setProducer(new OnSubscribeFromIterable.IterableProducer(t, iterator));
        }
    }
}