package jeql.syntax;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import jeql.api.error.JeqlException;
import jeql.api.function.AggregateFunction;
import jeql.api.function.SplittingFunction;
import jeql.engine.CompilationException;
import jeql.engine.FunctionRegistry;
import jeql.engine.Scope;
import jeql.engine.function.AggregateFunctionEvaluator;
import jeql.engine.function.CounterFunctionEvaluator;
import jeql.engine.function.IndexFunctionEvaluator;
import jeql.engine.function.FunctionEvaluator;
import jeql.engine.function.KeepFunctionEvaluator;
import jeql.engine.function.MethodFunctionEvaluator;
import jeql.engine.function.MethodFunctionInvoker;
import jeql.engine.function.PrevFunctionEvaluator;
import jeql.engine.function.RowNumFunctionEvaluator;
import jeql.engine.function.SplitFunctionEvaluator;
import jeql.engine.function.SplitListFunctionEvaluator;
import jeql.engine.function.ValFunctionEvaluator;
import jeql.engine.query.BaseQueryScope;
import jeql.engine.query.QueryScope;
import jeql.engine.query.group.GroupScope;
public class FunctionNode
extends ParseTreeNode
{
private FunctionEvaluator funcEval = null;
private String className = null;
private String name;
private List args;
public FunctionNode()
{
}
public boolean isSplitFunction()
{
return funcEval instanceof SplitListFunctionEvaluator
|| funcEval instanceof SplitFunctionEvaluator ;
}
public String getName() { return name; }
public void setFunction(String name1, String name2, List args)
{
if (name2 == null) {
this.name = name1;
}
else {
this.className = name1;
this.name = name2;
}
if (args == null)
this.args = new ArrayList();
else
this.args = args;
}
public void bind(Scope scope)
{
// skip if already bound
// if (funcEval != null) return;
/**
* No, need to bind always.
* E.g. for nested subqueries, need to rebind
* each time subquery is evaled, in order to
* create new state for aggregate functions
*
*/
/**
* If in a GroupScope and this is an aggregate function
* don't bind
* (binding has already taken place in the base scope)
*/
if (scope instanceof GroupScope
&& funcEval instanceof AggregateFunctionEvaluator)
return;
/**
* Create evaluator, which is cached and used
* for all evaluations of this function node.
*/
funcEval = createFunctionEvaluator(scope);
boolean isArgStar = args.size() == 1 && TableRefNode.isStar((ParseTreeNode) args.get(0));
if (isArgStar
&& ! (funcEval instanceof AggregateFunctionEvaluator)) {
throw new CompilationException(this, "Only aggregate functions can take * as their argument");
}
// ensure any exceptions thrown during function binding
// are reported at the function call line
/*
* MD - OLD I think!
try {
if (funcEval != null)
funcEval.bind(scope, args);
}
catch (JeqlException ex) {
ex.setLocation(this);
throw ex;
}
*/
BaseQueryScope aggFunScope = null;
if (funcEval instanceof AggregateFunctionEvaluator) {
// check for nested agg functions
if (scope instanceof BaseQueryScope) {
BaseQueryScope baseScope = (BaseQueryScope) scope;
if (baseScope.isBindingAggregateFunction()) {
throw new JeqlException(this, "Nested aggregate function found (" + name + ")");
}
aggFunScope = baseScope;
aggFunScope.addAggregateFunction(this);
}
// check for arg count = 1
if (args.size() > 1) {
throw new CompilationException(this, "Aggregate function arg list must have length 1");
}
}
if (aggFunScope != null) {
aggFunScope.setBindingAggregateFunction(true);
}
// bind args
for (int i = 0; i < args.size(); i++) {
((ParseTreeNode) args.get(i)).bind(scope);
}
if (aggFunScope != null) {
aggFunScope.setBindingAggregateFunction(false);
}
/*
* Bind function itself.
* Any exceptions thrown during function binding
* are reported at the function call line
*
*/
try {
if (funcEval != null)
funcEval.bind(scope, args);
}
catch (JeqlException ex) {
ex.setLocation(this);
throw ex;
}
}
private FunctionEvaluator createFunctionEvaluator(Scope scope)
{
// TODO: match using a registry of name-only functions (i.e. from builtins or imports)
FunctionEvaluator funcEval = null;
if (className == null) {
// pseudo-functions
if (name.equalsIgnoreCase(KeepFunctionEvaluator.FN_KEEP)) {
funcEval = new KeepFunctionEvaluator();
}
else if (name.equalsIgnoreCase(PrevFunctionEvaluator.FN_PREV)) {
funcEval = new PrevFunctionEvaluator();
}
else if (name.equalsIgnoreCase(IndexFunctionEvaluator.FN_NAME)) {
funcEval = new IndexFunctionEvaluator();
}
else if (name.equalsIgnoreCase(CounterFunctionEvaluator.FN_NAME)) {
funcEval = new CounterFunctionEvaluator();
}
else if (name.equalsIgnoreCase(RowNumFunctionEvaluator.FN_ROWNUM)) {
funcEval = new RowNumFunctionEvaluator();
}
else if (name.equalsIgnoreCase(ValFunctionEvaluator.FN_VAL)) {
funcEval = new ValFunctionEvaluator();
}
}
// if function is not a built-in one, look it up
if (funcEval == null) {
funcEval = createFunctionEvaluatorFromRegistry(scope);
}
return funcEval;
}
private FunctionEvaluator createFunctionEvaluatorFromRegistry(Scope scope)
{
String fullName = FunctionRegistry.functionName(className, name);
Class returnType = scope.getContext().getFunctionRegistry()
.getReturnType(fullName, args.size());
/*
if (returnType == SplittingFunction.class)
return new SplitFunctionEvaluator(className, name, args);
*/
// TODO: check for a parameterized List (and maybe also an annotation of "SplitFunction"?
if (returnType == List.class)
return new SplitListFunctionEvaluator(className, name, args);
if (returnType == AggregateFunction.class)
return new AggregateFunctionEvaluator(createAggFunction(scope, className, name, args));
// otherwise assume method is a regular function
return new MethodFunctionEvaluator(className, name, args);
}
private AggregateFunction createAggFunction(Scope scope, String className, String name, List args)
{
String fullName = FunctionRegistry.functionName(className, name);
Method method = scope.getContext().getFunction(fullName, args.size());
if (method == null)
throw new CompilationException("Unknown function - " + fullName);
if (method.getReturnType() != AggregateFunction.class) {
throw new CompilationException(this, "Function " + fullName
+ " is not a Aggregate function");
}
return (AggregateFunction) MethodFunctionInvoker.invoke(method, null);
}
public FunctionEvaluator getFunctionEvaluator() { return funcEval; }
public List getArgs() { return args; }
public Object eval(Scope scope)
{
if (funcEval instanceof AggregateFunctionEvaluator) {
return ((QueryScope) scope).getRow().getValue(
((AggregateFunctionEvaluator) funcEval).getColumnIndex());
}
try {
return funcEval.eval(scope);
}
catch (JeqlException ex) {
ex.setLocation(this);
throw ex;
}
}
public Class getType(Scope scope)
{
return funcEval.getType(scope);
}
public String toString()
{
return name + "()";
}
}