/*
* Copyright (C) 2007 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.redgeek.android.eventrend.synthetic;
import java.util.ArrayList;
import net.redgeek.android.eventrend.primitives.Datapoint;
import net.redgeek.android.eventrend.primitives.TimeSeries;
import net.redgeek.android.eventrend.synthetic.AST.BinaryOperation;
import net.redgeek.android.eventrend.synthetic.AST.GroupOperand;
import net.redgeek.android.eventrend.synthetic.AST.Operand;
import net.redgeek.android.eventrend.synthetic.AST.Operation;
import net.redgeek.android.eventrend.synthetic.AST.UnaryOperation;
import net.redgeek.android.eventrend.util.DateUtil;
/**
* A representation of formulas for generating synthetic time series. Note that
* the operations are pretty basic, so I'm not using whatever the java
* equivalent to [f]lex/{bison|yacc} is.
*
* @author barclay
*
*/
public class Formula {
private AST mAST;
public Formula() {
mAST = new AST();
}
public Formula(String input) {
mAST = new AST(input);
}
public void setFormula(String input) {
mAST.generate(input);
}
public boolean isValid() {
return mAST.isValid();
}
public String getErrorString() {
Tokenizer.Token err = mAST.getErrorToken();
return "Unexpecting value " + err.mValue + " at character " + err.mStart;
}
public String toString() {
return mAST.toString();
}
public ArrayList<String> getDependentNames() {
return mAST.getDependentNames();
}
public ArrayList<Datapoint> apply(ArrayList<TimeSeries> sources) {
OperandInstance result;
Operation root = mAST.getRoot();
if (root == null || mAST.isValid() == false)
return null;
// Duplicate all the timeseries so we don't overwrite the original value
ArrayList<TimeSeries> list = new ArrayList<TimeSeries>();
for (int i = 0; i < sources.size(); i++) {
list.add(new TimeSeries(sources.get(i)));
}
result = applyOp(list, mAST.getRoot());
if (result.mType == Tokenizer.TokenID.SERIES) {
return result.mTimeSeries.getDatapoints();
}
return null;
}
public static class OperandInstance {
public Tokenizer.TokenID mType;
public Float mFloat;
public Long mLong;
public TimeSeries mTimeSeries;
public DateUtil.Period mPeriod;
public boolean mTimestamp;
public OperandInstance() {
}
}
private OperandInstance makeInstance(ArrayList<TimeSeries> sources,
AST.Operand operand) {
OperandInstance instance = new OperandInstance();
if (operand.getClass().equals(AST.FloatOperand.class)) {
instance.mType = Tokenizer.TokenID.FLOAT_VALUE;
instance.mFloat = (Float) operand.getValue();
} else if (operand.getClass().equals(AST.LongOperand.class)) {
instance.mType = Tokenizer.TokenID.LONG_VALUE;
instance.mFloat = (Float) operand.getValue();
} else if (operand.getClass().equals(AST.UnitsOperand.class)) {
instance.mType = Tokenizer.TokenID.PERIOD_CONSTANT;
instance.mPeriod = (DateUtil.Period) operand.getValue();
} else if (operand.getClass().equals(AST.DeltaOperand.class)) {
instance.mType = Tokenizer.TokenID.DELTA;
if (((String) operand.getValue()).equals(Tokenizer.TIMESTAMP)) {
instance.mTimestamp = true;
} else {
instance.mTimestamp = false;
}
} else if (operand.getClass().equals(AST.TimeSeriesOperand.class)) {
TimeSeries ts = null;
instance.mType = Tokenizer.TokenID.SERIES;
String tsName = (String) operand.getValue();
for (int i = 0; i < sources.size(); i++) {
ts = sources.get(i);
if (tsName.equals(ts.getDbRow().getCategoryName()))
break;
}
instance.mTimeSeries = ts;
}
return instance;
}
private OperandInstance executeTimeseriesOp(OperandInstance left,
AST.Opcode opcode, OperandInstance right) {
if (left.mType == Tokenizer.TokenID.SERIES
&& right.mType == Tokenizer.TokenID.SERIES) {
left.mTimeSeries.timeseriesOp(right.mTimeSeries, opcode);
return left;
}
if (left.mType == Tokenizer.TokenID.SERIES) {
if (right.mType == Tokenizer.TokenID.FLOAT_VALUE)
left.mTimeSeries.floatOp(right.mFloat, opcode, false);
else if (right.mType == Tokenizer.TokenID.LONG_VALUE)
left.mTimeSeries.longOp(right.mLong, opcode, false);
else if (right.mType == Tokenizer.TokenID.DELTA) {
if (right.mTimestamp == true)
left.mTimeSeries.previousTimestamp();
else
left.mTimeSeries.previousValue();
} else if (right.mType == Tokenizer.TokenID.PERIOD_CONSTANT) {
Long l = new Long(DateUtil.mapPeriodToLong(right.mPeriod));
left.mTimeSeries.longOp(l, opcode, false);
}
return left;
}
if (right.mType == Tokenizer.TokenID.SERIES) {
if (left.mType == Tokenizer.TokenID.FLOAT_VALUE)
right.mTimeSeries.floatOp(right.mFloat, opcode, true);
else if (left.mType == Tokenizer.TokenID.LONG_VALUE)
right.mTimeSeries.longOp(right.mLong, opcode, true);
else if (left.mType == Tokenizer.TokenID.PERIOD_CONSTANT) {
Long l = new Long(DateUtil.mapPeriodToLong(left.mPeriod));
right.mTimeSeries.longOp(l, opcode, true);
}
return right;
}
return null;
}
private OperandInstance executeFloatOp(OperandInstance left,
AST.Opcode opcode, OperandInstance right) {
if (right.mType == Tokenizer.TokenID.LONG_VALUE
|| right.mType == Tokenizer.TokenID.PERIOD_CONSTANT) {
Long l;
if (right.mType == Tokenizer.TokenID.PERIOD_CONSTANT)
l = new Long(DateUtil.mapPeriodToLong(right.mPeriod));
else
l = new Long(right.mLong);
if (opcode == AST.Opcode.PLUS)
left.mFloat += l;
else if (opcode == AST.Opcode.MINUS)
left.mFloat -= l;
else if (opcode == AST.Opcode.MULTIPLY)
left.mFloat *= l;
else if (opcode == AST.Opcode.DIVIDE) {
if (l != 0)
left.mFloat /= l;
}
}
return left;
}
private OperandInstance executeLongOp(OperandInstance left,
AST.Opcode opcode, OperandInstance right) {
if (right.mType == Tokenizer.TokenID.LONG_VALUE
|| right.mType == Tokenizer.TokenID.PERIOD_CONSTANT) {
Long l;
if (right.mType == Tokenizer.TokenID.PERIOD_CONSTANT)
l = new Long(DateUtil.mapPeriodToLong(right.mPeriod));
else
l = new Long(right.mLong);
if (opcode == AST.Opcode.PLUS)
left.mLong += l;
else if (opcode == AST.Opcode.MINUS)
left.mLong -= l;
else if (opcode == AST.Opcode.MULTIPLY)
left.mLong *= l;
else if (opcode == AST.Opcode.DIVIDE) {
if (l != 0)
left.mLong /= l;
}
} else if (right.mType == Tokenizer.TokenID.FLOAT_VALUE) {
if (opcode == AST.Opcode.PLUS)
left.mFloat = left.mLong + right.mFloat;
else if (opcode == AST.Opcode.MINUS)
left.mFloat = left.mLong - right.mFloat;
else if (opcode == AST.Opcode.MULTIPLY)
left.mFloat = left.mLong * right.mFloat;
else if (opcode == AST.Opcode.DIVIDE) {
if (right.mFloat != 0)
left.mFloat = left.mLong / right.mFloat;
}
}
return left;
}
private OperandInstance executeOp(OperandInstance left, AST.Opcode opcode,
OperandInstance right) {
OperandInstance result = null;
if (left.mType == Tokenizer.TokenID.SERIES
|| right.mType == Tokenizer.TokenID.SERIES) {
result = executeTimeseriesOp(left, opcode, right);
} else if (left.mType == Tokenizer.TokenID.FLOAT_VALUE) {
result = executeFloatOp(left, opcode, right);
} else if (left.mType == Tokenizer.TokenID.LONG_VALUE) {
result = executeLongOp(left, opcode, right);
}
return result;
}
private OperandInstance applyOp(ArrayList<TimeSeries> sources, Operation op) {
GroupOperand group;
AST.Opcode oc = op.getOpcode();
OperandInstance l, r, result = null;
if (oc == AST.Opcode.GROUPING) {
group = ((UnaryOperation<GroupOperand>) op).getOperand();
result = applyOp(sources, group.getValue());
} else if (oc == AST.Opcode.PLUS || oc == AST.Opcode.MINUS
|| oc == AST.Opcode.MULTIPLY || oc == AST.Opcode.DIVIDE
|| oc == AST.Opcode.DELTA) {
Operand left = (Operand) ((BinaryOperation) op).getOperandLeft();
Operand right = (Operand) ((BinaryOperation) op).getOperandRight();
if (left.getClass().equals(GroupOperand.class)) {
l = applyOp(sources, (Operation) left.getValue());
} else
l = makeInstance(sources, left);
if (right.getClass().equals(GroupOperand.class)) {
r = applyOp(sources, (Operation) right.getValue());
} else
r = makeInstance(sources, right);
result = executeOp(l, oc, r);
}
return result;
}
}