PasteAction.java
/**
* Copyright (c) 2004-2025 Carnegie Mellon University and others. (see Contributors file).
* All Rights Reserved.
*
* NO WARRANTY. ALL MATERIAL IS FURNISHED ON AN "AS-IS" BASIS. CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY
* KIND, EITHER EXPRESSED OR IMPLIED, AS TO ANY MATTER INCLUDING, BUT NOT LIMITED TO, WARRANTY OF FITNESS FOR PURPOSE
* OR MERCHANTABILITY, EXCLUSIVITY, OR RESULTS OBTAINED FROM USE OF THE MATERIAL. CARNEGIE MELLON UNIVERSITY DOES NOT
* MAKE ANY WARRANTY OF ANY KIND WITH RESPECT TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
*
* This program and the accompanying materials are made available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
* SPDX-License-Identifier: EPL-2.0
*
* Created, in part, with funding and support from the United States Government. (see Acknowledgments file).
*
* This program includes and/or can make use of certain third party source code, object code, documentation and other
* files ("Third Party Software"). The Third Party Software that is used by this program is dependent upon your system
* configuration. By using this program, You agree to comply with any and all relevant Third Party Software terms and
* conditions contained in any such Third Party Software or separate license file distributed with such Third Party
* Software. The parties who own the Third Party Software ("Third Party Licensors") are intended third party benefici-
* aries to this license with respect to the terms applicable to their Third Party Software. Third Party Software li-
* censes only apply to the Third Party Software and not any other portion of this program or this program as a whole.
*/
package org.osate.ge.internal.ui.editor.actions;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import org.eclipse.e4.core.contexts.EclipseContextFactory;
import org.eclipse.e4.core.contexts.IEclipseContext;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.jface.action.Action;
import org.eclipse.ui.actions.ActionFactory;
import org.eclipse.xtext.resource.XtextResource;
import org.osate.aadl2.AadlPackage;
import org.osate.aadl2.Classifier;
import org.osate.aadl2.ComponentImplementation;
import org.osate.aadl2.ComponentType;
import org.osate.aadl2.ComponentTypeRename;
import org.osate.aadl2.Element;
import org.osate.aadl2.Feature;
import org.osate.aadl2.NamedElement;
import org.osate.aadl2.PackageSection;
import org.osate.aadl2.Subcomponent;
import org.osate.ge.RelativeBusinessObjectReference;
import org.osate.ge.aadl2.AadlImportsUtil;
import org.osate.ge.aadl2.internal.util.AadlNameUtil;
import org.osate.ge.aadl2.internal.util.classifiers.ClassifierCreationHelper;
import org.osate.ge.businessobjecthandling.BusinessObjectHandler;
import org.osate.ge.businessobjecthandling.CanRenameContext;
import org.osate.ge.businessobjecthandling.GetNameContext;
import org.osate.ge.graphics.Point;
import org.osate.ge.internal.diagram.runtime.AgeDiagram;
import org.osate.ge.internal.diagram.runtime.DiagramElement;
import org.osate.ge.internal.diagram.runtime.DiagramModification;
import org.osate.ge.internal.diagram.runtime.DiagramNode;
import org.osate.ge.internal.diagram.runtime.DiagramNodePredicates;
import org.osate.ge.internal.diagram.runtime.layout.DiagramElementLayoutUtil;
import org.osate.ge.internal.model.EmbeddedBusinessObject;
import org.osate.ge.internal.services.AadlModificationService;
import org.osate.ge.internal.services.AadlModificationService.SimpleModifier;
import org.osate.ge.internal.services.ClipboardService;
import org.osate.ge.internal.ui.RenameUtil;
import org.osate.ge.internal.ui.editor.InternalDiagramEditor;
import org.osate.ge.internal.ui.handlers.AgeHandlerUtil;
import org.osate.ge.internal.util.DiagramElementUtil;
import org.osate.ge.services.ReferenceBuilderService;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import com.google.common.base.Predicates;
public class PasteAction extends Action {
private final InternalDiagramEditor editor;
private final ClipboardService.Clipboard clipboard;
public PasteAction(final InternalDiagramEditor editor) {
this.editor = Objects.requireNonNull(editor, "editor must not be null");
setId(ActionFactory.PASTE.getId());
setText("Paste");
// Get clipboard
final Bundle bundle = FrameworkUtil.getBundle(getClass());
final IEclipseContext context = EclipseContextFactory.getServiceContext(bundle.getBundleContext());
this.clipboard = Objects.requireNonNull(context.get(ClipboardService.class), "Unable to get clipboard service")
.getClipboard();
}
@Override
public void run() {
// Get services
final Bundle bundle = FrameworkUtil.getBundle(getClass());
final IEclipseContext context = EclipseContextFactory.getServiceContext(bundle.getBundleContext());
final AadlModificationService aadlModificationService = Objects.requireNonNull(
context.getActive(AadlModificationService.class), "Unable to retrieve AADL modification service");
final ReferenceBuilderService refBuilder = Objects.requireNonNull(
context.getActive(ReferenceBuilderService.class), "Unable to retrieve reference builder service");
// Perform modification
final DiagramNode dstDiagramNode = getDestinationDiagramNode();
final EObject dstBo = getDestinationEObject(dstDiagramNode);
final AgeDiagram diagram = DiagramElementUtil.getDiagram(dstDiagramNode);
final List<DiagramElement> newDiagramElements = new ArrayList<>();
diagram.modify("Paste", m -> {
// The modifier will do the actual copying to the diagram elements. It will also copy the business objects
// if the copied element includes the business object.
final SimpleModifier<EObject> modifier = dstBoToModify -> {
newDiagramElements.addAll(copyClipboardContents(dstBoToModify, dstDiagramNode, m, refBuilder));
};
// If any non-embedded business objects have been copied, then modify the AADL model. Otherwise, just modify the diagram.
final boolean anyHaveNonEmbeddedBo = getCopiedDiagramElements().stream()
.anyMatch(de -> de.getCopiedBusinessObject() instanceof EObject);
if (anyHaveNonEmbeddedBo) {
aadlModificationService.modify(dstBo, modifier);
} else {
modifier.modify(null);
}
// Update the diagram. This will set business objects and update the diagram to be consistent.
editor.getDiagramUpdater().updateDiagram(diagram);
});
// Update selection to match created diagram elements
editor.selectDiagramNodes(newDiagramElements);
}
/**
* Copies the clipboard contents to the destination business object and diagram element.
* @return the diagram elements which were created. Does not include children of created diagram elements.
*/
private Collection<DiagramElement> copyClipboardContents(final EObject dstBoToModify,
final DiagramNode dstDiagramNode, final DiagramModification m, final ReferenceBuilderService refBuilder) {
// Determine the minimum coordinates from the elements whose positions will be copied
// The minimum coordinates is null if none of the copied diagram elements have an absolute position. This is reasonable because the minimum coordinates
// are only needed if a copied diagram element has an absolute position.
final Point minCoordinates = getCopiedDiagramElements().stream().map(CopiedDiagramElement::getAbsolutePosition)
.filter(Predicates.notNull()).reduce((a, b) -> new Point(Math.min(a.x, b.x), Math.min(a.y, b.y)))
.orElse(null);
// This list will contain the diagram elements that are created by the copying process. Does not contain their children.
final List<DiagramElement> newDiagramElements = new ArrayList<>();
// Copy each copied diagram element into the diagram and model.
for (final CopiedDiagramElement copiedDiagramElement : getCopiedDiagramElements()) {
final DiagramElement newDiagramElement;
if (copiedDiagramElement.getCopiedBusinessObject() == null) {
newDiagramElement = CopyAndPasteUtil.copyDiagramElement(copiedDiagramElement.getDiagramElement(),
dstDiagramNode, copiedDiagramElement.getDiagramElement().getRelativeReference(), refBuilder);
} else {
final Object boFromCopiedDiagramElement = copiedDiagramElement.getCopiedBusinessObject();
final RelativeBusinessObjectReference newRelativeRef;
if (boFromCopiedDiagramElement instanceof EmbeddedBusinessObject) {
// The relative reference will be assigned when copying the diagram element.
newRelativeRef = null;
} else if (boFromCopiedDiagramElement instanceof EObject) {
// Get the list that to which the copied object will be added
final EStructuralFeature compatibleFeature = getCompatibleStructuralFeature(
copiedDiagramElement.getContainingFeature(), dstBoToModify.eClass());
final Object containingFeatureValue = dstBoToModify.eGet(compatibleFeature);
if (!(containingFeatureValue instanceof Collection)) {
throw new RuntimeException("Unexpected case. Value of containing feature was not a collection");
}
@SuppressWarnings("unchecked")
final Collection<EObject> containingFeatureValueCollection = (Collection<EObject>) containingFeatureValue;
final EObject copiedEObject = EcoreUtil.copy((EObject) boFromCopiedDiagramElement);
containingFeatureValueCollection.add(copiedEObject);
ensureBusinessObjectHasUniqueName(copiedEObject,
copiedDiagramElement.getDiagramElement().getBusinessObjectHandler());
ensurePackagesAreImported(copiedEObject);
newRelativeRef = refBuilder.getRelativeReference(copiedEObject);
} else {
throw new RuntimeException("Unsupported case: " + boFromCopiedDiagramElement);
}
newDiagramElement = CopyAndPasteUtil.copyDiagramElement(copiedDiagramElement.getDiagramElement(),
dstDiagramNode, newRelativeRef, refBuilder);
}
// Set the position of the new diagram element. They are positioned relative to each other at a fixed offset within the new parent.
final Point cp = copiedDiagramElement.getAbsolutePosition();
final Point newPosition = cp == null ? null
: new Point(cp.x - minCoordinates.x + 50, cp.y - minCoordinates.y + 50);
DiagramElementLayoutUtil.moveElement(m, newDiagramElement, newPosition);
// Remove existing element
final DiagramElement existingDiagramElement = dstDiagramNode
.getChildByRelativeReference(newDiagramElement.getRelativeReference());
if (existingDiagramElement != null) {
m.removeElement(existingDiagramElement);
}
// Add the new diagram element to the diagram.
m.addElement(newDiagramElement);
newDiagramElements.add(newDiagramElement);
}
return newDiagramElements;
}
/**
* If the element is renameable and the name can be validated, then generate a name that passes validation.
* Otherwise, do not change the element's name. Contains special handling for component implementations.
* @param bo
* @param boHandler
*/
private static void ensureBusinessObjectHasUniqueName(final EObject bo,
final BusinessObjectHandler boHandler) {
if (supportsRenaming(bo, boHandler) && boHandler.canRename(new CanRenameContext(bo))) {
// Determine the current name of the business object.
final String originalName = boHandler.getNameForRenaming(new GetNameContext(bo));
// See the name of the business object to something other than the current name so that name validation for the
// copied business object will not treat the name as unchanged. For component implementations, the name needs to
// contain the component type / component type alias appropriate for the destination package.
if (bo instanceof ComponentImplementation) {
final ComponentImplementation ci = (ComponentImplementation) bo;
final ComponentType ct = ci.getType();
final ClassifierCreationHelper classifierCreationHelper = new ClassifierCreationHelper(
ci.eResource().getResourceSet());
final String ciTypeName;
if (ct == null) {
ciTypeName = ci.getTypeName();
} else {
if (!AadlNameUtil.namesAreEqual(ci.getNamespace(), ct.getNamespace())) {
if (!(ci.getNamespace() instanceof PackageSection)) {
throw new RuntimeException(
"New component implementation is not contained in a package section");
}
final PackageSection section = (PackageSection) ci.getNamespace();
// Import the package if necessary
final AadlPackage typePkg = (AadlPackage) ct.getNamespace().getOwner();
AadlImportsUtil.addImportIfNeeded(section, typePkg);
// Create an alias for the component type
final ClassifierCreationHelper.RenamedTypeDetails aliasDetails = classifierCreationHelper
.getRenamedType(section, typePkg, ct.getName());
if (!aliasDetails.exists) {
final ComponentTypeRename ctr = section.createOwnedComponentTypeRename();
ctr.setName(aliasDetails.aliasName);
ctr.setCategory(ct.getCategory());
ctr.setRenamedComponentType(ct);
}
ciTypeName = aliasDetails.aliasName;
} else {
ciTypeName = ct.getName();
}
}
setName(bo, boHandler, ciTypeName + ".osate_ge_temporary_name_00001");
} else {
// Set name to dummy name so that validate name will work as expected. Many implementations
// of validate name check if the name has changed.
setName(bo, boHandler, "");
}
// Determine a new name for the business object
final String baseName = originalName;
String newName = originalName;
int count = 1;
while (true) {
final String result = RenameUtil.checkNewNameValidity(bo, boHandler, newName);
if (result == null) {
break;
}
newName = baseName + "_copy" + (count == 1 ? "" : Integer.toString(count));
count++;
}
// For component implementation's, build a new name using the current type name. getTypeName() returns a type name
// based on the currently set name which could be an alias instead of getting the name from the referenced component type.
// This is not added before this point because the component implementation business object handler requires that the
// specified value does not include the type name.
if (bo instanceof ComponentImplementation) {
newName = ((ComponentImplementation) bo).getTypeName() + "." + newName;
}
// Update the business object's name
setName(bo, boHandler, newName);
}
}
/**
* Updates the model to ensure packages referenced by the the BO or its children are imported into the BO's package.
* @param bo
*/
private static void ensurePackagesAreImported(final EObject bo) {
final Classifier classifierToImport;
// If copied element is a classifier(type, implementation or feature group) ensure import of package for extended classifier
if (bo instanceof Classifier) {
classifierToImport = ((Classifier) bo).getExtended();
} else if (bo instanceof Subcomponent) { // If copied element is a subcomponent, ensure import of referenced classifier.
classifierToImport = ((Subcomponent) bo).getClassifier();
} else if (bo instanceof Feature) { // If copied element is a feature, ensure import of referenced classifier.
classifierToImport = ((Feature) bo).getClassifier();
} else {
classifierToImport = null;
}
if (classifierToImport != null) {
AadlImportsUtil.ensurePackageIsImportedForClassifier((Element) bo, classifierToImport);
}
for (final EObject child : bo.eContents()) {
ensurePackagesAreImported(child);
}
}
/**
* Returns true if the paste action supports renaming the business object
* @param bo
* @param handler
* @return
*/
private static boolean supportsRenaming(final Object bo, final BusinessObjectHandler handler) {
if (bo instanceof NamedElement) {
return true;
}
if (RenameUtil.supportsNonLtkRename(handler)) {
return true;
}
return false;
}
private static void setName(final Object bo, final BusinessObjectHandler handler, final String newName) {
if (bo instanceof NamedElement) {
((NamedElement) bo).setName(newName);
} else {
RenameUtil.performNonLtkRename(bo, handler, newName);
}
}
@Override
public boolean isEnabled() {
// Return value if this is called before constructor is finished
if (clipboard == null) {
return false;
}
final DiagramNode dstDiagramNode = getDestinationDiagramNode();
if (dstDiagramNode == null) {
return false;
}
final Collection<CopiedDiagramElement> copiedDiagramElements = getCopiedDiagramElements();
if (copiedDiagramElements.isEmpty()) {
return false;
}
final boolean anyEmbeddedBoCopied = copiedDiagramElements.stream()
.anyMatch(de -> de.getCopiedBusinessObject() instanceof EmbeddedBusinessObject);
final boolean anyEObjectCopied = copiedDiagramElements.stream()
.anyMatch(de -> de.getCopiedBusinessObject() instanceof EObject);
// Don't allow pasting inside an embedded business object.
// Such objects are not supported because layout issues have been observed.
return !(getBusinessObject(dstDiagramNode) instanceof EmbeddedBusinessObject)
&& (!anyEmbeddedBoCopied || DiagramNodePredicates.isDiagramOrUndockedShape(dstDiagramNode))
&& (!anyEObjectCopied || getDestinationEObject(dstDiagramNode) != null);
}
/**
* Returns the diagram node inside which contents will be pasted.
* @return the diagram node to use as a parent or null if there is not exactly one diagram node selected.
*/
private DiagramNode getDestinationDiagramNode() {
// Only allow pasting if a single diagram node is selected.
final List<DiagramNode> selectedDiagramNodes = AgeHandlerUtil.getSelectedDiagramNodes();
if (selectedDiagramNodes.size() != 1) {
return null;
}
return selectedDiagramNodes.get(0);
}
private static Object getBusinessObject(final DiagramNode dstDiagramNode) {
if (!(dstDiagramNode instanceof DiagramElement)) {
return null;
}
final DiagramElement dstDiagramElement = (DiagramElement) dstDiagramNode;
return dstDiagramElement.getBusinessObject();
}
private EObject getDestinationEObject(final DiagramNode dstDiagramNode) {
if (!(dstDiagramNode instanceof DiagramElement)) {
return null;
}
final DiagramElement dstDiagramElement = (DiagramElement) dstDiagramNode;
if (!(dstDiagramElement.getBusinessObject() instanceof EObject)) {
return null;
}
EObject dstBo = (EObject) dstDiagramElement.getBusinessObject();
final Collection<CopiedDiagramElement> copiedDiagramElements = getCopiedDiagramElements();
if (copiedDiagramElements.size() == 0) {
return null;
}
// Determine the business object inside which to copy the copied business objects
while (dstBo != null) {
// Check if all copied business objects may be copied to the potential destination business object
final EClass dstBoEClass = dstBo.eClass();
final boolean allElementsAreCompatible = copiedDiagramElements.stream().allMatch(
copiedDiagramElement -> !(copiedDiagramElement.getCopiedBusinessObject() instanceof EObject)
|| getCompatibleStructuralFeature(copiedDiagramElement.getContainingFeature(),
dstBoEClass) != null);
if (allElementsAreCompatible) {
break;
}
// Select a new destination business object.
if (dstBo instanceof ComponentImplementation) {
dstBo = ((ComponentImplementation) dstBo).getType();
} else if (dstBo instanceof Subcomponent) {
dstBo = ((Subcomponent) dstBo).getAllClassifier();
} else if (dstBo instanceof AadlPackage) {
dstBo = ((AadlPackage) dstBo).getPublicSection();
} else {
dstBo = null;
}
}
// Only allow pasting of the destination has a valid XtextResource
if (dstBo != null && !(dstBo.eResource() instanceof XtextResource)) {
return null;
}
return dstBo;
}
private Collection<CopiedDiagramElement> getCopiedDiagramElements() {
final Object contents = clipboard.getContents();
if (contents instanceof CopiedDiagramElements) {
return ((CopiedDiagramElements) contents).getCopiedDiagramElements();
}
return Collections.emptyList();
}
private static EStructuralFeature getCompatibleStructuralFeature(final EStructuralFeature searchFeature,
final EClass eClass) {
if (!(searchFeature.getEType() instanceof EClass)) {
return null;
}
final EClass searchEClass = (EClass) searchFeature.getEType();
// Look for the structural feature
for (final EStructuralFeature tmp : eClass.getEAllStructuralFeatures()) {
if (Objects.equals(searchFeature.getName(), tmp.getName()) && searchFeature.isMany() == tmp.isMany()
&& tmp.isChangeable()) {
if (tmp.getEType() instanceof EClass) {
final EClass tmpEClass = (EClass) tmp.getEType();
if (tmpEClass.isSuperTypeOf(searchEClass)) {
return tmp;
}
}
}
}
return null;
}
}