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