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 @@ -34,6 +34,7 @@ The `getCommentBody`, `getComment`, and `getDocumentation` query APIs are now pr
- https://github.com/eclipse-syson/syson/issues/1074[#1074] [diagrams] Fix available diagram tools on standard diagrams created on a root namespace
- https://github.com/eclipse-syson/syson/issues/2510[#2510] [import] Report syntax errors that may leave an uploaded SysML file partially imported.
The parsed elements are still imported, and the loading report includes the parser diagnostics and a warning about possibly missing elements.
- https://github.com/eclipse-syson/syson/issues/2527[#2527] [export] Export connector ends whose feature chain is only owned by the `ReferenceSubsetting`, and export `InterfaceUsage` with the `interface` keyword instead of `connection`.

=== Improvements

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1494,6 +1494,34 @@ public void checkConnectionUsageWithBody() throws IOException {
.check();
}

@Test
@DisplayName("GIVEN a model with an InterfaceUsage whose end is a feature chain, WHEN importing and exporting the model, THEN the InterfaceUsage should be exported properly")
public void checkInterfaceUsageWithChainedEnd() throws IOException {
var input = """
package P {
port def PI;
part def PD { port p : PI; }
part a { part b : PD; }
part c : PD;
interface i connect a.b.p to c.p;
}""";
var expected = """
package P {
port def PI;
part def PD {
port p : PI;
}
part a {
part b : PD;
}
part c : PD;
interface i connect a.b.p to c.p;
}""";
this.checker.textToImport(input)
.expectedResult(expected)
.check();
}

/**
* Test import/export on test file StateTest.sysml. The content of StateTest.sysml that have been copied below
* is under LGPL-3.0-only license. The LGPL-3.0-only license is accessible at the root of this repository, in the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ public class SysMLElementSerializerTest {

private static final String BODY = "A body";

private static final String SOCKET = "socket";

private ModelBuilder builder;

private List<Status> status;
Expand Down Expand Up @@ -1650,7 +1652,7 @@ 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 socket = this.builder.createInWithName(PortUsage.class, heater, SOCKET);
PortUsage outlet = this.builder.createInWithName(PortUsage.class, rootPart, "outlet");

Feature source = this.builder.createFeatureChaining(heater, socket);
Expand All @@ -1659,7 +1661,7 @@ public void interfaceUsageWithPortEnds() {
interfaceUsage.getOwnedRelationship().add(this.createConnectionEndFeatureMembership(source));
interfaceUsage.getOwnedRelationship().add(this.createConnectionEndFeatureMembership(outlet));

this.assertTextualFormEquals("connection interface1 connect heater.socket to outlet;", interfaceUsage);
this.assertTextualFormEquals("interface interface1 connect heater.socket to outlet;", interfaceUsage);
}

@Test
Expand All @@ -1677,7 +1679,42 @@ public void interfaceUsageWithNestedPortEnd() {
interfaceUsage.getOwnedRelationship().add(this.createConnectionEndFeatureMembership(source));
interfaceUsage.getOwnedRelationship().add(this.createConnectionEndFeatureMembership(outlet));

this.assertTextualFormEquals("connection interface1 connect livingRoom.heater.socket to outlet;", interfaceUsage);
this.assertTextualFormEquals("interface interface1 connect livingRoom.heater.socket to outlet;", interfaceUsage);
}

@Test
public void interfaceUsageWithChainedEndsWithoutReferencedFeature() {
PartUsage rootPart = this.builder.createWithName(PartUsage.class, "root");

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.createWithName(InterfaceUsage.class, "interface1");
this.addAsFeatureMember(rootPart, interfaceUsage);
interfaceUsage.getOwnedRelationship().add(this.createChainedEndFeatureMembership(source));
interfaceUsage.getOwnedRelationship().add(this.createConnectionEndFeatureMembership(outlet));

this.assertTextualFormEquals("interface interface1 connect livingRoom.heater.socket to outlet;", interfaceUsage);
}

/**
* Creates an end whose feature chain is only owned by the {@link ReferenceSubsetting}, without
* {@code referencedFeature} set, as produced by old models and imports.
*/
private EndFeatureMembership createChainedEndFeatureMembership(Feature chain) {
EndFeatureMembership endFeatureMembership = this.fact.createEndFeatureMembership();
PortUsage endFeature = this.fact.createPortUsage();
endFeature.setIsEnd(true);
endFeatureMembership.getOwnedRelatedElement().add(endFeature);
endFeatureMembership.setMemberElement(endFeature);
ReferenceSubsetting refSubsetting = this.fact.createReferenceSubsetting();
refSubsetting.setSubsettingFeature(endFeature);
refSubsetting.getOwnedRelatedElement().add(chain);
endFeature.getOwnedRelationship().add(refSubsetting);
return endFeatureMembership;
}

