com.espertech.esper.rowregex.EventRowRegexNFAView.java Source code

Java tutorial

Introduction

Here is the source code for com.espertech.esper.rowregex.EventRowRegexNFAView.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.rowregex;

import com.espertech.esper.client.EventBean;
import com.espertech.esper.client.EventType;
import com.espertech.esper.collection.Pair;
import com.espertech.esper.collection.SingleEventIterator;
import com.espertech.esper.core.context.util.AgentInstanceContext;
import com.espertech.esper.core.service.EPStatementHandleCallback;
import com.espertech.esper.core.service.ExtensionServicesContext;
import com.espertech.esper.epl.agg.service.AggregationServiceMatchRecognize;
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.epl.expression.ExprPreviousMatchRecognizeNode;
import com.espertech.esper.epl.spec.MatchRecognizeDefineItem;
import com.espertech.esper.epl.spec.MatchRecognizeMeasureItem;
import com.espertech.esper.epl.spec.MatchRecognizeSkipEnum;
import com.espertech.esper.epl.spec.MatchRecognizeSpec;
import com.espertech.esper.schedule.ScheduleHandleCallback;
import com.espertech.esper.schedule.ScheduleSlot;
import com.espertech.esper.util.ExecutionPathDebugLog;
import com.espertech.esper.util.StopCallback;
import com.espertech.esper.view.ViewSupport;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.*;

/**
 * View for match recognize support.
 */
public class EventRowRegexNFAView extends ViewSupport implements StopCallback, EventRowRegexNFAViewService {
    private static final Log log = LogFactory.getLog(EventRowRegexNFAView.class);
    private static final boolean IS_DEBUG = false;
    private static final Iterator<EventBean> NULL_ITERATOR = new SingleEventIterator(null);

    private final MatchRecognizeSpec matchRecognizeSpec;
    private final boolean isUnbound;
    private final boolean isIterateOnly;
    private final boolean isSelectAsksMultimatches;

    private final EventType compositeEventType;
    private final EventType rowEventType;
    private final AgentInstanceContext agentInstanceContext;
    private final AggregationServiceMatchRecognize aggregationService;

    // for interval-handling
    private final ScheduleSlot scheduleSlot;
    private final EPStatementHandleCallback handle;
    private final TreeMap<Long, Object> schedule;

    private final ExprEvaluator[] columnEvaluators;
    private final String[] columnNames;

    private final RegexNFAState[] startStates;
    private final RegexNFAState[] allStates;

    private final String[] variablesArray;
    private final LinkedHashMap<String, Pair<Integer, Boolean>> variableStreams;
    private final Map<Integer, String> streamsVariables;
    private final Set<String> variablesSingle;

    // state
    private RegexPartitionStateRepo regexPartitionStateRepo;
    private LinkedHashSet<EventBean> windowMatchedEventset; // this is NOT per partition - some optimizations are done for batch-processing (minus is out-of-sequence in partition) 
    private int eventSequenceNumber;

