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