001    /*
002     * Copyright 2005 John G. Wilson
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     *
016     */
017    
018    package org.codehaus.groovy.runtime;
019    
020    import groovy.lang.Closure;
021    import groovy.lang.GString;
022    import groovy.lang.GroovyRuntimeException;
023    import groovy.lang.MetaMethod;
024    
025    import java.lang.reflect.Array;
026    import java.lang.reflect.Constructor;
027    import java.lang.reflect.InvocationHandler;
028    import java.lang.reflect.InvocationTargetException;
029    import java.lang.reflect.Method;
030    import java.lang.reflect.Modifier;
031    import java.lang.reflect.Proxy;
032    import java.math.BigDecimal;
033    import java.math.BigInteger;
034    import java.util.Arrays;
035    import java.util.Iterator;
036    import java.util.List;
037    import java.util.logging.Level;
038    import java.util.logging.Logger;
039    
040    /**
041     * @author John Wilson
042     *
043     */
044    
045    public class MetaClassHelper {
046    
047        public static final Object[] EMPTY_ARRAY = {};
048        public static Class[] EMPTY_TYPE_ARRAY = {};
049        protected static final Object[] ARRAY_WITH_EMPTY_ARRAY = { EMPTY_ARRAY };
050        protected static final Object[] ARRAY_WITH_NULL = { null };
051        protected static final Logger log = Logger.getLogger(MetaClassHelper.class.getName());
052        private static final int MAX_ARG_LEN = 12;
053        
054        public static boolean accessibleToConstructor(final Class at, final Constructor constructor) {
055            boolean accessible = false;
056            if (Modifier.isPublic(constructor.getModifiers())) {
057                accessible = true;
058            }
059            else if (Modifier.isPrivate(constructor.getModifiers())) {
060                accessible = at.getName().equals(constructor.getName());
061            }
062            else if ( Modifier.isProtected(constructor.getModifiers()) ) {
063                if ( at.getPackage() == null && constructor.getDeclaringClass().getPackage() == null ) {
064                    accessible = true;
065                }
066                else if ( at.getPackage() == null && constructor.getDeclaringClass().getPackage() != null ) {
067                    accessible = false;
068                }
069                else if ( at.getPackage() != null && constructor.getDeclaringClass().getPackage() == null ) {
070                    accessible = false;
071                }
072                else if ( at.getPackage().equals(constructor.getDeclaringClass().getPackage()) ) {
073                    accessible = true;
074                }
075                else {
076                    boolean flag = false;
077                    Class clazz = at;
078                    while ( !flag && clazz != null ) {
079                        if (clazz.equals(constructor.getDeclaringClass()) ) {
080                            flag = true;
081                            break;
082                        }
083                        if (clazz.equals(Object.class) ) {
084                            break;
085                        }
086                        clazz = clazz.getSuperclass();
087                    }
088                    accessible = flag;
089                }
090            }
091            else {
092                if ( at.getPackage() == null && constructor.getDeclaringClass().getPackage() == null ) {
093                    accessible = true;
094                }
095                else if ( at.getPackage() == null && constructor.getDeclaringClass().getPackage() != null ) {
096                    accessible = false;
097                }
098                else if ( at.getPackage() != null && constructor.getDeclaringClass().getPackage() == null ) {
099                    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    }