/************************************************************************************** * Copyright (C) 2008 EsperTech, Inc. All rights reserved. * * http://esper.codehaus.org * * 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.epl.core.EngineNoSuchCtorException; import com.espertech.esper.epl.core.EngineNoSuchMethodException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * 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 Log log = LogFactory.getLog(MethodResolver.class); private static final Map<Class, Set<Class>> wideningConversions = new HashMap<Class, Set<Class>>(); private static final Map<Class, Set<Class>> wrappingConversions = new HashMap<Class, Set<Class>>(); static { // Initialize the map of wrapper conversions Set<Class> booleanWrappers = new HashSet<Class>(); booleanWrappers.add(boolean.class); booleanWrappers.add(Boolean.class); wrappingConversions.put(boolean.class, booleanWrappers); wrappingConversions.put(Boolean.class, booleanWrappers); Set<Class> charWrappers = new HashSet<Class>(); charWrappers.add(char.class); charWrappers.add(Character.class); wrappingConversions.put(char.class, charWrappers); wrappingConversions.put(Character.class, charWrappers); Set<Class> byteWrappers = new HashSet<Class>(); byteWrappers.add(byte.class); byteWrappers.add(Byte.class); wrappingConversions.put(byte.class, byteWrappers); wrappingConversions.put(Byte.class, byteWrappers); Set<Class> shortWrappers = new HashSet<Class>(); shortWrappers.add(short.class); shortWrappers.add(Short.class); wrappingConversions.put(short.class, shortWrappers); wrappingConversions.put(Short.class, shortWrappers); Set<Class> intWrappers = new HashSet<Class>(); intWrappers.add(int.class); intWrappers.add(Integer.class); wrappingConversions.put(int.class, intWrappers); wrappingConversions.put(Integer.class, intWrappers); Set<Class> longWrappers = new HashSet<Class>(); longWrappers.add(long.class); longWrappers.add(Long.class); wrappingConversions.put(long.class, longWrappers); wrappingConversions.put(Long.class, longWrappers); Set<Class> floatWrappers = new HashSet<Class>(); floatWrappers.add(float.class); floatWrappers.add(Float.class); wrappingConversions.put(float.class, floatWrappers); wrappingConversions.put(Float.class, floatWrappers); Set<Class> doubleWrappers = new HashSet<Class>(); doubleWrappers.add(double.class); doubleWrappers.add(Double.class); wrappingConversions.put(double.class, doubleWrappers); wrappingConversions.put(Double.class, doubleWrappers); // Initialize the map of widening conversions Set<Class> wideningConversions = new HashSet<Class>(byteWrappers); MethodResolver.wideningConversions.put(short.class, new HashSet<Class>(wideningConversions)); MethodResolver.wideningConversions.put(Short.class, new HashSet<Class>(wideningConversions)); wideningConversions.addAll(shortWrappers); wideningConversions.addAll(charWrappers); MethodResolver.wideningConversions.put(int.class, new HashSet<Class>(wideningConversions)); MethodResolver.wideningConversions.put(Integer.class, new HashSet<Class>(wideningConversions)); wideningConversions.addAll(intWrappers); MethodResolver.wideningConversions.put(long.class, new HashSet<Class>(wideningConversions)); MethodResolver.wideningConversions.put(Long.class, new HashSet<Class>(wideningConversions)); wideningConversions.addAll(longWrappers); MethodResolver.wideningConversions.put(float.class, new HashSet<Class>(wideningConversions)); MethodResolver.wideningConversions.put(Float.class, new HashSet<Class>(wideningConversions)); wideningConversions.addAll(floatWrappers); MethodResolver.wideningConversions.put(double.class, new HashSet<Class>(wideningConversions)); MethodResolver.wideningConversions.put(Double.class, new HashSet<Class>(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<Class, Set<Class>> getWideningConversions() { return wideningConversions; } /** * 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 * @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) 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 = compareParameterTypes(method.getParameterTypes(), paramTypes); // 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; } StringBuffer parameters = new StringBuffer(); 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 (paramTypes[i] == null || (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(wideningConversions.containsKey(declarationType)) { return wideningConversions.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); } } // 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 compareParameterTypes(Class[] declarationParameters, Class[] invocationParameters) { if(invocationParameters == null) { return declarationParameters.length == 0 ? 0 : -1; } if(declarationParameters.length != invocationParameters.length) { return -1; } int conversionCount = 0; int count = 0; for(Class parameter : declarationParameters) { if ((invocationParameters[count] == null) && !(parameter.isPrimitive())) { count++; continue; } if(!isIdentityConversion(parameter, invocationParameters[count])) { conversionCount++; if(!isWideningConversion(parameter, invocationParameters[count])) { conversionCount = -1; break; } } count++; } return conversionCount; } // Identity conversion means no conversion, wrapper conversion, // or conversion to a supertype private static boolean isIdentityConversion(Class declarationType, Class invocationType) { if(wrappingConversions.containsKey(declarationType)) { return wrappingConversions.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 = compareParameterTypes(ctor.getParameterTypes(), paramTypes); // 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 { StringBuffer parameters = new StringBuffer(); 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); } } }