    /**
     * Ctor.
     * @param compositeEventType final event type
     * @param rowEventType event type for input rows
     * @param matchRecognizeSpec specification
     * @param variableStreams variables and their assigned stream number
     * @param streamsVariables stream number and the assigned variable
     * @param variablesSingle single variables
     * @param callbacksPerIndex  for handling the 'prev' function
     * @param aggregationService handles aggregations
     * @param isUnbound true if unbound stream
     * @param isIterateOnly true for iterate-only
     * @param isSelectAsksMultimatches if asking for multimatches
     */
    public EventRowRegexNFAView(EventType compositeEventType, EventType rowEventType,
            MatchRecognizeSpec matchRecognizeSpec, LinkedHashMap<String, Pair<Integer, Boolean>> variableStreams,
            Map<Integer, String> streamsVariables, Set<String> variablesSingle,
            AgentInstanceContext agentInstanceContext,
            TreeMap<Integer, List<ExprPreviousMatchRecognizeNode>> callbacksPerIndex,
            AggregationServiceMatchRecognize aggregationService, boolean isUnbound, boolean isIterateOnly,
            boolean isSelectAsksMultimatches) {
        this.matchRecognizeSpec = matchRecognizeSpec;
        this.compositeEventType = compositeEventType;
        this.rowEventType = rowEventType;
        this.variableStreams = variableStreams;
        this.variablesArray = variableStreams.keySet().toArray(new String[variableStreams.keySet().size()]);
        this.streamsVariables = streamsVariables;
        this.variablesSingle = variablesSingle;
        this.aggregationService = aggregationService;
        this.isUnbound = isUnbound;
        this.isIterateOnly = isIterateOnly;
        this.agentInstanceContext = agentInstanceContext;
        this.isSelectAsksMultimatches = isSelectAsksMultimatches;

        if (matchRecognizeSpec.getInterval() != null) {
            scheduleSlot = agentInstanceContext.getStatementContext().getScheduleBucket().allocateSlot();
            ScheduleHandleCallback callback = new ScheduleHandleCallback() {
                public void scheduledTrigger(ExtensionServicesContext extensionServicesContext) {
                    EventRowRegexNFAView.this.triggered();
                }
            };
            handle = new EPStatementHandleCallback(agentInstanceContext.getEpStatementAgentInstanceHandle(),
                    callback);
            schedule = new TreeMap<Long, Object>();

            agentInstanceContext.addTerminationCallback(this);
        } else {
            scheduleSlot = null;
            handle = null;
            schedule = null;
        }

        this.windowMatchedEventset = new LinkedHashSet<EventBean>();

        // handle "previous" function nodes (performance-optimized for direct index access)
        RegexPartitionStateRandomAccessGetter randomAccessByIndexGetter;
        if (!callbacksPerIndex.isEmpty()) {
            // Build an array of indexes
            int[] randomAccessIndexesRequested = new int[callbacksPerIndex.size()];
            int count = 0;
            for (Map.Entry<Integer, List<ExprPreviousMatchRecognizeNode>> entry : callbacksPerIndex.entrySet()) {
                randomAccessIndexesRequested[count] = entry.getKey();
                count++;
            }
            randomAccessByIndexGetter = new RegexPartitionStateRandomAccessGetter(randomAccessIndexesRequested,
                    isUnbound);

            // Since an expression such as "prior(2, price), prior(8, price)" translates into {2, 8} the relative index is {0, 1}.
            // Map the expression-supplied index to a relative index
            count = 0;
            for (Map.Entry<Integer, List<ExprPreviousMatchRecognizeNode>> entry : callbacksPerIndex.entrySet()) {
                for (ExprPreviousMatchRecognizeNode callback : entry.getValue()) {
                    callback.setGetter(randomAccessByIndexGetter);
                    callback.setAssignedIndex(count);
                }
                count++;
            }
        } else {
            randomAccessByIndexGetter = null;
        }

        Map<String, ExprNode> variableDefinitions = new LinkedHashMap<String, ExprNode>();
        for (MatchRecognizeDefineItem defineItem : matchRecognizeSpec.getDefines()) {
            variableDefinitions.put(defineItem.getIdentifier(), defineItem.getExpression());
        }

        // build states
        RegexNFAStrandResult strand = EventRowRegexHelper.recursiveBuildStartStates(matchRecognizeSpec.getPattern(),
                variableDefinitions, variableStreams);
        startStates = strand.getStartStates().toArray(new RegexNFAState[strand.getStartStates().size()]);
        allStates = strand.getAllStates().toArray(new RegexNFAState[strand.getAllStates().size()]);

        if (log.isDebugEnabled() || IS_DEBUG) {
            log.info("NFA tree:\n" + print(startStates));
        }

        // create evaluators
        columnNames = new String[matchRecognizeSpec.getMeasures().size()];
        columnEvaluators = new ExprEvaluator[matchRecognizeSpec.getMeasures().size()];
        int count = 0;
        for (MatchRecognizeMeasureItem measureItem : matchRecognizeSpec.getMeasures()) {
            columnNames[count] = measureItem.getName();
            columnEvaluators[count] = measureItem.getExpr().getExprEvaluator();
            count++;
        }

        // create state repository
        if (this.matchRecognizeSpec.getPartitionByExpressions().isEmpty()) {
            regexPartitionStateRepo = new RegexPartitionStateRepoNoGroup(randomAccessByIndexGetter,
                    matchRecognizeSpec.getInterval() != null);
        } else {
            regexPartitionStateRepo = new RegexPartitionStateRepoGroup(randomAccessByIndexGetter,
                    ExprNodeUtility.getEvaluators(matchRecognizeSpec.getPartitionByExpressions()),
                    matchRecognizeSpec.getInterval() != null, agentInstanceContext);
        }
    }

    public void stop() {
        if (handle != null) {
            agentInstanceContext.getStatementContext().getSchedulingService().remove(handle, scheduleSlot);
        }
    }

    public void init(EventBean[] newEvents) {
        updateInternal(newEvents, null, false);
    }

    public void update(EventBean[] newData, EventBean[] oldData) {
        updateInternal(newData, oldData, true);
    }

