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> 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 boolean isAnnotationPresent(Method method, Class annotationClass) { return method.isAnnotationPresent(annotationClass) || method.getDeclaringClass().isAnnotationPresent(annotationClass); } public static T getAnnotation(Method method, Class annotationClass) { return method.isAnnotationPresent(annotationClass) ? method.getAnnotation(annotationClass) : method.getDeclaringClass().getAnnotation(annotationClass); } public static Stream 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> 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 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(); } }