/*
***************************************************************************************
* 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.epl.expression.time;
import com.espertech.esper.client.EPException;
import com.espertech.esper.client.EventBean;
import com.espertech.esper.client.util.TimePeriod;
import com.espertech.esper.epl.expression.core.*;
import com.espertech.esper.metrics.instrumentation.InstrumentationHelper;
import com.espertech.esper.util.JavaClassHelper;
import java.io.StringWriter;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayDeque;
import java.util.Calendar;
import java.util.TimeZone;
/**
* Expression representing a time period.
* <p>
* Child nodes to this expression carry the actual parts and must return a numeric value.
*/
public class ExprTimePeriodImpl extends ExprNodeBase implements ExprTimePeriod, ExprEvaluator {
private final TimeZone timeZone;
private final boolean hasYear;
private final boolean hasMonth;
private final boolean hasWeek;
private final boolean hasDay;
private final boolean hasHour;
private final boolean hasMinute;
private final boolean hasSecond;
private final boolean hasMillisecond;
private final boolean hasMicrosecond;
private boolean hasVariable;
private transient ExprEvaluator[] evaluators;
private transient TimePeriodAdder[] adders;
private final TimeAbacus timeAbacus;
private static final long serialVersionUID = -7229827032500659319L;
public ExprTimePeriodImpl(TimeZone timeZone, boolean hasYear, boolean hasMonth, boolean hasWeek, boolean hasDay, boolean hasHour, boolean hasMinute, boolean hasSecond, boolean hasMillisecond, boolean hasMicrosecond, TimeAbacus timeAbacus) {
this.timeZone = timeZone;
this.hasYear = hasYear;
this.hasMonth = hasMonth;
this.hasWeek = hasWeek;
this.hasDay = hasDay;
this.hasHour = hasHour;
this.hasMinute = hasMinute;
this.hasSecond = hasSecond;
this.hasMillisecond = hasMillisecond;
this.hasMicrosecond = hasMicrosecond;
this.timeAbacus = timeAbacus;
}
public ExprTimePeriodEvalDeltaConst constEvaluator(ExprEvaluatorContext context) {
if (!hasMonth && !hasYear) {
double seconds = evaluateAsSeconds(null, true, context);
long msec = timeAbacus.deltaForSecondsDouble(seconds);
return new ExprTimePeriodEvalDeltaConstGivenDelta(msec);
} else {
int[] values = new int[adders.length];
for (int i = 0; i < values.length; i++) {
values[i] = ((Number) evaluators[i].evaluate(null, true, context)).intValue();
}
return new ExprTimePeriodEvalDeltaConstGivenCalAdd(adders, values, timeZone, timeAbacus);
}
}
public ExprTimePeriodEvalDeltaNonConst nonconstEvaluator() {
if (!hasMonth && !hasYear) {
return new ExprTimePeriodEvalDeltaNonConstMsec(this);
} else {
return new ExprTimePeriodEvalDeltaNonConstCalAdd(timeZone, this);
}
}
public TimeAbacus getTimeAbacus() {
return timeAbacus;
}
public ExprEvaluator getExprEvaluator() {
return this;
}
public Object evaluate(EventBean[] eventsPerStream, boolean isNewData, ExprEvaluatorContext context) {
throw new IllegalStateException("Time-Period expression must be evaluated via any of " + ExprTimePeriod.class.getSimpleName() + " interface methods");
}
protected TimePeriodAdder[] getAdders() {
return adders;
}
public ExprEvaluator[] getEvaluators() {
return evaluators;
}
/**
* Indicator whether the time period has a day part child expression.
*
* @return true for part present, false for not present
*/
public boolean isHasDay() {
return hasDay;
}
/**
* Indicator whether the time period has a hour part child expression.
*
* @return true for part present, false for not present
*/
public boolean isHasHour() {
return hasHour;
}
/**
* Indicator whether the time period has a minute part child expression.
*
* @return true for part present, false for not present
*/
public boolean isHasMinute() {
return hasMinute;
}
/**
* Indicator whether the time period has a second part child expression.
*
* @return true for part present, false for not present
*/
public boolean isHasSecond() {
return hasSecond;
}
/**
* Indicator whether the time period has a millisecond part child expression.
*
* @return true for part present, false for not present
*/
public boolean isHasMillisecond() {
return hasMillisecond;
}
public boolean isHasMicrosecond() {
return hasMicrosecond;
}
/**
* Indicator whether the time period has a year part child expression.
*
* @return true for part present, false for not present
*/
public boolean isHasYear() {
return hasYear;
}
/**
* Indicator whether the time period has a month part child expression.
*
* @return true for part present, false for not present
*/
public boolean isHasMonth() {
return hasMonth;
}
/**
* Indicator whether the time period has a week part child expression.
*
* @return true for part present, false for not present
*/
public boolean isHasWeek() {
return hasWeek;
}
/**
* Indicator whether the time period has a variable in any of the child expressions.
*
* @return true for variable present, false for not present
*/
public boolean hasVariable() {
return hasVariable;
}
public ExprNode validate(ExprValidationContext validationContext) throws ExprValidationException {
evaluators = ExprNodeUtility.getEvaluators(this.getChildNodes());
for (ExprNode childNode : this.getChildNodes()) {
validate(childNode);
}
ArrayDeque<TimePeriodAdder> list = new ArrayDeque<TimePeriodAdder>();
if (hasYear) {
list.add(new TimePeriodAdderYear());
}
if (hasMonth) {
list.add(new TimePeriodAdderMonth());
}
if (hasWeek) {
list.add(new TimePeriodAdderWeek());
}
if (hasDay) {
list.add(new TimePeriodAdderDay());
}
if (hasHour) {
list.add(new TimePeriodAdderHour());
}
if (hasMinute) {
list.add(new TimePeriodAdderMinute());
}
if (hasSecond) {
list.add(new TimePeriodAdderSecond());
}
if (hasMillisecond) {
list.add(new TimePeriodAdderMSec());
}
if (hasMicrosecond) {
list.add(new TimePeriodAdderUSec());
}
adders = list.toArray(new TimePeriodAdder[list.size()]);
return null;
}
private void validate(ExprNode expression) throws ExprValidationException {
if (expression == null) {
return;
}
Class returnType = expression.getExprEvaluator().getType();
if (!JavaClassHelper.isNumeric(returnType)) {
throw new ExprValidationException("Time period expression requires a numeric parameter type");
}
if ((hasMonth || hasYear) && (JavaClassHelper.getBoxedType(returnType) != Integer.class)) {
throw new ExprValidationException("Time period expressions with month or year component require integer values, received a " + returnType.getSimpleName() + " value");
}
if (expression instanceof ExprVariableNode) {
hasVariable = true;
}
}
public double evaluateAsSeconds(EventBean[] eventsPerStream, boolean newData, ExprEvaluatorContext context) {
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().qExprTimePeriod(this);
}
double seconds = 0;
for (int i = 0; i < adders.length; i++) {
Double result = eval(evaluators[i], eventsPerStream, newData, context);
if (result == null) {
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().aExprTimePeriod(null);
}
throw new EPException("Failed to evaluate time period, received a null value for '" + ExprNodeUtility.toExpressionStringMinPrecedenceSafe(this) + "'");
}
seconds += adders[i].compute(result);
}
if (InstrumentationHelper.ENABLED) {
InstrumentationHelper.get().aExprTimePeriod(seconds);
}
return seconds;
}
private Double eval(ExprEvaluator expr, EventBean[] events, boolean isNewData, ExprEvaluatorContext exprEvaluatorContext) {
Object value = expr.evaluate(events, isNewData, exprEvaluatorContext);
if (value == null) {
return null;
}
if (value instanceof BigDecimal) {
return ((Number) value).doubleValue();
}
if (value instanceof BigInteger) {
return ((Number) value).doubleValue();
}
return ((Number) value).doubleValue();
}
public TimePeriod evaluateGetTimePeriod(EventBean[] eventsPerStream, boolean newData, ExprEvaluatorContext context) {
int exprCtr = 0;
Integer year = null;
if (hasYear) {
year = getInt(evaluators[exprCtr++].evaluate(eventsPerStream, newData, context));
}
Integer month = null;
if (hasMonth) {
month = getInt(evaluators[exprCtr++].evaluate(eventsPerStream, newData, context));
}
Integer week = null;
if (hasWeek) {
week = getInt(evaluators[exprCtr++].evaluate(eventsPerStream, newData, context));
}
Integer day = null;
if (hasDay) {
day = getInt(evaluators[exprCtr++].evaluate(eventsPerStream, newData, context));
}
Integer hours = null;
if (hasHour) {
hours = getInt(evaluators[exprCtr++].evaluate(eventsPerStream, newData, context));
}
Integer minutes = null;
if (hasMinute) {
minutes = getInt(evaluators[exprCtr++].evaluate(eventsPerStream, newData, context));
}
Integer seconds = null;
if (hasSecond) {
seconds = getInt(evaluators[exprCtr++].evaluate(eventsPerStream, newData, context));
}
Integer milliseconds = null;
if (hasMillisecond) {
milliseconds = getInt(evaluators[exprCtr++].evaluate(eventsPerStream, newData, context));
}
Integer microseconds = null;
if (hasMicrosecond) {
microseconds = getInt(evaluators[exprCtr].evaluate(eventsPerStream, newData, context));
}
return new TimePeriod(year, month, week, day, hours, minutes, seconds, milliseconds, microseconds);
}
private Integer getInt(Object evaluated) {
if (evaluated == null) {
return null;
}
return ((Number) evaluated).intValue();
}
public static interface TimePeriodAdder {
public double compute(Double value);
public void add(Calendar cal, int value);
boolean isMicroseconds();
}
public static class TimePeriodAdderYear implements TimePeriodAdder {
private static final double MULTIPLIER = 365 * 24 * 60 * 60;
public double compute(Double value) {
return value * MULTIPLIER;
}
public void add(Calendar cal, int value) {
cal.add(Calendar.YEAR, value);
}
public boolean isMicroseconds() {
return false;
}
}
public static class TimePeriodAdderMonth implements TimePeriodAdder {
private static final double MULTIPLIER = 30 * 24 * 60 * 60;
public double compute(Double value) {
return value * MULTIPLIER;
}
public void add(Calendar cal, int value) {
cal.add(Calendar.MONTH, value);
}
public boolean isMicroseconds() {
return false;
}
}
public static class TimePeriodAdderWeek implements TimePeriodAdder {
private static final double MULTIPLIER = 7 * 24 * 60 * 60;
public double compute(Double value) {
return value * MULTIPLIER;
}
public void add(Calendar cal, int value) {
cal.add(Calendar.WEEK_OF_YEAR, value);
}
public boolean isMicroseconds() {
return false;
}
}
public static class TimePeriodAdderDay implements TimePeriodAdder {
private static final double MULTIPLIER = 24 * 60 * 60;
public double compute(Double value) {
return value * MULTIPLIER;
}
public void add(Calendar cal, int value) {
cal.add(Calendar.DAY_OF_MONTH, value);
}
public boolean isMicroseconds() {
return false;
}
}
public static class TimePeriodAdderHour implements TimePeriodAdder {
private static final double MULTIPLIER = 60 * 60;
public double compute(Double value) {
return value * MULTIPLIER;
}
public void add(Calendar cal, int value) {
cal.add(Calendar.HOUR_OF_DAY, value);
}
public boolean isMicroseconds() {
return false;
}
}
public static class TimePeriodAdderMinute implements TimePeriodAdder {
private static final double MULTIPLIER = 60;
public double compute(Double value) {
return value * MULTIPLIER;
}
public void add(Calendar cal, int value) {
cal.add(Calendar.MINUTE, value);
}
public boolean isMicroseconds() {
return false;
}
}
public static class TimePeriodAdderSecond implements TimePeriodAdder {
public double compute(Double value) {
return value;
}
public void add(Calendar cal, int value) {
cal.add(Calendar.SECOND, value);
}
public boolean isMicroseconds() {
return false;
}
}
public static class TimePeriodAdderMSec implements TimePeriodAdder {
public double compute(Double value) {
return value / 1000d;
}
public void add(Calendar cal, int value) {
cal.add(Calendar.MILLISECOND, value);
}
public boolean isMicroseconds() {
return false;
}
}
public static class TimePeriodAdderUSec implements TimePeriodAdder {
public double compute(Double value) {
return value / 1000000d;
}
public void add(Calendar cal, int value) {
// no action : calendar does not add microseconds
}
public boolean isMicroseconds() {
return true;
}
}
public Class getType() {
return Double.class;
}
public boolean isConstantResult() {
for (ExprNode child : getChildNodes()) {
if (!child.isConstantResult()) {
return false;
}
}
return true;
}
public void toPrecedenceFreeEPL(StringWriter writer) {
int exprCtr = 0;
String delimiter = "";
if (hasYear) {
getChildNodes()[exprCtr++].toEPL(writer, getPrecedence());
writer.append(" years");
delimiter = " ";
}
if (hasMonth) {
writer.append(delimiter);
getChildNodes()[exprCtr++].toEPL(writer, getPrecedence());
writer.append(" months");
delimiter = " ";
}
if (hasWeek) {
writer.append(delimiter);
getChildNodes()[exprCtr++].toEPL(writer, getPrecedence());
writer.append(" weeks");
delimiter = " ";
}
if (hasDay) {
writer.append(delimiter);
getChildNodes()[exprCtr++].toEPL(writer, getPrecedence());
writer.append(" days");
delimiter = " ";
}
if (hasHour) {
writer.append(delimiter);
getChildNodes()[exprCtr++].toEPL(writer, getPrecedence());
writer.append(" hours");
delimiter = " ";
}
if (hasMinute) {
writer.append(delimiter);
getChildNodes()[exprCtr++].toEPL(writer, getPrecedence());
writer.append(" minutes");
delimiter = " ";
}
if (hasSecond) {
writer.append(delimiter);
getChildNodes()[exprCtr++].toEPL(writer, getPrecedence());
writer.append(" seconds");
delimiter = " ";
}
if (hasMillisecond) {
writer.append(delimiter);
getChildNodes()[exprCtr++].toEPL(writer, getPrecedence());
writer.append(" milliseconds");
delimiter = " ";
}
if (hasMicrosecond) {
writer.append(delimiter);
getChildNodes()[exprCtr].toEPL(writer, getPrecedence());
writer.append(" microseconds");
}
}
public ExprPrecedenceEnum getPrecedence() {
return ExprPrecedenceEnum.UNARY;
}
public boolean equalsNode(ExprNode node, boolean ignoreStreamPrefix) {
if (!(node instanceof ExprTimePeriodImpl)) {
return false;
}
ExprTimePeriodImpl other = (ExprTimePeriodImpl) node;
if (hasYear != other.hasYear) {
return false;
}
if (hasMonth != other.hasMonth) {
return false;
}
if (hasWeek != other.hasWeek) {
return false;
}
if (hasDay != other.hasDay) {
return false;
}
if (hasHour != other.hasHour) {
return false;
}
if (hasMinute != other.hasMinute) {
return false;
}
if (hasSecond != other.hasSecond) {
return false;
}
if (hasMillisecond != other.hasMillisecond) {
return false;
}
return hasMicrosecond == other.hasMicrosecond;
}
}