    private void updateInternal(EventBean[] newData, EventBean[] oldData, boolean postOutput) {
        if (isIterateOnly) {
            if (oldData != null) {
                regexPartitionStateRepo.removeOld(oldData, false, new boolean[oldData.length]);
            }
            if (newData != null) {
                for (EventBean newEvent : newData) {
                    RegexPartitionState partitionState = regexPartitionStateRepo.getState(newEvent, true);
                    if ((partitionState != null) && (partitionState.getRandomAccess() != null)) {
                        partitionState.getRandomAccess().newEventPrepare(newEvent);
                    }
                }
            }
            return;
        }

        if (oldData != null) {
            boolean isOutOfSequenceRemove = false;

            EventBean first = null;
            if (!windowMatchedEventset.isEmpty()) {
                first = windowMatchedEventset.iterator().next();
            }

            // remove old data, if found in set
            boolean[] found = new boolean[oldData.length];
            int count = 0;

            // detect out-of-sequence removes
            for (EventBean oldEvent : oldData) {
                boolean removed = windowMatchedEventset.remove(oldEvent);
                if (removed) {
                    if ((oldEvent != first) && (first != null)) {
                        isOutOfSequenceRemove = true;
                    }
                    found[count++] = true;
                    if (!windowMatchedEventset.isEmpty()) {
                        first = windowMatchedEventset.iterator().next();
                    }
                }
            }

            // remove old events from repository - and let the repository know there are no interesting events left
            regexPartitionStateRepo.removeOld(oldData, windowMatchedEventset.isEmpty(), found);

            // reset, rebuilding state
            if (isOutOfSequenceRemove) {
                regexPartitionStateRepo = regexPartitionStateRepo.copyForIterate();
                windowMatchedEventset = new LinkedHashSet<EventBean>();
                Iterator<EventBean> parentEvents = this.getParent().iterator();
                EventRowRegexIteratorResult iteratorResult = processIterator(startStates, parentEvents,
                        regexPartitionStateRepo);
                eventSequenceNumber = iteratorResult.getEventSequenceNum();
            }
        }

        if (newData == null) {
            return;
        }

        List<RegexNFAStateEntry> endStates = new ArrayList<RegexNFAStateEntry>();
        List<RegexNFAStateEntry> nextStates = new ArrayList<RegexNFAStateEntry>();

        for (EventBean newEvent : newData) {
            eventSequenceNumber++;

            // get state holder for this event
            RegexPartitionState partitionState = regexPartitionStateRepo.getState(newEvent, true);
            List<RegexNFAStateEntry> currentStates = partitionState.getCurrentStates();

            // add start states for each new event
            for (RegexNFAState startState : startStates) {
                long time = 0;
                if (matchRecognizeSpec.getInterval() != null) {
                    time = agentInstanceContext.getStatementContext().getSchedulingService().getTime();
                }
                currentStates.add(new RegexNFAStateEntry(eventSequenceNumber, time, startState,
                        new EventBean[variableStreams.size()], new int[allStates.length], null,
                        partitionState.getOptionalKeys()));
            }

            if (partitionState.getRandomAccess() != null) {
                partitionState.getRandomAccess().newEventPrepare(newEvent);
            }

            if ((ExecutionPathDebugLog.isDebugEnabled) && (log.isDebugEnabled()) || (IS_DEBUG)) {
                log.info("Evaluating event " + newEvent.getUnderlying() + "\n" + "current : "
                        + printStates(currentStates));
            }

            step(currentStates, newEvent, nextStates, endStates, !isUnbound, eventSequenceNumber,
                    partitionState.getOptionalKeys());

            if ((ExecutionPathDebugLog.isDebugEnabled) && (log.isDebugEnabled()) || (IS_DEBUG)) {
                log.info("Evaluated event " + newEvent.getUnderlying() + "\n" + "next : " + printStates(nextStates)
                        + "\n" + "end : " + printStates(endStates));
            }

            partitionState.setCurrentStates(nextStates);
            nextStates = currentStates;
            nextStates.clear();
        }

        if (endStates.isEmpty()) {
            return;
        }

        // perform inter-ranking and elimination of duplicate matches
        if (!matchRecognizeSpec.isAllMatches()) {
            endStates = rankEndStatesMultiPartition(endStates);
        }

        // handle interval for the set of matches
        if (matchRecognizeSpec.getInterval() != null) {
            Iterator<RegexNFAStateEntry> it = endStates.iterator();
            for (; it.hasNext();) {
                RegexNFAStateEntry endState = it.next();
                RegexPartitionState partitionState = regexPartitionStateRepo.getState(endState.getPartitionKey());
                if (partitionState == null) {
                    log.warn("Null partition state encountered, skipping row");
                    continue;
                }

                long matchBeginTime = endState.getMatchBeginEventTime();
                long current = agentInstanceContext.getStatementContext().getSchedulingService().getTime();
                long deltaFromStart = current - matchBeginTime;
                long deltaUntil = matchRecognizeSpec.getInterval().getMSec() - deltaFromStart;

                if (schedule.containsKey(matchBeginTime)) {
                    scheduleCallback(deltaUntil, endState);
                    it.remove();
                } else {
                    if (deltaFromStart < deltaUntil) {
                        scheduleCallback(deltaUntil, endState);
                        it.remove();
                    }
                }
            }
            if (endStates.isEmpty()) {
                return;
            }
        }
        // handle skip for incremental mode
        else if (matchRecognizeSpec.getSkip().getSkip() == MatchRecognizeSkipEnum.PAST_LAST_ROW) {
            Iterator<RegexNFAStateEntry> endStateIter = endStates.iterator();
            for (; endStateIter.hasNext();) {
                RegexNFAStateEntry endState = endStateIter.next();
                RegexPartitionState partitionState = regexPartitionStateRepo.getState(endState.getPartitionKey());
                if (partitionState == null) {
                    log.warn("Null partition state encountered, skipping row");
                    continue;
                }

                Iterator<RegexNFAStateEntry> stateIter = partitionState.getCurrentStates().iterator();
                for (; stateIter.hasNext();) {
                    RegexNFAStateEntry currentState = stateIter.next();
                    if (currentState.getMatchBeginEventSeqNo() <= endState.getMatchEndEventSeqNo()) {
                        stateIter.remove();
                    }
                }
            }
        } else if (matchRecognizeSpec.getSkip().getSkip() == MatchRecognizeSkipEnum.TO_NEXT_ROW) {
            Iterator<RegexNFAStateEntry> endStateIter = endStates.iterator();
            for (; endStateIter.hasNext();) {
                RegexNFAStateEntry endState = endStateIter.next();
                RegexPartitionState partitionState = regexPartitionStateRepo.getState(endState.getPartitionKey());
                if (partitionState == null) {
                    log.warn("Null partition state encountered, skipping row");
                    continue;
                }

                Iterator<RegexNFAStateEntry> stateIter = partitionState.getCurrentStates().iterator();
                for (; stateIter.hasNext();) {
                    RegexNFAStateEntry currentState = stateIter.next();
                    if (currentState.getMatchBeginEventSeqNo() <= endState.getMatchBeginEventSeqNo()) {
                        stateIter.remove();
                    }
                }
            }
        }

        EventBean[] outBeans = new EventBean[endStates.size()];
        int count = 0;
        for (RegexNFAStateEntry endState : endStates) {
            outBeans[count++] = generateOutputRow(endState);

            // check partition state - if empty delete (no states and no random access)
            if (endState.getPartitionKey() != null) {
                RegexPartitionState state = regexPartitionStateRepo.getState(endState.getPartitionKey());
                if (state.getCurrentStates().isEmpty() && state.getRandomAccess() == null) {
                    regexPartitionStateRepo.removeState(endState.getPartitionKey());
                }
            }
        }

        if (postOutput) {
            updateChildren(outBeans, null);
        }
    }

