Finding most specific overloaded method using MethodHandle

前端 未结 5 425
旧时难觅i
旧时难觅i 2020-12-14 01:50

Suppose I have three methods inside a given type (class/interface):

public void foo(Integer integer);
public void foo(Number number);
public void foo(Object          


        
5条回答
  •  不思量自难忘°
    2020-12-14 02:15

    Basically I searched all methods that can be executed with a set of parameters. So, I sorted them by the distance between the parameterType to the methodParameterType. Doing this, I could get the most specific overloaded method.

    To test:

    @Test
    public void test() throws Throwable {
        Object object = 1;
    
        Foo foo = new Foo();
    
        MethodExecutor.execute(foo, "foo", Void.class, object);
    }
    

    The Foo:

    class Foo {
        public void foo(Integer integer) {
            System.out.println("integer");
        }
    
        public void foo(Number number) {
            System.out.println("number");
        }
    
        public void foo(Object object) {
            System.out.println("object");
        }
    }
    

    The MethodExecutor:

    public class MethodExecutor{
        private static final Map, Class> equivalentTypeMap = new HashMap<>(18);
        static{
            equivalentTypeMap.put(boolean.class, Boolean.class);
            equivalentTypeMap.put(byte.class, Byte.class);
            equivalentTypeMap.put(char.class, Character.class);
            equivalentTypeMap.put(float.class, Float.class);
            equivalentTypeMap.put(int.class, Integer.class);
            equivalentTypeMap.put(long.class, Long.class);
            equivalentTypeMap.put(short.class, Short.class);
            equivalentTypeMap.put(double.class, Double.class);
            equivalentTypeMap.put(void.class, Void.class);
            equivalentTypeMap.put(Boolean.class, boolean.class);
            equivalentTypeMap.put(Byte.class, byte.class);
            equivalentTypeMap.put(Character.class, char.class);
            equivalentTypeMap.put(Float.class, float.class);
            equivalentTypeMap.put(Integer.class, int.class);
            equivalentTypeMap.put(Long.class, long.class);
            equivalentTypeMap.put(Short.class, short.class);
            equivalentTypeMap.put(Double.class, double.class);
            equivalentTypeMap.put(Void.class, void.class);
        }
    
        public static  T execute(Object instance, String methodName, Class returnType, Object ...parameters) throws InvocationTargetException, IllegalAccessException {
            List compatiblesMethods = getCompatiblesMethods(instance, methodName, returnType, parameters);
            Method mostSpecificOverloaded = getMostSpecificOverLoaded(compatiblesMethods, parameters);
            //noinspection unchecked
            return (T) mostSpecificOverloaded.invoke(instance, parameters);
        }
    
        private static List getCompatiblesMethods(Object instance, String methodName, Class returnType, Object[] parameters) {
            Class clazz = instance.getClass();
            Method[] methods = clazz.getMethods();
    
            List compatiblesMethods = new ArrayList<>();
    
            outerFor:
            for(Method method : methods){
                if(!method.getName().equals(methodName)){
                    continue;
                }
    
                Class methodReturnType = method.getReturnType();
                if(!canBeCast(returnType, methodReturnType)){
                    continue;
                }
    
                Class[] methodParametersType = method.getParameterTypes();
                if(methodParametersType.length != parameters.length){
                    continue;
                }
    
                for(int i = 0; i < methodParametersType.length; i++){
                    if(!canBeCast(parameters[i].getClass(), methodParametersType[i])){
                        continue outerFor;
                    }
                }
    
                compatiblesMethods.add(method);
            }
    
            if(compatiblesMethods.size() == 0){
                throw new IllegalArgumentException("Cannot find method.");
            }
    
            return compatiblesMethods;
        }
    
        private static Method getMostSpecificOverLoaded(List compatiblesMethods, Object[] parameters) {
            Method mostSpecificOverloaded = compatiblesMethods.get(0);
            int lastMethodScore = calculateMethodScore(mostSpecificOverloaded, parameters);
    
            for(int i = 1; i < compatiblesMethods.size(); i++){
                Method method = compatiblesMethods.get(i);
                int currentMethodScore = calculateMethodScore(method, parameters);
                if(lastMethodScore > currentMethodScore){
                    mostSpecificOverloaded = method;
                    lastMethodScore = currentMethodScore;
                }
            }
    
            return mostSpecificOverloaded;
        }
    
        private static int calculateMethodScore(Method method, Object... parameters){
            int score = 0;
    
            Class[] methodParametersType = method.getParameterTypes();
            for(int i = 0; i < parameters.length; i++){
                Class methodParameterType = methodParametersType[i];
                if(methodParameterType.isPrimitive()){
                    methodParameterType = getEquivalentType(methodParameterType);
                }
                Class parameterType = parameters[i].getClass();
    
                score += distanceBetweenClasses(parameterType, methodParameterType);
            }
    
            return score;
        }
    
        private static int distanceBetweenClasses(Class clazz, Class superClazz){
            return distanceFromObjectClass(clazz) - distanceFromObjectClass(superClazz);
        }
    
        private static int distanceFromObjectClass(Class clazz){
            int distance = 0;
            while(!clazz.equals(Object.class)){
                distance++;
                clazz = clazz.getSuperclass();
            }
    
            return distance;
        }
    
        private static boolean canBeCast(Class fromClass, Class toClass) {
            if (canBeRawCast(fromClass, toClass)) {
                return true;
            }
    
            Class equivalentFromClass = getEquivalentType(fromClass);
            return equivalentFromClass != null && canBeRawCast(equivalentFromClass, toClass);
        }
    
        private static boolean canBeRawCast(Class fromClass, Class toClass) {
            return fromClass.equals(toClass) || !toClass.isPrimitive() && toClass.isAssignableFrom(fromClass);
        }
    
        private static Class getEquivalentType(Class type){
            return equivalentTypeMap.get(type);
        }
    }
    

    Ofcourse it can be improved with some refactoring and comments.

提交回复
热议问题