Cutin/lib/src/main/java/net/tomatentum/cutin/util/ReflectionUtil.java

141 lines
4.9 KiB
Java

package net.tomatentum.cutin.util;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Stream;
public final class ReflectionUtil {
private ReflectionUtil() {}
private static final Map<Class<?>, Class<?>> PRIMITIVE_TO_BOXED = Map.ofEntries(
Map.entry(boolean.class, Boolean.class),
Map.entry(byte.class, Byte.class),
Map.entry(char.class, Character.class),
Map.entry(double.class, Double.class),
Map.entry(float.class, Float.class),
Map.entry(int.class, Integer.class),
Map.entry(long.class, Long.class),
Map.entry(short.class, Short.class),
Map.entry(void.class, Void.class)
);
public static Class<?> box(Class<?> type) {
return type.isPrimitive() ? PRIMITIVE_TO_BOXED.get(type) : type;
}
public static <T extends Annotation> boolean isAnnotationPresent(Method method, Class<T> annotationClass) {
return method.isAnnotationPresent(annotationClass) || method.getDeclaringClass().isAnnotationPresent(annotationClass);
}
public static <T extends Annotation> T getAnnotation(Method method, Class<T> annotationClass) {
return method.isAnnotationPresent(annotationClass) ?
method.getAnnotation(annotationClass) :
method.getDeclaringClass().getAnnotation(annotationClass);
}
public static Stream<Object> getReturnAsStream(Object ret) {
if (ret instanceof Object[] array)
return Stream.of(array);
if (ret instanceof Collection<?> coll)
return coll.stream().map(o -> (Object)o);
return Stream.of(ret);
}
public static int getCastDepth(Class<?> child, Class<?> parent) {
if (parent.equals(Object.class))
return Integer.MAX_VALUE;
if (!parent.isAssignableFrom(child)) {
throw new IllegalArgumentException("The specified class is not a child class of the specified parent.");
}
int depth = 0;
Class<?> curr = child;
List<Class<?>> parents = new ArrayList<>();
while (!curr.equals(parent)) {
depth++;
parents.add(curr.getSuperclass());
parents.addAll(Arrays.asList(curr.getInterfaces()));
for (Class<?> currParent : parents) {
if (currParent != null && parent.isAssignableFrom(currParent)) {
curr = currParent;
break;
}
}
parents.clear();
}
return depth;
}
public static Method getMostSpecificMethod(Method[] methods, Class<?>... parameters) {
List<Method> compatibleMethods = Arrays.stream(methods)
.filter(x -> isMethodCallable(x, parameters))
.toList();
if (compatibleMethods.isEmpty())
throw new IllegalArgumentException("There are no compatible Methods provided");
for (int i = 0; i < parameters.length; i++) {
final int currI = i;
Class<?>[] parameterTypes = compatibleMethods.stream()
.map(x -> getBoxedParameterTypes(x)[currI])
.toArray(x -> new Class[x]);
Class<?> mostSpecific = getMostSpecificClass(parameterTypes, parameters[i]);
compatibleMethods = compatibleMethods.stream()
.filter(x -> Objects.equals(getBoxedParameterTypes(x)[currI], mostSpecific))
.toList();
}
return compatibleMethods.getFirst();
}
public static Class<?> getMostSpecificClass(Class<?>[] classes, Class<?> base) {
int min = Integer.MAX_VALUE;
Class<?> currMostSpecific = null;
for (Class<?> currClass : classes) {
int currCastDepth = getCastDepth(base, currClass);
if (currCastDepth <= min) {
min = currCastDepth;
currMostSpecific = currClass;
}
}
return currMostSpecific;
}
public static boolean isMethodCallable(Method method, Class<?>... parameters) {
if (!Objects.equals(method.getParameterCount(), parameters.length))
return false;
Class<?>[] methodParams = getBoxedParameterTypes(method);
for (int i = 0; i < parameters.length; i++) {
if (!methodParams[i].isAssignableFrom(parameters[i]))
return false;
}
return true;
}
public static Class<?>[] getBoxedParameterTypes(Method method) {
return Arrays.stream(method.getParameterTypes())
.map(ReflectionUtil::box)
.toArray(Class<?>[]::new);
}
public static String getFullMethodName(Method method) {
return method.getDeclaringClass().getName() + "." + method.getName();
}
}