diff --git a/core/plugins/org.polarsys.capella.core.data.migration/plugin.xml b/core/plugins/org.polarsys.capella.core.data.migration/plugin.xml
index 45aba9831b..9ab99b4d1e 100644
--- a/core/plugins/org.polarsys.capella.core.data.migration/plugin.xml
+++ b/core/plugins/org.polarsys.capella.core.data.migration/plugin.xml
@@ -201,6 +201,9 @@
+
+
diff --git a/core/plugins/org.polarsys.capella.core.data.migration/src/org/polarsys/capella/core/data/migration/MigrationConstants.java b/core/plugins/org.polarsys.capella.core.data.migration/src/org/polarsys/capella/core/data/migration/MigrationConstants.java
index 89a80deb8f..6e82bb97fe 100644
--- a/core/plugins/org.polarsys.capella.core.data.migration/src/org/polarsys/capella/core/data/migration/MigrationConstants.java
+++ b/core/plugins/org.polarsys.capella.core.data.migration/src/org/polarsys/capella/core/data/migration/MigrationConstants.java
@@ -139,8 +139,9 @@ public class MigrationConstants {
public static final String MIGRATION_KIND__VIEWPOINT = "MIGRATION_KIND__VIEWPOINT";
public static final String MIGRATION_KIND__IMAGE_DEPENDENCIES = "MIGRATION_KIND__IMAGE_DEPENDENCIES";
public static final String MIGRATION_KIND__AUTOSIZE_SQUARE_STYLE = "MIGRATION_KIND__AUTOSIZE_SQUARE_STYLE";
+ public static final String MIGRATION_KIND__ASSOCIATION_CDB = "MIGRATION_KIND__ASSOCIATION_CDB";
public static final String[] DEFAULT_KIND_ORDER = { MIGRATION_KIND__CHECK_MISSING_VP, MIGRATION_KIND__PATTERN,
MIGRATION_KIND__SEMANTIC, MIGRATION_KIND__DIAGRAM, MIGRATION_KIND__FCDDIAGRAM, MIGRATION_KIND__AFM,
- MIGRATION_KIND__VIEWPOINT, MIGRATION_KIND__SCF, MIGRATION_KIND__IMAGE_DEPENDENCIES, MIGRATION_KIND__AUTOSIZE_SQUARE_STYLE };
+ MIGRATION_KIND__VIEWPOINT, MIGRATION_KIND__SCF, MIGRATION_KIND__IMAGE_DEPENDENCIES, MIGRATION_KIND__AUTOSIZE_SQUARE_STYLE, MIGRATION_KIND__ASSOCIATION_CDB };
}
diff --git a/core/plugins/org.polarsys.capella.core.data.migration/src/org/polarsys/capella/core/data/migration/aird/AssociationCDBMigrationContributor.java b/core/plugins/org.polarsys.capella.core.data.migration/src/org/polarsys/capella/core/data/migration/aird/AssociationCDBMigrationContributor.java
new file mode 100644
index 0000000000..5abd558584
--- /dev/null
+++ b/core/plugins/org.polarsys.capella.core.data.migration/src/org/polarsys/capella/core/data/migration/aird/AssociationCDBMigrationContributor.java
@@ -0,0 +1,376 @@
+/*******************************************************************************
+ * Copyright (c) 2024 THALES GLOBAL SERVICES.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *
+ * Contributors:
+ * Thales - initial API and implementation
+ *******************************************************************************/
+package org.polarsys.capella.core.data.migration.aird;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collector;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.draw2d.geometry.Point;
+import org.eclipse.draw2d.geometry.PointList;
+import org.eclipse.emf.common.util.URI;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.transaction.RecordingCommand;
+import org.eclipse.emf.transaction.TransactionalEditingDomain;
+import org.eclipse.gmf.runtime.diagram.ui.internal.util.LabelViewConstants;
+import org.eclipse.gmf.runtime.draw2d.ui.geometry.PointListUtilities;
+import org.eclipse.gmf.runtime.notation.Anchor;
+import org.eclipse.gmf.runtime.notation.Bounds;
+import org.eclipse.gmf.runtime.notation.Edge;
+import org.eclipse.gmf.runtime.notation.Node;
+import org.eclipse.gmf.runtime.notation.RelativeBendpoints;
+import org.eclipse.gmf.runtime.notation.View;
+import org.eclipse.gmf.runtime.notation.datatype.RelativeBendpoint;
+import org.eclipse.sirius.business.api.session.Session;
+import org.eclipse.sirius.business.api.session.SessionManager;
+import org.eclipse.sirius.common.tools.api.interpreter.IInterpreter;
+import org.eclipse.sirius.common.tools.api.interpreter.IInterpreterSiriusVariables;
+import org.eclipse.sirius.diagram.DDiagram;
+import org.eclipse.sirius.diagram.DEdge;
+import org.eclipse.sirius.diagram.DiagramPackage;
+import org.eclipse.sirius.diagram.EdgeArrows;
+import org.eclipse.sirius.diagram.EdgeTarget;
+import org.eclipse.sirius.diagram.business.internal.metamodel.helper.MappingWithInterpreterHelper;
+import org.eclipse.sirius.diagram.description.EdgeMapping;
+import org.eclipse.sirius.diagram.ui.business.api.view.SiriusGMFHelper;
+import org.eclipse.sirius.diagram.ui.internal.edit.parts.DEdgeBeginNameEditPart;
+import org.eclipse.sirius.diagram.ui.internal.edit.parts.DEdgeEndNameEditPart;
+import org.eclipse.sirius.diagram.ui.internal.edit.parts.DEdgeNameEditPart;
+import org.eclipse.sirius.diagram.ui.internal.edit.parts.locator.EdgeLabelQuery;
+import org.eclipse.sirius.diagram.ui.part.SiriusVisualIDRegistry;
+import org.eclipse.sirius.viewpoint.DSemanticDecorator;
+import org.eclipse.sirius.viewpoint.description.style.StyleDescription;
+import org.polarsys.capella.common.data.modellingcore.AbstractType;
+import org.polarsys.capella.core.data.information.Association;
+import org.polarsys.capella.core.data.information.Property;
+import org.polarsys.capella.core.data.migration.MigrationConstants;
+import org.polarsys.capella.core.data.migration.MigrationRunnable;
+import org.polarsys.capella.core.data.migration.context.MigrationContext;
+import org.polarsys.capella.core.diagram.helpers.naming.DiagramDescriptionConstants;
+import org.polarsys.capella.core.sirius.analysis.DiagramServices;
+import org.polarsys.capella.core.sirius.analysis.IMappingNameConstants;
+import org.polarsys.capella.core.sirius.analysis.InformationServices;
+
+/**
+ *
+ * This class migrates the association of the CDB diagram to always have the same source and target.
+ *
+ * @author Séraphin Costa
+ *
+ */
+public class AssociationCDBMigrationContributor extends AirdMigrationContributor {
+
+ private boolean isClassDiagramBlank(DDiagram diagram) {
+ String diagramName = DiagramDescriptionConstants.CLASS_BLANK_DIAGRAM_NAME;
+ return diagram.getDescription().getName().equalsIgnoreCase(diagramName);
+ }
+
+ private boolean isAssociation(DDiagram diagram, DEdge edge) {
+ String mappingName = IMappingNameConstants.CDB_ASSOCIATION_MAPPING_NAME;
+ EdgeMapping mapping = DiagramServices.getDiagramServices().getEdgeMapping(diagram, mappingName);
+ if (mapping == null) {
+ return false;
+ } else {
+ return DiagramServices.getDiagramServices().isMapping(edge, mapping);
+ }
+ }
+
+ private void migrateDEdgeStyle(DEdge dEdge) {
+ Session.of(dEdge).ifPresent(session -> {
+ EObject containerVariable;
+ if (dEdge.eContainer() instanceof DSemanticDecorator dEdgeContainer) {
+ containerVariable = dEdgeContainer.getTarget();
+ } else {
+ containerVariable = null;
+ }
+ IInterpreter interpreter = session.getInterpreter();
+ interpreter.setVariable(IInterpreterSiriusVariables.DIAGRAM, dEdge.getParentDiagram());
+ interpreter.setVariable(IInterpreterSiriusVariables.VIEW, dEdge);
+ interpreter.setVariable(IInterpreterSiriusVariables.SOURCE_VIEW, dEdge.getSourceNode());
+ interpreter.setVariable(IInterpreterSiriusVariables.TARGET_VIEW, dEdge.getTargetNode());
+
+ StyleDescription styleDescription = new MappingWithInterpreterHelper(interpreter).getBestStyleDescription(
+ dEdge.getDiagramElementMapping(), dEdge.getTarget(), dEdge, containerVariable, dEdge.getParentDiagram());
+
+ interpreter.unSetVariable(IInterpreterSiriusVariables.DIAGRAM);
+ interpreter.unSetVariable(IInterpreterSiriusVariables.VIEW);
+ interpreter.unSetVariable(IInterpreterSiriusVariables.SOURCE_VIEW);
+ interpreter.unSetVariable(IInterpreterSiriusVariables.TARGET_VIEW);
+
+ dEdge.getOwnedStyle().setDescription(styleDescription);
+ });
+ }
+
+ private void migrateDEdge(DEdge dEdge) {
+ EdgeTarget sourceNode = dEdge.getSourceNode();
+ EdgeTarget targetNode = dEdge.getTargetNode();
+ String beginLabel = dEdge.getBeginLabel();
+ String endLabel = dEdge.getEndLabel();
+ EdgeArrows sourceArrow = dEdge.getOwnedStyle().getSourceArrow();
+ EdgeArrows targetArrow = dEdge.getOwnedStyle().getTargetArrow();
+ List customFeatures = dEdge.getOwnedStyle().getCustomFeatures();
+
+ List sourceNodeIncommingEdges = sourceNode.getIncomingEdges();
+ List sourceNodeOutgoingEdges = sourceNode.getOutgoingEdges();
+ List targetNodeIncommingEdges = targetNode.getIncomingEdges();
+ List targetNodeOutgoingEdges = targetNode.getOutgoingEdges();
+
+ dEdge.setSourceNode(targetNode);
+ dEdge.setTargetNode(sourceNode);
+ dEdge.setBeginLabel(endLabel);
+ dEdge.setEndLabel(beginLabel);
+ sourceNodeIncommingEdges.add(dEdge);
+ targetNodeOutgoingEdges.add(dEdge);
+ sourceNodeOutgoingEdges.remove(dEdge);
+ targetNodeIncommingEdges.remove(dEdge);
+
+ dEdge.getOwnedStyle().setSourceArrow(targetArrow);
+ dEdge.getOwnedStyle().setTargetArrow(sourceArrow);
+
+ // inverse the custom features sourceArrow and targetArrow
+ String sourceArrowFeatureName = DiagramPackage.eINSTANCE.getEdgeStyle_SourceArrow().getName();
+ String targetArrowFeatureName = DiagramPackage.eINSTANCE.getEdgeStyle_TargetArrow().getName();
+ if (customFeatures.contains(sourceArrowFeatureName) && !customFeatures.contains(targetArrowFeatureName)) {
+ customFeatures.add(targetArrowFeatureName);
+ customFeatures.remove(sourceArrowFeatureName);
+ } else if (!customFeatures.contains(sourceArrowFeatureName) && customFeatures.contains(targetArrowFeatureName)) {
+ customFeatures.add(sourceArrowFeatureName);
+ customFeatures.remove(targetArrowFeatureName);
+ }
+
+ migrateDEdgeStyle(dEdge);
+ }
+
+ private boolean isBeginLabel(Object view) {
+ return view instanceof Node node
+ && SiriusVisualIDRegistry.getVisualID(node.getType()) == DEdgeBeginNameEditPart.VISUAL_ID;
+ }
+
+ private boolean isMiddleLabel(Object view) {
+ return view instanceof Node node
+ && SiriusVisualIDRegistry.getVisualID(node.getType()) == DEdgeNameEditPart.VISUAL_ID;
+ }
+
+ private boolean isEndLabel(Object view) {
+ return view instanceof Node node
+ && SiriusVisualIDRegistry.getVisualID(node.getType()) == DEdgeEndNameEditPart.VISUAL_ID;
+ }
+
+ private Object migrateBendpoint(Object rawBendpoint) {
+ if (rawBendpoint instanceof RelativeBendpoint bp) {
+ return new RelativeBendpoint(bp.getTargetX(), bp.getTargetY(), bp.getSourceX(), bp.getSourceY());
+ } else {
+ return rawBendpoint;
+ }
+ }
+
+ private List> getReversedBendpoints(List> points) {
+ List> newPoints = Arrays.asList(points.stream().map(this::migrateBendpoint).toArray());
+ Collections.reverse(newPoints);
+ return newPoints;
+ }
+
+ private Point getBendpointSourcePosition(Object rawBendpoint) {
+ if (rawBendpoint instanceof RelativeBendpoint bp) {
+ return new Point(bp.getSourceX(), bp.getSourceY());
+ } else {
+ return null;
+ }
+ }
+
+ private Point getBendpointTargetPosition(Object rawBendpoint) {
+ if (rawBendpoint instanceof RelativeBendpoint bp) {
+ return new Point(bp.getTargetX(), bp.getTargetY());
+ } else {
+ return null;
+ }
+ }
+
+ private Collector pointListCollector() {
+ return Collector.of(PointList::new, PointList::addPoint, (list1, list2) -> {
+ list1.addAll(list2);
+ return list1;
+ });
+ }
+
+ private int getLabelLocation(Node label) {
+ switch (SiriusVisualIDRegistry.getVisualID(label)) {
+ case DEdgeBeginNameEditPart.VISUAL_ID:
+ return LabelViewConstants.SOURCE_LOCATION;
+ case DEdgeEndNameEditPart.VISUAL_ID:
+ return LabelViewConstants.TARGET_LOCATION;
+ case DEdgeNameEditPart.VISUAL_ID:
+ default:
+ return LabelViewConstants.MIDDLE_LOCATION;
+ }
+ }
+
+ private int getLabelReverseLocation(Node label) {
+ switch (SiriusVisualIDRegistry.getVisualID(label)) {
+ case DEdgeBeginNameEditPart.VISUAL_ID:
+ return LabelViewConstants.TARGET_LOCATION;
+ case DEdgeEndNameEditPart.VISUAL_ID:
+ return LabelViewConstants.SOURCE_LOCATION;
+ case DEdgeNameEditPart.VISUAL_ID:
+ default:
+ return LabelViewConstants.MIDDLE_LOCATION;
+ }
+ }
+
+ private Point getNewLabelPosition(Node label, Bounds bounds, List> bendPoints, List> revBendPoints) {
+ Point labelOffset = new Point(bounds.getX(), bounds.getY());
+
+ PointList bendPointList = bendPoints.stream().map(this::getBendpointTargetPosition).collect(pointListCollector());
+ PointList newBendPointList = revBendPoints.stream().map(this::getBendpointSourcePosition)
+ .collect(pointListCollector());
+
+ int referenceLocation = getLabelLocation(label);
+ int newReferenceLocation = getLabelReverseLocation(label);
+
+ Point referencePoint = PointListUtilities.calculatePointRelativeToLine(bendPointList, 0, referenceLocation, true);
+ Point newReferencePoint = PointListUtilities.calculatePointRelativeToLine(newBendPointList, 0, newReferenceLocation,
+ true);
+
+ Point labelCenter = EdgeLabelQuery.relativeCenterCoordinateFromOffset(bendPointList, referencePoint, labelOffset);
+ return EdgeLabelQuery.offsetFromRelativeCoordinate(labelCenter, newBendPointList, newReferencePoint);
+ }
+
+ private void migrateLabelPosition(Node label, List> bendPoints, List> revBendPoints) {
+ Bounds bounds = (Bounds) label.getLayoutConstraint();
+ Point newPosition = getNewLabelPosition(label, bounds, bendPoints, revBendPoints);
+ bounds.setX(newPosition.x());
+ bounds.setY(newPosition.y());
+ }
+
+ @SuppressWarnings("unchecked")
+ private void migrateGMFEdge(Edge gmfEdge) {
+ // source/target, anchors, bendpoints
+ View sourceView = gmfEdge.getSource();
+ View targetView = gmfEdge.getTarget();
+ List sourceViewSourceEdges = sourceView.getSourceEdges();
+ List sourceViewTargetEdges = sourceView.getTargetEdges();
+ List targetViewSourceEdges = targetView.getSourceEdges();
+ List targetViewTargetEdges = targetView.getTargetEdges();
+ Anchor sourceAnchor = gmfEdge.getSourceAnchor();
+ Anchor targetAnchor = gmfEdge.getTargetAnchor();
+ RelativeBendpoints bendpoints = (RelativeBendpoints) gmfEdge.getBendpoints();
+ List> bendpointsList = bendpoints.getPoints();
+ List> revBendpointsList = getReversedBendpoints(bendpointsList);
+
+ // labels
+ List> edgeChildren = gmfEdge.getChildren();
+ Optional beginLabel = edgeChildren.stream().filter(this::isBeginLabel).map(Node.class::cast).findAny();
+ Optional middleLabel = edgeChildren.stream().filter(this::isMiddleLabel).map(Node.class::cast).findAny();
+ Optional endLabel = edgeChildren.stream().filter(this::isEndLabel).map(Node.class::cast).findAny();
+
+ // changes
+ beginLabel.ifPresent(label -> migrateLabelPosition(label, bendpointsList, revBendpointsList));
+ middleLabel.ifPresent(label -> migrateLabelPosition(label, bendpointsList, revBendpointsList));
+ endLabel.ifPresent(label -> migrateLabelPosition(label, bendpointsList, revBendpointsList));
+
+ beginLabel.ifPresent(label -> label.setType(SiriusVisualIDRegistry.getType(DEdgeEndNameEditPart.VISUAL_ID)));
+ endLabel.ifPresent(label -> label.setType(SiriusVisualIDRegistry.getType(DEdgeBeginNameEditPart.VISUAL_ID)));
+
+ gmfEdge.setSource(targetView);
+ gmfEdge.setTarget(sourceView);
+ sourceViewTargetEdges.add(gmfEdge);
+ targetViewSourceEdges.add(gmfEdge);
+ sourceViewSourceEdges.remove(gmfEdge);
+ targetViewTargetEdges.remove(gmfEdge);
+
+ gmfEdge.setSourceAnchor(targetAnchor);
+ gmfEdge.setTargetAnchor(sourceAnchor);
+ bendpoints.setPoints(revBendpointsList);
+ }
+
+ private void migrateAssociation(DEdge dEdge) {
+ migrateDEdge(dEdge);
+ migrateGMFEdge(SiriusGMFHelper.getGmfEdge(dEdge));
+ }
+
+ private boolean needMigration(DEdge dEdge) {
+ if (dEdge.getTarget() instanceof Association association) {
+ Property sourceProp = InformationServices.getService().getAssociationSource(association);
+ Property targetProp = InformationServices.getService().getAssociationTarget(association);
+ AbstractType expectedSource = sourceProp.getAbstractType();
+ AbstractType expectedTarget = targetProp.getAbstractType();
+ if (dEdge.getSourceNode() instanceof DSemanticDecorator sourceNode
+ && dEdge.getTargetNode() instanceof DSemanticDecorator targetNode) {
+ EObject sourceObject = sourceNode.getTarget();
+ EObject targetObject = targetNode.getTarget();
+ return sourceObject == expectedTarget && targetObject == expectedSource;
+ } else {
+ // error
+ return false;
+ }
+ } else {
+ // not association
+ return false;
+ }
+ }
+
+ private void migrateClassDiagramBlank(DDiagram diagram) {
+ diagram.getEdges().stream().filter(edge -> isAssociation(diagram, edge)).filter(this::needMigration)
+ .forEach(this::migrateAssociation);
+ }
+
+ private void migrate(Session session) {
+ session.getOwnedViews().stream().flatMap(dView -> dView.getOwnedRepresentationDescriptors().stream())
+ .map(dRepDesc -> dRepDesc.getRepresentation()).filter(DDiagram.class::isInstance).map(DDiagram.class::cast)
+ .filter(AssociationCDBMigrationContributor.this::isClassDiagramBlank)
+ .forEach(AssociationCDBMigrationContributor.this::migrateClassDiagramBlank);
+ }
+
+ @Override
+ public MigrationRunnable getRunnable(IFile file) {
+ return new AirdMigrationRunnable(file) {
+ @Override
+ public String getName() {
+ return Messages.MigrationAction_AssociationCDBMigration;
+ }
+
+ @Override
+ public IStatus run(MigrationContext context, boolean checkVersion) {
+ IFile airdFile = getFile();
+ URI airdURI = URI.createPlatformResourceURI(airdFile.getFullPath().toString(), true);
+ Session session = SessionManager.INSTANCE.getSession(airdURI, new NullProgressMonitor());
+ if (session != null) {
+ session.open(new NullProgressMonitor());
+
+ TransactionalEditingDomain editingDomain = session.getTransactionalEditingDomain();
+ editingDomain.getCommandStack().execute(new RecordingCommand(editingDomain) {
+ @Override
+ protected void doExecute() {
+ migrate(session);
+ }
+ });
+
+ session.save(new NullProgressMonitor());
+ session.close(new NullProgressMonitor());
+ }
+ return Status.OK_STATUS;
+ }
+ };
+ }
+
+ @Override
+ public String getKind() {
+ return MigrationConstants.MIGRATION_KIND__ASSOCIATION_CDB;
+ }
+}
diff --git a/core/plugins/org.polarsys.capella.core.data.migration/src/org/polarsys/capella/core/data/migration/aird/Messages.java b/core/plugins/org.polarsys.capella.core.data.migration/src/org/polarsys/capella/core/data/migration/aird/Messages.java
index 784f4f1187..caf4818cdc 100644
--- a/core/plugins/org.polarsys.capella.core.data.migration/src/org/polarsys/capella/core/data/migration/aird/Messages.java
+++ b/core/plugins/org.polarsys.capella.core.data.migration/src/org/polarsys/capella/core/data/migration/aird/Messages.java
@@ -24,6 +24,7 @@ public class Messages extends NLS {
public static String MigrationAction_AutoSizeSquareStyleMigration;
public static String MigrationAction_DiagramMigration;
public static String MigrationAction_ImageProjectDependencies;
+ public static String MigrationAction_AssociationCDBMigration;
static {
// initialize resource bundle
diff --git a/core/plugins/org.polarsys.capella.core.data.migration/src/org/polarsys/capella/core/data/migration/aird/messages.properties b/core/plugins/org.polarsys.capella.core.data.migration/src/org/polarsys/capella/core/data/migration/aird/messages.properties
index 3f2c9160c7..d2b167afee 100644
--- a/core/plugins/org.polarsys.capella.core.data.migration/src/org/polarsys/capella/core/data/migration/aird/messages.properties
+++ b/core/plugins/org.polarsys.capella.core.data.migration/src/org/polarsys/capella/core/data/migration/aird/messages.properties
@@ -12,4 +12,5 @@
#===============================================================================
MigrationAction_AutoSizeSquareStyleMigration=Auto-size Square style migration
MigrationAction_DiagramMigration=Diagrams migration
-MigrationAction_ImageProjectDependencies=Image project dependencies migration
\ No newline at end of file
+MigrationAction_ImageProjectDependencies=Image project dependencies migration
+MigrationAction_AssociationCDBMigration=CDB association direction migration
\ No newline at end of file
diff --git a/core/plugins/org.polarsys.capella.core.sirius.analysis/description/common.odesign b/core/plugins/org.polarsys.capella.core.sirius.analysis/description/common.odesign
index adea8097a7..596741cc9d 100644
--- a/core/plugins/org.polarsys.capella.core.sirius.analysis/description/common.odesign
+++ b/core/plugins/org.polarsys.capella.core.sirius.analysis/description/common.odesign
@@ -1868,7 +1868,7 @@
-
-
-
-