StateMachine.java
/*******************************************************************************
* Copyright (c) 2004, 2013 Steve Flasby
* All rights reserved.
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
* <ul>
* <li>Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.</li>
* <li>Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.</li>
* </ul>
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
******************************************************************************/
package org.flasby.util.statemachine;
import java.util.HashMap;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.logging.Logger;
/**
* implements a Finite State Machine.
*
* @author steve
*
*/
public class StateMachine<State, Event> {
private static final Logger LOG = Logger.getLogger("StateMachine");
public final BiConsumer<State, Event> NULL_CONSUMER = (a,b)->{};
private static class Transition<State, Event> {
private final State mToState;
private final BiConsumer<State, Event> mEnterStateAction;
private final BiConsumer<State, Event> mExitStateAction;
public Transition(State toState, BiConsumer<State, Event> enterStateAction,
BiConsumer<State, Event> exitStateAction) {
mToState = toState;
mEnterStateAction = enterStateAction;
mExitStateAction = exitStateAction;
}
public State getNextState() {
return mToState;
}
public BiConsumer<State, Event> getExitStateAction() {
return mExitStateAction;
}
public BiConsumer<State, Event> getEnterStateAction() {
return mEnterStateAction;
}
@Override
public String toString() {
return "Transition [mEnterStateAction=" + mEnterStateAction + ", mExitStateAction=" + mExitStateAction
+ ", mToState=" + mToState + "]";
}
}
private State mState;
private final Map<State, Map<Event, Transition<State, Event>>> mTransitions = new HashMap<>();
private final Map<Event, Transition<State, Event>> mGlobalTransition = new HashMap<>();
private Transition<State, Event> mCurrentTransition = new Transition<State, Event>(null, NULL_CONSUMER, NULL_CONSUMER);
private final BiConsumer<State, Event> mMonitor;
/**
* creates a new FSM in the defined initialState.
*
* @param initialState
*/
public StateMachine(State initialState) {
this(initialState, (s, e) -> {} );
}
/**
* creates a new FSM in the defined initialState.
*
* @param initialState
*/
public StateMachine(State initialState, BiConsumer<State, Event> monitor) {
setState(initialState);
this.mMonitor = monitor;
}
/**
* process the event and move from the current state to the next one if a
* transition is defined for the event.
*
* If no such transition is found then call handleNoTransition(...) and remain
* in the current state. If a transition is found the the FSM: - invokes the
* exitStateAction on the transition if such is defined. - moves to the next
* state - invokes the enterStateAction on the transition if such is defined.
*
* @param event The event to process
*/
public void process(Event event) {
Transition<State, Event> t = findTransition(event);
if (t == null) {
handleNoTransition(event);
} else {
transition(t, event);
}
// // is there a globalTransition defined?
// if ( mGlobalTransition.containsKey(event) ) {
// LOG.fine("Moving from "+getState() +" to
// "+mGlobalTransition.get(event).getNextState() +" because of global transition
// of : "
// +event);
// transition(mGlobalTransition.get(event),event);
// //setState(mGlobalTransition.get(event).getNextState() );
// //mMonitor.accept(getState(), event);
// // And execute the enter state action if one exists
// // if ( null != mGlobalTransition.get(event).getEnterStateAction() ) {
// // mGlobalTransition.get(event).getEnterStateAction().accept(getState(),
// event);
// //}
// } else {
// handleNoTransition(event);
// }
// } else {
// LOG.fine("Moving from "+getState() +" to "+t.getNextState()+" because:
// "+event);
// transition(t,event);
// // Get the Action for the current state and execute it if there is one
// // mCurrentTransition.getExitStateAction().accept(getState(), event);
// // Move to the next state
// //setState( t.getNextState() );
// //mMonitor.accept(getState(), event);
// // And execute the enter state action if one exists
// // if ( null != t.getEnterStateAction() ) {
// // t.getEnterStateAction().accept(getState(), event);
// // }
// // mCurrentTransition = t;
// }
}
// Can return null!
private Transition<State, Event> findTransition(Event event) {
Map<Event, Transition<State, Event>> ts = getTransitions(getState());
if (ts == null) {
if (mGlobalTransition.size() == 0) {
throw new IllegalStateException(
"No transitions defined for current state: " + getState() + " correct your state machine");
} else {
LOG.warning("Warning: only global transitions defined for current state: " + getState()
+ " correct your state machine");
}
}
Transition<State, Event> t = null;
if (ts != null) {
// If we have a Transition for this
LOG.info("Normal Transitions for this event are: " + ts.get(event));
t = ts.get(event);
} else {
LOG.info("No Normal Transitions for this event: " + event);
}
LOG.info("Global Transitions for this event are: " + mGlobalTransition.get(event));
if (t == null) {
t = mGlobalTransition.get(event);
}
LOG.info("Returning the Transition for this event: " + t);
return t;
}
private void transition(Transition<State, Event> transition, Event event) {
// Get the Action for the current state and execute it if there is one
mCurrentTransition.getExitStateAction().accept(getState(), event);
mState = transition.getNextState();
mMonitor.accept(getState(), event);
transition.getEnterStateAction().accept(getState(), event);
mCurrentTransition = transition;
}
protected void handleNoTransition(Event event) {
LOG.warning("No Transition defined from State:" + getState() + " for Event:" + event);
}
protected Map<Event, Transition<State, Event>> getTransitions(State state) {
return mTransitions.get(state);
}
public StateMachine<State, Event> addTransition(State from, State to, Event event) {
return addTransition(from, to, event, NULL_CONSUMER, NULL_CONSUMER);
}
public StateMachine<State, Event> addTransition(State from, State to, Event event,
BiConsumer<State, Event> enterStateAction) {
return addTransition(from, to, event, enterStateAction, NULL_CONSUMER);
}
public StateMachine<State, Event> addTransition(State from, State to, Event event,
BiConsumer<State, Event> enterStateAction, BiConsumer<State, Event> exitStateAction) {
Map<Event, Transition<State, Event>> ts = getTransitions(from);
if (ts == null) {
ts = new HashMap<>();
mTransitions.put(from, ts);
}
ts.put(event, createTransition(to, enterStateAction, exitStateAction));
return this;
}
public StateMachine<State, Event> addGlobalTransition(Event event, State to,
BiConsumer<State, Event> enterStateAction) {
mGlobalTransition.put(event, createTransition(to, enterStateAction, NULL_CONSUMER));
return this;
}
public StateMachine<State, Event> addGlobalTransition(Event event, State to,
BiConsumer<State, Event> enterStateAction, BiConsumer<State, Event> exitStateAction) {
mGlobalTransition.put(event, createTransition(to, enterStateAction, exitStateAction));
return this;
}
private Transition<State, Event> createTransition(State to, BiConsumer<State, Event> enterStateAction,
BiConsumer<State, Event> exitStateAction) {
return new Transition<State, Event>(to, enterStateAction, exitStateAction);
}
public State getState() {
return mState;
}
protected void setState(State state) {
mState = state;
}
public void forceTransition(State state, Event event) {
mCurrentTransition.getExitStateAction().accept(getState(), event);
mMonitor.accept(getState(), event);
// and then force the event
mState = state;
}
}