From cf19f758729ebf04f732225943fd994409d2488a Mon Sep 17 00:00:00 2001
From: Vyacheslav Boyko
Date: Thu, 7 Jul 2022 02:15:12 +0300
Subject: [PATCH] new fluent syntax
---
src/main/java/me/bvn13/fsm/Condition.java | 9 +
.../java/me/bvn13/fsm/ConditionBuilder.java | 34 +++
src/main/java/me/bvn13/fsm/Fsm.java | 267 ++++++++++++++++++
src/main/java/me/bvn13/fsm/FsmBuilder.java | 52 ++++
src/main/java/me/bvn13/fsm/SimpleFsm.java | 9 +
src/main/java/{ru => me}/bvn13/fsm/State.java | 12 +-
.../java/me/bvn13/fsm/StateBehaviour.java | 20 ++
src/main/java/me/bvn13/fsm/StateBuilder.java | 59 ++++
src/main/java/me/bvn13/fsm/StateHandler.java | 12 +
.../java/me/bvn13/fsm/StateProcessor.java | 13 +
.../java/me/bvn13/fsm/dummy/DummyHandler.java | 11 +
.../me/bvn13/fsm/dummy/DummyProcessor.java | 11 +
.../AmbiguousTransitionException.java | 6 +-
.../exceptions/BrokenTransitionException.java | 10 +
.../ConditionAlreadyExistsException.java | 10 +
.../bvn13/fsm/exceptions/FsmException.java} | 8 +-
.../exceptions/NotInitializedException.java | 13 +
.../StateAlreadyExistsException.java | 10 +
.../exceptions/TransitionMissedException.java | 10 +
src/main/java/ru/bvn13/fsm/Condition.java | 20 --
.../java/ru/bvn13/fsm/ConditionBehaviour.java | 8 -
.../Exceptions/BrokenTransitionException.java | 10 -
.../Exceptions/ConditionExistsException.java | 10 -
.../fsm/Exceptions/NotInitedException.java | 13 -
.../fsm/Exceptions/StateExistsException.java | 10 -
.../Exceptions/TransitionMissedException.java | 10 -
src/main/java/ru/bvn13/fsm/FSM.java | 142 ----------
.../java/ru/bvn13/fsm/StateBehaviour.java | 20 --
.../bvn13/fsm/examples/DialogApplication.java | 108 -------
src/test/java/me/bvn13/fsm/tests/FsmTest.java | 123 ++++++++
.../fsm/tests/examples/DialogApplication.java | 77 +++++
src/test/java/ru/bvn13/fsm/tests/FsmTest.java | 71 -----
32 files changed, 763 insertions(+), 435 deletions(-)
create mode 100644 src/main/java/me/bvn13/fsm/Condition.java
create mode 100644 src/main/java/me/bvn13/fsm/ConditionBuilder.java
create mode 100644 src/main/java/me/bvn13/fsm/Fsm.java
create mode 100644 src/main/java/me/bvn13/fsm/FsmBuilder.java
create mode 100644 src/main/java/me/bvn13/fsm/SimpleFsm.java
rename src/main/java/{ru => me}/bvn13/fsm/State.java (69%)
create mode 100644 src/main/java/me/bvn13/fsm/StateBehaviour.java
create mode 100644 src/main/java/me/bvn13/fsm/StateBuilder.java
create mode 100644 src/main/java/me/bvn13/fsm/StateHandler.java
create mode 100644 src/main/java/me/bvn13/fsm/StateProcessor.java
create mode 100644 src/main/java/me/bvn13/fsm/dummy/DummyHandler.java
create mode 100644 src/main/java/me/bvn13/fsm/dummy/DummyProcessor.java
rename src/main/java/{ru/bvn13/fsm/Exceptions => me/bvn13/fsm/exceptions}/AmbiguousTransitionException.java (71%)
create mode 100644 src/main/java/me/bvn13/fsm/exceptions/BrokenTransitionException.java
create mode 100644 src/main/java/me/bvn13/fsm/exceptions/ConditionAlreadyExistsException.java
rename src/main/java/{ru/bvn13/fsm/Exceptions/FSMException.java => me/bvn13/fsm/exceptions/FsmException.java} (74%)
create mode 100644 src/main/java/me/bvn13/fsm/exceptions/NotInitializedException.java
create mode 100644 src/main/java/me/bvn13/fsm/exceptions/StateAlreadyExistsException.java
create mode 100644 src/main/java/me/bvn13/fsm/exceptions/TransitionMissedException.java
delete mode 100644 src/main/java/ru/bvn13/fsm/Condition.java
delete mode 100644 src/main/java/ru/bvn13/fsm/ConditionBehaviour.java
delete mode 100644 src/main/java/ru/bvn13/fsm/Exceptions/BrokenTransitionException.java
delete mode 100644 src/main/java/ru/bvn13/fsm/Exceptions/ConditionExistsException.java
delete mode 100644 src/main/java/ru/bvn13/fsm/Exceptions/NotInitedException.java
delete mode 100644 src/main/java/ru/bvn13/fsm/Exceptions/StateExistsException.java
delete mode 100644 src/main/java/ru/bvn13/fsm/Exceptions/TransitionMissedException.java
delete mode 100644 src/main/java/ru/bvn13/fsm/FSM.java
delete mode 100644 src/main/java/ru/bvn13/fsm/StateBehaviour.java
delete mode 100644 src/main/java/ru/bvn13/fsm/examples/DialogApplication.java
create mode 100644 src/test/java/me/bvn13/fsm/tests/FsmTest.java
create mode 100644 src/test/java/me/bvn13/fsm/tests/examples/DialogApplication.java
delete mode 100644 src/test/java/ru/bvn13/fsm/tests/FsmTest.java
diff --git a/src/main/java/me/bvn13/fsm/Condition.java b/src/main/java/me/bvn13/fsm/Condition.java
new file mode 100644
index 0000000..52816d7
--- /dev/null
+++ b/src/main/java/me/bvn13/fsm/Condition.java
@@ -0,0 +1,9 @@
+package me.bvn13.fsm;
+
+/**
+ * Condition of transitions
+ */
+@FunctionalInterface
+public interface Condition {
+ boolean check(T fsm, E event);
+}
diff --git a/src/main/java/me/bvn13/fsm/ConditionBuilder.java b/src/main/java/me/bvn13/fsm/ConditionBuilder.java
new file mode 100644
index 0000000..ccdb607
--- /dev/null
+++ b/src/main/java/me/bvn13/fsm/ConditionBuilder.java
@@ -0,0 +1,34 @@
+package me.bvn13.fsm;
+
+public class ConditionBuilder {
+
+ private final FsmBuilder fsmBuilder;
+ private String from;
+ private String to;
+ private Condition condition;
+
+ ConditionBuilder(FsmBuilder fsmBuilder) {
+ this.fsmBuilder = fsmBuilder;
+ }
+
+ public ConditionBuilder from(String from) {
+ this.from = from;
+ return this;
+ }
+
+ public ConditionBuilder to(String to) {
+ this.to = to;
+ return this;
+ }
+
+ public ConditionBuilder checking(Condition condition) {
+ this.condition = condition;
+ return this;
+ }
+
+ public FsmBuilder end() {
+ fsmBuilder.addTransition(from, to, condition);
+ return fsmBuilder;
+ }
+
+}
diff --git a/src/main/java/me/bvn13/fsm/Fsm.java b/src/main/java/me/bvn13/fsm/Fsm.java
new file mode 100644
index 0000000..5d66b3f
--- /dev/null
+++ b/src/main/java/me/bvn13/fsm/Fsm.java
@@ -0,0 +1,267 @@
+package me.bvn13.fsm;
+
+import me.bvn13.fsm.exceptions.AmbiguousTransitionException;
+import me.bvn13.fsm.exceptions.BrokenTransitionException;
+import me.bvn13.fsm.exceptions.ConditionAlreadyExistsException;
+import me.bvn13.fsm.exceptions.FsmException;
+import me.bvn13.fsm.exceptions.NotInitializedException;
+import me.bvn13.fsm.exceptions.StateAlreadyExistsException;
+import me.bvn13.fsm.exceptions.TransitionMissedException;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+
+/**
+ *
+ * Final State Machine
+ *
+ *
+ *
+ * Each state machine must be prepared with:
+ * - Initial state
+ * - Finish state - may be not several states
+ * - Intermediate states - optionally
+ * - All transitions needed
+ *
+ *
+ *
+ *
+ *
+ * Each {@link State} may be specified with handlers:
+ * - Before handler - is called right before FSM changes INTO this state
+ * - After handler - is called right before FSM changes FROM this state to another
+ * - Processor - the method to process events
+ *
+ *
+ *
+ *
+ * Transition is the Rule provides FSM the possibility to change between states.
+ *
+ * Each transition must be determined in terms of:
+ *
+ * - From State - mandatory
+ * - To State - mandatory
+ * - Condition - optionally. If specified, the FSM will check the condition in order to check the possibility
+ * to change from FROM State into TO State
+ *
+ *
+ *
+ *
+ * Simple way to use it - to construct an inherited class specified with the type of events to be processed
+ * during transitions.
+ *
+ * {@code
+ * SimpleFsm simpleFsm = SimpleFsm
+ * ., String>withStates(SimpleFsm::new)
+ * .from("init")
+ * .withBeforeHandler(fsm -> initBefore.set(true))
+ * .withAfterHandler(fsm -> initAfter.set(true))
+ * .withProcessor((fsm, event) -> initProcess.set(true))
+ * .end()
+ * .finish("finish")
+ * .withBeforeHandler(fsm -> finishBefore.set(true))
+ * .withAfterHandler(fsm -> finishAfter.set(true))
+ * .withProcessor((fsm, event) -> finishProcess.set(true))
+ * .end()
+ * .withTransition()
+ * .from("init")
+ * .to("finish")
+ * .checking((fsm, event) -> true)
+ * .end()
+ * .create();
+ * }
+ *
+ *
+ * {@link SimpleFsm}
+ */
+public class Fsm {
+
+ private boolean done = false;
+ private State initialState;
+ private State currentState;
+ private State previousState;
+ private final Map> states = new HashMap<>();
+ private final Map>> transitions = new HashMap<>();
+
+ /**
+ * Initiate a builder
+ *
+ * @param supplier the original FSM inherited class constructor. You may specify '{@code () -> new SimpleFsm()}' in parameter
+ * @return FsmBuilder
+ * @param the original FSM inherited class type
+ * @param the class type of Events to be processed
+ */
+ @SuppressWarnings("unchecked")
+ public static FsmBuilder withStates(Supplier supplier) {
+ return new FsmBuilder<>(supplier);
+ }
+
+ /**
+ * To initialize FSM into initial state
+ * @throws NotInitializedException
+ */
+ public void init() throws NotInitializedException {
+ currentState = initialState;
+ if (currentState == null) {
+ throw new NotInitializedException();
+ }
+ done = false;
+ previousState = null;
+ currentState.beforeEvent();
+ }
+
+ /**
+ * Returns current state
+ *
+ * @return {@link State}
+ */
+ public State getCurrentState() {
+ return currentState;
+ }
+
+ /**
+ * Returns previous state
+ *
+ * @return {@link State}
+ */
+ public State getPreviousState() {
+ return previousState;
+ }
+
+ /**
+ * Main method to handle every event
+ *
+ * @param event event
+ * @throws FsmException
+ */
+ @SuppressWarnings("unchecked")
+ public void process(E event) throws FsmException {
+ if (done) {
+ return;
+ }
+ currentState.afterEvent();
+ if (currentState.isFinish()) {
+ done = true;
+ return;
+ }
+ if (!transitions.containsKey(currentState.getName())) {
+ throw new TransitionMissedException(currentState.getName());
+ }
+ Map> conditions = transitions.get(currentState.getName());
+ List nextStates = new ArrayList<>();
+ for (String key : conditions.keySet()) {
+ if (conditions.get(key) == null) {
+ nextStates.add(key);
+ } else if(conditions.get(key).check((T) this, event)) {
+ nextStates.add(key);
+ }
+ }
+ if (nextStates.size() > 1) {
+ throw new AmbiguousTransitionException(currentState.getName(), nextStates);
+ }
+ if (nextStates.size() == 0) {
+ throw new BrokenTransitionException(currentState.getName());
+ }
+ State nextState = states.get(nextStates.get(0));
+ nextState(nextState, event);
+ }
+
+ /**
+ * To specify initial state
+ *
+ * @param state {@link State}
+ * @throws FsmException
+ */
+ public void initState(State state) throws FsmException {
+ state.setFSM(this);
+ addState(state);
+ initialState = state;
+ }
+
+ /**
+ * To add another state
+ *
+ * @param state {@link State}
+ * @throws FsmException
+ */
+ public void addState(State state) throws FsmException {
+ checkStateExist(state.getName());
+ state.setFSM(this);
+ this.states.put(state.getName(), state);
+ }
+
+ /**
+ * To set the transition up
+ *
+ * @param fromState {@link State}
+ * @param toState {@link State}
+ * @throws FsmException
+ */
+ public void addTransition(String fromState, String toState) throws FsmException {
+ storeTransition(fromState, toState, null);
+ }
+
+ /**
+ * To set the transition up
+ *
+ * @param fromState {@link State}
+ * @param toState {@link State}
+ * @param condition {@link Condition}
+ * @throws FsmException
+ */
+ public void addTransition(String fromState, String toState, Condition condition) throws FsmException {
+ storeTransition(fromState, toState, condition);
+ }
+
+ /**
+ * To set the transition up
+ *
+ * @param fromState {@link State}
+ * @param toState {@link State}
+ * @throws FsmException
+ */
+ public void addTransition(String fromState, State toState) throws FsmException {
+ addState(toState);
+ addTransition(fromState, toState.getName());
+ }
+
+ /**
+ * To set the transition up
+ *
+ * @param fromState {@link State}
+ * @param toState {@link State}
+ * @param condition {@link Condition}
+ * @throws FsmException
+ */
+ public void addTransition(String fromState, State toState, Condition condition) throws FsmException {
+ addState(toState);
+ addTransition(fromState, toState.getName(), condition);
+ }
+
+ private void nextState(State state, E event) {
+ state.beforeEvent();
+ previousState = currentState;
+ currentState = state;
+ currentState.process(event);
+ }
+
+ private void checkStateExist(String name) throws StateAlreadyExistsException {
+ if (this.states.containsKey(name)) {
+ throw new StateAlreadyExistsException(name);
+ }
+ }
+
+ private void storeTransition(String fromState, String toState, Condition condition) throws FsmException {
+ if (!transitions.containsKey(fromState)) {
+ transitions.put(fromState, new HashMap<>());
+ }
+ if (transitions.get(fromState).containsKey(toState)) {
+ throw new ConditionAlreadyExistsException(fromState, toState);
+ }
+ transitions.get(fromState).put(toState, condition);
+ }
+
+}
diff --git a/src/main/java/me/bvn13/fsm/FsmBuilder.java b/src/main/java/me/bvn13/fsm/FsmBuilder.java
new file mode 100644
index 0000000..42862e1
--- /dev/null
+++ b/src/main/java/me/bvn13/fsm/FsmBuilder.java
@@ -0,0 +1,52 @@
+package me.bvn13.fsm;
+
+import java.util.function.Supplier;
+
+public class FsmBuilder {
+
+ private final T fsm;
+
+ FsmBuilder(Supplier supplier) {
+ fsm = supplier.get();
+ }
+
+ public StateBuilder from(String state) {
+ return new StateBuilder<>(this, state, true, false);
+ }
+
+ public StateBuilder state(String state) {
+ return new StateBuilder<>(this, state, false, false);
+ }
+
+ public StateBuilder finish(String state) {
+ return new StateBuilder<>(this, state, false, true);
+ }
+
+ public ConditionBuilder withTransition() {
+ return new ConditionBuilder<>(this);
+ }
+
+ public T create() {
+ fsm.init();
+ return fsm;
+ }
+
+ T getFsm() {
+ return fsm;
+ }
+
+ @SuppressWarnings("unchecked")
+ void addState(State state, boolean isInitial) {
+ if (isInitial) {
+ fsm.initState(state);
+ } else {
+ fsm.addState(state);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ void addTransition(String from, String to, Condition condition) {
+ fsm.addTransition(from, to, condition);
+ }
+
+}
diff --git a/src/main/java/me/bvn13/fsm/SimpleFsm.java b/src/main/java/me/bvn13/fsm/SimpleFsm.java
new file mode 100644
index 0000000..300ece4
--- /dev/null
+++ b/src/main/java/me/bvn13/fsm/SimpleFsm.java
@@ -0,0 +1,9 @@
+package me.bvn13.fsm;
+
+/**
+ * Simple FSM
+ * @param the type of Events
+ */
+public class SimpleFsm extends Fsm {
+
+}
diff --git a/src/main/java/ru/bvn13/fsm/State.java b/src/main/java/me/bvn13/fsm/State.java
similarity index 69%
rename from src/main/java/ru/bvn13/fsm/State.java
rename to src/main/java/me/bvn13/fsm/State.java
index 86ab1f5..a101c86 100644
--- a/src/main/java/ru/bvn13/fsm/State.java
+++ b/src/main/java/me/bvn13/fsm/State.java
@@ -1,18 +1,18 @@
-package ru.bvn13.fsm;
+package me.bvn13.fsm;
/**
* Created by bvn13 on 28.12.2017.
*/
-public class State implements StateBehaviour {
+public class State implements StateBehaviour {
- private String name;
+ private final String name;
private boolean finish;
- private FSM fsm;
- protected void setFSM(FSM fsm) {
+ private Fsm fsm;
+ protected void setFSM(Fsm fsm) {
this.fsm = fsm;
}
- public FSM getFSM() {
+ public Fsm getFSM() {
return this.fsm;
}
diff --git a/src/main/java/me/bvn13/fsm/StateBehaviour.java b/src/main/java/me/bvn13/fsm/StateBehaviour.java
new file mode 100644
index 0000000..7e7f29c
--- /dev/null
+++ b/src/main/java/me/bvn13/fsm/StateBehaviour.java
@@ -0,0 +1,20 @@
+package me.bvn13.fsm;
+
+/**
+ * State behavior
+ */
+public interface StateBehaviour {
+
+ default void beforeEvent() {
+
+ }
+
+ default void afterEvent() {
+
+ }
+
+ default void process(E event) {
+
+ }
+
+}
diff --git a/src/main/java/me/bvn13/fsm/StateBuilder.java b/src/main/java/me/bvn13/fsm/StateBuilder.java
new file mode 100644
index 0000000..5af28a8
--- /dev/null
+++ b/src/main/java/me/bvn13/fsm/StateBuilder.java
@@ -0,0 +1,59 @@
+package me.bvn13.fsm;
+
+import me.bvn13.fsm.dummy.DummyHandler;
+import me.bvn13.fsm.dummy.DummyProcessor;
+
+public class StateBuilder {
+
+ private final FsmBuilder fsmBuilder;
+ private final String name;
+ private final boolean isInitial;
+ private final boolean isFinishing;
+ private StateHandler beforeHandler = new DummyHandler<>();
+ private StateHandler afterHandler = new DummyHandler<>();
+ private StateProcessor processor = new DummyProcessor<>();
+
+ StateBuilder(FsmBuilder fsmBuilder, String name, boolean isInitial, boolean isFinishing) {
+ assert !(isInitial && isFinishing);
+ this.fsmBuilder = fsmBuilder;
+ this.name = name;
+ this.isInitial = isInitial;
+ this.isFinishing = isFinishing;
+ }
+
+ public FsmBuilder end() {
+ fsmBuilder.addState(new State(name, isFinishing) {
+ @Override
+ public void beforeEvent() {
+ beforeHandler.handle(fsmBuilder.getFsm());
+ }
+
+ @Override
+ public void afterEvent() {
+ afterHandler.handle(fsmBuilder.getFsm());
+ }
+
+ @Override
+ public void process(E event) {
+ processor.process(fsmBuilder.getFsm(), event);
+ }
+ }, isInitial);
+ return fsmBuilder;
+ }
+
+ public StateBuilder withBeforeHandler(StateHandler handler) {
+ this.beforeHandler = handler;
+ return this;
+ }
+
+ public StateBuilder withAfterHandler(StateHandler handler) {
+ this.afterHandler = handler;
+ return this;
+ }
+
+ public StateBuilder withProcessor(StateProcessor processor) {
+ this.processor = processor;
+ return this;
+ }
+
+}
diff --git a/src/main/java/me/bvn13/fsm/StateHandler.java b/src/main/java/me/bvn13/fsm/StateHandler.java
new file mode 100644
index 0000000..38c2d72
--- /dev/null
+++ b/src/main/java/me/bvn13/fsm/StateHandler.java
@@ -0,0 +1,12 @@
+package me.bvn13.fsm;
+
+/**
+ * State handler
+ * @param
+ */
+@FunctionalInterface
+public interface StateHandler {
+
+ void handle(T fms);
+
+}
diff --git a/src/main/java/me/bvn13/fsm/StateProcessor.java b/src/main/java/me/bvn13/fsm/StateProcessor.java
new file mode 100644
index 0000000..f9fd70e
--- /dev/null
+++ b/src/main/java/me/bvn13/fsm/StateProcessor.java
@@ -0,0 +1,13 @@
+package me.bvn13.fsm;
+
+/**
+ * State processor
+ * @param the type of FSM inherited class
+ * @param the type of Events
+ */
+@FunctionalInterface
+public interface StateProcessor {
+
+ void process(T fms, E event);
+
+}
diff --git a/src/main/java/me/bvn13/fsm/dummy/DummyHandler.java b/src/main/java/me/bvn13/fsm/dummy/DummyHandler.java
new file mode 100644
index 0000000..9559db3
--- /dev/null
+++ b/src/main/java/me/bvn13/fsm/dummy/DummyHandler.java
@@ -0,0 +1,11 @@
+package me.bvn13.fsm.dummy;
+
+import me.bvn13.fsm.Fsm;
+import me.bvn13.fsm.StateHandler;
+
+public class DummyHandler implements StateHandler {
+ @Override
+ public void handle(T fms) {
+
+ }
+}
diff --git a/src/main/java/me/bvn13/fsm/dummy/DummyProcessor.java b/src/main/java/me/bvn13/fsm/dummy/DummyProcessor.java
new file mode 100644
index 0000000..d308a73
--- /dev/null
+++ b/src/main/java/me/bvn13/fsm/dummy/DummyProcessor.java
@@ -0,0 +1,11 @@
+package me.bvn13.fsm.dummy;
+
+import me.bvn13.fsm.Fsm;
+import me.bvn13.fsm.StateProcessor;
+
+public class DummyProcessor implements StateProcessor {
+ @Override
+ public void process(T fms, E event) {
+
+ }
+}
diff --git a/src/main/java/ru/bvn13/fsm/Exceptions/AmbiguousTransitionException.java b/src/main/java/me/bvn13/fsm/exceptions/AmbiguousTransitionException.java
similarity index 71%
rename from src/main/java/ru/bvn13/fsm/Exceptions/AmbiguousTransitionException.java
rename to src/main/java/me/bvn13/fsm/exceptions/AmbiguousTransitionException.java
index ce2d4ac..ee6e210 100644
--- a/src/main/java/ru/bvn13/fsm/Exceptions/AmbiguousTransitionException.java
+++ b/src/main/java/me/bvn13/fsm/exceptions/AmbiguousTransitionException.java
@@ -1,11 +1,11 @@
-package ru.bvn13.fsm.Exceptions;
+package me.bvn13.fsm.exceptions;
import java.util.List;
/**
- * Created by bvn13 on 28.12.2017.
+ * is thrown if there are more than 1 appropriate transition from current state
*/
-public class AmbiguousTransitionException extends FSMException {
+public class AmbiguousTransitionException extends FsmException {
public AmbiguousTransitionException(String message) {
super(message);
}
diff --git a/src/main/java/me/bvn13/fsm/exceptions/BrokenTransitionException.java b/src/main/java/me/bvn13/fsm/exceptions/BrokenTransitionException.java
new file mode 100644
index 0000000..7669cf4
--- /dev/null
+++ b/src/main/java/me/bvn13/fsm/exceptions/BrokenTransitionException.java
@@ -0,0 +1,10 @@
+package me.bvn13.fsm.exceptions;
+
+/**
+ * is thrown if there are no further states from the current one
+ */
+public class BrokenTransitionException extends FsmException {
+ public BrokenTransitionException(String from) {
+ super("Broken transition from: "+from);
+ }
+}
diff --git a/src/main/java/me/bvn13/fsm/exceptions/ConditionAlreadyExistsException.java b/src/main/java/me/bvn13/fsm/exceptions/ConditionAlreadyExistsException.java
new file mode 100644
index 0000000..0f7cfc5
--- /dev/null
+++ b/src/main/java/me/bvn13/fsm/exceptions/ConditionAlreadyExistsException.java
@@ -0,0 +1,10 @@
+package me.bvn13.fsm.exceptions;
+
+/**
+ * is thrown in case of adding a transition FROM->TO, but it is already defined
+ */
+public class ConditionAlreadyExistsException extends FsmException {
+ public ConditionAlreadyExistsException(String from, String to) {
+ super(String.format("Condition exists: %s -> %s", from, to));
+ }
+}
diff --git a/src/main/java/ru/bvn13/fsm/Exceptions/FSMException.java b/src/main/java/me/bvn13/fsm/exceptions/FsmException.java
similarity index 74%
rename from src/main/java/ru/bvn13/fsm/Exceptions/FSMException.java
rename to src/main/java/me/bvn13/fsm/exceptions/FsmException.java
index 55c6cac..5205ae9 100644
--- a/src/main/java/ru/bvn13/fsm/Exceptions/FSMException.java
+++ b/src/main/java/me/bvn13/fsm/exceptions/FsmException.java
@@ -1,14 +1,14 @@
-package ru.bvn13.fsm.Exceptions;
+package me.bvn13.fsm.exceptions;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
- * Created by bvn13 on 28.12.2017.
+ * Parent FSM exception class
*/
-public class FSMException extends RuntimeException {
+public class FsmException extends RuntimeException {
protected String message;
- public FSMException(String message) {
+ public FsmException(String message) {
this.message = message;
}
protected String getStackTraceString() {
diff --git a/src/main/java/me/bvn13/fsm/exceptions/NotInitializedException.java b/src/main/java/me/bvn13/fsm/exceptions/NotInitializedException.java
new file mode 100644
index 0000000..a002848
--- /dev/null
+++ b/src/main/java/me/bvn13/fsm/exceptions/NotInitializedException.java
@@ -0,0 +1,13 @@
+package me.bvn13.fsm.exceptions;
+
+/**
+ * is thrown in case of using FSM before being initialized
+ */
+public class NotInitializedException extends FsmException {
+ public NotInitializedException(String message) {
+ super(message);
+ }
+ public NotInitializedException() {
+ super("FSM is not inited");
+ }
+}
diff --git a/src/main/java/me/bvn13/fsm/exceptions/StateAlreadyExistsException.java b/src/main/java/me/bvn13/fsm/exceptions/StateAlreadyExistsException.java
new file mode 100644
index 0000000..13dc360
--- /dev/null
+++ b/src/main/java/me/bvn13/fsm/exceptions/StateAlreadyExistsException.java
@@ -0,0 +1,10 @@
+package me.bvn13.fsm.exceptions;
+
+/**
+ * is thrown when trying to add the state which was already added before
+ */
+public class StateAlreadyExistsException extends FsmException {
+ public StateAlreadyExistsException(String stateName) {
+ super(String.format("State exist: %s", stateName));
+ }
+}
diff --git a/src/main/java/me/bvn13/fsm/exceptions/TransitionMissedException.java b/src/main/java/me/bvn13/fsm/exceptions/TransitionMissedException.java
new file mode 100644
index 0000000..9e81797
--- /dev/null
+++ b/src/main/java/me/bvn13/fsm/exceptions/TransitionMissedException.java
@@ -0,0 +1,10 @@
+package me.bvn13.fsm.exceptions;
+
+/**
+ * is thrown if there are no registered transitions from current state
+ */
+public class TransitionMissedException extends FsmException {
+ public TransitionMissedException(String from) {
+ super(String.format("Missed conditions from: %s", from));
+ }
+}
diff --git a/src/main/java/ru/bvn13/fsm/Condition.java b/src/main/java/ru/bvn13/fsm/Condition.java
deleted file mode 100644
index b1dbfef..0000000
--- a/src/main/java/ru/bvn13/fsm/Condition.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package ru.bvn13.fsm;
-
-/**
- * Created by bvn13 on 28.12.2017.
- */
-public class Condition implements ConditionBehaviour {
-
- private FSM fsm;
- protected void setFSM(FSM fsm) {
- this.fsm = fsm;
- }
- public FSM getFSM() {
- return this.fsm;
- }
-
- @Override
- public boolean check() {
- return false;
- }
-}
diff --git a/src/main/java/ru/bvn13/fsm/ConditionBehaviour.java b/src/main/java/ru/bvn13/fsm/ConditionBehaviour.java
deleted file mode 100644
index 3345ec3..0000000
--- a/src/main/java/ru/bvn13/fsm/ConditionBehaviour.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package ru.bvn13.fsm;
-
-/**
- * Created by bvn13 on 28.12.2017.
- */
-public interface ConditionBehaviour {
- boolean check();
-}
diff --git a/src/main/java/ru/bvn13/fsm/Exceptions/BrokenTransitionException.java b/src/main/java/ru/bvn13/fsm/Exceptions/BrokenTransitionException.java
deleted file mode 100644
index 05fbc0d..0000000
--- a/src/main/java/ru/bvn13/fsm/Exceptions/BrokenTransitionException.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package ru.bvn13.fsm.Exceptions;
-
-/**
- * Created by bvn13 on 28.12.2017.
- */
-public class BrokenTransitionException extends FSMException {
- public BrokenTransitionException(String from) {
- super("Broken transition from: "+from);
- }
-}
diff --git a/src/main/java/ru/bvn13/fsm/Exceptions/ConditionExistsException.java b/src/main/java/ru/bvn13/fsm/Exceptions/ConditionExistsException.java
deleted file mode 100644
index 3bff311..0000000
--- a/src/main/java/ru/bvn13/fsm/Exceptions/ConditionExistsException.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package ru.bvn13.fsm.Exceptions;
-
-/**
- * Created by bvn13 on 28.12.2017.
- */
-public class ConditionExistsException extends FSMException {
- public ConditionExistsException(String from, String to) {
- super(String.format("Condition exists: %s -> %s", from, to));
- }
-}
diff --git a/src/main/java/ru/bvn13/fsm/Exceptions/NotInitedException.java b/src/main/java/ru/bvn13/fsm/Exceptions/NotInitedException.java
deleted file mode 100644
index 22b42b9..0000000
--- a/src/main/java/ru/bvn13/fsm/Exceptions/NotInitedException.java
+++ /dev/null
@@ -1,13 +0,0 @@
-package ru.bvn13.fsm.Exceptions;
-
-/**
- * Created by bvn13 on 28.12.2017.
- */
-public class NotInitedException extends FSMException {
- public NotInitedException(String message) {
- super(message);
- }
- public NotInitedException() {
- super("FSM is not inited");
- }
-}
diff --git a/src/main/java/ru/bvn13/fsm/Exceptions/StateExistsException.java b/src/main/java/ru/bvn13/fsm/Exceptions/StateExistsException.java
deleted file mode 100644
index 7dd24c0..0000000
--- a/src/main/java/ru/bvn13/fsm/Exceptions/StateExistsException.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package ru.bvn13.fsm.Exceptions;
-
-/**
- * Created by bvn13 on 28.12.2017.
- */
-public class StateExistsException extends FSMException {
- public StateExistsException(String stateName) {
- super(String.format("State exist: %s", stateName));
- }
-}
diff --git a/src/main/java/ru/bvn13/fsm/Exceptions/TransitionMissedException.java b/src/main/java/ru/bvn13/fsm/Exceptions/TransitionMissedException.java
deleted file mode 100644
index 1c7d3b7..0000000
--- a/src/main/java/ru/bvn13/fsm/Exceptions/TransitionMissedException.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package ru.bvn13.fsm.Exceptions;
-
-/**
- * Created by bvn13 on 28.12.2017.
- */
-public class TransitionMissedException extends FSMException {
- public TransitionMissedException(String from) {
- super(String.format("Missed conditions from: %s", from));
- }
-}
diff --git a/src/main/java/ru/bvn13/fsm/FSM.java b/src/main/java/ru/bvn13/fsm/FSM.java
deleted file mode 100644
index 4195262..0000000
--- a/src/main/java/ru/bvn13/fsm/FSM.java
+++ /dev/null
@@ -1,142 +0,0 @@
-package ru.bvn13.fsm;
-
-import ru.bvn13.fsm.Exceptions.*;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * Created by bvn13 on 28.12.2017.
- */
-public class FSM {
-
- private boolean done = false;
- private State initialState;
- private State currentState;
- private State previousState;
- private final Map states;
- private final Map> transitions;
-
- public FSM() {
- this.states = new HashMap<>();
- this.transitions = new HashMap<>();
- }
-
- public void init() throws NotInitedException {
- currentState = initialState;
- if (currentState == null) {
- throw new NotInitedException();
- }
- done = false;
- previousState = null;
- currentState.beforeEvent();
- currentState.process();
- }
-
- public State getCurrentState() {
- return currentState;
- }
-
- public State getPreviousState() {
- return previousState;
- }
-
- public void next() throws FSMException {
- if (done) {
- return;
- }
- currentState.afterEvent();
- if (currentState.isFinish()) {
- done = true;
- return;
- }
- if (!transitions.containsKey(currentState.getName())) {
- throw new TransitionMissedException(currentState.getName());
- }
- Map conditions = transitions.get(currentState.getName());
- List nextStates = new ArrayList<>();
- for (String key : conditions.keySet()) {
- if (conditions.get(key) == null) {
- nextStates.add(key);
- } else if(conditions.get(key).check()) {
- nextStates.add(key);
- }
- }
- if (nextStates.size() > 1) {
- throw new AmbiguousTransitionException(currentState.getName(), nextStates);
- }
- if (nextStates.size() == 0) {
- throw new BrokenTransitionException(currentState.getName());
- }
- State nextState = states.get(nextStates.get(0));
- nextState(nextState);
- }
-
- public void prev() throws FSMException {
- if (done) {
- return;
- }
- currentState.afterEvent();
- if (getPreviousState() == null) {
- return;
- }
- nextState(getPreviousState());
- }
-
- public void initState(State state) throws FSMException {
- state.setFSM(this);
- addState(state);
- initialState = state;
- }
-
- public void addState(State state) throws FSMException {
- checkStateExist(state.getName());
- state.setFSM(this);
- this.states.put(state.getName(), state);
- }
-
- public void addTransition(String fromState, String toState) throws FSMException {
- storeTransition(fromState, toState, null);
- }
-
- public void addTransition(String fromState, String toState, Condition condition) throws FSMException {
- condition.setFSM(this);
- storeTransition(fromState, toState, condition);
- }
-
- public void addTransition(String fromState, State toState) throws FSMException {
- addState(toState);
- addTransition(fromState, toState.getName());
- }
-
- public void addTransition(String fromState, State toState, Condition condition) throws FSMException {
- addState(toState);
- addTransition(fromState, toState.getName(), condition);
- }
-
- private void nextState(State state) {
- state.beforeEvent();
- previousState = currentState;
- currentState = state;
- currentState.process();
- }
-
- private void checkStateExist(String name) throws StateExistsException {
- if (this.states.containsKey(name)) {
- throw new StateExistsException(name);
- }
- }
-
- private void storeTransition(String fromState, String toState, Condition condition) throws FSMException {
- if (!transitions.containsKey(fromState)) {
- transitions.put(fromState, new HashMap<>());
- }
- if (transitions.get(fromState).containsKey(toState)) {
- throw new ConditionExistsException(fromState, toState);
- }
- transitions.get(fromState).put(toState, condition);
- }
-
-}
diff --git a/src/main/java/ru/bvn13/fsm/StateBehaviour.java b/src/main/java/ru/bvn13/fsm/StateBehaviour.java
deleted file mode 100644
index 2b9597f..0000000
--- a/src/main/java/ru/bvn13/fsm/StateBehaviour.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package ru.bvn13.fsm;
-
-/**
- * Created by bvn13 on 28.12.2017.
- */
-public interface StateBehaviour {
-
- default void beforeEvent() {
-
- }
-
- default void afterEvent() {
-
- }
-
- default void process() {
-
- }
-
-}
diff --git a/src/main/java/ru/bvn13/fsm/examples/DialogApplication.java b/src/main/java/ru/bvn13/fsm/examples/DialogApplication.java
deleted file mode 100644
index 77109a3..0000000
--- a/src/main/java/ru/bvn13/fsm/examples/DialogApplication.java
+++ /dev/null
@@ -1,108 +0,0 @@
-package ru.bvn13.fsm.examples;
-
-import ru.bvn13.fsm.Condition;
-import ru.bvn13.fsm.Exceptions.FSMException;
-import ru.bvn13.fsm.Exceptions.NotInitedException;
-import ru.bvn13.fsm.FSM;
-import ru.bvn13.fsm.State;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStreamReader;
-
-/**
- * Created by bvn13 on 28.12.2017.
- */
-public class DialogApplication {
-
- public static class DialogFSM extends FSM {
- public String command;
- private BufferedReader br = null;
-
- public void readCommand() {
- System.out.print("Command: ");
- br = new BufferedReader(new InputStreamReader(System.in));
- try {
- command = br.readLine();
- } catch (IOException e) {
- e.printStackTrace();
- System.exit(-1);
- }
- }
- }
-
- public static void main(String argc[]) {
-
- DialogFSM fsm = new DialogFSM();
-
- try {
- fsm.initState(new State("greeting") {
- @Override
- public void beforeEvent() {
- System.out.println("Welcome!");
- }
- @Override
- public void process() {
- fsm.readCommand();
- try {
- fsm.next();
- } catch (FSMException e) {
- e.printStackTrace();
- System.exit(-1);
- }
- }
- @Override
- public void afterEvent() {
- System.out.println("Your command: "+fsm.command);
- }
- });
- } catch (FSMException e) {
- e.printStackTrace();
- }
-
- try {
- fsm.addTransition("greeting", new State("hello", true) {
- @Override
- public void beforeEvent() {
- System.out.println("Hello!");
- }
- @Override
- public void process() {
-
- }
- @Override
- public void afterEvent() {
- System.out.println("DONE");
- }
- }, new Condition() {
- @Override
- public boolean check() {
- return fsm.command.equalsIgnoreCase("hello");
- }
- });
- } catch (FSMException e) {
- e.printStackTrace();
- }
-
-
- try {
- fsm.addTransition("greeting", "greeting", new Condition() {
- @Override
- public boolean check() {
- return !fsm.command.equalsIgnoreCase("hello");
- }
- });
- } catch (FSMException e) {
- e.printStackTrace();
- }
-
-
- try {
- fsm.init();
- } catch (NotInitedException e) {
- e.printStackTrace();
- }
-
- }
-
-}
diff --git a/src/test/java/me/bvn13/fsm/tests/FsmTest.java b/src/test/java/me/bvn13/fsm/tests/FsmTest.java
new file mode 100644
index 0000000..818ff16
--- /dev/null
+++ b/src/test/java/me/bvn13/fsm/tests/FsmTest.java
@@ -0,0 +1,123 @@
+package me.bvn13.fsm.tests;
+
+import me.bvn13.fsm.ConditionBuilder;
+import me.bvn13.fsm.Fsm;
+import me.bvn13.fsm.FsmBuilder;
+import me.bvn13.fsm.SimpleFsm;
+import me.bvn13.fsm.State;
+import me.bvn13.fsm.StateBuilder;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Created by bvn13 on 28.12.2017.
+ */
+public class FsmTest {
+
+ public static class NamedFsm extends Fsm {
+
+ public NamedFsm() {
+ super();
+ }
+
+ private String name;
+
+ public NamedFsm setName(String name) {
+ this.name = name;
+ return this;
+ }
+
+ public String toString() {
+ return this.name;
+ }
+ }
+
+
+ @Test
+ public void creatingFSM() {
+
+ NamedFsm namedFsm = (new NamedFsm()).setName("TEST FSM");
+
+ AtomicBoolean initStatedProcessed = new AtomicBoolean(false);
+ AtomicBoolean firstStatedProcessed = new AtomicBoolean(false);
+ AtomicBoolean anotherStatedProcessed = new AtomicBoolean(false);
+
+ namedFsm.initState(new State("init") {
+ @Override
+ public void process(String event) {
+ initStatedProcessed.set(true);
+ }
+ });
+
+ namedFsm.addTransition("init", new State("first", true) {
+ @Override
+ public void process(String event) {
+ firstStatedProcessed.set(true);
+ }
+ });
+
+ namedFsm.addTransition("init", new State("another", true) {
+ @Override
+ public void process(String event) {
+ anotherStatedProcessed.set(true);
+ }
+ }, (fsm, event) -> false);
+
+ namedFsm.init();
+ namedFsm.process(null);
+
+ Assert.assertEquals("first", namedFsm.getCurrentState().getName());
+ Assert.assertTrue(initStatedProcessed.get());
+ Assert.assertTrue(firstStatedProcessed.get());
+ Assert.assertFalse(anotherStatedProcessed.get());
+ }
+
+ @Test
+ public void newSyntax() {
+
+ AtomicBoolean initBefore = new AtomicBoolean(false);
+ AtomicBoolean initAfter = new AtomicBoolean(false);
+ AtomicBoolean initProcess = new AtomicBoolean(false);
+ AtomicBoolean finishBefore = new AtomicBoolean(false);
+ AtomicBoolean finishAfter = new AtomicBoolean(false);
+ AtomicBoolean finishProcess = new AtomicBoolean(false);
+
+ // @formatter:off
+
+ SimpleFsm simpleFsm = SimpleFsm
+ ., String>withStates(SimpleFsm::new)
+ .from("init")
+ .withBeforeHandler(fsm -> initBefore.set(true))
+ .withAfterHandler(fsm -> initAfter.set(true))
+ .withProcessor((fsm, event) -> initProcess.set(true))
+ .end()
+ .finish("finish")
+ .withBeforeHandler(fsm -> finishBefore.set(true))
+ .withAfterHandler(fsm -> finishAfter.set(true))
+ .withProcessor((fsm, event) -> finishProcess.set(true))
+ .end()
+ .withTransition()
+ .from("init")
+ .to("finish")
+ .checking((fsm, event) -> true)
+ .end()
+ .create()
+ ;
+
+ // @formatter:on
+
+ simpleFsm.process("");
+
+ Assert.assertEquals("finish", simpleFsm.getCurrentState().getName());
+ Assert.assertTrue(initBefore.get());
+ Assert.assertTrue(initAfter.get());
+ Assert.assertFalse(initProcess.get());
+ Assert.assertTrue(finishBefore.get());
+ Assert.assertFalse(finishAfter.get());
+ Assert.assertTrue(finishProcess.get());
+
+ }
+
+}
diff --git a/src/test/java/me/bvn13/fsm/tests/examples/DialogApplication.java b/src/test/java/me/bvn13/fsm/tests/examples/DialogApplication.java
new file mode 100644
index 0000000..75b53e2
--- /dev/null
+++ b/src/test/java/me/bvn13/fsm/tests/examples/DialogApplication.java
@@ -0,0 +1,77 @@
+package me.bvn13.fsm.tests.examples;
+
+import me.bvn13.fsm.Fsm;
+import me.bvn13.fsm.State;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+/**
+ * Created by bvn13 on 28.12.2017.
+ */
+public class DialogApplication {
+
+ public static class DialogFSM extends Fsm {
+ public String command;
+
+ public void readCommand() {
+ System.out.print("Command: ");
+ BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
+ try {
+ command = br.readLine();
+ } catch (IOException e) {
+ e.printStackTrace();
+ System.exit(-1);
+ }
+ }
+ }
+
+ public static void main(String[] argv) {
+
+ DialogFSM fsm = new DialogFSM();
+
+ fsm.initState(new State("greeting") {
+ @Override
+ public void beforeEvent() {
+ System.out.println("Welcome!");
+ }
+
+ @Override
+ public void process(String event) {
+ fsm.readCommand();
+ fsm.process(event);
+ }
+
+ @Override
+ public void afterEvent() {
+ System.out.println("Your command: " + fsm.command);
+ }
+ });
+
+ fsm.addTransition("greeting", new State("hello", true) {
+ @Override
+ public void beforeEvent() {
+ System.out.println("Hello!");
+ }
+
+ @Override
+ public void process(String event) {
+
+ }
+
+ @Override
+ public void afterEvent() {
+ System.out.println("DONE");
+ }
+ }, (fsm12, event) -> fsm12.command.equalsIgnoreCase("hello"));
+
+
+ fsm.addTransition("greeting", "greeting", (fsm1, event) -> !fsm1.command.equalsIgnoreCase("hello"));
+
+
+ fsm.init();
+
+ }
+
+}
diff --git a/src/test/java/ru/bvn13/fsm/tests/FsmTest.java b/src/test/java/ru/bvn13/fsm/tests/FsmTest.java
deleted file mode 100644
index 9032d4d..0000000
--- a/src/test/java/ru/bvn13/fsm/tests/FsmTest.java
+++ /dev/null
@@ -1,71 +0,0 @@
-package ru.bvn13.fsm.tests;
-
-import org.junit.Assert;
-import org.junit.Test;
-import ru.bvn13.fsm.Condition;
-import ru.bvn13.fsm.FSM;
-import ru.bvn13.fsm.State;
-
-/**
- * Created by bvn13 on 28.12.2017.
- */
-public class FsmTest {
-
- public static class NamedFSM extends FSM {
-
- public NamedFSM() {
- super();
- }
-
- private String name;
-
- public NamedFSM setName(String name) {
- this.name = name;
- return this;
- }
-
- public String toString() {
- return this.name;
- }
- }
-
-
- @Test
- public void creatingFSM() {
-
- NamedFSM fsm = (new NamedFSM()).setName("TEST FSM");
-
- fsm.initState(new State("init") {
- @Override
- public void process() {
- System.out.println("" + fsm + " -> " + getName() + ": processed init");
- }
- });
-
- fsm.addTransition("init", new State("first", true) {
- @Override
- public void process() {
- System.out.println("" + fsm + " -> " + getName() + ": processed first");
- }
- });
-
- fsm.addTransition("init", new State("another", true) {
- @Override
- public void process() {
- System.out.println("" + fsm + " -> " + getName() + ": processed first");
- }
- }, new Condition() {
- @Override
- public boolean check() {
- return false;
- }
- });
-
- fsm.init();
- fsm.next();
-
- Assert.assertEquals("first", fsm.getCurrentState().getName());
-
- }
-
-}