Apache Commons SCXML: Finite State Machine Implementation
This article mentions about Finite State Machines (FSM), SCXML ( State Chart extensible Markup Language) and Apache Common’s SCXML library. A basic ATM finite state machine sample code is also provided with the article.
Finite State Machines:
You probably remember Finite State Machines from your Computer Science courses. FSMs are used to design computer programs or digital circuits.
A FSM is simply an abstract machine that can be in one of a finite number of states.The machine is in only one state at a time; the state it is in at any given time is called the current state. It can change from one state to another when initiated by a triggering event or condition, this is called a transition. A particular FSM is defined by a list of the possible transition states from each current state, and the triggering condition for each transition.
SCXML Language:
A working draft called SCXML (State Machine Notation for Control Abstraction, published by W3C) can be used to describe complex state machines. SCXML is a general-purpose xml-based state machine language. It is still a draft and latest version is 16 February 2012. Click here to get five minute introduction to SCXML documents.
Apache Commons SCXML Library:
Apache has an implementation aimed at creating and maintaining a Java SCXML engine capable of executing a state machine defined using a SCXML document, while abstracting out the environment interfaces. The latest stable version is 0.9.
- Library Website: http://commons.apache.org/scxml/index.html
- Eclipse Plugin: http://commons.apache.org/sandbox/gsoc/2010/scxml-eclipse/ (still under development)
- UseCases: http://commons.apache.org/scxml/usecases.html
SCXML Editors:
Apache’s Eclipse Plugin aims to provide a visual editor to edit SCXML files but it is still under development. There is also scxml gui ( http://code.google.com/p/scxmlgui/ ) which is very successful. You can also check State Forge’s visual State Machine Diagram : http://www.stateforge.com/StateMachineDiagram/StateMachineDiagram.html
Code Sample :
In this part of the article, we will implement a basic ATM Status state-machine. As a brief information, we assume an ATM can have following statuses. :
- idle: When ATM has no activity, simply it is closed
- loading: when an idle atm tries to connect to ATM Server, configs and info is started loading
- Out-of-service: If ATM loading fails or ATM is shutdown
- In-service: If ATM laoding is successful or ATM is started up
- Disconnected: If ATM is not connected to network
Sorry for the missing or incorrect information about ATM statuses. This is just an example. Let’s first draw our state machine using scxmlgui program. One can write his own scxml file but scxmlgui does that ugly task for you. Here is the state chart diagram which describes the status change of an ATM :
And the output SCXML file describing the transitions in the diagram above:
<scxml initial="idle" name="atm.connRestored" version="0.9" xmlns="http://www.w3.org/2005/07/scxml"> <state id="idle"> <transition event="atm.connected" target="loading"></transition> </state> <state id="loading"> <transition event="atm.loadSuccess" target="inService"></transition> <transition event="atm.connClosed" target="disconnected"></transition> <transition event="atm.loadFail" target="outOfService"></transition> </state> <state id="inService"> <transition event="atm.shutdown" target="outOfService"></transition> <transition event="atm.connLost" target="disconnected"></transition> </state> <state id="outOfService"> <transition event="atm.startup" target="inService"></transition> <transition event="atm.connLost" target="disconnected"></transition> </state> <state id="disconnected"> <transition event="atm.connRestored" target="inService"></transition> </state> </scxml>
Our FSM implemantation is in AtmStatusFSM class.
- AtmStatusFSM class extends org.apache.commons.scxml.env.AbstractStateMachine.
- FSM is configured by giving the scxml file (atm_status.xml) path to super constructor.
- ATM state changes are controlled by events. When fireEvent method is called with related event name [e.g. fireEvent(‘atm.connected’)], FSM state is updated automatically. You can get current state whenever you want.
- You can also write public methods having the state names of our FSM. These methods are called when the corresponding state is activated.
package net.javafun.example.atmstatusfsm; import java.util.Collection; import java.util.Set; import org.apache.commons.scxml.env.AbstractStateMachine; import org.apache.commons.scxml.model.State; /** * Atm Status Finite State Machine * * @see Apache Commons Scxml Library * @author ozkansari.com * */ public class AtmStatusFSM extends AbstractStateMachine { /** * State Machine uses this scmxml config file */ private static final String SCXML_CONFIG_ATM_STATUS = "net/javafun/example/atmstatusfsm/atm_status.xml"; /** CONSTRUCTOR(S) */ public AtmStatusFSM() { super(AtmStatusFSM.class.getClassLoader().getResource(SCXML_CONFIG_ATM_STATUS)); } /** HELPER METHOD(S) */ /** * Fire the event */ public void firePreDefinedEvent(AtmStatusEventEnum eventEnum){ System.out.println("EVENT: " + eventEnum); this.fireEvent(eventEnum.getEventName()); } public void callState(String name){ this.invoke(name); } /** * Get current state ID as string */ public String getCurrentStateId() { Set states = getEngine().getCurrentStatus().getStates(); State state = (State) states.iterator().next(); return state.getId(); } /** * Get current state as apache's State object */ public State getCurrentState() { Set states = getEngine().getCurrentStatus().getStates(); return ( (State) states.iterator().next()); } /** * Get events belongs to current status of the FSM */ public Collection getCurrentStateEvents() { return getEngine().getCurrentStatus().getEvents(); } /** STATES */ // Each method below is the activity corresponding to a state in the // SCXML document (see class constructor for pointer to the document). public void idle() { System.out.println("STATE: idle"); } public void loading() { System.out.println("STATE: loading"); } public void inService() { System.out.println("STATE: inService"); } public void outOfService() { System.out.println("STATE: outOfService"); } public void disconnected() { System.out.println("STATE: disconnected"); } }
We have the following enum file to describe our events. You don’t have to code such a class, but it might help to define the events. You can also get those events dynamically using getEngine().getCurrentStatus().getEvents()code fragment.
package net.javafun.example.atmstatusfsm; /** * Atm Status Change Events * * @author ozkansari.com * */ public enum AtmStatusEventEnum { CONNECT("atm.connected"), CONNECTION_CLOSED("atm.connClosed"), CONNECTION_LOST("atm.connLost"), CONNECTION_RESTORED("atm.connRestored"), LOAD_SUCCESS("atm.loadSuccess"), LOAD_FAIL("atm.loadFail"), SHUTDOWN("atm.shutdown"), STARTUP("atm.startup"); private final String eventName; private AtmStatusEventEnum(String eventName) { this.eventName = eventName; } public String getEventName() { return eventName; } public static String getNamesAsCsv(){ StringBuilder sb = new StringBuilder(); for (AtmStatusEventEnum e : AtmStatusEventEnum.values()) { sb.append(e.name()); sb.append(","); } return sb.substring(0,sb.length()-2); } }
You can see the basic GUI code below. The GUI first shows the possible events that can be fired. When an event is selected and submitted, current ATM status is displayed and event list is updated.
package net.javafun.example.atmstatusfsm; import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.List; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import org.apache.commons.scxml.model.Transition; /** * Atm Status Change GUI * * @author ozkansari.com * */ public class AtmDisplay extends JFrame implements ActionListener { private static final long serialVersionUID = -5083315372455956151L; private AtmStatusFSM atmStatusFSM; private JButton button; private JLabel state; private JComboBox eventComboBox = new JComboBox(); public static void main(String[] args) { new AtmDisplay(); } public AtmDisplay() { super("ATM Display Demo"); atmStatusFSM = new AtmStatusFSM(); setupUI(); } @SuppressWarnings("deprecation") private void setupUI() { JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); setContentPane(panel); button = makeButton("FIRE_EVENT", AtmStatusEventEnum.getNamesAsCsv(), "Submit" ); panel.add(button, BorderLayout.CENTER); state = new JLabel(atmStatusFSM.getCurrentStateId()); panel.add(state, BorderLayout.SOUTH); initEvents(); panel.add(eventComboBox, BorderLayout.NORTH); pack(); setLocation(200, 200); setResizable(false); setSize(300, 125); show(); setDefaultCloseOperation(EXIT_ON_CLOSE); } @SuppressWarnings("unchecked") private void initEvents() { eventComboBox.removeAllItems(); List transitionList = atmStatusFSM.getCurrentState().getTransitionsList(); for (Transition transition : transitionList) { eventComboBox.addItem(transition.getEvent() ); } } public void actionPerformed(ActionEvent e) { String command = e.getActionCommand(); if(command.equals("FIRE_EVENT")) { checkAndFireEvent(); } } private boolean checkAndFireEvent() { atmStatusFSM.fireEvent(eventComboBox.getSelectedItem().toString()); state.setText(atmStatusFSM.getCurrentStateId()); initEvents(); repaint(); return true; } private JButton makeButton(final String actionCommand, final String toolTipText, final String altText) { JButton button = new JButton(altText); button.setActionCommand(actionCommand); button.setToolTipText(toolTipText); button.addActionListener(this); button.setOpaque(false); return button; } }
The output of our simple program :
The project files (with required libraries) as shown in Eclipse are given in the following image:
For full source code visit https://github.com/ozkansari/atmstatemachine
Reference: Easy Finite State Machine Implementation with Apache Commons SCXML from our JCG partner Ozkan SARI at the Java Fun blog.
Hi Unsal,
Somehow the SCXML file is incorrect in that the xml closing tags are consequently wrong (i.e. the ‘/’ closing character is missing).
Hello, We have fixed the issues with the code snippets. Sorry for the inconvenience. As far as the author is concerned, we have fixed the reference section of the article but in order to properly attribute it and display the correct author at the top section we shall have the original author’s contact information. Since JavaFun is a blog with multiple authors we have kindly asked you to provide us with a list of all available authors so as to include them to our system. You haven’t done so up until now. So please send us (by mail) a list… Read more »
Can we trigger events from the fsm itself. eg: when the machine is in IDLE state initially, i do some processing and then fireEvent Connect and move to LOADING state.
I tried experimenting but if i call fireEvent() from the – public void idle() – method, it doesnt jump to LOADIND state. Though if i call firEvent() from some other class then the fsm jumps to Loading.