EasyFlow

EasyFlow is a simple and lightweight Finite State Machine for Java

EasyFlow 1.3 is out (12 Dec 2013)

  • refactored to use Java enums for states and events

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 event-driven java applications

All this in less then 30kB and no run-time overhead!

Here is a simple example illustrating how a state machine can be definded and implemented with EasyFlow

This is a State diargam fragment describing a simple ATM workflow

State diagram fragment

State diagram fragment

With EasyFlow we can define the above state machine like this

then all what’s left to do is to implement our state handlers like so

and start the flow

Complete ATM example on GitHub.
Get EasyFlow sources from GitHub.

To start using EasyFlow on your project, define Maven dependency like so

Share Button
47 comments on “EasyFlow
  1. Glenn says:

    I call this method with a handler

    public EasyFlow whenEnter(StateHandler onEnter) {
    handlers.setHandler(EventType.STATE_ENTER, null, null, onEnter);
    return (EasyFlow) this;
    }

    but in the handler collections:

    h = handlers.get(new HandlerType(EventType.ANY_EVENT_TRIGGER, null, null));

    Should it be in the first method EventType.ANY_EVENT_TRIGGER in place of EventType.STATE_ENTER ? Because it is not calling my handler.

  2. Terence Ng says:

    Hi, Andrey,

    First of all, good library and thanks.
    I have an question, I actually try to implement some application using your library, I come across an issue.
    When i trigger event with the context too fast, it throws exception.
    Say like, i do something like this:
    I am in state0,
    with event1, we will go from state0 to state1,
    and with event2, we will go from state1 to state2.
    When i do this
    context.trigger(event1);
    context.trigger(event2);
    It will complain exception, saying event2 is not a proper event2 for state0. However, i would expect it to be in state1, when we trigger event2.

    If i do
    context.trigger(event1);
    Thread.currentThread.sleep(5000).
    context.trigger(event2);
    It will then works, u have any idea?

    • Andrey Chausenko says:

      Hi Terence,
      What you are describing is called “race condition”.
      You see, when you call

      It does not change the state straight away. It only schedules a task to change the state. And then this task is executed by another thread. Because actual code that does the switch runs in another thread, you don’t know when exactly this switch will happen. And you cannot trigger next even right after the first. Your code with delay will work, but this is not the right approach either.
      There was a similar question on GitHub. Have a look. https://github.com/Beh01der/EasyFlow/issues/12
      In your case, you need to put second trigger call into status enter handler for state1:

      • Terence Ng says:

        Actually I have a stream of data pumping into my application continuously and upon some condition, the listener to the stream will call context.trigger(event1) and some other condition it calls context.trigger(event2), so it seems to me whenEnter() doesn’t fit this situation. I could have several threads checking for the condition and sending the events to the state machine triggering its transition.
        May be a queue can be created to store the event triggered and processed sequentially by the StateMachine.
        I am new to the library, comment may not be valid, grateful if you can shed some lights on that, thanks a lot.

        • Andrey Chausenko says:

          Terence,
          I don’t really know much about your application but the way you use StateMachine is a bit weird.
          I believe, the point of using FSM is to help you properly handle state of your application and for each state there is a number of allowed events. However, from what you describing it sounds like your application doesn’t have any control over that stream of events and this is the problem.
          You can still use EasyFlow, but you need to decide how to handle invalid events. The easiest way is to ignore them. In this case just replace trigger with safeTrigger and it should work for you. Otherwise, you may queue those event yourself and feed to EasyFlow one by one, but the logic will be more complex.

          • Terence Ng says:

            Hi, Andrey

            Actually I am trying to simulate an Stock Order state with your tool. I will need to monitor the order’s execution and it will resulted in order state changing from new-> partfill-> filled and many other exception states. On the other side, i am monitoring the market data, and which will resulted in my decision to keep my order or not in the market. So, the event is actually resulted asynchronously, however with whenenter() function and ContextHandler(), it seems to me i must synchronously waiting for the signal in call().

            Even for your example about the ATM

            if i change the whenenter(waiting_for_pin) to use simply print some sys out lines, and i create a Java Swing GUI to simulate user inputting the data, so all the logic of handling the GUI is there.
            And I have

            insertCardBtn.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent arg0) {
            System.out.println(“Present Card”);
            context.trigger(cardPresent);

            This to me just give more decoupling than putting the logic in the whenEnter(…)

            With this, if someone is able to click on the screen very fast, or i simulate the clicks with program, race condition happen again.

            So i am just thinking may be i need to send all the events to a queue, and the statemachine need a pooler to get one at a time, and notify when it finished changing its state in the statemachine so that it can take the next event.

          • Andrey Chausenko says:

            Hi Terence,
            Your example with insertCardBtn is absolutely valid.
            I would do it the same way. However, I would keep the button disabled until FMS gets into the correct state and disable it again, once user pressed it.
            Alternatively, you can hide button or remove listener from it once it leaves that state. The point is to prevent user from pressing it when FSM is not ready to handle that event.
            This would eliminate any race condition. You can see similar approach in Android example for ATM. It’s quite similar to Swing.
            With your stock order application, I would probably create an adapter that would convert / filter / delay events from the stream before they get to EasyFlow.

            Regards,
            Andrey

  3. TypeOverride says:

    Sry that i write so much. Today is the first day i try to work with your awesome library and i have
    so much questions and problems.

    When i am at the moment in a state, call it: STATEA
    and i do something like trigger(Event_That_Is_Not_A_Transit_OF_STATEA);

    than i get:
    [pool-2-thread-1] ERROR au.com.ds.ef.call.DefaultErrorHandler – Error
    java.lang.Exception: Execution Error in StateHolder [STATEA] on EventHolder [Event_That_Is_Not_A_Transit_OF_STATEA] with Context [ContextObject]
    at au.com.ds.ef.call.DefaultErrorHandler.call(DefaultErrorHandler.java:19)
    Caused by: au.com.ds.ef.err.ExecutionError: Execution Error in [trigger]
    … 4 more
    Caused by: au.com.ds.ef.err.LogicViolationError: Invalid Event: Event_That_Is_Not_A_Transit_OF_STATEA triggered while in State: STATEA for age: (ContextObject)
    at au.com.ds.ef.EasyFlow$2.run(EasyFlow.java:152)

    The problem appears because i call the event Event_That_Is_Not_A_Transit_OF_STATEA that is not a transition of the STATEA. Because of this i get the error.

    I will explain what i try to do or why i do this (this is now to my application related – The statemachine is running and when i touch an item on the display it triggers the event XY.
    So the user can touch an item, dont matter in which state the machine at the moment is or if the state has a transition with the event. So the user could theoretical trigger the event non-stop! But than the Error appears)

    My main question is now why you want that there are errors thrown when calling an event that is not a transition of the current active state. Shouldnt it be simple avoided?

    I know i can do:
    .whenError(new ExecutionErrorHandler() {
    @Override
    public void call(ExecutionError arg0, Tamagotchi context) {
    //ERROR
    }
    })
    to catch it. But then the statemachine stops!

    • Andrey Chausenko says:

      So, what behaviour do you need here? If a wrong event is triggered would a state machine just ignore it? If so, we could simply add another method, let’s say safeTrigger that would implement this behaviour.
      I glad you like the lib :)
      It’s just a tiny piece of software that I decided to separate from my Android project. It’s good to know that you find it useful.

  4. TypeOverride says:

    how can i see which event has invoke the whenEnter call ?

    .whenEnter(TiredState.TIRED, new ContextHandler() {
    @Override
    public void call(Tamagotchi context) throws Exception {
    //which event has invoke the call?????
    }
    }

    Other question is:
    I see you are using the command-pattern for .whenEnter and so on.
    void method(Callable func);
    method(new ContextHandler() {

    });

    An other soltion could be functions as parameter.
    This is working with:
    import java.lang.reflect.Method;

    I dont know if there are performance problems by using reflect.Method or
    typesafe problems.

    But when not, this could be an improvement in future releases.

    What do you think?

    • Andrey Chausenko says:

      Hi TypeOverride,
      Thanks for using EasyFlow.
      1. In whenEnter handler you cannot see what event was triggered, but you can use whenEvent handler. Just attach it to the event or events you want to monitor.
      2. There will be a performance penalty for sure, but what would be a benefit? Can you give me an example what would client’s code look like?

      • TypeOverride says:

        1)
        i want to make something like:

        |——-|
        |…….| CHECK_AGAIN_A
        |…….|
        |…….v
        |———–
        |
        | STATE_A
        |
        | .whenEnter(context, EventEnum invoker)
        | if(invoker != CHECK_AGAIN_A) {
        | context.playAnimationXY();
        | }
        |
        |
        | if(time > 20) {
        | context.trigger(EVENT_TO_B)
        | } else {
        | context.trigger(CHECK_AGAIN_A);
        | }
        |
        |————
        |…….|
        |…….|
        |…….|
        |…….v
        |————-
        |
        | STATE_B
        |————-

        To use the whenEvent it could be a solution. But which Event i should use when STATE_A is the inital state?

        .whenEvent(?, new ContextHandler() {
        …..
        }

        Second thing is the information from which state he comes would be nice to see in whenEvent

        So i would miss
        A) in whenEnter the knowledge about which event has triggered the state and
        B) in whenEvent the knowledge about from which state he comes. And perhaps to which state he wants to go.

        Wouldnt this bring useful informations to the calls?

        2) the way it is, i think its fine. Performance is more important than using latest Java features for that. Its fine
        in the way it is i think.

        • Andrey Chausenko says:

          Well, your statement B) is not correct as whenEvent handler has signature

          As you can see, additionally to event itself it gets information about initial and destination states.
          However, to make things simple for you, I guess, we can add method getLastEvent() to StatefulContext. Generally speaking, you should use context to carry additional information about your state machine status. That’s what context is for. For instance, in my example that implements ATM, I use context to store account balance and number of unsuccessful authorisation attempts

          One more thing. I am not going to release a new version soon, however I can make the changes you are asking for and commit to GitHub. Can get the sources from there and build jar yourself?

          • TypeOverride says:

            I thought too, about the context and more features that could be get from directly from the context itself. Like GetLastEvent(), GetCurrentState() or GetCurrentListOfPossibleEvents(). I think the context has a lot potential.

            Now ive realized too, that i can implement own things into the context. Thats nice too.

            Sounds all great. I will download it when you have implemented itand compile it myself. I need the safeTrigger so much :)

            Thanks alot!

          • TypeOverride says:

            wouldnt it be more usefull to have a interface like:

            .whenEnter(StateA, new NewStateHandler() {

            @Override
            public void call(EventEnum event, StateEnum from, DataContext context) throws Exception {

            }
            })

            so that when StateA is entered we have the information about:
            event – which event invokes the state change.
            from – from which state he comes.

            Yes the getLastEvent and getLastState is a solution too. But wouldnt be such an interface not more natural?

            Like for

            .whenEvent(EventXY, new NewEventHandler() {

            @Override
            public void call(StateEnum from, StateEnum to, DataContext context) throws Exception {

            }

            from – state from which he comes
            to – state to which he wants

          • TypeOverride says:

            At the moment i do this in my context, to get the needed information. Like you want to implement nativly.

            private EventEnum _lastEvent = null;

            public EventEnum GetLastEvent() {
            return _lastEvent;
            }

            @Override
            public void trigger(EventEnum event) {
            _lastEvent = event;
            super.trigger(event);
            }

            Dont know if you should change the StatefulContext for that. But something like a MoreStatefulContext that inherits from StatefulContext and implements such features could be enough.

  5. TypeOverride says:

    Is it possible to make hierarchical state machines?

    Something like:
    http://www.codeproject.com/KB/cs/statemachinetoolkitpartii/InitialStateUml.gif

    And if yes. Is there an example somewhere?

    • Andrey Chausenko says:

      There’s no specific support for this, but you can simply create 2 or more different EasyFlow instances and trigger events for second one from the first one and vice versa. I don’t have an example for it, but it should be quite easy to create one.

  6. TypeOverride says:

    How would you implement a workaround for whenEnterFrame or whenUpdate ?

    I dont know if such a function should be part of your library, so im looking for a
    way to solve this in a nice way.

    Solution A i thought about is:

    whenEnter make a timer?
    whenExit destroy timer?

    Solution B i thought about is to use a gameloop somehow,
    because my game runs in a gameloop and i prefer to use a delta time.

    void gameloop(float ftime);

    so i can make velocity*ftime for example.

    But how could i solve this?

    • TypeOverride says:

      Or is it possible to make a event autonomous, so when a event realized that a if statement becomes true it will trigger automaticly the state?

      something like .onif(

      • TypeOverride says:

        Another way to solve this is maybe to make a recusive call all the time for a state, like

        eventA
        |—–|
        | |
        | v
        |———-|
        | stateA |
        |———-|

        .whenEnter(stateA) {
        if(!isConditionTrue())
        context.trigger(eventA);
        }
        }

        a recursive call.

        Would this be the best solution?

    • Andrey Chausenko says:

      Sorry, I don’t quite understand the problem here. Could you explain, please?

  7. Jaffa Krishna says:

    Is it possible to specify exit states.

    • Andrey Chausenko says:

      Hi Jaffa,
      Yes, to do that, just use finish method instead of to while defining Flow.

      • Jaffa Krishna says:

        Thanks for the fast reply.

      • Jaffa Krishna says:

        The program doesn’t seem to terminate on entering the exit state.

        I have gone through your atm command line example but there you are calling:

        System.exit(0);

        I am trying to implement a web service where each request will be processed by an fsm in a new thread and the thread has to exit cleany which rules out the possiblity of exit function call.

        Is there a way to terminate the fsm other than using exit function call?

        Thanks

        • Andrey Chausenko says:

          Jaffa,
          In ATM Console example method main looks like this

          What happens here: when you start flow (on line 6) it creates a separate thread to run flow logic (because default executor for EasyFlow is AsyncExecutor which creates a new thread for each EasyFlow instance). But the main thread continues executing at the same time. Therefore, in order to wait for thread that runs EasyFlow, we use Thread.sleep which makes main thread to wait forever.
          Then, when we want to terminate program, we call System.exit(0)

          For your case, Web Server will take care of all thread management, therefore you can just use SyncExecutor which simply uses your current thread to do all the work.

          See example of SyncExecutor usage in EasyFlow test sources: RunSingleTest.testSyncExecutor()

  8. Lorenzo says:

    I’ve found this comment on github:
    fyi: here is an easy-flow re-write based on enums
    https://github.com/barchart/barchart-util/tree/master/flow

    I have tried the barchart version and after a short test, I’m very happy with it. For me it’s really nice to be able to have enums that display debug info with the event and state name, but before making my final choice, could you highlight some advantage of EasyFlow over Barchart. I’m not that experienced on state machine implementation and I’m afraid that I may get some trouble later that I didn’t foresee.

    • Andrey Chausenko says:

      Hi Lorenzo,
      If you are happy with barchart, no questions, go for it.
      The main point of creating EasyFlow was to make code that defines FSM simple and clean. Unfortunately, it’s not possible to use Enums for state definitions and keep code simple at the same time.

      Essentially difference between barchart and EasyFlow is here:

      vs

      I prefer simple code, that’s why EasyFlow doesn’t use Enums.

      Regards,
      Andrey

      • Andrey Chausenko says:

        It is, actually, possible to use enums.
        It took me a while to understand how.
        In the result of this effort – EasyFlow 1.3 has been released.

  9. Daemonis says:

    Why does not it allow circular transition? What if I want to call onEnter each time a paticular event happens?

    • Andrey Chausenko says:

      You can disable validation:

  10. G says:

    It looks very nice i want to use it in a game. I work with 4 other schoolmates to finish this project but we don’t work with Maven. Do you maybe have a libary that works with eclipse without the Maven stuff. Another nice thing would be that you can import the project from git and use it as a android-libary.

    Thanks anyway, nice work ;)

  11. mf says:

    How to start running from a specified state?

    • Andrey Chausenko says:

      Just create a context and set its state to the state you want to start with

      • mf says:

        I have set its state, but he still does not work。
        I guess start method need to add some code(Add the code after else)?
        public void start(final C context) {
        validate();
        this.context = context;

        if (context.getState() == null) {
        setCurrentState(startState, context);
        } else {
        setCurrentState(context.getState(), context);
        }
        }
        but this modification triggers prevState.leave(context),
        Or this modification
        public void start (final C context) {
        start (startState, context);
        }

        public void start (final State state, final C context) {
        validate ();
        this.context = context;
        if (state! = null) {
        setCurrentState (state, context);
        }
        }
        But this state parameter does not verify the legality。

        Please give me some guidance, or I use the wrong?

        • Andrey Chausenko says:

          I see where the confusion is. You probably expect it to trigger onEnter() event for your initial state, but it will not. When you specify initial state, you tell the FSM that it’s ALREADY in this state, so no transition will happen. If you want in this case onEnter() to be triggered, you need to modify EasyFlow code. Something like this

          then just call it like this

Leave a Reply

Your email address will not be published. Required fields are marked *

*


4 − = two

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Keep in touch

Download