All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.espertech.esper.util.MethodResolver Maven / Gradle / Ivy

/*
 ***************************************************************************************
 *  Copyright (C) 2006 EsperTech, Inc. All rights reserved.                            *
 *  http://www.espertech.com/esper                                                     *
 *  http://www.espertech.com                                                           *
 *  ---------------------------------------------------------------------------------- *
 *  The software in this package is published under the terms of the GPL license       *
 *  a copy of which has been included with this distribution in the license.txt file.  *
 ***************************************************************************************
 */
package com.espertech.esper.util;

import com.espertech.esper.client.EventBean;
import com.espertech.esper.client.hook.EPLMethodInvocationContext;
import com.espertech.esper.epl.core.EngineNoSuchCtorException;
import com.espertech.esper.epl.core.EngineNoSuchMethodException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * Used for retrieving static and instance method objects. It
 * provides two points of added functionality over the standard
 * java.lang.reflect mechanism of retrieving methods. First,
 * class names can be partial, and if the class name is partial
 * then java.lang is searched for the class. Second,
 * invocation parameter types don't have to match the declaration
 * parameter types exactly when the standard java conversion
 * mechanisms (currently autoboxing and widening conversions)
 * will make the invocation valid. Preference is given to those
 * methods that require the fewest widening conversions.
 */
public class MethodResolver {
    private static final Logger log = LoggerFactory.getLogger(MethodResolver.class);

    private static final Map> WIDENING_CONVERSIONS = new HashMap>();
    private static final Map> WRAPPING_CONVERSIONS = new HashMap>();

    static {
        // Initialize the map of wrapper conversions
        Set booleanWrappers = new HashSet();
        booleanWrappers.add(boolean.class);
        booleanWrappers.add(Boolean.class);
        WRAPPING_CONVERSIONS.put(boolean.class, booleanWrappers);
        WRAPPING_CONVERSIONS.put(Boolean.class, booleanWrappers);

        Set charWrappers = new HashSet();
        charWrappers.add(char.class);
        charWrappers.add(Character.class);
        WRAPPING_CONVERSIONS.put(char.class, charWrappers);
        WRAPPING_CONVERSIONS.put(Character.class, charWrappers);

        Set byteWrappers = new HashSet();
        byteWrappers.add(byte.class);
        byteWrappers.add(Byte.class);
        WRAPPING_CONVERSIONS.put(byte.class, byteWrappers);
        WRAPPING_CONVERSIONS.put(Byte.class, byteWrappers);

        Set shortWrappers = new HashSet();
        shortWrappers.add(short.class);
        shortWrappers.add(Short.class);
        WRAPPING_CONVERSIONS.put(short.class, shortWrappers);
        WRAPPING_CONVERSIONS.put(Short.class, shortWrappers);

        Set intWrappers = new HashSet();
        intWrappers.add(int.class);
        intWrappers.add(Integer.class);
        WRAPPING_CONVERSIONS.put(int.class, intWrappers);
        WRAPPING_CONVERSIONS.put(Integer.class, intWrappers);

        Set longWrappers = new HashSet();
        longWrappers.add(long.class);
        longWrappers.add(Long.class);
        WRAPPING_CONVERSIONS.put(long.class, longWrappers);
        WRAPPING_CONVERSIONS.put(Long.class, longWrappers);

        Set floatWrappers = new HashSet();
        floatWrappers.add(float.class);
        floatWrappers.add(Float.class);
        WRAPPING_CONVERSIONS.put(float.class, floatWrappers);
        WRAPPING_CONVERSIONS.put(Float.class, floatWrappers);

        Set doubleWrappers = new HashSet();
        doubleWrappers.add(double.class);
        doubleWrappers.add(Double.class);
        WRAPPING_CONVERSIONS.put(double.class, doubleWrappers);
        WRAPPING_CONVERSIONS.put(Double.class, doubleWrappers);

        // Initialize the map of widening conversions
        Set wideningConversions = new HashSet(byteWrappers);
        MethodResolver.WIDENING_CONVERSIONS.put(short.class, new HashSet(wideningConversions));
        MethodResolver.WIDENING_CONVERSIONS.put(Short.class, new HashSet(wideningConversions));

        wideningConversions.addAll(shortWrappers);
        wideningConversions.addAll(charWrappers);
        MethodResolver.WIDENING_CONVERSIONS.put(int.class, new HashSet(wideningConversions));
        MethodResolver.WIDENING_CONVERSIONS.put(Integer.class, new HashSet(wideningConversions));

        wideningConversions.addAll(intWrappers);
        MethodResolver.WIDENING_CONVERSIONS.put(long.class, new HashSet(wideningConversions));
        MethodResolver.WIDENING_CONVERSIONS.put(Long.class, new HashSet(wideningConversions));

        wideningConversions.addAll(longWrappers);
        MethodResolver.WIDENING_CONVERSIONS.put(float.class, new HashSet(wideningConversions));
        MethodResolver.WIDENING_CONVERSIONS.put(Float.class, new HashSet(wideningConversions));

        wideningConversions.addAll(floatWrappers);
        MethodResolver.WIDENING_CONVERSIONS.put(double.class, new HashSet(wideningConversions));
        MethodResolver.WIDENING_CONVERSIONS.put(Double.class, new HashSet(wideningConversions));
    }

