Skip to content

Commit 6e64797

Browse files
committed
Support Connection Point References
- Add support to uml parser to properly detect and use exit/entry points if those are used via connection ref points with submachine references. - Backport #323 - Relates to #307
1 parent 7371f67 commit 6e64797

File tree

6 files changed

+522
-3
lines changed

6 files changed

+522
-3
lines changed

docs/src/reference/asciidoc/sm.adoc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2229,6 +2229,19 @@ respectively.
22292229

22302230
image::images/papyrus-gs-17.png[scaledwidth="100%"]
22312231

2232+
[NOTE]
2233+
====
2234+
If state is defined as submachine reference and entry/exit points need
2235+
to be used, a _ConnectionPointReference_ has to be defined externally
2236+
, its entry/exit reference set to point to a correct entry/exit point
2237+
within a submachine reference. Only after that it is possible to
2238+
target a transition which correctly links from outside into inside of
2239+
a submachine reference. With _ConnectionPointReference_ you may need
2240+
to find these settings from _Properties_ -> _Advanced_ -> _UML_ ->
2241+
_Entry/Exit_. UML Spec allows to define multiple entries and exits but
2242+
with a state machine only one is allowed.
2243+
====
2244+
22322245
=== Define History
22332246
When working with history states three different concepts are in play.
22342247
UML defines a _Deep History_ and a _Shallow History_. _Default History

spring-statemachine-uml/src/main/java/org/springframework/statemachine/uml/support/UmlModelParser.java

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.eclipse.emf.common.util.EList;
2626
import org.eclipse.emf.ecore.util.EcoreUtil;
2727
import org.eclipse.uml2.uml.Activity;
28+
import org.eclipse.uml2.uml.ConnectionPointReference;
2829
import org.eclipse.uml2.uml.Constraint;
2930
import org.eclipse.uml2.uml.Event;
3031
import org.eclipse.uml2.uml.Model;
@@ -175,6 +176,24 @@ private void handleRegion(Region region) {
175176
}
176177
stateDatas.add(stateData);
177178

