blob: cbd360d1c5cedec4344fe2d9af6d0f582804b8e5 [file] [log] [blame]
/*
* 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;
}
}