diff --git a/docs/src/reference/asciidoc/sm.adoc b/docs/src/reference/asciidoc/sm.adoc index a0d7fc25..069ed3c4 100644 --- a/docs/src/reference/asciidoc/sm.adoc +++ b/docs/src/reference/asciidoc/sm.adoc @@ -2229,6 +2229,19 @@ respectively. image::images/papyrus-gs-17.png[scaledwidth="100%"] +[NOTE] +==== +If state is defined as submachine reference and entry/exit points need +to be used, a _ConnectionPointReference_ has to be defined externally +, its entry/exit reference set to point to a correct entry/exit point +within a submachine reference. Only after that it is possible to +target a transition which correctly links from outside into inside of +a submachine reference. With _ConnectionPointReference_ you may need +to find these settings from _Properties_ -> _Advanced_ -> _UML_ -> +_Entry/Exit_. UML Spec allows to define multiple entries and exits but +with a state machine only one is allowed. +==== + === Define History When working with history states three different concepts are in play. UML defines a _Deep History_ and a _Shallow History_. _Default History 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 b10e3342..2f604f9a 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 @@ -25,6 +25,7 @@ import java.util.Map; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.uml2.uml.Activity; +import org.eclipse.uml2.uml.ConnectionPointReference; import org.eclipse.uml2.uml.Constraint; import org.eclipse.uml2.uml.Event; import org.eclipse.uml2.uml.Model; @@ -175,6 +176,24 @@ public class UmlModelParser { } stateDatas.add(stateData); + // add states via entry/exit reference points + for (ConnectionPointReference cpr : state.getConnections()) { + if (cpr.getEntries() != null) { + for (Pseudostate cp : cpr.getEntries()) { + StateData cpStateData = new StateData<>(parent, regionId, cp.getName(), false); + cpStateData.setPseudoStateKind(PseudoStateKind.ENTRY); + stateDatas.add(cpStateData); + } + } + if (cpr.getExits() != null) { + for (Pseudostate cp : cpr.getExits()) { + StateData cpStateData = new StateData<>(parent, regionId, cp.getName(), false); + cpStateData.setPseudoStateKind(PseudoStateKind.EXIT); + stateDatas.add(cpStateData); + } + } + } + // add states via entry/exit points for (Pseudostate cp : state.getConnectionPoints()) { PseudoStateKind kind = null; @@ -242,6 +261,21 @@ public class UmlModelParser { // little unclear for now if link from points to a state should // have trigger? // anyway, we need to add entrys and exits to a model + + if (transition.getSource() instanceof ConnectionPointReference) { + // support ref points if only one is defined as for some + // reason uml can define multiple ones which is not + // realistic with state machines + EList cprentries = ((ConnectionPointReference)transition.getSource()).getEntries(); + if (cprentries != null && cprentries.size() == 1 && cprentries.get(0).getKind() == PseudostateKind.ENTRY_POINT_LITERAL) { + entrys.add(new EntryData(cprentries.get(0).getName(), transition.getTarget().getName())); + } + EList cprexits = ((ConnectionPointReference)transition.getSource()).getExits(); + if (cprexits != null && cprexits.size() == 1 && cprexits.get(0).getKind() == PseudostateKind.EXIT_POINT_LITERAL) { + exits.add(new ExitData(cprexits.get(0).getName(), transition.getTarget().getName())); + } + } + if (transition.getSource() instanceof Pseudostate) { if (((Pseudostate)transition.getSource()).getKind() == PseudostateKind.ENTRY_POINT_LITERAL) { entrys.add(new EntryData(transition.getSource().getName(), transition.getTarget().getName())); @@ -305,9 +339,19 @@ public class UmlModelParser { if (event instanceof SignalEvent) { Signal signal = ((SignalEvent)event).getSignal(); if (signal != null) { - transitionDatas.add(new TransitionData(transition.getSource().getName(), - transition.getTarget().getName(), signal.getName(), UmlUtils.resolveTransitionActions(transition, resolver), - guard, UmlUtils.mapUmlTransitionType(transition))); + // special case for ref point + if (transition.getTarget() instanceof ConnectionPointReference) { + EList cprentries = ((ConnectionPointReference)transition.getTarget()).getEntries(); + if (cprentries != null && cprentries.size() == 1) { + transitionDatas.add(new TransitionData(transition.getSource().getName(), + cprentries.get(0).getName(), signal.getName(), UmlUtils.resolveTransitionActions(transition, resolver), + guard, UmlUtils.mapUmlTransitionType(transition))); + } + } else { + transitionDatas.add(new TransitionData(transition.getSource().getName(), + transition.getTarget().getName(), signal.getName(), UmlUtils.resolveTransitionActions(transition, resolver), + guard, UmlUtils.mapUmlTransitionType(transition))); + } } } else if (event instanceof TimeEvent) { TimeEvent timeEvent = (TimeEvent)event; 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 3b022933..25f79079 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 @@ -873,6 +873,59 @@ public class UmlStateMachineModelFactoryTests extends AbstractUmlTests { assertThat(listener.entered, containsInAnyOrder("S22")); } + @Test + @SuppressWarnings("unchecked") + public void testSimpleConnectionPointRefMachine() throws Exception { + context.register(Config24.class); + context.refresh(); + StateMachine stateMachine = context.getBean(StateMachine.class); + + stateMachine.start(); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S1")); + stateMachine.sendEvent("E3"); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S2", "S22")); + stateMachine.sendEvent("E4"); + assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S4")); + } + + @Test + public void testConnectionPointRef() { + context.refresh(); + Resource model1 = new ClassPathResource("org/springframework/statemachine/uml/simple-connectionpointref.uml"); + UmlStateMachineModelFactory builder = new UmlStateMachineModelFactory(model1); + builder.setBeanFactory(context); + assertThat(model1.exists(), is(true)); + StateMachineModel stateMachineModel = builder.build(); + assertThat(stateMachineModel, notNullValue()); + Collection> stateDatas = stateMachineModel.getStatesData().getStateData(); + assertThat(stateDatas.size(), is(8)); + for (StateData stateData : stateDatas) { + if (stateData.getState().equals("S1")) { + assertThat(stateData.isInitial(), is(true)); + } else if (stateData.getState().equals("S2")) { + assertThat(stateData.isInitial(), is(false)); + } else if (stateData.getState().equals("S21")) { + assertThat(stateData.isInitial(), is(true)); + } else if (stateData.getState().equals("S22")) { + assertThat(stateData.isInitial(), is(false)); + } else if (stateData.getState().equals("S3")) { + assertThat(stateData.isInitial(), is(false)); + } else if (stateData.getState().equals("S4")) { + assertThat(stateData.isInitial(), is(false)); + } else if (stateData.getState().equals("ENTRY")) { + assertThat(stateData.isInitial(), is(false)); + assertThat(stateData.getPseudoStateKind(), is(PseudoStateKind.ENTRY)); + } else if (stateData.getState().equals("EXIT")) { + assertThat(stateData.getPseudoStateKind(), is(PseudoStateKind.EXIT)); + assertThat(stateData.isInitial(), is(false)); + } else { + throw new IllegalArgumentException(); + } + } + assertThat(stateMachineModel.getTransitionsData().getEntrys().size(), is(1)); + assertThat(stateMachineModel.getTransitionsData().getExits().size(), is(1)); + } + @Configuration @EnableStateMachine public static class Config2 extends StateMachineConfigurerAdapter { @@ -1327,6 +1380,24 @@ public class UmlStateMachineModelFactoryTests extends AbstractUmlTests { } } + @Configuration + @EnableStateMachine + public static class Config24 extends StateMachineConfigurerAdapter { + + @Override + public void configure(StateMachineModelConfigurer model) throws Exception { + model + .withModel() + .factory(modelFactory()); + } + + @Bean + public StateMachineModelFactory modelFactory() { + Resource model = new ClassPathResource("org/springframework/statemachine/uml/simple-connectionpointref.uml"); + return new UmlStateMachineModelFactory(model); + } + } + 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-connectionpointref.di b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-connectionpointref.di new file mode 100644 index 00000000..bf9abab3 --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-connectionpointref.di @@ -0,0 +1,2 @@ + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-connectionpointref.notation b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-connectionpointref.notation new file mode 100644 index 00000000..1903ee75 --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-connectionpointref.notation @@ -0,0 +1,341 @@ + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-connectionpointref.uml b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-connectionpointref.uml new file mode 100644 index 00000000..d436fe2f --- /dev/null +++ b/spring-statemachine-uml/src/test/resources/org/springframework/statemachine/uml/simple-connectionpointref.uml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +