/* * Copyright 2009-2010 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.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 (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() || TypeUtil.isDirectlyAssignableFrom(ClassHelper.LIST_TYPE, pt[pt.length - 1].getType())); 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; } }