/**************************************************************************************
* 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.epl.agg.access.AggregationAccessType;
import com.espertech.esper.epl.agg.service.AggregationMethodFactory;
import com.espertech.esper.epl.core.MethodResolutionService;
import com.espertech.esper.epl.core.StreamTypeService;
import com.espertech.esper.event.EventAdapterService;
import java.io.StringWriter;
import java.util.*;
public class ExprAccessAggNode extends ExprAggregateNodeBase implements ExprEvaluatorEnumeration
{
private static final long serialVersionUID = -6088874732989061687L;
private final AggregationAccessType accessType;
private final boolean isWildcard;
private final String streamWildcard;
private transient EventType containedType;
private transient ScalarCollectionEvaluator scalarCollectionEvaluator;
/**
* Ctor.
*/
public ExprAccessAggNode(AggregationAccessType accessType, boolean wildcard, String streamWildcard)
{
super(false);
this.accessType = accessType;
this.isWildcard = wildcard;
this.streamWildcard = streamWildcard;
}
public AggregationMethodFactory validateAggregationChild(StreamTypeService streamTypeService, MethodResolutionService methodResolutionService, ExprEvaluatorContext exprEvaluatorContext) throws ExprValidationException
{
int streamNum;
Class resultType;
ExprEvaluator evaluator;
ExprNode evaluatorIndex = null;
boolean istreamOnly;
if (isWildcard) {
if (streamTypeService.getStreamNames().length > 1) {
throw new ExprValidationException(getErrorPrefix() + " requires that in joins or subqueries the stream-wildcard (stream-alias.*) syntax is used instead");
}
streamNum = 0;
if (streamTypeService.getStreamNames().length == 0) { // could be the case for
throw new ExprValidationException(getErrorPrefix() + " requires that at least one stream is provided");
}
containedType = streamTypeService.getEventTypes()[0];
resultType = streamTypeService.getEventTypes()[0].getUnderlyingType();
final Class returnType = resultType;
evaluator = new ExprEvaluator() {
public Object evaluate(EventBean[] eventsPerStream, boolean isNewData, ExprEvaluatorContext context)
{
if ((eventsPerStream == null) || (eventsPerStream[0] == null)) {
return null;
}
return eventsPerStream[0].getUnderlying();
}
public Class getType()
{
return returnType;
}
public Map<String, Object> getEventType() {
return null;
}
};
istreamOnly = getIstreamOnly(streamTypeService, 0);
if ((accessType == AggregationAccessType.WINDOW) && istreamOnly && !streamTypeService.isOnDemandStreams()) {
throw new ExprValidationException(getErrorPrefix() + " requires that the aggregated events provide a remove stream; Defined a data window onto the stream or use 'firstever', 'lastever' or 'nth' instead");
}
this.getChildNodes().add(0, new ExprStreamUnderlyingNodeImpl(null, true, streamNum, resultType));
}
else if (streamWildcard != null) {
streamNum = streamTypeService.getStreamNumForStreamName(streamWildcard);
if (streamNum == -1) {
throw new ExprValidationException(getErrorPrefix() + " stream wildcard '" + streamWildcard + "' does not resolve to any stream");
}
istreamOnly = getIstreamOnly(streamTypeService, streamNum);
if ((accessType == AggregationAccessType.WINDOW) && istreamOnly && !streamTypeService.isOnDemandStreams()) {
throw new ExprValidationException(getErrorPrefix() + " requires that the aggregated events provide a remove stream; Defined a data window onto the stream or use 'firstever', 'lastever' or 'nth' instead");
}
EventType type = streamTypeService.getEventTypes()[streamNum];
containedType = type;
resultType = type.getUnderlyingType();
final int streamNumUsed = streamNum;
final Class returnType = resultType;
evaluator = new ExprEvaluator() {
public Object evaluate(EventBean[] eventsPerStream, boolean isNewData, ExprEvaluatorContext context)
{
if ((eventsPerStream == null) || (eventsPerStream[streamNumUsed] == null)) {
return null;
}
return eventsPerStream[streamNumUsed].getUnderlying();
}
public Class getType()
{
return returnType;
}
public Map<String, Object> getEventType() {
return null;
}
};
this.getChildNodes().add(0, new ExprStreamUnderlyingNodeImpl(streamWildcard, false, streamNum, resultType));
}
else {
if (this.getChildNodes().isEmpty()) {
throw new ExprValidationException(getErrorPrefix() + " requires a expression or wildcard (*) or stream wildcard (stream-alias.*)");
}
ExprNode child = this.getChildNodes().get(0);
Set<Integer> streams = ExprNodeUtility.getIdentStreamNumbers(child);
if ((streams.isEmpty() || (streams.size() > 1))) {
throw new ExprValidationException(getErrorPrefix() + " requires that any child expressions evaluate properties of the same stream; Use 'firstever' or 'lastever' or 'nth' instead");
}
streamNum = streams.iterator().next();
istreamOnly = getIstreamOnly(streamTypeService, streamNum);
if ((accessType == AggregationAccessType.WINDOW) && istreamOnly && !streamTypeService.isOnDemandStreams()) {
throw new ExprValidationException(getErrorPrefix() + " requires that the aggregated events provide a remove stream; Defined a data window onto the stream or use 'firstever', 'lastever' or 'nth' instead");
}
resultType = this.getChildNodes().get(0).getExprEvaluator().getType();
evaluator = this.getChildNodes().get(0).getExprEvaluator();
scalarCollectionEvaluator = new ScalarCollectionEvaluator(evaluator, streamNum, resultType);
}
if (this.getChildNodes().size() > 1) {
if (accessType == AggregationAccessType.WINDOW) {
throw new ExprValidationException(getErrorPrefix() + " does not accept an index expression; Use 'first' or 'last' instead");
}
evaluatorIndex = this.getChildNodes().get(1);
if (evaluatorIndex.getExprEvaluator().getType() != Integer.class) {
throw new ExprValidationException(getErrorPrefix() + " requires an index expression that returns an integer value");
}
}
return new ExprAccessAggNodeFactory(accessType, resultType, streamNum, evaluator, evaluatorIndex, istreamOnly, streamTypeService.isOnDemandStreams());
}
private boolean getIstreamOnly(StreamTypeService streamTypeService, int streamNum) {
if (streamNum < streamTypeService.getEventTypes().length) {
return streamTypeService.getIStreamOnly()[streamNum];
}
// this could happen for match-recognize which has different stream types for selection and for aggregation
return streamTypeService.getIStreamOnly()[0];
}
@Override
protected String getAggregationFunctionName() {
return accessType.toString().toLowerCase();
}
public String toExpressionString()
{
StringWriter writer = new StringWriter();
writer.append(accessType.toString().toLowerCase());
writer.append('(');
if (isWildcard) {
writer.append('*');
}
else if (streamWildcard != null) {
writer.append(streamWildcard);
writer.append(".*");
}
else {
writer.append(this.getChildNodes().get(0).toExpressionString());
}
writer.append(')');
return writer.toString();
}
public AggregationAccessType getAccessType()
{
return accessType;
}
public boolean isWildcard()
{
return isWildcard;
}
public String getStreamWildcard()
{
return streamWildcard;
}
public Collection<EventBean> evaluateGetROCollectionEvents(EventBean[] eventsPerStream, boolean isNewData, ExprEvaluatorContext context) {
return super.aggregationResultFuture.getCollection(column, context);
}
public Collection evaluateGetROCollectionScalar(EventBean[] eventsPerStream, boolean isNewData, ExprEvaluatorContext context) {
Collection<EventBean> events = evaluateGetROCollectionEvents(eventsPerStream, isNewData, context);
if (events == null) {
return null;
}
if (events.isEmpty()) {
return Collections.emptyList();
}
Deque list = new ArrayDeque();
for (EventBean bean : events) {
list.add(scalarCollectionEvaluator.evaluate(bean, isNewData, context));
}
return list;
}
public EventType getEventTypeCollection(EventAdapterService eventAdapterService) {
if (accessType == AggregationAccessType.FIRST || accessType == AggregationAccessType.LAST) {
return null;
}
return containedType;
}
public Class getComponentTypeCollection() throws ExprValidationException {
return scalarCollectionEvaluator == null ? null : scalarCollectionEvaluator.componentType;
}
public EventType getEventTypeSingle(EventAdapterService eventAdapterService, String statementId) throws ExprValidationException {
if (accessType == AggregationAccessType.FIRST || accessType == AggregationAccessType.LAST) {
return containedType;
}
return null;
}
public EventBean evaluateGetEventBean(EventBean[] eventsPerStream, boolean isNewData, ExprEvaluatorContext context) {
return super.aggregationResultFuture.getEventBean(column, context);
}
@Override
protected boolean equalsNodeAggregate(ExprAggregateNode node) {
if (this == node) return true;
if (node == null || getClass() != node.getClass()) return false;
ExprAccessAggNode that = (ExprAccessAggNode) node;
if (isWildcard != that.isWildcard) return false;
if (accessType != that.accessType) return false;
if (streamWildcard != null ? !streamWildcard.equals(that.streamWildcard) : that.streamWildcard != null)
return false;
return true;
}
private String getErrorPrefix() {
return "The '" + accessType.toString().toLowerCase() + "' aggregation function";
}
private static class ScalarCollectionEvaluator {
private final ExprEvaluator scalarEvaluator;
private final int streamId;
private final Class componentType;
private final EventBean[] eventsPerStream;
private ScalarCollectionEvaluator(ExprEvaluator scalarEvaluator, int streamId, Class componentType) {
this.scalarEvaluator = scalarEvaluator;
this.streamId = streamId;
this.componentType = componentType;
this.eventsPerStream = new EventBean[streamId + 1];
}
public Object evaluate(EventBean theEvent, boolean isNewData, ExprEvaluatorContext context) {
eventsPerStream[streamId] = theEvent;
return scalarEvaluator.evaluate(eventsPerStream, isNewData, context);
}
public Class getComponentType() {
return componentType;
}
}
}