com.bstek.dorado.data.method.MethodAutoMatchingUtils.java Source code

Java tutorial

Introduction

Here is the source code for com.bstek.dorado.data.method.MethodAutoMatchingUtils.java

Source

/*
 * This file is part of Dorado 7.x (http://dorado7.bsdn.org).
 * 
 * Copyright (c) 2002-2012 BSTEK Corp. All rights reserved.
 * 
 * This file is dual-licensed under the AGPLv3 (http://www.gnu.org/licenses/agpl-3.0.html) 
 * and BSDN commercial (http://www.bsdn.org/licenses) licenses.
 * 
 * If you are unsure which license is appropriate for your use, please contact the sales department
 * at http://www.bstek.com/contact.
 */

package com.bstek.dorado.data.method;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;

import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.MethodUtils;
import org.apache.commons.collections.keyvalue.MultiKey;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.NumberUtils;

import com.bstek.dorado.core.Context;
import com.bstek.dorado.core.resource.ResourceManager;
import com.bstek.dorado.core.resource.ResourceManagerUtils;
import com.bstek.dorado.data.entity.EntityCollection;
import com.bstek.dorado.data.type.AggregationDataType;
import com.bstek.dorado.data.type.DataType;
import com.bstek.dorado.util.proxy.ProxyBeanUtils;
import com.thoughtworks.paranamer.BytecodeReadingParanamer;
import com.thoughtworks.paranamer.CachingParanamer;
import com.thoughtworks.paranamer.Paranamer;

/**
 * ?
 * 
 * @author Benny Bao (mailto:benny.bao@bstek.com)
 * @since Jan 3, 2008
 */
public abstract class MethodAutoMatchingUtils {
    private static final ResourceManager resourceManager = ResourceManagerUtils.get(MethodAutoMatchingUtils.class);

    private static final Class<?>[] EMPTY_TYPES = new Class<?>[0];
    private static final Object[] EMPTY_ARGS = new Object[0];
    private static final String[] EMPTY_NAMES = new String[0];

    private static class MethodInfo {
        private String className;
        private String methodName;

        public MethodInfo(String className, String methodName) {
            this.className = className;
            this.methodName = methodName;
        }

        public String getClassName() {
            return className;
        }

        public String getMethodName() {
            return methodName;
        }
    }

    private static Map<Object, Method[]> methodCache = new Hashtable<Object, Method[]>();
    private static Paranamer paranamer = new CachingParanamer(new BytecodeReadingParanamer());
    private static Collection<ParameterFactory> systemOptionalParameters;

    private static class IgnoreType {
    };

    private static Collection<ParameterFactory> getSystemOptionalParameters() throws Exception {
        if (systemOptionalParameters == null) {
            Context context = Context.getCurrent();
            SystemOptionalParametersFactory systemOptionalParametersFactory = (SystemOptionalParametersFactory) context
                    .getServiceBean("systemOptionalParametersFactory");
            if (systemOptionalParametersFactory != null) {
                systemOptionalParameters = systemOptionalParametersFactory.getOptionalParameters();
            }
            if (systemOptionalParameters == null) {
                systemOptionalParameters = new ArrayList<ParameterFactory>();
            }
        }
        return systemOptionalParameters;
    }

    private static Object getCacheKey(Class<?> cl, String methodName) {
        return new MultiKey(cl, methodName);
    }

    /**
     * ClassClass?-1<br>
     * Class??Class??Class
     * 
     * @param types
     *            Class
     * @param type
     *            ?Class
     * @return ?
     */
    public static int indexOfTypes(Type[] types, Type type) {
        int index = -1;
        for (int i = 0; i < types.length; i++) {
            Type t = types[i];
            if (isTypeAssignableFrom(type, t) > 0) {
                index = i;
                break;
            }
        }
        return index;
    }