    private RegexNFAStateEntry rankEndStates(List<RegexNFAStateEntry> endStates) {

        RegexNFAStateEntry found = null;
        int min = Integer.MAX_VALUE;
        boolean multipleMinimums = false;
        for (RegexNFAStateEntry state : endStates) {
            if (state.getMatchBeginEventSeqNo() < min) {
                found = state;
                min = state.getMatchBeginEventSeqNo();
            } else if (state.getMatchBeginEventSeqNo() == min) {
                multipleMinimums = true;
            }
        }

        if (!multipleMinimums) {
            Collections.singletonList(found);
        }

        int[] best = null;
        found = null;
        for (RegexNFAStateEntry state : endStates) {
            if (state.getMatchBeginEventSeqNo() != min) {
                continue;
            }
            if (best == null) {
                best = state.getGreedycountPerState();
                found = state;
            } else {
                int[] current = state.getGreedycountPerState();
                if (compare(current, best)) {
                    best = current;
                    found = state;
                }
            }
        }

        return found;
    }

    private boolean compare(int[] current, int[] best) {
        for (RegexNFAState state : allStates) {
            if (state.isGreedy() == null) {
                continue;
            }
            if (state.isGreedy()) {
                if (current[state.getNodeNumFlat()] > best[state.getNodeNumFlat()]) {
                    return true;
                }
            } else {
                if (current[state.getNodeNumFlat()] < best[state.getNodeNumFlat()]) {
                    return true;
                }
            }
        }

        return false;
    }

