blob: 1032c07f8673174e97accf48b800e0a8c1fc4413 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2020, 2021 Obeo.
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.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.sirius.diagram.ui.tools.api.format;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.gmf.runtime.diagram.core.preferences.PreferencesHint;
import org.eclipse.gmf.runtime.diagram.ui.editparts.DiagramEditPart;
import org.eclipse.gmf.runtime.diagram.ui.parts.DiagramEditDomain;
import org.eclipse.gmf.runtime.notation.Diagram;
import org.eclipse.gmf.runtime.notation.Edge;
import org.eclipse.gmf.runtime.notation.Node;
import org.eclipse.gmf.runtime.notation.Shape;
import org.eclipse.gmf.runtime.notation.View;
import org.eclipse.sirius.business.api.dialect.DialectManager;
import org.eclipse.sirius.business.api.session.CustomDataConstants;
import org.eclipse.sirius.business.api.session.Session;
import org.eclipse.sirius.common.tools.api.util.EqualityHelper;
import org.eclipse.sirius.common.ui.tools.api.util.EclipseUIUtil;
import org.eclipse.sirius.diagram.DDiagram;
import org.eclipse.sirius.diagram.DDiagramElement;
import org.eclipse.sirius.diagram.DNode;
import org.eclipse.sirius.diagram.DSemanticDiagram;
import org.eclipse.sirius.diagram.DiagramPlugin;
import org.eclipse.sirius.diagram.business.api.diagramtype.DiagramTypeDescriptorRegistry;
import org.eclipse.sirius.diagram.business.api.diagramtype.IDiagramTypeDescriptor;
import org.eclipse.sirius.diagram.business.api.helper.display.DisplayMode;
import org.eclipse.sirius.diagram.business.api.helper.display.DisplayServiceManager;
import org.eclipse.sirius.diagram.business.api.query.DNodeQuery;
import org.eclipse.sirius.diagram.business.api.refresh.CanonicalSynchronizer;
import org.eclipse.sirius.diagram.business.api.refresh.CanonicalSynchronizerFactory;
import org.eclipse.sirius.diagram.business.api.refresh.DiagramCreationUtil;
import org.eclipse.sirius.diagram.business.internal.dialect.NotYetOpenedDiagramAdapter;
import org.eclipse.sirius.diagram.description.Layer;
import org.eclipse.sirius.diagram.description.filter.FilterDescription;
import org.eclipse.sirius.diagram.ui.business.api.helper.graphicalfilters.CompositeFilterApplicationBuilder;
import org.eclipse.sirius.diagram.ui.business.api.view.SiriusGMFHelper;
import org.eclipse.sirius.diagram.ui.business.api.view.SiriusLayoutDataManager;
import org.eclipse.sirius.diagram.ui.provider.Messages;
import org.eclipse.sirius.diagram.ui.tools.api.format.semantic.MappingBasedDiagramContentDuplicationSwitch;
import org.eclipse.sirius.diagram.ui.tools.api.format.semantic.MappingBasedSiriusFormatDataManager;
import org.eclipse.sirius.diagram.ui.tools.api.part.DiagramEditPartService;
import org.eclipse.sirius.diagram.ui.tools.api.util.GMFNotationHelper;
import org.eclipse.sirius.diagram.ui.tools.internal.format.semantic.diagram.util.MappingBasedSiriusFormatManagerFactoryHelper;
import org.eclipse.sirius.viewpoint.DRepresentation;
import org.eclipse.sirius.viewpoint.description.RepresentationDescription;
import org.eclipse.sirius.viewpoint.description.Viewpoint;
import org.eclipse.sirius.viewpoint.provider.SiriusEditPlugin;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Shell;
/**
* A factory for accessing EObject mapping-based format/layout/style application. Calls to the apply methods shall be
* embedded in a {@link Command}.
*
* @author adieumegard
*/
public class MappingBasedSiriusFormatManagerFactory {
/**
* The singleton INSTANCE.
*/
protected static final MappingBasedSiriusFormatManagerFactory INSTANCE = new MappingBasedSiriusFormatManagerFactory();
/**
* The Content duplication switch storing the transformation map.
*/
protected MappingBasedDiagramContentDuplicationSwitch diagramContentDuplicationSwitch;
/**
* The FormatDataManager handling format copy.
*/
protected MappingBasedSiriusFormatDataManager formatDataManager;
/**
* A map containing source diagram to target diagram copied notes and text notes.
*/
protected Map<Node, Node> sourceToTargetNoteMap;
/**
* A boolean stating if the call is done on a sequence diagram. Used for SequenceDiagram-specific format application
* actions.
*/
protected boolean isAppliedOnSequenceDiagram;
/**
* Gives access to the singleton instance of <code>MappingBasedSiriusFormatManagerFactory</code>.
*
* @return the singleton instance
*/
public static MappingBasedSiriusFormatManagerFactory getInstance() {
return INSTANCE;
}
/**
* Apply format on {@code targetDiagram} based on the {@code sourceDiagram}. Format data are applied only for
* diagram elements whose semantic object is provided in the {@code correspondenceMap}.
*
* Calls to this API shall be embedded in a command.
*
* @param sourceSession
* The {@link Session} for the source diagram. Must not be null.
* @param sourceDiagram
* The source diagram. Must not be null.
* @param correspondenceMap
* The mapping function between source diagram elements and target diagram elements Must not be null. In
* the case where {@code sourceDiagram} is a Sequence diagram, must provide a mapping for each semantic
* element of {@code sourceDiagram}.
* @param targetSession
* The {@link Session} for the target diagram. Must not be null.
* @param targetDiagram
* The target diagram. Must not be null.
* @param copyNotes
* Whether or not to copy source diagram notes to target diagram.
* @return The target diagram.
*/
public DDiagram applyFormatOnDiagram(Session sourceSession, DDiagram sourceDiagram, final Map<EObject, EObject> correspondenceMap, Session targetSession, DDiagram targetDiagram,
boolean copyNotes) {
// Check application correction
checkApplyFormatOnDiagramCallCorrection(sourceDiagram, correspondenceMap, targetDiagram, sourceSession, targetSession);
// Apply format according to map
applyFormatAccordingToMap(sourceSession, sourceDiagram, correspondenceMap, targetSession, targetDiagram, copyNotes);
return targetDiagram;
}
/**
* Apply format on a new {@link DDiagram} for name {@code targetDiagramName} based on the {@code sourceDiagram}.
* Format data are applied only for diagram elements whose semantic object is provided in the
* {@code correspondenceMap}.
*
* Calls to this API shall be embedded in a command.
*
* @param sourceSession
* The {@link Session} for the source diagram. Must not be null.
* @param sourceDiagram
* The source diagram. Must not be null.
* @param correspondenceMap
* The mapping function between source diagram elements and target diagram elements. Must not be null. In
* the case where {@code sourceDiagram} is a Sequence diagram, must provide a mapping for each semantic
* element of {@code sourceDiagram}.
* @param targetSession
* The {@link Session} for the target diagram. Must not be null.
* @param targetDiagramName
* The target diagram name. Must not be null or equal to "".
* @param targetDiagramRoot
* The root EObject for the new diagram. Must not be null.
* @param copyNotes
* Whether or not to copy source diagram notes to target diagram.
* @return The created target diagram.
*/
public DDiagram applyFormatOnNewDiagram(Session sourceSession, DDiagram sourceDiagram, final Map<EObject, EObject> correspondenceMap, Session targetSession, String targetDiagramName,
EObject targetDiagramRoot, boolean copyNotes) {
// Check application correction
checkApplyFormatOnNewDiagramCallCorrection(sourceDiagram, correspondenceMap, targetDiagramName, sourceSession, targetSession, targetDiagramRoot);
DSemanticDiagram targetDiagram = createRepresentation(sourceDiagram, targetSession, targetDiagramName, targetDiagramRoot);
if (targetDiagram != null) {
// Apply format according to map
applyFormatAccordingToMap(sourceSession, sourceDiagram, correspondenceMap, targetSession, targetDiagram, copyNotes);
return targetDiagram;
}
return null;
}
/**
* This is the core of the format application API.
*
* @param sourceSession
* The {@link Session} for the source diagram. Must not be null.
* @param sourceDiagram
* The source diagram. Must not be null.
* @param correspondenceMap
* The mapping function between source diagram elements and target diagram elements. Must not be null. In
* the case where {@code sourceDiagram} is a Sequence diagram, must provide a mapping for each semantic
* element of {@code sourceDiagram}.
* @param targetSession
* The {@link Session} for the target diagram. Must not be null.
* @param targetDiagramName
* The target diagram name. Must not be null or equal to "".
* @param copyNotes
* Whether or not to copy source diagram notes to target diagram.
*/
private void applyFormatAccordingToMap(Session sourceSession, DDiagram sourceDiagram, final Map<EObject, EObject> correspondenceMap, Session targetSession, DDiagram targetDiagram,
boolean copyNotes) {
diagramContentDuplicationSwitch = new MappingBasedDiagramContentDuplicationSwitch((DSemanticDiagram) targetDiagram, correspondenceMap, targetSession);
diagramContentDuplicationSwitch.doSwitch(sourceDiagram);
// Apply filters and layers state on target diagram
applyFiltersAndLayersState(sourceDiagram, targetDiagram);
synchronizeTargetDiagram(targetSession, (DSemanticDiagram) targetDiagram);
DiagramEditPart sourceDiagramEditPart = null;
DiagramEditPart targetDiagramEditPart = null;
Collection<DiagramEditPart> sourceDiagramEditParts = null;
Collection<DiagramEditPart> targetDiagramEditParts = null;
try {
sourceDiagramEditParts = getDiagramEditPart(sourceSession, sourceDiagram);
targetDiagramEditParts = getDiagramEditPart(targetSession, targetDiagram);
if (!sourceDiagramEditParts.isEmpty() && !targetDiagramEditParts.isEmpty()) {
sourceDiagramEditPart = sourceDiagramEditParts.stream().findFirst().get();
targetDiagramEditPart = targetDiagramEditParts.stream().findFirst().get();
// Apply format according to map
applyFormatOnDiagram(sourceDiagramEditPart, correspondenceMap, targetDiagramEditPart);
synchronizeTargetDiagram(targetSession, (DSemanticDiagram) targetDiagram);
// Copy notes if asked to
if (copyNotes) {
copyNotes(sourceDiagram, targetDiagram, targetSession);
synchronizeTargetDiagram(targetSession, (DSemanticDiagram) targetDiagram);
}
MappingBasedSiriusFormatManagerFactoryHelper.applyNodeDepthPositions(sourceDiagram, targetDiagram, copyNotes, diagramContentDuplicationSwitch, sourceToTargetNoteMap);
synchronizeTargetDiagram(targetSession, (DSemanticDiagram) targetDiagram);
}
} finally {
// Several org.eclipse.draw2d.DeferredUpdateManager (UpdateRequest) could be launched, in async, during the
// paste process.
// We should execute them to avoid potential "Widget is disposed" during the cleanAndDispose.
EclipseUIUtil.synchronizeWithUIThread();
// Even if in theory, only one diagram is created, the above code can create several DiagramEditParts for
// source and target, so we clean all of them and not directly sourceDiagramEditPart and
// targetDiagramEditPart
if (sourceDiagramEditParts != null) {
sourceDiagramEditParts.stream().forEach(dep -> cleanAndDispose(dep));
}
if (targetDiagramEditParts != null) {
targetDiagramEditParts.stream().forEach(dep -> cleanAndDispose(dep));
}
}
}
private void applyFiltersAndLayersState(DDiagram sourceDiagram, DDiagram targetDiagram) {
DisplayServiceManager.INSTANCE.getDisplayService().refreshAllElementsVisibility(targetDiagram);
Optional<ResourceSet> optionalTargetResourceSet = Optional.ofNullable(targetDiagram).map(EObject::eResource).map(Resource::getResourceSet);
if (optionalTargetResourceSet.isPresent()) {
ResourceSet targetResourceSet = optionalTargetResourceSet.get();
sourceDiagram.getActivatedLayers().forEach(layer -> {
EObject targetLayer = targetResourceSet.getEObject(EcoreUtil.getURI(layer), false);
if (targetLayer instanceof Layer) {
targetDiagram.getActivatedLayers().add((Layer) targetLayer);
}
});
targetDiagram.getActivatedFilters().clear();
sourceDiagram.getActivatedFilters().forEach(filter -> {
EObject targetFilter = targetResourceSet.getEObject(EcoreUtil.getURI(filter), false);
if (targetFilter instanceof FilterDescription) {
targetDiagram.getActivatedFilters().add((FilterDescription) targetFilter);
}
});
// Execute same code as in
// org.eclipse.sirius.diagram.ui.business.internal.command.RefreshDiagramOnOpeningCommand.doExecute()
if (targetDiagram.getActivatedFilters().size() != 0) {
CompositeFilterApplicationBuilder builder = new CompositeFilterApplicationBuilder(targetDiagram);
builder.computeCompositeFilterApplications();
}
if (DisplayMode.NORMAL.equals(DisplayServiceManager.INSTANCE.getMode())) {
DisplayServiceManager.INSTANCE.getDisplayService().refreshAllElementsVisibility(targetDiagram);
}
}
}
/**
* Checks
* {@link MappingBasedSiriusFormatManagerFactory#applyFormatOnDiagram(Session, DDiagram, Map, Session, DDiagram, boolean)}
* call arguments.
*
* @param sourceDiagram
* The source diagram.
* @param correspondenceMap
* The correspondence map.
* @param targetDiagram
* The target diagram.
* @param targetSession
* The source diagram session.
* @param sourceSession
* The target diagram session.
*/
private void checkApplyFormatOnDiagramCallCorrection(DDiagram sourceDiagram, final Map<EObject, EObject> correspondenceMap, DDiagram targetDiagram, Session sourceSession, Session targetSession) {
checkDiagram(sourceDiagram);
checkDiagram(targetDiagram);
checkSourceDiagramVSTargetDiagram(sourceDiagram, targetDiagram);
checkSourceAndTargetSessions(sourceSession, targetSession);
isAppliedOnSequenceDiagram = computeIsSequenceDiagram(sourceDiagram);
checkMapSourceCorrection(sourceDiagram, correspondenceMap);
}
private void checkDiagram(DDiagram diagram) {
if (diagram == null) {
throw new IllegalArgumentException(Messages.MappingBasedSiriusFormatManagerFactory_ErrorDiagramIsNull);
}
}
private void checkSourceDiagramVSTargetDiagram(DDiagram sourceDiagram, DDiagram targetDiagram) {
if (sourceDiagram.equals(targetDiagram)) {
throw new IllegalArgumentException(Messages.MappingBasedSiriusFormatManagerFactory_ErrorSourceAndTargetDiagramsAreTheSame);
}
if (!EqualityHelper.areEquals(sourceDiagram.getDescription(), targetDiagram.getDescription())) {
throw new IllegalArgumentException(MessageFormat.format(Messages.MappingBasedSiriusFormatManagerFactory_ErrorSourceAndTargetDiagramDecriptionsDoesNotMatch,
sourceDiagram.getDescription().getName(), targetDiagram.getDescription().getName(), sourceDiagram.getDescription(), targetDiagram.getDescription()));
}
}
private void checkSourceAndTargetSessions(Session sourceSession, Session targetSession) {
if (sourceSession == null || targetSession == null) {
throw new IllegalArgumentException(Messages.MappingBasedSiriusFormatManagerFactory_ErrorSourceAndOrTargetSessionsNull);
}
}
/**
* Checks
* {@link MappingBasedSiriusFormatManagerFactory#applyFormatOnNewDiagram(Session, DDiagram, Map, Session, String, EObject, boolean)}
* call arguments.
*
* @param sourceDiagram
* The source diagram.
* @param correspondenceMap
* The correspondence map.
* @param targetDiagramName
* The new target diagram name.
* @param targetSession
* The source diagram session.
* @param sourceSession
* The target diagram session.
* @param targetDiagramRoot
*/
private void checkApplyFormatOnNewDiagramCallCorrection(DDiagram sourceDiagram, final Map<EObject, EObject> correspondenceMap, String targetDiagramName, Session sourceSession,
Session targetSession, EObject targetDiagramRoot) {
checkDiagram(sourceDiagram);
if (targetDiagramName == null || targetDiagramName.isEmpty()) {
throw new IllegalArgumentException(Messages.MappingBasedSiriusFormatManagerFactory_ErrorTargetDiagramNameIsEmpty);
}
if (targetDiagramRoot == null) {
throw new IllegalArgumentException(Messages.MappingBasedSiriusFormatManagerFactory_ErrorTargetDiagramRootIsNull);
}
checkSourceAndTargetSessions(sourceSession, targetSession);
isAppliedOnSequenceDiagram = computeIsSequenceDiagram(sourceDiagram);
checkMapSourceCorrection(sourceDiagram, correspondenceMap);
}
/**
* Checks
* {@link MappingBasedSiriusFormatManagerFactory#applyFormatOnNewDiagram(Session, DDiagram, Map, Session, String, EObject, boolean)}
* call Map argument.
*
* @param sourceDiagram
* The source diagram.
* @param correspondenceMap
* The correspondence map to check.
*/
private void checkMapSourceCorrection(DDiagram sourceDiagram, final Map<EObject, EObject> correspondenceMap) {
if (correspondenceMap.isEmpty()) {
throw new IllegalArgumentException(Messages.MappingBasedSiriusFormatManagerFactory_ErrorMappingfunctionIsEmpty);
}
if (isAppliedOnSequenceDiagram) {
List<EObject> allSourceDiagramSemanticElements = sourceDiagram.getDiagramElements().stream().map(DDiagramElement::getTarget).collect(Collectors.toList());
boolean contains = correspondenceMap.keySet().containsAll(allSourceDiagramSemanticElements);
if (!contains) {
throw new IllegalArgumentException(MessageFormat.format(Messages.MappingBasedSiriusFormatManagerFactory_ErrorMappingfunctionIncompleteOnSequenceDiagram, sourceDiagram));
}
}
}
/**
* Synchronize {@code TargetDiagram} at GMF level and handle automatic layout.
*
* @param targetSession
* The session holding the target diagram.
* @param targetDiagram
* The target diagram.
*/
private void synchronizeTargetDiagram(Session targetSession, DSemanticDiagram targetDiagram) {
// We do a Sirius refresh before to keep both Sirius and GMF diagram consistent. Indeed, some DDiagramElements
// might have been created according to the source diagram but for some reasons (inconsistency between the
// target and source semantic model) some Sirius elements might have been removed by the refresh.
DialectManager.INSTANCE.refresh(targetDiagram, new NullProgressMonitor());
// Generate GMF views
final DiagramCreationUtil targetDiagramUtil = new DiagramCreationUtil(targetDiagram);
if (!targetDiagramUtil.findAssociatedGMFDiagram()) {
targetDiagramUtil.createNewGMFDiagram();
}
final Diagram targetGMFDiagram = targetDiagramUtil.getAssociatedGMFDiagram();
if (targetGMFDiagram != null) {
CanonicalSynchronizer canonicalSynchronizer = CanonicalSynchronizerFactory.INSTANCE.createCanonicalSynchronizer(targetGMFDiagram);
canonicalSynchronizer.storeViewsToArrange(false);
canonicalSynchronizer.synchronize();
// Prevent automatic layout
Map<Diagram, Set<View>> viewToArrangeCenter = SiriusLayoutDataManager.INSTANCE.getCreatedViewWithCenterLayout();
Map<Diagram, Set<View>> viewToArrange = SiriusLayoutDataManager.INSTANCE.getCreatedViewsToLayout();
Set<View> set = viewToArrange.get(targetGMFDiagram);
if (set != null) {
set.clear();
}
Set<View> set2 = viewToArrangeCenter.get(targetGMFDiagram);
if (set2 != null) {
set2.clear();
}
}
}
/**
* Initialize a new representation of name {@code targetDiagramName} based on {@code sourceDiagram} and with root
* element {@code targetDiagramRoot}.
*
* @param sourceDiagram
* The representation used as model for creation.
* @param targetSession
* The session holding the new representation.
* @param targetDiagramName
* The name of the new representation.
* @param targetDiagramRoot
* The root element used for initialization of the new representation.
* @return The new representation.
*/
private DSemanticDiagram createRepresentation(DDiagram sourceDiagram, Session targetSession, String targetDiagramName, EObject targetDiagramRoot) {
Collection<Viewpoint> selectedViewpoints = targetSession.getSelectedViewpoints(true);
Collection<RepresentationDescription> descs = DialectManager.INSTANCE.getAvailableRepresentationDescriptions(selectedViewpoints, targetDiagramRoot);
DRepresentation targetRepresentation = null;
String sourceDescName = sourceDiagram.getDescription().getName();
Optional<RepresentationDescription> optDesc = descs.stream().filter(desc -> desc.getName().equals(sourceDescName)).findFirst();
if (optDesc.isPresent()) {
// Create target diagram
targetRepresentation = DialectManager.INSTANCE.createRepresentation(targetDiagramName, targetDiagramRoot, optDesc.get(), targetSession, new NullProgressMonitor());
DSemanticDiagram targetDiagram = (DSemanticDiagram) targetRepresentation;
// Remove NotYetOpenedDiagramAdapter
if (targetDiagram.eAdapters().contains(NotYetOpenedDiagramAdapter.INSTANCE)) {
targetDiagram.eAdapters().remove(NotYetOpenedDiagramAdapter.INSTANCE);
}
return targetDiagram;
} else {
DiagramPlugin.getDefault()
.logError(MessageFormat.format(Messages.MappingBasedSiriusFormatManagerFactory_ImpossibleToSuitableDescription,
descs.stream().map(desc -> desc.getName()).collect(Collectors.joining(", ")), sourceDescName, //$NON-NLS-1$
SiriusEditPlugin.getPlugin().getUiCallback().getSessionNameToDisplayWhileSaving(targetSession)));
}
return null;
}
/**
* Apply format of {@code sourceDiagramEditPart} on {@code targetDiagramEditPart} based on the
* {@code correspondenceMap}. The method is only meant to be overridden and not called as API.
*
* @param sourceDiagramEditPart
* The {@link DiagramEditPart} for the source diagram.
* @param correspondenceMap
* The map between source diagram semantic elements and target diagram semantic elements.
* @param targetDiagramEditPart
* The {@link DiagramEditPart} for the target diagram.
*/
private void applyFormatOnDiagram(DiagramEditPart sourceDiagramEditPart, Map<EObject, EObject> correspondenceMap, DiagramEditPart targetDiagramEditPart) {
formatDataManager = new MappingBasedSiriusFormatDataManager(correspondenceMap);
formatDataManager.storeFormatData(sourceDiagramEditPart);
if (isAppliedOnSequenceDiagram) {
for (Entry<DDiagramElement, DDiagramElement> entry : diagramContentDuplicationSwitch.getSourceDDiagramElementToTargetDDiagramElementMap().entrySet()) {
View sourceGmfView = SiriusGMFHelper.getGmfView(entry.getKey());
View targetGmfView = SiriusGMFHelper.getGmfView(entry.getValue());
if (sourceGmfView instanceof Node && targetGmfView instanceof Node) {
Node sourceNode = (Node) sourceGmfView;
Node targetNode = (Node) targetGmfView;
MappingBasedSiriusFormatManagerFactoryHelper.copyNodeLayout(sourceNode, targetNode);
formatDataManager.copySiriusStyle(entry.getKey(), entry.getValue());
formatDataManager.copyGMFStyle(sourceNode, targetNode);
if (entry.getKey() instanceof DNode && new DNodeQuery((DNode) entry.getKey()).hasLabelOnBorder()) {
MappingBasedSiriusFormatManagerFactoryHelper.copyBorderLabelLayout(sourceNode, targetNode);
}
} else if (sourceGmfView instanceof Edge && targetGmfView instanceof Edge) {
Edge sourceEdge = (Edge) sourceGmfView;
Edge targetEdge = (Edge) targetGmfView;
MappingBasedSiriusFormatManagerFactoryHelper.copyEdgeLayout(sourceEdge, targetEdge);
formatDataManager.copySiriusStyle(entry.getKey(), entry.getValue());
formatDataManager.copyGMFStyle(sourceEdge, targetEdge);
}
}
} else {
formatDataManager.applyFormat(targetDiagramEditPart);
}
}
/**
* Computes whether or not {@code sourceDiagram} is a SequenceDiagram.
*
* @param sourceDiagram
* @return
*/
private boolean computeIsSequenceDiagram(DDiagram sourceDiagram) {
boolean isSequenceDiagram = false;
for (final IDiagramTypeDescriptor diagramTypeDescriptor : DiagramTypeDescriptorRegistry.getInstance().getAllDiagramTypeDescriptors()) {
if (diagramTypeDescriptor.getDiagramDescriptionProvider().handles(sourceDiagram.getDescription().eClass().getEPackage())) {
isSequenceDiagram = diagramTypeDescriptor.getDiagramDescriptionProvider().requiresNonStandardFormatDataManagers();
break;
}
}
return isSequenceDiagram;
}
/**
* Get all {@link DiagramEditPart} elements registered in {@code session} whose representation matches
* {@code representation}.
*
* @param session
* @param representation
* @return
*/
private Collection<DiagramEditPart> getDiagramEditPart(Session session, DRepresentation representation) {
final List<DiagramEditPart> result = new ArrayList<DiagramEditPart>();
final Collection<EObject> data = session.getServices().getCustomData(CustomDataConstants.GMF_DIAGRAMS, representation);
// Create a new shell for the diagramEditPart, it will be disposed later in cleanAndDispose(DiagramEditPart).
Shell shell = new Shell();
for (final EObject dataElement : data) {
if (dataElement instanceof Diagram) {
final Diagram diagram = (Diagram) dataElement;
final DiagramEditPartService tool = new DiagramEditPartService();
final DiagramEditPart diagramEditPart = tool.createDiagramEditPart(diagram, shell, PreferencesHint.USE_DEFAULTS);
result.add(diagramEditPart);
}
}
return result;
}
/**
* Copy GMF notes of {@code sourceDiagram} onto {@code targetDiagram}.
*
* @param sourceDiagram
* The diagram to copy notes from.
* @param targetDiagram
* The diagram to copy notes to.
* @param targetSession
* The session for the target diagram.
* @param diagramContentDuplicationSwitch
*/
private void copyNotes(DDiagram sourceDiagram, DDiagram targetDiagram, Session targetSession) {
// Get GMF diagrams
final Diagram sourceGMFDiagram = MappingBasedSiriusFormatManagerFactoryHelper.getGMFDiagram(sourceDiagram);
final Diagram targetGMFDiagram = MappingBasedSiriusFormatManagerFactoryHelper.getGMFDiagram(targetDiagram);
// Initialize and clear sourceToTargetNoteMap
if (sourceToTargetNoteMap == null) {
sourceToTargetNoteMap = new HashMap<Node, Node>();
}
sourceToTargetNoteMap.clear();
// Get all notes
Collection<Node> sourceNotes = GMFNotationHelper.getNotes(sourceGMFDiagram);
Collection<Node> targetNotes = GMFNotationHelper.getNotes(targetGMFDiagram);
// Duplicate notes into target diagram and apply source note style
sourceNotes.forEach(sourceNote -> {
if (sourceNote instanceof Shape && ((Shape) sourceNote).getDescription() != null) {
String labelOfNote = ((Shape) sourceNote).getDescription();
Optional<Node> existingTargetNote = search(targetNotes, (Shape) sourceNote, labelOfNote);
Node targetNote;
if (existingTargetNote.isPresent()) {
targetNote = existingTargetNote.get();
} else {
targetNote = GMFNotationHelper.createNote(targetGMFDiagram, GMFNotationHelper.getNoteDescription(sourceNote));
}
targetNote.setLayoutConstraint(EcoreUtil.copy(sourceNote.getLayoutConstraint()));
if (sourceNote.isSetElement()) {
targetNote.setElement(sourceNote.getElement());
}
formatDataManager.copyGMFStyle(sourceNote, targetNote);
sourceToTargetNoteMap.put(sourceNote, targetNote);
}
});
// Get all texts
Collection<Node> sourceTexts = GMFNotationHelper.getTextNotes(sourceGMFDiagram);
Collection<Node> targetTexts = GMFNotationHelper.getTextNotes(targetGMFDiagram);
// Duplicate texts into target diagram and apply source text style
sourceTexts.forEach(sourceText -> {
View targetParentNode = null;
if (sourceText.eContainer().equals(sourceGMFDiagram)) {
targetParentNode = targetGMFDiagram;
} else {
targetParentNode = MappingBasedSiriusFormatManagerFactoryHelper.getTargetDiagramTextNoteContainer(sourceText, diagramContentDuplicationSwitch);
}
if (targetParentNode == null) {
DiagramPlugin.getDefault().logInfo(MessageFormat.format(Messages.MappingBasedSiriusFormatManagerFactory_ImpossibleToFindTargetTextNoteContainer, sourceText));
return;
}
if (sourceText instanceof Shape && ((Shape) sourceText).getDescription() != null) {
String labelOfText = ((Shape) sourceText).getDescription();
Optional<Node> existingTargetText = search(targetTexts, (Shape) sourceText, labelOfText);
Node targetText;
if (existingTargetText.isPresent()) {
targetText = existingTargetText.get();
} else {
targetText = GMFNotationHelper.createTextNote(targetParentNode, labelOfText);
}
targetText.setLayoutConstraint(EcoreUtil.copy(sourceText.getLayoutConstraint()));
if (sourceText.isSetElement()) {
targetText.setElement(sourceText.getElement());
}
formatDataManager.copyGMFStyle(sourceText, targetText);
sourceToTargetNoteMap.put(sourceText, targetText);
}
});
// Duplicate note attachments if possible
Collection<Edge> notesAttachments = GMFNotationHelper.getNotesAttachments(sourceGMFDiagram);
notesAttachments.forEach(attach -> {
View nodeAttachment = attach.getSource();
Boolean noteIsSource = true;
if (!isNoteOrText(nodeAttachment)) {
nodeAttachment = attach.getTarget();
noteIsSource = false;
}
MappingBasedSiriusFormatManagerFactoryHelper.duplicateNoteAttachment(attach, sourceToTargetNoteMap.get(nodeAttachment), targetSession, noteIsSource, diagramContentDuplicationSwitch,
sourceToTargetNoteMap, formatDataManager, targetGMFDiagram);
});
}
private boolean isNoteOrText(View nodeAttachment) {
return nodeAttachment instanceof Node && (GMFNotationHelper.isNote((Node) nodeAttachment) || GMFNotationHelper.isTextNote((Node) nodeAttachment));
}
/**
* Search in <code>nodes</code> a Node (Text or Note) with the same label (<code>searchedLabel</code>) and the same
* attachments than the <code>sourceNode</code>.
*
* @param nodes
* List in which to search
* @param sourceNode
* The node to compare attachments with.
* @param searchedLabel
* The searched label
* @return An optional with the corresponding node if any.
*/
private Optional<Node> search(Collection<Node> nodes, Shape sourceNode, String searchedLabel) {
List<Node> matchingNodes = new ArrayList<Node>();
// Get the nodes with same description (same label) as the <code>searchedLabel</code>
for (Node node : nodes) {
if (node instanceof Shape) {
if (searchedLabel.equals(((Shape) node).getDescription())) {
matchingNodes.add(node);
}
}
}
// Remove elements that have not the same attachment on source side
matchingNodes = removeNotSameAttachment(sourceNode, matchingNodes, true);
// Remove elements that have not the same attachment on target side
matchingNodes = removeNotSameAttachment(sourceNode, matchingNodes, false);
// Only the first is considered. At this step, if several nodes remain, we are in a case not handled by the
// Copy/Paste format API.
return matchingNodes.stream().findFirst();
}
/**
* Search in <code>nodesCandidates</code> which candidate has the same source/target attachments.
*
* @param nodeToCompareWith
* the node to use as comparator
* @param nodesCandidates
* list of nodes
* @param testSource
* true if the attachment on source side must bu tested, false if the attachment on target side must be
* tested.
* @return a sub list of nodesCandidates
*/
protected List<Node> removeNotSameAttachment(Shape nodeToCompareWith, List<Node> nodesCandidates, boolean testSource) {
List<Node> matchingNodes;
boolean hasAttachment = testSource ? nodeToCompareWith.getSourceEdges().size() != 0 : nodeToCompareWith.getTargetEdges().size() != 0;
if (!hasAttachment) {
// Select only nodes without source/target attachment
if (testSource) {
matchingNodes = nodesCandidates.stream().filter(node -> node.getSourceEdges().size() == 0).collect(Collectors.toList());
} else {
matchingNodes = nodesCandidates.stream().filter(node -> node.getTargetEdges().size() == 0).collect(Collectors.toList());
}
} else {
// Select nodes that have the same source/target attachments
matchingNodes = new ArrayList<Node>(nodesCandidates);
for (Iterator<Node> iterator = matchingNodes.iterator(); iterator.hasNext(); /* */) {
Node node = iterator.next();
boolean sameAttachments = true;
for (Iterator<Edge> edgesIterators = testSource ? ((List<Edge>) nodeToCompareWith.getSourceEdges()).iterator()
: ((List<Edge>) nodeToCompareWith.getTargetEdges()).iterator(); edgesIterators.hasNext(); /* */ ) {
Edge e = edgesIterators.next();
// Get the target DDiagramElement corresponding to the copy of the other extremity of the
// NoteAttachment.
EObject otherExtremityElement = testSource ? e.getTarget().getElement() : e.getSource().getElement();
DDiagramElement targetDiagramElement = diagramContentDuplicationSwitch.getSourceDDiagramElementToTargetDDiagramElementMap().get(otherExtremityElement);
if (targetDiagramElement != null && targetDiagramElement.eResource() != null) {
if (testSource) {
sameAttachments = sameAttachments && node.getSourceEdges().stream().anyMatch(edge -> ((Edge) edge).getTarget().getElement().equals(targetDiagramElement));
} else {
sameAttachments = sameAttachments && node.getTargetEdges().stream().anyMatch(edge -> ((Edge) edge).getSource().getElement().equals(targetDiagramElement));
}
} else {
// Here, either the target of the note attachment is not present in the target diagram or
// the source/target semantic has not been added to the semantic map while source element
// being represented by a synchronized mapping.
DiagramPlugin.getDefault().logInfo(MessageFormat.format(Messages.MappingBasedSiriusFormatManagerFactory_ImpossibleToCopyNoteInNonExistingOrUnreachableTarget, e.getTarget()));
}
}
if (!sameAttachments) {
iterator.remove();
}
}
}
return matchingNodes;
}
/**
* Clean {@code diagramEditPart} element to avoid memory leaks.
*
* @param diagramEditPart
* The diagram edit part to dispose and to use to also dispose associated elements.
*/
private void cleanAndDispose(DiagramEditPart diagramEditPart) {
if (diagramEditPart != null) {
// Clean to avoid memory leaks
diagramEditPart.deactivate();
// Memory leak : also disposing the
// DiagramGraphicalViewer associated to this
// DiagramEditPart
diagramEditPart.getViewer().flush();
diagramEditPart.getViewer().getEditDomain().getCommandStack().flush();
Control control = diagramEditPart.getViewer().getControl();
if (control.getParent() != null) {
// Dispose the shell created in method getDiagramEditPart(Session, DRepresentation).
control.getParent().dispose();
} else {
// This code should not occurred, but it's just to be sure.
control.isDisposed();
}
((DiagramEditDomain) diagramEditPart.getViewer().getEditDomain()).removeViewer(diagramEditPart.getViewer());
}
}
}