    /**
     * ????
     * 
     * @param cl
     *            
     * @param methodName
     *            ??
     * @return 
     */
    public static Method[] getMethodsByName(Class<?> cl, String methodName) {
        cl = ProxyBeanUtils.getProxyTargetType(cl);
        Object cacheKey = getCacheKey(cl, methodName);
        Method[] methods = methodCache.get(cacheKey);
        if (methods == null) {
            List<Method> methodList = new ArrayList<Method>();
            Method[] allMethods = cl.getMethods();
            for (Method method : allMethods) {
                if (method.getName().equals(methodName)) {
                    methodList.add(method);
                }
            }

            methods = new Method[methodList.size()];
            methodList.toArray(methods);
            methodCache.put(cacheKey, methods);
        }
        return methods;
    }

    private static void trimClassTypes(Type[] types) {
        for (int i = 0; i < types.length; i++) {
            Type type = types[i];
            if (type instanceof Class<?>) {
                type = ProxyBeanUtils.getProxyTargetType((Class<?>) type);
            }
            types[i] = type;
        }
    }

    /**
     * ??????<br>
     * ???
     * 
     * @param methods
     *            
     * @param requiredTypes
     *            ???
     * @param exactTypes
     *            ??
     * @param optionalTypes
     *            ??
     * @param returnType
     *            null
     * @return ??
     * @throws MethodAutoMatchingException
     *             ??
     */
    private static MethodDescriptor findMatchingMethodByParameterTypes(Method[] methods, Type[] requiredTypes,
            Type[] exactTypes, Type[] optionalTypes, Type returnType) throws MethodAutoMatchingException {
        if (requiredTypes == null) {
            requiredTypes = EMPTY_TYPES;
        } else {
            trimClassTypes(requiredTypes);
        }

        if (exactTypes == null) {
            exactTypes = EMPTY_TYPES;
        } else {
            trimClassTypes(exactTypes);
        }

        if (optionalTypes == null) {
            optionalTypes = EMPTY_TYPES;
        } else {
            trimClassTypes(optionalTypes);
        }

        MethodDescriptor methodDescriptor = null;
        for (Method method : methods) {
            MethodDescriptor tmpMethodDescriptor = describMethodIfMatching(method, requiredTypes, exactTypes,
                    optionalTypes, returnType);
            if (tmpMethodDescriptor != null) {
                if (methodDescriptor != null) {
                    int matchingRate = methodDescriptor.getMatchingRate();
                    int tmpMatchingRate = tmpMethodDescriptor.getMatchingRate();
                    if (matchingRate == tmpMatchingRate) {
                        String detail = getExceptionMessage("dorado.common/matchingMethodByTypesDetail", methods,
                                requiredTypes, exactTypes, optionalTypes, returnType);
                        throw new MoreThanOneMethodsMatchsException(
                                resourceManager.getString("dorado.common/tooMoreMatchingMethodsErrorHeader"),
                                detail);
                    } else if (tmpMatchingRate < matchingRate) {
                        continue;
                    }
                }
                methodDescriptor = tmpMethodDescriptor;
            }
        }

        if (methodDescriptor == null) {
            String detail = getExceptionMessage("dorado.common/matchingMethodByTypesDetail", methods, requiredTypes,
                    exactTypes, optionalTypes, returnType);
            throw new MethodAutoMatchingException(
                    resourceManager.getString("dorado.common/noMatchingMethodErrorHeader"), detail);
        }
        return methodDescriptor;
    }

    private static Type toNonPrimitiveClass(Type type) {
        if (type instanceof Class<?>) {
            return MethodUtils.toNonPrimitiveClass((Class<?>) type);
        }
        return type;
    }