    private EventRowRegexIteratorResult processIterator(RegexNFAState[] startStates, Iterator<EventBean> events,
            RegexPartitionStateRepo regexPartitionStateRepo) {
        List<RegexNFAStateEntry> endStates = new ArrayList<RegexNFAStateEntry>();
        List<RegexNFAStateEntry> nextStates = new ArrayList<RegexNFAStateEntry>();
        List<RegexNFAStateEntry> currentStates;
        int eventSequenceNumber = 0;

        EventBean theEvent;
        for (; events.hasNext();) {
            theEvent = events.next();
            eventSequenceNumber++;

            RegexPartitionState partitionState = regexPartitionStateRepo.getState(theEvent, false);
            currentStates = partitionState.getCurrentStates();

            // add start states for each new event
            for (RegexNFAState startState : startStates) {
                long time = 0;
                if (matchRecognizeSpec.getInterval() != null) {
                    time = agentInstanceContext.getStatementContext().getSchedulingService().getTime();
                }
                currentStates.add(new RegexNFAStateEntry(eventSequenceNumber, time, startState,
                        new EventBean[variableStreams.size()], new int[allStates.length], null,
                        partitionState.getOptionalKeys()));
            }

            if (partitionState.getRandomAccess() != null) {
                partitionState.getRandomAccess().existingEventPrepare(theEvent);
            }

            if ((ExecutionPathDebugLog.isDebugEnabled) && (log.isDebugEnabled()) || (IS_DEBUG)) {
                log.info("Evaluating event " + theEvent.getUnderlying() + "\n" + "current : "
                        + printStates(currentStates));
            }

            step(currentStates, theEvent, nextStates, endStates, false, eventSequenceNumber,
                    partitionState.getOptionalKeys());

            if ((ExecutionPathDebugLog.isDebugEnabled) && (log.isDebugEnabled()) || (IS_DEBUG)) {
                log.info("Evaluating event " + theEvent.getUnderlying() + "\n" + "next : " + printStates(nextStates)
                        + "\n" + "end : " + printStates(endStates));
            }

            partitionState.setCurrentStates(nextStates);
            nextStates = currentStates;
            nextStates.clear();
        }

        return new EventRowRegexIteratorResult(endStates, eventSequenceNumber);
    }

    public EventType getEventType() {
        return rowEventType;
    }

    public Iterator<EventBean> iterator() {
        if (isUnbound) {
            return NULL_ITERATOR;
        }

        Iterator<EventBean> it = parent.iterator();

        RegexPartitionStateRepo regexPartitionStateRepoNew = regexPartitionStateRepo.copyForIterate();

        EventRowRegexIteratorResult iteratorResult = processIterator(startStates, it, regexPartitionStateRepoNew);
        List<RegexNFAStateEntry> endStates = iteratorResult.getEndStates();
        if (endStates.isEmpty()) {
            return NULL_ITERATOR;
        } else {
            endStates = rankEndStatesMultiPartition(endStates);
        }

        List<EventBean> output = new ArrayList<EventBean>();
        for (RegexNFAStateEntry endState : endStates) {
            output.add(generateOutputRow(endState));
        }
        return output.iterator();
    }

    private List<RegexNFAStateEntry> rankEndStatesMultiPartition(List<RegexNFAStateEntry> endStates) {
        if (endStates.isEmpty()) {
            return endStates;
        }
        if (endStates.size() == 1) {
            return endStates;
        }

        // unpartitioned case -
        if (matchRecognizeSpec.getPartitionByExpressions().isEmpty()) {
            return rankEndStatesWithinPartitionByStart(endStates);
        }

        // partitioned case - structure end states by partition
        Map<Object, Object> perPartition = new LinkedHashMap<Object, Object>();
        for (RegexNFAStateEntry endState : endStates) {
            Object value = perPartition.get(endState.getPartitionKey());
            if (value == null) {
                perPartition.put(endState.getPartitionKey(), endState);
            } else if (value instanceof List) {
                List<RegexNFAStateEntry> entries = (List<RegexNFAStateEntry>) value;
                entries.add(endState);
            } else {
                List<RegexNFAStateEntry> entries = new ArrayList<RegexNFAStateEntry>();
                entries.add((RegexNFAStateEntry) value);
                entries.add(endState);
                perPartition.put(endState.getPartitionKey(), entries);
            }
        }

        List<RegexNFAStateEntry> finalEndStates = new ArrayList<RegexNFAStateEntry>();
        for (Map.Entry<Object, Object> entry : perPartition.entrySet()) {
            if (entry.getValue() instanceof RegexNFAStateEntry) {
                finalEndStates.add((RegexNFAStateEntry) entry.getValue());
            } else {
                List<RegexNFAStateEntry> entries = (List<RegexNFAStateEntry>) entry.getValue();
                finalEndStates.addAll(rankEndStatesWithinPartitionByStart(entries));
            }
        }
        return finalEndStates;
    }

