package droidkit.util;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* @author Daniel Serdyukov
*/
public final class DynamicMethod {
private DynamicMethod() {
}
@Nullable
public static <T> T invoke(@NonNull Object target, @NonNull String method, Object... args)
throws DynamicException {
return invoke(target, find(target.getClass(), method, types(args)), args);
}
@Nullable
@SuppressWarnings("unchecked")
public static <T> T invoke(@Nullable Object target, @NonNull Method method, Object... args)
throws DynamicException {
final boolean isAccessible = method.isAccessible();
try {
method.setAccessible(true);
return (T) method.invoke(target, args);
} catch (InvocationTargetException | IllegalAccessException e) {
throw new DynamicException(e);
} finally {
method.setAccessible(isAccessible);
}
}
@Nullable
public static <T> T invokeStatic(@NonNull String className, @NonNull String method, Object... args)
throws DynamicException {
return invokeStatic(Dynamic.forName(className), method, args);
}
@Nullable
public static <T> T invokeStatic(@NonNull Class<?> target, @NonNull String method, Object... args)
throws DynamicException {
return invokeStatic(find(target, method, types(args)), args);
}
@Nullable
public static <T> T invokeStatic(@NonNull Method method, Object... args)
throws DynamicException {
return invoke(null, method, args);
}
@NonNull
public static Method find(@NonNull String className, @NonNull String name, Class<?>... argTypes)
throws DynamicException {
return find(Dynamic.forName(className), name, argTypes);
}
@NonNull
public static Method find(@NonNull Class<?> type, @NonNull String name, Class<?>... argTypes)
throws DynamicException {
do {
final Method[] methods = type.getDeclaredMethods();
for (final Method method : methods) {
if (TextUtils.equals(name, method.getName())
&& hasValidSignature(method.getParameterTypes(), argTypes)) {
return method;
}
}
} while ((type = type.getSuperclass()) != null);
throw new DynamicException("No such method " + name);
}
@NonNull
public static List<Method> annotatedWith(@NonNull Class<?> type, @NonNull Class<? extends Annotation> annotation) {
final List<Method> annotatedMethods = new ArrayList<>();
do {
final Method[] methods = type.getDeclaredMethods();
for (final Method method : methods) {
if (method.isAnnotationPresent(annotation)) {
annotatedMethods.add(method);
}
}
} while ((type = type.getSuperclass()) != null);
return annotatedMethods;
}
@NonNull
static Class<?>[] types(Object... args) {
final Class<?>[] argumentTypes = new Class<?>[args.length];
for (int index = 0; index < args.length; ++index) {
argumentTypes[index] = args[index].getClass();
}
return argumentTypes;
}
static boolean hasValidSignature(@NonNull Class<?>[] expected, @NonNull Class<?>[] actual) {
if (expected.length != actual.length) {
return false;
}
for (int index = 0; index < expected.length; ++index) {
if (!isAssignable(expected[index], actual[index])) {
return false;
}
}
return true;
}
static boolean isAssignable(@NonNull Class<?> expected, @NonNull Class<?> actual) {
if (expected.isAssignableFrom(actual)) {
return true;
}
if (expected.isPrimitive()) {
return Dynamic.unbox(expected).isAssignableFrom(actual);
}
return actual.isPrimitive() && expected.isAssignableFrom(Dynamic.unbox(actual));
}
}