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 02938612..9952c08d 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 @@ -67,7 +67,7 @@ import org.springframework.util.StringUtils; */ public class UmlModelParser { - private final static String LANGUAGE_BEAN = "bean"; + public final static String LANGUAGE_BEAN = "bean"; private final Model model; private final StateMachineComponentResolver resolver; private final Collection> stateDatas = new ArrayList>(); @@ -133,8 +133,13 @@ public class UmlModelParser { if (state.getOwner() instanceof Region) { regionId = ((Region)state.getOwner()).getName(); } + boolean isInitialState = UmlUtils.isInitialState(state); StateData stateData = handleActions( - new StateData(parent, regionId, state.getName(), UmlUtils.isInitialState(state)), state); + new StateData(parent, regionId, state.getName(), isInitialState), state); + if (isInitialState) { + // set possible initial transition + stateData.setInitialAction(resolveInitialTransitionAction(state)); + } stateData.setDeferred(UmlUtils.resolveDererredEvents(state)); if (UmlUtils.isFinalState(state)) { stateData.setEnd(true); @@ -272,8 +277,8 @@ public class UmlModelParser { Signal signal = ((SignalEvent)event).getSignal(); if (signal != null) { transitionDatas.add(new TransitionData(transition.getSource().getName(), - transition.getTarget().getName(), signal.getName(), resolveTransitionActions(transition), guard, - UmlUtils.mapUmlTransitionType(transition))); + transition.getTarget().getName(), signal.getName(), UmlUtils.resolveTransitionActions(transition, resolver), + guard, UmlUtils.mapUmlTransitionType(transition))); } } else if (event instanceof TimeEvent) { TimeEvent timeEvent = (TimeEvent)event; @@ -284,8 +289,8 @@ public class UmlModelParser { count = 1; } transitionDatas.add(new TransitionData(transition.getSource().getName(), - transition.getTarget().getName(), period, count, resolveTransitionActions(transition), guard, - UmlUtils.mapUmlTransitionType(transition))); + transition.getTarget().getName(), period, count, UmlUtils.resolveTransitionActions(transition, resolver), + guard, UmlUtils.mapUmlTransitionType(transition))); } } } @@ -293,7 +298,8 @@ public class UmlModelParser { // create anonymous transition if needed if (shouldCreateAnonymousTransition(transition)) { transitionDatas.add(new TransitionData(transition.getSource().getName(), transition.getTarget().getName(), - null, resolveTransitionActions(transition), resolveGuard(transition), UmlUtils.mapUmlTransitionType(transition))); + null, UmlUtils.resolveTransitionActions(transition, resolver), resolveGuard(transition), + UmlUtils.mapUmlTransitionType(transition))); } } } @@ -319,16 +325,13 @@ public class UmlModelParser { } } - private Collection> resolveTransitionActions(Transition transition) { - ArrayList> actions = new ArrayList>(); - if (transition.getEffect() instanceof OpaqueBehavior) { - String beanId = UmlUtils.resolveBodyByLanguage(LANGUAGE_BEAN, (OpaqueBehavior)transition.getEffect()); - Action bean = resolver.resolveAction(beanId); - if (bean != null) { - actions.add(bean); - } + private Action resolveInitialTransitionAction(State state) { + Transition transition = UmlUtils.resolveInitialTransition(state); + if (transition != null) { + return UmlUtils.resolveTransitionAction(transition, resolver); + } else { + return null; } - return actions; } private boolean shouldCreateAnonymousTransition(Transition transition) { diff --git a/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/support/UmlUtils.java b/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/support/UmlUtils.java index f2d4bc36..d285f8e4 100644 --- a/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/support/UmlUtils.java +++ b/spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/support/UmlUtils.java @@ -27,6 +27,7 @@ import org.eclipse.uml2.uml.BodyOwner; import org.eclipse.uml2.uml.Event; import org.eclipse.uml2.uml.FinalState; import org.eclipse.uml2.uml.Model; +import org.eclipse.uml2.uml.OpaqueBehavior; import org.eclipse.uml2.uml.Pseudostate; import org.eclipse.uml2.uml.PseudostateKind; import org.eclipse.uml2.uml.Signal; @@ -36,6 +37,8 @@ import org.eclipse.uml2.uml.Transition; import org.eclipse.uml2.uml.Trigger; import org.eclipse.uml2.uml.UMLPackage; import org.eclipse.uml2.uml.resource.UMLResource; +import org.springframework.statemachine.action.Action; +import org.springframework.statemachine.config.model.StateMachineComponentResolver; import org.springframework.statemachine.transition.TransitionKind; /** @@ -70,14 +73,62 @@ public abstract class UmlUtils { * @return true, if is initial state */ public static boolean isInitialState(State state) { + return resolveInitialTransition(state) != null; + } + + /** + * Resolve initial transition from a {@link State} if it + * exists, otherwise null is returned. + * + * @param state the state + * @return the transition + */ + public static Transition resolveInitialTransition(State state) { for (Transition t : state.getIncomings()) { if (t.getSource() instanceof Pseudostate) { if (((Pseudostate)t.getSource()).getKind() == PseudostateKind.INITIAL_LITERAL) { - return true; + return t; } } } - return false; + return null; + } + + /** + * Resolve transition actions. + * + * @param transition the transition + * @param resolver the state machine component resolver + * @return the collection of actions + */ + public static Collection> resolveTransitionActions(Transition transition, + StateMachineComponentResolver resolver) { + ArrayList> actions = new ArrayList>(); + Action action = resolveTransitionAction(transition, resolver); + if (action != null) { + actions.add(action); + } + return actions; + } + + /** + * Resolve transition action or null if no action was found. + * + * @param transition the transition + * @param resolver the state machine component resolver + * @return the action + */ + public static Action resolveTransitionAction(Transition transition, + StateMachineComponentResolver resolver) { + Action action = null; + if (transition.getEffect() instanceof OpaqueBehavior) { + String beanId = UmlUtils.resolveBodyByLanguage(UmlModelParser.LANGUAGE_BEAN, (OpaqueBehavior)transition.getEffect()); + Action bean = resolver.resolveAction(beanId); + if (bean != null) { + action = bean; + } + } + return action; } /** 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 62b622dd..5946a95a 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 @@ -526,6 +526,18 @@ public class UmlStateMachineModelFactoryTests extends AbstractUmlTests { assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S3")); } + @Test + @SuppressWarnings("unchecked") + public void testInitialActions() throws Exception { + context.register(Config15.class); + context.refresh(); + StateMachine stateMachine = context.getBean(StateMachine.class); + LatchAction initialAction = context.getBean("initialAction", LatchAction.class); + stateMachine.start(); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S1")); + assertThat(initialAction.latch.await(1, TimeUnit.SECONDS), is(true)); + } + @Configuration @EnableStateMachine public static class Config2 extends StateMachineConfigurerAdapter { @@ -811,6 +823,28 @@ public class UmlStateMachineModelFactoryTests extends AbstractUmlTests { } } + @Configuration + @EnableStateMachine + public static class Config15 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/initial-actions.uml"); + } + + @Bean + public LatchAction initialAction() { + return new LatchAction(); + } + } + 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/initial-actions.di b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/initial-actions.di new file mode 100644 index 00000000..bf9abab3 --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/initial-actions.di @@ -0,0 +1,2 @@ + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/initial-actions.notation b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/initial-actions.notation new file mode 100644 index 00000000..a0767bd5 --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/initial-actions.notation @@ -0,0 +1,97 @@ + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/initial-actions.uml b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/initial-actions.uml new file mode 100644 index 00000000..739052a6 --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/initial-actions.uml @@ -0,0 +1,21 @@ + + + + + + + + + + bean + initialAction + + + + + + + + + +