/* * 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; } }