Lightweight Java State Machine

Overview

Maven

    <dependency>
        <groupId>com.github.stateless4j</groupId>
        <artifactId>stateless4j</artifactId>
        <version>2.6.0</version>
    </dependency>

Build Status

Introduction

Create state machines and lightweight state machine-based workflows directly in java code.

StateMachineConfig<State, Trigger> phoneCallConfig = new StateMachineConfig<>();

phoneCallConfig.configure(State.OffHook)
        .permit(Trigger.CallDialed, State.Ringing);

phoneCallConfig.configure(State.Ringing)
        .permit(Trigger.HungUp, State.OffHook)
        .permit(Trigger.CallConnected, State.Connected);

// this example uses Java 8 method references
// a Java 7 example is provided in /examples
phoneCallConfig.configure(State.Connected)
        .onEntry(this::startCallTimer)
        .onExit(this::stopCallTimer)
        .permit(Trigger.LeftMessage, State.OffHook)
        .permit(Trigger.HungUp, State.OffHook)
        .permit(Trigger.PlacedOnHold, State.OnHold);

// ...

StateMachine<State, Trigger> phoneCall =
        new StateMachine<>(State.OffHook, phoneCallConfig);

phoneCall.fire(Trigger.CallDialed);
assertEquals(State.Ringing, phoneCall.getState());

stateless4j is a port of stateless for java

Features

Most standard state machine constructs are supported:

  • Generic support for states and triggers of any java type (numbers, strings, enums, etc.)
  • Hierarchical states
  • Entry/exit events for states
  • Guard clauses to support conditional transitions
  • User-defined actions can be executed when transitioning
  • Internal transitions (not calling onExit/onEntry)
  • Introspection

Some useful extensions are also provided:

  • Parameterised triggers
  • Reentrant states

Hierarchical States

In the example below, the OnHold state is a substate of the Connected state. This means that an OnHold call is still connected.

phoneCall.configure(State.OnHold)
    .substateOf(State.Connected)
    .permit(Trigger.TakenOffHold, State.Connected)
    .permit(Trigger.HungUp, State.OffHook)
    .permit(Trigger.PhoneHurledAgainstWall, State.PhoneDestroyed);

In addition to the StateMachine.getState() property, which will report the precise current state, an isInState(State) method is provided. isInState(State) will take substates into account, so that if the example above was in the OnHold state, isInState(State.Connected) would also evaluate to true.

Entry/Exit Events

In the example, the startCallTimer() method will be executed when a call is connected. The stopCallTimer() will be executed when call completes (by either hanging up or hurling the phone against the wall.)

The call can move between the Connected and OnHold states without the startCallTimer() and stopCallTimer() methods being called repeatedly because the OnHold state is a substate of the Connected state.

Entry/Exit event handlers can be supplied with a parameter of type Transition that describes the trigger, source and destination states.

Action on transition

It is possible to execute a user-defined action when doing a transition. For a 'normal' or 're-entrant' transition this action will be called without any parameters. For 'dynamic' transitions (those who compute the target state based on trigger-given parameters) the parameters of the trigger will be given to the action.

This action is only executed if the transition is actually taken; so if the transition is guarded and the guard forbids a transition, then the action is not executed.

