/******************************************************************************* * Copyright (c) 2007, 2014 compeople AG 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: * compeople AG - initial API and implementation *******************************************************************************/ package org.eclipse.riena.core.util; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Arrays; import java.util.Map; import org.osgi.framework.Bundle; import org.eclipse.core.runtime.Assert; import org.eclipse.riena.core.cache.LRUHashMap; import org.eclipse.riena.internal.core.Activator; import org.eclipse.riena.internal.core.ignore.IgnoreFindBugs; /** * The <code>ReflectionUtils</code> class is a collection of useful helpers when * working with reflection. <br> * <b>Note:</b> All methods only throw Failures (i.e. RuntimeExceptions)!<br> * <b>Note:</b> Use this helper only for test code!!! */ public final class ReflectionUtils { /** * */ private static final Class<Void> NULL_PARAMETER_TYPE = Void.class; /** A cache of Method (or NO_SUCH_METHOD) values. */ private static final Map<Integer, Object> METHOD_CACHE = LRUHashMap.createSynchronizedLRUHashMap(25); /** Placeholder indicating a 'null' result (vs a cache miss) */ private static final Object NO_SUCH_METHOD = new Object() { }; /** * Private default constructor. */ private ReflectionUtils() { // Utility class } /** * Create a new instance of type �className� by invoking the constructor * with the given list of arguments. * * @param className * the type of new instance. * @param args * the arguments for the constructor. * @return the new instance. * @pre className != null */ @SuppressWarnings("unchecked") public static <T> T newInstance(final String className, final Object... args) { Assert.isNotNull(className, "className must be given!"); //$NON-NLS-1$ try { return (T) newInstance(false, loadClass(className), args); } catch (final Exception e) { throw new ReflectionFailure("Error creating instance for " + className + " with parameters " //$NON-NLS-1$ //$NON-NLS-2$ + Arrays.asList(args) + "!", e); //$NON-NLS-1$ } } /** * Create a new instance of type �className� by invoking the constructor * with the given list of arguments. * * @param className * the type of new instance. * @param args * the arguments for the constructor. * @return the new instance. * @pre className != null */ @SuppressWarnings("unchecked") public static <T> T newInstanceHidden(final String className, final Object... args) { Assert.isNotNull(className, "className must be given!"); //$NON-NLS-1$ try { return (T) newInstance(true, loadClass(className), args); } catch (final Exception e) { throw new ReflectionFailure("Error creating instance for " + className + " with parameters " //$NON-NLS-1$ //$NON-NLS-2$ + Arrays.asList(args) + "!", e); //$NON-NLS-1$ } } /** * Create a new instance of type �clazz� by invoking the constructor with * the given list of arguments. * * @param clazz * the type of new instance. * @param args * the arguments for the constructor. * @return the new instance. * @pre clazz != null */ public static <T> T newInstance(final Class<T> clazz, final Object... args) { return newInstance(false, clazz, args); } /** * Create a new instance of type �clazz� by invoking the �private� * constructor with the given list of arguments. * * @param clazz * the type of new instance. * @param args * the arguments for the constructor. * @return the new instance. * @pre clazz != null */ public static <T> T newInstanceHidden(final Class<T> clazz, final Object... args) { return newInstance(true, clazz, args); } /** * Create a new proxy instance of type �interfaceName�. * * @param interfaceName * the type of new instance. * @param invocationHandler * the invocation handler for the proxy. * @return the new instance. * @pre interfaceName != null * @pre invocationHandler != null */ @SuppressWarnings("unchecked") public static <T> T newInstance(final String interfaceName, final InvocationHandler invocationHandler) { Assert.isNotNull(interfaceName, "interfaceName must be given!"); //$NON-NLS-1$ Assert.isNotNull(invocationHandler, "invocationHandler must be given!"); //$NON-NLS-1$ try { return (T) newInstance(Class.forName(interfaceName), invocationHandler); } catch (final Exception e) { throw new ReflectionFailure("Error creating proxy instance for " + interfaceName + " !", e); //$NON-NLS-1$ //$NON-NLS-2$ } } /** * Create a new proxy instance of type �interfaze�. * * @param interfaze * the type of new instance. * @param invocationHandler * the invocation handler for the proxy. * @return the new instance. * @pre interfaze != null * @pre invocationHandler != null */ public static <T> T newInstance(final Class<T> interfaze, final InvocationHandler invocationHandler) { Assert.isNotNull(interfaze, "interfaceName must be given"); //$NON-NLS-1$ Assert.isNotNull(invocationHandler, "invocationHandler must be given"); //$NON-NLS-1$ final Class<?> proxyClass = Proxy.getProxyClass(interfaze.getClassLoader(), new Class<?>[] { interfaze }); try { final Constructor<T> constructor = (Constructor<T>) proxyClass .getConstructor(new Class[] { InvocationHandler.class }); final T object = constructor.newInstance(new Object[] { invocationHandler }); return object; } catch (final Throwable throwable) { throw new ReflectionFailure("Error creating proxy instance for " + interfaze.getName() + " !", throwable); //$NON-NLS-1$ //$NON-NLS-2$ } } /** * Invoke an accessible method on a given object with the given parameters. * * @param instance * the instance to invoke the method on, or the class for static * methods. * @param methodName * the name of the method to invoke. * @param args * the actual arguments of the method to invoke. * @return the result of the invocation. * @pre instance != null * @pre methodName != null */ public static <T> T invoke(final Object instance, final String methodName, final Object... args) { return invoke(false, instance, methodName, args); } /** * Invoke a �hidden� (private, protected, ...) method on a given object. <br> * <b>This method should not be used for production code but only for unit * tests! </b> * * @param instance * the instance to invoke the method on, or the class for static * methods. * @param methodName * the name of the method to invoke. * @param args * the actual arguments of the method to invoke. * @return the result of the invocation. * @pre instance != null * @pre methodName != null */ public static <T> T invokeHidden(final Object instance, final String methodName, final Object... args) { return invoke(true, instance, methodName, args); } /** * Invoke an acccesible method on a given object allowing this method to * throw the given type of exception.<br> * <b>This method should not be used for production code but only for unit * tests! </b> * * @param <T> * @param instance * @param methodName * @param expectedException * @param args * @return * @throws T * @pre instance != null * @pre methodName != null * @pre expectedException != null */ public static <T extends Throwable> Object invoke(final Object instance, final String methodName, final Class<T> expectedException, final Object... args) throws T { Assert.isNotNull(expectedException, "expectedException should not be null!"); //$NON-NLS-1$ try { return invoke(instance, methodName, args); } catch (final InvocationTargetFailure e) { if (expectedException.isAssignableFrom(e.getCause().getClass())) { throw (T) e.getCause(); } throw e; } } /** * Invoke a �hidden� (private, protected, ...) method on a given object * allowing this method to throw the given type of exception.<br> * <b>This method should not be used for production code but only for unit * tests! </b> * * @param <T> * @param instance * @param methodName * @param expectedException * @param args * @return * @throws T * @pre instance != null * @pre methodName != null * @pre expectedException != null */ public static <T extends Throwable> Object invokeHidden(final Object instance, final String methodName, final Class<T> expectedException, final Object... args) throws T { Assert.isNotNull(expectedException, "expectedException should not be null!"); //$NON-NLS-1$ try { return invokeHidden(instance, methodName, args); } catch (final InvocationTargetFailure e) { if (expectedException.isAssignableFrom(e.getCause().getClass())) { throw (T) e.getCause(); } throw e; } } /** * Invoke a (private, protected, ...) method on a given object. If specified * try to make the method accessible. * * @param open * if true it is tried to make it accessible. * @param instance * the instance to invoke the method on, or the class for static * methods. * @param methodName * the name of the method to invoke. * @param args * the actual arguments of the method to invoke. * @return the result of the invocation. * @pre instance != null * @pre methodName != null */ private static <T> T invoke(final boolean open, final Object instance, final String methodName, final Object... args) { Assert.isNotNull(instance, "instance must be given!"); //$NON-NLS-1$ Assert.isNotNull(methodName, "methodName must be given!"); //$NON-NLS-1$ Class<?> clazz = getClass(instance); while (clazz != null) { Class<?>[] clazzes = classesPrimitiveFromObjects(args); Method method = findMatchingMethodCached(open, clazz, methodName, clazzes); if (method == null) { clazzes = classesFromObjects(args); method = findMatchingMethodCached(open, clazz, methodName, clazzes); } if (method != null) { if (open) { method.setAccessible(true); } try { return (T) method.invoke(instance, args); } catch (final InvocationTargetException ite) { throw new InvocationTargetFailure("Calling #" + methodName + " on " + instance + " failed.", ite //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ .getTargetException()); } catch (final IllegalArgumentException e) { throw new ReflectionFailure("Calling #" + methodName + " on " + instance + " failed.", e); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } catch (final IllegalAccessException e) { throw new ReflectionFailure("Calling #" + methodName + " on " + instance + " failed.", e); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } clazz = clazz.getSuperclass(); } throw new ReflectionFailure("Could not invoke hidden method " + methodName + " on " //$NON-NLS-1$ //$NON-NLS-2$ + instance.getClass().getName() + "!"); //$NON-NLS-1$ } /** * Set a �hidden� (private, protected, ...) field on a given object. <br> * <b>This field should not be used for production code but only for unit * test. </b> * * @param instance * the instance to set the field, or the class for static fields. * @param fieldName * the name of the field to set. * @param value * the new value for the field (use wrappers for primitive * types). * @pre instance != null * @pre fieldName != null * @pre value != null */ @IgnoreFindBugs(value = "DP_DO_INSIDE_DO_PRIVILEGED", justification = "only intended for unit tests") public static void setHidden(final Object instance, final String fieldName, final Object value) { Assert.isNotNull(instance, "instance must be given!"); //$NON-NLS-1$ Assert.isNotNull(fieldName, "fieldName must be given!"); //$NON-NLS-1$ final Class<?> clazz = getClass(instance); try { final Field field = getDeepField(clazz, fieldName); field.setAccessible(true); field.set(instance, value); } catch (final Exception e) { throw new ReflectionFailure("Could not set hidden field " + fieldName + " on " + clazz.getName() + "!", e); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } /** * Get a �hidden� (private, protected, ...) field from a given object. <br> * <b>This method should not be used for production code but only for unit * test. </b> * * @param instance * the instance to get the field, or the class for static fields * @param fieldName * the name of the field to set. * @return the value of the field (returns wrappers for primitive types). * @pre instance != null * @pre fieldName != null */ @IgnoreFindBugs(value = "DP_DO_INSIDE_DO_PRIVILEGED", justification = "only intended for unit tests") public static <T> T getHidden(final Object instance, final String fieldName) { Assert.isNotNull(instance, "instance must be given!"); //$NON-NLS-1$ Assert.isNotNull(fieldName, "fieldName must be given!"); //$NON-NLS-1$ final Class<?> clazz = getClass(instance); try { final Field field = getDeepField(clazz, fieldName); field.setAccessible(true); return (T) field.get(instance); } catch (final Exception e) { throw new ReflectionFailure("Could not get hidden field " + fieldName + " on " + clazz.getName() + "!", e); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } } /** * @param <T> * @param interfaze * @param clazz * @param args * @return */ public static <T> T newLazyInstance(final Class<T> interfaze, final Class<? extends T> clazz, final Object... args) { return newInstance(interfaze, new LazyInstantiationHandler(clazz, args)); } /** * @param <T> * @param interfaze * @param clazz * @param args * @return */ public static <T> T newLazyInstance(final Class<T> interfaze, final String clazz, final Object... args) { return newInstance(interfaze, new LazyInstantiationHandler(clazz, args)); } private static class LazyInstantiationHandler implements InvocationHandler { private Object instance; private Class<?> clazz; private String clazzName; private final Object[] params; /** * @param clazz * @param args */ public LazyInstantiationHandler(final Class<?> clazz, final Object[] args) { this.clazz = clazz; this.params = args; } /** * @param clazz * @param args */ public LazyInstantiationHandler(final String clazz, final Object[] args) { this.clazzName = clazz; this.params = args; } public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { if (instance == null) { instance = clazz != null ? newInstance(clazz, params) : newInstance(clazzName, params); } // try { return ReflectionUtils.invoke(instance, method.getName(), args); // } catch (Throwable e) { // if ( isDeclaredException( method, e )) { // throw e; // } // throw new ReflectionFailure("", e); // } } } @SuppressWarnings("unchecked") private static <T> Constructor<T> findMatchingConstructor(final boolean open, final Class<T> clazz, final Class<?>[] clazzes) { Assert.isNotNull(clazz); try { if (clazzes == null) { return open ? clazz.getDeclaredConstructor() : clazz.getConstructor(); } final Constructor<?>[] constructors = open ? clazz.getDeclaredConstructors() : clazz.getConstructors(); for (final Constructor<?> constructor : constructors) { final Class<?>[] expectedParameterTypes = constructor.getParameterTypes(); if (expectedParameterTypes.length == clazzes.length) { boolean stop = false; for (int j = 0; j < expectedParameterTypes.length && !stop; j++) { if (!expectedParameterTypes[j].isAssignableFrom(clazzes[j]) && clazzes[j] != NULL_PARAMETER_TYPE) { stop = true; } } if (!stop) { return (Constructor<T>) constructor; } } } throw new ReflectionFailure("Could not find a matching constructor for " + clazz.getName()); //$NON-NLS-1$ } catch (final NoSuchMethodException nsme) { throw new ReflectionFailure("Could not find a matching constructor for " + clazz.getName(), nsme); //$NON-NLS-1$ } } private static Method findMatchingMethodCached(final boolean open, final Class<?> clazz, final String name, final Class<?>[] clazzes) { // Reflective method lookup is expensive, therefore we cache the 25 most // frequent look-ups using a hashcode of (clazz x name x clazzes) final Integer key = computeKey(clazz, name, clazzes); final Object cachedMethod = METHOD_CACHE.get(key); if (cachedMethod != null) { // cache hit; return return cachedMethod == NO_SUCH_METHOD ? null : (Method) cachedMethod; } final Method result = findMatchingMethod(open, clazz, name, clazzes); // for a method miss add NO_SUCH_METHOD METHOD_CACHE.put(key, result == null ? NO_SUCH_METHOD : result); return result; } private static Method findMatchingMethod(final boolean open, final Class<?> clazz, final String name, final Class<?>[] clazzes) { Assert.isNotNull(clazz); Assert.isNotNull(name); try { if (clazzes == null) { return clazz.getDeclaredMethod(name); } final Method[] methods = open ? clazz.getDeclaredMethods() : clazz.getMethods(); for (final Method method : methods) { if (method.getName().equals(name)) { final Class<?>[] expectedParameterTypes = method.getParameterTypes(); if (expectedParameterTypes.length == clazzes.length) { boolean stop = false; for (int j = 0; j < expectedParameterTypes.length && !stop; j++) { if (!expectedParameterTypes[j].isAssignableFrom(clazzes[j]) && clazzes[j] != NULL_PARAMETER_TYPE) { stop = true; } } if (!stop) { return method; } } } } return null; } catch (final NoSuchMethodException nsme) { return null; } } private static Class<? extends Object>[] classesPrimitiveFromObjects(final Object[] objects) { if (objects == null) { return null; } final Class<?>[] clazzes = new Class<?>[objects.length]; for (int i = 0; i < objects.length; i++) { Class<?> argClass = Object.class; if (objects[i] != null) { argClass = objects[i].getClass(); if (argClass == Integer.class) { argClass = int.class; } else if (argClass == Long.class) { argClass = long.class; } else if (argClass == Short.class) { argClass = short.class; } else if (argClass == Boolean.class) { argClass = boolean.class; } else if (argClass == Byte.class) { argClass = byte.class; } else if (argClass == Float.class) { argClass = float.class; } else if (argClass == Double.class) { argClass = double.class; } else if (argClass == Character.class) { argClass = char.class; } } else { argClass = NULL_PARAMETER_TYPE; } clazzes[i] = argClass; } return clazzes; } private static Class<? extends Object>[] classesFromObjects(final Object[] objects) { if (objects == null) { return null; } final Class<?>[] clazzes = new Class<?>[objects.length]; for (int i = 0; i < objects.length; i++) { clazzes[i] = objects[i] == null ? Object.class : objects[i].getClass(); } return clazzes; } private static Integer computeKey(final Class<?> clazz, final String name, final Class<?>[] args) { int result = clazz.hashCode(); result = (37 * result) + name.hashCode(); if (args != null) { for (final Class<?> arg : args) { result = (37 * result) + arg.hashCode(); } } return result; } private static Class<?> getClass(final Object instance) { Assert.isNotNull(instance); return (instance instanceof Class<?>) ? (Class<?>) instance : instance.getClass(); } private static Field getDeepField(final Class<?> clazz, final String fieldName) throws NoSuchFieldException { Assert.isNotNull(clazz); Assert.isNotNull(fieldName); Class<?> lookIn = clazz; while (lookIn != null) { try { return lookIn.getDeclaredField(fieldName); } catch (final NoSuchFieldException nsfe) { lookIn = lookIn.getSuperclass(); } } throw new NoSuchFieldException("Could not find field " + fieldName + " within class " + clazz + "."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ } private static <T> Class<T> loadClass(final String className) { ClassNotFoundException cnfe = null; try { return (Class<T>) Class.forName(className); } catch (final ClassNotFoundException e) { cnfe = e; } // ok, do it the hard way!! final Bundle[] bundles = Activator.getDefault().getContext().getBundles(); Class<T> foundClass = null; for (final Bundle bundle : bundles) { try { if (foundClass != null) { throw new ReflectionFailure( "Could not load class " + className + " because it exists in at least two bundles."); //$NON-NLS-1$ //$NON-NLS-2$ } cnfe = null; foundClass = (Class<T>) bundle.loadClass(className); return (Class<T>) bundle.loadClass(className); } catch (final ClassNotFoundException e) { cnfe = e; } } if (foundClass == null) { throw new ReflectionFailure("Could not load class " + className + ".", cnfe); //$NON-NLS-1$ //$NON-NLS-2$ } return foundClass; } /** * Create a new instance of type �clazz� by invoking the constructor with * the given list of arguments. * * @param open * if true it is tried to make the constructor accessible. * @param clazz * the type of new instance. * @param args * the arguments for the constructor. * @return the new instance. * @pre clazz != null */ private static <T> T newInstance(final boolean open, final Class<T> clazz, final Object... args) { Assert.isNotNull(clazz, "clazz must be given!"); //$NON-NLS-1$ try { Class<?>[] clazzes = classesPrimitiveFromObjects(args); Constructor<T> constructor = findMatchingConstructor(open, clazz, clazzes); if (constructor == null) { clazzes = classesFromObjects(args); constructor = findMatchingConstructor(open, clazz, clazzes); } if (open) { constructor.setAccessible(true); } return constructor.newInstance(args); } catch (final Throwable t) { throw new ReflectionFailure("Error creating instance for " + clazz.getName() + " with parameters " //$NON-NLS-1$ //$NON-NLS-2$ + Arrays.asList(args) + "!", t); //$NON-NLS-1$ } } }