/*
***************************************************************************************
* 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.stat;
import com.espertech.esper.client.EventBean;
import com.espertech.esper.client.EventType;
import com.espertech.esper.collection.SingleEventIterator;
import com.espertech.esper.core.context.util.AgentInstanceViewFactoryChainContext;
import com.espertech.esper.core.service.StatementContext;
import com.espertech.esper.epl.expression.core.ExprEvaluator;
import com.espertech.esper.epl.expression.core.ExprNode;
import com.espertech.esper.metrics.instrumentation.InstrumentationHelper;
import com.espertech.esper.view.*;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* View for computing a weighted average. The view uses 2 fields within the parent view to compute the weighted average.
* The X field and weight field. In a price-volume example it calculates the volume-weighted average price
* as (sum(price * volume) / sum(volume)).
* Example: weighted_avg("price", "volume")
*/
public class WeightedAverageView extends ViewSupport implements CloneableView, DerivedValueView {
private final WeightedAverageViewFactory viewFactory;
private final AgentInstanceViewFactoryChainContext agentInstanceContext;
private final ExprEvaluator fieldNameXEvaluator;
private final ExprEvaluator fieldNameWeightEvaluator;
private EventBean[] eventsPerStream = new EventBean[1];
protected double sumXtimesW = Double.NaN;
protected double sumW = Double.NaN;
protected double currentValue = Double.NaN;
protected Object[] lastValuesEventNew;
private EventBean lastNewEvent;
public WeightedAverageView(WeightedAverageViewFactory viewFactory, AgentInstanceViewFactoryChainContext agentInstanceContext) {
this.viewFactory = viewFactory;
this.fieldNameXEvaluator = viewFactory.fieldNameX.getExprEvaluator();
this.fieldNameWeightEvaluator = viewFactory.fieldNameWeight.getExprEvaluator();
this.agentInstanceContext = agentInstanceContext;
}
public View cloneView() {
return viewFactory.makeView(agentInstanceContext);
}
/**
* Returns the expression supplying the X values.
*
* @return expression supplying X data points
*/
public final ExprNode getFieldNameX() {
return viewFactory.fieldNameX;
}
/**
* Returns the expression supplying the weight values.
*
* @return expression supplying weight
*/
public final ExprNode getFieldNameWeight() {
return viewFactory.fieldNameWeight;
}
public void update(EventBean[] newData, EventBean[] oldData) {
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().qViewProcessIRStream(this, WeightedAverageViewFactory.NAME, newData, oldData);
}
double oldValue = currentValue;
// If we have child views, keep a reference to the old values, so we can update them as old data event.
EventBean oldDataMap = null;
if (lastNewEvent == null) {
if (this.hasViews()) {
Map<String, Object> oldDataValues = new HashMap<String, Object>();
oldDataValues.put(ViewFieldEnum.WEIGHTED_AVERAGE__AVERAGE.getName(), oldValue);
addProperties(oldDataValues);
oldDataMap = agentInstanceContext.getStatementContext().getEventAdapterService().adapterForTypedMap(oldDataValues, viewFactory.eventType);
}
}
// add data points to the bean
if (newData != null) {
for (int i = 0; i < newData.length; i++) {
eventsPerStream[0] = newData[i];
Number pointnum = (Number) fieldNameXEvaluator.evaluate(eventsPerStream, true, agentInstanceContext);
Number weightnum = (Number) fieldNameWeightEvaluator.evaluate(eventsPerStream, true, agentInstanceContext);
if (pointnum != null && weightnum != null) {
double point = pointnum.doubleValue();
double weight = weightnum.doubleValue();
if (Double.valueOf(sumXtimesW).isNaN()) {
sumXtimesW = point * weight;
sumW = weight;
} else {
sumXtimesW += point * weight;
sumW += weight;
}
}
}
if ((viewFactory.additionalProps != null) && (newData.length != 0)) {
if (lastValuesEventNew == null) {
lastValuesEventNew = new Object[viewFactory.additionalProps.getAdditionalExpr().length];
}
for (int val = 0; val < viewFactory.additionalProps.getAdditionalExpr().length; val++) {
lastValuesEventNew[val] = viewFactory.additionalProps.getAdditionalExpr()[val].evaluate(eventsPerStream, true, agentInstanceContext);
}
}
}
// remove data points from the bean
if (oldData != null) {
for (int i = 0; i < oldData.length; i++) {
eventsPerStream[0] = oldData[i];
Number pointnum = (Number) fieldNameXEvaluator.evaluate(eventsPerStream, true, agentInstanceContext);
Number weightnum = (Number) fieldNameWeightEvaluator.evaluate(eventsPerStream, true, agentInstanceContext);
if (pointnum != null && weightnum != null) {
double point = pointnum.doubleValue();
double weight = weightnum.doubleValue();
sumXtimesW -= point * weight;
sumW -= weight;
}
}
}
if (sumW != 0) {
currentValue = sumXtimesW / sumW;
} else {
currentValue = Double.NaN;
}
// If there are child view, fireStatementStopped update method
if (this.hasViews()) {
Map<String, Object> newDataMap = new HashMap<String, Object>();
newDataMap.put(ViewFieldEnum.WEIGHTED_AVERAGE__AVERAGE.getName(), currentValue);
addProperties(newDataMap);
EventBean newDataEvent = agentInstanceContext.getStatementContext().getEventAdapterService().adapterForTypedMap(newDataMap, viewFactory.eventType);
EventBean[] newEvents = new EventBean[]{newDataEvent};
EventBean[] oldEvents;
if (lastNewEvent == null) {
oldEvents = new EventBean[]{oldDataMap};
} else {
oldEvents = new EventBean[]{lastNewEvent};
}
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().qViewIndicate(this, WeightedAverageViewFactory.NAME, newEvents, oldEvents);
}
updateChildren(newEvents, oldEvents);
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().aViewIndicate();
}
lastNewEvent = newDataEvent;
}
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().aViewProcessIRStream();
}
}
private void addProperties(Map<String, Object> newDataMap) {
if (viewFactory.additionalProps == null) {
return;
}
viewFactory.additionalProps.addProperties(newDataMap, lastValuesEventNew);
}
public final EventType getEventType() {
return viewFactory.eventType;
}
public final Iterator<EventBean> iterator() {
Map<String, Object> newDataMap = new HashMap<String, Object>();
newDataMap.put(ViewFieldEnum.WEIGHTED_AVERAGE__AVERAGE.getName(), currentValue);
addProperties(newDataMap);
return new SingleEventIterator(agentInstanceContext.getStatementContext().getEventAdapterService().adapterForTypedMap(newDataMap, viewFactory.eventType));
}
public final String toString() {
return this.getClass().getName() +
" fieldName=" + viewFactory.fieldNameX +
" fieldNameWeight=" + viewFactory.fieldNameWeight;
}
public static EventType createEventType(StatementContext statementContext, StatViewAdditionalProps additionalProps, int streamNum) {
Map<String, Object> schemaMap = new HashMap<String, Object>();
schemaMap.put(ViewFieldEnum.WEIGHTED_AVERAGE__AVERAGE.getName(), Double.class);
StatViewAdditionalProps.addCheckDupProperties(schemaMap, additionalProps, ViewFieldEnum.WEIGHTED_AVERAGE__AVERAGE);
String outputEventTypeName = statementContext.getStatementId() + "_wavgview_" + streamNum;
return statementContext.getEventAdapterService().createAnonymousMapType(outputEventTypeName, schemaMap, false);
}
public double getSumXtimesW() {
return sumXtimesW;
}
public void setSumXtimesW(double sumXtimesW) {
this.sumXtimesW = sumXtimesW;
}
public double getSumW() {
return sumW;
}
public void setSumW(double sumW) {
this.sumW = sumW;
}
public double getCurrentValue() {
return currentValue;
}
public void setCurrentValue(double currentValue) {
this.currentValue = currentValue;
}
public Object[] getLastValuesEventNew() {
return lastValuesEventNew;
}
public void setLastValuesEventNew(Object[] lastValuesEventNew) {
this.lastValuesEventNew = lastValuesEventNew;
}
public ViewFactory getViewFactory() {
return viewFactory;
}
}