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

com.github.davidmoten.rx.internal.operators.TransformerStateMachine Maven / Gradle / Ivy

package com.github.davidmoten.rx.internal.operators;

import com.github.davidmoten.rx.util.BackpressureStrategy;
import com.github.davidmoten.util.Preconditions;

import rx.Notification;
import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Observable.Transformer;
import rx.Subscriber;
import rx.functions.Func0;
import rx.functions.Func1;
import rx.functions.Func2;
import rx.functions.Func3;

public final class TransformerStateMachine implements Transformer {

    private final Func0 initialState;
    private final Func3, ? extends State> transition;
    private final Func2, Boolean> completion;
    private final BackpressureStrategy backpressureStrategy;
    private final int initialRequest;

    private TransformerStateMachine(Func0 initialState,
            Func3, ? extends State> transition,
            Func2, Boolean> completion,
            BackpressureStrategy backpressureStrategy, int initialRequest) {
        Preconditions.checkNotNull(initialState);
        Preconditions.checkNotNull(transition);
        Preconditions.checkNotNull(completion);
        Preconditions.checkNotNull(backpressureStrategy);
        Preconditions.checkArgument(initialRequest > 0, "initialRequest must be greater than zero");
        this.initialState = initialState;
        this.transition = transition;
        this.completion = completion;
        this.backpressureStrategy = backpressureStrategy;
        this.initialRequest = initialRequest;
    }

    public static  Transformer create(Func0 initialState,
            Func3, ? extends State> transition,
            Func2, Boolean> completion,
            BackpressureStrategy backpressureStrategy, int initialRequest) {
        return new TransformerStateMachine(initialState, transition, completion,
                backpressureStrategy, initialRequest);
    }

    @Override
    public Observable call(final Observable source) {
        // use defer so we can have a single state reference for each
        // subscription
        return Observable.defer(new Func0>() {
            @Override
            public Observable call() {
                Mutable state = new Mutable(initialState.call());
                return source.materialize()
                        // do state transitions and emit notifications
                        // use flatMap to emit notification values
                        .flatMap(execute(transition, completion, state, backpressureStrategy),
                                initialRequest)
                        // complete if we encounter an unsubscribed sentinel
                        .takeWhile(NOT_UNSUBSCRIBED)
                        // flatten notifications to a stream which will enable
                        // early termination from the state machine if desired
                        .dematerialize();
            }
        });
    }

    private static  Func1, Observable>> execute(
            final Func3, ? extends State> transition,
            final Func2, Boolean> completion,
            final Mutable state, final BackpressureStrategy backpressureStrategy) {

        return new Func1, Observable>>() {

            @Override
            public Observable> call(final Notification in) {

                Observable> o = Observable
                        .create(new OnSubscribe>() {

                    @Override
                    public void call(Subscriber> subscriber) {
                        Subscriber w = wrap(subscriber);
                        if (in.hasValue()) {
                            state.value = transition.call(state.value, in.getValue(), w);
                            if (!subscriber.isUnsubscribed())
                                subscriber.onCompleted();
                            else {
                                // this is a special emission to indicate that the
                                // transition called unsubscribe. It will be
                                // filtered later.
                                subscriber.onNext(UnsubscribedNotificationHolder.unsubscribedNotification());
                            }
                        } else if (in.isOnCompleted()) {
                            if (completion.call(state.value, w) && !subscriber.isUnsubscribed()) {
                                w.onCompleted();
                            }
                        } else if (!subscriber.isUnsubscribed()) {
                            w.onError(in.getThrowable());
                        }
                    }
                });
                // because the observable we just created does not
                // support backpressure we need to apply a backpressure
                // handling operator. This operator is supplied by the
                // user.
                return applyBackpressure(o, backpressureStrategy);
            }

        };
    }
    
    private static final class UnsubscribedNotificationHolder {
        private static final Notification INSTANCE = Notification.createOnNext(null);
        
        @SuppressWarnings("unchecked")
        static  Notification unsubscribedNotification() {
            return (Notification) INSTANCE;
        }
    }

    private static  Observable> applyBackpressure(
            Observable> o, final BackpressureStrategy backpressureStrategy) {
        if (backpressureStrategy == BackpressureStrategy.BUFFER)
            return o.onBackpressureBuffer();
        else if (backpressureStrategy == BackpressureStrategy.DROP)
            return o.onBackpressureDrop();
        else if (backpressureStrategy == BackpressureStrategy.LATEST)
            return o.onBackpressureLatest();
        else
            throw new IllegalArgumentException(
                    "backpressure strategy not supported: " + backpressureStrategy);
    }

    private static final Func1, Boolean> NOT_UNSUBSCRIBED = new Func1, Boolean>() {

        @Override
        public Boolean call(Notification t) {
            return t != UnsubscribedNotificationHolder.unsubscribedNotification();
        }

    };

    private static final class Mutable {
        T value;

        Mutable(T value) {
            this.value = value;
        }
    }

    private static  NotificationSubscriber wrap(
            Subscriber> sub) {
        return new NotificationSubscriber(sub);
    }

    private static final class NotificationSubscriber extends Subscriber {

        private final Subscriber> sub;

        NotificationSubscriber(Subscriber> sub) {
            this.sub = sub;
            add(sub);
        }

        @Override
        public void onCompleted() {
            sub.onNext(Notification. createOnCompleted());
        }

        @Override
        public void onError(Throwable e) {
            sub.onNext(Notification. createOnError(e));
        }

        @Override
        public void onNext(Out t) {
            sub.onNext(Notification.createOnNext(t));
        }

    }

}