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<State, In, Out> implements Transformer<In, Out> { private final Func0<? extends State> initialState; private final Func3<? super State, ? super In, ? super Subscriber<Out>, ? extends State> transition; private final Func2<? super State, ? super Subscriber<Out>, Boolean> completion; private final BackpressureStrategy backpressureStrategy; private final int initialRequest; private TransformerStateMachine(Func0<? extends State> initialState, Func3<? super State, ? super In, ? super Subscriber<Out>, ? extends State> transition, Func2<? super State, ? super Subscriber<Out>, 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 <State, In, Out> Transformer<In, Out> create(Func0<? extends State> initialState, Func3<? super State, ? super In, ? super Subscriber<Out>, ? extends State> transition, Func2<? super State, ? super Subscriber<Out>, Boolean> completion, BackpressureStrategy backpressureStrategy, int initialRequest) { return new TransformerStateMachine<State, In, Out>(initialState, transition, completion, backpressureStrategy, initialRequest); } @Override public Observable<Out> call(final Observable<In> source) { // use defer so we can have a single state reference for each // subscription return Observable.defer(new Func0<Observable<Out>>() { @Override public Observable<Out> call() { Mutable<State> state = new Mutable<State>(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 <State, Out, In> Func1<Notification<In>, Observable<Notification<Out>>> execute( final Func3<? super State, ? super In, ? super Subscriber<Out>, ? extends State> transition, final Func2<? super State, ? super Subscriber<Out>, Boolean> completion, final Mutable<State> state, final BackpressureStrategy backpressureStrategy) { return new Func1<Notification<In>, Observable<Notification<Out>>>() { @Override public Observable<Notification<Out>> call(final Notification<In> in) { Observable<Notification<Out>> o = Observable .create(new OnSubscribe<Notification<Out>>() { @Override public void call(Subscriber<? super Notification<Out>> subscriber) { Subscriber<Out> 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.<Out>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<Object> INSTANCE = Notification.createOnNext(null); @SuppressWarnings("unchecked") static <T> Notification<T> unsubscribedNotification() { return (Notification<T>) INSTANCE; } } private static <Out> Observable<Notification<Out>> applyBackpressure( Observable<Notification<Out>> 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<Notification<?>, Boolean> NOT_UNSUBSCRIBED = new Func1<Notification<?>, Boolean>() { @Override public Boolean call(Notification<?> t) { return t != UnsubscribedNotificationHolder.unsubscribedNotification(); } }; private static final class Mutable<T> { T value; Mutable(T value) { this.value = value; } } private static <Out> NotificationSubscriber<Out> wrap( Subscriber<? super Notification<Out>> sub) { return new NotificationSubscriber<Out>(sub); } private static final class NotificationSubscriber<Out> extends Subscriber<Out> { private final Subscriber<? super Notification<Out>> sub; NotificationSubscriber(Subscriber<? super Notification<Out>> sub) { this.sub = sub; add(sub); } @Override public void onCompleted() { sub.onNext(Notification.<Out> createOnCompleted()); } @Override public void onError(Throwable e) { sub.onNext(Notification.<Out> createOnError(e)); } @Override public void onNext(Out t) { sub.onNext(Notification.createOnNext(t)); } } }