    /**
     * Returns the allowable widening conversions.
     *
     * @return map where key is the class that we are asking to be widened into, and
     * a set of classes that can be widened from
     */
    public static Map> getWideningConversions() {
        return WIDENING_CONVERSIONS;
    }

    /**
     * Attempts to find the static or instance method described by the parameters,
     * or a method of the same name that will accept the same type of
     * parameters.
     *
     * @param declaringClass         - the class to search for the method
     * @param methodName             - the name of the method
     * @param paramTypes             - the parameter types for the method
     * @param allowInstance          - true to allow instance methods as well, false to allow only static method
     * @param allowEventBeanCollType whether event-bean-collection parameter type is allowed
     * @param allowEventBeanType     whether event-bean parameter type is allowed
     * @return - the Method object for this method
     * @throws EngineNoSuchMethodException if the method could not be found
     */
    public static Method resolveMethod(Class declaringClass, String methodName, Class[] paramTypes, boolean allowInstance, boolean[] allowEventBeanType, boolean[] allowEventBeanCollType)
            throws EngineNoSuchMethodException {
        // Get all the methods for this class
        Method[] methods = declaringClass.getMethods();

        Method bestMatch = null;
        int bestConversionCount = -1;

        // Examine each method, checking if the signature is compatible
        Method conversionFailedMethod = null;
        for (Method method : methods) {
            // Check the modifiers: we only want public and static, if required
            if (!isPublicAndStatic(method, allowInstance)) {
                continue;
            }

            // Check the name
            if (!method.getName().equals(methodName)) {
                continue;
            }

            // Check the parameter list
            int conversionCount = compareParameterTypesAllowContext(method.getParameterTypes(), paramTypes, allowEventBeanType, allowEventBeanCollType, method.getGenericParameterTypes(), method.isVarArgs());

            // Parameters don't match
            if (conversionCount == -1) {
                conversionFailedMethod = method;
                continue;
            }

            // Parameters match exactly
            if (conversionCount == 0) {
                bestMatch = method;
                break;
            }

            // No previous match
            if (bestMatch == null) {
                bestMatch = method;
                bestConversionCount = conversionCount;
            } else {
                // Current match is better
                if (conversionCount < bestConversionCount) {
                    bestMatch = method;
                    bestConversionCount = conversionCount;
                }
            }

        }

        if (bestMatch != null) {
            logWarnBoxedToPrimitiveType(declaringClass, methodName, bestMatch, paramTypes);
            return bestMatch;
        }

        StringBuilder parameters = new StringBuilder();
        if (paramTypes != null && paramTypes.length != 0) {
            String appendString = "";
            for (Object param : paramTypes) {
                parameters.append(appendString);
                if (param == null) {
                    parameters.append("(null)");
                } else {
                    parameters.append(param.toString());
                }
                appendString = ", ";
            }
        }
        throw new EngineNoSuchMethodException("Unknown method " + declaringClass.getSimpleName() + '.' + methodName + '(' + parameters + ')', conversionFailedMethod);
    }

    private static void logWarnBoxedToPrimitiveType(Class declaringClass, String methodName, Method bestMatch, Class[] paramTypes) {
        Class[] parametersMethod = bestMatch.getParameterTypes();
        for (int i = 0; i < parametersMethod.length; i++) {
            if (!parametersMethod[i].isPrimitive()) {
                continue;
            }
            // if null-type parameter, or non-JDK class and boxed type matches
            if (paramTypes[i] == null ||
                    (!declaringClass.getClass().getName().startsWith("java") &&
                            (JavaClassHelper.getBoxedType(parametersMethod[i])) == paramTypes[i])) {
                String paramTypeStr = paramTypes[i] == null ? "null" : paramTypes[i].getSimpleName();
                log.info("Method '" + methodName + "' in class '" + declaringClass.getName() + "' expects primitive type '" + parametersMethod[i] +
                        "' as parameter " + i + ", but receives a nullable (boxed) type " + paramTypeStr +
                        ". This may cause null pointer exception at runtime if the actual value is null, please consider using boxed types for method parameters.");
                return;
            }
        }
    }

