From bb085efa88cfe37cb3b0594a21e7e2865b5767e2 Mon Sep 17 00:00:00 2001 From: Vyacheslav Boyko Date: Tue, 12 Jul 2022 23:27:20 +0300 Subject: [PATCH] added possibility to start state machine from custom state --- src/main/java/me/bvn13/fsm/Fsm.java | 16 +++++ src/main/java/me/bvn13/fsm/FsmBuilder.java | 5 ++ .../AmbiguousTransitionException.java | 15 +++-- .../me/bvn13/fsm/exceptions/FsmException.java | 17 ++--- .../exceptions/NotInitializedException.java | 7 +- src/test/java/me/bvn13/fsm/tests/FsmTest.java | 67 ++++++++++++++++++- 6 files changed, 105 insertions(+), 22 deletions(-) diff --git a/src/main/java/me/bvn13/fsm/Fsm.java b/src/main/java/me/bvn13/fsm/Fsm.java index 0ad01ef..1253454 100644 --- a/src/main/java/me/bvn13/fsm/Fsm.java +++ b/src/main/java/me/bvn13/fsm/Fsm.java @@ -13,6 +13,8 @@ import java.util.List; import java.util.Map; import java.util.function.Supplier; +import static java.lang.String.format; + /** *

* Final State Machine @@ -239,6 +241,20 @@ public class Fsm { addTransition(fromState, toState.getName(), condition); } + /** + * Provides a possibility to initialize FSM in custom State + * @param name State name (must be added before) + */ + protected void setCurrentState(String name) { + try { + this.currentState = this.states.get(name); + } catch (NullPointerException e) { + throw new NotInitializedException(format("Unable to find state '%s'", name), e); + } + this.done = currentState.isFinish(); + this.currentState.beforeEvent(); + } + private void switchToNextState(E event) { if (!transitions.containsKey(currentState.getName())) { throw new TransitionMissedException(currentState.getName()); diff --git a/src/main/java/me/bvn13/fsm/FsmBuilder.java b/src/main/java/me/bvn13/fsm/FsmBuilder.java index 42862e1..8bd0631 100644 --- a/src/main/java/me/bvn13/fsm/FsmBuilder.java +++ b/src/main/java/me/bvn13/fsm/FsmBuilder.java @@ -31,6 +31,11 @@ public class FsmBuilder { return fsm; } + public T startingAt(String name) { + fsm.setCurrentState(name); + return fsm; + } + T getFsm() { return fsm; } diff --git a/src/main/java/me/bvn13/fsm/exceptions/AmbiguousTransitionException.java b/src/main/java/me/bvn13/fsm/exceptions/AmbiguousTransitionException.java index ee6e210..c10c909 100644 --- a/src/main/java/me/bvn13/fsm/exceptions/AmbiguousTransitionException.java +++ b/src/main/java/me/bvn13/fsm/exceptions/AmbiguousTransitionException.java @@ -2,6 +2,8 @@ package me.bvn13.fsm.exceptions; import java.util.List; +import static java.lang.String.format; + /** * is thrown if there are more than 1 appropriate transition from current state */ @@ -10,11 +12,14 @@ public class AmbiguousTransitionException extends FsmException { super(message); } public AmbiguousTransitionException(String from, List next) { - super(""); - String msg = ""; - for (String to : next) { - msg += (msg.length() > 0 ? ", " : "") + to; + super(format("Ambiguous transition from state %s. Candidates are: %s", from, join(next))); + } + + private static String join(List list) { + StringBuilder msg = new StringBuilder(); + for (String to : list) { + msg.append(msg.length() > 0 ? ", " : "").append(to); } - this.message = String.format("Ambiguous transition from state %s. Candidates are: %s", from, msg); + return msg.toString(); } } diff --git a/src/main/java/me/bvn13/fsm/exceptions/FsmException.java b/src/main/java/me/bvn13/fsm/exceptions/FsmException.java index 5205ae9..8a48568 100644 --- a/src/main/java/me/bvn13/fsm/exceptions/FsmException.java +++ b/src/main/java/me/bvn13/fsm/exceptions/FsmException.java @@ -1,23 +1,14 @@ package me.bvn13.fsm.exceptions; -import java.io.PrintWriter; -import java.io.StringWriter; - /** * Parent FSM exception class */ public class FsmException extends RuntimeException { - protected String message; public FsmException(String message) { - this.message = message; + super(message); } - protected String getStackTraceString() { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - this.printStackTrace(pw); - return sw.toString(); - } - public void printStackTrace() { - System.out.println(String.format("FSMException: %s / %s", message, getStackTraceString())); + + public FsmException(String message, Throwable cause) { + super(message, cause); } } diff --git a/src/main/java/me/bvn13/fsm/exceptions/NotInitializedException.java b/src/main/java/me/bvn13/fsm/exceptions/NotInitializedException.java index a002848..3afad58 100644 --- a/src/main/java/me/bvn13/fsm/exceptions/NotInitializedException.java +++ b/src/main/java/me/bvn13/fsm/exceptions/NotInitializedException.java @@ -7,7 +7,12 @@ public class NotInitializedException extends FsmException { public NotInitializedException(String message) { super(message); } + + public NotInitializedException(String message, Exception e) { + super(message, e); + } + public NotInitializedException() { - super("FSM is not inited"); + super("FSM is not initialized"); } } diff --git a/src/test/java/me/bvn13/fsm/tests/FsmTest.java b/src/test/java/me/bvn13/fsm/tests/FsmTest.java index ea75140..f79ff1e 100644 --- a/src/test/java/me/bvn13/fsm/tests/FsmTest.java +++ b/src/test/java/me/bvn13/fsm/tests/FsmTest.java @@ -109,11 +109,9 @@ public class FsmTest { .to("finish") .checking((fsm, event) -> true) .end() - .create() - ; + .create(); // @formatter:on - simpleFsm.process(""); simpleFsm.process(""); @@ -127,4 +125,67 @@ public class FsmTest { } + @Test + public void newSyntaxCustomState() { + + AtomicBoolean initBefore = new AtomicBoolean(false); + AtomicBoolean initAfter = new AtomicBoolean(false); + AtomicBoolean initProcess = new AtomicBoolean(false); + AtomicBoolean intermediateBefore = new AtomicBoolean(false); + AtomicBoolean intermediateAfter = new AtomicBoolean(false); + AtomicBoolean intermediateProcess = new AtomicBoolean(false); + AtomicBoolean finishBefore = new AtomicBoolean(false); + AtomicBoolean finishAfter = new AtomicBoolean(false); + AtomicBoolean finishProcess = new AtomicBoolean(false); + + // @formatter:off + + SimpleFsm simpleFsm = Fsm + ., String>from(SimpleFsm::new) + .withStates() + .from("init") + .withBeforeHandler(fsm -> initBefore.set(true)) + .withAfterHandler(fsm -> initAfter.set(true)) + .withProcessor((fsm, event) -> initProcess.set(true)) + .end() + .state("intermediate") + .withBeforeHandler(fsm -> intermediateBefore.set(true)) + .withAfterHandler(fsm -> intermediateAfter.set(true)) + .withProcessor((fsm, event) -> intermediateProcess.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("intermediate") + .checking((fsm, event) -> true) + .end() + .withTransition() + .from("intermediate") + .to("finish") + .checking((fsm, event) -> true) + .end() + .startingAt("intermediate") + ; + + // @formatter:on + + simpleFsm.process(""); + + Assert.assertEquals("finish", simpleFsm.getCurrentState().getName()); + Assert.assertFalse(initBefore.get()); + Assert.assertFalse(initProcess.get()); + Assert.assertFalse(initAfter.get()); + Assert.assertTrue(intermediateBefore.get()); + Assert.assertTrue(intermediateAfter.get()); + Assert.assertTrue(intermediateProcess.get()); + Assert.assertTrue(finishBefore.get()); + Assert.assertFalse(finishProcess.get()); + Assert.assertFalse(finishAfter.get()); + + } + }