Package com.espertech.esper.view.ext

Source Code of com.espertech.esper.view.ext.RankWindowView

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

import com.espertech.esper.client.EventBean;
import com.espertech.esper.client.EventType;
import com.espertech.esper.collection.MultiKeyUntyped;
import com.espertech.esper.collection.OneEventCollection;
import com.espertech.esper.core.context.util.AgentInstanceViewFactoryChainContext;
import com.espertech.esper.epl.expression.ExprEvaluator;
import com.espertech.esper.epl.expression.ExprEvaluatorContext;
import com.espertech.esper.epl.expression.ExprNode;
import com.espertech.esper.util.*;
import com.espertech.esper.view.CloneableView;
import com.espertech.esper.view.DataWindowView;
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.*;

/**
* Window sorting by values in the specified field extending a specified number of elements
* from the lowest value up or the highest value down and retaining only the last unique value per key.
*
* The type of the field to be sorted in the event must implement the Comparable interface.
*
* The natural order in which events arrived is used as the second sorting criteria. Thus should events arrive
* with equal sort values the oldest event leaves the sort window first.
*
* Old values removed from a another view are removed from the sort view.
*/
public final class RankWindowView extends ViewSupport implements DataWindowView, CloneableView
{
    private final RankWindowViewFactory rankWindowViewFactory;
    private final ExprEvaluator[] sortCriteriaEvaluators;
    private final ExprNode[] sortCriteriaExpressions;
    private final ExprEvaluator[] uniqueCriteriaEvaluators;
    private final ExprNode[] uniqueCriteriaExpressions;
    private final EventBean[] eventsPerStream = new EventBean[1];
    private final boolean[] isDescendingValues;
    private final int sortWindowSize;
    private final IStreamSortRankRandomAccess optionalRankedRandomAccess;
    private final AgentInstanceViewFactoryChainContext agentInstanceViewFactoryContext;

    private final Comparator<Object> comparator;

    private TreeMap<Object, Object> sortedEvents;   // key is computed sort-key, value is either List<EventBean> or EventBean
    private Map<Object, Object> uniqueKeySortKeys;  // key is computed unique-key, value is computed sort-key
    private int numberOfEvents;

    /**
     * Ctor.
     * @param sortCriteriaExpressions is the event property names to sort
     * @param descendingValues indicates whether to sort ascending or descending for each field
     * @param sortWindowSize is the window size
     * @param optionalRankedRandomAccess is the friend class handling the random access, if required by
     * expressions
     * @param rankWindowViewFactory for copying this view in a group-by
     * @param isSortUsingCollator for string value sorting using compare or Collator
     */
    public RankWindowView(RankWindowViewFactory rankWindowViewFactory,
                          ExprNode[] uniqueCriteriaExpressions,
                          ExprEvaluator[] uniqueCriteriaEvaluators,
                          ExprNode[] sortCriteriaExpressions,
                          ExprEvaluator[] sortCriteriaEvaluators,
                          boolean[] descendingValues,
                          int sortWindowSize,
                          IStreamSortRankRandomAccess optionalRankedRandomAccess,
                          boolean isSortUsingCollator,
                          AgentInstanceViewFactoryChainContext agentInstanceViewFactoryContext)
    {
        this.rankWindowViewFactory = rankWindowViewFactory;
        this.uniqueCriteriaExpressions = uniqueCriteriaExpressions;
        this.uniqueCriteriaEvaluators = uniqueCriteriaEvaluators;
        this.sortCriteriaExpressions = sortCriteriaExpressions;
        this.sortCriteriaEvaluators = sortCriteriaEvaluators;
        this.isDescendingValues = descendingValues;
        this.sortWindowSize = sortWindowSize;
        this.optionalRankedRandomAccess = optionalRankedRandomAccess;
        this.agentInstanceViewFactoryContext = agentInstanceViewFactoryContext;

        comparator = getComparator(sortCriteriaEvaluators, isSortUsingCollator, isDescendingValues);
        sortedEvents = new TreeMap<Object, Object>(comparator);
        uniqueKeySortKeys = new HashMap<Object, Object>();
    }

    /**
     * Returns the field names supplying the values to sort by.
     * @return field names to sort by
     */
    protected final ExprNode[] getSortCriteriaExpressions()
    {
        return sortCriteriaExpressions;
    }

    /**
     * Returns the flags indicating whether to sort in descending order on each property.
     * @return the isDescending value for each sort property
     */
    protected final boolean[] getIsDescendingValues()
    {
      return isDescendingValues;
    }

    /**
     * Returns the number of elements kept by the sort window.
     * @return size of window
     */
    protected final int getSortWindowSize()
    {
        return sortWindowSize;
    }

    /**
     * Returns the friend handling the random access, cal be null if not required.
     * @return random accessor to sort window contents
     */
    protected IStreamSortRankRandomAccess getOptionalRankedRandomAccess()
    {
        return optionalRankedRandomAccess;
    }

    public View cloneView()
    {
        return rankWindowViewFactory.makeView(agentInstanceViewFactoryContext);
    }

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

