Base support for submachines and regions

- resolves issues #2 and #4
- refactor states so that we can use submachines
  and regions to compose a state.
- refactor some concepts around StateMachine now that
  it extends Region.
This commit is contained in:
Janne Valkealahti
2015-02-07 18:03:48 +00:00
parent b6a62b2509
commit c6eee54dbc
22 changed files with 860 additions and 86 deletions

View File

@@ -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 <S> the type of state
* @param <E> the type of event
*/
public interface StateMachine<S, E> {
public interface StateMachine<S, E> extends Region<S, E> {
/**
* Gets the initial state {@code S}.
*
* @return initial state
*/
S getInitialState();
/**
* Gets the current state {@code S}.
*
* @return current state
*/
S getState();
State<S,E> getInitialState();
/**
* Start the state machine.
@@ -68,6 +63,6 @@ public interface StateMachine<S, E> {
*
* @param listener the listener
*/
void addStateListener(StateMachineListener<S, E> listener);
void addStateListener(StateMachineListener<State<S,E>, E> listener);
}

View File

@@ -45,8 +45,8 @@ import org.springframework.statemachine.transition.TransitionKind;
* @param <E> the type of event
*/
public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> extends LifecycleObjectSupport implements
StateMachineFactory<State<S, E>, E> {
StateMachineFactory<S, E> {
private final StateMachineTransitions<S, E> stateMachineTransitions;
private final StateMachineStates<S, E> stateMachineStates;
@@ -64,11 +64,11 @@ public class EnumStateMachineFactory<S extends Enum<S>, E extends Enum<E>> exten
}
@Override
public StateMachine<State<S, E>, E> getStateMachine() {
public StateMachine<S, E> getStateMachine() {
return stateMachine();
}
public StateMachine<State<S, E>, E> stateMachine() {
public StateMachine<S, E> stateMachine() {
Map<S, State<S, E>> stateMap = new HashMap<S, State<S, E>>();
for (StateData<S, E> stateData : stateMachineStates.getStates()) {

View File

@@ -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<S extends Enum<S>, E extends Enum<E>> extends
@@ -43,7 +42,7 @@ public class StateMachineConfiguration<S extends Enum<S>, E extends Enum<E>> ext
}
private static class StateMachineDelegatingFactoryBean<S extends Enum<S>, E extends Enum<E>> implements
FactoryBean<StateMachine<State<S, E>, E>>, BeanFactoryAware, InitializingBean {
FactoryBean<StateMachine<S, E>>, BeanFactoryAware, InitializingBean {
private final StateMachineConfigBuilder<S, E> builder;
@@ -51,7 +50,7 @@ public class StateMachineConfiguration<S extends Enum<S>, E extends Enum<E>> ext
private BeanFactory beanFactory;
private StateMachine<State<S, E>, E> stateMachine;
private StateMachine<S, E> stateMachine;
@SuppressWarnings("unused")
public StateMachineDelegatingFactoryBean(StateMachineConfigBuilder<S, E> builder) {
@@ -59,7 +58,7 @@ public class StateMachineConfiguration<S extends Enum<S>, E extends Enum<E>> ext
}
@Override
public StateMachine<State<S, E>, E> getObject() throws Exception {
public StateMachine<S, E> getObject() throws Exception {
return stateMachine;
}

View File

@@ -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<S extends Enum<S>, E extends Enum<E>> extends
@@ -43,7 +42,7 @@ public class StateMachineFactoryConfiguration<S extends Enum<S>, E extends Enum<
}
private static class StateMachineFactoryDelegatingFactoryBean<S extends Enum<S>, E extends Enum<E>> implements
FactoryBean<StateMachineFactory<State<S, E>, E>>, BeanFactoryAware, InitializingBean {
FactoryBean<StateMachineFactory<S, E>>, BeanFactoryAware, InitializingBean {
private final StateMachineConfigBuilder<S, E> builder;
@@ -51,7 +50,7 @@ public class StateMachineFactoryConfiguration<S extends Enum<S>, E extends Enum<
private BeanFactory beanFactory;
private StateMachineFactory<State<S, E>, E> stateMachineFactory;
private StateMachineFactory<S, E> stateMachineFactory;
@SuppressWarnings("unused")
public StateMachineFactoryDelegatingFactoryBean(StateMachineConfigBuilder<S, E> builder) {
@@ -59,7 +58,7 @@ public class StateMachineFactoryConfiguration<S extends Enum<S>, E extends Enum<
}
@Override
public StateMachineFactory<State<S, E>, E> getObject() throws Exception {
public StateMachineFactory<S, E> getObject() throws Exception {
return stateMachineFactory;
}

View File

@@ -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 <S> the type of state
* @param <E> the type of event
*/
public interface Region<S, E> {
/**
* Gets the current {@link State}.
*
* @return current state
*/
State<S,E> 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<State<S, E>> getStates();
/**
* Gets a {@link Transition}s for this region.
*
* @return immutable copy of transitions
*/
Collection<Transition<S,E>> getTransitions();
}

View File

@@ -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 <S> the type of state
* @param <E> the type of event
*/
public abstract class AbstractSimpleState<S, E> extends AbstractState<S, E> {
private final Collection<S> 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<E> deferred, Collection<Action> entryActions, Collection<Action> 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<E> 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<E> deferred, Collection<Action> entryActions, Collection<Action> exitActions,
PseudoState pseudoState, Collection<Region<S, E>> regions) {
super(deferred, entryActions, exitActions, pseudoState, regions);
this.ids = new ArrayList<S>();
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<E> deferred, Collection<Action> entryActions, Collection<Action> exitActions,
PseudoState pseudoState, StateMachine<S, E> submachine) {
super(deferred, entryActions, exitActions, pseudoState, submachine);
this.ids = new ArrayList<S>();
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<E> deferred, Collection<Action> entryActions, Collection<Action> exitActions,
PseudoState pseudoState) {
super(deferred, entryActions, exitActions, pseudoState);
this.ids = new ArrayList<S>();
this.ids.add(id);
}
@Override
public Collection<S> getIds() {
return Collections.unmodifiableCollection(ids);
}
}

View File

@@ -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<S, E> implements State<S, E> {
private final S id;
private final PseudoState pseudoState;
private final Collection<E> deferred;
private final Collection<Action> entryActions;
private final Collection<Action> exitActions;
private final Collection<Region<S, E>> regions = new ArrayList<Region<S, E>>();
private final StateMachine<S, E> 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<E> deferred) {
this(id, deferred, null, null);
public AbstractState(Collection<E> 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<E> deferred, Collection<Action> entryActions, Collection<Action> exitActions) {
this(id, deferred, entryActions, exitActions, null);
public AbstractState(Collection<E> deferred, Collection<Action> entryActions, Collection<Action> 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<E> deferred, Collection<Action> entryActions, Collection<Action> exitActions, PseudoState pseudoState) {
this.id = id;
public AbstractState(Collection<E> deferred, Collection<Action> entryActions, Collection<Action> 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<E> deferred, Collection<Action> entryActions, Collection<Action> exitActions,
PseudoState pseudoState, StateMachine<S, E> 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<E> deferred, Collection<Action> entryActions, Collection<Action> exitActions,
PseudoState pseudoState, Collection<Region<S, E>> 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<E> deferred, Collection<Action> entryActions, Collection<Action> exitActions,
PseudoState pseudoState, Collection<Region<S, E>> regions, StateMachine<S, E> 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<S> getIds();
@Override
public PseudoState getPseudoState() {
@@ -107,21 +147,49 @@ public abstract class AbstractState<S, E> implements State<S, E> {
public Collection<E> getDeferredEvents() {
return deferred;
}
@Override
public Collection<Action> getEntryActions() {
return entryActions;
}
@Override
public Collection<Action> 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<S, E> getSubmachine() {
return submachine;
}
protected Collection<Region<S, E>> getRegions() {
return regions;
}
@Override
public String toString() {
return "AbstractState [ids=" + StringUtils.collectionToCommaDelimitedString(getIds()) + ", pseudoState=" + pseudoState + ", deferred=" + deferred
+ ", entryActions=" + entryActions + ", exitActions=" + exitActions + "]";
}
}

View File

@@ -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<S extends Enum<S>, E extends Enum<E>> extends AbstractState<S, E> {
/**
* A {@link State} implementation where state and event is enum based.
*
* @author Janne Valkealahti
*
* @param <S> the type of state
* @param <E> the type of event
*/
public class EnumState<S extends Enum<S>, E extends Enum<E>> extends AbstractSimpleState<S, E> {
/**
* 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<E> 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<E> deferred, Collection<Action> entryActions, Collection<Action> 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<E> deferred, Collection<Action> entryActions, Collection<Action> 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<E> deferred, Collection<Action> entryActions, Collection<Action> exitActions,
PseudoState pseudoState, Collection<Region<S, E>> 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<E> deferred, Collection<Action> entryActions, Collection<Action> exitActions,
PseudoState pseudoState, StateMachine<S, E> 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() + "]";
}

View File

@@ -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 <S> the type of state
* @param <E> the type of event
*/
public class RegionState<S, E> extends AbstractState<S, E> {
/**
* Instantiates a new region state.
*
* @param regions the regions
*/
public RegionState(Collection<Region<S, E>> regions) {
super(null, null, null, null, regions);
}
/**
* Instantiates a new region state.
*
* @param regions the regions
* @param deferred the deferred
*/
public RegionState(Collection<Region<S, E>> regions, Collection<E> deferred) {
super(deferred, null, null, null, regions);
}
/**
* Instantiates a new region state.
*
* @param regions the regions
* @param pseudoState the pseudo state
*/
public RegionState(Collection<Region<S, E>> 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<Region<S, E>> regions, Collection<E> deferred, Collection<Action> entryActions, Collection<Action> 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<Region<S, E>> regions, Collection<E> deferred, Collection<Action> entryActions, Collection<Action> exitActions) {
super(deferred, entryActions, exitActions, null, regions);
}
@Override
public Collection<S> getIds() {
ArrayList<S> ids = new ArrayList<S>();
for (Region<S, E> r : getRegions()) {
ids.addAll(r.getState().getIds());
}
return ids;
}
}

View File

@@ -30,11 +30,12 @@ import org.springframework.statemachine.action.Action;
public interface State<S, E> {
/**
* 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<S> getIds();
/**
* Gets a {@link PseudoState} attached to a {@code State}.
@@ -65,5 +66,38 @@ public interface State<S, E> {
* @return the state exit actions
*/
Collection<Action> 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();
}

View File

@@ -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 <S> the type of state
* @param <E> the type of event
*/
public class StateMachineState<S, E> extends AbstractState<S, E> {
/**
* Instantiates a new state machine state.
*
* @param submachine the submachine
*/
public StateMachineState(StateMachine<S, E> submachine) {
super(null, null, null, null, submachine);
}
/**
* Instantiates a new state machine state.
*
* @param submachine the submachine
* @param deferred the deferred
*/
public StateMachineState(StateMachine<S, E> submachine, Collection<E> 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<S, E> 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<S, E> submachine, Collection<E> deferred, Collection<Action> entryActions, Collection<Action> 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<S, E> submachine, Collection<E> deferred, Collection<Action> entryActions, Collection<Action> exitActions) {
super(deferred, entryActions, exitActions, null, submachine);
}
@Override
public Collection<S> getIds() {
return getSubmachine().getState().getIds();
}
}

View File

@@ -61,7 +61,7 @@ import org.springframework.util.Assert;
* @param <S> the type of state
* @param <E> the type of event
*/
public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport implements StateMachine<State<S,E>, E> {
public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport implements StateMachine<S, E> {
private static final Log log = LogFactory.getLog(AbstractStateMachine.class);
@@ -180,10 +180,16 @@ public abstract class AbstractStateMachine<S, E> extends LifecycleObjectSupport
* an unmodifiable copy because states in a state machine are immutable.
*
* @return immutable copy of existing states
*/
public Collection<State<S,E>> getStates() {
*/
@Override
public Collection<State<S, E>> getStates() {
return Collections.unmodifiableCollection(states);
}
@Override
public Collection<Transition<S, E>> getTransitions() {
return transitions;
}
private void switchToState(State<S,E> state, Message<E> event) {
log.info("Moving into state=" + state + " from " + currentState);
@@ -337,8 +343,9 @@ public abstract class AbstractStateMachine<S, E> 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());
}

View File

@@ -33,16 +33,47 @@ import org.springframework.statemachine.trigger.Trigger;
*/
public interface Transition<S, E> {
/**
* 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<S,E> getSource();
/**
* Gets the target state of this transition.
*
* @return the target state
*/
State<S,E> getTarget();
/**
* Gets the transition actions.
*
* @return the transition actions
*/
Collection<Action> getActions();
/**
* Gets the transition trigger.
*
* @return the transition trigger
*/
Trigger<S, E> getTrigger();
/**
* Gets the transition kind.
*
* @return the transition kind
*/
TransitionKind getKind();
}

View File

@@ -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 {

View File

@@ -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<TestStates, TestEvents> stateMachineFactory =
ctx.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINEFACTORY, EnumStateMachineFactory.class);
StateMachine<State<TestStates, TestEvents>, TestEvents> machine = stateMachineFactory.getStateMachine();
StateMachine<TestStates,TestEvents> 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();
}

View File

@@ -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();
}

View File

@@ -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<TestStates,TestEvents> 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();
}

View File

@@ -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));

View File

@@ -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<TestStates,TestEvents> 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)

View File

@@ -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<TestStates,TestEvents> stateSI = new EnumState<TestStates,TestEvents>(TestStates.SI);
State<TestStates,TestEvents> stateS1 = new EnumState<TestStates,TestEvents>(TestStates.S1);
State<TestStates,TestEvents> stateS2 = new EnumState<TestStates,TestEvents>(TestStates.S2);
State<TestStates,TestEvents> stateS3 = new EnumState<TestStates,TestEvents>(TestStates.S3);
Collection<State<TestStates,TestEvents>> states = new ArrayList<State<TestStates,TestEvents>>();
states.add(stateSI);
states.add(stateS1);
states.add(stateS2);
states.add(stateS3);
Collection<Transition<TestStates,TestEvents>> transitions = new ArrayList<Transition<TestStates,TestEvents>>();
DefaultExternalTransition<TestStates,TestEvents> transitionFromSIToS1 =
new DefaultExternalTransition<TestStates,TestEvents>(stateSI, stateS1, null, TestEvents.E1, null);
DefaultExternalTransition<TestStates,TestEvents> transitionFromS1ToS2 =
new DefaultExternalTransition<TestStates,TestEvents>(stateS1, stateS2, null, TestEvents.E2, null);
DefaultExternalTransition<TestStates,TestEvents> transitionFromS2ToS3 =
new DefaultExternalTransition<TestStates,TestEvents>(stateS2, stateS3, null, TestEvents.E3, null);
transitions.add(transitionFromSIToS1);
transitions.add(transitionFromS1ToS2);
transitions.add(transitionFromS2ToS3);
SyncTaskExecutor taskExecutor = new SyncTaskExecutor();
EnumStateMachine<TestStates, TestEvents> machine = new EnumStateMachine<TestStates, TestEvents>(states, transitions, stateSI);
machine.setTaskExecutor(taskExecutor);
machine.start();
Collection<Region<TestStates,TestEvents>> regions = new ArrayList<Region<TestStates,TestEvents>>();
regions.add(machine);
RegionState<TestStates,TestEvents> state = new RegionState<TestStates,TestEvents>(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));
}
}

View File

@@ -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<TestStates,TestEvents> stateSI = new EnumState<TestStates,TestEvents>(TestStates.SI);
State<TestStates,TestEvents> stateS1 = new EnumState<TestStates,TestEvents>(TestStates.S1);
State<TestStates,TestEvents> stateS2 = new EnumState<TestStates,TestEvents>(TestStates.S2);
State<TestStates,TestEvents> stateS3 = new EnumState<TestStates,TestEvents>(TestStates.S3);
Collection<State<TestStates,TestEvents>> states = new ArrayList<State<TestStates,TestEvents>>();
states.add(stateSI);
states.add(stateS1);
states.add(stateS2);
states.add(stateS3);
Collection<Transition<TestStates,TestEvents>> transitions = new ArrayList<Transition<TestStates,TestEvents>>();
DefaultExternalTransition<TestStates,TestEvents> transitionFromSIToS1 =
new DefaultExternalTransition<TestStates,TestEvents>(stateSI, stateS1, null, TestEvents.E1, null);
DefaultExternalTransition<TestStates,TestEvents> transitionFromS1ToS2 =
new DefaultExternalTransition<TestStates,TestEvents>(stateS1, stateS2, null, TestEvents.E2, null);
DefaultExternalTransition<TestStates,TestEvents> transitionFromS2ToS3 =
new DefaultExternalTransition<TestStates,TestEvents>(stateS2, stateS3, null, TestEvents.E3, null);
transitions.add(transitionFromSIToS1);
transitions.add(transitionFromS1ToS2);
transitions.add(transitionFromS2ToS3);
SyncTaskExecutor taskExecutor = new SyncTaskExecutor();
EnumStateMachine<TestStates, TestEvents> machine = new EnumStateMachine<TestStates, TestEvents>(states, transitions, stateSI);
machine.setTaskExecutor(taskExecutor);
machine.start();
StateMachineState<TestStates,TestEvents> state = new StateMachineState<TestStates,TestEvents>(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));
}
}

View File

@@ -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<TestStates,TestEvents> 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();
}