From ffe5297f40882f8dfb96b53c2bdb0ca79a23bcd3 Mon Sep 17 00:00:00 2001 From: Pierre-Charles David Date: Tue, 3 Mar 2026 09:12:57 +0100 Subject: [PATCH] [1583] Improve support for Stakeholder Bug: https://github.com/eclipse-syson/syson/issues/1583 Signed-off-by: Pierre-Charles David --- CHANGELOG.adoc | 2 + .../GVSubNodeRequirementCreationTests.java | 3 +- .../sysml/impl/StakeholderMembershipImpl.java | 13 +- .../impl/RequirementDefinitionImplTest.java | 21 +++ .../services/DiagramQueryElementService.java | 5 + .../services/aql/ModelQueryAQLService.java | 7 + .../MetamodelQueryElementService.java | 15 ++ .../AbstractUsageNodeDescriptionProvider.java | 6 + .../common/view/services/ViewNodeService.java | 24 ++++ .../common/view/services/ViewToolService.java | 33 +++++ .../view/SDVDiagramDescriptionProvider.java | 50 ++++--- ...tedStakeholderEdgeDescriptionProvider.java | 128 ++++++++++++++++++ .../StakeholderNodeDescriptionProvider.java | 54 ++++++++ .../nodes/UsageNodeDescriptionProvider.java | 5 +- 14 files changed, 341 insertions(+), 25 deletions(-) create mode 100644 backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/edges/NestedStakeholderEdgeDescriptionProvider.java create mode 100644 backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/nodes/StakeholderNodeDescriptionProvider.java diff --git a/CHANGELOG.adoc b/CHANGELOG.adoc index 34d7cadc4..54cdf267f 100644 --- a/CHANGELOG.adoc +++ b/CHANGELOG.adoc @@ -12,6 +12,8 @@ === Improvements +- https://github.com/eclipse-syson/syson/issues/1583[#1583] [diagrams] Improve support for `Stakeholder`. + === New features diff --git a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSubNodeRequirementCreationTests.java b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSubNodeRequirementCreationTests.java index 828f43ef3..629cf4d10 100644 --- a/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSubNodeRequirementCreationTests.java +++ b/backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVSubNodeRequirementCreationTests.java @@ -1107,7 +1107,8 @@ private void createNewStakeholderIn(EClass eClassWithStakeholderParameter, Strin Consumer diagramCheck = assertRefreshedDiagramThat(newDiagram -> { var initialDiagram = diagram.get(); new CheckDiagramElementCount(this.diagramComparator) - .hasNewNodeCount(1) + .hasNewNodeCount(9) + .hasNewEdgeCount(1) .check(initialDiagram, newDiagram); new CheckNodeInCompartment(diagramDescriptionIdProvider, this.diagramComparator) .withParentLabel(parentNodeLabel) diff --git a/backend/metamodel/syson-sysml-metamodel/src/main/java/org/eclipse/syson/sysml/impl/StakeholderMembershipImpl.java b/backend/metamodel/syson-sysml-metamodel/src/main/java/org/eclipse/syson/sysml/impl/StakeholderMembershipImpl.java index 116b19948..9b4764065 100644 --- a/backend/metamodel/syson-sysml-metamodel/src/main/java/org/eclipse/syson/sysml/impl/StakeholderMembershipImpl.java +++ b/backend/metamodel/syson-sysml-metamodel/src/main/java/org/eclipse/syson/sysml/impl/StakeholderMembershipImpl.java @@ -1,5 +1,5 @@ /******************************************************************************* -* Copyright (c) 2023, 2025 Obeo. +* Copyright (c) 2023, 2026 Obeo. * This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -66,13 +66,14 @@ public PartUsage getOwnedStakeholderParameter() { /** * * - * @generated + * @generated NOT */ public PartUsage basicGetOwnedStakeholderParameter() { - // TODO: implement this method to return the 'Owned Stakeholder Parameter' reference - // -> do not perform proxy resolution - // Ensure that you remove @generated or mark it @generated NOT - return null; + return this.getOwnedRelatedElement().stream() + .filter(PartUsage.class::isInstance) + .map(PartUsage.class::cast) + .findFirst() + .orElse(null); } /** diff --git a/backend/metamodel/syson-sysml-metamodel/src/test/java/org/eclipse/syson/sysml/impl/RequirementDefinitionImplTest.java b/backend/metamodel/syson-sysml-metamodel/src/test/java/org/eclipse/syson/sysml/impl/RequirementDefinitionImplTest.java index cac2e010e..97cd80127 100644 --- a/backend/metamodel/syson-sysml-metamodel/src/test/java/org/eclipse/syson/sysml/impl/RequirementDefinitionImplTest.java +++ b/backend/metamodel/syson-sysml-metamodel/src/test/java/org/eclipse/syson/sysml/impl/RequirementDefinitionImplTest.java @@ -12,6 +12,7 @@ *******************************************************************************/ package org.eclipse.syson.sysml.impl; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.List; @@ -21,6 +22,7 @@ import org.eclipse.syson.sysml.PartUsage; import org.eclipse.syson.sysml.RequirementDefinition; import org.eclipse.syson.sysml.StakeholderMembership; +import org.eclipse.syson.sysml.SubjectMembership; import org.eclipse.syson.sysml.SysmlFactory; import org.eclipse.syson.sysml.SysmlPackage; import org.junit.jupiter.api.Assertions; @@ -61,16 +63,35 @@ public static void setUpRequirementDefinition() { @Test public void testGetActorParameter() { + // Check that we can find the actors from the Requirement final List actorParameterValue = REQUIREMENT_DEFINITION.getActorParameter(); Assertions.assertNotNull(actorParameterValue); Assertions.assertIterableEquals(ACTOR_PART_USAGES, actorParameterValue); + + // Also check that we can get them directly from the ActorMembership themselves + List actors = REQUIREMENT_DEFINITION.getOwnedMembership().stream() + .filter(ActorMembership.class::isInstance) + .map(ActorMembership.class::cast) + .map(ActorMembership::getOwnedActorParameter) + .toList(); + Assertions.assertIterableEquals(ACTOR_PART_USAGES, actors); } @Test public void testGetStakeholderParameter() { + // Check that we can find the stakeholders from the Requirement final List stakeholderParameterValue = REQUIREMENT_DEFINITION.getStakeholderParameter(); Assertions.assertNotNull(stakeholderParameterValue); Assertions.assertIterableEquals(STAKEHOLDER_PART_USAGES, stakeholderParameterValue); + + // Also check that we can get them directly from the StakeholderMembership themselves + List stakeholders = REQUIREMENT_DEFINITION.getOwnedMembership().stream() + .filter(StakeholderMembership.class::isInstance) + .map(StakeholderMembership.class::cast) + .map(StakeholderMembership::getOwnedStakeholderParameter) + .toList(); + Assertions.assertIterableEquals(STAKEHOLDER_PART_USAGES, stakeholders); + assertThat(stakeholders).hasSameElementsAs(STAKEHOLDER_PART_USAGES); } @Test diff --git a/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramQueryElementService.java b/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramQueryElementService.java index 44d37d5f7..0d211a7ca 100644 --- a/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramQueryElementService.java +++ b/backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramQueryElementService.java @@ -305,6 +305,11 @@ public Optional getNodeDescriptionId(Element element, Diagram diagram, I .filter(nodeDesc -> nodeDesc.getName().equals("GV Node " + SysmlPackage.eINSTANCE.getPackage().getName())) .map(nodeDesc -> this.diagramIdProvider.getId(nodeDesc)) .findFirst(); + } else if (this.metamodelQueryElementService.isStakeholder(element)) { + nodeDescriptionId = EMFUtils.allContainedObjectOfType(optViewDD.get(), org.eclipse.sirius.components.view.diagram.NodeDescription.class) + .filter(nodeDesc -> nodeDesc.getName().equals("GV Node Stakeholder")) + .map(nodeDesc -> this.diagramIdProvider.getId(nodeDesc)) + .findFirst(); } else { nodeDescriptionId = EMFUtils.allContainedObjectOfType(optViewDD.get(), org.eclipse.sirius.components.view.diagram.NodeDescription.class) .filter(nodeDesc -> nodeDesc.getName().equals("GV Node " + element.eClass().getName())) diff --git a/backend/services/syson-model-services/src/main/java/org/eclipse/syson/model/services/aql/ModelQueryAQLService.java b/backend/services/syson-model-services/src/main/java/org/eclipse/syson/model/services/aql/ModelQueryAQLService.java index dc847ecc7..6bff05564 100644 --- a/backend/services/syson-model-services/src/main/java/org/eclipse/syson/model/services/aql/ModelQueryAQLService.java +++ b/backend/services/syson-model-services/src/main/java/org/eclipse/syson/model/services/aql/ModelQueryAQLService.java @@ -41,6 +41,13 @@ public boolean isActor(Element element) { return this.metamodelQueryElementService.isActor(element); } + /** + * {@link MetamodelQueryElementService#isStakeholder(Element)}. + */ + public boolean isStakeholder(Element element) { + return this.metamodelQueryElementService.isStakeholder(element); + } + /** * {@link MetamodelQueryElementService#isSubject(Element)}. */ diff --git a/backend/services/syson-sysml-metamodel-services/src/main/java/org/eclipse/syson/sysml/metamodel/services/MetamodelQueryElementService.java b/backend/services/syson-sysml-metamodel-services/src/main/java/org/eclipse/syson/sysml/metamodel/services/MetamodelQueryElementService.java index 1c006a342..60ee50ff3 100644 --- a/backend/services/syson-sysml-metamodel-services/src/main/java/org/eclipse/syson/sysml/metamodel/services/MetamodelQueryElementService.java +++ b/backend/services/syson-sysml-metamodel-services/src/main/java/org/eclipse/syson/sysml/metamodel/services/MetamodelQueryElementService.java @@ -26,6 +26,7 @@ import org.eclipse.syson.sysml.FeatureValue; import org.eclipse.syson.sysml.PartUsage; import org.eclipse.syson.sysml.ReferenceUsage; +import org.eclipse.syson.sysml.StakeholderMembership; import org.eclipse.syson.sysml.SubjectMembership; /** @@ -63,6 +64,20 @@ public boolean isSubject(Element element) { return element instanceof ReferenceUsage && element.getOwningMembership() instanceof SubjectMembership; } + /** + * Return {@code true} if the provided {@code element} is a stakeholder, {@code false} otherwise. + *

