/*
***************************************************************************************
* 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.filter;
import com.espertech.esper.client.EventPropertyGetter;
import com.espertech.esper.client.EventType;
import com.espertech.esper.collection.Pair;
import com.espertech.esper.epl.expression.core.*;
import com.espertech.esper.epl.expression.dot.FilterSpecCompilerAdvIndexDescProvider;
import com.espertech.esper.epl.expression.funcs.ExprPlugInSingleRowNode;
import com.espertech.esper.epl.expression.ops.*;
import com.espertech.esper.epl.index.quadtree.AdvancedIndexConfigContextPartitionQuadTree;
import com.espertech.esper.epl.index.quadtree.EngineImportApplicationDotMethodPointInsideRectange;
import com.espertech.esper.epl.index.quadtree.EngineImportApplicationDotMethodRectangeIntersectsRectangle;
import com.espertech.esper.event.property.IndexedProperty;
import com.espertech.esper.event.property.NestedProperty;
import com.espertech.esper.event.property.Property;
import com.espertech.esper.event.property.PropertyParser;
import com.espertech.esper.spatial.quadtree.mxcif.XYWHRectangle;
import com.espertech.esper.spatial.quadtree.pointregion.XYPoint;
import com.espertech.esper.type.RelationalOpEnum;
import com.espertech.esper.util.JavaClassHelper;
import com.espertech.esper.util.SimpleNumberCoercer;
import com.espertech.esper.util.SimpleNumberCoercerFactory;
import java.io.StringWriter;
import java.lang.reflect.Array;
import java.util.*;
/**
* Helper to compile (validate and optimize) filter expressions as used in pattern and filter-based streams.
*/
public final class FilterSpecCompilerMakeParamUtil {
/**
* For a given expression determine if this is optimizable and create the filter parameter
* representing the expression, or null if not optimizable.
*
* @param constituent is the expression to look at
* @param arrayEventTypes event types that provide array values
* @param statementName statement name
* @param exprEvaluatorContext context
* @return filter parameter representing the expression, or null
* @throws ExprValidationException if the expression is invalid
*/
protected static FilterSpecParam makeFilterParam(ExprNode constituent, LinkedHashMap<String, Pair<EventType, String>> arrayEventTypes, ExprEvaluatorContext exprEvaluatorContext, String statementName)
throws ExprValidationException {
// Is this expression node a simple compare, i.e. a=5 or b<4; these can be indexed
if ((constituent instanceof ExprEqualsNode) ||
(constituent instanceof ExprRelationalOpNode)) {
FilterSpecParam param = handleEqualsAndRelOp(constituent, arrayEventTypes, exprEvaluatorContext, statementName);
if (param != null) {
return param;
}
}
constituent = rewriteOrToInIfApplicable(constituent);
// Is this expression node a simple compare, i.e. a=5 or b<4; these can be indexed
if (constituent instanceof ExprInNode) {
FilterSpecParam param = handleInSetNode((ExprInNode) constituent, arrayEventTypes, exprEvaluatorContext, statementName);
if (param != null) {
return param;
}
}
if (constituent instanceof ExprBetweenNode) {
FilterSpecParam param = handleRangeNode((ExprBetweenNode) constituent, arrayEventTypes, exprEvaluatorContext, statementName);
if (param != null) {
return param;
}
}
if (constituent instanceof ExprPlugInSingleRowNode) {
FilterSpecParam param = handlePlugInSingleRow((ExprPlugInSingleRowNode) constituent);
if (param != null) {
return param;
}
}
if (constituent instanceof FilterSpecCompilerAdvIndexDescProvider) {
FilterSpecParam param = handleAdvancedIndexDescProvider((FilterSpecCompilerAdvIndexDescProvider) constituent, arrayEventTypes, statementName, exprEvaluatorContext);
if (param != null) {
return param;
}
}
return null;
}
private static FilterSpecParam handleAdvancedIndexDescProvider(FilterSpecCompilerAdvIndexDescProvider provider, LinkedHashMap<String, Pair<EventType, String>> arrayEventTypes, String statementName, ExprEvaluatorContext exprEvaluatorContext) throws ExprValidationException {
FilterSpecCompilerAdvIndexDesc filterDesc = provider.getFilterSpecDesc();
if (filterDesc == null) {
return null;
}
ExprNode[] keyExpressions = filterDesc.getKeyExpressions();
EventPropertyGetter xGetter = resolveFilterIndexRequiredGetter(filterDesc.getIndexName(), keyExpressions[0]);
EventPropertyGetter yGetter = resolveFilterIndexRequiredGetter(filterDesc.getIndexName(), keyExpressions[1]);
EventPropertyGetter widthGetter = resolveFilterIndexRequiredGetter(filterDesc.getIndexName(), keyExpressions[2]);
EventPropertyGetter heightGetter = resolveFilterIndexRequiredGetter(filterDesc.getIndexName(), keyExpressions[3]);
AdvancedIndexConfigContextPartitionQuadTree config = (AdvancedIndexConfigContextPartitionQuadTree) filterDesc.getIndexSpec();
StringWriter builder = new StringWriter();
ExprNodeUtility.toExpressionString(keyExpressions[0], builder);
builder.append(",");
ExprNodeUtility.toExpressionString(keyExpressions[1], builder);
builder.append(",");
ExprNodeUtility.toExpressionString(keyExpressions[2], builder);
builder.append(",");
ExprNodeUtility.toExpressionString(keyExpressions[3], builder);
builder.append("/");
builder.append(filterDesc.getIndexName().toLowerCase(Locale.ENGLISH));
builder.append("/");
builder.append(filterDesc.getIndexType().toLowerCase(Locale.ENGLISH));
builder.append("/");
config.toConfiguration(builder);
String expression = builder.toString();
Class returnType;
switch (filterDesc.getIndexType()) {
case EngineImportApplicationDotMethodPointInsideRectange.INDEXTYPE_NAME:
returnType = XYPoint.class;
break;
case EngineImportApplicationDotMethodRectangeIntersectsRectangle.INDEXTYPE_NAME:
returnType = XYWHRectangle.class;
break;
default:
throw new IllegalStateException("Unrecognized index type " + filterDesc.getIndexType());
}
FilterSpecLookupableAdvancedIndex lookupable = new FilterSpecLookupableAdvancedIndex(expression, null, returnType, config, xGetter, yGetter, widthGetter, heightGetter, filterDesc.getIndexType());
ExprNode[] indexExpressions = filterDesc.getIndexExpressions();
FilterSpecParamFilterForEvalDouble xEval = resolveFilterIndexDoubleEval(filterDesc.getIndexName(), indexExpressions[0], arrayEventTypes, statementName, exprEvaluatorContext);
FilterSpecParamFilterForEvalDouble yEval = resolveFilterIndexDoubleEval(filterDesc.getIndexName(), indexExpressions[1], arrayEventTypes, statementName, exprEvaluatorContext);
switch (filterDesc.getIndexType()) {
case EngineImportApplicationDotMethodPointInsideRectange.INDEXTYPE_NAME:
return new FilterSpecParamAdvancedIndexQuadTreePointRegion(lookupable, FilterOperator.ADVANCED_INDEX, xEval, yEval);
case EngineImportApplicationDotMethodRectangeIntersectsRectangle.INDEXTYPE_NAME:
FilterSpecParamFilterForEvalDouble widthEval = resolveFilterIndexDoubleEval(filterDesc.getIndexName(), indexExpressions[2], arrayEventTypes, statementName, exprEvaluatorContext);
FilterSpecParamFilterForEvalDouble heightEval = resolveFilterIndexDoubleEval(filterDesc.getIndexName(), indexExpressions[3], arrayEventTypes, statementName, exprEvaluatorContext);
return new FilterSpecParamAdvancedIndexQuadTreeMXCIF(lookupable, FilterOperator.ADVANCED_INDEX, xEval, yEval, widthEval, heightEval);
default:
throw new IllegalStateException("Unrecognized index type " + filterDesc.getIndexType());
}
}
private static FilterSpecParamFilterForEvalDouble resolveFilterIndexDoubleEval(String indexName, ExprNode indexExpression, LinkedHashMap<String, Pair<EventType, String>> arrayEventTypes, String statementName, ExprEvaluatorContext exprEvaluatorContext) throws ExprValidationException {
FilterSpecParamFilterForEvalDouble resolved = null;
if (indexExpression instanceof ExprIdentNode) {
resolved = getIdentNodeDoubleEval((ExprIdentNode) indexExpression, arrayEventTypes, statementName);
} else if (indexExpression instanceof ExprContextPropertyNode) {
ExprContextPropertyNode node = (ExprContextPropertyNode) indexExpression;
resolved = new FilterForEvalContextPropDouble(node.getGetter(), node.getPropertyName());
} else if (ExprNodeUtility.isConstantValueExpr(indexExpression)) {
ExprConstantNode constantNode = (ExprConstantNode) indexExpression;
double d = ((Number) constantNode.getConstantValue(exprEvaluatorContext)).doubleValue();
resolved = new FilterForEvalConstantDouble(d);
}
if (resolved != null) {
return resolved;
}
throw new ExprValidationException("Invalid filter-indexable expression '" + ExprNodeUtility.toExpressionStringMinPrecedenceSafe(indexExpression) + "' in respect to index '" + indexName + "': expected either a constant, context-builtin or property from a previous pattern match");
}
private static EventPropertyGetter resolveFilterIndexRequiredGetter(String indexName, ExprNode keyExpression) throws ExprValidationException {
if (!(keyExpression instanceof ExprIdentNode)) {
throw new ExprValidationException("Invalid filter-index lookup expression '" + ExprNodeUtility.toExpressionStringMinPrecedenceSafe(keyExpression) + "' in respect to index '" + indexName + "': expected an event property name");
}
return ((ExprIdentNode) keyExpression).getExprEvaluatorIdent().getGetter();
}
public static ExprNode rewriteOrToInIfApplicable(ExprNode constituent) {
if (!(constituent instanceof ExprOrNode) || constituent.getChildNodes().length < 2) {
return constituent;
}
// check eligibility
ExprNode[] childNodes = constituent.getChildNodes();
for (ExprNode child : childNodes) {
if (!(child instanceof ExprEqualsNode)) {
return constituent;
}
ExprEqualsNode equalsNode = (ExprEqualsNode) child;
if (equalsNode.isIs() || equalsNode.isNotEquals()) {
return constituent;
}
}
// find common-expression node
ExprNode commonExpressionNode;
ExprNode lhs = childNodes[0].getChildNodes()[0];
ExprNode rhs = childNodes[0].getChildNodes()[1];
if (ExprNodeUtility.deepEquals(lhs, rhs, false)) {
return constituent;
}
if (isExprExistsInAllEqualsChildNodes(childNodes, lhs)) {
commonExpressionNode = lhs;
} else if (isExprExistsInAllEqualsChildNodes(childNodes, rhs)) {
commonExpressionNode = rhs;
} else {
return constituent;
}
// build node
ExprInNodeImpl in = new ExprInNodeImpl(false);
in.addChildNode(commonExpressionNode);
for (int i = 0; i < constituent.getChildNodes().length; i++) {
ExprNode child = constituent.getChildNodes()[i];
int nodeindex = ExprNodeUtility.deepEquals(commonExpressionNode, childNodes[i].getChildNodes()[0], false) ? 1 : 0;
in.addChildNode(child.getChildNodes()[nodeindex]);
}
// validate
try {
in.validateWithoutContext();
} catch (ExprValidationException ex) {
return constituent;
}
return in;
}
private static FilterSpecParam handlePlugInSingleRow(ExprPlugInSingleRowNode constituent) {
if (JavaClassHelper.getBoxedType(constituent.getExprEvaluator().getType()) != Boolean.class) {
return null;
}
if (!constituent.getFilterLookupEligible()) {
return null;
}
FilterSpecLookupable lookupable = constituent.getFilterLookupable();
return new FilterSpecParamConstant(lookupable, FilterOperator.EQUAL, true);
}
private static FilterSpecParam handleRangeNode(ExprBetweenNode betweenNode, LinkedHashMap<String, Pair<EventType, String>> arrayEventTypes, ExprEvaluatorContext exprEvaluatorContext, String statementName) {
ExprNode left = betweenNode.getChildNodes()[0];
if (left instanceof ExprFilterOptimizableNode) {
ExprFilterOptimizableNode filterOptimizableNode = (ExprFilterOptimizableNode) left;
FilterSpecLookupable lookupable = filterOptimizableNode.getFilterLookupable();
FilterOperator op = FilterOperator.parseRangeOperator(betweenNode.isLowEndpointIncluded(), betweenNode.isHighEndpointIncluded(),
betweenNode.isNotBetween());
FilterSpecParamFilterForEval low = handleRangeNodeEndpoint(betweenNode.getChildNodes()[1], arrayEventTypes, exprEvaluatorContext, statementName);
FilterSpecParamFilterForEval high = handleRangeNodeEndpoint(betweenNode.getChildNodes()[2], arrayEventTypes, exprEvaluatorContext, statementName);
if ((low != null) && (high != null)) {
return new FilterSpecParamRange(lookupable, op, low, high);
}
}
return null;
}
private static FilterSpecParamFilterForEval handleRangeNodeEndpoint(ExprNode endpoint, LinkedHashMap<String, Pair<EventType, String>> arrayEventTypes, ExprEvaluatorContext exprEvaluatorContext, String statementName) {
// constant
if (ExprNodeUtility.isConstantValueExpr(endpoint)) {
ExprConstantNode node = (ExprConstantNode) endpoint;
Object value = node.getConstantValue(exprEvaluatorContext);
if (value == null) {
return null;
}
if (value instanceof String) {
return new FilterForEvalConstantString((String) value);
} else {
return new FilterForEvalConstantDouble(((Number) value).doubleValue());
}
}
if (endpoint instanceof ExprContextPropertyNode) {
ExprContextPropertyNode node = (ExprContextPropertyNode) endpoint;
if (JavaClassHelper.isImplementsCharSequence(node.getType())) {
return new FilterForEvalContextPropString(node.getGetter(), node.getPropertyName());
} else {
return new FilterForEvalContextPropDouble(node.getGetter(), node.getPropertyName());
}
}
// or property
if (endpoint instanceof ExprIdentNode) {
return getIdentNodeDoubleEval((ExprIdentNode) endpoint, arrayEventTypes, statementName);
}
return null;
}
private static FilterSpecParam handleInSetNode(ExprInNode constituent, LinkedHashMap<String, Pair<EventType, String>> arrayEventTypes, ExprEvaluatorContext exprEvaluatorContext, String statementName)
throws ExprValidationException {
ExprNode left = constituent.getChildNodes()[0];
if (!(left instanceof ExprFilterOptimizableNode)) {
return null;
}
ExprFilterOptimizableNode filterOptimizableNode = (ExprFilterOptimizableNode) left;
FilterSpecLookupable lookupable = filterOptimizableNode.getFilterLookupable();
FilterOperator op = FilterOperator.IN_LIST_OF_VALUES;
if (constituent.isNotIn()) {
op = FilterOperator.NOT_IN_LIST_OF_VALUES;
}
int expectedNumberOfConstants = constituent.getChildNodes().length - 1;
List<FilterSpecParamInValue> listofValues = new ArrayList<FilterSpecParamInValue>();
Iterator<ExprNode> it = Arrays.asList(constituent.getChildNodes()).iterator();
it.next(); // ignore the first node as it's the identifier
while (it.hasNext()) {
ExprNode subNode = it.next();
if (ExprNodeUtility.isConstantValueExpr(subNode)) {
ExprConstantNode constantNode = (ExprConstantNode) subNode;
Object constant = constantNode.getConstantValue(exprEvaluatorContext);
if (constant instanceof Collection) {
return null;
}
if (constant instanceof Map) {
return null;
}
if ((constant != null) && (constant.getClass().isArray())) {
for (int i = 0; i < Array.getLength(constant); i++) {
Object arrayElement = Array.get(constant, i);
Object arrayElementCoerced = handleConstantsCoercion(lookupable, arrayElement);
listofValues.add(new FilterForEvalConstantAnyType(arrayElementCoerced));
if (i > 0) {
expectedNumberOfConstants++;
}
}
} else {
constant = handleConstantsCoercion(lookupable, constant);
listofValues.add(new FilterForEvalConstantAnyType(constant));
}
}
if (subNode instanceof ExprContextPropertyNode) {
ExprContextPropertyNode contextPropertyNode = (ExprContextPropertyNode) subNode;
Class returnType = contextPropertyNode.getType();
SimpleNumberCoercer coercer;
if (JavaClassHelper.isCollectionMapOrArray(returnType)) {
checkArrayCoercion(returnType, lookupable.getReturnType(), lookupable.getExpression());
coercer = null;
} else {
coercer = getNumberCoercer(left.getExprEvaluator().getType(), contextPropertyNode.getType(), lookupable.getExpression());
}
Class finalReturnType = coercer != null ? coercer.getReturnType() : returnType;
listofValues.add(new FilterForEvalContextPropMayCoerce(contextPropertyNode.getPropertyName(), contextPropertyNode.getGetter(), coercer, finalReturnType));
}
if (subNode instanceof ExprIdentNode) {
ExprIdentNode identNodeInner = (ExprIdentNode) subNode;
if (identNodeInner.getStreamId() == 0) {
break; // for same event evals use the boolean expression, via count compare failing below
}
boolean isMustCoerce = false;
Class coerceToType = JavaClassHelper.getBoxedType(lookupable.getReturnType());
Class identReturnType = identNodeInner.getExprEvaluator().getType();
if (JavaClassHelper.isCollectionMapOrArray(identReturnType)) {
checkArrayCoercion(identReturnType, lookupable.getReturnType(), lookupable.getExpression());
coerceToType = identReturnType;
// no action
} else if (identReturnType != lookupable.getReturnType()) {
if (JavaClassHelper.isNumeric(lookupable.getReturnType())) {
if (!JavaClassHelper.canCoerce(identReturnType, lookupable.getReturnType())) {
throwConversionError(identReturnType, lookupable.getReturnType(), lookupable.getExpression());
}
isMustCoerce = true;
} else {
break; // assumed not compatible
}
}
FilterSpecParamInValue inValue;
String streamName = identNodeInner.getResolvedStreamName();
if (arrayEventTypes != null && !arrayEventTypes.isEmpty() && arrayEventTypes.containsKey(streamName)) {
Pair<Integer, String> indexAndProp = getStreamIndex(identNodeInner.getResolvedPropertyName());
inValue = new FilterForEvalEventPropIndexedMayCoerce(identNodeInner.getResolvedStreamName(), indexAndProp.getFirst(),
indexAndProp.getSecond(), isMustCoerce, coerceToType, statementName);
} else {
inValue = new FilterForEvalEventPropMayCoerce(identNodeInner.getResolvedStreamName(), identNodeInner.getResolvedPropertyName(), isMustCoerce, coerceToType);
}
listofValues.add(inValue);
}
}
// Fallback if not all values in the in-node can be resolved to properties or constants
if (listofValues.size() == expectedNumberOfConstants) {
return new FilterSpecParamIn(lookupable, op, listofValues);
}
return null;
}
private static void checkArrayCoercion(Class returnTypeValue, Class returnTypeLookupable, String propertyName) throws ExprValidationException {
if (returnTypeValue == null || !returnTypeValue.isArray()) {
return;
}
if (!JavaClassHelper.isArrayTypeCompatible(returnTypeLookupable, returnTypeValue.getComponentType())) {
throwConversionError(returnTypeValue.getComponentType(), returnTypeLookupable, propertyName);
}
}
private static FilterSpecParam handleEqualsAndRelOp(ExprNode constituent, LinkedHashMap<String, Pair<EventType, String>> arrayEventTypes, ExprEvaluatorContext exprEvaluatorContext, String statementName)
throws ExprValidationException {
FilterOperator op;
if (constituent instanceof ExprEqualsNode) {
ExprEqualsNode equalsNode = (ExprEqualsNode) constituent;
if (!equalsNode.isIs()) {
op = FilterOperator.EQUAL;
if (equalsNode.isNotEquals()) {
op = FilterOperator.NOT_EQUAL;
}
} else {
op = FilterOperator.IS;
if (equalsNode.isNotEquals()) {
op = FilterOperator.IS_NOT;
}
}
} else {
ExprRelationalOpNode relNode = (ExprRelationalOpNode) constituent;
if (relNode.getRelationalOpEnum() == RelationalOpEnum.GT) {
op = FilterOperator.GREATER;
} else if (relNode.getRelationalOpEnum() == RelationalOpEnum.LT) {
op = FilterOperator.LESS;
} else if (relNode.getRelationalOpEnum() == RelationalOpEnum.LE) {
op = FilterOperator.LESS_OR_EQUAL;
} else if (relNode.getRelationalOpEnum() == RelationalOpEnum.GE) {
op = FilterOperator.GREATER_OR_EQUAL;
} else {
throw new IllegalStateException("Opertor '" + relNode.getRelationalOpEnum() + "' not mapped");
}
}
ExprNode left = constituent.getChildNodes()[0];
ExprNode right = constituent.getChildNodes()[1];
// check identifier and constant combination
if ((ExprNodeUtility.isConstantValueExpr(right)) && (left instanceof ExprFilterOptimizableNode)) {
ExprFilterOptimizableNode filterOptimizableNode = (ExprFilterOptimizableNode) left;
if (filterOptimizableNode.getFilterLookupEligible()) {
ExprConstantNode constantNode = (ExprConstantNode) right;
FilterSpecLookupable lookupable = filterOptimizableNode.getFilterLookupable();
Object constant = constantNode.getConstantValue(exprEvaluatorContext);
constant = handleConstantsCoercion(lookupable, constant);
return new FilterSpecParamConstant(lookupable, op, constant);
}
}
if ((ExprNodeUtility.isConstantValueExpr(left)) && (right instanceof ExprFilterOptimizableNode)) {
ExprFilterOptimizableNode filterOptimizableNode = (ExprFilterOptimizableNode) right;
if (filterOptimizableNode.getFilterLookupEligible()) {
ExprConstantNode constantNode = (ExprConstantNode) left;
FilterSpecLookupable lookupable = filterOptimizableNode.getFilterLookupable();
Object constant = constantNode.getConstantValue(exprEvaluatorContext);
constant = handleConstantsCoercion(lookupable, constant);
FilterOperator opReversed = op.isComparisonOperator() ? op.reversedRelationalOp() : op;
return new FilterSpecParamConstant(lookupable, opReversed, constant);
}
}
// check identifier and expression containing other streams
if ((left instanceof ExprIdentNode) && (right instanceof ExprIdentNode)) {
ExprIdentNode identNodeLeft = (ExprIdentNode) left;
ExprIdentNode identNodeRight = (ExprIdentNode) right;
if ((identNodeLeft.getStreamId() == 0) && (identNodeLeft.getFilterLookupEligible()) && (identNodeRight.getStreamId() != 0)) {
return handleProperty(op, identNodeLeft, identNodeRight, arrayEventTypes, statementName);
}
if ((identNodeRight.getStreamId() == 0) && (identNodeRight.getFilterLookupEligible()) && (identNodeLeft.getStreamId() != 0)) {
op = getReversedOperator(constituent, op); // reverse operators, as the expression is "stream1.prop xyz stream0.prop"
return handleProperty(op, identNodeRight, identNodeLeft, arrayEventTypes, statementName);
}
}
if ((left instanceof ExprFilterOptimizableNode) && (right instanceof ExprContextPropertyNode)) {
ExprFilterOptimizableNode filterOptimizableNode = (ExprFilterOptimizableNode) left;
ExprContextPropertyNode ctxNode = (ExprContextPropertyNode) right;
FilterSpecLookupable lookupable = filterOptimizableNode.getFilterLookupable();
if (filterOptimizableNode.getFilterLookupEligible()) {
SimpleNumberCoercer numberCoercer = getNumberCoercer(lookupable.getReturnType(), ctxNode.getType(), lookupable.getExpression());
return new FilterSpecParamContextProp(lookupable, op, ctxNode.getPropertyName(), ctxNode.getGetter(), numberCoercer);
}
}
if ((left instanceof ExprContextPropertyNode) && (right instanceof ExprFilterOptimizableNode)) {
ExprFilterOptimizableNode filterOptimizableNode = (ExprFilterOptimizableNode) right;
ExprContextPropertyNode ctxNode = (ExprContextPropertyNode) left;
FilterSpecLookupable lookupable = filterOptimizableNode.getFilterLookupable();
if (filterOptimizableNode.getFilterLookupEligible()) {
op = getReversedOperator(constituent, op); // reverse operators, as the expression is "stream1.prop xyz stream0.prop"
SimpleNumberCoercer numberCoercer = getNumberCoercer(lookupable.getReturnType(), ctxNode.getType(), lookupable.getExpression());
return new FilterSpecParamContextProp(lookupable, op, ctxNode.getPropertyName(), ctxNode.getGetter(), numberCoercer);
}
}
return null;
}
private static FilterOperator getReversedOperator(ExprNode constituent, FilterOperator op) {
if (!(constituent instanceof ExprRelationalOpNode)) {
return op;
}
ExprRelationalOpNode relNode = (ExprRelationalOpNode) constituent;
RelationalOpEnum relationalOpEnum = relNode.getRelationalOpEnum();
if (relationalOpEnum == RelationalOpEnum.GT) {
return FilterOperator.LESS;
} else if (relationalOpEnum == RelationalOpEnum.LT) {
return FilterOperator.GREATER;
} else if (relationalOpEnum == RelationalOpEnum.LE) {
return FilterOperator.GREATER_OR_EQUAL;
} else if (relationalOpEnum == RelationalOpEnum.GE) {
return FilterOperator.LESS_OR_EQUAL;
}
return op;
}
private static FilterSpecParam handleProperty(FilterOperator op, ExprIdentNode identNodeLeft, ExprIdentNode identNodeRight, LinkedHashMap<String, Pair<EventType, String>> arrayEventTypes, String statementName)
throws ExprValidationException {
String propertyName = identNodeLeft.getResolvedPropertyName();
Class leftType = identNodeLeft.getExprEvaluator().getType();
Class rightType = identNodeRight.getExprEvaluator().getType();
SimpleNumberCoercer numberCoercer = getNumberCoercer(leftType, rightType, propertyName);
boolean isMustCoerce = numberCoercer != null;
Class numericCoercionType = JavaClassHelper.getBoxedType(leftType);
String streamName = identNodeRight.getResolvedStreamName();
if (arrayEventTypes != null && !arrayEventTypes.isEmpty() && arrayEventTypes.containsKey(streamName)) {
Pair<Integer, String> indexAndProp = getStreamIndex(identNodeRight.getResolvedPropertyName());
return new FilterSpecParamEventPropIndexed(identNodeLeft.getFilterLookupable(), op, identNodeRight.getResolvedStreamName(), indexAndProp.getFirst(),
indexAndProp.getSecond(), isMustCoerce, numberCoercer, numericCoercionType, statementName);
}
return new FilterSpecParamEventProp(identNodeLeft.getFilterLookupable(), op, identNodeRight.getResolvedStreamName(), identNodeRight.getResolvedPropertyName(),
isMustCoerce, numberCoercer, numericCoercionType, statementName);
}
private static SimpleNumberCoercer getNumberCoercer(Class leftType, Class rightType, String expression) throws ExprValidationException {
Class numericCoercionType = JavaClassHelper.getBoxedType(leftType);
if (rightType != leftType) {
if (JavaClassHelper.isNumeric(rightType)) {
if (!JavaClassHelper.canCoerce(rightType, leftType)) {
throwConversionError(rightType, leftType, expression);
}
return SimpleNumberCoercerFactory.getCoercer(rightType, numericCoercionType);
}
}
return null;
}
private static Pair<Integer, String> getStreamIndex(String resolvedPropertyName) {
Property property = PropertyParser.parseAndWalkLaxToSimple(resolvedPropertyName);
if (!(property instanceof NestedProperty)) {
throw new IllegalStateException("Expected a nested property providing an index for array match '" + resolvedPropertyName + "'");
}
NestedProperty nested = (NestedProperty) property;
if (nested.getProperties().size() < 2) {
throw new IllegalStateException("Expected a nested property name for array match '" + resolvedPropertyName + "', none found");
}
if (!(nested.getProperties().get(0) instanceof IndexedProperty)) {
throw new IllegalStateException("Expected an indexed property for array match '" + resolvedPropertyName + "', please provide an index");
}
int index = ((IndexedProperty) nested.getProperties().get(0)).getIndex();
nested.getProperties().remove(0);
StringWriter writer = new StringWriter();
nested.toPropertyEPL(writer);
return new Pair<Integer, String>(index, writer.toString());
}
private static void throwConversionError(Class fromType, Class toType, String propertyName)
throws ExprValidationException {
String text = "Implicit conversion from datatype '" +
fromType.getSimpleName() +
"' to '" +
toType.getSimpleName() +
"' for property '" +
propertyName +
"' is not allowed (strict filter type coercion)";
throw new ExprValidationException(text);
}
// expressions automatically coerce to the most upwards type
// filters require the same type
private static Object handleConstantsCoercion(FilterSpecLookupable lookupable, Object constant)
throws ExprValidationException {
Class identNodeType = lookupable.getReturnType();
if (!JavaClassHelper.isNumeric(identNodeType)) {
return constant; // no coercion required, other type checking performed by expression this comes from
}
if (constant == null) {
// null constant type
return null;
}
if (!JavaClassHelper.canCoerce(constant.getClass(), identNodeType)) {
throwConversionError(constant.getClass(), identNodeType, lookupable.getExpression());
}
Class identNodeTypeBoxed = JavaClassHelper.getBoxedType(identNodeType);
return JavaClassHelper.coerceBoxed((Number) constant, identNodeTypeBoxed);
}
private static boolean isExprExistsInAllEqualsChildNodes(ExprNode[] childNodes, ExprNode search) {
for (ExprNode child : childNodes) {
ExprNode lhs = child.getChildNodes()[0];
ExprNode rhs = child.getChildNodes()[1];
if (!ExprNodeUtility.deepEquals(lhs, search, false) && !ExprNodeUtility.deepEquals(rhs, search, false)) {
return false;
}
if (ExprNodeUtility.deepEquals(lhs, rhs, false)) {
return false;
}
}
return true;
}
private static FilterSpecParamFilterForEvalDouble getIdentNodeDoubleEval(ExprIdentNode node, LinkedHashMap<String, Pair<EventType, String>> arrayEventTypes, String statementName) {
if (node.getStreamId() == 0) {
return null;
}
if (arrayEventTypes != null && !arrayEventTypes.isEmpty() && arrayEventTypes.containsKey(node.getResolvedStreamName())) {
Pair<Integer, String> indexAndProp = getStreamIndex(node.getResolvedPropertyName());
return new FilterForEvalEventPropIndexedDouble(node.getResolvedStreamName(), indexAndProp.getFirst(), indexAndProp.getSecond(), statementName);
} else {
return new FilterForEvalEventPropDouble(node.getResolvedStreamName(), node.getResolvedPropertyName());
}
}
}