488223: EMFTVMUtil#getMethodSignature() causes method lookup collisions 

Task-Url: https://bugs.eclipse.org/bugs/show_bug.cgi?id=488223
diff --git a/plugins/org.eclipse.m2m.atl.emftvm/src/org/eclipse/m2m/atl/emftvm/util/EMFTVMUtil.java b/plugins/org.eclipse.m2m.atl.emftvm/src/org/eclipse/m2m/atl/emftvm/util/EMFTVMUtil.java
index dc67540..67f09e7 100644
--- a/plugins/org.eclipse.m2m.atl.emftvm/src/org/eclipse/m2m/atl/emftvm/util/EMFTVMUtil.java
+++ b/plugins/org.eclipse.m2m.atl.emftvm/src/org/eclipse/m2m/atl/emftvm/util/EMFTVMUtil.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2011-2015 Dennis Wagelaar, Vrije Universiteit Brussel.
+ * Copyright (c) 2011-2017 Dennis Wagelaar, Vrije Universiteit Brussel.
  * 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
@@ -17,6 +17,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.PrintStream;
+import java.lang.ref.WeakReference;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -25,7 +26,6 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
-import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedHashSet;
 import java.util.List;
@@ -34,6 +34,8 @@
 import java.util.Set;
 import java.util.StringTokenizer;
 import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 import java.util.regex.Pattern;
 
 import org.eclipse.emf.common.util.EList;
@@ -157,7 +159,12 @@
 	 * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
 	 * @author <a href="mailto:dennis.wagelaar@vub.ac.be">Dennis Wagelaar</a>
 	 */
-	private static final Map<Class<?>, Map<MethodSignature, Method>> METHOD_CACHE = new WeakHashMap<Class<?>, Map<MethodSignature, Method>>();
+	private static final ConcurrentMap<MethodSignature, WeakReference<Method>> METHOD_CACHE = new ConcurrentHashMap<MethodSignature, WeakReference<Method>>();
+
+	/**
+	 * Represents the <code>null</code> method reference for the {@link #METHOD_CACHE}.
+	 */
+	private static final WeakReference<Method> NULL_METHOD_REFERENCE = new WeakReference<Method>(null);
 
 	/**
 	 * Cache used to store the found root methods for native Java methods.
@@ -167,7 +174,8 @@
 	 * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
 	 * @author <a href="mailto:dennis.wagelaar@vub.ac.be">Dennis Wagelaar</a>
 	 */
-	private static final Map<Method, Method> ROOT_METHOD_CACHE = Collections.synchronizedMap(new WeakHashMap<Method, Method>());
+	private static final Map<Method, WeakReference<Method>> ROOT_METHOD_CACHE = Collections
+			.synchronizedMap(new WeakHashMap<Method, WeakReference<Method>>());
 
 	/**
 	 * Singleton {@link RegistryTypeSwitch} instance.
@@ -178,6 +186,11 @@
 	private static Metamodel emfTvmMetamodel;
 	private static Metamodel traceMetamodel;
 
+	private static volatile int MethodCacheAccesses;
+	private static volatile int MethodCacheHits;
+	private static volatile int RootMethodCacheAccesses;
+	private static volatile int RootMethodCacheHits;
+
 	/**
 	 * Not used.
 	 */
@@ -1400,7 +1413,7 @@
 	 *            The class of the method
 	 * @param opname
 	 *            The method name
-	 * @param argumentTypes
+	 * @param argTypes
 	 *            The types of all arguments
 	 * @param isStatic
 	 *            Whether to look for a static method or not
@@ -1410,61 +1423,21 @@
 	 * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
 	 * @author <a href="mailto:dennis.wagelaar@vub.ac.be">Dennis Wagelaar</a>
 	 */