179+
// add states via entry/exit reference points
180+
for (ConnectionPointReference cpr : state.getConnections()) {
181+
if (cpr.getEntries() != null) {
182+
for (Pseudostate cp : cpr.getEntries()) {
183+
StateData<String, String> cpStateData = new StateData<>(parent, regionId, cp.getName(), false);
184+
cpStateData.setPseudoStateKind(PseudoStateKind.ENTRY);
185+
stateDatas.add(cpStateData);
186+
}
187+
}
188+
if (cpr.getExits() != null) {
189+
for (Pseudostate cp : cpr.getExits()) {
190+
StateData<String, String> cpStateData = new StateData<>(parent, regionId, cp.getName(), false);
191+
cpStateData.setPseudoStateKind(PseudoStateKind.EXIT);
192+
stateDatas.add(cpStateData);
193+
}
194+
}
195+
}
196+
178197
// add states via entry/exit points
179198
for (Pseudostate cp : state.getConnectionPoints()) {
180199
PseudoStateKind kind = null;
@@ -242,6 +261,21 @@ private void handleRegion(Region region) {
242261
// little unclear for now if link from points to a state should
243262
// have trigger?
244263
// anyway, we need to add entrys and exits to a model
264+
265+
if (transition.getSource() instanceof ConnectionPointReference) {
266+
// support ref points if only one is defined as for some
267+
// reason uml can define multiple ones which is not
268+
// realistic with state machines
269+
EList<Pseudostate> cprentries = ((ConnectionPointReference)transition.getSource()).getEntries();
270+
if (cprentries != null && cprentries.size() == 1 && cprentries.get(0).getKind() == PseudostateKind.ENTRY_POINT_LITERAL) {
271+
entrys.add(new EntryData<String, String>(cprentries.get(0).getName(), transition.getTarget().getName()));
272+
}
273+
EList<Pseudostate> cprexits = ((ConnectionPointReference)transition.getSource()).getExits();
274+
if (cprexits != null && cprexits.size() == 1 && cprexits.get(0).getKind() == PseudostateKind.EXIT_POINT_LITERAL) {
275+
exits.add(new ExitData<String, String>(cprexits.get(0).getName(), transition.getTarget().getName()));
276+
}
277+
}
278+
245279
if (transition.getSource() instanceof Pseudostate) {
246280
if (((Pseudostate)transition.getSource()).getKind() == PseudostateKind.ENTRY_POINT_LITERAL) {
247281
entrys.add(new EntryData<String, String>(transition.getSource().getName(), transition.getTarget().getName()));
@@ -305,9 +339,19 @@ private void handleRegion(Region region) {
305339
if (event instanceof SignalEvent) {
306340
Signal signal = ((SignalEvent)event).getSignal();
307341
if (signal != null) {
308-
transitionDatas.add(new TransitionData<String, String>(transition.getSource().getName(),
309-
transition.getTarget().getName(), signal.getName(), UmlUtils.resolveTransitionActions(transition, resolver),
310-
guard, UmlUtils.mapUmlTransitionType(transition)));
342+
// special case for ref point
343+
if (transition.getTarget() instanceof ConnectionPointReference) {
344+
EList<Pseudostate> cprentries = ((ConnectionPointReference)transition.getTarget()).getEntries();
345+
if (cprentries != null && cprentries.size() == 1) {
346+
transitionDatas.add(new TransitionData<String, String>(transition.getSource().getName(),
347+
cprentries.get(0).getName(), signal.getName(), UmlUtils.resolveTransitionActions(transition, resolver),
348+
guard, UmlUtils.mapUmlTransitionType(transition)));
349+
}
350+
} else {
351+
transitionDatas.add(new TransitionData<String, String>(transition.getSource().getName(),
352+
transition.getTarget().getName(), signal.getName(), UmlUtils.resolveTransitionActions(transition, resolver),
353+
guard, UmlUtils.mapUmlTransitionType(transition)));
354+
}
311355
}
312356
} else if (event instanceof TimeEvent) {
313357
TimeEvent timeEvent = (TimeEvent)event;

spring-statemachine-uml/src/test/java/org/springframework/statemachine/uml/UmlStateMachineModelFactoryTests.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -873,6 +873,59 @@ public void testSimpleLocaltransitionLocalToNonInitialSuperDoesNotEntryExitToPar
873873
assertThat(listener.entered, containsInAnyOrder("S22"));
874874
}
875875

876+
@Test
877+
@SuppressWarnings("unchecked")
878+
public void testSimpleConnectionPointRefMachine() throws Exception {
879+
context.register(Config24.class);
880+
context.refresh();
881+
StateMachine<String, String> stateMachine = context.getBean(StateMachine.class);
882+
883+
stateMachine.start();
884+
assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S1"));
885+
stateMachine.sendEvent("E3");
886+
assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S2", "S22"));
887+
stateMachine.sendEvent("E4");
888+
assertThat(stateMachine.getState().getIds(), containsInAnyOrder("S4"));
889+
}
890+
891+
@Test
892+
public void testConnectionPointRef() {
893+
context.refresh();
894+
Resource model1 = new ClassPathResource("org/springframework/statemachine/uml/simple-connectionpointref.uml");
895+
UmlStateMachineModelFactory builder = new UmlStateMachineModelFactory(model1);
896+
builder.setBeanFactory(context);
897+
assertThat(model1.exists(), is(true));
898+
StateMachineModel<String, String> stateMachineModel = builder.build();
899+
assertThat(stateMachineModel, notNullValue());
900+
Collection<StateData<String, String>> stateDatas = stateMachineModel.getStatesData().getStateData();
901+
assertThat(stateDatas.size(), is(8));
902+
for (StateData<String, String> stateData : stateDatas) {
903+
if (stateData.getState().equals("S1")) {
904+
assertThat(stateData.isInitial(), is(true));
905+
} else if (stateData.getState().equals("S2")) {
906+
assertThat(stateData.isInitial(), is(false));
907+
} else if (stateData.getState().equals("S21")) {
908+
assertThat(stateData.isInitial(), is(true));
909+
} else if (stateData.getState().equals("S22")) {
910+
assertThat(stateData.isInitial(), is(false));
911+
} else if (stateData.getState().equals("S3")) {
912+
assertThat(stateData.isInitial(), is(false));
913+
} else if (stateData.getState().equals("S4")) {
914+
assertThat(stateData.isInitial(), is(false));
915+
} else if (stateData.getState().equals("ENTRY")) {
916+
assertThat(stateData.isInitial(), is(false));
917+
assertThat(stateData.getPseudoStateKind(), is(PseudoStateKind.ENTRY));
918+
} else if (stateData.getState().equals("EXIT")) {
919+
assertThat(stateData.getPseudoStateKind(), is(PseudoStateKind.EXIT));
920+
assertThat(stateData.isInitial(), is(false));
921+
} else {
922+
throw new IllegalArgumentException();
923+
}
924+
}
925+
assertThat(stateMachineModel.getTransitionsData().getEntrys().size(), is(1));
926+
assertThat(stateMachineModel.getTransitionsData().getExits().size(), is(1));
927+
}
928+
876929
@Configuration
877930
@EnableStateMachine
878931
public static class Config2 extends StateMachineConfigurerAdapter<String, String> {
@@ -1327,6 +1380,24 @@ public StateMachineModelFactory<String, String> modelFactory() {
13271380
}
13281381
}
13291382

1383+
@Configuration
1384+
@EnableStateMachine
1385+
public static class Config24 extends StateMachineConfigurerAdapter<String, String> {
1386+
1387+
@Override
1388+
public void configure(StateMachineModelConfigurer<String, String> model) throws Exception {
1389+
model
1390+
.withModel()
1391+
.factory(modelFactory());
1392+
}
1393+
1394+
@Bean
1395+
public StateMachineModelFactory<String, String> modelFactory() {
1396+
Resource model = new ClassPathResource("org/springframework/statemachine/uml/simple-connectionpointref.uml");
1397+
return new UmlStateMachineModelFactory(model);
1398+
}
1399+
}
1400+
13301401
public static class LatchAction implements Action<String, String> {
13311402
CountDownLatch latch = new CountDownLatch(1);
13321403
@Override
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<xmi:XMI xmi:version="2.0" xmlns:xmi="http://www.omg.org/XMI"/>

0 commit comments

Comments
 (0)