    private List<RegexNFAStateEntry> rankEndStatesWithinPartitionByStart(List<RegexNFAStateEntry> endStates) {
        if (endStates.isEmpty()) {
            return endStates;
        }
        if (endStates.size() == 1) {
            return endStates;
        }

        TreeMap<Integer, Object> endStatesPerBeginEvent = new TreeMap<Integer, Object>();
        for (RegexNFAStateEntry entry : endStates) {
            Integer endNum = entry.getMatchBeginEventSeqNo();
            Object value = endStatesPerBeginEvent.get(endNum);
            if (value == null) {
                endStatesPerBeginEvent.put(endNum, entry);
            } else if (value instanceof List) {
                List<RegexNFAStateEntry> entries = (List<RegexNFAStateEntry>) value;
                entries.add(entry);
            } else {
                List<RegexNFAStateEntry> entries = new ArrayList<RegexNFAStateEntry>();
                entries.add((RegexNFAStateEntry) value);
                entries.add(entry);
                endStatesPerBeginEvent.put(endNum, entries);
            }
        }

        if (endStatesPerBeginEvent.size() == 1) {
            List<RegexNFAStateEntry> endStatesUnranked = (List<RegexNFAStateEntry>) endStatesPerBeginEvent.values()
                    .iterator().next();
            if (matchRecognizeSpec.isAllMatches()) {
                return endStatesUnranked;
            }
            RegexNFAStateEntry chosen = rankEndStates(endStatesUnranked);
            return Collections.singletonList(chosen);
        }

        List<RegexNFAStateEntry> endStatesRanked = new ArrayList<RegexNFAStateEntry>();
        Set<Integer> keyset = endStatesPerBeginEvent.keySet();
        Integer[] keys = keyset.toArray(new Integer[keyset.size()]);
        for (Integer key : keys) {
            Object value = endStatesPerBeginEvent.remove(key);
            if (value == null) {
                continue;
            }

            RegexNFAStateEntry entryTaken;
            if (value instanceof List) {
                List<RegexNFAStateEntry> endStatesUnranked = (List<RegexNFAStateEntry>) value;
                if (endStatesUnranked.isEmpty()) {
                    continue;
                }
                entryTaken = rankEndStates(endStatesUnranked);

                if (matchRecognizeSpec.isAllMatches()) {
                    endStatesRanked.addAll(endStatesUnranked); // we take all matches and don't rank except to determine skip-past
                } else {
                    endStatesRanked.add(entryTaken);
                }
            } else {
                entryTaken = (RegexNFAStateEntry) value;
                endStatesRanked.add(entryTaken);
            }
            // could be null as removals take place

            if (entryTaken != null) {
                if (matchRecognizeSpec.getSkip().getSkip() == MatchRecognizeSkipEnum.PAST_LAST_ROW) {
                    int skipPastRow = entryTaken.getMatchEndEventSeqNo();
                    removeSkippedEndStates(endStatesPerBeginEvent, skipPastRow);
                } else if (matchRecognizeSpec.getSkip().getSkip() == MatchRecognizeSkipEnum.TO_NEXT_ROW) {
                    int skipPastRow = entryTaken.getMatchBeginEventSeqNo();
                    removeSkippedEndStates(endStatesPerBeginEvent, skipPastRow);
                }
            }
        }

        return endStatesRanked;
    }

    private void removeSkippedEndStates(TreeMap<Integer, Object> endStatesPerEndEvent, int skipPastRow) {
        for (Map.Entry<Integer, Object> entry : endStatesPerEndEvent.entrySet()) {
            Object value = entry.getValue();

            if (value instanceof List) {
                List<RegexNFAStateEntry> endStatesUnranked = (List<RegexNFAStateEntry>) value;
                Iterator<RegexNFAStateEntry> it = endStatesUnranked.iterator();
                for (; it.hasNext();) {
                    RegexNFAStateEntry endState = it.next();
                    if (endState.getMatchBeginEventSeqNo() <= skipPastRow) {
                        it.remove();
                    }
                }
            } else {
                RegexNFAStateEntry endState = (RegexNFAStateEntry) value;
                if (endState.getMatchBeginEventSeqNo() <= skipPastRow) {
                    endStatesPerEndEvent.put(entry.getKey(), null);
                }
            }
        }
    }

    private void step(List<RegexNFAStateEntry> currentStates, EventBean theEvent,
            List<RegexNFAStateEntry> nextStates, List<RegexNFAStateEntry> endStates, boolean isRetainEventSet,
            int currentEventSequenceNumber, Object partitionKey) {
        for (RegexNFAStateEntry currentState : currentStates) {
            EventBean[] eventsPerStream = currentState.getEventsPerStream();
            int currentStateStreamNum = currentState.getState().getStreamNum();
            eventsPerStream[currentStateStreamNum] = theEvent;

            if (currentState.getState().matches(eventsPerStream, agentInstanceContext)) {
                if (isRetainEventSet) {
                    this.windowMatchedEventset.add(theEvent);
                }
                List<RegexNFAState> nextStatesFromHere = currentState.getState().getNextStates();

                // save state for each next state
                boolean copy = nextStatesFromHere.size() > 1;
                for (RegexNFAState next : nextStatesFromHere) {
                    EventBean[] eventsForState = eventsPerStream;
                    MultimatchState[] multimatches = currentState.getOptionalMultiMatches();
                    int[] greedyCounts = currentState.getGreedycountPerState();

                    if (copy) {
                        eventsForState = new EventBean[eventsForState.length];
                        System.arraycopy(eventsPerStream, 0, eventsForState, 0, eventsForState.length);

                        int[] greedyCountsCopy = new int[greedyCounts.length];
                        System.arraycopy(greedyCounts, 0, greedyCountsCopy, 0, greedyCounts.length);
                        greedyCounts = greedyCountsCopy;

                        if (isSelectAsksMultimatches) {
                            multimatches = deepCopy(multimatches);
                        }
                    }

                    if ((isSelectAsksMultimatches) && (currentState.getState().isMultiple())) {
                        multimatches = addTag(currentState.getState().getStreamNum(), theEvent, multimatches);
                    }

                    if ((currentState.getState().isGreedy() != null) && (currentState.getState().isGreedy())) {
                        greedyCounts[currentState.getState().getNodeNumFlat()]++;
                    }

                    RegexNFAStateEntry entry = new RegexNFAStateEntry(currentState.getMatchBeginEventSeqNo(),
                            currentState.getMatchBeginEventTime(), next, eventsForState, greedyCounts, multimatches,
                            partitionKey);
                    if (next instanceof RegexNFAStateEnd) {
                        entry.setMatchEndEventSeqNo(currentEventSequenceNumber);
                        endStates.add(entry);
                    } else {
                        nextStates.add(entry);
                    }
                }
            }
        }
    }

