59 private final String m_name;
61 private State m_initialState =
null;
62 private final List<State> m_states =
new ArrayList<>();
66 private boolean m_closedForNewStates =
false;
75 requireNonNullParam(
name,
"name",
"StateMachine");
138 requireNonNullParam(command,
"command",
"StateMachine.addState");
139 if (m_closedForNewStates) {
140 throw new IllegalStateException(
141 "Cannot add new states to a state machine after switchFromAny() has been called with no"
144 var state =
new State(
this, command);
177 if (states.length == 0) {
178 m_closedForNewStates =
true;
179 return new TransitionNeedsTargetStage(List.copyOf(m_states));
181 return new TransitionNeedsTargetStage(List.of(states));
196 requireNonNullParam(initialState,
"initialState",
"StateMachine.setInitialState");
197 if (!this.equals(initialState.m_stateMachine)) {
198 throw new IllegalArgumentException(
"Cannot set initial state in a different state machine");
200 m_initialState = initialState;
205 if (m_initialState ==
null) {
206 throw new IllegalStateException(
207 m_name +
" does not have an initial state. Use .setInitialState() to provide one.");
210 var currentState = m_initialState;
213 while (currentState !=
null) {
214 final var currentCommand = currentState.command();
217 coroutine.
fork(currentCommand);
218 currentState.runEnterCallbacks();
219 boolean didYield =
false;
222 for (var transition : currentState.transitions()) {
223 if (transition.shouldTransition()) {
232 currentState.runExitCallbacks();
234 currentState = verifyState(transition.nextState());
252 currentState.runExitCallbacks();
253 currentState = verifyState(currentState.nextState());
254 if (!didYield && currentState !=
null) {
261 private State verifyState(State next) {
262 if (next ==
null || this.equals(next.m_stateMachine)) {
268 throw new IllegalStateException(
269 "The next state does not belong to this state machine. Check the state for "
270 + next.command().name());
281 public static final class State {
286 private final Command m_command;
289 private final List<Completion> m_completions =
new ArrayList<>();
292 private Supplier<State> m_defaultNextState = () ->
null;
298 private final List<Transition> m_transitions =
new ArrayList<>();
300 private final List<Runnable> m_enterCallbacks =
new ArrayList<>();
301 private final List<Runnable> m_exitCallbacks =
new ArrayList<>();
303 private State(
StateMachine stateMachine, Command command) {
304 m_stateMachine = stateMachine;
308 private Command command() {
312 private List<Transition> transitions() {
313 return m_transitions;
316 private void addTransition(Transition transition) {
317 m_transitions.add(transition);
327 private void setNextState(Supplier<State> nextState) {
328 m_defaultNextState = nextState;
334 @SuppressWarnings(
"PMD.CompareObjectsWithEquals")
335 private
void addCompletion(BooleanSupplier condition, Supplier<State> next) {
337 m_completions.removeIf(c -> c.getCondition() == condition);
338 m_completions.add(
new Completion(next, condition));
341 private State nextState() {
342 for (var completion : m_completions) {
343 if (completion.shouldTransition()) {
344 return completion.nextState();
350 return m_defaultNextState.get();
353 private void runEnterCallbacks() {
354 m_enterCallbacks.forEach(Runnable::run);
357 private void runExitCallbacks() {
358 m_exitCallbacks.forEach(Runnable::run);
370 public void onEnter(Runnable callback) {
371 requireNonNullParam(callback,
"callback",
"State.onEnter");
372 m_enterCallbacks.add(callback);
383 public void onExit(Runnable callback) {
384 requireNonNullParam(callback,
"callback",
"State.onExit");
385 m_exitCallbacks.add(callback);
394 public TransitionNeedsConditionStage switchTo(State to) {
395 requireNonNullParam(to,
"to",
"State.switchTo");
396 if (!m_stateMachine.equals(to.m_stateMachine)) {
397 throw new IllegalArgumentException(
398 "Cannot transition to a state in a different state machine");
400 return new TransitionNeedsTargetStage(List.of(
this)).to(to);
410 public TransitionNeedsConditionStage switchTo(Supplier<State> dynamic) {
411 requireNonNullParam(dynamic,
"dynamic",
"State.switchTo");
414 return new TransitionNeedsTargetStage(List.of(
this)).to(dynamic);
423 public TransitionNeedsConditionStage exitStateMachine() {
424 return new TransitionNeedsConditionStage(List.of(
this), () ->
null);
432 @NoDiscard(
"Use .to() to specify the target state")
433 public static final class TransitionNeedsTargetStage {
434 private final List<State> m_from;
436 private TransitionNeedsTargetStage(List<State> from) {
446 public TransitionNeedsConditionStage to(State to) {
447 requireNonNullParam(to,
"to",
"NeedsTargetTransitionBuilder.to");
448 for (var state : m_from) {
449 if (!state.m_stateMachine.equals(to.m_stateMachine)) {
450 throw new IllegalArgumentException(
451 "Cannot transition to a state in a different state machine");
454 return new TransitionNeedsConditionStage(m_from, () -> to);
464 public TransitionNeedsConditionStage to(Supplier<State> dynamic) {
465 requireNonNullParam(dynamic,
"dynamic",
"NeedsTargetTransitionBuilder.to");
466 return new TransitionNeedsConditionStage(m_from, dynamic);
475 public TransitionNeedsConditionStage toExitStateMachine() {
476 return new TransitionNeedsConditionStage(m_from, () ->
null);
486 @NoDiscard(
"Use .when() or .whenComplete() to specify the transition condition")
487 public static final class TransitionNeedsConditionStage {
488 private final List<State> m_originatingStates;
492 private final Supplier<State> m_targetStateSupplier;
494 private TransitionNeedsConditionStage(List<State> from, Supplier<State> to) {
495 requireNonNullParam(from,
"from",
"TransitionNeedsConditionStage");
496 requireNonNullParam(to,
"to",
"TransitionNeedsConditionStage");
498 m_originatingStates = from;
499 m_targetStateSupplier = to;
525 public void when(BooleanSupplier condition) {
526 requireNonNullParam(condition,
"condition",
"NeedsConditionTransitionBuilder.when");
527 var transition =
new Transition(m_targetStateSupplier, condition);
528 m_originatingStates.forEach(originatingState -> originatingState.addTransition(transition));
549 public void whenComplete() {
550 m_originatingStates.forEach(state -> state.setNextState(m_targetStateSupplier));
572 public void whenCompleteAnd(BooleanSupplier condition) {
573 requireNonNullParam(condition,
"condition",
"NeedsConditionTransitionBuilder.whenComplete");
574 m_originatingStates.forEach(state -> state.addCompletion(condition, m_targetStateSupplier));
582 private static final class Completion {
583 private final Supplier<State> m_nextSupplier;
584 private final BooleanSupplier m_condition;
592 private Completion(Supplier<State> next, BooleanSupplier condition) {
593 m_nextSupplier = next;
594 m_condition = condition;
597 private boolean shouldTransition() {
598 return m_condition.getAsBoolean();
601 public State nextState() {
602 return m_nextSupplier.get();
605 public BooleanSupplier getCondition() {
614 private static final class Transition {
616 private final Supplier<State> m_nextSupplier;
619 private final BooleanSupplier m_condition;
621 private boolean m_previousSignal =
false;
623 private Transition(Supplier<State> next, BooleanSupplier condition) {
624 m_nextSupplier = next;
625 m_condition = condition;
629 private boolean shouldTransition() {
634 boolean currentValue = m_condition.getAsBoolean();
635 boolean isRisingEdge = currentValue && !m_previousSignal;
636 m_previousSignal = currentValue;
640 private State nextState() {
641 return m_nextSupplier.get();