Repository: groovy Updated Branches: refs/heads/GROOVY_2_6_X 25007b260 -> f8be74296
http://git-wip-us.apache.org/repos/asf/groovy/blob/0edfcde9/src/main/java/org/codehaus/groovy/runtime/MetaClassHelper.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/codehaus/groovy/runtime/MetaClassHelper.java b/src/main/java/org/codehaus/groovy/runtime/MetaClassHelper.java new file mode 100644 index 0000000..7839a59 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/MetaClassHelper.java @@ -0,0 +1,1034 @@ +/* + * 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.Closure; +import groovy.lang.GString; +import groovy.lang.GroovyObject; +import groovy.lang.GroovyRuntimeException; +import groovy.lang.MetaClass; +import groovy.lang.MetaMethod; +import org.codehaus.groovy.reflection.CachedClass; +import org.codehaus.groovy.reflection.ParameterTypes; +import org.codehaus.groovy.reflection.ReflectionCache; +import org.codehaus.groovy.runtime.wrappers.Wrapper; +import org.codehaus.groovy.util.FastArray; + +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 = { + boolean.class, + Boolean.class, + byte.class, + Byte.class, + short.class, + Short.class, + char.class, + Character.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 = { + // 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 + /*boolean[0]*/ { 0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2,}, + /*Boolean[1]*/ { 1, 0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 2,}, + /*byte[2]*/ {18, 19, 0, 1, 2, 3, 16, 17, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,}, + /*Byte[3]*/ {18, 19, 1, 0, 2, 3, 16, 17, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,}, + /*short[4]*/ {18, 19, 14, 15, 0, 1, 16, 17, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,}, + /*Short[5]*/ {18, 19, 14, 15, 1, 0, 16, 17, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,}, + /*char[6]*/ {18, 19, 16, 17, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,}, + /*Character[7]*/ {18, 19, 16, 17, 14, 15, 1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,}, + /*int[8]*/ {18, 19, 14, 15, 12, 13, 16, 17, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,}, + /*Integer[9]*/ {18, 19, 14, 15, 12, 13, 16, 17, 1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,}, + /*long[10]*/ {18, 19, 14, 15, 12, 13, 16, 17, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,}, + /*Long[11]*/ {18, 19, 14, 15, 12, 13, 16, 17, 10, 11, 1, 0, 2, 3, 4, 5, 6, 7, 8, 9,}, + /*BigInteger[12]*/ {18, 19, 9, 10, 7, 8, 16, 17, 5, 6, 3, 4, 0, 14, 15, 12, 13, 11, 1, 2,}, + /*float[13]*/ {18, 19, 14, 15, 12, 13, 16, 17, 10, 11, 8, 9, 7, 0, 1, 2, 3, 4, 5, 6,}, + /*Float[14]*/ {18, 19, 14, 15, 12, 13, 16, 17, 10, 11, 8, 9, 7, 1, 0, 2, 3, 4, 5, 6,}, + /*double[15]*/ {18, 19, 14, 15, 12, 13, 16, 17, 10, 11, 8, 9, 7, 5, 6, 0, 1, 2, 3, 4,}, + /*Double[16]*/ {18, 19, 14, 15, 12, 13, 16, 17, 10, 11, 8, 9, 7, 5, 6, 1, 0, 2, 3, 4,}, + /*BigDecimal[17]*/ {18, 19, 14, 15, 12, 13, 16, 17, 10, 11, 8, 9, 7, 5, 6, 3, 4, 0, 1, 2,}, + /*Number[18]*/ {18, 19, 14, 15, 12, 13, 16, 17, 10, 11, 8, 9, 7, 5, 6, 3, 4, 2, 0, 1,}, + /*Object[19]*/ {18, 19, 14, 15, 12, 13, 16, 17, 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); + } +}
