diff options
author | Martin Fleck | 2017-04-13 10:02:50 +0000 |
---|---|---|
committer | Martin Fleck | 2017-04-13 11:04:13 +0000 |
commit | b749253052bfd1a97ea9c87dc9941244e09308ad (patch) | |
tree | 127d39be552124b3beb7e4f65df550f8714b0287 | |
parent | 6b0705d7642b83bdd546cface671e619616dac9e (diff) | |
download | org.eclipse.emf.compare-b749253052bfd1a97ea9c87dc9941244e09308ad.tar.gz org.eclipse.emf.compare-b749253052bfd1a97ea9c87dc9941244e09308ad.tar.xz org.eclipse.emf.compare-b749253052bfd1a97ea9c87dc9941244e09308ad.zip |
[515041] Papyrus profile migration preventing regular UML comparisons
Different Guava versions between our plugin and the Papyrus plugin
result in incompatible classes that cannot be assigned or queried
(instanceof) correctly.
- Use proxies implementing the expected class for correct assignment
of Guava objects in Papyrus classes.
- Use reflection to query and call methods of a Guava object retrieved
from Papyrus classes.
- Make profile migration more resilient against problems by querying if
all fields have been set as expected.
Bug: 515041
Change-Id: If1998961e6e1c5837146b285dc55f1f5989f7dbb
Signed-off-by: Martin Fleck <mfleck@eclipsesource.com>
3 files changed, 200 insertions, 32 deletions
diff --git a/plugins/org.eclipse.emf.compare.uml2.papyrus/src/org/eclipse/emf/compare/uml2/papyrus/internal/hook/migration/DelegatingInvocationHandler.java b/plugins/org.eclipse.emf.compare.uml2.papyrus/src/org/eclipse/emf/compare/uml2/papyrus/internal/hook/migration/DelegatingInvocationHandler.java new file mode 100644 index 000000000..c9e231ba1 --- /dev/null +++ b/plugins/org.eclipse.emf.compare.uml2.papyrus/src/org/eclipse/emf/compare/uml2/papyrus/internal/hook/migration/DelegatingInvocationHandler.java @@ -0,0 +1,67 @@ +/******************************************************************************* + * Copyright (c) 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 + *******************************************************************************/ +package org.eclipse.emf.compare.uml2.papyrus.internal.hook.migration; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.emf.compare.uml2.papyrus.internal.UMLPapyrusComparePlugin; + +/** + * An invocation handler that wraps a given object and delegates all method calls to this object. The + * underlying assumption therefore is that there is a 1:1 mapping between the methods of a proxy instance + * created with this handler and the object. Any errors occurring during method calls will be silently logged + * and the called proxy method will simply return null. + * + * @author Martin Fleck <mfleck@eclipsesource.com> + */ +public class DelegatingInvocationHandler implements InvocationHandler { + + /** Object handling the method calls. */ + private Object object; + + /** + * Creates a new invocation handler with the given object. Any calls made to a proxy instance created with + * this handler will be delegated to this object. If errors occur, they will be logged and the called proxy + * method will simply return null. + * + * @param object + * object handling the method calls + */ + public DelegatingInvocationHandler(Object object) { + this.object = object; + } + + // CHECKSTYLE:OFF - for Throwable + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + // CHECKSTYLE:ON + if (object == null) { + return null; + } + try { + Method objectMethod = object.getClass().getDeclaredMethod(method.getName(), + method.getParameterTypes()); + return objectMethod.invoke(object, args); + } catch (NullPointerException | NoSuchMethodException | SecurityException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException e) { + UMLPapyrusComparePlugin.getDefault().getLog() + .log(new Status(IStatus.WARNING, UMLPapyrusComparePlugin.PLUGIN_ID, + "Unable to invoke method '" + method.getName() //$NON-NLS-1$ + + "' on Object " + object, //$NON-NLS-1$ + e)); + } + return null; + } +} diff --git a/plugins/org.eclipse.emf.compare.uml2.papyrus/src/org/eclipse/emf/compare/uml2/papyrus/internal/hook/migration/ProfileNamespaceURIPatternAPI.java b/plugins/org.eclipse.emf.compare.uml2.papyrus/src/org/eclipse/emf/compare/uml2/papyrus/internal/hook/migration/ProfileNamespaceURIPatternAPI.java index 6374e923d..2476bd7df 100644 --- a/plugins/org.eclipse.emf.compare.uml2.papyrus/src/org/eclipse/emf/compare/uml2/papyrus/internal/hook/migration/ProfileNamespaceURIPatternAPI.java +++ b/plugins/org.eclipse.emf.compare.uml2.papyrus/src/org/eclipse/emf/compare/uml2/papyrus/internal/hook/migration/ProfileNamespaceURIPatternAPI.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016 EclipseSource Services GmbH and others. + * 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 @@ -7,11 +7,10 @@ * * Contributors: * Martin Fleck - initial API and implementation + * Martin Fleck - bug 515041 *******************************************************************************/ package org.eclipse.emf.compare.uml2.papyrus.internal.hook.migration; -import com.google.common.base.Optional; - import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -66,6 +65,16 @@ public final class ProfileNamespaceURIPatternAPI { private static final String COMPARISON_METHOD_IS_EQUAL_VERSIONLESS_NAMESPACE_URI = "isEqualVersionlessNamespaceURI"; //$NON-NLS-1$ /** + * Name of the isPresent method of the Optional class from Guava. + */ + private static final String OPTIONAL_METHOD_IS_PRESENT = "isPresent"; //$NON-NLS-1$ + + /** + * Name of the get method of the Optional class from Guava. + */ + private static final String OPTIONAL_METHOD_GET = "get"; //$NON-NLS-1$ + + /** * ProfileNamespaceURIPatternRegistry singleton instance. */ private static Object registryInstance; @@ -224,10 +233,26 @@ public final class ProfileNamespaceURIPatternAPI { } Object optionalComparison = callMethod(registryTryFindComparisonMethod, registryInstance, lhsNamespaceUri, rhsNamespaceUri); - if (optionalComparison instanceof Optional<?> && ((Optional<?>)optionalComparison).isPresent()) { - Object comparison = ((Optional<?>)optionalComparison).get(); - Object isEqual = callMethod(comparisonIsEqualVersionlessNamespaceURIMethod, comparison); - return isEqual != null && new Boolean(isEqual.toString()).booleanValue(); + if (optionalComparison == null) { + return false; + } + // use reflection to query result due to potentially incompatible Guava versions, cf. bug 515041 + try { + Method isPresentMethod = optionalComparison.getClass().getMethod(OPTIONAL_METHOD_IS_PRESENT); + isPresentMethod.setAccessible(true); + Object isPresent = isPresentMethod.invoke(optionalComparison); + if (isPresent != null && Boolean.parseBoolean(isPresent.toString())) { + Method getMethod = optionalComparison.getClass().getMethod(OPTIONAL_METHOD_GET); + getMethod.setAccessible(true); + Object comparison = getMethod.invoke(optionalComparison); + if (comparison != null) { + Object isEqual = callMethod(comparisonIsEqualVersionlessNamespaceURIMethod, comparison); + return isEqual != null && new Boolean(isEqual.toString()).booleanValue(); + } + } + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException + | InvocationTargetException e) { + // do nothing } return false; } diff --git a/plugins/org.eclipse.emf.compare.uml2.papyrus/src/org/eclipse/emf/compare/uml2/papyrus/internal/hook/migration/StereotypeApplicationRepair.java b/plugins/org.eclipse.emf.compare.uml2.papyrus/src/org/eclipse/emf/compare/uml2/papyrus/internal/hook/migration/StereotypeApplicationRepair.java index 3ce5ca7fb..ce0dc9c61 100644 --- a/plugins/org.eclipse.emf.compare.uml2.papyrus/src/org/eclipse/emf/compare/uml2/papyrus/internal/hook/migration/StereotypeApplicationRepair.java +++ b/plugins/org.eclipse.emf.compare.uml2.papyrus/src/org/eclipse/emf/compare/uml2/papyrus/internal/hook/migration/StereotypeApplicationRepair.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016 EclipseSource Services GmbH and others. + * 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 @@ -9,12 +9,15 @@ * 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; @@ -45,10 +48,23 @@ import org.eclipse.uml2.uml.UMLPackage; @SuppressWarnings("restriction") public class StereotypeApplicationRepair extends StereotypeApplicationRepairSnippet { - /** - * The resource under repair. - */ - private Resource resource; + /** 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. @@ -59,15 +75,15 @@ public class StereotypeApplicationRepair extends StereotypeApplicationRepairSnip public StereotypeApplicationRepair(Resource resource) { // new constructor to provide our own profile supplier super(); - this.resource = resource; - setLabelProviderService(createLabelProviderService()); - setProfileSupplier(createProfileSupplier()); + this.fResource = resource; + this.fLabelProviderService = setLabelProviderService(createLabelProviderService()); + this.fProfileSupplier = setProfileSupplier(createProfileSupplier()); } @Override public void dispose(ModelSet modelsManager) { try { - LabelProviderService s = (LabelProviderService)getSuperField("labelProviderService"); //$NON-NLS-1$ + LabelProviderService s = (LabelProviderService)getSuperField(FIELD_LABEL_PROVIDER_SERVICE); if (s != null) { s.disposeService(); } @@ -87,16 +103,21 @@ public class StereotypeApplicationRepair extends StereotypeApplicationRepairSnip * 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 void setSuperField(String fieldName, Object fieldValue) { + 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; } /** @@ -128,7 +149,7 @@ public class StereotypeApplicationRepair extends StereotypeApplicationRepairSnip */ private void setAdapter(ModelSet resourceSet) { // adapter needed to provide EPackage.Registry via the adapters resourceSet - final Object adapterObject = getSuperField("adapter"); //$NON-NLS-1$ + final Object adapterObject = getSuperField(FIELD_ADAPTER); if (adapterObject instanceof Adapter.Internal) { ((Adapter.Internal)adapterObject).setTarget(resourceSet); } @@ -142,9 +163,10 @@ public class StereotypeApplicationRepair extends StereotypeApplicationRepairSnip * * @param labelProviderService * label provider service + * @return the set label provider service or null, if the label provider service could not be set */ - private void setLabelProviderService(LabelProviderService labelProviderService) { - setSuperField("labelProviderService", labelProviderService); //$NON-NLS-1$ + private LabelProviderService setLabelProviderService(LabelProviderService labelProviderService) { + return setSuperField(FIELD_LABEL_PROVIDER_SERVICE, labelProviderService); } /** @@ -152,9 +174,10 @@ public class StereotypeApplicationRepair extends StereotypeApplicationRepairSnip * * @param profileSupplier * supplier of profiles for missing packages. + * @return the set profile supplier or null, if the profile supplier could not be set */ - protected void setProfileSupplier(Function<EPackage, Profile> profileSupplier) { - setSuperField("dynamicProfileSupplier", profileSupplier); //$NON-NLS-1$ + protected Object setProfileSupplier(Object profileSupplier) { + return setSuperField(FIELD_DYNAMIC_PROFILE_SUPPLIER, profileSupplier); } /** @@ -180,11 +203,46 @@ public class StereotypeApplicationRepair extends StereotypeApplicationRepairSnip /*** * 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 Function<EPackage, Profile> createProfileSupplier() { - return new MissingProfileSupplier(getRootElement(resource)); + 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)); } /** @@ -193,7 +251,7 @@ public class StereotypeApplicationRepair extends StereotypeApplicationRepairSnip * @return resource */ public Resource getResource() { - return resource; + return fResource; } /** @@ -207,11 +265,20 @@ public class StereotypeApplicationRepair extends StereotypeApplicationRepairSnip protected ModelSet createModelSetWrapper(ResourceSet resourceSet) { final ModelSetWrapper modelSet = new ModelSetWrapper(resourceSet); // avoid read-only for our resource - modelSet.setReadOnly(resource, Boolean.FALSE); + 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 @@ -221,19 +288,28 @@ public class StereotypeApplicationRepair extends StereotypeApplicationRepairSnip * @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 = resource.getResourceSet(); + final ResourceSet resourceSet = fResource.getResourceSet(); final ModelSet modelSet = createModelSetWrapper(resourceSet); setAdapter(modelSet); - modelSet.getResources().add(resource); - final ZombieStereotypesDescriptor stereotypesDescriptor = getZombieStereotypes(resource); - resourceSet.getResources().add(resource); + modelSet.getResources().add(fResource); + final ZombieStereotypesDescriptor stereotypesDescriptor = getZombieStereotypes(fResource); + resourceSet.getResources().add(fResource); return stereotypesDescriptor; // CHECKSTYLE:OFF } catch (Exception e) { // CHECKSTYLE:ON - resource.getErrors().add(new ProfileMigrationDiagnostic( - UMLPapyrusCompareMessages.getString("profile.migration.exception", e, resource))); //$NON-NLS-1$ + 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$ |