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 * <b>year</b>: Same as getYear<br>
95 * <b>month</b>: Same as getMonth<br>
96 * <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