/*
 * Copyright (c) 1997, 2018 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0, which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

package org.glassfish.pfl.basic.fsm ;

import java.util.Collections ;
import java.util.Map ;
import java.util.EnumMap ;
import java.util.HashSet ;
import java.util.Set ;
import java.util.Iterator ;

Encodes the state transition function for a finite state machine.
Author:Ken Cavanaugh
/** * Encodes the state transition function for a finite state machine. * * @author Ken Cavanaugh */
public class StateEngine {
Create an empty uninitialized state engine.
/** Create an empty uninitialized state engine. */
public static StateEngine create() { return new StateEngine() ; } // An action that does nothing at all. private static Action emptyAction = new Action.Base( "Empty" ) { @Override public void doIt( FSM fsm, Input in ) { // NO-OP } } ; private boolean initializing ; private Action defaultAction ; private Map<State.Kind,Set<State>> stateKinds ; private Map<State.Kind,Set<State>> unmodifiableStateKinds ; private StateEngine() { initializing = true ; defaultAction = new Action.Base("Invalid Transition") { @Override public void doIt( FSM fsm, Input in ) { throw new IllegalStateException( "Invalid transition attempted from " + fsm.getState() + " under " + in ) ; } } ; stateKinds = new EnumMap<State.Kind,Set<State>>( State.Kind.class ) ; final Map<State.Kind,Set<State>> map = new EnumMap<State.Kind,Set<State>>( State.Kind.class ) ; for (State.Kind kind : State.Kind.class.getEnumConstants()) { Set<State> states = new HashSet<State>() ; stateKinds.put( kind, states ) ; map.put( kind, Collections.unmodifiableSet( states )) ; } unmodifiableStateKinds = Collections.unmodifiableMap( stateKinds ) ; } private void dprint( String msg ) { System.out.println( "StateEngine: " + msg ) ; } private Set<State> getKindSet( State.Kind kind ) { Set<State> result = stateKinds.get( kind ) ; return result ; } private void updateStateMap( State oldState, State newState ) { if (oldState.getKind() == State.Kind.FINAL) { throw new IllegalStateException( "Cannot create a transition that leaves a final state"); } if (oldState.getKind() == State.Kind.REFERENCE) { throw new IllegalStateException( "Cannot create a transition that leaves a reference state"); } Set<State> oldKindSet = getKindSet( oldState.getKind() ) ; oldKindSet.add( oldState ) ; Set<State> newKindSet = getKindSet( newState.getKind() ) ; newKindSet.add( newState ) ; }
Return the set of final states for this state engine. This may be an empty set.
/** Return the set of final states for this state engine. * This may be an empty set. */
public Set<State> getStates( State.Kind kind ) { return unmodifiableStateKinds.get( kind ) ; }
Add a new transition (old,in,guard,act,new) to the state engine. Multiple calls to add with the same old and in are permitted, in which case only a transition in which the guard evaluates to true will be taken. If no such transition is enabled, a default will be taken. If more than one transition is enabled, one will be chosen arbitrarily. This method can only be called before done(). An attempt to call it after done() results in an IllegalStateException.
/** Add a new transition (old,in,guard,act,new) to the state engine. * Multiple calls to add with the same old and in are permitted, * in which case only a transition in which the guard evaluates to * true will be taken. If no such transition is enabled, a default * will be taken. If more than one transition is enabled, one will * be chosen arbitrarily. * This method can only be called before done(). An attempt to * call it after done() results in an IllegalStateException. */
public StateEngine add( State oldState, Input input, Guard guard, Action action, State newState ) { mustBeInitializing() ; updateStateMap( oldState, newState ) ; Transition ga = new Transition( guard, action, newState ) ; oldState.addTransition( input, ga ) ; return this ; }
Repeatedly calls add( State, Input, Guard, Action, State ) for each element of input.
/** Repeatedly calls add( State, Input, Guard, Action, State ) for * each element of input. */
public StateEngine add( State oldState, Set<Input> input, Guard guard, Action action, State newState ) { for (Input in : input) { add(oldState, in, guard, action, newState); } return this ; }
Add a transition with a guard that always evaluates to true.
/** Add a transition with a guard that always evaluates to true. */
public StateEngine add( State oldState, Input input, Action action, State newState ) { mustBeInitializing() ; updateStateMap( oldState, newState ) ; Transition ta = new Transition( action, newState ) ; oldState.addTransition( input, ta ) ; return this ; }
Repeatedly call add( State, Input, Action, State ) for each element of input.
/** Repeatedly call add( State, Input, Action, State ) for each * element of input. */
public StateEngine add( State oldState, Set<Input> input, Action action, State newState ) { for (Input in : input) { add(oldState, in, action, newState); } return this ; }
Set the default transition and action for a state. This transition will be used if no more specific transition was defined for the actual input. Repeated calls to this method simply change the default. This method can only be called before done(). An attempt to call it after done() results in an IllegalStateException.
/** Set the default transition and action for a state. * This transition will be used if no more specific transition was * defined for the actual input. Repeated calls to this method * simply change the default. * This method can only be called before done(). An attempt to * call it after done() results in an IllegalStateException. */
public StateEngine setDefault( State oldState, Action action, State newState ) { mustBeInitializing() ; oldState.setDefaultAction( action ) ; oldState.setDefaultNextState( newState ) ; return this ; }
Equivalent to setDefault( oldState, act, newState ) where act is an action that does nothing.
/** Equivalent to setDefault( oldState, act, newState ) where act is an * action that does nothing. */
public StateEngine setDefault( State oldState, State newState ) { return setDefault( oldState, emptyAction, newState ) ; }
Euaivalent to setDefault( oldState, oldState )
/** Euaivalent to setDefault( oldState, oldState ) */
public StateEngine setDefault( State oldState ) { return setDefault( oldState, oldState ) ; }
Called after all transitions have been added to the state engine. This provides an opportunity for the implementation to optimize its representation before the state engine is used. This method may only be called once. An attempt to call it more than once results in an IllegalStateException.

Note that a valid StateEngine must satisfy the following conditions:

