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 }