Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/ujac/util/exi/ExpressionInterpreter.java


1   /*
2    * Copyright (C) 2003 by Christian Lauer.
3    *
4    * This library is free software; you can redistribute it and/or
5    * modify it under the terms of the GNU Library General Public
6    * License as published by the Free Software Foundation; either
7    * version 2 of the License, or (at your option) any later version.
8    *
9    * This library is distributed in the hope that it will be useful,
10   * but WITHOUT ANY WARRANTY; without even the implied warranty of
11   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12   * Library General Public License for more details.
13   *
14   * You should have received a copy of the GNU Library General Public
15   * License along with this library; if not, write to the Free
16   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17   *
18   * If you didn't download this code from the following link, you should check if
19   * you aren't using an obsolete version:
20   * http://sourceforge.net/projects/ujac
21   */
22  
23  package org.ujac.util.exi;
24  
25  import java.io.Writer;
26  import java.io.IOException;
27  import java.io.StringWriter;
28  
29  import java.lang.reflect.Method;
30  import java.lang.reflect.InvocationTargetException;
31  
32  import java.math.BigDecimal;
33  
34  import java.sql.Time;
35  import java.sql.Timestamp;
36  import java.text.DateFormat;
37  import java.text.NumberFormat;
38  import java.text.ParseException;
39  
40  import java.util.Map;
41  import java.util.Date;
42  import java.util.List;
43  import java.util.Arrays;
44  import java.util.HashMap;
45  import java.util.Iterator;
46  import java.util.ArrayList;
47  import java.util.Collection;
48  
49  import org.ujac.util.exi.type.MapType;
50  import org.ujac.util.exi.type.ResourceBundleType;
51  import org.ujac.util.exi.type.RowType;
52  import org.ujac.util.exi.type.DateType;
53  import org.ujac.util.exi.type.LongType;
54  import org.ujac.util.exi.type.TableType;
55  import org.ujac.util.exi.type.DoubleType;
56  import org.ujac.util.exi.type.StringType;
57  import org.ujac.util.exi.type.DefaultType;
58  import org.ujac.util.exi.type.IntegerType;
59  import org.ujac.util.exi.type.BooleanType;
60  import org.ujac.util.exi.type.CollectionType;
61  
62  import org.ujac.util.table.Table;
63  
64  import org.ujac.util.text.FormatHelper;
65  
66  /**
67   * Title:       ExpressionInterpreter<br>
68   * Description: Interpreter for expressions fitting into the following syntax.<br>
69   * Basically an expression consists of an object, an operation and an operand: ${object operation operand} <br>
70   * The operation and the operand are optional.<br>
71   * The object can be an expression itself, so encapsulation of expressions is possible. <br> 
72   * Example: ${${today decrYear 1} getYear} produces a String containing the value of last year (currently 2001). <br>
73   * <br>
74   * Operations can be operators as well. Supported operators are +, -, / and * for mathematical operations. <br> 
75   * Additional there are two operators '.' and '->' which can be used to reference a property on an object. <br>
76   * The special thing about operators is, that they do not need to be separated from the object and the operand. 
77   * So is for example the expression ${test+10} valid, in case the object test contains a numeric object.<br>
78   * <br>
79   * List of currently supported operations for Date values: <br>
80   * <b>incrYear</b>: used to increment the refering date by an amount of years. <br> 
81   * <b>incrMonth</b>: used to increment the refering date by an amount of months. <br> 
82   * <b>incrDay</b>: used to increment the refering date by an amount of days. <br> 
83   * <b>decrYear</b>: used to decrement the refering date by an amount of years. <br> 
84   * <b>decrMonth</b>: used to decrement the refering date by an amount of months. <br> 
85   * <b>decrDay</b>: used to decrement the refering date by an amount of days. <br> 
86   * <b>min</b>: used to determine the minimum value of object and operand. <br> 
87   * <b>max</b>: used to determine the maximum value of object and operand. <br> 
88   * <b>prevUltimo</b>: used to determine the previous ultimo date of the refering date. <br> 
89   * <b>nextUltimo</b>: used to determine the next ultimo date of the refering date. <br> 
90   * <b>getYear</b>: used to fetch the year from the refering date. <br>
91   * <b>getMonth</b>: used to fetch the month from the refering date. <br>
92   * <b>getDay</b>: used to fetch the day from the refering date. <br>
93   * <b>properties, accessible by reference operator: <br>
94   * &nbsp;<b>year</b>: Same as getYear<br>
95   * &nbsp;<b>month</b>: Same as getMonth<br>
96   * &nbsp;<b>day</b>: Same as getDay<br>
97   * <br>
98   * List of currently supported operations for String values: <br>
99   * <b>length</b>: used to fetch the length from the refering string. <br> 
100  * <br>
101  * List of currently supported operations for Integer values: <br>
102  * <b>min</b>: used to determine the minimum value of object and operand. <br> 
103  * <b>max</b>: used to determine the maximum value of object and operand. <br> 
104  * <b>add</b> or <b>+</b>: used to add the value of the operand to the object. <br> 
105  * <b>sub</b> or <b>-</b>: used to substract the value of the operand from the object. <br> 
106  * <b>mul</b> or <b>*</b>: used to multiply the value of the operand with the object. <br> 
107  * <b>div</b> or <b>/</b>: used to divide the operand value from the object's value. <br> 
108  * <br>
109  * List of currently supported operations for Long values: <br>
110  * <b>min</b>: used to determine the minimum value of object and operand. <br> 
111  * <b>max</b>: used to determine the maximum value of object and operand. <br> 
112  * <b>add</b> or <b>+</b>: used to add the value of the operand to the object. <br> 
113  * <b>sub</b> or <b>-</b>: used to substract the value of the operand from the object. <br> 
114  * <b>mul</b> or <b>*</b>: used to multiply the value of the operand with the object. <br> 
115  * <b>div</b> or <b>/</b>: used to divide the operand value from the object's value. <br> 
116  * <br>
117  * List of currently supported operations for Double values: <br>
118  * <b>min</b>: used to determine the minimum value of object and operand. <br> 
119  * <b>max</b>: used to determine the maximum value of object and operand. <br> 
120  * <b>add</b> or <b>+</b>: used to add the value of the operand to the object. <br> 
121  * <b>sub</b> or <b>-</b>: used to substract the value of the operand from the object. <br> 
122  * <b>mul</b> or <b>*</b>: used to multiply the value of the operand with the object. <br> 
123  * <b>div</b> or <b>/</b>: used to divide the operand value from the object's value. <br> 
124  * <br>
125  *<br>Log: $Log: ExpressionInterpreter.java,v $
126  *<br>Log: Revision 1.43  2003/12/02 21:36:32  lauerc
127  *<br>Log: Fixed comparison operator parsing.
128  *<br>Log:
129  *<br>Log: Revision 1.42  2003/11/23 03:23:39  lauerc
130  *<br>Log: Cleaned up code.
131  *<br>Log: Fixed javadoc comments.
132  *<br>Log:
133  *<br>Log: Revision 1.41  2003/11/22 01:48:42  lauerc
134  *<br>Log: Added support for resource bundles.
135  *<br>Log:
136  *<br>Log: Revision 1.40  2003/11/01 12:06:38  lauerc
137  *<br>Log: Added copyright notice.
138  *<br>Log:
139  *<br>Log: Revision 1.39  2003/11/01 01:52:39  lauerc
140  *<br>Log: At method parseOperand: added support for operator token '!'.
141  *<br>Log:
142  *<br>Log: Revision 1.38  2003/10/30 23:41:50  lauerc
143  *<br>Log: Fixed bugs in statement handling.
144  *<br>Log:
145  *<br>Log: Revision 1.37  2003/10/29 00:15:15  lauerc
146  *<br>Log: Removed unneccessary imports.
147  *<br>Log:
148  *<br>Log: Revision 1.36  2003/10/29 00:11:24  lauerc
149  *<br>Log: Fixed evaluation of procedures at method evalStatement.
150  *<br>Log: Removed main method, performing tests in jUnit test classes now.
151  *<br>Log:
152  *<br>Log: Revision 1.35  2003/10/28 07:08:09  lauerc
153  *<br>Log: Added support for procedures.
154  *<br>Log:
155  *<br>Log: Revision 1.34  2003/10/13 19:12:19  lauerc
156  *<br>Log: Fixed method evalBoolean: In case of non Boolean expression result parsing boolean value from expression result instead of expression code.
157  *<br>Log:
158  *<br>Log: Revision 1.33  2003/10/07 23:53:56  lauerc
159  *<br>Log: Removed unneccessary else branch at method evalStringOperand.
160  *<br>Log:
161  *<br>Log: Revision 1.32  2003/10/04 21:07:40  lauerc
162  *<br>Log: Implemented type handling in a smarter way at method evalStringOperand.
163  *<br>Log:
164  *<br>Log: Revision 1.31  2003/10/04 00:18:17  lauerc
165  *<br>Log: Improved localization.
166  *<br>Log:
167  *<br>Log: Revision 1.30  2003/10/03 06:59:47  lauerc
168  *<br>Log: Added localization test to main method.
169  *<br>Log:
170  *<br>Log: Revision 1.29  2003/10/02 23:58:59  lauerc
171  *<br>Log: Added localization support to expression interpreter.
172  *<br>Log:
173  *<br>Log: Revision 1.28  2003/09/28 15:38:23  lauerc
174  *<br>Log: Fixed javadoc comments.
175  *<br>Log:
176  *<br>Log: Revision 1.27  2003/09/03 18:47:15  lauerc
177  *<br>Log: Fixed number formatting at method evalStringOperand.
178  *<br>Log:
179  *<br>Log: Revision 1.26  2003/09/02 19:44:39  lauerc
180  *<br>Log: Added support for MapType.
181  *<br>Log:
182  *<br>Log: Revision 1.25  2003/08/29 05:11:05  lauerc
183  *<br>Log: Considering inner encapsulations now (Changes at methods parseExpr and parseOperand).
184  *<br>Log: Allowed multiple evaluations of expressions to make stuff like the _foreach_ statement work correctly.
185  *<br>Log:
186  *<br>Log: Revision 1.24  2003/08/29 04:49:43  lauerc
187  *<br>Log: Added support for Collection type handler.
188  *<br>Log:
189  *<br>Log: Revision 1.23  2003/07/28 23:11:46  lauerc
190  *<br>Log: Redesigned expression parsing.
191  *<br>Log: Implemented priority rules for operators.
192  *<br>Log: Removed unneccessary usage of StringBuffer to gain better performance.
193  *<br>Log:
194  *<br>Log: Revision 1.22  2003/07/26 15:59:33  lauerc
195  *<br>Log: Fixed statement processing.
196  *<br>Log:
197  *<br>Log: Revision 1.21  2003/07/23 22:38:23  lauerc
198  *<br>Log: Added missing support for boolean specific 'getters' with
199  *<br>Log: common form 'isXyz' at method getParameterValues.
200  *<br>Log:
201  *<br>Log: Revision 1.20  2003/07/23 21:34:16  lauerc
202  *<br>Log: Added support for Arrays at _foreach_ statement evaluation.
203  *<br>Log:
204  *<br>Log: Revision 1.19  2003/07/15 21:50:59  lauerc
205  *<br>Log: Improved result handling at method evalStringExpr.
206  *<br>Log: Statement handling stuff (methods scanForStatements + evalStatement) rewritten.
207  *<br>Log: Fixed handling of constant values at method parseOperand.
208  *<br>Log:
209  *<br>Log: Revision 1.18  2003/07/11 22:34:48  lauerc
210  *<br>Log: Fixed method getTypeHandler: If the type of the object doesn't match an registered type,
211  *<br>Log: checking the compability of the type with the registered types.
212  *<br>Log: If all failed, using default type.
213  *<br>Log:
214  *<br>Log: Revision 1.17  2003/05/20 21:19:31  lauerc
215  *<br>Log: Enhanced exception handling: Added the expression code to the message for easier problem identification.
216  *<br>Log: Changed behaviour at eval methods for primitives: In case the expression resolved to null, returning the default value, not throwing an exception anymore.
217  *<br>Log:
218  *<br>Log: Revision 1.16  2003/05/10 09:24:46  lauerc
219  *<br>Log: Fixed incompabilities with JDK 1.3.
220  *<br>Log: Added support for BigDecimal type.
221  *<br>Log:
222  *<br>Log: Revision 1.15  2003/04/29 05:32:18  lauerc
223  *<br>Log: Added support for Float type.
224  *<br>Log: Made number processing more error tolerant.
225  *<br>Log:
226  *<br>Log: Revision 1.14  2003/04/26 00:45:03  lauerc
227  *<br>Log: Removed previously added exception at method evalObject, returning the given text instead.
228  *<br>Log:
229  *<br>Log: Revision 1.13  2003/04/26 00:22:04  lauerc
230  *<br>Log: Throwing an exception in case no expression was detected in method evalObject instead of letting the method run into the NullPointerException knife.
231  *<br>Log:
232  *<br>Log: Revision 1.12  2003/03/20 07:05:15  lauerc
233  *<br>Log: Fixed bug at output of static text at eval method.
234  *<br>Log:
235  *<br>Log: Revision 1.11  2003/03/17 23:40:46  lauerc
236  *<br>Log: Added capability for streaming the expression output at method eval by passing a writer instance as argument.
237  *<br>Log:
238  *<br>Log: Revision 1.10  2003/03/17 20:28:18  lauerc
239  *<br>Log: Registered IntegerType for new class SequenceIndex.
240  *<br>Log: Setting sequence index variable when evaluating sequences.
241  *<br>Log:
242  *<br>Log: Revision 1.9  2003/03/17 07:58:38  lauerc
243  *<br>Log: Enhanced support for _if_ and _foreach_ statements.
244  *<br>Log:
245  *<br>Log: Revision 1.8  2003/03/16 01:46:57  lauerc
246  *<br>Log: Identificating the expression types by their class now.
247  *<br>Log: Initial support for statements, they do provide conditional execution of expressions at the eval method, 
248  *<br>Log: (_if_ statement) and iterations (_foreach_ statement).
249  *<br>Log:
250  *<br>Log: Revision 1.7  2003/03/13 00:28:20  lauerc
251  *<br>Log: Added support for table type.
252  *<br>Log:
253  *<br>Log: Revision 1.6  2003/03/11 06:35:00  lauerc
254  *<br>Log: Added better support for type formats.
255  *<br>Log:
256  *<br>Log: Revision 1.5  2003/03/09 14:13:29  lauerc
257  *<br>Log: Cleaned up code.
258  *<br>Log:
259  *<br>Log: Revision 1.4  2003/03/09 14:08:50  lauerc
260  *<br>Log: Performance tuning:
261  *<br>Log:   - Changed type of source to char array.
262  *<br>Log:   - Reimplemented method scanExpression.
263  *<br>Log:
264  *<br>Log: Revision 1.3  2003/03/08 18:12:17  lauerc
265  *<br>Log: Passing the total position and length of an expression to its nested expressions.
266  *<br>Log:
267  *<br>Log: Revision 1.2  2003/03/08 16:55:58  lauerc
268  *<br>Log: Completely reimplemented parser.
269  *<br>Log:
270  *<br>Log: Revision 1.1  2003/03/05 06:23:25  lauerc
271  *<br>Log: Initial revision.
272  *<br>Log:
273  *@author  $Author: lauerc $
274  *@version $Revision: 1.43 $
275  */
276 public class ExpressionInterpreter {
277   
278   /** Constant for the parse state 'object'. */
279   public static final int PS_OBJECT = 1;
280   /** Constant for the parse state 'operation'. */
281   public static final int PS_OPERATION = 2;
282   /** Constant for the parse state 'operand'. */
283   public static final int PS_OPERAND = 3;
284 
285   /** The name of the default type handler. */
286   public static final Class DEFAULT_TYPE = Object.class; 
287 
288   /** The argument type list for a getter method. */
289   public static final Class[] GETTER_ARG_TYPES = new Class[]{};
290   /** The argument list for a getter method. */
291   public static final Object[] GETTER_ARGS = new Object[]{};
292   
293   /** 
294    * A flag which defines, whether BXTable fields are returned as formatted Strings,  
295    * or their defineddata types are considered for returning values. 
296    */
297   private boolean formatTableValues = true;
298 
299   /** The format helper to use. */
300   private FormatHelper formatHelper = null;
301 
302   /** A map holding the registered type handlers by their names. */
303   private Map typeHandlers = new HashMap();
304   
305   /**
306    * Constructs an ExpressionInterpreter instance with no specific attributes. 
307    */
308   public ExpressionInterpreter() {
309     this(new FormatHelper());
310   }
311   
312   /**
313    * Constructs an ExpressionInterpreter instance with no specific attributes.
314    * @param formatHelper The format helper to use. 
315    */
316   public ExpressionInterpreter(FormatHelper formatHelper) {
317     this.formatHelper = formatHelper;
318 
319     // registering the default type handlers.
320     registerDefaultTypeHandlers();
321   }
322 
323   /**
324    * Gets the used format helper.
325    * @return The format helper, used for output formats.
326    */
327   public FormatHelper getFormatHelper() {
328     return formatHelper;
329   }
330   /**
331    * Sets the used format helper.
332    * @param formatHelper The format helper to set.
333    */
334   public void setFormatHelper(FormatHelper formatHelper) {
335     this.formatHelper = formatHelper;
336   }
337 
338   /**
339    * Registers an expression type
340    * @param type The new expression type.
341    */
342   public void registerTypeHandler(ExpressionType type) {
343     registerTypeHandler(type.getType(), type);
344   }
345   /**
346    * Registers an expression type
347    * @param typeClass The type class.
348    * @param type The new expression type.
349    */
350   public void registerTypeHandler(Class typeClass, ExpressionType type) {
351     typeHandlers.put(typeClass, type);
352   }
353 
354   /**
355    * Unregisters an expression type
356    * @param type The expression type to unregister.
357    */
358   public void unregisterTypeHandler(ExpressionType type) {
359     unregisterTypeHandler(type.getType());
360   }
361   /**
362    * Unregisters an expression type
363    * @param typeClass The class of the expression type to unregister.
364    */
365   public void unregisterTypeHandler(Class typeClass) {
366     typeHandlers.remove(typeClass);
367   }
368   
369   /**
370    * Unregisters all expression types
371    */
372   public void unregisterAllTypeHandlers() {
373     typeHandlers.clear();
374   }
375 
376   /**
377    * Registers the default type handlers. This method is called by the constructor.
378    */
379   public void registerDefaultTypeHandlers() {
380     registerTypeHandler(new DefaultType(this));
381     registerTypeHandler(new BooleanType(this));
382     registerTypeHandler(new DateType(this));
383     registerTypeHandler(new DoubleType(this));
384     registerTypeHandler(BigDecimal.class, new DoubleType(this));
385     registerTypeHandler(new IntegerType(this));
386     registerTypeHandler(SequenceIndex.class, new IntegerType(this));
387     registerTypeHandler(new LongType(this));
388     registerTypeHandler(new StringType(this));
389     registerTypeHandler(new TableType(this));
390     registerTypeHandler(new RowType(this));
391     registerTypeHandler(new CollectionType(this));
392     registerTypeHandler(new MapType(this));
393     registerTypeHandler(new ResourceBundleType(this));
394   }
395 
396   /**
397    * Getter method for the flag formatTableValues.
398    * @return The current value of the property formatTableValues.
399    */
400   public boolean getFormatTableValues() {
401     return formatTableValues;
402   }  
403   /**
404    * Setter method for the flag formatTableValues.
405    * @param formatTableValues The new value for the property formatTableValues.
406    */
407   public void setFormatTableValues(boolean formatTableValues) {
408     this.formatTableValues = formatTableValues;
409   }
410   
411 
412   /**
413    * Retrieves the value for the given parameter name.
414    * @param paramName The parameter name.
415    * @param params The parameter map.
416    * @param bean The bean used to retrieve parameter values, if the parameter value didn't exist in the params map.
417    * @return The value that's found or null.
418    * @exception ExpressionException If the parameter could not be successfuly evaluated.
419    */ 
420   public Object getParameterValue(String paramName, Map params, Object bean) throws ExpressionException {
421     Object value = params.get(paramName);
422     if (value != null) {
423       return value;
424     }
425     
426     if (bean == null) {
427       // no bean defined, which could get queried!
428       return null;
429     }
430     
431     if (paramName == null) {
432       return null;
433     }
434     
435     try {
436       // getting property object from bean using "getNnnn", where nnnn is parameter name
437       Method getterMethod = null;
438       try {
439         // first trying form getPropertyNaae for regular value
440         String getterName = new StringBuffer("get")
441           .append(Character.toUpperCase(paramName.charAt(0)))
442           .append(paramName.substring(1)).toString();
443         Class paramClass = bean.getClass();
444         getterMethod = paramClass.getMethod(getterName, ExpressionInterpreter.GETTER_ARG_TYPES);
445       } catch (NoSuchMethodException ex) {
446         // next trying isPropertyNaae for possible boolean
447         String getterName = new StringBuffer("is")
448           .append(Character.toUpperCase(paramName.charAt(0)))
449           .append(paramName.substring(1)).toString();
450         Class paramClass = bean.getClass();
451         getterMethod = paramClass.getMethod(getterName, ExpressionInterpreter.GETTER_ARG_TYPES);
452       }
453       value = getterMethod.invoke(bean, GETTER_ARGS);
454       params.put(paramName, value);
455     } catch (NoSuchMethodError ex) {
456       throw new ExpressionException("Property '" + paramName + "' is undefined for given bean from class " + bean.getClass().getName() + ".");
457     } catch (NoSuchMethodException ex) {
458       throw new ExpressionException("Property '" + paramName + "' is undefined for given bean from class " + bean.getClass().getName() + ".");
459     } catch (InvocationTargetException ex) {
460       throw new ExpressionException("Property '" + paramName + "' could not be evaluated for given bean from class " + bean.getClass().getName() + ".");
461     } catch (IllegalAccessException ex) {
462       throw new ExpressionException("Property '" + paramName + "' could not be accessed for given bean from class " + bean.getClass().getName() + ".");
463     }
464     return value;    
465   }
466   
467   /**
468    * Evaluates a text, which is likely to contain expressions.
469    * @param text The text to evaluate.
470    * @param params The parameters.
471    * @return The produced text.
472    * @exception ExpressionException If the expression could not be successfuly resolved
473    */
474   public String eval(String text, Map params) throws ExpressionException {
475     return eval(text, params, null);
476   }
477 
478   /**
479    * Evaluates a text, which is likely to contain expressions.
480    * @param text The text to evaluate.
481    * @param writer The writer, to which to write the result.
482    * @param params The parameters.
483    * @exception ExpressionException If the expression could not be successfuly resolved
484    * @exception IOException If something went wrong while writing the result to the given writer.
485    */
486   public void eval(String text, Writer writer, Map params) throws ExpressionException, IOException {    
487     eval(text, writer, params, null);
488   }
489   
490   /**
491    * Evaluates a text, which is likely to contain expressions.
492    * @param text The text to evaluate.
493    * @param params The parameters.
494    * @param bean The bean used to retrieve parameter values, if the parameter value didn't exist in the params map.
495    * @return The produced text.
496    * @exception ExpressionException If the expression could not be successfuly resolved
497    */
498   public String eval(String text, Map params, Object bean) throws ExpressionException {
499     if (text == null) {
500       return null;
501     }
502     try {
503       StringWriter stringWriter = new StringWriter();
504       eval(text, stringWriter, params, bean);
505       stringWriter.flush();
506       stringWriter.close();
507       return stringWriter.toString();
508     } catch (IOException ex) {
509       // What could happen, when writing to a StringWriter?
510       throw new ExpressionException(ex.getMessage());
511     }
512   }
513   
514   /**
515    * Evaluates a text, which is likely to contain expressions.
516    * @param text The text to evaluate.
517    * @param writer The writer, to which to write the result.
518    * @param params The parameters.
519    * @param bean The bean used to retrieve parameter values, if the parameter value didn't exist in the params map.
520    * @exception ExpressionException If the expression could not be successfuly resolved
521    * @exception IOException If something went wrong while writing the result to the given writer.
522    */
523   public void eval(String text, Writer writer, Map params, Object bean) 
524     throws ExpressionException, IOException {
525     
526     char[] textChars = text.toCharArray();
527     eval(textChars, writer, 0, textChars.length - 1, null, 0, params, bean);
528   }
529 
530   /**
531    * Evaluates a text, which is likely to contain expressions.
532    * @param text The text to evaluate.
533    * @param writer The expression result writer.
534    * @param firstPos The fist position of the expression within the given text.
535    * @param lastPos The last position of the expression within the given text.
536    * @param tokens The list of expression tokens to fill.
537    * @param level The evaluation level.
538    * @param params The parameters.
539    * @param bean The bean used to retrieve parameter values, if the parameter value didn't exist in the params map.
540    * @exception ExpressionException If the expression could not be successfuly resolved
541    * @exception IOException In case a I/O problem occured while writing the expression result.
542    */
543   public void eval(char[] text, Writer writer, int firstPos, int lastPos, List tokens, 
544                    int level, Map params, Object bean) 
545     throws ExpressionException, IOException {
546       
547     //System.out.println("the text to evaluate: '" + new String(text, firstPos, lastPos-firstPos+1) + "'");
548     boolean recordTokens = (tokens != null); 
549     int pos = firstPos;
550     StatementInfo stmtInfo = null;
551     ExpressionTuple expr = null;
552     
553     while (true) {
554       // looking for statements
555       stmtInfo = scanForStatements(text, pos, lastPos);
556       //System.out.println(stmtInfo);
557       if (stmtInfo != null) {
558         int startPosition = stmtInfo.getStartPosition();
559         if (pos < startPosition) {
560           // detected static text or expression
561           //System.out.println("static text '" + staticText + "'");
562           //resultBuf.append(eval(text, pos, stmtInfo.startPosition-pos-1, tokens, level, params, bean));
563           eval(text, writer, pos, startPosition-1, tokens, level, params, bean);
564         }
565         
566         if (recordTokens) {
567           tokens.add(stmtInfo);
568         }
569       
570         // evaluating statement
571         evalStatement(stmtInfo, writer, text, level, params, bean);
572         
573         // setting new position
574         pos = stmtInfo.getContinuePosition();
575         continue;
576       }
577             
578       //if (pos <= lastPos) {
579       //        System.out.println("the remaining text to evaluate: '" + new String(text, pos, lastPos-pos+1) + "'");
580       //}
581       if ((pos >= lastPos) || ((expr = parseExpr(text, pos, lastPos, params, bean)) == null)) {
582         // OK, our job is done!
583         break;
584       }
585       
586       if (pos < expr.getTotalPosition()-2) {
587         // detected static text
588         String staticText = new String(text, pos, expr.getTotalPosition() - pos - 2);
589         //System.out.println("static text '" + staticText + "'");
590         if (recordTokens) {
591           tokens.add(staticText);
592         }
593         writeOutput(writer, staticText);
594       }
595       
596       //int exprStart = expr.getPosition();
597       //int exprLength = expr.getLength();
598       //System.out.println("expression: '" + text.substring(exprStart, exprStart + exprLength) + "'");
599       pos = expr.getTotalPosition() + expr.getTotalLength() + 1;
600 
601       // evaluating expression
602       if (recordTokens) {
603         tokens.add(expr);
604       }
605       writeOutput(writer, evalStringOperand(expr, params, bean));
606     }
607     
608     if (pos <= lastPos) {
609       // detected trailing static text
610       String staticText = new String(text, pos, lastPos - pos + 1);
611       //System.out.println("static text '" + staticText + "'");
612       if (recordTokens) {
613         tokens.add(staticText);
614       }
615       writeOutput(writer, staticText);
616     }
617     
618     //System.out.println("parsed expression: '" + expression + "'");
619   }
620   
621   /**
622    * Writes output to the given string writer.
623    * @param writer The writer to print to.
624    * @param content The content to print
625    * @exception IOException In case the output failed.
626    */
627   private void writeOutput(Writer writer, String content) throws IOException {
628     if (content == null) {
629       // some writers don't accept null value, serving them an empty string ... 
630       content = ""; 
631     }
632     writer.write(content);
633   }
634 
635   /**
636    * Scanning for statements:
637    * Statements do begin with '_' and end with the same character. 
638    * Valid statements are:
639    * - _if_{expr} ... [_else_ ... ] _endif_
640    * - _if_{expr} ... [_elseif_ ... ] _endif_
641    * - _if_{expr} ... [_elseif_ ... _else_] _endif_
642    * - _foreach_{<variablename> expr} ... _endfor_
643    * - _loop_{expr} .... _endloop_
644    * @param text The text to scan.
645    * @param firstPos The first position to look.
646    * @param lastPos The last position to look.
647    * @return StatementInfo The filled statement info instance.
648    * @exception StatementException In case the statement scanning has been failed.
649    */
650   private StatementInfo scanForStatements(char[] text, int firstPos, int lastPos) 
651     throws StatementException {
652 
653     StatementInfo stmtInfo = null;
654     AncestorDefinition[] ancestors = null;
655     int ancestorsIdx = -1;
656     char c = 0;
657     int tokenStartPos = -1;
658     int tokenLength = -1;
659     String stmt = null;
660   
661     scanLoop : {
662       for (int i = firstPos; i <= lastPos; i++) {
663         c = text[i];
664         if (c == '_') {
665           int oldTokenStartPos = tokenStartPos;
666           tokenStartPos = i;
667           ++i;
668           while (i <= lastPos) {
669             if (text[i] == '_') {
670               ++i;         
671               break;
672             }
673             ++i;         
674           }
675           tokenLength = i - tokenStartPos;
676           if (tokenLength < 3) {
677             // empty tokens doesn't hold statements, that's guaranteed!
678             if (i > lastPos) {
679               continue;
680             }
681             i -= 2;
682             tokenStartPos = oldTokenStartPos;
683             continue;
684           }
685           stmt = new String(text, tokenStartPos + 1, tokenLength - 2);
686           //System.out.println("stmt: '" + stmt + "'");
687           Integer statementType = StatementInfo.getStatementType(stmt);
688           if (statementType == null) {
689             // detected non statement text
690             i -= 2;
691             continue;
692           }
693 
694           if (StatementInfo.hasParameters(statementType)) {
695             // scanning parenthesises
696             if (text[i] != '(') {
697               throw new StatementException("Syntax error, requested format is " + 
698                                            StatementInfo.getStatementTypeName(statementType) + 
699                                            "(...)!");
700             }
701             int closingParenthesis = -1;
702             for (; i <= lastPos; i++) {
703               c = text[i];
704               if (c == ')') {
705                 closingParenthesis = i;
706                 //++i;
707                 break;
708               }
709             }
710             if (closingParenthesis < 0) {
711               throw new StatementException("Syntax error, requested format is " + 
712                                            StatementInfo.getStatementTypeName(statementType) + 
713                                            "(...)!");
714             }
715             ++i;
716           }
717 
718           // OK, detected statement, looking for statement end now!
719           if (stmtInfo == null) {
720             // finding ancestors
721             ancestors = StatementInfo.getStatementAncestors(statementType);
722             ancestorsIdx = 0;
723             if (ancestors == null) {
724               // scanning for argument list
725               int argsStart = -1;
726               for (; i <= lastPos; i++) {
727                 c = text[i];
728                 if (argsStart < 0) {
729                   if (!Character.isWhitespace(c)) {
730                     argsStart = i;
731                   }
732                   continue;
733                 } else {
734                   if (c == ')') {
735                     // detected end of argument list
736                     ++i;
737                     stmtInfo = new StatementInfo(statementType, tokenStartPos, i - tokenStartPos);
738                     break;
739                   }
740                 }
741               }
742               return stmtInfo;
743               /*
744               throw new StatementException("Detected closing statement " +
745                                            StatementInfo.getStatementTypeName(statementType) +
746                                            " without previous opeing statement.");
747               */
748             }
749             // detected statement start
750             stmtInfo = new StatementInfo(statementType, tokenStartPos, i - tokenStartPos);
751             --i;
752             continue;
753           } else {
754             // checking statement type for start type
755             if (StatementInfo.isStartToken(statementType)) {
756               // scanning nested statements
757               StatementInfo nestedStatement = scanForStatements(text, tokenStartPos, lastPos);
758               i = nestedStatement.getEndPosition() - 1;
759               stmt = null;
760               continue;
761             }
762           }
763 
764           // finding token in ancestor list
765           AncestorDefinition ancestor = ancestors[ancestorsIdx];
766           Integer ancestorType = ancestor.getType();
767           while ((ancestorType != statementType) && (ancestor.isOptional())) {
768             ++ancestorsIdx;
769             ancestor = ancestors[ancestorsIdx];
770             ancestorType = ancestor.getType();
771           }
772           if (ancestorType == statementType) {
773             stmtInfo.addToken(new StatementToken(statementType, tokenStartPos, i - tokenStartPos));
774             stmt = null;
775           } else {
776             throw new StatementException("Syntax error, detected token '" +                                       
777                                          StatementInfo.getStatementTypeName(statementType) +
778                                          "' requested token is " +
779                                          StatementInfo.getStatementTypeName(ancestorType) + "!");
780           }
781 
782           if (!ancestor.isMultiple()) {
783             ++ancestorsIdx;
784           }
785 
786           if (ancestorsIdx >= ancestors.length) {
787             // OK, seems we have completed the parsing of the statement
788             break;
789           }
790         }
791       }
792     }
793 
794     return stmtInfo;
795   }
796 
797   /**
798    * Evaluates the given statement.
799    * @param stmtInfo The statement info instance.
800    * @param writer The writer, to which to write the result.
801    * @param text The whole expression text.
802    * @param level The encapsulation level.
803    * @param params The parameters.
804    * @param bean The parameter bean.
805    * @exception IOException In case a I/O problem occured while writing the expression result.
806    * @exception ExpressionException If something went wrong.
807    */
808   private void evalStatement(StatementInfo stmtInfo, Writer writer, char[] text, int level, 
809                              Map params, Object bean)
810     throws IOException, ExpressionException {
811     
812     int currentTokenIdx = 0;
813     StatementToken currentToken = stmtInfo.getToken(currentTokenIdx);
814     Integer currentType = currentToken.getType();
815     if (currentType == StatementInfo.STMT_IF) {
816       // evaluating if condition
817       int exprPos = currentToken.getPosition() + 5;
818       int exprLength = currentToken.getLength() - 6;
819       // bringing expression into required form
820       String condExpr = new StringBuffer("${").append(text, exprPos, exprLength)
821         .append("}").toString();
822       //System.out.println("evaluating condition: '" + condExpr + "'");
823       boolean cond = evalBoolean(condExpr, params, bean);
824       while (!cond) {
825         // getting next condition
826         ++currentTokenIdx;
827         currentToken = stmtInfo.getToken(currentTokenIdx);
828         currentType = currentToken.getType();
829         if (currentType == StatementInfo.STMT_ELSEIF) {
830           // evaluating elseif condition
831           exprPos = currentToken.getPosition() + 9;
832           exprLength = currentToken.getLength() - 10;
833           condExpr = null;
834           if ((text[exprPos] == '$') &&
835               (text[exprPos + 1] == '{') &&
836               (text[exprPos + exprLength - 1] == '}')) {
837             // expression is already in required form
838             condExpr = new String(text, exprPos, exprLength);
839           } else {
840             // bringing expression into required form
841             condExpr = new StringBuffer("${").append(text, exprPos, exprLength)
842               .append("}").toString();
843           }
844           cond = evalBoolean(condExpr, params, bean);
845         } else if (currentType == StatementInfo.STMT_ELSE) {
846           // else case is always true
847           cond = true;
848         } else if (currentType == StatementInfo.STMT_ENDIF) {
849           // endif not valid for evaluation -> end result is false!
850           break;
851         }
852       }
853       
854       if (cond) {
855         StatementToken endToken = stmtInfo.getToken(currentTokenIdx + 1);
856         int endPos = endToken.getPosition(); 
857         // evaluating if case
858         eval(text, writer, currentToken.getPosition() + currentToken.getLength(), 
859              endPos - 1, null, level + 1, params, bean);
860       }
861       stmtInfo.setContinuePosition(stmtInfo.getEndPosition());
862     } else if (currentType == StatementInfo.STMT_FOREACH) {
863       int startPos = currentToken.getPosition() + 10;
864       //String foreachAttrs = new String(text, startPos, currentToken.getLength() - 11);
865       //System.out.println("evaluating attributes: '" + foreachAttrs + "'");
866       String loopVariable = null;
867       Object sequenceHolder = null;
868       String evenStyle = null;
869       String oddStyle = null;
870       char c = 0;
871       int endPos = currentToken.getPosition() + currentToken.getLength();
872       String sequenceHolderExpression = null;
873 
874       int tokenStart = -1;
875       for (int i = startPos; i < endPos - 1; i++) {
876         // looking for loop variable
877         c = text[i];
878         if (Character.isWhitespace(c)) {
879           if (tokenStart >= 0) {
880             String token = new String(text, tokenStart, i - tokenStart);
881             if (loopVariable == null) {
882               // OK, detected loop variable
883               loopVariable = token;
884             } else if (sequenceHolder == null) {
885               sequenceHolderExpression = token;
886               sequenceHolder = evalObject(sequenceHolderExpression, params, bean);
887             } else if (evenStyle == null) {
888               evenStyle = getConstantValue(token);
889             } else if (oddStyle == null) {
890               oddStyle = getConstantValue(token);
891             }
892             tokenStart = -1;
893           }
894           continue;
895         }
896         if (tokenStart == -1) {
897           tokenStart = i;
898         }
899       }
900 
901       if (tokenStart >= 0) {
902         String token = new String(text, tokenStart, endPos - tokenStart - 1);
903         if (loopVariable == null) {
904           // OK, detected loop variable
905           loopVariable = token;
906         } else if (sequenceHolder == null) {
907           sequenceHolderExpression = token;
908           sequenceHolder = evalObject(sequenceHolderExpression, params, bean);
909         } else if (evenStyle == null) {
910           evenStyle = getConstantValue(token);
911         } else if (oddStyle == null) {
912           oddStyle = getConstantValue(token);
913         }
914       }
915  
916       Iterator sequenceIter = null;
917       if (sequenceHolder instanceof Table) {
918         sequenceIter = ((Table) sequenceHolder).iterator();
919       } else if (sequenceHolder instanceof Collection) {
920         sequenceIter = ((Collection) sequenceHolder).iterator();
921       } else if (sequenceHolder instanceof Map) {
922         sequenceIter = ((Map) sequenceHolder).keySet().iterator();
923       } else if (sequenceHolder instanceof Object[]) {
924         List sequenceList = Arrays.asList((Object[]) sequenceHolder);
925         sequenceIter = sequenceList.iterator();
926       } else {
927         throw new ExpressionException("Unable to determine sequence from expression '" + 
928                                       sequenceHolderExpression + "'.");
929       }
930                                  
931       // setting loop index variable
932       SequenceIndex idx = new SequenceIndex();
933       String indexVariable = loopVariable + "Idx";
934       params.put(indexVariable, idx);
935       // setting style variable
936       String styleVariable = loopVariable + "Style";
937       String style = evenStyle;
938       boolean stylesDefined = false;
939       if ((evenStyle != null) || (oddStyle != null)) {
940         stylesDefined = true;
941         params.put(styleVariable, style);
942       }
943 
944       if (sequenceIter.hasNext()) {
945         // evaluating foreach body ...
946         List loopTokens = new ArrayList();
947         // setting loop variable
948         params.put(loopVariable, sequenceIter.next());              
949         // setting loop index variable
950         params.put(indexVariable, idx);
951         // getting end position of foreach body
952         StatementToken endToken = stmtInfo.getToken(currentTokenIdx + 1);
953         int endForeachBodyPos = endToken.getPosition();
954         eval(text, writer, endPos, endForeachBodyPos - 1, loopTokens, level + 1, params, bean);
955         int numLoopTokens = loopTokens.size();
956           
957         while (sequenceIter.hasNext()) {
958           // increments the index by 1.
959           idx.increment();
960           // setting loop variable
961           params.put(loopVariable, sequenceIter.next());
962           if (stylesDefined) {                    
963             // setting style variable
964             if ((idx.intValue() % 2) == 0) {
965               style = evenStyle;
966             } else {
967               style = oddStyle;
968             }
969             params.put(styleVariable, style);
970           }
971           // evaluating foreach body ...
972           for (int i = 0; i < numLoopTokens; i++) {
973             Object token = loopTokens.get(i);
974             if (token instanceof ExpressionTuple) {  
975               writeOutput(writer, evalStringOperand((ExpressionTuple) token, params, bean));
976             } else if (token instanceof StatementInfo) {
977               evalStatement((StatementInfo) token, writer, text, level + 1, params, bean);
978             } else {
979               writeOutput(writer, token.toString());
980             }
981           }
982         }
983       }
984       stmtInfo.setContinuePosition(stmtInfo.getEndPosition());
985     } else if (currentType == StatementInfo.STMT_PROCEDURE) {
986       int startPos = currentToken.getPosition() + 11;
987       StatementToken endToken = stmtInfo.getToken(currentTokenIdx + 1);
988       int endPos = endToken.getPosition();
989       String procName = null;
990       int nameStart = -1;
991       // determining procedure name
992       int idx = startPos;
993       for (; idx < endPos; idx++) {
994         char c = text[idx];
995         if (nameStart < 0) {
996           if (!Character.isWhitespace(c)) {
997             nameStart = idx;
998           }
999           continue;
1000        } else {
1001          if (Character.isWhitespace(c) || (c == '(')) {
1002            procName = new String(text, nameStart, idx - nameStart);
1003            break;
1004          }
1005        }
1006      }
1007      if (procName == null) {
1008        throw new ExpressionException("Unable to determine procedure name from statement '" + 
1009                                      new String(text, currentToken.getPosition(), endToken.getPosition() + endToken.getLength() - currentToken.getPosition()) + "'.");
1010      }
1011      // determining procedure argumnents
1012      int argStart = -1;
1013      List argList = new ArrayList();
1014      for (; idx < endPos; idx++) {
1015        char c = text[idx];
1016        if (c == ')') {
1017          // this is the end my friend!
1018          String argName = new String(text, argStart, idx - argStart);
1019          argList.add(argName);
1020          ++idx;
1021          break;
1022        }
1023          
1024        if (argStart < 0) {
1025          if (!Character.isWhitespace(c) && (c != '(') && (c != ',')) {
1026            argStart = idx;
1027          }
1028          continue;
1029        } else {
1030          if (Character.isWhitespace(c) || (c == ')') || (c == ',')) {
1031            String argName = new String(text, argStart, idx - argStart);
1032            argList.add(argName);
1033            argStart = -1;
1034          }
1035        }
1036      }
1037      
1038      // registering the procedure info
1039      ProcedureInfo procInfo = new ProcedureInfo(procName, argList, text, idx, endToken.getPosition() - idx);
1040      params.put("PROCEDURE_" + procName, procInfo);
1041      //System.out.println("procedureAttrs=" + procedureAttrs);
1042      stmtInfo.setContinuePosition(stmtInfo.getEndPosition());
1043    } else if (currentType == StatementInfo.STMT_CALLPROC) {
1044      int startPos = currentToken.getPosition() + 11;
1045
1046      // determining procedure argumnents
1047      String procName = null;
1048      int argStart = -1;
1049      List argList = new ArrayList();
1050      int idx = startPos;
1051      int endPos = currentToken.getPosition() + currentToken.getLength();
1052      for (; idx < endPos; idx++) {
1053        char c = text[idx];
1054        if (c == ')') {
1055          // this is the end my friend!
1056          if (argStart >= 0) {
1057            String arg = new String(text, argStart, idx - argStart);
1058            if (procName == null) {
1059              procName = arg;
1060            } else {
1061              if (arg.length() > 0) {
1062                // adding argument            
1063                argList.add(arg);
1064              }
1065            }
1066          }
1067          ++idx;
1068          break;
1069        }
1070          
1071        if (argStart < 0) {
1072          if (c == '$') {
1073            // scanning expression
1074            ++idx;
1075            c = text[idx];
1076            if (c != '{') {
1077              --idx;
1078              continue;
1079            }
1080            ++idx;
1081            int exprLevel = 1;
1082            argStart = idx - 2;
1083           scanLoop:
1084            for (; idx < endPos; idx++) {
1085              c = text[idx];
1086              switch (c) {
1087                case '}':
1088                  --exprLevel;
1089                  if (exprLevel <= 0) {
1090                    // OK, scanning finished
1091                    break scanLoop;
1092                  }
1093                  break;
1094                case '{':
1095                  ++exprLevel;
1096                  break;
1097              }
1098            }
1099            String arg = new String(text, argStart, idx - argStart + 1);
1100            // evaluating argument value            
1101            argList.add(evalObject(arg, params));
1102            argStart = -1;
1103            continue;
1104          }
1105          
1106          if ((c == '\'') || (c == '\"')) {
1107            // parsing constant
1108            ++idx;
1109            argStart = idx;
1110            for (; idx < endPos; idx++) {
1111              char nc = text[idx];
1112              if (nc == c) {
1113                String arg = new String(text, argStart, idx - argStart);
1114                // evaluating argument value            
1115                argList.add(arg);
1116                argStart = -1;
1117              }
1118            }
1119          }
1120
1121          if (!Character.isWhitespace(c) && (c != '(') && (c != ',')) {
1122            argStart = idx;
1123          }
1124          continue;
1125        } else {
1126          if (Character.isWhitespace(c) || (c == ')') || (c == ',')) {
1127            String arg = new String(text, argStart, idx - argStart);
1128            if (procName == null) {
1129              // detected procedure name
1130              procName = arg;
1131            } else {
1132              // evaluating argument value            
1133              argList.add(evalObject(arg, params));
1134            }
1135            argStart = -1;
1136          }
1137        }
1138      }
1139
1140      //System.out.println("Calling procedure: '" + procName + " with arguments: " + argList);
1141      // looking up procedure info
1142      ProcedureInfo procInfo = (ProcedureInfo) params.get("PROCEDURE_" + procName);
1143      if (procInfo == null) {
1144        throw new ExpressionException("No procedure registered with name '" + procName + "'.");
1145      }
1146      // calling procedure
1147      callProcedure(procInfo, argList, writer, level, params, bean);
1148      stmtInfo.setContinuePosition(stmtInfo.getEndPosition());
1149    }
1150  }
1151  
1152  /**
1153   * Calls the given procedure 
1154   * @param procInfo The procedure to call
1155   * @param arguments The arguments to use for the procedure call.
1156   * @param writer The writer, to which to write the result.
1157   * @param level The encapsulation level.
1158   * @param params The parameters.
1159   * @param bean The parameter bean.
1160   * @exception IOException In case an I/O problem occurred.
1161   * @exception ExpressionException If something went wrong.
1162   */
1163  private void callProcedure(ProcedureInfo procInfo, List arguments,
1164                              Writer writer, int level, Map params, Object bean)
1165    throws IOException, ExpressionException {
1166    
1167    // cloning parameter list
1168    params = cloneParams(params);
1169    
1170    // setting the procedure arguments
1171    int numArguments = arguments.size();
1172    List argumentNames = procInfo.getArguments();
1173    if (numArguments != argumentNames.size()) {
1174      throw new ExpressionException("Invalid number of argument list given for call of procedure " + procInfo + ".");
1175    }
1176    for (int i = 0; i < numArguments; i++) {
1177      params.put(argumentNames.get(i), arguments.get(i));
1178    }
1179    
1180    // evaluating the procedure code
1181    eval(procInfo.getCode(), writer, procInfo.getCodeOffset(), 
1182         procInfo.getCodeOffset() + procInfo.getCodeLength() - 1, null, level + 1, 
1183         params, bean);
1184  }
1185  
1186  /**
1187   * Clones the given parameter map.
1188   * @param params The parameter map to clone.
1189   * @return The cloned map.
1190   */
1191  private Map cloneParams(Map params) {
1192    return (Map) ((HashMap) params).clone();
1193  }
1194  
1195  /**
1196   * Parses constant from given text.
1197   * @param constant The constant text.
1198   * @return The extracted text.
1199   * @throws ExpressionException In case a syntax error was detected. 
1200   */
1201  public String getConstantValue(String constant) throws ExpressionException {
1202    int length = constant.length();
1203    if (constant.charAt(0) == '\'') {
1204      if (constant.charAt(length - 1) != '\'') {
1205        throw new ExpressionException("Syntax error in constant definition at token '" + 
1206                                      constant + "'.");
1207      }
1208      return constant.substring(1, length - 1);
1209    }
1210    if (constant.charAt(0) == '\"') {
1211      if (constant.charAt(length - 1) != '\"') {
1212        throw new ExpressionException("Syntax error in constant definition at token '" + 
1213                                      constant + "'.");
1214      }
1215      return constant.substring(1, length - 1);
1216    }
1217    return constant;
1218  }
1219  
1220  /**
1221   * Evaluates a text, which is likely to contain expressions as a Boolean.
1222   * @param text The text to evaluate.
1223   * @param params The parameters.
1224   * @return The computed boolean value.
1225   * @exception ExpressionException If the expression could not be successfuly resolved
1226   */
1227  public boolean evalBoolean(String text, Map params) throws ExpressionException {
1228    return evalBoolean(text, params, null);
1229  }
1230
1231  /**
1232   * Evaluates a text, which is likely to contain expressions as a Boolean.
1233   * @param text The text to evaluate.
1234   * @param params The parameters.
1235   * @param bean The bean used to retrieve parameter values, if the parameter value didn't exist in the params map.
1236   * @return The computed boolean value.
1237   * @exception ExpressionException If the expression could not be successfuly resolved
1238   */
1239  public boolean evalBoolean(String text, Map params, Object bean) throws ExpressionException {
1240    if (text == null) {
1241      return false;
1242    }
1243    Object result = evalObject(text, params, bean);
1244    if (result == null) {
1245      return false;
1246    }
1247    if (result instanceof Boolean) {
1248      // evaluating expression
1249      return ((Boolean) result).booleanValue();
1250    } else {
1251      // try to parse date
1252      return new Boolean(result.toString()).booleanValue();
1253    }
1254  }
1255
1256  /**
1257   * Evaluates a text, which is likely to contain expressions as a Date.
1258   * @param text The text to evaluate.
1259   * @param params The parameters.
1260   * @return The computed Date value.
1261   * @exception ExpressionException If the expression could not be successfuly resolved
1262   */
1263  public Date evalDate(String text, Map params) throws ExpressionException {
1264    return evalDate(text, params, null);
1265  }
1266
1267  /**
1268   * Evaluates a text, which is likely to contain expressions as a Date.
1269   * @param text The text to evaluate.
1270   * @param params The parameters.
1271   * @param bean The bean used to retrieve parameter values, if the parameter value didn't exist in the params map.
1272   * @return The computed Date.
1273   * @exception ExpressionException If the expression could not be successfuly resolved
1274   */
1275  public Date evalDate(String text, Map params, Object bean) throws ExpressionException {
1276    if (text == null) {
1277      return null;
1278    }
1279    Object result = evalObject(text, params, bean);
1280    if (result instanceof Date) {
1281      // evaluating expression
1282      return (Date) result;
1283    } else {
1284      try {
1285        // trying to parse date
1286        return getDateFormat().parse(text);
1287      } catch (Exception ex) {
1288        // unable to parse date.
1289        throw new ExpressionException("Unable to evaluate code '" + text +
1290                                      "' as a date value.");
1291