/******************************************************************************* * 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 *******************************************************************************/ 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; /** * 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()); } @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); } /** * 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; } /** * Creates a {@link ModelSet} wrapper around the given resource set to be used for profile migration * within Eclipse Luna. * * @param resourceSet * resource set containing the resource under repair * @return newly created model set wrapper */ protected ModelSet createModelSetWrapper(ResourceSet resourceSet) { final ModelSetWrapper modelSet = new ModelSetWrapper(resourceSet); // avoid read-only for our resource modelSet.setReadOnly(fResource, Boolean.FALSE); return modelSet; } /** * 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(); final ModelSet modelSet = createModelSetWrapper(resourceSet); setAdapter(modelSet); modelSet.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); } }