    private MultimatchState[] deepCopy(MultimatchState[] multimatchStates) {
        if (multimatchStates == null) {
            return null;
        }

        MultimatchState[] copy = new MultimatchState[multimatchStates.length];
        for (int i = 0; i < copy.length; i++) {
            if (multimatchStates[i] != null) {
                copy[i] = new MultimatchState(multimatchStates[i]);
            }
        }

        return copy;
    }

    private MultimatchState[] addTag(int streamNum, EventBean theEvent, MultimatchState[] multimatches) {
        if (multimatches == null) {
            multimatches = new MultimatchState[variablesArray.length];
        }

        MultimatchState state = multimatches[streamNum];
        if (state == null) {
            multimatches[streamNum] = new MultimatchState(theEvent);
            return multimatches;
        }

        multimatches[streamNum].add(theEvent);
        return multimatches;
    }

    private String printStates(List<RegexNFAStateEntry> states) {
        StringBuilder buf = new StringBuilder();
        String delimiter = "";
        for (RegexNFAStateEntry state : states) {
            buf.append(delimiter);
            buf.append(state.getState().getNodeNumNested());

            buf.append("{");
            EventBean[] eventsPerStream = state.getEventsPerStream();
            if (eventsPerStream == null) {
                buf.append("null");
            } else {
                String eventDelimiter = "";
                for (Map.Entry<Integer, String> streamVariable : streamsVariables.entrySet()) {
                    buf.append(eventDelimiter);
                    buf.append(streamVariable.getValue());
                    buf.append('=');
                    if (variablesSingle.contains(streamVariable.getValue())) {
                        if (eventsPerStream[streamVariable.getKey()] == null) {
                            buf.append("null");
                        } else {
                            buf.append(eventsPerStream[streamVariable.getKey()].getUnderlying());
                        }
                    } else {
                        int streamNum = state.getState().getStreamNum();
                        if (state.getOptionalMultiMatches() == null) {
                            buf.append("null-mm");
                        } else if (state.getOptionalMultiMatches()[streamNum] == null) {
                            buf.append("no-entry");
                        } else {
                            buf.append("{");
                            String arrayEventDelimiter = "";
                            EventBean[] multiMatch = state.getOptionalMultiMatches()[streamNum].getBuffer();
                            int count = state.getOptionalMultiMatches()[streamNum].getCount();
                            for (int i = 0; i < count; i++) {
                                buf.append(arrayEventDelimiter);
                                buf.append(multiMatch[i].getUnderlying());
                                arrayEventDelimiter = ", ";
                            }
                            buf.append("}");
                        }
                    }
                    eventDelimiter = ", ";
                }
            }
            buf.append("}");

            delimiter = ", ";
        }
        return buf.toString();
    }

    private String print(RegexNFAState[] states) {
        StringWriter writer = new StringWriter();
        PrintWriter buf = new PrintWriter(writer);
        Stack<RegexNFAState> currentStack = new Stack<RegexNFAState>();
        print(Arrays.asList(states), buf, 0, currentStack);
        return writer.toString();
    }

    private void print(List<RegexNFAState> states, PrintWriter writer, int indent,
            Stack<RegexNFAState> currentStack) {

        for (RegexNFAState state : states) {
            indent(writer, indent);
            if (currentStack.contains(state)) {
                writer.println("(self)");
            } else {
                writer.println(printState(state));

                currentStack.push(state);
                print(state.getNextStates(), writer, indent + 4, currentStack);
                currentStack.pop();
            }
        }
    }

    private String printState(RegexNFAState state) {
        if (state instanceof RegexNFAStateEnd) {
            return "#" + state.getNodeNumNested();
        } else {
            return "#" + state.getNodeNumNested() + " " + state.getVariableName() + " s" + state.getStreamNum()
                    + " defined as " + state;
        }
    }

    private void indent(PrintWriter writer, int indent) {
        for (int i = 0; i < indent; i++) {
            writer.append(' ');
        }
    }

