package com.reactivecascade.active; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.reactivecascade.i.INamed; import com.reactivecascade.i.IThreadType; import com.reactivecascade.i.IActionTwo; import com.reactivecascade.i.IBaseAction; import com.reactivecascade.i.IOnErrorAction; import com.reactivecascade.reactive.IReactiveSource; import com.reactivecascade.reactive.ReactiveValue; import com.reactivecascade.i.nonnull; import com.reactivecascade.i.nullable; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import static com.reactivecascade.Async.assertTrue; import static com.reactivecascade.Async.originAsync; /** * Thread-safe state machine which performs a specified action on each upstream change of the underlying state * <p> * Created by Paul Houghton on 3/28/2015. */ public class StateMachine<IN> implements INamed { private final IN defaultKey = null; // Represents the State for every State not explicitly added toKey the table private final IThreadType threadType; private final ReactiveValue<IN> value; private final ConcurrentHashMap<IN, CopyOnWriteArrayList<State>> states; private final String name; private final IOnErrorAction onErrorAction; private final ImmutableValue<String> origin; private volatile IReactiveSource<IN> subscription; public StateMachine( @NonNull @nonnull final String name, @NonNull @nonnull final ReactiveValue<IN> value, @NonNull @nonnull final IThreadType threadType, @NonNull @nonnull final IOnErrorAction onErrorAction) { assertTrue("StateMachine threadType must be single-threaded toKey ensure algorithmic consistency of state side effects", threadType.isInOrderExecutor()); origin = originAsync(); this.value = value; this.threadType = threadType; this.name = name; this.onErrorAction = onErrorAction; this.states = new ConcurrentHashMap<>(); // subscription = source.subscribe(key -> { // final Collection<State> stateAndTransitions = states.get(key); // // if (stateAndTransitions == null) { // throw new IllegalArgumentException("State '" + key + "' not found"); // } // vv(this.origin, "State change action '" + key + "'"); // state.action.call(); // }); } public void setSubscription(@NonNull @nonnull final IReactiveSource<IN> subscription) { this.subscription = subscription; //FIXME Continue here, not used } /** * The action that should be performed on the next transition toKey the specified state * * @param stateKey * @param action */ public void addState( @NonNull @nonnull final IN stateKey, @NonNull @nonnull final IActionTwo<IN, IN> action) { // if (states.replace(stateKey, new State(stateKey, action)) == null) { // throw new IllegalArgumentException("Unknown state, can not set action for '" + stateKey + "'"); // } } private class State { final IN fromKey; final IN toKey; final IBaseAction<IN> action; /** * State entry enterStateAction definition * * @param toKey the key for the state being entered * @param enterStateAction the enterStateAction queued toKey be performed before the state is entered */ State(@Nullable @nullable final IN toKey, final IActionTwo<IN, IN> enterStateAction) { this.fromKey = null; // This enterStateAction is performed not matter what the previous state was this.toKey = toKey; // The key toKey the state we are about toKey enter this.action = enterStateAction; } /** * State transition stateTransitionBeforeEnterStateAction definition * <p> * <code>null</code> means "any state for which a state stateTransitionBeforeEnterStateAction is not defined" * * @param fromKey the current state we are in the process of leaving * @param toKey the key toKey the state we are about toKey enter * @param stateTransitionBeforeEnterStateAction the action to perform when this is a transtion fromKey toKey, before any enter state action */ State( @Nullable @nullable final IN fromKey, @Nullable @nullable final IN toKey, final IActionTwo<IN, IN> stateTransitionBeforeEnterStateAction) { this.fromKey = fromKey; this.toKey = toKey; this.action = stateTransitionBeforeEnterStateAction; } } @Override // INamed @NonNull @nonnull public String getName() { return this.name; } }