From b8f5320e38a8eae113857fb73120b5015b49462b Mon Sep 17 00:00:00 2001 From: Janne Valkealahti Date: Wed, 11 Feb 2015 13:36:52 +0000 Subject: [PATCH] Base support for local transitions - resolves #5 - big conceptual changes to how sub-machines are handled order to get events working with a multi-level sub states. - added more typing throughout few interfaces to properly pass in state context via action axecution chain. this type change effectively caused change to most of the classes but not that much functional change of behaviour. - cleaning and tidy up --- build.gradle | 1 + gradle.properties | 2 + .../statemachine/StateContext.java | 11 +- .../statemachine/StateMachine.java | 11 +- .../statemachine/action/Action.java | 8 +- .../config/builders/StateMachineStates.java | 10 +- .../StateMachineTransitionBuilder.java | 9 +- .../StateMachineTransitionConfigurer.java | 3 + .../builders/StateMachineTransitions.java | 10 +- .../DefaultExternalTransitionConfigurer.java | 10 +- .../DefaultInternalTransitionConfigurer.java | 10 +- .../DefaultLocalTransitionConfigurer.java | 98 ++++++++ .../configurers/DefaultStateConfigurer.java | 2 +- .../LocalTransitionConfigurer.java | 39 +++ .../config/configurers/StateConfigurer.java | 2 +- .../configurers/TransitionConfigurer.java | 4 +- .../statemachine/guard/Guard.java | 8 +- .../guard/SpelExpressionGuard.java | 4 +- .../state/AbstractSimpleState.java | 8 +- .../statemachine/state/AbstractState.java | 43 ++-- .../statemachine/state/EnumState.java | 29 ++- .../statemachine/state/RegionState.java | 23 +- .../statemachine/state/State.java | 27 +- .../statemachine/state/StateMachineState.java | 63 ++++- .../support/AbstractStateMachine.java | 103 ++++---- .../support/DefaultStateContext.java | 13 +- .../support/StateMachineUtils.java | 49 ++++ .../AbstractExternalTransition.java | 2 +- .../AbstractInternalTransition.java | 2 +- .../transition/AbstractLocalTransition.java | 2 +- .../transition/AbstractTransition.java | 14 +- .../transition/DefaultExternalTransition.java | 2 +- .../transition/DefaultInternalTransition.java | 2 +- .../transition/DefaultLocalTransition.java | 35 +++ .../statemachine/transition/Transition.java | 4 +- .../AbstractStateMachineTests.java | 89 +++++-- .../statemachine/EnumStateMachineTests.java | 20 +- .../statemachine/StateMachineTests.java | 7 +- .../statemachine/SubStateMachineTests.java | 234 ++++++++++++++++++ .../statemachine/action/ActionTests.java | 4 +- .../config/ConfigurationTests.java | 29 +++ .../SimpleAnnotationConfiguration2Tests.java | 1 - .../guard/SpelExpressionGuardTests.java | 4 +- .../statemachine/listener/ListenerTests.java | 4 +- .../statemachine/state/StateActionTests.java | 10 +- .../transition/TransitionTests.java | 14 +- .../src/test/resources/log4j.properties | 8 + 47 files changed, 882 insertions(+), 205 deletions(-) create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultLocalTransitionConfigurer.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/LocalTransitionConfigurer.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineUtils.java create mode 100644 spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultLocalTransition.java create mode 100644 spring-statemachine-core/src/test/java/org/springframework/statemachine/SubStateMachineTests.java create mode 100644 spring-statemachine-core/src/test/resources/log4j.properties diff --git a/build.gradle b/build.gradle index c2ab7a4a..e9b55cbe 100644 --- a/build.gradle +++ b/build.gradle @@ -100,6 +100,7 @@ project('spring-statemachine-core') { testCompile "org.hamcrest:hamcrest-core:$hamcrestVersion" testCompile "org.hamcrest:hamcrest-library:$hamcrestVersion" testCompile "junit:junit:$junitVersion" + testRuntime("log4j:log4j:$log4jVersion") } } diff --git a/gradle.properties b/gradle.properties index a7b02616..a0694db1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,3 +2,5 @@ version=1.0.0.BUILD-SNAPSHOT springVersion = 4.1.4.RELEASE hamcrestVersion = 1.3 junitVersion = 4.11 +log4jVersion = 1.2.17 + diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateContext.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateContext.java index a07c77c9..fea23026 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateContext.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateContext.java @@ -24,11 +24,11 @@ import org.springframework.statemachine.transition.Transition; * {@code StateContext} is representing a current context used in * {@link Transition}s, {@link Action}s and {@link Guard}s order to get access * to event headers and {@link ExtendedState}. - * + * * @author Janne Valkealahti * */ -public interface StateContext { +public interface StateContext { /** * Gets the event message headers. @@ -44,4 +44,11 @@ public interface StateContext { */ ExtendedState getExtendedState(); + /** + * Gets the transition. + * + * @return the transition + */ + Transition getTransition(); + } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachine.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachine.java index 7efe869f..1982fb6a 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachine.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/StateMachine.java @@ -23,7 +23,7 @@ import org.springframework.statemachine.state.State; /** * {@code StateMachine} provides an APIs for generic finite state machine needed * for basic operations like working with states, events and a lifecycle. - * + * * @author Janne Valkealahti * * @param the type of state @@ -43,17 +43,22 @@ public interface StateMachine extends Region { */ void start(); + /** + * Stop the state machine. + */ + void stop(); + /** * Send an event {@code E} wrapped with a {@link Message} to the state * machine. - * + * * @param event the wrapped event to send */ void sendEvent(Message event); /** * Send an event {@code E} to the state machine. - * + * * @param event the event to send */ void sendEvent(E event); diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/action/Action.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/action/Action.java index 41a10ee3..c63aad28 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/action/Action.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/action/Action.java @@ -22,15 +22,17 @@ import org.springframework.statemachine.StateContext; * events by executing an {@code Action} with a {@link StateContext}. * * @author Janne Valkealahti - * + * + * @param the type of state + * @param the type of event */ -public interface Action { +public interface Action { /** * Execute action with a {@link StateContext}. * * @param context the state context */ - void execute(StateContext context); + void execute(StateContext context); } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStates.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStates.java index 39633e80..e83488d9 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStates.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineStates.java @@ -50,12 +50,12 @@ public class StateMachineStates { public static class StateData { private S state; private Collection deferred; - private Collection entryActions; - private Collection exitActions; + private Collection> entryActions; + private Collection> exitActions; public StateData(S state, Collection deferred) { this(state, deferred, null, null); } - public StateData(S state, Collection deferred, Collection entryActions, Collection exitActions) { + public StateData(S state, Collection deferred, Collection> entryActions, Collection> exitActions) { this.state = state; this.deferred = deferred; this.entryActions = entryActions; @@ -70,10 +70,10 @@ public class StateMachineStates { public Collection getDeferred() { return deferred; } - public Collection getEntryActions() { + public Collection> getEntryActions() { return entryActions; } - public Collection getExitActions() { + public Collection> getExitActions() { return exitActions; } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionBuilder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionBuilder.java index ec59f9fb..971ad6c2 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionBuilder.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionBuilder.java @@ -24,8 +24,10 @@ import org.springframework.statemachine.config.common.annotation.AbstractConfigu import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor; import org.springframework.statemachine.config.configurers.DefaultExternalTransitionConfigurer; import org.springframework.statemachine.config.configurers.DefaultInternalTransitionConfigurer; +import org.springframework.statemachine.config.configurers.DefaultLocalTransitionConfigurer; import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer; import org.springframework.statemachine.config.configurers.InternalTransitionConfigurer; +import org.springframework.statemachine.config.configurers.LocalTransitionConfigurer; import org.springframework.statemachine.guard.Guard; import org.springframework.statemachine.transition.TransitionKind; @@ -64,8 +66,13 @@ public class StateMachineTransitionBuilder public InternalTransitionConfigurer withInternal() throws Exception { return apply(new DefaultInternalTransitionConfigurer()); } + + @Override + public LocalTransitionConfigurer withLocal() throws Exception { + return apply(new DefaultLocalTransitionConfigurer()); + } - public void add(S source, S target, E event, Collection actions, Guard guard, TransitionKind kind) { + public void add(S source, S target, E event, Collection> actions, Guard guard, TransitionKind kind) { transitionData.add(new TransitionData(source, target, event, actions, guard, kind)); } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionConfigurer.java index a1cc39a4..a5d776c0 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionConfigurer.java @@ -17,11 +17,14 @@ package org.springframework.statemachine.config.builders; import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer; import org.springframework.statemachine.config.configurers.InternalTransitionConfigurer; +import org.springframework.statemachine.config.configurers.LocalTransitionConfigurer; public interface StateMachineTransitionConfigurer { ExternalTransitionConfigurer withExternal() throws Exception; InternalTransitionConfigurer withInternal() throws Exception; + + LocalTransitionConfigurer withLocal() throws Exception; } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitions.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitions.java index f71a8a15..eb08cfd1 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitions.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitions.java @@ -37,10 +37,10 @@ public class StateMachineTransitions { S source; S target; E event; - Collection actions; - Guard guard; + Collection> actions; + Guard guard; TransitionKind kind; - public TransitionData(S source, S target, E event, Collection actions, Guard guard, TransitionKind kind) { + public TransitionData(S source, S target, E event, Collection> actions, Guard guard, TransitionKind kind) { this.source = source; this.target = target; this.event = event; @@ -57,10 +57,10 @@ public class StateMachineTransitions { public E getEvent() { return event; } - public Collection getActions() { + public Collection> getActions() { return actions; } - public Guard getGuard() { + public Guard getGuard() { return guard; } public TransitionKind getKind() { diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultExternalTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultExternalTransitionConfigurer.java index baaa93fd..a47cd53d 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultExternalTransitionConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultExternalTransitionConfigurer.java @@ -48,9 +48,9 @@ public class DefaultExternalTransitionConfigurer private E event; - private Collection actions = new ArrayList(); + private Collection> actions = new ArrayList>(); - private Guard guard; + private Guard guard; @Override public void configure(StateMachineTransitionBuilder builder) throws Exception { @@ -76,13 +76,13 @@ public class DefaultExternalTransitionConfigurer } @Override - public ExternalTransitionConfigurer action(Action action) { + public ExternalTransitionConfigurer action(Action action) { actions.add(action); return this; } @Override - public ExternalTransitionConfigurer guard(Guard guard) { + public ExternalTransitionConfigurer guard(Guard guard) { this.guard = guard; return this; } @@ -91,7 +91,7 @@ public class DefaultExternalTransitionConfigurer public ExternalTransitionConfigurer guardExpression(String expression) { SpelExpressionParser parser = new SpelExpressionParser( new SpelParserConfiguration(SpelCompilerMode.MIXED, null)); - this.guard = new SpelExpressionGuard(parser.parseExpression(expression)); + this.guard = new SpelExpressionGuard(parser.parseExpression(expression)); return this; } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultInternalTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultInternalTransitionConfigurer.java index c976f825..a00a38a8 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultInternalTransitionConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultInternalTransitionConfigurer.java @@ -48,9 +48,9 @@ public class DefaultInternalTransitionConfigurer private E event; - private Collection actions = new ArrayList(); + private Collection> actions = new ArrayList>(); - private Guard guard; + private Guard guard; @Override public void configure(StateMachineTransitionBuilder builder) throws Exception { @@ -70,13 +70,13 @@ public class DefaultInternalTransitionConfigurer } @Override - public InternalTransitionConfigurer action(Action action) { + public InternalTransitionConfigurer action(Action action) { actions.add(action); return this; } @Override - public InternalTransitionConfigurer guard(Guard guard) { + public InternalTransitionConfigurer guard(Guard guard) { this.guard = guard; return this; } @@ -85,7 +85,7 @@ public class DefaultInternalTransitionConfigurer public InternalTransitionConfigurer guardExpression(String expression) { SpelExpressionParser parser = new SpelExpressionParser( new SpelParserConfiguration(SpelCompilerMode.MIXED, null)); - this.guard = new SpelExpressionGuard(parser.parseExpression(expression)); + this.guard = new SpelExpressionGuard(parser.parseExpression(expression)); return this; } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultLocalTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultLocalTransitionConfigurer.java new file mode 100644 index 00000000..ebbcd239 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultLocalTransitionConfigurer.java @@ -0,0 +1,98 @@ +/* + * Copyright 2015 the original author or authors. + * + * 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. + */ +package org.springframework.statemachine.config.configurers; + +import java.util.ArrayList; +import java.util.Collection; + +import org.springframework.expression.spel.SpelCompilerMode; +import org.springframework.expression.spel.SpelParserConfiguration; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.config.builders.StateMachineTransitions; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.guard.SpelExpressionGuard; +import org.springframework.statemachine.transition.TransitionKind; + +/** + * Default implementation of a {@link LocalTransitionConfigurer}. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class DefaultLocalTransitionConfigurer + extends AnnotationConfigurerAdapter, StateMachineTransitionConfigurer, StateMachineTransitionBuilder> + implements LocalTransitionConfigurer { + + private S source; + + private S target; + + private E event; + + private Collection> actions = new ArrayList>(); + + private Guard guard; + + @Override + public void configure(StateMachineTransitionBuilder builder) throws Exception { + builder.add(source, target, event, actions, guard, TransitionKind.LOCAL); + } + + @Override + public LocalTransitionConfigurer source(S source) { + this.source = source; + return this; + } + + @Override + public LocalTransitionConfigurer target(S target) { + this.target = target; + return this; + } + + @Override + public LocalTransitionConfigurer event(E event) { + this.event = event; + return this; + } + + @Override + public LocalTransitionConfigurer action(Action action) { + actions.add(action); + return this; + } + + @Override + public LocalTransitionConfigurer guard(Guard guard) { + this.guard = guard; + return this; + } + + @Override + public LocalTransitionConfigurer guardExpression(String expression) { + SpelExpressionParser parser = new SpelExpressionParser( + new SpelParserConfiguration(SpelCompilerMode.MIXED, null)); + this.guard = new SpelExpressionGuard(parser.parseExpression(expression)); + return this; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurer.java index aaf97fac..fb107a2e 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultStateConfigurer.java @@ -61,7 +61,7 @@ public class DefaultStateConfigurer } @Override - public StateConfigurer state(S state, Collection entryActions, Collection exitActions) { + public StateConfigurer state(S state, Collection> entryActions, Collection> exitActions) { states.add(new StateData(state, null, entryActions, exitActions)); return this; } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/LocalTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/LocalTransitionConfigurer.java new file mode 100644 index 00000000..582b14ed --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/LocalTransitionConfigurer.java @@ -0,0 +1,39 @@ +/* + * Copyright 2015 the original author or authors. + * + * 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. + */ +package org.springframework.statemachine.config.configurers; + +import org.springframework.statemachine.transition.Transition; + +/** + * {@code TransitionConfigurer} interface for configuring local {@link Transition}s. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface LocalTransitionConfigurer extends + TransitionConfigurer, S, E> { + + /** + * Specify a target state {@code S} for this {@link Transition}. + * + * @param target the target state {@code S} + * @return configurer for chaining + */ + LocalTransitionConfigurer target(S target); + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/StateConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/StateConfigurer.java index c2aa3856..bc64e4fc 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/StateConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/StateConfigurer.java @@ -29,7 +29,7 @@ public interface StateConfigurer extends StateConfigurer state(S state); - StateConfigurer state(S state, Collection entryActions, Collection exitActions); + StateConfigurer state(S state, Collection> entryActions, Collection> exitActions); StateConfigurer state(S state, E... deferred); diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/TransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/TransitionConfigurer.java index 01690fbd..fead6e7d 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/TransitionConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/TransitionConfigurer.java @@ -55,7 +55,7 @@ public interface TransitionConfigurer extends * @param action the action * @return configurer for chaining */ - T action(Action action); + T action(Action action); /** * Specify a {@link Guard} for this {@link Transition}. @@ -63,7 +63,7 @@ public interface TransitionConfigurer extends * @param guard the guard * @return configurer for chaining */ - T guard(Guard guard); + T guard(Guard guard); /** * Specify a {@link Guard} backed by a SpEL expression for this {@link Transition}. diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/guard/Guard.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/guard/Guard.java index b0477733..440f2a2a 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/guard/Guard.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/guard/Guard.java @@ -24,9 +24,11 @@ import org.springframework.statemachine.StateContext; * {@code FALSE}. * * @author Janne Valkealahti - * + * + * @param the type of state + * @param the type of event */ -public interface Guard { +public interface Guard { /** * Evaluate a guard condition. @@ -34,6 +36,6 @@ public interface Guard { * @param context the state context * @return true, if guard evaluation is successful, false otherwise. */ - boolean evaluate(StateContext context); + boolean evaluate(StateContext context); } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/guard/SpelExpressionGuard.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/guard/SpelExpressionGuard.java index 7b731ab3..1a83718d 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/guard/SpelExpressionGuard.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/guard/SpelExpressionGuard.java @@ -26,7 +26,7 @@ import org.springframework.util.Assert; * @author Janne Valkealahti * */ -public class SpelExpressionGuard implements Guard { +public class SpelExpressionGuard implements Guard { private final Expression expression; @@ -41,7 +41,7 @@ public class SpelExpressionGuard implements Guard { } @Override - public boolean evaluate(StateContext context) { + public boolean evaluate(StateContext context) { StandardEvaluationContext evaluationContext = new StandardEvaluationContext(context); return expression.getValue(evaluationContext, Boolean.class); } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractSimpleState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractSimpleState.java index 07b22e52..30408e72 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractSimpleState.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractSimpleState.java @@ -52,7 +52,7 @@ public abstract class AbstractSimpleState extends AbstractState { * @param entryActions the entry actions * @param exitActions the exit actions */ - public AbstractSimpleState(S id, Collection deferred, Collection entryActions, Collection exitActions) { + public AbstractSimpleState(S id, Collection deferred, Collection> entryActions, Collection> exitActions) { this(id, deferred, entryActions, exitActions, null); } @@ -86,7 +86,7 @@ public abstract class AbstractSimpleState extends AbstractState { * @param pseudoState the pseudo state * @param regions the regions */ - public AbstractSimpleState(S id, Collection deferred, Collection entryActions, Collection exitActions, + public AbstractSimpleState(S id, Collection deferred, Collection> entryActions, Collection> exitActions, PseudoState pseudoState, Collection> regions) { super(deferred, entryActions, exitActions, pseudoState, regions); this.ids = new ArrayList(); @@ -103,7 +103,7 @@ public abstract class AbstractSimpleState extends AbstractState { * @param pseudoState the pseudo state * @param submachine the submachine */ - public AbstractSimpleState(S id, Collection deferred, Collection entryActions, Collection exitActions, + public AbstractSimpleState(S id, Collection deferred, Collection> entryActions, Collection> exitActions, PseudoState pseudoState, StateMachine submachine) { super(deferred, entryActions, exitActions, pseudoState, submachine); this.ids = new ArrayList(); @@ -119,7 +119,7 @@ public abstract class AbstractSimpleState extends AbstractState { * @param exitActions the exit actions * @param pseudoState the pseudo state */ - public AbstractSimpleState(S id, Collection deferred, Collection entryActions, Collection exitActions, + public AbstractSimpleState(S id, Collection deferred, Collection> entryActions, Collection> exitActions, PseudoState pseudoState) { super(deferred, entryActions, exitActions, pseudoState); this.ids = new ArrayList(); diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractState.java index 3fcb0c1b..9cd0e30d 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractState.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractState.java @@ -18,6 +18,7 @@ package org.springframework.statemachine.state; import java.util.ArrayList; import java.util.Collection; +import org.springframework.statemachine.StateContext; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.action.Action; import org.springframework.statemachine.region.Region; @@ -25,7 +26,7 @@ import org.springframework.util.StringUtils; /** * Base implementation of a {@link State}. - * + * * @author Janne Valkealahti * * @param the type of state @@ -35,8 +36,8 @@ public abstract class AbstractState implements State { private final PseudoState pseudoState; private final Collection deferred; - private final Collection entryActions; - private final Collection exitActions; + private final Collection> entryActions; + private final Collection> exitActions; private final Collection> regions = new ArrayList>(); private final StateMachine submachine; @@ -48,7 +49,7 @@ public abstract class AbstractState implements State { public AbstractState(PseudoState pseudoState) { this(null, null, null, pseudoState); } - + /** * Instantiates a new abstract state. * @@ -65,10 +66,10 @@ public abstract class AbstractState implements State { * @param entryActions the entry actions * @param exitActions the exit actions */ - public AbstractState(Collection deferred, Collection entryActions, Collection exitActions) { + public AbstractState(Collection deferred, Collection> entryActions, Collection> exitActions) { this(deferred, entryActions, exitActions, null); } - + /** * Instantiates a new abstract state. * @@ -77,7 +78,7 @@ public abstract class AbstractState implements State { * @param exitActions the exit actions * @param pseudoState the pseudo state */ - public AbstractState(Collection deferred, Collection entryActions, Collection exitActions, + public AbstractState(Collection deferred, Collection> entryActions, Collection> exitActions, PseudoState pseudoState) { this(deferred, entryActions, exitActions, pseudoState, null, null); } @@ -91,7 +92,7 @@ public abstract class AbstractState implements State { * @param pseudoState the pseudo state * @param submachine the submachine */ - public AbstractState(Collection deferred, Collection entryActions, Collection exitActions, + public AbstractState(Collection deferred, Collection> entryActions, Collection> exitActions, PseudoState pseudoState, StateMachine submachine) { this(deferred, entryActions, exitActions, pseudoState, null, submachine); } @@ -105,11 +106,11 @@ public abstract class AbstractState implements State { * @param pseudoState the pseudo state * @param regions the regions */ - public AbstractState(Collection deferred, Collection entryActions, Collection exitActions, + public AbstractState(Collection deferred, Collection> entryActions, Collection> exitActions, PseudoState pseudoState, Collection> regions) { this(deferred, entryActions, exitActions, pseudoState, regions, null); } - + /** * Instantiates a new abstract state. * @@ -120,13 +121,13 @@ public abstract class AbstractState implements State { * @param regions the regions * @param submachine the submachine */ - private AbstractState(Collection deferred, Collection entryActions, Collection exitActions, + private AbstractState(Collection deferred, Collection> entryActions, Collection> exitActions, PseudoState pseudoState, Collection> regions, StateMachine submachine) { this.deferred = deferred; this.entryActions = entryActions; this.exitActions = exitActions; this.pseudoState = pseudoState; - + // use of private ctor should prevent user to // add regions and a submachine which is not allowed. if (regions != null) { @@ -134,10 +135,16 @@ public abstract class AbstractState implements State { } this.submachine = submachine; } - + + @Override + public abstract void exit(E event, StateContext context); + + @Override + public abstract void entry(E event, StateContext context); + @Override public abstract Collection getIds(); - + @Override public PseudoState getPseudoState() { return pseudoState; @@ -149,12 +156,12 @@ public abstract class AbstractState implements State { } @Override - public Collection getEntryActions() { + public Collection> getEntryActions() { return entryActions; } @Override - public Collection getExitActions() { + public Collection> getExitActions() { return exitActions; } @@ -177,11 +184,11 @@ public abstract class AbstractState implements State { public boolean isSubmachineState() { return submachine != null; } - + protected StateMachine getSubmachine() { return submachine; } - + protected Collection> getRegions() { return regions; } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/EnumState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/EnumState.java index 4f898b03..9c5adea0 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/EnumState.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/EnumState.java @@ -17,6 +17,7 @@ package org.springframework.statemachine.state; import java.util.Collection; +import org.springframework.statemachine.StateContext; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.action.Action; import org.springframework.statemachine.region.Region; @@ -68,7 +69,7 @@ public class EnumState, E extends Enum> extends AbstractSim * @param entryActions the entry actions * @param exitActions the exit actions */ - public EnumState(S id, Collection deferred, Collection entryActions, Collection exitActions) { + public EnumState(S id, Collection deferred, Collection> entryActions, Collection> exitActions) { super(id, deferred, entryActions, exitActions); } @@ -81,7 +82,7 @@ public class EnumState, E extends Enum> extends AbstractSim * @param exitActions the exit actions * @param pseudoState the pseudo state */ - public EnumState(S id, Collection deferred, Collection entryActions, Collection exitActions, + public EnumState(S id, Collection deferred, Collection> entryActions, Collection> exitActions, PseudoState pseudoState) { super(id, deferred, entryActions, exitActions, pseudoState); } @@ -96,7 +97,7 @@ public class EnumState, E extends Enum> extends AbstractSim * @param pseudoState the pseudo state * @param regions the regions */ - public EnumState(S id, Collection deferred, Collection entryActions, Collection exitActions, + public EnumState(S id, Collection deferred, Collection> entryActions, Collection> exitActions, PseudoState pseudoState, Collection> regions) { super(id, deferred, entryActions, exitActions, pseudoState, regions); } @@ -111,11 +112,31 @@ public class EnumState, E extends Enum> extends AbstractSim * @param pseudoState the pseudo state * @param submachine the submachine */ - public EnumState(S id, Collection deferred, Collection entryActions, Collection exitActions, + public EnumState(S id, Collection deferred, Collection> entryActions, Collection> exitActions, PseudoState pseudoState, StateMachine submachine) { super(id, deferred, entryActions, exitActions, pseudoState, submachine); } + @Override + public void exit(E event, StateContext context) { + Collection> actions = getExitActions(); + if (actions != null) { + for (Action action : actions) { + action.execute(context); + } + } + } + + @Override + public void entry(E event, StateContext context) { + Collection> actions = getEntryActions(); + if (actions != null) { + for (Action action : actions) { + action.execute(context); + } + } + } + @Override public String toString() { return "EnumState [getIds()=" + getIds() + ", getClass()=" + getClass() + ", hashCode()=" + hashCode() diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/RegionState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/RegionState.java index 1c54d033..08757c7f 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/RegionState.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/RegionState.java @@ -18,12 +18,13 @@ package org.springframework.statemachine.state; import java.util.ArrayList; import java.util.Collection; +import org.springframework.statemachine.StateContext; import org.springframework.statemachine.action.Action; import org.springframework.statemachine.region.Region; /** * A {@link State} implementation where states are wrapped in a regions.. - * + * * @author Janne Valkealahti * * @param the type of state @@ -59,7 +60,7 @@ public class RegionState extends AbstractState { public RegionState(Collection> regions, PseudoState pseudoState) { super(null, null, null, pseudoState, regions); } - + /** * Instantiates a new region state. * @@ -69,7 +70,7 @@ public class RegionState extends AbstractState { * @param exitActions the exit actions * @param pseudoState the pseudo state */ - public RegionState(Collection> regions, Collection deferred, Collection entryActions, Collection exitActions, + public RegionState(Collection> regions, Collection deferred, Collection> entryActions, Collection> exitActions, PseudoState pseudoState) { super(deferred, entryActions, exitActions, pseudoState, regions); } @@ -82,10 +83,22 @@ public class RegionState extends AbstractState { * @param entryActions the entry actions * @param exitActions the exit actions */ - public RegionState(Collection> regions, Collection deferred, Collection entryActions, Collection exitActions) { + public RegionState(Collection> regions, Collection deferred, Collection> entryActions, Collection> exitActions) { super(deferred, entryActions, exitActions, null, regions); } + @Override + public void exit(E event, StateContext context) { + // TODO Auto-generated method stub + + } + @Override + public void entry(E event, StateContext context) { + // TODO Auto-generated method stub + + } + + @Override public Collection getIds() { ArrayList ids = new ArrayList(); @@ -94,5 +107,5 @@ public class RegionState extends AbstractState { } return ids; } - + } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/State.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/State.java index 3082890a..dc7dd349 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/State.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/State.java @@ -17,11 +17,12 @@ package org.springframework.statemachine.state; import java.util.Collection; +import org.springframework.statemachine.StateContext; import org.springframework.statemachine.action.Action; /** * {@code State} is an interface representing possible state in a state machine. - * + * * @author Janne Valkealahti * * @param the type of state @@ -29,6 +30,22 @@ import org.springframework.statemachine.action.Action; */ public interface State { + /** + * Initiate an exit sequence for the state. + * + * @param event the event + * @param context the context + */ + void exit(E event, StateContext context); + + /** + * Initiate an entry sequence for the state. + * + * @param event the event + * @param context the context + */ + void entry(E event, StateContext context); + /** * Gets the state identifiers. Usually returned collection contains only one * identifier except in a case where state is an orthogonal. @@ -41,7 +58,7 @@ public interface State { * Gets a {@link PseudoState} attached to a {@code State}. * {@link PseudoState} is not required and thus this method return * {@code NULL} if it's not set. - * + * * @return pseudostate or null if state doesn't have one */ PseudoState getPseudoState(); @@ -52,20 +69,20 @@ public interface State { * @return the state deferred events */ Collection getDeferredEvents(); - + /** * Gets {@link Action}s executed entering in this state. * * @return the state entry actions */ - Collection getEntryActions(); + Collection> getEntryActions(); /** * Gets {@link Action}s executed exiting from this state. * * @return the state exit actions */ - Collection getExitActions(); + Collection> getExitActions(); /** * Checks if state is a simple state. A simple state does not have any diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/StateMachineState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/StateMachineState.java index e9f38552..e106f2a6 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/StateMachineState.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/StateMachineState.java @@ -16,13 +16,17 @@ package org.springframework.statemachine.state; import java.util.Collection; +import java.util.Collections; +import org.springframework.statemachine.StateContext; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.transition.Transition; +import org.springframework.statemachine.transition.TransitionKind; /** * A {@link State} implementation where state is wrapped in a substatemachine. - * + * * @author Janne Valkealahti * * @param the type of state @@ -58,7 +62,7 @@ public class StateMachineState extends AbstractState { public StateMachineState(StateMachine submachine, PseudoState pseudoState) { super(null, null, null, pseudoState, submachine); } - + /** * Instantiates a new state machine state. * @@ -68,7 +72,7 @@ public class StateMachineState extends AbstractState { * @param exitActions the exit actions * @param pseudoState the pseudo state */ - public StateMachineState(StateMachine submachine, Collection deferred, Collection entryActions, Collection exitActions, + public StateMachineState(StateMachine submachine, Collection deferred, Collection> entryActions, Collection> exitActions, PseudoState pseudoState) { super(deferred, entryActions, exitActions, pseudoState, submachine); } @@ -81,13 +85,60 @@ public class StateMachineState extends AbstractState { * @param entryActions the entry actions * @param exitActions the exit actions */ - public StateMachineState(StateMachine submachine, Collection deferred, Collection entryActions, Collection exitActions) { + public StateMachineState(StateMachine submachine, Collection deferred, Collection> entryActions, Collection> exitActions) { super(deferred, entryActions, exitActions, null, submachine); } @Override public Collection getIds() { - return getSubmachine().getState().getIds(); + State state = getSubmachine().getState(); + if (state != null) { + return state.getIds(); + } else { + return Collections.emptyList(); + } } - + + @Override + public void exit(E event, StateContext context) { + getSubmachine().getState().exit(event, context); + getSubmachine().stop(); + Collection> actions = getExitActions(); + if (actions != null && !isLocal(context)) { + for (Action action : actions) { + action.execute(context); + } + } + } + + @Override + public void entry(E event, StateContext context) { + Collection> actions = getEntryActions(); + if (actions != null && !isLocal(context)) { + for (Action action : actions) { + action.execute(context); + } + } + if (getPseudoState() != null && getPseudoState().getKind() == PseudoStateKind.INITIAL) { + getSubmachine().start(); + } else { + getSubmachine().getState().entry(event, context); + } + } + + private boolean isLocal(StateContext context) { + Transition transition = context.getTransition(); + if (transition != null && TransitionKind.LOCAL == transition.getKind() && this == transition.getTarget()) { + return true; + } else { + return false; + } + } + + @Override + public String toString() { + return "StateMachineState [getIds()=" + getIds() + ", getClass()=" + getClass() + ", hashCode()=" + hashCode() + + ", toString()=" + super.toString() + "]"; + } + } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java index 989bf1a5..fa82a3ca 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/AbstractStateMachine.java @@ -38,7 +38,6 @@ import org.springframework.messaging.support.MessageBuilder; import org.springframework.statemachine.ExtendedState; import org.springframework.statemachine.StateContext; import org.springframework.statemachine.StateMachine; -import org.springframework.statemachine.action.Action; import org.springframework.statemachine.annotation.OnTransition; import org.springframework.statemachine.event.StateMachineEventPublisher; import org.springframework.statemachine.listener.CompositeStateMachineListener; @@ -56,7 +55,7 @@ import org.springframework.util.Assert; /** * Base implementation of a {@link StateMachine} loosely modelled from UML state * machine. - * + * * @author Janne Valkealahti * * @param the type of state @@ -73,7 +72,7 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport private final State initialState; private final State endState; - + private final Message initialEvent; private final ExtendedState extendedState; @@ -112,7 +111,7 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport State initialState, State endState) { this(states, transitions, initialState, endState, null, null); } - + /** * Instantiates a new abstract state machine. * @@ -179,7 +178,7 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport @Override protected void onInit() throws Exception { super.onInit(); - Assert.notNull(initialState, "Initial state must be set"); + Assert.notNull(initialState, "Initial state must be set"); Assert.state(initialState.getPseudoState() != null && initialState.getPseudoState().getKind() == PseudoStateKind.INITIAL, "Initial state's pseudostate kind must be INITIAL"); @@ -188,14 +187,20 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport @Override protected void doStart() { super.doStart(); - switchToState(initialState, initialEvent); + switchToState(initialState, initialEvent, null); + } + + @Override + protected void doStop() { + super.doStop(); + currentState = null; } @Override public void addStateListener(StateMachineListener, E> listener) { stateListener.register(listener); } - + @Override public boolean isComplete() { return (endState != null && endState.equals(currentState)); @@ -206,62 +211,55 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport * an unmodifiable copy because states in a state machine are immutable. * * @return immutable copy of existing states - */ + */ @Override public Collection> getStates() { return Collections.unmodifiableCollection(states); } - + @Override public Collection> getTransitions() { return transitions; } - - private void switchToState(State state, Message event) { - log.info("Moving into state=" + state + " from " + currentState); - - exitFromState(currentState, event); + + private void switchToState(State state, Message event, Transition transition) { + exitFromState(currentState, event, transition); notifyStateChanged(currentState, state); - + callHandlers(currentState, state, event); - + currentState = state; - entryToState(state, event); + entryToState(state, event, transition); - for (Transition transition : transitions) { - State source = transition.getSource(); - State target = transition.getTarget(); - if (transition.getTrigger() == null && source.equals(currentState)) { - switchToState(target, event); + // TODO: should handle triggerles transition some how differently + for (Transition t : transitions) { + State source = t.getSource(); + State target = t.getTarget(); + if (t.getTrigger() == null && source.equals(currentState)) { + switchToState(target, event, t); } } } - private void exitFromState(State state, Message event) { + private void exitFromState(State state, Message event, Transition transition) { if (state != null) { + log.trace("Exit state=[" + state + "]"); MessageHeaders messageHeaders = event != null ? event.getHeaders() : new MessageHeaders( new HashMap()); - Collection actions = state.getExitActions(); - if (actions != null) { - for (Action action : actions) { - action.execute(new DefaultStateContext(messageHeaders, extendedState)); - } - } + StateContext stateContext = new DefaultStateContext(messageHeaders, extendedState, transition); + state.exit(event != null ? event.getPayload() : null, stateContext); } } - private void entryToState(State state, Message event) { + private void entryToState(State state, Message event, Transition transition) { if (state != null) { + log.trace("Enter state=[" + state + "]"); MessageHeaders messageHeaders = event != null ? event.getHeaders() : new MessageHeaders( new HashMap()); - Collection actions = state.getEntryActions(); - if (actions != null) { - for (Action action : actions) { - action.execute(new DefaultStateContext(messageHeaders, extendedState)); - } - } + StateContext stateContext = new DefaultStateContext(messageHeaders, extendedState, transition); + state.entry(event != null ? event.getPayload() : null, stateContext); } } @@ -274,11 +272,13 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport State source = transition.getSource(); State target = transition.getTarget(); Trigger trigger = transition.getTrigger(); - if (source.equals(currentState)) { + + if (StateMachineUtils.containsAtleastOne(source.getIds(), currentState.getIds())) { if (trigger != null && trigger.evaluate(queuedEvent.getPayload())) { - boolean transit = transition.transit(new DefaultStateContext(queuedEvent.getHeaders(), extendedState)); + StateContext stateContext = new DefaultStateContext(queuedEvent.getHeaders(), extendedState, transition); + boolean transit = transition.transit(stateContext); if (transit && transition.getKind() != TransitionKind.INTERNAL) { - switchToState(target, queuedEvent); + switchToState(target, queuedEvent, transition); } break; } else if (source.getDeferredEvents() != null && source.getDeferredEvents().contains(queuedEvent.getPayload())) { @@ -304,9 +304,10 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport Trigger trigger = transition.getTrigger(); if (source.equals(currentState)) { if (trigger != null && trigger.evaluate(event.getPayload())) { - boolean transit = transition.transit(new DefaultStateContext(event.getHeaders(), extendedState)); + StateContext stateContext = new DefaultStateContext(event.getHeaders(), extendedState, transition); + boolean transit = transition.transit(stateContext); if (transit && transition.getKind() != TransitionKind.INTERNAL) { - switchToState(target, event); + switchToState(target, event, transition); } iterator.remove(); } @@ -333,16 +334,16 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport if (sourceState != null && targetState != null) { MessageHeaders messageHeaders = event != null ? event.getHeaders() : new MessageHeaders( new HashMap()); - StateContext stateContext = new DefaultStateContext(messageHeaders, extendedState); + StateContext stateContext = new DefaultStateContext(messageHeaders, extendedState, null); getStateMachineHandlerResults(getStateMachineHandlers(sourceState, targetState), stateContext); } - + } - private List getStateMachineHandlerResults(List stateMachineHandlers, final StateContext stateContext) { - StateMachineRuntime runtime = new StateMachineRuntime() { + private List getStateMachineHandlerResults(List stateMachineHandlers, final StateContext stateContext) { + StateMachineRuntime runtime = new StateMachineRuntime() { @Override - public StateContext getStateContext() { + public StateContext getStateContext() { return stateContext; } }; @@ -353,9 +354,9 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport return results; } - private List getStateMachineHandlers(State sourceState, State targetState) { + private List getStateMachineHandlers(State sourceState, State targetState) { BeanFactory beanFactory = getBeanFactory(); - + // TODO think how to handle null bf if (beanFactory == null) { return Collections.emptyList(); @@ -376,11 +377,11 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport handlersList.add(entry.getValue()); } } - + OrderComparator comparator = new OrderComparator(); Collections.sort(handlersList, comparator); return handlersList; - } + } private void notifyStateChanged(State source, State target) { stateListener.stateChanged(source, target); @@ -389,5 +390,5 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport eventPublisher.publishStateChanged(this, source, target); } } - + } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateContext.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateContext.java index db0e7949..c10d7847 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateContext.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/DefaultStateContext.java @@ -18,16 +18,20 @@ package org.springframework.statemachine.support; import org.springframework.messaging.MessageHeaders; import org.springframework.statemachine.ExtendedState; import org.springframework.statemachine.StateContext; +import org.springframework.statemachine.transition.Transition; -public class DefaultStateContext implements StateContext { +public class DefaultStateContext implements StateContext { private final MessageHeaders messageHeaders; private final ExtendedState extendedState; + + private final Transition transition; - public DefaultStateContext(MessageHeaders messageHeaders, ExtendedState extendedState) { + public DefaultStateContext(MessageHeaders messageHeaders, ExtendedState extendedState, Transition transition) { this.messageHeaders = messageHeaders; this.extendedState = extendedState; + this.transition = transition; } @Override @@ -39,5 +43,10 @@ public class DefaultStateContext implements StateContext { public ExtendedState getExtendedState() { return extendedState; } + + @Override + public Transition getTransition() { + return transition; + } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineUtils.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineUtils.java new file mode 100644 index 00000000..a9a4d946 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/support/StateMachineUtils.java @@ -0,0 +1,49 @@ +/* + * Copyright 2015 the original author or authors. + * + * 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. + */ +package org.springframework.statemachine.support; + +import java.util.Collection; + +/** + * Various utility methods for state machine. + * + * @author Janne Valkealahti + * + */ +public abstract class StateMachineUtils { + + /** + * Checks if right hand collection has atleast one same item as left hand + * collection. + * + * @param the generic type + * @param left the left collection + * @param right the right collection + * @return true, right contains at least one item from left, false otherwise. + */ + public static boolean containsAtleastOne(Collection left, Collection right) { + if (left == null || right == null) { + return false; + } + for (S id : left) { + if (right.contains(id)) { + return true; + } + } + return false; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractExternalTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractExternalTransition.java index 23189c03..b2478400 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractExternalTransition.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractExternalTransition.java @@ -23,7 +23,7 @@ import org.springframework.statemachine.state.State; public abstract class AbstractExternalTransition extends AbstractTransition implements Transition { - public AbstractExternalTransition(State source, State target, Collection actions, E event, Guard guard) { + public AbstractExternalTransition(State source, State target, Collection> actions, E event, Guard guard) { super(source, target, actions, event, TransitionKind.EXTERNAL, guard); } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractInternalTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractInternalTransition.java index d278e67f..247808d8 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractInternalTransition.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractInternalTransition.java @@ -23,7 +23,7 @@ import org.springframework.statemachine.state.State; public class AbstractInternalTransition extends AbstractTransition implements Transition { - public AbstractInternalTransition(State source,Collection actions, E event, Guard guard) { + public AbstractInternalTransition(State source,Collection> actions, E event, Guard guard) { super(source, source, actions, event, TransitionKind.INTERNAL, guard); } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractLocalTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractLocalTransition.java index de101a39..a76acc20 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractLocalTransition.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractLocalTransition.java @@ -23,7 +23,7 @@ import org.springframework.statemachine.state.State; public class AbstractLocalTransition extends AbstractTransition implements Transition { - public AbstractLocalTransition(State source, State target,Collection actions, E event, Guard guard) { + public AbstractLocalTransition(State source, State target,Collection> actions, E event, Guard guard) { super(source, target, actions, event, TransitionKind.LOCAL, guard); } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractTransition.java index e3845ca3..aa55cf2d 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractTransition.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/AbstractTransition.java @@ -39,16 +39,16 @@ public abstract class AbstractTransition implements Transition { private final State target; - private final Collection actions; + private final Collection> actions; private final TransitionKind kind; - private final Guard guard; + private final Guard guard; private Trigger trigger; - public AbstractTransition(State source, State target, Collection actions, E event, - TransitionKind kind, Guard guard) { + public AbstractTransition(State source, State target, Collection> actions, E event, + TransitionKind kind, Guard guard) { Assert.notNull(source, "Source must be set"); // Assert.notNull(target, "Target must be set"); Assert.notNull(kind, "Transition type must be set"); @@ -73,7 +73,7 @@ public abstract class AbstractTransition implements Transition { } @Override - public Collection getActions() { + public Collection> getActions() { return actions; } @@ -83,14 +83,14 @@ public abstract class AbstractTransition implements Transition { } @Override - public boolean transit(StateContext context) { + public boolean transit(StateContext context) { if (guard != null) { if (!guard.evaluate(context)) { return false; } } if (actions != null) { - for (Action action : actions) { + for (Action action : actions) { action.execute(context); } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultExternalTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultExternalTransition.java index a167a2e0..9134fe94 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultExternalTransition.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultExternalTransition.java @@ -23,7 +23,7 @@ import org.springframework.statemachine.state.State; public class DefaultExternalTransition extends AbstractExternalTransition { - public DefaultExternalTransition(State source, State target, Collection actions, E event, Guard guard) { + public DefaultExternalTransition(State source, State target, Collection> actions, E event, Guard guard) { super(source, target, actions, event, guard); } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultInternalTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultInternalTransition.java index 08d428de..4a23bf91 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultInternalTransition.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultInternalTransition.java @@ -23,7 +23,7 @@ import org.springframework.statemachine.state.State; public class DefaultInternalTransition extends AbstractInternalTransition { - public DefaultInternalTransition(State source, Collection actions, E event, Guard guard) { + public DefaultInternalTransition(State source, Collection> actions, E event, Guard guard) { super(source, actions, event, guard); } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultLocalTransition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultLocalTransition.java new file mode 100644 index 00000000..f3f4cede --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/DefaultLocalTransition.java @@ -0,0 +1,35 @@ +/* + * Copyright 2015 the original author or authors. + * + * 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. + */ +package org.springframework.statemachine.transition; + +import java.util.Collection; + +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.guard.Guard; +import org.springframework.statemachine.state.State; + +public class DefaultLocalTransition extends AbstractLocalTransition { + + public DefaultLocalTransition(State source, State target, Collection> actions, E event, Guard guard) { + super(source, target, actions, event, guard); + } + + @Override + public String toString() { + return "DefaultLocalTransition [getSource()=" + getSource() + ", getTarget()=" + getTarget() + "]"; + } + +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/Transition.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/Transition.java index 1a4cabfe..37051ac3 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/Transition.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/transition/Transition.java @@ -39,7 +39,7 @@ public interface Transition { * @param context the state context * @return true, if transition happened, false otherwise */ - boolean transit(StateContext context); + boolean transit(StateContext context); /** * Gets the source state of this transition. @@ -60,7 +60,7 @@ public interface Transition { * * @return the transition actions */ - Collection getActions(); + Collection> getActions(); /** * Gets the transition trigger. diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java index cbf6183d..c04e8de9 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/AbstractStateMachineTests.java @@ -15,8 +15,12 @@ */ package org.springframework.statemachine; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.CountDownLatch; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.junit.After; import org.junit.Before; import org.springframework.context.annotation.AnnotationConfigApplicationContext; @@ -30,46 +34,41 @@ import org.springframework.statemachine.guard.Guard; /** * Base class for stace machine tests. - * + * * @author Janne Valkealahti * */ public abstract class AbstractStateMachineTests { + private final static Log log = LogFactory.getLog(AbstractStateMachineTests.class); + protected AnnotationConfigApplicationContext context; @Before public void setup() { context = buildContext(); } - + @After public void clean() { if (context != null) { context.close(); } } - + protected AnnotationConfigApplicationContext buildContext() { return null; } - + public enum TestStates { - SI,S1,S2,S3,S4,SF + SI,S1,S2,S3,S4,SF, + S11,S111,S21,S211 } - public enum TestSubStates { - SUBSI,SUBS1,SUBS2,SUBS3,SUBS4 - } - public enum TestEvents { E1,E2,E3,E4,EF } - public enum TestSubEvents { - SUBE1,SUBE2,SUBE3,SUBE4 - } - @Configuration public static class BaseConfig { @@ -77,46 +76,86 @@ public abstract class AbstractStateMachineTests { public TaskExecutor taskExecutor() { return new SyncTaskExecutor(); } - + } public static class TestEntryAction extends AbstractTestAction { + + public TestEntryAction() { + super(); + } + + public TestEntryAction(String message) { + super(message); + } + + @Override + public String toString() { + return "TestEntryAction [message=" + message + "]"; + } + } public static class TestExitAction extends AbstractTestAction { + + public TestExitAction() { + super(); + } + + public TestExitAction(String message) { + super(message); + } + + @Override + public String toString() { + return "TestExitAction [message=" + message + "]"; + } + } public static class TestAction extends AbstractTestAction { } - - public static class TestGuard implements Guard { - + + public static class TestGuard implements Guard { + public CountDownLatch onEvaluateLatch = new CountDownLatch(1); - boolean evaluationResult = true; public TestGuard() { } - + public TestGuard(boolean evaluationResult) { this.evaluationResult = evaluationResult; } @Override - public boolean evaluate(StateContext context) { + public boolean evaluate(StateContext context) { onEvaluateLatch.countDown(); return evaluationResult; } - } + } - protected static class AbstractTestAction implements Action { + protected static class AbstractTestAction implements Action { + protected String message = null; public CountDownLatch onExecuteLatch = new CountDownLatch(1); - + public List> stateContexts = new ArrayList>(); + + public AbstractTestAction() { + } + + public AbstractTestAction(String message) { + this.message = message; + } + @Override - public void execute(StateContext context) { + public void execute(StateContext context) { + if (message != null) { + log.info(this); + } onExecuteLatch.countDown(); + stateContexts.add(context); } } - + } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/EnumStateMachineTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/EnumStateMachineTests.java index 1b00d57b..4af99b5b 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/EnumStateMachineTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/EnumStateMachineTests.java @@ -26,8 +26,6 @@ import org.apache.commons.logging.LogFactory; import org.junit.Test; import org.springframework.core.task.SyncTaskExecutor; import org.springframework.messaging.support.MessageBuilder; -import org.springframework.statemachine.EnumStateMachine; -import org.springframework.statemachine.StateContext; import org.springframework.statemachine.action.Action; import org.springframework.statemachine.state.EnumState; import org.springframework.statemachine.state.State; @@ -53,17 +51,17 @@ public class EnumStateMachineTests extends AbstractStateMachineTests { Collection> transitions = new ArrayList>(); - Collection actionsFromSIToS1 = new ArrayList(); + Collection> actionsFromSIToS1 = new ArrayList>(); actionsFromSIToS1.add(new LoggingAction("actionsFromSIToS1")); DefaultExternalTransition transitionFromSIToS1 = new DefaultExternalTransition(stateSI, stateS1, actionsFromSIToS1, TestEvents.E1, null); - Collection actionsFromS1ToS2 = new ArrayList(); + Collection> actionsFromS1ToS2 = new ArrayList>(); actionsFromS1ToS2.add(new LoggingAction("actionsFromS1ToS2")); DefaultExternalTransition transitionFromS1ToS2 = new DefaultExternalTransition(stateS1, stateS2, actionsFromS1ToS2, TestEvents.E2, null); - Collection actionsFromS2ToS3 = new ArrayList(); + Collection> actionsFromS2ToS3 = new ArrayList>(); actionsFromS1ToS2.add(new LoggingAction("actionsFromS2ToS3")); DefaultExternalTransition transitionFromS2ToS3 = new DefaultExternalTransition(stateS2, stateS3, actionsFromS2ToS3, TestEvents.E3, null); @@ -123,17 +121,17 @@ public class EnumStateMachineTests extends AbstractStateMachineTests { // transitions Collection> transitions = new ArrayList>(); - Collection actionsFromSIToS1 = new ArrayList(); + Collection> actionsFromSIToS1 = new ArrayList>(); actionsFromSIToS1.add(new LoggingAction("actionsFromSIToS1")); DefaultExternalTransition transitionFromSIToS1 = new DefaultExternalTransition(stateSI, stateS1, actionsFromSIToS1, TestEvents.E1, null); - Collection actionsFromS1ToS2 = new ArrayList(); + Collection> actionsFromS1ToS2 = new ArrayList>(); actionsFromS1ToS2.add(new LoggingAction("actionsFromS1ToS2")); DefaultExternalTransition transitionFromS1ToS2 = new DefaultExternalTransition(stateS1, stateS2, actionsFromS1ToS2, TestEvents.E2, null); - Collection actionsFromS2ToS3 = new ArrayList(); + Collection> actionsFromS2ToS3 = new ArrayList>(); actionsFromS1ToS2.add(new LoggingAction("actionsFromS2ToS3")); DefaultExternalTransition transitionFromS2ToS3 = new DefaultExternalTransition(stateS2, stateS3, actionsFromS2ToS3, TestEvents.E3, null); @@ -174,7 +172,7 @@ public class EnumStateMachineTests extends AbstractStateMachineTests { Collection> states = new ArrayList>(); states.add(stateSI); - Collection actionsInSI = new ArrayList(); + Collection> actionsInSI = new ArrayList>(); actionsInSI.add(new LoggingAction("actionsInSI")); DefaultInternalTransition transitionInternalSI = new DefaultInternalTransition(stateSI, actionsInSI, TestEvents.E1, null); @@ -191,7 +189,7 @@ public class EnumStateMachineTests extends AbstractStateMachineTests { machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); } - private static class LoggingAction implements Action { + private static class LoggingAction implements Action { private static final Log log = LogFactory.getLog(LoggingAction.class); @@ -202,7 +200,7 @@ public class EnumStateMachineTests extends AbstractStateMachineTests { } @Override - public void execute(StateContext context) { + public void execute(StateContext context) { log.info("Hello from LoggingAction " + message); } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineTests.java index f111fa79..85b136b0 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineTests.java @@ -28,9 +28,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.core.task.SyncTaskExecutor; import org.springframework.core.task.TaskExecutor; import org.springframework.messaging.support.MessageBuilder; -import org.springframework.statemachine.EnumStateMachine; -import org.springframework.statemachine.StateContext; -import org.springframework.statemachine.StateMachineSystemConstants; import org.springframework.statemachine.action.Action; import org.springframework.statemachine.config.EnableStateMachine; import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; @@ -53,7 +50,7 @@ public class StateMachineTests extends AbstractStateMachineTests { ctx.close(); } - private static class LoggingAction implements Action { + private static class LoggingAction implements Action { private static final Log log = LogFactory.getLog(StateMachineTests.LoggingAction.class); @@ -64,7 +61,7 @@ public class StateMachineTests extends AbstractStateMachineTests { } @Override - public void execute(StateContext context) { + public void execute(StateContext context) { log.info("Hello from LoggingAction " + message + " foo=" + context.getMessageHeaders().get("foo")); } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/SubStateMachineTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/SubStateMachineTests.java new file mode 100644 index 00000000..46a17d0e --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/SubStateMachineTests.java @@ -0,0 +1,234 @@ +/* + * Copyright 2015 the original author or authors. + * + * 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. + */ +package org.springframework.statemachine; + +import static org.hamcrest.CoreMatchers.is; +import static org.junit.Assert.assertThat; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.state.DefaultPseudoState; +import org.springframework.statemachine.state.EnumState; +import org.springframework.statemachine.state.PseudoState; +import org.springframework.statemachine.state.PseudoStateKind; +import org.springframework.statemachine.state.State; +import org.springframework.statemachine.state.StateMachineState; +import org.springframework.statemachine.transition.DefaultExternalTransition; +import org.springframework.statemachine.transition.DefaultLocalTransition; +import org.springframework.statemachine.transition.Transition; + +public class SubStateMachineTests extends AbstractStateMachineTests { + + @Test + public void testExternalTransition() throws Exception { + /** + * +-------------------------------------------+ + * *-init->| S1 | + * +-------------------------------------------+ + * | entry/ | + * | exit/ | + * | +--------------------------+ | + * | *-->| S11 | | + * | +--------------------------+ | + * | | entry/ | | + * | | exit/ | | + * | | +-----------+ | | + * | | *-->| S111 | | | + * | | +-----------+ | | + * | | | entry/ | | | + * |<----E1-----------| exit/ | | | + * | | | | | | + * | | +-----------+ | | + * | | | | + * | +--------------------------+ | + * | | + * +-------------------------------------------+ + */ + PseudoState pseudoState = new DefaultPseudoState(PseudoStateKind.INITIAL); + + TestEntryAction entryActionS111 = new TestEntryAction("S111"); + TestExitAction exitActionS111 = new TestExitAction("S111"); + Collection> entryActionsS111 = new ArrayList>(); + entryActionsS111.add(entryActionS111); + Collection> exitActionsS111 = new ArrayList>(); + exitActionsS111.add(exitActionS111); + State stateS111 = new EnumState(TestStates.S111, null, entryActionsS111, exitActionsS111, pseudoState); + + // submachine 11 + Collection> substates111 = new ArrayList>(); + substates111.add(stateS111); + Collection> subtransitions111 = new ArrayList>(); + EnumStateMachine submachine11 = new EnumStateMachine(substates111, subtransitions111, stateS111, null); + + // submachine 1 + TestEntryAction entryActionS11 = new TestEntryAction("S11"); + TestExitAction exitActionS11 = new TestExitAction("S11"); + Collection> entryActionsS11 = new ArrayList>(); + entryActionsS11.add(entryActionS11); + Collection> exitActionsS11 = new ArrayList>(); + exitActionsS11.add(exitActionS11); + StateMachineState stateS11 = new StateMachineState(submachine11, null, entryActionsS11, exitActionsS11, pseudoState); + + Collection> substates11 = new ArrayList>(); + substates11.add(stateS11); + Collection> subtransitions11 = new ArrayList>(); + EnumStateMachine submachine1 = new EnumStateMachine(substates11, subtransitions11, stateS11, null); + + // machine + TestEntryAction entryActionS1 = new TestEntryAction("S1"); + TestExitAction exitActionS1 = new TestExitAction("S1"); + Collection> entryActionsS1 = new ArrayList>(); + entryActionsS1.add(entryActionS1); + Collection> exitActionsS1 = new ArrayList>(); + exitActionsS1.add(exitActionS1); + + StateMachineState stateS1 = new StateMachineState(submachine1, null, entryActionsS1, exitActionsS1, pseudoState); + Collection> states = new ArrayList>(); + states.add(stateS1); + Collection> transitions = new ArrayList>(); + DefaultExternalTransition transitionFromS11ToS1 = + new DefaultExternalTransition(stateS111, stateS1, null, TestEvents.E1, null); + transitions.add(transitionFromS11ToS1); + EnumStateMachine machine = new EnumStateMachine(states, transitions, stateS1, null); + + + SyncTaskExecutor taskExecutor = new SyncTaskExecutor(); + machine.setTaskExecutor(taskExecutor); + machine.afterPropertiesSet(); + machine.start(); + submachine1.setTaskExecutor(taskExecutor); + submachine11.setTaskExecutor(taskExecutor); + + machine.sendEvent(TestEvents.E1); + + assertThat(entryActionS111.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(exitActionS111.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(entryActionS11.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(exitActionS11.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(entryActionS1.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(exitActionS1.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + + assertThat(entryActionS11.stateContexts.size(), is(2)); + assertThat(exitActionS11.stateContexts.size(), is(1)); + assertThat(entryActionS11.stateContexts.size(), is(2)); + assertThat(exitActionS11.stateContexts.size(), is(1)); + assertThat(entryActionS1.stateContexts.size(), is(2)); + assertThat(exitActionS1.stateContexts.size(), is(1)); + } + + @Test + public void testLocalTransition() throws Exception { + /** + * +-------------------------------------------+ + * *-init->| S1 | + * +-------------------------------------------+ + * | entry/ | + * | exit/ | + * | +--------------------------+ | + * | *-->| S11 | | + * | +--------------------------+ | + * | | entry/ | | + * | | exit/ | | + * | | +-----------+ | | + * | | *-->| S111 | | | + * | | +-----------+ | | + * | | | entry/ | | | + * |<----E1-----------| exit/ | | | + * | | | | | | + * | | +-----------+ | | + * | | | | + * | +--------------------------+ | + * | | + * +-------------------------------------------+ + */ + PseudoState pseudoState = new DefaultPseudoState(PseudoStateKind.INITIAL); + + TestEntryAction entryActionS111 = new TestEntryAction("S111"); + TestExitAction exitActionS111 = new TestExitAction("S111"); + Collection> entryActionsS111 = new ArrayList>(); + entryActionsS111.add(entryActionS111); + Collection> exitActionsS111 = new ArrayList>(); + exitActionsS111.add(exitActionS111); + State stateS111 = new EnumState(TestStates.S111, null, entryActionsS111, exitActionsS111, pseudoState); + + // submachine 11 + Collection> substates111 = new ArrayList>(); + substates111.add(stateS111); + Collection> subtransitions111 = new ArrayList>(); + EnumStateMachine submachine11 = new EnumStateMachine(substates111, subtransitions111, stateS111, null); + + // submachine 1 + TestEntryAction entryActionS11 = new TestEntryAction("S11"); + TestExitAction exitActionS11 = new TestExitAction("S11"); + Collection> entryActionsS11 = new ArrayList>(); + entryActionsS11.add(entryActionS11); + Collection> exitActionsS11 = new ArrayList>(); + exitActionsS11.add(exitActionS11); + StateMachineState stateS11 = new StateMachineState(submachine11, null, entryActionsS11, exitActionsS11, pseudoState); + + Collection> substates11 = new ArrayList>(); + substates11.add(stateS11); + Collection> subtransitions11 = new ArrayList>(); + EnumStateMachine submachine1 = new EnumStateMachine(substates11, subtransitions11, stateS11, null); + + // machine + TestEntryAction entryActionS1 = new TestEntryAction("S1"); + TestExitAction exitActionS1 = new TestExitAction("S1"); + Collection> entryActionsS1 = new ArrayList>(); + entryActionsS1.add(entryActionS1); + Collection> exitActionsS1 = new ArrayList>(); + exitActionsS1.add(exitActionS1); + + StateMachineState stateS1 = new StateMachineState(submachine1, null, entryActionsS1, exitActionsS1, pseudoState); + Collection> states = new ArrayList>(); + states.add(stateS1); + Collection> transitions = new ArrayList>(); + DefaultLocalTransition transitionFromS11ToS1 = + new DefaultLocalTransition(stateS111, stateS1, null, TestEvents.E1, null); + transitions.add(transitionFromS11ToS1); + EnumStateMachine machine = new EnumStateMachine(states, transitions, stateS1, null); + + + SyncTaskExecutor taskExecutor = new SyncTaskExecutor(); + machine.setTaskExecutor(taskExecutor); + machine.afterPropertiesSet(); + machine.start(); + submachine1.setTaskExecutor(taskExecutor); + submachine11.setTaskExecutor(taskExecutor); + + machine.sendEvent(TestEvents.E1); + + assertThat(entryActionS111.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(exitActionS111.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(entryActionS11.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(exitActionS11.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(entryActionS1.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); + assertThat(exitActionS1.onExecuteLatch.await(1, TimeUnit.SECONDS), is(false)); + + assertThat(entryActionS11.stateContexts.size(), is(2)); + assertThat(exitActionS11.stateContexts.size(), is(1)); + assertThat(entryActionS11.stateContexts.size(), is(2)); + assertThat(exitActionS11.stateContexts.size(), is(1)); + assertThat(entryActionS1.stateContexts.size(), is(1)); + assertThat(exitActionS1.stateContexts.size(), is(0)); + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/action/ActionTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/action/ActionTests.java index d0f1119c..d94afc11 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/action/ActionTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/action/ActionTests.java @@ -68,7 +68,7 @@ public class ActionTests extends AbstractStateMachineTests { } - private static class TestCountAction implements Action { + private static class TestCountAction implements Action { int count = 0; @@ -77,7 +77,7 @@ public class ActionTests extends AbstractStateMachineTests { } @Override - public void execute(StateContext context) { + public void execute(StateContext context) { count++; } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/ConfigurationTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/ConfigurationTests.java index 6936a5ad..c31ea492 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/ConfigurationTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/ConfigurationTests.java @@ -144,5 +144,34 @@ public class ConfigurationTests extends AbstractStateMachineTests { } } + + @Configuration + @EnableStateMachine + public static class Config4 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .end(TestStates.SF) + .states(EnumSet.allOf(TestStates.class)); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.SI) + .target(TestStates.S1) + .event(TestEvents.E1) + .and() + .withLocal() + .source(TestStates.S1) + .target(TestStates.S2) + .event(TestEvents.E2); + } + + } } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/SimpleAnnotationConfiguration2Tests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/SimpleAnnotationConfiguration2Tests.java index 359ca809..ba542141 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/SimpleAnnotationConfiguration2Tests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/common/annotation/SimpleAnnotationConfiguration2Tests.java @@ -32,7 +32,6 @@ import org.springframework.core.io.Resource; import org.springframework.statemachine.config.common.annotation.simple.EnableSimpleTest2; import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfig; import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBeanABuilder; -import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBeanB; import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBeanBConfigurer; import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigBuilder; import org.springframework.statemachine.config.common.annotation.simple.SimpleTestConfigurerAdapter; diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/SpelExpressionGuardTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/SpelExpressionGuardTests.java index 9ac26461..82b0342e 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/SpelExpressionGuardTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/SpelExpressionGuardTests.java @@ -55,11 +55,11 @@ public class SpelExpressionGuardTests extends AbstractStateMachineTests { SpelExpressionParser parser = new SpelExpressionParser( new SpelParserConfiguration(SpelCompilerMode.MIXED, null)); Expression expression = parser.parseExpression("messageHeaders.get('foo')=='bar'"); - SpelExpressionGuard guard = new SpelExpressionGuard(expression); + SpelExpressionGuard guard = new SpelExpressionGuard(expression); Map map = new HashMap(); map.put("foo", "bar"); MessageHeaders headers = new MessageHeaders(map); - DefaultStateContext stateContext = new DefaultStateContext(headers, null); + DefaultStateContext stateContext = new DefaultStateContext(headers, null, null); assertThat(guard.evaluate(stateContext), is(true)); } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/listener/ListenerTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/listener/ListenerTests.java index cb04b19c..1574d9e0 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/listener/ListenerTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/listener/ListenerTests.java @@ -77,7 +77,7 @@ public class ListenerTests extends AbstractStateMachineTests { ctx.close(); } - private static class LoggingAction implements Action { + private static class LoggingAction implements Action { private static final Log log = LogFactory.getLog(LoggingAction.class); @@ -88,7 +88,7 @@ public class ListenerTests extends AbstractStateMachineTests { } @Override - public void execute(StateContext context) { + public void execute(StateContext context) { log.info("Hello from LoggingAction " + message + " foo=" + context.getMessageHeaders().get("foo")); } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/StateActionTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/StateActionTests.java index ba37a50c..b3332d9a 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/StateActionTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/StateActionTests.java @@ -71,8 +71,10 @@ public class StateActionTests extends AbstractStateMachineTests { @Override public void configure(StateMachineStateConfigurer states) throws Exception { - Collection entryActions = Arrays.asList(testEntryAction()); - Collection exitActions = Arrays.asList(testExitAction()); + @SuppressWarnings("unchecked") + Collection> entryActions = Arrays.asList(testEntryAction()); + @SuppressWarnings("unchecked") + Collection> exitActions = Arrays.asList(testExitAction()); states .withStates() .initial(TestStates.S1) @@ -90,12 +92,12 @@ public class StateActionTests extends AbstractStateMachineTests { } @Bean - public Action testEntryAction() { + public Action testEntryAction() { return new TestEntryAction(); } @Bean - public Action testExitAction() { + public Action testExitAction() { return new TestExitAction(); } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/transition/TransitionTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/transition/TransitionTests.java index e1d2fece..4084adfc 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/transition/TransitionTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/transition/TransitionTests.java @@ -126,8 +126,10 @@ public class TransitionTests extends AbstractStateMachineTests { @Override public void configure(StateMachineStateConfigurer states) throws Exception { - Collection entryActions = Arrays.asList(testEntryAction()); - Collection exitActions = Arrays.asList(testExitAction()); + @SuppressWarnings("unchecked") + Collection> entryActions = Arrays.asList(testEntryAction()); + @SuppressWarnings("unchecked") + Collection> exitActions = Arrays.asList(testExitAction()); states .withStates() .initial(TestStates.S1) @@ -151,22 +153,22 @@ public class TransitionTests extends AbstractStateMachineTests { } @Bean - public Action testEntryAction() { + public Action testEntryAction() { return new TestEntryAction(); } @Bean - public Action testExitAction() { + public Action testExitAction() { return new TestExitAction(); } @Bean - public Action externalTestAction() { + public Action externalTestAction() { return new TestAction(); } @Bean - public Action internalTestAction() { + public Action internalTestAction() { return new TestAction(); } diff --git a/spring-statemachine-core/src/test/resources/log4j.properties b/spring-statemachine-core/src/test/resources/log4j.properties new file mode 100644 index 00000000..4589b1e9 --- /dev/null +++ b/spring-statemachine-core/src/test/resources/log4j.properties @@ -0,0 +1,8 @@ +log4j.rootCategory=INFO, stdout + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2} [%t] - %m%n + +log4j.category.org.springframework.statemachine=TRACE +