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 }