    private static MethodDescriptor describMethodIfMatching(Method method, Type[] requiredTypes, Type[] exactTypes,
            Type[] optionalTypes, Type returnType) {
        Type[] argTypes = method.getGenericParameterTypes();
        if (argTypes.length > (requiredTypes.length + exactTypes.length + optionalTypes.length)) {
            return null;
        }

        int[] argIndexs = new int[argTypes.length];
        int[] argMatchingRates = new int[argTypes.length];
        for (int i = 0; i < argIndexs.length; i++) {
            argIndexs[i] = -1;
            argMatchingRates[i] = 0;
        }

        Type[] requiredTypeArray = new Type[requiredTypes.length];
        Type[] exactTypeArray = new Type[exactTypes.length];
        Type[] optionalTypeArray = new Type[optionalTypes.length];
        System.arraycopy(requiredTypes, 0, requiredTypeArray, 0, requiredTypes.length);
        System.arraycopy(exactTypes, 0, exactTypeArray, 0, exactTypes.length);
        System.arraycopy(optionalTypes, 0, optionalTypeArray, 0, optionalTypes.length);

        // 
        if (returnType != null && !returnType.equals(IgnoreType.class)) {
            Type methodReturnType = method.getGenericReturnType();
            if (isTypeAssignableFrom(returnType, methodReturnType) == 0) {
                methodReturnType = toNonPrimitiveClass(methodReturnType);
                if (isTypeAssignableFrom(returnType, methodReturnType) == 0) {
                    return null;
                }
            }
        }

        // ?
        for (int i = 0; i < requiredTypeArray.length; i++) {
            Type requiredType = requiredTypeArray[i];
            int matchingArg = -1;
            for (int j = 0; j < argTypes.length; j++) {
                Type argType = argTypes[j];
                if (isTypeAssignableFrom(argType, requiredType) > 0) {
                    if (matchingArg == -1) {
                        matchingArg = j;
                    } else {
                        Type conflictType = argTypes[matchingArg];
                        if (argType.equals(conflictType)) {
                            // ????
                            return null;
                        } else if (isTypeAssignableFrom(conflictType, argType) > 0) {
                            matchingArg = j;
                        }
                    }
                }
            }

            // ?
            if (matchingArg != -1) {
                argIndexs[matchingArg] = i;
                argMatchingRates[matchingArg] = 10;
            } else {
                return null;
            }
        }

        // ??
        for (int i = 0; i < exactTypeArray.length; i++) {
            Type exactType = exactTypeArray[i];
            int matchingArg = -1;
            for (int j = 0; j < argTypes.length; j++) {
                Type argType = argTypes[j];
                if (isTypeAssignableFrom(exactType, argType) > 0) {
                    if (matchingArg == -1) {
                        matchingArg = j;
                    } else {
                        // ?????
                        return null;
                    }
                }
            }

            // ??
            if (matchingArg != -1) {
                if (argMatchingRates[matchingArg] == 0) {
                    argIndexs[matchingArg] = requiredTypeArray.length + i;
                    argMatchingRates[matchingArg] = 9;
                } else {
                    // ?????
                    return null;
                }
            }
        }

        // ???
        int conflictArg = -1, matchingRate = 1000;
        for (int i = 0; i < argTypes.length; i++) {
            if (argMatchingRates[i] == 0) {
                Type argType = argTypes[i];
                for (int j = 0; j < optionalTypeArray.length; j++) {
                    Type optionalType = optionalTypeArray[j];
                    if (optionalType != null) {
                        int rate = isTypeAssignableFrom(argType, optionalType);
                        if (rate == 0) {
                            rate = isTypesCompatible(argType, optionalType);
                        }
                        if (rate > 0) {
                            int originMatchingRate = argMatchingRates[i];
                            if (originMatchingRate == 0) {
                                argIndexs[i] = requiredTypeArray.length + exactTypeArray.length + j;
                                argMatchingRates[i] = rate;
                                matchingRate += (rate * 2);
                            } else if (conflictArg != -1) {
                                // ????
                                return null;
                            } else if (originMatchingRate > rate) {
                                matchingRate -= (5 - (originMatchingRate - rate)); // 
                            } else if (rate > originMatchingRate) {
                                argIndexs[i] = requiredTypeArray.length + exactTypeArray.length + j;
                                argMatchingRates[i] = rate;
                                matchingRate -= (5 - (rate - originMatchingRate)); // 
                            } else {
                                // ????
                                argIndexs[i] = -1;
                                conflictArg = i;
                                matchingRate -= (argTypes.length * 10);
                            }
                        }
                    }
                }

                if (argIndexs[i] != -1) {
                    optionalTypeArray[argIndexs[i] - exactTypeArray.length - requiredTypeArray.length] = null;
                }
            }
        }

        // ????
        if (conflictArg != -1) {
            Type argType = argTypes[conflictArg];
            for (int i = 0; i < optionalTypeArray.length; i++) {
                Type optionalType = optionalTypeArray[i];
                if (optionalType != null && isTypeAssignableFrom(argType, optionalType) > 0) {
                    if (argIndexs[conflictArg] == -1) {
                        argIndexs[conflictArg] = requiredTypeArray.length + i;
                    } else {
                        return null;
                    }
                }
            }
        }

        int undetermine = 0, undetermineIndex = -1;
        for (int i = 0; i < argIndexs.length; i++) {
            if (argIndexs[i] == -1) {
                undetermine++;
                undetermineIndex = i;
            }
        }

        // ??????
        if (undetermine == 1 && optionalTypes.length == 1) {
            Type argType = argTypes[undetermineIndex];
            if (isSimpleType(argType) && isSimpleType(optionalTypes[0])) {
                argIndexs[undetermineIndex] = requiredTypeArray.length + exactTypeArray.length;
                undetermine = 0;
                matchingRate -= 200;
            }
        }

        if (undetermine > 0) {
            return null;
        }
        return new MethodDescriptor(method, argIndexs, matchingRate);
    }

