/**************************************************************************************
* 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.epl.agg.service.AggregationMethodFactory;
import com.espertech.esper.epl.agg.service.AggregationResultFuture;
import com.espertech.esper.epl.core.MethodResolutionService;
import com.espertech.esper.epl.core.StreamTypeService;
import com.espertech.esper.util.JavaClassHelper;
import java.util.Map;
/**
* Base expression node that represents an aggregation function such as 'sum' or 'count'.
* <p>
* In terms of validation each concrete aggregation node must implement it's own validation.
* <p>
* In terms of evaluation this base class will ask the assigned {@link com.espertech.esper.epl.agg.service.AggregationResultFuture} for the current state,
* using a column number assigned to the node.
* <p>
* Concrete subclasses must supply an aggregation state prototype node {@link com.espertech.esper.epl.agg.aggregator.AggregationMethod} that reflects
* each group's (there may be group-by critera) current aggregation state.
*/
public abstract class ExprAggregateNodeBase extends ExprNodeBase implements ExprEvaluator, ExprAggregateNode
{
private static final long serialVersionUID = 4859196214837888423L;
protected transient AggregationResultFuture aggregationResultFuture;
protected int column;
private transient AggregationMethodFactory aggregationMethodFactory;
/**
* Indicator for whether the aggregation is distinct - i.e. only unique values are considered.
*/
protected boolean isDistinct;
/**
* Returns the aggregation function name for representation in a generate expression string.
* @return aggregation function name
*/
protected abstract String getAggregationFunctionName();
/**
* Return true if a expression aggregate node semantically equals the current node, or false if not.
* <p>For use by the equalsNode implementation which compares the distinct flag.
* @param node to compare to
* @return true if semantically equal, or false if not equals
*/
protected abstract boolean equalsNodeAggregate(ExprAggregateNode node);
/**
* Gives the aggregation node a chance to validate the sub-expression types.
* @param streamTypeService is the types per stream
* @param methodResolutionService used for resolving method and function names
* @param exprEvaluatorContext context for expression evaluation
* @return aggregation function factory to use
* @throws com.espertech.esper.epl.expression.ExprValidationException when expression validation failed
*/
protected abstract AggregationMethodFactory validateAggregationChild(StreamTypeService streamTypeService, MethodResolutionService methodResolutionService, ExprEvaluatorContext exprEvaluatorContext)
throws ExprValidationException;
/**
* Ctor.
* @param distinct - sets the flag indicatating whether only unique values should be aggregated
*/
protected ExprAggregateNodeBase(boolean distinct)
{
isDistinct = distinct;
}
public ExprEvaluator getExprEvaluator()
{
return this;
}
public boolean isConstantResult()
{
return false;
}
@Override
public Map<String, Object> getEventType() {
return null;
}
public void validate(ExprValidationContext validationContext) throws ExprValidationException
{
aggregationMethodFactory = validateAggregationChild(validationContext.getStreamTypeService(), validationContext.getMethodResolutionService(), validationContext.getExprEvaluatorContext());
}
public Class getType()
{
if (aggregationMethodFactory == null)
{
throw new IllegalStateException("Aggregation method has not been set");
}
return aggregationMethodFactory.getResultType();
}
/**
* Returns the aggregation state factory for use in grouping aggregation states per group-by keys.
* @return prototype aggregation state as a factory for aggregation states per group-by key value
*/
public AggregationMethodFactory getFactory()
{
if (aggregationMethodFactory == null)
{
throw new IllegalStateException("Aggregation method has not been set");
}
return aggregationMethodFactory;
}
/**
* Assigns to the node the future which can be queried for the current aggregation state at evaluation time.
* @param aggregationResultFuture - future containing state
* @param column - column to hand to future for easy access
*/
public void setAggregationResultFuture(AggregationResultFuture aggregationResultFuture, int column)
{
this.aggregationResultFuture = aggregationResultFuture;
this.column = column;
}
public final Object evaluate(EventBean[] events, boolean isNewData, ExprEvaluatorContext exprEvaluatorContext)
{
return aggregationResultFuture.getValue(column, exprEvaluatorContext.getAgentInstanceId());
}
/**
* Returns true if the aggregation node is only aggregatig distinct values, or false if
* aggregating all values.
* @return true if 'distinct' keyword was given, false if not
*/
public boolean isDistinct()
{
return isDistinct;
}
public final boolean equalsNode(ExprNode node)
{
if (!(node instanceof ExprAggregateNode))
{
return false;
}
ExprAggregateNode other = (ExprAggregateNode) node;
if (other.isDistinct() != this.isDistinct)
{
return false;
}
return this.equalsNodeAggregate(other);
}
/**
* For use by implementing classes, validates the aggregation node expecting
* a single numeric-type child node.
* @param streamTypeService - types represented in streams
* @return numeric type of single child
* @throws com.espertech.esper.epl.expression.ExprValidationException if the validation failed
*/
protected final Class validateNumericChildAllowFilter(StreamTypeService streamTypeService, boolean hasFilter)
throws ExprValidationException
{
if (this.getChildNodes().size() == 0 || this.getChildNodes().size() > 2)
{
throw new ExprValidationException(getAggregationFunctionName() + " node must have at least 1 or maximum 2 child nodes");
}
// validate child expression (filter expression is actually always the first expression)
ExprNode child = this.getChildNodes().get(0);
if (hasFilter) {
validateFilter(getChildNodes().get(1).getExprEvaluator());
}
Class childType = child.getExprEvaluator().getType();
if (!JavaClassHelper.isNumeric(childType))
{
throw new ExprValidationException("Implicit conversion from datatype '" +
childType.getSimpleName() +
"' to numeric is not allowed for aggregation function '" + getAggregationFunctionName() + "'");
}
return childType;
}
/**
* Renders the aggregation function expression.
* @return expression string is the textual rendering of the aggregation function and it's sub-expression
*/
public String toExpressionString()
{
StringBuilder buffer = new StringBuilder();
buffer.append(getAggregationFunctionName());
buffer.append('(');
if (isDistinct)
{
buffer.append("distinct ");
}
if (!this.getChildNodes().isEmpty())
{
buffer.append(this.getChildNodes().get(0).toExpressionString());
}
else
{
buffer.append('*');
}
buffer.append(')');
return buffer.toString();
}
public void validateFilter(ExprEvaluator filterEvaluator) throws ExprValidationException{
if (JavaClassHelper.getBoxedType(filterEvaluator.getType()) != Boolean.class) {
throw new ExprValidationException("Invalid filter expression parameter to the aggregation function '" +
getAggregationFunctionName() +
"' is expected to return a boolean value but returns " + JavaClassHelper.getClassNameFullyQualPretty(filterEvaluator.getType()));
}
}
}