blob: b3622116bdab5e7cae8d616a3f48905a9f3e45c9 [file] [log] [blame]
/*******************************************************************************
* 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 <code>true</code> if we need name for class loading, <code>false</code> 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 <code>"()"</code>).
*/
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<String, Method> getMethods(Class<?> clazz) {
Map<String, Method> 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<Map<String, Method>> 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 <code>null</code> 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 <code>null</code> if no such method found.
*/
public static Method getMethodBySignature(Class<?> clazz, String signature) {
Assert.isNotNull(clazz);
Assert.isNotNull(signature);
// prepare cache
Map<String, Method> 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;
}
}