/*
***************************************************************************************
* 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.view.std;
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.core.ExprEvaluator;
import com.espertech.esper.epl.expression.core.ExprNode;
import com.espertech.esper.epl.expression.core.ExprNodeUtility;
import com.espertech.esper.metrics.instrumentation.InstrumentationHelper;
import com.espertech.esper.view.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* This view includes only the most recent among events having the same value for the specified field or fields.
* The view accepts the field name as parameter from which the unique values are obtained.
* For example, a trade's symbol could be used as a unique value.
* In this example, the first trade for symbol IBM would be posted as new data to child views.
* When the second trade for symbol IBM arrives the second trade is posted as new data to child views,
* and the first trade is posted as old data.
* Should more than one trades for symbol IBM arrive at the same time (like when batched)
* then the child view will get all new events in newData and all new events in oldData minus the most recent event.
* When the current new event arrives as old data, the the current unique event gets thrown away and
* posted as old data to child views.
* Iteration through the views data shows only the most recent events received for the unique value in the order
* that events arrived in.
* The type of the field returning the unique value can be any type but should override equals and hashCode()
* as the type plays the role of a key in a map storing unique values.
*/
public class UniqueByPropertyView extends ViewSupport implements CloneableView, DataWindowView {
private final UniqueByPropertyViewFactory viewFactory;
protected final ExprEvaluator[] criteriaExpressionsEvals;
protected final Map<Object, EventBean> mostRecentEvents = new HashMap<Object, EventBean>();
private final EventBean[] eventsPerStream = new EventBean[1];
protected final AgentInstanceViewFactoryChainContext agentInstanceViewFactoryContext;
public UniqueByPropertyView(UniqueByPropertyViewFactory viewFactory, AgentInstanceViewFactoryChainContext agentInstanceViewFactoryContext) {
this.viewFactory = viewFactory;
this.criteriaExpressionsEvals = ExprNodeUtility.getEvaluators(viewFactory.criteriaExpressions);
this.agentInstanceViewFactoryContext = agentInstanceViewFactoryContext;
}
public View cloneView() {
return new UniqueByPropertyView(viewFactory, agentInstanceViewFactoryContext);
}
/**
* Returns the name of the field supplying the unique value to keep the most recent record for.
*
* @return expressions for unique value
*/
public final ExprNode[] getCriteriaExpressions() {
return viewFactory.criteriaExpressions;
}
public final EventType getEventType() {
// The schema is the parent view's schema
return parent.getEventType();
}
public final void update(EventBean[] newData, EventBean[] oldData) {
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().qViewProcessIRStream(this, UniqueByPropertyViewFactory.NAME, newData, oldData);
}
OneEventCollection postOldData = null;
if (this.hasViews()) {
postOldData = new OneEventCollection();
}
if (newData != null) {
for (int i = 0; i < newData.length; i++) {
// Obtain unique value
Object key = getUniqueKey(newData[i]);
// If there are no child views, just update the own collection
if (!this.hasViews()) {
mostRecentEvents.put(key, newData[i]);
continue;
}
// Post the last value as old data
EventBean lastValue = mostRecentEvents.get(key);
if (lastValue != null) {
postOldData.add(lastValue);
}
// Override with recent event
mostRecentEvents.put(key, newData[i]);
}
}
if (oldData != null) {
for (int i = 0; i < oldData.length; i++) {
// Obtain unique value
Object key = getUniqueKey(oldData[i]);
// If the old event is the current unique event, remove and post as old data
EventBean lastValue = mostRecentEvents.get(key);
if (lastValue == null || !lastValue.equals(oldData[i])) {
continue;
}
postOldData.add(lastValue);
mostRecentEvents.remove(key);
}
}
// If there are child views, fireStatementStopped update method
if (this.hasViews()) {
if (postOldData.isEmpty()) {
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().qViewIndicate(this, UniqueByPropertyViewFactory.NAME, newData, null);
}
updateChildren(newData, null);
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().aViewIndicate();
}
} else {
EventBean[] postOldDataArray = postOldData.toArray();
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().qViewIndicate(this, UniqueByPropertyViewFactory.NAME, newData, postOldDataArray);
}
updateChildren(newData, postOldDataArray);
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().aViewIndicate();
}
}
}
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().aViewProcessIRStream();
}
}
/**
* Returns true if the view is empty.
*
* @return true if empty
*/
public boolean isEmpty() {
return mostRecentEvents.isEmpty();
}
public final Iterator<EventBean> iterator() {
return mostRecentEvents.values().iterator();
}
public final String toString() {
return this.getClass().getName() + " uniqueFieldNames=" + Arrays.toString(viewFactory.criteriaExpressions);
}
protected Object getUniqueKey(EventBean theEvent) {
eventsPerStream[0] = theEvent;
if (criteriaExpressionsEvals.length == 1) {
return criteriaExpressionsEvals[0].evaluate(eventsPerStream, true, agentInstanceViewFactoryContext);
}
Object[] values = new Object[criteriaExpressionsEvals.length];
for (int i = 0; i < criteriaExpressionsEvals.length; i++) {
values[i] = criteriaExpressionsEvals[i].evaluate(eventsPerStream, true, agentInstanceViewFactoryContext);
}
return new MultiKeyUntyped(values);
}
public void visitView(ViewDataVisitor viewDataVisitor) {
viewDataVisitor.visitPrimary(mostRecentEvents, true, UniqueByPropertyViewFactory.NAME, mostRecentEvents.size(), mostRecentEvents.size());
}
public ViewFactory getViewFactory() {
return viewFactory;
}
}