.
8 |
9 | ### Published artifacts
10 |
11 | The artifacts of this project are intended to be received via [Jitpack.io](https://jitpack.io/)
12 |
13 | Gradle dependency example:
14 |
15 | compile 'com.github.artcom:hsm-java:0.0.3'
16 |
17 |
18 | ### Publish to Maven Local
19 |
20 | In order to publish to the local Maven repository please use this command:
21 |
22 | ./gradlew -Pgroup=com.github.artcom publishToMavenLocal
23 |
24 | It is necessary to provide the same groupId as used by jitpack in order to be able
25 | to force the usage of the version in the local maven repository.
26 |
27 | ### License
28 |
29 | Copyright © 2015 [Art+Com AG](http://www.artcom.de/).
30 | Distributed under the MIT License.
31 | Please see [License File](LICENSE) for more information.
32 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | wrapper {
2 | gradleVersion = '6.5.1'
3 | }
4 |
5 | allprojects {
6 | repositories {
7 | mavenCentral()
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | projectVersion=0.2.3
2 | projectArtifactId=hsm-java
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/artcom/hsm-java/5aced1c85c749e691bfff1ce5e7bc56f9fd1fa96/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Jul 09 20:07:52 CEST 2020
2 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5.1-all.zip
3 | distributionBase=GRADLE_USER_HOME
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto init
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto init
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :init
68 | @rem Get command-line arguments, handling Windows variants
69 |
70 | if not "%OS%" == "Windows_NT" goto win9xME_args
71 |
72 | :win9xME_args
73 | @rem Slurp the command line arguments.
74 | set CMD_LINE_ARGS=
75 | set _SKIP=2
76 |
77 | :win9xME_args_slurp
78 | if "x%~1" == "x" goto execute
79 |
80 | set CMD_LINE_ARGS=%*
81 |
82 | :execute
83 | @rem Setup the command line
84 |
85 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
86 |
87 |
88 | @rem Execute Gradle
89 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
90 |
91 | :end
92 | @rem End local scope for the variables with windows NT shell
93 | if "%ERRORLEVEL%"=="0" goto mainEnd
94 |
95 | :fail
96 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
97 | rem the _cmd.exe /c_ return code!
98 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
99 | exit /b 1
100 |
101 | :mainEnd
102 | if "%OS%"=="Windows_NT" endlocal
103 |
104 | :omega
105 |
--------------------------------------------------------------------------------
/hsm/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 | apply plugin: 'pmd'
3 | apply plugin: 'maven-publish'
4 |
5 | pmd {
6 | toolVersion = '6.0.0'
7 | ignoreFailures = true
8 | ruleSetFiles = files("${rootDir}/pmd-rules.xml")
9 | }
10 |
11 | sourceCompatibility = 1.7
12 | targetCompatibility = 1.7
13 |
14 | if (project.version == 'unspecified' && project.hasProperty('projectVersion')) {
15 | project.version = project.projectVersion
16 | }
17 |
18 | def publishArtifactId = project.name
19 | if (project.hasProperty("projectArtifactId")) {
20 | publishArtifactId = projectArtifactId
21 | }
22 |
23 | publishing {
24 | publications {
25 | maven(MavenPublication) {
26 | artifactId publishArtifactId
27 | from components.java
28 | }
29 | }
30 | }
31 |
32 | dependencies {
33 | compile 'org.slf4j:slf4j-api:1.7.3'
34 | /* compile 'com.google.guava:guava:17.0' */
35 | compile 'com.google.guava:guava-jdk5:13.0'
36 | }
37 |
38 | task reportDate doLast {
39 | println "execution at: " + new Date()
40 | println "group: " + project.group
41 | println "name: " + project.name
42 | println "version: " + project.version
43 | }
44 |
45 | allprojects {
46 | afterEvaluate { project ->
47 | project.tasks.publishToMavenLocal.dependsOn project.tasks.reportDate
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/hsm/src/main/java/de/artcom/hsm/Action.java:
--------------------------------------------------------------------------------
1 | package de.artcom.hsm;
2 |
3 | import java.util.Map;
4 |
5 | public abstract class Action {
6 |
7 | protected State mPreviousState;
8 | protected State mNextState;
9 | protected Map mPayload;
10 |
11 | public abstract void run();
12 |
13 | void setPreviousState(State state) {
14 | mPreviousState = state;
15 | }
16 |
17 | void setNextState(State state) {
18 | mNextState = state;
19 | }
20 |
21 | void setPayload(Map payload) {
22 | mPayload = payload;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/hsm/src/main/java/de/artcom/hsm/Event.java:
--------------------------------------------------------------------------------
1 | package de.artcom.hsm;
2 |
3 | import java.util.Map;
4 |
5 | class Event {
6 |
7 | private final Map mPayload;
8 |
9 | private final String mName;
10 |
11 | public Event(String name, Map payload) {
12 | mName = name;
13 | mPayload = payload;
14 | }
15 |
16 | public Map getPayload() {
17 | return mPayload;
18 | }
19 |
20 | public String getName() {
21 | return mName;
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/hsm/src/main/java/de/artcom/hsm/EventHandler.java:
--------------------------------------------------------------------------------
1 | package de.artcom.hsm;
2 |
3 | import java.util.Map;
4 |
5 | public interface EventHandler {
6 |
7 | public void handleEvent(String event);
8 | public void handleEvent(String event, Map payload);
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/hsm/src/main/java/de/artcom/hsm/Guard.java:
--------------------------------------------------------------------------------
1 | package de.artcom.hsm;
2 |
3 | import java.util.Map;
4 |
5 | public interface Guard {
6 |
7 | public boolean evaluate(Map payload);
8 | }
9 |
--------------------------------------------------------------------------------
/hsm/src/main/java/de/artcom/hsm/Handler.java:
--------------------------------------------------------------------------------
1 | package de.artcom.hsm;
2 |
3 | class Handler {
4 |
5 | private final State mTargetState;
6 | private final TransitionKind mKind;
7 | private Guard mGuard;
8 | private Action mAction;
9 |
10 | public Handler(State targetState, TransitionKind kind, Action action, Guard guard) {
11 | mTargetState = targetState;
12 | mKind = kind;
13 | mAction = action;
14 | mGuard = guard;
15 | }
16 |
17 | public Handler(State targetState, TransitionKind kind, Guard guard) {
18 | mTargetState = targetState;
19 | mKind = kind;
20 | mGuard = guard;
21 | }
22 |
23 | public Handler(State targetState, TransitionKind kind, Action action) {
24 | mTargetState = targetState;
25 | mKind = kind;
26 | mAction = action;
27 | }
28 |
29 | public Handler(State targetState, TransitionKind kind) {
30 | mTargetState = targetState;
31 | mKind = kind;
32 | }
33 |
34 | public boolean evaluate(Event event) {
35 | if (mGuard != null) {
36 | return mGuard.evaluate(event.getPayload());
37 | }
38 | return true;
39 | }
40 |
41 | public State getTargetState() {
42 | return mTargetState;
43 | }
44 |
45 | public Action getAction() {
46 | return mAction;
47 | }
48 |
49 | public TransitionKind getKind() {
50 | return mKind;
51 | }
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/hsm/src/main/java/de/artcom/hsm/Parallel.java:
--------------------------------------------------------------------------------
1 | package de.artcom.hsm;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Arrays;
5 | import java.util.Collection;
6 | import java.util.List;
7 | import java.util.Map;
8 |
9 | public class Parallel extends State {
10 |
11 | private List mStateMachineList;
12 |
13 | public Parallel(String id, StateMachine... stateMachines) {
14 | super(id);
15 | setStateMachineList(Arrays.asList(stateMachines));
16 | }
17 |
18 | public void setStateMachineList(List stateMachineList) {
19 | mStateMachineList = stateMachineList;
20 | for (StateMachine stateMachine : mStateMachineList) {
21 | stateMachine.setContainer(this);
22 | }
23 | }
24 |
25 | @Override
26 | protected Parallel getThis() {
27 | return this;
28 | }
29 |
30 | @Override
31 | void enter(State prev, State next, Map payload) {
32 | super.enter(prev, next, payload);
33 | for (StateMachine stateMachine : mStateMachineList) {
34 | stateMachine.enterState(prev, next, payload);
35 | }
36 | }
37 |
38 | @Override
39 | void exit(State prev, State next, Map payload) {
40 | super.exit(prev, next, payload);
41 | for (StateMachine stateMachine : mStateMachineList) {
42 | stateMachine.teardown(payload);
43 | }
44 | }
45 |
46 | @Override
47 | boolean handleWithOverride(Event event) {
48 | boolean isHandled = false;
49 | for (StateMachine stateMachine : mStateMachineList) {
50 | if (stateMachine.handleWithOverride(event)) {
51 | isHandled = true;
52 | }
53 | }
54 | if (!isHandled) {
55 | return super.handleWithOverride(event);
56 | }
57 | return true;
58 | }
59 |
60 | @Override
61 | public String toString() {
62 | StringBuilder sb = new StringBuilder();
63 | sb.append(getId());
64 | sb.append("/(");
65 | for (StateMachine stateMachine : mStateMachineList) {
66 | sb.append(stateMachine.toString());
67 | sb.append('|');
68 | }
69 | sb.deleteCharAt(sb.length() - 1);
70 | sb.append(')');
71 | return sb.toString();
72 | }
73 |
74 | @Override
75 | void addParent(StateMachine stateMachine) {
76 | for (StateMachine machine : mStateMachineList) {
77 | machine.addParent(stateMachine);
78 | }
79 | }
80 |
81 | @Override
82 | Collection extends State> getDescendantStates() {
83 | List descendantStates = new ArrayList();
84 | for (StateMachine stateMachine : mStateMachineList) {
85 | descendantStates.addAll(stateMachine.getDescendantStates());
86 | }
87 | return descendantStates;
88 | }
89 |
90 | public List getAllActiveStates() {
91 | List stateList = new ArrayList();
92 | for(StateMachine stateMachine : mStateMachineList) {
93 | stateList.addAll(stateMachine.getAllActiveStates());
94 | }
95 | return stateList;
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/hsm/src/main/java/de/artcom/hsm/State.java:
--------------------------------------------------------------------------------
1 | package de.artcom.hsm;
2 |
3 | import com.google.common.collect.LinkedListMultimap;
4 |
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 |
8 | import java.util.ArrayList;
9 | import java.util.Collection;
10 | import java.util.HashMap;
11 | import java.util.List;
12 | import java.util.Map;
13 |
14 | public class State> {
15 |
16 | final static Logger LOGGER = LoggerFactory.getLogger(State.class);
17 |
18 | private final String mId;
19 | private Action mOnEnterAction;
20 | private Action mOnExitAction;
21 | private final LinkedListMultimap mHandlers;
22 | protected StateMachine mOwner;
23 |
24 | protected T getThis() {
25 | return (T) this;
26 | }
27 |
28 | public State(String id) {
29 | mHandlers = LinkedListMultimap.create();
30 | mId = id;
31 | }
32 |
33 | public T onEnter(Action onEnterAction) {
34 | mOnEnterAction = onEnterAction;
35 | return getThis();
36 | }
37 |
38 | public T onExit(Action onExitAction) {
39 | mOnExitAction = onExitAction;
40 | return getThis();
41 | }
42 |
43 | public T addHandler(String eventName, State target, TransitionKind kind, Guard guard) {
44 | mHandlers.put(eventName, new Handler(target, kind, guard));
45 | return getThis();
46 | }
47 |
48 | public T addHandler(String eventName, State target, TransitionKind kind, Action action) {
49 | mHandlers.put(eventName, new Handler(target, kind, action));
50 | return getThis();
51 | }
52 |
53 | public T addHandler(String eventName, State target, TransitionKind kind, Action action, Guard guard) {
54 | mHandlers.put(eventName, new Handler(target, kind, action, guard));
55 | return getThis();
56 | }
57 |
58 | public T addHandler(String eventName, State target, TransitionKind kind) {
59 | mHandlers.put(eventName, new Handler(target, kind));
60 | return getThis();
61 | }
62 |
63 | void setOwner(StateMachine ownerMachine) {
64 | mOwner = ownerMachine;
65 | }
66 |
67 | StateMachine getOwner() {
68 | return mOwner;
69 | }
70 |
71 | public String getId() {
72 | return mId;
73 | }
74 |
75 | void enter(State prev, State next, Map payload) {
76 | LOGGER.debug("[" + mOwner.getName() + "] " + getId() + " - enter");
77 | if (mOnEnterAction != null) {
78 | mOnEnterAction.setPreviousState(prev);
79 | mOnEnterAction.setNextState(next);
80 | mOnEnterAction.setPayload(payload);
81 | mOnEnterAction.run();
82 | }
83 | }
84 |
85 | void exit(State prev, State next, Map payload) {
86 | LOGGER.debug("[" + mOwner.getName() + "] " + getId() + " - exit");
87 | if (mOnExitAction != null) {
88 | mOnExitAction.setPreviousState(prev);
89 | mOnExitAction.setNextState(next);
90 | mOnExitAction.setPayload(payload);
91 | mOnExitAction.run();
92 | }
93 | }
94 |
95 | Handler findHandler(Event event) {
96 | for (Handler handler : mHandlers.get(event.getName())) {
97 | if (handler.evaluate(event)) {
98 | return handler;
99 | }
100 | }
101 | return null;
102 | }
103 |
104 | boolean handleWithOverride(Event event) {
105 | Handler handler = findHandler(event);
106 | if (handler != null) {
107 | LOGGER.debug("[" + mOwner.getName() + "] " + mId + " - handle Event: " + event.getName());
108 | mOwner.executeHandler(handler, event);
109 | return true;
110 | }
111 | return false;
112 | }
113 |
114 | @Override
115 | public String toString() {
116 | return mId;
117 | }
118 |
119 | void addParent(StateMachine stateMachine) {
120 | // do nothing
121 | }
122 |
123 | Collection extends State> getDescendantStates() {
124 | return new ArrayList();
125 | }
126 |
127 | public EventHandler getEventHandler() {
128 | return mOwner.getPath().get(0);
129 | }
130 |
131 | public List getAllActiveStates() {
132 | return new ArrayList();
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/hsm/src/main/java/de/artcom/hsm/StateMachine.java:
--------------------------------------------------------------------------------
1 | package de.artcom.hsm;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 |
6 | import java.util.ArrayList;
7 | import java.util.Arrays;
8 | import java.util.HashMap;
9 | import java.util.HashSet;
10 | import java.util.List;
11 | import java.util.Map;
12 | import java.util.Queue;
13 | import java.util.Set;
14 | import java.util.concurrent.ConcurrentLinkedQueue;
15 |
16 | public class StateMachine implements EventHandler {
17 |
18 | final static Logger LOGGER = LoggerFactory.getLogger(StateMachine.class);
19 |
20 | private final List mStateList = new ArrayList();
21 | private final List mDescendantStateList = new ArrayList();
22 | private String mName;
23 | private State mInitialState;
24 | private State mCurrentState;
25 | private final Queue mEventQueue = new ConcurrentLinkedQueue();
26 | private boolean mEventQueueInProgress = false;
27 | private final List mPath = new ArrayList();
28 | private State mContainer;
29 |
30 | public StateMachine(String name, State initialState, State... states) {
31 | this(initialState, states);
32 | mName = name;
33 | }
34 |
35 | public StateMachine(State initialState, State... states) {
36 | mStateList.addAll(Arrays.asList(states));
37 | mStateList.add(initialState);
38 | mInitialState = initialState;
39 | setOwner();
40 | generatePath();
41 | generateDescendantStateList();
42 | mName = "";
43 | }
44 |
45 | void setContainer(State container) {
46 | mContainer = container;
47 | }
48 |
49 | State getContainer() {
50 | return mContainer;
51 | }
52 |
53 | private void generateDescendantStateList() {
54 | mDescendantStateList.addAll(mStateList);
55 | for (State state : mStateList) {
56 | mDescendantStateList.addAll(state.getDescendantStates());
57 | }
58 | }
59 |
60 | private void generatePath() {
61 | mPath.add(0, this);
62 | for (State state : mStateList) {
63 | state.addParent(this);
64 | }
65 | }
66 |
67 | public void init() {
68 | init(new HashMap());
69 | }
70 |
71 | public void init(Map payload) {
72 | LOGGER.debug(mName + " init");
73 | if (mInitialState == null) {
74 | throw new IllegalStateException(mName + " Can't init without states defined.");
75 | } else {
76 | mEventQueueInProgress = true;
77 | if(payload == null) {
78 | payload = new HashMap();
79 | }
80 | enterState(null, mInitialState, payload);
81 | mEventQueueInProgress = false;
82 | processEventQueue();
83 | }
84 | }
85 |
86 | void teardown(Map payload) {
87 | LOGGER.debug(mName + " teardown");
88 | if(payload == null) {
89 | payload = new HashMap();
90 | }
91 | exitState(mCurrentState, null, payload);
92 | mCurrentState = null;
93 | }
94 |
95 | public void teardown() {
96 | teardown(new HashMap());
97 | }
98 |
99 | @Override
100 | public void handleEvent(String event) {
101 | handleEvent(event, new HashMap());
102 | }
103 |
104 | @Override
105 | public void handleEvent(String eventName, Map payload) {
106 | if(mCurrentState == null) {
107 | return; // TODO: throw an exception here
108 | }
109 | // TODO: make a deep copy of the payload (also do this in Parallel)
110 | mEventQueue.add(new Event(eventName, payload));
111 | processEventQueue();
112 | }
113 |
114 | private void processEventQueue() {
115 | if (mEventQueueInProgress) {
116 | return;
117 | }
118 | mEventQueueInProgress = true;
119 | while (mEventQueue.peek() != null) {
120 | Event event = mEventQueue.poll();
121 | if (!mCurrentState.handleWithOverride(event)) {
122 | LOGGER.debug(mName + " nobody handled event: " + event.getName());
123 | }
124 | }
125 | mEventQueueInProgress = false;
126 | }
127 |
128 | boolean handleWithOverride(Event event) {
129 | if (mCurrentState != null ) {
130 | return mCurrentState.handleWithOverride(event);
131 | } else {
132 | return false;
133 | }
134 | }
135 |
136 | void executeHandler(Handler handler, Event event) {
137 | LOGGER.debug(mName + " execute handler for event: " + event.getName());
138 |
139 | Action action = handler.getAction();
140 | State targetState = handler.getTargetState();
141 | if (targetState == null) {
142 | throw new IllegalStateException(mName + " cant find target state for transition " + event.getName());
143 | }
144 | switch (handler.getKind()) {
145 | case External:
146 | doExternalTransition(mCurrentState, targetState, action, event);
147 | break;
148 | case Local:
149 | doLocalTransition(mCurrentState, targetState, action, event);
150 | break;
151 | case Internal:
152 | executeAction(action, mCurrentState, targetState, event.getPayload());
153 | break;
154 | }
155 | }
156 |
157 | private void executeAction(Action action, State previousState, State targetState, Map payload) {
158 | if (action != null) {
159 | action.setPreviousState(previousState);
160 | action.setNextState(targetState);
161 | action.setPayload(payload);
162 | action.run();
163 | }
164 | }
165 |
166 | private void doExternalTransition(State previousState, State targetState, Action action, Event event) {
167 | StateMachine lca = findLowestCommonAncestor(targetState);
168 | lca.switchState(previousState, targetState, action, event.getPayload());
169 | }
170 |
171 | private void doLocalTransition(State previousState, State targetState, Action action, Event event) {
172 | if(previousState.getDescendantStates().contains(targetState)) {
173 | StateMachine stateMachine = findNextStateMachineOnPathTo(targetState);
174 | stateMachine.switchState(previousState, targetState, action, event.getPayload());
175 | } else if(targetState.getDescendantStates().contains(previousState)) {
176 | int targetLevel = targetState.getOwner().getPath().size();
177 | StateMachine stateMachine = mPath.get(targetLevel);
178 | stateMachine.switchState(previousState, targetState, action, event.getPayload());
179 | } else if(previousState.equals(targetState)) {
180 | //TODO: clarify desired behavior for local transition on self
181 | // currently behaves like an internal transition
182 | } else {
183 | doExternalTransition(previousState, targetState, action, event);
184 | }
185 | }
186 |
187 | private void switchState(State previousState, State nextState, Action action, Map payload) {
188 | exitState(previousState, nextState, payload);
189 | executeAction(action, previousState, nextState, payload);
190 | enterState(previousState, nextState, payload);
191 | }
192 |
193 | void enterState(State previousState, State targetState, Map payload) {
194 | int targetLevel = targetState.getOwner().getPath().size();
195 | int localLevel = mPath.size();
196 | State nextState;
197 | if (targetLevel < localLevel) {
198 | nextState = mInitialState;
199 | } else if (targetLevel == localLevel) {
200 | nextState = targetState;
201 | } else { // if targetLevel > localLevel
202 | nextState = findNextStateOnPathTo(targetState);
203 | }
204 | if (mStateList.contains(nextState)) {
205 | mCurrentState = nextState;
206 | } else {
207 | mCurrentState = mInitialState;
208 | }
209 | mCurrentState.enter(previousState, targetState, payload);
210 | }
211 |
212 | private State findNextStateOnPathTo(State targetState) {
213 | return findNextStateMachineOnPathTo(targetState).getContainer();
214 | }
215 |
216 | private StateMachine findNextStateMachineOnPathTo(State targetState) {
217 | int localLevel = mPath.size();
218 | StateMachine targetOwner = targetState.getOwner();
219 | return targetOwner.getPath().get(localLevel);
220 | }
221 |
222 | private void exitState(State previousState, State nextState, Map payload) {
223 | mCurrentState.exit(previousState, nextState, payload);
224 | }
225 |
226 | private void setOwner() {
227 | for (State state : mStateList) {
228 | state.setOwner(this);
229 | }
230 | }
231 |
232 | @Override
233 | public String toString() {
234 | if (mCurrentState == null) {
235 | return mInitialState.toString();
236 | }
237 | return mCurrentState.toString();
238 | }
239 |
240 | List getPath() {
241 | return mPath;
242 | }
243 |
244 | public String getPathString() {
245 | StringBuilder sb = new StringBuilder();
246 | int count = 0;
247 | sb.append("\r\n");
248 | for (StateMachine stateMachine : mPath) {
249 | sb.append(Integer.toString(++count));
250 | sb.append(' ');
251 | sb.append(stateMachine.toString());
252 | sb.append("\r\n");
253 | }
254 | return sb.toString();
255 | }
256 |
257 | void addParent(StateMachine stateMachine) {
258 | mPath.add(0, stateMachine);
259 | for (State state : mStateList) {
260 | state.addParent(stateMachine);
261 | }
262 | }
263 |
264 | private StateMachine findLowestCommonAncestor(State targetState) {
265 | if (targetState.getOwner() == null) {
266 | throw new IllegalStateException(mName + " Target state '" + targetState.getId() + "' is not contained in state machine model.");
267 | }
268 | List targetPath = targetState.getOwner().getPath();
269 | int size = mPath.size();
270 | for (int i = 1; i < size; i++) {
271 | try {
272 | StateMachine targetAncestor = targetPath.get(i);
273 | StateMachine localAncestor = mPath.get(i);
274 | if (!targetAncestor.equals(localAncestor)) {
275 | return mPath.get(i - 1);
276 | }
277 | } catch (IndexOutOfBoundsException e) {
278 | return mPath.get(i - 1);
279 | }
280 | }
281 | return this;
282 | }
283 |
284 | List getDescendantStates() {
285 | return mDescendantStateList;
286 | }
287 |
288 | public List getAllActiveStates() {
289 | ArrayList stateList = new ArrayList();
290 | stateList.add(mCurrentState);
291 | stateList.addAll(mCurrentState.getAllActiveStates());
292 | return stateList;
293 | }
294 |
295 |
296 | String getName() {
297 | return mName;
298 | }
299 |
300 | void setName(String name) {
301 | this.mName = name;
302 | }
303 | }
304 |
--------------------------------------------------------------------------------
/hsm/src/main/java/de/artcom/hsm/Sub.java:
--------------------------------------------------------------------------------
1 | package de.artcom.hsm;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Collection;
5 | import java.util.List;
6 | import java.util.Map;
7 |
8 | public class Sub extends State {
9 |
10 | private final StateMachine mSubMachine;
11 |
12 | @Override
13 | protected Sub getThis() {
14 | return this;
15 | }
16 |
17 | public Sub(String id, StateMachine subMachine) {
18 | super(id);
19 | mSubMachine = subMachine;
20 | mSubMachine.setContainer(this);
21 | }
22 |
23 | public Sub(String id, State initialState, State... states) {
24 | super(id);
25 | mSubMachine = new StateMachine(initialState, states);
26 | mSubMachine.setContainer(this);
27 | }
28 |
29 | @Override
30 | void enter(State prev, State next, Map payload) {
31 | super.enter(prev, next, payload);
32 | mSubMachine.enterState(prev, next, payload);
33 | }
34 |
35 | @Override
36 | void exit(State prev, State next, Map payload) {
37 | mSubMachine.teardown(payload);
38 | super.exit(prev, next, payload);
39 | }
40 |
41 | @Override
42 | boolean handleWithOverride(Event event) {
43 | if (mSubMachine.handleWithOverride(event)) {
44 | return true;
45 | } else {
46 | return super.handleWithOverride(event);
47 | }
48 | }
49 |
50 | @Override
51 | public String toString() {
52 | return getId() + "/(" + mSubMachine.toString() + ")";
53 | }
54 |
55 | @Override
56 | void addParent(StateMachine stateMachine) {
57 | mSubMachine.addParent(stateMachine);
58 | }
59 |
60 | @Override
61 | Collection extends State> getDescendantStates() {
62 | return mSubMachine.getDescendantStates();
63 | }
64 |
65 | public List getAllActiveStates() {
66 | return mSubMachine.getAllActiveStates();
67 | }
68 |
69 | @Override
70 | void setOwner(StateMachine ownerMachine) {
71 | super.setOwner(ownerMachine);
72 | mSubMachine.setName(mOwner.getName());
73 | }
74 | }
--------------------------------------------------------------------------------
/hsm/src/main/java/de/artcom/hsm/TransitionKind.java:
--------------------------------------------------------------------------------
1 | package de.artcom.hsm;
2 |
3 | public enum TransitionKind {
4 | External,
5 | Local,
6 | Internal
7 | }
8 |
--------------------------------------------------------------------------------
/pmd-rules.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include 'hsm'
2 | include 'tests'
3 |
--------------------------------------------------------------------------------
/tests/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'java'
2 | apply plugin: 'groovy'
3 | apply plugin: 'jacoco'
4 | apply plugin: 'pmd'
5 |
6 | task jacocoReport(type: JacocoReport) {
7 | getClassDirectories().setFrom(files(project(':hsm').sourceSets.main.output))
8 | getSourceDirectories().setFrom(files(project(':hsm').sourceSets.main.java.getSrcDirs()))
9 | getExecutionData().setFrom(test)
10 | }
11 |
12 | pmd {
13 | toolVersion = '6.0.0'
14 | ignoreFailures = true
15 | ruleSetFiles = files("${rootDir}/pmd-rules.xml")
16 | }
17 |
18 | dependencies {
19 | compile project(':hsm')
20 | testCompile 'ch.qos.logback:logback-classic:1.1.2'
21 | testCompile 'ch.qos.logback:logback-core:1.1.2'
22 | testCompile 'org.hamcrest:hamcrest-integration:1.3'
23 | testCompile 'org.hamcrest:hamcrest-core:1.3'
24 | testCompile 'org.hamcrest:hamcrest-library:1.3'
25 |
26 | testCompile 'org.codehaus.groovy:groovy-all:2.4.0'
27 | testCompile 'org.spockframework:spock-core:0.7-groovy-2.0'
28 |
29 | testCompile 'junit:junit:4.12'
30 |
31 | testCompile 'org.mockito:mockito-core:1.10.8'
32 | }
33 |
--------------------------------------------------------------------------------
/tests/src/test/java/de/artcom/hsm/test/BasicStateMachineTest.java:
--------------------------------------------------------------------------------
1 | package de.artcom.hsm.test;
2 |
3 | import org.junit.Assert;
4 | import org.junit.Test;
5 | import org.mockito.InOrder;
6 |
7 | import java.util.HashMap;
8 | import java.util.List;
9 | import java.util.Map;
10 |
11 | import de.artcom.hsm.Action;
12 | import de.artcom.hsm.Parallel;
13 | import de.artcom.hsm.State;
14 | import de.artcom.hsm.StateMachine;
15 | import de.artcom.hsm.Sub;
16 | import de.artcom.hsm.TransitionKind;
17 |
18 | import static org.hamcrest.MatcherAssert.assertThat;
19 | import static org.hamcrest.Matchers.hasItems;
20 | import static org.hamcrest.Matchers.not;
21 | import static org.hamcrest.Matchers.notNullValue;
22 | import static org.hamcrest.Matchers.equalTo;
23 | import static org.mockito.Mockito.inOrder;
24 | import static org.mockito.Mockito.mock;
25 | import static org.mockito.Mockito.verify;
26 | import static org.mockito.Mockito.verifyZeroInteractions;
27 |
28 | public class BasicStateMachineTest {
29 |
30 | @Test
31 | public void cannotCreateEmptyStateMachine() {
32 |
33 | try {
34 | StateMachine sm = new StateMachine(null);
35 | Assert.fail("NullPointerException should raise when creating a empty StateMachine");
36 | } catch (NullPointerException npe) {
37 | }
38 | }
39 |
40 | @Test
41 | public void canChainMethods() {
42 | State state = new State("foo")
43 | .onEnter(mock(Action.class))
44 | .onExit(mock(Action.class));
45 | }
46 |
47 | @Test
48 | public void initialStateIsEntered() {
49 | //given:
50 | Action enterAction = mock(Action.class);
51 | State on = new State("on")
52 | .onEnter(enterAction);
53 | StateMachine sm = new StateMachine(on);
54 | //when:
55 | sm.init();
56 | //then:
57 | verify(enterAction).run();
58 | }
59 |
60 | @Test
61 | public void currentStateIsExited() {
62 | //given:
63 | Action exitAction = mock(Action.class);
64 | State on = new State("on")
65 | .onExit(exitAction);
66 | StateMachine sm = new StateMachine(on);
67 | sm.init();
68 | //when:
69 | sm.teardown();
70 | //then:
71 | verify(exitAction).run();
72 | }
73 |
74 | @Test
75 | public void eventsWithoutPayloadCauseStateTransition() {
76 | //given:
77 | Action onExitAction = mock(Action.class);
78 | Action offEnterAction = mock(Action.class);
79 | State off = new State("off")
80 | .onEnter(offEnterAction);
81 | State on = new State("on")
82 | .addHandler("toggle", off, TransitionKind.External)
83 | .onExit(onExitAction);
84 | StateMachine sm = new StateMachine(on, off);
85 | sm.init();
86 |
87 | //when:
88 | sm.handleEvent("toggle");
89 |
90 | //then:
91 | verify(onExitAction).run();
92 | verify(offEnterAction).run();
93 | }
94 |
95 | @Test
96 | public void impossibleTransitionTest() {
97 | // given:
98 | Action onExitAction = mock(Action.class);
99 | State off = new State("off");
100 | State on = new State("on")
101 | .addHandler("toggle", off, TransitionKind.External)
102 | .onExit(onExitAction);
103 | StateMachine sm = new StateMachine(on);
104 | sm.init();
105 |
106 | // when:
107 | try {
108 | sm.handleEvent("toggle");
109 | Assert.fail("expected NullpointerException but nothing happnend");
110 | } catch (IllegalStateException npe) {
111 | }
112 | }
113 |
114 | @Test
115 | public void eventsWithPayloadCauseStateTransition() {
116 | //given:
117 |
118 | Action offEnterAction = mock(Action.class);
119 | State off = new State("off")
120 | .onEnter(offEnterAction);
121 |
122 | Action onExitAction = mock(Action.class);
123 | State on = new State("on")
124 | .addHandler("toggle", off, TransitionKind.External)
125 | .onExit(onExitAction);
126 |
127 |
128 | StateMachine sm = new StateMachine(on, off);
129 | sm.init();
130 |
131 | //when:
132 | sm.handleEvent("toggle", new HashMap());
133 |
134 | //then:
135 | verify(onExitAction).run();
136 | verify(offEnterAction).run();
137 | }
138 |
139 | @Test
140 | public void actionsAreCalledBetweenExitAndEnter() {
141 | Action aExit = mock(Action.class);
142 | Action aaExit = mock(Action.class);
143 | Action bEnter = mock(Action.class);
144 | Action bbEnter = mock(Action.class);
145 | Action action = mock(Action.class);
146 |
147 | State aa = new State("aa").onExit(aaExit);
148 | Sub a = new Sub("a", aa).onExit(aExit);
149 | State bb = new State("bb").onEnter(bbEnter);
150 | Sub b = new Sub("b", bb).onEnter(bEnter);
151 |
152 | aa.addHandler("T", bb, TransitionKind.External, action);
153 |
154 | StateMachine sm = new StateMachine(a, b);
155 | sm.init();
156 |
157 | sm.handleEvent("T");
158 |
159 | InOrder inOrder = inOrder(aaExit, aExit, action, bEnter, bbEnter);
160 | inOrder.verify(aaExit).run();
161 | inOrder.verify(aExit).run();
162 | inOrder.verify(action).run();
163 | inOrder.verify(bEnter).run();
164 | inOrder.verify(bbEnter).run();
165 | }
166 |
167 | @Test
168 | public void actionsAreCalledOnTransitionsWithPayload() {
169 | //given:
170 | final boolean[] actionGotCalled = {false};
171 | Action toggleAction = new Action() {
172 | @Override
173 | public void run() {
174 | actionGotCalled[0] = true;
175 | assertThat(mPayload, notNullValue());
176 | Assert.assertTrue(mPayload.containsKey("foo"));
177 | Assert.assertTrue(mPayload.get("foo") instanceof String);
178 | Assert.assertTrue(((String) mPayload.get("foo")).equals("bar"));
179 | }
180 | };
181 | State off = new State("off");
182 | State on = new State("on")
183 | .addHandler("toggle", off, TransitionKind.External, toggleAction);
184 |
185 | StateMachine sm = new StateMachine(on, off);
186 | sm.init();
187 | Map payload = new HashMap();
188 | payload.put("foo", "bar");
189 |
190 | //when:
191 | sm.handleEvent("toggle", payload);
192 |
193 | //then:
194 | if (!actionGotCalled[0]) {
195 | Assert.fail("action was not called");
196 | }
197 | }
198 |
199 | @Test
200 | public void actionsAreCalledAlwaysWithValidPayload() {
201 | //given:
202 | final boolean[] actionGotCalled = {false};
203 | Action toggleAction = new Action() {
204 | @Override
205 | public void run() {
206 | actionGotCalled[0] = true;
207 | assertThat(mPayload, notNullValue());
208 | Assert.assertTrue(mPayload.isEmpty());
209 | }
210 | };
211 | State off = new State("off");
212 | State on = new State("on")
213 | .addHandler("toggle", off, TransitionKind.External, toggleAction);
214 |
215 | StateMachine sm = new StateMachine(on, off);
216 | sm.init();
217 |
218 | //when:
219 | sm.handleEvent("toggle");
220 |
221 | //then:
222 | if (!actionGotCalled[0]) {
223 | Assert.fail("action was not called");
224 | }
225 | }
226 |
227 | @Test
228 | public void actionsCanBeInternal() {
229 | //given:
230 | Action onExitAction = mock(Action.class);
231 | Action toggleAction = mock(Action.class);
232 | State on = new State("on");
233 | on.addHandler("toggle", on, TransitionKind.Internal, toggleAction)
234 | .onExit(onExitAction);
235 | StateMachine sm = new StateMachine(on);
236 | sm.init();
237 |
238 | //when:
239 | sm.handleEvent("toggle");
240 |
241 | //then:
242 | verify(toggleAction).run();
243 | verifyZeroInteractions(onExitAction);
244 | }
245 |
246 | @Test
247 | public void noMatchingStateAvailable() {
248 | // given:
249 | State off = new State("off");
250 | State on = new State("on").addHandler("toggle", off, TransitionKind.External);
251 | StateMachine sm = new StateMachine(on);
252 | sm.init();
253 |
254 | // when:
255 | try {
256 | sm.handleEvent("toggle");
257 | Assert.fail("expected IllegalStateException since target State was not part of StateMachine");
258 | } catch (IllegalStateException e) {
259 | }
260 | }
261 |
262 | @Test
263 | public void canGetPathString() {
264 | // given:
265 | State b201 = new State("b201");
266 | Sub b21 = new Sub("b21", b201);
267 | Sub b1 = new Sub("b1", b21);
268 | Sub b = new Sub("b", b1);
269 | Sub bar = new Sub("bar", b);
270 |
271 | State a1 = new State("a1").addHandler("T1", b201, TransitionKind.External);
272 | Sub a = new Sub("a", a1);
273 | Sub foo = new Sub("foo", a);
274 |
275 | StateMachine sm = new StateMachine(foo, bar);
276 |
277 | // when:
278 | String pathString = sm.getPathString();
279 |
280 | // then:
281 | assertThat(pathString, notNullValue());
282 | }
283 |
284 | @Test
285 | public void enumTest() {
286 | // just for the code coverage (^__^)
287 | TransitionKind local = TransitionKind.valueOf("Local");
288 | assertThat(local, equalTo(TransitionKind.Local));
289 | }
290 |
291 | @Test
292 | public void emittedEventTestHandledFromTopStateMachine() {
293 | class SampleState extends State {
294 |
295 | public SampleState(String id) {
296 | super(id);
297 | onEnter(new Action() {
298 | @Override
299 | public void run() {
300 | SampleState.this.getEventHandler().handleEvent("T1");
301 | }
302 | });
303 | }
304 | }
305 |
306 | // given:
307 | Action bAction = mock(Action.class);
308 | SampleState a1 = new SampleState("a1");
309 |
310 | Sub a = new Sub("a", a1);
311 | Sub b = new Sub("b", a);
312 | b.addHandler("T1", b, TransitionKind.Internal, bAction);
313 | Sub c = new Sub("c", b);
314 | StateMachine stateMachine = new StateMachine(c);
315 |
316 | // when:
317 | stateMachine.init();
318 |
319 | // then:
320 | verify(bAction).run();
321 | }
322 |
323 | @Test
324 | public void getAllActiveStates() {
325 | // given:
326 | State a11 = new State("a11");
327 | State a22 = new State("a22");
328 | State a33 = new State("a33");
329 | Parallel a1 = new Parallel("a1", new StateMachine(a11), new StateMachine(a22, a33));
330 | Sub a = new Sub("a", a1);
331 | StateMachine sm = new StateMachine(a);
332 | sm.init();
333 |
334 | // when:
335 | List allActiveStates = sm.getAllActiveStates();
336 |
337 | // then:
338 | assertThat(allActiveStates, hasItems(a, a1, a11, a22));
339 | assertThat(allActiveStates, not(hasItems(a33)));
340 | }
341 |
342 | }
343 |
--------------------------------------------------------------------------------
/tests/src/test/java/de/artcom/hsm/test/ComplexTests.java:
--------------------------------------------------------------------------------
1 | package de.artcom.hsm.test;
2 |
3 | import org.junit.Before;
4 | import org.junit.Test;
5 |
6 | import de.artcom.hsm.Parallel;
7 | import de.artcom.hsm.State;
8 | import de.artcom.hsm.StateMachine;
9 | import de.artcom.hsm.Sub;
10 |
11 | public class ComplexTests {
12 |
13 | private State a1;
14 | private State a2;
15 | private State a3;
16 | private Sub a;
17 |
18 | private State b1;
19 | private Sub b2;
20 | private State b21;
21 | private State b22;
22 | private Sub b;
23 |
24 | private State c11;
25 | private State c12;
26 | private State c21;
27 | private State c22;
28 | private StateMachine c1Machine;
29 | private StateMachine c2Machine;
30 | private Parallel c;
31 |
32 | private StateMachine stateMachine;
33 |
34 | @Before
35 | public void setUpTest() {
36 | a1 = new State("a1");
37 | a2 = new State("a2");
38 | a3 = new State("a3");
39 | a = new Sub("a", a1, a2 , a3);
40 |
41 | b1 = new State("b1");
42 | b21 = new State("b21");
43 | b22 = new State("b22");
44 | b2 = new Sub("b2", b21, b22);
45 | b = new Sub("b", b1, b2);
46 |
47 | c11 = new State("c11");
48 | c12 = new State("c12");
49 | c21 = new State("c21");
50 | c22 = new State("c22");
51 | c1Machine = new StateMachine(c11, c12);
52 | c2Machine = new StateMachine(c21, c22);
53 | c = new Parallel("c", c1Machine, c2Machine);
54 |
55 | stateMachine = new StateMachine(a, b, c);
56 | }
57 |
58 | @Test
59 | public void complexStateMachineTest1() {
60 | //when:
61 | stateMachine.init();
62 | stateMachine.teardown();
63 | //then: no exceptions
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/tests/src/test/java/de/artcom/hsm/test/EventHandlingTest.java:
--------------------------------------------------------------------------------
1 | package de.artcom.hsm.test;
2 |
3 | import org.hamcrest.Matchers;
4 | import org.hamcrest.collection.IsMapContaining;
5 | import org.hamcrest.core.IsEqual;
6 | import org.junit.Test;
7 |
8 | import java.util.HashMap;
9 | import java.util.Map;
10 |
11 | import de.artcom.hsm.Action;
12 | import de.artcom.hsm.Guard;
13 | import de.artcom.hsm.State;
14 | import de.artcom.hsm.StateMachine;
15 | import de.artcom.hsm.Sub;
16 | import de.artcom.hsm.TransitionKind;
17 |
18 | import static org.hamcrest.Matchers.equalTo;
19 | import static org.junit.Assert.assertThat;
20 | import static org.junit.Assert.fail;
21 | import static org.mockito.Mockito.mock;
22 | import static org.mockito.Mockito.verify;
23 | import static org.mockito.Mockito.verifyZeroInteractions;
24 |
25 | public class EventHandlingTest {
26 |
27 | @Test
28 | public void runToCompletionTest() {
29 | // given:
30 | State rawEgg = new State("rawEgg");
31 | State softEgg = new State("softEgg");
32 | State hardEgg = new State("hardEgg");
33 | final StateMachine sm = new StateMachine(rawEgg, softEgg, hardEgg);
34 |
35 | Action onEnterHardEgg = mock(Action.class);
36 | Action onEnterSoftEgg = mock(Action.class);
37 |
38 | softEgg.onEnter(onEnterSoftEgg);
39 | hardEgg.onEnter(onEnterHardEgg);
40 |
41 | Action boilAction = new Action() {
42 | @Override
43 | public void run() {
44 | sm.handleEvent("boil_too_long");
45 | }
46 | };
47 | rawEgg.addHandler("boil", softEgg, TransitionKind.External, boilAction);
48 |
49 | Action boilTooLongAction = mock(Action.class);
50 | rawEgg.addHandler("boil_too_long", hardEgg, TransitionKind.External, boilTooLongAction);
51 |
52 | Action boilTooLongAction2 = mock(Action.class);
53 | softEgg.addHandler("boil_too_long", softEgg, TransitionKind.Internal, boilTooLongAction2);
54 |
55 | sm.init();
56 |
57 | // when:
58 | sm.handleEvent("boil");
59 |
60 | // then:
61 | verifyZeroInteractions(onEnterHardEgg);
62 | verifyZeroInteractions(boilTooLongAction);
63 | verify(onEnterSoftEgg).run();
64 | verify(boilTooLongAction2).run();
65 | }
66 |
67 | @Test
68 | public void dontBubbleUpTest() {
69 | // given:
70 | Action enterA2 = mock(Action.class);
71 | Action enterB = mock(Action.class);
72 |
73 | State a1 = new State("a1");
74 | State a2 = new State("a2").onEnter(enterA2);
75 | Sub a = new Sub("a", a1, a2);
76 | State b = new State("b").onEnter(enterB);
77 |
78 | a1.addHandler("T1", a2, TransitionKind.External, new Guard() {
79 | @Override
80 | public boolean evaluate(Map payload) {
81 | return payload.containsKey("foo");
82 | }
83 | });
84 | a.addHandler("T1", b, TransitionKind.External);
85 |
86 | StateMachine sm = new StateMachine(a, b);
87 | sm.init();
88 |
89 | // when:
90 | Map payload = new HashMap();
91 | payload.put("foo", "bar");
92 | sm.handleEvent("T1", payload);
93 |
94 | // then:
95 | verify(enterA2).run();
96 | verifyZeroInteractions(enterB);
97 | }
98 |
99 | @Test
100 | public void bubbleUpTest() {
101 | // given:
102 | Action enterA2 = mock(Action.class);
103 | Action enterB = mock(Action.class);
104 |
105 | State a1 = new State("a1");
106 | State a2 = new State("a2").onEnter(enterA2);
107 | Sub a = new Sub("a", a1, a2);
108 | State b = new State("b").onEnter(enterB);
109 |
110 | a1.addHandler("T1", a2, TransitionKind.External, new Guard() {
111 | @Override
112 | public boolean evaluate(Map payload) {
113 | return payload.containsKey("foo");
114 | }
115 | });
116 | a.addHandler("T1", b, TransitionKind.External);
117 |
118 | StateMachine sm = new StateMachine(a, b);
119 | sm.init();
120 |
121 | // when:
122 | sm.handleEvent("T1");
123 |
124 | // then:
125 | verify(enterB).run();
126 | verifyZeroInteractions(enterA2);
127 | }
128 |
129 | @Test
130 | public void handleTransitionWithPayload() {
131 | // given
132 | State a1 = new State("a1");
133 | State a2 = new State("a2")
134 | .onEnter(new Action() {
135 | @Override
136 | public void run() {
137 | assertThat(mPayload, Matchers.hasKey("foo"));
138 | }
139 | });
140 |
141 | a1.addHandler("T1", a2, TransitionKind.External)
142 | .onExit(new Action() {
143 | @Override
144 | public void run() {
145 | assertThat(mPayload, Matchers.hasKey("foo"));
146 | }
147 | });
148 |
149 | StateMachine sm = new StateMachine(a1, a2);
150 | sm.init();
151 |
152 | Map payload = new HashMap();
153 | payload.put("foo", "bar");
154 |
155 | // when
156 | sm.handleEvent("T1", payload);
157 | }
158 |
159 | @Test
160 | public void initWithPayload() {
161 | // given
162 | Action a1Enter = new Action() {
163 | @Override
164 | public void run() {
165 | // then
166 | assertThat(mPayload, IsMapContaining.hasKey("foo"));
167 | }
168 | };
169 | State a1 = new State("a1").onEnter(a1Enter);
170 | StateMachine sm = new StateMachine(a1);
171 | Map payload = new HashMap();
172 | payload.put("foo", "bar");
173 |
174 | // when
175 | sm.init(payload);
176 | }
177 |
178 | @Test
179 | public void initWithNullPayload() {
180 | // given
181 | Action a1Enter = mock(Action.class);
182 | State a1 = new State("a1").onEnter(a1Enter);
183 | StateMachine sm = new StateMachine(a1);
184 |
185 | // when
186 | try {
187 | sm.init(null);
188 | } catch (NullPointerException npe) {
189 | fail("StateMachine.init() should instantiate a new payload instead of throwing npe");
190 | }
191 |
192 | // then
193 | }
194 |
195 | @Test
196 | public void teardownWithPayload() {
197 | // when
198 | State a1 = new State("a1");
199 | State a = new Sub("a", a1);
200 | State b1 = new State("b1").onEnter(new Action() {
201 | @Override
202 | public void run() {
203 | assertThat(mPayload, IsMapContaining.hasKey("foo"));
204 | }
205 | });
206 |
207 | a1.addHandler("T1", b1, TransitionKind.External).onExit(new Action() {
208 | @Override
209 | public void run() {
210 | mPayload.put("foo", "bar");
211 | }
212 | });
213 |
214 | State b = new Sub("b", a, b1);
215 | StateMachine sm = new StateMachine(b);
216 | sm.init();
217 | sm.handleEvent("T1");
218 | }
219 | }
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
--------------------------------------------------------------------------------
/tests/src/test/java/de/artcom/hsm/test/GuardTest.java:
--------------------------------------------------------------------------------
1 | package de.artcom.hsm.test;
2 |
3 | import org.junit.Test;
4 |
5 | import java.util.HashMap;
6 | import java.util.Map;
7 |
8 | import de.artcom.hsm.Action;
9 | import de.artcom.hsm.Guard;
10 | import de.artcom.hsm.State;
11 | import de.artcom.hsm.StateMachine;
12 | import de.artcom.hsm.TransitionKind;
13 |
14 | import static org.mockito.Mockito.mock;
15 | import static org.mockito.Mockito.verify;
16 | import static org.mockito.Mockito.verifyZeroInteractions;
17 |
18 | public class GuardTest {
19 |
20 | @Test
21 | public void testFirstGuard() {
22 | // given:
23 | Action enterA2 = mock(Action.class);
24 | Action enterA3 = mock(Action.class);
25 |
26 | State a1 = new State("a1");
27 | State a2 = new State("a2").onEnter(enterA2);
28 | State a3 = new State("a3").onEnter(enterA3);
29 |
30 | a1.addHandler("T1", a2, TransitionKind.External, new Guard() {
31 | @Override
32 | public boolean evaluate(Map payload) {
33 | Boolean foo = (Boolean) payload.get("foo");
34 | return foo;
35 | }
36 | }).addHandler("T1", a3, TransitionKind.External, new Guard() {
37 | @Override
38 | public boolean evaluate(Map payload) {
39 | Boolean foo = (Boolean) payload.get("foo");
40 | return !foo;
41 | }
42 | });
43 |
44 | StateMachine sm = new StateMachine(a1, a2, a3);
45 | sm.init();
46 |
47 | //when:
48 | Map payload = new HashMap();
49 | payload.put("foo", true);
50 | sm.handleEvent("T1", payload);
51 |
52 | //then:
53 | verify(enterA2).run();
54 | verifyZeroInteractions(enterA3);
55 | }
56 |
57 | @Test
58 | public void testSecondGuard() {
59 | // given:
60 | Action enterA2 = mock(Action.class);
61 | Action enterA3 = mock(Action.class);
62 |
63 | State a1 = new State("a1");
64 | State a2 = new State("a2").onEnter(enterA2);
65 | State a3 = new State("a3").onEnter(enterA3);
66 |
67 | a1.addHandler("T1", a2, TransitionKind.External, new Guard() {
68 | @Override
69 | public boolean evaluate(Map payload) {
70 | Boolean foo = (Boolean) payload.get("foo");
71 | return foo;
72 | }
73 | }).addHandler("T1", a3, TransitionKind.External, new Guard() {
74 | @Override
75 | public boolean evaluate(Map payload) {
76 | Boolean foo = (Boolean) payload.get("foo");
77 | return !foo;
78 | }
79 | });
80 |
81 | StateMachine sm = new StateMachine(a1, a2, a3);
82 | sm.init();
83 |
84 | //when:
85 | Map payload = new HashMap();
86 | payload.put("foo", false);
87 | sm.handleEvent("T1", payload);
88 |
89 | //then:
90 | verify(enterA3).run();
91 | verifyZeroInteractions(enterA2);
92 | }
93 |
94 | @Test
95 | public void createHandlerWithActionAndGuard() {
96 | // given:
97 | Action a1Action = mock(Action.class);
98 | State a = new State("a");
99 | a.addHandler("T1", a, TransitionKind.Internal, a1Action, new Guard() {
100 | @Override
101 | public boolean evaluate(Map payload) {
102 | return payload.containsKey("foo");
103 | }
104 | });
105 | StateMachine sm = new StateMachine(a);
106 | sm.init();
107 |
108 | // when:
109 | Map payload = new HashMap();
110 | payload.put("foo", "bar");
111 | sm.handleEvent("T1", payload);
112 |
113 | // then:
114 | verify(a1Action).run();
115 | }
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/tests/src/test/java/de/artcom/hsm/test/LcaTest.java:
--------------------------------------------------------------------------------
1 | package de.artcom.hsm.test;
2 |
3 | import org.junit.Test;
4 | import org.mockito.InOrder;
5 |
6 | import de.artcom.hsm.Action;
7 | import de.artcom.hsm.State;
8 | import de.artcom.hsm.StateMachine;
9 | import de.artcom.hsm.Sub;
10 | import de.artcom.hsm.TransitionKind;
11 |
12 | import static org.mockito.Mockito.inOrder;
13 | import static org.mockito.Mockito.mock;
14 | import static org.mockito.Mockito.verify;
15 | import static org.mockito.Mockito.verifyZeroInteractions;
16 |
17 | public class LcaTest {
18 |
19 | @Test
20 | public void testLowestCommonAncestor1() {
21 | // given:
22 | Action exitA1 = mock(Action.class);
23 | Action exitA = mock(Action.class);
24 | Action exitFoo = mock(Action.class);
25 | State a1 = new State("a1").onExit(exitA1);
26 | Sub a = new Sub("a", a1).onExit(exitA);
27 | Sub foo = new Sub("foo", a).onExit(exitFoo);
28 |
29 | Action enterB21 = mock(Action.class);
30 | Action enterB201 = mock(Action.class);
31 | Action enterB1 = mock(Action.class);
32 | Action enterB = mock(Action.class);
33 | Action enterBar = mock(Action.class);
34 | State b201 = new State("b201").onEnter(enterB201);
35 | Sub b21 = new Sub("b21", b201).onEnter(enterB21);
36 | Sub b1 = new Sub("b1", b21).onEnter(enterB1);
37 | Sub b = new Sub("b", b1).onEnter(enterB);
38 | Sub bar = new Sub("bar", b).onEnter(enterBar);
39 | StateMachine sm = new StateMachine(foo, bar);
40 |
41 | a1.addHandler("T1", bar, TransitionKind.External);
42 |
43 | sm.init();
44 |
45 | // when:
46 | sm.handleEvent("T1");
47 |
48 | // then:
49 | InOrder inOrder = inOrder(exitA1, exitA, exitFoo, enterBar, enterB, enterB1, enterB21, enterB201);
50 | inOrder.verify(exitA1).run();
51 | inOrder.verify(exitA).run();
52 | inOrder.verify(exitFoo).run();
53 | inOrder.verify(enterBar).run();
54 | inOrder.verify(enterB).run();
55 | inOrder.verify(enterB1).run();
56 | inOrder.verify(enterB21).run();
57 | inOrder.verify(enterB201).run();
58 | }
59 |
60 | @Test
61 | public void testLowestCommonAncestor2() {
62 | // given:
63 | Action exitA1 = mock(Action.class);
64 | Action exitA = mock(Action.class);
65 | Action exitFoo = mock(Action.class);
66 | State a1 = new State("a1").onExit(exitA1);
67 | Sub a = new Sub("a", a1).onExit(exitA);
68 | Sub foo = new Sub("foo", a).onExit(exitFoo);
69 |
70 | Action enterB21 = mock(Action.class);
71 | Action enterB201 = mock(Action.class);
72 | Action enterB1 = mock(Action.class);
73 | Action enterB = mock(Action.class);
74 | Action enterBar = mock(Action.class);
75 | State b201 = new State("b201").onEnter(enterB201);
76 | Sub b21 = new Sub("b21", b201).onEnter(enterB21);
77 | Sub b1 = new Sub("b1", b21).onEnter(enterB1);
78 | Sub b = new Sub("b", b1).onEnter(enterB);
79 | Sub bar = new Sub("bar", b).onEnter(enterBar);
80 | StateMachine sm = new StateMachine(foo, bar);
81 |
82 | a1.addHandler("T1", b201, TransitionKind.External);
83 |
84 | sm.init();
85 |
86 | // when:
87 | sm.handleEvent("T1");
88 |
89 | // then:
90 | InOrder inOrder = inOrder(exitA1, exitA, exitFoo, enterBar, enterB, enterB1, enterB21, enterB201);
91 | inOrder.verify(exitA1).run();
92 | inOrder.verify(exitA).run();
93 | inOrder.verify(exitFoo).run();
94 | inOrder.verify(enterBar).run();
95 | inOrder.verify(enterB).run();
96 | inOrder.verify(enterB1).run();
97 | inOrder.verify(enterB21).run();
98 | inOrder.verify(enterB201).run();
99 | }
100 |
101 | @Test
102 | public void testLowestCommonAncestor3() {
103 | // given:
104 | Action enterA2 = mock(Action.class);
105 | Action enterB2 = mock(Action.class);
106 | Action exitM = mock(Action.class);
107 |
108 | State a1 = new State("a1");
109 | State a2 = new State("a2")
110 | .onEnter(enterA2);
111 |
112 | Sub a = new Sub("a", a1, a2);
113 | Sub foo = new Sub("foo", a);
114 |
115 | State b1 = new State("b1");
116 | State b2 = new State("b2")
117 | .onEnter(enterB2);
118 |
119 | Sub b = new Sub("b", b1, b2);
120 | Sub bar = new Sub("bar", b);
121 |
122 | Sub main = new Sub("main", foo, bar)
123 | .onExit(exitM);
124 |
125 | StateMachine sm = new StateMachine(main);
126 |
127 | a1.addHandler("B1", b1, TransitionKind.External)
128 | .addHandler("T1", a2, TransitionKind.External);
129 | b1.addHandler("T1", a2, TransitionKind.External);
130 |
131 | sm.init();
132 |
133 | // when:
134 | sm.handleEvent("B1");
135 | sm.handleEvent("T1");
136 |
137 | // then:
138 | verify(enterA2).run();
139 | verifyZeroInteractions(enterB2);
140 | verifyZeroInteractions(exitM);
141 | }
142 |
143 | }
144 |
--------------------------------------------------------------------------------
/tests/src/test/java/de/artcom/hsm/test/LocalTransitionTest.java:
--------------------------------------------------------------------------------
1 | package de.artcom.hsm.test;
2 |
3 | import org.junit.Assert;
4 | import org.junit.Test;
5 | import org.junit.Ignore;
6 |
7 | import de.artcom.hsm.Action;
8 | import de.artcom.hsm.State;
9 | import de.artcom.hsm.StateMachine;
10 | import de.artcom.hsm.Sub;
11 | import de.artcom.hsm.TransitionKind;
12 |
13 | import static org.mockito.Mockito.mock;
14 | import static org.mockito.Mockito.times;
15 | import static org.mockito.Mockito.verify;
16 | import static org.mockito.Mockito.inOrder;
17 | import org.mockito.InOrder;
18 |
19 | public class LocalTransitionTest {
20 |
21 | @Test
22 | public void canExecuteLocalTransition() {
23 | // given:
24 | Action sEnter = mock(Action.class);
25 | Action s1Enter = mock(Action.class);
26 | State s1 = new State("s1").onEnter(s1Enter);
27 | Sub s = new Sub("s", s1).onEnter(sEnter).addHandler("T1", s1, TransitionKind.Local);
28 | StateMachine sm = new StateMachine(s);
29 | sm.init();
30 |
31 | // when:
32 | sm.handleEvent("T1");
33 |
34 | // then:
35 | verify(sEnter, times(1)).run();
36 | verify(s1Enter, times(2)).run();
37 | }
38 |
39 | @Test
40 | public void canExecuteLocalTransitionToAncestorState() {
41 | // given:
42 | Action sEnter = mock(Action.class);
43 | Action s1Enter = mock(Action.class);
44 | State s1 = new State("s1").onEnter(s1Enter);
45 | State s2 = new State("s2");
46 | Sub s = new Sub("s", s1, s2).onEnter(sEnter);
47 |
48 | s1.addHandler("T1", s2, TransitionKind.External);
49 | s2.addHandler("T2", s, TransitionKind.Local);
50 |
51 | StateMachine sm = new StateMachine(s);
52 | sm.init();
53 |
54 | // when:
55 | sm.handleEvent("T1");
56 | sm.handleEvent("T2");
57 |
58 | // then:
59 | verify(sEnter, times(1)).run();
60 | verify(s1Enter, times(2)).run();
61 | }
62 |
63 | @Test
64 | public void canExecuteLocalTransitionWhichResultsInExternal() {
65 | // given:
66 | Action sExit = mock(Action.class);
67 | Action s1Exit = mock(Action.class);
68 | Action b1Enter = mock(Action.class);
69 |
70 | State s1 = new State("s1").onExit(s1Exit);
71 | Sub s = new Sub("s", s1).onExit(sExit);
72 |
73 | State b1 = new State("b1").onEnter(b1Enter);
74 | Sub a = new Sub("a", s, b1);
75 | StateMachine sm = new StateMachine(a);
76 |
77 | s.addHandler("T1", b1, TransitionKind.Local);
78 |
79 | sm.init();
80 |
81 | // when:
82 | sm.handleEvent("T1");
83 |
84 | // then:
85 | InOrder inOrder = inOrder(s1Exit, sExit, b1Enter);
86 | inOrder.verify(s1Exit).run();
87 | inOrder.verify(sExit).run();
88 | inOrder.verify(b1Enter).run();
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/tests/src/test/java/de/artcom/hsm/test/ParallelStateMachineTest.java:
--------------------------------------------------------------------------------
1 | package de.artcom.hsm.test;
2 |
3 | import org.junit.Test;
4 | import org.mockito.InOrder;
5 |
6 | import de.artcom.hsm.Action;
7 | import de.artcom.hsm.Parallel;
8 | import de.artcom.hsm.State;
9 | import de.artcom.hsm.StateMachine;
10 | import de.artcom.hsm.Sub;
11 | import de.artcom.hsm.TransitionKind;
12 |
13 | import static org.mockito.Mockito.inOrder;
14 | import static org.mockito.Mockito.mock;
15 | import static org.mockito.Mockito.verify;
16 | import static org.mockito.Mockito.never;
17 |
18 | public class ParallelStateMachineTest {
19 |
20 | @Test
21 | public void canCreateParallelState() {
22 | // given:
23 | State capsOn = new State("caps_on");
24 | State capsOff = new State("caps_off");
25 | StateMachine capsStateMachine = new StateMachine(capsOff, capsOn);
26 |
27 | State numOn = new State("num_on");
28 | State numOff = new State("num_off");
29 | StateMachine numStateMachine = new StateMachine(numOff, numOn);
30 |
31 | Parallel keyboardOn = new Parallel("keyboard_on", capsStateMachine, numStateMachine);
32 | StateMachine sm = new StateMachine(keyboardOn);
33 |
34 | // when:
35 | sm.init();
36 | System.out.println(sm.toString());
37 |
38 | // then:
39 | // no exception
40 | }
41 |
42 |
43 | @Test
44 | public void canSwitchStatesInParallel() {
45 | // given:
46 | Action onEnterCapsOn = mock(Action.class);
47 | Action onEnterCapsOff = mock(Action.class);
48 | Action onEnterNumOn = mock(Action.class);
49 | Action onEnterNumOff = mock(Action.class);
50 | Action onEnterKeyboardOn = mock(Action.class);
51 | Action onEnterKeyboardOff = mock(Action.class);
52 |
53 | State capsOn = new State("caps_on")
54 | .onEnter(onEnterCapsOn);
55 |
56 | State capsOff = new State("caps_off")
57 | .onEnter(onEnterCapsOff);
58 |
59 | StateMachine capsStateMachine = new StateMachine(capsOff, capsOn);
60 |
61 | capsOn.addHandler("capslock", capsOff, TransitionKind.External);
62 | capsOff.addHandler("capslock", capsOn, TransitionKind.External);
63 |
64 | State numOn = new State("num_on")
65 | .onEnter(onEnterNumOn);
66 |
67 | State numOff = new State("num_off")
68 | .onEnter(onEnterNumOff);
69 |
70 | StateMachine numStateMachine = new StateMachine(numOff, numOn);
71 |
72 | numOn.addHandler("numlock", numOff, TransitionKind.External);
73 | numOff.addHandler("numlock", numOn, TransitionKind.External);
74 |
75 | Parallel keyboardOn = new Parallel("keyboard_on", capsStateMachine, numStateMachine)
76 | .onEnter(onEnterKeyboardOn);
77 | State keyboardOff = new State("keyboard_off")
78 | .onEnter(onEnterKeyboardOff);
79 | StateMachine sm = new StateMachine(keyboardOff, keyboardOn);
80 |
81 | keyboardOn.addHandler("unplug", keyboardOff, TransitionKind.External);
82 | keyboardOff.addHandler("plug", keyboardOn, TransitionKind.External);
83 |
84 | sm.init();
85 |
86 | // when:
87 | sm.handleEvent("plug");
88 | sm.handleEvent("capslock");
89 | sm.handleEvent("capslock");
90 | sm.handleEvent("numlock");
91 | sm.handleEvent("unplug");
92 | sm.handleEvent("capslock");
93 | sm.handleEvent("plug");
94 |
95 | // then:
96 | InOrder inOrder = inOrder(onEnterCapsOff, onEnterCapsOn, onEnterKeyboardOff,
97 | onEnterKeyboardOn, onEnterNumOff, onEnterNumOn);
98 |
99 | inOrder.verify(onEnterKeyboardOff).run();
100 | inOrder.verify(onEnterKeyboardOn).run();
101 | inOrder.verify(onEnterCapsOff).run();
102 | inOrder.verify(onEnterNumOff).run();
103 | inOrder.verify(onEnterCapsOn).run();
104 | inOrder.verify(onEnterCapsOff).run();
105 | inOrder.verify(onEnterNumOn).run();
106 | inOrder.verify(onEnterKeyboardOff).run();
107 | inOrder.verify(onEnterKeyboardOn).run();
108 | inOrder.verify(onEnterCapsOff).run();
109 | inOrder.verify(onEnterNumOff).run();
110 | }
111 |
112 | @Test
113 | public void anotherParallelStateTest() {
114 | // given:
115 | Action p11Enter = mock(Action.class);
116 | Action p21Enter = mock(Action.class);
117 | State p11 = new State("p11").onEnter(p11Enter);
118 | State p21 = new State("p21").onEnter(p21Enter);
119 | State s1 = new State("s1");
120 | StateMachine p1 = new StateMachine(p11);
121 | StateMachine p2 = new StateMachine(p21);
122 | Parallel s2 = new Parallel("s2", p1, p2);
123 | Sub s = new Sub("s", s1, s2).addHandler("T1", p21, TransitionKind.External);
124 |
125 | StateMachine sm = new StateMachine(s);
126 | sm.init();
127 |
128 | // when:
129 | sm.handleEvent("T1");
130 |
131 | // then:
132 | verify(p11Enter).run();
133 | verify(p21Enter).run();
134 | }
135 |
136 | @Test
137 | public void parallelStatesCanEmitEventInEnter() {
138 | // given:
139 | final State p1 = new State("p1");
140 | final State p2 = new State("p2");
141 | Action p1Enter = new Action() {
142 | @Override
143 | public void run(){
144 | p1.getEventHandler().handleEvent("foo");
145 | }
146 | };
147 | Action p2Action = mock(Action.class);
148 | p1.onEnter(p1Enter);
149 | p2.addHandler("foo", p2, TransitionKind.Internal, p2Action);
150 | Parallel p = new Parallel("p",
151 | new StateMachine(p1),
152 | new StateMachine(p2)
153 | );
154 | StateMachine sm = new StateMachine(p);
155 |
156 | // when:
157 | sm.init();
158 |
159 | // then:
160 | verify(p2Action).run();
161 | }
162 |
163 | @Test
164 | public void parallelStatesCanEmitEventInExitAndHandleAction() {
165 | // given:
166 | final State p1 = new State("p1");
167 | final State p2 = new State("p2");
168 | Action p1Action = new Action() {
169 | @Override
170 | public void run(){
171 | p1.getEventHandler().handleEvent("foo");
172 | }
173 | };
174 | Action p2Action = mock(Action.class);
175 | p1.onExit(p1Action);
176 | p2.addHandler("foo", p2, TransitionKind.Internal, p2Action);
177 | Parallel p = new Parallel("p",
178 | new StateMachine(p1),
179 | new StateMachine(p2)
180 | );
181 | StateMachine sm = new StateMachine(p);
182 |
183 | // when:
184 | sm.init();
185 | sm.teardown();
186 |
187 | // then:
188 | verify(p2Action).run();
189 | }
190 |
191 | @Test
192 | public void parallelStatesCanEmitEventInExitButIsNotHandledInFinishedStates() {
193 | // given:
194 | final State p1 = new State("p1");
195 | final State p2 = new State("p2");
196 | Action p1Action = new Action() {
197 | @Override
198 | public void run(){
199 | p1.getEventHandler().handleEvent("foo");
200 | }
201 | };
202 | Action p2Action = mock(Action.class);
203 | p1.onExit(p1Action);
204 | p2.addHandler("foo", p2, TransitionKind.Internal, p2Action);
205 | Parallel p = new Parallel("p",
206 | new StateMachine(p2),
207 | new StateMachine(p1)
208 | );
209 | StateMachine sm = new StateMachine(p);
210 |
211 | // when:
212 | sm.init();
213 | sm.teardown();
214 |
215 | // then:
216 | verify(p2Action, never()).run();
217 | }
218 |
219 |
220 | }
221 |
--------------------------------------------------------------------------------
/tests/src/test/java/de/artcom/hsm/test/SubStateMachineTest.java:
--------------------------------------------------------------------------------
1 | package de.artcom.hsm.test;
2 |
3 | import org.junit.Test;
4 | import org.mockito.InOrder;
5 |
6 | import de.artcom.hsm.Action;
7 | import de.artcom.hsm.State;
8 | import de.artcom.hsm.StateMachine;
9 | import de.artcom.hsm.Sub;
10 | import de.artcom.hsm.TransitionKind;
11 |
12 | import static org.mockito.Mockito.inOrder;
13 | import static org.mockito.Mockito.mock;
14 |
15 | public class SubStateMachineTest {
16 |
17 | @Test
18 | public void canCreateSubStateMachine() {
19 | //given:
20 | State loud = new State("loud");
21 | State quiet = new State("quiet");
22 | Sub on = new Sub("on", new StateMachine(quiet, loud));
23 |
24 | //when:
25 | StateMachine sm = new StateMachine(on);
26 | sm.init();
27 |
28 | //then: no exception
29 | }
30 |
31 | @Test
32 | public void canTransitionSubStates() {
33 | //given:
34 | Action onEnterLoud = mock(Action.class);
35 | Action onEnterQuiet = mock(Action.class);
36 | Action onEnterOn = mock(Action.class);
37 | Action onEnterOff = mock(Action.class);
38 | State loud = new State("loud")
39 | .onEnter(onEnterLoud);
40 | State quiet = new State("quiet")
41 | .onEnter(onEnterQuiet);
42 |
43 | quiet.addHandler("volume_up", loud, TransitionKind.External);
44 | loud.addHandler("volume_down", quiet, TransitionKind.External);
45 |
46 | Sub on = new Sub("on", new StateMachine(quiet, loud))
47 | .onEnter(onEnterOn);
48 |
49 | State off = new State("off")
50 | .onEnter(onEnterOff);
51 |
52 | on.addHandler("switched_off", off, TransitionKind.External);
53 | off.addHandler("switched_on", on, TransitionKind.External);
54 |
55 | StateMachine sm = new StateMachine(off, on);
56 | sm.init();
57 |
58 | //when:
59 | sm.handleEvent("switched_on");
60 | sm.handleEvent("volume_up");
61 | sm.handleEvent("switched_off");
62 | sm.handleEvent("switched_on");
63 |
64 | //then:
65 | InOrder inOrder = inOrder(onEnterOff, onEnterOn, onEnterQuiet, onEnterLoud);
66 | inOrder.verify(onEnterOff).run();
67 | inOrder.verify(onEnterOn).run();
68 | inOrder.verify(onEnterQuiet).run();
69 | inOrder.verify(onEnterLoud).run();
70 | inOrder.verify(onEnterOff).run();
71 | inOrder.verify(onEnterOn).run();
72 | inOrder.verify(onEnterQuiet).run();
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------