/************************************************************************************** * 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.core.service; import com.espertech.esper.client.EPException; import com.espertech.esper.client.EPSubscriberException; import com.espertech.esper.epl.expression.ExprValidationException; import com.espertech.esper.util.JavaClassHelper; import com.espertech.esper.util.TypeWidener; import com.espertech.esper.util.TypeWidenerFactory; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Map; /** * Factory for creating a dispatch strategy based on the subscriber object * and the columns produced by a select-clause. */ public class ResultDeliveryStrategyFactory { /** * Creates a strategy implementation that indicates to subscribers * the statement results based on the select-clause columns. * @param subscriber to indicate to * @param selectClauseTypes are the types of each column in the select clause * @param selectClauseColumns the names of each column in the select clause * @return strategy for dispatching naturals * @throws EPSubscriberException if the subscriber is invalid */ public static ResultDeliveryStrategy create(String statementName, Object subscriber, Class[] selectClauseTypes, String[] selectClauseColumns) throws EPSubscriberException { if (selectClauseTypes == null) { selectClauseTypes = new Class[0]; selectClauseColumns = new String[0]; } // Locate update methods Method subscriptionMethod = null; ArrayList<Method> updateMethods = new ArrayList<Method>(); for (Method method : subscriber.getClass().getMethods()) { if ((method.getName().equals("update")) && (Modifier.isPublic(method.getModifiers()))) { updateMethods.add(method); } } // none found if (updateMethods.size() == 0) { String message = "Subscriber object does not provide a public method by name 'update'"; throw new EPSubscriberException(message); } // match to parameters boolean isMapArrayDelivery = false; boolean isObjectArrayDelivery = false; boolean isSingleRowMap = false; boolean isSingleRowObjectArr = false; boolean isTypeArrayDelivery = false; // find an exact-matching method: no conversions and not even unboxing/boxing for (Method method : updateMethods) { Class[] parameters = method.getParameterTypes(); if (parameters.length == selectClauseTypes.length) { boolean fits = true; for (int i = 0; i < parameters.length; i++) { if ((selectClauseTypes[i] != null) && (selectClauseTypes[i] != parameters[i])) { fits = false; break; } } if (fits) { subscriptionMethod = method; break; } } } // when not yet resolved, find an exact-matching method with boxing/unboxing if (subscriptionMethod == null) { for (Method method : updateMethods) { Class[] parameters = method.getParameterTypes(); if (parameters.length == selectClauseTypes.length) { boolean fits = true; for (int i = 0; i < parameters.length; i++) { Class boxedExpressionType = JavaClassHelper.getBoxedType(selectClauseTypes[i]); Class boxedParameterType = JavaClassHelper.getBoxedType(parameters[i]); if ((boxedExpressionType != null) && (boxedExpressionType != boxedParameterType)) { fits = false; break; } } if (fits) { subscriptionMethod = method; break; } } } } // when not yet resolved, find assignment-compatible methods that may require widening (including Integer to Long etc.) boolean checkWidening = false; if (subscriptionMethod == null) { for (Method method : updateMethods) { Class[] parameters = method.getParameterTypes(); if (parameters.length == selectClauseTypes.length) { boolean fits = true; for (int i = 0; i < parameters.length; i++) { Class boxedExpressionType = JavaClassHelper.getBoxedType(selectClauseTypes[i]); Class boxedParameterType = JavaClassHelper.getBoxedType(parameters[i]); if ((boxedExpressionType != null) && (!JavaClassHelper.isAssignmentCompatible(boxedExpressionType, boxedParameterType))) { fits = false; break; } } if (fits) { subscriptionMethod = method; checkWidening = true; break; } } } } // when not yet resolved, find first-fit wildcard method if (subscriptionMethod == null) { for (Method method : updateMethods) { Class[] parameters = method.getParameterTypes(); if ((parameters.length == 1) && (parameters[0] == Map.class)) { isSingleRowMap = true; subscriptionMethod = method; break; } if ((parameters.length == 1) && (parameters[0] == Object[].class)) { isSingleRowObjectArr = true; subscriptionMethod = method; break; } if ((parameters.length == 2) && (parameters[0] == Map[].class) && (parameters[1] == Map[].class)) { subscriptionMethod = method; isMapArrayDelivery = true; break; } if ((parameters.length == 2) && (parameters[0] == Object[][].class) && (parameters[1] == Object[][].class)) { subscriptionMethod = method; isObjectArrayDelivery = true; break; } // Handle uniform underlying or column type array dispatch if ((parameters.length == 2) && (parameters[0].equals(parameters[1])) && (parameters[0].isArray()) && (selectClauseTypes.length == 1)) { Class componentType = parameters[0].getComponentType(); if (JavaClassHelper.isAssignmentCompatible(selectClauseTypes[0], componentType)) { subscriptionMethod = method; isTypeArrayDelivery = true; break; } } if ((parameters.length == 0) && (selectClauseTypes.length == 1) && (selectClauseTypes[0] == null)) { subscriptionMethod = method; } } } if (subscriptionMethod == null) { if (updateMethods.size() > 1) { String parametersDesc = JavaClassHelper.getParameterAsString(selectClauseTypes); String message = "No suitable subscriber method named 'update' found, expecting a method that takes " + selectClauseTypes.length + " parameter of type " + parametersDesc; throw new EPSubscriberException(message); } else { Class[] parameters = updateMethods.get(0).getParameterTypes(); String parametersDesc = JavaClassHelper.getParameterAsString(selectClauseTypes); if (parameters.length != selectClauseTypes.length) { if (selectClauseTypes.length > 0) { String message = "No suitable subscriber method named 'update' found, expecting a method that takes " + selectClauseTypes.length + " parameter of type " + parametersDesc; throw new EPSubscriberException(message); } else { String message = "No suitable subscriber method named 'update' found, expecting a method that takes no parameters"; throw new EPSubscriberException(message); } } for (int i = 0; i < parameters.length; i++) { Class boxedExpressionType = JavaClassHelper.getBoxedType(selectClauseTypes[i]); Class boxedParameterType = JavaClassHelper.getBoxedType(parameters[i]); if ((boxedExpressionType != null) && (!JavaClassHelper.isAssignmentCompatible(boxedExpressionType, boxedParameterType))) { String message = "Subscriber method named 'update' for parameter number " + (i + 1) + " is not assignable, " + "expecting type '" + JavaClassHelper.getParameterAsString(selectClauseTypes[i]) + "' but found type '" + JavaClassHelper.getParameterAsString(parameters[i]) + "'"; throw new EPSubscriberException(message); } } } } if (isMapArrayDelivery) { return new ResultDeliveryStrategyMap(statementName, subscriber, subscriptionMethod, selectClauseColumns); } else if (isObjectArrayDelivery) { return new ResultDeliveryStrategyObjectArr(statementName, subscriber, subscriptionMethod); } else if (isTypeArrayDelivery) { return new ResultDeliveryStrategyTypeArr(statementName, subscriber, subscriptionMethod); } // Try to find the "start", "end" and "updateRStream" methods Method startMethod = null; Method endMethod = null; Method rStreamMethod = null; try { startMethod = subscriber.getClass().getMethod("updateStart", int.class, int.class); } catch (NoSuchMethodException e) { // expected } try { endMethod = subscriber.getClass().getMethod("updateEnd"); } catch (NoSuchMethodException e) { // expected } try { rStreamMethod = subscriber.getClass().getMethod("updateRStream", subscriptionMethod.getParameterTypes()); } catch (NoSuchMethodException e) { // expected } DeliveryConvertor convertor; if (isSingleRowMap) { convertor = new DeliveryConvertorMap(selectClauseColumns); } else if (isSingleRowObjectArr) { convertor = new DeliveryConvertorObjectArr(); } else { if (checkWidening) { convertor = determineWideningDeliveryConvertor(selectClauseTypes, subscriptionMethod.getParameterTypes(), subscriptionMethod); } else { convertor = DeliveryConvertorNull.INSTANCE; } } return new ResultDeliveryStrategyImpl(statementName, subscriber, convertor, subscriptionMethod, startMethod, endMethod, rStreamMethod); } private static DeliveryConvertor determineWideningDeliveryConvertor(Class[] selectClauseTypes, Class[] parameterTypes, Method method) { boolean needWidener = false; for (int i = 0; i < selectClauseTypes.length; i++) { TypeWidener optionalWidener = getWidener(i, selectClauseTypes[i], parameterTypes[i], method); if (optionalWidener != null) { needWidener = true; break; } } if (!needWidener) { return DeliveryConvertorNull.INSTANCE; } TypeWidener[] wideners = new TypeWidener[selectClauseTypes.length]; for (int i = 0; i < selectClauseTypes.length; i++) { wideners[i] = getWidener(i, selectClauseTypes[i], parameterTypes[i], method); } return new DeliveryConvertorWidener(wideners); } private static TypeWidener getWidener(int columnNum, Class selectClauseType, Class parameterType, Method method) { if (selectClauseType == null || parameterType == null) { return null; } if (selectClauseType == parameterType) { return null; } try { return TypeWidenerFactory.getCheckPropertyAssignType("Select-Clause Column " + columnNum, selectClauseType, parameterType, "Method Parameter " + columnNum); } catch (ExprValidationException e) { throw new EPException("Unexpected exception assigning select clause columns to subscriber method " + method + ": " + e.getMessage(), e); } } }