From c4f1dcc16b24f5975864e9f58d25abb59e4fdfdf Mon Sep 17 00:00:00 2001 From: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Date: Fri, 12 Jun 2026 06:03:24 -0700 Subject: [PATCH] fix: connection interface end names dropped on textual SysML export Fixes #2264 Signed-off-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> --- .../textual/SysMLElementSerializerTest.java | 99 +++++++++++++++++++ .../textual/SysMLElementSerializer.java | 20 ++-- 2 files changed, 110 insertions(+), 9 deletions(-) diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/sysml/textual/SysMLElementSerializerTest.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/sysml/textual/SysMLElementSerializerTest.java index c0b45d91a..83877403c 100644 --- a/backend/application/syson-application/src/test/java/org/eclipse/syson/sysml/textual/SysMLElementSerializerTest.java +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/sysml/textual/SysMLElementSerializerTest.java @@ -35,6 +35,7 @@ import org.eclipse.syson.sysml.Definition; import org.eclipse.syson.sysml.Documentation; import org.eclipse.syson.sysml.Element; +import org.eclipse.syson.sysml.EndFeatureMembership; import org.eclipse.syson.sysml.EnumerationDefinition; import org.eclipse.syson.sysml.EnumerationUsage; import org.eclipse.syson.sysml.Feature; @@ -43,7 +44,10 @@ import org.eclipse.syson.sysml.FeatureReferenceExpression; import org.eclipse.syson.sysml.FeatureTyping; import org.eclipse.syson.sysml.FeatureValue; +import org.eclipse.syson.sysml.FlowEnd; +import org.eclipse.syson.sysml.FlowUsage; import org.eclipse.syson.sysml.InterfaceDefinition; +import org.eclipse.syson.sysml.InterfaceUsage; import org.eclipse.syson.sysml.ItemDefinition; import org.eclipse.syson.sysml.LibraryPackage; import org.eclipse.syson.sysml.LiteralBoolean; @@ -889,6 +893,42 @@ private void assertTextualFormEquals(String expected, Element elementToTest) { assertEquals(expected, content); } + private EndFeatureMembership createConnectionEndFeatureMembership(Feature referencedFeature) { + EndFeatureMembership endFeatureMembership = this.fact.createEndFeatureMembership(); + PortUsage endFeature = this.fact.createPortUsage(); + endFeature.setIsEnd(true); + endFeatureMembership.getOwnedRelatedElement().add(endFeature); + endFeatureMembership.setMemberElement(endFeature); + this.builder.addReferenceSubsetting(endFeature, referencedFeature); + return endFeatureMembership; + } + + private EndFeatureMembership createUnresolvedConnectionEndFeatureMembership() { + EndFeatureMembership endFeatureMembership = this.fact.createEndFeatureMembership(); + Feature endFeature = this.fact.createFeature(); + endFeature.setIsEnd(true); + endFeatureMembership.getOwnedRelatedElement().add(endFeature); + endFeatureMembership.setMemberElement(endFeature); + return endFeatureMembership; + } + + private EndFeatureMembership createFlowEndFeatureMembership(Feature referencedFeature, String ownedFeatureName) { + EndFeatureMembership endFeatureMembership = this.fact.createEndFeatureMembership(); + FlowEnd flowEnd = this.fact.createFlowEnd(); + flowEnd.setIsEnd(true); + endFeatureMembership.getOwnedRelatedElement().add(flowEnd); + endFeatureMembership.setMemberElement(flowEnd); + this.builder.addReferenceSubsetting(flowEnd, referencedFeature); + + FeatureMembership featureMembership = this.fact.createFeatureMembership(); + flowEnd.getOwnedRelationship().add(featureMembership); + ReferenceUsage ownedFeature = this.fact.createReferenceUsage(); + ownedFeature.setDeclaredName(ownedFeatureName); + featureMembership.getOwnedRelatedElement().add(ownedFeature); + featureMembership.setMemberElement(ownedFeature); + return endFeatureMembership; + } + @Test public void attributeUsage() { AttributeUsage attributeUsage = this.builder.createWithName(AttributeUsage.class, ATTRIBUTE1); @@ -1592,6 +1632,65 @@ public void successionUsageWithFeatureChaining() { } + @Test + public void interfaceUsageWithPortEnds() { + PartUsage rootPart = this.builder.createWithName(PartUsage.class, "part1"); + + PartUsage heater = this.builder.createInWithName(PartUsage.class, rootPart, "heater"); + PortUsage socket = this.builder.createInWithName(PortUsage.class, heater, "socket"); + PortUsage outlet = this.builder.createInWithName(PortUsage.class, rootPart, "outlet"); + + Feature source = this.builder.createFeatureChaining(heater, socket); + InterfaceUsage interfaceUsage = this.builder.createInWithName(InterfaceUsage.class, rootPart, "interface1"); + interfaceUsage.getOwnedRelationship().add(this.createConnectionEndFeatureMembership(source)); + interfaceUsage.getOwnedRelationship().add(this.createConnectionEndFeatureMembership(outlet)); + + this.assertTextualFormEquals("connection interface1 connect heater.socket to outlet;", interfaceUsage); + } + + @Test + public void interfaceUsageWithNestedPortEnd() { + PartUsage rootPart = this.builder.createWithName(PartUsage.class, "part1"); + + PartUsage livingRoom = this.builder.createInWithName(PartUsage.class, rootPart, "livingRoom"); + PartUsage heater = this.builder.createInWithName(PartUsage.class, livingRoom, "heater"); + PortUsage socket = this.builder.createInWithName(PortUsage.class, heater, "socket"); + PortUsage outlet = this.builder.createInWithName(PortUsage.class, rootPart, "outlet"); + + Feature source = this.builder.createFeatureChaining(livingRoom, heater, socket); + InterfaceUsage interfaceUsage = this.builder.createInWithName(InterfaceUsage.class, rootPart, "interface1"); + interfaceUsage.getOwnedRelationship().add(this.createConnectionEndFeatureMembership(source)); + interfaceUsage.getOwnedRelationship().add(this.createConnectionEndFeatureMembership(outlet)); + + this.assertTextualFormEquals("connection interface1 connect livingRoom.heater.socket to outlet;", interfaceUsage); + } + + @Test + public void interfaceUsageWithUnresolvedPortEnd() { + PartUsage rootPart = this.builder.createWithName(PartUsage.class, "part1"); + + PortUsage socket = this.builder.createInWithName(PortUsage.class, rootPart, "socket"); + InterfaceUsage interfaceUsage = this.builder.createInWithName(InterfaceUsage.class, rootPart, "interface1"); + interfaceUsage.getOwnedRelationship().add(this.createConnectionEndFeatureMembership(socket)); + interfaceUsage.getOwnedRelationship().add(this.createUnresolvedConnectionEndFeatureMembership()); + + this.assertTextualFormEquals("connection interface1 connect socket to ;", interfaceUsage); + } + + @Test + public void flowUsageWithPortEnds() { + PartUsage rootPart = this.builder.createWithName(PartUsage.class, "part1"); + + PartUsage heater = this.builder.createInWithName(PartUsage.class, rootPart, "heater"); + PartUsage adapter = this.builder.createInWithName(PartUsage.class, rootPart, "adapter"); + + FlowUsage flowUsage = this.builder.createInWithName(FlowUsage.class, rootPart, "flow1"); + flowUsage.getOwnedRelationship().add(this.createFlowEndFeatureMembership(heater, "socket")); + flowUsage.getOwnedRelationship().add(this.createFlowEndFeatureMembership(adapter, "outlet")); + + this.assertTextualFormEquals("flow flow1 from heater.socket to adapter.outlet;", flowUsage); + } + @DisplayName("ActionUsage with SuccessionAsUsage linked to an action defined in the ActionDefinition") @Test public void actionUsageWithActionDefinitionAndSuccession() { diff --git a/backend/services/syson-sysml-metamodel-services/src/main/java/org/eclipse/syson/sysml/metamodel/services/textual/SysMLElementSerializer.java b/backend/services/syson-sysml-metamodel-services/src/main/java/org/eclipse/syson/sysml/metamodel/services/textual/SysMLElementSerializer.java index a625cd3c2..952603535 100644 --- a/backend/services/syson-sysml-metamodel-services/src/main/java/org/eclipse/syson/sysml/metamodel/services/textual/SysMLElementSerializer.java +++ b/backend/services/syson-sysml-metamodel-services/src/main/java/org/eclipse/syson/sysml/metamodel/services/textual/SysMLElementSerializer.java @@ -1736,28 +1736,30 @@ private boolean isSuccessionUsageImplicitSource(EndFeatureMembership endFeatureM private void appendConnectorEndMember(Appender builder, EndFeatureMembership endFeatureMembership) { endFeatureMembership.getOwnedRelatedElement().stream() - .filter(ReferenceUsage.class::isInstance) - .map(ReferenceUsage.class::cast) + .filter(Feature.class::isInstance) + .map(Feature.class::cast) .findFirst() - .ifPresent(ref -> this.appendConnectorEnd(builder, ref)); + .ifPresent(endFeature -> this.appendConnectorEnd(builder, endFeature)); } - private void appendConnectorEnd(Appender builder, ReferenceUsage referenceUsage) { + private void appendConnectorEnd(Appender builder, Feature endFeature) { // Handle ownedRelationship += OwnedMultiplicity - referenceUsage.getOwnedRelationship().stream().filter(OwningMembership.class::isInstance) + endFeature.getOwnedRelationship().stream().filter(OwningMembership.class::isInstance) .map(OwningMembership.class::cast) .filter(owningMembership -> owningMembership.getOwnedMemberElement() instanceof Feature) .findFirst() .ifPresent(owningMembership -> this.appendOwnedCrossMultiplicityMember(builder, owningMembership)); - String declaredName = referenceUsage.getDeclaredName(); + if (endFeature instanceof ReferenceUsage referenceUsage) { + String declaredName = referenceUsage.getDeclaredName(); - if (declaredName != null && !declaredName.isBlank()) { - builder.appendWithSpaceIfNeeded(declaredName).append(SPACE).append(LabelConstants.REFERENCES); + if (declaredName != null && !declaredName.isBlank()) { + builder.appendWithSpaceIfNeeded(declaredName).append(SPACE).append(LabelConstants.REFERENCES); + } } - ReferenceSubsetting refSubsetting = referenceUsage.getOwnedReferenceSubsetting(); + ReferenceSubsetting refSubsetting = endFeature.getOwnedReferenceSubsetting(); if (refSubsetting != null) { this.appendOwnedReferenceSubsetting(builder, refSubsetting);