com.espertech.esper.epl.expression.core.ExprNodeUtility.java Source code

Java tutorial

Introduction

Here is the source code for com.espertech.esper.epl.expression.core.ExprNodeUtility.java

Source

/**************************************************************************************
 * Copyright (C) 2006-2015 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.epl.expression.core;

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.client.util.TimePeriod;
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.core.start.EPStatementStartMethodHelperSubselect;
import com.espertech.esper.epl.core.*;
import com.espertech.esper.epl.declexpr.ExprDeclaredNode;
import com.espertech.esper.epl.enummethod.dot.EnumMethodEnum;
import com.espertech.esper.epl.enummethod.dot.ExprDeclaredOrLambdaNode;
import com.espertech.esper.epl.enummethod.dot.ExprLambdaGoesNode;
import com.espertech.esper.epl.expression.baseagg.ExprAggregateNode;
import com.espertech.esper.epl.expression.baseagg.ExprAggregateNodeUtil;
import com.espertech.esper.epl.expression.dot.ExprDotNode;
import com.espertech.esper.epl.expression.funcs.ExprPlugInSingleRowNode;
import com.espertech.esper.epl.expression.methodagg.ExprPlugInAggFunctionFactoryNode;
import com.espertech.esper.epl.expression.ops.ExprAndNode;
import com.espertech.esper.epl.expression.ops.ExprAndNodeImpl;
import com.espertech.esper.epl.expression.ops.ExprEqualsNode;
import com.espertech.esper.epl.expression.subquery.ExprSubselectNode;
import com.espertech.esper.epl.expression.table.ExprTableAccessNode;
import com.espertech.esper.epl.expression.time.ExprTimePeriod;
import com.espertech.esper.epl.expression.visitor.*;
import com.espertech.esper.epl.spec.OnTriggerSetAssignment;
import com.espertech.esper.epl.table.mgmt.TableMetadata;
import com.espertech.esper.epl.table.mgmt.TableService;
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 boolean deepEqualsIsSubset(ExprNode[] subset, ExprNode[] superset) {
        for (ExprNode subsetNode : subset) {
            boolean found = false;
            for (ExprNode supersetNode : superset) {
                if (deepEquals(subsetNode, supersetNode)) {
                    found = true;
                    break;
                }
            }
            if (!found) {
                return false;
            }
        }
        return true;
    }

    public static boolean deepEqualsIgnoreDupAndOrder(ExprNode[] setOne, ExprNode[] setTwo) {
        if ((setOne.length == 0 && setTwo.length != 0) || (setOne.length != 0 && setTwo.length == 0)) {
            return false;
        }

        // find set-one expressions in set two
        boolean[] foundTwo = new boolean[setTwo.length];
        for (ExprNode one : setOne) {
            boolean found = false;
            for (int i = 0; i < setTwo.length; i++) {
                if (deepEquals(one, setTwo[i])) {
                    found = true;
                    foundTwo[i] = true;
                }
            }
            if (!found) {
                return false;
            }
        }

        // find any remaining set-two expressions in set one
        for (int i = 0; i < foundTwo.length; i++) {
            if (foundTwo[i]) {
                continue;
            }
            for (ExprNode one : setOne) {
                if (deepEquals(one, setTwo[i])) {
                    break;
                }
            }
            return false;
        }
        return true;
    }

    public static Map<ExprDeclaredNode, List<ExprDeclaredNode>> getDeclaredExpressionCallHierarchy(
            ExprDeclaredNode[] declaredExpressions) {
        ExprNodeSubselectDeclaredDotVisitor visitor = new ExprNodeSubselectDeclaredDotVisitor();
        Map<ExprDeclaredNode, List<ExprDeclaredNode>> calledToCallerMap = new HashMap<ExprDeclaredNode, List<ExprDeclaredNode>>();
        for (ExprDeclaredNode node : declaredExpressions) {
            visitor.reset();
            node.accept(visitor);
            for (ExprDeclaredNode called : visitor.getDeclaredExpressions()) {
                if (called == node) {
                    continue;
                }
                List<ExprDeclaredNode> callers = calledToCallerMap.get(called);
                if (callers == null) {
                    callers = new ArrayList<ExprDeclaredNode>(2);
                    calledToCallerMap.put(called, callers);
                }
                callers.add(node);
            }
            if (!calledToCallerMap.containsKey(node)) {
                calledToCallerMap.put(node, Collections.<ExprDeclaredNode>emptyList());
            }
        }
        return calledToCallerMap;
    }

    public static String toExpressionStringMinPrecedenceSafe(ExprNode node) {
        try {
            StringWriter writer = new StringWriter();
            node.toEPL(writer, ExprPrecedenceEnum.MINIMUM);
            return writer.toString();
        } catch (RuntimeException ex) {
            log.debug("Failed to render expression text: " + ex.getMessage(), ex);
            return "";
        }
    }

    public static String toExpressionStringMinPrecedence(ExprNode[] nodes) {
        StringWriter writer = new StringWriter();
        String delimiter = "";
        for (ExprNode node : nodes) {
            writer.append(delimiter);
            node.toEPL(writer, ExprPrecedenceEnum.MINIMUM);
            delimiter = ",";
        }
        return writer.toString();
    }

    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)
            throws ExprValidationException {
        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) {
            ExprVariableNode variableNode = (ExprVariableNode) equals.getChildNodes()[0];
            return new Pair<String, ExprNode>(variableNode.getVariableNameWithSubProp(), equals.getChildNodes()[1]);
        }
        if (equals.getChildNodes()[0] instanceof ExprTableAccessNode) {
            throw new ExprValidationException(
                    "Table access expression not allowed on the left hand side, please remove the table prefix");
        }
        return null;
    }

    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(Iterator<EventBean> iterator, ExprEvaluator filterExpression,
            ExprEvaluatorContext exprEvaluatorContext, Collection<EventBean> eventsInWindow) {
        EventBean[] events = new EventBean[1];
        for (; iterator.hasNext();) {
            events[0] = iterator.next();
            Object result = filterExpression.evaluate(events, true, exprEvaluatorContext);
            if ((result == null) || (!((Boolean) result))) {
                continue;
            }
            eventsInWindow.add(events[0]);
        }
    }

    public static ExprNode connectExpressionsByLogicalAnd(List<ExprNode> nodes, ExprNode optionalAdditionalFilter) {
        if (nodes.isEmpty()) {
            return optionalAdditionalFilter;
        }
        if (optionalAdditionalFilter == null) {
            if (nodes.size() == 1) {
                return nodes.get(0);
            }
            return connectExpressionsByLogicalAnd(nodes);
        }
        if (nodes.size() == 1) {
            return connectExpressionsByLogicalAnd(Arrays.asList(nodes.get(0), optionalAdditionalFilter));
        }
        ExprAndNode andNode = connectExpressionsByLogicalAnd(nodes);
        andNode.addChildNode(optionalAdditionalFilter);
        return andNode;
    }

    public static ExprAndNode connectExpressionsByLogicalAnd(Collection<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 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(ExprNodeOrigin origin, ExprNode exprNode,
            ExprValidationContext validationContext) throws ExprValidationException {
        if (exprNode instanceof ExprLambdaGoesNode) {
            return exprNode;
        }

        try {
            return getValidatedSubtreeInternal(exprNode, validationContext, true);
        } catch (ExprValidationException ex) {
            try {
                String text;
                if (exprNode instanceof ExprSubselectNode) {
                    ExprSubselectNode subselect = (ExprSubselectNode) exprNode;
                    text = EPStatementStartMethodHelperSubselect
                            .getSubqueryInfoText(subselect.getSubselectNumber() - 1, subselect);
                } else {
                    text = ExprNodeUtility.toExpressionStringMinPrecedenceSafe(exprNode);
                    if (text.length() > 40) {
                        String shortened = text.substring(0, 35);
                        text = shortened + "...(" + text.length() + " chars)";
                    }
                    text = "'" + text + "'";
                }
                throw new ExprValidationException("Failed to validate " + origin.getClauseName() + " expression "
                        + text + ": " + ex.getMessage(), ex);
            } catch (RuntimeException rtex) {
                log.debug("Failed to render nice validation message text: " + rtex.getMessage(), rtex);
                throw ex;
            }
        }
    }

    public static void getValidatedSubtree(ExprNodeOrigin origin, ExprNode[] exprNode,
            ExprValidationContext validationContext) throws ExprValidationException {
        if (exprNode == null) {
            return;
        }
        for (int i = 0; i < exprNode.length; i++) {
            exprNode[i] = getValidatedSubtree(origin, exprNode[i], validationContext);
        }
    }

    public static void getValidatedSubtree(ExprNodeOrigin origin, ExprNode[][] exprNode,
            ExprValidationContext validationContext) throws ExprValidationException {
        if (exprNode == null) {
            return;
        }
        for (ExprNode[] anExprNode : exprNode) {
            getValidatedSubtree(origin, anExprNode, validationContext);
        }
    }

    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 optionalReplacement = exprNode.validate(validationContext);
            if (optionalReplacement != null) {
                return getValidatedSubtreeInternal(optionalReplacement, validationContext, isTopLevel);
            }
        } 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);
        }

        // absolutely 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
     */
    public 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,
            TableService tableService) 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 (!EnumMethodEnum.isEnumerationMethod(methodName) && childNode instanceof ExprLambdaGoesNode) {
                throw new ExprValidationException(
                        "Unexpected lambda-expression encountered as parameter to UDF or static method '"
                                + methodName + "'");
            }
            if (childNode instanceof ExprWildcard) {
                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;
                TableMetadata tableMetadata = tableService.getTableMetadataFromEventType(und.getEventType());
                if (tableMetadata == null) {
                    childEvals[count] = childNode.getExprEvaluator();
                    childEvalsEventBeanReturnTypes[count] = new ExprNodeUtilExprEvalStreamNumEvent(
                            und.getStreamId());
                } else {
                    childEvals[count] = new BindProcessorEvaluatorStreamTable(und.getStreamId(),
                            und.getEventType().getUnderlyingType(), tableMetadata);
                    childEvalsEventBeanReturnTypes[count] = new ExprNodeUtilExprEvalStreamNumEventTable(
                            und.getStreamId(), tableMetadata);
                }
                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(ExprNodeOrigin origin, 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 " + origin.getClauseName() + " expression '"
                    + expressionTextualName
                    + "': Aggregation, sub-select, previous or prior functions are not supported in this context");
        }
    }

    public static ExprNode validateSimpleGetSubtree(ExprNodeOrigin origin, ExprNode expression,
            StatementContext statementContext, EventType optionalEventType, boolean allowBindingConsumption)
            throws ExprValidationException {

        ExprNodeUtility.validatePlainExpression(origin, toExpressionStringMinPrecedenceSafe(expression),
                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(), statementContext.getTableService(),
                new ExprEvaluatorContextStatement(statementContext, false),
                statementContext.getEventAdapterService(), statementContext.getStatementName(),
                statementContext.getStatementId(), statementContext.getAnnotations(),
                statementContext.getContextDescriptor(), false, false, allowBindingConsumption, false, null, false);
        return ExprNodeUtility.getValidatedSubtree(origin, 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[] toExpressionStringsMinPrecedence(ExprNode[] expressions) {
        String[] texts = new String[expressions.length];
        for (int i = 0; i < expressions.length; i++) {
            texts[i] = toExpressionStringMinPrecedenceSafe(expressions[i]);
        }
        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);
        }
    }

    public static void toExpressionStringParameterList(ExprNode[] childNodes, StringWriter buffer) {
        String delimiter = "";
        for (ExprNode childNode : childNodes) {
            buffer.append(delimiter);
            buffer.append(ExprNodeUtility.toExpressionStringMinPrecedenceSafe(childNode));
            delimiter = ",";
        }
    }

    public static void toExpressionStringWFunctionName(String functionName, ExprNode[] childNodes,
            StringWriter writer) {
        writer.append(functionName);
        writer.append("(");
        toExpressionStringParameterList(childNodes, writer);
        writer.append(')');
    }

    public static String[] getIdentResolvedPropertyNames(ExprNode[] nodes) {
        String[] propertyNames = new String[nodes.length];
        for (int i = 0; i < propertyNames.length; i++) {
            if (!(nodes[i] instanceof ExprIdentNode)) {
                throw new IllegalArgumentException("Expressions are not ident nodes");
            }
            propertyNames[i] = ((ExprIdentNode) nodes[i]).getResolvedPropertyName();
        }
        return propertyNames;
    }

    public static Class[] getExprResultTypes(ExprNode[] groupByNodes) {
        Class[] types = new Class[groupByNodes.length];
        for (int i = 0; i < types.length; i++) {
            types[i] = groupByNodes[i].getExprEvaluator().getType();
        }
        return types;
    }

    public static ExprEvaluator makeUnderlyingEvaluator(final int streamNum, final Class resultType,
            TableMetadata tableMetadata) {
        if (tableMetadata != null) {
            return new ExprNodeUtilUnderlyingEvaluatorTable(streamNum, resultType, tableMetadata);
        }
        return new ExprNodeUtilUnderlyingEvaluator(streamNum, resultType);
    }

    public static boolean hasStreamSelect(List<ExprNode> exprNodes) {
        ExprNodeStreamSelectVisitor visitor = new ExprNodeStreamSelectVisitor(false);
        for (ExprNode node : exprNodes) {
            node.accept(visitor);
            if (visitor.hasStreamSelect()) {
                return true;
            }
        }
        return false;
    }

    public static void validateNoSpecialsGroupByExpressions(ExprNode[] groupByNodes)
            throws ExprValidationException {
        ExprNodeSubselectDeclaredDotVisitor visitorSubselects = new ExprNodeSubselectDeclaredDotVisitor();
        ExprNodeGroupingVisitorWParent visitorGrouping = new ExprNodeGroupingVisitorWParent();
        List<ExprAggregateNode> aggNodesInGroupBy = new ArrayList<ExprAggregateNode>(1);

        for (ExprNode groupByNode : groupByNodes) {

            // no subselects
            groupByNode.accept(visitorSubselects);
            if (visitorSubselects.getSubselects().size() > 0) {
                throw new ExprValidationException("Subselects not allowed within group-by");
            }

            // no special grouping-clauses
            groupByNode.accept(visitorGrouping);
            if (!visitorGrouping.getGroupingIdNodes().isEmpty()) {
                throw ExprGroupingIdNode.makeException("grouping_id");
            }
            if (!visitorGrouping.getGroupingNodes().isEmpty()) {
                throw ExprGroupingIdNode.makeException("grouping");
            }

            // no aggregations allowed
            ExprAggregateNodeUtil.getAggregatesBottomUp(groupByNode, aggNodesInGroupBy);
            if (!aggNodesInGroupBy.isEmpty()) {
                throw new ExprValidationException("Group-by expressions cannot contain aggregate functions");
            }
        }
    }

    public static Map<String, ExprNamedParameterNode> getNamedExpressionsHandleDups(List<ExprNode> parameters)
            throws ExprValidationException {
        Map<String, ExprNamedParameterNode> nameds = null;

        for (ExprNode node : parameters) {
            if (node instanceof ExprNamedParameterNode) {
                ExprNamedParameterNode named = (ExprNamedParameterNode) node;
                if (nameds == null) {
                    nameds = new HashMap<String, ExprNamedParameterNode>();
                }
                String lowerCaseName = named.getParameterName().toLowerCase();
                if (nameds.containsKey(lowerCaseName)) {
                    throw new ExprValidationException("Duplicate parameter '" + lowerCaseName + "'");
                }
                nameds.put(lowerCaseName, named);
            }
        }
        if (nameds == null) {
            return Collections.emptyMap();
        }
        return nameds;
    }

    public static void validateNamed(Map<String, ExprNamedParameterNode> namedExpressions, String[] namedParameters)
            throws ExprValidationException {
        for (Map.Entry<String, ExprNamedParameterNode> entry : namedExpressions.entrySet()) {
            boolean found = false;
            for (String named : namedParameters) {
                if (named.equals(entry.getKey())) {
                    found = true;
                    break;
                }
            }
            if (!found) {
                throw new ExprValidationException("Unexpected named parameter '" + entry.getKey()
                        + "', expecting any of the following: " + CollectionUtil.toStringArray(namedParameters));
            }
        }
    }

    public static boolean validateNamedExpectType(ExprNamedParameterNode namedParameterNode, Class[] expectedTypes)
            throws ExprValidationException {
        if (namedParameterNode.getChildNodes().length != 1) {
            throw getNamedValidationException(namedParameterNode.getParameterName(), expectedTypes);
        }

        ExprNode childNode = namedParameterNode.getChildNodes()[0];
        Class returnType = JavaClassHelper.getBoxedType(childNode.getExprEvaluator().getType());

        boolean found = false;
        for (Class expectedType : expectedTypes) {
            if (expectedType == TimePeriod.class && childNode instanceof ExprTimePeriod) {
                found = true;
                break;
            }
            if (returnType == JavaClassHelper.getBoxedType(expectedType)) {
                found = true;
                break;
            }
        }

        if (found) {
            return namedParameterNode.getChildNodes()[0].isConstantResult();
        }
        throw getNamedValidationException(namedParameterNode.getParameterName(), expectedTypes);
    }

    private static ExprValidationException getNamedValidationException(String parameterName, Class[] expected) {
        String expectedType;
        if (expected.length == 1) {
            expectedType = "a " + JavaClassHelper.getSimpleNameForClass(expected[0]) + "-typed value";
        } else {
            StringWriter buf = new StringWriter();
            buf.append("any of the following types: ");
            String delimiter = "";
            for (Class clazz : expected) {
                buf.append(delimiter);
                buf.append(JavaClassHelper.getSimpleNameForClass(clazz));
                delimiter = ",";
            }
            expectedType = buf.toString();
        }
        String message = "Failed to validate named parameter '" + parameterName
                + "', expected a single expression returning " + expectedType;
        return new ExprValidationException(message);
    }

    /**
     * Encapsulates the parse result parsing a mapped property as a class and method name with args.
     */
    public 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 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 ExprNodePropOrStreamSet getNonAggregatedProps(EventType[] types, List<ExprNode> exprNodes,
            ContextPropertyRegistry contextPropertyRegistry) {
        // Determine all event properties in the clause
        ExprNodePropOrStreamSet nonAggProps = new ExprNodePropOrStreamSet();
        ExprNodeIdentifierAndStreamRefVisitor visitor = new ExprNodeIdentifierAndStreamRefVisitor(false);
        for (ExprNode node : exprNodes) {
            visitor.reset();
            node.accept(visitor);
            addNonAggregatedProps(nonAggProps, visitor.getRefs(), types, contextPropertyRegistry);
        }

        return nonAggProps;
    }

    private static void addNonAggregatedProps(ExprNodePropOrStreamSet nonAggProps,
            List<ExprNodePropOrStreamDesc> refs, EventType[] types,
            ContextPropertyRegistry contextPropertyRegistry) {
        for (ExprNodePropOrStreamDesc pair : refs) {
            if (pair instanceof ExprNodePropOrStreamPropDesc) {
                ExprNodePropOrStreamPropDesc propDesc = (ExprNodePropOrStreamPropDesc) pair;
                EventType originType = types.length > pair.getStreamNum() ? types[pair.getStreamNum()] : null;
                if (originType == null || contextPropertyRegistry == null
                        || !contextPropertyRegistry.isPartitionProperty(originType, propDesc.getPropertyName())) {
                    nonAggProps.add(pair);
                }
            } else {
                nonAggProps.add(pair);
            }
        }
    }

    public static void addNonAggregatedProps(ExprNode exprNode, ExprNodePropOrStreamSet set, EventType[] types,
            ContextPropertyRegistry contextPropertyRegistry) {
        ExprNodeIdentifierAndStreamRefVisitor visitor = new ExprNodeIdentifierAndStreamRefVisitor(false);
        exprNode.accept(visitor);
        addNonAggregatedProps(set, visitor.getRefs(), types, contextPropertyRegistry);
    }

    public static ExprNodePropOrStreamSet getAggregatedProperties(List<ExprAggregateNode> aggregateNodes) {
        // Get a list of properties being aggregated in the clause.
        ExprNodePropOrStreamSet propertiesAggregated = new ExprNodePropOrStreamSet();
        ExprNodeIdentifierAndStreamRefVisitor visitor = new ExprNodeIdentifierAndStreamRefVisitor(true);
        for (ExprNode selectAggExprNode : aggregateNodes) {
            visitor.reset();
            selectAggExprNode.accept(visitor);
            List<ExprNodePropOrStreamDesc> properties = visitor.getRefs();
            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 hasRemoveStreamForAggregations(ExprNode child, StreamTypeService streamTypeService,
            boolean unidirectionalJoin) {

        // 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) {
                if (unidirectionalJoin) {
                    return false;
                }
                // 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;
    }

    public static void toExpressionString(List<ExprChainedSpec> chainSpec, StringWriter 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 toExpressionStringParameterList(List<ExprNode> parameters, StringWriter buffer) {
        String delimiter = "";
        for (ExprNode param : parameters) {
            buffer.append(delimiter);
            delimiter = ",";
            buffer.append(ExprNodeUtility.toExpressionStringMinPrecedenceSafe(param));
        }
    }

    public static void toExpressionStringIncludeParen(List<ExprNode> parameters, StringWriter buffer) {
        buffer.append("(");
        toExpressionStringParameterList(parameters, buffer);
        buffer.append(")");
    }

    public static void validate(ExprNodeOrigin origin, 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(origin, expr, validationContext));
                if (expr instanceof ExprNamedParameterNode) {
                    throw new ExprValidationException("Named parameters are not allowed");
                }
            }
            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) {
        writer.append('(');
        String delimiter = "";
        for (ExprNode childNode : params) {
            writer.append(delimiter);
            delimiter = ",";
            writer.append(ExprNodeUtility.toExpressionStringMinPrecedenceSafe(childNode));
        }
        writer.append(')');
    }

    public static String printEvaluators(ExprEvaluator[] evaluators) {
        StringWriter writer = new StringWriter();
        String delimiter = "";
        for (ExprEvaluator evaluator : evaluators) {
            writer.append(delimiter);
            writer.append(evaluator.getClass().getSimpleName());
            delimiter = ", ";
        }
        return writer.toString();
    }

    public static ScheduleSpec toCrontabSchedule(ExprNodeOrigin origin, List<ExprNode> scheduleSpecExpressionList,
            StatementContext context, boolean allowBindingConsumption) throws ExprValidationException {

        // Validate the expressions
        ExprEvaluator[] expressions = new ExprEvaluator[scheduleSpecExpressionList.size()];
        int count = 0;
        ExprEvaluatorContextStatement evaluatorContextStmt = new ExprEvaluatorContextStatement(context, false);
        for (ExprNode parameters : scheduleSpecExpressionList) {
            ExprValidationContext validationContext = new ExprValidationContext(
                    new StreamTypeServiceImpl(context.getEngineURI(), false), context.getMethodResolutionService(),
                    null, context.getSchedulingService(), context.getVariableService(), context.getTableService(),
                    evaluatorContextStmt, context.getEventAdapterService(), context.getStatementName(),
                    context.getStatementId(), context.getAnnotations(), context.getContextDescriptor(), false,
                    false, allowBindingConsumption, false, null, false);
            ExprNode node = ExprNodeUtility.getValidatedSubtree(origin, 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 ExprNodePropOrStreamSet getGroupByPropertiesValidateHasOne(ExprNode[] groupByNodes)
            throws ExprValidationException {
        // Get the set of properties refered to by all group-by expression nodes.
        ExprNodePropOrStreamSet propertiesGroupBy = new ExprNodePropOrStreamSet();
        ExprNodeIdentifierAndStreamRefVisitor visitor = new ExprNodeIdentifierAndStreamRefVisitor(true);

        for (ExprNode groupByNode : groupByNodes) {
            visitor.reset();
            groupByNode.accept(visitor);
            List<ExprNodePropOrStreamDesc> propertiesNode = visitor.getRefs();
            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);
}