Skip to content

Commit 40e9b4e

Browse files
adaussyAxelRICHARD
authored andcommitted
[1048] Improve TransitionUsage textual import with implicit source
Bug: #1048 Signed-off-by: Arthur Daussy <arthur.daussy@obeo.fr>
1 parent 75bbe65 commit 40e9b4e

File tree

10 files changed

+255
-111
lines changed

10 files changed

+255
-111
lines changed

CHANGELOG.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
- https://github.com/eclipse-syson/syson/issues/1083[#1083] [metamodel] Fix an issue where resolving against "unrestricted" name did not work
2222
- https://github.com/eclipse-syson/syson/issues/1075[#1075] [import] Fix a ClassCastException thrown while importing a model with a name conflict.
2323
- https://github.com/eclipse-syson/syson/issues/1091[#1091] [export] Fix `FeatureReferenceExpression` export for elements that should be exported as qualified names.
24-
Exporting a SysML model containing a `FeatureReferenceExpression` now correctly procudes qualified names where it should.
24+
Exporting a SysML model containing a `FeatureReferenceExpression` now correctly produces qualified names where it should.
2525

2626
=== Improvements
2727

@@ -39,6 +39,7 @@ A new dra & drop tool is available on the diagram, allowing moving `Satisfy Requ
3939
- https://github.com/eclipse-syson/syson/issues/1042[#1042] [import] Improve textual import of `SuccessionAsUsage` which define a new target action directly after the 'then' keyword.
4040
- https://github.com/eclipse-syson/syson/issues/1045[#1045] [export] Improve textual export by properly handle named `SuccessionAsUsage`.
4141
- https://github.com/eclipse-syson/syson/issues/1080[#1080] [import] Improve textual import and resolution against `ConjugatedPorts`.
42+
- https://github.com/eclipse-syson/syson/issues/1048[#1048] [import] Improve textual import of `TransitionUsage` by handling _implicit_ source property.
4243

4344
=== New features
4445

backend/application/syson-application/src/test/java/org/eclipse/syson/application/imports/ImportSysMLModelTest.java

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.eclipse.syson.AbstractIntegrationTests;
2626
import org.eclipse.syson.application.configuration.SysMLEditingContextProcessor;
2727
import org.eclipse.syson.services.UtilService;
28+
import org.eclipse.syson.sysml.ActionUsage;
2829
import org.eclipse.syson.sysml.ConjugatedPortDefinition;
2930
import org.eclipse.syson.sysml.Feature;
3031
import org.eclipse.syson.sysml.Membership;
@@ -34,7 +35,9 @@
3435
import org.eclipse.syson.sysml.Redefinition;
3536
import org.eclipse.syson.sysml.ReferenceUsage;
3637
import org.eclipse.syson.sysml.Specialization;
38+
import org.eclipse.syson.sysml.Succession;
3739
import org.eclipse.syson.sysml.SuccessionAsUsage;
40+
import org.eclipse.syson.sysml.TransitionUsage;
3841
import org.eclipse.syson.sysml.Type;
3942
import org.eclipse.syson.sysml.helper.EMFUtils;
4043
import org.eclipse.syson.sysml.upload.SysMLExternalResourceLoaderService;
@@ -71,7 +74,7 @@ public void setUp() {
7174
}
7275

7376
@Test
74-
@DisplayName("Given a model with duplicated names, when importing the model, then the resolution of name shoud match the closest matching element")
77+
@DisplayName("Given a model with duplicated names, when importing the model, then the resolution of name should match the closest matching element")
7578
public void checkNameResolutionProcessWithDuplicatedName() throws IOException {
7679
var input = """
7780
package p1 {
@@ -99,7 +102,7 @@ public void checkNameResolutionProcessWithDuplicatedName() throws IOException {
99102
}
100103

101104
@Test
102-
@DisplayName("Given a model with a Redefintion, when importing the model, then the resolution of name should special Redefinition rules")
105+
@DisplayName("Given a model with a Redefintion, when importing the model, then the resolution of name should specialize Redefinition rules")
103106
public void checkNameResolutionProcessInRedifinition() throws IOException {
104107
var input = """
105108
package p1 {
@@ -129,7 +132,7 @@ public void checkNameResolutionProcessInRedifinition() throws IOException {
129132
}
130133

131134
@Test
132-
@DisplayName("Given a model with a reference to an invalid type, when importing the model then the resolution of name shoud not set a reference with an incompatible target")
135+
@DisplayName("Given a model with a reference to an invalid type, when importing the model, then the resolution of name shoud not set a reference with an incompatible target")
133136
public void checkNameResolutionProcessWithInvalidTargetName() throws IOException {
134137
var input = """
135138
package p1 {
@@ -156,7 +159,78 @@ public void checkNameResolutionProcessWithInvalidTargetName() throws IOException
156159
}
157160

158161
@Test
159-
@DisplayName("Given a model with PortDefinitions, when importing the model, then a conjugated port is create for each conjugated ports")
162+
@DisplayName("Given a model with a TransitionUsage, when importing the model, then a TransitionUsage should be created.")
163+
public void checkSimpleTransitionUsage() throws IOException {
164+
var input = """
165+
action def A1 {
166+
private import ScalarValues::Integer;
167+
action a1 ;
168+
action a2 ;
169+
attribute x : Integer;
170+
succession S first a1 if x == 1 then a2;
171+
}""";
172+
173+
this.checker.checkImportedModel(resource -> {
174+
TransitionUsage transitionUsage = EMFUtils.allContainedObjectOfType(resource, TransitionUsage.class)
175+
.filter(t -> "S".equals(t.getDeclaredName()))
176+
.findFirst().get();
177+
178+
ActionUsage source = transitionUsage.getSource();
179+
assertThat(source).isNotNull().matches(s -> "a1".equals(s.getName()));
180+
ActionUsage target = transitionUsage.getTarget();
181+
assertThat(target).isNotNull().matches(t -> "a2".equals(t.getName()));
182+
183+
Succession succession = transitionUsage.getSuccession();
184+
assertThat(succession).isNotNull();
185+
186+
}).check(input);
187+
}
188+
189+
@Test
190+
@DisplayName("Given a model with TransitionUsage with an implicit source, when importing the model, then the implicit source should be correctly resolved.")
191+
public void checkTransitionWithImplicitSource() throws IOException {
192+
var input = """
193+
action def A1 {
194+
private import ScalarValues::Integer;
195+
attribute x : Integer;
196+
action a1;
197+
action a2;
198+
action a3;
199+
decide d;
200+
if x == 1 then a1;
201+
if x == 2 then a2;
202+
else a3;
203+
}""";
204+
205+
this.checker.checkImportedModel(resource -> {
206+
List<TransitionUsage> transitionUsages = EMFUtils.allContainedObjectOfType(resource, TransitionUsage.class)
207+
.toList();
208+
209+
assertThat(transitionUsages).hasSize(3);
210+
211+
TransitionUsage t1 = transitionUsages.get(0);
212+
ActionUsage source1 = t1.getSource();
213+
assertThat(source1).isNotNull().matches(s -> "d".equals(s.getName()));
214+
ActionUsage target1 = t1.getTarget();
215+
assertThat(target1).isNotNull().matches(t -> "a1".equals(t.getName()));
216+
217+
TransitionUsage t2 = transitionUsages.get(1);
218+
ActionUsage source2 = t2.getSource();
219+
assertThat(source2).isNotNull().matches(s -> "d".equals(s.getName()));
220+
ActionUsage target2 = t2.getTarget();
221+
assertThat(target2).isNotNull().matches(t -> "a2".equals(t.getName()));
222+
223+
TransitionUsage t3 = transitionUsages.get(2);
224+
ActionUsage source3 = t3.getSource();
225+
assertThat(source3).isNotNull().matches(s -> "d".equals(s.getName()));
226+
ActionUsage target3 = t3.getTarget();
227+
assertThat(target3).isNotNull().matches(t -> "a3".equals(t.getName()));
228+
229+
}).check(input);
230+
}
231+
232+
@Test
233+
@DisplayName("Given a model with PortDefinitions, when importing the model, then a conjugated port is created for each conjugated ports")
160234
public void checkConjugatedPortCreation() throws IOException {
161235
var input = """
162236
package Conjugated {
@@ -172,7 +246,6 @@ public void checkConjugatedPortCreation() throws IOException {
172246
}
173247
}""";
174248

175-
176249
this.checker.checkImportedModel(resource -> {
177250
PortDefinition tempPort = EMFUtils.allContainedObjectOfType(resource, PortDefinition.class)
178251
.filter(pod -> "TempPort".equals(pod.getDeclaredName()))

backend/application/syson-application/src/test/resources/part_changes.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@
8686
"unioningType": [],
8787
"chainingFeature": [],
8888
"endOwningType": null,
89-
"featureTarget": null,
89+
"featureTarget": {
90+
"@id":"a4f51a38-bfeb-4e0d-a870-55f8fe90405e"
91+
},
9092
"featuringType": [],
9193
"ownedFeatureChaining": [],
9294
"ownedFeatureInverting": [],

backend/application/syson-application/src/test/resources/simple_project.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,9 @@
283283
"unioningType": [],
284284
"chainingFeature": [],
285285
"endOwningType": null,
286-
"featureTarget": null,
286+
"featureTarget": {
287+
"@id": "a4f51a38-bfeb-4e0d-a870-55f8fe90405e"
288+
},
287289
"featuringType": [],
288290
"ownedFeatureChaining": [],
289291
"ownedFeatureInverting": [],

backend/application/syson-application/src/test/resources/simple_project_changes.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,9 @@
312312
"unioningType": [],
313313
"chainingFeature": [],
314314
"endOwningType": null,
315-
"featureTarget": null,
315+
"featureTarget": {
316+
"@id":"a4f51a38-bfeb-4e0d-a870-55f8fe90405e"
317+
},
316318
"featuringType": [],
317319
"ownedFeatureChaining": [],
318320
"ownedFeatureInverting": [],

backend/application/syson-sysml-import/src/main/java/org/eclipse/syson/sysml/ASTTransformer.java

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,11 +83,23 @@ public Resource convertResource(final InputStream input, final ResourceSet resou
8383
this.astTreeParser.resolveAllReference(proxiedReferences);
8484
this.logger.info("End of references resolving");
8585

86+
this.postResolvingFixingPhase(rootSysmlObjects);
8687
}
8788
}
8889
return result;
8990
}
9091

92+
private void postResolvingFixingPhase(List<EObject> rootSysmlObjects) {
93+
for (EObject root : rootSysmlObjects) {
94+
this.fixTransitionUsageImplicitSource(root);
95+
}
96+
}
97+
98+
private void fixTransitionUsageImplicitSource(EObject root) {
99+
List<TransitionUsage> transitionUsages = EMFUtils.allContainedObjectOfType(root, TransitionUsage.class).toList();
100+
this.fixImplicitTransitionSourceFeature(transitionUsages);
101+
}
102+
91103
private void preResolvingFixingPhase(List<EObject> rootSysmlObjects) {
92104
for (EObject root : rootSysmlObjects) {
93105
this.fixSuccessionUsageImplicitSource(root);
@@ -171,7 +183,7 @@ private void fixImplicitSourceFeature(List<SuccessionAsUsage> succesionAsUsage)
171183
ReferenceUsage refUsage = SysmlFactory.eINSTANCE.createReferenceUsage();
172184
EList<Element> ownedRelatedElements = owningFeatureMembershit.getOwnedRelatedElement();
173185

174-
Membership previousMembershipFeature = this.computePreviousFeatureMembership(suc);
186+
Membership previousMembershipFeature = this.computePreviousFeatureMembership(suc, m -> m.eClass() == SysmlPackage.eINSTANCE.getMembership());
175187
if (previousMembershipFeature != null) {
176188
// For implicit source that targets an element of the standard library, we need to keep a
177189
// "virtual link" to the previous feature membership to identify the source of SuccessionAsUsage
@@ -186,16 +198,39 @@ private void fixImplicitSourceFeature(List<SuccessionAsUsage> succesionAsUsage)
186198
});
187199
}
188200

189-
private Membership computePreviousFeatureMembership(SuccessionAsUsage successionAsUsage) {
190-
Type owningType = successionAsUsage.getOwningType();
201+
private void fixImplicitTransitionSourceFeature(List<TransitionUsage> transitionUsages) {
202+
transitionUsages.stream().filter(this::hasImplicitSourceFeature)
203+
.forEach(transition -> {
204+
// The specification define the computation of the sourceFeature of TransitionUsage by:
205+
// Return the Feature to be used as the source of the succession of this TransitionUsage, which is
206+
// the first ownedMember of the TransitionUsage that is a Feature not owned via a FeatureMembership
207+
// whose featureTarget is an ActionUsage.
208+
// The current implementation of SysIDE providing the AST does create such element. This fix aims to
209+
// provide a workaround.
210+
Membership previousFeature = this.computePreviousFeatureMembership(transition, m -> this.isValidPreviousFeature(m));
211+
if (previousFeature != null && previousFeature.getMemberElement() instanceof ActionUsage actionUsage) {
212+
Membership membership = SysmlFactory.eINSTANCE.createMembership();
213+
membership.setMemberElement(actionUsage);
214+
transition.getOwnedRelationship().add(0, membership);
215+
}
216+
});
217+
}
218+
219+
private boolean isValidPreviousFeature(Membership m) {
220+
Element memberElement = m.getMemberElement();
221+
return memberElement instanceof ActionUsage && !(memberElement instanceof TransitionUsage);
222+
}
223+
224+
private Membership computePreviousFeatureMembership(Feature testedFeature, java.util.function.Predicate<Membership> filter) {
225+
Type owningType = testedFeature.getOwningType();
191226
if (owningType != null) {
192227
EList<Membership> ownedMemberships = owningType.getOwnedMembership();
193-
int index = ownedMemberships.indexOf(successionAsUsage.getOwningMembership());
228+
int index = ownedMemberships.indexOf(testedFeature.getOwningMembership());
194229
if (index > 0) {
195230
ListIterator<Membership> iterator = ownedMemberships.subList(0, index).listIterator(index);
196231
while (iterator.hasPrevious()) {
197232
Membership previous = iterator.previous();
198-
if (previous.getMemberElement() instanceof Feature) {
233+
if (previous.getMemberElement() instanceof Feature && (filter == null || filter.test(previous))) {
199234
return previous;
200235
}
201236
}
@@ -233,6 +268,11 @@ private boolean hasImplicitSourceFeature(SuccessionAsUsage successionUsage) {
233268
return false;
234269
}
235270

271+
private boolean hasImplicitSourceFeature(TransitionUsage transitionUsage) {
272+
ActionUsage source = transitionUsage.getSource();
273+
return source == null;
274+
}
275+
236276
private boolean hasImplicitTargetFeature(SuccessionAsUsage successionUsage) {
237277
EList<Feature> ends = successionUsage.getConnectorEnd();
238278
if (ends.size() > 1) {

0 commit comments

Comments
 (0)