If the transition is taken, the action will be executed between the onExit handler of the current state and the onEntry handler of the target state (which might be the same state in case of a re-entrant transition.

License

Apache 2.0 License

Created by @oxo42

Maintained by Chris Narkiewicz @ezaquarii

Comments
  • Actions of initial state are not executed

    Actions of initial state are not executed

    Consider the following test case (when copied into StateMachnineTests.java) :

        @Test
        public void ActionOfInitialStateAreExecuted() {
            StateMachineConfig<State, Trigger> config = new StateMachineConfig<>();
    
            fired = false;
    
            config.configure(State.B)
                    .onEntry(new Action() {
    
                        @Override
                        public void doIt() {
                            setFired();
                        }
                    });
    
            StateMachine<State, Trigger> sm = new StateMachine<>(State.B, config);
    
            assertTrue(fired);
        }
    

    Shouldn't this test pass ?

    question 
    opened by snaewe 6
  • Update Java Minimum Version to JDK8

    Update Java Minimum Version to JDK8

    Stateless4J introduces a number of (Functional) Interfaces that are present in a similair form in the jdk since java 8 (Action1 -> Consumer, Action2 -> BiConsumer, Func -> Supplier, Func2 -> Function, Func3 -> BiFunction, FuncBoolean -> BooleanSupplier).

    What do you think about updating to a more recent java, removing the duplicates and renaming some of the other interfaces to match the JDK Naming (e.g. Func4 -> TriFunction, Action3 -> TriConsumer)? Using the familiar names would make it easier for the reader.

    request for comments 
    opened by martinei 5
  • should be possible to define guard function that takes parameters as with actions.

    should be possible to define guard function that takes parameters as with actions.

    Feels like it should be possible to have paramtric guard functions as guards. Just as with the Dynamic transitions and Actions? Let the guard functions be Func<P1,...,PN,Boolean> ? And the parameter types determined by the TriggerWithParamaters...

    question 
    opened by gurgl 5
  • Please use slf4j instead of java.util.logging

    Please use slf4j instead of java.util.logging

    JUL is but one of many widely used logging framework. Having many of these in the same application is really not nice. slf4j provides a logging API for libraries with several backends, like JUL, log4j, apache, logback etc. and thus allows the users of the library to seamlessly integrate the logging of the library with the rest of their application.

    opened by benzht 5
  • Please add web page link to ParallelStateless4j

    Please add web page link to ParallelStateless4j

    Hi Chris,

    So You've taken over the stateless4j project of Carter, I see.

    I had some contact with Carter in the past, and we spoke about adding parallel states to stateless4j. We concluded that parallel states would be a too heavy alteration to the project, so I created a stateless4j fork, which does include parallel states. It is called ParallelStateless4j.

    Carter agreed to add a link to the ParallelStateless4j project from the stateless4j project, but apparantly he didn't find the time for it (or forgot about it).

    So my question is: would you agree on placing a link to the ParallelStateless4j project, and if yes, could you place that link on the main page?

    The parallelStateless4j project can be found here.

    If you're interested in contributing to ParallelStateless4j... you're of course wellcome.

    Parallel website: https://gitlab.com/erasmusmc-public-health/parallelstateless4j/-/wikis/home

    On any questions, please contact me at [email protected]

    best, Rinke

    opened by RinkeH 4
  • Fixed Issue-52 / Implemented Issue15 and Issue-17

    Fixed Issue-52 / Implemented Issue15 and Issue-17

    Motivation

    There's a bug in StateMachineConfig.disableEntryActionOfInitialState(). In its current form this method enables the entry action instead of disabling it.

    Modifications

    Wrote a (red) unit test to show the wrong behaviour. Replaced 'true' with 'false'. Unit test turned green.

    Result

    StateMachineConfig.disableEntryActionOfInitialState now works correctly as its name says.

    opened by aytchell 4
  • StateMachine Swallowing Exceptions

    StateMachine Swallowing Exceptions

    In StateMachine::publicFire(TTrigger, Object...) there is a catch block that swallows all exceptions. This is pretty dangerous and has caught us out a few times. Should this not be calling the following?

    _unhandledTriggerAction.doIt(getCurrentRepresentation().getUnderlyingState(), trigger);

    question 
    opened by johnhmarks 4
  • Action2 parameters are flipped

    Action2 parameters are flipped

    public <TArg0> StateConfiguration<S, T> onEntryFrom(TriggerWithParameters1<TArg0, S, T> trigger, final Action2<**TArg0, Transition<S, T>**> entryAction, Class<TArg0> classe0) {

    But while firing, the entry action execution flips the parameters-

    void executeEntryActions(Transition<S, T> transition, Object[] entryArgs) {
    ...
    while(var3.hasNext()) {
                Action2<**Transition<S, T>, Object[]**> action = (Action2)var3.next();
                action.doIt(transition, entryArgs);
            }
    
    opened by sudeshbainsla 3
  • Issue-15: Support action on transition

    Issue-15: Support action on transition

    Motivation

    UML state machine describes a transition as:

                ---- event [ guard ] / action --->
    

    Current implementation of stateless4j only supports actions on entry and exit which is not the same.

    Modifications

    This feature was implemented in three steps:

    • Add actions to 'permit' and 'permitIf'
    • Add actions to 'permitReentry' and 'permitReentryIf'
    • Add actions to 'permitDynamic' and 'permitDynamicIf'

    See the commit messages for details

    Result

    One can add an action to a transition which is executed if this transition is taken. In case of a dynamic transition, the action will receive the parameters of trigger.

    opened by aytchell 3
  • Guarded Transition for not changing State

    Guarded Transition for not changing State

    When I configure a state transition with permitIf and the guard function doesn't allow the transition I'm getting following exception. However I except the state unchanged.

            smCfg.configure(State.STATE_0)
                .permitIf(Event.TC_setup, State.STATE_1, new FuncBoolean()
                {
    
                    @Override
                    public boolean call()
                    {
                        boolean result = hdlcIo.setup(); 
                        return result;
                    }
                })
    
    java.lang.IllegalStateException: No valid leaving transitions are permitted from state 'STATE_0' for trigger 'TC_setup'. Consider ignoring the trigger.
        at com.github.oxo42.stateless4j.StateMachine$1.doIt(StateMachine.java:30)
        at com.github.oxo42.stateless4j.StateMachine.publicFire(StateMachine.java:196)
        at com.github.oxo42.stateless4j.StateMachine.fire(StateMachine.java:133)
        at com.ayes.atmgw.service.ph.fde.FdeStateMachine.setup(FdeStateMachine.java:472)
        at com.ayes.atmgw.tests.TestFdeStateMachine.test02_STATE0_TC_Setup_fails(TestFdeStateMachine.java:60)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
        at java.lang.reflect.Method.invoke(Unknown Source)
        at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47)
        at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
        at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44)
        at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
        at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
        at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70)
        at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50)
        at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238)
        at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63)
        at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236)
        at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53)
        at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229)
        at org.junit.runners.ParentRunner.run(ParentRunner.java:309)
        at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:50)
        at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:459)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:675)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:382)
        at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:192)
    
    opened by tuncatunc 3
  • Bump junit from 4.11 to 4.13.1

    Bump junit from 4.11 to 4.13.1

    Bumps junit from 4.11 to 4.13.1.

    Release notes

    Sourced from junit's releases.

    JUnit 4.13.1

    Please refer to the release notes for details.

    JUnit 4.13

    Please refer to the release notes for details.

    JUnit 4.13 RC 2

    Please refer to the release notes for details.

    JUnit 4.13 RC 1

    Please refer to the release notes for details.

    JUnit 4.13 Beta 3

    Please refer to the release notes for details.

    JUnit 4.13 Beta 2

    Please refer to the release notes for details.

    JUnit 4.13 Beta 1

    Please refer to the release notes for details.

    JUnit 4.12

    Please refer to the release notes for details.

    JUnit 4.12 Beta 3

    Please refer to the release notes for details.

    JUnit 4.12 Beta 2

    No release notes provided.

    JUnit 4.12 Beta 1

    No release notes provided.

    Commits
    • 1b683f4 [maven-release-plugin] prepare release r4.13.1
    • ce6ce3a Draft 4.13.1 release notes
    • c29dd82 Change version to 4.13.1-SNAPSHOT
    • 1d17486 Add a link to assertThrows in exception testing
    • 543905d Use separate line for annotation in Javadoc
    • 510e906 Add sub headlines to class Javadoc
    • 610155b Merge pull request from GHSA-269g-pwp5-87pp
    • b6cfd1e Explicitly wrap float parameter for consistency (#1671)
    • a5d205c Fix GitHub link in FAQ (#1672)
    • 3a5c6b4 Deprecated since jdk9 replacing constructor instance of Double and Float (#1660)
    • Additional commits viewable in compare view

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 2
  • Missed action1/actionN for permitInternal.

    Missed action1/actionN for permitInternal.

    Describe the bug It's impossible to pass parameters to permitInternal handler. To Reproduce Create permitInternal handler and fire trigger with parameters.

    Expected behavior As I understood parameters should pass to the handler.

    bug 
    opened by aaenin 0
  • missed Action2/Action3 for enter2/enter3

    missed Action2/Action3 for enter2/enter3

    image

    from this com.github.oxo42.stateless4j.StateConfiguration design, there are many onEntry and onEntryFrom methods for different situation. If passed this userInfo as context by calling fire method in state machine, how to get this parameter context in onXXX method ?

            UserInfo userInfo = new UserInfo(1L);
            //phoneCall.fire(Trigger.CallDialed);
            phoneCall.fire(new TriggerWithParameters1(Trigger.CallDialed, UserInfo.class), userInfo);
    

    Please give me some suggestion, thx.

    opened by pli2014 1
  • External state storage calls mutator when not needed

    External state storage calls mutator when not needed

    I have an issue where the state is maintained in database. Constructor "Construct a state machine with external state storage" triggers database update in construction phase calling doIt for mutator.

    However this creates extra updates every time state machine is created since I have just loaded the state from database, giving it as an ininitalState and doing an update right after this is not necessary. Also I'm bit confused why initialState is given separate and not fetched using given accessor.

    Any ideas how the constructor should really work in this case? I would like to avoid unnecessary database calls but let state machine still do the updates.

    opened by jukkasi 1
  • Stronger trigger-fire typing

    Stronger trigger-fire typing

    Hi, is it possible to have the trigger firing be more strongly typed/parameterised?

    Specifically - to the params provided. It would be nice to force using of a certain number and type of args for a particular trigger. As I understand, currently I can 'fire' with or without any params. I then need to deal with that in my onEntryBlocks

    I had a think about how you might achieve this:

    1. After making a TriggerWithParameters - you call a method on it to generate a concrete instance of the trigger. I.e. you have a trigger factory of sorts that you use to get triggers when firing events. This way, you can only fire an event if you made a trigger the 'right' way.
    2. Use a reflection method similar to Retrofit. Supply an interface of events that is parsed down into TriggerWithParameters objects. This might be a Java 8 only option because to configure the onEntryBlock you might have to use a method refs to identify the event methods.
    opened by samskiter 2
  • Update Introduction & provide more examples

    Update Introduction & provide more examples

    The introduction example no longer works after merging 'external state'.

    Furthermore, I would really like to see examples passing arguments to actions - could be in the intro, but could also be an example FSM in code. Because from the parameter lists and the Java doc it is very difficult to figure out how to do this. See, for instance, a question on StackOverflow. I do hope that there is a simpler way then the one offered in the answer there :-).

    Maybe, my real question is: with the exernal_state_storage merged, where is the callTimer of the Actions implemented and where its state? How can I run multiple instances of this state machine in parallel, each with its own callTimer?

    enhancement 
    opened by benzht 2
Owner
Stateless 4 Java
Stateless implementation for Java
Stateless 4 Java
Java lib for monitoring directories or individual files via java.nio.file.WatchService

ch.vorburger.fswatch Java lib for monitoring directories or individual files based on the java.nio.file.WatchService. Usage Get it from Maven Central

Michael Vorburger ⛑️ 21 Jan 7, 2022
Tencent Kona JDK11 is a no-cost, production-ready distribution of the Open Java Development Kit (OpenJDK), Long-Term Support(LTS) with quarterly updates. Tencent Kona JDK11 is certified as compatible with the Java SE standard.

Tencent Kona JDK11 Tencent Kona JDK11 is a no-cost, production-ready distribution of the Open Java Development Kit (OpenJDK), Long-Term Support(LTS) w

Tencent 268 Dec 16, 2022
This repository contains Java programs to become zero to hero in Java.

This repository contains Java programs to become zero to hero in Java. Data Structure programs topic wise are also present to learn data structure problem solving in Java. Programs related to each and every concep are present from easy to intermidiate level

Sahil Batra 15 Oct 9, 2022
An open-source Java library for Constraint Programming

Documentation, Support and Issues Contributing Download and installation Choco-solver is an open-source Java library for Constraint Programming. Curre

null 607 Jan 3, 2023
Java Constraint Programming solver

https://maven-badges.herokuapp.com/maven-central/org.jacop/jacop/badge.svg [] (https://maven-badges.herokuapp.com/maven-central/org.jacop/jacop/) JaCo

null 202 Dec 30, 2022
Java Constraint Solver to solve vehicle routing, employee rostering, task assignment, conference scheduling and other planning problems.

OptaPlanner www.optaplanner.org Looking for Quickstarts? OptaPlanner’s quickstarts have moved to optaplanner-quickstarts repository. Quick development

KIE (Drools, OptaPlanner and jBPM) 2.8k Jan 2, 2023
Alibaba Java Diagnostic Tool Arthas/Alibaba Java诊断利器Arthas

Arthas Arthas is a Java Diagnostic tool open sourced by Alibaba. Arthas allows developers to troubleshoot production issues for Java applications with

Alibaba 31.5k Jan 4, 2023
Java rate limiting library based on token/leaky-bucket algorithm.

Java rate-limiting library based on token-bucket algorithm. Advantages of Bucket4j Implemented on top of ideas of well known algorithm, which are by d

Vladimir Bukhtoyarov 1.7k Jan 8, 2023
Object-Oriented Java primitives, as an alternative to Google Guava and Apache Commons

Project architect: @victornoel ATTENTION: We're still in a very early alpha version, the API may and will change frequently. Please, use it at your ow

Yegor Bugayenko 691 Dec 27, 2022
Dex : The Data Explorer -- A data visualization tool written in Java/Groovy/JavaFX capable of powerful ETL and publishing web visualizations.

Dex Dex : The data explorer is a powerful tool for data science. It is written in Groovy and Java on top of JavaFX and offers the ability to: Read in

Patrick Martin 1.3k Jan 8, 2023
Google core libraries for Java

Guava: Google Core Libraries for Java Guava is a set of core Java libraries from Google that includes new collection types (such as multimap and multi

Google 46.5k Jan 1, 2023
Java regular expressions made easy.

JavaVerbalExpressions VerbalExpressions is a Java library that helps to construct difficult regular expressions. Getting Started Maven Dependency: <de

null 2.6k Dec 30, 2022
MinIO Client SDK for Java

MinIO Java SDK for Amazon S3 Compatible Cloud Storage MinIO Java SDK is Simple Storage Service (aka S3) client to perform bucket and object operations

High Performance, Kubernetes Native Object Storage 787 Jan 3, 2023
java port of Underscore.js

underscore-java Requirements Java 1.8 and later or Java 11. Installation Include the following in your pom.xml for Maven: <dependencies> <dependency

Valentyn Kolesnikov 411 Dec 6, 2022
(cross-platform) Java Version Manager

jabba Java Version Manager inspired by nvm (Node.js). Written in Go. The goal is to provide unified pain-free experience of installing (and switching

Stanley Shyiko 2.5k Jan 9, 2023
Manage your Java environment

Master your Java Environment with jenv Website : http://www.jenv.be Maintainers : Gildas Cuisinier Future maintainer in discussion: Benjamin Berman As

jEnv 4.6k Dec 30, 2022
The shell for the Java Platform

______ .~ ~. |`````````, .'. ..'''' | | | |'''|''''' .''```. .'' |_________| |

CRaSH Repositories 916 Dec 30, 2022
Hashids algorithm v1.0.0 implementation in Java

Hashids.java A small Java class to generate YouTube-like hashes from one or many numbers. Ported from javascript hashids.js by Ivan Akimov What is it?

CELLA 944 Jan 5, 2023
a pug implementation written in Java (formerly known as jade)

Attention: jade4j is now pug4j In alignment with the javascript template engine we renamed jade4j to pug4j. You will find it under https://github.com/

neuland - Büro für Informatik 700 Oct 16, 2022