blob: ed095f20ad0e35901bea7a28be1862fb010037d7 [file] [log] [blame]
/*****************************************************************************
* Copyright (c) 2020 CEA LIST.
*
*
* All rights reserved. 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:
* Asma Smaoui (CEA LIST) asma.smaoui@cea.fr - Initial API and implementation
*
*****************************************************************************/
package org.eclipse.papyrus.aas.import2papyrus.transformations;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.util.ExtendedMetaData;
import org.eclipse.emf.ecore.xmi.XMIResource;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.ecore.xmi.impl.XMIResourceImpl;
import org.eclipse.gmf.runtime.emf.core.resources.GMFResource;
import org.eclipse.gmf.runtime.notation.Diagram;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.papyrus.infra.emf.resource.ShardResourceHelper;
import org.eclipse.papyrus.infra.emf.utils.EMFHelper;
import org.eclipse.papyrus.uml.m2m.qvto.common.Activator;
import org.eclipse.papyrus.uml.m2m.qvto.common.MigrationParameters.MigrationParametersFactory;
import org.eclipse.papyrus.uml.m2m.qvto.common.MigrationParameters.ThreadConfig;
import org.eclipse.papyrus.uml.m2m.qvto.common.concurrent.ExecutorsPool;
import org.eclipse.papyrus.uml.m2m.qvto.common.internal.extension.TransformationExtension;
import org.eclipse.papyrus.uml.m2m.qvto.common.transformation.IDependencyAnalysisHelper;
import org.eclipse.papyrus.uml.m2m.qvto.common.transformation.IImportTransformation;
import org.eclipse.papyrus.uml.m2m.qvto.common.transformation.MigrationResourceSet;
import org.eclipse.papyrus.uml.m2m.qvto.common.transformation.MigrationResourceSetImpl;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.statushandlers.StatusManager;
import org.eclipse.uml2.common.util.CacheAdapter;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Extension;
import org.eclipse.uml2.uml.resource.UMLResource;
import org.eclipse.uml2.uml.util.UMLUtil;
/**
* Executes a single import-to-Papyrus transformation
*
*
*/
public abstract class AbstractImportTransformation implements IImportTransformation {
/** For debug purpose */
protected static boolean DEBUG = true;
// SourceURI is the input
protected final URI sourceURI;
// targetURI is computed during the transformation
protected URI targetURI;
protected URI outUML, outNotation, outSashModel, inPapyrusProfiles;
protected MigrationResourceSet resourceSet;
protected Job job;
protected ThreadConfig parameters;
protected boolean complete = false;
/** Execution time, in nano-seconds */
protected long executionTime = 0L;
/** Execution time of the initial model loading / ns */
protected long loadingTime = 0L;
/** Execution time for handling dangling references / ns */
protected long danglingRefTime = 0L;
/** Execution time for executing the UML-RT transformation / ns */
protected long importExtensionsTime = 0L;
/** Source URI to Target URI map (For Models/Libraries/Fragments) */
protected final Map<URI, URI> uriMappings = new HashMap<>();
/** Source URI to Target URI map (For Profiles) */
protected final Map<URI, URI> profileURIMappings = new HashMap<>();
protected List<Diagram> diagramsToDelete = new LinkedList<>();
protected static final ExecutorsPool executorsPool = new ExecutorsPool(2);
/**
* EPackages corresponding to source native profiles with specific support in
* the transformation
*/
protected static final Set<EPackage> sourceEPackages = new HashSet<>();
protected final IDependencyAnalysisHelper analysisHelper;
/** Extensions contributed via other plug-ins */
protected final List<TransformationExtension> extensions;
/** Accumulation of incremental update traces from each transformation. */
public AbstractImportTransformation(URI sourceURI) {
this(sourceURI, MigrationParametersFactory.eINSTANCE.createThreadConfig(), null);
}
public AbstractImportTransformation(URI sourceURI, ThreadConfig config, IDependencyAnalysisHelper analysisHelper) {
Assert.isNotNull(sourceURI);
this.sourceURI = sourceURI;
this.parameters = config;
this.analysisHelper = analysisHelper;
this.extensions = getAllExtensions();
}
/**
* Instantiate all the extensions for a specific transformation
*
* @return A non-null (potentially empty) list of extensions
*/
protected static List<TransformationExtension> getAllExtensions() {
return Collections.emptyList();
}
/**
* Executes the transformation
*
* The transformation will be executed asynchronously in a Job
*/
public void run(final boolean isUserJob) {
job = new Job("Import " + getModelName()) {
@Override
protected IStatus run(IProgressMonitor monitor) {
long begin = System.nanoTime();
IStatus result = AbstractImportTransformation.this.run(monitor);
long end = System.nanoTime();
executionTime = end - begin;
return result;
}
};
job.setUser(isUserJob);
job.addJobChangeListener(new JobChangeAdapter() {
@Override
public void done(IJobChangeEvent event) {
complete = true;
if (isUserJob) {
if (event.getResult().getSeverity() == IStatus.OK) {
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
MessageDialog.openInformation(
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), job.getName(),
String.format("Model %s has been successfully imported", getModelName()));
}
});
} else if (event.getResult().getSeverity() == IStatus.CANCEL) {
Display.getDefault().asyncExec(new Runnable() {
@Override
public void run() {
MessageDialog.openInformation(
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), job.getName(),
String.format("Operation canceled: %s", getModelName()));
}
});
} else {
StatusManager.getManager().handle(event.getResult(), StatusManager.BLOCK);
}
}
}
});
job.schedule();
}
public void waitForCompletion() {
try {
job.join();
} catch (InterruptedException ex) {
Activator.log.error(ex);
}
}
public boolean isComplete() {
return complete;
}
public IStatus getStatus() {
if (job == null) { // If job hasn't been created, the operation has probably been canceled before
// the transformation is ran
return new Status(IStatus.CANCEL, Activator.PLUGIN_ID, "Operation canceled");
}
return job.getResult();
}
public long getExecutionTime() {
return executionTime;
}
public long getLoadingTime() {
return loadingTime;
}
public long getHandleDanglingRefTime() {
return danglingRefTime;
}
public long getImportExtensionsTime() {
return importExtensionsTime;
}
public URI getTargetURI() {
return targetURI;
}
/**
* Initializes the resource set, and resolve all dependencies
*/
protected void initResourceSet(IProgressMonitor monitor) {
resourceSet = new MigrationResourceSetImpl();
synchronized (UMLUtil.class) {
UMLUtil.init(resourceSet);
}
resourceSet.getLoadOptions().put(XMLResource.OPTION_DEFER_ATTACHMENT, true);
resourceSet.getLoadOptions().put(XMLResource.OPTION_DEFER_IDREF_RESOLUTION, true);
resourceSet.getLoadOptions().put(XMLResource.OPTION_RECORD_UNKNOWN_FEATURE, Boolean.TRUE);
resourceSet.getLoadOptions().put(XMLResource.OPTION_USE_PACKAGE_NS_URI_AS_LOCATION, Boolean.FALSE);
monitor.subTask("Loading source model " + getModelName());
try {
File resourceFile = new File(sourceURI.toFileString());
if (resourceFile.exists()) {
resourceSet.getResource(sourceURI, true);
}
} catch (Exception ex) {
Activator.log.error("An error occurred while loading " + getModelName(), ex);
}
}
protected abstract int countSupportedElements(); // TODO
protected static IStatus createStatusFromDiagnostic(Diagnostic diagnostic) {
return new Status(diagnostic.getSeverity(), diagnostic.getSource(), diagnostic.getMessage(),
diagnostic.getException());
}
/**
* Actually runs the transformation (in the current thread)
*
* @param monitor
* @return The transformation IStatus
*/
protected abstract IStatus run(final IProgressMonitor monitor);
public static MultiStatus aggregateStatus(List<IStatus> statuses) {
return new MultiStatus(Activator.PLUGIN_ID, IStatus.OK, statuses.toArray(new IStatus[statuses.size()]), "",
null);
}
/**
* @param resource
*/
protected void cleanMetadataAnnotations(Resource resource) {
// Bug 471684: UML2.x to UML2.5 creates (invalid) Ecore Metadata EAnnotations,
// which then cause OCL validation to fail
// Remove these EAnnotations from the model to avoid side effects
Iterator<EObject> rootElementsIterator = resource.getContents().iterator();
while (rootElementsIterator.hasNext()) {
EObject root = rootElementsIterator.next();
if (root instanceof EAnnotation) {
EAnnotation annotation = (EAnnotation) root;
if (ExtendedMetaData.ANNOTATION_URI.equals(annotation.getSource())) {
rootElementsIterator.remove();
}
}
}
}
protected void handleDanglingURIs(Collection<Resource> resourcesToSave) {
if (analysisHelper != null) {
resourceSet.freeze();
try {
analysisHelper.computeURIMappings(resourcesToSave);
} finally {
resourceSet.unfreeze();
}
}
}
protected void unloadResourceSet(ResourceSet resourceSet) {
EMFHelper.unload(resourceSet);
}
protected URI getInProfileDefinitions() {
// TODO : not used, useful for common and Rpy import
return null;
}
protected URI getInPapyrusProfiles() {
if (inPapyrusProfiles == null) {
loadInPapyrusProfiles();
}
return inPapyrusProfiles;
}
protected abstract Diagnostic loadInPapyrusProfiles(); // TODO : add path of profile are parameters ?
protected void checkResource(Resource resource) {
Assert.isNotNull(resource);
Assert.isTrue(!resource.getContents().isEmpty(), "The resource " + resource.getURI() + " is empty");
for (EObject rootElement : resource.getContents()) {
Assert.isTrue(!rootElement.eIsProxy());
}
}
protected abstract Resource createUMLResource(ResourceSet resourceSet, URI sourceResourceURI,
URI targetResourceURI);
protected Collection<Resource> handleFragments(Resource umlResource, Resource notationResource,
Resource sashResource) {
Collection<Resource> result = new HashSet<>();
result.add(umlResource);
result.add(notationResource);
result.add(sashResource);
ResourceSet resourceSet = umlResource.getResourceSet();
Iterator<EObject> elementIterator = umlResource.getAllContents();
Set<Resource> fragmentResources = new HashSet<>();
List<EAnnotation> rsaAnnotations = new ArrayList<>();
while (elementIterator.hasNext()) {
EObject element = elementIterator.next();
Resource possibleFragment = element.eResource();
if ((possibleFragment != umlResource) && possibleFragment.getContents().contains(element)) { // Controlled/Fragment
// root
fragmentResources.add(possibleFragment);
}
// TODO commented because it is RSA import only
// collectRSAAnnotations(element, rsaAnnotations);
}
// Strip all RSA fragment annotations
rsaAnnotations.forEach(EcoreUtil::remove);
List<Resource> fragmentUMLResources = new LinkedList<>();
for (Resource fragmentResource : fragmentResources) {
URI papyrusFragmentURI = convertToPapyrus(fragmentResource.getURI(), UMLResource.FILE_EXTENSION);
uriMappings.put(fragmentResource.getURI(), papyrusFragmentURI);
Resource newResource = resourceSet.getResource(papyrusFragmentURI, false);
if (newResource == null) {
newResource = createUMLResource(resourceSet, fragmentResource.getURI(), papyrusFragmentURI);
fragmentUMLResources.add(newResource);
Resource fragmentNotationResource = new GMFResource(convertToPapyrus(papyrusFragmentURI, "notation"));
Resource fragmentDiResource = new XMIResourceImpl(convertToPapyrus(papyrusFragmentURI, "di"));
result.add(fragmentNotationResource);
result.add(fragmentDiResource);
resourceSet.getResources().add(fragmentNotationResource);
resourceSet.getResources().add(fragmentDiResource);
}
newResource.getContents().addAll(fragmentResource.getContents());
// Make it a Papyrus controlled unit of the "shard" variety
try (ShardResourceHelper shard = new ShardResourceHelper(newResource)) {
shard.setShard(true);
}
result.add(newResource);
}
//deleteSourceStereotypes(fragmentResources);
List<EObject> importedElements = new LinkedList<>(notationResource.getContents());
for (EObject notationElement : importedElements) {
if (notationElement instanceof Diagram) {
EObject semanticElement = ((Diagram) notationElement).getElement();
if (semanticElement.eResource() != umlResource && semanticElement.eResource() != null) {
URI notationFragmentURI = convertToPapyrus(semanticElement.eResource().getURI(), "notation");
Resource newNotationResource = resourceSet.getResource(notationFragmentURI, false);
if (newNotationResource == null) {
newNotationResource = new GMFResource(notationFragmentURI);
resourceSet.getResources().add(newNotationResource);
}
newNotationResource.getContents().add(notationElement);
result.add(newNotationResource);
}
}
}
handleFragmentStereotypes(umlResource, fragmentUMLResources);
for (Resource resource : result) {
if (resource instanceof XMIResource) {
configureResource((XMIResource) resource);
}
}
return result;
}
/*
* Bug 447097: [Model Import] Importing a fragmented model causes stereotype
* applications to be lost in resulting submodel
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=447097
*
* Before the transformation, We moved all root elements from the fragment
* resources to the main resource, then we transformed some of them to Papyrus
* Stereotype Applications. We need to move these stereotype applications back
* to the proper fragment resource
*/
protected void handleFragmentStereotypes(Resource mainUMLResource, List<Resource> umlResources) {
Iterator<EObject> rootElementIterator = mainUMLResource.getContents().iterator();
while (rootElementIterator.hasNext()) {
EObject rootElement = rootElementIterator.next();
if (rootElement instanceof Element) {
continue;
}
Resource targetStereotypeResource = getTargetStereotypeResource(rootElement, umlResources);
if (targetStereotypeResource != null && targetStereotypeResource != mainUMLResource) {
rootElementIterator.remove(); // To avoid ConcurrentModificationException when moving to the other
// resource
targetStereotypeResource.getContents().add(rootElement);
}
}
}
protected Resource getTargetStereotypeResource(EObject rootElement, List<Resource> umlResources) {
for (EReference eReference : rootElement.eClass().getEAllReferences()) {
if (eReference.getName().startsWith(Extension.METACLASS_ROLE_PREFIX)) {
Object value = rootElement.eGet(eReference);
if (value instanceof Element) {
return ((Element) value).eResource();
}
}
}
return null;
}
protected void deleteSourceStereotypes(Collection<Resource> fragmentResources) {
Set<Resource> allResources = new HashSet<>(fragmentResources);
//allResources.add(umlResource);
for (Resource resource : allResources) {
List<EObject> resourceContents = new LinkedList<>(resource.getContents());
for (EObject rootElement : resourceContents) {
if (sourceEPackages.contains(rootElement.eClass().getEPackage())) {
delete(rootElement);
}
}
}
}
protected abstract URI convertToPapyrus(URI rsaURI, String extension);
protected void configureResource(XMIResource resource) {
Map<Object, Object> saveOptions = new HashMap<>();
// default save options.
saveOptions.put(XMLResource.OPTION_DECLARE_XML, Boolean.TRUE);
saveOptions.put(XMLResource.OPTION_PROCESS_DANGLING_HREF, XMLResource.OPTION_PROCESS_DANGLING_HREF_DISCARD);
saveOptions.put(XMLResource.OPTION_SCHEMA_LOCATION, Boolean.TRUE);
saveOptions.put(XMIResource.OPTION_USE_XMI_TYPE, Boolean.TRUE);
saveOptions.put(XMLResource.OPTION_SAVE_TYPE_INFORMATION, Boolean.TRUE);
saveOptions.put(XMLResource.OPTION_SKIP_ESCAPE_URI, Boolean.FALSE);
saveOptions.put(XMLResource.OPTION_ENCODING, "UTF-8");
// see bug 397987: [Core][Save] The referenced plugin models are saved using
// relative path
saveOptions.put(XMLResource.OPTION_URI_HANDLER,
new org.eclipse.emf.ecore.xmi.impl.URIHandlerImpl.PlatformSchemeAware());
resource.setEncoding("UTF-8");
resource.getDefaultSaveOptions().putAll(saveOptions);
}
public abstract URI getInOutUMLModel();
/*
* Notation model is initially empty, but will be filled successively by each
* transformation
*/
public URI getInoutNotationModel() {
return outNotation;
}
protected URI getOutSashModel() {
return outSashModel;
}
protected abstract Collection<URI> getDiagramTransformationURIs();
protected abstract Collection<URI> getProfilesTransformationURI();
protected Collection<URI> getAdditionalTransformationURIs() {
return Collections.emptyList();
}
protected URI getTransformationURI(String transformationName, String pluginID) {
return URI.createPlatformPluginURI(String.format("%s/transform/%s.qvto", pluginID, transformationName), true); //$NON-NLS-1$
}
public String getModelName() {
return URI.decode(sourceURI.lastSegment());
}
public void cancel() {
job.cancel();
}
/**
* Lightweight delete operation, which only removes the object from its parent.
* Incoming references are not deleted
*/
public void delete(EObject elementToDelete) {
CacheAdapter adapter = CacheAdapter.getCacheAdapter(elementToDelete); // bug 541313 [CDO] - change is not
// required here
if (adapter == null) {
adapter = CacheAdapter.getInstance();
}
adapter.unsetTarget(elementToDelete);
if (elementToDelete.eResource() != null) {
elementToDelete.eResource().getContents().remove(elementToDelete);
}
EObject parent = elementToDelete.eContainer();
if (parent == null) {
return;
}
EReference containmentFeature = elementToDelete.eContainmentFeature();
if (containmentFeature.getUpperBound() == 1) {
parent.eUnset(containmentFeature);
} else {
List<?> values = (List<?>) parent.eGet(containmentFeature);
values.remove(elementToDelete);
}
}
}