    public static boolean isSimpleType(Type type) {
        Class<?> cl = toClass(type);
        return (String.class.equals(cl) || cl.isPrimitive() || Boolean.class.equals(cl)
                || Number.class.isAssignableFrom(cl) || cl.isEnum());
    }

    public static String[] getParameterNames(Method method) throws SecurityException, NoSuchMethodException {
        Class<?> declaringClass = method.getDeclaringClass();
        if (ProxyBeanUtils.isProxy(declaringClass)) {
            Class<?> targetType = ProxyBeanUtils.getProxyTargetType(declaringClass);
            method = targetType.getMethod(method.getName(), method.getParameterTypes());
        }
        return paranamer.lookupParameterNames(method);
    }

    private static MethodDescriptor describMethodIfMatching(Method method, String[] requiredParameterNames,
            String[] optionalParameterNames, String[] extraParameterNames)
            throws SecurityException, NoSuchMethodException {
        String[] methodParameterNames = getParameterNames(method);
        if (methodParameterNames.length > requiredParameterNames.length + optionalParameterNames.length) {
            return null;
        }

        int rate = 1000;
        int[] argIndexs = new int[methodParameterNames.length];
        for (int i = 0; i < argIndexs.length; i++) {
            argIndexs[i] = -1;
        }

        for (int i = 0; i < requiredParameterNames.length; i++) {
            int index = ArrayUtils.indexOf(methodParameterNames, requiredParameterNames[i]);
            if (index < 0) {
                return null;
            }
            argIndexs[index] = i;
        }

        for (int i = 0; i < argIndexs.length; i++) {
            if (argIndexs[i] == -1) {
                String parameterName = methodParameterNames[i];
                int index = ArrayUtils.indexOf(optionalParameterNames, parameterName);
                if (index < 0) {
                    index = ArrayUtils.indexOf(extraParameterNames, parameterName);
                    if (index >= 0) {
                        argIndexs[i] = requiredParameterNames.length + optionalParameterNames.length + index;
                        rate += 90;
                        continue;
                    }
                    return null;
                }
                argIndexs[i] = requiredParameterNames.length + index;
                rate += 100;
            }
        }
        return new MethodDescriptor(method, argIndexs, rate);
    }