    public final void update(EventBean[] newData, EventBean[] oldData)
    {
        OneEventCollection removedEvents = new OneEventCollection();

        // Remove old data
        if (oldData != null)
        {
            for (int i = 0; i < oldData.length; i++)
            {
                Object uniqueKey = getUniqueValues(oldData[i]);
                Object existingSortKey = uniqueKeySortKeys.get(uniqueKey);

                if (existingSortKey == null) {
                    continue;
                }

                EventBean event = removeFromSortedEvents(existingSortKey, uniqueKey);
                if (event != null) {
                    numberOfEvents--;
                    uniqueKeySortKeys.remove(uniqueKey);
                    removedEvents.add(event);
                }
            }
        }

        // Add new data
        if (newData != null)
        {
            for (int i = 0; i < newData.length; i++)
            {
                Object uniqueKey = getUniqueValues(newData[i]);
                Object newSortKey = getSortValues(newData[i]);
                Object existingSortKey = uniqueKeySortKeys.get(uniqueKey);

                // not currently found: its a new entry
                if (existingSortKey == null) {
                    compareAndAddOrPassthru(newData[i], uniqueKey, newSortKey, removedEvents);
                }
                // same unique-key event found already, remove and add again
                else {

                    // key did not change, perform in-place substitute of event
                    if (existingSortKey.equals(newSortKey)) {
                        EventBean replaced = inplaceReplaceSortedEvents(existingSortKey, uniqueKey, newData[i]);
                        if (replaced != null) {
                            removedEvents.add(replaced);
                        }
                    }
                    else {
                        EventBean removed = removeFromSortedEvents(existingSortKey, uniqueKey);
                        if (removed != null) {
                            numberOfEvents--;
                            removedEvents.add(removed);
                        }
                        compareAndAddOrPassthru(newData[i], uniqueKey, newSortKey, removedEvents);
                    }
                }
            }
        }

        // Remove data that sorts to the bottom of the window
        if (numberOfEvents > sortWindowSize)
        {
            while(numberOfEvents > sortWindowSize) {
                Object lastKey = sortedEvents.lastKey();
                Object existing = sortedEvents.get(lastKey);
                if (existing instanceof List) {
                    List<EventBean> existingList = (List<EventBean>) existing;
                    while(numberOfEvents > sortWindowSize && !existingList.isEmpty()) {
                        EventBean newestEvent = existingList.remove(0);
                        Object uniqueKey = getUniqueValues(newestEvent);
                        uniqueKeySortKeys.remove(uniqueKey);
                        numberOfEvents--;
                        removedEvents.add(newestEvent);
                    }
                    if (existingList.isEmpty()) {
                        sortedEvents.remove(lastKey);
                    }
                }
                else {
                    EventBean lastSortedEvent = (EventBean) existing;
                    Object uniqueKey = getUniqueValues(lastSortedEvent);
                    uniqueKeySortKeys.remove(uniqueKey);
                    numberOfEvents--;
                    removedEvents.add(lastSortedEvent);
                    sortedEvents.remove(lastKey);
                }
            }
        }

        // If there are child views, fireStatementStopped update method
        if (optionalRankedRandomAccess != null)
        {
            optionalRankedRandomAccess.refresh(sortedEvents, numberOfEvents, sortWindowSize);
        }
        if (this.hasViews())
        {
            EventBean[] expiredArr = null;
            if (!removedEvents.isEmpty())
            {
                expiredArr = removedEvents.toArray();
            }

            updateChildren(newData, expiredArr);
        }
    }

    private void compareAndAddOrPassthru(EventBean eventBean, Object uniqueKey, Object newSortKey, OneEventCollection removedEvents) {
        // determine full or not
        if (numberOfEvents >= sortWindowSize) {
            int compared = comparator.compare(sortedEvents.lastKey(), newSortKey);

            // this new event will fall outside of the ranks or coincides with the last entry, so its an old event already
            if (compared < 0) {
                removedEvents.add(eventBean);
            }
            // this new event is higher in sort key then the last entry so we are interested
            else {
                uniqueKeySortKeys.put(uniqueKey, newSortKey);
                numberOfEvents++;
                addToSortedEvents(newSortKey, eventBean);
            }
        }
        // not yet filled, need to add
        else {
            uniqueKeySortKeys.put(uniqueKey, newSortKey);
            numberOfEvents++;
            addToSortedEvents(newSortKey, eventBean);
        }
    }

    private EventBean removeFromSortedEvents(Object sortKey, Object uniqueKeyToRemove) {
        Object existing = sortedEvents.get(sortKey);

        EventBean removedOldEvent = null;
        if (existing != null) {
            if (existing instanceof List) {
                List<EventBean> existingList = (List<EventBean>) existing;
                Iterator<EventBean> it = existingList.iterator();
                for (;it.hasNext();) {
                    EventBean eventForRank = it.next();
                    if (getUniqueValues(eventForRank).equals(uniqueKeyToRemove)) {
                        it.remove();
                        removedOldEvent = eventForRank;
                        break;
                    }
                }

                if (existingList.isEmpty()) {
                    sortedEvents.remove(sortKey);
                }
            }
            else {
                removedOldEvent = (EventBean) existing;
                sortedEvents.remove(sortKey);
            }
        }
        return removedOldEvent;
    }