@Test
Expand All @@ -1697,7 +1734,7 @@ public void connectionUsageWithMetadataOnItsEnds() {
interfaceUsage.getOwnedRelationship().add(sourceEnd);
interfaceUsage.getOwnedRelationship().add(targetEnd);

this.assertTextualFormEquals("connection interface1 connect #original socket to #derive outlet;", interfaceUsage);
this.assertTextualFormEquals("interface interface1 connect #original socket to #derive outlet;", interfaceUsage);
}

/**
Expand Down Expand Up @@ -1735,7 +1772,7 @@ public void interfaceUsageWithUnresolvedPortEnd() {
interfaceUsage.getOwnedRelationship().add(this.createConnectionEndFeatureMembership(socket));
interfaceUsage.getOwnedRelationship().add(this.createUnresolvedConnectionEndFeatureMembership());

this.assertTextualFormEquals("connection interface1 connect socket to ;", interfaceUsage);
this.assertTextualFormEquals("interface interface1 connect socket to ;", interfaceUsage);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
import org.eclipse.syson.sysml.Import;
import org.eclipse.syson.sysml.IncludeUseCaseUsage;
import org.eclipse.syson.sysml.InterfaceDefinition;
import org.eclipse.syson.sysml.InterfaceUsage;
import org.eclipse.syson.sysml.InvocationExpression;
import org.eclipse.syson.sysml.ItemDefinition;
import org.eclipse.syson.sysml.ItemUsage;
Expand Down Expand Up @@ -421,9 +422,14 @@ public String caseConnectionUsage(ConnectionUsage connectionUsage) {
.filter(e -> !ends.contains(e))
.toList();

if (!declarationAndValueBuilder.isEmpty() || !contentMemberships.isEmpty()) {
// If the ConnectionUsage as a declaration, a value or some content we need to use the keyword connection
builder.appendWithSpaceIfNeeded("connection").appendWithSpaceIfNeeded(declarationAndValueBuilder);
if (connectionUsage instanceof InterfaceUsage || !declarationAndValueBuilder.isEmpty() || !contentMemberships.isEmpty()) {
// InterfaceUsage always requires its keyword; ConnectionUsage only when it has a declaration, a value or some content
if (connectionUsage instanceof InterfaceUsage) {
builder.appendWithSpaceIfNeeded(this.getUsageKeyword(connectionUsage));
} else {
builder.appendWithSpaceIfNeeded(SysMLv2Keywords.CONNECTION);
}
builder.appendWithSpaceIfNeeded(declarationAndValueBuilder);
}

if (!connectorPartBuilder.isEmpty()) {
Expand Down Expand Up @@ -1776,7 +1782,7 @@ private void appendConnectorEnd(Appender builder, Feature endFeature) {
}

private void appendOwnedReferenceSubsetting(Appender builder, ReferenceSubsetting refSubsetting) {
Feature referencedFeature = refSubsetting.getReferencedFeature();
Feature referencedFeature = this.resolveReferencedFeature(refSubsetting);

if (referencedFeature != null) {

Expand All @@ -1792,6 +1798,26 @@ private void appendOwnedReferenceSubsetting(Appender builder, ReferenceSubsettin
}
}

/**
* Resolves the referenced feature, falling back to an owned feature chain when no reference is set.
*
* @param refSubsetting
* the reference subsetting to resolve
* @return the referenced feature or an owned feature chain, or {@code null} if neither is available
*/
private Feature resolveReferencedFeature(ReferenceSubsetting refSubsetting) {
Feature referencedFeature = refSubsetting.getReferencedFeature();
if (referencedFeature == null) {
referencedFeature = refSubsetting.getOwnedRelatedElement().stream()
.filter(Feature.class::isInstance)
.map(Feature.class::cast)
.filter(feature -> !feature.eIsProxy() && !feature.getOwnedFeatureChaining().isEmpty())
.findFirst()
.orElse(null);
}
return referencedFeature;
}

private void appendOwnedCrossMultiplicityMember(Appender builder, OwningMembership owningMembership) {
if (owningMembership.getOwnedMemberElement() instanceof Feature ownedFeature) {
this.appendMultiplicityPart(builder, ownedFeature);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ Use the existing content tools to display these elements. Existing representatio

** Report syntax errors that may leave an uploaded SysML file partially imported.
The parsed elements are still imported, and the loading report identifies the syntax problem and warns about possibly missing elements.
** Export connector ends that reference feature chains, such as `battery.powerPort to batmobileEngine.enginePort`, instead of leaving the ends empty.
** `InterfaceUsage` elements are now exported with the `interface` keyword.

== Improvements

Expand Down
Loading