0001: /*
0002: * Copyright 2005 John G. Wilson
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: *
0016: */
0017:
0018: package org.codehaus.groovy.runtime;
0019:
0020: import groovy.lang.Closure;
0021: import groovy.lang.GString;
0022: import groovy.lang.GroovyRuntimeException;
0023: import groovy.lang.MetaMethod;
0024:
0025: import java.lang.reflect.Array;
0026: import java.lang.reflect.Constructor;
0027: import java.lang.reflect.InvocationHandler;
0028: import java.lang.reflect.InvocationTargetException;
0029: import java.lang.reflect.Method;
0030: import java.lang.reflect.Modifier;
0031: import java.lang.reflect.Proxy;
0032: import java.math.BigDecimal;
0033: import java.math.BigInteger;
0034: import java.util.Iterator;
0035: import java.util.List;
0036: import java.util.logging.Level;
0037: import java.util.logging.Logger;
0038:
0039: import org.codehaus.groovy.GroovyBugError;
0040: import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
0041: import org.codehaus.groovy.runtime.wrappers.Wrapper;
0042:
0043: /**
0044: * @author John Wilson
0045: * @author Jochen Theodorou
0046: */
0047: public class MetaClassHelper {
0048:
0049: public static final Object[] EMPTY_ARRAY = {};
0050: public static Class[] EMPTY_TYPE_ARRAY = {};
0051: protected static final Object[] ARRAY_WITH_NULL = { null };
0052: protected static final Logger log = Logger
0053: .getLogger(MetaClassHelper.class.getName());
0054: private static final int MAX_ARG_LEN = 12;
0055:
0056: public static boolean accessibleToConstructor(final Class at,
0057: final Constructor constructor) {
0058: boolean accessible = false;
0059: if (Modifier.isPublic(constructor.getModifiers())) {
0060: accessible = true;
0061: } else if (Modifier.isPrivate(constructor.getModifiers())) {
0062: accessible = at.getName().equals(constructor.getName());
0063: } else if (Modifier.isProtected(constructor.getModifiers())) {
0064: if (at.getPackage() == null
0065: && constructor.getDeclaringClass().getPackage() == null) {
0066: accessible = true;
0067: } else if (at.getPackage() == null
0068: && constructor.getDeclaringClass().getPackage() != null) {
0069: accessible = false;
0070: } else if (at.getPackage() != null
0071: && constructor.getDeclaringClass().getPackage() == null) {
0072: accessible = false;
0073: } else if (at.getPackage().equals(
0074: constructor.getDeclaringClass().getPackage())) {
0075: accessible = true;
0076: } else {
0077: boolean flag = false;
0078: Class clazz = at;
0079: while (!flag && clazz != null) {
0080: if (clazz.equals(constructor.getDeclaringClass())) {
0081: flag = true;
0082: break;
0083: }
0084: if (clazz.equals(Object.class)) {
0085: break;
0086: }
0087: clazz = clazz.getSuperclass();
0088: }
0089: accessible = flag;
0090: }
0091: } else {
0092: if (at.getPackage() == null
0093: && constructor.getDeclaringClass().getPackage() == null) {
0094: accessible = true;
0095: } else if (at.getPackage() == null
0096: && constructor.getDeclaringClass().getPackage() != null) {
0097: accessible = false;
0098: } else if (at.getPackage() != null
0099: && constructor.getDeclaringClass().getPackage() == null) {
0100: accessible = false;
0101: } else if (at.getPackage().equals(
0102: constructor.getDeclaringClass().getPackage())) {
0103: accessible = true;
0104: }
0105: }
0106: return accessible;
0107: }
0108:
0109: public static Object[] asWrapperArray(Object parameters,
0110: Class componentType) {
0111: Object[] ret = null;
0112: if (componentType == boolean.class) {
0113: boolean[] array = (boolean[]) parameters;
0114: ret = new Object[array.length];
0115: for (int i = 0; i < array.length; i++) {
0116: ret[i] = new Boolean(array[i]);
0117: }
0118: } else if (componentType == char.class) {
0119: char[] array = (char[]) parameters;
0120: ret = new Object[array.length];
0121: for (int i = 0; i < array.length; i++) {
0122: ret[i] = new Character(array[i]);
0123: }
0124: } else if (componentType == byte.class) {
0125: byte[] array = (byte[]) parameters;
0126: ret = new Object[array.length];
0127: for (int i = 0; i < array.length; i++) {
0128: ret[i] = new Byte(array[i]);
0129: }
0130: } else if (componentType == int.class) {
0131: int[] array = (int[]) parameters;
0132: ret = new Object[array.length];
0133: for (int i = 0; i < array.length; i++) {
0134: ret[i] = new Integer(array[i]);
0135: }
0136: } else if (componentType == short.class) {
0137: short[] array = (short[]) parameters;
0138: ret = new Object[array.length];
0139: for (int i = 0; i < array.length; i++) {
0140: ret[i] = new Short(array[i]);
0141: }
0142: } else if (componentType == long.class) {
0143: long[] array = (long[]) parameters;
0144: ret = new Object[array.length];
0145: for (int i = 0; i < array.length; i++) {
0146: ret[i] = new Long(array[i]);
0147: }
0148: } else if (componentType == double.class) {
0149: double[] array = (double[]) parameters;
0150: ret = new Object[array.length];
0151: for (int i = 0; i < array.length; i++) {
0152: ret[i] = new Double(array[i]);
0153: }
0154: } else if (componentType == float.class) {
0155: float[] array = (float[]) parameters;
0156: ret = new Object[array.length];
0157: for (int i = 0; i < array.length; i++) {
0158: ret[i] = new Float(array[i]);
0159: }
0160: }
0161:
0162: return ret;
0163: }
0164:
0165: /**
0166: * @param list
0167: * @param parameterType
0168: */
0169: public static Object asPrimitiveArray(List list, Class parameterType) {
0170: Class arrayType = parameterType.getComponentType();
0171: Object objArray = Array.newInstance(arrayType, list.size());
0172: for (int i = 0; i < list.size(); i++) {
0173: Object obj = list.get(i);
0174: if (arrayType.isPrimitive()) {
0175: if (obj instanceof Integer) {
0176: Array.setInt(objArray, i, ((Integer) obj)
0177: .intValue());
0178: } else if (obj instanceof Double) {
0179: Array.setDouble(objArray, i, ((Double) obj)
0180: .doubleValue());
0181: } else if (obj instanceof Boolean) {
0182: Array.setBoolean(objArray, i, ((Boolean) obj)
0183: .booleanValue());
0184: } else if (obj instanceof Long) {
0185: Array
0186: .setLong(objArray, i, ((Long) obj)
0187: .longValue());
0188: } else if (obj instanceof Float) {
0189: Array.setFloat(objArray, i, ((Float) obj)
0190: .floatValue());
0191: } else if (obj instanceof Character) {
0192: Array.setChar(objArray, i, ((Character) obj)
0193: .charValue());
0194: } else if (obj instanceof Byte) {
0195: Array
0196: .setByte(objArray, i, ((Byte) obj)
0197: .byteValue());
0198: } else if (obj instanceof Short) {
0199: Array.setShort(objArray, i, ((Short) obj)
0200: .shortValue());
0201: }
0202: } else {
0203: Array.set(objArray, i, obj);
0204: }
0205: }
0206: return objArray;
0207: }
0208:
0209: protected static Class autoboxType(Class type) {
0210: if (type.isPrimitive()) {
0211: if (type == int.class) {
0212: return Integer.class;
0213: } else if (type == double.class) {
0214: return Double.class;
0215: } else if (type == long.class) {
0216: return Long.class;
0217: } else if (type == boolean.class) {
0218: return Boolean.class;
0219: } else if (type == float.class) {
0220: return Float.class;
0221: } else if (type == char.class) {
0222: return Character.class;
0223: } else if (type == byte.class) {
0224: return Byte.class;
0225: } else if (type == short.class) {
0226: return Short.class;
0227: }
0228: }
0229: return type;
0230: }
0231:
0232: private static Class[] primitives = { byte.class, Byte.class,
0233: short.class, Short.class, int.class, Integer.class,
0234: long.class, Long.class, BigInteger.class, float.class,
0235: Float.class, double.class, Double.class, BigDecimal.class,
0236: Number.class, Object.class };
0237: private static int[][] primitiveDistanceTable = {
0238: // byte Byte short Short int Integer long Long BigInteger float Float double Double BigDecimal, Number, Object
0239: /* byte*/{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
0240: 14, 15, },
0241: /*Byte*/{ 1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
0242: 14, 15, },
0243: /*short*/{ 14, 15, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
0244: 12, 13, },
0245: /*Short*/{ 14, 15, 1, 0, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,
0246: 12, 13, },
0247: /*int*/{ 14, 15, 12, 13, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
0248: 11, },
0249: /*Integer*/{ 14, 15, 12, 13, 1, 0, 2, 3, 4, 5, 6, 7, 8, 9,
0250: 10, 11, },
0251: /*long*/{ 14, 15, 12, 13, 10, 11, 0, 1, 2, 3, 4, 5, 6, 7,
0252: 8, 9, },
0253: /*Long*/{ 14, 15, 12, 13, 10, 11, 1, 0, 2, 3, 4, 5, 6, 7,
0254: 8, 9, },
0255: /*BigInteger*/{ 14, 15, 12, 13, 10, 11, 8, 9, 0, 1, 2, 3,
0256: 4, 5, 6, 7, },
0257: /*float*/{ 14, 15, 12, 13, 10, 11, 8, 9, 7, 0, 1, 2, 3, 4,
0258: 5, 6, },
0259: /*Float*/{ 14, 15, 12, 13, 10, 11, 8, 9, 7, 1, 0, 2, 3, 4,
0260: 5, 6, },
0261: /*double*/{ 14, 15, 12, 13, 10, 11, 8, 9, 7, 5, 6, 0, 1,
0262: 2, 3, 4, },
0263: /*Double*/{ 14, 15, 12, 13, 10, 11, 8, 9, 7, 5, 6, 1, 0,
0264: 2, 3, 4, },
0265: /*BigDecimal*/{ 14, 15, 12, 13, 10, 11, 8, 9, 7, 5, 6, 3,
0266: 4, 0, 1, 2, },
0267: /*Numer*/{ 14, 15, 12, 13, 10, 11, 8, 9, 7, 5, 6, 3, 4, 2,
0268: 0, 1, },
0269: /*Object*/{ 14, 15, 12, 13, 10, 11, 8, 9, 7, 5, 6, 3, 4,
0270: 2, 1, 0, }, };
0271:
0272: private static int getPrimitiveIndex(Class c) {
0273: for (byte i = 0; i < primitives.length; i++) {
0274: if (primitives[i] == c)
0275: return i;
0276: }
0277: return -1;
0278: }
0279:
0280: private static int getPrimitiveDistance(Class from, Class to) {
0281: // we know here that from!=to, so a distance of 0 is never valid
0282: // get primitive type indexes
0283: int fromIndex = getPrimitiveIndex(from);
0284: int toIndex = getPrimitiveIndex(to);
0285: if (fromIndex == -1 || toIndex == -1)
0286: return -1;
0287: return primitiveDistanceTable[toIndex][fromIndex];
0288: }
0289:
0290: private static int getMaximumInterfaceDistance(Class c,
0291: Class interfaceClass) {
0292: if (c == interfaceClass)
0293: return 0;
0294: Class[] interfaces = c.getInterfaces();
0295: int max = 0;
0296: for (int i = 0; i < interfaces.length; i++) {
0297: int sub = 0;
0298: if (interfaces[i].isAssignableFrom(c)) {
0299: sub = 1 + getMaximumInterfaceDistance(interfaces[i],
0300: interfaceClass);
0301: }
0302: max = Math.max(max, sub);
0303: }
0304: return max;
0305: }
0306:
0307: public static long calculateParameterDistance(Class[] arguments,
0308: Class[] parameters) {
0309: int objectDistance = 0, interfaceDistance = 0;
0310: for (int i = 0; i < arguments.length; i++) {
0311: if (parameters[i] == arguments[i])
0312: continue;
0313:
0314: if (parameters[i].isInterface()) {
0315: objectDistance += primitives.length;
0316: interfaceDistance += getMaximumInterfaceDistance(
0317: arguments[i], parameters[i]);
0318: continue;
0319: }
0320:
0321: if (arguments[i] != null) {
0322: int pd = getPrimitiveDistance(parameters[i],
0323: arguments[i]);
0324: if (pd != -1) {
0325: objectDistance += pd;
0326: continue;
0327: }
0328:
0329: // add one to dist to be sure interfaces are prefered
0330: objectDistance += primitives.length + 1;
0331: Class clazz = autoboxType(arguments[i]);
0332: while (clazz != null) {
0333: if (clazz == parameters[i])
0334: break;
0335: if (clazz == GString.class
0336: && parameters[i] == String.class) {
0337: objectDistance += 2;
0338: break;
0339: }
0340: clazz = clazz.getSuperclass();
0341: objectDistance += 3;
0342: }
0343: } else {
0344: // choose the distance to Object if a parameter is null
0345: // this will mean that Object is prefered over a more
0346: // specific type
0347: // remove one to dist to be sure Object is prefered
0348: objectDistance--;
0349: Class clazz = parameters[i];
0350: if (clazz.isPrimitive()) {
0351: objectDistance += 2;
0352: } else {
0353: while (clazz != Object.class) {
0354: clazz = clazz.getSuperclass();
0355: objectDistance += 2;
0356: }
0357: }
0358: }
0359: }
0360: long ret = objectDistance;
0361: ret <<= 32;
0362: ret |= interfaceDistance;
0363: return ret;
0364: }
0365:
0366: public static String capitalize(String property) {
0367: return property.substring(0, 1).toUpperCase()
0368: + property.substring(1, property.length());
0369: }
0370:
0371: /**
0372: * @return the method with 1 parameter which takes the most general type of
0373: * object (e.g. Object)
0374: */
0375: public static Object chooseEmptyMethodParams(List methods) {
0376: for (Iterator iter = methods.iterator(); iter.hasNext();) {
0377: Object method = iter.next();
0378: Class[] paramTypes = getParameterTypes(method);
0379: int paramLength = paramTypes.length;
0380: if (paramLength == 0) {
0381: return method;
0382: }
0383: }
0384: return null;
0385: }
0386:
0387: /**
0388: * @return the method with 1 parameter which takes the most general type of
0389: * object (e.g. Object) ignoring primitve types
0390: */
0391: public static Object chooseMostGeneralMethodWith1NullParam(
0392: List methods) {
0393: // lets look for methods with 1 argument which matches the type of the
0394: // arguments
0395: Class closestClass = null;
0396: Object answer = null;
0397:
0398: for (Iterator iter = methods.iterator(); iter.hasNext();) {
0399: Object method = iter.next();
0400: Class[] paramTypes = getParameterTypes(method);
0401: int paramLength = paramTypes.length;
0402: if (paramLength == 1) {
0403: Class theType = paramTypes[0];
0404: if (theType.isPrimitive())
0405: continue;
0406: if (closestClass == null
0407: || isAssignableFrom(theType, closestClass)) {
0408: closestClass = theType;
0409: answer = method;
0410: }
0411: }
0412: }
0413: return answer;
0414: }
0415:
0416: /**
0417: * Coerces a GString instance into String if needed
0418: *
0419: * @return the coerced argument
0420: */
0421: protected static Object coerceGString(Object argument, Class clazz) {
0422: if (clazz != String.class)
0423: return argument;
0424: if (!(argument instanceof GString))
0425: return argument;
0426: return argument.toString();
0427: }
0428:
0429: protected static Object coerceNumber(Object argument, Class param) {
0430: if ((Number.class.isAssignableFrom(param) || param
0431: .isPrimitive())
0432: && argument instanceof Number) { // Number types
0433: Object oldArgument = argument;
0434: boolean wasDouble = false;
0435: boolean wasFloat = false;
0436: if (param == Byte.class || param == Byte.TYPE) {
0437: argument = new Byte(((Number) argument).byteValue());
0438: } else if (param == Double.class || param == Double.TYPE) {
0439: wasDouble = true;
0440: argument = new Double(((Number) argument).doubleValue());
0441: } else if (param == Float.class || param == Float.TYPE) {
0442: wasFloat = true;
0443: argument = new Float(((Number) argument).floatValue());
0444: } else if (param == Integer.class || param == Integer.TYPE) {
0445: argument = new Integer(((Number) argument).intValue());
0446: } else if (param == Long.class || param == Long.TYPE) {
0447: argument = new Long(((Number) argument).longValue());
0448: } else if (param == Short.class || param == Short.TYPE) {
0449: argument = new Short(((Number) argument).shortValue());
0450: } else if (param == BigDecimal.class) {
0451: argument = new BigDecimal(String
0452: .valueOf((Number) argument));
0453: } else if (param == BigInteger.class) {
0454: argument = new BigInteger(String
0455: .valueOf((Number) argument));
0456: }
0457:
0458: if (oldArgument instanceof BigDecimal) {
0459: BigDecimal oldbd = (BigDecimal) oldArgument;
0460: boolean throwException = false;
0461: if (wasDouble) {
0462: Double d = (Double) argument;
0463: if (d.isInfinite())
0464: throwException = true;
0465: } else if (wasFloat) {
0466: Float f = (Float) argument;
0467: if (f.isInfinite())
0468: throwException = true;
0469: } else {
0470: BigDecimal newbd = new BigDecimal(String
0471: .valueOf((Number) argument));
0472: throwException = !oldArgument.equals(newbd);
0473: }
0474:
0475: if (throwException)
0476: throw new IllegalArgumentException(
0477: param
0478: + " out of range while converting from BigDecimal");
0479: }
0480:
0481: }
0482: return argument;
0483: }
0484:
0485: protected static Object coerceArray(Object argument, Class param) {
0486: if (!param.isArray())
0487: return argument;
0488: Class argumentClass = argument.getClass();
0489: if (!argumentClass.isArray())
0490: return argument;
0491:
0492: Class paramComponent = param.getComponentType();
0493: if (paramComponent.isPrimitive()) {
0494: if (paramComponent == boolean.class
0495: && argumentClass == Boolean[].class) {
0496: argument = DefaultTypeTransformation
0497: .convertToBooleanArray(argument);
0498: } else if (paramComponent == byte.class
0499: && argumentClass == Byte[].class) {
0500: argument = DefaultTypeTransformation
0501: .convertToByteArray(argument);
0502: } else if (paramComponent == char.class
0503: && argumentClass == Character[].class) {
0504: argument = DefaultTypeTransformation
0505: .convertToCharArray(argument);
0506: } else if (paramComponent == short.class
0507: && argumentClass == Short[].class) {
0508: argument = DefaultTypeTransformation
0509: .convertToShortArray(argument);
0510: } else if (paramComponent == int.class
0511: && argumentClass == Integer[].class) {
0512: argument = DefaultTypeTransformation
0513: .convertToIntArray(argument);
0514: } else if (paramComponent == long.class
0515: && (argumentClass == Long[].class || argumentClass == Integer[].class)) {
0516: argument = DefaultTypeTransformation
0517: .convertToLongArray(argument);
0518: } else if (paramComponent == float.class
0519: && (argumentClass == Float[].class || argumentClass == Integer[].class)) {
0520: argument = DefaultTypeTransformation
0521: .convertToFloatArray(argument);
0522: } else if (paramComponent == double.class
0523: && (argumentClass == Double[].class
0524: || argumentClass == Float[].class || BigDecimal.class
0525: .isAssignableFrom(argumentClass))) {
0526: argument = DefaultTypeTransformation
0527: .convertToDoubleArray(argument);
0528: }
0529: } else if (paramComponent == String.class
0530: && argument instanceof GString[]) {
0531: GString[] strings = (GString[]) argument;
0532: String[] ret = new String[strings.length];
0533: for (int i = 0; i < strings.length; i++) {
0534: ret[i] = strings[i].toString();
0535: }
0536: argument = ret;
0537: }
0538: return argument;
0539: }
0540:
0541: /**
0542: * @return true if a method of the same matching prototype was found in the
0543: * list
0544: */
0545: public static boolean containsMatchingMethod(List list,
0546: MetaMethod method) {
0547: for (Iterator iter = list.iterator(); iter.hasNext();) {
0548: MetaMethod aMethod = (MetaMethod) iter.next();
0549: Class[] params1 = aMethod.getParameterTypes();
0550: Class[] params2 = method.getParameterTypes();
0551: if (params1.length == params2.length) {
0552: boolean matches = true;
0553: for (int i = 0; i < params1.length; i++) {
0554: if (params1[i] != params2[i]) {
0555: matches = false;
0556: break;
0557: }
0558: }
0559: if (matches) {
0560: return true;
0561: }
0562: }
0563: }
0564: return false;
0565: }
0566:
0567: /**
0568: * param instance array to the type array
0569: * @param args
0570: */
0571: public static Class[] convertToTypeArray(Object[] args) {
0572: if (args == null)
0573: return null;
0574: int s = args.length;
0575: Class[] ans = new Class[s];
0576: for (int i = 0; i < s; i++) {
0577: Object o = args[i];
0578: if (o == null) {
0579: ans[i] = null;
0580: } else if (o instanceof Wrapper) {
0581: ans[i] = ((Wrapper) o).getType();
0582: } else {
0583: ans[i] = o.getClass();
0584: }
0585: }
0586: return ans;
0587: }
0588:
0589: /**
0590: * @param listenerType
0591: * the interface of the listener to proxy
0592: * @param listenerMethodName
0593: * the name of the method in the listener API to call the
0594: * closure on
0595: * @param closure
0596: * the closure to invoke on the listenerMethodName method
0597: * invocation
0598: * @return a dynamic proxy which calls the given closure on the given
0599: * method name
0600: */
0601: public static Object createListenerProxy(Class listenerType,
0602: final String listenerMethodName, final Closure closure) {
0603: InvocationHandler handler = new ClosureListener(
0604: listenerMethodName, closure);
0605: return Proxy.newProxyInstance(listenerType.getClassLoader(),
0606: new Class[] { listenerType }, handler);
0607: }
0608:
0609: public static Object doConstructorInvoke(Constructor constructor,
0610: Object[] argumentArray) {
0611: if (log.isLoggable(Level.FINER)) {
0612: logMethodCall(constructor.getDeclaringClass(), constructor
0613: .getName(), argumentArray);
0614: }
0615: argumentArray = coerceArgumentsToClasses(argumentArray,
0616: constructor.getParameterTypes());
0617: try {
0618: return constructor.newInstance(argumentArray);
0619: } catch (InvocationTargetException e) {
0620: throw new InvokerInvocationException(e);
0621: } catch (IllegalArgumentException e) {
0622: throw createExceptionText("failed to invoke constructor: ",
0623: constructor, argumentArray, e, false);
0624: } catch (IllegalAccessException e) {
0625: throw createExceptionText("could not access constructor: ",
0626: constructor, argumentArray, e, false);
0627: } catch (Exception e) {
0628: throw createExceptionText("failed to invoke constructor: ",
0629: constructor, argumentArray, e, true);
0630: }
0631: }
0632:
0633: private static GroovyRuntimeException createExceptionText(
0634: String init, Constructor constructor,
0635: Object[] argumentArray, Throwable e, boolean setReason) {
0636: throw new GroovyRuntimeException(init + constructor
0637: + " with arguments: "
0638: + InvokerHelper.toString(argumentArray) + " reason: "
0639: + e, setReason ? e : null);
0640: }
0641:
0642: public static Object[] coerceArgumentsToClasses(
0643: Object[] argumentArray, Class[] paramTypes) {
0644: // correct argumentArray's length
0645: if (argumentArray == null) {
0646: argumentArray = EMPTY_ARRAY;
0647: } else if (paramTypes.length == 1 && argumentArray.length == 0) {
0648: if (isVargsMethod(paramTypes, argumentArray))
0649: argumentArray = new Object[] { Array.newInstance(
0650: paramTypes[0].getComponentType(), 0) };
0651: else
0652: argumentArray = ARRAY_WITH_NULL;
0653: } else if (isVargsMethod(paramTypes, argumentArray)) {
0654: argumentArray = fitToVargs(argumentArray, paramTypes);
0655: }
0656:
0657: //correct Type
0658: for (int i = 0; i < argumentArray.length; i++) {
0659: Object argument = argumentArray[i];
0660: if (argument == null)
0661: continue;
0662: Class parameterType = paramTypes[i];
0663: if (parameterType.isInstance(argument))
0664: continue;
0665:
0666: argument = coerceGString(argument, parameterType);
0667: argument = coerceNumber(argument, parameterType);
0668: argument = coerceArray(argument, parameterType);
0669: argumentArray[i] = argument;
0670: }
0671: return argumentArray;
0672: }
0673:
0674: private static Object makeCommonArray(Object[] arguments,
0675: int offset, Class fallback) {
0676: // arguments.leght>0 && !=null
0677: Class baseClass = null;
0678: for (int i = offset; i < arguments.length; i++) {
0679: if (arguments[i] == null)
0680: continue;
0681: Class argClass = arguments[i].getClass();
0682: if (baseClass == null) {
0683: baseClass = argClass;
0684: } else {
0685: for (; baseClass != Object.class; baseClass = baseClass
0686: .getSuperclass()) {
0687: if (baseClass.isAssignableFrom(argClass))
0688: break;
0689: }
0690: }
0691: }
0692: if (baseClass == null) {
0693: // all arguments were null
0694: baseClass = fallback;
0695: }
0696: Object result = makeArray(null, baseClass, arguments.length
0697: - offset);
0698: System.arraycopy(arguments, offset, result, 0, arguments.length
0699: - offset);
0700: return result;
0701: }
0702:
0703: private static Object makeArray(Object obj, Class secondary,
0704: int length) {
0705: Class baseClass = secondary;
0706: if (obj != null) {
0707: baseClass = obj.getClass();
0708: }
0709: /*if (GString.class.isAssignableFrom(baseClass)) {
0710: baseClass = GString.class;
0711: }*/
0712: return Array.newInstance(baseClass, length);
0713: }
0714:
0715: /**
0716: * this method is called when the number of arguments to a method is greater than 1
0717: * and if the method is a vargs method. This method will then transform the given
0718: * arguments to make the method callable
0719: *
0720: * @param argumentArray the arguments used to call the method
0721: * @param paramTypes the types of the paramters the method takes
0722: */
0723: private static Object[] fitToVargs(Object[] argumentArray,
0724: Class[] paramTypes) {
0725: Class vargsClass = autoboxType(paramTypes[paramTypes.length - 1]
0726: .getComponentType());
0727:
0728: if (argumentArray.length == paramTypes.length - 1) {
0729: // the vargs argument is missing, so fill it with an empty array
0730: Object[] newArgs = new Object[paramTypes.length];
0731: System.arraycopy(argumentArray, 0, newArgs, 0,
0732: argumentArray.length);
0733: Object vargs = makeArray(null, vargsClass, 0);
0734: newArgs[newArgs.length - 1] = vargs;
0735: return newArgs;
0736: } else if (argumentArray.length == paramTypes.length) {
0737: // the number of arguments is correct, but if the last argument
0738: // is no array we have to wrap it in a array. if the last argument
0739: // is null, then we don't have to do anything
0740: Object lastArgument = argumentArray[argumentArray.length - 1];
0741: if (lastArgument != null
0742: && !lastArgument.getClass().isArray()) {
0743: // no array so wrap it
0744: Object vargs = makeArray(lastArgument, vargsClass, 1);
0745: System.arraycopy(argumentArray,
0746: argumentArray.length - 1, vargs, 0, 1);
0747: argumentArray[argumentArray.length - 1] = vargs;
0748: return argumentArray;
0749: } else {
0750: // we may have to box the arguemnt!
0751: return argumentArray;
0752: }
0753: } else if (argumentArray.length > paramTypes.length) {
0754: // the number of arguments is too big, wrap all exceeding elements
0755: // in an array, but keep the old elements that are no vargs
0756: Object[] newArgs = new Object[paramTypes.length];
0757: // copy arguments that are not a varg
0758: System.arraycopy(argumentArray, 0, newArgs, 0,
0759: paramTypes.length - 1);
0760: // create a new array for the vargs and copy them
0761: int numberOfVargs = argumentArray.length
0762: - paramTypes.length;
0763: Object vargs = makeCommonArray(argumentArray,
0764: paramTypes.length - 1, vargsClass);
0765: newArgs[newArgs.length - 1] = vargs;
0766: return newArgs;
0767: } else {
0768: throw new GroovyBugError(
0769: "trying to call a vargs method without enough arguments");
0770: }
0771: }
0772:
0773: private static GroovyRuntimeException createExceptionText(
0774: String init, MetaMethod method, Object object,
0775: Object[] args, Throwable reason, boolean setReason) {
0776: return new GroovyRuntimeException(init + method + " on: "
0777: + object + " with arguments: "
0778: + InvokerHelper.toString(args) + " reason: " + reason,
0779: setReason ? reason : null);
0780: }
0781:
0782: public static Object doMethodInvoke(Object object,
0783: MetaMethod method, Object[] argumentArray) {
0784: Class[] paramTypes = method.getParameterTypes();
0785: argumentArray = coerceArgumentsToClasses(argumentArray,
0786: paramTypes);
0787: try {
0788: return method.invoke(object, argumentArray);
0789: } catch (IllegalArgumentException e) {
0790: //TODO: test if this is ok with new MOP, should be changed!
0791: // we don't want the exception being unwrapped if it is a IllegalArgumentException
0792: // but in the case it is for example a IllegalThreadStateException, we want the unwrapping
0793: // from the runtime
0794: //Note: the reason we want unwrapping sometimes and sometimes not is that the method
0795: // invokation tries to invoke the method with and then reacts with type transformation
0796: // if the invokation failed here. This is ok for IllegalArgumentException, but it is
0797: // possible that a Reflector will be used to execute the call and then an Exception from inside
0798: // the method is not wrapped in a InvocationTargetException and we will end here.
0799: boolean setReason = e.getClass() != IllegalArgumentException.class;
0800: throw createExceptionText("failed to invoke method: ",
0801: method, object, argumentArray, e, setReason);
0802: } catch (RuntimeException e) {
0803: throw e;
0804: } catch (Exception e) {
0805: throw createExceptionText("failed to invoke method: ",
0806: method, object, argumentArray, e, true);
0807: }
0808: }
0809:
0810: protected static String getClassName(Object object) {
0811: if (object == null)
0812: return null;
0813: return (object instanceof Class) ? ((Class) object).getName()
0814: : object.getClass().getName();
0815: }
0816:
0817: /**
0818: * Returns a callable object for the given method name on the object.
0819: * The object acts like a Closure in that it can be called, like a closure
0820: * and passed around - though really its a method pointer, not a closure per se.
0821: */
0822: public static Closure getMethodPointer(Object object,
0823: String methodName) {
0824: return new MethodClosure(object, methodName);
0825: }
0826:
0827: public static Class[] getParameterTypes(Object methodOrConstructor) {
0828: if (methodOrConstructor instanceof MetaMethod) {
0829: MetaMethod method = (MetaMethod) methodOrConstructor;
0830: return method.getParameterTypes();
0831: }
0832: if (methodOrConstructor instanceof Method) {
0833: Method method = (Method) methodOrConstructor;
0834: return method.getParameterTypes();
0835: }
0836: if (methodOrConstructor instanceof Constructor) {
0837: Constructor constructor = (Constructor) methodOrConstructor;
0838: return constructor.getParameterTypes();
0839: }
0840: throw new IllegalArgumentException(
0841: "Must be a Method or Constructor");
0842: }
0843:
0844: public static boolean isAssignableFrom(Class classToTransformTo,
0845: Class classToTransformFrom) {
0846: if (classToTransformFrom == null)
0847: return true;
0848: classToTransformTo = autoboxType(classToTransformTo);
0849: classToTransformFrom = autoboxType(classToTransformFrom);
0850:
0851: if (classToTransformTo == classToTransformFrom) {
0852: return true;
0853: }
0854: // note: there is not coercion for boolean and char. Range matters, precision doesn't
0855: else if (classToTransformTo == Integer.class) {
0856: if (classToTransformFrom == Integer.class
0857: || classToTransformFrom == Short.class
0858: || classToTransformFrom == Byte.class
0859: || classToTransformFrom == BigInteger.class)
0860: return true;
0861: } else if (classToTransformTo == Double.class) {
0862: if (classToTransformFrom == Double.class
0863: || classToTransformFrom == Integer.class
0864: || classToTransformFrom == Long.class
0865: || classToTransformFrom == Short.class
0866: || classToTransformFrom == Byte.class
0867: || classToTransformFrom == Float.class
0868: || classToTransformFrom == BigDecimal.class
0869: || classToTransformFrom == BigInteger.class)
0870: return true;
0871: } else if (classToTransformTo == BigDecimal.class) {
0872: if (classToTransformFrom == Double.class
0873: || classToTransformFrom == Integer.class
0874: || classToTransformFrom == Long.class
0875: || classToTransformFrom == Short.class
0876: || classToTransformFrom == Byte.class
0877: || classToTransformFrom == Float.class
0878: || classToTransformFrom == BigDecimal.class
0879: || classToTransformFrom == BigInteger.class)
0880: return true;
0881: } else if (classToTransformTo == BigInteger.class) {
0882: if (classToTransformFrom == Integer.class
0883: || classToTransformFrom == Long.class
0884: || classToTransformFrom == Short.class
0885: || classToTransformFrom == Byte.class
0886: || classToTransformFrom == BigInteger.class)
0887: return true;
0888: } else if (classToTransformTo == Long.class) {
0889: if (classToTransformFrom == Long.class
0890: || classToTransformFrom == Integer.class
0891: || classToTransformFrom == Short.class
0892: || classToTransformFrom == Byte.class)
0893: return true;
0894: } else if (classToTransformTo == Float.class) {
0895: if (classToTransformFrom == Float.class
0896: || classToTransformFrom == Integer.class
0897: || classToTransformFrom == Long.class
0898: || classToTransformFrom == Short.class
0899: || classToTransformFrom == Byte.class)
0900: return true;
0901: } else if (classToTransformTo == Short.class) {
0902: if (classToTransformFrom == Short.class
0903: || classToTransformFrom == Byte.class)
0904: return true;
0905: } else if (classToTransformTo == String.class) {
0906: if (classToTransformFrom == String.class
0907: || GString.class
0908: .isAssignableFrom(classToTransformFrom)) {
0909: return true;
0910: }
0911: }
0912:
0913: return classToTransformTo
0914: .isAssignableFrom(classToTransformFrom);
0915: }
0916:
0917: public static boolean isGenericSetMethod(MetaMethod method) {
0918: return (method.getName().equals("set"))
0919: && method.getParameterTypes().length == 2;
0920: }
0921:
0922: protected static boolean isSuperclass(Class claszz, Class super class) {
0923: while (claszz != null) {
0924: if (claszz == super class)
0925: return true;
0926: claszz = claszz.getSuperclass();
0927: }
0928: return false;
0929: }
0930:
0931: public static boolean isValidMethod(Class[] paramTypes,
0932: Class[] arguments, boolean includeCoerce) {
0933: if (arguments == null) {
0934: return true;
0935: }
0936: int size = arguments.length;
0937:
0938: if ((size >= paramTypes.length || size == paramTypes.length - 1)
0939: && paramTypes.length > 0
0940: && paramTypes[paramTypes.length - 1].isArray()) {
0941: // first check normal number of parameters
0942: for (int i = 0; i < paramTypes.length - 1; i++) {
0943: if (isAssignableFrom(paramTypes[i], arguments[i]))
0944: continue;
0945: return false;
0946: }
0947: // check varged
0948: Class clazz = paramTypes[paramTypes.length - 1]
0949: .getComponentType();
0950: for (int i = paramTypes.length; i < size; i++) {
0951: if (isAssignableFrom(clazz, arguments[i]))
0952: continue;
0953: return false;
0954: }
0955: return true;
0956: } else if (paramTypes.length == size) {
0957: // lets check the parameter types match
0958: for (int i = 0; i < size; i++) {
0959: if (isAssignableFrom(paramTypes[i], arguments[i]))
0960: continue;
0961: return false;
0962: }
0963: return true;
0964: } else if (paramTypes.length == 1 && size == 0) {
0965: return true;
0966: }
0967: return false;
0968:
0969: }
0970:
0971: public static boolean isValidMethod(Object method,
0972: Class[] arguments, boolean includeCoerce) {
0973: Class[] paramTypes = getParameterTypes(method);
0974: return isValidMethod(paramTypes, arguments, includeCoerce);
0975: }
0976:
0977: public static boolean isVargsMethod(Class[] paramTypes,
0978: Object[] arguments) {
0979: if (paramTypes.length == 0)
0980: return false;
0981: if (!paramTypes[paramTypes.length - 1].isArray())
0982: return false;
0983: // -1 because the varg part is optional
0984: if (paramTypes.length - 1 == arguments.length)
0985: return true;
0986: if (paramTypes.length - 1 > arguments.length)
0987: return false;
0988: if (arguments.length > paramTypes.length)
0989: return true;
0990:
0991: // only case left is arguments.length==paramTypes.length
0992: Object last = arguments[arguments.length - 1];
0993: if (last == null)
0994: return true;
0995: Class clazz = last.getClass();
0996: if (clazz.equals(paramTypes[paramTypes.length - 1]))
0997: return false;
0998:
0999: return true;
1000: }
1001:
1002: public static void logMethodCall(Object object, String methodName,
1003: Object[] arguments) {
1004: String className = getClassName(object);
1005: String logname = "methodCalls." + className + "." + methodName;
1006: Logger objLog = Logger.getLogger(logname);
1007: if (!objLog.isLoggable(Level.FINER))
1008: return;
1009: StringBuffer msg = new StringBuffer(methodName);
1010: msg.append("(");
1011: if (arguments != null) {
1012: for (int i = 0; i < arguments.length;) {
1013: msg.append(normalizedValue(arguments[i]));
1014: if (++i < arguments.length) {
1015: msg.append(",");
1016: }
1017: }
1018: }
1019: msg.append(")");
1020: objLog.logp(Level.FINER, className, msg.toString(),
1021: "called from MetaClass.invokeMethod");
1022: }
1023:
1024: protected static String normalizedValue(Object argument) {
1025: String value;
1026: try {
1027: value = argument.toString();
1028: if (value.length() > MAX_ARG_LEN) {
1029: value = value.substring(0, MAX_ARG_LEN - 2) + "..";
1030: }
1031: if (argument instanceof String) {
1032: value = "\'" + value + "\'";
1033: }
1034: } catch (Exception e) {
1035: value = shortName(argument);
1036: }
1037: return value;
1038: }
1039:
1040: public static boolean parametersAreCompatible(Class[] arguments,
1041: Class[] parameters) {
1042: if (arguments.length != parameters.length)
1043: return false;
1044: for (int i = 0; i < arguments.length; i++) {
1045: if (!isAssignableFrom(parameters[i], arguments[i]))
1046: return false;
1047: }
1048: return true;
1049: }
1050:
1051: protected static String shortName(Object object) {
1052: if (object == null || object.getClass() == null)
1053: return "unknownClass";
1054: String name = getClassName(object);
1055: if (name == null)
1056: return "unknownClassName"; // *very* defensive...
1057: int lastDotPos = name.lastIndexOf('.');
1058: if (lastDotPos < 0 || lastDotPos >= name.length() - 1)
1059: return name;
1060: return name.substring(lastDotPos + 1);
1061: }
1062:
1063: public static Class[] wrap(Class[] classes) {
1064: Class[] wrappedArguments = new Class[classes.length];
1065: for (int i = 0; i < wrappedArguments.length; i++) {
1066: Class c = classes[i];
1067: if (c == null)
1068: continue;
1069: if (c.isPrimitive()) {
1070: if (c == Integer.TYPE) {
1071: c = Integer.class;
1072: } else if (c == Byte.TYPE) {
1073: c = Byte.class;
1074: } else if (c == Long.TYPE) {
1075: c = Long.class;
1076: } else if (c == Double.TYPE) {
1077: c = Double.class;
1078: } else if (c == Float.TYPE) {
1079: c = Float.class;
1080: }
1081: } else if (isSuperclass(c, GString.class)) {
1082: c = String.class;
1083: }
1084: wrappedArguments[i] = c;
1085: }
1086: return wrappedArguments;
1087: }
1088: }
|