    /**
     * ?????<br>
     * ???
     * 
     * @param methods
     *            
     * @param requiredParameterNames
     *            ?????
     * @param optionalParameterNames
     *            ????
     * @return ??
     * @throws MethodAutoMatchingException
     *             ??
     */
    private static MethodDescriptor findMatchingMethodByParameterNames(Method[] methods,
            String[] requiredParameterNames, String[] optionalParameterNames, String[] extraParameterNames)
            throws MethodAutoMatchingException, SecurityException, NoSuchMethodException {
        if (requiredParameterNames == null) {
            requiredParameterNames = EMPTY_NAMES;
        }
        if (optionalParameterNames == null) {
            optionalParameterNames = EMPTY_NAMES;
        }
        if (extraParameterNames == null) {
            extraParameterNames = EMPTY_NAMES;
        }

        MethodDescriptor methodDescriptor = null;
        for (Method method : methods) {
            MethodDescriptor tmpMethodDescriptor = describMethodIfMatching(method, requiredParameterNames,
                    optionalParameterNames, extraParameterNames);
            if (tmpMethodDescriptor != null) {
                if (methodDescriptor != null) {
                    int matchingRate = methodDescriptor.getMatchingRate();
                    int tmpMatchingRate = tmpMethodDescriptor.getMatchingRate();
                    if (matchingRate == tmpMatchingRate) {
                        String detail = getExceptionMessage("dorado.common/matchingMethodByNamesDetail", methods,
                                requiredParameterNames, optionalParameterNames, extraParameterNames);
                        throw new MethodAutoMatchingException(
                                resourceManager.getString("dorado.common/tooMoreMatchingMethodsErrorHeader"),
                                detail);
                    } else if (tmpMatchingRate < matchingRate) {
                        continue;
                    }
                }
                methodDescriptor = tmpMethodDescriptor;
            }
        }

        if (methodDescriptor == null) {
            String detail = getExceptionMessage("dorado.common/matchingMethodByNamesDetail", methods,
                    requiredParameterNames, optionalParameterNames, extraParameterNames);
            throw new MethodAutoMatchingException(
                    resourceManager.getString("dorado.common/noMatchingMethodErrorHeader"), detail);
        }
        return methodDescriptor;
    }

    /**
     * ???
     * 
     * @param methodDescriptor
     *            ??
     * @param object
     *            ??
     * @param parameters
     *            ??
     * @return ?
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    private static Object invokeMethod(MethodDescriptor methodDescriptor, Object object, Object[] parameters)
            throws Exception {
        Method method = methodDescriptor.getMethod();
        Class<?>[] parameterTypes = method.getParameterTypes();
        int[] argIndexs = methodDescriptor.getArgIndexs();
        Object[] realArgs = new Object[argIndexs.length];
        for (int i = 0; i < argIndexs.length; i++) {
            Object arg = parameters[argIndexs[i]];
            if (arg != null) {
                Class<?> defType = parameterTypes[i];
                Class<?> argType = ProxyBeanUtils.getProxyTargetType(arg.getClass());
                if (!defType.isAssignableFrom(argType)) {
                    if (Number.class.isAssignableFrom(defType) && Number.class.isAssignableFrom(argType)) {
                        arg = NumberUtils.convertNumberToTargetClass((Number) arg,
                                (Class<? extends Number>) defType);
                    } else if (isSimpleType(defType) || isSimpleType(argType)) {
                        arg = ConvertUtils.convert(arg, defType);
                    }
                }
            }
            realArgs[i] = arg;
        }
        return method.invoke(object, realArgs);
    }

    /**
     * ??????
     * 
     * @param methods
     *            
     * @param object
     *            
     * @param requiredParameterTypes
     *            ???
     * @param requiredParameters
     *            ?
     * @param exactParameterTypes
     *            ??
     * @param exactParameters
     *            ??
     * @param optionalParameterTypes
     *            ??
     * @param optionalParameters
     *            ??
     * @param returnType
     *            null
     * @return ?
     * @throws MethodAutoMatchingException
     * @throws Exception
     */
    public static Object invokeMethod(Method[] methods, Object object, Type[] requiredParameterTypes,
            Object[] requiredParameters, Type[] exactParameterTypes, Object[] exactParameters,
            Type[] optionalParameterTypes, Object[] optionalParameters, Type returnType)
            throws MethodAutoMatchingException, Exception {
        Collection<ParameterFactory> systemOptionalParameters = getSystemOptionalParameters();
        if (!systemOptionalParameters.isEmpty()) {
            if (exactParameterTypes != null) {
                Class<?>[] newExactParameterTypes = new Class<?>[exactParameterTypes.length
                        + systemOptionalParameters.size()];
                System.arraycopy(exactParameterTypes, 0, newExactParameterTypes, 0, exactParameterTypes.length);

                Object[] newExactParameters = new Object[exactParameters.length + systemOptionalParameters.size()];
                System.arraycopy(exactParameters, 0, newExactParameters, 0, exactParameters.length);

                int i = exactParameters.length;
                for (ParameterFactory parameterFactory : systemOptionalParameters) {
                    newExactParameterTypes[i] = parameterFactory.getParameterType();
                    newExactParameters[i] = parameterFactory.getParameter();
                    i++;
                }

                exactParameterTypes = newExactParameterTypes;
                exactParameters = newExactParameters;
            } else {
                exactParameterTypes = new Class<?>[systemOptionalParameters.size()];
                exactParameters = new Object[systemOptionalParameters.size()];

                int i = 0;
                for (ParameterFactory parameterFactory : systemOptionalParameters) {
                    exactParameterTypes[i] = parameterFactory.getParameterType();
                    exactParameters[i] = parameterFactory.getParameter();
                    i++;
                }
            }
        }

        MethodDescriptor methodDescriptor = findMatchingMethodByParameterTypes(methods, requiredParameterTypes,
                exactParameterTypes, optionalParameterTypes, returnType);

        if (requiredParameters == null) {
            requiredParameters = EMPTY_ARGS;
        }
        if (exactParameters == null) {
            exactParameters = EMPTY_ARGS;
        }
        if (optionalParameters == null) {
            optionalParameters = EMPTY_ARGS;
        }

        Object[] args = new Object[requiredParameters.length + exactParameters.length + optionalParameters.length];
        System.arraycopy(requiredParameters, 0, args, 0, requiredParameters.length);
        System.arraycopy(exactParameters, 0, args, requiredParameters.length, exactParameters.length);
        System.arraycopy(optionalParameters, 0, args, requiredParameters.length + exactParameters.length,
                optionalParameters.length);
        return invokeMethod(methodDescriptor, object, args);
    }

