/**************************************************************************************
* 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.epl.expression;
import com.espertech.esper.client.EventBean;
import com.espertech.esper.client.EventType;
import com.espertech.esper.client.hook.AggregationFunctionFactory;
import com.espertech.esper.client.hook.EPLMethodInvocationContext;
import com.espertech.esper.collection.Pair;
import com.espertech.esper.core.context.util.ContextPropertyRegistry;
import com.espertech.esper.core.service.ExprEvaluatorContextStatement;
import com.espertech.esper.core.service.StatementContext;
import com.espertech.esper.epl.agg.service.AggregationSupport;
import com.espertech.esper.epl.core.*;
import com.espertech.esper.epl.declexpr.ExprDeclaredNode;
import com.espertech.esper.epl.enummethod.dot.ExprDeclaredOrLambdaNode;
import com.espertech.esper.epl.enummethod.dot.ExprLambdaGoesNode;
import com.espertech.esper.epl.spec.OnTriggerSetAssignment;
import com.espertech.esper.event.EventAdapterService;
import com.espertech.esper.event.EventBeanUtility;
import com.espertech.esper.schedule.ScheduleParameterException;
import com.espertech.esper.schedule.ScheduleSpec;
import com.espertech.esper.schedule.ScheduleSpecUtil;
import com.espertech.esper.util.CollectionUtil;
import com.espertech.esper.util.JavaClassHelper;
import net.sf.cglib.reflect.FastClass;
import net.sf.cglib.reflect.FastMethod;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.util.*;
public class ExprNodeUtility {
public static final ExprNode[] EMPTY_EXPR_ARRAY = new ExprNode[0];
public static final ExprDeclaredNode[] EMPTY_DECLARED_ARR = new ExprDeclaredNode[0];
public static Pair<String, ExprNode> checkGetAssignmentToProp(ExprNode node) {
if (!(node instanceof ExprEqualsNode)) {
return null;
}
ExprEqualsNode equals = (ExprEqualsNode) node;
if (!(equals.getChildNodes()[0] instanceof ExprIdentNode)) {
return null;
}
ExprIdentNode identNode = (ExprIdentNode) equals.getChildNodes()[0];
return new Pair<String, ExprNode>(identNode.getFullUnresolvedName(), equals.getChildNodes()[1]);
}
public static Pair<String, ExprNode> checkGetAssignmentToVariableOrProp(ExprNode node) {
Pair<String, ExprNode> prop = checkGetAssignmentToProp(node);
if (prop != null) {
return prop;
}
if (!(node instanceof ExprEqualsNode)) {
return null;
}
ExprEqualsNode equals = (ExprEqualsNode) node;
if (!(equals.getChildNodes()[0] instanceof ExprVariableNode)) {
return null;
}
ExprVariableNode variableNode = (ExprVariableNode) equals.getChildNodes()[0];
return new Pair<String, ExprNode>(variableNode.getVariableNameWithSubProp(), equals.getChildNodes()[1]);
}
public static void applyFilterExpressionsIterable(Iterable<EventBean> iterable, List<ExprNode> filterExpressions, ExprEvaluatorContext exprEvaluatorContext, Collection<EventBean> eventsInWindow) {
ExprEvaluator[] evaluators = ExprNodeUtility.getEvaluators(filterExpressions);
EventBean[] events = new EventBean[1];
for (EventBean theEvent : iterable) {
events[0] = theEvent;
boolean add = true;
for (ExprEvaluator filter : evaluators) {
Object result = filter.evaluate(events, true, exprEvaluatorContext);
if ((result == null) || (!((Boolean) result))) {
add = false;
break;
}
}
if (add) {
eventsInWindow.add(events[0]);
}
}
}
public static void applyFilterExpressionIterable(Iterable<EventBean> iterable, ExprEvaluator filterExpression, ExprEvaluatorContext exprEvaluatorContext, Collection<EventBean> eventsInWindow) {
EventBean[] events = new EventBean[1];
for (EventBean theEvent : iterable) {
events[0] = theEvent;
Object result = filterExpression.evaluate(events, true, exprEvaluatorContext);
if ((result == null) || (!((Boolean) result))) {
continue;
}
eventsInWindow.add(events[0]);
}
}
public static ExprAndNode connectExpressionsByLogicalAnd(List<ExprNode> nodes) {
if (nodes.size() < 2) {
throw new IllegalArgumentException("Invalid empty or 1-element list of nodes");
}
ExprAndNode andNode = new ExprAndNodeImpl();
for (ExprNode node : nodes) {
andNode.addChildNode(node);
}
return andNode;
}
/**
* Walk expression returning properties used.
* @param exprNode to walk
* @param visitAggregateNodes true to visit aggregation nodes
* @return list of props
*/
public static List<Pair<Integer, String>> getExpressionProperties(ExprNode exprNode, boolean visitAggregateNodes)
{
ExprNodeIdentifierVisitor visitor = new ExprNodeIdentifierVisitor(visitAggregateNodes);
exprNode.accept(visitor);
return visitor.getExprProperties();
}
public static boolean isConstantValueExpr(ExprNode exprNode) {
if (!(exprNode instanceof ExprConstantNode)) {
return false;
}
ExprConstantNode constantNode = (ExprConstantNode) exprNode;
return constantNode.isConstantValue();
}
/**
* Validates the expression node subtree that has this
* node as root. Some of the nodes of the tree, including the
* root, might be replaced in the process.
* @throws com.espertech.esper.epl.expression.ExprValidationException when the validation fails
* @return the root node of the validated subtree, possibly
* different than the root node of the unvalidated subtree
*/
public static ExprNode getValidatedSubtree(ExprNode exprNode, ExprValidationContext validationContext) throws ExprValidationException
{
if (exprNode instanceof ExprLambdaGoesNode) {
return exprNode;
}
return getValidatedSubtreeInternal(exprNode, validationContext, true);
}
public static ExprNode getValidatedAssignment(OnTriggerSetAssignment assignment, ExprValidationContext validationContext) throws ExprValidationException
{
Pair<String, ExprNode> strictAssignment = checkGetAssignmentToVariableOrProp(assignment.getExpression());
if (strictAssignment != null) {
ExprNode validatedRightSide = getValidatedSubtreeInternal(strictAssignment.getSecond(), validationContext, true);
assignment.getExpression().setChildNode(1, validatedRightSide);
return assignment.getExpression();
}
else {
return getValidatedSubtreeInternal(assignment.getExpression(), validationContext, true);
}
}
private static ExprNode getValidatedSubtreeInternal(ExprNode exprNode, ExprValidationContext validationContext, boolean isTopLevel) throws ExprValidationException
{
ExprNode result = exprNode;
if (exprNode instanceof ExprLambdaGoesNode) {
return exprNode;
}
for (int i = 0; i < exprNode.getChildNodes().length; i++)
{
ExprNode childNode = exprNode.getChildNodes()[i];
if (childNode instanceof ExprDeclaredOrLambdaNode) {
ExprDeclaredOrLambdaNode node = (ExprDeclaredOrLambdaNode) childNode;
if (node.validated()) {
continue;
}
}
ExprNode childNodeValidated = getValidatedSubtreeInternal(childNode, validationContext, false);
exprNode.setChildNode(i, childNodeValidated);
}
try
{
exprNode.validate(validationContext);
}
catch(ExprValidationException e)
{
if (exprNode instanceof ExprIdentNode)
{
ExprIdentNode identNode = (ExprIdentNode) exprNode;
try
{
result = resolveStaticMethodOrField(identNode, e, validationContext);
}
catch(ExprValidationException ex)
{
e = ex;
result = resolveAsStreamName(identNode, e, validationContext);
}
}
else
{
throw e;
}
}
// For top-level expressions check if we perform audit
if (isTopLevel) {
if (validationContext.isExpressionAudit()) {
return (ExprNode) ExprNodeProxy.newInstance(validationContext.getStreamTypeService().getEngineURIQualifier(), validationContext.getStatementName(), result);
}
}
else {
if (validationContext.isExpressionNestedAudit() && !(result instanceof ExprIdentNode) && !(ExprNodeUtility.isConstantValueExpr(result))) {
return (ExprNode) ExprNodeProxy.newInstance(validationContext.getStreamTypeService().getEngineURIQualifier(), validationContext.getStatementName(), result);
}
}
return result;
}
private static ExprNode resolveAsStreamName(ExprIdentNode identNode, ExprValidationException existingException, ExprValidationContext validationContext)
throws ExprValidationException
{
ExprStreamUnderlyingNode exprStream = new ExprStreamUnderlyingNodeImpl(identNode.getUnresolvedPropertyName(), false);
try
{
exprStream.validate(validationContext);
}
catch (ExprValidationException ex)
{
throw existingException;
}
return exprStream;
}
// Since static method calls such as "Class.method('a')" and mapped properties "Stream.property('key')"
// look the same, however as the validation could not resolve "Stream.property('key')" before calling this method,
// this method tries to resolve the mapped property as a static method.
// Assumes that this is an ExprIdentNode.
private static ExprNode resolveStaticMethodOrField(ExprIdentNode identNode, ExprValidationException propertyException, ExprValidationContext validationContext)
throws ExprValidationException
{
// Reconstruct the original string
StringBuilder mappedProperty = new StringBuilder(identNode.getUnresolvedPropertyName());
if(identNode.getStreamOrPropertyName() != null)
{
mappedProperty.insert(0, identNode.getStreamOrPropertyName() + '.');
}
// Parse the mapped property format into a class name, method and single string parameter
MappedPropertyParseResult parse = parseMappedProperty(mappedProperty.toString());
if (parse == null)
{
ExprConstantNode constNode = resolveIdentAsEnumConst(mappedProperty.toString(), validationContext.getMethodResolutionService());
if (constNode == null)
{
throw propertyException;
}
else
{
return constNode;
}
}
// If there is a class name, assume a static method is possible.
if (parse.getClassName() != null)
{
List<ExprNode> parameters = Collections.singletonList((ExprNode) new ExprConstantNodeImpl(parse.getArgString()));
List<ExprChainedSpec> chain = new ArrayList<ExprChainedSpec>();
chain.add(new ExprChainedSpec(parse.getClassName(), Collections.<ExprNode>emptyList(), false));
chain.add(new ExprChainedSpec(parse.getMethodName(), parameters, false));
ExprNode result = new ExprDotNode(chain, validationContext.getMethodResolutionService().isDuckType(), validationContext.getMethodResolutionService().isUdfCache());
// Validate
try
{
result.validate(validationContext);
}
catch(ExprValidationException e)
{
throw new ExprValidationException("Failed to resolve enumeration method, date-time method or mapped property '" + mappedProperty + "': " + e.getMessage());
}
return result;
}
// There is no class name, try a single-row function
String functionName = parse.getMethodName();
try
{
Pair<Class, EngineImportSingleRowDesc> classMethodPair = validationContext.getMethodResolutionService().resolveSingleRow(functionName);
List<ExprNode> parameters = Collections.singletonList((ExprNode) new ExprConstantNodeImpl(parse.getArgString()));
List<ExprChainedSpec> chain = Collections.singletonList(new ExprChainedSpec(classMethodPair.getSecond().getMethodName(), parameters, false));
ExprNode result = new ExprPlugInSingleRowNode(functionName, classMethodPair.getFirst(), chain, classMethodPair.getSecond());
// Validate
try
{
result.validate(validationContext);
}
catch (RuntimeException e)
{
throw new ExprValidationException("Plug-in aggregation function '" + parse.getMethodName() + "' failed validation: " + e.getMessage());
}
return result;
}
catch (EngineImportUndefinedException e)
{
// Not an single-row function
}
catch (EngineImportException e)
{
throw new IllegalStateException("Error resolving single-row function: " + e.getMessage(), e);
}
// Try an aggregation function factory
try
{
AggregationFunctionFactory aggregationFactory = validationContext.getMethodResolutionService().resolveAggregationFactory(parse.getMethodName());
ExprNode result = new ExprPlugInAggFunctionFactoryNode(false, aggregationFactory, parse.getMethodName());
result.addChildNode(new ExprConstantNodeImpl(parse.getArgString()));
// Validate
try
{
result.validate(validationContext);
}
catch (RuntimeException e)
{
throw new ExprValidationException("Plug-in aggregation function '" + parse.getMethodName() + "' failed validation: " + e.getMessage());
}
return result;
}
catch (EngineImportUndefinedException e)
{
// Not an aggregation function
}
catch (EngineImportException e)
{
throw new IllegalStateException("Error resolving aggregation: " + e.getMessage(), e);
}
// There is no class name, try an aggregation function (AggregationSupport version, deprecated)
try
{
AggregationSupport aggregation = validationContext.getMethodResolutionService().resolveAggregation(parse.getMethodName());
ExprNode result = new ExprPlugInAggFunctionNode(false, aggregation, parse.getMethodName());
result.addChildNode(new ExprConstantNodeImpl(parse.getArgString()));
// Validate
try
{
result.validate(validationContext);
}
catch (RuntimeException e)
{
throw new ExprValidationException("Plug-in aggregation function '" + parse.getMethodName() + "' failed validation: " + e.getMessage());
}
return result;
}
catch (EngineImportUndefinedException e)
{
// Not an aggregation function
}
catch (EngineImportException e)
{
throw new IllegalStateException("Error resolving aggregation: " + e.getMessage(), e);
}
// absolutly cannot be resolved
throw propertyException;
}
private static ExprConstantNode resolveIdentAsEnumConst(String constant, MethodResolutionService methodResolutionService)
throws ExprValidationException
{
Object enumValue = JavaClassHelper.resolveIdentAsEnumConst(constant, methodResolutionService, null);
if (enumValue != null)
{
return new ExprConstantNodeImpl(enumValue);
}
return null;
}
/**
* Parse the mapped property into classname, method and string argument.
* Mind this has been parsed already and is a valid mapped property.
* @param property is the string property to be passed as a static method invocation
* @return descriptor object
*/
protected static MappedPropertyParseResult parseMappedProperty(String property)
{
// get argument
int indexFirstDoubleQuote = property.indexOf("\"");
int indexFirstSingleQuote = property.indexOf("'");
int startArg;
if ((indexFirstSingleQuote == -1) && (indexFirstDoubleQuote == -1))
{
return null;
}
if ((indexFirstSingleQuote != -1) && (indexFirstDoubleQuote != -1))
{
if (indexFirstSingleQuote < indexFirstDoubleQuote)
{
startArg = indexFirstSingleQuote;
}
else
{
startArg = indexFirstDoubleQuote;
}
}
else if (indexFirstSingleQuote != -1)
{
startArg = indexFirstSingleQuote;
}
else
{
startArg = indexFirstDoubleQuote;
}
int indexLastDoubleQuote = property.lastIndexOf("\"");
int indexLastSingleQuote = property.lastIndexOf("'");
int endArg;
if ((indexLastSingleQuote == -1) && (indexLastDoubleQuote == -1))
{
return null;
}
if ((indexLastSingleQuote != -1) && (indexLastDoubleQuote != -1))
{
if (indexLastSingleQuote > indexLastDoubleQuote)
{
endArg = indexLastSingleQuote;
}
else
{
endArg = indexLastDoubleQuote;
}
}
else if (indexLastSingleQuote != -1)
{
if (indexLastSingleQuote == indexFirstSingleQuote) {
return null;
}
endArg = indexLastSingleQuote;
}
else
{
if (indexLastDoubleQuote == indexFirstDoubleQuote) {
return null;
}
endArg = indexLastDoubleQuote;
}
String argument = property.substring(startArg + 1, endArg);
// get method
String splitDots[] = property.split("[\\.]");
if (splitDots.length == 0)
{
return null;
}
// find which element represents the method, its the element with the parenthesis
int indexMethod = -1;
for (int i = 0; i < splitDots.length; i++)
{
if (splitDots[i].contains("("))
{
indexMethod = i;
break;
}
}
if (indexMethod == -1)
{
return null;
}
String method = splitDots[indexMethod];
int indexParan = method.indexOf("(");
method = method.substring(0, indexParan);
if (method.length() == 0)
{
return null;
}
if (splitDots.length == 1)
{
// no class name
return new MappedPropertyParseResult(null, method, argument);
}
// get class
StringBuilder clazz = new StringBuilder();
for (int i = 0; i < indexMethod; i++)
{
if (i > 0)
{
clazz.append('.');
}
clazz.append(splitDots[i]);
}
return new MappedPropertyParseResult(clazz.toString(), method, argument);
}
public static boolean isAllConstants(List<ExprNode> parameters) {
for (ExprNode node : parameters) {
if (!node.isConstantResult()) {
return false;
}
}
return true;
}
public static ExprIdentNode getExprIdentNode(EventType[] typesPerStream, int streamId, String property) {
return new ExprIdentNodeImpl(typesPerStream[streamId], property, streamId);
}
public static Class[] getExprResultTypes(ExprEvaluator[] evaluators) {
Class[] returnTypes = new Class[evaluators.length];
for (int i = 0; i < evaluators.length; i++) {
returnTypes[i] = evaluators[i].getType();
}
return returnTypes;
}
public static Class[] getExprResultTypes(List<ExprNode> expressions) {
Class[] returnTypes = new Class[expressions.size()];
for (int i = 0; i < expressions.size(); i++) {
returnTypes[i] = expressions.get(i).getExprEvaluator().getType();
}
return returnTypes;
}
public static ExprNodeUtilMethodDesc resolveMethodAllowWildcardAndStream(String className,
Class optionalClass,
String methodName,
List<ExprNode> parameters,
MethodResolutionService methodResolutionService,
EventAdapterService eventAdapterService,
String statementId,
boolean allowWildcard,
final EventType wildcardType,
ExprNodeUtilResolveExceptionHandler exceptionHandler,
String functionName) throws ExprValidationException {
Class[] paramTypes = new Class[parameters.size()];
ExprEvaluator[] childEvals = new ExprEvaluator[parameters.size()];
int count = 0;
boolean[] allowEventBeanType = new boolean[parameters.size()];
boolean[] allowEventBeanCollType = new boolean[parameters.size()];
ExprEvaluator[] childEvalsEventBeanReturnTypes = new ExprEvaluator[parameters.size()];
boolean allConstants = true;
for(ExprNode childNode : parameters)
{
if (childNode instanceof ExprLambdaGoesNode) {
throw new ExprValidationException("Unexpected lambda-expression encountered as parameter to UDF or static method '" + methodName + "'");
}
if (childNode instanceof ExprNumberSetWildcardMarker) {
if (wildcardType == null || !allowWildcard) {
throw new ExprValidationException("Failed to resolve wildcard parameter to a given event type");
}
childEvals[count] = new ExprNodeUtilExprEvalStreamNumUnd(0, wildcardType.getUnderlyingType());
childEvalsEventBeanReturnTypes[count] = new ExprNodeUtilExprEvalStreamNumEvent(0);
paramTypes[count] = wildcardType.getUnderlyingType();
allowEventBeanType[count] = true;
allConstants = false;
count++;
continue;
}
if (childNode instanceof ExprStreamUnderlyingNode) {
ExprStreamUnderlyingNode und = (ExprStreamUnderlyingNode) childNode;
childEvals[count] = childNode.getExprEvaluator();
childEvalsEventBeanReturnTypes[count] = new ExprNodeUtilExprEvalStreamNumEvent(und.getStreamId());
paramTypes[count] = childEvals[count].getType();
allowEventBeanType[count] = true;
allConstants = false;
count++;
continue;
}
if (childNode instanceof ExprEvaluatorEnumeration) {
ExprEvaluatorEnumeration enumeration = (ExprEvaluatorEnumeration) childNode;
EventType eventType = enumeration.getEventTypeSingle(eventAdapterService, statementId);
childEvals[count] = childNode.getExprEvaluator();
paramTypes[count] = childEvals[count].getType();
allConstants = false;
if (eventType != null) {
childEvalsEventBeanReturnTypes[count] = new ExprNodeUtilExprEvalStreamNumEnumSingle(enumeration);
allowEventBeanType[count] = true;
count++;
continue;
}
EventType eventTypeColl = enumeration.getEventTypeCollection(eventAdapterService, statementId);
if (eventTypeColl != null) {
childEvalsEventBeanReturnTypes[count] = new ExprNodeUtilExprEvalStreamNumEnumColl(enumeration);
allowEventBeanCollType[count] = true;
count++;
continue;
}
}
ExprEvaluator eval = childNode.getExprEvaluator();
childEvals[count] = eval;
paramTypes[count] = eval.getType();
count++;
if (!(childNode.isConstantResult()))
{
allConstants = false;
}
}
// Try to resolve the method
final FastMethod staticMethod;
Method method;
try
{
if (optionalClass != null) {
method = methodResolutionService.resolveMethod(optionalClass, methodName, paramTypes, allowEventBeanType, allowEventBeanCollType);
}
else {
method = methodResolutionService.resolveMethod(className, methodName, paramTypes, allowEventBeanType, allowEventBeanCollType);
}
FastClass declaringClass = FastClass.create(Thread.currentThread().getContextClassLoader(), method.getDeclaringClass());
staticMethod = declaringClass.getMethod(method);
}
catch(Exception e)
{
throw exceptionHandler.handle(e);
}
// rewrite those evaluator that should return the event itself
if (CollectionUtil.isAnySet(allowEventBeanType)) {
for (int i = 0; i < parameters.size(); i++) {
if (allowEventBeanType[i] && method.getParameterTypes()[i] == EventBean.class) {
childEvals[i] = childEvalsEventBeanReturnTypes[i];
}
}
}
// rewrite those evaluators that should return the event collection
if (CollectionUtil.isAnySet(allowEventBeanCollType)) {
for (int i = 0; i < parameters.size(); i++) {
if (allowEventBeanCollType[i] && method.getParameterTypes()[i] == Collection.class) {
childEvals[i] = childEvalsEventBeanReturnTypes[i];
}
}
}
// add an evaluator if the method expects a context object
if (method.getParameterTypes().length > 0 &&
method.getParameterTypes()[method.getParameterTypes().length - 1] == EPLMethodInvocationContext.class) {
childEvals = (ExprEvaluator[]) CollectionUtil.arrayExpandAddSingle(childEvals, new ExprNodeUtilExprEvalMethodContext(functionName));
}
return new ExprNodeUtilMethodDesc(allConstants, paramTypes, childEvals, method, staticMethod);
}
public static void validatePlainExpression(String clauseName, String expressionTextualName, ExprNode expression) throws ExprValidationException {
ExprNodeSummaryVisitor summaryVisitor = new ExprNodeSummaryVisitor();
expression.accept(summaryVisitor);
if (summaryVisitor.isHasAggregation() || summaryVisitor.isHasSubselect() || summaryVisitor.isHasStreamSelect() || summaryVisitor.isHasPreviousPrior()) {
throw new ExprValidationException("Invalid " + clauseName + " expression '" + expressionTextualName + "': Aggregation, sub-select, previous or prior functions are not supported in this context");
}
}
public static ExprNode validateSimpleGetSubtree(String clauseName, ExprNode expression, StatementContext statementContext, EventType optionalEventType)
throws ExprValidationException {
ExprNodeUtility.validatePlainExpression(clauseName, expression.toExpressionString(), expression);
StreamTypeServiceImpl streamTypes;
if (optionalEventType != null) {
streamTypes = new StreamTypeServiceImpl(optionalEventType, null, true, statementContext.getEngineURI());
}
else {
streamTypes = new StreamTypeServiceImpl(statementContext.getEngineURI(), false);
}
ExprValidationContext validationContext = new ExprValidationContext(streamTypes, statementContext.getMethodResolutionService(), null, statementContext.getSchedulingService(), statementContext.getVariableService(), new ExprEvaluatorContextStatement(statementContext), statementContext.getEventAdapterService(), statementContext.getStatementName(), statementContext.getStatementId(), statementContext.getAnnotations(), statementContext.getContextDescriptor(), false);
return ExprNodeUtility.getValidatedSubtree(expression, validationContext);
}
public static Set<String> getPropertyNamesIfAllProps(ExprNode[] expressions) {
for (ExprNode expression : expressions) {
if (!(expression instanceof ExprIdentNode)) {
return null;
}
}
Set<String> uniquePropertyNames = new HashSet<String>();
for (ExprNode expression : expressions) {
ExprIdentNode identNode = (ExprIdentNode) expression;
uniquePropertyNames.add(identNode.getUnresolvedPropertyName());
}
return uniquePropertyNames;
}
public static String[] toExpressionStrings(ExprNode[] expressions) {
String[] texts = new String[expressions.length];
for (int i = 0; i < expressions.length; i++) {
texts[i] = expressions[i].toExpressionString();
}
return texts;
}
public static List<Pair<ExprNode, ExprNode>> findExpression(ExprNode selectExpression, ExprNode searchExpression) {
List<Pair<ExprNode, ExprNode>> pairs = new ArrayList<Pair<ExprNode, ExprNode>>();
if (deepEquals(selectExpression, searchExpression)) {
pairs.add(new Pair<ExprNode, ExprNode>(null, selectExpression));
return pairs;
}
findExpressionChildRecursive(selectExpression, searchExpression, pairs);
return pairs;
}
private static void findExpressionChildRecursive(ExprNode parent, ExprNode searchExpression, List<Pair<ExprNode, ExprNode>> pairs) {
for (ExprNode child : parent.getChildNodes()) {
if (deepEquals(child, searchExpression)) {
pairs.add(new Pair<ExprNode, ExprNode>(parent, child));
continue;
}
findExpressionChildRecursive(child, searchExpression, pairs);
}
}
/**
* Encapsulates the parse result parsing a mapped property as a class and method name with args.
*/
protected static class MappedPropertyParseResult
{
private String className;
private String methodName;
private String argString;
/**
* Returns class name.
* @return name of class
*/
public String getClassName()
{
return className;
}
/**
* Returns the method name.
* @return method name
*/
public String getMethodName()
{
return methodName;
}
/**
* Returns the method argument.
* @return arg
*/
public String getArgString()
{
return argString;
}
/**
* Returns the parse result of the mapped property.
* @param className is the class name, or null if there isn't one
* @param methodName is the method name
* @param argString is the argument
*/
public MappedPropertyParseResult(String className, String methodName, String argString)
{
this.className = className;
this.methodName = methodName;
this.argString = argString;
}
}
public static void acceptChain(ExprNodeVisitor visitor, List<ExprChainedSpec> chainSpec) {
for (ExprChainedSpec chain : chainSpec) {
for (ExprNode param : chain.getParameters()) {
param.accept(visitor);
}
}
}
public static void acceptChain(ExprNodeVisitorWithParent visitor, List<ExprChainedSpec> chainSpec) {
for (ExprChainedSpec chain : chainSpec) {
for (ExprNode param : chain.getParameters()) {
param.accept(visitor);
}
}
}
public static void acceptChain(ExprNodeVisitorWithParent visitor, List<ExprChainedSpec> chainSpec, ExprNode parent) {
for (ExprChainedSpec chain : chainSpec) {
for (ExprNode param : chain.getParameters()) {
param.acceptChildnodes(visitor, parent);
}
}
}
public static final void replaceChildNode(ExprNode parentNode, ExprNode nodeToReplace, ExprNode newNode) {
int index = ExprNodeUtility.findChildNode(parentNode, nodeToReplace);
if (index == -1) {
parentNode.replaceUnlistedChildNode(nodeToReplace, newNode);
}
else {
parentNode.setChildNode(index, newNode);
}
}
private static int findChildNode(ExprNode parentNode, ExprNode childNode) {
for (int i = 0; i < parentNode.getChildNodes().length; i++) {
if (parentNode.getChildNodes()[i] == childNode) {
return i;
}
}
return -1;
}
public static void replaceChainChildNode(ExprNode nodeToReplace, ExprNode newNode, List<ExprChainedSpec> chainSpec) {
for (ExprChainedSpec chained : chainSpec) {
int index = chained.getParameters().indexOf(nodeToReplace);
if (index != -1) {
chained.getParameters().set(index, newNode);
}
}
}
public static Set<Pair<Integer, String>> getNonAggregatedProps(EventType[] types, List<ExprNode> exprNodes, ContextPropertyRegistry contextPropertyRegistry)
{
// Determine all event properties in the clause
Set<Pair<Integer, String>> nonAggProps = new HashSet<Pair<Integer, String>>();
for (ExprNode node : exprNodes)
{
ExprNodeIdentifierVisitor visitor = new ExprNodeIdentifierVisitor(false);
node.accept(visitor);
List<Pair<Integer, String>> propertiesNodes = visitor.getExprProperties();
for (Pair<Integer, String> pair : propertiesNodes) {
EventType originType = types.length > pair.getFirst() ? types[pair.getFirst()] : null;
if (originType == null || contextPropertyRegistry == null || !contextPropertyRegistry.isPartitionProperty(originType, pair.getSecond())) {
nonAggProps.add(pair);
}
}
}
return nonAggProps;
}
public static void addNonAggregatedProps(ExprNode exprNode, Set<Pair<Integer, String>> set) {
ExprNodeIdentifierVisitor visitor = new ExprNodeIdentifierVisitor(false);
exprNode.accept(visitor);
set.addAll(visitor.getExprProperties());
}
public static Set<Pair<Integer, String>> getAggregatedProperties(List<ExprAggregateNode> aggregateNodes)
{
// Get a list of properties being aggregated in the clause.
Set<Pair<Integer, String>> propertiesAggregated = new HashSet<Pair<Integer, String>>();
for (ExprNode selectAggExprNode : aggregateNodes)
{
ExprNodeIdentifierVisitor visitor = new ExprNodeIdentifierVisitor(true);
selectAggExprNode.accept(visitor);
List<Pair<Integer, String>> properties = visitor.getExprProperties();
propertiesAggregated.addAll(properties);
}
return propertiesAggregated;
}
public static ExprEvaluator[] getEvaluators(ExprNode[] exprNodes) {
if (exprNodes == null) {
return null;
}
ExprEvaluator[] eval = new ExprEvaluator[exprNodes.length];
for (int i = 0; i < exprNodes.length; i++) {
ExprNode node = exprNodes[i];
if (node != null) {
eval[i] = node.getExprEvaluator();
}
}
return eval;
}
public static ExprEvaluator[] getEvaluators(List<ExprNode> childNodes)
{
ExprEvaluator[] eval = new ExprEvaluator[childNodes.size()];
for (int i = 0; i < childNodes.size(); i++) {
eval[i] = childNodes.get(i).getExprEvaluator();
}
return eval;
}
public static Set<Integer> getIdentStreamNumbers(ExprNode child) {
Set<Integer> streams = new HashSet<Integer>();
ExprNodeIdentifierCollectVisitor visitor = new ExprNodeIdentifierCollectVisitor();
child.accept(visitor);
for (ExprIdentNode node : visitor.getExprProperties()) {
streams.add(node.getStreamId());
}
return streams;
}
/**
* Returns true if all properties within the expression are witin data window'd streams.
* @param child expression to interrogate
* @param streamTypeService streams
* @return indicator
*/
public static boolean hasRemoveStream(ExprNode child, StreamTypeService streamTypeService) {
// Determine whether all streams are istream-only or irstream
boolean[] isIStreamOnly = streamTypeService.getIStreamOnly();
boolean isAllIStream = true; // all true?
boolean isAllIRStream = true; // all false?
for (boolean anIsIStreamOnly : isIStreamOnly) {
if (!anIsIStreamOnly) {
isAllIStream = false;
}
else {
isAllIRStream = false;
}
}
// determine if a data-window applies to this max function
boolean hasDataWindows = true;
if (isAllIStream) {
hasDataWindows = false;
}
else if (!isAllIRStream) {
if (streamTypeService.getEventTypes().length > 1) {
// In a join we assume that a data window is present or implicit via unidirectional
}
else {
hasDataWindows = false;
// get all aggregated properties to determine if any is from a windowed stream
ExprNodeIdentifierCollectVisitor visitor = new ExprNodeIdentifierCollectVisitor();
child.accept(visitor);
for (ExprIdentNode node : visitor.getExprProperties()) {
if (!isIStreamOnly[node.getStreamId()]) {
hasDataWindows = true;
break;
}
}
}
}
return hasDataWindows;
}
/**
* Apply a filter expression.
* @param filter expression
* @param streamZeroEvent the event that represents stream zero
* @param streamOneEvents all events thate are stream one events
* @param exprEvaluatorContext context for expression evaluation
* @return filtered stream one events
*/
public static EventBean[] applyFilterExpression(ExprEvaluator filter, EventBean streamZeroEvent, EventBean[] streamOneEvents, ExprEvaluatorContext exprEvaluatorContext)
{
EventBean[] eventsPerStream = new EventBean[2];
eventsPerStream[0] = streamZeroEvent;
EventBean[] filtered = new EventBean[streamOneEvents.length];
int countPass = 0;
for (EventBean eventBean : streamOneEvents)
{
eventsPerStream[1] = eventBean;
Boolean result = (Boolean) filter.evaluate(eventsPerStream, true, exprEvaluatorContext);
if ((result != null) && result)
{
filtered[countPass] = eventBean;
countPass++;
}
}
if (countPass == streamOneEvents.length)
{
return streamOneEvents;
}
return EventBeanUtility.resizeArray(filtered, countPass);
}
/**
* Apply a filter expression returning a pass indicator.
* @param filter to apply
* @param eventsPerStream events per stream
* @param exprEvaluatorContext context for expression evaluation
* @return pass indicator
*/
public static boolean applyFilterExpression(ExprEvaluator filter, EventBean[] eventsPerStream, ExprEvaluatorContext exprEvaluatorContext)
{
Boolean result = (Boolean) filter.evaluate(eventsPerStream, true, exprEvaluatorContext);
return (result != null) && result;
}
/**
* Compare two expression nodes and their children in exact child-node sequence,
* returning true if the 2 expression nodes trees are equals, or false if they are not equals.
* <p>
* Recursive call since it uses this method to compare child nodes in the same exact sequence.
* Nodes are compared using the equalsNode method.
* @param nodeOne - first expression top node of the tree to compare
* @param nodeTwo - second expression top node of the tree to compare
* @return false if this or all child nodes are not equal, true if equal
*/
public static boolean deepEquals(ExprNode nodeOne, ExprNode nodeTwo)
{
if (nodeOne.getChildNodes().length != nodeTwo.getChildNodes().length)
{
return false;
}
if (!nodeOne.equalsNode(nodeTwo))
{
return false;
}
for (int i = 0; i < nodeOne.getChildNodes().length; i++)
{
ExprNode childNodeOne = nodeOne.getChildNodes()[i];
ExprNode childNodeTwo = nodeTwo.getChildNodes()[i];
if (!ExprNodeUtility.deepEquals(childNodeOne, childNodeTwo))
{
return false;
}
}
return true;
}
/**
* Compares two expression nodes via deep comparison, considering all
* child nodes of either side.
* @param one array of expressions
* @param two array of expressions
* @return true if the expressions are equal, false if not
*/
public static boolean deepEquals(ExprNode[] one, ExprNode[] two)
{
if (one.length != two.length)
{
return false;
}
for (int i = 0; i < one.length; i++)
{
if (!ExprNodeUtility.deepEquals(one[i], two[i]))
{
return false;
}
}
return true;
}
public static boolean deepEquals(List<ExprNode> one, List<ExprNode> two)
{
if (one.size() != two.size())
{
return false;
}
for (int i = 0; i < one.size(); i++)
{
if (!ExprNodeUtility.deepEquals(one.get(i), two.get(i)))
{
return false;
}
}
return true;
}
/**
* Check if the expression is minimal: does not have a subselect, aggregation and does not need view resources
* @param expression to inspect
* @return null if minimal, otherwise name of offending sub-expression
*/
public static String isMinimalExpression(ExprNode expression)
{
ExprNodeSubselectDeclaredDotVisitor subselectVisitor = new ExprNodeSubselectDeclaredDotVisitor();
expression.accept(subselectVisitor);
if (subselectVisitor.getSubselects().size() > 0)
{
return "a subselect";
}
ExprNodeViewResourceVisitor viewResourceVisitor = new ExprNodeViewResourceVisitor();
expression.accept(viewResourceVisitor);
if (viewResourceVisitor.getExprNodes().size() > 0)
{
return "a function that requires view resources (prior, prev)";
}
List<ExprAggregateNode> aggregateNodes = new LinkedList<ExprAggregateNode>();
ExprAggregateNodeUtil.getAggregatesBottomUp(expression, aggregateNodes);
if (!aggregateNodes.isEmpty())
{
return "an aggregation function";
}
return null;
}
protected static void toExpressionString(List<ExprChainedSpec> chainSpec, StringBuilder buffer, boolean prefixDot, String functionName)
{
String delimiterOuter = "";
if (prefixDot) {
delimiterOuter = ".";
}
boolean isFirst = true;
for (ExprChainedSpec element : chainSpec) {
buffer.append(delimiterOuter);
if (functionName != null) {
buffer.append(functionName);
}
else {
buffer.append(element.getName());
}
// the first item without dot-prefix and empty parameters should not be appended with parenthesis
if (!isFirst || prefixDot || !element.getParameters().isEmpty()) {
toExpressionStringIncludeParen(element.getParameters(), buffer);
}
delimiterOuter = ".";
isFirst = false;
}
}
public static void toExpressionString(List<ExprNode> parameters, StringBuilder buffer) {
String delimiter = "";
for (ExprNode param : parameters) {
buffer.append(delimiter);
delimiter = ", ";
buffer.append(param.toExpressionString());
}
}
public static void toExpressionString(List<ExprNode> parameters, StringWriter buffer) {
String delimiter = "";
for (ExprNode param : parameters) {
buffer.append(delimiter);
delimiter = ", ";
buffer.append(param.toExpressionString());
}
}
public static void toExpressionStringIncludeParen(List<ExprNode> parameters, StringBuilder buffer) {
buffer.append("(");
toExpressionString(parameters, buffer);
buffer.append(")");
}
public static void validate(List<ExprChainedSpec> chainSpec, ExprValidationContext validationContext) throws ExprValidationException {
// validate all parameters
for (ExprChainedSpec chainElement : chainSpec) {
List<ExprNode> validated = new ArrayList<ExprNode>();
for (ExprNode expr : chainElement.getParameters()) {
validated.add(ExprNodeUtility.getValidatedSubtree(expr, validationContext));
}
chainElement.setParameters(validated);
}
}
public static List<ExprNode> collectChainParameters(List<ExprChainedSpec> chainSpec) {
List<ExprNode> result = new ArrayList<ExprNode>();
for (ExprChainedSpec chainElement : chainSpec) {
result.addAll(chainElement.getParameters());
}
return result;
}
public static void toExpressionStringParams(StringWriter writer, ExprNode[] params, boolean isWildcard, String streamWildcard, boolean firstParamOnly) {
writer.append('(');
if (isWildcard) {
writer.append('*');
}
else if (streamWildcard != null) {
writer.append(streamWildcard);
writer.append(".*");
}
else {
String delimiter = "";
for (ExprNode childNode : params) {
writer.append(delimiter);
delimiter = ",";
writer.append(childNode.toExpressionString());
if (firstParamOnly) {
break;
}
}
}
writer.append(')');
}
public static String printEvaluators(ExprEvaluator[] evaluators) {
StringWriter writer = new StringWriter();
String delimiter = "";
for (int i = 0; i < evaluators.length; i++) {
writer.append(delimiter);
writer.append(evaluators[i].getClass().getSimpleName());
delimiter = ", ";
}
return writer.toString();
}
public static ScheduleSpec toCrontabSchedule(List<ExprNode> scheduleSpecExpressionList, StatementContext context)
throws ExprValidationException {
// Validate the expressions
ExprEvaluator[] expressions = new ExprEvaluator[scheduleSpecExpressionList.size()];
int count = 0;
ExprEvaluatorContextStatement evaluatorContextStmt = new ExprEvaluatorContextStatement(context);
for (ExprNode parameters : scheduleSpecExpressionList)
{
ExprValidationContext validationContext = new ExprValidationContext(new StreamTypeServiceImpl(context.getEngineURI(), false), context.getMethodResolutionService(), null, context.getSchedulingService(), context.getVariableService(), evaluatorContextStmt, context.getEventAdapterService(), context.getStatementName(), context.getStatementId(), context.getAnnotations(), context.getContextDescriptor(), false);
ExprNode node = ExprNodeUtility.getValidatedSubtree(parameters, validationContext);
expressions[count++] = node.getExprEvaluator();
}
// Build a schedule
try
{
Object[] scheduleSpecParameterList = evaluateExpressions(expressions, evaluatorContextStmt);
return ScheduleSpecUtil.computeValues(scheduleSpecParameterList);
}
catch (ScheduleParameterException e)
{
throw new ExprValidationException("Invalid schedule specification: " + e.getMessage(), e);
}
}
public static Object[] evaluateExpressions(ExprEvaluator[] parameters, ExprEvaluatorContext exprEvaluatorContext)
{
Object[] results = new Object[parameters.length];
int count = 0;
for (ExprEvaluator expr : parameters)
{
try
{
results[count] = expr.evaluate(null, true, exprEvaluatorContext);
count++;
}
catch (RuntimeException ex)
{
String message = "Failed expression evaluation in crontab timer-at for parameter " + count + ": " + ex.getMessage();
log.error(message, ex);
throw new IllegalArgumentException(message);
}
}
return results;
}
public static ExprNode[] toArray(Collection<ExprNode> expressions) {
if (expressions.isEmpty()) {
return EMPTY_EXPR_ARRAY;
}
return expressions.toArray(new ExprNode[expressions.size()]);
}
public static ExprDeclaredNode[] toArray(List<ExprDeclaredNode> declaredNodes) {
if (declaredNodes.isEmpty()) {
return EMPTY_DECLARED_ARR;
}
return declaredNodes.toArray(new ExprDeclaredNode[declaredNodes.size()]);
}
public static Set<Pair<Integer, String>> getGroupByPropertiesValidateHasOne(ExprNode[] groupByNodes)
throws ExprValidationException
{
// Get the set of properties refered to by all group-by expression nodes.
Set<Pair<Integer, String>> propertiesGroupBy = new HashSet<Pair<Integer, String>>();
for (ExprNode groupByNode : groupByNodes)
{
ExprNodeIdentifierVisitor visitor = new ExprNodeIdentifierVisitor(true);
groupByNode.accept(visitor);
List<Pair<Integer, String>> propertiesNode = visitor.getExprProperties();
propertiesGroupBy.addAll(propertiesNode);
// For each group-by expression node, require at least one property.
if (propertiesNode.isEmpty())
{
throw new ExprValidationException("Group-by expressions must refer to property names");
}
}
return propertiesGroupBy;
}
private static final Log log = LogFactory.getLog(ExprNodeUtility.class);
}