+ * A stakeholder is a kind of parameter stored in an {@link StakeholderMembership}. + *

+ * + * @param element + * the element to check + * @return {@code true} if the provided {@code element} is an stakeholder, {@code false} otherwise + */ + public boolean isStakeholder(Element element) { + return element instanceof PartUsage && element.getOwningMembership() instanceof StakeholderMembership; + } + /** * Get the source of a {@link Connector}. * diff --git a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/AbstractUsageNodeDescriptionProvider.java b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/AbstractUsageNodeDescriptionProvider.java index d4805f153..7eccf4b4b 100644 --- a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/AbstractUsageNodeDescriptionProvider.java +++ b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/nodes/AbstractUsageNodeDescriptionProvider.java @@ -48,6 +48,7 @@ import org.eclipse.syson.diagram.services.aql.DiagramQueryAQLService; import org.eclipse.syson.services.DeleteService; import org.eclipse.syson.services.UtilService; +import org.eclipse.syson.util.AQLConstants; import org.eclipse.syson.util.IDescriptionNameGenerator; import org.eclipse.syson.util.ServiceMethod; import org.eclipse.syson.util.SysMLMetamodelHelper; @@ -168,6 +169,7 @@ public NodeDescription create() { .collapsible(true) .defaultHeightExpression(defaultSizeExpression.defaultHeightExpression()) .defaultWidthExpression(defaultSizeExpression.defaultWidthExpression()) + .isHiddenByDefaultExpression(this.getHiddenByDefaultExpression()) .domainType(domainType) .insideLabel(this.createInsideLabelDescription()) .outsideLabels(this.createOutsideLabelDescriptions().toArray(OutsideLabelDescription[]::new)) @@ -202,6 +204,10 @@ public void link(DiagramDescription diagramDescription, IViewDiagramElementFinde }); } + protected String getHiddenByDefaultExpression() { + return AQLConstants.AQL_FALSE; + } + protected InsideLabelDescription createInsideLabelDescription() { return this.diagramBuilderHelper.newInsideLabelDescription() .labelExpression(ServiceMethod.of0(DiagramQueryAQLService::getContainerLabel).aqlSelf()) diff --git a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewNodeService.java b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewNodeService.java index 26bdb000e..575f7288f 100644 --- a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewNodeService.java +++ b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewNodeService.java @@ -408,6 +408,30 @@ public List getExposedActors(Element element, EClass domainType, List .toList(); } + /** + * Get all Stakeholder ({@link PartUsage}) in {@link ViewUsage}'s exposed elements. + * + * @param element + * the {@link Element} for which we want the Stakeholders. + * @param domainType + * the domain type to the elements to retrieve + * @param ancestors + * the given ancestors (semantic elements of the graphical nodes that are the ancestors) of the Node on + * which this service has been called. + * @param editingContext + * the given {@link IEditingContext} in which this service has been called. + * @param diagramContext + * the given {@link DiagramContext} in which this service has been called. + * @return the list of Actor {@link PartUsage}s in {@link ViewUsage}'s exposed elements. + */ + public List getExposedStakeholders(Element element, EClass domainType, List ancestors, IEditingContext editingContext, DiagramContext diagramContext) { + return this.getExposedElements(element, domainType, ancestors, editingContext, diagramContext).stream() + .filter(PartUsage.class::isInstance) + .map(PartUsage.class::cast) + .filter(this.metamodelQueryElementService::isStakeholder) + .toList(); + } + /** * Get all Subjects ({@link ReferenceUsage}) in {@link ViewUsage}'s exposed elements. * diff --git a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewToolService.java b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewToolService.java index ce5fc697a..77191805d 100644 --- a/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewToolService.java +++ b/backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/ViewToolService.java @@ -58,6 +58,7 @@ import org.eclipse.syson.sysml.PartUsage; import org.eclipse.syson.sysml.RequirementDefinition; import org.eclipse.syson.sysml.RequirementUsage; +import org.eclipse.syson.sysml.StakeholderMembership; import org.eclipse.syson.sysml.StateDefinition; import org.eclipse.syson.sysml.StateUsage; import org.eclipse.syson.sysml.SubjectMembership; @@ -236,6 +237,38 @@ public Element reconnectSourceNestedActorEdge(Element self, Element newSource, E return otherEnd; } + /** + * Reconnects the source of a nested stakeholder edge. + *

+ * The source of this edge is a Requirement, and can only be reconnected to Requirements. + *

+ * + * @param self + * the current Requirement + * @param newSource + * the new Requirement + * @param otherEnd + * the Stakeholder connected to the Requirement + * @return the Stakeholder + */ + public Element reconnectSourceNestedStakeholderEdge(Element self, Element newSource, Element otherEnd) { + if (newSource instanceof RequirementUsage || newSource instanceof RequirementDefinition) { + if (otherEnd.getOwningMembership() instanceof StakeholderMembership stakeholderMembership) { + newSource.getOwnedRelationship().add(stakeholderMembership); + } else { + // This is an error, an Stakeholder should always be contained in an StakeholderMembership. + String errorMessage = "Cannot reconnect the Stakeholder, it is not owned by an " + StakeholderMembership.class.getSimpleName(); + this.logger.error(errorMessage); + this.feedbackMessageService.addFeedbackMessage(new Message(errorMessage, MessageLevel.ERROR)); + } + } else { + String errorMessage = "Cannot reconnect an Stakeholder to non-Requirement element"; + this.logger.warn(errorMessage); + this.feedbackMessageService.addFeedbackMessage(new Message(errorMessage, MessageLevel.WARNING)); + } + return otherEnd; + } + /** * Reconnects the source of a nested subject edge. *

diff --git a/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/SDVDiagramDescriptionProvider.java b/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/SDVDiagramDescriptionProvider.java index b8944be70..88409ae93 100644 --- a/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/SDVDiagramDescriptionProvider.java +++ b/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/SDVDiagramDescriptionProvider.java @@ -77,6 +77,7 @@ import org.eclipse.syson.standard.diagrams.view.edges.IncludeUseCaseDescriptionProvider; import org.eclipse.syson.standard.diagrams.view.edges.InterfaceUsageEdgeDescriptionProvider; import org.eclipse.syson.standard.diagrams.view.edges.NestedActorEdgeDescriptionProvider; +import org.eclipse.syson.standard.diagrams.view.edges.NestedStakeholderEdgeDescriptionProvider; import org.eclipse.syson.standard.diagrams.view.edges.NestedSubjectEdgeDescriptionProvider; import org.eclipse.syson.standard.diagrams.view.edges.RedefinitionEdgeDescriptionProvider; import org.eclipse.syson.standard.diagrams.view.edges.ReferenceSubsettingEdgeDescriptionProvider; @@ -123,6 +124,7 @@ import org.eclipse.syson.standard.diagrams.view.nodes.RequirementUsageStakeholdersCompartmentNodeDescriptionProvider; import org.eclipse.syson.standard.diagrams.view.nodes.RequirementUsageSubjectCompartmentNodeDescriptionProvider; import org.eclipse.syson.standard.diagrams.view.nodes.SDVNodeDescriptionProviderSwitch; +import org.eclipse.syson.standard.diagrams.view.nodes.StakeholderNodeDescriptionProvider; import org.eclipse.syson.standard.diagrams.view.nodes.SubjectNodeDescriptionProvider; import org.eclipse.syson.sysml.FeatureDirectionKind; import org.eclipse.syson.sysml.SysmlPackage; @@ -720,28 +722,36 @@ private List> createCompartmentsForActorPa private List> createCompartmentsForStakeholderParameter(IColorProvider colorProvider) { final List> compartmentNodeDescriptionProviders = new ArrayList<>(); + // RequirementDefinition: stakeholder compartment & items + compartmentNodeDescriptionProviders.add(new RequirementDefinitionStakeholdersCompartmentNodeDescriptionProvider(SysmlPackage.eINSTANCE.getRequirementDefinition(), + SysmlPackage.eINSTANCE.getRequirementDefinition_StakeholderParameter(), colorProvider, this.getDescriptionNameGenerator())); + compartmentNodeDescriptionProviders.add(new CompartmentItemNodeDescriptionProvider(SysmlPackage.eINSTANCE.getRequirementDefinition(), + SysmlPackage.eINSTANCE.getRequirementDefinition_StakeholderParameter(), colorProvider, this.getDescriptionNameGenerator())); + // RequirementUsage: stakeholder compartment & items + compartmentNodeDescriptionProviders.add(new RequirementUsageStakeholdersCompartmentNodeDescriptionProvider(SysmlPackage.eINSTANCE.getRequirementUsage(), + SysmlPackage.eINSTANCE.getRequirementUsage_StakeholderParameter(), colorProvider, this.getDescriptionNameGenerator())); + compartmentNodeDescriptionProviders + .add(new CompartmentItemNodeDescriptionProvider(SysmlPackage.eINSTANCE.getRequirementUsage(), SysmlPackage.eINSTANCE.getRequirementUsage_StakeholderParameter(), + colorProvider, this.getDescriptionNameGenerator())); + + // ConcernDefinition: stakeholder compartment & items compartmentNodeDescriptionProviders.add(new RequirementDefinitionStakeholdersCompartmentNodeDescriptionProvider(SysmlPackage.eINSTANCE.getConcernDefinition(), SysmlPackage.eINSTANCE.getRequirementDefinition_StakeholderParameter(), colorProvider, this.getDescriptionNameGenerator())); compartmentNodeDescriptionProviders.add(new CompartmentItemNodeDescriptionProvider(SysmlPackage.eINSTANCE.getConcernDefinition(), SysmlPackage.eINSTANCE.getRequirementDefinition_StakeholderParameter(), colorProvider, this.getDescriptionNameGenerator())); + + // ConcernUsage: stakeholder compartment & items compartmentNodeDescriptionProviders.add(new RequirementUsageStakeholdersCompartmentNodeDescriptionProvider(SysmlPackage.eINSTANCE.getConcernUsage(), SysmlPackage.eINSTANCE.getRequirementUsage_StakeholderParameter(), colorProvider, this.getDescriptionNameGenerator())); - compartmentNodeDescriptionProviders.add(new RequirementUsageStakeholdersCompartmentNodeDescriptionProvider(SysmlPackage.eINSTANCE.getSatisfyRequirementUsage(), - SysmlPackage.eINSTANCE.getRequirementUsage_StakeholderParameter(), colorProvider, this.getDescriptionNameGenerator())); compartmentNodeDescriptionProviders.add(new CompartmentItemNodeDescriptionProvider(SysmlPackage.eINSTANCE.getConcernUsage(), SysmlPackage.eINSTANCE.getRequirementUsage_StakeholderParameter(), colorProvider, this.getDescriptionNameGenerator())); - compartmentNodeDescriptionProviders.add(new CompartmentItemNodeDescriptionProvider(SysmlPackage.eINSTANCE.getSatisfyRequirementUsage(), + + // SatisfyRequirementUsage: stakeholder compartment & items + compartmentNodeDescriptionProviders.add(new RequirementUsageStakeholdersCompartmentNodeDescriptionProvider(SysmlPackage.eINSTANCE.getSatisfyRequirementUsage(), SysmlPackage.eINSTANCE.getRequirementUsage_StakeholderParameter(), colorProvider, this.getDescriptionNameGenerator())); - compartmentNodeDescriptionProviders.add(new RequirementDefinitionStakeholdersCompartmentNodeDescriptionProvider(SysmlPackage.eINSTANCE.getRequirementDefinition(), - SysmlPackage.eINSTANCE.getRequirementDefinition_StakeholderParameter(), colorProvider, this.getDescriptionNameGenerator())); - compartmentNodeDescriptionProviders.add(new CompartmentItemNodeDescriptionProvider(SysmlPackage.eINSTANCE.getRequirementDefinition(), - SysmlPackage.eINSTANCE.getRequirementDefinition_StakeholderParameter(), colorProvider, this.getDescriptionNameGenerator())); - compartmentNodeDescriptionProviders.add(new RequirementUsageStakeholdersCompartmentNodeDescriptionProvider(SysmlPackage.eINSTANCE.getRequirementUsage(), + compartmentNodeDescriptionProviders.add(new CompartmentItemNodeDescriptionProvider(SysmlPackage.eINSTANCE.getSatisfyRequirementUsage(), SysmlPackage.eINSTANCE.getRequirementUsage_StakeholderParameter(), colorProvider, this.getDescriptionNameGenerator())); - compartmentNodeDescriptionProviders - .add(new CompartmentItemNodeDescriptionProvider(SysmlPackage.eINSTANCE.getRequirementUsage(), SysmlPackage.eINSTANCE.getRequirementUsage_StakeholderParameter(), - colorProvider, this.getDescriptionNameGenerator())); return compartmentNodeDescriptionProviders; } @@ -756,6 +766,7 @@ private List> createAllCustomNodeDescripti customNodeDescriptionProviders.add(new MergeActionNodeDescriptionProvider(colorProvider, this.getDescriptionNameGenerator())); customNodeDescriptionProviders.add(new DecisionActionNodeDescriptionProvider(colorProvider, this.getDescriptionNameGenerator())); customNodeDescriptionProviders.add(new ActorNodeDescriptionProvider(colorProvider)); + customNodeDescriptionProviders.add(new StakeholderNodeDescriptionProvider(colorProvider)); customNodeDescriptionProviders.add(new SubjectNodeDescriptionProvider(colorProvider)); customNodeDescriptionProviders.add(new ImportedPackageNodeDescriptionProvider(colorProvider, this.getDescriptionNameGenerator())); @@ -889,19 +900,26 @@ private List> createAllUsageCompositeEdgeD new NestedActorEdgeDescriptionProvider(SysmlPackage.eINSTANCE.getRequirementUsage(), SysmlPackage.eINSTANCE.getRequirementUsage_ActorParameter(), colorProvider, this.getDescriptionNameGenerator())); usageCompositeEdgeDescriptionProviders.add( - new NestedActorEdgeDescriptionProvider(SysmlPackage.eINSTANCE.getRequirementUsage(), SysmlPackage.eINSTANCE.getRequirementUsage_StakeholderParameter(), colorProvider, + new NestedActorEdgeDescriptionProvider(SysmlPackage.eINSTANCE.getRequirementDefinition(), SysmlPackage.eINSTANCE.getRequirementDefinition_ActorParameter(), colorProvider, this.getDescriptionNameGenerator())); usageCompositeEdgeDescriptionProviders.add( - new NestedActorEdgeDescriptionProvider(SysmlPackage.eINSTANCE.getRequirementDefinition(), SysmlPackage.eINSTANCE.getRequirementDefinition_ActorParameter(), colorProvider, + new NestedActorEdgeDescriptionProvider(SysmlPackage.eINSTANCE.getSatisfyRequirementUsage(), SysmlPackage.eINSTANCE.getRequirementUsage_ActorParameter(), colorProvider, this.getDescriptionNameGenerator())); + usageCompositeEdgeDescriptionProviders.add( - new NestedActorEdgeDescriptionProvider(SysmlPackage.eINSTANCE.getRequirementDefinition(), SysmlPackage.eINSTANCE.getRequirementDefinition_StakeholderParameter(), colorProvider, + new NestedStakeholderEdgeDescriptionProvider(SysmlPackage.eINSTANCE.getRequirementUsage(), SysmlPackage.eINSTANCE.getRequirementUsage_StakeholderParameter(), colorProvider, this.getDescriptionNameGenerator())); usageCompositeEdgeDescriptionProviders.add( - new NestedActorEdgeDescriptionProvider(SysmlPackage.eINSTANCE.getSatisfyRequirementUsage(), SysmlPackage.eINSTANCE.getRequirementUsage_ActorParameter(), colorProvider, + new NestedStakeholderEdgeDescriptionProvider(SysmlPackage.eINSTANCE.getRequirementDefinition(), SysmlPackage.eINSTANCE.getRequirementDefinition_StakeholderParameter(), colorProvider, + this.getDescriptionNameGenerator())); + usageCompositeEdgeDescriptionProviders.add( + new NestedStakeholderEdgeDescriptionProvider(SysmlPackage.eINSTANCE.getSatisfyRequirementUsage(), SysmlPackage.eINSTANCE.getRequirementUsage_StakeholderParameter(), colorProvider, + this.getDescriptionNameGenerator())); + usageCompositeEdgeDescriptionProviders.add( + new NestedStakeholderEdgeDescriptionProvider(SysmlPackage.eINSTANCE.getConcernUsage(), SysmlPackage.eINSTANCE.getRequirementUsage_StakeholderParameter(), colorProvider, this.getDescriptionNameGenerator())); usageCompositeEdgeDescriptionProviders.add( - new NestedActorEdgeDescriptionProvider(SysmlPackage.eINSTANCE.getSatisfyRequirementUsage(), SysmlPackage.eINSTANCE.getRequirementUsage_StakeholderParameter(), colorProvider, + new NestedStakeholderEdgeDescriptionProvider(SysmlPackage.eINSTANCE.getConcernDefinition(), SysmlPackage.eINSTANCE.getRequirementDefinition_StakeholderParameter(), colorProvider, this.getDescriptionNameGenerator())); usageCompositeEdgeDescriptionProviders.add( diff --git a/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/edges/NestedStakeholderEdgeDescriptionProvider.java b/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/edges/NestedStakeholderEdgeDescriptionProvider.java new file mode 100644 index 000000000..95fd83773 --- /dev/null +++ b/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/edges/NestedStakeholderEdgeDescriptionProvider.java @@ -0,0 +1,128 @@ +/******************************************************************************* + * Copyright (c) 2026 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +package org.eclipse.syson.standard.diagrams.view.edges; + +import java.util.ArrayList; +import java.util.Objects; + +import org.eclipse.emf.ecore.EClass; +import org.eclipse.emf.ecore.EReference; +import org.eclipse.sirius.components.view.builder.IViewDiagramElementFinder; +import org.eclipse.sirius.components.view.builder.generated.view.ChangeContextBuilder; +import org.eclipse.sirius.components.view.builder.providers.IColorProvider; +import org.eclipse.sirius.components.view.diagram.ArrowStyle; +import org.eclipse.sirius.components.view.diagram.DiagramDescription; +import org.eclipse.sirius.components.view.diagram.EdgeDescription; +import org.eclipse.sirius.components.view.diagram.EdgeStyle; +import org.eclipse.sirius.components.view.diagram.LineStyle; +import org.eclipse.sirius.components.view.diagram.NodeDescription; +import org.eclipse.sirius.components.view.diagram.SynchronizationPolicy; +import org.eclipse.syson.diagram.common.view.edges.AbstractEdgeDescriptionProvider; +import org.eclipse.syson.diagram.common.view.services.ViewToolService; +import org.eclipse.syson.standard.diagrams.view.nodes.StakeholderNodeDescriptionProvider; +import org.eclipse.syson.util.AQLConstants; +import org.eclipse.syson.util.IDescriptionNameGenerator; +import org.eclipse.syson.util.ServiceMethod; +import org.eclipse.syson.util.SysMLMetamodelHelper; +import org.eclipse.syson.util.ViewConstants; + +/** + * Used to create the edge description between elements and their nested stakeholders in General View. + * + * @author pcdavid + */ +public class NestedStakeholderEdgeDescriptionProvider extends AbstractEdgeDescriptionProvider { + + private final IDescriptionNameGenerator nameGenerator; + + private final EClass eClass; + + private final EReference eReference; + + private final String edgeName; + + public NestedStakeholderEdgeDescriptionProvider(EClass eClass, EReference eReference, IColorProvider colorProvider, IDescriptionNameGenerator nameGenerator) { + super(colorProvider); + this.nameGenerator = Objects.requireNonNull(nameGenerator); + this.eClass = Objects.requireNonNull(eClass); + this.eReference = Objects.requireNonNull(eReference); + this.edgeName = this.nameGenerator.getEdgeName("Nested Stakeholder " + this.eClass.getName()); + } + + @Override + public EdgeDescription create() { + String domainType = SysMLMetamodelHelper.buildQualifiedName(this.eClass); + return this.diagramBuilderHelper.newEdgeDescription() + .domainType(domainType) + .isDomainBasedEdge(false) + .name(this.edgeName) + .centerLabelExpression("") + .sourceExpression(AQLConstants.AQL_SELF) + .style(this.createEdgeStyle()) + .synchronizationPolicy(SynchronizationPolicy.SYNCHRONIZED) + .targetExpression(AQLConstants.AQL_SELF + "." + this.eReference.getName()) + .build(); + } + + @Override + public void link(DiagramDescription diagramDescription, IViewDiagramElementFinder cache) { + var optEdgeDescription = cache.getEdgeDescription(this.edgeName); + var optStakeholderNodeDescription = cache.getNodeDescription(StakeholderNodeDescriptionProvider.NAME); + var sourceNodes = new ArrayList(); + + if (optEdgeDescription.isPresent() && optStakeholderNodeDescription.isPresent()) { + cache.getNodeDescription(this.nameGenerator.getNodeName(this.eClass)).ifPresent(sourceNodes::add); + + EdgeDescription edgeDescription = optEdgeDescription.get(); + diagramDescription.getEdgeDescriptions().add(edgeDescription); + edgeDescription.getSourceDescriptions().addAll(sourceNodes); + edgeDescription.getTargetDescriptions().add(optStakeholderNodeDescription.get()); + + edgeDescription.setPalette(this.createEdgePalette(cache)); + } + } + + private EdgeStyle createEdgeStyle() { + return this.diagramBuilderHelper.newEdgeStyle() + .borderSize(0) + .color(this.colorProvider.getColor(ViewConstants.DEFAULT_EDGE_COLOR)) + .edgeWidth(1) + .lineStyle(LineStyle.SOLID) + .sourceArrowStyle(ArrowStyle.NONE) + .targetArrowStyle(ArrowStyle.NONE) + .build(); + } + + @Override + protected boolean isDeletable() { + // This edge cannot be deleted. This would mean that the existing stakeholder is not contained in an + // Requirement anymore, and should likely be represented as a regular part, which is not possible out of + // the box. + return false; + } + + @Override + protected ChangeContextBuilder getSourceReconnectToolBody() { + return this.viewBuilderHelper.newChangeContext() + .expression(ServiceMethod.of2(ViewToolService::reconnectSourceNestedStakeholderEdge).aql(AQLConstants.EDGE_SEMANTIC_ELEMENT, AQLConstants.SEMANTIC_RECONNECTION_TARGET, + AQLConstants.SEMANTIC_OTHER_END)); + } + + @Override + protected ChangeContextBuilder getTargetReconnectToolBody() { + // It is not possible to reconnect the target (actor) of this edge. This would mean that the existing actor is + // not contained in a UseCase/Requirement anymore. + return this.viewBuilderHelper.newChangeContext(); + } + +} diff --git a/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/nodes/StakeholderNodeDescriptionProvider.java b/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/nodes/StakeholderNodeDescriptionProvider.java new file mode 100644 index 000000000..93b052a57 --- /dev/null +++ b/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/nodes/StakeholderNodeDescriptionProvider.java @@ -0,0 +1,54 @@ +/******************************************************************************* + * Copyright (c) 2026 Obeo. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Obeo - initial API and implementation + *******************************************************************************/ +package org.eclipse.syson.standard.diagrams.view.nodes; + +import org.eclipse.sirius.components.collaborative.diagrams.DiagramContext; +import org.eclipse.sirius.components.core.api.IEditingContext; +import org.eclipse.sirius.components.view.builder.providers.IColorProvider; +import org.eclipse.syson.diagram.common.view.services.ViewNodeService; +import org.eclipse.syson.model.services.aql.ModelQueryAQLService; +import org.eclipse.syson.standard.diagrams.view.SDVDescriptionNameGenerator; +import org.eclipse.syson.sysml.PartUsage; +import org.eclipse.syson.sysml.SysmlPackage; +import org.eclipse.syson.util.ServiceMethod; + +/** + * Node description provider for stakeholder {@link PartUsage} in the General View diagram. + * + * @author pcdavid + */ +public class StakeholderNodeDescriptionProvider extends UsageNodeDescriptionProvider { + + public static final String NAME = SDVDescriptionNameGenerator.PREFIX + " Node Stakeholder"; + + public StakeholderNodeDescriptionProvider(IColorProvider colorProvider) { + super(SysmlPackage.eINSTANCE.getPartUsage(), colorProvider); + } + + @Override + protected String createPreconditionExpression() { + return ServiceMethod.of0(ModelQueryAQLService::isStakeholder).aqlSelf(); + } + + @Override + protected String getNodeDescriptionName() { + return NAME; + } + + @Override + protected String getSemanticCandidatesExpression(String domainType) { + return ServiceMethod.of4(ViewNodeService::getExposedStakeholders).aqlSelf(domainType, org.eclipse.sirius.components.diagrams.description.NodeDescription.ANCESTORS, + IEditingContext.EDITING_CONTEXT, + DiagramContext.DIAGRAM_CONTEXT); + } +} diff --git a/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/nodes/UsageNodeDescriptionProvider.java b/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/nodes/UsageNodeDescriptionProvider.java index 45c5a8627..68a1aaf03 100644 --- a/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/nodes/UsageNodeDescriptionProvider.java +++ b/backend/views/syson-standard-diagrams-view/src/main/java/org/eclipse/syson/standard/diagrams/view/nodes/UsageNodeDescriptionProvider.java @@ -60,11 +60,12 @@ protected String getSemanticCandidatesExpression(String domainType) { @Override protected String createPreconditionExpression() { // Actors are handled with a different NodeDescription: ActorNodeDescriptionProvider. - // Subjects are handled with a different NodeDescription: SubjectDescriptionProvider. + // Subjects are handled with a different NodeDescription: SubjectNodeDescriptionProvider. + // Stakeholder are handled with a different NodeDescription: StakeholderNodeDescriptionProvider. // We can't represent actors with conditional styles because they require to remove inside labels and add // outside labels, and set the keepAspectRatio property, which are all defined in the NodeDescription and not in // the style. - return AQLConstants.AQL + "not self.isActor() and not self.isSubject()"; + return AQLConstants.AQL + "not self.isActor() and not self.isSubject() and not self.isStakeholder()"; } @Override