  1. It must contain exactly one state with Kind INITIAL.
  2. No final state may have an outgoing transition.
If either of these conditions are violated, done() throws an IllegalStateException.
/** Called after all transitions have been added to the state engine. * This provides an opportunity for the implementation to optimize * its representation before the state engine is used. This method * may only be called once. An attempt to call it more than once * results in an IllegalStateException. * <P> * Note that a valid StateEngine must satisfy the following conditions: * <OL> * <LI>It must contain exactly one state with Kind INITIAL. * <LI>No final state may have an outgoing transition. * </OL> * If either of these conditions are violated, done() throws an * IllegalStateException. */
public void done() { mustBeInitializing() ; // optimize FSM here if desired. For example, // we could choose different strategies for implementing // the state transition function based on the distribution // of values for states and input labels. initializing = false ; }
Set the default action used in this state engine. This is the action that is called whenever there is no applicable transition. Normally this would simply flag an error. This method can only be called before done(). An attempt to call it after done() results in an IllegalStateException.
/** Set the default action used in this state engine. This is the * action that is called whenever there is no applicable transition. * Normally this would simply flag an error. This method can only * be called before done(). An attempt to * call it after done() results in an IllegalStateException. */
public void setDefaultAction( Action act ) { mustBeInitializing() ; defaultAction = act ; }
Actually perform a state transition on the FSM on the runner.peek() FSM under Input in.
/** Actually perform a state transition on the FSM on * the runner.peek() FSM under Input in. */
public void doIt( Runner runner, Input in, boolean debug ) { // This method is present only for debugging. // innerDoIt does the actual transition. FSM fsm = runner.peek() ; if (debug) { dprint("doIt enter: currentState = " + fsm.getState() + " in = " + in); } try { innerDoIt( runner, in, debug ) ; } finally { if (debug) { dprint("doIt exit"); } } } private State getDefaultNextState( State currentState ) { // Use the currentState defaults if // set, otherwise use the state engine default. State nextState = currentState.getDefaultNextState() ; if (nextState == null) { // The state engine default never changes the state nextState = currentState ; } return nextState ; } private Action getDefaultAction( State currentState ) { Action action = currentState.getDefaultAction() ; if (action == null) { action = defaultAction; } return action ; } private void innerDoIt( Runner runner, Input in, boolean debug ) { if (debug) { dprint( "Calling innerDoIt with input " + in ) ; } FSM fsm = runner.peek() ; // Locals needed for performing the state transition, once we determine // the required transition. State currentState = null ; State nextState = null ; Action action = null ; // Do until no guard has deferred. boolean deferral = false ; do { deferral = false ; // clear this after each deferral! currentState = fsm.getState() ; nextState = getDefaultNextState( currentState ) ; action = getDefaultAction( currentState ) ; if (debug) { dprint( "currentState = " + currentState ) ; dprint( "in = " + in ) ; dprint( "default nextState = " + nextState ) ; dprint( "default action = " + action ) ; } Set<Transition> gas = currentState.getTransitions(in) ; if (gas != null) { Iterator<Transition> iter = gas.iterator() ; // Search for a guard that is not DISABLED. // All DISABLED means use defaults. while (iter.hasNext()) { Transition ga = iter.next() ; Guard.Result gr = ga.getGuard().evaluate( fsm, in ) ; if (debug) { dprint("doIt: evaluated " + ga + " with result " + gr); } if (gr == Guard.Result.ENABLED) { // ga has the next state and action. nextState = ga.getNextState() ; action = ga.getAction() ; if (debug) { dprint( "nextState = " + nextState ) ; dprint( "action = " + action ) ; } break ; } else if (gr == Guard.Result.DEFERRED) { deferral = true ; break ; } } } } while (deferral) ; performStateTransition( runner, in, nextState, action, debug ) ; fsm = runner.peek() ; final State state = fsm.getState() ; if (state.getKind() == State.Kind.FINAL) { runner.pop() ; final FSM nextFSM = runner.peek() ; if (nextFSM == null) { return; } final State st1 = nextFSM.getState() ; final State newState = st1.returnAction( nextFSM, fsm ) ; final StateEngine se = nextFSM.getStateEngine() ; se.performStateTransition( runner, null, newState, null, false ) ; } } // We can't really allow this, because it would invalidate the // pre/post semantics. // This is needed in Runner. void performStateTransition( Runner runner, Input in, State nextState, Action action, boolean debug ) { FSM fsm = runner.peek() ; State currentState = fsm.getState() ; // Perform the state transition. Pre and post actions are only // performed if the state changes (see UML hidden transitions). boolean different = !currentState.equals( nextState ) ; if (different) { if (debug) { dprint("doIt: executing postAction for state " + currentState); } try { currentState.postAction( fsm ) ; } catch (Throwable thr) { if (debug) { dprint("doIt: postAction threw " + thr); } } } try { // Note that action may be null in a transition, which simply // means that no action is needed. Note that action.doIt may // throw an exception, in which case the exception is // propagated after making sure that the transition is properly // completed. if (action != null) { action.doIt(fsm, in); } } finally { if (different) { if (debug) { dprint("doIt: executing preAction for state " + nextState); } FSM newFSM = null ; try { newFSM = nextState.preAction( fsm ) ; } catch (Throwable thr) { if (debug) { dprint("doIt: preAction threw " + thr); } } if (newFSM == null) { fsm.setState( nextState ) ; } else { runner.push( newFSM ) ; } } if (debug) { dprint("doIt: state is now " + nextState); } } } private void mustBeInitializing() { if (!initializing) { throw new IllegalStateException( "Invalid method call after initialization completed"); } } private void mustNotBeInitializing() { if (initializing) { throw new IllegalStateException( "Invalid method call before initialization completed"); } } } // end of StateEngineImpl.java