diff --git a/subprojects/executor/src/main/java/eu/mihosoft/vsm/executor/FSMExecutor.java b/subprojects/executor/src/main/java/eu/mihosoft/vsm/executor/FSMExecutor.java index 3e10ff4..2d20731 100644 --- a/subprojects/executor/src/main/java/eu/mihosoft/vsm/executor/FSMExecutor.java +++ b/subprojects/executor/src/main/java/eu/mihosoft/vsm/executor/FSMExecutor.java @@ -33,12 +33,16 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; +/** + * An async executor for state machines. + */ class FSMExecutor implements AsyncFSMExecutor { + private final Deque evtQueue = new ConcurrentLinkedDeque<>(); - private Thread doActionThread; - private CompletableFuture doActionFuture; - private Thread executionThread; + private volatile Thread doActionThread; + private volatile CompletableFuture doActionFuture; + private volatile Thread executionThread; private final Map stateExited = new HashMap<>(); private final int depth; private final FSM fsm; @@ -49,10 +53,10 @@ class FSMExecutor implements AsyncFSMExecutor { private final AsyncFSMExecutor.ExecutionMode mode; - private static final long MAX_EVT_CONSUMED_ACTION_TIMEOUT = 1000 /*ms*/; - private static final long MAX_ENTER_ACTION_TIMEOUT = 1000 /*ms*/; - private static final long MAX_EXIT_ACTION_TIMEOUT = 1000 /*ms*/; - private static final long MAX_TRANSITION_ACTION_TIMEOUT = 1000 /*ms*/; + private static final long MAX_EVT_CONSUMED_ACTION_TIMEOUT = 10_000 /*ms*/; + private static final long MAX_ENTER_ACTION_TIMEOUT = 10_000 /*ms*/; + private static final long MAX_EXIT_ACTION_TIMEOUT = 10_000 /*ms*/; + private static final long MAX_TRANSITION_ACTION_TIMEOUT = 10_000 /*ms*/; private static Optional getLCA(FSMExecutor a, FSMExecutor b) { int start = Math.min(a.pathToRoot.size(), b.pathToRoot.size()); @@ -77,6 +81,12 @@ private FSMExecutor(FSM fsm, ExecutionMode mode, int depth, FSMExecutor parent) pathToRoot.add(this); } + /** + * Creates a new async executor instance. + * @param fsm the fsm to execute + * @param mode the execution mode + * @return the new executor instance + */ public static FSMExecutor newInstance(FSM fsm, ExecutionMode mode) { return new FSMExecutor(fsm, mode, 0, null); } @@ -128,7 +138,34 @@ public void triggerFirst(Event event) { } } + @Override + public boolean process(Event evt) { + + if(executorRunning.get()) { + throw new RuntimeException( + "Cannot call 'process()' if machine is already running,"+ + " try calling trigger(). The 'process()' method triggers and" + + " processes the event in a single method call."); + } + + try { + trigger(evt); + return processRemainingEvents(); + } finally { + // + } + } + + @Override public boolean process(String evt, EventConsumedAction onConsumed, Object... args) { + + if(executorRunning.get()) { + throw new RuntimeException( + "Cannot call 'process()' if machine is already running,"+ + " try calling trigger(). The 'process()' method triggers and" + + " processes the event in a single method call."); + } + try { trigger(evt, onConsumed, args); return processRemainingEvents(); @@ -140,12 +177,12 @@ public boolean process(String evt, EventConsumedAction onConsumed, Object... arg @Override public boolean process(String evt, Object... args) { -// if() { -// throw new RuntimeException( -// "Cannot call 'process()' if machine is already running,"+ -// " try calling trigger(). The 'process()' method triggers and" + -// " processes the event in a single method call."); -// } + if(executorRunning.get()) { + throw new RuntimeException( + "Cannot call 'process()' if machine is already running,"+ + " try calling trigger(). The 'process()' method triggers and" + + " processes the event in a single method call."); + } try { trigger(evt, args); @@ -184,10 +221,11 @@ public void accessFSMSafe(Consumer fsmTask) { } } - boolean firedFinalState = false; - boolean firedDoActionDone = false; - boolean firedStateDone = false; + private volatile boolean firedFinalState = false; + private volatile boolean firedDoActionDone = false; + private volatile boolean firedStateDone = false; + @Override public boolean processRemainingEvents() { // everything modified concurrently with start(), reset(), stop() etc. must be inside @@ -218,31 +256,31 @@ public boolean processRemainingEvents() { boolean consumed = false; State prevState = getCaller().getCurrentState(); - if(prevState instanceof FSMState) { - // if we are in a state with nested fsm we process any upcoming events even if we don't - // currently have events in our queue - if (prevState instanceof FSMState) { - FSMState fsmState = (FSMState) prevState; - for (FSM childFSM : fsmState.getFSMs()) { - if (childFSM != null) { - childFSM.getExecutor().processRemainingEvents(); - } - } // end for each child fsm - - boolean allMatch = fsmState.getFSMs().stream() - .allMatch(fsm->!fsm.isRunning()&&fsm.getFinalState().contains(fsm.getCurrentState())); - if(allMatch && !firedFinalState) { - log("> triggering final-state, currently in state " + prevState.getName()); - triggerFirst(Event.newBuilder().withName(FSMEvents.FINAL_STATE.getName()).withLocal(true) - .withArgs(fsmState.getName()+":"+System.identityHashCode(fsmState)).build()); - firedFinalState = true; - log(" -> final state reached via: " - + fsmState.getFSMs().stream().map(cfsm->cfsm.getName()).collect(Collectors.toList())); + // if we are in a state with nested fsm we process any upcoming events even if we don't + // currently have events in our queue + if (prevState instanceof FSMState) { + FSMState fsmState = (FSMState) prevState; + for (FSM childFSM : fsmState.getFSMs()) { + if (childFSM != null) { + childFSM.getExecutor().processRemainingEvents(); } + } // end for each child fsm + + boolean allMatch = fsmState.getFSMs().stream() + .allMatch(fsm -> !fsm.isRunning() && fsm.getFinalState().contains(fsm.getCurrentState())); + + if (allMatch && !firedFinalState) { + log("> triggering final-state, currently in state " + prevState.getName()); + triggerFirst(Event.newBuilder().withName(FSMEvents.FINAL_STATE.getName()).withLocal(true) + .withArgs(fsmState.getName() + ":" + System.identityHashCode(fsmState)).build()); + firedFinalState = true; + log(" -> final state reached via: " + + fsmState.getFSMs().stream().map(cfsm -> cfsm.getName()).collect(Collectors.toList())); } } + for (Iterator iter = evtQueue.iterator(); iter.hasNext() && getCaller().isRunning(); ) { fsmLock.lock(); @@ -298,14 +336,7 @@ public boolean processRemainingEvents() { removed = removedParam.get(); consumed = consumedParam.get(); } else { -// if(!firedFinalState) { -// triggerFirst(Event.newBuilder() -// .withName(FSMEvents.FINAL_STATE.getName()) -// .withLocal(true).build()); -// log("> triggering final-state, currently in state " + currentState.getName()); -// log(" -> final state reached via: current state (no children available)"); -// firedFinalState = true; -// } + // } if(FSMEvents.FINAL_STATE.getName().equals(evt.getName())) { @@ -787,7 +818,7 @@ private boolean executeDoActionOfNewState(Event evt, State oldState, State newSt return true; } - //@Override + @Override public void exitDoActionOfState(Event evt, State state) { this.exitDoActionOfOldState(evt, state, null); } @@ -900,6 +931,7 @@ private boolean defers(State s, Event evt) { || s.getDeferredEvents().stream().anyMatch(dE-> Pattern.matches(dE, evt.getName())); } + @Override public void startAndWait() { var f = new CompletableFuture(); @@ -923,46 +955,59 @@ public void startAndWait() { private long duration2 = 100; private long duration3 = 10; private long waitTime = 10; + + private final AtomicBoolean executorRunning = new AtomicBoolean(); + private void start_int() { - while(getCaller().isRunning()&&!Thread.currentThread().isInterrupted()) { - try { - long currentTime = System.currentTimeMillis(); - boolean eventsProcessed = getCaller().getExecutor().processRemainingEvents(); + try { + executorRunning.set(true); + while (getCaller().isRunning() && !Thread.currentThread().isInterrupted()) { + try { + long currentTime = System.currentTimeMillis(); + boolean eventsProcessed = getCaller().getExecutor().processRemainingEvents(); - if(Thread.currentThread() == executionThread) { - if(eventsProcessed||timestamp==0) { - timestamp = currentTime; - } + if (Thread.currentThread() == executionThread) { + if (eventsProcessed || timestamp == 0) { + timestamp = currentTime; + } - long timeDiff = currentTime - timestamp; - if (timeDiff > duration1) { - waitTime = 100; - } else if (timeDiff > duration2) { - waitTime = 10; - } else if (timeDiff > duration3) { - waitTime = 1; - } else { - // full speed - waitTime = 0; - } + long timeDiff = currentTime - timestamp; + if (timeDiff > duration1) { + waitTime = 100; + } else if (timeDiff > duration2) { + waitTime = 10; + } else if (timeDiff > duration3) { + waitTime = 1; + } else { + // full speed + waitTime = 0; + } - try { - synchronized (executionThread) { - if(waitTime>0) { - executionThread.wait(waitTime); + try { + synchronized (executionThread) { + if (waitTime > 0) { + executionThread.wait(waitTime); + } } + } catch (InterruptedException iEx) { + Thread.currentThread().interrupt(); } - } catch (InterruptedException iEx) { - Thread.currentThread().interrupt(); } + } catch (Exception ex) { + Thread.currentThread().interrupt(); + throw ex; } - } catch (Exception ex) { - Thread.currentThread().interrupt(); - throw ex; } + } finally { + executorRunning.set(false); } } + @Override + public boolean isRunning() { + return executorRunning.get(); + } + @Override public CompletableFuture startAsync() { diff --git a/subprojects/executor/src/test/java/eu/mihosoft/vsm/model/FSMTest.java b/subprojects/executor/src/test/java/eu/mihosoft/vsm/model/FSMTest.java index 81173a3..e92d282 100644 --- a/subprojects/executor/src/test/java/eu/mihosoft/vsm/model/FSMTest.java +++ b/subprojects/executor/src/test/java/eu/mihosoft/vsm/model/FSMTest.java @@ -24,6 +24,7 @@ * This Java source file was generated by the Gradle 'init' task. */ package eu.mihosoft.vsm.model; + import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.*; @@ -32,6 +33,7 @@ import org.junit.Test; import java.lang.management.ManagementFactory; +import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; @@ -43,7 +45,7 @@ public class FSMTest { private static final AsyncFSMExecutor.ExecutionMode MODE - = AsyncFSMExecutor.ExecutionMode.PARALLEL_REGIONS; + = AsyncFSMExecutor.ExecutionMode.PARALLEL_REGIONS; private static final int NUM_ITERATIONS_LARGE_TESTS = 5; private static final int NUM_ITERATIONS_SMALL_TESTS = 10; @@ -59,92 +61,93 @@ static boolean sleepRandom(long min, long max) { } } - @Test public void testATMFSM() throws InterruptedException { + @Test + public void testATMFSM() throws InterruptedException { - for(int i = 0; i < NUM_ITERATIONS_LARGE_TESTS; i++) { + for (int i = 0; i < NUM_ITERATIONS_LARGE_TESTS; i++) { State idleState = State.newBuilder().withName("idle").withOnEntryAction( - (s, e) -> { - System.out.println("Machine Idle State"); - }).build(); + (s, e) -> { + System.out.println("Machine Idle State"); + }).build(); State cardInserted = State.newBuilder().withName("cardInserted") - .withOnEntryAction((s, e) -> { - System.out.println("Card Inserted State"); - }).build(); + .withOnEntryAction((s, e) -> { + System.out.println("Card Inserted State"); + }).build(); State pinEnteredState = State.newBuilder().withName("pinEntered") - .withOnEntryAction((s, e) -> { - System.out.println("Pin Entered State"); - }).build(); + .withOnEntryAction((s, e) -> { + System.out.println("Pin Entered State"); + }).build(); State amountRequested = State.newBuilder().withName("amountRequested") - .withOnEntryAction((s, e) -> { - System.out.println("Amount Requested State"); - }).build(); + .withOnEntryAction((s, e) -> { + System.out.println("Amount Requested State"); + }).build(); Transition insertCardTransition = Transition.newBuilder().withTrigger("insert-card") - .withGuard((t, evt) -> { - if (evt.getArgs().isEmpty()) return false; - return Objects.equals("DE6594339437", evt.getArgs().get(0)); - }) - .withActions((s, e) -> { - System.out.println("-> correct card inserted"); - }).withSource(idleState).withTarget(cardInserted).build(); + .withGuard((t, evt) -> { + if (evt.getArgs().isEmpty()) return false; + return Objects.equals("DE6594339437", evt.getArgs().get(0)); + }) + .withActions((s, e) -> { + System.out.println("-> correct card inserted"); + }).withSource(idleState).withTarget(cardInserted).build(); Transition enterPinTransition = Transition.newBuilder().withTrigger("enter-pin") - .withGuard((t, evt) -> { - System.out.println("-> pin entered"); - System.out.println("-> checking..."); + .withGuard((t, evt) -> { + System.out.println("-> pin entered"); + System.out.println("-> checking..."); - sleepRandom(0,250); + sleepRandom(0, 250); - if (evt.getArgs().isEmpty()) { - return false; - } + if (evt.getArgs().isEmpty()) { + return false; + } - return Objects.equals(1234, evt.getArgs().get(0)); - }).withActions((s, e) -> { - System.out.println("-> valid."); - }).withSource(cardInserted).withTarget(pinEnteredState).build(); + return Objects.equals(1234, evt.getArgs().get(0)); + }).withActions((s, e) -> { + System.out.println("-> valid."); + }).withSource(cardInserted).withTarget(pinEnteredState).build(); Transition requestAmountTransition = Transition.newBuilder().withTrigger("request-amount") - .withActions((s, e) -> { - System.out.println("-> amount-requested"); - }).withSource(pinEnteredState).withTarget(amountRequested).build(); + .withActions((s, e) -> { + System.out.println("-> amount-requested"); + }).withSource(pinEnteredState).withTarget(amountRequested).build(); Transition moneyDispatchedTransition = Transition.newBuilder().withTrigger("dispatch-money") - .withActions((s, e) -> { - System.out.println("-> checking whether requested amount is available"); - sleepRandom(0,80); - System.out.println("-> money-dispatched"); - }) - .withSource(amountRequested) - .withTarget(idleState) - .build(); + .withActions((s, e) -> { + System.out.println("-> checking whether requested amount is available"); + sleepRandom(0, 80); + System.out.println("-> money-dispatched"); + }) + .withSource(amountRequested) + .withTarget(idleState) + .build(); State errorState = State.newBuilder() - .withName("Error") - .withOnEntryAction((state, event) -> { - System.out.println("ERROR: "); - }) - .build(); + .withName("Error") + .withOnEntryAction((state, event) -> { + System.out.println("ERROR: "); + }) + .build(); FSM fsm = FSM.newBuilder() - .withName("ATM") - .withOwnedState( - idleState, cardInserted, pinEnteredState, amountRequested, errorState - ) - .withInitialState(idleState) - .withErrorState(errorState) - .withTransitions( - insertCardTransition, - enterPinTransition, - requestAmountTransition, - moneyDispatchedTransition - ) - .withVerbose(true) - .build(); + .withName("ATM") + .withOwnedState( + idleState, cardInserted, pinEnteredState, amountRequested, errorState + ) + .withInitialState(idleState) + .withErrorState(errorState) + .withTransitions( + insertCardTransition, + enterPinTransition, + requestAmountTransition, + moneyDispatchedTransition + ) + .withVerbose(true) + .build(); var visitedStates = Collections.synchronizedList(new ArrayList()); @@ -152,9 +155,9 @@ static boolean sleepRandom(long min, long max) { var oldV = (State) change.propertyChange().orElseThrow().oldValue(); var newV = (State) change.propertyChange().orElseThrow().newValue(); System.out.println(Thread.currentThread() + " > transitioned from " + - (oldV == null ? "" : oldV.getName()) + - " to " + - (newV == null ? "" : newV.getName())); + (oldV == null ? "" : oldV.getName()) + + " to " + + (newV == null ? "" : newV.getName())); if (newV != null) { visitedStates.add(newV); } @@ -164,20 +167,22 @@ static boolean sleepRandom(long min, long max) { executor.startAsync(); - sleepRandom(0,2500); + sleepRandom(0, 2500); executor.trigger("insert-card", "DE6594339437"); - sleepRandom(0,250); + sleepRandom(0, 250); executor.trigger("enter-pin", 1234); - sleepRandom(0,350); + sleepRandom(0, 350); executor.trigger("request-amount", 35); var f = new CompletableFuture(); - executor.trigger("dispatch-money",(e, t) -> {f.complete(null);} , 35); + executor.trigger("dispatch-money", (e, t) -> { + f.complete(null); + }, 35); // wait until last event is consumed f.orTimeout(10000, TimeUnit.MILLISECONDS).join(); @@ -185,8 +190,8 @@ static boolean sleepRandom(long min, long max) { executor.stop(); System.out.println("> Visited States: " - + visitedStates.stream().map(s -> s.getName()). - collect(Collectors.joining(", ")) + + visitedStates.stream().map(s -> s.getName()). + collect(Collectors.joining(", ")) ); System.out.println("> number of active threads: " + ManagementFactory.getThreadMXBean().getThreadCount()); @@ -197,7 +202,7 @@ static boolean sleepRandom(long min, long max) { pinEnteredState, amountRequested, idleState - ) + ) ); } @@ -207,7 +212,7 @@ static boolean sleepRandom(long min, long max) { @Test public void nestedOrthogonalWithDoActionProcessingTest() throws InterruptedException { - for(int i = 0; i < NUM_ITERATIONS_LARGE_TESTS; i++) { + for (int i = 0; i < NUM_ITERATIONS_LARGE_TESTS; i++) { System.out.println("-------------- nestedOrthogonalWithDoActionProcessingTest(), iteration: " + i); @@ -228,7 +233,7 @@ public void nestedOrthogonalWithDoActionProcessingTest() throws InterruptedExcep executor.processRemainingEvents(); } - if(MODE == AsyncFSMExecutor.ExecutionMode.PARALLEL_REGIONS) { + if (MODE == AsyncFSMExecutor.ExecutionMode.PARALLEL_REGIONS) { Thread.sleep(100); // TODO (hasRemainingEvents() might still be buggy) @@ -241,23 +246,23 @@ public void nestedOrthogonalWithDoActionProcessingTest() throws InterruptedExcep fsm.setRunning(false); var expectedEvtList = Arrays.asList( - "enter state a", // <- fsm:init - "exit state a", // <- myEvent 1 - "enter state b", // - "exit state b", // <- myEvent 2 - "enter state c", // + "enter state a", // <- fsm:init + "exit state a", // <- myEvent 1 + "enter state b", // + "exit state b", // <- myEvent 2 + "enter state c", // // "enter do-action-in-state-c", // test without enter do-action-in-state-c, because position may vary - "enter state ca1", // - "enter state ca2", // - "exit state ca2", // <- myEvent 2 - "enter state cb2", // - "exit state cb2", // (fsm reached final state) - "exit state ca1", // <- myEvent 1 - "enter state cb1", // - "exit state cb1", // (fsm reached final state) - "exit do-action-in-state-c", // <- timeout (sleep) - "exit state c", // - "enter state a" // + "enter state ca1", // + "enter state ca2", // + "exit state ca2", // <- myEvent 2 + "enter state cb2", // + "exit state cb2", // (fsm reached final state) + "exit state ca1", // <- myEvent 1 + "enter state cb1", // + "exit state cb1", // (fsm reached final state) + "exit do-action-in-state-c", // <- timeout (sleep) + "exit state c", // + "enter state a" // ); // test without enter do-action-in-state-c, because position may vary @@ -272,7 +277,7 @@ public void nestedOrthogonalWithDoActionProcessingTest() throws InterruptedExcep @Test public void nestedOrthogonalWithDoActionInterruptedProcessingTest() { - for(int i = 0; i < NUM_ITERATIONS_LARGE_TESTS; i++) { + for (int i = 0; i < NUM_ITERATIONS_LARGE_TESTS; i++) { var actualEvtList = new ArrayList(); @@ -294,24 +299,24 @@ public void nestedOrthogonalWithDoActionInterruptedProcessingTest() { fsm.setRunning(false); var expectedEvtList = Arrays.asList( - "enter state a", // <- fsm:init - "exit state a", // <- myEvent 1 - "enter state b", // - "exit state b", // <- myEvent 2 - "enter state c", // + "enter state a", // <- fsm:init + "exit state a", // <- myEvent 1 + "enter state b", // + "exit state b", // <- myEvent 2 + "enter state c", // // "enter do-action-in-state-c", // test without enter do-action-in-state-c, because position may vary - "enter state ca1", // - "enter state ca2", // - "exit state ca2", // <- myEvent 2 - "enter state cb2", // - "exit state cb2", // (fsm reached final state) - "exit state ca1", // <- myEvent 1 - "enter state cb1", // - "exit state cb1", // (fsm reached final state) - "interrupt do-action-in-state-c", // <- myEvent 1 - "exit do-action-in-state-c", // - "exit state c", // - "enter state a" // + "enter state ca1", // + "enter state ca2", // + "exit state ca2", // <- myEvent 2 + "enter state cb2", // + "exit state cb2", // (fsm reached final state) + "exit state ca1", // <- myEvent 1 + "enter state cb1", // + "exit state cb1", // (fsm reached final state) + "interrupt do-action-in-state-c", // <- myEvent 1 + "exit do-action-in-state-c", // + "exit state c", // + "enter state a" // ); // test without enter do-action-in-state-c, because position may vary @@ -326,7 +331,7 @@ public void nestedOrthogonalWithDoActionInterruptedProcessingTest() { @Test public void nestedOrthogonalWithDoActionAsyncTest() throws InterruptedException { - for(int i = 0; i < NUM_ITERATIONS_LARGE_TESTS; i++) { + for (int i = 0; i < NUM_ITERATIONS_LARGE_TESTS; i++) { var actualEvtList = new ArrayList(); @@ -346,23 +351,23 @@ public void nestedOrthogonalWithDoActionAsyncTest() throws InterruptedException executor.stop(); var expectedEvtList = Arrays.asList( - "enter state a", // <- fsm:init - "exit state a", // <- myEvent 1 - "enter state b", // - "exit state b", // <- myEvent 2 - "enter state c", // + "enter state a", // <- fsm:init + "exit state a", // <- myEvent 1 + "enter state b", // + "exit state b", // <- myEvent 2 + "enter state c", // // "enter do-action-in-state-c", // test without enter do-action-in-state-c, because position may vary - "enter state ca1", // - "enter state ca2", // - "exit state ca2", // <- myEvent 2 - "enter state cb2", // - "exit state cb2", // (fsm reached final state) - "exit state ca1", // <- myEvent 1 - "enter state cb1", // - "exit state cb1", // (fsm reached final state) - "exit do-action-in-state-c", // <- timeout (sleep) - "exit state c", // - "enter state a" // + "enter state ca1", // + "enter state ca2", // + "exit state ca2", // <- myEvent 2 + "enter state cb2", // + "exit state cb2", // (fsm reached final state) + "exit state ca1", // <- myEvent 1 + "enter state cb1", // + "exit state cb1", // (fsm reached final state) + "exit do-action-in-state-c", // <- timeout (sleep) + "exit state c", // + "enter state a" // ); // test without enter do-action-in-state-c, because position may vary @@ -380,7 +385,7 @@ public void nestedOrthogonalWithDoActionAsyncTest() throws InterruptedException @Test public void nestedOrthogonalWithDoActionInterruptAsyncTest() throws InterruptedException { - for(int i = 0; i < NUM_ITERATIONS_LARGE_TESTS; i++) { + for (int i = 0; i < NUM_ITERATIONS_LARGE_TESTS; i++) { var actualEvtList = new ArrayList(); @@ -400,24 +405,24 @@ public void nestedOrthogonalWithDoActionInterruptAsyncTest() throws InterruptedE executor.stop(); var expectedEvtList = Arrays.asList( - "enter state a", // <- fsm:init - "exit state a", // <- myEvent 1 - "enter state b", // - "exit state b", // <- myEvent 2 - "enter state c", // + "enter state a", // <- fsm:init + "exit state a", // <- myEvent 1 + "enter state b", // + "exit state b", // <- myEvent 2 + "enter state c", // // "enter do-action-in-state-c", // test without enter do-action-in-state-c, because position may vary - "enter state ca1", // - "enter state ca2", // - "exit state ca2", // <- myEvent 2 - "enter state cb2", // - "exit state cb2", // (fsm reached final state) - "exit state ca1", // <- myEvent 1 - "enter state cb1", // - "exit state cb1", // (fsm reached final state) - "interrupt do-action-in-state-c", // <- myEvent 1 - "exit do-action-in-state-c", // - "exit state c", // - "enter state a" // + "enter state ca1", // + "enter state ca2", // + "exit state ca2", // <- myEvent 2 + "enter state cb2", // + "exit state cb2", // (fsm reached final state) + "exit state ca1", // <- myEvent 1 + "enter state cb1", // + "exit state cb1", // (fsm reached final state) + "interrupt do-action-in-state-c", // <- myEvent 1 + "exit do-action-in-state-c", // + "exit state c", // + "enter state a" // ); // test without enter do-action-in-state-c, because position may vary @@ -435,7 +440,7 @@ public void nestedOrthogonalWithDoActionInterruptViaFinalStateAsyncTest() throws // if 'final-state' from nested is emitted, we can interrupt the do-action if we define a // transition with trigger 'final-state' - for(int i = 0; i < NUM_ITERATIONS_LARGE_TESTS; i++) { + for (int i = 0; i < NUM_ITERATIONS_LARGE_TESTS; i++) { var actualEvtList = new ArrayList(); @@ -503,128 +508,170 @@ private static FSM createNestedWithOrthogonal(List enterExitListOrig, bo final var enterExitList = Collections.synchronizedList(enterExitListOrig); State stateA = State.newBuilder() - .withName("State A") - .withOnEntryAction((s, e) -> {System.out.println("enter state a");enterExitList.add("enter state a");}) + .withName("State A") + .withOnEntryAction((s, e) -> { + System.out.println("enter state a"); + enterExitList.add("enter state a"); + }) // .withDoAction() - .withOnExitAction((s, e) -> {System.out.println("exit state a");enterExitList.add("exit state a");}) - .build(); + .withOnExitAction((s, e) -> { + System.out.println("exit state a"); + enterExitList.add("exit state a"); + }) + .build(); State stateB = State.newBuilder() - .withName("State B") - .withOnEntryAction((s, e) -> {System.out.println("enter state b");enterExitList.add("enter state b");}) + .withName("State B") + .withOnEntryAction((s, e) -> { + System.out.println("enter state b"); + enterExitList.add("enter state b"); + }) // .withDoAction() - .withOnExitAction((s, e) -> {System.out.println("exit state b");enterExitList.add("exit state b");}) - .build(); + .withOnExitAction((s, e) -> { + System.out.println("exit state b"); + enterExitList.add("exit state b"); + }) + .build(); State stateCA1 = State.newBuilder() - .withName("State CA1") - .withOnEntryAction((s, e) -> {System.out.println(" enter state ca1");enterExitList.add("enter state ca1");}) + .withName("State CA1") + .withOnEntryAction((s, e) -> { + System.out.println(" enter state ca1"); + enterExitList.add("enter state ca1"); + }) // .withDoAction() - .withOnExitAction((s, e) -> {System.out.println(" exit state ca1");enterExitList.add("exit state ca1");}) - .build(); + .withOnExitAction((s, e) -> { + System.out.println(" exit state ca1"); + enterExitList.add("exit state ca1"); + }) + .build(); State stateCB1 = State.newBuilder() - .withName("State CB1") - .withOnEntryAction((s, e) -> {System.out.println(" enter state cb1");enterExitList.add("enter state cb1");}) + .withName("State CB1") + .withOnEntryAction((s, e) -> { + System.out.println(" enter state cb1"); + enterExitList.add("enter state cb1"); + }) // .withDoAction() - .withOnExitAction((s, e) -> {System.out.println(" exit state cb1");enterExitList.add("exit state cb1");}) - .build(); + .withOnExitAction((s, e) -> { + System.out.println(" exit state cb1"); + enterExitList.add("exit state cb1"); + }) + .build(); State stateCA2 = State.newBuilder() - .withName("State CA2") - .withOnEntryAction((s, e) -> {System.out.println(" enter state ca2");enterExitList.add("enter state ca2");}) + .withName("State CA2") + .withOnEntryAction((s, e) -> { + System.out.println(" enter state ca2"); + enterExitList.add("enter state ca2"); + }) // .withDoAction() - .withOnExitAction((s, e) -> {System.out.println(" exit state ca2");enterExitList.add("exit state ca2");}) - .build(); + .withOnExitAction((s, e) -> { + System.out.println(" exit state ca2"); + enterExitList.add("exit state ca2"); + }) + .build(); State stateCB2 = State.newBuilder() - .withName("State CB2") - .withOnEntryAction((s, e) -> {System.out.println(" enter state cb2");enterExitList.add("enter state cb2");}) + .withName("State CB2") + .withOnEntryAction((s, e) -> { + System.out.println(" enter state cb2"); + enterExitList.add("enter state cb2"); + }) // .withDoAction() - .withOnExitAction((s, e) -> {System.out.println(" exit state cb2");enterExitList.add("exit state cb2");}) - .build(); + .withOnExitAction((s, e) -> { + System.out.println(" exit state cb2"); + enterExitList.add("exit state cb2"); + }) + .build(); FSMState stateC = FSMState.newBuilder() - .withName("State C") - .withOnEntryAction((s, e) -> {System.out.println("enter state c");enterExitList.add("enter state c");}) - .withOnExitAction((s, e) -> {System.out.println("exit state c");enterExitList.add("exit state c");}) - .withDoAction((s, e) -> { - try { - System.out.println("enter do-action-in-state-c"); - enterExitList.add("enter do-action-in-state-c"); - Thread.sleep(1000); - } catch (InterruptedException interruptedException) { - System.out.println("interrupt do-action-in-state-c"); - enterExitList.add("interrupt do-action-in-state-c"); - Thread.currentThread().interrupt(); - } finally { - System.out.println("exit do-action-in-state-c"); - enterExitList.add("exit do-action-in-state-c"); - } - }) - .withFSMs( - FSM.newBuilder() - .withName("FSM C1") + .withName("State C") + .withOnEntryAction((s, e) -> { + System.out.println("enter state c"); + enterExitList.add("enter state c"); + }) + .withOnExitAction((s, e) -> { + System.out.println("exit state c"); + enterExitList.add("exit state c"); + }) + .withDoAction((s, e) -> { + try { + System.out.println("enter do-action-in-state-c"); + enterExitList.add("enter do-action-in-state-c"); + Thread.sleep(1000); + } catch (InterruptedException interruptedException) { + System.out.println("interrupt do-action-in-state-c"); + enterExitList.add("interrupt do-action-in-state-c"); + Thread.currentThread().interrupt(); + } finally { + System.out.println("exit do-action-in-state-c"); + enterExitList.add("exit do-action-in-state-c"); + } + }) + .withFSMs( + FSM.newBuilder() + .withName("FSM C1") // .withVerbose(true) - .withOwnedState(stateCA1, stateCB1) - .withInitialState(stateCA1) - .withFinalState(stateCB1) - .withTransitions( - Transition.newBuilder() - .withSource(stateCA1) - .withTarget(stateCB1) - .withTrigger("myEvent1") - .build() - ) - .build(), - FSM.newBuilder() - .withName("FSM C2") + .withOwnedState(stateCA1, stateCB1) + .withInitialState(stateCA1) + .withFinalState(stateCB1) + .withTransitions( + Transition.newBuilder() + .withSource(stateCA1) + .withTarget(stateCB1) + .withTrigger("myEvent1") + .build() + ) + .build(), + FSM.newBuilder() + .withName("FSM C2") // .withVerbose(true) - .withOwnedState(stateCA2, stateCB2) - .withInitialState(stateCA2) - .withFinalState(stateCB2) - .withTransitions( - Transition.newBuilder() - .withSource(stateCA2) - .withTarget(stateCB2) - .withTrigger("myEvent2") - .build() - ) - .build() - ) - .build(); + .withOwnedState(stateCA2, stateCB2) + .withInitialState(stateCA2) + .withFinalState(stateCB2) + .withTransitions( + Transition.newBuilder() + .withSource(stateCA2) + .withTarget(stateCB2) + .withTrigger("myEvent2") + .build() + ) + .build() + ) + .build(); FSM fsm = FSM.newBuilder() - .withName("FSM") - .withVerbose(true) - .withInitialState(stateA) - .withOwnedState(stateA,stateB,stateC) - .withTransitions( - Transition.newBuilder() - .withSource(stateA) - .withTarget(stateB) - .withTrigger("myEvent1") - .build(), - Transition.newBuilder() - .withSource(stateB) - .withTarget(stateC) - .withTrigger("myEvent2") - .build(), - Transition.newBuilder() - .withSource(stateC) - .withTarget(stateA) - .withTrigger("myEvent1") - .build() - , - Transition.newBuilder() - .withSource(stateC) - .withTarget(stateA) - .withTrigger(finalStateInNested - ? FSMExecutor.FSMEvents.FINAL_STATE.getName() - : FSMExecutor.FSMEvents.DO_ACTION_DONE.getName()) - .build() - ) - .build(); + .withName("FSM") + .withVerbose(true) + .withInitialState(stateA) + .withOwnedState(stateA, stateB, stateC) + .withTransitions( + Transition.newBuilder() + .withSource(stateA) + .withTarget(stateB) + .withTrigger("myEvent1") + .build(), + Transition.newBuilder() + .withSource(stateB) + .withTarget(stateC) + .withTrigger("myEvent2") + .build(), + Transition.newBuilder() + .withSource(stateC) + .withTarget(stateA) + .withTrigger("myEvent1") + .build() + , + Transition.newBuilder() + .withSource(stateC) + .withTarget(stateA) + .withTrigger(finalStateInNested + ? FSMExecutor.FSMEvents.FINAL_STATE.getName() + : FSMExecutor.FSMEvents.DO_ACTION_DONE.getName()) + .build() + ) + .build(); return fsm; } @@ -632,48 +679,48 @@ private static FSM createNestedWithOrthogonal(List enterExitListOrig, bo @Test public void enterNestedStateDirectlyTest() throws InterruptedException { - for(int i = 0; i < NUM_ITERATIONS_SMALL_TESTS; i++) { + for (int i = 0; i < NUM_ITERATIONS_SMALL_TESTS; i++) { var actualEvtList = new ArrayList(); FSM fsm_a = FSM.newBuilder() - .withName("FSM a") - .build(); + .withName("FSM a") + .build(); FSM fsm_a_a = FSM.newBuilder() - .withName("FSM a_a") - .build(); + .withName("FSM a_a") + .build(); FSM fsm_a_b = FSM.newBuilder() - .withName("FSM a_b") - .build(); + .withName("FSM a_b") + .build(); State state_a = FSMState.newBuilder() - .withName("a") - .withOnEntryAction((s, e) -> actualEvtList.add("enter state a")) - .withOnExitAction((s, e) -> actualEvtList.add("exit state a")) - .withFSMs(fsm_a) - .build(); + .withName("a") + .withOnEntryAction((s, e) -> actualEvtList.add("enter state a")) + .withOnExitAction((s, e) -> actualEvtList.add("exit state a")) + .withFSMs(fsm_a) + .build(); State state_a_a = FSMState.newBuilder() - .withName("a_a") - .withOnEntryAction((s, e) -> actualEvtList.add("enter state a_a")) - .withOnExitAction((s, e) -> actualEvtList.add("exit state a_a")) - .withFSMs(fsm_a_a) - .build(); + .withName("a_a") + .withOnEntryAction((s, e) -> actualEvtList.add("enter state a_a")) + .withOnExitAction((s, e) -> actualEvtList.add("exit state a_a")) + .withFSMs(fsm_a_a) + .build(); State state_a_a_a = State.newBuilder() - .withName("a_a_a") - .withOnEntryAction((s, e) -> actualEvtList.add("enter state a_a_a")) - .withOnExitAction((s, e) -> actualEvtList.add("exit state a_a_a")) - .build(); + .withName("a_a_a") + .withOnEntryAction((s, e) -> actualEvtList.add("enter state a_a_a")) + .withOnExitAction((s, e) -> actualEvtList.add("exit state a_a_a")) + .build(); State state_a_b = FSMState.newBuilder() - .withName("a_b") - .withOnEntryAction((s, e) -> actualEvtList.add("enter state a_b")) - .withOnExitAction((s, e) -> actualEvtList.add("exit state a_b")) - .withFSMs(fsm_a_b) - .build(); + .withName("a_b") + .withOnEntryAction((s, e) -> actualEvtList.add("enter state a_b")) + .withOnExitAction((s, e) -> actualEvtList.add("exit state a_b")) + .withFSMs(fsm_a_b) + .build(); fsm_a.getOwnedState().add(state_a_a); fsm_a.getOwnedState().add(state_a_b); @@ -683,24 +730,24 @@ public void enterNestedStateDirectlyTest() throws InterruptedException { fsm_a_a.setInitialState(state_a_a_a); State state_a_b_a = State.newBuilder() - .withName("a_b_a") - .withOnEntryAction((s, e) -> actualEvtList.add("enter state a_b_a")) - .withOnExitAction((s, e) -> actualEvtList.add("exit state a_b_a")) - .build(); + .withName("a_b_a") + .withOnEntryAction((s, e) -> actualEvtList.add("enter state a_b_a")) + .withOnExitAction((s, e) -> actualEvtList.add("exit state a_b_a")) + .build(); Transition a_a__a_b_a = Transition.newBuilder() - .withTrigger("myEvent1") - .withSource(state_a_a_a) - .withTarget(state_a_b_a) - .build(); + .withTrigger("myEvent1") + .withSource(state_a_a_a) + .withTarget(state_a_b_a) + .build(); fsm_a_b.getOwnedState().add(state_a_b_a); fsm_a_b.setInitialState(state_a_b_a); FSM fsm = FSM.newBuilder() - .withOwnedState(state_a) - .withInitialState(state_a) - .build(); + .withOwnedState(state_a) + .withInitialState(state_a) + .build(); fsm.getTransitions().add(a_a__a_b_a); @@ -710,13 +757,13 @@ public void enterNestedStateDirectlyTest() throws InterruptedException { fsm.setRunning(false); var expectedEvtList = Arrays.asList( - "enter state a", // <- fsm:init - "enter state a_a", // - "enter state a_a_a", // - "exit state a_a_a", // <- myEvent1 - "exit state a_a", // - "enter state a_b", // - "enter state a_b_a" // + "enter state a", // <- fsm:init + "enter state a_a", // + "enter state a_a_a", // + "exit state a_a_a", // <- myEvent1 + "exit state a_a", // + "enter state a_b", // + "enter state a_b_a" // ); Assert.assertEquals(expectedEvtList, actualEvtList); @@ -730,77 +777,77 @@ public void lockedDoorTest() throws InterruptedException { var actualEvtList = new ArrayList(); - String event_close = "close"; - String event_open = "open"; - String event_lock = "lock"; + String event_close = "close"; + String event_open = "open"; + String event_lock = "lock"; String event_unlock = "unlock"; State opened = State.newBuilder() - .withName("opened") - .withOnEntryAction((s,e)->{ - actualEvtList.add("enter opened"); - }) - .withOnExitAction((s,e)->actualEvtList.add("exit opened")) - .build(); + .withName("opened") + .withOnEntryAction((s, e) -> { + actualEvtList.add("enter opened"); + }) + .withOnExitAction((s, e) -> actualEvtList.add("exit opened")) + .build(); State closed = State.newBuilder() - .withName("closed") - .withOnEntryAction((s,e)->actualEvtList.add("enter closed")) - .withOnExitAction((s,e)->actualEvtList.add("exit closed")) - .build(); + .withName("closed") + .withOnEntryAction((s, e) -> actualEvtList.add("enter closed")) + .withOnExitAction((s, e) -> actualEvtList.add("exit closed")) + .build(); State locked = State.newBuilder() - .withName("locked") - .withOnEntryAction((s,e)->actualEvtList.add("enter locked")) - .withOnExitAction((s,e)->actualEvtList.add("exit locked")) - .build(); + .withName("locked") + .withOnEntryAction((s, e) -> actualEvtList.add("enter locked")) + .withOnExitAction((s, e) -> actualEvtList.add("exit locked")) + .build(); Transition closeDoor = Transition.newBuilder() - .withTrigger(event_close) - .withSource(opened) - .withTarget(closed) - .withActions((t, e) -> { - System.out.println("closeDoor(), evt: " + e.getName()); - actualEvtList.add("closeDoor()"); - }) - .build(); + .withTrigger(event_close) + .withSource(opened) + .withTarget(closed) + .withActions((t, e) -> { + System.out.println("closeDoor(), evt: " + e.getName()); + actualEvtList.add("closeDoor()"); + }) + .build(); Transition openDoor = Transition.newBuilder() - .withTrigger(event_open) - .withSource(closed) - .withTarget(opened) - .withActions((t, e) -> { - System.out.println("openDoor(), evt: " + e.getName()); - actualEvtList.add("openDoor()"); - }) - .build(); + .withTrigger(event_open) + .withSource(closed) + .withTarget(opened) + .withActions((t, e) -> { + System.out.println("openDoor(), evt: " + e.getName()); + actualEvtList.add("openDoor()"); + }) + .build(); Transition lockDoor = Transition.newBuilder() - .withTrigger(event_lock) - .withSource(closed) - .withTarget(locked) - .withActions((t, e) -> { - System.out.println("lockDoor(), evt: " + e.getName()); - actualEvtList.add("lockDoor()"); - }) - .build(); + .withTrigger(event_lock) + .withSource(closed) + .withTarget(locked) + .withActions((t, e) -> { + System.out.println("lockDoor(), evt: " + e.getName()); + actualEvtList.add("lockDoor()"); + }) + .build(); Transition unlockDoor = Transition.newBuilder() - .withTrigger(event_unlock) - .withSource(locked) - .withTarget(closed) - .withActions((t, e) -> { - System.out.println("unlockDoor(), evt: " + e.getName()); - actualEvtList.add("unlockDoor()"); - }) - .build(); + .withTrigger(event_unlock) + .withSource(locked) + .withTarget(closed) + .withActions((t, e) -> { + System.out.println("unlockDoor(), evt: " + e.getName()); + actualEvtList.add("unlockDoor()"); + }) + .build(); FSM fsm = FSM.newBuilder() - .withInitialState(opened) - .withOwnedState(opened,closed,locked) - .withTransitions(openDoor,closeDoor,lockDoor,unlockDoor) - .build(); + .withInitialState(opened) + .withOwnedState(opened, closed, locked) + .withTransitions(openDoor, closeDoor, lockDoor, unlockDoor) + .build(); var executor = FSMExecutors.newAsyncExecutor(fsm, MODE); @@ -820,38 +867,38 @@ public void lockedDoorTest() throws InterruptedException { fsm.setRunning(false); var expectedEvtList = Arrays.asList( - "enter opened", // <- fsm:init + "enter opened", // <- fsm:init - "exit opened", - "closeDoor()", - "enter closed", + "exit opened", + "closeDoor()", + "enter closed", - "exit closed", - "lockDoor()", - "enter locked", + "exit closed", + "lockDoor()", + "enter locked", - "exit locked", - "unlockDoor()", - "enter closed", + "exit locked", + "unlockDoor()", + "enter closed", - "exit closed", - "openDoor()", - "enter opened", + "exit closed", + "openDoor()", + "enter opened", - // not "lockDoor()" because it's invalid + // not "lockDoor()" because it's invalid - "exit opened", - "closeDoor()", - "enter closed", + "exit opened", + "closeDoor()", + "enter closed", - "exit closed", - "lockDoor()", - "enter locked" + "exit closed", + "lockDoor()", + "enter locked" - // not "openDoor()" because it's invalid + // not "openDoor()" because it's invalid ); - Assert.assertEquals(expectedEvtList,actualEvtList); + Assert.assertEquals(expectedEvtList, actualEvtList); } @@ -860,78 +907,78 @@ public void lockedDoorWithLockVariableAndLocalTransitionTest() throws Interrupte var actualEvtList = new ArrayList(); - String event_close = "close"; - String event_open = "open"; - String event_lock = "lock"; + String event_close = "close"; + String event_open = "open"; + String event_lock = "lock"; String event_unlock = "unlock"; AtomicBoolean lockedState = new AtomicBoolean(); State opened = State.newBuilder() - .withName("opened") - .withOnEntryAction((s,e)->{ - actualEvtList.add("enter opened"); - }) - .withOnExitAction((s,e)->actualEvtList.add("exit opened")) - .build(); + .withName("opened") + .withOnEntryAction((s, e) -> { + actualEvtList.add("enter opened"); + }) + .withOnExitAction((s, e) -> actualEvtList.add("exit opened")) + .build(); State closed = State.newBuilder() - .withName("closed") - .withOnEntryAction((s,e)->actualEvtList.add("enter closed")) - .withOnExitAction((s,e)->actualEvtList.add("exit closed")) - .build(); + .withName("closed") + .withOnEntryAction((s, e) -> actualEvtList.add("enter closed")) + .withOnExitAction((s, e) -> actualEvtList.add("exit closed")) + .build(); Transition closeDoor = Transition.newBuilder() - .withTrigger(event_close) - .withSource(opened) - .withTarget(closed) - .withActions((t, e) -> { - System.out.println("closeDoor(), evt: " + e.getName()); - actualEvtList.add("closeDoor()"); - }) - .build(); + .withTrigger(event_close) + .withSource(opened) + .withTarget(closed) + .withActions((t, e) -> { + System.out.println("closeDoor(), evt: " + e.getName()); + actualEvtList.add("closeDoor()"); + }) + .build(); Transition openDoor = Transition.newBuilder() - .withTrigger(event_open) - .withSource(closed) - .withTarget(opened) - .withGuard((transition, event) -> !lockedState.get()) - .withActions((t, e) -> { - System.out.println("openDoor(), evt: " + e.getName()); - actualEvtList.add("openDoor()"); - }) - .build(); + .withTrigger(event_open) + .withSource(closed) + .withTarget(opened) + .withGuard((transition, event) -> !lockedState.get()) + .withActions((t, e) -> { + System.out.println("openDoor(), evt: " + e.getName()); + actualEvtList.add("openDoor()"); + }) + .build(); Transition lockDoor = Transition.newBuilder() - .withLocal(true) - .withTrigger(event_lock) - .withSource(closed) - .withTarget(closed) - .withActions((t, e) -> { - System.out.println("lockDoor(), evt: " + e.getName()); - actualEvtList.add("lockDoor()"); - lockedState.set(true); - }) - .build(); + .withLocal(true) + .withTrigger(event_lock) + .withSource(closed) + .withTarget(closed) + .withActions((t, e) -> { + System.out.println("lockDoor(), evt: " + e.getName()); + actualEvtList.add("lockDoor()"); + lockedState.set(true); + }) + .build(); Transition unlockDoor = Transition.newBuilder() - .withLocal(true) - .withTrigger(event_unlock) - .withSource(closed) - .withTarget(closed) - .withActions((t, e) -> { - System.out.println("unlockDoor(), evt: " + e.getName()); - actualEvtList.add("unlockDoor()"); - lockedState.set(false); - }) - .build(); + .withLocal(true) + .withTrigger(event_unlock) + .withSource(closed) + .withTarget(closed) + .withActions((t, e) -> { + System.out.println("unlockDoor(), evt: " + e.getName()); + actualEvtList.add("unlockDoor()"); + lockedState.set(false); + }) + .build(); FSM fsm = FSM.newBuilder() - .withInitialState(opened) - .withOwnedState(opened,closed) - .withTransitions(openDoor,closeDoor,lockDoor,unlockDoor) - .build(); + .withInitialState(opened) + .withOwnedState(opened, closed) + .withTransitions(openDoor, closeDoor, lockDoor, unlockDoor) + .build(); var executor = FSMExecutors.newAsyncExecutor(fsm, MODE); @@ -951,39 +998,39 @@ public void lockedDoorWithLockVariableAndLocalTransitionTest() throws Interrupte fsm.setRunning(false); var expectedEvtList = Arrays.asList( - "enter opened", // <- fsm:init + "enter opened", // <- fsm:init - "exit opened", - "closeDoor()", - "enter closed", + "exit opened", + "closeDoor()", + "enter closed", // "exit closed", // self, deactivated due to local transitions (no enter and exit) - "lockDoor()", + "lockDoor()", // "enter closed", // self, deactivated due to local transitions (no enter and exit) // "exit closed", // self, deactivated due to local transitions (no enter and exit) - "unlockDoor()", + "unlockDoor()", // "enter closed", // self, deactivated due to local transitions (no enter and exit) - "exit closed", - "openDoor()", - "enter opened", + "exit closed", + "openDoor()", + "enter opened", - // not "lockDoor()" because it's invalid + // not "lockDoor()" because it's invalid - "exit opened", - "closeDoor()", - "enter closed", + "exit opened", + "closeDoor()", + "enter closed", // "exit closed" // self, deactivated due to local transitions (no enter and exit) - "lockDoor()" + "lockDoor()" // "enter closed" // self, deactivated due to local transitions (no enter and exit) - // not "openDoor()" because it's invalid + // not "openDoor()" because it's invalid ); - Assert.assertEquals(expectedEvtList,actualEvtList); + Assert.assertEquals(expectedEvtList, actualEvtList); } @@ -992,76 +1039,76 @@ public void lockedDoorWithLockVariableTest() throws InterruptedException { var actualEvtList = new ArrayList(); - String event_close = "close"; - String event_open = "open"; - String event_lock = "lock"; + String event_close = "close"; + String event_open = "open"; + String event_lock = "lock"; String event_unlock = "unlock"; AtomicBoolean lockedState = new AtomicBoolean(); State opened = State.newBuilder() - .withName("opened") - .withOnEntryAction((s,e)->{ - actualEvtList.add("enter opened"); - }) - .withOnExitAction((s,e)->actualEvtList.add("exit opened")) - .build(); + .withName("opened") + .withOnEntryAction((s, e) -> { + actualEvtList.add("enter opened"); + }) + .withOnExitAction((s, e) -> actualEvtList.add("exit opened")) + .build(); State closed = State.newBuilder() - .withName("closed") - .withOnEntryAction((s,e)->actualEvtList.add("enter closed")) - .withOnExitAction((s,e)->actualEvtList.add("exit closed")) - .build(); + .withName("closed") + .withOnEntryAction((s, e) -> actualEvtList.add("enter closed")) + .withOnExitAction((s, e) -> actualEvtList.add("exit closed")) + .build(); Transition closeDoor = Transition.newBuilder() - .withTrigger(event_close) - .withSource(opened) - .withTarget(closed) - .withActions((t, e) -> { - System.out.println("closeDoor(), evt: " + e.getName()); - actualEvtList.add("closeDoor()"); - }) - .build(); + .withTrigger(event_close) + .withSource(opened) + .withTarget(closed) + .withActions((t, e) -> { + System.out.println("closeDoor(), evt: " + e.getName()); + actualEvtList.add("closeDoor()"); + }) + .build(); Transition openDoor = Transition.newBuilder() - .withTrigger(event_open) - .withSource(closed) - .withTarget(opened) - .withGuard((transition, event) -> !lockedState.get()) - .withActions((t, e) -> { - System.out.println("openDoor(), evt: " + e.getName()); - actualEvtList.add("openDoor()"); - }) - .build(); + .withTrigger(event_open) + .withSource(closed) + .withTarget(opened) + .withGuard((transition, event) -> !lockedState.get()) + .withActions((t, e) -> { + System.out.println("openDoor(), evt: " + e.getName()); + actualEvtList.add("openDoor()"); + }) + .build(); Transition lockDoor = Transition.newBuilder() - .withTrigger(event_lock) - .withSource(closed) - .withTarget(closed) - .withActions((t, e) -> { - System.out.println("lockDoor(), evt: " + e.getName()); - actualEvtList.add("lockDoor()"); - lockedState.set(true); - }) - .build(); + .withTrigger(event_lock) + .withSource(closed) + .withTarget(closed) + .withActions((t, e) -> { + System.out.println("lockDoor(), evt: " + e.getName()); + actualEvtList.add("lockDoor()"); + lockedState.set(true); + }) + .build(); Transition unlockDoor = Transition.newBuilder() - .withTrigger(event_unlock) - .withSource(closed) - .withTarget(closed) - .withActions((t, e) -> { - System.out.println("unlockDoor(), evt: " + e.getName()); - actualEvtList.add("unlockDoor()"); - lockedState.set(false); - }) - .build(); + .withTrigger(event_unlock) + .withSource(closed) + .withTarget(closed) + .withActions((t, e) -> { + System.out.println("unlockDoor(), evt: " + e.getName()); + actualEvtList.add("unlockDoor()"); + lockedState.set(false); + }) + .build(); FSM fsm = FSM.newBuilder() - .withInitialState(opened) - .withOwnedState(opened,closed) - .withTransitions(openDoor,closeDoor,lockDoor,unlockDoor) - .build(); + .withInitialState(opened) + .withOwnedState(opened, closed) + .withTransitions(openDoor, closeDoor, lockDoor, unlockDoor) + .build(); var executor = FSMExecutors.newAsyncExecutor(fsm, MODE); @@ -1081,39 +1128,39 @@ public void lockedDoorWithLockVariableTest() throws InterruptedException { fsm.setRunning(false); var expectedEvtList = Arrays.asList( - "enter opened", // <- fsm:init + "enter opened", // <- fsm:init - "exit opened", - "closeDoor()", - "enter closed", + "exit opened", + "closeDoor()", + "enter closed", - "exit closed", // self - "lockDoor()", - "enter closed", // self + "exit closed", // self + "lockDoor()", + "enter closed", // self - "exit closed", // self - "unlockDoor()", - "enter closed", // self + "exit closed", // self + "unlockDoor()", + "enter closed", // self - "exit closed", - "openDoor()", - "enter opened", + "exit closed", + "openDoor()", + "enter opened", - // not "lockDoor()" because it's invalid + // not "lockDoor()" because it's invalid - "exit opened", - "closeDoor()", - "enter closed", + "exit opened", + "closeDoor()", + "enter closed", - "exit closed", // self - "lockDoor()", - "enter closed" // self + "exit closed", // self + "lockDoor()", + "enter closed" // self - // not "openDoor()" because it's invalid + // not "openDoor()" because it's invalid ); - Assert.assertEquals(expectedEvtList,actualEvtList); + Assert.assertEquals(expectedEvtList, actualEvtList); } @@ -1186,7 +1233,7 @@ public void lockedDoorWithLockVariableTest() throws InterruptedException { @Test public void transitionPriorityTest1() throws InterruptedException, ExecutionException { - for(int i = 0; i < NUM_ITERATIONS_LARGE_TESTS; i++) { + for (int i = 0; i < NUM_ITERATIONS_LARGE_TESTS; i++) { System.out.println("# ITERATIONS: " + i); @@ -1210,18 +1257,18 @@ public void transitionPriorityTest1() throws InterruptedException, ExecutionExce System.out.println(String.join("\n", actualEvtList)); var expectedEvtList = Arrays.asList( - "enter s1", // <- fsm:init - - "exit s1", // <- event1 - "transitioning from s1 to parent, via event event1", - "enter parent", - "enter c1", - - "exit c1", // <- event2 - "exit parent", - "transitioning from parent to c2, via event event2", - "enter parent", - "enter c2" + "enter s1", // <- fsm:init + + "exit s1", // <- event1 + "transitioning from s1 to parent, via event event1", + "enter parent", + "enter c1", + + "exit c1", // <- event2 + "exit parent", + "transitioning from parent to c2, via event event2", + "enter parent", + "enter c2" ); Assert.assertEquals(expectedEvtList, actualEvtList); @@ -1247,17 +1294,17 @@ public void transitionPriorityTest1() throws InterruptedException, ExecutionExce System.out.println(String.join("\n", actualEvtList)); var expectedEvtList = Arrays.asList( - "enter s1", // <- fsm:init + "enter s1", // <- fsm:init - "exit s1", // <- event1 - "transitioning from s1 to parent, via event event1", - "enter parent", - "enter c1", + "exit s1", // <- event1 + "transitioning from s1 to parent, via event event1", + "enter parent", + "enter c1", - "exit c1", // <- event2 - "exit parent", - "transitioning from parent to s2, via event event2", - "enter s2" + "exit c1", // <- event2 + "exit parent", + "transitioning from parent to s2, via event event2", + "enter s2" ); Assert.assertEquals(expectedEvtList, actualEvtList); @@ -1280,83 +1327,83 @@ private FSM createTransitionPriorityFSM(List actualEvtListOrig, boolean TransitionAction transitioned = (t, e) -> { actualEvtList.add("transitioning from " + t.getSource().getName() - + " to " + t.getTarget().getName() + ", via event " + e.getName()); + + " to " + t.getTarget().getName() + ", via event " + e.getName()); }; State c1 = State.newBuilder() - .withName("c1") - .withOnEntryAction(entryAction) - .withOnExitAction(exitAction) - .build(); + .withName("c1") + .withOnEntryAction(entryAction) + .withOnExitAction(exitAction) + .build(); State c2 = State.newBuilder() - .withName("c2") - .withOnEntryAction(entryAction) - .withOnExitAction(exitAction) - .build(); + .withName("c2") + .withOnEntryAction(entryAction) + .withOnExitAction(exitAction) + .build(); Transition c1_c2 = Transition.newBuilder() - .withTrigger("event1") - .withSource(c1) - .withTarget(c2) - .withActions(transitioned) - .build(); + .withTrigger("event1") + .withSource(c1) + .withTarget(c2) + .withActions(transitioned) + .build(); FSMState parent = FSMState.newBuilder() - .withName("parent") - .withOnEntryAction(entryAction) - .withOnExitAction(exitAction) - .withFSMs(FSM.newBuilder() - .withName("parent:r1") - .withOwnedState(c1, c2) - .withInitialState(c1) - .withTransitions(c1_c2) - .build() - ) - .build(); + .withName("parent") + .withOnEntryAction(entryAction) + .withOnExitAction(exitAction) + .withFSMs(FSM.newBuilder() + .withName("parent:r1") + .withOwnedState(c1, c2) + .withInitialState(c1) + .withTransitions(c1_c2) + .build() + ) + .build(); State s1 = State.newBuilder() - .withName("s1") - .withOnEntryAction(entryAction) - .withOnExitAction(exitAction) - .build(); + .withName("s1") + .withOnEntryAction(entryAction) + .withOnExitAction(exitAction) + .build(); State s2 = State.newBuilder() - .withName("s2") - .withOnEntryAction(entryAction) - .withOnExitAction(exitAction) - .build(); + .withName("s2") + .withOnEntryAction(entryAction) + .withOnExitAction(exitAction) + .build(); Transition s1_parent = Transition.newBuilder() - .withSource(s1) - .withTarget(parent) - .withTrigger("event1") - .withActions(transitioned) - .build(); + .withSource(s1) + .withTarget(parent) + .withTrigger("event1") + .withActions(transitioned) + .build(); Transition parent_s2 = Transition.newBuilder() - .withSource(parent) - .withTarget(s2) - .withTrigger("event1") - .withActions(transitioned) - .build(); + .withSource(parent) + .withTarget(s2) + .withTrigger("event1") + .withActions(transitioned) + .build(); Transition parent_c2 = Transition.newBuilder() - .withTrigger("event2") - .withActions(transitioned) - .build(); + .withTrigger("event2") + .withActions(transitioned) + .build(); Transition parent_s2_b = Transition.newBuilder() - .withTrigger("event2") - .withActions(transitioned) - .build(); + .withTrigger("event2") + .withActions(transitioned) + .build(); FSM fsm = FSM.newBuilder() - .withName("fsm") - .withOwnedState(parent, s1, s2) - .withInitialState(s1) - .withTransitions(s1_parent, parent_s2, parent_s2_b, parent_c2) - .build(); + .withName("fsm") + .withOwnedState(parent, s1, s2) + .withInitialState(s1) + .withTransitions(s1_parent, parent_s2, parent_s2_b, parent_c2) + .build(); c2.getIncomingTransitions().add(parent_c2); s2.getIncomingTransitions().add(parent_s2_b); @@ -1393,31 +1440,31 @@ public void testChildrenDoneEvent() throws InterruptedException { TransitionAction transitioned = (t, e) -> { actualEvtList.add("transitioning from " + t.getSource().getName() - + " to " + t.getTarget().getName() + ", via event " + e.getName()); + + " to " + t.getTarget().getName() + ", via event " + e.getName()); System.out.println("transitioning from " + t.getSource().getName() - + " to " + t.getTarget().getName() + ", via event " + e.getName()); + + " to " + t.getTarget().getName() + ", via event " + e.getName()); }; State c1 = State.newBuilder() - .withName("c1") - .build(); + .withName("c1") + .build(); State c2 = State.newBuilder() - .withName("c2") - .build(); + .withName("c2") + .build(); Transition c1_c2 = Transition.newBuilder() - .withTrigger(FSMExecutor.FSMEvents.STATE_DONE.getName()) - .withSource(c1) - .withTarget(c2) - .build(); + .withTrigger(FSMExecutor.FSMEvents.STATE_DONE.getName()) + .withSource(c1) + .withTarget(c2) + .build(); FSM childFSM = FSM.newBuilder() - .withName("Child FSM") - .withInitialState(c1) - .withOwnedState(c1, c2) - .withFinalState(c2) - .withTransitions(c1_c2) - .build(); + .withName("Child FSM") + .withInitialState(c1) + .withOwnedState(c1, c2) + .withFinalState(c2) + .withTransitions(c1_c2) + .build(); FSM childFSM1 = childFSM.clone(); childFSM1.setName("Child FSM 1"); @@ -1428,73 +1475,73 @@ public void testChildrenDoneEvent() throws InterruptedException { childFSM2.getOwnedState().forEach(s -> s.setName(s.getName() + "-fsm2")); State s1 = State.newBuilder() - .withName("s1") - .build(); + .withName("s1") + .build(); AtomicBoolean childFSMStateWasInterrupted = new AtomicBoolean(); FSMState childFSMState = FSMState.newBuilder() - .withName("ChildFSMState") - .withFSMs(childFSM1, childFSM2) - .withDoAction((s, e) -> { - try { - System.out.println("!!! cfsm: enter"); - Thread.sleep(1500); - System.out.println("!!! cfsm: exit"); - } catch (InterruptedException interruptedException) { - System.out.println("!!! cfsm: interrupted"); - Thread.currentThread().interrupt(); - childFSMStateWasInterrupted.set(true); - } - }) - .build(); + .withName("ChildFSMState") + .withFSMs(childFSM1, childFSM2) + .withDoAction((s, e) -> { + try { + System.out.println("!!! cfsm: enter"); + Thread.sleep(1500); + System.out.println("!!! cfsm: exit"); + } catch (InterruptedException interruptedException) { + System.out.println("!!! cfsm: interrupted"); + Thread.currentThread().interrupt(); + childFSMStateWasInterrupted.set(true); + } + }) + .build(); AtomicBoolean s2WasInterrupted = new AtomicBoolean(); State s2 = State.newBuilder() - .withName("s2") - .withDoAction((s, e) -> { - try { - System.out.println("!!! s2: enter"); - Thread.sleep(150); - System.out.println("!!! s2: exit"); - } catch (InterruptedException interruptedException) { - System.out.println("!!! s2: interrupt"); - Thread.currentThread().interrupt(); - s2WasInterrupted.set(true); - } - }) - .build(); + .withName("s2") + .withDoAction((s, e) -> { + try { + System.out.println("!!! s2: enter"); + Thread.sleep(150); + System.out.println("!!! s2: exit"); + } catch (InterruptedException interruptedException) { + System.out.println("!!! s2: interrupt"); + Thread.currentThread().interrupt(); + s2WasInterrupted.set(true); + } + }) + .build(); State s3 = State.newBuilder() - .withName("s3") - .build(); + .withName("s3") + .build(); Transition s1_childFSMState = Transition.newBuilder() - .withTrigger(FSMExecutor.FSMEvents.STATE_DONE.getName()) - .withSource(s1) - .withTarget(childFSMState) - .build(); + .withTrigger(FSMExecutor.FSMEvents.STATE_DONE.getName()) + .withSource(s1) + .withTarget(childFSMState) + .build(); Transition childFSMState_s2 = Transition.newBuilder() - .withTrigger(FSMExecutor.FSMEvents.FINAL_STATE.getName()) - .withSource(childFSMState) - .withTarget(s2) - .build(); + .withTrigger(FSMExecutor.FSMEvents.FINAL_STATE.getName()) + .withSource(childFSMState) + .withTarget(s2) + .build(); Transition s2_s3 = Transition.newBuilder() - .withTrigger(FSMExecutor.FSMEvents.STATE_DONE.getName()) - .withSource(s2) - .withTarget(s3) - .build(); + .withTrigger(FSMExecutor.FSMEvents.STATE_DONE.getName()) + .withSource(s2) + .withTarget(s3) + .build(); FSM fsm = FSM.newBuilder() - .withName("FSM") - .withInitialState(s1) - .withOwnedState(s1, childFSMState, s2, s3) - .withFinalState(s3) - .withTransitions(s1_childFSMState, childFSMState_s2, s2_s3) - .build(); + .withName("FSM") + .withInitialState(s1) + .withOwnedState(s1, childFSMState, s2, s3) + .withFinalState(s3) + .withTransitions(s1_childFSMState, childFSMState_s2, s2_s3) + .build(); fsm.vmf().content().stream(State.class).forEach(s -> { s.setOnEntryAction(entryAction); @@ -1515,7 +1562,7 @@ public void testChildrenDoneEvent() throws InterruptedException { executor.processRemainingEvents(); } - if(MODE == AsyncFSMExecutor.ExecutionMode.PARALLEL_REGIONS) { + if (MODE == AsyncFSMExecutor.ExecutionMode.PARALLEL_REGIONS) { Thread.sleep(100); // TODO (hasRemainingEvents() might still be buggy) @@ -1527,37 +1574,37 @@ public void testChildrenDoneEvent() throws InterruptedException { fsm.setRunning(false); var expectedEvtList = Arrays.asList( - "enter s1", // <- fsm:init + "enter s1", // <- fsm:init - "exit s1", // <- fsm:state-done - "transitioning from s1 to ChildFSMState, via event fsm:state-done", - "enter ChildFSMState", + "exit s1", // <- fsm:state-done + "transitioning from s1 to ChildFSMState, via event fsm:state-done", + "enter ChildFSMState", - // FSM1 (Region 1) - "enter c1-fsm1", // <- fsm:init + // FSM1 (Region 1) + "enter c1-fsm1", // <- fsm:init - "exit c1-fsm1", // <- fsm:state-done - "transitioning from c1-fsm1 to c2-fsm1, via event fsm:state-done", - "enter c2-fsm1", + "exit c1-fsm1", // <- fsm:state-done + "transitioning from c1-fsm1 to c2-fsm1, via event fsm:state-done", + "enter c2-fsm1", - "exit c2-fsm1", // <- fsm stopped (no event generated) + "exit c2-fsm1", // <- fsm stopped (no event generated) - // FSM1 (Region 2) - "enter c1-fsm2", // <- fsm:init + // FSM1 (Region 2) + "enter c1-fsm2", // <- fsm:init - "exit c1-fsm2", // <- fsm:state-done - "transitioning from c1-fsm2 to c2-fsm2, via event fsm:state-done", - "enter c2-fsm2", + "exit c1-fsm2", // <- fsm:state-done + "transitioning from c1-fsm2 to c2-fsm2, via event fsm:state-done", + "enter c2-fsm2", - "exit c2-fsm2", // <- fsm stopped (no event generated) - "exit ChildFSMState", // <- fsm:final-state (all regions of ChildFSMState reached final state) - "transitioning from ChildFSMState to s2, via event fsm:final-state", - "enter s2", + "exit c2-fsm2", // <- fsm stopped (no event generated) + "exit ChildFSMState", // <- fsm:final-state (all regions of ChildFSMState reached final state) + "transitioning from ChildFSMState to s2, via event fsm:final-state", + "enter s2", - "exit s2", // <- fsm:state-done (does not interrupt do-action of s2) - "transitioning from s2 to s3, via event fsm:state-done", - "enter s3", - "exit s3" // <- fsm stopped (no event generated) + "exit s2", // <- fsm:state-done (does not interrupt do-action of s2) + "transitioning from s2 to s3, via event fsm:state-done", + "enter s3", + "exit s3" // <- fsm stopped (no event generated) ); System.out.println("--------------"); @@ -1571,12 +1618,12 @@ public void testChildrenDoneEvent() throws InterruptedException { Assert.assertEquals(expectedEvtList.size(), actualEvtList.size()); Assert.assertTrue("do-action of childFSMState should be" + - " interrupted since there is a consuming transition that causes childFSMState to exit (evt: fsm:final-state)", - childFSMStateWasInterrupted.get()); + " interrupted since there is a consuming transition that causes childFSMState to exit (evt: fsm:final-state)", + childFSMStateWasInterrupted.get()); Assert.assertFalse("do-action of s2 should not be" + - " interrupted since there is no consuming transition that would cause s2 to exit (evt: fsm:state-done)", - s2WasInterrupted.get()); + " interrupted since there is no consuming transition that would cause s2 to exit (evt: fsm:state-done)", + s2WasInterrupted.get()); } // end for i @@ -1597,46 +1644,46 @@ public void testStateDone() throws InterruptedException { TransitionAction transitioned = (t, e) -> { actualEvtList.add("transitioning from " + t.getSource().getName() - + " to " + t.getTarget().getName() + ", via event " + e.getName()); + + " to " + t.getTarget().getName() + ", via event " + e.getName()); }; State s1 = State.newBuilder() - .withName("s1") - .build(); + .withName("s1") + .build(); State s2 = State.newBuilder() - .withName("s2") - .build(); + .withName("s2") + .build(); State s3 = State.newBuilder() - .withName("s3") - .build(); + .withName("s3") + .build(); Transition s1_s2 = Transition.newBuilder() - .withTrigger(FSMExecutor.FSMEvents.STATE_DONE.getName()) - .withSource(s1) - .withTarget(s2) - .build(); + .withTrigger(FSMExecutor.FSMEvents.STATE_DONE.getName()) + .withSource(s1) + .withTarget(s2) + .build(); Transition s2_s3 = Transition.newBuilder() - .withTrigger(FSMExecutor.FSMEvents.STATE_DONE.getName()) - .withSource(s2) - .withTarget(s3) - .build(); + .withTrigger(FSMExecutor.FSMEvents.STATE_DONE.getName()) + .withSource(s2) + .withTarget(s3) + .build(); FSM fsm = FSM.newBuilder() - .withName("FSM") - .withInitialState(s1) - .withOwnedState(s1,s2,s3) - .withFinalState(s3) - .withTransitions(s1_s2, s2_s3) - .build(); + .withName("FSM") + .withInitialState(s1) + .withOwnedState(s1, s2, s3) + .withFinalState(s3) + .withTransitions(s1_s2, s2_s3) + .build(); - fsm.vmf().content().stream(State.class).forEach(s-> { + fsm.vmf().content().stream(State.class).forEach(s -> { s.setOnEntryAction(entryAction); s.setOnExitAction(exitAction); }); - fsm.vmf().content().stream(Transition.class).forEach(t-> { + fsm.vmf().content().stream(Transition.class).forEach(t -> { t.getActions().add(transitioned); }); @@ -1645,22 +1692,22 @@ public void testStateDone() throws InterruptedException { var executor = FSMExecutors.newAsyncExecutor(fsm, MODE); fsm.setRunning(true); - while(executor.hasRemainingEvents()) { + while (executor.hasRemainingEvents()) { executor.processRemainingEvents(); } fsm.setRunning(false); var expectedEvtList = Arrays.asList( - "enter s1", // <- fsm:init + "enter s1", // <- fsm:init - "exit s1", // <- fsm:state-done - "transitioning from s1 to s2, via event fsm:state-done", - "enter s2", + "exit s1", // <- fsm:state-done + "transitioning from s1 to s2, via event fsm:state-done", + "enter s2", - "exit s2", // <- fsm:state-done - "transitioning from s2 to s3, via event fsm:state-done", - "enter s3", - "exit s3" // <- fsm stopped (no event generated) + "exit s2", // <- fsm:state-done + "transitioning from s2 to s3, via event fsm:state-done", + "enter s3", + "exit s3" // <- fsm stopped (no event generated) ); System.out.println(String.join("\n", actualEvtList)); @@ -1672,33 +1719,33 @@ public void testStateDone() throws InterruptedException { @Test public void errorStateTest1() { State s1 = State.newBuilder() - .withName("s1") - .build(); + .withName("s1") + .build(); State s2 = State.newBuilder() - .withName("s1") - .withOnEntryAction((s, e) -> { - throw new RuntimeException("Exception in s2"); - }) - .build(); + .withName("s1") + .withOnEntryAction((s, e) -> { + throw new RuntimeException("Exception in s2"); + }) + .build(); Transition s1s2 = Transition.newBuilder() - .withTrigger("myEvent1") - .withSource(s1) - .withTarget(s2) - .build(); + .withTrigger("myEvent1") + .withSource(s1) + .withTarget(s2) + .build(); State error = State.newBuilder() - .withName("ERROR") - .withOnEntryAction((s, e) -> System.out.println("Error: " + e.getArgs().get(0))) - .build(); + .withName("ERROR") + .withOnEntryAction((s, e) -> System.out.println("Error: " + e.getArgs().get(0))) + .build(); FSM fsm = FSM.newBuilder() - .withName("FSM") - .withInitialState(s1) - .withOwnedState(s1,s2, error) - .withErrorState(error) - .withTransitions(s1s2) - .build(); + .withName("FSM") + .withInitialState(s1) + .withOwnedState(s1, s2, error) + .withErrorState(error) + .withTransitions(s1s2) + .build(); var executor = FSMExecutors.newAsyncExecutor(fsm, AsyncFSMExecutor.ExecutionMode.SERIAL_REGIONS); @@ -1714,45 +1761,45 @@ public void errorStateTest1() { @Test public void errorStateNestedFSMTest2() { State s1 = State.newBuilder() - .withName("s1") - .build(); + .withName("s1") + .build(); State s2 = State.newBuilder() - .withName("s1") - .withOnEntryAction((s, e) -> { - throw new RuntimeException("Exception in s2"); - }) - .build(); + .withName("s1") + .withOnEntryAction((s, e) -> { + throw new RuntimeException("Exception in s2"); + }) + .build(); Transition s1s2 = Transition.newBuilder() - .withTrigger("myEvent1") - .withSource(s1) - .withTarget(s2) - .build(); + .withTrigger("myEvent1") + .withSource(s1) + .withTarget(s2) + .build(); FSM fsmChild = FSM.newBuilder() - .withName("FSM Child") - .withInitialState(s1) - .withOwnedState(s1,s2) - .withTransitions(s1s2) - .build(); + .withName("FSM Child") + .withInitialState(s1) + .withOwnedState(s1, s2) + .withTransitions(s1s2) + .build(); State error = State.newBuilder() - .withName("ERROR") - .withOnEntryAction((s, e) -> System.out.println("Error: " + e.getArgs().get(0))) - .build(); + .withName("ERROR") + .withOnEntryAction((s, e) -> System.out.println("Error: " + e.getArgs().get(0))) + .build(); FSMState child = FSMState.newBuilder() - .withName("Region 1") - .withFSMs(fsmChild) - .build(); + .withName("Region 1") + .withFSMs(fsmChild) + .build(); FSM fsm = FSM.newBuilder() - .withName("FSM") - .withInitialState(child) - .withOwnedState(child, error) - .withErrorState(error) - .build(); + .withName("FSM") + .withInitialState(child) + .withOwnedState(child, error) + .withErrorState(error) + .build(); var executor = FSMExecutors.newAsyncExecutor(fsm, AsyncFSMExecutor.ExecutionMode.SERIAL_REGIONS); @@ -1773,47 +1820,47 @@ public void errorStateNestedFSMTest3() throws InterruptedException, ExecutionExc // in the nested fsm State s1 = State.newBuilder() - .withName("s1") - .build(); + .withName("s1") + .build(); State s2 = State.newBuilder() - .withName("s2") - .withOnEntryAction((s, e) -> { - throw new RuntimeException("Exception in s2"); - }) - .build(); + .withName("s2") + .withOnEntryAction((s, e) -> { + throw new RuntimeException("Exception in s2"); + }) + .build(); Transition s1s2 = Transition.newBuilder() - .withTrigger("myEvent1") - .withSource(s1) - .withTarget(s2) - .build(); + .withTrigger("myEvent1") + .withSource(s1) + .withTarget(s2) + .build(); FSM fsmChild = FSM.newBuilder() - .withName("FSM Child") - .withInitialState(s1) - .withOwnedState(s1,s2) - .withTransitions(s1s2) - .build(); + .withName("FSM Child") + .withInitialState(s1) + .withOwnedState(s1, s2) + .withTransitions(s1s2) + .build(); State error = State.newBuilder() - .withName("ERROR") - .withOnEntryAction((s, e) -> System.out.println("Error: " + e.getArgs().get(0))) - .build(); + .withName("ERROR") + .withOnEntryAction((s, e) -> System.out.println("Error: " + e.getArgs().get(0))) + .build(); FSMState child = FSMState.newBuilder() - .withName("Region 1") - .withFSMs(fsmChild) - .build(); + .withName("Region 1") + .withFSMs(fsmChild) + .build(); FSM fsm = FSM.newBuilder() - .withName("FSM") - .withVerbose(true) - .withInitialState(child) - .withOwnedState(child) - .withOwnedState(child, error) - .withErrorState(error) - .build(); + .withName("FSM") + .withVerbose(true) + .withInitialState(child) + .withOwnedState(child) + .withOwnedState(child, error) + .withErrorState(error) + .build(); var executor = FSMExecutors.newAsyncExecutor(fsm, AsyncFSMExecutor.ExecutionMode.SERIAL_REGIONS); executor.startAsync(); @@ -1826,9 +1873,9 @@ public void errorStateNestedFSMTest3() throws InterruptedException, ExecutionExc } - @Test + @Test(timeout = 100_000) public void nestedFSMEventConsumedActionTest() throws InterruptedException, ExecutionException { - + // +------------------+ // +---------+ S3| R1 | // | s3-done |---/ | @@ -1850,161 +1897,162 @@ public void nestedFSMEventConsumedActionTest() throws InterruptedException, Exec // - does each region (R1, R2) consume the event EV1? - var s1EnteredF = new CompletableFuture(); - State s1 = State.newBuilder() - .withName("s1") - .withOnEntryAction((s, e) -> { - System.out.println("entered state " + s.getName()); - s1EnteredF.complete(null); - }) - .build(); - - State s2 = State.newBuilder() - .withName("s2") - .withOnEntryAction((s, e) -> { - System.out.println("entered state " + s.getName()); - }) - .build(); + for (int idx = 0; idx < NUM_ITERATIONS_SMALL_TESTS; idx++) { - Function fsmCreator = (i) -> { - State s1i = State.newBuilder() - .withName("s1i") - .withOnEntryAction((s, e) -> { - System.out.println("cfsm" + i + " entered state " + s.getName()); - sleepRandom(0, 250); + var s1EnteredF = new CompletableFuture(); + State s1 = State.newBuilder() + .withName("s1") + .withOnEntryAction((s, e) -> { + System.out.println("entered state " + s.getName()); + s1EnteredF.complete(null); }) .build(); - State s2i = State.newBuilder() - .withName("s2i") + State s2 = State.newBuilder() + .withName("s2") .withOnEntryAction((s, e) -> { - System.out.println("cfsm" + i + " entered state " + s.getName()); - sleepRandom(0, 250); + System.out.println("entered state " + s.getName()); }) .build(); - Transition s1s2i = Transition.newBuilder() - .withTrigger("EV1") - .withSource(s1i) - .withTarget(s2i) - .build(); + Function fsmCreator = (i) -> { + State s1i = State.newBuilder() + .withName("s1i") + .withOnEntryAction((s, e) -> { + System.out.println("cfsm" + i + " entered state " + s.getName()); + sleepRandom(0, 250); + }) + .build(); - FSM fsmChild = FSM.newBuilder() - .withName("Child " + i) - .withInitialState(s1i) - .withOwnedState(s1i, s2i) - .withTransitions(s1s2i) - .withFinalState(s2i) - .build(); + State s2i = State.newBuilder() + .withName("s2i") + .withOnEntryAction((s, e) -> { + System.out.println("cfsm" + i + " entered state " + s.getName()); + sleepRandom(0, 250); + }) + .build(); - return fsmChild; - }; + Transition s1s2i = Transition.newBuilder() + .withTrigger("EV1") + .withSource(s1i) + .withTarget(s2i) + .build(); - int numberOFChildren = 10; + FSM fsmChild = FSM.newBuilder() + .withName("Child " + i) + .withInitialState(s1i) + .withOwnedState(s1i, s2i) + .withTransitions(s1s2i) + .withFinalState(s2i) + .build(); - var childFSMs = IntStream.range(1, numberOFChildren+1) - .mapToObj(i->fsmCreator.apply(i)).collect(Collectors.toList()); + return fsmChild; + }; - var s3EnteredF = new CompletableFuture(); - FSMState s3 = FSMState.newBuilder() - .withName("s3") - .withFSMs(childFSMs) - .withOnEntryAction((s, e) -> { - System.out.println("entering state " + s.getName()); - s3EnteredF.complete(null); - }) - .build(); + int numberOFChildren = ThreadLocalRandom.current().nextInt(30) + 1; - Transition s1s2 = Transition.newBuilder() - .withTrigger("EV1") - .withSource(s1) - .withTarget(s2) - .build(); + var childFSMs = IntStream.range(1, numberOFChildren + 1) + .mapToObj(i -> fsmCreator.apply(i)).collect(Collectors.toList()); - Transition s2s3 = Transition.newBuilder() - .withTrigger("EV2") - .withSource(s2) - .withTarget(s3) - .build(); + var s3EnteredF = new CompletableFuture(); + FSMState s3 = FSMState.newBuilder() + .withName("s3") + .withFSMs(childFSMs) + .withOnEntryAction((s, e) -> { + System.out.println("entering state " + s.getName()); + s3EnteredF.complete(null); + }) + .build(); - var s3s1F = new CompletableFuture<>(); - Transition s3s1 = Transition.newBuilder() - .withTrigger(FSMExecutor.FSMEvents.STATE_DONE.getName()) - .withSource(s3) - .withTarget(s1) - .withActions((t, e) -> { - System.out.println("transitioning from S3 to S1"); - s3s1F.complete(null); - }) - .build(); + Transition s1s2 = Transition.newBuilder() + .withTrigger("EV1") + .withSource(s1) + .withTarget(s2) + .build(); - FSM fsm = FSM.newBuilder() - .withName("FSM") - .withInitialState(s1) - .withOwnedState(s1, s2, s3) - .withTransitions(s1s2, s1s2, s2s3, s3s1) - .withVerbose(true) - .build(); + Transition s2s3 = Transition.newBuilder() + .withTrigger("EV2") + .withSource(s2) + .withTarget(s3) + .build(); - var executor = FSMExecutors.newAsyncExecutor(fsm, AsyncFSMExecutor.ExecutionMode.PARALLEL_REGIONS); + var s3s1F = new CompletableFuture<>(); + Transition s3s1 = Transition.newBuilder() + .withTrigger(FSMExecutor.FSMEvents.STATE_DONE.getName()) + .withSource(s3) + .withTarget(s1) + .withActions((t, e) -> { + System.out.println("transitioning from S3 to S1"); + s3s1F.complete(null); + }) + .build(); - executor.startAsync(); + FSM fsm = FSM.newBuilder() + .withName("FSM") + .withInitialState(s1) + .withOwnedState(s1, s2, s3) + .withTransitions(s1s2, s1s2, s2s3, s3s1) + .withVerbose(true) + .build(); - s1EnteredF.join(); + var mode = idx % 2 == 0 + ? AsyncFSMExecutor.ExecutionMode.SERIAL_REGIONS + : AsyncFSMExecutor.ExecutionMode.PARALLEL_REGIONS; - { - var f = new CompletableFuture(); - System.out.println("> triggering event 1"); - executor.trigger("EV1", (e, t) -> { - System.out.println("myEvent1 consumed"); - f.complete(null); - }); - f.orTimeout(1000, TimeUnit.MILLISECONDS).join(); - } + System.out.println("> running executor with " + mode.name() + ", n-child-fsms: " + numberOFChildren); + var executor = FSMExecutors.newAsyncExecutor(fsm, AsyncFSMExecutor.ExecutionMode.PARALLEL_REGIONS); - { - var f = new CompletableFuture(); - System.out.println("> triggering event 2"); - executor.trigger("EV2", (e, t) -> { - System.out.println("EV2 consumed"); - f.complete(null); - }); - f.orTimeout(1000, TimeUnit.MILLISECONDS).join(); - } + executor.startAsync(); - s3EnteredF.orTimeout(1000, TimeUnit.MILLISECONDS).join(); + s1EnteredF.join(); - var consumeCount = new AtomicInteger(); - { - System.out.println("> triggering event 1"); - executor.trigger("EV1", (e, t) -> { - System.out.println("EV1 consumed for inner by state " + t.getTarget().getName() + " in fsm " + t.getOwningFSM().getName()); - consumeCount.incrementAndGet(); - }); - } + { + var f = new CompletableFuture(); + System.out.println("> triggering event 1"); + executor.trigger("EV1", (e, t) -> { + System.out.println("myEvent1 consumed"); + f.complete(null); + }); + f.join(); + } -// { -// System.out.println("> triggering event 3"); -// var f = new CompletableFuture(); -// executor.trigger("EV3", (e, t) -> { -// f.complete(null); -// System.out.println("EV3 consumed"); -// }); -// f.orTimeout(1000, TimeUnit.MILLISECONDS).join(); -// } + { + var f = new CompletableFuture(); + System.out.println("> triggering event 2"); + executor.trigger("EV2", (e, t) -> { + System.out.println("EV2 consumed"); + f.complete(null); + }); + f.join(); + } - s3s1F.orTimeout(10000*numberOFChildren, TimeUnit.MILLISECONDS).join(); + s3EnteredF.join(); - executor.stop(); + var consumeCount = new AtomicInteger(); + { + System.out.println("> triggering event 1"); + executor.trigger("EV1", (e, t) -> { + System.out.println("EV1 consumed for inner by state " + + t.getTarget().getName() + " in fsm " + t.getOwningFSM().getName() + ); + consumeCount.incrementAndGet(); + }); + } - Assert.assertEquals( - "EV1 should be consumed by each region R1..R"+numberOFChildren, - numberOFChildren, consumeCount.get() - ); + s3s1F.join(); + + executor.stop(); + + Assert.assertEquals( + "EV1 should be consumed by each region R1..R" + numberOFChildren, + numberOFChildren, consumeCount.get() + ); + + } } - @Test + @Test(timeout = 100_000) public void nestedFSMEventConsumedActionTest2() throws InterruptedException, ExecutionException { // +------------------+ @@ -2028,160 +2076,182 @@ public void nestedFSMEventConsumedActionTest2() throws InterruptedException, Exe // - does each region (R1, R2) consume the event EV1? - var s1EnteredF = new CompletableFuture(); - State s1 = State.newBuilder() - .withName("s1") - .withOnEntryAction((s, e) -> { - System.out.println("entered state " + s.getName()); - s1EnteredF.complete(null); - }) - .build(); - - State s2 = State.newBuilder() - .withName("s2") - .withOnEntryAction((s, e) -> { - System.out.println("entered state " + s.getName()); - }) - .build(); + for (int idx = 0; idx < NUM_ITERATIONS_SMALL_TESTS; idx++) { - Function fsmCreator = (i) -> { - State s1i = State.newBuilder() - .withName("s1i") + var s1EnteredF = new CompletableFuture(); + State s1 = State.newBuilder() + .withName("s1") .withOnEntryAction((s, e) -> { - System.out.println("cfsm" + i + " entered state " + s.getName()); - sleepRandom(0, 250); + System.out.println("entered state " + s.getName() + + " " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date())); + s1EnteredF.complete(null); }) .build(); - State s2i = State.newBuilder() - .withName("s2i") + State s2 = State.newBuilder() + .withName("s2") .withOnEntryAction((s, e) -> { - System.out.println("cfsm" + i + " entered state " + s.getName()); - sleepRandom(0, 250); + System.out.println("entered state " + s.getName() + + " " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()) + ); }) .build(); - Transition s1s2i = Transition.newBuilder() - .withTrigger("EV1") - .withSource(s1i) - .withTarget(s2i) - .build(); + Function fsmCreator = (i) -> { + State s1i = State.newBuilder() + .withName("s1i") + .withOnEntryAction((s, e) -> { + System.out.println("cfsm" + i + " entered state " + s.getName() + + " " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()) + ); + sleepRandom(0, 250); + }) + .build(); - FSM fsmChild = FSM.newBuilder() - .withName("Child " + i) - .withInitialState(s1i) - .withOwnedState(s1i, s2i) - .withTransitions(s1s2i) - .withFinalState(s2i) - .build(); + State s2i = State.newBuilder() + .withName("s2i") + .withOnEntryAction((s, e) -> { + System.out.println("cfsm" + i + " entered state " + s.getName() + + " " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date())); + sleepRandom(0, 250); + }) + .build(); - return fsmChild; - }; + Transition s1s2i = Transition.newBuilder() + .withTrigger("EV1") + .withSource(s1i) + .withTarget(s2i) + .build(); - int numberOFChildren = 10; + FSM fsmChild = FSM.newBuilder() + .withName("Child " + i) + .withInitialState(s1i) + .withOwnedState(s1i, s2i) + .withTransitions(s1s2i) + .withFinalState(s2i) + .build(); - var childFSMs = IntStream.range(1, numberOFChildren+1) - .mapToObj(i->fsmCreator.apply(i)).collect(Collectors.toList()); + return fsmChild; + }; - var s3EnteredF = new CompletableFuture(); - FSMState s3 = FSMState.newBuilder() - .withName("s3") - .withFSMs(childFSMs) - .withOnEntryAction((s, e) -> { - System.out.println("entering state " + s.getName()); - s3EnteredF.complete(null); - }) - .build(); + int numberOFChildren = ThreadLocalRandom.current().nextInt(30) + 1; - Transition s1s2 = Transition.newBuilder() - .withTrigger("EV1") - .withSource(s1) - .withTarget(s2) - .build(); + var childFSMs = IntStream.range(1, numberOFChildren + 1) + .mapToObj(i -> fsmCreator.apply(i)).collect(Collectors.toList()); - Transition s2s3 = Transition.newBuilder() - .withTrigger("EV2") - .withSource(s2) - .withTarget(s3) - .build(); + var s3EnteredF = new CompletableFuture(); + FSMState s3 = FSMState.newBuilder() + .withName("s3") + .withFSMs(childFSMs) + .withOnEntryAction((s, e) -> { + System.out.println("entering state " + s.getName() + + " " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date())); + s3EnteredF.complete(null); + }) + .build(); - Transition s3s1 = Transition.newBuilder() - .withTrigger("EV3") - .withSource(s3) - .withTarget(s1) - .withActions((t, e) -> System.out.println("transitioning from S3 to S1")) - .build(); + Transition s1s2 = Transition.newBuilder() + .withTrigger("EV1") + .withSource(s1) + .withTarget(s2) + .build(); - FSM fsm = FSM.newBuilder() - .withName("FSM") - .withInitialState(s1) - .withOwnedState(s1, s2, s3) - .withTransitions(s1s2, s1s2, s2s3, s3s1) - .withVerbose(true) - .build(); + Transition s2s3 = Transition.newBuilder() + .withTrigger("EV2") + .withSource(s2) + .withTarget(s3) + .build(); - var executor = FSMExecutors.newAsyncExecutor(fsm, AsyncFSMExecutor.ExecutionMode.PARALLEL_REGIONS); + Transition s3s1 = Transition.newBuilder() + .withTrigger("EV3") + .withSource(s3) + .withTarget(s1) + .withActions((t, e) -> System.out.println("transitioning from S3 to S1")) + .build(); - executor.startAsync(); + FSM fsm = FSM.newBuilder() + .withName("FSM") + .withInitialState(s1) + .withOwnedState(s1, s2, s3) + .withTransitions(s1s2, s1s2, s2s3, s3s1) + .withVerbose(true) + .build(); - s1EnteredF.join(); - { - var f = new CompletableFuture(); - System.out.println("> triggering event 1"); - executor.trigger("EV1", (e, t) -> { - System.out.println("myEvent1 consumed"); - f.complete(null); - }); - f.orTimeout(1000, TimeUnit.MILLISECONDS).join(); - } + var mode = idx % 2 == 0 + ? AsyncFSMExecutor.ExecutionMode.SERIAL_REGIONS + : AsyncFSMExecutor.ExecutionMode.PARALLEL_REGIONS; - { - var f = new CompletableFuture(); - System.out.println("> triggering event 2"); - executor.trigger("EV2", (e, t) -> { - System.out.println("EV2 consumed"); - f.complete(null); - }); - f.orTimeout(1000, TimeUnit.MILLISECONDS).join(); - } + System.out.println("> running executor with " + mode.name() + ", n-child-fsms: " + numberOFChildren); + var executor = FSMExecutors.newAsyncExecutor(fsm, mode); - s3EnteredF.orTimeout(1000, TimeUnit.MILLISECONDS).join(); + executor.startAsync(); - var consumeCount = new AtomicInteger(); - { - var f = new CompletableFuture(); - System.out.println("> triggering event 1"); - executor.trigger("EV1", (e, t) -> { - System.out.println("EV1 consumed for inner by state " - + t.getTarget().getName() + " in fsm " + t.getOwningFSM().getName()); - consumeCount.incrementAndGet(); - f.complete(null); - }); - f.orTimeout(1000*numberOFChildren, TimeUnit.MILLISECONDS).join(); - } + s1EnteredF.join(); - { - System.out.println("> triggering event 3"); - var f = new CompletableFuture(); - executor.trigger("EV3", (e, t) -> { - f.complete(null); - System.out.println("EV3 consumed"); - }); - f.orTimeout(10000*numberOFChildren, TimeUnit.MILLISECONDS).join(); - } + { + var f = new CompletableFuture(); + System.out.println("> triggering event 1"); + executor.trigger("EV1", (e, t) -> { + System.out.println("EV1 consumed " + + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date())); + f.complete(null); + }); + f.join(); + } - executor.stop(); + { + var f = new CompletableFuture(); + System.out.println("> triggering event 2"); + executor.trigger("EV2", (e, t) -> { + System.out.println("EV2 consumed " + + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()) + ); + f.complete(null); + }); + f.join(); + } - Assert.assertEquals( - "EV1 should be consumed by each region R1..R"+numberOFChildren, - numberOFChildren, consumeCount.get() - ); + s3EnteredF.join(); + + var consumeCount = new AtomicInteger(); + { + var f = new CompletableFuture(); + System.out.println("> triggering event 1"); + executor.trigger("EV1", (e, t) -> { + System.out.println("EV1 consumed for inner by state " + + t.getTarget().getName() + " in fsm " + t.getOwningFSM().getName() + + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()) + ); + consumeCount.incrementAndGet(); + f.complete(null); + }); + f.join(); + } + { + System.out.println("> triggering event 3"); + var f = new CompletableFuture(); + executor.trigger("EV3", (e, t) -> { + f.complete(null); + System.out.println("EV3 consumed" + + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()) + ); + }); + f.join(); + } + + executor.stop(); + + Assert.assertEquals( + "EV1 should be consumed by each region R1..R" + numberOFChildren, + numberOFChildren, consumeCount.get() + ); + } } - @Test + @Test(timeout = 100_000) public void nestedFSM() throws InterruptedException, ExecutionException { // +------------------+ @@ -2205,157 +2275,172 @@ public void nestedFSM() throws InterruptedException, ExecutionException { // - does each region (R1, R2) consume the event EV1? - var s1EnteredF = new CompletableFuture(); - State s1 = State.newBuilder() - .withName("s1") - .withOnEntryAction((s, e) -> { - System.out.println("entered state " + s.getName()); - s1EnteredF.complete(null); - }) - .build(); + for (int idx = 0; idx < NUM_ITERATIONS_SMALL_TESTS; idx++) { - State s2 = State.newBuilder() - .withName("s2") - .withOnEntryAction((s, e) -> { - System.out.println("entered state " + s.getName()); - }) - .build(); - - Function fsmCreator = (i) -> { - State s1i = State.newBuilder() - .withName("s1i") + var s1EnteredF = new CompletableFuture(); + State s1 = State.newBuilder() + .withName("s1") .withOnEntryAction((s, e) -> { - System.out.println("cfsm" + i + " entered state " + s.getName()); - sleepRandom(0, 250); + System.out.println("entered state " + s.getName()); + s1EnteredF.complete(null); }) .build(); - State s2i = State.newBuilder() - .withName("s2i") + State s2 = State.newBuilder() + .withName("s2") .withOnEntryAction((s, e) -> { - System.out.println("cfsm" + i + " entered state " + s.getName()); - sleepRandom(0, 250); + System.out.println("entered state " + s.getName()); }) .build(); - Transition s1s2i = Transition.newBuilder() - .withTrigger("EV1") - .withSource(s1i) - .withTarget(s2i) - .build(); + Function fsmCreator = (i) -> { + State s1i = State.newBuilder() + .withName("s1i") + .withOnEntryAction((s, e) -> { + System.out.println("cfsm" + i + " entered state " + s.getName()); + sleepRandom(0, 250); + }) + .build(); - FSM fsmChild = FSM.newBuilder() - .withName("Child " + i) - .withInitialState(s1i) - .withOwnedState(s1i, s2i) - .withTransitions(s1s2i) - .withFinalState(s2i) - .build(); + State s2i = State.newBuilder() + .withName("s2i") + .withOnEntryAction((s, e) -> { + System.out.println("cfsm" + i + " entered state " + s.getName()); + sleepRandom(0, 250); + }) + .build(); - return fsmChild; - }; + Transition s1s2i = Transition.newBuilder() + .withTrigger("EV1") + .withSource(s1i) + .withTarget(s2i) + .build(); - int numberOFChildren = 10; + FSM fsmChild = FSM.newBuilder() + .withName("Child " + i) + .withInitialState(s1i) + .withOwnedState(s1i, s2i) + .withTransitions(s1s2i) + .withFinalState(s2i) + .build(); - var childFSMs = IntStream.range(1, numberOFChildren+1) - .mapToObj(i->fsmCreator.apply(i)).collect(Collectors.toList()); + return fsmChild; + }; - var s3EnteredF = new CompletableFuture(); - FSMState s3 = FSMState.newBuilder() - .withName("s3") - .withFSMs(childFSMs) - .withOnEntryAction((s, e) -> { - System.out.println("entering state " + s.getName()); - s3EnteredF.complete(null); - }) - .build(); + int numberOFChildren = ThreadLocalRandom.current().nextInt(30) + 1; - Transition s1s2 = Transition.newBuilder() - .withTrigger("EV1") - .withSource(s1) - .withTarget(s2) - .build(); + var childFSMs = IntStream.range(1, numberOFChildren + 1) + .mapToObj(i -> fsmCreator.apply(i)).collect(Collectors.toList()); - Transition s2s3 = Transition.newBuilder() - .withTrigger("EV2") - .withSource(s2) - .withTarget(s3) - .build(); + var s3EnteredF = new CompletableFuture(); + FSMState s3 = FSMState.newBuilder() + .withName("s3") + .withFSMs(childFSMs) + .withOnEntryAction((s, e) -> { + System.out.println("entering state " + s.getName()); + s3EnteredF.complete(null); + }) + .build(); - Transition s3s1 = Transition.newBuilder() - .withTrigger("EV3") - .withSource(s3) - .withTarget(s1) - .withActions((t, e) -> System.out.println("transitioning from S3 to S1")) - .build(); + Transition s1s2 = Transition.newBuilder() + .withTrigger("EV1") + .withSource(s1) + .withTarget(s2) + .build(); - FSM fsm = FSM.newBuilder() - .withName("FSM") - .withInitialState(s1) - .withOwnedState(s1, s2, s3) - .withTransitions(s1s2, s1s2, s2s3, s3s1) - .withVerbose(true) - .build(); + Transition s2s3 = Transition.newBuilder() + .withTrigger("EV2") + .withSource(s2) + .withTarget(s3) + .build(); - var executor = FSMExecutors.newAsyncExecutor(fsm, AsyncFSMExecutor.ExecutionMode.PARALLEL_REGIONS); + Transition s3s1 = Transition.newBuilder() + .withTrigger("EV3") + .withSource(s3) + .withTarget(s1) + .withActions((t, e) -> System.out.println("transitioning from S3 to S1")) + .build(); - executor.startAsync(); - s1EnteredF.join(); + FSM fsm = FSM.newBuilder() + .withName("FSM") + .withInitialState(s1) + .withOwnedState(s1, s2, s3) + .withTransitions(s1s2, s1s2, s2s3, s3s1) + .withVerbose(true) + .build(); - { - var f = new CompletableFuture(); - System.out.println("> triggering event 1"); - executor.trigger("EV1", (e, t) -> { - System.out.println("myEvent1 consumed"); - f.complete(null); - }); - f.orTimeout(1000, TimeUnit.MILLISECONDS).join(); - } - { - var f = new CompletableFuture(); - System.out.println("> triggering event 2"); - executor.trigger("EV2", (e, t) -> { - System.out.println("EV2 consumed"); - f.complete(null); - }); - f.orTimeout(1000, TimeUnit.MILLISECONDS).join(); - } + var mode = idx % 2 == 0 + ? AsyncFSMExecutor.ExecutionMode.SERIAL_REGIONS + : AsyncFSMExecutor.ExecutionMode.PARALLEL_REGIONS; - s3EnteredF.orTimeout(1000, TimeUnit.MILLISECONDS).join(); + System.out.println("> running executor with " + mode.name() + ", n-child-fsms: " + numberOFChildren); + var executor = FSMExecutors.newAsyncExecutor(fsm, AsyncFSMExecutor.ExecutionMode.PARALLEL_REGIONS); - var consumeCount = new AtomicInteger(); - { - var f = new CompletableFuture(); - System.out.println("> triggering event 1"); - executor.trigger("EV1", (e, t) -> { - System.out.println("EV1 consumed for inner by state " - + t.getTarget().getName() + " in fsm " + t.getOwningFSM().getName()); - consumeCount.incrementAndGet(); - f.complete(null); - }); - f.orTimeout(1000*numberOFChildren, TimeUnit.MILLISECONDS).join(); - } + executor.startAsync(); - { - System.out.println("> triggering event 3"); - var f = new CompletableFuture(); - executor.trigger("EV3", (e, t) -> { - f.complete(null); - System.out.println("EV3 consumed"); - }); - f.orTimeout(10000*numberOFChildren, TimeUnit.MILLISECONDS).join(); - } + s1EnteredF.join(); - executor.stop(); + { + var f = new CompletableFuture(); + System.out.println("> triggering event 1"); + executor.trigger("EV1", (e, t) -> { + System.out.println("EV1 consumed " + + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()) + ); + f.complete(null); + }); + f.join(); + } - Assert.assertEquals( - "EV1 should be consumed by each region R1..R"+numberOFChildren, - numberOFChildren, consumeCount.get() - ); + { + var f = new CompletableFuture(); + System.out.println("> triggering event 2"); + executor.trigger("EV2", (e, t) -> { + System.out.println("EV2 consumed " + + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()) + ); + f.complete(null); + }); + f.join(); + } - } + s3EnteredF.join(); + var consumeCount = new AtomicInteger(); + { + var f = new CompletableFuture(); + System.out.println("> triggering event 1"); + executor.trigger("EV1", (e, t) -> { + System.out.println("EV1 consumed for inner by state " + + t.getTarget().getName() + " in fsm " + t.getOwningFSM().getName() + " " + + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()) + ); + consumeCount.incrementAndGet(); + f.complete(null); + }); + f.join(); + } + { + System.out.println("> triggering event 3"); + var f = new CompletableFuture(); + executor.trigger("EV3", (e, t) -> { + f.complete(null); + System.out.println("EV3 consumed: " + + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()) + ); + }); + f.join(); + } + + executor.stop(); + + Assert.assertEquals( + "EV1 should be consumed by each region R1..R" + numberOFChildren, + numberOFChildren, consumeCount.get() + ); + } + } } diff --git a/subprojects/fsm/src/main/java/eu/mihosoft/vsm/model/FSMExecutor.java b/subprojects/fsm/src/main/java/eu/mihosoft/vsm/model/FSMExecutor.java index 7d3d0af..9269fca 100644 --- a/subprojects/fsm/src/main/java/eu/mihosoft/vsm/model/FSMExecutor.java +++ b/subprojects/fsm/src/main/java/eu/mihosoft/vsm/model/FSMExecutor.java @@ -54,20 +54,51 @@ default void trigger(String evt, Object... args) { void trigger(Event event); /** - * Triggers and processes the specified event. The state machine must - * not be running if this method should be used. + * Triggers and processes the specified event. The executor of the state machine must + * not be running (via {@link Executor#isRunning()}) if this method is used to process events. + * This method returns as soon as the triggered event and events created as consequence + * of triggering this event have been processed. * @param evt event identifier * @param args optional event arguments * @return {@code true} if the method processed events; {@code false} otherwise */ boolean process(String evt, Object... args); + /** + * Triggers and processes the specified event. The executor of the state machine must + * not be running (via {@link Executor#isRunning()}) if this method is used to process events. + * This method returns as soon as the triggered event and events created as consequence + * of triggering this event have been processed. + * @param evt event identifier + * @return {@code true} if the method processed events; {@code false} otherwise + */ + public boolean process(Event evt); + + /** + * Triggers and processes the specified event. The executor of the state machine must + * not be running (via {@link Executor#isRunning()}) if this method is used to process events. + * This method returns as soon as the triggered event and events created as consequence + * of triggering this event have been processed. + * @param evt event identifier + * @param onConsumed an optional action that is executed if and when the event is consumed + * @param args optional event arguments + * @return {@code true} if the method processed events; {@code false} otherwise + */ + public boolean process(String evt, EventConsumedAction onConsumed, Object... args); + /** * Processes events that are on the event queue and haven't been processed yet. * @return {@code true} if the method processed events; {@code false} otherwise */ boolean processRemainingEvents(); + /** + * Indicates whether this executor is currently running. + * + * @return {@code true} if this executor is currently running; {@code false} otherwise + */ + boolean isRunning(); + /** * Resets the associated state machine excluding nested state machines. */ @@ -119,8 +150,10 @@ default void trigger(String evt, Object... args) { /** * Events triggered by the state machine. + * + *

Caution: Do not trigger these events manually.

*/ - public enum FSMEvents { + enum FSMEvents { /** * Triggerred if state is done. diff --git a/subprojects/fsm/src/main/vmf/eu/mihosoft/vsm/model/vmfmodel/FSM.java b/subprojects/fsm/src/main/vmf/eu/mihosoft/vsm/model/vmfmodel/FSM.java index 76c75b0..5ed5008 100644 --- a/subprojects/fsm/src/main/vmf/eu/mihosoft/vsm/model/vmfmodel/FSM.java +++ b/subprojects/fsm/src/main/vmf/eu/mihosoft/vsm/model/vmfmodel/FSM.java @@ -22,28 +22,6 @@ */ package eu.mihosoft.vsm.model.vmfmodel; -/** - * Copyright 2019-200 Michael Hoffer . All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - * If you use this software for scientific research then please cite the following publication(s): - * - * M. Hoffer, C. Poliwoda, & G. Wittum. (2013). Visual reflection library: - * a framework for declarative GUI programming on the Java platform. - * Computing and Visualization in Science, 2013, 16(4), - * 181–192. http://doi.org/10.1007/s00791-014-0230-y - */ import eu.mihosoft.vmf.core.*; import eu.mihosoft.vmf.core.VMFEquals.EqualsType; @@ -86,18 +64,24 @@ interface WithArgs { } +@Doc("Child of an FSM.") @InterfaceOnly interface FSMChild { + @Doc("FSM that owns this object.") FSM getOwningFSM(); } +@Doc("Parent of an FSM.") @InterfaceOnly interface FSMParent { + @Doc("Child FSMs.") FSM[] getFSMs(); } +@Doc("Child of an FSM state.") @InterfaceOnly interface StateChild { + @Doc("Parent state.") FSMState getParentState(); } @@ -176,7 +160,6 @@ interface State extends FSMChild, WithName, WithStateActions { @DefaultValue("java.time.Duration.ofSeconds(1)") Duration getCancellationTimeout(); - } @Doc("A state that contains a list of state machines" + diff --git a/subprojects/vrlfsm/build.gradle b/subprojects/vrlfsm/build.gradle index 61ebe0f..eacbdbd 100644 --- a/subprojects/vrlfsm/build.gradle +++ b/subprojects/vrlfsm/build.gradle @@ -18,14 +18,14 @@ plugins { id 'idea' // id 'com.github.hierynomus.license' version '0.14.0' - id 'maven-publish' +// id 'maven-publish' id 'net.nemerosa.versioning' version '2.7.1' id 'com.github.ben-manes.versions' version '0.13.0' } // load plugins apply plugin: 'eu.mihosoft.vmf' -apply from: 'gradle/publishing.gradle' +//apply from: 'gradle/publishing.gradle' repositories { mavenCentral()