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.Arrays;
35  import java.util.Iterator;
36  import java.util.List;
37  import java.util.logging.Level;
38  import java.util.logging.Logger;
39  
40  /***
41   * @author John Wilson
42   *
43   */
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_EMPTY_ARRAY = { EMPTY_ARRAY };
50      protected static final Object[] ARRAY_WITH_NULL = { null };
51      protected static final Logger log = Logger.getLogger(MetaClassHelper.class.getName());
52      private static final int MAX_ARG_LEN = 12;
53      
54      public static boolean accessibleToConstructor(final Class at, final Constructor constructor) {
55          boolean accessible = false;
56          if (Modifier.isPublic(constructor.getModifiers())) {
57              accessible = true;
58          }
59          else if (Modifier.isPrivate(constructor.getModifiers())) {
60              accessible = at.getName().equals(constructor.getName());
61          }
62          else if ( Modifier.isProtected(constructor.getModifiers()) ) {
63              if ( at.getPackage() == null && constructor.getDeclaringClass().getPackage() == null ) {
64                  accessible = true;
65              }
66              else if ( at.getPackage() == null && constructor.getDeclaringClass().getPackage() != null ) {
67                  accessible = false;
68              }
69              else if ( at.getPackage() != null && constructor.getDeclaringClass().getPackage() == null ) {
70                  accessible = false;
71              }
72              else if ( at.getPackage().equals(constructor.getDeclaringClass().getPackage()) ) {
73                  accessible = true;
74              }
75              else {
76                  boolean flag = false;
77                  Class clazz = at;
78                  while ( !flag && clazz != null ) {
79                      if (clazz.equals(constructor.getDeclaringClass()) ) {
80                          flag = true;
81                          break;
82                      }
83                      if (clazz.equals(Object.class) ) {
84                          break;
85                      }
86                      clazz = clazz.getSuperclass();
87                  }
88                  accessible = flag;
89              }
90          }
91          else {
92              if ( at.getPackage() == null && constructor.getDeclaringClass().getPackage() == null ) {
93                  accessible = true;
94              }
95              else if ( at.getPackage() == null && constructor.getDeclaringClass().getPackage() != null ) {
96                  accessible = false;
97              }
98              else if ( at.getPackage() != null && constructor.getDeclaringClass().getPackage() == null ) {
99                  accessible = false;
100             }
101             else if ( at.getPackage().equals(constructor.getDeclaringClass().getPackage()) ) {
102                 accessible = true;
103             }
104         }
105         return accessible;
106     }
107     
108     /***
109      * @param list
110      * @param parameterType
111      * @return
112      */
113     public static Object asPrimitiveArray(List list, Class parameterType) {
114         Class arrayType = parameterType.getComponentType();
115         Object objArray = Array.newInstance(arrayType, list.size());
116         for (int i = 0; i < list.size(); i++) {
117             Object obj = list.get(i);
118             if (arrayType.isPrimitive()) {
119                 if (obj instanceof Integer) {
120                     Array.setInt(objArray, i, ((Integer) obj).intValue());
121                 }
122                 else if (obj instanceof Double) {
123                     Array.setDouble(objArray, i, ((Double) obj).doubleValue());
124                 }
125                 else if (obj instanceof Boolean) {
126                     Array.setBoolean(objArray, i, ((Boolean) obj).booleanValue());
127                 }
128                 else if (obj instanceof Long) {
129                     Array.setLong(objArray, i, ((Long) obj).longValue());
130                 }
131                 else if (obj instanceof Float) {
132                     Array.setFloat(objArray, i, ((Float) obj).floatValue());
133                 }
134                 else if (obj instanceof Character) {
135                     Array.setChar(objArray, i, ((Character) obj).charValue());
136                 }
137                 else if (obj instanceof Byte) {
138                     Array.setByte(objArray, i, ((Byte) obj).byteValue());
139                 }
140                 else if (obj instanceof Short) {
141                     Array.setShort(objArray, i, ((Short) obj).shortValue());
142                 }
143             }
144             else {
145                 Array.set(objArray, i, obj);
146             }
147         }
148         return objArray;
149     }
150     
151     protected static Class autoboxType(Class type) {
152         if (type.isPrimitive()) {
153             if (type == int.class) {
154                 return Integer.class;
155             }
156             else if (type == double.class) {
157                 return Double.class;
158             }
159             else if (type == long.class) {
160                 return Long.class;
161             }
162             else if (type == boolean.class) {
163                 return Boolean.class;
164             }
165             else if (type == float.class) {
166                 return Float.class;
167             }
168             else if (type == char.class) {
169                 return Character.class;
170             }
171             else if (type == byte.class) {
172                 return Byte.class;
173             }
174             else if (type == short.class) {
175                 return Short.class;
176             }
177         }
178         return type;
179     }
180     
181     public static int calculateParameterDistance(Class[] arguments, Class[] parameters) {
182         int dist=0;
183         for (int i=0; i<arguments.length; i++) {
184             if (parameters[i]==arguments[i]) continue;
185             
186             if (parameters[i].isInterface()) {
187                 dist+=2;
188                 continue;
189             }
190             
191             if (arguments[i]!=null) {
192                 if (arguments[i].isPrimitive() || parameters[i].isPrimitive()) {
193                     // type is not equal, increase distance by one to reflect
194                     // the change in type
195                     dist++;
196                     continue;
197                 }
198                 
199                 // add one to dist to be sure interfaces are prefered
200                 dist++;
201                 Class clazz = arguments[i];
202                 while (clazz!=null) {
203                     if (clazz==parameters[i]) break;
204                     if (clazz==GString.class && parameters[i]==String.class) {
205                         dist+=2;
206                         break;
207                     }
208                     clazz = clazz.getSuperclass();
209                     dist+=2;
210                 }
211             } else {
212                 // choose the distance to Object if a parameter is null
213                 // this will mean that Object is prefered over a more
214                 // specific type
215                 // remove one to dist to be sure Object is prefered
216                 dist--;
217                 Class clazz = parameters[i];
218                 while (clazz!=Object.class) {
219                     clazz = clazz.getSuperclass();
220                     dist+=2;
221                 }
222             }
223         }
224         return dist;
225     }
226     
227     public static String capitalize(String property) {
228         return property.substring(0, 1).toUpperCase() + property.substring(1, property.length());
229     }
230     
231     /***
232      * Checks that one of the parameter types is a superset of the other and
233      * that the two lists of types don't conflict. e.g. foo(String, Object) and
234      * foo(Object, String) would conflict if called with foo("a", "b").
235      *
236      * Note that this method is only called with 2 possible signatures. i.e.
237      * possible invalid combinations will already have been filtered out. So if
238      * there were methods foo(String, Object) and foo(Object, String) then one
239      * of these would be already filtered out if foo was called as foo(12, "a")
240      */
241     protected static void checkForInvalidOverloading(String name, Class[] baseTypes, Class[] derivedTypes) {
242         for (int i = 0, size = baseTypes.length; i < size; i++) {
243             Class baseType = baseTypes[i];
244             Class derivedType = derivedTypes[i];
245             if (!isAssignableFrom(derivedType, baseType)) {
246                 throw new GroovyRuntimeException(
247                         "Ambiguous method overloading for method: "
248                         + name
249                         + ". Cannot resolve which method to invoke due to overlapping prototypes between: "
250                         + InvokerHelper.toString(baseTypes)
251                         + " and: "
252                         + InvokerHelper.toString(derivedTypes));
253             }
254         }
255     }
256     
257     /***
258      * @return the method with 1 parameter which takes the most general type of
259      *         object (e.g. Object)
260      */
261     public static Object chooseEmptyMethodParams(List methods) {
262         for (Iterator iter = methods.iterator(); iter.hasNext();) {
263             Object method = iter.next();
264             Class[] paramTypes = getParameterTypes(method);
265             int paramLength = paramTypes.length;
266             if (paramLength == 0) {
267                 return method;
268             }
269         }
270         return null;
271     }
272     
273     /***
274      * @return the method with 1 parameter which takes the most general type of
275      *         object (e.g. Object) ignoring primitve types
276      */
277     public static Object chooseMostGeneralMethodWith1NullParam(List methods) {
278         // lets look for methods with 1 argument which matches the type of the
279         // arguments
280         Class closestClass = null;
281         Object answer = null;
282         
283         for (Iterator iter = methods.iterator(); iter.hasNext();) {
284             Object method = iter.next();
285             Class[] paramTypes = getParameterTypes(method);
286             int paramLength = paramTypes.length;
287             if (paramLength == 1) {
288                 Class theType = paramTypes[0];
289                 if (theType.isPrimitive()) continue;
290                 if (closestClass == null || isAssignableFrom(closestClass, theType)) {
291                     closestClass = theType;
292                     answer = method;
293                 }
294             }
295         }
296         return answer;
297     }
298     
299     /***
300      * Coerces any GString instances into Strings
301      *
302      * @return true if some coercion was done.
303      */
304     public static boolean coerceGStrings(Object[] arguments) {
305         boolean coerced = false;
306         for (int i = 0, size = arguments.length; i < size; i++) {
307             Object argument = arguments[i];
308             if (argument instanceof GString) {
309                 arguments[i] = argument.toString();
310                 coerced = true;
311             }
312         }
313         return coerced;
314     }
315     
316     protected static Object[] coerceNumbers(MetaMethod method, Object[] arguments) {
317         Object[] ans = null;
318         boolean coerced = false; // to indicate that at least one param is coerced
319         
320         Class[] params = method.getParameterTypes();
321         
322         if (params.length != arguments.length) {
323             return null;
324         }
325         
326         ans = new Object[arguments.length];
327         
328         for (int i = 0, size = arguments.length; i < size; i++) {
329             Object argument = arguments[i];
330             Class param = params[i];
331             if ((Number.class.isAssignableFrom(param) || param.isPrimitive()) && argument instanceof Number) { // Number types
332                 if (param == Byte.class || param == Byte.TYPE ) {
333                     ans[i] = new Byte(((Number)argument).byteValue());
334                     coerced = true; continue;
335                 }
336                 if (param == Double.class || param == Double.TYPE) {
337                     ans[i] = new Double(((Number)argument).doubleValue());
338                     coerced = true; continue;
339                 }
340                 if (param == Float.class || param == Float.TYPE) {
341                     ans[i] = new Float(((Number)argument).floatValue());
342                     coerced = true; continue;
343                 }
344                 if (param == Integer.class || param == Integer.TYPE) {
345                     ans[i] = new Integer(((Number)argument).intValue());
346                     coerced = true; continue;
347                 }
348                 if (param == Long.class || param == Long.TYPE) {
349                     ans[i] = new Long(((Number)argument).longValue());
350                     coerced = true; continue;
351                 }
352                 if (param == Short.class || param == Short.TYPE) {
353                     ans[i] = new Short(((Number)argument).shortValue());
354                     coerced = true; continue;
355                 }
356                 if (param == BigDecimal.class ) {
357                     ans[i] = new BigDecimal(((Number)argument).doubleValue());
358                     coerced = true; continue;
359                 }
360                 if (param == BigInteger.class) {
361                     ans[i] = new BigInteger(String.valueOf(((Number)argument).longValue()));
362                     coerced = true; continue;
363                 }
364             }
365             else if (param.isArray() && argument.getClass().isArray()) {
366                 Class paramElem = param.getComponentType();
367                 if (paramElem.isPrimitive()) {
368                     if (paramElem == boolean.class && argument.getClass().getName().equals("[Ljava.lang.Boolean;")) {
369                         ans[i] = InvokerHelper.convertToBooleanArray(argument);
370                         coerced = true;
371                         continue;
372                     }
373                     if (paramElem == byte.class && argument.getClass().getName().equals("[Ljava.lang.Byte;")) {
374                         ans[i] = InvokerHelper.convertToByteArray(argument);
375                         coerced = true;
376                         continue;
377                     }
378                     if (paramElem == char.class && argument.getClass().getName().equals("[Ljava.lang.Character;")) {
379                         ans[i] = InvokerHelper.convertToCharArray(argument);
380                         coerced = true;
381                         continue;
382                     }
383                     if (paramElem == short.class && argument.getClass().getName().equals("[Ljava.lang.Short;")) {
384                         ans[i] = InvokerHelper.convertToShortArray(argument);
385                         coerced = true;
386                         continue;
387                     }
388                     if (paramElem == int.class && argument.getClass().getName().equals("[Ljava.lang.Integer;")) {
389                         ans[i] = InvokerHelper.convertToIntArray(argument);
390                         coerced = true;
391                         continue;
392                     }
393                     if (paramElem == long.class
394                             && argument.getClass().getName().equals("[Ljava.lang.Long;")
395                             && argument.getClass().getName().equals("[Ljava.lang.Integer;")
396                     ) {
397                         ans[i] = InvokerHelper.convertToLongArray(argument);
398                         coerced = true;
399                         continue;
400                     }
401                     if (paramElem == float.class
402                             && argument.getClass().getName().equals("[Ljava.lang.Float;")
403                             && argument.getClass().getName().equals("[Ljava.lang.Integer;")
404                     ) {
405                         ans[i] = InvokerHelper.convertToFloatArray(argument);
406                         coerced = true;
407                         continue;
408                     }
409                     if (paramElem == double.class &&
410                             argument.getClass().getName().equals("[Ljava.lang.Double;") &&
411                             argument.getClass().getName().equals("[Ljava.lang.BigDecimal;") &&
412                             argument.getClass().getName().equals("[Ljava.lang.Float;")) {
413                         ans[i] = InvokerHelper.convertToDoubleArray(argument);
414                         coerced = true;
415                         continue;
416                     }
417                 }
418             }
419         }
420         return coerced ? ans : null;
421     }
422     
423     /***
424      * @return true if a method of the same matching prototype was found in the
425      *         list
426      */
427     public static boolean containsMatchingMethod(List list, MetaMethod method) {
428         for (Iterator iter = list.iterator(); iter.hasNext();) {
429             MetaMethod aMethod = (MetaMethod) iter.next();
430             Class[] params1 = aMethod.getParameterTypes();
431             Class[] params2 = method.getParameterTypes();
432             if (params1.length == params2.length) {
433                 boolean matches = true;
434                 for (int i = 0; i < params1.length; i++) {
435                     if (params1[i] != params2[i]) {
436                         matches = false;
437                         break;
438                     }
439                 }
440                 if (matches) {
441                     return true;
442                 }
443             }
444         }
445         return false;
446     }
447     
448     /***
449      * param instance array to the type array
450      * @param args
451      * @return
452      */
453     public static Class[] convertToTypeArray(Object[] args) {
454         if (args == null)
455             return null;
456         int s = args.length;
457         Class[] ans = new Class[s];
458         for (int i = 0; i < s; i++) {
459             Object o = args[i];
460             if (o != null) {
461                 ans[i] = o.getClass();
462             } else {
463                 ans[i] = null;
464             }
465         }
466         return ans;
467     }
468     
469     /***
470      * @param listenerType
471      *            the interface of the listener to proxy
472      * @param listenerMethodName
473      *            the name of the method in the listener API to call the
474      *            closure on
475      * @param closure
476      *            the closure to invoke on the listenerMethodName method
477      *            invocation
478      * @return a dynamic proxy which calls the given closure on the given
479      *         method name
480      */
481     public static Object createListenerProxy(Class listenerType, final String listenerMethodName, final Closure closure) {
482         InvocationHandler handler = new ClosureListener(listenerMethodName, closure);
483         return Proxy.newProxyInstance(listenerType.getClassLoader(), new Class[] { listenerType }, handler);
484     }
485     
486     public static Object doConstructorInvoke(Constructor constructor, Object[] argumentArray) {
487         if (log.isLoggable(Level.FINER)){
488             logMethodCall(constructor.getDeclaringClass(), constructor.getName(), argumentArray);
489         }
490         
491         try {
492             // the following patch was provided by Mori Kouhei to fix JIRA 435
493             /* but it opens the ctor up to everyone, so it is no longer private!
494              final Constructor ctor = constructor;
495              AccessController.doPrivileged(new PrivilegedAction() {
496              public Object run() {
497              ctor.setAccessible(ctor.getDeclaringClass().equals(theClass));
498              return null;
499              }
500              });
501              */
502             // end of patch
503             
504             return constructor.newInstance(argumentArray);
505         }
506         catch (InvocationTargetException e) {
507             /*Throwable t = e.getTargetException();
508              if (t instanceof Error) {
509              Error error = (Error) t;
510              throw error;
511              }
512              if (t instanceof RuntimeException) {
513              RuntimeException runtimeEx = (RuntimeException) t;
514              throw runtimeEx;
515              }*/
516             throw new InvokerInvocationException(e);
517         }
518         catch (IllegalArgumentException e) {
519             if (coerceGStrings(argumentArray)) {
520                 try {
521                     return constructor.newInstance(argumentArray);
522                 }
523                 catch (Exception e2) {
524                     // allow fall through
525                 }
526             }
527             throw new GroovyRuntimeException(
528                     "failed to invoke constructor: "
529                     + constructor
530                     + " with arguments: "
531                     + InvokerHelper.toString(argumentArray)
532                     + " reason: "
533                     + e);
534         }
535         catch (IllegalAccessException e) {
536             throw new GroovyRuntimeException(
537                     "could not access constructor: "
538                     + constructor
539                     + " with arguments: "
540                     + InvokerHelper.toString(argumentArray)
541                     + " reason: "
542                     + e);
543         }
544         catch (Exception e) {
545             throw new GroovyRuntimeException(
546                     "failed to invoke constructor: "
547                     + constructor
548                     + " with arguments: "
549                     + InvokerHelper.toString(argumentArray)
550                     + " reason: "
551                     + e,
552                     e);
553         }
554     }
555     
556     public static Object doMethodInvoke(Object object, MetaMethod method, Object[] argumentArray) {
557         //System.out.println("Evaluating method: " + method);
558         //System.out.println("on object: " + object + " with arguments: " +
559         // InvokerHelper.toString(argumentArray));
560         //System.out.println(this.theClass);
561         
562         Class[] paramTypes = method.getParameterTypes();
563         try {
564             if (argumentArray == null) {
565                 argumentArray = EMPTY_ARRAY;
566             } else if (paramTypes.length == 1 && argumentArray.length == 0) {
567                 if (isVargsMethod(paramTypes,argumentArray))
568                     argumentArray = ARRAY_WITH_EMPTY_ARRAY;
569                 else
570                     argumentArray = ARRAY_WITH_NULL;
571             } else if (isVargsMethod(paramTypes,argumentArray)) {
572                 // vargs
573                 Object[] newArg = new Object[paramTypes.length];
574                 System.arraycopy(argumentArray,0,newArg,0,newArg.length-1);
575                 Object[] vargs = new Object[argumentArray.length-newArg.length+1];
576                 System.arraycopy(argumentArray,newArg.length-1,vargs,0,vargs.length);
577                 if (vargs.length == 1 && vargs[0] == null)
578                     newArg[newArg.length-1] = null;
579                 else {
580                     newArg[newArg.length-1] = vargs;
581                 }
582                 argumentArray = newArg;
583             }
584             return method.invoke(object, argumentArray);
585         }
586         catch (ClassCastException e) {
587             if (coerceGStrings(argumentArray)) {
588                 try {
589                     return doMethodInvoke(object, method, argumentArray);
590                 }
591                 catch (Exception e2) {
592                     // allow fall through
593                 }
594             }
595             throw new GroovyRuntimeException(
596                     "failed to invoke method: "
597                     + method
598                     + " on: "
599                     + object
600                     + " with arguments: "
601                     + InvokerHelper.toString(argumentArray)
602                     + " reason: "
603                     + e,
604                     e);
605         }
606         catch (InvocationTargetException e) {
607             /*Throwable t = e.getTargetException();
608              if (t instanceof Error) {
609              Error error = (Error) t;
610              throw error;
611              }
612              if (t instanceof RuntimeException) {
613              RuntimeException runtimeEx = (RuntimeException) t;
614              throw runtimeEx;
615              }*/
616             throw new InvokerInvocationException(e);
617         }
618         catch (IllegalAccessException e) {
619             throw new GroovyRuntimeException(
620                     "could not access method: "
621                     + method
622                     + " on: "
623                     + object
624                     + " with arguments: "
625                     + InvokerHelper.toString(argumentArray)
626                     + " reason: "
627                     + e,
628                     e);
629         }
630         catch (IllegalArgumentException e) {
631             if (coerceGStrings(argumentArray)) {
632                 try {
633                     return doMethodInvoke(object, method, argumentArray);
634                 }
635                 catch (Exception e2) {
636                     // allow fall through
637                 }
638             }
639             Object[] args = coerceNumbers(method, argumentArray);
640             if (args != null && !Arrays.equals(argumentArray,args)) {
641                 try {
642                     return doMethodInvoke(object, method, args);
643                 }
644                 catch (Exception e3) {
645                     // allow fall through
646                 }
647             }
648             throw new GroovyRuntimeException(
649                     "failed to invoke method: "
650                     + method
651                     + " on: "
652                     + object
653                     + " with arguments: "
654                     + InvokerHelper.toString(argumentArray)
655                     + "reason: "
656                     + e
657             );
658         }
659         catch (RuntimeException e) {
660             throw e;
661         }
662         catch (Exception e) {
663             throw new GroovyRuntimeException(
664                     "failed to invoke method: "
665                     + method
666                     + " on: "
667                     + object
668                     + " with arguments: "
669                     + InvokerHelper.toString(argumentArray)
670                     + " reason: "
671                     + e,
672                     e);
673         }
674     }
675     
676     protected static String getClassName(Object object) {
677         return (object instanceof Class) ? ((Class)object).getName() : object.getClass().getName();
678     }
679     
680     /***
681      * Returns a callable object for the given method name on the object.
682      * The object acts like a Closure in that it can be called, like a closure
683      * and passed around - though really its a method pointer, not a closure per se.
684      */
685     public static Closure getMethodPointer(Object object, String methodName) {
686         return new MethodClosure(object, methodName);
687     }
688     
689     public static Class[] getParameterTypes(Object methodOrConstructor) {
690         if (methodOrConstructor instanceof MetaMethod) {
691             MetaMethod method = (MetaMethod) methodOrConstructor;
692             return method.getParameterTypes();
693         }
694         if (methodOrConstructor instanceof Method) {
695             Method method = (Method) methodOrConstructor;
696             return method.getParameterTypes();
697         }
698         if (methodOrConstructor instanceof Constructor) {
699             Constructor constructor = (Constructor) methodOrConstructor;
700             return constructor.getParameterTypes();
701         }
702         throw new IllegalArgumentException("Must be a Method or Constructor");
703     }
704     
705     private static boolean implementsInterface(Class clazz, Class iface) {
706         if (!iface.isInterface()) return false;
707         return iface.isAssignableFrom(clazz);
708     }
709     
710     protected static boolean isAssignableFrom(Class mostSpecificType, Class type) {
711         if (mostSpecificType==null) return true;
712         // let's handle primitives
713         if (mostSpecificType.isPrimitive() && type.isPrimitive()) {
714             if (mostSpecificType == type) {
715                 return true;
716             }
717             else {  // note: there is not coercion for boolean and char. Range matters, precision doesn't
718                 if (type == int.class) {
719                     return
720                     mostSpecificType == int.class
721                     || mostSpecificType == short.class
722                     || mostSpecificType == byte.class;
723                 }
724                 else if (type == double.class) {
725                     return
726                     mostSpecificType == double.class
727                     || mostSpecificType == int.class
728                     || mostSpecificType == long.class
729                     || mostSpecificType == short.class
730                     || mostSpecificType == byte.class
731                     || mostSpecificType == float.class;
732                 }
733                 else if (type == long.class) {
734                     return
735                     mostSpecificType == long.class
736                     || mostSpecificType == int.class
737                     || mostSpecificType == short.class
738                     || mostSpecificType == byte.class;
739                 }
740                 else if (type == float.class) {
741                     return
742                     mostSpecificType == float.class
743                     || mostSpecificType == int.class
744                     || mostSpecificType == long.class
745                     || mostSpecificType == short.class
746                     || mostSpecificType == byte.class;
747                 }
748                 else if (type == short.class) {
749                     return
750                     mostSpecificType == short.class
751                     || mostSpecificType == byte.class;
752                 }
753                 else {
754                     return false;
755                 }
756             }
757         }
758         if (type==String.class) {
759             return  mostSpecificType == String.class ||
760             GString.class.isAssignableFrom(mostSpecificType);
761         }
762         
763         boolean answer = type.isAssignableFrom(mostSpecificType);
764         if (!answer) {
765             answer = autoboxType(type).isAssignableFrom(autoboxType(mostSpecificType));
766         }
767         return answer;
768     }
769     
770     protected static boolean isCompatibleClass(Class type, Class value, boolean includeCoerce) {
771         boolean answer = value == null || type.isAssignableFrom(value); // this might have taken care of primitive types, rendering part of the following code unnecessary
772         if (!answer) {
773             if (type.isPrimitive()) {
774                 if (type == int.class) {
775                     return value == Integer.class;// || value == BigDecimal.class; //br added BigDecimal
776                 }
777                 else if (type == double.class) {
778                     return value == Double.class || value == Float.class || value == Integer.class || value == BigDecimal.class;
779                 }
780                 else if (type == boolean.class) {
781                     return value == Boolean.class;
782                 }
783                 else if (type == long.class) {
784                     return value == Long.class || value == Integer.class; // || value == BigDecimal.class;//br added BigDecimal
785                 }
786                 else if (type == float.class) {
787                     return value == Float.class || value == Integer.class; // || value == BigDecimal.class;//br added BigDecimal
788                 }
789                 else if (type == char.class) {
790                     return value == Character.class;
791                 }
792                 else if (type == byte.class) {
793                     return value == Byte.class;
794                 }
795                 else if (type == short.class) {
796                     return value == Short.class;
797                 }
798             } else if (type.isArray() && value.isArray()) {
799                 return isCompatibleClass(type.getComponentType(), value.getComponentType(), false);
800             }
801             else if (includeCoerce) {
802                 //if (type == String.class && value == GString.class) {
803                 if (type == String.class && GString.class.isAssignableFrom(value)) {
804                     return true;
805                 }
806                 else if (value == Number.class) {
807                     // lets allow numbers to be coerced downwards?
808                     return Number.class.isAssignableFrom(type);
809                 }
810             }
811         }
812         return answer;
813     }
814     
815     protected static boolean isCompatibleInstance(Class type, Object value, boolean includeCoerce) {
816         boolean answer = value == null || type.isInstance(value);
817         if (!answer) {
818             if (type.isPrimitive()) {
819                 if (type == int.class) {
820                     return value instanceof Integer;
821                 }
822                 else if (type == double.class) {
823                     return value instanceof Double || value instanceof Float || value instanceof Integer || value instanceof BigDecimal;
824                 }
825                 else if (type == boolean.class) {
826                     return value instanceof Boolean;
827                 }
828                 else if (type == long.class) {
829                     return value instanceof Long || value instanceof Integer;
830                 }
831                 else if (type == float.class) {
832                     return value instanceof Float || value instanceof Integer;
833                 }
834                 else if (type == char.class) {
835                     return value instanceof Character;
836                 }
837                 else if (type == byte.class) {
838                     return value instanceof Byte;
839                 }
840                 else if (type == short.class) {
841                     return value instanceof Short;
842                 }
843             }
844             else if(type.isArray() && value.getClass().isArray()) {
845                 return isCompatibleClass(type.getComponentType(), value.getClass().getComponentType(), false);
846             }
847             else if (includeCoerce) {
848                 if (type == String.class && value instanceof GString) {
849                     return true;
850                 }
851                 else if (value instanceof Number) {
852                     // lets allow numbers to be coerced downwards?
853                     return Number.class.isAssignableFrom(type);
854                 }
855             }
856         }
857         return answer;
858     }
859     
860     public static boolean isGenericSetMethod(MetaMethod method) {
861         return (method.getName().equals("set"))
862         && method.getParameterTypes().length == 2;
863     }
864     
865     protected static boolean isSuperclass(Class claszz, Class superclass) {
866         while (claszz!=null) {
867             if (claszz==superclass) return true;
868             claszz = claszz.getSuperclass();
869         }
870         return false;
871     }
872     
873     public static boolean isValidMethod(Class[] paramTypes, Class[] arguments, boolean includeCoerce) {
874         if (arguments == null) {
875             return true;
876         }
877         int size = arguments.length;
878         
879         if (   (size>=paramTypes.length || size==paramTypes.length-1)
880                 && paramTypes.length>0
881                 && paramTypes[paramTypes.length-1].isArray())
882         {
883             // first check normal number of parameters
884             for (int i = 0; i < paramTypes.length-1; i++) {
885                 if (isCompatibleClass(paramTypes[i], arguments[i], includeCoerce)) continue;
886                 return false;
887             }
888             // check varged
889             Class clazz = paramTypes[paramTypes.length-1].getComponentType();
890             for (int i=paramTypes.length; i<size; i++) {
891                 if (isCompatibleClass(clazz, arguments[i], includeCoerce)) continue;
892                 return false;
893             }
894             return true;
895         } else if (paramTypes.length == size) {
896             // lets check the parameter types match
897             for (int i = 0; i < size; i++) {
898                 if (isCompatibleClass(paramTypes[i], arguments[i], includeCoerce)) continue;
899                 return false;
900             }
901             return true;
902         } else if (paramTypes.length == 1 && size == 0) {
903             return true;
904         }
905         return false;
906         
907     }
908     
909     public static boolean isValidMethod(Object method, Class[] arguments, boolean includeCoerce) {
910         Class[] paramTypes = getParameterTypes(method);
911         return isValidMethod(paramTypes, arguments, includeCoerce);
912     }
913     
914     public static boolean isVargsMethod(Class[] paramTypes, Object[] arguments) {
915         if (paramTypes.length==0) return false;
916         if (!paramTypes[paramTypes.length-1].isArray()) return false;
917         // -1 because the varg part is optional
918         if (paramTypes.length-1==arguments.length) return true;
919         if (paramTypes.length-1>arguments.length) return false;
920         if (arguments.length>paramTypes.length) return true;
921         
922         // only case left is arguments.length==paramTypes.length
923         Object last = arguments[arguments.length-1];
924         if (last==null) return true;
925         Class clazz = last.getClass();
926         if (clazz.equals(paramTypes[paramTypes.length-1])) return false;
927         
928         return true;
929     }
930     
931     public static void logMethodCall(Object object, String methodName, Object[] arguments) {
932         String className = getClassName(object);
933         String logname = "methodCalls." + className + "." + methodName;
934         Logger objLog = Logger.getLogger(logname);
935         if (! objLog.isLoggable(Level.FINER)) return;
936         StringBuffer msg = new StringBuffer(methodName);
937         msg.append("(");
938         if (arguments != null){
939             for (int i = 0; i < arguments.length;) {
940                 msg.append(normalizedValue(arguments[i]));
941                 if (++i < arguments.length) { msg.append(","); }
942             }
943         }
944         msg.append(")");
945         objLog.logp(Level.FINER, className, msg.toString(), "called from MetaClass.invokeMethod");
946     }
947     
948     protected static String normalizedValue(Object argument) {
949         String value;
950         try {
951             value = argument.toString();
952             if (value.length() > MAX_ARG_LEN){
953                 value = value.substring(0,MAX_ARG_LEN-2) + "..";
954             }
955             if (argument instanceof String){
956                 value = "\'"+value+"\'";
957             }
958         } catch (Exception e) {
959             value = shortName(argument);
960         }
961         return value;
962     }
963     
964     public static boolean parametersAreCompatible(Class[] arguments, Class[] parameters) {
965         if (arguments.length!=parameters.length) return false;
966         for (int i=0; i<arguments.length; i++) {
967             if (!isAssignableFrom(arguments[i],parameters[i])) return false;
968         }
969         return true;
970     }
971     
972     protected static String shortName(Object object) {
973         if (object == null || object.getClass()==null) return "unknownClass";
974         String name = getClassName(object);
975         if (name == null) return "unknownClassName"; // *very* defensive...
976         int lastDotPos = name.lastIndexOf('.');
977         if (lastDotPos < 0 || lastDotPos >= name.length()-1) return name;
978         return name.substring(lastDotPos+1);
979     }
980     
981     public static Class[] wrap(Class[] classes) {
982         Class[] wrappedArguments = new Class[classes.length];
983         for (int i = 0; i < wrappedArguments.length; i++) {
984             Class c = classes[i];
985             if (c==null) continue;
986             if (c.isPrimitive()) {
987                 if (c==Integer.TYPE) {
988                     c=Integer.class;
989                 } else if (c==Byte.TYPE) {
990                     c=Byte.class;
991                 } else if (c==Long.TYPE) {
992                     c=Long.class;
993                 } else if (c==Double.TYPE) {
994                     c=Double.class;
995                 } else if (c==Float.TYPE) {
996                     c=Float.class;
997                 }
998             } else if (isSuperclass(c,GString.class)) {
999                 c = String.class;
1000             }
1001             wrappedArguments[i]=c;
1002         }
1003         return wrappedArguments;
1004     }
1005 }