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 }