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 154f427b..7efe869f 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 @@ -17,6 +17,8 @@ package org.springframework.statemachine; import org.springframework.messaging.Message; import org.springframework.statemachine.listener.StateMachineListener; +import org.springframework.statemachine.region.Region; +import org.springframework.statemachine.state.State; /** * {@code StateMachine} provides an APIs for generic finite state machine needed @@ -27,21 +29,14 @@ import org.springframework.statemachine.listener.StateMachineListener; * @param the type of state * @param the type of event */ -public interface StateMachine { +public interface StateMachine extends Region { /** * Gets the initial state {@code S}. * * @return initial state */ - S getInitialState(); - - /** - * Gets the current state {@code S}. - * - * @return current state - */ - S getState(); + State getInitialState(); /** * Start the state machine. @@ -68,6 +63,6 @@ public interface StateMachine { * * @param listener the listener */ - void addStateListener(StateMachineListener listener); + void addStateListener(StateMachineListener, E> listener); } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java index 4c126077..456f5d81 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/EnumStateMachineFactory.java @@ -45,8 +45,8 @@ import org.springframework.statemachine.transition.TransitionKind; * @param the type of event */ public class EnumStateMachineFactory, E extends Enum> extends LifecycleObjectSupport implements - StateMachineFactory, E> { - + StateMachineFactory { + private final StateMachineTransitions stateMachineTransitions; private final StateMachineStates stateMachineStates; @@ -64,11 +64,11 @@ public class EnumStateMachineFactory, E extends Enum> exten } @Override - public StateMachine, E> getStateMachine() { + public StateMachine getStateMachine() { return stateMachine(); } - public StateMachine, E> stateMachine() { + public StateMachine stateMachine() { Map> stateMap = new HashMap>(); for (StateData stateData : stateMachineStates.getStates()) { diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineConfiguration.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineConfiguration.java index d201f06c..87d3e4d0 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineConfiguration.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineConfiguration.java @@ -21,7 +21,6 @@ import org.springframework.statemachine.config.builders.StateMachineStates; import org.springframework.statemachine.config.builders.StateMachineTransitions; import org.springframework.statemachine.config.common.annotation.AbstractImportingAnnotationConfiguration; import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer; -import org.springframework.statemachine.state.State; @Configuration public class StateMachineConfiguration, E extends Enum> extends @@ -43,7 +42,7 @@ public class StateMachineConfiguration, E extends Enum> ext } private static class StateMachineDelegatingFactoryBean, E extends Enum> implements - FactoryBean, E>>, BeanFactoryAware, InitializingBean { + FactoryBean>, BeanFactoryAware, InitializingBean { private final StateMachineConfigBuilder builder; @@ -51,7 +50,7 @@ public class StateMachineConfiguration, E extends Enum> ext private BeanFactory beanFactory; - private StateMachine, E> stateMachine; + private StateMachine stateMachine; @SuppressWarnings("unused") public StateMachineDelegatingFactoryBean(StateMachineConfigBuilder builder) { @@ -59,7 +58,7 @@ public class StateMachineConfiguration, E extends Enum> ext } @Override - public StateMachine, E> getObject() throws Exception { + public StateMachine getObject() throws Exception { return stateMachine; } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineFactoryConfiguration.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineFactoryConfiguration.java index 13d84571..a0c3adcf 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineFactoryConfiguration.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configuration/StateMachineFactoryConfiguration.java @@ -21,7 +21,6 @@ import org.springframework.statemachine.config.builders.StateMachineStates; import org.springframework.statemachine.config.builders.StateMachineTransitions; import org.springframework.statemachine.config.common.annotation.AbstractImportingAnnotationConfiguration; import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer; -import org.springframework.statemachine.state.State; @Configuration public class StateMachineFactoryConfiguration, E extends Enum> extends @@ -43,7 +42,7 @@ public class StateMachineFactoryConfiguration, E extends Enum< } private static class StateMachineFactoryDelegatingFactoryBean, E extends Enum> implements - FactoryBean, E>>, BeanFactoryAware, InitializingBean { + FactoryBean>, BeanFactoryAware, InitializingBean { private final StateMachineConfigBuilder builder; @@ -51,7 +50,7 @@ public class StateMachineFactoryConfiguration, E extends Enum< private BeanFactory beanFactory; - private StateMachineFactory, E> stateMachineFactory; + private StateMachineFactory stateMachineFactory; @SuppressWarnings("unused") public StateMachineFactoryDelegatingFactoryBean(StateMachineConfigBuilder builder) { @@ -59,7 +58,7 @@ public class StateMachineFactoryConfiguration, E extends Enum< } @Override - public StateMachineFactory, E> getObject() throws Exception { + public StateMachineFactory getObject() throws Exception { return stateMachineFactory; } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/region/Region.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/region/Region.java new file mode 100644 index 00000000..c39fa47d --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/region/Region.java @@ -0,0 +1,56 @@ +/* + * 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.region; + +import java.util.Collection; + +import org.springframework.statemachine.state.State; +import org.springframework.statemachine.transition.Transition; + +/** + * A region is an orthogonal part of either a composite state or a state + * machine. It contains states and transitions. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface Region { + + /** + * Gets the current {@link State}. + * + * @return current state + */ + State getState(); + + /** + * Gets the {@link State}s defined in this region. Returned collection is + * an unmodifiable copy because states in a state machine are immutable. + * + * @return immutable copy of states + */ + Collection> getStates(); + + /** + * Gets a {@link Transition}s for this region. + * + * @return immutable copy of transitions + */ + Collection> getTransitions(); + +} 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 new file mode 100644 index 00000000..07b22e52 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/AbstractSimpleState.java @@ -0,0 +1,134 @@ +/* + * 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.state; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; + +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.region.Region; + +/** + * Base implementation of a {@link State} having a single state identifier. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public abstract class AbstractSimpleState extends AbstractState { + + private final Collection ids; + + /** + * Instantiates a new abstract simple state. + * + * @param id the id + */ + public AbstractSimpleState(S id) { + this(id, null, null, null, null); + } + + /** + * Instantiates a new abstract simple state. + * + * @param id the id + * @param deferred the deferred + * @param entryActions the entry actions + * @param exitActions the exit actions + */ + public AbstractSimpleState(S id, Collection deferred, Collection entryActions, Collection exitActions) { + this(id, deferred, entryActions, exitActions, null); + } + + /** + * Instantiates a new abstract simple state. + * + * @param id the id + * @param deferred the deferred + */ + public AbstractSimpleState(S id, Collection deferred) { + this(id, deferred, null, null, null); + } + + /** + * Instantiates a new abstract simple state. + * + * @param id the id + * @param pseudoState the pseudo state + */ + public AbstractSimpleState(S id, PseudoState pseudoState) { + this(id, null, null, null, pseudoState); + } + + /** + * Instantiates a new abstract simple state. + * + * @param id the id + * @param deferred the deferred + * @param entryActions the entry actions + * @param exitActions the exit actions + * @param pseudoState the pseudo state + * @param regions the regions + */ + public AbstractSimpleState(S id, Collection deferred, Collection entryActions, Collection exitActions, + PseudoState pseudoState, Collection> regions) { + super(deferred, entryActions, exitActions, pseudoState, regions); + this.ids = new ArrayList(); + this.ids.add(id); + } + + /** + * Instantiates a new abstract simple state. + * + * @param id the id + * @param deferred the deferred + * @param entryActions the entry actions + * @param exitActions the exit actions + * @param pseudoState the pseudo state + * @param submachine the submachine + */ + public AbstractSimpleState(S id, Collection deferred, Collection entryActions, Collection exitActions, + PseudoState pseudoState, StateMachine submachine) { + super(deferred, entryActions, exitActions, pseudoState, submachine); + this.ids = new ArrayList(); + this.ids.add(id); + } + + /** + * Instantiates a new abstract simple state. + * + * @param id the id + * @param deferred the deferred + * @param entryActions the entry actions + * @param exitActions the exit actions + * @param pseudoState the pseudo state + */ + public AbstractSimpleState(S id, Collection deferred, Collection entryActions, Collection exitActions, + PseudoState pseudoState) { + super(deferred, entryActions, exitActions, pseudoState); + this.ids = new ArrayList(); + this.ids.add(id); + } + + @Override + public Collection getIds() { + return Collections.unmodifiableCollection(ids); + } + +} 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 2556b239..3fcb0c1b 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 @@ -15,9 +15,13 @@ */ package org.springframework.statemachine.state; +import java.util.ArrayList; import java.util.Collection; +import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.region.Region; +import org.springframework.util.StringUtils; /** * Base implementation of a {@link State}. @@ -29,74 +33,110 @@ import org.springframework.statemachine.action.Action; */ public abstract class AbstractState implements State { - private final S id; private final PseudoState pseudoState; private final Collection deferred; private final Collection entryActions; private final Collection exitActions; + private final Collection> regions = new ArrayList>(); + private final StateMachine submachine; /** * Instantiates a new abstract state. * - * @param id the id - */ - public AbstractState(S id) { - this(id, null, null, null, null); - } - - /** - * Instantiates a new abstract state. - * - * @param id the id * @param pseudoState the pseudo state */ - public AbstractState(S id, PseudoState pseudoState) { - this(id, null, null, null, pseudoState); + public AbstractState(PseudoState pseudoState) { + this(null, null, null, pseudoState); } /** * Instantiates a new abstract state. * - * @param id the id * @param deferred the deferred */ - public AbstractState(S id, Collection deferred) { - this(id, deferred, null, null); + public AbstractState(Collection deferred) { + this(deferred, null, null); } /** * Instantiates a new abstract state. * - * @param id the id * @param deferred the deferred * @param entryActions the entry actions * @param exitActions the exit actions */ - public AbstractState(S id, Collection deferred, Collection entryActions, Collection exitActions) { - this(id, deferred, entryActions, exitActions, null); + public AbstractState(Collection deferred, Collection entryActions, Collection exitActions) { + this(deferred, entryActions, exitActions, null); } /** * Instantiates a new abstract state. * - * @param id the id * @param deferred the deferred * @param entryActions the entry actions * @param exitActions the exit actions * @param pseudoState the pseudo state */ - public AbstractState(S id, Collection deferred, Collection entryActions, Collection exitActions, PseudoState pseudoState) { - this.id = id; + public AbstractState(Collection deferred, Collection entryActions, Collection exitActions, + PseudoState pseudoState) { + this(deferred, entryActions, exitActions, pseudoState, null, null); + } + + /** + * Instantiates a new abstract state. + * + * @param deferred the deferred + * @param entryActions the entry actions + * @param exitActions the exit actions + * @param pseudoState the pseudo state + * @param submachine the submachine + */ + public AbstractState(Collection deferred, Collection entryActions, Collection exitActions, + PseudoState pseudoState, StateMachine submachine) { + this(deferred, entryActions, exitActions, pseudoState, null, submachine); + } + + /** + * Instantiates a new abstract state. + * + * @param deferred the deferred + * @param entryActions the entry actions + * @param exitActions the exit actions + * @param pseudoState the pseudo state + * @param regions the regions + */ + public AbstractState(Collection deferred, Collection entryActions, Collection exitActions, + PseudoState pseudoState, Collection> regions) { + this(deferred, entryActions, exitActions, pseudoState, regions, null); + } + + /** + * Instantiates a new abstract state. + * + * @param deferred the deferred + * @param entryActions the entry actions + * @param exitActions the exit actions + * @param pseudoState the pseudo state + * @param regions the regions + * @param submachine the submachine + */ + 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) { + this.regions.addAll(regions); + } + this.submachine = submachine; } @Override - public S getId() { - return id; - } + public abstract Collection getIds(); @Override public PseudoState getPseudoState() { @@ -107,21 +147,49 @@ public abstract class AbstractState implements State { public Collection getDeferredEvents() { return deferred; } - + @Override public Collection getEntryActions() { return entryActions; } - + @Override public Collection getExitActions() { return exitActions; } @Override - public String toString() { - return "AbstractState [id=" + id + ", pseudoState=" + pseudoState + ", deferred=" + deferred - + ", entryActions=" + entryActions + ", exitActions=" + exitActions + "]"; + public boolean isComposite() { + return !regions.isEmpty(); + } + + @Override + public boolean isOrthogonal() { + return regions.size() > 1; + } + + @Override + public boolean isSimple() { + return isSubmachineState() && isComposite(); + } + + @Override + public boolean isSubmachineState() { + return submachine != null; } + protected StateMachine getSubmachine() { + return submachine; + } + + protected Collection> getRegions() { + return regions; + } + + @Override + public String toString() { + return "AbstractState [ids=" + StringUtils.collectionToCommaDelimitedString(getIds()) + ", pseudoState=" + pseudoState + ", deferred=" + deferred + + ", entryActions=" + entryActions + ", exitActions=" + exitActions + "]"; + } + } 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 ce791b8c..4f898b03 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,34 +17,108 @@ package org.springframework.statemachine.state; import java.util.Collection; +import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.region.Region; -public class EnumState, E extends Enum> extends AbstractState { +/** + * A {@link State} implementation where state and event is enum based. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class EnumState, E extends Enum> extends AbstractSimpleState { + /** + * Instantiates a new enum state. + * + * @param id the id + */ public EnumState(S id) { super(id); } + /** + * Instantiates a new enum state. + * + * @param id the id + * @param pseudoState the pseudo state + */ public EnumState(S id, PseudoState pseudoState) { super(id, pseudoState); } + /** + * Instantiates a new enum state. + * + * @param id the id + * @param deferred the deferred + */ public EnumState(S id, Collection deferred) { super(id, deferred); } + /** + * Instantiates a new enum state. + * + * @param id the id + * @param deferred the deferred + * @param entryActions the entry actions + * @param exitActions the exit actions + */ public EnumState(S id, Collection deferred, Collection entryActions, Collection exitActions) { super(id, deferred, entryActions, exitActions); } + /** + * Instantiates a new enum state. + * + * @param id the id + * @param deferred the deferred + * @param entryActions the entry actions + * @param exitActions the exit actions + * @param pseudoState the pseudo state + */ public EnumState(S id, Collection deferred, Collection entryActions, Collection exitActions, PseudoState pseudoState) { super(id, deferred, entryActions, exitActions, pseudoState); } + /** + * Instantiates a new enum state. + * + * @param id the id + * @param deferred the deferred + * @param entryActions the entry actions + * @param exitActions the exit actions + * @param pseudoState the pseudo state + * @param regions the regions + */ + public EnumState(S id, Collection deferred, Collection entryActions, Collection exitActions, + PseudoState pseudoState, Collection> regions) { + super(id, deferred, entryActions, exitActions, pseudoState, regions); + } + + /** + * Instantiates a new enum state. + * + * @param id the id + * @param deferred the deferred + * @param entryActions the entry actions + * @param exitActions the exit actions + * @param pseudoState the pseudo state + * @param submachine the submachine + */ + public EnumState(S id, Collection deferred, Collection entryActions, Collection exitActions, + PseudoState pseudoState, StateMachine submachine) { + super(id, deferred, entryActions, exitActions, pseudoState, submachine); + } + @Override public String toString() { - return "EnumState [getId()=" + getId() + ", getClass()=" + getClass() + ", hashCode()=" + hashCode() + return "EnumState [getIds()=" + getIds() + ", getClass()=" + getClass() + ", hashCode()=" + hashCode() + ", toString()=" + super.toString() + "]"; } 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 new file mode 100644 index 00000000..1c54d033 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/RegionState.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.state; + +import java.util.ArrayList; +import java.util.Collection; + +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 + * @param the type of event + */ +public class RegionState extends AbstractState { + + /** + * Instantiates a new region state. + * + * @param regions the regions + */ + public RegionState(Collection> regions) { + super(null, null, null, null, regions); + } + + /** + * Instantiates a new region state. + * + * @param regions the regions + * @param deferred the deferred + */ + public RegionState(Collection> regions, Collection deferred) { + super(deferred, null, null, null, regions); + } + + /** + * Instantiates a new region state. + * + * @param regions the regions + * @param pseudoState the pseudo state + */ + public RegionState(Collection> regions, PseudoState pseudoState) { + super(null, null, null, pseudoState, regions); + } + + /** + * Instantiates a new region state. + * + * @param regions the regions + * @param deferred the deferred + * @param entryActions the entry actions + * @param exitActions the exit actions + * @param pseudoState the pseudo state + */ + public RegionState(Collection> regions, Collection deferred, Collection entryActions, Collection exitActions, + PseudoState pseudoState) { + super(deferred, entryActions, exitActions, pseudoState, regions); + } + + /** + * Instantiates a new region state. + * + * @param regions the regions + * @param deferred the deferred + * @param entryActions the entry actions + * @param exitActions the exit actions + */ + public RegionState(Collection> regions, Collection deferred, Collection entryActions, Collection exitActions) { + super(deferred, entryActions, exitActions, null, regions); + } + + @Override + public Collection getIds() { + ArrayList ids = new ArrayList(); + for (Region r : getRegions()) { + ids.addAll(r.getState().getIds()); + } + 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 15e835e3..3082890a 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 @@ -30,11 +30,12 @@ import org.springframework.statemachine.action.Action; public interface State { /** - * Gets the state identifier. + * Gets the state identifiers. Usually returned collection contains only one + * identifier except in a case where state is an orthogonal. * - * @return the identifier + * @return the state identifiers */ - S getId(); + Collection getIds(); /** * Gets a {@link PseudoState} attached to a {@code State}. @@ -65,5 +66,38 @@ public interface State { * @return the state exit actions */ Collection getExitActions(); - + + /** + * Checks if state is a simple state. A simple state does not have any + * regions and it does not refer to any submachine state machine. + * + * @return true, if state is a simple state + */ + boolean isSimple(); + + /** + * Checks if state is a composite state. A composite state is a state that + * contains at least one region. + * + * @return true, if state is a composite state + */ + boolean isComposite(); + + /** + * Checks if state is an orthogonal state. An orthogonal composite state + * contains two or more regions. If this method returns {@code TRUE}, + * {@link #isComposite()} will also always return {@code TRUE}. + * + * @return true, if state is an orthogonal state + */ + boolean isOrthogonal(); + + /** + * Checks if state is a submachine state. This kind of state refers to a + * state machine(submachine). + * + * @return true, if state is a submachine state + */ + boolean isSubmachineState(); + } 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 new file mode 100644 index 00000000..e9f38552 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/StateMachineState.java @@ -0,0 +1,93 @@ +/* + * 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.state; + +import java.util.Collection; + +import org.springframework.statemachine.StateMachine; +import org.springframework.statemachine.action.Action; + +/** + * A {@link State} implementation where state is wrapped in a substatemachine. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class StateMachineState extends AbstractState { + + /** + * Instantiates a new state machine state. + * + * @param submachine the submachine + */ + public StateMachineState(StateMachine submachine) { + super(null, null, null, null, submachine); + } + + /** + * Instantiates a new state machine state. + * + * @param submachine the submachine + * @param deferred the deferred + */ + public StateMachineState(StateMachine submachine, Collection deferred) { + super(deferred, null, null, null, submachine); + } + + /** + * Instantiates a new state machine state. + * + * @param submachine the submachine + * @param pseudoState the pseudo state + */ + public StateMachineState(StateMachine submachine, PseudoState pseudoState) { + super(null, null, null, pseudoState, submachine); + } + + /** + * Instantiates a new state machine state. + * + * @param submachine the submachine + * @param deferred the deferred + * @param entryActions the entry actions + * @param exitActions the exit actions + * @param pseudoState the pseudo state + */ + public StateMachineState(StateMachine submachine, Collection deferred, Collection entryActions, Collection exitActions, + PseudoState pseudoState) { + super(deferred, entryActions, exitActions, pseudoState, submachine); + } + + /** + * Instantiates a new state machine state. + * + * @param submachine the submachine + * @param deferred the deferred + * @param entryActions the entry actions + * @param exitActions the exit actions + */ + public StateMachineState(StateMachine submachine, Collection deferred, Collection entryActions, Collection exitActions) { + super(deferred, entryActions, exitActions, null, submachine); + } + + @Override + public Collection getIds() { + return getSubmachine().getState().getIds(); + } + +} 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 439be491..0e11eef8 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 @@ -61,7 +61,7 @@ import org.springframework.util.Assert; * @param the type of state * @param the type of event */ -public abstract class AbstractStateMachine extends LifecycleObjectSupport implements StateMachine, E> { +public abstract class AbstractStateMachine extends LifecycleObjectSupport implements StateMachine { private static final Log log = LogFactory.getLog(AbstractStateMachine.class); @@ -180,10 +180,16 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport * an unmodifiable copy because states in a state machine are immutable. * * @return immutable copy of existing states - */ - public Collection> getStates() { + */ + @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); @@ -337,8 +343,9 @@ public abstract class AbstractStateMachine extends LifecycleObjectSupport OnTransition annotation = entry.getValue().getAnnotation(); String source = annotation.source(); String target = annotation.target(); - String s = sourceState.getId().toString(); - String t = targetState.getId().toString(); + // TODO: need major fixes + String s = sourceState.getIds().iterator().next().toString(); + String t = targetState.getIds().iterator().next().toString(); if (s.equals(source) && t.equals(target)) { handlersList.add(entry.getValue()); } 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 7cb9c7dd..1a4cabfe 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 @@ -33,16 +33,47 @@ import org.springframework.statemachine.trigger.Trigger; */ public interface Transition { + /** + * Transit this transition with a give state context. + * + * @param context the state context + * @return true, if transition happened, false otherwise + */ boolean transit(StateContext context); + /** + * Gets the source state of this transition. + * + * @return the source state + */ State getSource(); + /** + * Gets the target state of this transition. + * + * @return the target state + */ State getTarget(); + /** + * Gets the transition actions. + * + * @return the transition actions + */ Collection getActions(); + /** + * Gets the transition trigger. + * + * @return the transition trigger + */ Trigger getTrigger(); + /** + * Gets the transition kind. + * + * @return the transition kind + */ TransitionKind getKind(); } 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 5e8aa5f8..c44ca1bb 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 @@ -58,10 +58,18 @@ public abstract class AbstractStateMachineTests { SI,S1,S2,S3,S4 } + public enum TestSubStates { + SUBSI,SUBS1,SUBS2,SUBS3,SUBS4 + } + public enum TestEvents { E1,E2,E3,E4 } + public enum TestSubEvents { + SUBE1,SUBE2,SUBE3,SUBE4 + } + @Configuration public static class BaseConfig { diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineFactoryTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineFactoryTests.java index 57fafff6..70719c26 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineFactoryTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/StateMachineFactoryTests.java @@ -15,7 +15,7 @@ */ package org.springframework.statemachine; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.contains; import static org.junit.Assert.assertThat; import org.junit.Test; @@ -30,7 +30,6 @@ import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter import org.springframework.statemachine.config.EnumStateMachineFactory; import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; -import org.springframework.statemachine.state.State; public class StateMachineFactoryTests extends AbstractStateMachineTests { @@ -41,11 +40,11 @@ public class StateMachineFactoryTests extends AbstractStateMachineTests { EnumStateMachineFactory stateMachineFactory = ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINEFACTORY, EnumStateMachineFactory.class); - StateMachine, TestEvents> machine = stateMachineFactory.getStateMachine(); + StateMachine machine = stateMachineFactory.getStateMachine(); - assertThat(machine.getState().getId(), is(TestStates.S1)); + assertThat(machine.getState().getIds(), contains(TestStates.S1)); machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); - assertThat(machine.getState().getId(), is(TestStates.S2)); + assertThat(machine.getState().getIds(), contains(TestStates.S2)); ctx.close(); } diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/GuardTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/GuardTests.java index cf959bbc..b606bdac 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/GuardTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/guard/GuardTests.java @@ -16,6 +16,7 @@ package org.springframework.statemachine.guard; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; @@ -27,12 +28,12 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.task.SyncTaskExecutor; import org.springframework.core.task.TaskExecutor; -import org.springframework.statemachine.EnumStateMachine; -import org.springframework.statemachine.StateMachineSystemConstants; import org.springframework.statemachine.AbstractStateMachineTests.TestAction; import org.springframework.statemachine.AbstractStateMachineTests.TestEvents; import org.springframework.statemachine.AbstractStateMachineTests.TestGuard; import org.springframework.statemachine.AbstractStateMachineTests.TestStates; +import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.StateMachineSystemConstants; import org.springframework.statemachine.config.EnableStateMachine; import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; @@ -77,12 +78,12 @@ public class GuardTests { assertThat(testAction, notNullValue()); machine.start(); - assertThat(machine.getState().getId(), is(TestStates.S1)); + assertThat(machine.getState().getIds(), contains(TestStates.S1)); machine.sendEvent(TestEvents.E1); assertThat(testGuard.onEvaluateLatch.await(2, TimeUnit.SECONDS), is(true)); assertThat(testAction.onExecuteLatch.await(2, TimeUnit.SECONDS), is(false)); - assertThat(machine.getState().getId(), is(TestStates.S1)); + assertThat(machine.getState().getIds(), contains(TestStates.S1)); ctx.close(); } 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 b0a52dd2..9ac26461 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 @@ -16,6 +16,7 @@ package org.springframework.statemachine.guard; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.contains; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -39,7 +40,6 @@ import org.springframework.statemachine.config.EnableStateMachine; import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; -import org.springframework.statemachine.guard.SpelExpressionGuard; import org.springframework.statemachine.support.DefaultStateContext; /** @@ -72,9 +72,9 @@ public class SpelExpressionGuardTests extends AbstractStateMachineTests { EnumStateMachine machine = ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); - assertThat(machine.getState().getId(), is(TestStates.S1)); + assertThat(machine.getState().getIds(), contains(TestStates.S1)); machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); - assertThat(machine.getState().getId(), is(TestStates.S1)); + assertThat(machine.getState().getIds(), contains(TestStates.S1)); ctx.close(); } 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 fdddac9c..cb04b19c 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 @@ -15,6 +15,7 @@ */ package org.springframework.statemachine.listener; +import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; @@ -40,7 +41,6 @@ import org.springframework.statemachine.config.EnableStateMachine; import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter; import org.springframework.statemachine.config.builders.StateMachineStateConfigurer; import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; -import org.springframework.statemachine.listener.StateMachineListener; import org.springframework.statemachine.state.State; /** @@ -65,12 +65,12 @@ public class ListenerTests extends AbstractStateMachineTests { assertThat(machine, notNullValue()); machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).setHeader("foo", "jee1").build()); assertThat(listener.states.size(), is(1)); - assertThat(listener.states.get(0).from.getId(), is(TestStates.S1)); - assertThat(listener.states.get(0).to.getId(), is(TestStates.S2)); + assertThat(listener.states.get(0).from.getIds(), contains(TestStates.S1)); + assertThat(listener.states.get(0).to.getIds(), contains(TestStates.S2)); machine.sendEvent(MessageBuilder.withPayload(TestEvents.E2).setHeader("foo", "jee2").build()); assertThat(listener.states.size(), is(2)); - assertThat(listener.states.get(1).from.getId(), is(TestStates.S2)); - assertThat(listener.states.get(1).to.getId(), is(TestStates.S3)); + assertThat(listener.states.get(1).from.getIds(), contains(TestStates.S2)); + assertThat(listener.states.get(1).to.getIds(), contains(TestStates.S3)); machine.sendEvent(MessageBuilder.withPayload(TestEvents.E4).setHeader("foo", "jee2").build()); assertThat(listener.states.size(), is(2)); diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/InitialStateTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/InitialStateTests.java index fc857111..97c7623f 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/InitialStateTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/InitialStateTests.java @@ -15,7 +15,7 @@ */ package org.springframework.statemachine.state; -import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.contains; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -49,7 +49,7 @@ public class InitialStateTests extends AbstractStateMachineTests { EnumStateMachine machine = context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); - assertThat(machine.getState().getId(), is(TestStates.S1)); + assertThat(machine.getState().getIds(), contains(TestStates.S1)); } @Test(expected = Exception.class) diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/RegionStateTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/RegionStateTests.java new file mode 100644 index 00000000..46d259ee --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/RegionStateTests.java @@ -0,0 +1,90 @@ +/* + * 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.state; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.contains; +import static org.junit.Assert.assertThat; + +import java.util.ArrayList; +import java.util.Collection; + +import org.junit.Test; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.statemachine.AbstractStateMachineTests; +import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.region.Region; +import org.springframework.statemachine.transition.DefaultExternalTransition; +import org.springframework.statemachine.transition.Transition; + +/** + * Tests for states using a submachine. + * + * @author Janne Valkealahti + * + */ +public class RegionStateTests extends AbstractStateMachineTests { + + @Test + public void testSimpleRegionState() { + + State stateSI = new EnumState(TestStates.SI); + State stateS1 = new EnumState(TestStates.S1); + State stateS2 = new EnumState(TestStates.S2); + State stateS3 = new EnumState(TestStates.S3); + + Collection> states = new ArrayList>(); + states.add(stateSI); + states.add(stateS1); + states.add(stateS2); + states.add(stateS3); + + Collection> transitions = new ArrayList>(); + + DefaultExternalTransition transitionFromSIToS1 = + new DefaultExternalTransition(stateSI, stateS1, null, TestEvents.E1, null); + + DefaultExternalTransition transitionFromS1ToS2 = + new DefaultExternalTransition(stateS1, stateS2, null, TestEvents.E2, null); + + DefaultExternalTransition transitionFromS2ToS3 = + new DefaultExternalTransition(stateS2, stateS3, null, TestEvents.E3, null); + + transitions.add(transitionFromSIToS1); + transitions.add(transitionFromS1ToS2); + transitions.add(transitionFromS2ToS3); + + SyncTaskExecutor taskExecutor = new SyncTaskExecutor(); + EnumStateMachine machine = new EnumStateMachine(states, transitions, stateSI); + machine.setTaskExecutor(taskExecutor); + machine.start(); + + Collection> regions = new ArrayList>(); + regions.add(machine); + RegionState state = new RegionState(regions); + + assertThat(state.isSimple(), is(false)); + assertThat(state.isComposite(), is(true)); + assertThat(state.isOrthogonal(), is(false)); + assertThat(state.isSubmachineState(), is(false)); + + assertThat(state.getIds(), contains(TestStates.SI)); + + + + } + +} diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/SubmachineStateTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/SubmachineStateTests.java new file mode 100644 index 00000000..db2064be --- /dev/null +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/SubmachineStateTests.java @@ -0,0 +1,87 @@ +/* + * 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.state; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.contains; +import static org.junit.Assert.assertThat; + +import java.util.ArrayList; +import java.util.Collection; + +import org.junit.Test; +import org.springframework.core.task.SyncTaskExecutor; +import org.springframework.statemachine.AbstractStateMachineTests; +import org.springframework.statemachine.EnumStateMachine; +import org.springframework.statemachine.transition.DefaultExternalTransition; +import org.springframework.statemachine.transition.Transition; + +/** + * Tests for states using a submachine. + * + * @author Janne Valkealahti + * + */ +public class SubmachineStateTests extends AbstractStateMachineTests { + + @Test + public void testSimpleSubmachineState() { + + State stateSI = new EnumState(TestStates.SI); + State stateS1 = new EnumState(TestStates.S1); + State stateS2 = new EnumState(TestStates.S2); + State stateS3 = new EnumState(TestStates.S3); + + Collection> states = new ArrayList>(); + states.add(stateSI); + states.add(stateS1); + states.add(stateS2); + states.add(stateS3); + + Collection> transitions = new ArrayList>(); + + DefaultExternalTransition transitionFromSIToS1 = + new DefaultExternalTransition(stateSI, stateS1, null, TestEvents.E1, null); + + DefaultExternalTransition transitionFromS1ToS2 = + new DefaultExternalTransition(stateS1, stateS2, null, TestEvents.E2, null); + + DefaultExternalTransition transitionFromS2ToS3 = + new DefaultExternalTransition(stateS2, stateS3, null, TestEvents.E3, null); + + transitions.add(transitionFromSIToS1); + transitions.add(transitionFromS1ToS2); + transitions.add(transitionFromS2ToS3); + + SyncTaskExecutor taskExecutor = new SyncTaskExecutor(); + EnumStateMachine machine = new EnumStateMachine(states, transitions, stateSI); + machine.setTaskExecutor(taskExecutor); + machine.start(); + + StateMachineState state = new StateMachineState(machine); + + assertThat(state.isSimple(), is(false)); + assertThat(state.isComposite(), is(false)); + assertThat(state.isOrthogonal(), is(false)); + assertThat(state.isSubmachineState(), is(true)); + + assertThat(state.getIds(), contains(TestStates.SI)); + + + + } + +} 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 eacf4e1f..e1d2fece 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 @@ -16,6 +16,7 @@ package org.springframework.statemachine.transition; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.contains; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -54,9 +55,9 @@ public class TransitionTests extends AbstractStateMachineTests { EnumStateMachine machine = ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, EnumStateMachine.class); - assertThat(machine.getState().getId(), is(TestStates.S1)); + assertThat(machine.getState().getIds(), contains(TestStates.S1)); machine.sendEvent(MessageBuilder.withPayload(TestEvents.E1).build()); - assertThat(machine.getState().getId(), is(TestStates.S3)); + assertThat(machine.getState().getIds(), contains(TestStates.S3)); ctx.close(); } @@ -74,7 +75,7 @@ public class TransitionTests extends AbstractStateMachineTests { TestAction externalTestAction = ctx.getBean("externalTestAction", TestAction.class); TestAction internalTestAction = ctx.getBean("internalTestAction", TestAction.class); - assertThat(machine.getState().getId(), is(TestStates.S1)); + assertThat(machine.getState().getIds(), contains(TestStates.S1)); assertThat(testExitAction.onExecuteLatch.await(1, TimeUnit.SECONDS), is(false)); assertThat(testEntryAction.onExecuteLatch.await(1, TimeUnit.SECONDS), is(false)); @@ -88,7 +89,7 @@ public class TransitionTests extends AbstractStateMachineTests { assertThat(testEntryAction.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); assertThat(externalTestAction.onExecuteLatch.await(1, TimeUnit.SECONDS), is(true)); - assertThat(machine.getState().getId(), is(TestStates.S2)); + assertThat(machine.getState().getIds(), contains(TestStates.S2)); ctx.close(); }