    /**
     * ?????<br>
     * ???
     * 
     * @param methods
     *            
     * @param object
     *            
     * @param requiredParameterNames
     *            ???
     * @param requiredParameters
     *            ?
     * @param optionalParameterNames
     *            ????
     * @param optionalParameters
     *            ??
     * @return ?
     * @throws optionalParameterNames
     * @throws Exception
     */
    public static Object invokeMethod(Method[] methods, Object object, String[] requiredParameterNames,
            Object[] requiredParameters, String[] optionalParameterNames, Object[] optionalParameters,
            String[] extraParameterNames, Object[] extraParameters) throws MethodAutoMatchingException, Exception {
        Collection<ParameterFactory> systemOptionalParameters = getSystemOptionalParameters();
        if (!systemOptionalParameters.isEmpty()) {
            if (optionalParameterNames != null) {
                String[] newOptionalParameterNames = new String[optionalParameterNames.length
                        + systemOptionalParameters.size()];
                System.arraycopy(optionalParameterNames, 0, newOptionalParameterNames, 0,
                        optionalParameterNames.length);

                Object[] newOptionalParameters = new Object[optionalParameters.length
                        + systemOptionalParameters.size()];
                System.arraycopy(optionalParameters, 0, newOptionalParameters, 0, optionalParameters.length);

                int i = optionalParameterNames.length;
                for (ParameterFactory parameterFactory : systemOptionalParameters) {
                    newOptionalParameterNames[i] = parameterFactory.getParameterName();
                    newOptionalParameters[i] = parameterFactory.getParameter();
                    i++;
                }

                optionalParameterNames = newOptionalParameterNames;
                optionalParameters = newOptionalParameters;
            } else {
                optionalParameterNames = new String[systemOptionalParameters.size()];
                optionalParameters = new Object[systemOptionalParameters.size()];

                int i = 0;
                for (ParameterFactory parameterFactory : systemOptionalParameters) {
                    optionalParameterNames[i] = parameterFactory.getParameterName();
                    optionalParameters[i] = parameterFactory.getParameter();
                    i++;
                }
            }
        }

        MethodDescriptor methodDescriptor = findMatchingMethodByParameterNames(methods, requiredParameterNames,
                optionalParameterNames, extraParameterNames);

        if (requiredParameters == null) {
            requiredParameters = EMPTY_ARGS;
        }
        if (optionalParameters == null) {
            optionalParameters = EMPTY_ARGS;
        }
        if (extraParameters == null) {
            extraParameters = EMPTY_ARGS;
        }

        Object[] args = new Object[requiredParameters.length + optionalParameters.length + extraParameters.length];
        System.arraycopy(requiredParameters, 0, args, 0, requiredParameters.length);
        System.arraycopy(optionalParameters, 0, args, requiredParameters.length, optionalParameters.length);
        System.arraycopy(extraParameters, 0, args, requiredParameters.length + optionalParameters.length,
                extraParameters.length);
        return invokeMethod(methodDescriptor, object, args);
    }

