| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.codehaus.groovy.runtime; |
| |
| import groovy.lang.*; |
| |
| import org.codehaus.groovy.reflection.CachedClass; |
| import org.codehaus.groovy.util.FastArray; |
| import org.codehaus.groovy.reflection.ParameterTypes; |
| import org.codehaus.groovy.reflection.ReflectionCache; |
| import org.codehaus.groovy.runtime.wrappers.Wrapper; |
| |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Modifier; |
| import java.math.BigDecimal; |
| import java.math.BigInteger; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| /** |
| * @author John Wilson |
| * @author Jochen Theodorou |
| */ |
| public class MetaClassHelper { |
| |
| public static final Object[] EMPTY_ARRAY = {}; |
| public static final Class[] EMPTY_TYPE_ARRAY = {}; |
| public static final Object[] ARRAY_WITH_NULL = {null}; |
| protected static final Logger LOG = Logger.getLogger(MetaClassHelper.class.getName()); |
| private static final int MAX_ARG_LEN = 12; |
| private static final int |
| OBJECT_SHIFT = 23, INTERFACE_SHIFT = 0, |
| PRIMITIVE_SHIFT = 21, VARGS_SHIFT = 44; |
| /* dist binary layout: |
| * 0-20: interface |
| * 21-22: primitive dist |
| * 23-43: object dist |
| * 44-48: vargs penalty |
| */ |
| |
| public static final Class[] EMPTY_CLASS_ARRAY = new Class[0]; |
| |
| public static boolean accessibleToConstructor(final Class at, final Constructor constructor) { |
| boolean accessible = false; |
| final int modifiers = constructor.getModifiers(); |
| if (Modifier.isPublic(modifiers)) { |
| accessible = true; |
| } else if (Modifier.isPrivate(modifiers)) { |
| accessible = at.getName().equals(constructor.getName()); |
| } else if (Modifier.isProtected(modifiers)) { |
| Boolean isAccessible = checkCompatiblePackages(at, constructor); |
| if (isAccessible != null) { |
| accessible = isAccessible; |
| } else { |
| boolean flag = false; |
| Class clazz = at; |
| while (!flag && clazz != null) { |
| if (clazz.equals(constructor.getDeclaringClass())) { |
| flag = true; |
| break; |
| } |
| if (clazz.equals(Object.class)) { |
| break; |
| } |
| clazz = clazz.getSuperclass(); |
| } |
| accessible = flag; |
| } |
| } else { |
| Boolean isAccessible = checkCompatiblePackages(at, constructor); |
| if (isAccessible != null) { |
| accessible = isAccessible; |
| } |
| } |
| return accessible; |
| } |
| |
| private static Boolean checkCompatiblePackages(Class at, Constructor constructor) { |
| if (at.getPackage() == null && constructor.getDeclaringClass().getPackage() == null) { |
| return Boolean.TRUE; |
| } |
| if (at.getPackage() == null && constructor.getDeclaringClass().getPackage() != null) { |
| return Boolean.FALSE; |
| } |
| if (at.getPackage() != null && constructor.getDeclaringClass().getPackage() == null) { |
| return Boolean.FALSE; |
| } |
| if (at.getPackage().equals(constructor.getDeclaringClass().getPackage())) { |
| return Boolean.TRUE; |
| } |
| return null; |
| } |
| |
| public static Object[] asWrapperArray(Object parameters, Class componentType) { |
| Object[] ret = null; |
| if (componentType == boolean.class) { |
| boolean[] array = (boolean[]) parameters; |
| ret = new Object[array.length]; |
| for (int i = 0; i < array.length; i++) { |
| ret[i] = array[i]; |
| } |
| } else if (componentType == char.class) { |
| char[] array = (char[]) parameters; |
| ret = new Object[array.length]; |
| for (int i = 0; i < array.length; i++) { |
| ret[i] = array[i]; |
| } |
| } else if (componentType == byte.class) { |
| byte[] array = (byte[]) parameters; |
| ret = new Object[array.length]; |
| for (int i = 0; i < array.length; i++) { |
| ret[i] = array[i]; |
| } |
| } else if (componentType == int.class) { |
| int[] array = (int[]) parameters; |
| ret = new Object[array.length]; |
| for (int i = 0; i < array.length; i++) { |
| ret[i] = array[i]; |
| } |
| } else if (componentType == short.class) { |
| short[] array = (short[]) parameters; |
| ret = new Object[array.length]; |
| for (int i = 0; i < array.length; i++) { |
| ret[i] = array[i]; |
| } |
| } else if (componentType == long.class) { |
| long[] array = (long[]) parameters; |
| ret = new Object[array.length]; |
| for (int i = 0; i < array.length; i++) { |
| ret[i] = array[i]; |
| } |
| } else if (componentType == double.class) { |
| double[] array = (double[]) parameters; |
| ret = new Object[array.length]; |
| for (int i = 0; i < array.length; i++) { |
| ret[i] = array[i]; |
| } |
| } else if (componentType == float.class) { |
| float[] array = (float[]) parameters; |
| ret = new Object[array.length]; |
| for (int i = 0; i < array.length; i++) { |
| ret[i] = array[i]; |
| } |
| } |
| |
| return ret; |
| } |
| |
| |
| /** |
| * @param list the original list |
| * @param parameterType the resulting array type |
| * @return the constructed array |
| */ |
| public static Object asPrimitiveArray(List list, Class parameterType) { |
| Class arrayType = parameterType.getComponentType(); |
| Object objArray = Array.newInstance(arrayType, list.size()); |
| for (int i = 0; i < list.size(); i++) { |
| Object obj = list.get(i); |
| if (arrayType.isPrimitive()) { |
| if (obj instanceof Integer) { |
| Array.setInt(objArray, i, (Integer) obj); |
| } else if (obj instanceof Double) { |
| Array.setDouble(objArray, i, (Double) obj); |
| } else if (obj instanceof Boolean) { |
| Array.setBoolean(objArray, i, (Boolean) obj); |
| } else if (obj instanceof Long) { |
| Array.setLong(objArray, i, (Long) obj); |
| } else if (obj instanceof Float) { |
| Array.setFloat(objArray, i, (Float) obj); |
| } else if (obj instanceof Character) { |
| Array.setChar(objArray, i, (Character) obj); |
| } else if (obj instanceof Byte) { |
| Array.setByte(objArray, i, (Byte) obj); |
| } else if (obj instanceof Short) { |
| Array.setShort(objArray, i, (Short) obj); |
| } |
| } else { |
| Array.set(objArray, i, obj); |
| } |
| } |
| return objArray; |
| } |
| |
| private static final Class[] PRIMITIVES = { |
| byte.class, Byte.class, short.class, Short.class, |
| int.class, Integer.class, long.class, Long.class, |
| BigInteger.class, float.class, Float.class, |
| double.class, Double.class, BigDecimal.class, |
| Number.class, Object.class |
| }; |
| 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*/{9, 10, 7, 8, 5, 6, 3, 4, 0, 14, 15, 12, 13, 11, 1, 2,}, |
| /*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,}, |
| /*Number*/{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 int getPrimitiveIndex(Class c) { |
| for (byte i = 0; i < PRIMITIVES.length; i++) { |
| if (PRIMITIVES[i] == c) return i; |
| } |
| return -1; |
| } |
| |
| private static int getPrimitiveDistance(Class from, Class 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(Class c, Class interfaceClass) { |
| // -1 means a mismatch |
| if (c == null) return -1; |
| // 0 means a direct match |
| if (c == interfaceClass) return 0; |
| Class[] interfaces = c.getInterfaces(); |
| int max = -1; |
| for (Class 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); |
| if (superClassMax != -1) superClassMax++; |
| return Math.max(max, superClassMax); |
| } |
| |
| private static long calculateParameterDistance(Class argument, CachedClass 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 (parameter.getTheClass() == argument) return 0; |
| |
| if (parameter.isInterface()) { |
| int dist = getMaximumInterfaceDistance(argument, parameter.getTheClass()) << INTERFACE_SHIFT; |
| if (dist>-1 || !(argument!=null && Closure.class.isAssignableFrom(argument))) { |
| return dist; |
| } // else go to object case |
| } |
| |
| long objectDistance = 0; |
| if (argument != null) { |
| long pd = getPrimitiveDistance(parameter.getTheClass(), argument); |
| if (pd != -1) return pd << PRIMITIVE_SHIFT; |
| |
| // add one to dist to be sure interfaces are preferred |
| objectDistance += PRIMITIVES.length + 1; |
| |
| // GROOVY-5114 : if we have to choose between two methods |
| // foo(Object[]) and foo(Object) and that the argument is an array type |
| // then the array version should be preferred |
| if (argument.isArray() && !parameter.isArray) { |
| objectDistance+=4; |
| } |
| Class clazz = ReflectionCache.autoboxType(argument); |
| while (clazz != null) { |
| if (clazz == parameter.getTheClass()) break; |
| if (clazz == GString.class && parameter.getTheClass() == String.class) { |
| 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 |
| Class clazz = parameter.getTheClass(); |
| if (clazz.isPrimitive()) { |
| objectDistance += 2; |
| } else { |
| while (clazz != Object.class && clazz != null) { |
| clazz = clazz.getSuperclass(); |
| objectDistance += 2; |
| } |
| } |
| } |
| return objectDistance << OBJECT_SHIFT; |
| } |
| |
| public static long calculateParameterDistance(Class[] arguments, ParameterTypes pt) { |
| CachedClass[] parameters = pt.getParameterTypes(); |
| 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]); |
| } |
| |
| 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 |
| CachedClass baseType = parameters[noVargsLength]; // case C |
| if (!parameters[noVargsLength].isAssignableFrom(arguments[noVargsLength])) { |
| baseType = ReflectionCache.getCachedClass(baseType.getTheClass().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 |
| CachedClass vargsType = ReflectionCache.getCachedClass(parameters[noVargsLength].getTheClass().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; |
| } |
| |
| /** |
| * This is the complement to the java.beans.Introspector.decapitalize(String) method. |
| * We handle names that begin with an initial lowerCase followed by upperCase specially |
| * (which is to make no change). |
| * See GROOVY-3211. |
| * |
| * @param property the property name to capitalize |
| * @return the name capitalized, except when we don't |
| */ |
| public static String capitalize(final String property) { |
| final String rest = property.substring(1); |
| |
| // Funky rule so that names like 'pNAME' will still work. |
| if (Character.isLowerCase(property.charAt(0)) && (rest.length() > 0) && Character.isUpperCase(rest.charAt(0))) { |
| return property; |
| } |
| |
| return property.substring(0, 1).toUpperCase() + rest; |
| } |
| |
| /** |
| * @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) { |
| Object method = data[i]; |
| final ParameterTypes pt = (ParameterTypes) method; |
| CachedClass[] paramTypes = pt.getParameterTypes(); |
| int paramLength = paramTypes.length; |
| if (paramLength == 0) { |
| return method; |
| } else if (paramLength == 1 && pt.isVargsMethod(EMPTY_ARRAY)) { |
| vargsMethod = method; |
| } |
| } |
| return vargsMethod; |
| } |
| |
| /** |
| * Warning: this method does not choose properly if multiple methods with |
| * the same distance are encountered |
| * @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 primitive types |
| * @deprecated |
| */ |
| @Deprecated |
| public static Object chooseMostGeneralMethodWith1NullParam(FastArray methods) { |
| // let's look for methods with 1 argument which matches the type of the |
| // arguments |
| CachedClass closestClass = null; |
| CachedClass 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(); |
| Object method = data[i]; |
| final ParameterTypes pt = (ParameterTypes) method; |
| CachedClass[] paramTypes = pt.getParameterTypes(); |
| int paramLength = paramTypes.length; |
| if (paramLength == 0 || paramLength > 2) continue; |
| |
| CachedClass theType = paramTypes[0]; |
| if (theType.isPrimitive) continue; |
| |
| if (paramLength == 2) { |
| if (!pt.isVargsMethod(ARRAY_WITH_NULL)) continue; |
| if (closestClass == null) { |
| closestVargsClass = paramTypes[1]; |
| closestClass = theType; |
| answer = method; |
| } else if (closestClass.getTheClass() == theType.getTheClass()) { |
| if (closestVargsClass == null) continue; |
| CachedClass newVargsClass = paramTypes[1]; |
| if (isAssignableFrom(newVargsClass.getTheClass(), closestVargsClass.getTheClass())) { |
| closestVargsClass = newVargsClass; |
| answer = method; |
| } |
| } else if (isAssignableFrom(theType.getTheClass(), closestClass.getTheClass())) { |
| closestVargsClass = paramTypes[1]; |
| closestClass = theType; |
| answer = method; |
| } |
| } else { |
| if (closestClass == null || isAssignableFrom(theType.getTheClass(), closestClass.getTheClass())) { |
| 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 = closestClass.getSuperClassDistance(); |
| int newDist = theType.getSuperClassDistance(); |
| if (newDist < closestDist) { |
| closestDist = newDist; |
| closestVargsClass = null; |
| closestClass = theType; |
| answer = method; |
| } |
| } |
| } |
| } |
| return answer; |
| } |
| |
| // |
| |
| /** |
| * @param list a list of MetaMethods |
| * @param method the MetaMethod of interest |
| * @return true if a method of the same matching prototype was found in the |
| * list |
| */ |
| public static boolean containsMatchingMethod(List list, MetaMethod method) { |
| for (Object aList : list) { |
| MetaMethod aMethod = (MetaMethod) aList; |
| CachedClass[] params1 = aMethod.getParameterTypes(); |
| CachedClass[] params2 = method.getParameterTypes(); |
| if (params1.length == params2.length) { |
| boolean matches = true; |
| for (int i = 0; i < params1.length; i++) { |
| if (params1[i] != params2[i]) { |
| matches = false; |
| break; |
| } |
| } |
| if (matches) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * param instance array to the type array |
| * |
| * @param args the arguments |
| * @return the types of the arguments |
| */ |
| public static Class[] convertToTypeArray(Object[] args) { |
| if (args == null) |
| return null; |
| int s = args.length; |
| Class[] ans = new Class[s]; |
| for (int i = 0; i < s; i++) { |
| Object o = args[i]; |
| ans[i] = getClassWithNullAndWrapper(o); |
| } |
| return ans; |
| } |
| |
| public static Object makeCommonArray(Object[] arguments, int offset, Class fallback) { |
| // arguments.length>0 && !=null |
| Class baseClass = null; |
| for (int i = offset; i < arguments.length; i++) { |
| if (arguments[i] == null) continue; |
| Class argClass = arguments[i].getClass(); |
| if (baseClass == null) { |
| baseClass = argClass; |
| } else { |
| for (; baseClass != Object.class; baseClass = baseClass.getSuperclass()) { |
| if (baseClass.isAssignableFrom(argClass)) break; |
| } |
| } |
| } |
| if (baseClass == null) { |
| // all arguments were null |
| baseClass = fallback; |
| } |
| /* |
| * If no specific super class has been found and type fallback is an interface, check if all arg classes |
| * implement it. If yes, then that interface is the common type across arguments. |
| */ |
| if (baseClass == Object.class && fallback.isInterface()) { |
| int tmpCount = 0; |
| for (int i = offset; i < arguments.length; i++) { |
| if (arguments[i] != null) { |
| Class tmpClass; |
| Set<Class> intfs = new HashSet<Class>(); |
| tmpClass = arguments[i].getClass(); |
| for (; tmpClass != Object.class; tmpClass = tmpClass.getSuperclass()) { |
| intfs.addAll(Arrays.asList(tmpClass.getInterfaces())); |
| } |
| if (intfs.contains(fallback)) { |
| tmpCount++; |
| } |
| } |
| } |
| // all arg classes implement interface fallback, so use that as the array component type |
| if (tmpCount == arguments.length - offset) { |
| baseClass = fallback; |
| } |
| } |
| Object result = makeArray(null, baseClass, arguments.length - offset); |
| System.arraycopy(arguments, offset, result, 0, arguments.length - offset); |
| return result; |
| } |
| |
| public static Object makeArray(Object obj, Class secondary, int length) { |
| Class baseClass = secondary; |
| if (obj != null) { |
| baseClass = obj.getClass(); |
| } |
| /*if (GString.class.isAssignableFrom(baseClass)) { |
| baseClass = GString.class; |
| }*/ |
| return Array.newInstance(baseClass, length); |
| } |
| |
| public static GroovyRuntimeException createExceptionText(String init, MetaMethod method, Object object, Object[] args, Throwable reason, boolean setReason) { |
| return new GroovyRuntimeException( |
| init |
| + method |
| + " on: " |
| + object |
| + " with arguments: " |
| + InvokerHelper.toString(args) |
| + " reason: " |
| + reason, |
| setReason ? reason : null); |
| } |
| |
| protected static String getClassName(Object object) { |
| if (object == null) return null; |
| return (object instanceof Class) ? ((Class) object).getName() : object.getClass().getName(); |
| } |
| |
| /** |
| * Returns a callable object for the given method name on the object. |
| * The object acts like a Closure in that it can be called, like a closure |
| * and passed around - though really its a method pointer, not a closure per se. |
| * |
| * @param object the object containing the method |
| * @param methodName the method of interest |
| * @return the resulting closure-like method pointer |
| */ |
| public static Closure getMethodPointer(Object object, String methodName) { |
| return new MethodClosure(object, methodName); |
| } |
| |
| public static boolean isAssignableFrom(Class classToTransformTo, Class classToTransformFrom) { |
| if (classToTransformTo == classToTransformFrom |
| || classToTransformFrom == null |
| || classToTransformTo == Object.class) { |
| return true; |
| } |
| |
| classToTransformTo = ReflectionCache.autoboxType(classToTransformTo); |
| classToTransformFrom = ReflectionCache.autoboxType(classToTransformFrom); |
| if (classToTransformTo == classToTransformFrom) return true; |
| |
| // note: there is no coercion for boolean and char. Range matters, precision doesn't |
| if (classToTransformTo == Integer.class) { |
| if (classToTransformFrom == Short.class |
| || classToTransformFrom == Byte.class |
| || classToTransformFrom == BigInteger.class) |
| return true; |
| } else if (classToTransformTo == Double.class) { |
| if (classToTransformFrom == Integer.class |
| || classToTransformFrom == Long.class |
| || classToTransformFrom == Short.class |
| || classToTransformFrom == Byte.class |
| || classToTransformFrom == Float.class |
| || classToTransformFrom == BigDecimal.class |
| || classToTransformFrom == BigInteger.class) |
| return true; |
| } else if (classToTransformTo == BigDecimal.class) { |
| if (classToTransformFrom == Double.class |
| || classToTransformFrom == Integer.class |
| || classToTransformFrom == Long.class |
| || classToTransformFrom == Short.class |
| || classToTransformFrom == Byte.class |
| || classToTransformFrom == Float.class |
| || classToTransformFrom == BigInteger.class) |
| return true; |
| } else if (classToTransformTo == BigInteger.class) { |
| if (classToTransformFrom == Integer.class |
| || classToTransformFrom == Long.class |
| || classToTransformFrom == Short.class |
| || classToTransformFrom == Byte.class) |
| return true; |
| } else if (classToTransformTo == Long.class) { |
| if (classToTransformFrom == Integer.class |
| || classToTransformFrom == Short.class |
| || classToTransformFrom == Byte.class) |
| return true; |
| } else if (classToTransformTo == Float.class) { |
| if (classToTransformFrom == Integer.class |
| || classToTransformFrom == Long.class |
| || classToTransformFrom == Short.class |
| || classToTransformFrom == Byte.class) |
| return true; |
| } else if (classToTransformTo == Short.class) { |
| if (classToTransformFrom == Byte.class) |
| return true; |
| } else if (classToTransformTo == String.class) { |
| if (GString.class.isAssignableFrom(classToTransformFrom)) { |
| return true; |
| } |
| } |
| |
| return ReflectionCache.isAssignableFrom(classToTransformTo, classToTransformFrom); |
| } |
| |
| public static boolean isGenericSetMethod(MetaMethod method) { |
| return (method.getName().equals("set")) |
| && method.getParameterTypes().length == 2; |
| } |
| |
| protected static boolean isSuperclass(Class clazz, Class superclass) { |
| while (clazz != null) { |
| if (clazz == superclass) return true; |
| clazz = clazz.getSuperclass(); |
| } |
| return false; |
| } |
| |
| public static boolean parametersAreCompatible(Class[] arguments, Class[] parameters) { |
| if (arguments.length != parameters.length) return false; |
| for (int i = 0; i < arguments.length; i++) { |
| if (!isAssignableFrom(parameters[i], arguments[i])) return false; |
| } |
| return true; |
| } |
| |
| public static void logMethodCall(Object object, String methodName, Object[] arguments) { |
| String className = getClassName(object); |
| String logname = "methodCalls." + className + "." + methodName; |
| Logger objLog = Logger.getLogger(logname); |
| if (!objLog.isLoggable(Level.FINER)) return; |
| StringBuilder msg = new StringBuilder(methodName); |
| msg.append("("); |
| if (arguments != null) { |
| for (int i = 0; i < arguments.length;) { |
| msg.append(normalizedValue(arguments[i])); |
| if (++i < arguments.length) { |
| msg.append(","); |
| } |
| } |
| } |
| msg.append(")"); |
| objLog.logp(Level.FINER, className, msg.toString(), "called from MetaClass.invokeMethod"); |
| } |
| |
| protected static String normalizedValue(Object argument) { |
| String value; |
| try { |
| value = argument.toString(); |
| if (value.length() > MAX_ARG_LEN) { |
| value = value.substring(0, MAX_ARG_LEN - 2) + ".."; |
| } |
| if (argument instanceof String) { |
| value = "\'" + value + "\'"; |
| } |
| } catch (Exception e) { |
| value = shortName(argument); |
| } |
| return value; |
| } |
| |
| protected static String shortName(Object object) { |
| if (object == null || object.getClass() == null) return "unknownClass"; |
| String name = getClassName(object); |
| if (name == null) return "unknownClassName"; // *very* defensive... |
| int lastDotPos = name.lastIndexOf('.'); |
| if (lastDotPos < 0 || lastDotPos >= name.length() - 1) return name; |
| return name.substring(lastDotPos + 1); |
| } |
| |
| public static Class[] wrap(Class[] classes) { |
| Class[] wrappedArguments = new Class[classes.length]; |
| for (int i = 0; i < wrappedArguments.length; i++) { |
| Class c = classes[i]; |
| if (c == null) continue; |
| if (c.isPrimitive()) { |
| if (c == Integer.TYPE) { |
| c = Integer.class; |
| } else if (c == Byte.TYPE) { |
| c = Byte.class; |
| } else if (c == Long.TYPE) { |
| c = Long.class; |
| } else if (c == Double.TYPE) { |
| c = Double.class; |
| } else if (c == Float.TYPE) { |
| c = Float.class; |
| } |
| } else if (isSuperclass(c, GString.class)) { |
| c = String.class; |
| } |
| wrappedArguments[i] = c; |
| } |
| return wrappedArguments; |
| } |
| |
| public static boolean sameClasses(Class[] params, Object[] arguments, boolean weakNullCheck) { |
| if (params.length != arguments.length) |
| return false; |
| |
| for (int i = params.length - 1; i >= 0; i--) { |
| Object arg = arguments[i]; |
| Class compareClass = getClassWithNullAndWrapper(arg); |
| if (params[i] != compareClass) return false; |
| } |
| |
| return true; |
| } |
| |
| private static Class getClassWithNullAndWrapper(Object arg) { |
| if (arg == null) return null; |
| if (arg instanceof Wrapper) { |
| Wrapper w = (Wrapper) arg; |
| return w.getType(); |
| } |
| return arg.getClass(); |
| } |
| |
| public static boolean sameClasses(Class[] params, Object[] arguments) { |
| if (params.length != arguments.length) |
| return false; |
| |
| for (int i = params.length - 1; i >= 0; i--) { |
| Object arg = arguments[i]; |
| if (arg == null) { |
| if (params[i] != null) |
| return false; |
| } else { |
| if (params[i] != getClassWithNullAndWrapper(arg)) |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| public static boolean sameClasses(Class[] params) { |
| if (params.length != 0) |
| return false; |
| |
| return true; |
| } |
| |
| public static boolean sameClasses(Class[] params, Object arg1) { |
| if (params.length != 1) |
| return false; |
| |
| if (params[0] != getClassWithNullAndWrapper(arg1)) return false; |
| |
| return true; |
| } |
| |
| public static boolean sameClasses(Class[] params, Object arg1, Object arg2) { |
| if (params.length != 2) |
| return false; |
| |
| if (params[0] != getClassWithNullAndWrapper(arg1)) return false; |
| if (params[1] != getClassWithNullAndWrapper(arg2)) return false; |
| |
| return true; |
| } |
| |
| public static boolean sameClasses(Class[] params, Object arg1, Object arg2, Object arg3) { |
| if (params.length != 3) |
| return false; |
| |
| if (params[0] != getClassWithNullAndWrapper(arg1)) return false; |
| if (params[1] != getClassWithNullAndWrapper(arg2)) return false; |
| if (params[2] != getClassWithNullAndWrapper(arg3)) return false; |
| |
| return true; |
| } |
| |
| public static boolean sameClasses(Class[] params, Object arg1, Object arg2, Object arg3, Object arg4) { |
| if (params.length != 4) |
| return false; |
| |
| if (params[0] != getClassWithNullAndWrapper(arg1)) return false; |
| if (params[1] != getClassWithNullAndWrapper(arg2)) return false; |
| if (params[2] != getClassWithNullAndWrapper(arg3)) return false; |
| if (params[3] != getClassWithNullAndWrapper(arg4)) return false; |
| |
| return true; |
| } |
| |
| public static boolean sameClass(Class[] params, Object arg) { |
| return params[0] == getClassWithNullAndWrapper(arg); |
| |
| } |
| |
| public static Class[] castArgumentsToClassArray(Object[] argTypes) { |
| if (argTypes == null) return EMPTY_CLASS_ARRAY; |
| Class[] classes = new Class[argTypes.length]; |
| for (int i = 0; i < argTypes.length; i++) { |
| Object argType = argTypes[i]; |
| if (argType instanceof Class) { |
| classes[i] = (Class) argType; |
| } else if (argType == null) { |
| classes[i] = null; |
| } else { |
| // throw new IllegalArgumentException("Arguments to method [respondsTo] must be of type java.lang.Class!"); |
| classes[i] = argType.getClass(); |
| } |
| } |
| return classes; |
| } |
| |
| public static void unwrap(Object[] arguments) { |
| // |
| // Temp code to ignore wrapped parameters |
| // The New MOP will deal with these properly |
| // |
| for (int i = 0; i != arguments.length; i++) { |
| if (arguments[i] instanceof Wrapper) { |
| arguments[i] = ((Wrapper) arguments[i]).unwrap(); |
| } |
| } |
| } |
| |
| /** |
| * Sets the meta class for an object, by delegating to the appropriate |
| * {@link DefaultGroovyMethods} helper method. This method was introduced as |
| * a breaking change in 2.0 to solve rare cases of stack overflow. See GROOVY-5285. |
| * |
| * The method is named doSetMetaClass in order to prevent misusages. Do not use |
| * this method directly unless you know what you do. |
| * |
| * @param self the object for which to set the meta class |
| * @param mc the metaclass |
| */ |
| public static void doSetMetaClass(Object self, MetaClass mc) { |
| if (self instanceof GroovyObject) { |
| DefaultGroovyMethods.setMetaClass((GroovyObject)self, mc); |
| } else { |
| DefaultGroovyMethods.setMetaClass(self, mc); |
| } |
| } |
| |
| /** |
| * Converts a String into a standard property name. |
| * |
| * @param prop the original name |
| * @return the converted name |
| */ |
| public static String convertPropertyName(String prop) { |
| if (Character.isDigit(prop.charAt(0))) { |
| return prop; |
| } |
| return java.beans.Introspector.decapitalize(prop); |
| } |
| } |