How to implement a FSM - Finite State Machine in Java
Asked Answered
G

8

66

I have something to do for work and I need your help. We want to implement a FSM - Finite State Machine, to identify char sequence(like: A, B, C, A, C), and tell if it accepted.

We think to implement three classes: State, Event and Machine. The state class presents a node in the FSM, we thought to implement it with State design pattern, every node will extend from the abstract class state and every class would handle different types of events and indicate transitions to a new state. Is it good idea in your opinion?

Second thing, we don't know how to save all the transitions. Again we thought to implement it with some kind of map, that hold the starting point and gets some kind of vector with the next states, but I'm not sure thats a good idea.

I would be happy to get some ideas of how to implement it or maybe you can give me some starting points.

How should I save the FSM, meaning how should I build the tree at the beginning of the program? I googled it and found a lot of examples but nothing that helps me.

Thanks a lot.

Goodell answered 4/11, 2012 at 17:46 Comment(3)
A nice idea, for sure, but if you're going to identify strings, wouldn't you better use e.g. a regular expression?Lessor
maybe you can try 2-d arrays.Moriahmoriarty
I have a library for that you might want to look at: github.com/mtimmerm/dfalexAbebi
T
52

The heart of a state machine is the transition table, which takes a state and a symbol (what you're calling an event) to a new state. That's just a two-index array of states. For sanity and type safety, declare the states and symbols as enumerations. I always add a "length" member in some way (language-specific) for checking array bounds. When I've hand-coded FSM's, I format the code in row and column format with whitespace fiddling. The other elements of a state machine are the initial state and the set of accepting states. The most direct implementation of the set of accepting states is an array of booleans indexed by the states. In Java, however, enumerations are classes, and you can specify an argument "accepting" in the declaration for each enumerated value and initialize it in the constructor for the enumeration.

For the machine type, you can write it as a generic class. It would take two type arguments, one for the states and one for the symbols, an array argument for the transition table, a single state for the initial. The only other detail (though it's critical) is that you have to call Enum.ordinal() to get an integer suitable for indexing the transition array, since you there's no syntax for directly declaring an array with a enumeration index (though there ought to be).

To preempt one issue, EnumMap won't work for the transition table, because the key required is a pair of enumeration values, not a single one.

enum State {
    Initial( false ),
    Final( true ),
    Error( false );
    static public final Integer length = 1 + Error.ordinal();

    final boolean accepting;

    State( boolean accepting ) {
        this.accepting = accepting;
    }
}

enum Symbol {
    A, B, C;
    static public final Integer length = 1 + C.ordinal();
}

State transition[][] = {
    //  A               B               C
    {
        State.Initial,  State.Final,    State.Error
    }, {
        State.Final,    State.Initial,  State.Error
    }
};
Tarsal answered 5/11, 2012 at 1:6 Comment(10)
That's a really nice idea. I need that you elaborate more please. What do you mean in two-index array of states, and if I have a couple of routes from one state? For the state class, we have an abstract class that each derived class is (start state, regular state and accepted state), that way we have a type for each one. Way do you think we need machine class as a generic class? And one last thing, do you have any link to give me that your method is presented? Thanks a lot for your help.Goodell
A two-index array is a two-dimensional array. See example. Start states and acceptance states are regular states. You don't need or even want separate classes for these. Acceptance is a boolean predicate on states. The initial state is a property of the state machine, not of the state itself. The machine itself doesn't need to be generic, though I find it good practice to make it so. In particular, it allows you to unit-test the state machine code independent of the specific state machine you want to use with it, which would get its own set of tests.Tarsal
Thanks for your help. What I've done till now is to create an abstract class for state, the derived classes is from different types(start, standard, end and error). Each state has a hashTable<symbol, state> that holds his children, or error state in case of null. What I want to do now is to print different message for different sequence. Do you have any suggestions how to achieve this? Thanks.Goodell
If you want to have messages that are specific to sequences, then you need a state that represents that sequence. When you enter the state, you can trigger your action. But this concern is how to design the machine rather than to implement it, which was your original question.Tarsal
I have one last question for you. This implementation, is from some kind of interview. They told me that I have an abstract class for State and the derived classes should implement the logic of the FSM. I think that each derived class should be from different type, like I said, e.g start, standard, end and error. Do you think it is the right choice, or you can think of an other idea? Thanks.Goodell
I think putting state transition information into a scattered collection of state machines is (usually) really stupid. It demonstrates to me that the people asking the question have never needed to implement a serious and reliable state machine. Particularly, just tossing everything else into some undefined-behavior bin is analytically lazy and usually creates more problems in the long run.Tarsal
You don't need to declare static length in Java this way. Use State.values().length or Symbol.values().length instead.Gerhan
@Gerhan Caching the length (in some way) improves efficiency as the Enum.values() method re-creates the values array every time it's calledFitted
@NathanAdams Even when efficiency matters, static public final int length = State.values().length is extremely efficient since it is called just once. See also question 'enum values length vs private field'Gerhan
Exactly, like I said caching the length improves efficiencyFitted
K
12

EasyFSM is a dynamic Java Library which can be used to implement an FSM.

You can find documentation for the same at : Finite State Machine in Java

Also, you can download the library at : Java FSM Library : DynamicEasyFSM

Knotty answered 8/4, 2014 at 8:19 Comment(0)
K
10

You can implement Finite State Machine in two different ways.

Option 1:

Finite State machine with a pre-defined workflow : Recommended if you know all states in advance and state machine is almost fixed without any changes in future

  1. Identify all possible states in your application

  2. Identify all the events in your application

  3. Identify all the conditions in your application, which may lead state transition

  4. Occurrence of an event may cause transitions of state

  5. Build a finite state machine by deciding a workflow of states & transitions.

    e.g If an event 1 occurs at State 1, the state will be updated and machine state may still be in state 1.

    If an event 2 occurs at State 1, on some condition evaluation, the system will move from State 1 to State 2

This design is based on State and Context patterns.

Have a look at Finite State Machine prototype classes.

Option 2:

Behavioural trees: Recommended if there are frequent changes to state machine workflow. You can dynamically add new behaviour without breaking the tree.

enter image description here

The base Task class provides a interface for all these tasks, the leaf tasks are the ones just mentioned, and the parent tasks are the interior nodes that decide which task to execute next.

The Tasks have only the logic they need to actually do what is required of them, all the decision logic of whether a task has started or not, if it needs to update, if it has finished with success, etc. is grouped in the TaskController class, and added by composition.

The decorators are tasks that “decorate” another class by wrapping over it and giving it additional logic.

Finally, the Blackboard class is a class owned by the parent AI that every task has a reference to. It works as a knowledge database for all the leaf tasks

Have a look at this article by Jaime Barrachina Verdia for more details

Kuykendall answered 1/4, 2016 at 12:0 Comment(1)
Great answer! The link for the finite state machine might be replaced to: github.com/eclipse-ee4j/orb-gmbal-pfl/tree/master/pfl-basic/src/… These are the same classes however instead of being part of the proprietary Oracle JDK sources (see also confidentially header in sources) they are part of Eclipse and published under a 3 clause BSD license as part of the Java EE donation by Oracle to the Eclipse foundation.Sybille
F
8

Hmm, I would suggest that you use Flyweight to implement the states. Purpose: Avoid the memory overhead of a large number of small objects. State machines can get very, very big.

http://en.wikipedia.org/wiki/Flyweight_pattern

I'm not sure that I see the need to use design pattern State to implement the nodes. The nodes in a state machine are stateless. They just match the current input symbol to the available transitions from the current state. That is, unless I have entirely forgotten how they work (which is a definite possiblilty).

If I were coding it, I would do something like this:

interface FsmNode {
  public boolean canConsume(Symbol sym);
  public FsmNode consume(Symbol sym);
  // Other methods here to identify the state we are in
}

  List<Symbol> input = getSymbols();
  FsmNode current = getStartState();
  for (final Symbol sym : input) {
    if (!current.canConsume(sym)) {
      throw new RuntimeException("FSM node " + current + " can't consume symbol " + sym);
    }
    current = current.consume(sym);
  }
  System.out.println("FSM consumed all input, end state is " + current);

What would Flyweight do in this case? Well, underneath the FsmNode there would probably be something like this:

Map<Integer, Map<Symbol, Integer>> fsm; // A state is an Integer, the transitions are from symbol to state number
FsmState makeState(int stateNum) {
  return new FsmState() {
    public FsmState consume(final Symbol sym) {
      final Map<Symbol, Integer> transitions = fsm.get(stateNum);
      if (transisions == null) {
        throw new RuntimeException("Illegal state number " + stateNum);
      }
      final Integer nextState = transitions.get(sym);  // May be null if no transition
      return nextState;
    }
    public boolean canConsume(final Symbol sym) {
      return consume(sym) != null;
    }
  }
}

This creates the State objects on a need-to-use basis, It allows you to use a much more efficient underlying mechanism to store the actual state machine. The one I use here (Map(Integer, Map(Symbol, Integer))) is not particulary efficient.

Note that the Wikipedia page focuses on the cases where many somewhat similar objects share the similar data, as is the case in the String implementation in Java. In my opinion, Flyweight is a tad more general, and covers any on-demand creation of objects with a short life span (use more CPU to save on a more efficient underlying data structure).

Frost answered 5/11, 2012 at 11:14 Comment(7)
Thanks for your help. What I've done till now is to create an abstract class for state, the derived classes is from different types(start, standard, end and error). Each state has a hashTable<symbol, state> that holds his children, or error state in case of null. What I want to do now is to print different message for different sequence. Do you have any suggestions how to achieve this? Thanks.Goodell
Sure. You can do this in several ways: If the message should depend on the path taken to the end symbol you either need to let the nodes print parts of the message in their consume method, or record the path in some list that is maintained while consuming the nodes. If the message depends on which end node is reached as the final node, just let that node print it as part of its consume method. Did that make sense?Frost
This implementation, is from some kind of interview. They told me that I have an abstract class for State and the derived classes should implement the logic of the FSM. I think that each derived class should be from different type, like I said, e.g start, standard, end and error. Do you think it is the right choice, or you can think of an other idea? Thanks.Goodell
I'm not sure that you need distinct classes for that. You could handle it in a lot of other ways. Basically, if you ever find yourself checking the exact type of some reference you are probably doing something in the calling code that should be handled by the object (see Liskov Substitution Principle). If you really really need it (say, to check if you are in an end state) implement a method on the common interface instead (isEndState()). In my code, I might have an Error class that could be returned instead of a null. That implementation would basically fail whenever any method was invoked.Frost
So in that case, how do you think that I should use the abstract class? I'm am really don't know how to continue with this. I saw so much examples that make me confuse.Goodell
My example is not very helpful as a start, since it does not use any classes as such. It is made to illustrate use of flyweight pattern. The abstract base class would look much like the stuff in my return new State() {} code above, but would also have an abstract method something like boolean isEndState().Frost
Here you are using maps. We can do the same thing using 2D matrix. Since its an FSM, it has finite number of states (Say S) and symbols (Say M). So we can build int transition[S][M] matrix where transition[i][j] = k means -> we have applied 'j' th event on 'i' th state to reach 'k' th state.Sorbian
P
7

Consider the easy, lightweight Java library EasyFlow. From their docs:

With EasyFlow you can:

  • implement complex logic but keep your code simple and clean
  • handle asynchronous calls with ease and elegance
  • avoid concurrency by using event-driven programming approach
  • avoid StackOverflow error by avoiding recursion
  • simplify design, programming and testing of complex java applications
President answered 25/4, 2016 at 17:36 Comment(0)
P
6

I design & implemented a simple finite state machine example with java.

IFiniteStateMachine: The public interface to manage the finite state machine
such as add new states to the finite state machine or transit to next states by
specific actions.

interface IFiniteStateMachine {
    void setStartState(IState startState);

    void setEndState(IState endState);

    void addState(IState startState, IState newState, Action action);

    void removeState(String targetStateDesc);

    IState getCurrentState();

    IState getStartState();

    IState getEndState();

    void transit(Action action);
}

IState: The public interface to get state related info
such as state name and mappings to connected states.

interface IState {
    // Returns the mapping for which one action will lead to another state
    Map<String, IState> getAdjacentStates();

    String getStateDesc();

    void addTransit(Action action, IState nextState);

    void removeTransit(String targetStateDesc);
}

Action: the class which will cause the transition of states.

public class Action {
    private String mActionName;

    public Action(String actionName) {
        mActionName = actionName;
    }

    String getActionName() {
        return mActionName;
    }

    @Override
    public String toString() {
        return mActionName;
    }

}

StateImpl: the implementation of IState. I applied data structure such as HashMap to keep Action-State mappings.

public class StateImpl implements IState {
    private HashMap<String, IState> mMapping = new HashMap<>();
    private String mStateName;

    public StateImpl(String stateName) {
        mStateName = stateName;
    }

    @Override
    public Map<String, IState> getAdjacentStates() {
        return mMapping;
    }

    @Override
    public String getStateDesc() {
        return mStateName;
    }

    @Override
    public void addTransit(Action action, IState state) {
        mMapping.put(action.toString(), state);
    }

    @Override
    public void removeTransit(String targetStateDesc) {
        // get action which directs to target state
        String targetAction = null;
        for (Map.Entry<String, IState> entry : mMapping.entrySet()) {
            IState state = entry.getValue();
            if (state.getStateDesc().equals(targetStateDesc)) {
                targetAction = entry.getKey();
            }
        }
        mMapping.remove(targetAction);
    }

}

FiniteStateMachineImpl: Implementation of IFiniteStateMachine. I use ArrayList to keep all the states.

public class FiniteStateMachineImpl implements IFiniteStateMachine {
    private IState mStartState;
    private IState mEndState;
    private IState mCurrentState;
    private ArrayList<IState> mAllStates = new ArrayList<>();
    private HashMap<String, ArrayList<IState>> mMapForAllStates = new HashMap<>();

    public FiniteStateMachineImpl(){}
    @Override
    public void setStartState(IState startState) {
        mStartState = startState;
        mCurrentState = startState;
        mAllStates.add(startState);
        // todo: might have some value
        mMapForAllStates.put(startState.getStateDesc(), new ArrayList<IState>());
    }

    @Override
    public void setEndState(IState endState) {
        mEndState = endState;
        mAllStates.add(endState);
        mMapForAllStates.put(endState.getStateDesc(), new ArrayList<IState>());
    }

    @Override
    public void addState(IState startState, IState newState, Action action) {
        // validate startState, newState and action

        // update mapping in finite state machine
        mAllStates.add(newState);
        final String startStateDesc = startState.getStateDesc();
        final String newStateDesc = newState.getStateDesc();
        mMapForAllStates.put(newStateDesc, new ArrayList<IState>());
        ArrayList<IState> adjacentStateList = null;
        if (mMapForAllStates.containsKey(startStateDesc)) {
            adjacentStateList = mMapForAllStates.get(startStateDesc);
            adjacentStateList.add(newState);
        } else {
            mAllStates.add(startState);
            adjacentStateList = new ArrayList<>();
            adjacentStateList.add(newState);
        }
        mMapForAllStates.put(startStateDesc, adjacentStateList);

        // update mapping in startState
        for (IState state : mAllStates) {
            boolean isStartState = state.getStateDesc().equals(startState.getStateDesc());
            if (isStartState) {
                startState.addTransit(action, newState);
            }
        }
    }

    @Override
    public void removeState(String targetStateDesc) {
        // validate state
        if (!mMapForAllStates.containsKey(targetStateDesc)) {
            throw new RuntimeException("Don't have state: " + targetStateDesc);
        } else {
            // remove from mapping
            mMapForAllStates.remove(targetStateDesc);
        }

        // update all state
        IState targetState = null;
        for (IState state : mAllStates) {
            if (state.getStateDesc().equals(targetStateDesc)) {
                targetState = state;
            } else {
                state.removeTransit(targetStateDesc);
            }
        }

        mAllStates.remove(targetState);

    }

    @Override
    public IState getCurrentState() {
        return mCurrentState;
    }

    @Override
    public void transit(Action action) {
        if (mCurrentState == null) {
            throw new RuntimeException("Please setup start state");
        }
        Map<String, IState> localMapping = mCurrentState.getAdjacentStates();
        if (localMapping.containsKey(action.toString())) {
            mCurrentState = localMapping.get(action.toString());
        } else {
            throw new RuntimeException("No action start from current state");
        }
    }

    @Override
    public IState getStartState() {
        return mStartState;
    }

    @Override
    public IState getEndState() {
        return mEndState;
    }
}

example:

public class example {

    public static void main(String[] args) {
        System.out.println("Finite state machine!!!");
        IState startState = new StateImpl("start");
        IState endState = new StateImpl("end");
        IFiniteStateMachine fsm = new FiniteStateMachineImpl();
        fsm.setStartState(startState);
        fsm.setEndState(endState);
        IState middle1 = new StateImpl("middle1");
        middle1.addTransit(new Action("path1"), endState);
        fsm.addState(startState, middle1, new Action("path1"));
        System.out.println(fsm.getCurrentState().getStateDesc());
        fsm.transit(new Action(("path1")));
        System.out.println(fsm.getCurrentState().getStateDesc());
        fsm.addState(middle1, endState, new Action("path1-end"));
        fsm.transit(new Action(("path1-end")));
        System.out.println(fsm.getCurrentState().getStateDesc());
        fsm.addState(endState, middle1, new Action("path1-end"));
    }

}

Full example on Github

Pantaloons answered 3/8, 2018 at 17:15 Comment(1)
Plz stop adding "I" to an interfaceEpsom
H
0

Well this is an old question but while nobody mentioned here, I will advice to check two existing frameworks before you implement you own State Machines.

One is Spring State Machine most of you are familiar with Spring framework, which allow us to use several features of Spring like dependency injection and everything else that Spring can offer.

It is really great for modelling the lifecycle of an Apparat, with states like INITIALIZING, STARTED, ERROR, RECOVERING, SHUTTINGDOWN, etc.. but I see lots of people are trying to model a Shopping Chart, a Reservation System with it, the memory footprint a Spring State Machine is relatively big to model millions of Shopping Charts or Reservations.

One other disadvantage, Spring State Machine, while has a capability to persist itself for long running processes but it does not have any mechanism to adapt to changes in these processes, if you persist a process and you have to recover it lets say 10 days later with a change occurred in your business process because of new software release / requirement, you have no built in means to deal with it.

I have several blogs, blog1 blog2, demonstrating how you can program Spring State Machine, specially model driven way, if you want to check it.

Mainly because the disadvantages I mentioned, I advice you to look another framework first, Akka FSM (Finite State Machine) which is more fitting with its low memory footprint to have millions and millions of instances and has a capability to adapt changing long running processes.

Now you can develop with Akka framework with Java but believe me because of some missing language elements, you don't want to read the produced code, Scala is a much more fitting language to develop with Akka. Now I hear you saying Scala is too complex, I can't convince my project leads to develop with Scala, to convince you all this is an option, I developed a Proof of Concept application using a Java/Scala hybrid with all Scala Akka Finite State Machine code generated from an UML model, if you want to check it out here the links to the blogs, blog3 blog4.

I hope this information would help you.

Hangup answered 5/6, 2022 at 10:3 Comment(0)
N
-2

Here is a SUPER SIMPLE implementation/example of a FSM using just "if-else"s which avoids all of the above subclassing answers (taken from Using Finite State Machines for Pattern Matching in Java, where he is looking for a string which ends with "@" followed by numbers followed by "#"--see state graph here):

public static void main(String[] args) {
    String s = "A1@312#";
    String digits = "0123456789";
    int state = 0;
    for (int ind = 0; ind < s.length(); ind++) {
        if (state == 0) {
            if (s.charAt(ind) == '@')
                state = 1;
        } else {
            boolean isNumber = digits.indexOf(s.charAt(ind)) != -1;
            if (state == 1) {
                if (isNumber)
                    state = 2;
                else if (s.charAt(ind) == '@')
                    state = 1;
                else
                    state = 0;
            } else if (state == 2) {
                if (s.charAt(ind) == '#') {
                    state = 3;
                } else if (isNumber) {
                    state = 2;
                } else if (s.charAt(ind) == '@')
                    state = 1;
                else
                    state = 0;
            } else if (state == 3) {
                if (s.charAt(ind) == '@')
                    state = 1;
                else
                    state = 0;
            }
        }
    } //end for loop

    if (state == 3)
        System.out.println("It matches");
    else
        System.out.println("It does not match");
}

P.S: Does not answer your question directly, but shows you how to implement a FSM very easily in Java.

Naturally answered 15/6, 2020 at 20:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.