001 /** 002 * ========================================= 003 * LibFormula : a free Java formula library 004 * ========================================= 005 * 006 * Project Info: http://reporting.pentaho.org/libformula/ 007 * 008 * (C) Copyright 2006-2007, by Pentaho Corporation and Contributors. 009 * 010 * This library is free software; you can redistribute it and/or modify it under the terms 011 * of the GNU Lesser General Public License as published by the Free Software Foundation; 012 * either version 2.1 of the License, or (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 015 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 016 * See the GNU Lesser General Public License for more details. 017 * 018 * You should have received a copy of the GNU Lesser General Public License along with this 019 * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, 020 * Boston, MA 02111-1307, USA. 021 * 022 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 023 * in the United States and other countries.] 024 * 025 * 026 * ------------ 027 * $Id: FormulaFunction.java 3521 2007-10-16 10:55:14Z tmorgner $ 028 * ------------ 029 * (C) Copyright 2006-2007, by Pentaho Corporation. 030 */ 031 package org.jfree.formula.lvalues; 032 033 import org.jfree.formula.EvaluationException; 034 import org.jfree.formula.FormulaContext; 035 import org.jfree.formula.LibFormulaErrorValue; 036 import org.jfree.formula.function.Function; 037 import org.jfree.formula.function.FunctionDescription; 038 import org.jfree.formula.function.FunctionRegistry; 039 import org.jfree.formula.function.ParameterCallback; 040 import org.jfree.formula.typing.Type; 041 import org.jfree.formula.typing.TypeRegistry; 042 import org.jfree.util.Log; 043 044 /** 045 * A function. Formulas consist of functions, references or static values, which 046 * are connected by operators. 047 * <p/> 048 * Functions always have a cannonical name, which must be unique and which 049 * identifies the function. Functions can have a list of parameters. The number 050 * of parameters can vary, and not all parameters need to be filled. 051 * <p/> 052 * Functions can have required and optional parameters. Mixing required and 053 * optional parameters is not allowed. Optional parameters cannot be ommited, 054 * unless they are the last parameter in the list. 055 * <p/> 056 * This class provides the necessary wrapper functionality to fill in the 057 * parameters. 058 * 059 * @author Thomas Morgner 060 */ 061 public class FormulaFunction extends AbstractLValue 062 { 063 private static class FormulaParameterCallback implements ParameterCallback 064 { 065 private TypeValuePair[] backend; 066 private FormulaFunction function; 067 068 private FormulaParameterCallback(final FormulaFunction function) 069 { 070 this.function = function; 071 this.backend = new TypeValuePair[function.parameters.length]; 072 } 073 074 private TypeValuePair get(final int pos) throws EvaluationException 075 { 076 final LValue parameter = function.parameters[pos]; 077 final Type paramType = function.metaData.getParameterType(pos); 078 if (parameter != null) 079 { 080 final TypeValuePair result = parameter.evaluate(); 081 // lets do some type checking, right? 082 final TypeRegistry typeRegistry = function.getContext().getTypeRegistry(); 083 final TypeValuePair converted = typeRegistry.convertTo(paramType, result); 084 if (converted == null) 085 { 086 Log.debug("Failed to evaluate parameter " + pos + " on function " + function); 087 throw new EvaluationException(LibFormulaErrorValue.ERROR_INVALID_AUTO_ARGUMENT_VALUE); 088 } 089 return converted; 090 } 091 else 092 { 093 return new TypeValuePair(paramType, function.metaData.getDefaultValue(pos)); 094 } 095 } 096 097 public LValue getRaw(final int position) 098 { 099 return function.parameters[position]; 100 } 101 102 public Object getValue(final int position) throws EvaluationException 103 { 104 final TypeValuePair retval = backend[position]; 105 if (retval != null) 106 { 107 return retval.getValue(); 108 } 109 110 final TypeValuePair pair = get(position); 111 backend[position] = pair; 112 return pair.getValue(); 113 } 114 115 public Type getType(final int position) throws EvaluationException 116 { 117 final TypeValuePair retval = backend[position]; 118 if (retval != null) 119 { 120 return retval.getType(); 121 } 122 123 final TypeValuePair pair = get(position); 124 backend[position] = pair; 125 return pair.getType(); 126 } 127 128 public int getParameterCount() 129 { 130 return backend.length; 131 } 132 } 133 134 private String functionName; 135 private LValue[] parameters; 136 private Function function; 137 private FunctionDescription metaData; 138 139 public FormulaFunction(final String functionName, final LValue[] parameters) 140 { 141 this.functionName = functionName; 142 this.parameters = parameters; 143 } 144 145 public void initialize(final FormulaContext context) throws EvaluationException 146 { 147 super.initialize(context); 148 final FunctionRegistry registry = context.getFunctionRegistry(); 149 function = registry.createFunction(functionName); 150 metaData = registry.getMetaData(functionName); 151 152 for (int i = 0; i < parameters.length; i++) 153 { 154 parameters[i].initialize(context); 155 } 156 } 157 158 /** 159 * Returns the function's name. This is the normalized name and may not be 160 * suitable for the user. Query the function's metadata to retrieve a 161 * display-name. 162 * 163 * @return the function's name. 164 */ 165 public String getFunctionName() 166 { 167 return functionName; 168 } 169 170 /** 171 * Returns the initialized function. Be aware that this method will return 172 * null if this LValue instance has not yet been initialized. 173 * 174 * @return the function instance or null, if the FormulaFunction instance has 175 * not yet been initialized. 176 */ 177 public Function getFunction() 178 { 179 return function; 180 } 181 182 /** 183 * Returns the function's meta-data. Be aware that this method will return 184 * null if this LValue instance has not yet been initialized. 185 * 186 * @return the function description instance or null, if the FormulaFunction 187 * instance has not yet been initialized. 188 */ 189 public FunctionDescription getMetaData() 190 { 191 return metaData; 192 } 193 194 public Object clone() throws CloneNotSupportedException 195 { 196 final FormulaFunction fn = (FormulaFunction) super.clone(); 197 fn.parameters = (LValue[]) parameters.clone(); 198 for (int i = 0; i < parameters.length; i++) 199 { 200 final LValue parameter = parameters[i]; 201 fn.parameters[i] = (LValue) parameter.clone(); 202 } 203 return fn; 204 } 205 206 public TypeValuePair evaluate() throws EvaluationException 207 { 208 // First, grab the parameters and their types. 209 final FormulaContext context = getContext(); 210 // And if everything is ok, compute the stuff .. 211 if (function == null) 212 { 213 throw new EvaluationException(LibFormulaErrorValue.ERROR_INVALID_FUNCTION_VALUE); 214 } 215 try 216 { 217 return function.evaluate(context, new FormulaParameterCallback(this)); 218 } 219 catch(EvaluationException e) 220 { 221 throw e; 222 } 223 catch(Exception e) 224 { 225 Log.error("Unexpected exception while evaluating", e); 226 throw new EvaluationException(LibFormulaErrorValue.ERROR_UNEXPECTED_VALUE); 227 } 228 } 229 230 /** 231 * Returns any dependent lvalues (parameters and operands, mostly). 232 * 233 * @return 234 */ 235 public LValue[] getChildValues() 236 { 237 return (LValue[]) parameters.clone(); 238 } 239 240 241 public String toString() 242 { 243 final StringBuffer b = new StringBuffer(); 244 b.append(functionName); 245 b.append("("); 246 for (int i = 0; i < parameters.length; i++) 247 { 248 if (i > 0) 249 { 250 b.append(";"); 251 } 252 final LValue parameter = parameters[i]; 253 b.append(parameter); 254 } 255 b.append(")"); 256 return b.toString(); 257 } 258 259 /** 260 * Checks, whether the LValue is constant. Constant lvalues always return the 261 * same value. 262 * 263 * @return 264 */ 265 public boolean isConstant() 266 { 267 if (metaData.isVolatile()) 268 { 269 return false; 270 } 271 for (int i = 0; i < parameters.length; i++) 272 { 273 final LValue value = parameters[i]; 274 if (value.isConstant() == false) 275 { 276 return false; 277 } 278 } 279 return true; 280 } 281 282 283 }