/******************************************************************************* * Copyright (c) 2011 Google, Inc. * 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: * Google, Inc. - initial API and implementation *******************************************************************************/ package org.eclipse.wb.internal.core.utils.reflect; import com.google.common.collect.Maps; import org.eclipse.wb.internal.core.utils.check.Assert; import java.lang.reflect.Field; import java.lang.reflect.GenericArrayType; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.lang.reflect.WildcardType; import java.util.Map; /** * Contains different Java reflection utilities. * * @author scheglov_ke * @coverage core.util */ public class ReflectionUtils { //////////////////////////////////////////////////////////////////////////// // // Constructor // //////////////////////////////////////////////////////////////////////////// private ReflectionUtils() { } //////////////////////////////////////////////////////////////////////////// // // Signature // //////////////////////////////////////////////////////////////////////////// /** * @param runtime * is true if we need name for class loading, false if we need * name for source generation. * * @return the fully qualified name of given {@link Type}. */ public static String getFullyQualifiedName(Type type, boolean runtime) { Assert.isNotNull(type); // Class if (type instanceof Class) { Class clazz = (Class) type; // array if (clazz.isArray()) { return getFullyQualifiedName(clazz.getComponentType(), runtime) + "[]"; } // object String name = clazz.getName(); if (!runtime) { name = name.replace('$', '.'); } return name; } // GenericArrayType if (type instanceof GenericArrayType) { GenericArrayType genericArrayType = (GenericArrayType) type; return getFullyQualifiedName(genericArrayType.getGenericComponentType(), runtime) + "[]"; } // ParameterizedType if (type instanceof ParameterizedType) { ParameterizedType parameterizedType = (ParameterizedType) type; Type rawType = parameterizedType.getRawType(); // raw type StringBuilder sb = new StringBuilder(); sb.append(getFullyQualifiedName(rawType, runtime)); // type arguments sb.append("<"); boolean firstTypeArgument = true; for (Type typeArgument : parameterizedType.getActualTypeArguments()) { if (!firstTypeArgument) { sb.append(","); } firstTypeArgument = false; sb.append(getFullyQualifiedName(typeArgument, runtime)); } sb.append(">"); // done return sb.toString(); } // WildcardType if (type instanceof WildcardType) { WildcardType wildcardType = (WildcardType) type; return "? extends " + getFullyQualifiedName(wildcardType.getUpperBounds()[0], runtime); } // TypeVariable TypeVariable typeVariable = (TypeVariable) type; return typeVariable.getName(); } /** * Appends fully qualified names of given parameter types (appends also "()"). */ private static void appendParameterTypes(StringBuilder buffer, Type[] parameterTypes) { buffer.append('('); boolean firstParameter = true; for (Type parameterType : parameterTypes) { if (firstParameter) { firstParameter = false; } else { buffer.append(','); } buffer.append(getFullyQualifiedName(parameterType, false)); } buffer.append(')'); } //////////////////////////////////////////////////////////////////////////// // // Method // //////////////////////////////////////////////////////////////////////////// /** * @return all declared {@link Method}'s, including protected and private. */ public static Map getMethods(Class clazz) { Map methods = Maps.newHashMap(); // process classes for (Class c = clazz; c != null; c = c.getSuperclass()) { for (Method method : c.getDeclaredMethods()) { String signature = getMethodSignature(method); if (!methods.containsKey(signature)) { method.setAccessible(true); methods.put(signature, method); } } } // process interfaces for (Class interfaceClass : clazz.getInterfaces()) { for (Method method : interfaceClass.getDeclaredMethods()) { String signature = getMethodSignature(method); if (!methods.containsKey(signature)) { method.setAccessible(true); methods.put(signature, method); } } } // done return methods; } /** * @return signature for given {@link Method}. This signature is not same signature as in JVM or * JDT, just some string that unique identifies method in its {@link Class}. */ public static String getMethodSignature(Method method) { Assert.isNotNull(method); return getMethodSignature(method.getName(), method.getParameterTypes()); } /** * Returns the signature of {@link Method} with given combination of name and parameter types. * This signature is not same signature as in JVM or JDT, just some string that unique identifies * method in its {@link Class}. * * @param name * the name of {@link Method}. * @param parameterTypes * the types of {@link Method} parameters. * * @return signature of {@link Method}. */ public static String getMethodSignature(String name, Type... parameterTypes) { Assert.isNotNull(name); Assert.isNotNull(parameterTypes); // StringBuilder buffer = new StringBuilder(); buffer.append(name); appendParameterTypes(buffer, parameterTypes); return buffer.toString(); } private static final ClassMap> m_getMethodBySignature = ClassMap.create(); /** * Returns the {@link Method} defined in {@link Class}. This method can have any visibility, i.e. * we can find even protected/private methods. Can return null if no method with * given signature found. * * @param clazz * the {@link Class} to get method from it, or its superclass. * @param signature * the signature of method in same format as {@link #getMethodSignature(Method)}. * * @return the {@link Method} for given signature, or null if no such method found. */ public static Method getMethodBySignature(Class clazz, String signature) { Assert.isNotNull(clazz); Assert.isNotNull(signature); // prepare cache Map cache = m_getMethodBySignature.get(clazz); if (cache == null) { cache = getMethods(clazz); m_getMethodBySignature.put(clazz, cache); } // use cache return cache.get(signature); } /** * @return the {@link Object} result of invoking method with given signature. */ public static Object invokeMethod(Object object, String signature, Object... arguments) throws Exception { Assert.isNotNull(object); Assert.isNotNull(arguments); // prepare class/object Class refClass = getRefClass(object); Object refObject = getRefObject(object); // prepare method Method method = getMethodBySignature(refClass, signature); Assert.isNotNull(method, "Can not find method " + signature + " in " + refClass); // do invoke try { return method.invoke(refObject, arguments); } catch (InvocationTargetException e) { throw propagate(e.getCause()); } } /** * Invokes method by name and parameter types. * * @param object * the object to call, may be {@link Class} for invoking static method. * @param name * the name of method. * @param parameterTypes * the types of parameters. * @param arguments * the values of argument for invocation. * * @return the {@link Object} result of invoking method. */ public static Object invokeMethod2(Object object, String name, Class[] parameterTypes, Object[] arguments) throws Exception { Assert.equals(parameterTypes.length, arguments.length); String signature = getMethodSignature(name, parameterTypes); return invokeMethod(object, signature, arguments); } //////////////////////////////////////////////////////////////////////////// // // Utils // //////////////////////////////////////////////////////////////////////////// /** * @return the {@link Class} of given {@link Object} or casted object, if it is {@link Class} * itself. */ private static Class getRefClass(Object object) { return object instanceof Class ? (Class) object : object.getClass(); } /** * @return the {@link Object} that should be used as argument for {@link Field#get(Object)} and * {@link Method#invoke(Object, Object[])}. */ private static Object getRefObject(Object object) { return object instanceof Class ? null : object; } //////////////////////////////////////////////////////////////////////////// // // Throwable propagation // //////////////////////////////////////////////////////////////////////////// /** * Helper class used in {@link #propagate(Throwable)}. */ private static class ExceptionThrower { private static Throwable throwable; private ExceptionThrower() throws Throwable { if (System.getProperty("wbp.ReflectionUtils.propagate().InstantiationException") != null) { throw new InstantiationException(); } if (System.getProperty("wbp.ReflectionUtils.propagate().IllegalAccessException") != null) { throw new IllegalAccessException(); } throw throwable; } public static synchronized void spit(Throwable t) { if (System.getProperty("wbp.ReflectionUtils.propagate().dontThrow") == null) { ExceptionThrower.throwable = t; try { ExceptionThrower.class.newInstance(); } catch (InstantiationException e) { } catch (IllegalAccessException e) { } finally { ExceptionThrower.throwable = null; } } } } /** * Propagates {@code throwable} as-is without any wrapping. This is trick. * * @return nothing will ever be returned; this return type is only for your convenience, to use * this method in "throw" statement. */ public static RuntimeException propagate(Throwable throwable) { if (System.getProperty("wbp.ReflectionUtils.propagate().forceReturn") == null) { ExceptionThrower.spit(throwable); } return null; } }