package org.yamcs.yarch.streamsql;
import java.io.StringReader;
import java.lang.reflect.Constructor;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.codehaus.janino.SimpleCompiler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yamcs.yarch.ColumnDefinition;
import org.yamcs.yarch.CompiledExpression;
import org.yamcs.yarch.DataType;
import org.yamcs.yarch.DbReaderStream;
import org.yamcs.yarch.TupleDefinition;
import org.yamcs.yarch.streamsql.StreamSqlException.ErrCode;
public abstract class Expression {
protected DataType type=null;
protected Expression[] children;
protected TupleDefinition inputDef;
protected boolean hasAggregates;
protected boolean constant=false;
String colName;
static Logger log = LoggerFactory.getLogger(Expression.class);
public Expression(Expression[] children) {
this.children=children;
hasAggregates=false;
if(children!=null) {
for(Expression c:children) {
if(c.isAggregate() || c.hasAggregates) {
hasAggregates=true;
}
}
}
colName=String.format("%s0x%xd", this.getClass().getSimpleName(), this.hashCode());
}
protected boolean isAggregate() {return false;}
final public boolean isConstant() {
return constant;
}
/**
* add a filter to the table if applicable and returns an expression where the condition is removed.
* @param tableStream
* @return
* @throws StreamSqlException
*/
public Expression addFilter(DbReaderStream tableStream) throws StreamSqlException {
// by default do nothing
return this;
}
public void collectAggregates(List<AggregateExpression> list) {
if(isAggregate()) list.add((AggregateExpression)this);
else if(children!=null) {
for(Expression c:children) {
if(c.hasAggregates || c.isAggregate()) {
c.collectAggregates(list);
}
}
}
}
protected abstract void doBind() throws StreamSqlException;
public void bind(TupleDefinition inputDef2) throws StreamSqlException {
this.inputDef=inputDef2;
if(children!=null) {
for(Expression c:children) {
c.bind(inputDef);
}
}
doBind();
}
public DataType getType() {
return type;
}
protected void fillCode_Declarations(StringBuilder code) {
//do nothing by default
}
protected void fillCode_Constructor(StringBuilder code) throws StreamSqlException {
//do nothing by default
}
protected void fillCode_AllInputDefVars(StringBuilder code){
for(ColumnDefinition cd:inputDef.getColumnDefinitions()) {
if (cd.getType().val != DataType._type.PROTOBUF) {
code.append("\t\t"+cd.getType().javaType()+" col"+cd.getName()+"=null;\n")
.append("\t\tif(tuple.hasColumn(\""+cd.getName()+"\")) {\n")
.append("\t\t\tcol"+cd.getName()+"=("+cd.getType().javaType()+")tuple.getColumn(\""+cd.getName()+"\");\n")
.append("\t\t}\n");
}
}
}
protected void fillCode_getValueBody(StringBuilder code) throws StreamSqlException{};
protected abstract void fillCode_getValueReturn(StringBuilder code) throws StreamSqlException;
private static AtomicInteger counter=new AtomicInteger();
public CompiledExpression compile() throws StreamSqlException {
String className = "Expression"+counter.incrementAndGet();
StringBuilder source=new StringBuilder();
source.append("package org.yamcs.yarch;\n")
.append("public class "+className+" implements CompiledExpression {\n")
.append("\tColumnDefinition cdef;\n");
fillCode_Declarations(source);
source.append("\tpublic "+className+"(ColumnDefinition cdef) {\n")
.append("\t\tthis.cdef=cdef;\n");
fillCode_Constructor(source);
source.append("\t}\n");
source.append("\tpublic Object getValue(Tuple tuple) {\n");
if(!isConstant()) fillCode_AllInputDefVars(source);
fillCode_getValueBody(source);
// source.append("Value colid=t.getColumn(\"id\");\n");
source.append("\n\t\treturn ");
fillCode_getValueReturn(source);
source.append(";\n");
source.append("\t}\n")
.append("\tpublic ColumnDefinition getDefinition() {\n")
.append("\t\treturn cdef;\n")
.append("\t}\n")
.append("}\n");
try {
SimpleCompiler compiler=new SimpleCompiler();
compiler.cook(new StringReader(source.toString()));
@SuppressWarnings("unchecked")
Class<CompiledExpression> cexprClass = (Class<CompiledExpression>) compiler.getClassLoader().loadClass("org.yamcs.yarch."+className);
Constructor<CompiledExpression> cexprConstructor=cexprClass.getConstructor(ColumnDefinition.class);
ColumnDefinition cdef=new ColumnDefinition(colName, type);
return cexprConstructor.newInstance(cdef);
} catch (Exception e) {
log.warn("Got exception when compiling {} ", source.toString(), e);
throw new StreamSqlException(ErrCode.COMPILE_ERROR, e.toString());
}
}
/**
* when the expression behaves like a column expression, this is the column name
* @return
*/
public String getColName() {
return colName;
}
}