package org.activityinfo.model.expr.eval;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.activityinfo.model.expr.*;
import org.activityinfo.model.expr.diagnostic.CircularReferenceException;
import org.activityinfo.model.expr.diagnostic.ExprException;
import org.activityinfo.model.expr.diagnostic.ExprSyntaxException;
import org.activityinfo.model.form.FormClass;
import org.activityinfo.model.form.FormField;
import org.activityinfo.model.resource.Record;
import org.activityinfo.model.resource.ResourceId;
import org.activityinfo.model.type.*;
import org.activityinfo.model.type.expr.CalculatedFieldType;
import org.activityinfo.model.type.number.Quantity;
import org.activityinfo.model.type.number.QuantityType;
import org.activityinfo.model.type.primitive.BooleanFieldValue;
import org.activityinfo.model.type.primitive.BooleanType;
import org.activityinfo.model.type.primitive.TextType;
import org.activityinfo.model.type.primitive.TextValue;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Partially evaluates a field-level expression, expanding any calculated indicators
* on which the expression depends.
*
*/
public class PartialEvaluator<InstanceT> {
private final FormSymbolTable symbolTable;
private final Map<String, FieldReader> readers = Maps.newHashMap();
private final LinkedList<FormField> stack = new LinkedList<>();
private final PartiallyEvaluatingVisitor visitor = new PartiallyEvaluatingVisitor();
private final FieldReaderFactory<InstanceT> readerFactory;
public PartialEvaluator(FormClass formClass, FieldReaderFactory<InstanceT> readerFactory) {
this.readerFactory = readerFactory;
this.symbolTable = new FormSymbolTable(formClass);
}
public PartialEvaluator(FormSymbolTable symbolTable, FieldReaderFactory<InstanceT> readerFactory) {
this.symbolTable = symbolTable;
this.readerFactory = readerFactory;
}
public FieldReader<InstanceT> partiallyEvaluate(ExprNode node) {
try {
return node.accept(visitor);
} catch(ExprException e) {
return new ConstantReader<InstanceT>(new ErrorValue(e), ErrorType.INSTANCE);
}
}
public FieldReader<InstanceT> partiallyEvaluate(FormField field) {
try {
return visitor.fieldReader(field);
} catch(ExprException e) {
return new ConstantReader<InstanceT>(new ErrorValue(e), ErrorType.INSTANCE);
}
}
public FormField getField(ResourceId fieldId) {
return symbolTable.resolveFieldById(fieldId.asString());
}
public FormField getField(String fieldName) {
return symbolTable.resolveFieldById(fieldName);
}
private static class ConstantReader<InstanceT> implements FieldReader<InstanceT> {
private final FieldValue value;
private final FieldType type;
private ConstantReader(FieldValue value, FieldType type) {
this.value = value;
this.type = type;
}
@Override
public FieldValue readField(InstanceT record) {
return value;
}
@Override
public FieldType getType() {
return type;
}
}
private class PartiallyEvaluatingVisitor implements ExprVisitor<FieldReader<InstanceT>> {
@Override
public FieldReader<InstanceT> visitConstant(ConstantExpr node) {
final FieldValue constantValue = node.getValue();
final FieldType type = node.getType();
return new FieldReader<InstanceT>() {
@Override
public FieldValue readField(InstanceT record) {
return constantValue;
}
@Override
public FieldType getType() {
return type;
}
};
}
@Override
public FieldReader<InstanceT> visitSymbol(SymbolExpr symbol) {
return fieldReader(symbolTable.resolveSymbol(symbol));
}
public FieldReader<InstanceT> fieldReader(FormField field) {
// have we already created a FieldReader for this field name?
FieldReader reader = readers.get(field.getName());
if (reader != null) {
return reader;
}
if (field.getType() instanceof CalculatedFieldType) {
// expand this expression
reader = expandCalculatedField(field);
} else {
reader = readerFactory.create(field);
}
// cache partial evaluation
readers.put(field.getName(), reader);
return reader;
}
private FieldReader<InstanceT> expandCalculatedField(FormField field) {
// detect cycles
if (stack.contains(field)) {
throw new CircularReferenceException(stack);
}
stack.add(field);
try {
CalculatedFieldType calculatedType = (CalculatedFieldType) field.getType();
ExprNode calculatedNode = ExprParser.parse(calculatedType.getExpression().getExpression());
return calculatedNode.accept(this);
} finally {
stack.removeLast();
}
}
@Override
public FieldReader<InstanceT> visitGroup(GroupExpr expr) {
return expr.getExpr().accept(this);
}
@Override
public FieldReader<InstanceT> visitCompoundExpr(CompoundExpr compoundExpr) {
throw new ExprSyntaxException("Compound expressions not supported in field-level expressions.");
}
@Override
public FieldReader<InstanceT> visitFunctionCall(final FunctionCallNode functionCallNode) {
// Partially evaluate arguments
final List<FieldReader<InstanceT>> arguments = Lists.newArrayList();
final List<FieldType> argumentTypes = Lists.newArrayList();
for (ExprNode argumentExpr : functionCallNode.getArguments()) {
FieldReader<InstanceT> argumentReader = argumentExpr.accept(this);
arguments.add(argumentReader);
argumentTypes.add(argumentReader.getType());
}
// Resolve type of the function's value
final FieldType functionType = functionCallNode.getFunction().resolveResultType(argumentTypes);
return new FieldReader<InstanceT>() {
@Override
public FieldValue readField(InstanceT instance) {
List<FieldValue> argumentValues = Lists.newArrayList();
for (FieldReader<InstanceT> argument : arguments) {
argumentValues.add(argument.readField(instance));
}
return functionCallNode.getFunction().apply(argumentValues);
}
@Override
public FieldType getType() {
return functionType;
}
};
}
}
}