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