    private static boolean isWideningConversion(Class declarationType, Class invocationType) {
        if (WIDENING_CONVERSIONS.containsKey(declarationType)) {
            return WIDENING_CONVERSIONS.get(declarationType).contains(invocationType);
        } else {
            return false;
        }
    }

    private static boolean isPublicAndStatic(Method method, boolean allowInstance) {
        int modifiers = method.getModifiers();
        if (allowInstance) {
            return Modifier.isPublic(modifiers);
        } else {
            return Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers);
        }
    }

    private static int compareParameterTypesAllowContext(Class[] declarationParameters,
                                                         Class[] invocationParameters,
                                                         boolean[] optionalAllowEventBeanType,
                                                         boolean[] optionalAllowEventBeanCollType,
                                                         Type[] genericParameterTypes,
                                                         boolean isVarargs) {

        // determine if the last parameter is EPLMethodInvocationContext (no varargs)
        Class[] declaredNoContext = declarationParameters;
        if (!isVarargs && declarationParameters.length > 0 &&
                declarationParameters[declarationParameters.length - 1] == EPLMethodInvocationContext.class) {
            declaredNoContext = JavaClassHelper.takeFirstN(declarationParameters, declarationParameters.length - 1);
        }

        // determine if the previous-to-last parameter is EPLMethodInvocationContext (varargs-only)
        if (isVarargs && declarationParameters.length > 1 &&
                declarationParameters[declarationParameters.length - 2] == EPLMethodInvocationContext.class) {
            Class[] rewritten = new Class[declarationParameters.length - 1];
            System.arraycopy(declarationParameters, 0, rewritten, 0, declarationParameters.length - 2);
            rewritten[rewritten.length - 1] = declarationParameters[declarationParameters.length - 1];
            declaredNoContext = rewritten;
        }

        return compareParameterTypesNoContext(declaredNoContext, invocationParameters,
                optionalAllowEventBeanType, optionalAllowEventBeanCollType, genericParameterTypes, isVarargs);
    }

    // Returns -1 if the invocation parameters aren't applicable
    // to the method. Otherwise returns the number of parameters
    // that have to be converted
    private static int compareParameterTypesNoContext(Class[] declarationParameters,
                                                      Class[] invocationParameters,
                                                      boolean[] optionalAllowEventBeanType,
                                                      boolean[] optionalAllowEventBeanCollType,
                                                      Type[] genericParameterTypes,
                                                      boolean isVarargs) {
        if (invocationParameters == null) {
            return declarationParameters.length == 0 ? 0 : -1;
        }

        // handle varargs
        if (isVarargs) {
            if (invocationParameters.length < declarationParameters.length - 1) {
                return -1;
            }
            if (invocationParameters.length == 0) {
                return 0;
            }

            AtomicInteger conversionCount = new AtomicInteger();

            // check declared types (non-vararg)
            for (int i = 0; i < declarationParameters.length - 1; i++) {
                boolean compatible = compareParameterTypeCompatible(invocationParameters[i],
                        declarationParameters[i],
                        optionalAllowEventBeanType == null ? null : optionalAllowEventBeanType[i],
                        optionalAllowEventBeanCollType == null ? null : optionalAllowEventBeanCollType[i],
                        genericParameterTypes[i],
                        conversionCount);
                if (!compatible) {
                    return -1;
                }
            }

            Class varargDeclarationParameter = declarationParameters[declarationParameters.length - 1].getComponentType();

            // handle array of compatible type passed into vararg
            if (invocationParameters.length == declarationParameters.length) {
                Class providedType = invocationParameters[invocationParameters.length - 1];
                if (providedType != null && providedType.isArray()) {
                    if (providedType.getComponentType() == varargDeclarationParameter) {
                        return conversionCount.get();
                    }
                    if (JavaClassHelper.isSubclassOrImplementsInterface(providedType.getComponentType(), varargDeclarationParameter)) {
                        conversionCount.incrementAndGet();
                        return conversionCount.get();
                    }
                }
            }

            // handle compatible types passed into vararg
            Type varargGenericParameterTypes = genericParameterTypes[genericParameterTypes.length - 1];
            for (int i = declarationParameters.length - 1; i < invocationParameters.length; i++) {
                boolean compatible = compareParameterTypeCompatible(invocationParameters[i],
                        varargDeclarationParameter,
                        optionalAllowEventBeanType == null ? null : optionalAllowEventBeanType[i],
                        optionalAllowEventBeanCollType == null ? null : optionalAllowEventBeanCollType[i],
                        varargGenericParameterTypes,
                        conversionCount);
                if (!compatible) {
                    return -1;
                }
            }
            return conversionCount.get();
        }

        // handle non-varargs
        if (declarationParameters.length != invocationParameters.length) {
            return -1;
        }

        AtomicInteger conversionCount = new AtomicInteger();
        for (int i = 0; i < declarationParameters.length; i++) {
            boolean compatible = compareParameterTypeCompatible(invocationParameters[i],
                    declarationParameters[i],
                    optionalAllowEventBeanType == null ? null : optionalAllowEventBeanType[i],
                    optionalAllowEventBeanCollType == null ? null : optionalAllowEventBeanCollType[i],
                    genericParameterTypes[i],
                    conversionCount);
            if (!compatible) {
                return -1;
            }
        }
        return conversionCount.get();
    }

    private static boolean compareParameterTypeCompatible(Class invocationParameter,
                                                          Class declarationParameter,
                                                          Boolean optionalAllowEventBeanType,
                                                          Boolean optionalAllowEventBeanCollType,
                                                          Type genericParameterType,
                                                          AtomicInteger conversionCount) {
        if ((invocationParameter == null) && !(declarationParameter.isPrimitive())) {
            return true;
        }
        if (optionalAllowEventBeanType != null && declarationParameter == EventBean.class && optionalAllowEventBeanType) {
            return true;
        }
        if (optionalAllowEventBeanCollType != null &&
                declarationParameter == Collection.class &&
                optionalAllowEventBeanCollType &&
                JavaClassHelper.getGenericType(genericParameterType, 0) == EventBean.class) {
            return true;
        }
        if (!isIdentityConversion(declarationParameter, invocationParameter)) {
            conversionCount.incrementAndGet();
            if (!isWideningConversion(declarationParameter, invocationParameter)) {
                return false;
            }
        }
        return true;
    }

    // Identity conversion means no conversion, wrapper conversion,
    // or conversion to a supertype
    private static boolean isIdentityConversion(Class declarationType, Class invocationType) {
        if (WRAPPING_CONVERSIONS.containsKey(declarationType)) {
            return WRAPPING_CONVERSIONS.get(declarationType).contains(invocationType) || declarationType.isAssignableFrom(invocationType);
        } else {
            if (invocationType == null) {
                return !declarationType.isPrimitive();
            }
            return declarationType.isAssignableFrom(invocationType);
        }

    }

    public static Constructor resolveCtor(Class declaringClass, Class[] paramTypes) throws EngineNoSuchCtorException {
        // Get all the methods for this class
        Constructor[] ctors = declaringClass.getConstructors();

        Constructor bestMatch = null;
        int bestConversionCount = -1;

        // Examine each method, checking if the signature is compatible
        Constructor conversionFailedCtor = null;
        for (Constructor ctor : ctors) {
            // Check the modifiers: we only want public
            if (!Modifier.isPublic(ctor.getModifiers())) {
                continue;
            }

            // Check the parameter list
            int conversionCount = compareParameterTypesNoContext(ctor.getParameterTypes(), paramTypes, null, null, ctor.getGenericParameterTypes(), ctor.isVarArgs());

            // Parameters don't match
            if (conversionCount == -1) {
                conversionFailedCtor = ctor;
                continue;
            }

            // Parameters match exactly
            if (conversionCount == 0) {
                bestMatch = ctor;
                break;
            }

            // No previous match
            if (bestMatch == null) {
                bestMatch = ctor;
                bestConversionCount = conversionCount;
            } else {
                // Current match is better
                if (conversionCount < bestConversionCount) {
                    bestMatch = ctor;
                    bestConversionCount = conversionCount;
                }
            }

        }

        if (bestMatch != null) {
            return bestMatch;
        } else {
            StringBuilder parameters = new StringBuilder();
            String message = "Constructor not found for " + declaringClass.getSimpleName() + " taking ";
            if (paramTypes != null && paramTypes.length != 0) {
                String appendString = "";
                for (Object param : paramTypes) {
                    parameters.append(appendString);
                    if (param == null) {
                        parameters.append("(null)");
                    } else {
                        parameters.append(param.toString());
                    }
                    appendString = ", ";
                }
                message += "('" + parameters + "')'";
            } else {
                message += "no parameters";
            }
            throw new EngineNoSuchCtorException(message, conversionFailedCtor);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy