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 }