org.glassfish.pfl.basic.fsm.StateEngine Maven / Gradle / Ivy
/*
* 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
*/
public class StateEngine {
/** 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> stateKinds ;
private Map> 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.class ) ;
final Map> map =
new EnumMap>( State.Kind.class ) ;
for (State.Kind kind : State.Kind.class.getEnumConstants()) {
Set states = new HashSet() ;
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 getKindSet( State.Kind kind ) {
Set 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 oldKindSet = getKindSet( oldState.getKind() ) ;
oldKindSet.add( oldState ) ;
Set newKindSet = getKindSet( newState.getKind() ) ;
newKindSet.add( newState ) ;
}
/** Return the set of final states for this state engine.
* This may be an empty set.
*/
public Set 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.
*/
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.
*/
public StateEngine add( State oldState, Set 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.
*/
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.
*/
public StateEngine add( State oldState, Set 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.
*/
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.
*/
public StateEngine setDefault( State oldState, State newState ) {
return setDefault( oldState, emptyAction, newState ) ;
}
/** 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:
*
* - It must contain exactly one state with Kind INITIAL.
*
- No final state may have an outgoing transition.
*
* 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.
*/
public void setDefaultAction( Action act ) {
mustBeInitializing() ;
defaultAction = act ;
}
/** 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 gas = currentState.getTransitions(in) ;
if (gas != null) {
Iterator 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