StateMachine.java

package io.github.jonloucks.concurrency.api;

import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
 * State machine.
 * User defined states with rules to restrict state transitions.
 *
 * @param <T> the user define state type
 */
public interface StateMachine<T> extends WaitableSupplier<T> {
    
    /**
     * Set the current state, state must already exist and be an allowed transition
     *
     * @param event the event name
     * @param state the new state
     * @return true if state was changed
     * @throws IllegalArgumentException when event is null, state is null, or unknown
     */
    boolean setState(String event, T state);
    
    /**
     * Get the current state
     *
     * @return the current state, never null
     */
    default T getState() {
        return get();
    }
    
    /**
     * Determine if the given state is known
     *
     * @param state the state to check
     * @return true iif the state is known
     * @throws IllegalArgumentException when state is null
     */
    boolean hasState(T state);
    
    /**
     * Determine if a transition is allowed from the current state to a new one.
     *
     * @param event the event that is triggering the transition
     * @param state the candidate state to transition to
     * @return if transition event is allowed to change the current state to the given state
     * @throws IllegalArgumentException when event is null, state is null, or unknown
     */
    boolean isTransitionAllowed(String event, T state);
    
    /**
     * Execute a transition from the current state to another
     *
     * @param builderConsumer the transition builder consumer
     * @param <B>             the transition return value
     * @param <R>             the return type of the transition. For example, a Closeable during open.
     * @return the builder consumer.
     * @throws IllegalArgumentException when builderConsumer is null or required fields are not present.
     */
    <B extends Transition.Builder<B, T, R>, R> R transition(Consumer<Transition.Builder<B, T, R>> builderConsumer);
    
    /**
     * Execute a transition from the current state to another
     *
     * @param transition the transition to execute
     * @param <R>        the return type of the transition. For example, a Closeable during open.
     * @return the transition return value
     * @throws IllegalArgumentException when transition is null or required fields are not present.
     */
    <R> R transition(Transition<T, R> transition);
    
    /**
     * Defines how a transition between states will be done
     *
     * @param <R> return type of the transition
     */
    interface Transition<S, R> {
        
        /**
         * @return the name of the event
         */
        String getEvent();
        
        /**
         * @return the success state of this transition
         */
        S getSuccessState();
        
        /**
         * @return the optional state if an exception is thrown
         */
        Optional<S> getErrorState();
        
        /**
         * @return the optional state if the transition is not allowed
         */
        Optional<S> getFailedState();
        
        /**
         * @return the optional return value on success
         */
        Optional<Supplier<R>> getSuccessValue();
        
        /**
         * @return the optional return value on exception thrown
         */
        Optional<Supplier<R>> getErrorValue();
        
        /**
         * @return the optional return value if transition is not allowed
         */
        Optional<Supplier<R>> getFailedValue();
   
        /**
         * Responsible for building a Transition
         *
         * @param <S> the return type of the transition
         */
        interface Builder<B extends Builder<B, S, R>, S, R> extends Transition<S, R> {
            
            /**
             * Assign the required event name
             *
             * @param event the event name
             * @return this builder
             */
            Builder<B, S, R> event(String event);
            
            /**
             * Assign the required success state
             *
             * @param state the success state
             * @return this builder
             */
            Builder<B, S, R> successState(S state);
            
            /**
             * Assign the optional success value
             *
             * @param valueSupplier the success value supplier
             * @return this builder
             */
            Builder<B, S, R> successValue(Supplier<R> valueSupplier);
            
            /**
             * Assign the optional success block
             *
             * @param runnable the runnable action
             * @return this builder
             */
            Builder<B, S, R> successValue(Runnable runnable);
            
            /**
             * Assign the optional error state used if there is exception
             *
             * @param state the error state
             * @return this builder
             */
            Builder<B, S, R> errorState(S state);
            
            /**
             * Assign the optional error value
             * Note: if this is set, when an exception occurs this value will be returned
             *
             * @param valueSupplier the error value supplier
             * @return this builder
             */
            Builder<B, S, R> errorValue(Supplier<R> valueSupplier);
            
            /**
             * Assign the optional failed state used if the goal state is not allowed
             *
             * @param state the failed state
             * @return this builder
             */
            Builder<B, S, R> failedState(S state);
            
            /**
             * Assign the optional failed value used when the goal state is not allowed
             *
             * @param valueSupplier the failed value supplier
             * @return this builder
             */
            Builder<B, S, R> failedValue(Supplier<R> valueSupplier);
        }
    }
    
    /**
     * Opt-in interface a state type can implement to assist in determining the valid transitions
     */
    interface Rule<T> {
        
        /**
         * Determine if 'this' state can transition to the target
         *
         * @param event the event name
         * @param goal  the goal state
         * @return true if the transition is valid
         */
        boolean canTransition(String event, T goal);
    }
    
    /**
     * StateMachine configuration
     *
     * @param <T> the type of each state
     */
    interface Config<T> {
        
        /**
         * Return the initial value. It is required, the use of required is because
         * the builder may not have provided a value
         *
         * @return the optional initial state
         */
        Optional<T> getInitial();
        
        /**
         * @return the list of states in the state machine
         */
        List<T> getStates();
        
        /**
         * Get all the rules for a specified state
         *
         * @param state the state
         * @return the rules of the state
         */
        List<Rule<T>> getStateRules(T state);
        
        /**
         * The Builder for a State Machine
         * @param <T> the type of each state
         */
        interface Builder<T> extends Config<T> {
            
            /**
             * Assign the required initial state
             * @param state the initial state
             * @return this builder
             */
            Builder<T> initial(T state);
            
            /**
             * Add a new state
             * @param state the state to add
             * @return this builder
             */
            Builder<T> state(T state);
            
            /**
             * Add a list of states
             * @param states the states to add
             * @return this builder
             */
            Builder<T> states(List<T> states);
            
            /**
             * Add a rule to a state
             *
             * @param state the state to add rules to
             * @param rule the rule to add
             * @return this builder
             */
            Builder<T> rule(T state, Rule<T> rule);
            
            /**
             * Add many rules to a state
             * @param state the to add rules to
             * @param rules the rules to add
             * @return this builder
             */
            Builder<T> rules(T state, List<Rule<T>> rules);
        }
    }
}