    @SuppressWarnings("unchecked")
    public static Type getTypeForMatching(Object object) {
        if (object == null) {
            return null;
        } else if (object instanceof EntityCollection) {
            AggregationDataType dataType = ((EntityCollection<?>) object).getDataType();
            if (dataType != null) {
                DataType elementDataType = dataType.getElementDataType();
                if (elementDataType != null) {
                    Class<?> elementType = elementDataType.getCreationType();
                    if (elementType == null || Object.class.equals(elementType)) {
                        elementType = elementDataType.getMatchType();
                    }
                    if (elementType != null && !Object.class.equals(elementType)) {
                        return new ParameterizedCollectionType(
                                (Class<Collection<?>>) ProxyBeanUtils.getProxyTargetType(object.getClass()),
                                elementType);
                    }
                }
            }
        }
        return ProxyBeanUtils.getProxyTargetType(object.getClass());
    }

    @SuppressWarnings("unchecked")
    public static Type getTypeForMatching(DataType dataType) {
        if (dataType instanceof AggregationDataType) {
            AggregationDataType aggDataType = (AggregationDataType) dataType;
            Class<?> aggType = aggDataType.getCreationType();
            if (aggType == null) {
                aggType = List.class;
            }

            DataType elementDataType = aggDataType.getElementDataType();
            if (elementDataType != null) {

                Class<?> elementType = elementDataType.getCreationType();
                if (elementType == null || Object.class.equals(elementType)) {
                    elementType = elementDataType.getMatchType();
                }
                if (elementType != null && !Object.class.equals(elementType)) {
                    return new ParameterizedCollectionType((Class<Collection<?>>) aggType, elementType);
                }
            }
            return aggType;
        } else {
            Type type = dataType.getMatchType();
            if (type == null) {
                type = Map.class;
            }
            return type;
        }
    }

    private static Class<?> toClass(Type type) {
        if (type instanceof Class<?>) {
            return (Class<?>) type;
        } else {
            try {
                if (type instanceof ParameterizedType) {
                    return (Class<?>) ((ParameterizedType) type).getRawType();
                } else if (type instanceof ParameterizedCollectionType) {
                    return ((ParameterizedCollectionType) type).getCollectionType();
                }
            } catch (ClassCastException e) {
                // do nothing
            }
            String message = resourceManager.getString("dorado.common/unsupportedArgType", type.toString());
            throw new IllegalArgumentException(message);
        }
    }

    private static Class<?> getElementType(Type type) {
        if (type instanceof ParameterizedType) {
            ParameterizedType paramedType = (ParameterizedType) type;
            if (Collection.class.isAssignableFrom(toClass(paramedType.getRawType()))) {
                return toClass(paramedType.getActualTypeArguments()[0]);
            }
        } else if (type instanceof ParameterizedCollectionType) {
            return ((ParameterizedCollectionType) type).getElementType();
        }
        return null;
    }

    private static int isTypeAssignableFrom(Type targetType, Type sourceType) {
        Class<?> targetClass = toClass(targetType);
        Class<?> sourceClass = toClass(sourceType);

        int rate = 5;
        if (!targetType.equals(sourceType)) {
            rate = 4;
            boolean b = targetClass.isAssignableFrom(sourceClass);
            if (b) {
                Class<?> targetElementClass = getElementType(targetType);
                if (targetElementClass != null) {
                    Class<?> sourceElementClass = getElementType(sourceType);
                    if (sourceElementClass != null) {
                        return isTypeAssignableFrom(targetElementClass, sourceElementClass);
                    }
                }
            } else if (targetClass.isPrimitive()) {
                targetClass = MethodUtils.toNonPrimitiveClass(targetClass);
                b = targetClass.isAssignableFrom(sourceClass);
            }

            if (!b) {
                b = Number.class.isAssignableFrom(targetClass) && Number.class.isAssignableFrom(sourceClass);
            }
            if (!b) {
                rate = 0;
            }
        }
        return rate;
    }

