/*
* © Copyright FOCONIS AG, 2014
*
* 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 org.openntf.formula.impl;
import java.util.HashSet;
import java.util.Set;
import org.openntf.formula.FormulaContext;
import org.openntf.formula.FormulaReturnException;
import org.openntf.formula.ValueHolder;
import org.openntf.formula.ast.ASTUserDefinedFunctionParameter;
import org.openntf.formula.ast.ASTUserDefinedFunctionVariable;
import org.openntf.formula.ast.Node;
import org.openntf.formula.ast.SimpleNode;
import org.openntf.formula.parse.AtFormulaParserImpl;
import org.openntf.formula.parse.ParseException;
/**
* ExtendedFunctions are custom defined functions like FUNCTION @MyFunction(a;b:="default") := @Do(...)
*
* @author Roland Praml, Foconis AG
*
*/
public class UserDefinedFunction extends AtFunction {
private String functionName;
private SimpleNode function;
private ASTUserDefinedFunctionParameter[] parameter;
private int paramUB;
private int paramLB;
private ASTUserDefinedFunctionVariable[] variable;
/**
* Constructor
*
* @param functionName
* the function-name (with @-sign)
* @param parameter
* the parameters for this function
* @param function
* the function-node to execute (if @functionName is invoked in formula)
* @param parser
* needed to throw propert ParseException
* @throws ParseException
* if you do not put optional parameter to the end
*/
public UserDefinedFunction(final String functionName, final ASTUserDefinedFunctionParameter[] parameter,
final AtFormulaParserImpl parser) throws ParseException {
super(functionName);
this.functionName = functionName;
this.parameter = parameter;
paramUB = parameter.length;
for (paramLB = 0; paramLB < parameter.length; paramLB++) {
if (parameter[paramLB].isOptional())
break;
}
for (int i = paramLB; i < parameter.length; i++) {
if (!parameter[i].isOptional())
throw new ParseException(parser, "non-optional parameter after optional parameter");
}
}
public void setVariables(final ASTUserDefinedFunctionVariable[] v) {
variable = v;
}
public void setFunction(final Node f) {
function = (SimpleNode) f;
}
/**
* Return the name of the function
*
* @return the name of the function
*/
public String getName() {
return functionName;
}
@Override
public ValueHolder evaluate(final FormulaContext ctx, final ValueHolder[] params) throws FormulaReturnException {
if (function == null)
throw new UnsupportedOperationException("'" + functionName + "' is not properly declared");
ValueHolder paramVals[] = new ValueHolder[parameter.length];
ValueHolder varVals[] = new ValueHolder[variable.length];
for (int i = 0; i < parameter.length; i++) {
if (params == null || i >= params.length) {
paramVals[i] = parameter[i].evaluate(ctx); //Optional params
} else {
paramVals[i] = params[i];
}
}
for (int i = 0; i < variable.length; i++) {
varVals[i] = variable[i].evaluate(ctx);
}
try {
// Initialize params & values & save old state
for (int i = 0; i < parameter.length; i++) {
paramVals[i] = ctx.setVarLC(parameter[i].getNameLC(), paramVals[i]);
}
for (int i = 0; i < variable.length; i++) {
varVals[i] = ctx.setVarLC(variable[i].getNameLC(), varVals[i]);
}
return function.evaluate(ctx);
} finally {
// restore old state
for (int i = 0; i < parameter.length; i++) {
ctx.setVarLC(parameter[i].getNameLC(), paramVals[i]);
}
for (int i = 0; i < variable.length; i++) {
ctx.setVarLC(variable[i].getNameLC(), varVals[i]);
}
}
}
@Override
public boolean checkParamCount(final int i) {
return paramLB <= i && i <= paramUB;
}
public void inspect(final Set<String> readFields, final Set<String> modifiedFields, final Set<String> variables,
final Set<String> functions) {
if (functions.contains(functionName.toUpperCase())) // dirty hack. As functions are normally written in lowercase...
return;
try {
functions.add(functionName.toUpperCase()); // ... we write the function in uppercase here, to detect loops
Set<String> tmpVariables = new HashSet<String>();
tmpVariables.addAll(variables);
for (ASTUserDefinedFunctionParameter param : parameter) {
tmpVariables.add(param.getNameLC());
param.inspect(readFields, modifiedFields, variables, functions);
}
for (ASTUserDefinedFunctionVariable var : variable) {
tmpVariables.add(var.getNameLC());
var.inspect(readFields, modifiedFields, variables, functions);
}
function.inspect(readFields, modifiedFields, tmpVariables, functions);
for (ASTUserDefinedFunctionParameter param : parameter) {
tmpVariables.remove(param.getNameLC());
}
for (ASTUserDefinedFunctionVariable var : variable) {
tmpVariables.remove(var.getNameLC());
}
variables.addAll(tmpVariables);
} finally {
functions.remove(functionName.toUpperCase());
}
}
}