    private EventBean generateOutputRow(RegexNFAStateEntry entry) {
        // we first generate a raw row of <String, Object> for each variable name.
        Map<String, Object> rowDataRaw = new HashMap<String, Object>();
        for (Map.Entry<String, Pair<Integer, Boolean>> variableDef : variableStreams.entrySet()) {
            if (!variableDef.getValue().getSecond()) {
                rowDataRaw.put(variableDef.getKey(), entry.getEventsPerStream()[variableDef.getValue().getFirst()]);
            }
        }
        if (aggregationService != null) {
            aggregationService.clearResults();
        }
        if (entry.getOptionalMultiMatches() != null) {
            MultimatchState[] multimatchState = entry.getOptionalMultiMatches();
            for (int i = 0; i < multimatchState.length; i++) {
                if (multimatchState[i] == null) {
                    continue;
                }
                EventBean[] multimatchEvents = multimatchState[i].getEventArray();
                rowDataRaw.put(variablesArray[i], multimatchEvents);

                if (aggregationService != null) {
                    EventBean[] eventsPerStream = entry.getEventsPerStream();

                    for (EventBean multimatchEvent : multimatchEvents) {
                        eventsPerStream[i] = multimatchEvent;
                        aggregationService.applyEnter(eventsPerStream, i, agentInstanceContext);
                    }
                }
            }
        }
        EventBean rowRaw = agentInstanceContext.getStatementContext().getEventAdapterService()
                .adapterForTypedMap(rowDataRaw, compositeEventType);

        Map<String, Object> row = new HashMap<String, Object>();
        int columnNum = 0;
        for (ExprEvaluator expression : columnEvaluators) {
            Object result = expression.evaluate(new EventBean[] { rowRaw }, true, agentInstanceContext);
            row.put(columnNames[columnNum], result);
            columnNum++;
        }

        return agentInstanceContext.getStatementContext().getEventAdapterService().adapterForTypedMap(row,
                rowEventType);
    }

    private void scheduleCallback(long msecAfterCurrentTime, RegexNFAStateEntry endState) {
        long matchBeginTime = endState.getMatchBeginEventTime();
        if (schedule.isEmpty()) {
            schedule.put(matchBeginTime, endState);
            agentInstanceContext.getStatementContext().getSchedulingService().add(msecAfterCurrentTime, handle,
                    scheduleSlot);
        } else {
            Object value = schedule.get(matchBeginTime);
            if (value == null) {
                long currentFirstKey = schedule.firstKey();
                if (currentFirstKey > matchBeginTime) {
                    agentInstanceContext.getStatementContext().getSchedulingService().remove(handle, scheduleSlot);
                    agentInstanceContext.getStatementContext().getSchedulingService().add(msecAfterCurrentTime,
                            handle, scheduleSlot);
                }

                schedule.put(matchBeginTime, endState);
            } else if (value instanceof RegexNFAStateEntry) {
                RegexNFAStateEntry valueEntry = (RegexNFAStateEntry) value;
                List<RegexNFAStateEntry> list = new ArrayList<RegexNFAStateEntry>();
                list.add(valueEntry);
                list.add(endState);
                schedule.put(matchBeginTime, list);
            } else {
                List<RegexNFAStateEntry> list = (List<RegexNFAStateEntry>) value;
                list.add(endState);
            }
        }
    }

    private void triggered() {
        long currentTime = agentInstanceContext.getStatementContext().getSchedulingService().getTime();
        if (schedule.isEmpty()) {
            return;
        }

        List<RegexNFAStateEntry> indicatables = new ArrayList<RegexNFAStateEntry>();
        while (true) {
            long firstKey = schedule.firstKey();
            long cutOffTime = currentTime - this.matchRecognizeSpec.getInterval().getMSec();
            if (firstKey > cutOffTime) {
                break;
            }

            Object value = schedule.remove(firstKey);

            if (value instanceof RegexNFAStateEntry) {
                indicatables.add((RegexNFAStateEntry) value);
            } else {
                List<RegexNFAStateEntry> list = (List<RegexNFAStateEntry>) value;
                indicatables.addAll(list);
            }

            if (schedule.isEmpty()) {
                break;
            }
        }

        // schedule next
        if (!schedule.isEmpty()) {
            long msecAfterCurrentTime = schedule.firstKey() + this.matchRecognizeSpec.getInterval().getMSec()
                    - agentInstanceContext.getStatementContext().getSchedulingService().getTime();
            agentInstanceContext.getStatementContext().getSchedulingService().add(msecAfterCurrentTime, handle,
                    scheduleSlot);
        }

        if (!matchRecognizeSpec.isAllMatches()) {
            indicatables = rankEndStatesMultiPartition(indicatables);
        }

        EventBean[] outBeans = new EventBean[indicatables.size()];
        int count = 0;
        for (RegexNFAStateEntry endState : indicatables) {
            outBeans[count++] = generateOutputRow(endState);
        }

        updateChildren(outBeans, null);
    }
}