    private EventBean inplaceReplaceSortedEvents(Object sortKey, Object uniqueKeyToReplace, EventBean newData) {
        Object existing = sortedEvents.get(sortKey);

        EventBean replaced = null;
        if (existing != null) {
            if (existing instanceof List) {
                List<EventBean> existingList = (List<EventBean>) existing;
                Iterator<EventBean> it = existingList.iterator();
                for (;it.hasNext();) {
                    EventBean eventForRank = it.next();
                    if (getUniqueValues(eventForRank).equals(uniqueKeyToReplace)) {
                        it.remove();
                        replaced = eventForRank;
                        break;
                    }
                }
                existingList.add(newData)// add to back as this is now the newest event
            }
            else {
                replaced = (EventBean) existing;
                sortedEvents.put(sortKey, newData);
            }
        }
        return replaced;
    }

    private void addToSortedEvents(Object sortKey, EventBean eventBean) {
        Object existing = sortedEvents.get(sortKey);
        if (existing == null) {
            sortedEvents.put(sortKey, eventBean);
        }
        else {
            if (existing instanceof List) {
                List<EventBean> existingList = (List<EventBean>) existing;
                existingList.add(eventBean);
            }
            else {
                List<EventBean> existingList = new LinkedList<EventBean>();
                existingList.add((EventBean)existing);
                existingList.add(eventBean);
                sortedEvents.put(sortKey, existingList);
            }
        }
    }

    public final Iterator<EventBean> iterator()
    {
        return new RankWindowIterator(sortedEvents);
    }

    public final String toString()
    {
        return this.getClass().getName() +
                " uniqueFieldName=" + Arrays.toString(uniqueCriteriaExpressions) +
                " sortFieldName=" + Arrays.toString(sortCriteriaExpressions) +
                " isDescending=" + Arrays.toString(isDescendingValues) +
                " sortWindowSize=" + sortWindowSize;
    }

    private Object getUniqueValues(EventBean theEvent) {
        return getCriteriaKey(eventsPerStream, uniqueCriteriaEvaluators, theEvent, agentInstanceViewFactoryContext);
    }

    private Object getSortValues(EventBean theEvent) {
        return getCriteriaKey(eventsPerStream, sortCriteriaEvaluators, theEvent, agentInstanceViewFactoryContext);
    }

    public static Object getCriteriaKey(EventBean[] eventsPerStream, ExprEvaluator[] evaluators, EventBean theEvent, ExprEvaluatorContext evalContext)
    {
        eventsPerStream[0] = theEvent;
        if (evaluators.length > 1) {
            return getCriteriaMultiKey(eventsPerStream, evaluators, evalContext);
        }
        else {
            return evaluators[0].evaluate(eventsPerStream, true, evalContext);
        }
    }

    public static MultiKeyUntyped getCriteriaMultiKey(EventBean[] eventsPerStream, ExprEvaluator[] evaluators, ExprEvaluatorContext evalContext)
    {
        Object[] result = new Object[evaluators.length];
        int count = 0;
        for(ExprEvaluator expr : evaluators)
        {
            result[count++] = expr.evaluate(eventsPerStream, true, evalContext);
        }
        return new MultiKeyUntyped(result);
    }

    public static Comparator<Object> getComparator(ExprEvaluator[] sortCriteriaEvaluators, boolean isSortUsingCollator, boolean[] isDescendingValues) {
        // determine string-type sorting
        boolean hasStringTypes = false;
        boolean stringTypes[] = new boolean[sortCriteriaEvaluators.length];

        int count = 0;
        for(ExprEvaluator node : sortCriteriaEvaluators)
        {
            if (node.getType() == String.class)
            {
                hasStringTypes = true;
                stringTypes[count] = true;
            }
            count++;
        }

        if (sortCriteriaEvaluators.length > 1) {
            if ((!hasStringTypes) || (!isSortUsingCollator)) {
                MultiKeyComparator comparatorMK = new MultiKeyComparator(isDescendingValues);
                return new MultiKeyCastingComparator(comparatorMK);
            }
            else {
                MultiKeyCollatingComparator comparatorMk = new MultiKeyCollatingComparator(isDescendingValues, stringTypes);
                return new MultiKeyCastingComparator(comparatorMk);
            }
        }
        else {
            if ((!hasStringTypes) || (!isSortUsingCollator)) {
                return new ObjectComparator(isDescendingValues[0]);
            }
            else {
                return new ObjectCollatingComparator(isDescendingValues[0]);
            }
        }
    }

    /**
     * True to indicate the sort window is empty, or false if not empty.
     * @return true if empty sort window
     */
    public boolean isEmpty()
    {
        if (sortedEvents == null)
        {
            return true;
        }
        return sortedEvents.isEmpty();
    }

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

Related Classes of com.espertech.esper.view.ext.RankWindowView

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.