package org.openpnp.util; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import org.openpnp.model.AbstractModelObject; import org.pmw.tinylog.Logger; public class FiniteStateMachine<State, Message> extends AbstractModelObject { private final State initialState; private State state; private Map<State, Map<Message, Transition>> transitions = new HashMap<>(); public FiniteStateMachine(State initialState) { this.initialState = initialState; setState(initialState); } public void send(Message message) throws Exception { while (message != null) { State state = getState(); Map<Message, Transition> transitions = this.transitions.get(state); if (transitions == null) { throw new Exception("No defined transitions from " + state); } Transition transition = transitions.get(message); if (transition == null) { throw new Exception("No defined transitions from " + state + " for " + message); } if (transition.task != null) { transition.task.task(); } setState(transition.toState); Logger.trace(message + " => " + state + " -> " + transition.toState); message = transition.nextMessage; } } public boolean canSend(Message message) { State state = getState(); Map<Message, Transition> transitions = this.transitions.get(state); if (transitions == null) { return false; } Transition transition = transitions.get(message); if (transition == null) { return false; } return true; } public void add(State fromState, Message message, State toState) { add(fromState, message, toState, null, null); } public void add(State fromState, Message message, State toState, Task task) { add(fromState, message, toState, task, null); } public void add(State fromState, Message message, State toState, Message nextMessage) { add(fromState, message, toState, null, nextMessage); } public void add(State fromState, Message message, State toState, Task task, Message nextMessage) { Map<Message, Transition> t = transitions.get(fromState); if (t == null) { t = new HashMap<>(); transitions.put(fromState, t); } t.put(message, new Transition(toState, task, nextMessage)); } private void setState(State state) { Object oldValue = getState(); this.state = state; firePropertyChange("state", oldValue, state); } public State getState() { return state; } /** * Dump the FSM states to Graphviz format. It can be visualized using: * http://www.webgraphviz.com/ More information about the output format can be found at: * http://www.graphviz.org/content/dot-language * * @return */ public String toGraphviz() { StringBuilder sb = new StringBuilder(); sb.append("digraph fsm {\n"); for (Entry<State, Map<Message, Transition>> entry : transitions.entrySet()) { sb.append(" subgraph {\n"); if (entry.getKey() == initialState) { sb.append(" rank=source;\n"); } for (Entry<Message, Transition> t : entry.getValue().entrySet()) { sb.append(String.format(" %s -> %s [ label = %s ];\n", entry.getKey(), t.getValue().toState, t.getKey())); } sb.append(" }\n"); } sb.append("}\n"); return sb.toString(); } public class Transition { public final State toState; public final Task task; public final Message nextMessage; public Transition(State toState, Task task, Message nextMessage) { this.toState = toState; this.task = task; this.nextMessage = nextMessage; } } public interface Task { void task() throws Exception; } }