1 /*
2 * JasperReports - Free Java Reporting Library.
3 * Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved.
4 * http://www.jaspersoft.com
5 *
6 * Unless you have purchased a commercial license agreement from Jaspersoft,
7 * the following license terms apply:
8 *
9 * This program is part of JasperReports.
10 *
11 * JasperReports is free software: you can redistribute it and/or modify
12 * it under the terms of the GNU Lesser General Public License as published by
13 * the Free Software Foundation, either version 3 of the License, or
14 * (at your option) any later version.
15 *
16 * JasperReports is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU Lesser General Public License for more details.
20 *
21 * You should have received a copy of the GNU Lesser General Public License
22 * along with JasperReports. If not, see <http://www.gnu.org/licenses/>.
23 */
24 package net.sf.jasperreports.engine.fill;
25
26 import java.lang.reflect.InvocationTargetException;
27 import java.text.MessageFormat;
28 import java.util.HashMap;
29 import java.util.Locale;
30 import java.util.Map;
31 import java.util.MissingResourceException;
32 import java.util.ResourceBundle;
33
34 import net.sf.jasperreports.annotations.properties.Property;
35 import net.sf.jasperreports.annotations.properties.PropertyScope;
36 import net.sf.jasperreports.compilers.DirectExpressionEvaluator;
37 import net.sf.jasperreports.compilers.DirectExpressionEvaluators;
38 import net.sf.jasperreports.engine.JRException;
39 import net.sf.jasperreports.engine.JRExpression;
40 import net.sf.jasperreports.engine.JRParameter;
41 import net.sf.jasperreports.engine.JRPropertiesUtil;
42 import net.sf.jasperreports.engine.JRRuntimeException;
43 import net.sf.jasperreports.engine.type.WhenResourceMissingTypeEnum;
44 import net.sf.jasperreports.functions.FunctionSupport;
45 import net.sf.jasperreports.properties.PropertyConstants;
46
47 /**
48 * Base class for the dynamically generated expression evaluator classes.
49 * This class also provides some built-in functions that will be described next.
50 * <h3>Built-in Functions</h3>
51 * Report expressions can perform method calls on various objects that are available during
52 * report filling, such as parameters, fields, or variable values, but can also call methods on
53 * a special object that is already available as the <code>this</code> reference. This is the calculator
54 * object. It has public utility methods that are ready to use inside report expressions.
55 * <p>
56 * Currently, there are only a few utility methods of the calculator object available as built-in
57 * functions inside report expressions. These are the following:</p>
58 * <ul>
59 * <li><code>msg</code> - this function offers a convenient way to format messages based on the current
60 * report locale, just as you would normally do when using a
61 * <code>java.text.MessageFormat</code> instance. Furthermore, several signatures for this
62 * function take up to three message parameters in order to make the formatting
63 * functionality easier to use.</li>
64 * <li><code>str</code> - this function is the equivalent of the <code>$R{}</code> syntax. It gives access
65 * to locale specific resources from the associated resource bundle.</li>
66 * </ul>
67 *
68 *
69 * @author Lucian Chirita (lucianc@users.sourceforge.net)
70 */
71 public abstract class JREvaluator implements DatasetExpressionEvaluator
72 {
73 public static final String EXCEPTION_MESSAGE_KEY_RESOURCE_NOT_FOUND = "fill.evaluator.resource.not.found";
74
75 /**
76 * The expression evaluation engine in JasperReports has always ignored java.lang.NullPointerException
77 * exceptions raised during expression evaluation. An expression raising NullPointerException is evaluated to null.
78 * However, in certain cases, users want to be able to track down the source of their NPE and this configuration
79 * property can be set to instruct the expression evaluation engine to treat NPEs just the way all other expression
80 * exceptions are treated.
81 * The default value of this configuration property is true, meaning NPEs are ignored.
82 * The property can be set globally, at report or at dataset level.
83 */
84 @Property(
85 category = PropertyConstants.CATEGORY_FILL,
86 defaultValue = PropertyConstants.BOOLEAN_TRUE,
87 scopes = {PropertyScope.CONTEXT, PropertyScope.DATASET},
88 sinceVersion = PropertyConstants.VERSION_6_1_1,
89 valueType = Boolean.class
90 )
91 public static final String PROPERTY_IGNORE_NPE = JRPropertiesUtil.PROPERTY_PREFIX + "evaluator.ignore.npe";
92
93 /**
94 * The resource bundle parameter.
95 */
96 private JRFillParameter resourceBundle;
97
98 /**
99 * The resource missing type.
100 */
101 private WhenResourceMissingTypeEnum whenResourceMissingType;
102
103 /**
104 * The report Locale used when parsing the bundle message.
105 */
106 private JRFillParameter locale;
107
108 /**
109 * The function objects.
110 */
111 private Map<String, FunctionSupport> functions;
112
113 /**
114 *
115 */
116 private FillFunctionContext functionContext;
117
118 /**
119 *
120 */
121 protected boolean ignoreNPE = true;
122
123 private DirectExpressionEvaluators directExpressionEvaluators;
124
125 /**
126 * Default constructor.
127 */
128 protected JREvaluator()
129 {
130 }
131
132 public void setDirectExpressionEvaluators(DirectExpressionEvaluators directExpressionEvaluators)
133 {
134 this.directExpressionEvaluators = directExpressionEvaluators;
135 }
136
137 private DirectExpressionEvaluator directEvaluator(JRExpression expression)
138 {
139 return directExpressionEvaluators == null ? null
140 : directExpressionEvaluators.getEvaluator(expression);
141 }
142
143 /**
144 * Initializes the evaluator by setting the parameter, field and variable objects.
145 *
146 * @param parametersMap the parameters indexed by name
147 * @param fieldsMap the fields indexed by name
148 * @param variablesMap the variables indexed by name
149 * @param resourceMissingType the resource missing type
150 * @throws JRException
151 */
152 @Override
153 public void init(
154 Map<String, JRFillParameter> parametersMap,
155 Map<String, JRFillField> fieldsMap,
156 Map<String, JRFillVariable> variablesMap,
157 WhenResourceMissingTypeEnum resourceMissingType,
158 boolean ignoreNPE
159 ) throws JRException
160 {
161 whenResourceMissingType = resourceMissingType;
162 this.ignoreNPE = ignoreNPE;
163 resourceBundle = parametersMap.get(JRParameter.REPORT_RESOURCE_BUNDLE);
164 locale = parametersMap.get(JRParameter.REPORT_LOCALE);
165
166 functions = new HashMap<String, FunctionSupport>();
167 functionContext = new FillFunctionContext(parametersMap);
168
169 customizedInit(parametersMap, fieldsMap, variablesMap);
170
171 if (directExpressionEvaluators != null)
172 {
173 directExpressionEvaluators.init(this, parametersMap, fieldsMap, variablesMap);
174 }
175 }
176
177
178 /**
179 *
180 */
181 @SuppressWarnings("unchecked")
182 public <T extends FunctionSupport> T getFunctionSupport(Class<T> clazz)
183 {
184 String classId = clazz.getName();
185 if (!functions.containsKey(classId))
186 {
187 try
188 {
189 FunctionSupport functionSupport = clazz.getDeclaredConstructor().newInstance();
190 functionSupport.init(functionContext);
191 functions.put(classId, functionSupport);
192 }
193 catch (IllegalAccessException | InstantiationException
194 | NoSuchMethodException | InvocationTargetException e)
195 {
196 throw new JRRuntimeException(e);
197 }
198 }
199 return (T)functions.get(classId);
200 }
201
202 /**
203 * Constructs a message using a pattern with one parameter.
204 *
205 * @param pattern the message pattern
206 * @param arg0 the message parameter
207 * @return the constructed message
208 * @see MessageFormat#format(java.lang.Object[],java.lang.StringBuffer, java.text.FieldPosition)
209 */
210 public String msg(String pattern, Object arg0)
211 {
212 return getMessageFormat(pattern).format(new Object[] { arg0 }, new StringBuffer(), null).toString();
213 }
214
215 /**
216 * Constructs a message using a pattern with two parameters.
217 *
218 * @param pattern the message pattern
219 * @param arg0 the first message parameter
220 * @param arg1 the second message parameter
221 * @return the constructed message
222 * @see MessageFormat#format(java.lang.Object[],java.lang.StringBuffer, java.text.FieldPosition)
223 */
224 public String msg(String pattern, Object arg0, Object arg1)
225 {
226 return getMessageFormat(pattern).format(new Object[] { arg0, arg1 }, new StringBuffer(), null).toString();
227 }
228
229
230 /**
231 * Constructs a message using a pattern with three parameters.
232 *
233 * @param pattern the message pattern
234 * @param arg0 the first message parameter
235 * @param arg1 the second message parameter
236 * @param arg2 the third parameter
237 * @return the constructed message
238 * @see MessageFormat#format(java.lang.Object[],java.lang.StringBuffer, java.text.FieldPosition)
239 */
240 public String msg(String pattern, Object arg0, Object arg1, Object arg2)
241 {
242 return getMessageFormat(pattern).format(new Object[] { arg0, arg1, arg2 }, new StringBuffer(), null).toString();
243 }
244
245 /**
246 * Constructs a message using a pattern with any number of parameters.
247 *
248 * @param pattern the message pattern
249 * @param args the message parameters
250 * @return the constructed message
251 * @see MessageFormat#format(java.lang.Object[],java.lang.StringBuffer, java.text.FieldPosition)
252 */
253 public String msg(String pattern, Object... args)
254 {
255 return getMessageFormat(pattern).format(args, new StringBuffer(), null).toString();
256 }
257
258 /**
259 * Returns a string for a given key from the resource bundle associated with the evaluator.
260 *
261 * @param key the key
262 * @return the string for the given key
263 * @see ResourceBundle#getString(java.lang.String)
264 */
265 public String str(String key)
266 {
267 String str = null;
268
269 try
270 {
271 str = ((ResourceBundle) resourceBundle.getValue()).getString(key);
272 }
273 catch (NullPointerException e) //NOPMD
274 {
275 if (ignoreNPE)
276 {
277 str = handleMissingResource(key, e);
278 }
279 else
280 {
281 throw e;
282 }
283 }
284 catch (MissingResourceException e)
285 {
286 str = handleMissingResource(key, e);
287 }
288
289 return str;
290 }
291
292 protected Object handleEvaluationException(JRExpression expression, Throwable e) throws JRExpressionEvalException
293 {
294 throw new JRExpressionEvalException(expression, e);
295 }
296
297 @Override
298 public Object evaluate(JRExpression expression) throws JRExpressionEvalException
299 {
300 Object value = null;
301
302 if (expression != null)
303 {
304 DirectExpressionEvaluator directEvaluator = directEvaluator(expression);
305 try
306 {
307 if (directEvaluator != null)
308 {
309 value = directEvaluator.evaluate();
310 }
311 else
312 {
313 value = evaluate(expression.getId());
314 }
315 }
316 catch (NullPointerException e) //NOPMD
317 {
318 if (!ignoreNPE) throw new JRExpressionEvalException(expression, e);
319 }
320 catch (OutOfMemoryError e)
321 {
322 throw e;
323 }
324 // we have to catch Throwable because there is no way we could modify the signature
325 // of the evaluate method, without breaking backward compatibility of compiled report templates
326 catch (Throwable e) //NOPMD
327 {
328 value = handleEvaluationException(expression, e);
329 }
330 }
331
332 return value;
333 }
334
335
336 @Override
337 public Object evaluateOld(JRExpression expression) throws JRExpressionEvalException
338 {
339 Object value = null;
340
341 if (expression != null)
342 {
343 DirectExpressionEvaluator directEvaluator = directEvaluator(expression);
344 try
345 {
346 if (directEvaluator != null)
347 {
348 value = directEvaluator.evaluateOld();
349 }
350 else
351 {
352 value = evaluateOld(expression.getId());
353 }
354 }
355 catch (NullPointerException e) //NOPMD
356 {
357 if (!ignoreNPE) throw new JRExpressionEvalException(expression, e);
358 }
359 catch (OutOfMemoryError e)
360 {
361 throw e;
362 }
363 // we have to catch Throwable because there is no way we could modify the signature
364 // of the evaluate method, without breaking backward compatibility of compiled report templates
365 catch (Throwable e) //NOPMD
366 {
367 value = handleEvaluationException(expression, e);
368 }
369 }
370
371 return value;
372 }
373
374
375 @Override
376 public Object evaluateEstimated(JRExpression expression) throws JRExpressionEvalException
377 {
378 Object value = null;
379
380 if (expression != null)
381 {
382 DirectExpressionEvaluator directEvaluator = directEvaluator(expression);
383 try
384 {
385 if (directEvaluator != null)
386 {
387 value = directEvaluator.evaluateEstimated();
388 }
389 else
390 {
391 value = evaluateEstimated(expression.getId());
392 }
393 }
394 catch (NullPointerException e) //NOPMD
395 {
396 if (!ignoreNPE) throw new JRExpressionEvalException(expression, e);
397 }
398 catch (OutOfMemoryError e)
399 {
400 throw e;
401 }
402 // we have to catch Throwable because there is no way we could modify the signature
403 // of the evaluate method, without breaking backward compatibility of compiled report templates
404 catch (Throwable e) //NOPMD
405 {
406 value = handleEvaluationException(expression, e);
407 }
408 }
409
410 return value;
411 }
412
413
414 /**
415 * Handles the case when a resource is missing.
416 *
417 * @param key
418 * the resource key
419 * @param e
420 * the exception
421 * @return the value to use for the resource
422 * @throws JRRuntimeException
423 * when the resource missing handling type is Error
424 */
425 protected String handleMissingResource(String key, Exception e) throws JRRuntimeException
426 {
427 String str;
428 switch (whenResourceMissingType)
429 {
430 case EMPTY:
431 {
432 str = "";
433 break;
434 }
435 case KEY:
436 {
437 str = key;
438 break;
439 }
440 case ERROR:
441 {
442 throw
443 new JRRuntimeException(
444 EXCEPTION_MESSAGE_KEY_RESOURCE_NOT_FOUND,
445 new Object[]{key},
446 e);
447 }
448 case NULL:
449 default:
450 {
451 str = null;
452 break;
453 }
454 }
455
456 return str;
457 }
458
459
460 /**
461 * Initializes the parameters, fields and variables of the evaluator.
462 *
463 * @param parametersMap the parameters indexed by name
464 * @param fieldsMap the fields indexed by name
465 * @param variablesMap the variables indexed by name
466 * @throws JRException
467 */
468 protected abstract void customizedInit(
469 Map<String, JRFillParameter> parametersMap,
470 Map<String, JRFillField> fieldsMap,
471 Map<String, JRFillVariable> variablesMap
472 ) throws JRException;
473
474
475 /**
476 * Evaluates an expression using current fields and variables values.
477 *
478 * @param id the expression id
479 * @return the result of the evaluation
480 * @throws Throwable
481 * @see net.sf.jasperreports.engine.JRExpression#EVALUATION_DEFAULT
482 * @see JRFillVariable#getValue()
483 * @see JRFillField#getValue()
484 */
485 protected abstract Object evaluate(int id) throws Throwable; //NOSONAR
486
487
488 /**
489 * Evaluates an expression using old fields and variables values.
490 *
491 * @param id the expression id
492 * @return the result of the evaluation
493 * @throws Throwable
494 * @see net.sf.jasperreports.engine.JRExpression#EVALUATION_OLD
495 * @see JRFillVariable#getOldValue()
496 * @see JRFillField#getOldValue()
497 */
498 protected abstract Object evaluateOld(int id) throws Throwable; //NOSONAR
499
500
501 /**
502 * Evaluates an expression using estimated variables values.
503 *
504 * @param id the expression id
505 * @return the result of the evaluation
506 * @throws Throwable
507 * @see net.sf.jasperreports.engine.JRExpression#EVALUATION_ESTIMATED
508 * @see JRFillVariable#getEstimatedValue()
509 */
510 protected abstract Object evaluateEstimated(int id) throws Throwable; //NOSONAR
511
512
513 /**
514 *
515 */
516 private MessageFormat getMessageFormat(String pattern)
517 {
518 MessageFormat messageFormat = new MessageFormat("");
519 messageFormat.setLocale((Locale)locale.getValue());
520 messageFormat.applyPattern(pattern);
521 return messageFormat;
522 }
523
524 }
525