| /* |
| * Copyright 2005 John G. Wilson |
| * |
| * 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.codehaus.groovy.runtime; |
| |
| import groovy.lang.Closure; |
| import groovy.lang.GString; |
| import groovy.lang.GroovyRuntimeException; |
| import groovy.lang.MetaMethod; |
| |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.InvocationHandler; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.lang.reflect.Proxy; |
| import java.math.BigDecimal; |
| import java.math.BigInteger; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| import org.codehaus.groovy.GroovyBugError; |
| import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; |
| import org.codehaus.groovy.runtime.wrappers.Wrapper; |
| |
| /** |
| * @author John Wilson |
| * @author Jochen Theodorou |
| */ |
| public class MetaClassHelper { |
| |
| public static final Object[] EMPTY_ARRAY = {}; |
| public static Class[] EMPTY_TYPE_ARRAY = {}; |
| protected 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; |
| |
| public static boolean accessibleToConstructor(final Class at, final Constructor constructor) { |
| boolean accessible = false; |
| if (Modifier.isPublic(constructor.getModifiers())) { |
| accessible = true; |
| } |
| else if (Modifier.isPrivate(constructor.getModifiers())) { |
| accessible = at.getName().equals(constructor.getName()); |
| } |
| else if ( Modifier.isProtected(constructor.getModifiers()) ) { |
| if ( at.getPackage() == null && constructor.getDeclaringClass().getPackage() == null ) { |
| accessible = true; |
| } |
| else if ( at.getPackage() == null && constructor.getDeclaringClass().getPackage() != null ) { |
| accessible = false; |
| } |
| else if ( at.getPackage() != null && constructor.getDeclaringClass().getPackage() == null ) { |
| accessible = false; |
| } |
| else if ( at.getPackage().equals(constructor.getDeclaringClass().getPackage()) ) { |
| accessible = true; |
| } |
| 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 { |
| if ( at.getPackage() == null && constructor.getDeclaringClass().getPackage() == null ) { |
| accessible = true; |
| } |
| else if ( at.getPackage() == null && constructor.getDeclaringClass().getPackage() != null ) { |
| accessible = false; |
| } |
| else if ( at.getPackage() != null && constructor.getDeclaringClass().getPackage() == null ) { |
| accessible = false; |
| } |
| else if ( at.getPackage().equals(constructor.getDeclaringClass().getPackage()) ) { |
| accessible = true; |
| } |
| } |
| return accessible; |
| } |
| |
| 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] = new Boolean(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] = new Character(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] = new Byte(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] = new Integer(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] = new Short(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] = new Long(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] = new Double(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] = new Float(array[i]); |
| } |
| } |
| |
| return ret; |
| } |
| |
| |
| /** |
| * @param list |
| * @param parameterType |
| */ |
| 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).intValue()); |
| } |
| else if (obj instanceof Double) { |
| Array.setDouble(objArray, i, ((Double) obj).doubleValue()); |
| } |
| else if (obj instanceof Boolean) { |
| Array.setBoolean(objArray, i, ((Boolean) obj).booleanValue()); |
| } |
| else if (obj instanceof Long) { |
| Array.setLong(objArray, i, ((Long) obj).longValue()); |
| } |
| else if (obj instanceof Float) { |
| Array.setFloat(objArray, i, ((Float) obj).floatValue()); |
| } |
| else if (obj instanceof Character) { |
| Array.setChar(objArray, i, ((Character) obj).charValue()); |
| } |
| else if (obj instanceof Byte) { |
| Array.setByte(objArray, i, ((Byte) obj).byteValue()); |
| } |
| else if (obj instanceof Short) { |
| Array.setShort(objArray, i, ((Short) obj).shortValue()); |
| } |
| } |
| else { |
| Array.set(objArray, i, obj); |
| } |
| } |
| return objArray; |
| } |
| |
| protected static Class autoboxType(Class type) { |
| if (type.isPrimitive()) { |
| if (type == int.class) { |
| return Integer.class; |
| } |
| else if (type == double.class) { |
| return Double.class; |
| } |
| else if (type == long.class) { |
| return Long.class; |
| } |
| else if (type == boolean.class) { |
| return Boolean.class; |
| } |
| else if (type == float.class) { |
| return Float.class; |
| } |
| else if (type == char.class) { |
| return Character.class; |
| } |
| else if (type == byte.class) { |
| return Byte.class; |
| } |
| else if (type == short.class) { |
| return Short.class; |
| } |
| } |
| return type; |
| } |
| |
| private static 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 int[][] primitiveDistanceTable = { |
| // 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 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 primitiveDistanceTable[toIndex][fromIndex]; |
| } |
| |
| private static int getMaximumInterfaceDistance(Class c, Class interfaceClass) { |
| if (c==interfaceClass) return 0; |
| Class[] interfaces = c.getInterfaces(); |
| int max = 0; |
| for (int i=0; i<interfaces.length; i++) { |
| int sub = 0; |
| if (interfaces[i].isAssignableFrom(c)) { |
| sub = 1+ getMaximumInterfaceDistance(interfaces[i],interfaceClass); |
| } |
| max = Math.max(max,sub); |
| } |
| return max; |
| } |
| |
| public static long calculateParameterDistance(Class[] arguments, Class[] parameters) { |
| int objectDistance=0, interfaceDistance=0; |
| for (int i=0; i<arguments.length; i++) { |
| if (parameters[i]==arguments[i]) continue; |
| |
| if (parameters[i].isInterface()) { |
| objectDistance+=primitives.length; |
| interfaceDistance += getMaximumInterfaceDistance(arguments[i],parameters[i]); |
| continue; |
| } |
| |
| if (arguments[i]!=null) { |
| int pd = getPrimitiveDistance(parameters[i],arguments[i]); |
| if (pd!=-1) { |
| objectDistance += pd; |
| continue; |
| } |
| |
| // add one to dist to be sure interfaces are prefered |
| objectDistance += primitives.length+1; |
| Class clazz = autoboxType(arguments[i]); |
| while (clazz!=null) { |
| if (clazz==parameters[i]) break; |
| if (clazz==GString.class && parameters[i]==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 prefered over a more |
| // specific type |
| // remove one to dist to be sure Object is prefered |
| objectDistance--; |
| Class clazz = parameters[i]; |
| if (clazz.isPrimitive()) { |
| objectDistance+=2; |
| } else { |
| while (clazz!=Object.class) { |
| clazz = clazz.getSuperclass(); |
| objectDistance+=2; |
| } |
| } |
| } |
| } |
| long ret = objectDistance; |
| ret <<= 32; |
| ret |= interfaceDistance; |
| return ret; |
| } |
| |
| public static String capitalize(String property) { |
| return property.substring(0, 1).toUpperCase() + property.substring(1, property.length()); |
| } |
| |
| /** |
| * @return the method with 1 parameter which takes the most general type of |
| * object (e.g. Object) |
| */ |
| public static Object chooseEmptyMethodParams(List methods) { |
| for (Iterator iter = methods.iterator(); iter.hasNext();) { |
| Object method = iter.next(); |
| Class[] paramTypes = getParameterTypes(method); |
| int paramLength = paramTypes.length; |
| if (paramLength == 0) { |
| return method; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * @return the method with 1 parameter which takes the most general type of |
| * object (e.g. Object) ignoring primitve types |
| */ |
| public static Object chooseMostGeneralMethodWith1NullParam(List methods) { |
| // lets look for methods with 1 argument which matches the type of the |
| // arguments |
| Class closestClass = null; |
| Object answer = null; |
| |
| for (Iterator iter = methods.iterator(); iter.hasNext();) { |
| Object method = iter.next(); |
| Class[] paramTypes = getParameterTypes(method); |
| int paramLength = paramTypes.length; |
| if (paramLength == 1) { |
| Class theType = paramTypes[0]; |
| if (theType.isPrimitive()) continue; |
| if (closestClass == null || isAssignableFrom(theType, closestClass)) { |
| closestClass = theType; |
| answer = method; |
| } |
| } |
| } |
| return answer; |
| } |
| |
| /** |
| * Coerces a GString instance into String if needed |
| * |
| * @return the coerced argument |
| */ |
| protected static Object coerceGString(Object argument, Class clazz) { |
| if (clazz!=String.class) return argument; |
| if (!(argument instanceof GString)) return argument; |
| return argument.toString(); |
| } |
| |
| protected static Object coerceNumber(Object argument, Class param) { |
| if ((Number.class.isAssignableFrom(param) || param.isPrimitive()) && argument instanceof Number) { // Number types |
| Object oldArgument = argument; |
| boolean wasDouble = false; |
| boolean wasFloat = false; |
| if (param == Byte.class || param == Byte.TYPE ) { |
| argument = new Byte(((Number)argument).byteValue()); |
| } else if (param == Double.class || param == Double.TYPE) { |
| wasDouble = true; |
| argument = new Double(((Number)argument).doubleValue()); |
| } else if (param == Float.class || param == Float.TYPE) { |
| wasFloat = true; |
| argument = new Float(((Number)argument).floatValue()); |
| } else if (param == Integer.class || param == Integer.TYPE) { |
| argument = new Integer(((Number)argument).intValue()); |
| } else if (param == Long.class || param == Long.TYPE) { |
| argument = new Long(((Number)argument).longValue()); |
| } else if (param == Short.class || param == Short.TYPE) { |
| argument = new Short(((Number)argument).shortValue()); |
| } else if (param == BigDecimal.class ) { |
| argument = new BigDecimal(String.valueOf((Number)argument)); |
| } else if (param == BigInteger.class) { |
| argument = new BigInteger(String.valueOf((Number)argument)); |
| } |
| |
| if (oldArgument instanceof BigDecimal) { |
| BigDecimal oldbd = (BigDecimal) oldArgument; |
| boolean throwException = false; |
| if (wasDouble) { |
| Double d = (Double) argument; |
| if (d.isInfinite()) throwException = true; |
| } else if (wasFloat) { |
| Float f = (Float) argument; |
| if (f.isInfinite()) throwException = true; |
| } else { |
| BigDecimal newbd = new BigDecimal(String.valueOf((Number)argument)); |
| throwException = !oldArgument.equals(newbd); |
| } |
| |
| if (throwException) throw new IllegalArgumentException(param+" out of range while converting from BigDecimal"); |
| } |
| |
| } |
| return argument; |
| } |
| |
| protected static Object coerceArray(Object argument, Class param) { |
| if (!param.isArray()) return argument; |
| Class argumentClass = argument.getClass(); |
| if (!argumentClass.isArray()) return argument; |
| |
| Class paramComponent = param.getComponentType(); |
| if (paramComponent.isPrimitive()) { |
| if (paramComponent == boolean.class && argumentClass==Boolean[].class) { |
| argument = DefaultTypeTransformation.convertToBooleanArray(argument); |
| } else if (paramComponent == byte.class && argumentClass==Byte[].class) { |
| argument = DefaultTypeTransformation.convertToByteArray(argument); |
| } else if (paramComponent == char.class && argumentClass==Character[].class) { |
| argument = DefaultTypeTransformation.convertToCharArray(argument); |
| } else if (paramComponent == short.class && argumentClass==Short[].class) { |
| argument = DefaultTypeTransformation.convertToShortArray(argument); |
| } else if (paramComponent == int.class && argumentClass==Integer[].class) { |
| argument = DefaultTypeTransformation.convertToIntArray(argument); |
| } else if (paramComponent == long.class && |
| (argumentClass == Long[].class || argumentClass == Integer[].class)) |
| { |
| argument = DefaultTypeTransformation.convertToLongArray(argument); |
| } else if (paramComponent == float.class && |
| (argumentClass == Float[].class || argumentClass == Integer[].class)) |
| { |
| argument = DefaultTypeTransformation.convertToFloatArray(argument); |
| } else if (paramComponent == double.class && |
| (argumentClass == Double[].class || argumentClass==Float[].class |
| || BigDecimal.class.isAssignableFrom(argumentClass))) |
| { |
| argument = DefaultTypeTransformation.convertToDoubleArray(argument); |
| } |
| } else if (paramComponent==String.class && argument instanceof GString[]) { |
| GString[] strings = (GString[]) argument; |
| String[] ret = new String[strings.length]; |
| for (int i=0; i<strings.length; i++) { |
| ret[i] = strings[i].toString(); |
| } |
| argument = ret; |
| } |
| return argument; |
| } |
| |
| /** |
| * @return true if a method of the same matching prototype was found in the |
| * list |
| */ |
| public static boolean containsMatchingMethod(List list, MetaMethod method) { |
| for (Iterator iter = list.iterator(); iter.hasNext();) { |
| MetaMethod aMethod = (MetaMethod) iter.next(); |
| Class[] params1 = aMethod.getParameterTypes(); |
| Class[] 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 |
| */ |
| 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]; |
| if (o == null) { |
| ans[i] = null; |
| } else if (o instanceof Wrapper) { |
| ans[i] = ((Wrapper) o).getType(); |
| } else { |
| ans[i] = o.getClass(); |
| } |
| } |
| return ans; |
| } |
| |
| /** |
| * @param listenerType |
| * the interface of the listener to proxy |
| * @param listenerMethodName |
| * the name of the method in the listener API to call the |
| * closure on |
| * @param closure |
| * the closure to invoke on the listenerMethodName method |
| * invocation |
| * @return a dynamic proxy which calls the given closure on the given |
| * method name |
| */ |
| public static Object createListenerProxy(Class listenerType, final String listenerMethodName, final Closure closure) { |
| InvocationHandler handler = new ClosureListener(listenerMethodName, closure); |
| return Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[] { listenerType }, handler); |
| } |
| |
| public static Object doConstructorInvoke(Constructor constructor, Object[] argumentArray) { |
| if (log.isLoggable(Level.FINER)){ |
| logMethodCall(constructor.getDeclaringClass(), constructor.getName(), argumentArray); |
| } |
| argumentArray = coerceArgumentsToClasses(argumentArray,constructor.getParameterTypes()); |
| try { |
| return constructor.newInstance(argumentArray); |
| } catch (InvocationTargetException e) { |
| throw new InvokerInvocationException(e); |
| } catch (IllegalArgumentException e) { |
| throw createExceptionText("failed to invoke constructor: ", constructor, argumentArray, e, false); |
| } catch (IllegalAccessException e) { |
| throw createExceptionText("could not access constructor: ", constructor, argumentArray, e, false); |
| } catch (Exception e) { |
| throw createExceptionText("failed to invoke constructor: ", constructor, argumentArray, e, true); |
| } |
| } |
| |
| private static GroovyRuntimeException createExceptionText(String init, Constructor constructor, Object[] argumentArray, Throwable e, boolean setReason) { |
| throw new GroovyRuntimeException( |
| init |
| + constructor |
| + " with arguments: " |
| + InvokerHelper.toString(argumentArray) |
| + " reason: " |
| + e, |
| setReason?e:null); |
| } |
| |
| public static Object[] coerceArgumentsToClasses(Object[] argumentArray, Class[] paramTypes) { |
| // correct argumentArray's length |
| if (argumentArray == null) { |
| argumentArray = EMPTY_ARRAY; |
| } else if (paramTypes.length == 1 && argumentArray.length == 0) { |
| if (isVargsMethod(paramTypes,argumentArray)) |
| argumentArray = new Object[]{Array.newInstance(paramTypes[0].getComponentType(),0)}; |
| else |
| argumentArray = ARRAY_WITH_NULL; |
| } else if (isVargsMethod(paramTypes,argumentArray)) { |
| argumentArray = fitToVargs(argumentArray, paramTypes); |
| } |
| |
| //correct Type |
| for (int i=0; i<argumentArray.length; i++) { |
| Object argument = argumentArray[i]; |
| if (argument==null) continue; |
| Class parameterType = paramTypes[i]; |
| if (parameterType.isInstance(argument)) continue; |
| |
| argument = coerceGString(argument,parameterType); |
| argument = coerceNumber(argument,parameterType); |
| argument = coerceArray(argument,parameterType); |
| argumentArray[i] = argument; |
| } |
| return argumentArray; |
| } |
| |
| private static Object makeCommonArray(Object[] arguments, int offset, Class fallback) { |
| // arguments.leght>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; |
| } |
| Object result = makeArray(null,baseClass,arguments.length-offset); |
| System.arraycopy(arguments,offset,result,0,arguments.length-offset); |
| return result; |
| } |
| |
| private 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); |
| } |
| |
| /** |
| * this method is called when the number of arguments to a method is greater than 1 |
| * and if the method is a vargs method. This method will then transform the given |
| * arguments to make the method callable |
| * |
| * @param argumentArray the arguments used to call the method |
| * @param paramTypes the types of the paramters the method takes |
| */ |
| private static Object[] fitToVargs(Object[] argumentArray, Class[] paramTypes) { |
| Class vargsClass = autoboxType(paramTypes[paramTypes.length-1].getComponentType()); |
| |
| if (argumentArray.length == paramTypes.length-1) { |
| // the vargs argument is missing, so fill it with an empty array |
| Object[] newArgs = new Object[paramTypes.length]; |
| System.arraycopy(argumentArray,0,newArgs,0,argumentArray.length); |
| Object vargs = makeArray(null,vargsClass,0); |
| newArgs[newArgs.length-1] = vargs; |
| return newArgs; |
| } else if (argumentArray.length==paramTypes.length) { |
| // the number of arguments is correct, but if the last argument |
| // is no array we have to wrap it in a array. if the last argument |
| // is null, then we don't have to do anything |
| Object lastArgument = argumentArray[argumentArray.length-1]; |
| if (lastArgument!=null && !lastArgument.getClass().isArray()) { |
| // no array so wrap it |
| Object vargs = makeArray(lastArgument,vargsClass,1); |
| System.arraycopy(argumentArray,argumentArray.length-1,vargs,0,1); |
| argumentArray[argumentArray.length-1]=vargs; |
| return argumentArray; |
| } else { |
| // we may have to box the arguemnt! |
| return argumentArray; |
| } |
| } else if (argumentArray.length>paramTypes.length) { |
| // the number of arguments is too big, wrap all exceeding elements |
| // in an array, but keep the old elements that are no vargs |
| Object[] newArgs = new Object[paramTypes.length]; |
| // copy arguments that are not a varg |
| System.arraycopy(argumentArray,0,newArgs,0,paramTypes.length-1); |
| // create a new array for the vargs and copy them |
| int numberOfVargs = argumentArray.length-paramTypes.length; |
| Object vargs = makeCommonArray(argumentArray,paramTypes.length-1,vargsClass); |
| newArgs[newArgs.length-1] = vargs; |
| return newArgs; |
| } else { |
| throw new GroovyBugError("trying to call a vargs method without enough arguments"); |
| } |
| } |
| |
| private 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); |
| } |
| |
| public static Object doMethodInvoke(Object object, MetaMethod method, Object[] argumentArray) { |
| Class[] paramTypes = method.getParameterTypes(); |
| argumentArray = coerceArgumentsToClasses(argumentArray,paramTypes); |
| try { |
| return method.invoke(object, argumentArray); |
| } catch (IllegalArgumentException e) { |
| //TODO: test if this is ok with new MOP, should be changed! |
| // we don't want the exception being unwrapped if it is a IllegalArgumentException |
| // but in the case it is for example a IllegalThreadStateException, we want the unwrapping |
| // from the runtime |
| //Note: the reason we want unwrapping sometimes and sometimes not is that the method |
| // invokation tries to invoke the method with and then reacts with type transformation |
| // if the invokation failed here. This is ok for IllegalArgumentException, but it is |
| // possible that a Reflector will be used to execute the call and then an Exception from inside |
| // the method is not wrapped in a InvocationTargetException and we will end here. |
| boolean setReason = e.getClass() != IllegalArgumentException.class; |
| throw createExceptionText("failed to invoke method: ", method, object, argumentArray, e, setReason); |
| } catch (RuntimeException e) { |
| throw e; |
| } catch (Exception e) { |
| throw createExceptionText("failed to invoke method: ", method, object, argumentArray, e, true); |
| } |
| } |
| |
| 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. |
| */ |
| public static Closure getMethodPointer(Object object, String methodName) { |
| return new MethodClosure(object, methodName); |
| } |
| |
| public static Class[] getParameterTypes(Object methodOrConstructor) { |
| if (methodOrConstructor instanceof MetaMethod) { |
| MetaMethod method = (MetaMethod) methodOrConstructor; |
| return method.getParameterTypes(); |
| } |
| if (methodOrConstructor instanceof Method) { |
| Method method = (Method) methodOrConstructor; |
| return method.getParameterTypes(); |
| } |
| if (methodOrConstructor instanceof Constructor) { |
| Constructor constructor = (Constructor) methodOrConstructor; |
| return constructor.getParameterTypes(); |
| } |
| throw new IllegalArgumentException("Must be a Method or Constructor"); |
| } |
| |
| public static boolean isAssignableFrom(Class classToTransformTo, Class classToTransformFrom) { |
| if (classToTransformFrom==null) return true; |
| classToTransformTo = autoboxType(classToTransformTo); |
| classToTransformFrom = autoboxType(classToTransformFrom); |
| |
| if (classToTransformTo == classToTransformFrom) { |
| return true; |
| } |
| // note: there is not coercion for boolean and char. Range matters, precision doesn't |
| else if (classToTransformTo == Integer.class) { |
| if ( classToTransformFrom == Integer.class |
| || classToTransformFrom == Short.class |
| || classToTransformFrom == Byte.class |
| || classToTransformFrom == BigInteger.class) |
| return true; |
| } |
| else if (classToTransformTo == Double.class) { |
| if ( classToTransformFrom == Double.class |
| || 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 == BigDecimal.class |
| || classToTransformFrom == BigInteger.class) |
| return true; |
| } |
| else if (classToTransformTo == BigInteger.class) { |
| if ( classToTransformFrom == Integer.class |
| || classToTransformFrom == Long.class |
| || classToTransformFrom == Short.class |
| || classToTransformFrom == Byte.class |
| || classToTransformFrom == BigInteger.class) |
| return true; |
| } |
| else if (classToTransformTo == Long.class) { |
| if ( classToTransformFrom == Long.class |
| || classToTransformFrom == Integer.class |
| || classToTransformFrom == Short.class |
| || classToTransformFrom == Byte.class) |
| return true; |
| } |
| else if (classToTransformTo == Float.class) { |
| if ( classToTransformFrom == Float.class |
| || classToTransformFrom == Integer.class |
| || classToTransformFrom == Long.class |
| || classToTransformFrom == Short.class |
| || classToTransformFrom == Byte.class) |
| return true; |
| } |
| else if (classToTransformTo == Short.class) { |
| if ( classToTransformFrom == Short.class |
| || classToTransformFrom == Byte.class) |
| return true; |
| } |
| else if (classToTransformTo==String.class) { |
| if ( classToTransformFrom == String.class || |
| GString.class.isAssignableFrom(classToTransformFrom)) { |
| return true; |
| } |
| } |
| |
| return classToTransformTo.isAssignableFrom(classToTransformFrom); |
| } |
| |
| public static boolean isGenericSetMethod(MetaMethod method) { |
| return (method.getName().equals("set")) |
| && method.getParameterTypes().length == 2; |
| } |
| |
| protected static boolean isSuperclass(Class claszz, Class superclass) { |
| while (claszz!=null) { |
| if (claszz==superclass) return true; |
| claszz = claszz.getSuperclass(); |
| } |
| return false; |
| } |
| |
| public static boolean isValidMethod(Class[] paramTypes, Class[] arguments, boolean includeCoerce) { |
| if (arguments == null) { |
| return true; |
| } |
| int size = arguments.length; |
| |
| if ( (size>=paramTypes.length || size==paramTypes.length-1) |
| && paramTypes.length>0 |
| && paramTypes[paramTypes.length-1].isArray()) |
| { |
| // first check normal number of parameters |
| for (int i = 0; i < paramTypes.length-1; i++) { |
| if (isAssignableFrom(paramTypes[i], arguments[i])) continue; |
| return false; |
| } |
| // check varged |
| Class clazz = paramTypes[paramTypes.length-1].getComponentType(); |
| for (int i=paramTypes.length; i<size; i++) { |
| if (isAssignableFrom(clazz, arguments[i])) continue; |
| return false; |
| } |
| return true; |
| } else if (paramTypes.length == size) { |
| // lets check the parameter types match |
| for (int i = 0; i < size; i++) { |
| if (isAssignableFrom(paramTypes[i], arguments[i])) continue; |
| return false; |
| } |
| return true; |
| } else if (paramTypes.length == 1 && size == 0) { |
| return true; |
| } |
| return false; |
| |
| } |
| |
| public static boolean isValidMethod(Object method, Class[] arguments, boolean includeCoerce) { |
| Class[] paramTypes = getParameterTypes(method); |
| return isValidMethod(paramTypes, arguments, includeCoerce); |
| } |
| |
| public static boolean isVargsMethod(Class[] paramTypes, Object[] arguments) { |
| if (paramTypes.length==0) return false; |
| if (!paramTypes[paramTypes.length-1].isArray()) return false; |
| // -1 because the varg part is optional |
| if (paramTypes.length-1==arguments.length) return true; |
| if (paramTypes.length-1>arguments.length) return false; |
| if (arguments.length>paramTypes.length) return true; |
| |
| // only case left is arguments.length==paramTypes.length |
| Object last = arguments[arguments.length-1]; |
| if (last==null) return true; |
| Class clazz = last.getClass(); |
| if (clazz.equals(paramTypes[paramTypes.length-1])) 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; |
| StringBuffer msg = new StringBuffer(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; |
| } |
| |
| 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; |
| } |
| |
| 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; |
| } |
| } |