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    }