    private static int isTypesCompatible(Type targetType, Type sourceType) {
        Class<?> targetClass = toClass(targetType);
        Class<?> sourceClass = toClass(sourceType);

        boolean b = targetClass.isAssignableFrom(sourceClass) || sourceClass.isAssignableFrom(targetClass);
        if (!b && (targetClass.isPrimitive() || sourceClass.isPrimitive())) {
            targetClass = MethodUtils.toNonPrimitiveClass(targetClass);
            sourceClass = MethodUtils.toNonPrimitiveClass(sourceClass);
            b = targetClass.isAssignableFrom(sourceClass) || sourceClass.isAssignableFrom(targetClass);
        }
        if (!b) {
            b = Number.class.isAssignableFrom(targetClass) && Number.class.isAssignableFrom(sourceClass);
        }
        return (b) ? 1 : 0;
    }

    private static String getClassName(Type type) {
        return (type instanceof Class) ? ((Class<?>) type).getName() : type.toString();
    }

    private static String getExceptionMessage(String resourceKey, Method[] methods, Type[] requiredTypes,
            Type[] exactTypes, Type[] optionalTypes, Type returnType) {
        MethodInfo methodInfo = getMethodInfo(methods);

        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < requiredTypes.length; i++) {
            if (i > 0) {
                sb.append(",");
            }
            Type argType = requiredTypes[i];
            if (argType != null) {
                sb.append(getClassName(argType));
            } else {
                sb.append("*");
            }
        }
        String required = sb.toString();

        sb.setLength(0);
        for (int i = 0; i < exactTypes.length; i++) {
            if (i > 0) {
                sb.append(",");
            }
            Type argType = exactTypes[i];
            if (argType != null) {
                sb.append(getClassName(argType));
            } else {
                sb.append("*");
            }
        }
        String exact = sb.toString();

        sb.setLength(0);
        for (int i = 0; i < optionalTypes.length; i++) {
            if (i > 0) {
                sb.append(",");
            }
            Type argType = optionalTypes[i];
            if (argType != null) {
                sb.append(getClassName(argType));
            } else {
                sb.append("*");
            }
        }
        String optional = sb.toString();

        return resourceManager.getString(resourceKey, methodInfo.getClassName(), methodInfo.getMethodName(),
                required, exact, optional, ((returnType != null) ? returnType.toString() : "*"));
    }

    private static String getExceptionMessage(String resourceKey, Method[] methods, String[] requiredParameterNames,
            String[] optionalParameterNames, String[] extraParameterNames) {
        MethodInfo methodInfo = getMethodInfo(methods);
        return resourceManager.getString(resourceKey, methodInfo.getClassName(), methodInfo.getMethodName(),
                StringUtils.join(requiredParameterNames, ','), StringUtils.join(optionalParameterNames, ','),
                StringUtils.join(extraParameterNames, ','));
    }

    private static MethodInfo getMethodInfo(Method[] methods) {
        boolean classIsSame = true;
        boolean methodNameIsSame = true;
        Class<?> cl = null;
        String methodName = null;
        for (Method method : methods) {
            if (classIsSame) {
                Class<?> declaringClass = method.getDeclaringClass();
                if (cl == null) {
                    cl = declaringClass;
                } else if (!declaringClass.equals(cl)) {
                    if (declaringClass.isAssignableFrom(cl)) {
                        cl = declaringClass;
                    } else if (cl.isAssignableFrom(declaringClass)) {
                        // do nothing
                    } else {
                        classIsSame = false;
                    }
                }
            }

            if (methodNameIsSame) {
                if (methodName == null) {
                    methodName = method.getName();
                } else if (!method.getName().equals(methodName)) {
                    methodNameIsSame = false;
                }
            }
        }

        String className = cl.getName();
        if (!classIsSame) {
            className += "*";
        }

        if (!methodNameIsSame) {
            methodName += "*";
        }
        return new MethodInfo(className, methodName);
    }
}