-	// TODO implement multi-methods in ExecEnv
 	public static Method findNativeMethod(final Class<?> context, final String opname, final Class<?>[] argTypes, final boolean isStatic) {
 		if (context == Void.TYPE) {
 			return null; // Java methods cannot be invoked on null, or defined on Void
 		}
 
-		final MethodSignature sig = getMethodSignature(opname, argTypes, isStatic);
-		Method ret = findCachedMethod(context, sig);
-		if (ret != null || hasCachedMethod(context, sig)) {
-			return ret;
+		final MethodSignature sig = getMethodSignature(context, opname, argTypes, isStatic);
+		MethodCacheAccesses++;
+		final WeakReference<Method> methodRef = METHOD_CACHE.get(sig);
+		final Method ret = methodRef != null ? methodRef.get() : null;
+		if (ret != null || methodRef == NULL_METHOD_REFERENCE) {
+			MethodCacheHits++;
+			return methodRef.get();
 		}
 
-		final Method[] methods = context.getDeclaredMethods();
-		METHODS: for (int i = 0; i < methods.length; i++) {
-			Method method = methods[i];
-			if ((Modifier.isStatic(method.getModifiers()) == isStatic) && method.getName().equals(opname)) {
-				Class<?>[] pts = method.getParameterTypes();
-				if (pts.length == argTypes.length) {
-					boolean ok = true;
-					for (int j = 0; (j < pts.length) && ok; j++) {
-						if (argTypes[j] == EnumLiteral.class && Enumerator.class.isAssignableFrom(pts[j])) {
-							continue;
-						}
-						if (!pts[j].isAssignableFrom(argTypes[j])) {
-							if (pts[j] == boolean.class)
-								ok = argTypes[j] == Boolean.class;
-							else if (pts[j] == int.class)
-								ok = argTypes[j] == Integer.class;
-							else if (pts[j] == char.class)
-								ok = argTypes[j] == Character.class;
-							else if (pts[j] == long.class)
-								ok = argTypes[j] == Long.class;
-							else if (pts[j] == float.class)
-								ok = argTypes[j] == Float.class;
-							else if (pts[j] == double.class)
-								ok = argTypes[j] == Double.class;
-							else
-								ok = argTypes[j] == Void.TYPE; // any type
-						}
-					}
-					if (ok) {
-						ret = method;
-						break METHODS;
-					}
-				}
-			}
-		}
-
-		if ((ret == null) && !isStatic && (context.getSuperclass() != null)) {
-			ret = findNativeMethod(context.getSuperclass(), opname, argTypes, isStatic);
-		}
-
-		cacheMethod(context, sig, ret);
-
-		return ret;
+		return findNativeMethodInternal(context, opname, argTypes, isStatic, sig);
 	}
 
 	/**
@@ -1484,61 +1457,21 @@
 	 * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
 	 * @author <a href="mailto:dennis.wagelaar@vub.ac.be">Dennis Wagelaar</a>
 	 */
-	// TODO implement multi-methods in ExecEnv
 	public static Method findNativeMethod(final Class<?> context, final String opname, final Class<?> argType, final boolean isStatic) {
 		if (context == Void.TYPE) {
 			return null; // Java methods cannot be invoked on null, or defined on Void
 		}
 
-		final MethodSignature sig = getMethodSignature(opname, argType, isStatic);
-		Method ret = findCachedMethod(context, sig);
-		if (ret != null || hasCachedMethod(context, sig)) {
+		final MethodSignature sig = getMethodSignature(context, opname, argType, isStatic);
+		MethodCacheAccesses++;
+		final WeakReference<Method> methodRef = METHOD_CACHE.get(sig);
+		final Method ret = methodRef != null ? methodRef.get() : null;
+		if (ret != null || methodRef == NULL_METHOD_REFERENCE) {
+			MethodCacheHits++;
 			return ret;
 		}
 
-		final Method[] methods = context.getDeclaredMethods();
-		METHODS: for (int i = 0; i < methods.length; i++) {
-			Method method = methods[i];
-			if ((Modifier.isStatic(method.getModifiers()) == isStatic) && method.getName().equals(opname)) {
-				Class<?>[] pts = method.getParameterTypes();
-				if (pts.length == 1) {
-					Class<?> pt = pts[0];
-					if (argType == EnumLiteral.class && Enumerator.class.isAssignableFrom(pt)) {
-						ret = method;
-						break METHODS;
-					}
-					boolean ok = true;
-					if (!pt.isAssignableFrom(argType)) {
-						if (pt == boolean.class)
-							ok = argType == Boolean.class;
-						else if (pt == int.class)
-							ok = argType == Integer.class;
-						else if (pt == char.class)
-							ok = argType == Character.class;
-						else if (pt == long.class)
-							ok = argType == Long.class;
-						else if (pt == float.class)
-							ok = argType == Float.class;
-						else if (pt == double.class)
-							ok = argType == Double.class;
-						else
-							ok = argType == Void.TYPE; // any type
-					}
-					if (ok) {
-						ret = method;
-						break METHODS;
-					}
-				}
-			}
-		}
-
-		if ((ret == null) && !isStatic && (context.getSuperclass() != null)) {
-			ret = findNativeMethod(context.getSuperclass(), opname, argType, isStatic);
-		}
-
-		cacheMethod(context, sig, ret);
-
-		return ret;
+		return findNativeMethodInternal(context, opname, argType, isStatic, sig);
 	}
 
 	/**
@@ -1561,12 +1494,126 @@
 			return null; // Java methods cannot be invoked on null, or defined on Void
 		}
 
-		final MethodSignature sig = getMethodSignature(opname, isStatic);
-		Method ret = findCachedMethod(context, sig);
-		if (ret != null || hasCachedMethod(context, sig)) {
+		final MethodSignature sig = getMethodSignature(context, opname, isStatic);
+		MethodCacheAccesses++;
+		final WeakReference<Method> methodRef = METHOD_CACHE.get(sig);
+		Method ret = methodRef != null ? methodRef.get() : null;
+		if (ret != null || methodRef == NULL_METHOD_REFERENCE) {
+			MethodCacheHits++;
 			return ret;
 		}
 
+		return findNativeMethodInternal(context, opname, isStatic, sig);
+	}
+
+	/**
+	 * Looks for a native Java method.
+	 * 
+	 * @param context
+	 *            The class of the method
+	 * @param opname
+	 *            The method name
+	 * @param argTypes
+	 *            The types of all arguments
+	 * @param isStatic
+	 *            Whether to look for a static method or not
+	 * @param sig
+	 *            the method signature
+	 * @return the method if found, null otherwise
+	 */
+	private static Method findNativeMethodInternal(final Class<?> context, final String opname,
+			final Class<?>[] argTypes, final boolean isStatic, final MethodSignature sig) {
+		Method ret = null;
+	
+		final Method[] methods = context.getDeclaredMethods();
+		METHODS: for (int i = 0; i < methods.length; i++) {
+			Method method = methods[i];
+			if ((Modifier.isStatic(method.getModifiers()) == isStatic) && method.getName().equals(opname)) {
+				Class<?>[] pts = method.getParameterTypes();
+				if (pts.length == argTypes.length) {
+					boolean ok = true;
+					for (int j = 0; (j < pts.length) && ok; j++) {
+						if (!checkParameterType(argTypes[j], pts[j])) {
+							ok = false;
+							break;
+						}
+					}
+					if (ok) {
+						ret = method;
+						break METHODS;
+					}
+				}
+			}
+		}
+	
+		if ((ret == null) && !isStatic && (context.getSuperclass() != null)) {
+			ret = findNativeMethodInternal(context.getSuperclass(), opname, argTypes, isStatic, sig);
+		} else {
+			METHOD_CACHE.put(sig, ret != null ? new WeakReference<Method>(ret) : NULL_METHOD_REFERENCE);
+		}
+	
+		return ret;
+	}
+
+	/**
+	 * Looks for a native Java method.
+	 * 
+	 * @param context
+	 *            The class of the method
+	 * @param opname
+	 *            The method name
+	 * @param argType
+	 *            The type of the argument
+	 * @param isStatic
+	 *            Whether to look for a static method or not
+	 * @param sig
+	 *            the method signature
+	 * @return the method if found, null otherwise
+	 */
+	private static Method findNativeMethodInternal(final Class<?> context, final String opname, final Class<?> argType,
+			final boolean isStatic, final MethodSignature sig) {
+		Method ret = null;
+	
+		final Method[] methods = context.getDeclaredMethods();
+		METHODS: for (int i = 0; i < methods.length; i++) {
+			Method method = methods[i];
+			if ((Modifier.isStatic(method.getModifiers()) == isStatic) && method.getName().equals(opname)) {
+				Class<?>[] pts = method.getParameterTypes();
+				if (pts.length == 1) {
+					if (checkParameterType(argType, pts[0])) {
+						ret = method;
+						break METHODS;
+					}
+				}
+			}
+		}
+	
+		if ((ret == null) && !isStatic && (context.getSuperclass() != null)) {
+			ret = findNativeMethodInternal(context.getSuperclass(), opname, argType, isStatic, sig);
+		} else {
+			METHOD_CACHE.put(sig, ret != null ? new WeakReference<Method>(ret) : NULL_METHOD_REFERENCE);
+		}
+	
+		return ret;
+	}
+
+	/**
+	 * Looks for a native Java method without arguments.
+	 * 
+	 * @param context
+	 *            The class of the method
+	 * @param opname
+	 *            The method name
+	 * @param isStatic
+	 *            Whether to look for a static method or not
+	 * @param sig
+	 *            the method signature
+	 * @return the method if found, null otherwise
+	 */
+	private static Method findNativeMethodInternal(final Class<?> context, final String opname, final boolean isStatic,
+			final MethodSignature sig) {
+		Method ret = null;
+
 		final Method[] methods = context.getDeclaredMethods();
 		METHODS: for (int i = 0; i < methods.length; i++) {
 			Method method = methods[i];
@@ -1579,15 +1626,53 @@
 		}
 
 		if ((ret == null) && !isStatic && (context.getSuperclass() != null)) {
-			ret = findNativeMethod(context.getSuperclass(), opname, isStatic);
+			ret = findNativeMethodInternal(context.getSuperclass(), opname, isStatic, sig);
+		} else {
+			METHOD_CACHE.put(sig, ret != null ? new WeakReference<Method>(ret) : NULL_METHOD_REFERENCE);
 		}
 
-		cacheMethod(context, sig, ret);
-
 		return ret;
 	}
 
 	/**
+	 * Checks whether the parameter type is compatible with the argument type
+	 * 
+	 * @param argType
+	 *            the invocation argument type
+	 * @param pt
+	 *            the method parameter type
+	 * @return <code>true</code> if the parameter type is compatible,
+	 *         <code>false</code> otherwise
+	 */
+	private static boolean checkParameterType(final Class<?> argType, final Class<?> pt) {
+		if (argType == EnumLiteral.class && Enumerator.class.isAssignableFrom(pt)) {
+			return true;
+		}
+		if (pt.isAssignableFrom(argType)) {
+			return true;
+		}
+		if (pt == boolean.class){
+			return argType == Boolean.class;
+		}
+		if (pt == int.class) {
+			return argType == Integer.class;
+		}
+		if (pt == char.class) {
+			return argType == Character.class;
+		}
+		if (pt == long.class) {
+			return argType == Long.class;
+		}
+		if (pt == float.class) {
+			return argType == Float.class;
+		}
+		if (pt == double.class) {
+			return argType == Double.class;
+		}
+		return argType == Void.TYPE; // any type
+	}
+
+	/**
 	 * Compares 0-parameter <code>op</code> to <code>method</code>.
 	 * 
 	 * @param op
@@ -1951,113 +2036,71 @@
 	}
 
 	/**
-	 * Returns <code>true</code> if the method cache contains the given caller and signature.
-	 * 
-	 * @param caller
-	 *            The class of the method
-	 * @param signature
-	 *            The method signature
-	 * @return <code>true</code> if the method cache contains the given caller and signature
-	 */
-	private static boolean hasCachedMethod(final Class<?> caller, final MethodSignature signature) {
-		synchronized (METHOD_CACHE) {
-			final Map<MethodSignature, Method> sigMap = METHOD_CACHE.get(caller);
-			return (sigMap != null) && sigMap.containsKey(signature);
-		}
-	}
-
-	/**
-	 * Find a method in the cache.
-	 * 
-	 * @param caller
-	 *            The class of the method
-	 * @param signature
-	 *            The method signature
-	 * @return the method
-	 * @author <a href="mailto:frederic.jouault@univ-nantes.fr">Frederic Jouault</a>
-	 * @author <a href="mailto:william.piers@obeo.fr">William Piers</a>
-	 * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
-	 * @author <a href="mailto:dennis.wagelaar@vub.ac.be">Dennis Wagelaar</a>
-	 */
-	private static Method findCachedMethod(final Class<?> caller, final MethodSignature signature) {
-		synchronized (METHOD_CACHE) {
-			final Map<MethodSignature, Method> sigMap = METHOD_CACHE.get(caller);
-			return (sigMap != null) ? sigMap.get(signature) : null;
-		}
-	}
-
-	/**
-	 * Stores a method in a cache.
-	 * 
-	 * @param caller
-	 *            The class of the method
-	 * @param signature
-	 *            The method signature
-	 * @param method
-	 *            The method to store
-	 * @author <a href="mailto:frederic.jouault@univ-nantes.fr">Frederic Jouault</a>
-	 * @author <a href="mailto:william.piers@obeo.fr">William Piers</a>
-	 * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
-	 * @author <a href="mailto:dennis.wagelaar@vub.ac.be">Dennis Wagelaar</a>
-	 */
-	private static void cacheMethod(final Class<?> caller, final MethodSignature signature, final Method method) {
-		synchronized (METHOD_CACHE) {
-			Map<MethodSignature, Method> sigMap = METHOD_CACHE.get(caller);
-			if (sigMap == null) {
-				sigMap = new HashMap<MethodSignature, Method>();
-				METHOD_CACHE.put(caller, sigMap);
-			}
-			sigMap.put(signature, method);
-		}
-	}
-
-	/**
 	 * Generates a signature to store methods.
 	 * 
+	 * @param context
+	 *            the method declaring class
 	 * @param name
+	 *            the method name
 	 * @param argumentTypes
+	 *            the argument types
 	 * @param isStatic
+	 *            whether the method is static
 	 * @return The method signature
-	 * @author <a href="mailto:frederic.jouault@univ-nantes.fr">Frederic Jouault</a>
+	 * @author <a href="mailto:frederic.jouault@univ-nantes.fr">Frederic
+	 *         Jouault</a>
 	 * @author <a href="mailto:william.piers@obeo.fr">William Piers</a>
 	 * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
 	 * @author <a href="mailto:dennis.wagelaar@vub.ac.be">Dennis Wagelaar</a>
 	 */
-	private static MethodSignature getMethodSignature(final String name, final Class<?>[] argumentTypes,
-			final boolean isStatic) {
-		return new MethodSignature(name, argumentTypes, isStatic);
+	private static MethodSignature getMethodSignature(final Class<?> context, final String name,
+			final Class<?>[] argumentTypes, final boolean isStatic) {
+		return new MethodSignature(context, name, argumentTypes, isStatic);
 	}
 
 	/**
 	 * Generates a signature to store methods.
 	 * 
+	 * @param context
+	 *            the method declaring class
 	 * @param name
+	 *            the method name
 	 * @param argumentType
+	 *            the (single) argument type
+	 * @param isStatic
+	 *            whether the method is static
 	 * @param isStatic
 	 * @return The method signature
-	 * @author <a href="mailto:frederic.jouault@univ-nantes.fr">Frederic Jouault</a>
+	 * @author <a href="mailto:frederic.jouault@univ-nantes.fr">Frederic
+	 *         Jouault</a>
 	 * @author <a href="mailto:william.piers@obeo.fr">William Piers</a>
 	 * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
 	 * @author <a href="mailto:dennis.wagelaar@vub.ac.be">Dennis Wagelaar</a>
 	 */
-	private static MethodSignature getMethodSignature(final String name, final Class<?> argumentType,
-			final boolean isStatic) {
-		return new MethodSignature(name, new Class<?>[] { argumentType }, isStatic);
+	private static MethodSignature getMethodSignature(final Class<?> context, final String name,
+			final Class<?> argumentType, final boolean isStatic) {
+		return new MethodSignature(context, name, new Class<?>[] { argumentType }, isStatic);
 	}
 
 	/**
 	 * Generates a signature to store methods.
 	 * 
+	 * @param context
+	 *            the method declaring class
 	 * @param name
+	 *            the method name
 	 * @param isStatic
+	 *            whether the method is static
 	 * @return The method signature
-	 * @author <a href="mailto:frederic.jouault@univ-nantes.fr">Frederic Jouault</a>
+	 * @author <a href="mailto:frederic.jouault@univ-nantes.fr">Frederic
+	 *         Jouault</a>
 	 * @author <a href="mailto:william.piers@obeo.fr">William Piers</a>
 	 * @author <a href="mailto:mikael.barbero@obeo.fr">Mikael Barbero</a>
 	 * @author <a href="mailto:dennis.wagelaar@vub.ac.be">Dennis Wagelaar</a>
 	 */
-	private static MethodSignature getMethodSignature(final String name, final boolean isStatic) {
-		return new MethodSignature(name, null, isStatic);
+	private static MethodSignature getMethodSignature(final Class<?> context, final String name,
+			final boolean isStatic) {
+		return new MethodSignature(context, name, null, isStatic);
 	}
 
 	/**
@@ -2460,12 +2503,15 @@
 		if (method == null) {
 			return null;
 		}
-		Method rootMethod = ROOT_METHOD_CACHE.get(method);
+		RootMethodCacheAccesses++;
+		final WeakReference<Method> rootMethodReference = ROOT_METHOD_CACHE.get(method);
+		Method rootMethod = rootMethodReference != null ? rootMethodReference.get() : null;
 		if (rootMethod != null) {
+			RootMethodCacheHits++;
 			return rootMethod;
 		}
 		rootMethod = findRootMethodInner(method);
-		ROOT_METHOD_CACHE.put(method, rootMethod);
+		ROOT_METHOD_CACHE.put(method, new WeakReference<Method>(rootMethod));
 		return rootMethod;
 	}
 
@@ -2521,4 +2567,24 @@
 		return methodModifiers & (Modifier.PRIVATE + Modifier.PROTECTED + Modifier.PUBLIC + Modifier.STATIC);
 	}
 
+	/**
+	 * Returns the hit rate of the method cache.
+	 * 
+	 * @return the hit rate of the method cache, or <code>-1.0</code> if no hits
+	 *         were recorded yet
+	 */
+	public static double getMethodCacheHitRate() {
+		return MethodCacheAccesses > 0 ? (double) MethodCacheHits / (double) MethodCacheAccesses : -1.0;
+	}
+
+	/**
+	 * Returns the hit rate of the root method cache.
+	 * 
+	 * @return the hit rate of the root method cache, or <code>-1.0</code> if no
+	 *         hits were recorded yet
+	 */
+	public static double getRootMethodCacheHitRate() {
+		return RootMethodCacheAccesses > 0 ? (double) RootMethodCacheHits / (double) RootMethodCacheAccesses : -1.0;
+	}
+
 }
diff --git a/plugins/org.eclipse.m2m.atl.emftvm/src/org/eclipse/m2m/atl/emftvm/util/MethodSignature.java b/plugins/org.eclipse.m2m.atl.emftvm/src/org/eclipse/m2m/atl/emftvm/util/MethodSignature.java
index 6c44da0..f150c0f 100644
--- a/plugins/org.eclipse.m2m.atl.emftvm/src/org/eclipse/m2m/atl/emftvm/util/MethodSignature.java
+++ b/plugins/org.eclipse.m2m.atl.emftvm/src/org/eclipse/m2m/atl/emftvm/util/MethodSignature.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2016 Dennis Wagelaar.
+ * Copyright (c) 2016-2017 Dennis Wagelaar.
  * 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
@@ -21,8 +21,9 @@
  */
 public final class MethodSignature {
 
+	private final String context;
 	private final String name;
-	private final Class<?>[] argumentTypes;
+	private final String[] argumentTypes;
 	private final boolean isStatic;
 
 	/**
@@ -35,14 +36,31 @@
 	 * @param isStatic
 	 *            whether the method is static
 	 */
-	public MethodSignature(String name, Class<?>[] argumentTypes, boolean isStatic) {
+	public MethodSignature(Class<?> context, String name, Class<?>[] argumentTypes, boolean isStatic) {
 		super();
+		this.context = context.getName();
 		this.name = name;
-		this.argumentTypes = argumentTypes;
+		if (argumentTypes != null) {
+			this.argumentTypes = new String[argumentTypes.length];
+			for (int i = 0; i < argumentTypes.length; i++) {
+				this.argumentTypes[i] = argumentTypes[i].getName();
+			}
+		} else {
+			this.argumentTypes = null;
+		}
 		this.isStatic = isStatic;
 	}
 
 	/**
+	 * Returns the method context (i.e. declaring class).
+	 * 
+	 * @return the context
+	 */
+	public String getContext() {
+		return context;
+	}
+
+	/**
 	 * Returns the method name.
 	 * 
 	 * @return the name
@@ -56,7 +74,7 @@
 	 * 
 	 * @return the argumentTypes
 	 */
-	public Class<?>[] getArgumentTypes() {
+	public String[] getArgumentTypes() {
 		return argumentTypes;
 	}
 
@@ -76,9 +94,10 @@
 	public int hashCode() {
 		final int prime = 31;
 		int result = 1;
-		result = prime * result + Arrays.hashCode(argumentTypes);
-		result = prime * result + (isStatic ? 1231 : 1237);
+		result = prime * result + ((context == null) ? 0 : context.hashCode());
 		result = prime * result + ((name == null) ? 0 : name.hashCode());
+		result = prime * result + (isStatic ? 1231 : 1237);
+		result = prime * result + Arrays.hashCode(argumentTypes);
 		return result;
 	}
 
@@ -94,15 +113,20 @@
 		if (getClass() != obj.getClass())
 			return false;
 		MethodSignature other = (MethodSignature) obj;
-		if (!Arrays.equals(argumentTypes, other.argumentTypes))
-			return false;
-		if (isStatic != other.isStatic)
+		if (context == null) {
+			if (other.context != null)
+				return false;
+		} else if (!context.equals(other.context))
 			return false;
 		if (name == null) {
 			if (other.name != null)
 				return false;
 		} else if (!name.equals(other.name))
 			return false;
+		if (isStatic != other.isStatic)
+			return false;
+		if (!Arrays.equals(argumentTypes, other.argumentTypes))
+			return false;
 		return true;
 	}
 
@@ -111,8 +135,8 @@
 	 */
 	@Override
 	public String toString() {
-		return "MethodSignature [name=" + name + ", argumentTypes=" + Arrays.toString(argumentTypes) + ", isStatic="
-				+ isStatic + "]";
+		return "MethodSignature [context=" + context + ", name=" + name + ", argumentTypes="
+				+ Arrays.toString(argumentTypes) + ", isStatic=" + isStatic + "]";
 	}
 
 }
diff --git a/tests/org.eclipse.m2m.atl.emftvm.tests/src/org/eclipse/m2m/atl/emftvm/util/tests/EMFTVMUtilTest.java b/tests/org.eclipse.m2m.atl.emftvm.tests/src/org/eclipse/m2m/atl/emftvm/util/tests/EMFTVMUtilTest.java
index abbe575..165ab38 100644
--- a/tests/org.eclipse.m2m.atl.emftvm.tests/src/org/eclipse/m2m/atl/emftvm/util/tests/EMFTVMUtilTest.java
+++ b/tests/org.eclipse.m2m.atl.emftvm.tests/src/org/eclipse/m2m/atl/emftvm/util/tests/EMFTVMUtilTest.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2016 Dennis Wagelaar.
+ * Copyright (c) 2016-2017 Dennis Wagelaar.
  * 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
@@ -13,6 +13,9 @@
 
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
 
 import org.eclipse.emf.ecore.EClass;
 import org.eclipse.emf.ecore.EReference;
@@ -178,4 +181,55 @@
 		}
 	}
 
+	/**
+	 * Test method for {@link EMFTVMUtil#findRootMethod(Method)}.
+	 */
+	public void testFindRootMethod() {
+		final Map<String, String> sourceMap = new HashMap<String, String>();
+		sourceMap.put("key", "value");
+		final Map<String, String> wrapperMap = Collections.unmodifiableMap(sourceMap);
+		final Method getMethod = EMFTVMUtil.findNativeMethod(wrapperMap.getClass(), "get", String.class, false);
+
+		assertNotNull(getMethod);
+		assertNotSame(Map.class, getMethod.getDeclaringClass());
+		assertTrue(Modifier.isPrivate(getMethod.getDeclaringClass().getModifiers()));
+
+		final Method rootMethod = EMFTVMUtil.findRootMethod(getMethod);
+
+		assertNotNull(rootMethod);
+		assertNotSame(getMethod, rootMethod);
+		assertEquals(Map.class, rootMethod.getDeclaringClass());
+	}
+
+	/**
+	 * Test method for {@link EMFTVMUtil#getMethodCacheHitRate()}.
+	 */
+	public void testGetMethodCacheHitRate() {
+		testFindNativeMethodClassOfQStringBoolean();
+		testFindNativeMethodClassOfQStringBoolean();
+		testFindNativeMethodClassOfQStringBoolean();
+		testFindNativeMethodClassOfQStringClassOfQBoolean();
+		testFindNativeMethodClassOfQStringClassOfQBoolean();
+		testFindNativeMethodClassOfQStringClassOfQBoolean();
+		testFindNativeMethodClassOfQStringClassOfQArrayBoolean();
+		testFindNativeMethodClassOfQStringClassOfQArrayBoolean();
+		testFindNativeMethodClassOfQStringClassOfQArrayBoolean();
+
+		final double hitRate = EMFTVMUtil.getMethodCacheHitRate();
+
+		assertTrue("Expected hitRate >= 0.5, but was " + hitRate, hitRate >= 0.5);
+	}
+
+	/**
+	 * Test method for {@link EMFTVMUtil#getRootMethodCacheHitRate()}.
+	 */
+	public void testGetRootMethodCacheHitRate() {
+		testFindRootMethod();
+		testFindRootMethod();
+
+		final double hitRate = EMFTVMUtil.getRootMethodCacheHitRate();
+
+		assertTrue("Expected hitRate >= 0.5, but was " + hitRate, hitRate >= 0.5);
+	}
+
 }