/************************************************************************************** * 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.pattern; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; /** * 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]; } @Override public EvalNode getFactoryNode() { return evalAndNode; } public final void start(MatchedEventMap 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); } } } public boolean isFilterStateNode() { return false; } public boolean isNotOperator() { return false; } public boolean isFilterChildNonQuitting() { return false; } public final void evaluateTrue(MatchedEventMap matchEvent, EvalStateNode fromNode, boolean isQuitted) { 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) { return; } // Add the event received to the list of events per child addMatchEvent(eventsPerChild, indexFrom, matchEvent); // If all nodes have events received, the AND expression turns true boolean allHaveEvents = true; for (int i = 0; i < eventsPerChild.length; i++) { if (eventsPerChild[i] == null) { allHaveEvents = false; break; } } if (!allHaveEvents) { return; } // For each combination in eventsPerChild for all other state nodes generate an event to the parent List<MatchedEventMap> result = generateMatchEvents(matchEvent, eventsPerChild, indexFrom); boolean hasActive = false; for (int i = 0 ; i < activeChildNodes.length; i++) { if (activeChildNodes[i] != null) { hasActive = true; break; } } // 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) { quit(); } // Send results to parent for (MatchedEventMap theEvent : result) { this.getParentEvaluator().evaluateTrue(theEvent, this, quitted); } } public final void evaluateFalse(EvalStateNode fromNode) { 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 this.getParentEvaluator().evaluateFalse(this); quit(); } /** * 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. * @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() { for (EvalStateNode child : activeChildNodes) { if (child != null) { child.quit(); } } Arrays.fill(activeChildNodes, null); eventsPerChild = null; } public final Object accept(EvalStateNodeVisitor visitor, Object data) { return visitor.visit(this, data); } public final Object childrenAccept(EvalStateNodeVisitor visitor, Object data) { for (EvalStateNode node : activeChildNodes) { if (node != null) { node.accept(visitor, data); } } return data; } 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 static final Log log = LogFactory.getLog(EvalAndStateNode.class); }