View Javadoc

    1   /*
    2    * Copyright 2005 John G. Wilson
    3    *
    4    * Licensed under the Apache License, Version 2.0 (the "License");
    5    * you may not use this file except in compliance with the License.
    6    * You may obtain a copy of the License at
    7    *
    8    *     http://www.apache.org/licenses/LICENSE-2.0
    9    *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   *
    16   */
    17
    18  package org.codehaus.groovy.runtime;
    19
    20  import groovy.lang.Closure;
    21  import groovy.lang.GString;
    22  import groovy.lang.GroovyRuntimeException;
    23  import groovy.lang.MetaMethod;
    24
    25  import java.lang.reflect.Array;
    26  import java.lang.reflect.Constructor;
    27  import java.lang.reflect.InvocationHandler;
    28  import java.lang.reflect.InvocationTargetException;
    29  import java.lang.reflect.Method;
    30  import java.lang.reflect.Modifier;
    31  import java.lang.reflect.Proxy;
    32  import java.math.BigDecimal;
    33  import java.math.BigInteger;
    34  import java.util.Iterator;
    35  import java.util.List;
    36  import java.util.logging.Level;
    37  import java.util.logging.Logger;
    38
    39  import org.codehaus.groovy.GroovyBugError;
    40
    41  /***
    42   * @author John Wilson
    43   * @author Jochen Theodorou
    44   */
    45  public class MetaClassHelper {
    46
    47      public static final Object[] EMPTY_ARRAY = {};
    48      public static Class[] EMPTY_TYPE_ARRAY = {};
    49      protected static final Object[] ARRAY_WITH_NULL = { null };
    50      protected static final Logger log = Logger.getLogger(MetaClassHelper.class.getName());
    51      private static final int MAX_ARG_LEN = 12;
    52
    53      public static boolean accessibleToConstructor(final Class at, final Constructor constructor) {
    54          boolean accessible = false;
    55          if (Modifier.isPublic(constructor.getModifiers())) {
    56              accessible = true;
    57          }
    58          else if (Modifier.isPrivate(constructor.getModifiers())) {
    59              accessible = at.getName().equals(constructor.getName());
    60          }
    61          else if ( Modifier.isProtected(constructor.getModifiers()) ) {
    62              if ( at.getPackage() == null && constructor.getDeclaringClass().getPackage() == null ) {
    63                  accessible = true;
    64              }
    65              else if ( at.getPackage() == null && constructor.getDeclaringClass().getPackage() != null ) {
    66                  accessible = false;
    67              }
    68              else if ( at.getPackage() != null && constructor.getDeclaringClass().getPackage() == null ) {
    69                  accessible = false;
    70              }
    71              else if ( at.getPackage().equals(constructor.getDeclaringClass().getPackage()) ) {
    72                  accessible = true;
    73              }
    74              else {
    75                  boolean flag = false;
    76                  Class clazz = at;
    77                  while ( !flag && clazz != null ) {
    78                      if (clazz.equals(constructor.getDeclaringClass()) ) {
    79                          flag = true;
    80                          break;
    81                      }
    82                      if (clazz.equals(Object.class) ) {
    83                          break;
    84                      }
    85                      clazz = clazz.getSuperclass();
    86                  }
    87                  accessible = flag;
    88              }
    89          }
    90          else {
    91              if ( at.getPackage() == null && constructor.getDeclaringClass().getPackage() == null ) {
    92                  accessible = true;
    93              }
    94              else if ( at.getPackage() == null && constructor.getDeclaringClass().getPackage() != null ) {
    95                  accessible = false;
    96              }
    97              else if ( at.getPackage() != null && constructor.getDeclaringClass().getPackage() == null ) {
    98                  accessible = false;
    99              }
    100             else if ( at.getPackage().equals(constructor.getDeclaringClass().getPackage()) ) {
    101                 accessible = true;
    102             }
    103         }
    104         return accessible;
    105     }
    106
    107     public static Object[] asWrapperArray(Object parameters, Class componentType) {
    108         Object[] ret=null;
    109         if (componentType == boolean.class) {
    110             boolean[] array = (boolean[]) parameters;
    111             ret = new Object[array.length];
    112             for (int i=0; i<array.length; i++) {
    113                 ret[i] = new Boolean(array[i]);
    114             }
    115         } else if (componentType == char.class) {
    116             char[] array = (char[]) parameters;
    117             ret = new Object[array.length];
    118             for (int i=0; i<array.length; i++) {
    119                 ret[i] = new Character(array[i]);
    120             }
    121         } else if (componentType == byte.class) {
    122             byte[] array = (byte[]) parameters;
    123             ret = new Object[array.length];
    124             for (int i=0; i<array.length; i++) {
    125                 ret[i] = new Byte(array[i]);
    126             }
    127         } else if (componentType == int.class) {
    128             int[] array = (int[]) parameters;
    129             ret = new Object[array.length];
    130             for (int i=0; i<array.length; i++) {
    131                 ret[i] = new Integer(array[i]);
    132             }
    133         } else if (componentType == short.class) {
    134             short[] array = (short[]) parameters;
    135             ret = new Object[array.length];
    136             for (int i=0; i<array.length; i++) {
    137                 ret[i] = new Short(array[i]);
    138             }
    139         } else if (componentType == long.class) {
    140             long[] array = (long[]) parameters;
    141             ret = new Object[array.length];
    142             for (int i=0; i<array.length; i++) {
    143                 ret[i] = new Long(array[i]);
    144             }
    145         } else if (componentType == double.class) {
    146             double[] array = (double[]) parameters;
    147             ret = new Object[array.length];
    148             for (int i=0; i<array.length; i++) {
    149                 ret[i] = new Double(array[i]);
    150             }
    151         } else if (componentType == float.class) {
    152             float[] array = (float[]) parameters;
    153             ret = new Object[array.length];
    154             for (int i=0; i<array.length; i++) {
    155                 ret[i] = new Float(array[i]);
    156             }
    157         }
    158
    159         return ret;
    160     }
    161
    162
    163     /***
    164      * @param list
    165      * @param parameterType
    166      * @return
    167      */
    168     public static Object asPrimitiveArray(List list, Class parameterType) {
    169         Class arrayType = parameterType.getComponentType();
    170         Object objArray = Array.newInstance(arrayType, list.size());
    171         for (int i = 0; i < list.size(); i++) {
    172             Object obj = list.get(i);
    173             if (arrayType.isPrimitive()) {
    174                 if (obj instanceof Integer) {
    175                     Array.setInt(objArray, i, ((Integer) obj).intValue());
    176                 }
    177                 else if (obj instanceof Double) {
    178                     Array.setDouble(objArray, i, ((Double) obj).doubleValue());
    179                 }
    180                 else if (obj instanceof Boolean) {
    181                     Array.setBoolean(objArray, i, ((Boolean) obj).booleanValue());
    182                 }
    183                 else if (obj instanceof Long) {
    184                     Array.setLong(objArray, i, ((Long) obj).longValue());
    185                 }
    186                 else if (obj instanceof Float) {
    187                     Array.setFloat(objArray, i, ((Float) obj).floatValue());
    188                 }
    189                 else if (obj instanceof Character) {
    190                     Array.setChar(objArray, i, ((Character) obj).charValue());
    191                 }
    192                 else if (obj instanceof Byte) {
    193                     Array.setByte(objArray, i, ((Byte) obj).byteValue());
    194                 }
    195                 else if (obj instanceof Short) {
    196                     Array.setShort(objArray, i, ((Short) obj).shortValue());
    197                 }
    198             }
    199             else {
    200                 Array.set(objArray, i, obj);
    201             }
    202         }
    203         return objArray;
    204     }
    205
    206     protected static Class autoboxType(Class type) {
    207         if (type.isPrimitive()) {
    208             if (type == int.class) {
    209                 return Integer.class;
    210             }
    211             else if (type == double.class) {
    212                 return Double.class;
    213             }
    214             else if (type == long.class) {
    215                 return Long.class;
    216             }
    217             else if (type == boolean.class) {
    218                 return Boolean.class;
    219             }
    220             else if (type == float.class) {
    221                 return Float.class;
    222             }
    223             else if (type == char.class) {
    224                 return Character.class;
    225             }
    226             else if (type == byte.class) {
    227                 return Byte.class;
    228             }
    229             else if (type == short.class) {
    230                 return Short.class;
    231             }
    232         }
    233         return type;
    234     }
    235
    236     public static int calculateParameterDistance(Class[] arguments, Class[] parameters) {
    237         int dist=0;
    238         for (int i=0; i<arguments.length; i++) {
    239             if (parameters[i]==arguments[i]) continue;
    240
    241             if (parameters[i].isInterface()) {
    242                 dist+=3;
    243                 continue;
    244             }
    245
    246             if (arguments[i]!=null) {
    247                 if (autoboxType(parameters[i]) == autoboxType(arguments[i])){
    248                     // type is not equal, but boxed types are. Increase distance
    249                     // by 1 to reflect the change in type
    250                     dist +=1;
    251                     continue;
    252                 }
    253                 if (arguments[i].isPrimitive() || parameters[i].isPrimitive()) {
    254                     // type is not equal, increase distance by 2 to reflect
    255                     // the change in type
    256                     dist+=2;
    257                     continue;
    258                 }
    259
    260                 // add one to dist to be sure interfaces are prefered
    261                 dist++;
    262                 Class clazz = arguments[i];
    263                 while (clazz!=null) {
    264                     if (clazz==parameters[i]) break;
    265                     if (clazz==GString.class && parameters[i]==String.class) {
    266                         dist+=2;
    267                         break;
    268                     }
    269                     clazz = clazz.getSuperclass();
    270                     dist+=3;
    271                 }
    272             } else {
    273                 // choose the distance to Object if a parameter is null
    274                 // this will mean that Object is prefered over a more
    275                 // specific type
    276                 // remove one to dist to be sure Object is prefered
    277                 dist--;
    278                 Class clazz = parameters[i];
    279                 if (clazz.isPrimitive()) {
    280                     dist+=2;
    281                 } else {
    282                     while (clazz!=Object.class) {
    283                         clazz = clazz.getSuperclass();
    284                         dist+=2;
    285                     }
    286                 }
    287             }
    288         }
    289         return dist;
    290     }
    291
    292     public static String capitalize(String property) {
    293         return property.substring(0, 1).toUpperCase() + property.substring(1, property.length());
    294     }
    295
    296     /***
    297      * @return the method with 1 parameter which takes the most general type of
    298      *         object (e.g. Object)
    299      */
    300     public static Object chooseEmptyMethodParams(List methods) {
    301         for (Iterator iter = methods.iterator(); iter.hasNext();) {
    302             Object method = iter.next();
    303             Class[] paramTypes = getParameterTypes(method);
    304             int paramLength = paramTypes.length;
    305             if (paramLength == 0) {
    306                 return method;
    307             }
    308         }
    309         return null;
    310     }
    311
    312     /***
    313      * @return the method with 1 parameter which takes the most general type of
    314      *         object (e.g. Object) ignoring primitve types
    315      */
    316     public static Object chooseMostGeneralMethodWith1NullParam(List methods) {
    317         // lets look for methods with 1 argument which matches the type of the
    318         // arguments
    319         Class closestClass = null;
    320         Object answer = null;
    321
    322         for (Iterator iter = methods.iterator(); iter.hasNext();) {
    323             Object method = iter.next();
    324             Class[] paramTypes = getParameterTypes(method);
    325             int paramLength = paramTypes.length;
    326             if (paramLength == 1) {
    327                 Class theType = paramTypes[0];
    328                 if (theType.isPrimitive()) continue;
    329                 if (closestClass == null || isAssignableFrom(theType, closestClass)) {
    330                     closestClass = theType;
    331                     answer = method;
    332                 }
    333             }
    334         }
    335         return answer;
    336     }
    337
    338     /***
    339      * Coerces a GString instance into String if needed
    340      *
    341      * @return the coerced argument
    342      */
    343     protected static Object coerceGString(Object argument, Class clazz) {
    344         if (clazz!=String.class) return argument;
    345         if (!(argument instanceof GString)) return argument;
    346         return argument.toString();
    347     }
    348
    349     protected static Object coerceNumber(Object argument, Class param) {
    350         if ((Number.class.isAssignableFrom(param) || param.isPrimitive()) && argument instanceof Number) { // Number types
    351             Object oldArgument = argument;
    352             boolean wasDouble = false;
    353             boolean wasFloat = false;
    354             if (param == Byte.class || param == Byte.TYPE ) {
    355                 argument = new Byte(((Number)argument).byteValue());
    356             } else if (param == Double.class || param == Double.TYPE) {
    357                 wasDouble = true;
    358                 argument = new Double(((Number)argument).doubleValue());
    359             } else if (param == Float.class || param == Float.TYPE) {
    360                 wasFloat = true;
    361                 argument = new Float(((Number)argument).floatValue());
    362             } else if (param == Integer.class || param == Integer.TYPE) {
    363                 argument = new Integer(((Number)argument).intValue());
    364             } else if (param == Long.class || param == Long.TYPE) {
    365                 argument = new Long(((Number)argument).longValue());
    366             } else if (param == Short.class || param == Short.TYPE) {
    367                 argument = new Short(((Number)argument).shortValue());
    368             } else if (param == BigDecimal.class ) {
    369                 argument = new BigDecimal(String.valueOf((Number)argument));
    370             } else if (param == BigInteger.class) {
    371                 argument = new BigInteger(String.valueOf((Number)argument));
    372             }
    373
    374             if (oldArgument instanceof BigDecimal) {
    375                 BigDecimal oldbd = (BigDecimal) oldArgument;
    376                 boolean throwException = false;
    377                 if (wasDouble) {
    378                     Double d = (Double) argument;
    379                     if (d.isInfinite()) throwException = true;
    380                 } else if (wasFloat) {
    381                     Float f = (Float) argument;
    382                     if (f.isInfinite()) throwException = true;
    383                 } else {
    384                     BigDecimal newbd = new BigDecimal(String.valueOf((Number)argument));
    385                     throwException = !oldArgument.equals(newbd);
    386                 }
    387
    388                 if (throwException) throw new IllegalArgumentException(param+" out of range while converting from BigDecimal");
    389             }
    390
    391         }
    392         return argument;
    393     }
    394
    395      protected static Object coerceArray(Object argument, Class param) {
    396          if (!param.isArray()) return argument;
    397          Class argumentClass = argument.getClass();
    398          if (!argumentClass.isArray()) return argument;
    399
    400          Class paramComponent = param.getComponentType();
    401          if (paramComponent.isPrimitive()) {
    402              if (paramComponent == boolean.class && argumentClass==Boolean[].class) {
    403                  argument = InvokerHelper.convertToBooleanArray(argument);
    404              } else if (paramComponent == byte.class && argumentClass==Byte[].class) {
    405                  argument = InvokerHelper.convertToByteArray(argument);
    406              } else if (paramComponent == char.class && argumentClass==Character[].class) {
    407                  argument = InvokerHelper.convertToCharArray(argument);
    408              } else if (paramComponent == short.class && argumentClass==Short[].class) {
    409                  argument = InvokerHelper.convertToShortArray(argument);
    410              } else if (paramComponent == int.class && argumentClass==Integer[].class) {
    411                  argument = InvokerHelper.convertToIntArray(argument);
    412              } else if (paramComponent == long.class &&
    413                         (argumentClass == Long[].class || argumentClass  == Integer[].class))
    414              {
    415                  argument = InvokerHelper.convertToLongArray(argument);
    416              } else if (paramComponent == float.class &&
    417                         (argumentClass == Float[].class || argumentClass == Integer[].class))
    418              {
    419                  argument = InvokerHelper.convertToFloatArray(argument);
    420              } else if (paramComponent == double.class &&
    421                         (argumentClass == Double[].class || argumentClass==Float[].class
    422                          || BigDecimal.class.isAssignableFrom(argumentClass)))
    423              {
    424                  argument = InvokerHelper.convertToDoubleArray(argument);
    425              }
    426          } else if (paramComponent==String.class && argument instanceof GString[]) {
    427              GString[] strings = (GString[]) argument;
    428              String[] ret = new String[strings.length];
    429              for (int i=0; i<strings.length; i++) {
    430                  ret[i] = strings[i].toString();
    431              }
    432              argument = ret;
    433          }
    434          return argument;
    435     }
    436
    437     /***
    438      * @return true if a method of the same matching prototype was found in the
    439      *         list
    440      */
    441     public static boolean containsMatchingMethod(List list, MetaMethod method) {
    442         for (Iterator iter = list.iterator(); iter.hasNext();) {
    443             MetaMethod aMethod = (MetaMethod) iter.next();
    444             Class[] params1 = aMethod.getParameterTypes();
    445             Class[] params2 = method.getParameterTypes();
    446             if (params1.length == params2.length) {
    447                 boolean matches = true;
    448                 for (int i = 0; i < params1.length; i++) {
    449                     if (params1[i] != params2[i]) {
    450                         matches = false;
    451                         break;
    452                     }
    453                 }
    454                 if (matches) {
    455                     return true;
    456                 }
    457             }
    458         }
    459         return false;
    460     }
    461
    462     /***
    463      * param instance array to the type array
    464      * @param args
    465      * @return
    466      */
    467     public static Class[] convertToTypeArray(Object[] args) {
    468         if (args == null)
    469             return null;
    470         int s = args.length;
    471         Class[] ans = new Class[s];
    472         for (int i = 0; i < s; i++) {
    473             Object o = args[i];
    474             if (o != null) {
    475                 ans[i] = o.getClass();
    476             } else {
    477                 ans[i] = null;
    478             }
    479         }
    480         return ans;
    481     }
    482
    483     /***
    484      * @param listenerType
    485      *            the interface of the listener to proxy
    486      * @param listenerMethodName
    487      *            the name of the method in the listener API to call the
    488      *            closure on
    489      * @param closure
    490      *            the closure to invoke on the listenerMethodName method
    491      *            invocation
    492      * @return a dynamic proxy which calls the given closure on the given
    493      *         method name
    494      */
    495     public static Object createListenerProxy(Class listenerType, final String listenerMethodName, final Closure closure) {
    496         InvocationHandler handler = new ClosureListener(listenerMethodName, closure);
    497         return Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[] { listenerType }, handler);
    498     }
    499
    500     public static Object doConstructorInvoke(Constructor constructor, Object[] argumentArray) {
    501         if (log.isLoggable(Level.FINER)){
    502             logMethodCall(constructor.getDeclaringClass(), constructor.getName(), argumentArray);
    503         }
    504         argumentArray = coerceArgumentsToClasses(argumentArray,constructor.getParameterTypes());
    505         try {
    506             return constructor.newInstance(argumentArray);
    507         } catch (InvocationTargetException e) {
    508             throw new InvokerInvocationException(e);
    509         } catch (IllegalArgumentException e) {
    510             throw createExceptionText("failed to invoke constructor: ", constructor, argumentArray, e, false);
    511         } catch (IllegalAccessException e) {
    512             throw createExceptionText("could not access constructor: ", constructor, argumentArray, e, false);
    513         } catch (Exception e) {
    514             throw createExceptionText("failed to invoke constructor: ", constructor, argumentArray, e, true);
    515         }
    516     }
    517
    518     private static GroovyRuntimeException createExceptionText(String init, Constructor constructor, Object[] argumentArray, Throwable e, boolean setReason) {
    519         throw new GroovyRuntimeException(
    520                 init
    521                 + constructor
    522                 + " with arguments: "
    523                 + InvokerHelper.toString(argumentArray)
    524                 + " reason: "
    525                 + e,
    526                 setReason?e:null);
    527     }
    528
    529     public static Object[] coerceArgumentsToClasses(Object[] argumentArray, Class[] paramTypes) {
    530         // correct argumentArray's length
    531         if (argumentArray == null) {
    532             argumentArray = EMPTY_ARRAY;
    533         } else if (paramTypes.length == 1 && argumentArray.length == 0) {
    534             if (isVargsMethod(paramTypes,argumentArray))
    535                 argumentArray = new Object[]{Array.newInstance(paramTypes[0].getComponentType(),0)};
    536             else
    537                 argumentArray = ARRAY_WITH_NULL;
    538         } else if (isVargsMethod(paramTypes,argumentArray)) {
    539             argumentArray = fitToVargs(argumentArray, paramTypes);
    540         }
    541
    542         //correct Type
    543         for (int i=0; i<argumentArray.length; i++) {
    544             Object argument = argumentArray[i];
    545             if (argument==null) continue;
    546             Class parameterType = paramTypes[i];
    547             if (parameterType.isInstance(argument)) continue;
    548
    549             argument = coerceGString(argument,parameterType);
    550             argument = coerceNumber(argument,parameterType);
    551             argument = coerceArray(argument,parameterType);
    552             argumentArray[i] = argument;
    553         }
    554         return argumentArray;
    555     }
    556
    557     private static Object makeCommonArray(Object[] arguments, int offset, Class fallback) {
    558     	// arguments.leght>0 && !=null
    559     	Class baseClass = null;
    560     	for (int i = offset; i < arguments.length; i++) {
    561 			if (arguments[i]==null) continue;
    562 			Class argClass = arguments[i].getClass();
    563 			if (baseClass==null) {
    564 				baseClass = argClass;
    565 			} else {
    566 				for (;baseClass!=Object.class; baseClass=baseClass.getSuperclass()){
    567 					if (baseClass.isAssignableFrom(argClass)) break;
    568 				}
    569 			}
    570 		}
    571         if (baseClass==null) {
    572             // all arguments were null
    573             baseClass = fallback;
    574         }
    575     	Object result = makeArray(null,baseClass,arguments.length-offset);
    576     	System.arraycopy(arguments,offset,result,0,arguments.length-offset);
    577     	return result;
    578     }
    579
    580     private static Object makeArray(Object obj, Class secondary, int length) {
    581     	Class baseClass = secondary;
    582     	if (obj!=null) {
    583     		baseClass = obj.getClass();
    584     	}
    585     	/*if (GString.class.isAssignableFrom(baseClass)) {
    586     		baseClass = GString.class;
    587     	}*/
    588     	return Array.newInstance(baseClass,length);
    589     }
    590
    591     /***
    592      * this method is called when the number of arguments to a method is greater than 1
    593      * and if the method is a vargs method. This method will then transform the given
    594      * arguments to make the method callable
    595      *
    596      * @param argumentArray the arguments used to call the method
    597      * @param paramTypes the types of the paramters the method takes
    598      */
    599     private static Object[] fitToVargs(Object[] argumentArray, Class[] paramTypes) {
    600     	Class vargsClass = autoboxType(paramTypes[paramTypes.length-1].getComponentType());
    601
    602         if (argumentArray.length == paramTypes.length-1) {
    603             // the vargs argument is missing, so fill it with an empty array
    604             Object[] newArgs = new Object[paramTypes.length];
    605             System.arraycopy(argumentArray,0,newArgs,0,argumentArray.length);
    606             Object vargs = makeArray(null,vargsClass,0);
    607             newArgs[newArgs.length-1] = vargs;
    608             return newArgs;
    609         } else if (argumentArray.length==paramTypes.length) {
    610             // the number of arguments is correct, but if the last argument
    611             // is no array we have to wrap it in a array. if the last argument
    612             // is null, then we don't have to do anything
    613             Object lastArgument = argumentArray[argumentArray.length-1];
    614             if (lastArgument!=null && !lastArgument.getClass().isArray()) {
    615                 // no array so wrap it
    616                 Object vargs = makeArray(lastArgument,vargsClass,1);
    617                 System.arraycopy(argumentArray,argumentArray.length-1,vargs,0,1);
    618                 argumentArray[argumentArray.length-1]=vargs;
    619                 return argumentArray;
    620             } else {
    621                 // we may have to box the arguemnt!
    622                 return argumentArray;
    623             }
    624         } else if (argumentArray.length>paramTypes.length) {
    625             // the number of arguments is too big, wrap all exceeding elements
    626             // in an array, but keep the old elements that are no vargs
    627             Object[] newArgs = new Object[paramTypes.length];
    628             // copy arguments that are not a varg
    629             System.arraycopy(argumentArray,0,newArgs,0,paramTypes.length-1);
    630             // create a new array for the vargs and copy them
    631             int numberOfVargs = argumentArray.length-paramTypes.length;
    632             Object vargs = makeCommonArray(argumentArray,paramTypes.length-1,vargsClass);
    633             newArgs[newArgs.length-1] = vargs;
    634             return newArgs;
    635         } else {
    636             throw new GroovyBugError("trying to call a vargs method without enough arguments");
    637         }
    638     }
    639
    640     private static GroovyRuntimeException createExceptionText(String init, MetaMethod method, Object object, Object[] args, Throwable reason, boolean setReason) {
    641         return new GroovyRuntimeException(
    642                 init
    643                 + method
    644                 + " on: "
    645                 + object
    646                 + " with arguments: "
    647                 + InvokerHelper.toString(args)
    648                 + " reason: "
    649                 + reason,
    650                 setReason?reason:null);
    651     }
    652
    653     public static Object doMethodInvoke(Object object, MetaMethod method, Object[] argumentArray) {
    654         Class[] paramTypes = method.getParameterTypes();
    655         argumentArray = coerceArgumentsToClasses(argumentArray,paramTypes);
    656         try {
    657             return method.invoke(object, argumentArray);
    658         } catch (InvocationTargetException e) {
    659             throw new InvokerInvocationException(e);
    660         } catch (IllegalAccessException e) {
    661             throw createExceptionText("could not access method: ", method, object, argumentArray, e, true);
    662         } catch (IllegalArgumentException e) {
    663             //TODO: test if this is ok with new MOP, should be changed!
    664             // we don't want the exception being unwrapped if it is a IllegalArgumentException
    665             // but in the case it is for example a IllegalThreadStateException, we want the unwrapping
    666             // from the runtime
    667             //Note: the reason we want unwrapping sometimes and sometimes not is that the method
    668             // invokation tries to invoke the method with and then reacts with type transformation
    669             // if the invokation failed here. This is ok for IllegalArgumentException, but it is
    670             // possible that a Reflector will be used to execute the call and then an Exception from inside
    671             // the method is not wrapped in a InvocationTargetException and we will end here.
    672             boolean setReason = e.getClass() != IllegalArgumentException.class;
    673             throw createExceptionText("failed to invoke method: ", method, object, argumentArray, e, setReason);
    674         } catch (RuntimeException e) {
    675             throw e;
    676         } catch (Exception e) {
    677             throw createExceptionText("failed to invoke method: ", method, object, argumentArray, e, true);
    678         }
    679     }
    680
    681     protected static String getClassName(Object object) {
    682         return (object instanceof Class) ? ((Class)object).getName() : object.getClass().getName();
    683     }
    684
    685     /***
    686      * Returns a callable object for the given method name on the object.
    687      * The object acts like a Closure in that it can be called, like a closure
    688      * and passed around - though really its a method pointer, not a closure per se.
    689      */
    690     public static Closure getMethodPointer(Object object, String methodName) {
    691         return new MethodClosure(object, methodName);
    692     }
    693
    694     public static Class[] getParameterTypes(Object methodOrConstructor) {
    695         if (methodOrConstructor instanceof MetaMethod) {
    696             MetaMethod method = (MetaMethod) methodOrConstructor;
    697             return method.getParameterTypes();
    698         }
    699         if (methodOrConstructor instanceof Method) {
    700             Method method = (Method) methodOrConstructor;
    701             return method.getParameterTypes();
    702         }
    703         if (methodOrConstructor instanceof Constructor) {
    704             Constructor constructor = (Constructor) methodOrConstructor;
    705             return constructor.getParameterTypes();
    706         }
    707         throw new IllegalArgumentException("Must be a Method or Constructor");
    708     }
    709
    710     protected static boolean isAssignableFrom(Class classToTransformTo, Class classToTransformFrom) {
    711         if (classToTransformFrom==null) return true;
    712         classToTransformTo = autoboxType(classToTransformTo);
    713         classToTransformFrom = autoboxType(classToTransformFrom);
    714
    715         if (classToTransformTo == classToTransformFrom) {
    716         	return true;
    717         }
    718         // note: there is not coercion for boolean and char. Range matters, precision doesn't
    719         else if (classToTransformTo == Integer.class) {
    720         	if (	classToTransformFrom == Integer.class
    721         			|| classToTransformFrom == Short.class
    722         			|| classToTransformFrom == Byte.class
    723                     || classToTransformFrom == BigInteger.class)
    724         	return true;
    725         }
    726         else if (classToTransformTo == Double.class) {
    727         	if (	classToTransformFrom == Double.class
    728         			|| classToTransformFrom == Integer.class
    729         			|| classToTransformFrom == Long.class
    730         			|| classToTransformFrom == Short.class
    731         			|| classToTransformFrom == Byte.class
    732         			|| classToTransformFrom == Float.class
    733                     || classToTransformFrom == BigDecimal.class
    734                     || classToTransformFrom == BigInteger.class)
    735         	return true;
    736         }
    737         else if (classToTransformTo == BigDecimal.class) {
    738             if (    classToTransformFrom == Double.class
    739                     || classToTransformFrom == Integer.class
    740                     || classToTransformFrom == Long.class
    741                     || classToTransformFrom == Short.class
    742                     || classToTransformFrom == Byte.class
    743                     || classToTransformFrom == Float.class
    744                     || classToTransformFrom == BigDecimal.class
    745                     || classToTransformFrom == BigInteger.class)
    746             return true;
    747         }
    748         else if (classToTransformTo == BigInteger.class) {
    749             if (    classToTransformFrom == Integer.class
    750                     || classToTransformFrom == Long.class
    751                     || classToTransformFrom == Short.class
    752                     || classToTransformFrom == Byte.class
    753                     || classToTransformFrom == BigInteger.class)
    754             return true;
    755         }
    756         else if (classToTransformTo == Long.class) {
    757         	if (	classToTransformFrom == Long.class
    758         			|| classToTransformFrom == Integer.class
    759         			|| classToTransformFrom == Short.class
    760         			|| classToTransformFrom == Byte.class)
    761         	return true;
    762         }
    763         else if (classToTransformTo == Float.class) {
    764         	if (	classToTransformFrom == Float.class
    765         			|| classToTransformFrom == Integer.class
    766         			|| classToTransformFrom == Long.class
    767         			|| classToTransformFrom == Short.class
    768         			|| classToTransformFrom == Byte.class)
    769         	return true;
    770         }
    771         else if (classToTransformTo == Short.class) {
    772         	if (	classToTransformFrom == Short.class
    773         			|| classToTransformFrom == Byte.class)
    774         	return true;
    775         }
    776         else if (classToTransformTo==String.class) {
    777             if (	classToTransformFrom == String.class ||
    778             		GString.class.isAssignableFrom(classToTransformFrom))
    779             return true;
    780         }
    781
    782         boolean answer = classToTransformTo.isAssignableFrom(classToTransformFrom);
    783         return answer;
    784     }
    785
    786     public static boolean isGenericSetMethod(MetaMethod method) {
    787         return (method.getName().equals("set"))
    788         && method.getParameterTypes().length == 2;
    789     }
    790
    791     protected static boolean isSuperclass(Class claszz, Class superclass) {
    792         while (claszz!=null) {
    793             if (claszz==superclass) return true;
    794             claszz = claszz.getSuperclass();
    795         }
    796         return false;
    797     }
    798
    799     public static boolean isValidMethod(Class[] paramTypes, Class[] arguments, boolean includeCoerce) {
    800         if (arguments == null) {
    801             return true;
    802         }
    803         int size = arguments.length;
    804
    805         if (   (size>=paramTypes.length || size==paramTypes.length-1)
    806                 && paramTypes.length>0
    807                 && paramTypes[paramTypes.length-1].isArray())
    808         {
    809             // first check normal number of parameters
    810             for (int i = 0; i < paramTypes.length-1; i++) {
    811                 if (isAssignableFrom(paramTypes[i], arguments[i])) continue;
    812                 return false;
    813             }
    814             // check varged
    815             Class clazz = paramTypes[paramTypes.length-1].getComponentType();
    816             for (int i=paramTypes.length; i<size; i++) {
    817                 if (isAssignableFrom(clazz, arguments[i])) continue;
    818                 return false;
    819             }
    820             return true;
    821         } else if (paramTypes.length == size) {
    822             // lets check the parameter types match
    823             for (int i = 0; i < size; i++) {
    824                 if (isAssignableFrom(paramTypes[i], arguments[i])) continue;
    825                 return false;
    826             }
    827             return true;
    828         } else if (paramTypes.length == 1 && size == 0) {
    829             return true;
    830         }
    831         return false;
    832
    833     }
    834
    835     public static boolean isValidMethod(Object method, Class[] arguments, boolean includeCoerce) {
    836         Class[] paramTypes = getParameterTypes(method);
    837         return isValidMethod(paramTypes, arguments, includeCoerce);
    838     }
    839
    840     public static boolean isVargsMethod(Class[] paramTypes, Object[] arguments) {
    841         if (paramTypes.length==0) return false;
    842         if (!paramTypes[paramTypes.length-1].isArray()) return false;
    843         // -1 because the varg part is optional
    844         if (paramTypes.length-1==arguments.length) return true;
    845         if (paramTypes.length-1>arguments.length) return false;
    846         if (arguments.length>paramTypes.length) return true;
    847
    848         // only case left is arguments.length==paramTypes.length
    849         Object last = arguments[arguments.length-1];
    850         if (last==null) return true;
    851         Class clazz = last.getClass();
    852         if (clazz.equals(paramTypes[paramTypes.length-1])) return false;
    853
    854         return true;
    855     }
    856
    857     public static void logMethodCall(Object object, String methodName, Object[] arguments) {
    858         String className = getClassName(object);
    859         String logname = "methodCalls." + className + "." + methodName;
    860         Logger objLog = Logger.getLogger(logname);
    861         if (! objLog.isLoggable(Level.FINER)) return;
    862         StringBuffer msg = new StringBuffer(methodName);
    863         msg.append("(");
    864         if (arguments != null){
    865             for (int i = 0; i < arguments.length;) {
    866                 msg.append(normalizedValue(arguments[i]));
    867                 if (++i < arguments.length) { msg.append(","); }
    868             }
    869         }
    870         msg.append(")");
    871         objLog.logp(Level.FINER, className, msg.toString(), "called from MetaClass.invokeMethod");
    872     }
    873
    874     protected static String normalizedValue(Object argument) {
    875         String value;
    876         try {
    877             value = argument.toString();
    878             if (value.length() > MAX_ARG_LEN){
    879                 value = value.substring(0,MAX_ARG_LEN-2) + "..";
    880             }
    881             if (argument instanceof String){
    882                 value = "\'"+value+"\'";
    883             }
    884         } catch (Exception e) {
    885             value = shortName(argument);
    886         }
    887         return value;
    888     }
    889
    890     public static boolean parametersAreCompatible(Class[] arguments, Class[] parameters) {
    891         if (arguments.length!=parameters.length) return false;
    892         for (int i=0; i<arguments.length; i++) {
    893             if (!isAssignableFrom(parameters[i],arguments[i])) return false;
    894         }
    895         return true;
    896     }
    897
    898     protected static String shortName(Object object) {
    899         if (object == null || object.getClass()==null) return "unknownClass";
    900         String name = getClassName(object);
    901         if (name == null) return "unknownClassName"; // *very* defensive...
    902         int lastDotPos = name.lastIndexOf('.');
    903         if (lastDotPos < 0 || lastDotPos >= name.length()-1) return name;
    904         return name.substring(lastDotPos+1);
    905     }
    906
    907     public static Class[] wrap(Class[] classes) {
    908         Class[] wrappedArguments = new Class[classes.length];
    909         for (int i = 0; i < wrappedArguments.length; i++) {
    910             Class c = classes[i];
    911             if (c==null) continue;
    912             if (c.isPrimitive()) {
    913                 if (c==Integer.TYPE) {
    914                     c=Integer.class;
    915                 } else if (c==Byte.TYPE) {
    916                     c=Byte.class;
    917                 } else if (c==Long.TYPE) {
    918                     c=Long.class;
    919                 } else if (c==Double.TYPE) {
    920                     c=Double.class;
    921                 } else if (c==Float.TYPE) {
    922                     c=Float.class;
    923                 }
    924             } else if (isSuperclass(c,GString.class)) {
    925                 c = String.class;
    926             }
    927             wrappedArguments[i]=c;
    928         }
    929         return wrappedArguments;
    930     }
    931 }