Skip to content

Commit ed653cc

Browse files
gdanielAxelRICHARD
authored andcommitted
[1090] Fix the import of MultiplicityRange with lower and upper bounds
Bug: #1090 Signed-off-by: Gwendal Daniel <gwendal.daniel@obeosoft.com>
1 parent 3d33a28 commit ed653cc

File tree

6 files changed

+135
-0
lines changed

6 files changed

+135
-0
lines changed

CHANGELOG.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ When creating a new `Perform Action` referencing an existing `Action`, all compa
4040
- https://github.com/eclipse-syson/syson/issues/1300[#1300] [general-view] Fix wrong tool section name found in some compartments' palette.
4141
- https://github.com/eclipse-syson/syson/issues/1303[#1303] [general-view] Fix the name of `Constraint` creation tools inside `Requirement`.
4242
- https://github.com/eclipse-syson/syson/issues/1324[#1324] [details] Fix an issue that prevents ending up with a `null` reference in the reference widget.
43+
- https://github.com/eclipse-syson/syson/issues/1090[#1090] [import] Fix the textual import of `MultiplicityRange` with lower and upper bounds.
44+
The import now correctly creates a `MultiplicityRange` containing `LiteralInteger` elements for integer bounds, and `FeatureReferenceExpression` elements for feature bounds.
4345

4446
=== Improvements
4547

