Skip to main content
summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Fleck2017-04-13 10:02:50 +0000
committerMartin Fleck2017-04-13 11:04:13 +0000
commitb749253052bfd1a97ea9c87dc9941244e09308ad (patch)
tree127d39be552124b3beb7e4f65df550f8714b0287
parent6b0705d7642b83bdd546cface671e619616dac9e (diff)
downloadorg.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>
-rw-r--r--plugins/org.eclipse.emf.compare.uml2.papyrus/src/org/eclipse/emf/compare/uml2/papyrus/internal/hook/migration/DelegatingInvocationHandler.java67
-rw-r--r--plugins/org.eclipse.emf.compare.uml2.papyrus/src/org/eclipse/emf/compare/uml2/papyrus/internal/hook/migration/ProfileNamespaceURIPatternAPI.java39
-rw-r--r--plugins/org.eclipse.emf.compare.uml2.papyrus/src/org/eclipse/emf/compare/uml2/papyrus/internal/hook/migration/StereotypeApplicationRepair.java126
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$

Back to the top