diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineFactory.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineFactory.java index b4bc7003..51b50e78 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineFactory.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/AbstractStateMachineFactory.java @@ -40,6 +40,7 @@ import org.springframework.statemachine.action.Action; import org.springframework.statemachine.config.model.ChoiceData; import org.springframework.statemachine.config.model.EntryData; import org.springframework.statemachine.config.model.ExitData; +import org.springframework.statemachine.config.model.HistoryData; import org.springframework.statemachine.config.model.JunctionData; import org.springframework.statemachine.config.model.StateData; import org.springframework.statemachine.config.model.StateMachineModel; @@ -412,6 +413,7 @@ public abstract class AbstractStateMachineFactory extends LifecycleObjectS } + @SuppressWarnings("unchecked") private StateMachine buildMachine(Map> machineMap, Map> stateMap, Map> holderMap, Collection> stateDatas, Collection> transitionsData, BeanFactory beanFactory, Boolean contextEvents, DefaultExtendedState defaultExtendedState, @@ -458,11 +460,9 @@ public abstract class AbstractStateMachineFactory extends LifecycleObjectS } else if (stateData.isEnd()) { pseudoState = new DefaultPseudoState(PseudoStateKind.END); } else if (stateData.getPseudoStateKind() == PseudoStateKind.HISTORY_SHALLOW) { - pseudoState = new HistoryPseudoState(PseudoStateKind.HISTORY_SHALLOW); - historyState = pseudoState; + continue; } else if (stateData.getPseudoStateKind() == PseudoStateKind.HISTORY_DEEP) { - pseudoState = new HistoryPseudoState(PseudoStateKind.HISTORY_DEEP); - historyState = pseudoState; + continue; } else if (stateData.getPseudoStateKind() == PseudoStateKind.JOIN) { continue; } else if (stateData.getPseudoStateKind() == PseudoStateKind.FORK) { @@ -489,6 +489,48 @@ public abstract class AbstractStateMachineFactory extends LifecycleObjectS } for (StateData stateData : stateDatas) { + if (stateData.getPseudoStateKind() == PseudoStateKind.HISTORY_SHALLOW) { + State defaultState = null; + S s = stateData.getState(); + Collection> historys = stateMachineTransitions.getHistorys(); + for (HistoryData history : historys) { + if (history.getSource().equals(s)) { + defaultState = stateMap.get(history.getTarget()); + } + } + StateHolder defaultStateHolder = new StateHolder(defaultState); + StateHolder containingStateHolder = new StateHolder(stateMap.get(stateData.getParent())); + if (containingStateHolder.getState() == null) { + holderMap.put((S)stateData.getParent(), containingStateHolder); + } + PseudoState pseudoState = new HistoryPseudoState(PseudoStateKind.HISTORY_SHALLOW, defaultStateHolder, containingStateHolder); + state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(), + stateData.getExitActions(), pseudoState); + states.add(state); + stateMap.put(stateData.getState(), state); + historyState = pseudoState; + } else if (stateData.getPseudoStateKind() == PseudoStateKind.HISTORY_DEEP) { + State defaultState = null; + S s = stateData.getState(); + Collection> historys = stateMachineTransitions.getHistorys(); + for (HistoryData history : historys) { + if (history.getSource().equals(s)) { + defaultState = stateMap.get(history.getTarget()); + } + } + StateHolder defaultStateHolder = new StateHolder(defaultState); + StateHolder containingStateHolder = new StateHolder(stateMap.get(stateData.getParent())); + if (containingStateHolder.getState() == null) { + holderMap.put((S)stateData.getParent(), containingStateHolder); + } + PseudoState pseudoState = new HistoryPseudoState(PseudoStateKind.HISTORY_DEEP, defaultStateHolder, containingStateHolder); + state = buildStateInternal(stateData.getState(), stateData.getDeferred(), stateData.getEntryActions(), + stateData.getExitActions(), pseudoState); + states.add(state); + stateMap.put(stateData.getState(), state); + historyState = pseudoState; + } + if (stateData.getPseudoStateKind() == PseudoStateKind.CHOICE) { S s = stateData.getState(); List> list = stateMachineTransitions.getChoices().get(s); diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionBuilder.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionBuilder.java index a6e739e1..8c05d31d 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionBuilder.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionBuilder.java @@ -31,6 +31,7 @@ import org.springframework.statemachine.config.configurers.DefaultEntryTransitio import org.springframework.statemachine.config.configurers.DefaultExitTransitionConfigurer; import org.springframework.statemachine.config.configurers.DefaultExternalTransitionConfigurer; import org.springframework.statemachine.config.configurers.DefaultForkTransitionConfigurer; +import org.springframework.statemachine.config.configurers.DefaultHistoryTransitionConfigurer; import org.springframework.statemachine.config.configurers.DefaultInternalTransitionConfigurer; import org.springframework.statemachine.config.configurers.DefaultJoinTransitionConfigurer; import org.springframework.statemachine.config.configurers.DefaultJunctionTransitionConfigurer; @@ -39,6 +40,7 @@ import org.springframework.statemachine.config.configurers.EntryTransitionConfig import org.springframework.statemachine.config.configurers.ExitTransitionConfigurer; import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer; import org.springframework.statemachine.config.configurers.ForkTransitionConfigurer; +import org.springframework.statemachine.config.configurers.HistoryTransitionConfigurer; import org.springframework.statemachine.config.configurers.InternalTransitionConfigurer; import org.springframework.statemachine.config.configurers.JoinTransitionConfigurer; import org.springframework.statemachine.config.configurers.JunctionTransitionConfigurer; @@ -47,6 +49,7 @@ import org.springframework.statemachine.config.model.ChoiceData; import org.springframework.statemachine.config.model.ConfigurationData; import org.springframework.statemachine.config.model.EntryData; import org.springframework.statemachine.config.model.ExitData; +import org.springframework.statemachine.config.model.HistoryData; import org.springframework.statemachine.config.model.JunctionData; import org.springframework.statemachine.config.model.TransitionData; import org.springframework.statemachine.config.model.TransitionsData; @@ -74,6 +77,7 @@ public class StateMachineTransitionBuilder private final Map> joins = new HashMap>(); private final Collection> entryData = new ArrayList>(); private final Collection> exitData = new ArrayList>(); + private final Collection> historyData = new ArrayList>(); /** * Instantiates a new state machine transition builder. @@ -104,7 +108,7 @@ public class StateMachineTransitionBuilder @Override protected TransitionsData performBuild() throws Exception { - return new TransitionsData(transitionData, choices, junctions, forks, joins, entryData, exitData); + return new TransitionsData(transitionData, choices, junctions, forks, joins, entryData, exitData, historyData); } @Override @@ -152,6 +156,11 @@ public class StateMachineTransitionBuilder return apply(new DefaultExitTransitionConfigurer()); } + @Override + public HistoryTransitionConfigurer withHistory() throws Exception { + return apply(new DefaultHistoryTransitionConfigurer()); + } + /** * Adds the transition. * @@ -236,4 +245,14 @@ public class StateMachineTransitionBuilder public void addJoin(S target, List sources) { this.joins.put(target, sources); } + + /** + * Adds the default history. + * + * @param source the source + * @param target the target + */ + public void addDefaultHistory(S source, S target) { + this.historyData.add(new HistoryData(source, target)); + } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionConfigurer.java index dadd6083..79405a37 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionConfigurer.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/builders/StateMachineTransitionConfigurer.java @@ -20,6 +20,7 @@ import org.springframework.statemachine.config.configurers.EntryTransitionConfig import org.springframework.statemachine.config.configurers.ExitTransitionConfigurer; import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer; import org.springframework.statemachine.config.configurers.ForkTransitionConfigurer; +import org.springframework.statemachine.config.configurers.HistoryTransitionConfigurer; import org.springframework.statemachine.config.configurers.InternalTransitionConfigurer; import org.springframework.statemachine.config.configurers.JoinTransitionConfigurer; import org.springframework.statemachine.config.configurers.JunctionTransitionConfigurer; @@ -114,4 +115,12 @@ public interface StateMachineTransitionConfigurer { * @throws Exception if configuration error happens */ ExitTransitionConfigurer withExit() throws Exception; + + /** + * Gets a configurer for default history transition. + * + * @return {@link HistoryTransitionConfigurer} for chaining + * @throws Exception if configuration error happens + */ + HistoryTransitionConfigurer withHistory() throws Exception; } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultHistoryTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultHistoryTransitionConfigurer.java new file mode 100644 index 00000000..4ac375e6 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/DefaultHistoryTransitionConfigurer.java @@ -0,0 +1,54 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.statemachine.config.configurers; + +import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder; +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter; +import org.springframework.statemachine.config.model.TransitionsData; + +/** + * Default implementation of a {@link HistoryTransitionConfigurer}. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public class DefaultHistoryTransitionConfigurer + extends AnnotationConfigurerAdapter, StateMachineTransitionConfigurer, StateMachineTransitionBuilder> + implements HistoryTransitionConfigurer { + + private S source; + private S target; + + @Override + public void configure(StateMachineTransitionBuilder builder) throws Exception { + builder.addDefaultHistory(source, target); + } + + @Override + public HistoryTransitionConfigurer source(S source) { + this.source = source; + return this; + } + + @Override + public HistoryTransitionConfigurer target(S target) { + this.target = target; + return this; + } +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/HistoryTransitionConfigurer.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/HistoryTransitionConfigurer.java new file mode 100644 index 00000000..6f757602 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/configurers/HistoryTransitionConfigurer.java @@ -0,0 +1,49 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.statemachine.config.configurers; + +import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer; +import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder; +import org.springframework.statemachine.transition.Transition; + +/** + * {@code TransitionConfigurer} interface for configuring {@link Transition} + * with default history state. + * + * @author Janne Valkealahti + * + * @param the type of state + * @param the type of event + */ +public interface HistoryTransitionConfigurer + extends AnnotationConfigurerBuilder> { + + /** + * Specify a source state {@code S} for this {@link Transition}. + * + * @param source the source state {@code S} + * @return configurer for chaining + */ + HistoryTransitionConfigurer source(S source); + + /** + * Specify a target state {@code S} for this {@link Transition}. + * + * @param target the target state {@code S} + * @return configurer for chaining + */ + HistoryTransitionConfigurer target(S target); +} diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/HistoryData.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/HistoryData.java new file mode 100644 index 00000000..e2236fe7 --- /dev/null +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/HistoryData.java @@ -0,0 +1,56 @@ +/* + * Copyright 2016 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.statemachine.config.model; + +/** + * A simple data object keeping history related configs in a same place. + * + * @param the type of state + * @param the type of event + */ +public class HistoryData { + private final S source; + private final S target; + + /** + * Instantiates a new history data. + * + * @param source the source + * @param target the target + */ + public HistoryData(S source, S target) { + this.source = source; + this.target = target; + } + + /** + * Gets the source. + * + * @return the source + */ + public S getSource() { + return source; + } + + /** + * Gets the target. + * + * @return the target + */ + public S getTarget() { + return target; + } +} \ No newline at end of file diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/TransitionsData.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/TransitionsData.java index 78b665c0..b399b4eb 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/TransitionsData.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/config/model/TransitionsData.java @@ -36,6 +36,7 @@ public class TransitionsData { private final Map> joins; private final Collection> entrys; private final Collection> exits; + private final Collection> historys; /** * Instantiates a new transitions data. @@ -43,7 +44,7 @@ public class TransitionsData { * @param transitionsData the transitions data */ public TransitionsData(Collection> transitionsData) { - this(transitionsData, null, null, null, null, null, null); + this(transitionsData, null, null, null, null, null, null, null); } /** @@ -56,10 +57,11 @@ public class TransitionsData { * @param joins the joins * @param entrys the entrys * @param exits the exits + * @param historys the historys */ public TransitionsData(Collection> transitionsData, Map>> choices, Map>> junctions, Map> forks, Map> joins, Collection> entrys, - Collection> exits) { + Collection> exits, Collection> historys) { this.transitions = transitionsData; this.choices = choices; this.junctions = junctions; @@ -67,6 +69,7 @@ public class TransitionsData { this.joins = joins; this.entrys = entrys; this.exits = exits; + this.historys = historys; } /** @@ -131,4 +134,13 @@ public class TransitionsData { public Collection> getExits() { return exits; } + + /** + * Gets the historys. + * + * @return the historys + */ + public Collection> getHistorys() { + return historys; + } } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/HistoryPseudoState.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/HistoryPseudoState.java index 7ec293f2..7f39accd 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/HistoryPseudoState.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/state/HistoryPseudoState.java @@ -28,24 +28,57 @@ import org.springframework.util.Assert; */ public class HistoryPseudoState extends AbstractPseudoState { + private final StateHolder defaultState; + private final StateHolder containingState; private State state; /** * Instantiates a new history pseudo state. * * @param kind the kind + * @param containingState the parent containing state */ - public HistoryPseudoState(PseudoStateKind kind) { + public HistoryPseudoState(PseudoStateKind kind, StateHolder containingState) { + this(kind, containingState, new StateHolder(null)); + } + + /** + * Instantiates a new history pseudo state. + * + * @param kind the kind + * @param defaultState the default history state + * @param containingState the parent containing state + */ + public HistoryPseudoState(PseudoStateKind kind, StateHolder defaultState, StateHolder containingState) { super(kind); Assert.isTrue(PseudoStateKind.HISTORY_SHALLOW == kind || PseudoStateKind.HISTORY_DEEP == kind, "Pseudo state must be either shallow or deep"); + Assert.notNull(defaultState, "Holder defaultState must be set"); + Assert.notNull(containingState, "Holder containingState must be set"); + this.defaultState = defaultState; + this.containingState = containingState; } @Override public State entry(StateContext context) { - return state; + // if no logged history or history is final state, + // go to default state. go to containing parent if + // we have no history and there's no default state. + if (state == null) { + if (defaultState.getState() == null) { + return containingState.getState(); + } else { + return defaultState.getState(); + } + } else { + if (defaultState.getState() != null && state.getPseudoState() != null && state.getPseudoState().getKind() == PseudoStateKind.END) { + return defaultState.getState(); + } else { + return state; + } + } } - + /** * Sets the current recorded state. * @@ -63,5 +96,4 @@ public class HistoryPseudoState extends AbstractPseudoState { public State getState() { return state; } - } 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 b725daa1..c5e726d4 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 @@ -73,23 +73,23 @@ public abstract class AbstractStateMachineTests { } public enum TestEvents { - E1,E2,E3,E4,EF + E1,E2,E3,E4,EF,EH } public static enum TestStates2 { - BUSY, PLAYING, PAUSED, - IDLE, CLOSED, OPEN, - PAUSED1, PAUSED2 + BUSY, PLAYING, PAUSED, + IDLE, CLOSED, OPEN, + PAUSED1, PAUSED2 } public static enum TestStates3 { - READY, - FORK, JOIN, - TASKS, T1, T1E, T2, T2E, T3, T3E + READY, + FORK, JOIN, + TASKS, T1, T1E, T2, T2E, T3, T3E } public static enum TestEvents2 { - PLAY, STOP, PAUSE, EJECT, LOAD + PLAY, STOP, PAUSE, EJECT, LOAD } @Configuration diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/model/StateMachineModelTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/model/StateMachineModelTests.java index 2bfee851..ca752077 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/model/StateMachineModelTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/config/model/StateMachineModelTests.java @@ -77,7 +77,7 @@ public class StateMachineModelTests { Map>> junctions = new HashMap<>(); Map> forks = new HashMap<>(); Map> joins = new HashMap<>(); - TransitionsData transitionsData = new TransitionsData<>(transitions, choices, junctions, forks, joins, null, null); + TransitionsData transitionsData = new TransitionsData<>(transitions, choices, junctions, forks, joins, null, null, null); StateMachineModel stateMachineModel = new DefaultStateMachineModel<>(configurationData, statesData, transitionsData); ObjectStateMachineFactory factory = new ObjectStateMachineFactory<>(stateMachineModel); diff --git a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/HistoryStateTests.java b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/HistoryStateTests.java index c2e9b194..21c9525f 100644 --- a/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/HistoryStateTests.java +++ b/spring-statemachine-core/src/test/java/org/springframework/statemachine/state/HistoryStateTests.java @@ -55,6 +55,20 @@ public class HistoryStateTests extends AbstractStateMachineTests { assertThat(machine.getState().getIds(), contains(TestStates.S2, TestStates.S21)); } + @Test + @SuppressWarnings("unchecked") + public void testShallowNoHistoryDefaultsNormalEntry() { + context.register(BaseConfig.class, Config1.class); + context.refresh(); + ObjectStateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class); + assertThat(machine, notNullValue()); + machine.start(); + machine.sendEvent(TestEvents.E4); + + assertThat(machine.getState().getIds(), contains(TestStates.S2, TestStates.S20)); + } + @Test @SuppressWarnings("unchecked") public void testDeep() { @@ -88,6 +102,40 @@ public class HistoryStateTests extends AbstractStateMachineTests { assertThat(machine.getState().getIds(), contains(TestStates.S2, TestStates.S21, TestStates.S211)); } + @Test + @SuppressWarnings("unchecked") + public void testDefaultNotEntered() { + context.register(BaseConfig.class, Config4.class); + context.refresh(); + ObjectStateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class); + assertThat(machine, notNullValue()); + machine.start(); + assertThat(machine.getState().getIds(), contains(TestStates.S1)); + machine.sendEvent(TestEvents.EH); + assertThat(machine.getState().getIds(), contains(TestStates.S3, TestStates.S33)); + } + + @Test + @SuppressWarnings("unchecked") + public void testDefaultHistoryIsFinal() { + context.register(BaseConfig.class, Config4.class); + context.refresh(); + ObjectStateMachine machine = + context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class); + assertThat(machine, notNullValue()); + machine.start(); + assertThat(machine.getState().getIds(), contains(TestStates.S1)); + machine.sendEvent(TestEvents.E1); + assertThat(machine.getState().getIds(), contains(TestStates.S3, TestStates.S30)); + machine.sendEvent(TestEvents.EF); + assertThat(machine.getState().getIds(), contains(TestStates.S3, TestStates.SF)); + machine.sendEvent(TestEvents.E4); + assertThat(machine.getState().getIds(), contains(TestStates.S1)); + machine.sendEvent(TestEvents.EH); + assertThat(machine.getState().getIds(), contains(TestStates.S3, TestStates.S33)); + } + @Configuration @EnableStateMachine static class Config1 extends EnumStateMachineConfigurerAdapter { @@ -106,7 +154,6 @@ public class HistoryStateTests extends AbstractStateMachineTests { .state(TestStates.S20) .state(TestStates.S21) .history(TestStates.SH, History.SHALLOW); - } @Override @@ -239,4 +286,64 @@ public class HistoryStateTests extends AbstractStateMachineTests { } + @Configuration + @EnableStateMachine + static class Config4 extends EnumStateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineStateConfigurer states) throws Exception { + states + .withStates() + .initial(TestStates.S1) + .state(TestStates.S1) + .state(TestStates.S3) + .and() + .withStates() + .parent(TestStates.S3) + .initial(TestStates.S30) + .state(TestStates.S31) + .state(TestStates.S32) + .state(TestStates.S33) + .end(TestStates.SF) + .history(TestStates.SH, History.SHALLOW); + } + + @Override + public void configure(StateMachineTransitionConfigurer transitions) throws Exception { + transitions + .withExternal() + .source(TestStates.S1) + .target(TestStates.S3) + .event(TestEvents.E1) + .and() + .withExternal() + .source(TestStates.S30) + .target(TestStates.S31) + .event(TestEvents.E2) + .and() + .withExternal() + .source(TestStates.S31) + .target(TestStates.S32) + .event(TestEvents.E3) + .and() + .withExternal() + .source(TestStates.S30) + .target(TestStates.SF) + .event(TestEvents.EF) + .and() + .withExternal() + .source(TestStates.S3) + .target(TestStates.S1) + .event(TestEvents.E4) + .and() + .withExternal() + .source(TestStates.S1) + .target(TestStates.SH) + .event(TestEvents.EH) + .and() + .withHistory() + .source(TestStates.SH) + .target(TestStates.S33); + } + } } diff --git a/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/support/UmlModelParser.java b/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/support/UmlModelParser.java index 789363a7..f2e76d16 100644 --- a/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/support/UmlModelParser.java +++ b/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/support/UmlModelParser.java @@ -46,6 +46,7 @@ import org.springframework.statemachine.action.Action; import org.springframework.statemachine.config.model.ChoiceData; import org.springframework.statemachine.config.model.EntryData; import org.springframework.statemachine.config.model.ExitData; +import org.springframework.statemachine.config.model.HistoryData; import org.springframework.statemachine.config.model.JunctionData; import org.springframework.statemachine.config.model.StateData; import org.springframework.statemachine.config.model.StateMachineComponentResolver; @@ -72,6 +73,7 @@ public class UmlModelParser { private final Collection> transitionDatas = new ArrayList>(); private final Collection> entrys = new ArrayList>(); private final Collection> exits = new ArrayList>(); + private final Collection> historys = new ArrayList>(); private final Map>> choices = new HashMap>>(); private final Map>> junctions = new HashMap>>(); private final Map> forks = new HashMap>(); @@ -112,7 +114,7 @@ public class UmlModelParser { HashMap>> junctionsCopy = new HashMap>>(); junctionsCopy.putAll(junctions); return new DataHolder(new StatesData<>(stateDatas), - new TransitionsData(transitionDatas, choicesCopy, junctionsCopy, forks, joins, entrys, exits)); + new TransitionsData(transitionDatas, choicesCopy, junctionsCopy, forks, joins, entrys, exits, historys)); } private void handleRegion(Region region) { @@ -259,6 +261,10 @@ public class UmlModelParser { forks.put(transition.getSource().getName(), list); } list.add(transition.getTarget().getName()); + } else if (((Pseudostate)transition.getSource()).getKind() == PseudostateKind.SHALLOW_HISTORY_LITERAL) { + historys.add(new HistoryData(transition.getSource().getName(), transition.getTarget().getName())); + } else if (((Pseudostate)transition.getSource()).getKind() == PseudostateKind.DEEP_HISTORY_LITERAL) { + historys.add(new HistoryData(transition.getSource().getName(), transition.getTarget().getName())); } } if (transition.getTarget() instanceof Pseudostate) { diff --git a/spring-statemachine-uml/src/test/java/org/springframework/statemachine/uml/UmlStateMachineModelFactoryTests.java b/spring-statemachine-uml/src/test/java/org/springframework/statemachine/uml/UmlStateMachineModelFactoryTests.java index bad357ca..b5771b46 100644 --- a/spring-statemachine-uml/src/test/java/org/springframework/statemachine/uml/UmlStateMachineModelFactoryTests.java +++ b/spring-statemachine-uml/src/test/java/org/springframework/statemachine/uml/UmlStateMachineModelFactoryTests.java @@ -462,6 +462,18 @@ public class UmlStateMachineModelFactoryTests extends AbstractUmlTests { } } + @Test + @SuppressWarnings("unchecked") + public void testSimpleHistoryDefault() { + context.register(Config12.class); + context.refresh(); + StateMachine stateMachine = context.getBean(StateMachine.class); + stateMachine.start(); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S1")); + stateMachine.sendEvent("E4"); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S2", "S22")); + } + @Configuration @EnableStateMachine public static class Config2 extends StateMachineConfigurerAdapter { @@ -681,6 +693,23 @@ public class UmlStateMachineModelFactoryTests extends AbstractUmlTests { } } + @Configuration + @EnableStateMachine + public static class Config12 extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineModelConfigurer model) throws Exception { + model + .withModel() + .factory(modelFactory()); + } + + @Bean + public StateMachineModelFactory modelFactory() { + return new UmlStateMachineModelFactory("classpath:org/springframework/statemachine/uml/simple-history-default.uml"); + } + } + public static class LatchAction implements Action { CountDownLatch latch = new CountDownLatch(1); @Override diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-history-default.di b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-history-default.di new file mode 100644 index 00000000..bf9abab3 --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-history-default.di @@ -0,0 +1,2 @@ + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-history-default.notation b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-history-default.notation new file mode 100644 index 00000000..ed6fa2df --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-history-default.notation @@ -0,0 +1,243 @@ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-history-default.uml b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-history-default.uml new file mode 100644 index 00000000..61f6e66c --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-history-default.uml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +