com.espertech.esper.view.std.GroupByViewImpl.java Source code

Java tutorial

Introduction

Here is the source code for com.espertech.esper.view.std.GroupByViewImpl.java

Source

/**************************************************************************************
 * 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.view.std;

import com.espertech.esper.client.EPException;
import com.espertech.esper.client.EventBean;
import com.espertech.esper.client.EventType;
import com.espertech.esper.collection.MultiKeyUntyped;
import com.espertech.esper.collection.Pair;
import com.espertech.esper.core.context.util.AgentInstanceViewFactoryChainContext;
import com.espertech.esper.epl.expression.ExprEvaluator;
import com.espertech.esper.epl.expression.ExprNode;
import com.espertech.esper.epl.expression.ExprNodeUtility;
import com.espertech.esper.event.EventBeanUtility;
import com.espertech.esper.view.CloneableView;
import com.espertech.esper.view.View;
import com.espertech.esper.view.ViewSupport;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.*;

/**
 * The group view splits the data in a stream to multiple subviews, based on a key index.
 * The key is one or more fields in the stream. Any view that follows the GROUP view will be executed
 * separately on each subview, one per unique key.
 *
 * The view takes a single parameter which is the field name returning the key value to group.
 *
 * This view can, for example, be used to calculate the average price per symbol for a list of symbols.
 *
 * The view treats its child views and their child views as prototypes. It dynamically instantiates copies
 * of each child view and their child views, and the child view's child views as so on. When there are
 * no more child views or the special merge view is encountered, it ends. The view installs a special merge
 * view unto each leaf child view that merges the value key that was grouped by back into the stream
 * using the group-by field name.
 */
public class GroupByViewImpl extends ViewSupport implements CloneableView, GroupByView {
    private final ExprNode[] criteriaExpressions;
    private final ExprEvaluator[] criteriaEvaluators;
    protected final AgentInstanceViewFactoryChainContext agentInstanceContext;
    private EventBean[] eventsPerStream = new EventBean[1];

    protected String[] propertyNames;
    protected final Map<Object, Object> subViewsPerKey = new HashMap<Object, Object>();

    private final HashMap<Object, Pair<Object, Object>> groupedEvents = new HashMap<Object, Pair<Object, Object>>();

    /**
     * Constructor.
     * @param criteriaExpressions is the fields from which to pull the values to group by
     * @param agentInstanceContext contains required view services
     */
    public GroupByViewImpl(AgentInstanceViewFactoryChainContext agentInstanceContext,
            ExprNode[] criteriaExpressions, ExprEvaluator[] criteriaEvaluators) {
        this.agentInstanceContext = agentInstanceContext;
        this.criteriaExpressions = criteriaExpressions;
        this.criteriaEvaluators = criteriaEvaluators;

        propertyNames = new String[criteriaExpressions.length];
        for (int i = 0; i < criteriaExpressions.length; i++) {
            propertyNames[i] = criteriaExpressions[i].toExpressionString();
        }
    }

    public View cloneView() {
        return new GroupByViewImpl(agentInstanceContext, criteriaExpressions, criteriaEvaluators);
    }

    /**
     * Returns the field name that provides the key valie by which to group by.
     * @return field name providing group-by key.
     */
    public ExprNode[] getCriteriaExpressions() {
        return criteriaExpressions;
    }

    public final EventType getEventType() {
        // The schema is the parent view's schema
        return parent.getEventType();
    }

    public final void update(EventBean[] newData, EventBean[] oldData) {
        // Algorithm for single new event
        if ((newData != null) && (oldData == null) && (newData.length == 1)) {
            EventBean theEvent = newData[0];
            EventBean[] newDataToPost = new EventBean[] { theEvent };

            Object groupByValuesKey = getGroupKey(theEvent);

            // Get child views that belong to this group-by value combination
            Object subViews = this.subViewsPerKey.get(groupByValuesKey);

            // If this is a new group-by value, the list of subviews is null and we need to make clone sub-views
            if (subViews == null) {
                subViews = makeSubViews(this, propertyNames, groupByValuesKey, agentInstanceContext);
                subViewsPerKey.put(groupByValuesKey, subViews);
            }

            updateChildViews(subViews, newDataToPost, null);
        } else {
            // Algorithm for dispatching multiple events
            if (newData != null) {
                for (EventBean newValue : newData) {
                    handleEvent(newValue, true);
                }
            }

            if (oldData != null) {
                for (EventBean oldValue : oldData) {
                    handleEvent(oldValue, false);
                }
            }

            // Update child views
            for (Map.Entry<Object, Pair<Object, Object>> entry : groupedEvents.entrySet()) {
                EventBean[] newEvents = convertToArray(entry.getValue().getFirst());
                EventBean[] oldEvents = convertToArray(entry.getValue().getSecond());
                updateChildViews(entry.getKey(), newEvents, oldEvents);
            }

            groupedEvents.clear();
        }
    }

    public final Iterator<EventBean> iterator() {
        throw new UnsupportedOperationException("Cannot iterate over group view, this operation is not supported");
    }

    public final String toString() {
        return this.getClass().getName() + " groupFieldNames=" + Arrays.toString(criteriaExpressions);
    }

    /**
     * Instantiate subviews for the given group view and the given key value to group-by.
     * Makes shallow copies of each child view and its subviews up to the merge point.
     * Sets up merge data views for merging the group-by key value back in.
     * @param groupView is the parent view for which to copy subviews for
     * @param groupByValues is the key values to group-by
     * @param agentInstanceContext is the view services that sub-views may need
     * @param propertyNames names of expressions or properties
     * @return a single view or a list of views that are copies of the original list, with copied children, with
     * data merge views added to the copied child leaf views.
     */
    public static Object makeSubViews(GroupByView groupView, String[] propertyNames, Object groupByValues,
            AgentInstanceViewFactoryChainContext agentInstanceContext) {
        if (!groupView.hasViews()) {
            String message = "Unexpected empty list of child nodes for group view";
            log.fatal(".copySubViews " + message);
            throw new EPException(message);
        }

        Object subviewHolder;
        if (groupView.getViews().length == 1) {
            subviewHolder = copyChildView(groupView, propertyNames, groupByValues, agentInstanceContext,
                    groupView.getViews()[0]);
        } else {
            // For each child node
            ArrayList<View> subViewList = new ArrayList<View>(4);
            subviewHolder = subViewList;
            for (View originalChildView : groupView.getViews()) {
                View copyChildView = copyChildView(groupView, propertyNames, groupByValues, agentInstanceContext,
                        originalChildView);
                subViewList.add(copyChildView);
            }
        }

        return subviewHolder;
    }

    protected static void updateChildViews(Object subViews, EventBean[] newData, EventBean[] oldData) {
        if (subViews instanceof List) {
            List<View> viewList = (List<View>) subViews;
            ViewSupport.updateChildren(viewList, newData, oldData);
        } else {
            ((View) subViews).update(newData, oldData);
        }
    }

    private void handleEvent(EventBean theEvent, boolean isNew) {
        Object groupByValuesKey = getGroupKey(theEvent);

        // Get child views that belong to this group-by value combination
        Object subViews = this.subViewsPerKey.get(groupByValuesKey);

        // If this is a new group-by value, the list of subviews is null and we need to make clone sub-views
        if (subViews == null) {
            subViews = makeSubViews(this, propertyNames, groupByValuesKey, agentInstanceContext);
            subViewsPerKey.put(groupByValuesKey, subViews);
        }

        // Construct a pair of lists to hold the events for the grouped value if not already there
        Pair<Object, Object> pair = groupedEvents.get(subViews);
        if (pair == null) {
            pair = new Pair<Object, Object>(null, null);
            groupedEvents.put(subViews, pair);
        }

        // Add event to a child view event list for later child update that includes new and old events
        if (isNew) {
            pair.setFirst(addUpgradeToDequeIfPopulated(pair.getFirst(), theEvent));
        } else {
            pair.setSecond(addUpgradeToDequeIfPopulated(pair.getSecond(), theEvent));
        }
    }

    private static View copyChildView(GroupByView groupView, String[] propertyNames, Object groupByValues,
            AgentInstanceViewFactoryChainContext agentInstanceContext, View originalChildView) {
        if (originalChildView instanceof MergeView) {
            String message = "Unexpected merge view as child of group-by view";
            log.fatal(".copySubViews " + message);
            throw new EPException(message);
        }

        if (!(originalChildView instanceof CloneableView)) {
            throw new EPException("Unexpected error copying subview " + originalChildView.getClass().getName());
        }
        CloneableView cloneableView = (CloneableView) originalChildView;

        // Copy child node
        View copyChildView = cloneableView.cloneView();
        copyChildView.setParent(groupView);

        // Make the sub views for child copying from the original to the child
        copySubViews(groupView.getCriteriaExpressions(), propertyNames, groupByValues, originalChildView,
                copyChildView, agentInstanceContext);

        return copyChildView;
    }

    private static void copySubViews(ExprNode[] criteriaExpressions, String[] propertyNames, Object groupByValues,
            View originalView, View copyView, AgentInstanceViewFactoryChainContext agentInstanceContext) {
        for (View subView : originalView.getViews()) {
            // Determine if view is our merge view
            if (subView instanceof MergeViewMarker) {
                MergeViewMarker mergeView = (MergeViewMarker) subView;
                if (ExprNodeUtility.deepEquals(mergeView.getGroupFieldNames(), criteriaExpressions)) {
                    // We found our merge view - install a new data merge view on top of it
                    AddPropertyValueView mergeDataView = new AddPropertyValueView(agentInstanceContext,
                            propertyNames, groupByValues, mergeView.getEventType());

                    // Add to the copied parent subview the view merge data view
                    copyView.addView(mergeDataView);

                    // Add to the new merge data view the actual single merge view instance that clients may attached to
                    mergeDataView.addView(mergeView);

                    // Add a parent view to the single merge view instance
                    mergeView.addParentView(mergeDataView);

                    continue;
                }
            }

            if (!(subView instanceof CloneableView)) {
                throw new EPException("Unexpected error copying subview");
            }
            CloneableView cloneableView = (CloneableView) subView;
            View copiedChild = cloneableView.cloneView();
            copyView.addView(copiedChild);

            // Make the sub views for child
            copySubViews(criteriaExpressions, propertyNames, groupByValues, subView, copiedChild,
                    agentInstanceContext);
        }
    }

    private Object getGroupKey(EventBean theEvent) {
        eventsPerStream[0] = theEvent;
        if (criteriaEvaluators.length == 1) {
            return criteriaEvaluators[0].evaluate(eventsPerStream, true, agentInstanceContext);
        }

        Object[] values = new Object[criteriaEvaluators.length];
        for (int i = 0; i < criteriaEvaluators.length; i++) {
            values[i] = criteriaEvaluators[i].evaluate(eventsPerStream, true, agentInstanceContext);
        }
        return new MultiKeyUntyped(values);
    }

    protected static Object addUpgradeToDequeIfPopulated(Object holder, EventBean theEvent) {
        if (holder == null) {
            return theEvent;
        } else if (holder instanceof Deque) {
            Deque<EventBean> deque = (Deque<EventBean>) holder;
            deque.add(theEvent);
            return deque;
        } else {
            ArrayDeque<EventBean> deque = new ArrayDeque<EventBean>(4);
            deque.add((EventBean) holder);
            deque.add(theEvent);
            return deque;
        }
    }

    protected static EventBean[] convertToArray(Object eventOrDeque) {
        if (eventOrDeque == null) {
            return null;
        }
        if (eventOrDeque instanceof EventBean) {
            return new EventBean[] { (EventBean) eventOrDeque };
        }
        return EventBeanUtility.toArray((ArrayDeque<EventBean>) eventOrDeque);
    }

    private static final Log log = LogFactory.getLog(GroupByViewImpl.class);
}