/* *************************************************************************************** * Copyright (C) 2006 EsperTech, Inc. All rights reserved. * * http://www.espertech.com/esper * * 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.pattern; import com.espertech.esper.client.EventBean; import com.espertech.esper.metrics.instrumentation.InstrumentationHelper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; /** * This class represents the state of an "and" operator in the evaluation state tree. */ public class EvalAndStateNode extends EvalStateNode implements Evaluator { protected final EvalAndNode evalAndNode; protected final EvalStateNode[] activeChildNodes; protected Object[] eventsPerChild; /** * Constructor. * * @param parentNode is the parent evaluator to call to indicate truth value * @param evalAndNode is the factory node associated to the state */ public EvalAndStateNode(Evaluator parentNode, EvalAndNode evalAndNode) { super(parentNode); this.evalAndNode = evalAndNode; this.activeChildNodes = new EvalStateNode[evalAndNode.getChildNodes().length]; this.eventsPerChild = new Object[evalAndNode.getChildNodes().length]; } public void removeMatch(Set<EventBean> matchEvent) { boolean quit = false; if (eventsPerChild != null) { for (Object entry : eventsPerChild) { if (entry instanceof MatchedEventMap) { quit = PatternConsumptionUtil.containsEvent(matchEvent, (MatchedEventMap) entry); } else if (entry != null) { List<MatchedEventMap> list = (List<MatchedEventMap>) entry; for (MatchedEventMap map : list) { quit = PatternConsumptionUtil.containsEvent(matchEvent, map); if (quit) { break; } } } if (quit) { break; } } } if (!quit && activeChildNodes != null) { for (EvalStateNode child : activeChildNodes) { if (child != null) { child.removeMatch(matchEvent); } } } if (quit) { quit(); this.getParentEvaluator().evaluateFalse(this, true); } } @Override public EvalNode getFactoryNode() { return evalAndNode; } public final void start(MatchedEventMap beginState) { if (InstrumentationHelper.ENABLED) { InstrumentationHelper.get().qPatternAndStart(evalAndNode, beginState); } // In an "and" expression we need to create a state for all child listeners int count = 0; for (EvalNode node : evalAndNode.getChildNodes()) { EvalStateNode childState = node.newState(this, null, 0L); activeChildNodes[count++] = childState; } // Start all child nodes for (EvalStateNode child : activeChildNodes) { if (child != null) { child.start(beginState); } } if (InstrumentationHelper.ENABLED) { InstrumentationHelper.get().aPatternAndStart(); } } public boolean isFilterStateNode() { return false; } public boolean isNotOperator() { return false; } public boolean isFilterChildNonQuitting() { return false; } public boolean isObserverStateNodeNonRestarting() { return false; } public final void evaluateTrue(MatchedEventMap matchEvent, EvalStateNode fromNode, boolean isQuitted) { if (InstrumentationHelper.ENABLED) { InstrumentationHelper.get().qPatternAndEvaluateTrue(evalAndNode, matchEvent); } Integer indexFrom = null; for (int i = 0; i < activeChildNodes.length; i++) { if (activeChildNodes[i] == fromNode) { indexFrom = i; } } // If one of the children quits, remove the child if (isQuitted && indexFrom != null) { activeChildNodes[indexFrom] = null; } if (eventsPerChild == null || indexFrom == null) { if (InstrumentationHelper.ENABLED) { InstrumentationHelper.get().aPatternAndEvaluateTrue(true); } return; } // If all nodes have events received, the AND expression turns true boolean allHaveEventsExcludingFromChild = true; for (int i = 0; i < eventsPerChild.length; i++) { if (indexFrom != i && eventsPerChild[i] == null) { allHaveEventsExcludingFromChild = false; break; } } // if we don't have events from all child nodes, add event and done if (!allHaveEventsExcludingFromChild) { addMatchEvent(eventsPerChild, indexFrom, matchEvent); if (InstrumentationHelper.ENABLED) { InstrumentationHelper.get().aPatternAndEvaluateTrue(false); } return; } // if all other nodes have quit other then the from-node, don't retain matching event boolean allOtherNodesQuit = true; boolean hasActive = false; for (int i = 0; i < eventsPerChild.length; i++) { if (activeChildNodes[i] != null) { hasActive = true; if (i != indexFrom) { allOtherNodesQuit = false; } } } // if not all other nodes have quit, add event to received list if (!allOtherNodesQuit) { addMatchEvent(eventsPerChild, indexFrom, matchEvent); } // For each combination in eventsPerChild for all other state nodes generate an event to the parent List<MatchedEventMap> result = generateMatchEvents(matchEvent, eventsPerChild, indexFrom); // Check if this is quitting boolean quitted = true; if (hasActive) { for (EvalStateNode stateNode : activeChildNodes) { if (stateNode != null && !(stateNode.isNotOperator())) { quitted = false; } } } // So we are quitting if all non-not child nodes have quit, since the not-node wait for evaluate false if (quitted) { quitInternal(); } // Send results to parent for (MatchedEventMap theEvent : result) { this.getParentEvaluator().evaluateTrue(theEvent, this, quitted); } if (InstrumentationHelper.ENABLED) { InstrumentationHelper.get().aPatternAndEvaluateTrue(eventsPerChild == null); } } public final void evaluateFalse(EvalStateNode fromNode, boolean restartable) { if (InstrumentationHelper.ENABLED) { InstrumentationHelper.get().qPatternAndEvaluateFalse(evalAndNode); } Integer indexFrom = null; for (int i = 0; i < activeChildNodes.length; i++) { if (activeChildNodes[i] == fromNode) { activeChildNodes[i] = null; indexFrom = i; } } if (indexFrom != null) { eventsPerChild[indexFrom] = null; } // The and node cannot turn true anymore, might as well quit all child nodes quitInternal(); this.getParentEvaluator().evaluateFalse(this, restartable ? true : false); if (InstrumentationHelper.ENABLED) { InstrumentationHelper.get().aPatternAndEvaluateFalse(); } } /** * Generate a list of matching event combinations constisting of the events per child that are passed in. * * @param matchEvent can be populated with prior events that must be passed on * @param eventsPerChild is the list of events for each child node to the "And" node. * @param indexFrom from-index * @return list of events populated with all possible combinations */ public static List<MatchedEventMap> generateMatchEvents(MatchedEventMap matchEvent, Object[] eventsPerChild, int indexFrom) { // Place event list for each child state node into an array, excluding the node where the event came from ArrayList<List<MatchedEventMap>> listArray = new ArrayList<List<MatchedEventMap>>(); int index = 0; for (int i = 0; i < eventsPerChild.length; i++) { Object eventsChild = eventsPerChild[i]; if (indexFrom != i && eventsChild != null) { if (eventsChild instanceof MatchedEventMap) { listArray.add(index++, Collections.singletonList((MatchedEventMap) eventsChild)); } else { listArray.add(index++, (List<MatchedEventMap>) eventsChild); } } } // Recusively generate MatchedEventMap instances for all accumulated events List<MatchedEventMap> results = new ArrayList<MatchedEventMap>(); generateMatchEvents(listArray, 0, results, matchEvent); return results; } /** * For each combination of MatchedEventMap instance in all collections, add an entry to the list. * Recursive method. * * @param eventList is an array of lists containing MatchedEventMap instances to combine * @param index is the current index into the array * @param result is the resulting list of MatchedEventMap * @param matchEvent is the start MatchedEventMap to generate from */ protected static void generateMatchEvents(ArrayList<List<MatchedEventMap>> eventList, int index, List<MatchedEventMap> result, MatchedEventMap matchEvent) { List<MatchedEventMap> events = eventList.get(index); for (MatchedEventMap theEvent : events) { MatchedEventMap current = matchEvent.shallowCopy(); current.merge(theEvent); // If this is the very last list in the array of lists, add accumulated MatchedEventMap events to result if ((index + 1) == eventList.size()) { result.add(current); } else { // make a copy of the event collection and hand to next list of events generateMatchEvents(eventList, index + 1, result, current); } } } public final void quit() { if (eventsPerChild == null) { return; } if (InstrumentationHelper.ENABLED) { InstrumentationHelper.get().qPatternAndQuit(evalAndNode); } quitInternal(); if (InstrumentationHelper.ENABLED) { InstrumentationHelper.get().aPatternAndQuit(); } } public final void accept(EvalStateNodeVisitor visitor) { visitor.visitAnd(evalAndNode.getFactoryNode(), this, eventsPerChild); for (EvalStateNode node : activeChildNodes) { if (node != null) { node.accept(visitor); } } } public final String toString() { return "EvalAndStateNode"; } public static void addMatchEvent(Object[] eventsPerChild, int indexFrom, MatchedEventMap matchEvent) { Object matchEventHolder = eventsPerChild[indexFrom]; if (matchEventHolder == null) { eventsPerChild[indexFrom] = matchEvent; } else if (matchEventHolder instanceof MatchedEventMap) { List<MatchedEventMap> list = new ArrayList<MatchedEventMap>(4); list.add((MatchedEventMap) matchEventHolder); list.add(matchEvent); eventsPerChild[indexFrom] = list; } else { List<MatchedEventMap> list = (List<MatchedEventMap>) matchEventHolder; list.add(matchEvent); } } private void quitInternal() { for (EvalStateNode child : activeChildNodes) { if (child != null) { child.quit(); } } Arrays.fill(activeChildNodes, null); eventsPerChild = null; } private static final Logger log = LoggerFactory.getLogger(EvalAndStateNode.class); }