/*
* Copyright (c) 2006 Rogério Liesenfeld
* This file is subject to the terms of the MIT license (see LICENSE.txt).
*/
package mockit.internal.reflection;
import java.lang.reflect.*;
import javax.annotation.*;
import static java.lang.reflect.Modifier.*;
import mockit.*;
import mockit.internal.util.*;
import static mockit.internal.reflection.ParameterReflection.*;
public final class MethodReflection
{
private MethodReflection() {}
@Nullable
public static <T> T invoke(
@Nonnull Class<?> theClass, @Nullable Object targetInstance, @Nonnull String methodName,
@Nonnull Class<?>[] paramTypes, @Nullable Object... methodArgs)
{
if (methodArgs == null) {
throw invalidArguments();
}
Method method = findSpecifiedMethod(theClass, methodName, paramTypes);
T result = invoke(targetInstance, method, methodArgs);
return result;
}
@Nonnull
private static Method findSpecifiedMethod(
@Nonnull Class<?> theClass, @Nonnull String methodName, @Nonnull Class<?>[] paramTypes)
{
while (true) {
Method declaredMethod = findSpecifiedMethodInGivenClass(theClass, methodName, paramTypes);
if (declaredMethod != null) {
return declaredMethod;
}
Class<?> superClass = theClass.getSuperclass();
if (superClass == null || superClass == Object.class) {
String paramTypesDesc = getParameterTypesDescription(paramTypes);
throw new IllegalArgumentException("Specified method not found: " + methodName + paramTypesDesc);
}
//noinspection AssignmentToMethodParameter
theClass = superClass;
}
}
@Nullable
private static Method findSpecifiedMethodInGivenClass(
@Nonnull Class<?> theClass, @Nonnull String methodName, @Nonnull Class<?>[] paramTypes)
{
for (Method declaredMethod : theClass.getDeclaredMethods()) {
if (declaredMethod.getName().equals(methodName)) {
Class<?>[] declaredParameterTypes = declaredMethod.getParameterTypes();
int firstRealParameter = indexOfFirstRealParameter(declaredParameterTypes, paramTypes);
if (
firstRealParameter >= 0 &&
matchesParameterTypes(declaredMethod.getParameterTypes(), paramTypes, firstRealParameter)
) {
return declaredMethod;
}
}
}
return null;
}
@Nullable
public static <T> T invokePublicIfAvailable(
@Nonnull Class<?> aClass, @Nullable Object targetInstance, @Nonnull String methodName,
@Nonnull Class<?>[] parameterTypes, @Nonnull Object... methodArgs)
{
Method publicMethod;
try { publicMethod = aClass.getMethod(methodName, parameterTypes); }
catch (NoSuchMethodException ignore) { return null; }
T result = invoke(targetInstance, publicMethod, methodArgs);
return result;
}
@Nonnull
public static String readAnnotationAttribute(@Nonnull Object annotationInstance, @Nonnull String attributeName)
{
try {
Method publicMethod = annotationInstance.getClass().getMethod(attributeName, NO_PARAMETERS);
String result = (String) publicMethod.invoke(annotationInstance);
return result;
}
catch (NoSuchMethodException e) { throw new RuntimeException(e); }
catch (IllegalAccessException e) { throw new RuntimeException(e); }
catch (InvocationTargetException e) { throw new RuntimeException(e.getCause()); }
}
@Nullable
public static <T> T invokeWithCheckedThrows(
@Nonnull Class<?> theClass, @Nullable Object targetInstance, @Nonnull String methodName,
@Nonnull Class<?>[] paramTypes, @Nonnull Object... methodArgs)
throws Throwable
{
Method method = findSpecifiedMethod(theClass, methodName, paramTypes);
T result = invokeWithCheckedThrows(targetInstance, method, methodArgs);
return result;
}
@Nullable
public static <T> T invoke(@Nullable Object targetInstance, @Nonnull Method method, @Nonnull Object... methodArgs)
{
Utilities.ensureThatMemberIsAccessible(method);
try {
//noinspection unchecked
return (T) method.invoke(targetInstance, methodArgs);
}
catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
catch (IllegalArgumentException e) {
StackTrace.filterStackTrace(e);
throw new IllegalArgumentException("Failure to invoke method: " + method, e);
}
catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof Error) {
throw (Error) cause;
}
else if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
}
else {
ThrowOfCheckedException.doThrow((Exception) cause);
return null;
}
}
}
@Nullable
public static <T> T invokeWithCheckedThrows(
@Nullable Object targetInstance, @Nonnull Method method, @Nonnull Object... methodArgs)
throws Throwable
{
Utilities.ensureThatMemberIsAccessible(method);
try {
//noinspection unchecked
return (T) method.invoke(targetInstance, methodArgs);
}
catch (IllegalArgumentException e) {
StackTrace.filterStackTrace(e);
throw new IllegalArgumentException("Failure to invoke method: " + method, e);
}
catch (InvocationTargetException e) {
throw e.getCause();
}
}
@Nullable
public static <T> T invoke(
@Nonnull Class<?> theClass, @Nullable Object targetInstance, @Nonnull String methodName,
@Nullable Object... methodArgs)
{
if (methodArgs == null) {
throw invalidArguments();
}
boolean staticMethod = targetInstance == null;
Class<?>[] argTypes = getArgumentTypesFromArgumentValues(methodArgs);
Method method = staticMethod ?
findCompatibleStaticMethod(theClass, methodName, argTypes) :
findCompatibleMethod(theClass, methodName, argTypes);
if (staticMethod && !isStatic(method.getModifiers())) {
throw new IllegalArgumentException(
"Attempted to invoke non-static method without an instance to invoke it on");
}
T result = invoke(targetInstance, method, methodArgs);
return result;
}
@Nonnull
private static Method findCompatibleStaticMethod(
@Nonnull Class<?> theClass, @Nonnull String methodName, @Nonnull Class<?>[] argTypes)
{
Method methodFound = findCompatibleMethodInClass(theClass, methodName, argTypes);
if (methodFound != null) {
return methodFound;
}
String argTypesDesc = getParameterTypesDescription(argTypes);
throw new IllegalArgumentException("No compatible static method found: " + methodName + argTypesDesc);
}
@Nonnull
public static Method findCompatibleMethod(
@Nonnull Class<?> theClass, @Nonnull String methodName, @Nonnull Class<?>[] argTypes)
{
Method methodFound = findCompatibleMethodIfAvailable(theClass, methodName, argTypes);
if (methodFound != null) {
return methodFound;
}
String argTypesDesc = getParameterTypesDescription(argTypes);
throw new IllegalArgumentException("No compatible method found: " + methodName + argTypesDesc);
}
@Nullable
private static Method findCompatibleMethodIfAvailable(
@Nonnull Class<?> theClass, @Nonnull String methodName, @Nonnull Class<?>[] argTypes)
{
Method methodFound = null;
while (true) {
Method compatibleMethod = findCompatibleMethodInClass(theClass, methodName, argTypes);
if (
compatibleMethod != null &&
(methodFound == null ||
hasMoreSpecificTypes(compatibleMethod.getParameterTypes(), methodFound.getParameterTypes()))
) {
methodFound = compatibleMethod;
}
Class<?> superClass = theClass.getSuperclass();
if (superClass == null || superClass == Object.class) {
break;
}
//noinspection AssignmentToMethodParameter
theClass = superClass;
}
return methodFound;
}
@Nullable
private static Method findCompatibleMethodInClass(
@Nonnull Class<?> theClass, @Nonnull String methodName, @Nonnull Class<?>[] argTypes)
{
Method found = null;
Class<?>[] foundParamTypes = null;
for (Method declaredMethod : theClass.getDeclaredMethods()) {
if (declaredMethod.getName().equals(methodName)) {
Class<?>[] declaredParamTypes = declaredMethod.getParameterTypes();
int firstRealParameter = indexOfFirstRealParameter(declaredParamTypes, argTypes);
if (
firstRealParameter >= 0 &&
(matchesParameterTypes(declaredParamTypes, argTypes, firstRealParameter) ||
acceptsArgumentTypes(declaredParamTypes, argTypes, firstRealParameter)) &&
(foundParamTypes == null || hasMoreSpecificTypes(declaredParamTypes, foundParamTypes))
) {
found = declaredMethod;
foundParamTypes = declaredParamTypes;
}
}
}
return found;
}
@Nonnull
public static Method findNonPrivateHandlerMethod(@Nonnull Object handler)
{
Class<?> handlerClass = handler.getClass();
Method nonPrivateMethod;
do {
nonPrivateMethod = findNonPrivateHandlerMethod(handlerClass);
if (nonPrivateMethod != null) {
break;
}
handlerClass = handlerClass.getSuperclass();
}
while (handlerClass != null && handlerClass != Object.class);
if (nonPrivateMethod == null) {
throw new IllegalArgumentException("No non-private instance method found");
}
return nonPrivateMethod;
}
@Nullable
private static Method findNonPrivateHandlerMethod(@Nonnull Class<?> handlerClass)
{
Method[] declaredMethods = handlerClass.getDeclaredMethods();
Method found = null;
for (Method declaredMethod : declaredMethods) {
int methodModifiers = declaredMethod.getModifiers();
if (!isPrivate(methodModifiers) && !isStatic(methodModifiers)) {
if (found != null) {
String methodType = Delegate.class.isAssignableFrom(handlerClass) ? "delegate" : "invocation handler";
throw new IllegalArgumentException(
"More than one candidate " + methodType + " method found: " +
methodSignature(found) + ", " + methodSignature(declaredMethod));
}
found = declaredMethod;
}
}
return found;
}
@Nonnull
private static String methodSignature(@Nonnull Method method)
{
String signature = method.toGenericString().replace("java.lang.", "");
int p = signature.lastIndexOf('(');
int q = signature.lastIndexOf('.', p);
return signature.substring(q + 1);
}
}