Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ A new compartment named _satisfy requirements_ has also been added to `PartDefin
- https://github.com/eclipse-syson/syson/issues/1737[#1737] [diagrams] Add creation tools to InterconnectionCompartmentNode
- https://github.com/eclipse-syson/syson/issues/1395[#1395] Provide a way to duplicate a semantic element ans its representation node in the _General View_ diagram.
- https://github.com/eclipse-syson/syson/issues/1740[#1740] [diagrams] Add _items_ compartment and graphical border node on `PortDefinition` graphical nodes in the _General View_ diagram.
- https://github.com/eclipse-syson/syson/issues/1786[#1786] [export] Implement textual export of `RequirementConstraintMembership`.

== v2025.12.0

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,73 @@ public void checkFlowUsageWithPayload() throws IOException {
this.checker.check(input, expected);
}

@Test
@DisplayName("GIVEN a model with RequirementConstraintMembership, WHEN importing/exporting the file, THEN the exported text file should be the same as the imported one using the full notation.")
public void checkRequirementConstraintMembershipFullNotation() throws IOException {
var input = """
private import SI::kilogram;
private import ScalarValues::*;
part def P1 {
attribute x : Real;
}
requirement r1 {
subject sub : P1;
attribute actualMass :> ISQBase::mass;
attribute maxMass :> ISQBase::mass;
assume constraint NamedRequirementConstraint {
actualMass <= maxMass
}
assume constraint {
sub.x <= 500
}
require constraint {
actualMass >= 500 [kg]
}
}
constraint def C;
constraint c : C;
requirement def R1 {
require constraint c1 :>> c;
}""";
this.checker.check(input, input);
}

@Test
@DisplayName("GIVEN a model with RequirementConstraintMembership having ReferenceSubsetting, WHEN importing/exporting the file, THEN the exported text file should be the same as the imported one"
+ "using the shorthand notation when possible.")
public void checkRequirementConstraintMembershipShorthandNotation() throws IOException {
var input = """
requirement r1;
requirement r3 {
requirement r4 {
requirement r5;
}
require r1;
require r4.r5;
require r4 {
doc /* Some doc */
}
}""";
this.checker.check(input, input);
}

@Test
@DisplayName("GIVEN a model with RequirementConstraintMembership having MetadataUsage, WHEN importing/exporting the file, THEN the exported text file should be the same as the imported one.")
public void checkRequirementConstraintMembershipWithMetadataUsage() throws IOException {
var input = """
private import Metaobjects::SemanticMetadata;
requirement def Goal;
requirement goals : Goal;
metadata def goal :> SemanticMetadata {
:>> baseType = goals meta SysML::Systems::RequirementUsage;
}
#goal requirement r2 {
assume #goal constraint c1;
require #goal constraint c2;
}""";
this.checker.check(input, input);
}

@Test
@DisplayName("GIVEN a model with ForkNode, WHEN importing/exporting the file, THEN the exported text file should be the same as the imported one.")
public void checkForkNode() throws IOException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@
import org.eclipse.syson.sysml.ReferenceUsage;
import org.eclipse.syson.sysml.Relationship;
import org.eclipse.syson.sysml.RenderingUsage;
import org.eclipse.syson.sysml.RequirementConstraintMembership;
import org.eclipse.syson.sysml.RequirementDefinition;
import org.eclipse.syson.sysml.RequirementUsage;
import org.eclipse.syson.sysml.ReturnParameterMembership;
Expand Down Expand Up @@ -925,6 +926,24 @@ public String caseRenderingUsage(RenderingUsage rendering) {
return builder.toString();
}

@Override
public String caseRequirementConstraintMembership(RequirementConstraintMembership requirementConstraintMembership) {
var builder = this.newAppender();
this.appendMembershipPrefix(requirementConstraintMembership, builder);
String kind = switch (requirementConstraintMembership.getKind()) {
case REQUIREMENT -> "require";
case ASSUMPTION -> "assume";
};
builder.appendWithSpaceIfNeeded(kind);
requirementConstraintMembership.getOwnedRelatedElement().stream()
.filter(ConstraintUsage.class::isInstance)
.map(ConstraintUsage.class::cast)
.forEach(constraintUsage -> this.appendRequirementConstraintUsage(builder, constraintUsage));


return builder.toString();
}

@Override
public String caseRequirementDefinition(RequirementDefinition requirement) {
Appender builder = this.newAppender();
Expand Down Expand Up @@ -2461,15 +2480,29 @@ private void appendExtensionKeyword(Appender builder, Type type) {
owningMember.getOwnedRelatedElement().stream()
.filter(MetadataUsage.class::isInstance)
.map(MetadataUsage.class::cast)
.map(MetadataUsage::getMetadataDefinition)
.filter(Objects::nonNull)
.forEach(mDef -> this.appendPrefixMetadataMember(builder, mDef, type));
.forEach(metadataUsage -> this.appendPrefixMetadataMember(builder, metadataUsage, type));
}
}
}

private void appendPrefixMetadataMember(Appender builder, Metaclass def, Type type) {
builder.appendSpaceIfNeeded().append("#").append(this.getDeresolvableName(def, type));
private void appendPrefixMetadataMember(Appender builder, MetadataUsage metadataUsage, Type type) {
Metaclass def = metadataUsage.getMetadataDefinition();
if (def != null) {
builder.appendSpaceIfNeeded().append("#").append(this.getDeresolvableName(def, type));
this.childrenMembershipToSkip.add(metadataUsage.getOwningMembership());
}

}

private void appendRequirementConstraintUsage(Appender builder, ConstraintUsage constraintUsage) {
if (this.useRequirementConstraintUsageShortHandNotation(constraintUsage)) {
this.appendRequirementConstraintUsageShorthandNotation(builder, constraintUsage);
} else {
// Only use the full form :
// UsageExtensionKeyword* 'constraint' ConstraintUsageDeclaration CalculationBody
builder.appendWithSpaceIfNeeded(this.caseConstraintUsage(constraintUsage));
}

}

private void appendSimpleName(Appender appender, Element e) {
Expand Down Expand Up @@ -2706,4 +2739,43 @@ private String appendFeatureRefOrFeatureChain(Feature feature, Element context)
}
return builder.toString();
}

private void appendRequirementConstraintUsageShorthandNotation(Appender builder, ConstraintUsage constraintUsage) {
ReferenceSubsetting ownedReferenceSubsetting = constraintUsage.getOwnedReferenceSubsetting();
if (ownedReferenceSubsetting != null) {
this.appendOwnedReferenceSubsetting(builder, ownedReferenceSubsetting);
}
Appender metadataUsageBuilder = this.newAppender();
this.appendExtensionKeyword(metadataUsageBuilder, constraintUsage);
List<Specialization> ownedSpecialization = constraintUsage.getOwnedSpecialization().stream()
// The owned reference subsetting is already handled previously
.filter(specialization -> specialization != ownedReferenceSubsetting)
.toList();
this.appendFeatureSpecializationPart(builder, constraintUsage, ownedSpecialization, false);
this.appendChildrenContent(builder, constraintUsage, constraintUsage.getOwnedMembership());
}

/**
* Checks if the shorthand notation for a {@link ConstraintUsage} stored in a {@link RequirementConstraintMembership} can be used.
* Shorthand format :"ownedRelationship += OwnedReferenceSubsetting FeatureSpecializationPart? RequirementBody"
* <p>
* Use this form when :
* <ul>
* <li>has one referenceSubsetting</li>
* <li>has no declared name</li>
* <li>has no declared shortName</li>
* <li>has no MetadataUsage</li>
* </ul>
* </p>
*
* @param constraintUsage
* the constraint to test
* @return {@code true} if the shorthand notation can be used
*/
private boolean useRequirementConstraintUsageShortHandNotation(ConstraintUsage constraintUsage) {
return constraintUsage.getDeclaredName() == null
&& constraintUsage.getDeclaredShortName() == null
&& constraintUsage.getOwnedReferenceSubsetting() != null
&& constraintUsage.getNestedMetadata().isEmpty();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.eclipse.syson.sysml.AttributeUsage;
import org.eclipse.syson.sysml.ConcernDefinition;
import org.eclipse.syson.sysml.ConcernUsage;
import org.eclipse.syson.sysml.ConstraintDefinition;
import org.eclipse.syson.sysml.ConstraintUsage;
import org.eclipse.syson.sysml.EnumerationDefinition;
import org.eclipse.syson.sysml.EnumerationUsage;
Expand Down Expand Up @@ -129,6 +130,11 @@ public String caseActionUsage(ActionUsage object) {
return ACTION;
}

@Override
public String caseConstraintDefinition(ConstraintDefinition object) {
return CONSTRAINT_KEYWORD;
}

@Override
public String caseEnumerationUsage(EnumerationUsage object) {
if (object.getOwningDefinition() instanceof EnumerationDefinition && !this.isNullOrEmpty(object.getName())) {
Expand Down
30 changes: 30 additions & 0 deletions doc/content/modules/user-manual/pages/release-notes/2026.1.0.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,36 @@ image::explorer-duplicate-object-dialog.png[Duplicate Object dialog, width=25%,h

image::manage-elements-duplicate-from-diagram.png[Duplicate element from Diagram]

- `RequirementConstraintMembership` are now properly exported in the textual format such as in:

```
private import SI::kilogram;
private import ScalarValues::*;
part def P1 {
attribute x : Real;
}
requirement r1 {
subject sub : P1;
attribute actualMass :> ISQBase::mass;
attribute maxMass :> ISQBase::mass;
assume constraint NamedRequirementConstraint { //Here
actualMass <= maxMass
}
assume constraint { // And here
sub.x <= 500
}
require constraint { // And here
actualMass >= 500 [kg]
}
}
constraint def C;
constraint c : C;
requirement def R1 {
require constraint c1 :>> c; // And here
}
```


== Bug fixes

- Fix an issue that displayed imported libraries at the root of the project if they contained `LibraryPackage` elements and non-`Namespace` elements.
Expand Down
Loading