backend/application/syson-application/src/test/java/org/eclipse/syson/application/export/ImportExportTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -737,4 +737,12 @@ public void checkLiteralString() throws IOException {
737737
attribute myAttribute = "value";""";
738738
this.checker.check(input, input);
739739
}
740+
741+
@Test
742+
@DisplayName("Given a model with a PartUsage containing a Multiplicity with LiteralInteger bounds, when importing and exporting the model, then the exported text file should be the same as the imported one")
743+
public void checkMultiplicityRangeWithLiteralIntegerBounds() throws IOException {
744+
var input = """
745+
part myPart [1..2];""";
746+
this.checker.check(input, input);
747+
}
740748
}

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

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import java.io.IOException;
2121
import java.util.List;
22+
import java.util.Objects;
2223
import java.util.Optional;
2324
import java.util.UUID;
2425

@@ -38,6 +39,7 @@
3839
import org.eclipse.syson.AbstractIntegrationTests;
3940
import org.eclipse.syson.services.UtilService;
4041
import org.eclipse.syson.sysml.ActionUsage;
42+
import org.eclipse.syson.sysml.AttributeUsage;
4143
import org.eclipse.syson.sysml.ConjugatedPortDefinition;
4244
import org.eclipse.syson.sysml.DecisionNode;
4345
import org.eclipse.syson.sysml.Expression;
@@ -47,6 +49,7 @@
4749
import org.eclipse.syson.sysml.LiteralInteger;
4850
import org.eclipse.syson.sysml.LiteralString;
4951
import org.eclipse.syson.sysml.Membership;
52+
import org.eclipse.syson.sysml.MultiplicityRange;
5053
import org.eclipse.syson.sysml.OperatorExpression;
5154
import org.eclipse.syson.sysml.PartUsage;
5255
import org.eclipse.syson.sysml.PortDefinition;
@@ -642,6 +645,89 @@ public void checkAttributeUsageLiteralStringValueTest() throws IOException {
642645
}).check(input);
643646
}
644647

648+
@Test
649+
@DisplayName("Given a PartUsage with a Multiplicity with LiteralInteger bounds, when importing the model, then the Multiplicity value is set")
650+
public void checkPartUsageMultiplicityLiteralIntegerBoundsValueTest() throws IOException {
651+
var input = """
652+
part myPart[1..2];
653+
""";
654+
this.checker.checkImportedModel(resource -> {
655+
List<PartUsage> partUsages = EMFUtils.allContainedObjectOfType(resource, PartUsage.class).toList();
656+
assertThat(partUsages).hasSize(1);
657+
PartUsage partUsage = partUsages.get(0);
658+
assertThat(partUsage.getMultiplicity()).isInstanceOf(MultiplicityRange.class);
659+
MultiplicityRange multiplicityRange = (MultiplicityRange) partUsage.getMultiplicity();
660+
assertThat(multiplicityRange.getLowerBound()).isInstanceOf(LiteralInteger.class);
661+
LiteralInteger multiplicityRangerLowerBound = (LiteralInteger) multiplicityRange.getLowerBound();
662+
assertThat(multiplicityRangerLowerBound.getValue()).isEqualTo(1);
663+
assertThat(multiplicityRange.getUpperBound()).isInstanceOf(LiteralInteger.class);
664+
LiteralInteger multiplicityRangeUpperBound = (LiteralInteger) multiplicityRange.getUpperBound();
665+
assertThat(multiplicityRangeUpperBound.getValue()).isEqualTo(2);
666+
}).check(input);
667+
}
668+
669+
@Test
670+
@DisplayName("Given a PartUsage with a Multiplicity with FeatureReferenceExpression bounds, when importing the model, then the Multiplicity value is set")
671+
public void checkPartUsageMultiplicityChainedFeatureReferenceExpressionBoundsTest() throws IOException {
672+
// The result of the bound expression(s) of a MultiplicityRange must be typed by ScalarValues::Integer (see
673+
// KerML 8.3.4.11.2).
674+
var input = """
675+
part bounds {
676+
attribute lower:ScalarValues::Integer = 1;
677+
part upperBounds {
678+
attribute upper:ScalarValues::Integer = 2;
679+
}
680+
}
681+
682+
part myPart[bounds::lower..bounds::upperBounds::upper];
683+
""";
684+
this.checker.checkImportedModel(resource -> {
685+
Optional<PartUsage> optionalPartUsage = EMFUtils.allContainedObjectOfType(resource, PartUsage.class)
686+
.filter(partUsage -> Objects.equals(partUsage.getName(), "myPart"))
687+
.findFirst();
688+
assertThat(optionalPartUsage).isPresent();
689+
assertThat(optionalPartUsage.get().getMultiplicity()).isInstanceOf(MultiplicityRange.class);
690+
MultiplicityRange multiplicityRange = (MultiplicityRange) optionalPartUsage.get().getMultiplicity();
691+
assertThat(multiplicityRange.getLowerBound()).isInstanceOf(FeatureReferenceExpression.class);
692+
FeatureReferenceExpression multiplicityRangeLowerBound = (FeatureReferenceExpression) multiplicityRange.getLowerBound();
693+
assertThat(multiplicityRangeLowerBound.getReferent())
694+
.isInstanceOf(AttributeUsage.class)
695+
.returns("lower", Feature::getName);
696+
assertThat(multiplicityRange.getUpperBound()).isInstanceOf(FeatureReferenceExpression.class);
697+
FeatureReferenceExpression multiplicityRangeUpperBound = (FeatureReferenceExpression) multiplicityRange.getUpperBound();
698+
assertThat(multiplicityRangeUpperBound.getReferent())
699+
.isInstanceOf(AttributeUsage.class)
700+
.returns("upper", Feature::getName);
701+
}).check(input);
702+
}
703+
704+
@Test
705+
@DisplayName("Given a PartUsage with a Multiplicity with top-level FeatureReferenceExpression bounds, when importing the model, then the Multiplicity value is set ")
706+
public void checkPartUsageMultiplicityTopLevelFeatureReferenceExpressionBoundTest() throws IOException {
707+
var input = """
708+
attribute lower:ScalarValues::Integer = 1;
709+
attribute upper:ScalarValues::Integer = 2;
710+
part myPart[lower..upper];
711+
""";
712+
this.checker.checkImportedModel(resource -> {
713+
List<PartUsage> partUsages = EMFUtils.allContainedObjectOfType(resource, PartUsage.class).toList();
714+
assertThat(partUsages).hasSize(1);
715+
PartUsage partUsage = partUsages.get(0);
716+
assertThat(partUsage.getMultiplicity()).isInstanceOf(MultiplicityRange.class);
717+
MultiplicityRange multiplicityRange = (MultiplicityRange) partUsage.getMultiplicity();
718+
assertThat(multiplicityRange.getLowerBound()).isInstanceOf(FeatureReferenceExpression.class);
719+
FeatureReferenceExpression multiplicityRangeLowerBound = (FeatureReferenceExpression) multiplicityRange.getLowerBound();
720+
assertThat(multiplicityRangeLowerBound.getReferent())
721+
.isInstanceOf(AttributeUsage.class)
722+
.returns("lower", Feature::getName);
723+
assertThat(multiplicityRange.getUpperBound()).isInstanceOf(FeatureReferenceExpression.class);
724+
FeatureReferenceExpression multiplicityRangeUpperBound = (FeatureReferenceExpression) multiplicityRange.getUpperBound();
725+
assertThat(multiplicityRangeUpperBound.getReferent())
726+
.isInstanceOf(AttributeUsage.class)
727+
.returns("upper", Feature::getName);
728+
}).check(input);
729+
}
730+
645731
private void assertOperatorExpressionGuard(TransitionUsage t1, String expectedOperator, Class<?> expectedFirstParameterType, Class<?> expectedSecondParameterType) {
646732
EList<Expression> guardExpression1 = t1.getGuardExpression();
647733
assertThat(guardExpression1).hasSize(1);

backend/application/syson-sysml-import/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@
6767
<artifactId>syson-sysml-metamodel</artifactId>
6868
<version>2025.4.5</version>
6969
</dependency>
70+
<dependency>
71+
<groupId>org.eclipse.syson</groupId>
72+
<artifactId>syson-services</artifactId>
73+
<version>2025.4.5</version>
74+
</dependency>
7075
<dependency>
7176
<groupId>org.springframework.boot</groupId>
7277
<artifactId>spring-boot-starter-test</artifactId>

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.util.ArrayList;
2323
import java.util.List;
2424
import java.util.ListIterator;
25+
import java.util.Objects;
2526

2627
import org.eclipse.emf.common.util.EList;
2728
import org.eclipse.emf.ecore.EObject;
@@ -30,6 +31,7 @@
3031
import org.eclipse.sirius.components.emf.services.JSONResourceFactory;
3132
import org.eclipse.sirius.components.representations.Message;
3233
import org.eclipse.sirius.components.representations.MessageLevel;
34+
import org.eclipse.syson.services.DeleteService;
3335
import org.eclipse.syson.sysml.helper.EMFUtils;
3436
import org.eclipse.syson.sysml.parser.AstTreeParser;
3537
import org.eclipse.syson.sysml.parser.ContainmentReferenceHandler;
@@ -161,6 +163,36 @@ private void addInParent(Element parent, Element child) {
161163
private void postResolvingFixingPhase(List<? extends EObject> rootSysmlObjects) {
162164
for (EObject root : rootSysmlObjects) {
163165
this.fixTransitionUsageImplicitSource(root);
166+
this.fixOperatorExpressionUsedAsRanges(root);
167+
}
168+
}
169+
170+
private void fixOperatorExpressionUsedAsRanges(EObject root) {
171+
// Only get the OperatorExpressions used in MultiplicityRange. Based on KerML 8.2.5.8.1 OperatorExpression
172+
// referring to the ".." function can exist, but the specification does not allow them in MultiplicityRange (see
173+
// SysML 8.2.2.6.6 and KerML 8.2.5.11).
174+
List<OperatorExpression> operatorExpressions = EMFUtils.allContainedObjectOfType(root, MultiplicityRange.class)
175+
.flatMap(multiplicityRange -> multiplicityRange.getOwnedMember().stream())
176+
.filter(OperatorExpression.class::isInstance)
177+
.map(OperatorExpression.class::cast)
178+
.filter(operatorExpression -> Objects.equals(operatorExpression.getOperator(), ".."))
179+
.toList();
180+
for (OperatorExpression operatorExpression : operatorExpressions) {
181+
Element owner = operatorExpression.getOwner();
182+
for (Feature parameter : operatorExpression.getParameter()) {
183+
Expression parameterValue = parameter.getValuation().getValue();
184+
// Only LiteralExpressions and FeatureReferenceExpressions can be used in a MultiplicityRange
185+
if (parameterValue instanceof LiteralExpression || parameterValue instanceof FeatureReferenceExpression) {
186+
OwningMembership newOwningMembership = SysmlFactory.eINSTANCE.createOwningMembership();
187+
owner.getOwnedRelationship().add(newOwningMembership);
188+
newOwningMembership.getOwnedRelatedElement().add(parameterValue);
189+
}
190+
}
191+
if (operatorExpression.getOwningMembership() != null) {
192+
new DeleteService().deleteFromModel(operatorExpression.getOwningMembership());
193+
} else {
194+
new DeleteService().deleteFromModel(operatorExpression);
195+
}
164196
}
165197
this.logger.info("Post resolving fixing phase done");
166198
}

doc/content/modules/user-manual/pages/release-notes/2025.6.0.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ They are now named:
3939
* `New Assume constraint`
4040

4141
- In the _Details_ view, fix an issue that prevents opening the candidates on a reference widget.
42+
- Fix an issue in the textual import of `MultiplicityRange` with explicit lower and upper bounds.
43+
The textual import now correctly creates a `MultiplicityRange` containing `LiteralInteger` elements for integer bounds, and `FeatureReferenceExpression` elements for feature bounds.
4244

4345
== New features
4446

0 commit comments

Comments
 (0)