blob: 33dd42cc4f13479c43a99464541c1852f03cdef3 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016, 2018 EclipseSource Services GmbH and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Martin Fleck - initial API and implementation
* Philip Langer - bug 516484
* Christian W. Damus - bug 529217
*******************************************************************************/
package org.eclipse.papyrus.compare.uml2.internal.hook;
import static org.eclipse.papyrus.infra.emf.internal.resource.AbstractCrossReferenceIndex.SHARD_ANNOTATION_SOURCE;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.ListIterator;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.emf.common.util.BasicDiagnostic;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.compare.ide.hook.AbstractResourceSetHooks;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.InternalEObject;
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.InternalEList;
import org.eclipse.papyrus.compare.uml2.internal.hook.migration.StereotypeApplicationRepair;
import org.eclipse.papyrus.uml.modelrepair.internal.stereotypes.IRepairAction;
import org.eclipse.papyrus.uml.modelrepair.internal.stereotypes.ZombieStereotypesDescriptor;
import org.eclipse.papyrus.uml.tools.model.UmlModel;
import org.eclipse.uml2.uml.PackageableElement;
import org.eclipse.uml2.uml.UMLPackage;
/**
* This class migrates missing UML stereotype applications before the comparison, if possible. For any missing
* stereotype application, we aim to find an available profile definition (EPackage) that provides the
* stereotype. If such a definition can be found, we migrate to the respective profile and stereotype
* applications. If no definition can be found, the model is left unchanged. Profile definitions are searched
* based on the URI of the missing stereotypes package URI.
*
* @author Martin Fleck <mfleck@eclipsesource.com>
*/
@SuppressWarnings("restriction")
public class ProfileMigrationHook extends AbstractResourceSetHooks {
@Override
public void postLoadingHook(ResourceSet resourceSet, Collection<? extends URI> uris) {
final List<Resource> umlResources = getUMLResources(resourceSet);
if (umlResources.isEmpty()) {
return; // we are not responsible
}
// Two stages: ensure sub-unit linkages and then repair
// First, ensure that the linkages between sub-units are correctly
// established (container proxies) so that packages can find profile
// applications in parent units and we don't create redundant new
// profile applications in the next step that will introduce bogus diffs
for (final Resource umlResource : umlResources) {
ensureParentUnitLinkage(umlResource);
}
// Then, do whatever it takes to repair profile applications
for (final Resource umlResource : umlResources) {
repairProfileApplications(umlResource);
}
}
@Override
public boolean isHookFor(Collection<? extends URI> uris) {
for (final URI uri : uris) {
if (isUMLResource(uri)) {
return true;
}
}
return false;
}
/**
* Checks if the given URI represents a UML resource.
*
* @param uri
* URI to check
* @return true if the given URI represents a UML resource, false otherwise
*/
private boolean isUMLResource(final URI uri) {
return uri != null && UmlModel.UML_FILE_EXTENSION.equals(uri.fileExtension());
}
/**
* Checks if the given resource is a UML resource based on its URI. If the given resource is null, false
* is returned.
*
* @param resource
* resource to check
* @return true if the given resource represents a UML resource, false otherwise.
* @see #isUMLResource(URI)
*/
private boolean isUMLResource(final Resource resource) {
return resource != null && isUMLResource(resource.getURI());
}
/**
* Filters all UML resources from the given resource set. If no UML resources can be found, an empty list
* is returned.
*
* @param resourceSet
* loaded resource set
* @return all UML resources from the given resource set
* @see #isUMLResource(Resource)
*/
private List<Resource> getUMLResources(final ResourceSet resourceSet) {
final List<Resource> umlResources = new ArrayList<Resource>();
for (final Resource resource : resourceSet.getResources()) {
if (isUMLResource(resource)) {
umlResources.add(resource);
}
}
return umlResources;
}
/**
* Repairs the profile applications of missing stereotypes, if possible, by first analyzing the resource
* for missing stereotypes and then delegating to the model repair mechanism provided by Papyrus.
*
* @param resource
* resource to be repaired
*/
protected void repairProfileApplications(final Resource resource) {
if (resource == null) {
return; // nothing to repair
}
final StereotypeApplicationRepair repair = new StereotypeApplicationRepair(resource);
try {
final ZombieStereotypesDescriptor stereotypesDescriptor = repair.repair();
if (stereotypesDescriptor == null || !stereotypesDescriptor.hasZombies()) {
return; // nothing to repair
}
// for each schema (missing EPackages) try to repair the respective stereotype applications
for (final IAdaptable schema : stereotypesDescriptor.getZombieSchemas()) {
// the stereotype descriptor already provides the most suitable repair action
// deletion for orphans (stereotypes whose base element is missing)
// profile migration for zombies (stereotypes whose defining package can not be found)
final IRepairAction repairAction = stereotypesDescriptor.getSuggestedRepairAction(schema);
if (repairAction != null) {
// execute any suggested action
stereotypesDescriptor.repair(schema, repairAction, new BasicDiagnostic(),
new NullProgressMonitor());
}
}
} finally {
repair.dispose();
}
}
/**
* Ensure that a sub-model unit correctly resolves its {@code eContainer} link to its parent unit so that
* profile applications may be found in that parent unit.
*
* @param resource
* a UML resource that may or may not be a sub-model unit
*/
protected void ensureParentUnitLinkage(Resource resource) {
org.eclipse.uml2.uml.Package subUnit = (org.eclipse.uml2.uml.Package)EcoreUtil
.getObjectByType(resource.getContents(), UMLPackage.Literals.PACKAGE);
if (subUnit != null) {
// Search for the shard annotation and resolve the parent package's
// proxy for this nested package
EAnnotation annotation = subUnit.getEAnnotation(SHARD_ANNOTATION_SOURCE);
if (annotation != null) {
org.eclipse.uml2.uml.Package parentUnit = (org.eclipse.uml2.uml.Package)EcoreUtil
.getObjectByType(annotation.getReferences(), UMLPackage.Literals.PACKAGE);
if (parentUnit != null) {
final URI proxyURI = EcoreUtil.getURI(subUnit);
// Trigger containment proxy resolution
for (ListIterator<PackageableElement> iter = ((InternalEList<PackageableElement>)parentUnit
.getPackagedElements()).basicListIterator(); iter.hasNext();) {
PackageableElement next = iter.next();
if (proxyURI.equals(((InternalEObject)next).eProxyURI())) {
parentUnit.getPackagedElements().get(iter.previousIndex());
// Needn't continue further
break;
}
}
}
}
}
}
}