blob: 52cace4663bbc1191ad94bb81b4b5750a7b87145 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016, 2017 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
* Stefan Dirix - bug 498583
* Laurent Delaigue - bug 498583
* Martin Fleck - bug 515041
* Philip Langer - bug 516484
*******************************************************************************/
package org.eclipse.emf.compare.uml2.papyrus.internal.hook.migration;
import com.google.common.base.Function;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.compare.uml2.papyrus.internal.UMLPapyrusCompareMessages;
import org.eclipse.emf.compare.uml2.papyrus.internal.UMLPapyrusComparePlugin;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.papyrus.infra.core.resource.ModelSet;
import org.eclipse.papyrus.infra.core.services.ServiceException;
import org.eclipse.papyrus.infra.services.labelprovider.service.LabelProviderService;
import org.eclipse.papyrus.uml.modelrepair.internal.stereotypes.StereotypeApplicationRepairSnippet;
import org.eclipse.papyrus.uml.modelrepair.internal.stereotypes.ZombieStereotypesDescriptor;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Profile;
import org.eclipse.uml2.uml.UMLPackage;
/**
* Analyzer to retrieve zombie and orphan stereotype applications. Zombies are stereotype applications for
* which the defining {@link EPackage} could not be found. Orphans are stereotype applications for which the
* referenced base element is missing. The implementation of this class is based on the Papyrus' model repair
* capabilities.
*
* @author Martin Fleck <mfleck@eclipsesource.com>
*/
@SuppressWarnings("restriction")
public class StereotypeApplicationRepair extends StereotypeApplicationRepairSnippet {
/** The name of the private label provider service field in the super class. */
private static final String FIELD_LABEL_PROVIDER_SERVICE = "labelProviderService"; //$NON-NLS-1$
/** The name of the private adapter field in the super class. */
private static final String FIELD_ADAPTER = "adapter"; //$NON-NLS-1$
/** The name of the private dynamic profile supplier field in the super class. */
private static final String FIELD_DYNAMIC_PROFILE_SUPPLIER = "dynamicProfileSupplier"; //$NON-NLS-1$
/** The label provider service used to displays a user dialog during the migration. */
private LabelProviderService fLabelProviderService;
/** The resource under repair. */
private Resource fResource;
/** The profile supplier used to find a profile if a package is missing. */
private Object fProfileSupplier;
/** The model set used by {@link #repair()} and {@link #dispose()}. */
private ModelSetWrapper fModelSet;
/**
* Creates a new repair analyzer for zombie and orphan stereotype applications for the given resource.
*
* @param resource
* the resource under repair
*/
public StereotypeApplicationRepair(Resource resource) {
// new constructor to provide our own profile supplier
super();
this.fResource = resource;
this.fLabelProviderService = setLabelProviderService(createLabelProviderService());
this.fProfileSupplier = setProfileSupplier(createProfileSupplier());
this.fModelSet = new ModelSetWrapper(resource.getResourceSet());
// avoid read-only for our resource
fModelSet.setReadOnly(resource, Boolean.FALSE);
}
@Override
public void dispose(ModelSet modelsManager) {
try {
LabelProviderService s = (LabelProviderService)getSuperField(FIELD_LABEL_PROVIDER_SERVICE);
if (s != null) {
s.disposeService();
}
} catch (ServiceException ex) {
UMLPapyrusComparePlugin.getDefault().getLog()
.log(new Status(IStatus.WARNING, UMLPapyrusComparePlugin.PLUGIN_ID,
"Unable to dispose Label Provider Service", //$NON-NLS-1$
ex));
}
super.dispose(modelsManager);
}
/**
* Disposed the instance.
*/
public void dispose() {
dispose(this.fModelSet);
}
/**
* Reflectively sets the field with the given name in the super class to the specified fieldValue.
*
* @param fieldName
* name of the field in the super class
* @param fieldValue
* new value of the field in the super class
* @param <T>
* type of the field value
* @return the value set at the given field. If an exception occurred, null is returned.
*/
protected <T> T setSuperField(String fieldName, T fieldValue) {
try {
final Field superField = getClass().getSuperclass().getDeclaredField(fieldName);
superField.setAccessible(true);
superField.set(this, fieldValue);
return fieldValue;
} catch (final NoSuchFieldException | SecurityException | IllegalArgumentException
| IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
/**
* Reflectively returns the value of field with the given name from the super class. If no such field can
* be found or an exception is thrown, null is returned.
*
* @param fieldName
* name of the field in the super class
* @return field value or null
*/
protected Object getSuperField(String fieldName) {
try {
final Field superField = getClass().getSuperclass().getDeclaredField(fieldName);
superField.setAccessible(true);
return superField.get(this);
} catch (final NoSuchFieldException | SecurityException | IllegalArgumentException
| IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
/**
* Reflectively sets the adapter for this repair snippet. This is needed for Eclipse Luna as it expects a
* ModelSet as resource set for the migrated resource.
*
* @param resourceSet
* resource set containing the resource under repair
*/
private void setAdapter(ModelSet resourceSet) {
// adapter needed to provide EPackage.Registry via the adapters resourceSet
final Object adapterObject = getSuperField(FIELD_ADAPTER);
if (adapterObject instanceof Adapter.Internal) {
((Adapter.Internal)adapterObject).setTarget(resourceSet);
}
}
/**
* Reflectively sets the labelProviderService for this repair snippet. This label provider service is not
* used during the comparison, but necessary for Papyrus which displays a user dialog during the
* migration. The Papyrus label provider needs the Workbench to be initialized, therefore we should use a
* simpler label provider to avoid this requirement.
*
* @param labelProviderService
* label provider service
* @return the set label provider service or null, if the label provider service could not be set
*/
private LabelProviderService setLabelProviderService(LabelProviderService labelProviderService) {
return setSuperField(FIELD_LABEL_PROVIDER_SERVICE, labelProviderService);
}
/**
* Reflectively sets the profileSupplier for this repair snippet.
*
* @param profileSupplier
* supplier of profiles for missing packages.
* @return the set profile supplier or null, if the profile supplier could not be set
*/
protected Object setProfileSupplier(Object profileSupplier) {
return setSuperField(FIELD_DYNAMIC_PROFILE_SUPPLIER, profileSupplier);
}
/**
* Creates a new label provider service that is used during the migration. In automatic migration, this
* label provider service is not used.
*
* @return newly created label provider service
*/
protected LabelProviderService createLabelProviderService() {
// we use a label provider service that does not need any special UI capabilities
UMLLabelProviderService umlLabelProviderService = new UMLLabelProviderService();
try {
umlLabelProviderService.startService();
} catch (ServiceException ex) {
UMLPapyrusComparePlugin.getDefault().getLog()
.log(new Status(IStatus.WARNING, UMLPapyrusComparePlugin.PLUGIN_ID,
"Unable to start UML Label Provider Service", //$NON-NLS-1$
ex));
}
return umlLabelProviderService;
}
/***
* Creates a new profile supplier that is called if a package is missing and we need to find a profile
* that defines such a package.
* <p>
* <i>Note: The return type of this method is Object, as we may need to wrap our supplier in a
* {@link Proxy#newProxyInstance(ClassLoader, Class[], InvocationHandler) dynamic proxy} due to a
* different {@link Function} interface version being used in the super class (cf. bug 515041).</i>
* </p>
*
* @see #createProfileSupplierProxy(Function, Class)
* @return newly created profile supplier
*/
protected Object createProfileSupplier() {
final MissingProfileSupplier missingProfileSupplier = new MissingProfileSupplier(
getRootElement(fResource));
try {
// check if our supplier is compatible and if not wrap it in a proxy
final Field superProfileSupplier = getClass().getSuperclass()
.getDeclaredField(FIELD_DYNAMIC_PROFILE_SUPPLIER);
Class<?> superProfileSupplierType = superProfileSupplier.getType();
if (superProfileSupplierType.isInstance(missingProfileSupplier)) {
return missingProfileSupplier;
}
return createProfileSupplierProxy(missingProfileSupplier, superProfileSupplierType);
} catch (final NoSuchFieldException | SecurityException | IllegalArgumentException e) {
e.printStackTrace();
}
return missingProfileSupplier;
}
/**
* Creates a proxy instance for the given profile supplier to be compatible with the given type.
*
* @param profileSupplier
* profile supplier
* @param profileSupplierType
* type of the returned proxy
* @return proxy wrapping the provided profile supplier
*/
protected Object createProfileSupplierProxy(final Function<EPackage, Profile> profileSupplier,
Class<?> profileSupplierType) {
return Proxy.newProxyInstance(StereotypeApplicationRepairSnippet.class.getClassLoader(),
new Class<?>[] {profileSupplierType }, new DelegatingInvocationHandler(profileSupplier));
}
/**
* Returns the resource under analysis.
*
* @return resource
*/
public Resource getResource() {
return fResource;
}
/**
* Evaluates whether all necessary fiels have been set successfully and a repair is possible.
*
* @return true if a repair is possible, false otherwise.
*/
protected boolean isFieldMissing() {
return fResource == null || fLabelProviderService == null || fProfileSupplier == null;
}
/**
* Analyzes the stereotype applications of the given resources root element and returns a descriptor
* containing zombie and orphan stereotype applications. For zombies, the defining package could not be
* found and for orphans the base element could not be found. The descriptor also already suggests repair
* actions, i.e., migrating the missing package for zombies if possible and deleting the stereotype
* application for orphans.
*
* @return descriptor of zombie and orphan stereotypes
*/
public ZombieStereotypesDescriptor repair() {
if (isFieldMissing()) {
// fail silently but log warning
UMLPapyrusComparePlugin.getDefault().getLog().log(new Status(IStatus.WARNING,
UMLPapyrusComparePlugin.PLUGIN_ID,
"Unable to analyze and repair resource " + fResource //$NON-NLS-1$
+ " due to missing field: {resource=" + fResource + ", labelProviderService=" //$NON-NLS-1$ //$NON-NLS-2$
+ fLabelProviderService + ", profileSupplier=" + fProfileSupplier + "}")); //$NON-NLS-1$//$NON-NLS-2$
return null;
}
try {
final ResourceSet resourceSet = fResource.getResourceSet();
setAdapter(fModelSet);
fModelSet.getResources().add(fResource);
final ZombieStereotypesDescriptor stereotypesDescriptor = getZombieStereotypes(fResource);
resourceSet.getResources().add(fResource);
return stereotypesDescriptor;
// CHECKSTYLE:OFF
} catch (Exception e) {
// CHECKSTYLE:ON
fResource.getErrors().add(new ProfileMigrationDiagnostic(
UMLPapyrusCompareMessages.getString("profile.migration.exception", e, fResource))); //$NON-NLS-1$
UMLPapyrusComparePlugin.getDefault().getLog()
.log(new Status(IStatus.ERROR, UMLPapyrusComparePlugin.PLUGIN_ID,
"Exception occurred during profile migration", //$NON-NLS-1$
e)); // The exception stack trace will appear in the error log
}
return null;
}
/**
* Returns the root {@link Element element} in the given resource. If multiple elements are present the
* first one is returned.
*
* @param resource
* resource to check
* @return The first root element or null if no element is found
*/
protected static Element getRootElement(Resource resource) {
return (Element)EcoreUtil.getObjectByType(resource.getContents(), UMLPackage.Literals.ELEMENT);
}
}