/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright (c) 2006 - 2013 Pentaho Corporation and Contributors. All rights reserved.
*/
package org.pentaho.reporting.libraries.formula.lvalues;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.libraries.formula.EvaluationException;
import org.pentaho.reporting.libraries.formula.FormulaContext;
import org.pentaho.reporting.libraries.formula.LibFormulaErrorValue;
import org.pentaho.reporting.libraries.formula.function.Function;
import org.pentaho.reporting.libraries.formula.function.FunctionDescription;
import org.pentaho.reporting.libraries.formula.function.FunctionRegistry;
import org.pentaho.reporting.libraries.formula.function.ParameterCallback;
import org.pentaho.reporting.libraries.formula.typing.Type;
import org.pentaho.reporting.libraries.formula.typing.TypeRegistry;
/**
* A function. Formulas consist of functions, references or static values, which are connected by operators.
* <p/>
* Functions always have a cannonical name, which must be unique and which identifies the function. Functions can have a
* list of parameters. The number of parameters can vary, and not all parameters need to be filled.
* <p/>
* Functions can have required and optional parameters. Mixing required and optional parameters is not allowed. Optional
* parameters cannot be ommited, unless they are the last parameter in the list.
* <p/>
* This class provides the necessary wrapper functionality to fill in the parameters.
*
* @author Thomas Morgner
*/
public class FormulaFunction extends AbstractLValue
{
private static final Log logger = LogFactory.getLog(FormulaFunction.class);
private static class FormulaParameterCallback implements ParameterCallback
{
private TypeValuePair[] backend;
private FormulaFunction function;
private FormulaParameterCallback(final FormulaFunction function)
{
this.function = function;
this.backend = new TypeValuePair[function.parameters.length];
}
private TypeValuePair get(final int pos) throws EvaluationException
{
final LValue parameter = function.parameters[pos];
final Type paramType = function.metaData.getParameterType(pos);
if (parameter != null)
{
final TypeValuePair result = parameter.evaluate();
if (result.getValue() == null)
{
return result;
}
// lets do some type checking, right?
final TypeRegistry typeRegistry = function.getContext().getTypeRegistry();
final TypeValuePair converted = typeRegistry.convertTo(paramType, result);
if (converted == null)
{
if (logger.isDebugEnabled())
{
logger.debug("Failed to evaluate parameter " + pos + " on function " + function);
}
throw EvaluationException.getInstance(LibFormulaErrorValue.ERROR_INVALID_AUTO_ARGUMENT_VALUE);
}
return converted;
}
else
{
return new TypeValuePair(paramType, function.metaData.getDefaultValue(pos));
}
}
public LValue getRaw(final int position)
{
return function.parameters[position];
}
public Object getValue(final int position) throws EvaluationException
{
final TypeValuePair retval = backend[position];
if (retval != null)
{
return retval.getValue();
}
final TypeValuePair pair = get(position);
backend[position] = pair;
return pair.getValue();
}
public Type getType(final int position) throws EvaluationException
{
final TypeValuePair retval = backend[position];
if (retval != null)
{
return retval.getType();
}
final TypeValuePair pair = get(position);
backend[position] = pair;
return pair.getType();
}
public int getParameterCount()
{
return backend.length;
}
}
private String functionName;
private LValue[] parameters;
private Function function;
private FunctionDescription metaData;
private static final long serialVersionUID = 8023588016882997962L;
public FormulaFunction(final String functionName,
final LValue[] parameters,
final ParsePosition parsePosition)
{
this.functionName = functionName;
setParsePosition(parsePosition);
this.parameters = (LValue[]) parameters.clone();
}
public FormulaFunction(final String functionName, final LValue[] parameters)
{
this(functionName, parameters, null);
}
public void initialize(final FormulaContext context) throws EvaluationException
{
super.initialize(context);
final FunctionRegistry registry = context.getFunctionRegistry();
if (function == null)
{
function = registry.createFunction(functionName);
}
if (metaData == null)
{
metaData = registry.getMetaData(functionName);
}
for (int i = 0; i < parameters.length; i++)
{
parameters[i].initialize(context);
}
}
/**
* Returns the function's name. This is the normalized name and may not be suitable for the user. Query the function's
* metadata to retrieve a display-name.
*
* @return the function's name.
*/
public String getFunctionName()
{
return functionName;
}
/**
* Returns the initialized function. Be aware that this method will return null if this LValue instance has not yet
* been initialized.
*
* @return the function instance or null, if the FormulaFunction instance has not yet been initialized.
*/
public Function getFunction()
{
return function;
}
/**
* Returns the function's meta-data. Be aware that this method will return null if this LValue instance has not yet
* been initialized.
*
* @return the function description instance or null, if the FormulaFunction instance has not yet been initialized.
*/
public FunctionDescription getMetaData()
{
return metaData;
}
public Object clone() throws CloneNotSupportedException
{
final FormulaFunction fn = (FormulaFunction) super.clone();
fn.parameters = (LValue[]) parameters.clone();
for (int i = 0; i < parameters.length; i++)
{
final LValue parameter = parameters[i];
fn.parameters[i] = (LValue) parameter.clone();
}
return fn;
}
public TypeValuePair evaluate() throws EvaluationException
{
// First, grab the parameters and their types.
final FormulaContext context = getContext();
// And if everything is ok, compute the stuff ..
if (function == null)
{
throw EvaluationException.getInstance(LibFormulaErrorValue.ERROR_INVALID_FUNCTION_VALUE);
}
try
{
return function.evaluate(context, new FormulaParameterCallback(this));
}
catch (EvaluationException e)
{
throw e;
}
catch (Exception e)
{
logger.error("Unexpected exception while evaluating", e);
throw EvaluationException.getInstance(LibFormulaErrorValue.ERROR_UNEXPECTED_VALUE);
}
}
/**
* Returns any dependent lvalues (parameters and operands, mostly).
*
* @return
*/
public LValue[] getChildValues()
{
return (LValue[]) parameters.clone();
}
public String toString()
{
final StringBuffer b = new StringBuffer(100);
b.append(functionName);
b.append('(');
for (int i = 0; i < parameters.length; i++)
{
if (i > 0)
{
b.append(';');
}
final LValue parameter = parameters[i];
b.append(parameter);
}
b.append(')');
return b.toString();
}
/**
* Checks whether the LValue is constant. Constant lvalues always return the same value.
*
* @return true, if the function will always return the same value.
*/
public boolean isConstant()
{
if (metaData == null || metaData.isVolatile())
{
return false;
}
for (int i = 0; i < parameters.length; i++)
{
final LValue value = parameters[i];
if (value.isConstant() == false)
{
return false;
}
}
return true;
}
}