org.mbte.groovypp.compiler.MethodSelection.java Source code

Java tutorial

Introduction

Here is the source code for org.mbte.groovypp.compiler.MethodSelection.java

Source

/*
 * Copyright 2009-2011 MBTE Sweden AB.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.mbte.groovypp.compiler;

import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.util.FastArray;
import org.mbte.groovypp.compiler.AccessibilityCheck;
import org.mbte.groovypp.compiler.ClassNodeCache;
import org.mbte.groovypp.compiler.ClosureUtil;
import org.mbte.groovypp.compiler.TypeUtil;
import org.objectweb.asm.Opcodes;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import static org.codehaus.groovy.ast.ClassHelper.*;

public class MethodSelection {

    private static final int OBJECT_SHIFT = 23, INTERFACE_SHIFT = 0, PRIMITIVE_SHIFT = 21, VARGS_SHIFT = 44;

    private static final ClassNode[] PRIMITIVES = { byte_TYPE, Byte_TYPE, short_TYPE, Short_TYPE, int_TYPE,
            Integer_TYPE, long_TYPE, Long_TYPE, BigInteger_TYPE, float_TYPE, Float_TYPE, double_TYPE, Double_TYPE,
            BigDecimal_TYPE, make(Number.class), OBJECT_TYPE };

    private static final int[][] PRIMITIVE_DISTANCE_TABLE = {
            //              byte    Byte    short   Short   int     Integer     long    Long    BigInteger  float   Float   double  Double  BigDecimal, Number, Object
            /* byte*/{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, },
            /*Byte*/{ 1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, },
            /*short*/{ 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, },
            /*Short*/{ 14, 15, 1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, },
            /*int*/{ 14, 15, 12, 13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, },
            /*Integer*/{ 14, 15, 12, 13, 1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, },
            /*long*/{ 14, 15, 12, 13, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, },
            /*Long*/{ 14, 15, 12, 13, 10, 11, 1, 0, 2, 3, 4, 5, 6, 7, 8, 9, },
            /*BigInteger*/{ 14, 15, 12, 13, 10, 11, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, },
            /*float*/{ 14, 15, 12, 13, 10, 11, 8, 9, 7, 0, 1, 2, 3, 4, 5, 6, },
            /*Float*/{ 14, 15, 12, 13, 10, 11, 8, 9, 7, 1, 0, 2, 3, 4, 5, 6, },
            /*double*/{ 14, 15, 12, 13, 10, 11, 8, 9, 7, 5, 6, 0, 1, 2, 3, 4, },
            /*Double*/{ 14, 15, 12, 13, 10, 11, 8, 9, 7, 5, 6, 1, 0, 2, 3, 4, },
            /*BigDecimal*/{ 14, 15, 12, 13, 10, 11, 8, 9, 7, 5, 6, 3, 4, 0, 1, 2, },
            /*Numer*/{ 14, 15, 12, 13, 10, 11, 8, 9, 7, 5, 6, 3, 4, 2, 0, 1, },
            /*Object*/{ 14, 15, 12, 13, 10, 11, 8, 9, 7, 5, 6, 3, 4, 2, 1, 0, }, };
    private static final ClassNode[] ARRAY_WITH_NULL = { null };

    private static int getPrimitiveIndex(ClassNode c) {
        for (byte i = 0; i < PRIMITIVES.length; i++) {
            if (PRIMITIVES[i].equals(c))
                return i;
        }
        return -1;
    }

    private static int getPrimitiveDistance(ClassNode from, ClassNode to) {
        // we know here that from!=to, so a distance of 0 is never valid
        // get primitive type indexes
        int fromIndex = getPrimitiveIndex(from);
        int toIndex = getPrimitiveIndex(to);
        if (from.isGenericsPlaceHolder() && !ClassHelper.isPrimitiveType(to))
            return 0;
        if (fromIndex == -1 || toIndex == -1)
            return -1;
        return PRIMITIVE_DISTANCE_TABLE[toIndex][fromIndex];
    }

    private static int getMaximumInterfaceDistance(ClassNode c, ClassNode interfaceClass) {
        // -1 means a mismatch
        if (c == null)
            return -1;
        // 0 means a direct match
        if (c.redirect() == interfaceClass)
            return 0;
        ClassNode[] interfaces = c.getInterfaces();
        int max = -1;
        for (ClassNode anInterface : interfaces) {
            int sub = getMaximumInterfaceDistance(anInterface, interfaceClass);
            // we need to keep the -1 to track the mismatch, a +1
            // by any means could let it look like a direct match
            // we want to add one, because there is an interface between
            // the interface we search for and the interface we are in.
            if (sub != -1)
                sub++;
            // we are interested in the longest path only
            max = Math.max(max, sub);
        }
        // we do not add one for super classes, only for interfaces
        int superClassMax = getMaximumInterfaceDistance(c.getSuperClass(), interfaceClass);
        return Math.max(max, superClassMax);
    }

    public static Object chooseMethod(String methodName, Object methodOrList, ClassNode type, ClassNode[] arguments,
            ClassNode contextClass) {
        if (methodOrList instanceof MethodNode) {
            final MethodNode mn = (MethodNode) methodOrList;
            if (isValidMethod(mn.getParameters(), arguments, type, mn.getDeclaringClass())) {
                return methodOrList;
            }
            return null;
        }

        if (methodOrList == null)
            return null;
        FastArray methods = ((FastArray) methodOrList).copy();
        int methodCount = methods.size();
        if (methodCount > 1 && contextClass != null) {
            for (int i = 0; i < methodCount; i++) {
                final MethodNode methodNode = (MethodNode) methods.get(i);
                if (methodNode == null || !AccessibilityCheck.isAccessible(methodNode.getModifiers(),
                        methodNode.getDeclaringClass(), contextClass, null)) {
                    methodCount--;
                    methods.remove(i--);
                }
            }
        }
        methodCount = methods.size();

        if (methodCount <= 0) {
            return null;
        } else if (methodCount == 1) {
            MethodNode method = (MethodNode) methods.get(0);
            if (isValidMethod(method.getParameters(), arguments, type, method.getDeclaringClass())) {
                return method;
            }
            return null;
        }
        Object answer;
        if (arguments == null || arguments.length == 0) {
            answer = chooseEmptyMethodParams(methods);
        } else if (arguments.length == 1 && arguments[0] == null) {
            answer = chooseMostGeneralMethodWith1NullParam(methods);
        } else {
            List<MethodNode> matchingMethods = new ArrayList<MethodNode>();

            final int len = methods.size;
            Object data[] = methods.getArray();
            boolean isValidExactFound = false;
            for (int i = 0; i != len; ++i) {
                MethodNode method = (MethodNode) data[i];

                if (isValidMethod(method.getParameters(), arguments, type, method.getDeclaringClass())) {
                    if (isValidExactMethod(arguments, method.getParameters(), type, method.getDeclaringClass())) {
                        if (!isValidExactFound)
                            matchingMethods.clear();
                        isValidExactFound = true;
                    } else {
                        if (isValidExactFound)
                            continue;
                    }
                    matchingMethods.add(method);
                }
            }

            if (matchingMethods.size() == 0) {
                return null;
            } else if (matchingMethods.size() == 1) {
                return matchingMethods.get(0);
            }
            return chooseMostSpecificParams(methodName, matchingMethods, arguments);
        }
        if (answer != null) {
            return answer;
        }
        //        throw new MethodSelectionException(methodName, methods, arguments);
        return null;
    }

    private static Object chooseMostSpecificParams(String name, List<MethodNode> matchingMethods,
            ClassNode[] arguments) {
        long matchesDistance = -1;
        int matchesIndirect = -1;
        List<MethodNode> matches = new ArrayList<MethodNode>();
        for (Iterator<MethodNode> iter = matchingMethods.iterator(); iter.hasNext();) {
            MethodNode method = iter.next();
            Parameter[] paramTypes = method.getParameters();
            long dist = calculateParameterDistance(arguments, paramTypes);
            int indirectCount = getIndirectlyAssignableParamsCount(paramTypes, arguments);
            if (matches.size() == 0) {
                matches.add(method);
                matchesDistance = dist;
                matchesIndirect = indirectCount;
            } else if (indirectCount < matchesIndirect
                    || (indirectCount == matchesIndirect && dist < matchesDistance)) {
                matchesDistance = dist;
                matchesIndirect = indirectCount;
                matches.clear();
                matches.add(method);
            } else if (indirectCount == matchesIndirect && dist == matchesDistance) {
                matches.add(method);
            }

        }
        if (matches.size() == 1) {
            return matches.get(0);
        }
        if (matches.size() == 0) {
            return null;
        }

        return null;
    }

    private static int getIndirectlyAssignableParamsCount(Parameter[] params, ClassNode[] args) {
        int res = 0;
        for (int i = 0; i < params.length - 1; i++) {
            if (!isAssignableDirectly(params[i].getType(), args[i]))
                res++;
        }
        final ClassNode lastParamType = params[params.length - 1].getType();
        if (params.length == args.length && isAssignableOrInference(lastParamType, args[params.length - 1])) {
            if (!isAssignableDirectly(lastParamType, args[params.length - 1])) {
                res++;
                if (lastParamType.isArray() && (args[params.length - 1].equals(TypeUtil.TMAP_NULL)
                        || args[params.length - 1].equals(TypeUtil.TLIST_NULL)))
                    res++;
            }
        } else if (args.length > params.length) {
            ClassNode last;
            if (lastParamType.isArray()) {
                last = lastParamType.getComponentType();
            } else {
                last = TypeUtil.getSubstitutedType(ClassHelper.LIST_TYPE.getGenericsTypes()[0].getType(),
                        ClassHelper.LIST_TYPE, lastParamType);
            }
            for (int i = params.length - 1; i < args.length; i++) {
                if (!isAssignableDirectly(last, args[i])) {
                    res++;
                    break;
                }
            }
        }
        return res;
    }

    private static boolean isAssignableDirectly(ClassNode param, ClassNode arg) {
        if (arg == null)
            return true;
        if (param == ClassHelper.boolean_TYPE) {
            return arg == ClassHelper.boolean_TYPE;
        }

        if (getPrimitiveIndex(param) >= 0 && getPrimitiveIndex(arg) >= 0)
            return true;

        // We cannot argue about closure assignability here.
        if (arg.implementsInterface(TypeUtil.TCLOSURE) || arg.redirect() == TypeUtil.TCLOSURE_NULL) {
            return true;
        }

        if (arg.implementsInterface(TypeUtil.TLIST))
            arg = TypeUtil.ARRAY_LIST_TYPE;
        if (arg.implementsInterface(TypeUtil.TMAP))
            arg = TypeUtil.LINKED_HASH_MAP_TYPE;

        return TypeUtil.isDirectlyAssignableFrom(TypeUtil.wrapSafely(param), TypeUtil.wrapSafely(arg));
    }

    private static long calculateParameterDistance(ClassNode argument, ClassNode parameter) {
        /**
         * note: when shifting with 32 bit, you should only shift on a long. If you do
         *       that with an int, then i==(i<<32), which means you loose the shift
         *       information
         */

        if (argument == null || parameter == null || parameter.equals(argument))
            return 0;

        if (argument.implementsInterface(TypeUtil.TLIST))
            argument = TypeUtil.ARRAY_LIST_TYPE;
        if (argument.implementsInterface(TypeUtil.TMAP))
            argument = TypeUtil.LINKED_HASH_MAP_TYPE;

        if (parameter.isInterface()) {
            return getMaximumInterfaceDistance(argument, parameter) << INTERFACE_SHIFT;
        }

        long objectDistance = 0;
        if (argument != null) {
            long pd = getPrimitiveDistance(parameter, argument);
            if (pd != -1)
                return pd << PRIMITIVE_SHIFT;

            // add one to dist to be sure interfaces are preferred
            objectDistance += PRIMITIVES.length + 1;
            ClassNode clazz = TypeUtil.wrapSafely(argument);
            while (clazz != null) {
                if (clazz.equals(parameter))
                    break;
                if (clazz.equals(GSTRING_TYPE) && parameter.equals(STRING_TYPE)) {
                    objectDistance += 2;
                    break;
                }
                clazz = clazz.getSuperClass();
                objectDistance += 3;
            }
        } else {
            // choose the distance to Object if a parameter is null
            // this will mean that Object is preferred over a more
            // specific type
            // remove one to dist to be sure Object is preferred
            objectDistance--;
            ClassNode clazz = parameter;
            if (isPrimitiveType(clazz)) {
                objectDistance += 2;
            } else {
                while (!clazz.equals(OBJECT_TYPE)) {
                    clazz = clazz.getSuperClass();
                    objectDistance += 2;
                }
            }
        }
        return objectDistance << OBJECT_SHIFT;
    }

    public static long calculateParameterDistance(ClassNode[] arguments, Parameter[] parameters) {
        if (parameters.length == 0)
            return 0;

        long ret = 0;
        int noVargsLength = parameters.length - 1;

        // if the number of parameters does not match we have
        // a vargs usage
        //
        // case A: arguments.length<parameters.length
        //
        //         In this case arguments.length is always equal to
        //         noVargsLength because only the last parameter
        //         might be a optional vargs parameter
        //
        //         VArgs penalty: 1l
        //
        // case B: arguments.length>parameters.length
        //
        //         In this case all arguments with a index bigger than
        //         paramMinus1 are part of the vargs, so a
        //         distance calculation needs to be done against
        //         parameters[noVargsLength].getComponentType()
        //
        //         VArgs penalty: 2l+arguments.length-parameters.length
        //
        // case C: arguments.length==parameters.length &&
        //         isAssignableFrom( parameters[noVargsLength],
        //                           arguments[noVargsLength] )
        //
        //         In this case we have no vargs, so calculate directly
        //
        //         VArgs penalty: 0l
        //
        // case D: arguments.length==parameters.length &&
        //         !isAssignableFrom( parameters[noVargsLength],
        //                            arguments[noVargsLength] )
        //
        //         In this case we have a vargs case again, we need
        //         to calculate arguments[noVargsLength] against
        //         parameters[noVargsLength].getComponentType
        //
        //         VArgs penalty: 2l
        //
        //         This gives: VArgs_penalty(C)<VArgs_penalty(A)
        //                     VArgs_penalty(A)<VArgs_penalty(D)
        //                     VArgs_penalty(D)<VArgs_penalty(B)

        /**
         * In general we want to match the signature that allows us to use
         * as less arguments for the vargs part as possible. That means the
         * longer signature usually wins if both signatures are vargs, while
         * vargs looses always against a signature without vargs.
         *
         *  A vs B :
         *      def foo(Object[] a) {1}     -> case B
         *      def foo(a,b,Object[] c) {2} -> case A
         *      assert foo(new Object(),new Object()) == 2
         *  --> A preferred over B
         *
         *  A vs C :
         *      def foo(Object[] a) {1}     -> case B
         *      def foo(a,b)        {2}     -> case C
         *      assert foo(new Object(),new Object()) == 2
         *  --> C preferred over A
         *
         *  A vs D :
         *      def foo(Object[] a) {1}     -> case D
         *      def foo(a,Object[] b) {2}   -> case A
         *      assert foo(new Object()) == 2
         *  --> A preferred over D
         *
         *  This gives C<A<B,D
         *
         *  B vs C :
         *      def foo(Object[] a) {1}     -> case B
         *      def foo(a,b) {2}            -> case C
         *      assert foo(new Object(),new Object()) == 2
         *  --> C preferred over B, matches C<A<B,D
         *
         *  B vs D :
         *      def foo(Object[] a)   {1}   -> case B
         *      def foo(a,Object[] b) {2}   -> case D
         *      assert foo(new Object(),new Object()) == 2
         *  --> D preferred over B
         *
         *  This gives C<A<D<B
         */

        // first we calculate all arguments, that are for sure not part
        // of vargs.  Since the minimum for arguments is noVargsLength
        // we can safely iterate to this point
        for (int i = 0; i < noVargsLength; i++) {
            ret += calculateParameterDistance(arguments[i], parameters[i].getType());
        }

        if (arguments.length == parameters.length) {
            // case C&D, we use baseType to calculate and set it
            // to the value we need according to case C and D
            ClassNode baseType = parameters[noVargsLength].getType(); // case C
            if (!isAssignableOrInference(parameters[noVargsLength].getType(), arguments[noVargsLength])) {
                baseType = baseType.getComponentType(); // case D
                ret += 2l << VARGS_SHIFT; // penalty for vargs
            }
            ret += calculateParameterDistance(arguments[noVargsLength], baseType);
        } else if (arguments.length > parameters.length) {
            // case B
            // we give our a vargs penalty for each exceeding argument and iterate
            // by using parameters[noVargsLength].getComponentType()
            ret += (2l + arguments.length - parameters.length) << VARGS_SHIFT; // penalty for vargs
            ClassNode vargsType = (parameters[noVargsLength].getType().getComponentType());
            for (int i = noVargsLength; i < arguments.length; i++) {
                ret += calculateParameterDistance(arguments[i], vargsType);
            }
        } else {
            // case A
            // we give a penalty for vargs, since we have no direct
            // match for the last argument
            ret += 1l << VARGS_SHIFT;
        }

        return ret;
    }

    /**
     * @param methods the methods to choose from
     * @return the method with 1 parameter which takes the most general type of
     *         object (e.g. Object)
     */
    public static Object chooseEmptyMethodParams(FastArray methods) {
        Object vargsMethod = null;
        final int len = methods.size();
        final Object[] data = methods.getArray();
        for (int i = 0; i != len; ++i) {
            MethodNode method = (MethodNode) data[i];
            final Parameter pt[] = method.getParameters();
            int paramLength = pt.length;
            if (paramLength == 0) {
                return method;
            } else if (paramLength == 1 && isVargsMethodNoParams(pt)) {
                vargsMethod = method;
            }
        }
        return vargsMethod;
    }

    /**
     * @param methods the methods to choose from
     * @return the method with 1 parameter which takes the most general type of
     *         object (e.g. Object) ignoring primitve types
     */
    public static Object chooseMostGeneralMethodWith1NullParam(FastArray methods) {
        // let's look for methods with 1 argument which matches the type of the
        // arguments
        ClassNode closestClass = null;
        ClassNode closestVargsClass = null;
        Object answer = null;
        int closestDist = -1;
        final int len = methods.size();
        for (int i = 0; i != len; ++i) {
            final Object[] data = methods.getArray();
            MethodNode method = (MethodNode) data[i];
            final Parameter[] pt = method.getParameters();
            int paramLength = pt.length;
            if (paramLength == 0 || paramLength > 2)
                continue;

            ClassNode theType = pt[0].getType();
            if (isPrimitiveType(theType))
                continue;

            if (paramLength == 2) {
                if (!isVargsMethod(pt, ARRAY_WITH_NULL))
                    continue;
                if (closestClass == null) {
                    closestVargsClass = pt[1].getType();
                    closestClass = theType;
                    answer = method;
                } else if (closestClass.equals(theType)) {
                    if (closestVargsClass == null)
                        continue;
                    ClassNode newVargsClass = pt[1].getType();
                    if (closestVargsClass == null || isAssignableOrInference(newVargsClass, closestVargsClass)) {
                        closestVargsClass = newVargsClass;
                        answer = method;
                    }
                } else if (isAssignableOrInference(theType, closestClass)) {
                    closestVargsClass = pt[1].getType();
                    closestClass = theType;
                    answer = method;
                }
            } else {
                if (closestClass == null || isAssignableOrInference(theType, closestClass)) {
                    closestVargsClass = null;
                    closestClass = theType;
                    answer = method;
                    closestDist = -1;
                } else {
                    // closestClass and theType are not in a subtype relation, we need
                    // to check the distance to Object
                    if (closestDist == -1)
                        closestDist = getSuperClassDistance(closestClass);
                    int newDist = getSuperClassDistance(theType);
                    if (newDist == closestDist)
                        answer = null;
                    if (newDist < closestDist) {
                        closestDist = newDist;
                        closestVargsClass = null;
                        closestClass = theType;
                        answer = method;
                    }
                }
            }
        }
        return answer;
    }

    private static int getSuperClassDistance(ClassNode klazz) {
        int distance = 0;
        for (; klazz != null; klazz = klazz.getSuperClass()) {
            distance++;
        }
        return distance;
    }

    private static boolean isVargsMethod(Parameter pt[], ClassNode[] arguments) {
        if (pt.length == 0 || !pt[pt.length - 1].getType().isArray())
            return false;

        final int lenMinus1 = pt.length - 1;
        // -1 because the varg part is optional
        if (lenMinus1 == arguments.length)
            return true;
        if (lenMinus1 > arguments.length)
            return false;
        if (arguments.length > pt.length)
            return true;

        // only case left is arguments.length == parameterTypes.length
        ClassNode last = arguments[arguments.length - 1];
        return last == null || !last.equals(pt[lenMinus1].getType());

    }

    public static boolean isVargsMethodNoParams(Parameter pt[]) {
        return (pt.length == 1 && pt[pt.length - 1].getType().isArray());
    }

    private static boolean isValidMethod(Parameter[] pt, ClassNode[] arguments, ClassNode accessType,
            ClassNode declaringClass) {
        if (arguments == null)
            return true;

        final int size = arguments.length;
        final int paramMinus1 = pt.length - 1;

        if (isValidExactMethod(arguments, pt, accessType, declaringClass)) {
            return true;
        }

        boolean isVargsMethod = pt.length != 0 && (pt[pt.length - 1].getType().isArray());

        if (isVargsMethod && size >= paramMinus1)
            return isValidVarargsMethod(arguments, size, pt, paramMinus1, accessType, declaringClass);

        return false;
    }

    private static boolean isValidExactMethod(ClassNode[] arguments, Parameter[] pt, ClassNode accessType,
            ClassNode declaringClass) {
        if (pt.length != arguments.length)
            return false;
        // lets check the parameter types match
        int size = pt.length;
        for (int i = 0; i < size; i++) {
            ClassNode t = pt[i].getType();
            if (accessType != null) {
                t = TypeUtil.getSubstitutedType(t, declaringClass, accessType);
            }
            if (!isAssignableOrInference(t, arguments[i])) {
                return false;
            }
        }
        return true;
    }

    private static boolean testComponentAssignable(ClassNode toTestAgainst, ClassNode toTest) {
        ClassNode component = toTest.getComponentType();
        return component != null && isAssignableOrInference(toTestAgainst, component);
    }

    private static boolean isValidVarargsMethod(ClassNode[] arguments, int size, Parameter[] pt, int paramMinus1,
            ClassNode accessType, ClassNode declaringClass) {
        // first check normal number of parameters
        for (int i = 0; i < paramMinus1; i++) {
            ClassNode t = pt[i].getType();
            if (accessType != null) {
                t = TypeUtil.getSubstitutedType(t, declaringClass, accessType);
            }
            if (isAssignableOrInference(t, arguments[i]))
                continue;
            return false;
        }

        // check direct match
        ClassNode varg = pt[paramMinus1].getType();
        ClassNode componentType;
        if (varg.isArray()) {
            componentType = varg.getComponentType();
        } else {
            // List type
            final GenericsType[] generics = LIST_TYPE.getGenericsTypes();
            if (generics == null)
                return false;
            componentType = TypeUtil.getSubstitutedType(generics[0].getType(), LIST_TYPE, varg);
        }
        if (accessType != null) {
            componentType = TypeUtil.getSubstitutedType(componentType, declaringClass, accessType);
        }

        if (size == pt.length && (TypeUtil.isDirectlyAssignableFrom(varg, arguments[paramMinus1])
                || testComponentAssignable(componentType, arguments[paramMinus1]))) {
            return true;
        }

        // check varged
        for (int i = paramMinus1; i < size; i++) {
            if (isAssignableOrInference(componentType, arguments[i]))
                continue;
            return false;
        }
        return true;
    }

    public static MethodNode findPublicMethodInClass(ClassNode classToTransformFrom, String methodName,
            ClassNode[] args) {
        Object methodOrList = ClassNodeCache.getMethods(classToTransformFrom, methodName);
        if (methodOrList == null)
            return null;

        if (methodOrList instanceof MethodNode) {
            if (!((MethodNode) methodOrList).isPublic())
                return null;
        } else {
            final FastArray array = ((FastArray) methodOrList).copy();
            for (int i = 0; i < array.size(); i++) {
                final MethodNode method = (MethodNode) array.get(i);
                if (!method.isPublic())
                    array.remove(i);
            }
        }
        final Object selected = chooseMethod(methodName, methodOrList, classToTransformFrom, args, null);
        return selected instanceof MethodNode ? (MethodNode) selected : null;
    }

    private static boolean isAssignableOrInference(ClassNode classToTransformTo, ClassNode classToTransformFrom) {
        if (classToTransformFrom == null) {
            if (!ClassHelper.isPrimitiveType(classToTransformTo)
                    || ClassHelper.boolean_TYPE.equals(classToTransformTo))
                return true;
            else
                return false;
        }

        if (TypeUtil.isAssignableFrom(classToTransformTo, classToTransformFrom))
            return true;

        if (classToTransformFrom.equals(TypeUtil.TCLOSURE_NULL)) {
            if (classToTransformTo.equals(ClassHelper.CLOSURE_TYPE)) {
                return true;
            } else {
                List<MethodNode> one = ClosureUtil.isOneMethodAbstract(classToTransformTo);
                if (one != null) {

                    MethodNode missing = one.get(0);
                    Parameter[] missingMethodParameters = missing.getParameters();
                    List<MethodNode> methods = classToTransformFrom.getGenericsTypes()[0].getType()
                            .getDeclaredMethods("doCall");

                    for (MethodNode method : methods) {
                        Parameter[] closureParameters = method.getParameters();

                        if (closureParameters.length != missingMethodParameters.length)
                            continue;

                        return true;
                    }
                }
                return false;
            }
        }

        if (classToTransformFrom.equals(TypeUtil.TLIST_NULL)) {
            return true;
        }

        if (classToTransformFrom.equals(TypeUtil.TMAP_NULL)) {
            return !classToTransformTo.isArray() && ((classToTransformTo.getModifiers() & Opcodes.ACC_FINAL) == 0);
        }

        return false;
    }
}