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.sql.Connection;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.HashMap;
30 import java.util.HashSet;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.Locale;
34 import java.util.Map;
35 import java.util.ResourceBundle;
36 import java.util.Set;
37 import java.util.TimeZone;
38 import java.util.UUID;
39
40 import org.apache.commons.logging.Log;
41 import org.apache.commons.logging.LogFactory;
42
43 import net.sf.jasperreports.data.cache.CachedDataset;
44 import net.sf.jasperreports.data.cache.DataCacheHandler;
45 import net.sf.jasperreports.data.cache.DataRecorder;
46 import net.sf.jasperreports.data.cache.DataSnapshot;
47 import net.sf.jasperreports.data.cache.DataSnapshotException;
48 import net.sf.jasperreports.data.cache.DatasetRecorder;
49 import net.sf.jasperreports.engine.DatasetFilter;
50 import net.sf.jasperreports.engine.DatasetPropertyExpression;
51 import net.sf.jasperreports.engine.DefaultJasperReportsContext;
52 import net.sf.jasperreports.engine.EvaluationType;
53 import net.sf.jasperreports.engine.JRAbstractScriptlet;
54 import net.sf.jasperreports.engine.JRDataSource;
55 import net.sf.jasperreports.engine.JRDataset;
56 import net.sf.jasperreports.engine.JRDefaultScriptlet;
57 import net.sf.jasperreports.engine.JRException;
58 import net.sf.jasperreports.engine.JRExpression;
59 import net.sf.jasperreports.engine.JRField;
60 import net.sf.jasperreports.engine.JRGroup;
61 import net.sf.jasperreports.engine.JRParameter;
62 import net.sf.jasperreports.engine.JRPropertiesHolder;
63 import net.sf.jasperreports.engine.JRPropertiesMap;
64 import net.sf.jasperreports.engine.JRPropertiesUtil;
65 import net.sf.jasperreports.engine.JRQuery;
66 import net.sf.jasperreports.engine.JRRuntimeException;
67 import net.sf.jasperreports.engine.JRScriptlet;
68 import net.sf.jasperreports.engine.JRSortField;
69 import net.sf.jasperreports.engine.JRVariable;
70 import net.sf.jasperreports.engine.JasperCompileManager;
71 import net.sf.jasperreports.engine.JasperReport;
72 import net.sf.jasperreports.engine.JasperReportsContext;
73 import net.sf.jasperreports.engine.ParameterContributor;
74 import net.sf.jasperreports.engine.ParameterContributorContext;
75 import net.sf.jasperreports.engine.ParameterContributorFactory;
76 import net.sf.jasperreports.engine.data.IndexedDataSource;
77 import net.sf.jasperreports.engine.design.JRDesignVariable;
78 import net.sf.jasperreports.engine.query.JRQueryExecuter;
79 import net.sf.jasperreports.engine.query.QueryExecuterFactory;
80 import net.sf.jasperreports.engine.query.SimpleQueryExecutionContext;
81 import net.sf.jasperreports.engine.scriptlets.ScriptletFactory;
82 import net.sf.jasperreports.engine.scriptlets.ScriptletFactoryContext;
83 import net.sf.jasperreports.engine.type.CalculationEnum;
84 import net.sf.jasperreports.engine.type.IncrementTypeEnum;
85 import net.sf.jasperreports.engine.type.ParameterEvaluationTimeEnum;
86 import net.sf.jasperreports.engine.type.PropertyEvaluationTimeEnum;
87 import net.sf.jasperreports.engine.type.ResetTypeEnum;
88 import net.sf.jasperreports.engine.type.WhenResourceMissingTypeEnum;
89 import net.sf.jasperreports.engine.util.DigestUtils;
90 import net.sf.jasperreports.engine.util.JRDataUtils;
91 import net.sf.jasperreports.engine.util.JRQueryExecuterUtils;
92 import net.sf.jasperreports.engine.util.JRResourcesUtil;
93 import net.sf.jasperreports.engine.util.MD5Digest;
94 import net.sf.jasperreports.repo.RepositoryContext;
95 import net.sf.jasperreports.repo.SimpleRepositoryContext;
96
97 /**
98  * @author Lucian Chirita (lucianc@users.sourceforge.net)
99  */

100 public class JRFillDataset implements JRDataset, DatasetFillContext
101 {
102     
103     private static final Log log = LogFactory.getLog(JRFillDataset.class);
104     
105     public static final String EXCEPTION_MESSAGE_KEY_NO_SUCH_FIELD = "fill.dataset.no.such.field";
106     public static final String EXCEPTION_MESSAGE_KEY_NO_SUCH_PARAMETER = "fill.dataset.no.such.parameter";
107     public static final String EXCEPTION_MESSAGE_KEY_NO_SUCH_SNAPSHOT_DATA = "fill.dataset.no.such.snapshot.data";
108     public static final String EXCEPTION_MESSAGE_KEY_NO_SUCH_SNAPSHOT_PARAMETER = "fill.dataset.no.such.snapshot.parameter";
109     public static final String EXCEPTION_MESSAGE_KEY_NO_SUCH_VARIABLE = "fill.dataset.no.such.variable";
110     
111     /**
112      * The filler that created this object.
113      */

114     private final BaseReportFiller filler;
115     
116     /**
117      *
118      */

119     private JasperReportsContext jasperReportsContext;
120     
121     private JRPropertiesUtil propertiesUtil;
122     
123     /**
124      * The template dataset.
125      */

126     private final JRDataset parent;
127     
128     /**
129      * Whether this is the main dataset of the report.
130      */

131     private final boolean isMain;
132     
133     /**
134      * The dataset query.
135      */

136     protected JRQuery query;
137     
138     private boolean useDatasourceParamValue;
139     private boolean useConnectionParamValue;
140     
141     /**
142      * The dataset parameters.
143      */

144     protected JRFillParameter[] parameters;
145
146     /**
147      * The dataset parameters indexed by name.
148      */

149     protected Map<String,JRFillParameter> parametersMap;
150
151     /**
152      * The dataset fields.
153      */

154     protected JRFillField[] fields;
155     
156     /**
157      * The dataset fields indexed by name.
158      */

159     protected Map<String,JRFillField> fieldsMap;
160     
161     /**
162      * The dataset variables.
163      */

164     protected JRFillVariable[] variables;
165     
166     /**
167      * The dataset variables indexed by name.
168      */

169     protected Map<String,JRFillVariable> variablesMap;
170     
171     /**
172      * Set of {@link VariableCalculationReq VariableCalculationReq} objects.
173      */

174     protected Set<VariableCalculationReq> variableCalculationReqs;
175
176     /**
177      * The element datasets.
178      */

179     protected JRFillElementDataset[] elementDatasets;
180     
181     /**
182      * Used to save the original element datasets when
183      * {@link #filterElementDatasets(JRFillElementDataset) filterElementDatasets} is called.
184      */

185     protected JRFillElementDataset[] origElementDatasets;
186
187     /**
188      * The dataset groups.
189      */

190     protected JRFillGroup[] groups;
191
192     /**
193      * The resource bundle base name.
194      */

195     protected String resourceBundleBaseName;
196     
197     /**
198      * The resource missing handle type.
199      */

200     protected WhenResourceMissingTypeEnum whenResourceMissingType;
201     
202     /**
203      * The scriptlet class name.
204      */

205     protected String scriptletClassName;
206
207     /**
208      * The data source. 
209      */

210     protected JRDataSource dataSource;
211     
212     /**
213      * The {@link Locale Locale} to be used by the dataset.
214      */

215     protected Locale locale;
216     
217     /**
218      * The loaded resource bundle.
219      */

220     protected ResourceBundle resourceBundle;
221
222     /**
223      * The {@link TimeZone TimeZone} to be used by the dataset.
224      */

225     protected TimeZone timeZone;
226     
227     /**
228      * The cursor used when iterating the data source.
229      */

230     protected int reportCount;
231
232     /**
233      * The calculator used by the dataset.
234      */

235     protected JRCalculator calculator;
236
237     /**
238      * The scriptlets used by the dataset.
239      */

240     protected List<JRAbstractScriptlet> scriptlets;
241
242     protected List<DatasetPropertyExpression> propertyExpressions;
243     protected JRPropertiesMap staticProperties;
244     protected JRPropertiesMap mergedProperties;
245     
246     /**
247      *
248      */

249     protected JRAbstractScriptlet delegateScriptlet = new JRFillDatasetScriptlet();
250
251     /**
252      * The value of the {@link JRParameter#REPORT_MAX_COUNT max count} parameter.
253      */

254     protected Integer reportMaxCount;
255
256     private JRQueryExecuter queryExecuter;
257     private List<ParameterContributor> parameterContributors;
258     
259     protected DatasetFilter filter;
260
261     protected FillDatasetPosition fillPosition;
262     protected DatasetRecorder dataRecorder;
263     
264     private Map<Integer, CacheRecordIndexCallback> cacheRecordIndexCallbacks;
265     private boolean cacheSkipped;
266     private CachedDataset cachedDataset;
267     private boolean sortedDataSource;
268     
269     private boolean ended;
270     private int cacheRecordCount;
271     private int previousCacheRecordIndex;
272     private int currentCacheRecordIndex;
273     
274     /**
275      * Creates a fill dataset object.
276      * @param filler the filler
277      * @param dataset the template dataset
278      * @param factory the fill object factory
279      */

280     public JRFillDataset(BaseReportFiller filler, JRDataset dataset, JRFillObjectFactory factory)
281     {
282         factory.put(dataset, this);
283         
284         this.filler = filler;
285         this.propertiesUtil = filler == null ? JRPropertiesUtil.getInstance(DefaultJasperReportsContext.getInstance()) 
286                 : filler.getPropertiesUtil();
287         this.parent = dataset;
288         this.isMain = dataset.isMainDataset();
289         
290         scriptletClassName = dataset.getScriptletClass();
291         resourceBundleBaseName = dataset.getResourceBundle();
292         whenResourceMissingType = dataset.getWhenResourceMissingTypeValue();
293         
294         query = dataset.getQuery();
295         
296         setParameters(dataset, factory);
297
298         setFields(dataset, factory);
299         
300         setVariables(dataset, factory);
301         
302         setGroups(dataset, factory);
303         
304         staticProperties = dataset.hasProperties() ? dataset.getPropertiesMap().cloneProperties() : null;
305         mergedProperties = staticProperties;
306         
307         DatasetPropertyExpression[] datasetPropertyExpressions = dataset.getPropertyExpressions();
308         propertyExpressions = 
309             datasetPropertyExpressions == null 
310             ? new ArrayList<DatasetPropertyExpression>(0)
311             : new ArrayList<DatasetPropertyExpression>(Arrays.asList(datasetPropertyExpressions));
312     }
313     
314     
315     public BaseReportFiller getFiller()
316     {
317         return filler;
318     }
319
320
321     private void setParameters(JRDataset dataset, JRFillObjectFactory factory)
322     {
323         JRParameter[] jrParameters = dataset.getParameters();
324         if (jrParameters != null && jrParameters.length > 0)
325         {
326             parameters = new JRFillParameter[jrParameters.length];
327             parametersMap = new HashMap<String,JRFillParameter>();
328             for (int i = 0; i < parameters.length; i++)
329             {
330                 parameters[i] = factory.getParameter(jrParameters[i]);
331                 parametersMap.put(parameters[i].getName(), parameters[i]);
332             }
333         }
334     }
335
336
337     private void setGroups(JRDataset dataset, JRFillObjectFactory factory)
338     {
339         JRGroup[] jrGroups = dataset.getGroups();
340         if (jrGroups != null && jrGroups.length > 0)
341         {
342             groups = new JRFillGroup[jrGroups.length];
343             for (int i = 0; i < groups.length; i++)
344             {
345                 groups[i] = factory.getGroup(jrGroups[i]);
346             }
347         }
348     }
349
350
351     private void setVariables(JRDataset dataset, JRFillObjectFactory factory)
352     {
353         JRVariable[] jrVariables = dataset.getVariables();
354         if (jrVariables != null && jrVariables.length > 0)
355         {
356             List<JRFillVariable> variableList = new ArrayList<JRFillVariable>(jrVariables.length * 3);
357
358             variablesMap = new HashMap<String,JRFillVariable>();
359             for (int i = 0; i < jrVariables.length; i++)
360             {
361                 addVariable(jrVariables[i], variableList, factory);
362             }
363
364             setVariables(variableList);
365         }
366     }
367     
368     
369     private JRFillVariable addVariable(JRVariable parentVariable, List<JRFillVariable> variableList, JRFillObjectFactory factory)
370     {
371         JRFillVariable variable = factory.getVariable(parentVariable);
372
373         CalculationEnum calculation = variable.getCalculationValue();
374         switch (calculation)
375         {
376             case AVERAGE:
377             case VARIANCE:
378             {
379                 JRVariable countVar = createHelperVariable(parentVariable, "_COUNT", CalculationEnum.COUNT);
380                 JRFillVariable fillCountVar = addVariable(countVar, variableList, factory);
381                 variable.setHelperVariable(fillCountVar, JRCalculable.HELPER_COUNT);
382
383                 JRVariable sumVar = createHelperVariable(parentVariable, "_SUM", CalculationEnum.SUM);
384                 JRFillVariable fillSumVar = addVariable(sumVar, variableList, factory);
385                 variable.setHelperVariable(fillSumVar, JRCalculable.HELPER_SUM);
386
387                 break;
388             }
389             case STANDARD_DEVIATION:
390             {
391                 JRVariable varianceVar = createHelperVariable(parentVariable, "_VARIANCE", CalculationEnum.VARIANCE);
392                 JRFillVariable fillVarianceVar = addVariable(varianceVar, variableList, factory);
393                 variable.setHelperVariable(fillVarianceVar, JRCalculable.HELPER_VARIANCE);
394
395                 break;
396             }
397             case DISTINCT_COUNT:
398             {
399                 JRVariable countVar = createDistinctCountHelperVariable(parentVariable);
400                 JRFillVariable fillCountVar = addVariable(countVar, variableList, factory);
401                 variable.setHelperVariable(fillCountVar, JRCalculable.HELPER_COUNT);
402
403                 break;
404             }
405         }
406
407         variableList.add(variable);
408         return variable;
409     }
410
411     private JRVariable createHelperVariable(JRVariable variable, String nameSuffix, CalculationEnum calculation)
412     {
413         JRDesignVariable helper = new JRDesignVariable();
414         helper.setName(variable.getName() + nameSuffix);
415         helper.setValueClassName(variable.getValueClassName());
416         helper.setIncrementerFactoryClassName(variable.getIncrementerFactoryClassName());
417         helper.setResetType(variable.getResetTypeValue());
418         helper.setResetGroup(variable.getResetGroup());
419         helper.setIncrementType(variable.getIncrementTypeValue());
420         helper.setIncrementGroup(variable.getIncrementGroup());
421         helper.setCalculation(calculation);
422         helper.setSystemDefined(true);
423         helper.setExpression(variable.getExpression());
424
425         return helper;
426     }
427
428     private JRVariable createDistinctCountHelperVariable(JRVariable variable)
429     {
430         JRDesignVariable helper = new JRDesignVariable();
431         helper.setName(variable.getName() + "_DISTINCT_COUNT");
432         helper.setValueClassName(variable.getValueClassName());
433         helper.setIncrementerFactoryClassName(JRDistinctCountIncrementerFactory.class.getName());
434         helper.setResetType(ResetTypeEnum.REPORT);
435
436         if (variable.getIncrementTypeValue() != IncrementTypeEnum.NONE)
437         {
438             helper.setResetType(ResetTypeEnum.getByValue(variable.getIncrementTypeValue().getValue()));
439         }
440         helper.setResetGroup(variable.getIncrementGroup());
441         helper.setCalculation(CalculationEnum.NOTHING);
442         helper.setSystemDefined(true);
443         helper.setExpression(variable.getExpression());
444         
445         return helper;
446     }
447
448     private void setVariables(List<JRFillVariable> variableList)
449     {
450         variables = new JRFillVariable[variableList.size()];
451         variables = variableList.toArray(variables);
452
453         for (int i = 0; i < variables.length; i++)
454         {
455             variablesMap.put(variables[i].getName(), variables[i]);
456         }
457     }
458
459
460     private void setFields(JRDataset dataset, JRFillObjectFactory factory)
461     {
462         JRField[] jrFields = dataset.getFields();
463         if (jrFields != null && jrFields.length > 0)
464         {
465             fields = new JRFillField[jrFields.length];
466             fieldsMap = new HashMap<String,JRFillField>();
467             for (int i = 0; i < fields.length; i++)
468             {
469                 fields[i] = factory.getField(jrFields[i]);
470                 fieldsMap.put(fields[i].getName(), fields[i]);
471             }
472         }
473     }
474
475
476     /**
477      * Creates the calculator
478      * @param jasperReport the report
479      * @throws JRException
480      */

481     public void createCalculator(JasperReport jasperReport) throws JRException
482     {
483         setCalculator(createCalculator(getJasperReportsContext(), jasperReport, this));
484     }
485
486     protected void setCalculator(JRCalculator calculator)
487     {
488         this.calculator = calculator;
489     }
490
491     protected static JRCalculator createCalculator(JasperReportsContext jasperReportsContext, JasperReport jasperReport, JRDataset dataset) throws JRException
492     {
493         JREvaluator evaluator = JasperCompileManager.getInstance(jasperReportsContext).getEvaluator(jasperReport, dataset);
494         return new JRCalculator(evaluator);
495     }
496
497
498     /**
499      * Initializes the calculator.
500      * 
501      * @throws JRException
502      */

503     public void initCalculator() throws JRException
504     {
505         calculator.init(this);
506     }
507
508
509     /**
510      * Inherits properties from the report.
511      */

512     public void inheritFromMain()
513     {
514         if (resourceBundleBaseName == null && !isMain)
515         {
516             resourceBundleBaseName = filler.jasperReport.getResourceBundle();
517             whenResourceMissingType = filler.jasperReport.getWhenResourceMissingTypeValue();
518         }
519     }
520     
521     
522     /**
523      * Creates the scriptlets.
524      * 
525      * @return the scriptlets list
526      * @throws JRException
527      */

528     protected List<JRAbstractScriptlet> createScriptlets(Map<String,Object> parameterValues) throws JRException
529     {
530         ScriptletFactoryContext context = new ScriptletFactoryContext(getJasperReportsContext(), this, parameterValues);
531         
532         scriptlets = new ArrayList<JRAbstractScriptlet>();
533         
534         List<ScriptletFactory> factories = getJasperReportsContext().getExtensions(ScriptletFactory.class);
535         for (Iterator<ScriptletFactory> it = factories.iterator(); it.hasNext();)
536         {
537             ScriptletFactory factory = it.next();
538             List<JRAbstractScriptlet> tmpScriptlets = factory.getScriplets(context);
539             if (tmpScriptlets != null)
540             {
541                 scriptlets.addAll(tmpScriptlets);
542             }
543         }
544         
545         if (scriptlets.size() == 0)
546         {
547             scriptlets.add(0, new JRDefaultScriptlet());
548         }
549
550         return scriptlets;
551     }
552
553
554     /**
555      * Initializes the element datasets.
556      * 
557      * @param factory the fill object factory used by the filler
558      */

559     protected void initElementDatasets(JRFillObjectFactory factory)
560     {
561         elementDatasets = factory.getElementDatasets(this);
562     }
563
564
565     /**
566      * Filters the element datasets, leaving only one.
567      * <p>
568      * This method is used when a dataset is instantiated by a chart or crosstab.
569      * 
570      * @param elementDataset the element dataset that should remain
571      */

572     protected void filterElementDatasets(JRFillElementDataset elementDataset)
573     {
574         origElementDatasets = elementDatasets;
575         elementDatasets = new JRFillElementDataset[]{elementDataset};
576     }
577     
578     
579     /**
580      * Restores the original element datasets.
581      * <p>
582      * This method should be called after {@link #filterElementDatasets(JRFillElementDataset) filterElementDatasets}.
583      */

584     protected void restoreElementDatasets()
585     {
586         if (origElementDatasets != null)
587         {
588             elementDatasets = origElementDatasets;
589             origElementDatasets = null;
590         }
591     }
592     
593
594     /**
595      * Loads the resource bundle corresponding to the resource bundle base name and locale.
596      */

597     protected ResourceBundle loadResourceBundle()
598     {
599         ResourceBundle loadedBundle;
600         if (resourceBundleBaseName == null)
601         {
602             loadedBundle = null;
603         }
604         else
605         {
606             loadedBundle = JRResourcesUtil.loadResourceBundle(getRepositoryContext(), resourceBundleBaseName, locale);
607         }
608         return loadedBundle;
609     }
610
611
612     /**
613      * Reads built-in parameter values from the value map.
614      * 
615      * @param parameterValues the parameter values
616      * @throws JRException 
617      */

618     public void setParameterValues(Map<String,Object> parameterValues) throws JRException
619     {
620         parameterValues.put(JRParameter.REPORT_PARAMETERS_MAP, parameterValues);
621         
622         parameterValues.put(JRParameter.JASPER_REPORTS_CONTEXT, getJasperReportsContext());
623         
624         if (filler != null)
625         {
626             // the only case when this filler is null is when called from JRParameterDefaultValuesEvaluator
627             // and that utility method already sets the report object in the map
628             parameterValues.put(JRParameter.JASPER_REPORT, filler.getJasperReport());
629         }
630         
631         reportMaxCount = (Integer) parameterValues.get(JRParameter.REPORT_MAX_COUNT);
632
633         locale = (Locale) parameterValues.get(JRParameter.REPORT_LOCALE);
634         if (locale == null)
635         {
636             locale = defaultLocale();
637             parameterValues.put(JRParameter.REPORT_LOCALE, locale);
638         }
639         
640         resourceBundle = (ResourceBundle) parameterValues.get(JRParameter.REPORT_RESOURCE_BUNDLE);
641         if (resourceBundle == null)
642         {
643             resourceBundle = loadResourceBundle();
644             if (resourceBundle != null)
645             {
646                 parameterValues.put(JRParameter.REPORT_RESOURCE_BUNDLE, resourceBundle);
647             }
648         }
649         
650         timeZone = (TimeZone) parameterValues.get(JRParameter.REPORT_TIME_ZONE);
651         if (timeZone == null)
652         {
653             timeZone = defaultTimeZone();
654             parameterValues.put(JRParameter.REPORT_TIME_ZONE, timeZone);
655         }
656         
657         scriptlets = createScriptlets(parameterValues);
658         delegateScriptlet.setData(this);//FIXMESCRIPTLET use some context
659
660         // initializing cache because we need the cached parameter values
661         cacheInit();
662         
663         setFillParameterValuesFromMap(parameterValues, true);
664         setFillParameterValuesFromCache(parameterValues);
665         evaluateParameterValues(ParameterEvaluationTimeEnum.EARLY, parameterValues);
666         
667         mergedProperties = staticProperties;
668         evaluateProperties(PropertyEvaluationTimeEnum.EARLY);
669         
670         //FIXME do not call on default parameter value evaluation and when a data snapshot is used?
671         contributeParameters(parameterValues);
672         
673         setFillParameterValuesFromMap(parameterValues, false);
674         evaluateParameterValues(ParameterEvaluationTimeEnum.LATE, parameterValues);
675
676         evaluateProperties(PropertyEvaluationTimeEnum.LATE);
677         
678         filter = (DatasetFilter) parameterValues.get(JRParameter.FILTER);
679
680         // after we have the parameter values, init cache recording
681         cacheInitRecording();
682         
683         // initialize the filter
684         if (filter != null)
685         {
686             filter.init(this);
687         }
688     }
689
690     protected Locale defaultLocale()
691     {
692         String localeCode = propertiesUtil.getProperty(this, JRFiller.PROPERTY_DEFAULT_LOCALE);
693         Locale locale = (localeCode == null || localeCode.isEmpty()) ? Locale.getDefault()
694                 : JRDataUtils.getLocale(localeCode);
695         return locale;
696     }
697
698     protected TimeZone defaultTimeZone()
699     {
700         String timezoneId = propertiesUtil.getProperty(this, JRFiller.PROPERTY_DEFAULT_TIMEZONE);
701         TimeZone timezone = (timezoneId == null || timezoneId.isEmpty()) ? TimeZone.getDefault()
702                 : JRDataUtils.getTimeZone(timezoneId);
703         return timezone;
704     }
705     
706     
707     /**
708      * Initializes the data source which will be used by this dataset.
709      * 
710      * If the dataset includes a query, this involves invoking the appropriate
711      * query executer to execute the query and create a data source from the
712      * results.
713      * 
714      * @throws JRException
715      */

716     public void initDatasource() throws JRException
717     {
718         queryExecuter = null;
719         dataSource = null;
720
721         if (cachedDataset != null)
722         {
723             // get the cached data source
724             dataSource = cachedDataset.getDataSource();
725         }
726         
727         if (dataSource == null)
728         {
729             dataSource = (JRDataSource) getParameterValue(JRParameter.REPORT_DATA_SOURCE);
730             if (!useDatasourceParamValue && (useConnectionParamValue || dataSource == null))
731             {
732                 dataSource = createQueryDatasource();
733                 setParameter(JRParameter.REPORT_DATA_SOURCE, dataSource);
734             }
735         }
736
737         if (DatasetSortUtil.needSorting(this))
738         {
739             dataSource = DatasetSortUtil.getSortedDataSource(filler, this, locale);
740             setParameter(JRParameter.REPORT_DATA_SOURCE, dataSource);
741             sortedDataSource = true;
742         }
743     }
744
745     public FillDatasetPosition getFillPosition()
746     {
747         return fillPosition;
748     }
749
750     public void setFillPosition(FillDatasetPosition fillPosition)
751     {
752         if (fillPosition != null)
753         {
754             // add default attributes
755             fillPosition.addAttribute("datasetUUID", getUUID());
756             
757             if (query != null)
758             {
759                 MD5Digest queryMD5 = DigestUtils.instance().md5(query.getText());
760                 fillPosition.addAttribute("queryMD5", queryMD5);
761             }
762             //FIXME optimize storage for repeating subreports/subdatasets
763         }
764
765         this.fillPosition = fillPosition;
766     }
767
768
769     public void setCacheSkipped(boolean cacheSkipped)
770     {
771         this.cacheSkipped = cacheSkipped;
772         
773         if (cacheSkipped && log.isDebugEnabled())
774         {
775             log.debug("data cache skipped at position " + fillPosition);
776         }
777     }
778     
779     protected void cacheInit() throws DataSnapshotException
780     {
781         // resetting
782         cachedDataset = null;
783         dataRecorder = null;
784         
785         if (fillPosition == null)
786         {
787             log.debug("No fill position present, not using the data cache");
788             return;
789         }
790         
791         if (filler != null)
792         {
793             // try to load a cached data snapshot
794             cacheInitSnapshot();
795         }
796     }
797     
798     protected void cacheInitSnapshot() throws DataSnapshotException
799     {
800         if (cacheSkipped)
801         {
802             return;
803         }
804
805         DataSnapshot dataSnapshot = filler.fillContext.getDataSnapshot();
806         if (dataSnapshot != null)
807         {
808             // using cached data
809             cachedDataset = dataSnapshot.getCachedData(fillPosition);
810             if (cachedDataset == null)
811             {
812                 throw 
813                     new DataSnapshotException(
814                         EXCEPTION_MESSAGE_KEY_NO_SUCH_SNAPSHOT_DATA,
815                         new Object[]{fillPosition});
816             }
817             
818             if (log.isDebugEnabled())
819             {
820                 log.debug("Using cached data for " + fillPosition);
821             }
822         }
823     }
824
825     protected void cacheInitRecording()
826     {
827         if (cacheSkipped)
828         {
829             return;
830         }
831
832         if (cachedDataset != null)
833         {
834             // we have a cache dataset, nothing to do
835             return;
836         }
837         
838         if (filler == null)
839         {
840             // not a regular report fill, nothing to do
841             return;
842         }
843         
844         // if no cached data snapshot, initialize data recording
845         DataRecorder cacheRecorder = filler.fillContext.getDataRecorder();
846         if (cacheRecorder != null && cacheRecorder.isEnabled())
847         {
848             // see if data recording is inhibited
849             boolean dataRecorable = JRPropertiesUtil.getInstance(getJasperReportsContext()).getBooleanProperty(
850                     this, DataCacheHandler.PROPERTY_DATA_RECORDABLE, true);
851             if (dataRecorable)
852             {
853                 // check whether the data snapshot can be persisted
854                 boolean dataPersistable = JRPropertiesUtil.getInstance(getJasperReportsContext()).getBooleanProperty(
855                         this, DataCacheHandler.PROPERTY_DATA_PERSISTABLE, true);
856                 if (!dataPersistable)
857                 {
858                     if (log.isDebugEnabled())
859                     {
860                         log.debug("data not persistable by property for " + fillPosition);
861                     }
862                     
863                     cacheRecorder.disablePersistence();
864                 }
865                 
866                 // populating the cache
867                 dataRecorder = cacheRecorder.createRecorder();
868                 // this will also remove existing data on rewind
869                 dataRecorder.start(parent.getFields());
870                 
871                 if (log.isDebugEnabled())
872                 {
873                     log.debug("Populating data cache for " + fillPosition);
874                 }
875                 
876                 // storing persisted parameter values
877                 for (JRFillParameter parameter : parameters)
878                 {
879                     if (parameter.hasProperties())
880                     {
881                         boolean includedInCache = isIncludedInDataCache(parameter);
882                         if (includedInCache)
883                         {
884                             if (log.isDebugEnabled())
885                             {
886                                 log.debug("storing value of paramter " + parameter.getName() 
887                                         + " in data snapshot");
888                             }
889                             
890                             Object value = parameter.getValue();
891                             // we store nulls as well
892                             dataRecorder.addParameter(parameter.getName(), value);
893                         }
894                     }
895                 }
896                 
897                 cacheRecordIndexCallbacks = new HashMap<Integer, CacheRecordIndexCallback>();
898             }
899             else
900             {
901                 if (log.isDebugEnabled())
902                 {
903                     log.debug("data recording inhibited by property for " + fillPosition);
904                 }
905                 
906                 // disable all recording
907                 cacheRecorder.disableRecording();
908             }
909         }
910     }
911
912     protected boolean isIncludedInDataCache(JRFillParameter parameter)
913     {
914         String includedProp = JRPropertiesUtil.getOwnProperty(parameter, DataCacheHandler.PROPERTY_INCLUDED); 
915         return JRPropertiesUtil.asBoolean(includedProp);
916     }
917
918     protected ParameterEvaluationTimeEnum getDefaultParameterEvaluationTime()
919     {
920         String evalTimeProp = 
921             propertiesUtil.getProperty(
922                 ParameterEvaluationTimeEnum.PROPERTY_EVALUATION_TIME, 
923                 this
924                 );
925         return ParameterEvaluationTimeEnum.byName(evalTimeProp);
926     }
927
928     protected PropertyEvaluationTimeEnum getDefaultPropertyEvaluationTime()
929     {
930         String evalTimeProp = 
931             propertiesUtil.getProperty(
932                 PropertyEvaluationTimeEnum.PROPERTY_EVALUATION_TIME, 
933                 this
934                 );
935         return PropertyEvaluationTimeEnum.byName(evalTimeProp);
936     }
937
938     protected void cacheRecord()
939     {
940         if (dataRecorder != null && !dataRecorder.hasEnded())
941         {
942             Object[] values;
943             if (fields == null)
944             {
945                 values = new Object[0];
946             }
947             else
948             {
949                 values = new Object[fields.length];
950                 for (int i = 0; i < fields.length; i++)
951                 {
952                     values[i] = fields[i].getValue();
953                 }
954             }
955             
956             dataRecorder.addRecord(values);
957         }
958     }
959
960     protected void cacheEnd()
961     {
962         if (dataRecorder != null && !dataRecorder.hasEnded())
963         {
964             // if we had a sorted data source, compute the original filtered record indexes
965             if (sortedDataSource)
966             {
967                 if (log.isDebugEnabled())
968                 {
969                     log.debug("populating unsorted cache");
970                 }
971                 
972                 int recordIndex = 0;
973                 // ugly cast
974                 List<SortedDataSource.SortRecord> sortRecords = ((SortedDataSource) dataSource).getRecords();
975                 for (SortedDataSource.SortRecord sortRecord : sortRecords)
976                 {
977                     if (sortRecord.isFiltered())
978                     {
979                         Object[] recordValues = sortRecord.getValues();
980                         Object[] fieldValues;
981                         // the sort record can also contain sort variable values
982                         if (fields.length == recordValues.length)
983                         {
984                             fieldValues = recordValues;
985                         }
986                         else
987                         {
988                             fieldValues = new Object[fields.length];
989                             System.arraycopy(recordValues, 0, fieldValues, 0, fields.length);
990                         }
991                         
992                         // add the record to the data snapshot
993                         dataRecorder.addRecord(fieldValues);
994                         
995                         // current unsorted index
996                         ++recordIndex;
997                         int originalIndex = sortRecord.getRecordIndex() + 1;
998                         
999                         if (log.isDebugEnabled())
1000                         {
1001                             log.debug("unsorted index " + recordIndex + for original index " + originalIndex);
1002                         }
1003                         
1004                         // call delayed record index callbacks
1005                         CacheRecordIndexCallback recordIndexCallback = cacheRecordIndexCallbacks.get(originalIndex);
1006                         if (recordIndexCallback != null)
1007                         {
1008                             recordIndexCallback.cacheRecordIndexAvailable(recordIndex);
1009                         }
1010                     }
1011                 }
1012             }
1013             
1014             Object recorded = dataRecorder.end();
1015             if (recorded != null)
1016             {
1017                 // adding the recorded data to a temporary list because the fill position might not be final
1018                 filler.fillContext.addDataRecordResult(fillPosition, recorded);
1019             }
1020         }
1021     }
1022
1023
1024     /**
1025      * Sets the parameter values from the values map.
1026      * 
1027      * @param parameterValues the values map
1028      * @throws JRException
1029      */

1030     private void evaluateParameterValues(ParameterEvaluationTimeEnum evaluationTime, Map<String,Object> parameterValues) throws JRException
1031     {
1032         if (parameters != null && parameters.length > 0)
1033         {
1034             ParameterEvaluationTimeEnum defaultEvaluationTime = getDefaultParameterEvaluationTime();
1035             for (int i = 0; i < parameters.length; i++)
1036             {
1037                 JRFillParameter parameter = parameters[i];
1038                 String paramName = parameter.getName();
1039                 ParameterEvaluationTimeEnum paramEvalTime = parameter.getEvaluationTime() == null ? defaultEvaluationTime : parameter.getEvaluationTime();
1040                 
1041                 if (
1042                     !parameterValues.containsKey(paramName) //cheaper to test this first
1043                     && !parameter.isSystemDefined() //cheaper to test this first
1044                     && evaluationTime == paramEvalTime
1045                     && (!isIncludedInDataCache(parameter) || cachedDataset == null)
1046                     )
1047                 {
1048                     Object value = calculator.evaluate(parameter.getDefaultValueExpression(), JRExpression.EVALUATION_DEFAULT);
1049                     if (value != null)
1050                     {
1051                         parameterValues.put(paramName, value);
1052                     }
1053                     setParameter(parameter, value);
1054                 }
1055             }
1056         }
1057     }
1058
1059
1060     /**
1061      * 
1062      */

1063     private void setFillParameterValuesFromMap(Map<String,Object> parameterValues, boolean reset) throws JRException
1064     {
1065         if (parameters != null && parameters.length > 0)
1066         {
1067             for (int i = 0; i < parameters.length; i++)
1068             {
1069                 JRFillParameter parameter = parameters[i];
1070                 String paramName = parameter.getName();
1071                 
1072                 Object value = null;
1073                 if (parameterValues.containsKey(paramName))
1074                 {
1075                     value = parameterValues.get(paramName);
1076                     setParameter(parameter, value);
1077                 }
1078                 else if (reset)
1079                 {
1080                     setParameter(parameter, null);
1081                 }
1082             }
1083         }
1084     }
1085
1086
1087     /**
1088      * Sets the parameter values from the values map.
1089      * 
1090      * @param parameterValues the values map
1091      * @throws JRException
1092      */

1093     private void setFillParameterValuesFromCache(Map<String,Object> parameterValues) throws JRException
1094     {
1095         if (parameters != null && parameters.length > 0)
1096         {
1097             for (int i = 0; i < parameters.length; i++)
1098             {
1099                 JRFillParameter parameter = parameters[i];
1100                 String paramName = parameter.getName();
1101                 
1102                 if (
1103                     !parameterValues.containsKey(paramName)
1104                     && !parameter.isSystemDefined()
1105                     )
1106                 {
1107                     if (isIncludedInDataCache(parameter) && cachedDataset != null)
1108                     {
1109                         // if it's a cache parameter and we have a cached dataset,
1110                         // look for the value in the cache
1111                         if (!cachedDataset.hasParameter(paramName))
1112                         {
1113                             // cached data is invalid
1114                             throw 
1115                                 new DataSnapshotException(
1116                                     EXCEPTION_MESSAGE_KEY_NO_SUCH_SNAPSHOT_PARAMETER,
1117                                     new Object[]{paramName});
1118                         }
1119                         
1120                         if (log.isDebugEnabled())
1121                         {
1122                             log.debug("loading parameter " + paramName + " value from data snapshot");
1123                         }
1124                         
1125                         Object value = cachedDataset.getParameterValue(paramName);
1126                         setParameter(parameter, value);
1127                     }
1128                 }
1129             }
1130         }
1131     }
1132
1133
1134     /**
1135      * 
1136      */

1137     public void evaluateFieldProperties() throws JRException
1138     {
1139         if (fields != null && fields.length > 0)
1140         {
1141             for (JRFillField field : fields)
1142             {
1143                 field.evaluateProperties();
1144             }
1145         }
1146     }
1147
1148
1149     /**
1150      * 
1151      */

1152     public void contributeParameters(Map<String,Object> parameterValues) throws JRException
1153     {
1154         parameterContributors = getParameterContributors(new ParameterContributorContext(getRepositoryContext(), this, parameterValues));
1155         if (parameterContributors != null)
1156         {
1157             for(ParameterContributor contributor : parameterContributors)
1158             {
1159                 contributor.contributeParameters(parameterValues);
1160             }
1161         }
1162     }
1163
1164     public void setJasperReportsContext(JasperReportsContext jasperReportsContext)
1165     {
1166         this.jasperReportsContext = jasperReportsContext;
1167         if (jasperReportsContext != null)
1168         {
1169             this.propertiesUtil = JRPropertiesUtil.getInstance(jasperReportsContext);
1170         }
1171     }
1172     
1173     protected JasperReportsContext getJasperReportsContext()
1174     {
1175         return filler == null
1176                 ? (jasperReportsContext == null ? DefaultJasperReportsContext.getInstance() : jasperReportsContext)
1177                 : filler.getJasperReportsContext();
1178     }
1179     
1180     protected RepositoryContext getRepositoryContext()
1181     {
1182         return filler == null
1183                 ? SimpleRepositoryContext.of(jasperReportsContext == null ? DefaultJasperReportsContext.getInstance() : jasperReportsContext)
1184                 : filler.getRepositoryContext();
1185     }
1186     
1187     /**
1188      * 
1189      */

1190     public void disposeParameterContributors()
1191     {
1192         if (parameterContributors != null)
1193         {
1194             for(ParameterContributor contributor : parameterContributors)
1195             {
1196                 contributor.dispose();
1197             }
1198         }
1199     }
1200
1201     
1202     /**
1203      *
1204      */

1205     private List<ParameterContributor> getParameterContributors(ParameterContributorContext context) throws JRException
1206     {
1207         List<ParameterContributor> allContributors = null;
1208         List<?> factories = getJasperReportsContext().getExtensions(ParameterContributorFactory.class);
1209         if (factories != null && factories.size() > 0)
1210         {
1211             allContributors = new ArrayList<ParameterContributor>();
1212             for (Iterator<?> it = factories.iterator(); it.hasNext();)
1213             {
1214                 ParameterContributorFactory factory = (ParameterContributorFactory)it.next();
1215                 List<ParameterContributor> contributors = factory.getContributors(context);
1216                 if (contributors != null)
1217                 {
1218                     allContributors.addAll(contributors);
1219                 }
1220             }
1221         }
1222         return allContributors;
1223     }
1224
1225     
1226     /**
1227      * Returns the map of parameter values.
1228      * 
1229      * @return the map of parameter values
1230      */

1231     protected Map<String,Object> getParameterValuesMap()
1232     {
1233         JRFillParameter paramValuesParameter = parametersMap.get(JRParameter.REPORT_PARAMETERS_MAP);
1234         return (Map<String,Object>) paramValuesParameter.getValue();
1235     }
1236     
1237     /**
1238      * Creates the data source from a connection.
1239      * 
1240      * @return the data source to be used
1241      * @throws JRException
1242      */

1243     private JRDataSource createQueryDatasource() throws JRException
1244     {
1245         if (query == null)
1246         {
1247             return null;
1248         }
1249
1250         try
1251         {
1252             if (log.isDebugEnabled())
1253             {
1254                 log.debug("Fill " + filler.fillerId + ": Creating " + query.getLanguage() + " query executer");
1255             }
1256             
1257             QueryExecuterFactory queryExecuterFactory = JRQueryExecuterUtils.getInstance(getJasperReportsContext()).getExecuterFactory(query.getLanguage());
1258             SimpleQueryExecutionContext queryExecutionContext = SimpleQueryExecutionContext.of(
1259                     getJasperReportsContext(), getRepositoryContext());
1260             queryExecuter = queryExecuterFactory.createQueryExecuter(queryExecutionContext, this, parametersMap);
1261             filler.fillContext.setRunningQueryExecuter(queryExecuter);
1262             
1263             return queryExecuter.createDatasource();
1264         }
1265         finally
1266         {
1267             filler.fillContext.clearRunningQueryExecuter();
1268         }
1269     }
1270
1271
1272     protected void reset()
1273     {
1274         useDatasourceParamValue = false;
1275         useConnectionParamValue = false;
1276     }
1277
1278     
1279     /**
1280      * Sets the data source to be used.
1281      * 
1282      * @param parameterValues the parameter values
1283      * @param ds the data source
1284      */

1285     public void setDatasourceParameterValue(Map<String,Object> parameterValues, JRDataSource ds)
1286     {
1287         useDatasourceParamValue = true;
1288         
1289         if (ds != null)
1290         {
1291             parameterValues.put(JRParameter.REPORT_DATA_SOURCE, ds);
1292         }
1293     }
1294
1295
1296     /**
1297      * Sets the JDBC connection to be used.
1298      * 
1299      * @param parameterValues the parameter values
1300      * @param conn the connection
1301      */

1302     public void setConnectionParameterValue(Map<String,Object> parameterValues, Connection conn)
1303     {
1304         useConnectionParamValue = true;
1305         
1306         if (conn != null)
1307         {
1308             parameterValues.put(JRParameter.REPORT_CONNECTION, conn);
1309         }
1310     }
1311     
1312     
1313     /**
1314      * Closes the data source used by this dataset if this data source was
1315      * obtained via a query executer.
1316      * 
1317      * @see JRQueryExecuter#close()
1318      */

1319     public void closeDatasource()
1320     {
1321         closeQueryExecuter();
1322         reset();
1323
1324         if (ended)
1325         {
1326             // if the whole data source was iterated, submit the recorded data
1327             cacheEnd();
1328         }
1329     }
1330
1331     protected void closeQueryExecuter()
1332     {
1333         if (queryExecuter != null)
1334         {
1335             if (log.isDebugEnabled())
1336             {
1337                 log.debug("Fill " + filler.fillerId + ": closing query executer");
1338             }
1339
1340             queryExecuter.close();
1341             queryExecuter = null;
1342         }
1343     }
1344
1345     
1346     /**
1347      * Starts the iteration on the data source.
1348      */

1349     public void start()
1350     {
1351         // resetting the variables is required for cases such as sort fields which
1352         // iterate and calculate variables.
1353         resetVariables();
1354         
1355         reportCount = 0;
1356         ended = false;
1357         
1358         cacheRecordCount = 0;
1359         previousCacheRecordIndex = 0;
1360         currentCacheRecordIndex = 0;
1361     }
1362
1363     
1364     protected void resetVariables()
1365     {
1366         if (variables != null)
1367         {
1368             for (JRFillVariable variable : variables)
1369             {
1370                 variable.reset();
1371             }
1372         }
1373     }
1374
1375
1376     /**
1377      * Moves to the next record in the data source.
1378      * 
1379      * @return <code>true</code> if the data source was not exhausted
1380      * @throws JRException
1381      */

1382     public boolean next() throws JRException
1383     {
1384         return next(false);
1385     }
1386
1387
1388     /**
1389      * Moves to the next record in the data source.
1390      * 
1391      * @param sorting whether the method is called as part of the data sorting phase
1392      * @return <code>true</code> if the data source was not exhausted
1393      * @throws JRException
1394      */

1395     protected boolean next(boolean sorting) throws JRException
1396     {
1397         boolean hasNext = false;
1398
1399         if (dataSource != null)
1400         {
1401             boolean includeRow = true;
1402             do
1403             {
1404                 // limits are applied after sorting to support top-N reports
1405                 hasNext = advanceDataSource(!sorting);
1406                 if (hasNext)
1407                 {
1408                     setOldValues();
1409
1410                     calculator.estimateVariables();
1411                     
1412                     // filters are applied after sorting to support top-N reports
1413                     if (!sorting)
1414                     {
1415                         includeRow = true;
1416                         
1417                         // evaluate the filter expression
1418                         JRExpression filterExpression = getFilterExpression();
1419                         if (filterExpression != null)
1420                         {
1421                             Boolean filterExprResult = (Boolean) calculator.evaluate(
1422                                     filterExpression, JRExpression.EVALUATION_ESTIMATED);
1423                             includeRow = filterExprResult != null && filterExprResult;
1424                         }
1425
1426                         if (includeRow)
1427                         {
1428                             advanceCacheRecordIndexes();
1429                             
1430                             if (sortedDataSource)
1431                             {
1432                                 // mark the record as filtered in the sorted data source
1433                                 // ugly cast
1434                                 ((SortedDataSource) dataSource).setRecordFilteredIndex(cacheRecordCount - 1);
1435                             }
1436                             else
1437                             {
1438                                 // cache the record if the filter expression evaluated to true;
1439                                 // dynamic filters do not exclude records from the cache.
1440                                 // sorted data source cache records at the end because they compute original indexes.
1441                                 cacheRecord();
1442                             }
1443                             
1444                             if (filter != null)
1445                             {
1446                                 includeRow = filter.matches(EvaluationType.ESTIMATED);
1447                                 if (log.isDebugEnabled())
1448                                 {
1449                                     log.debug("Record matched by filter: " + includeRow);
1450                                 }
1451                             }
1452                         }
1453                     }
1454
1455                     if (!includeRow)
1456                     {
1457                         revertToOldValues();
1458                     }
1459                 }
1460             }
1461             while(hasNext && !includeRow);
1462             
1463             if (hasNext)
1464             {
1465                 ++reportCount;
1466             }
1467         }
1468         
1469         if (!hasNext)
1470         {
1471             ended = true;
1472         }
1473
1474         return hasNext;
1475     }
1476
1477     protected void advanceCacheRecordIndexes()
1478     {
1479         ++cacheRecordCount;
1480         previousCacheRecordIndex = currentCacheRecordIndex;
1481         
1482         // if we're using a cached data source, return the original record index
1483         // this covers both sorting a cached data source and filtering a cached data source
1484         if (cachedDataset != null)
1485         {
1486             // ugly cast
1487             int dataSourceIndex = ((IndexedDataSource) dataSource).getRecordIndex();
1488             // indexes are 1-based
1489             currentCacheRecordIndex = dataSourceIndex + 1;
1490         }
1491         else
1492         {
1493             currentCacheRecordIndex = cacheRecordCount;
1494         }
1495     }
1496
1497
1498     protected void setOldValues() throws JRException
1499     {
1500         if (fields != null && fields.length > 0)
1501         {
1502             for (int i = 0; i < fields.length; i++)
1503             {
1504                 JRFillField field = fields[i];
1505                 field.setPreviousOldValue(field.getOldValue());
1506                 field.setOldValue(field.getValue());
1507                 field.setValue(dataSource.getFieldValue(field));
1508             }
1509         }
1510
1511         if (variables != null && variables.length > 0)
1512         {
1513             for (int i = 0; i < variables.length; i++)
1514             {
1515                 JRFillVariable variable = variables[i];
1516                 variable.setPreviousOldValue(variable.getOldValue());
1517                 variable.setOldValue(variable.getValue());
1518             }
1519         }
1520     }
1521
1522
1523     protected void revertToOldValues()
1524     {
1525         if (fields != null && fields.length > 0)
1526         {
1527             for (int i = 0; i < fields.length; i++)
1528             {
1529                 JRFillField field = fields[i];
1530                 field.setValue(field.getOldValue());
1531                 field.setOldValue(field.getPreviousOldValue());
1532             }
1533         }
1534         
1535         revertVariablesToOldValues();
1536     }
1537
1538
1539     protected void revertVariablesToOldValues()
1540     {
1541         if (variables != null && variables.length > 0)
1542         {
1543             for (int i = 0; i < variables.length; i++)
1544             {
1545                 JRFillVariable variable = variables[i];
1546                 variable.setValue(variable.getOldValue());
1547                 variable.setOldValue(variable.getPreviousOldValue());
1548             }
1549         }
1550     }
1551
1552
1553     protected boolean advanceDataSource(boolean limit) throws JRException
1554     {
1555         boolean hasNext;
1556         if (limit && reportMaxCount != null && reportCount >= reportMaxCount)
1557         {
1558             hasNext = false;
1559         }
1560         else
1561         {
1562             hasNext = dataSource.next();
1563         }
1564         return hasNext;
1565     }
1566     
1567     /**
1568      * Sets the value of a parameter.
1569      * 
1570      * @param parameterName the parameter name
1571      * @param value the value
1572      * @throws JRException
1573      */

1574     protected void setParameter(String parameterName, Object value) throws JRException
1575     {
1576         JRFillParameter parameter = parametersMap.get(parameterName);
1577         if (parameter != null)
1578         {
1579             setParameter(parameter, value);
1580         }
1581     }
1582     
1583     
1584     /**
1585      * Sets the value of the parameter.
1586      * 
1587      * @param parameter the parameter
1588      * @param value the value
1589      * @throws JRException
1590      */

1591     protected void setParameter(JRFillParameter parameter, Object value) throws JRException
1592     {
1593 //        if (value != null)
1594 //        {
1595 //            if (parameter.getValueClass().isInstance(value))
1596 //            {
1597 //                parameter.setValue(value);
1598 //            }
1599 //            else
1600 //            {
1601 //                throw new JRException(
1602 //                    "Incompatible " 
1603 //                    + value.getClass().getName() 
1604 //                    + " value assigned to parameter " 
1605 //                    + parameter.getName() 
1606 //                    + " in the " + getName() + " dataset."
1607 //                    );
1608 //            }
1609 //        }
1610 //        else
1611 //        {
1612             parameter.setValue(value);
1613 //        }
1614     }
1615
1616     public JRFillVariable getVariable(String variableName)
1617     {
1618         return variablesMap.get(variableName);
1619     }
1620     
1621     /**
1622      * Returns the value of a variable.
1623      * 
1624      * @param variableName the variable name
1625      * @return the variable value
1626      */

1627     public Object getVariableValue(String variableName)
1628     {
1629         return getVariableValue(variableName, EvaluationType.DEFAULT);
1630     }
1631
1632     @Override
1633     public Object getVariableValue(String variableName, EvaluationType evaluation)
1634     {
1635         JRFillVariable var = variablesMap.get(variableName);
1636         if (var == null)
1637         {
1638             throw 
1639                 new JRRuntimeException(
1640                     EXCEPTION_MESSAGE_KEY_NO_SUCH_VARIABLE,  
1641                     new Object[]{variableName} 
1642                     );
1643         }
1644         return var.getValue(evaluation.getType());
1645     }
1646
1647     public JRFillVariable getFillVariable(String variableName)
1648     {
1649         return variablesMap.get(variableName);
1650     }
1651     
1652     /**
1653      * Returns the value of a parameter.
1654      * 
1655      * @param parameterName the parameter name
1656      * @return the parameter value
1657      */

1658     @Override
1659     public Object getParameterValue(String parameterName)
1660     {
1661         return getParameterValue(parameterName, false);
1662     }
1663
1664     
1665     /**
1666      * Returns the value of a parameter.
1667      * 
1668      * @param parameterName the parameter name
1669      * @param ignoreMissing if set, <code>null</code> will be returned for inexisting parameters
1670      * @return the parameter value
1671      */

1672     public Object getParameterValue(String parameterName, boolean ignoreMissing)
1673     {
1674         JRFillParameter param = parametersMap.get(parameterName);
1675         Object value;
1676         if (param == null)
1677         {
1678             if (!ignoreMissing)
1679             {
1680                 throw 
1681                     new JRRuntimeException(
1682                         EXCEPTION_MESSAGE_KEY_NO_SUCH_PARAMETER,  
1683                         new Object[]{parameterName} 
1684                         );
1685             }
1686             
1687             // look into REPORT_PARAMETERS_MAP
1688             Map<String, Object> valuesMap = getParameterValuesMap();
1689             value = valuesMap == null ? null : valuesMap.get(parameterName);
1690         }
1691         else
1692         {
1693             value = param.getValue();
1694         }
1695         return value;
1696     }
1697
1698     
1699     /**
1700      * Returns the value of a field.
1701      * 
1702      * @param fieldName the field name
1703      * @return the field value
1704      */

1705     public Object getFieldValue(String fieldName)
1706     {
1707         return getFieldValue(fieldName, EvaluationType.DEFAULT);
1708     }
1709     
1710     @Override
1711     public Object getFieldValue(String fieldName, EvaluationType evaluation)
1712     {
1713         JRFillField field = fieldsMap.get(fieldName);
1714         if (field == null)
1715         {
1716             throw 
1717                 new JRRuntimeException(
1718                     EXCEPTION_MESSAGE_KEY_NO_SUCH_FIELD,  
1719                     new Object[]{fieldName} 
1720                     );
1721         }
1722         return field.getValue(evaluation.getType());
1723     }
1724     
1725     public JRFillField getFillField(String fieldName)
1726     {
1727         return fieldsMap.get(fieldName);
1728     }
1729     
1730     /**
1731      * Class used to hold expression calculation  requirements.
1732      */

1733     protected static class VariableCalculationReq
1734     {
1735         String variableName;
1736
1737         CalculationEnum calculation;
1738
1739         VariableCalculationReq(String variableName, CalculationEnum calculation)
1740         {
1741             this.variableName = variableName;
1742             this.calculation = calculation;
1743         }
1744
1745         @Override
1746         public boolean equals(Object o)
1747         {
1748             if (o == null || !(o instanceof VariableCalculationReq))
1749             {
1750                 return false;
1751             }
1752
1753             VariableCalculationReq r = (VariableCalculationReq) o;
1754
1755             return variableName.equals(r.variableName) && calculation == r.calculation;
1756         }
1757
1758         @Override
1759         public int hashCode()
1760         {
1761             return 31 * calculation.ordinal() + variableName.hashCode();
1762         }
1763     }
1764     
1765     
1766     /**
1767      * Adds a variable calculation requirement.
1768      * 
1769      * @param variableName the variable name
1770      * @param calculation the required calculation
1771      */

1772     protected void addVariableCalculationReq(String variableName, CalculationEnum calculation)
1773     {
1774         if (variableCalculationReqs == null)
1775         {
1776             variableCalculationReqs = new HashSet<VariableCalculationReq>();
1777         }
1778
1779         variableCalculationReqs.add(new VariableCalculationReq(variableName, calculation));
1780     }
1781
1782     
1783     /**
1784      * Checks if there are variable calculation requirements and creates the required variables.
1785      * 
1786      * @param factory the fill object factory
1787      */

1788     protected void checkVariableCalculationReqs(JRFillObjectFactory factory)
1789     {
1790         if (variableCalculationReqs != null && !variableCalculationReqs.isEmpty())
1791         {
1792             List<JRFillVariable> variableList = new ArrayList<JRFillVariable>(variables.length * 2);
1793
1794             for (int i = 0; i < variables.length; i++)
1795             {
1796                 JRFillVariable variable = variables[i];
1797                 checkVariableCalculationReq(variable, variableList, factory);
1798             }
1799
1800             setVariables(variableList);
1801         }
1802     }
1803
1804     
1805     private void checkVariableCalculationReq(JRFillVariable variable, List<JRFillVariable> variableList, JRFillObjectFactory factory)
1806     {
1807         JRVariable parentVariable = variable.getParent();
1808         
1809         if (hasVariableCalculationReq(variable, CalculationEnum.AVERAGE) || hasVariableCalculationReq(variable, CalculationEnum.VARIANCE))
1810         {
1811             if (variable.getHelperVariable(JRCalculable.HELPER_COUNT) == null)
1812             {
1813                 JRVariable countVar = createHelperVariable(parentVariable, "_COUNT", CalculationEnum.COUNT);
1814                 JRFillVariable fillCountVar = factory.getVariable(countVar);
1815                 checkVariableCalculationReq(fillCountVar, variableList, factory);
1816                 variable.setHelperVariable(fillCountVar, JRCalculable.HELPER_COUNT);
1817             }
1818
1819             if (variable.getHelperVariable(JRCalculable.HELPER_SUM) == null)
1820             {
1821                 JRVariable sumVar = createHelperVariable(parentVariable, "_SUM", CalculationEnum.SUM);
1822                 JRFillVariable fillSumVar = factory.getVariable(sumVar);
1823                 checkVariableCalculationReq(fillSumVar, variableList, factory);
1824                 variable.setHelperVariable(fillSumVar, JRCalculable.HELPER_SUM);
1825             }
1826         }
1827
1828         if (hasVariableCalculationReq(variable, CalculationEnum.STANDARD_DEVIATION))
1829         {
1830             if (variable.getHelperVariable(JRCalculable.HELPER_VARIANCE) == null)
1831             {
1832                 JRVariable varianceVar = createHelperVariable(parentVariable, "_VARIANCE", CalculationEnum.VARIANCE);
1833                 JRFillVariable fillVarianceVar = factory.getVariable(varianceVar);
1834                 checkVariableCalculationReq(fillVarianceVar, variableList, factory);
1835                 variable.setHelperVariable(fillVarianceVar, JRCalculable.HELPER_VARIANCE);
1836             }
1837         }
1838
1839         if (hasVariableCalculationReq(variable, CalculationEnum.DISTINCT_COUNT))
1840         {
1841             if (variable.getHelperVariable(JRCalculable.HELPER_COUNT) == null)
1842             {
1843                 JRVariable countVar = createDistinctCountHelperVariable(parentVariable);
1844                 JRFillVariable fillCountVar = factory.getVariable(countVar);
1845                 checkVariableCalculationReq(fillCountVar, variableList, factory);
1846                 variable.setHelperVariable(fillCountVar, JRCalculable.HELPER_COUNT);
1847             }
1848         }
1849
1850         variableList.add(variable);
1851     }
1852
1853     
1854     private boolean hasVariableCalculationReq(JRVariable var, CalculationEnum calculation)
1855     {
1856         return variableCalculationReqs.contains(new VariableCalculationReq(var.getName(), calculation));
1857     }
1858
1859     @Override
1860     public UUID getUUID()
1861     {
1862         return parent.getUUID();
1863     }
1864
1865     @Override
1866     public String getName()
1867     {
1868         return parent.getName();
1869     }
1870
1871     @Override
1872     public String getScriptletClass()
1873     {
1874         return parent.getScriptletClass();
1875     }
1876
1877     @Override
1878     public JRScriptlet[] getScriptlets()
1879     {
1880         return parent.getScriptlets();
1881     }
1882
1883     @Override
1884     public JRParameter[] getParameters()
1885     {
1886         return parameters;
1887     }
1888
1889     public Map<String,JRFillParameter> getParametersMap()
1890     {
1891         return parametersMap;
1892     }
1893
1894     @Override
1895     public JRQuery getQuery()
1896     {
1897         return query;
1898     }
1899
1900     @Override
1901     public JRField[] getFields()
1902     {
1903         return fields;
1904     }
1905
1906     public Map<String,JRFillField> getFieldsMap()
1907     {
1908         return fieldsMap;
1909     }
1910
1911     @Override
1912     public JRSortField[] getSortFields()
1913     {
1914         return parent.getSortFields();
1915     }
1916
1917     @Override
1918     public JRVariable[] getVariables()
1919     {
1920         return variables;
1921     }
1922
1923     public Map<String,JRFillVariable> getVariablesMap()
1924     {
1925         return variablesMap;
1926     }
1927
1928     @Override
1929     public JRGroup[] getGroups()
1930     {
1931         return groups;
1932     }
1933
1934     @Override
1935     public boolean isMainDataset()
1936     {
1937         return isMain;
1938     }
1939
1940     @Override
1941     public String getResourceBundle()
1942     {
1943         return parent.getResourceBundle();
1944     }
1945
1946
1947     @Override
1948     public WhenResourceMissingTypeEnum getWhenResourceMissingTypeValue()
1949     {
1950         return whenResourceMissingType;
1951     }
1952
1953
1954     @Override
1955     public void setWhenResourceMissingType(WhenResourceMissingTypeEnum whenResourceMissingType)
1956     {
1957         this.whenResourceMissingType = whenResourceMissingType;
1958     }
1959
1960     
1961     @Override
1962     public boolean hasProperties()
1963     {
1964         return mergedProperties != null && mergedProperties.hasProperties();
1965     }
1966
1967
1968     @Override
1969     public JRPropertiesMap getPropertiesMap()
1970     {
1971         return mergedProperties;
1972     }
1973
1974     
1975     @Override
1976     public JRPropertiesHolder getParentProperties()
1977     {
1978         // report properties propagate to subdatasets
1979         return 
1980             isMain 
1981             ? (filler == null || filler.parent == null ? null : filler.parent.getParentProperties()) 
1982             : filler.getMainDataset();
1983     }
1984
1985
1986     @Override
1987     public DatasetPropertyExpression[] getPropertyExpressions()
1988     {
1989         return propertyExpressions.toArray(new DatasetPropertyExpression[propertyExpressions.size()]);
1990     }
1991
1992
1993     /**
1994      *
1995      */

1996     protected void evaluateProperties(PropertyEvaluationTimeEnum evaluationTime) throws JRException
1997     {
1998         if (!propertyExpressions.isEmpty())
1999         {
2000             JRPropertiesMap dynamicProperties = new JRPropertiesMap();
2001             
2002             PropertyEvaluationTimeEnum defaultEvaluationTime = getDefaultPropertyEvaluationTime();
2003             for (DatasetPropertyExpression prop : propertyExpressions)
2004             {
2005                 PropertyEvaluationTimeEnum propEvalTime = prop.getEvaluationTime() == null ? defaultEvaluationTime : prop.getEvaluationTime();
2006                 if (evaluationTime == propEvalTime)
2007                 {
2008                     String value = (String) evaluateExpression(prop.getValueExpression(), JRExpression.EVALUATION_DEFAULT);
2009                     //if (value != null) //is the null value significant for some field properties?
2010                     {
2011                         dynamicProperties.setProperty(prop.getName(), value);
2012                     }
2013                 }
2014             }
2015
2016             JRPropertiesMap newMergedProperties = dynamicProperties.cloneProperties();
2017             newMergedProperties.setBaseProperties(mergedProperties);
2018             mergedProperties = newMergedProperties;
2019         }
2020     }
2021
2022
2023     @Override
2024     public JRExpression getFilterExpression()
2025     {
2026         return parent.getFilterExpression();
2027     }
2028     
2029     @Override
2030     public Object clone() 
2031     {
2032         throw new UnsupportedOperationException();
2033     }
2034
2035     /**
2036      * Evaluates an expression
2037      * @param expression the expression
2038      * @param evaluation the evaluation type
2039      * @return the evaluation result
2040      * @throws JRException
2041      */

2042     public Object evaluateExpression(JRExpression expression, byte evaluation) throws JRException
2043     {
2044         return calculator.evaluate(expression, evaluation);
2045     }
2046     
2047     @Override
2048     public Locale getLocale()
2049     {
2050         return locale;
2051     }
2052     
2053     public TimeZone getTimeZone()
2054     {
2055         return timeZone;
2056     }
2057
2058     public FillDatasetPosition getDatasetPosition()
2059     {
2060         return fillPosition;
2061     }
2062     
2063     protected static interface CacheRecordIndexCallback
2064     {
2065         void cacheRecordIndexAvailable(int recordIndex);
2066     }
2067     
2068     protected static class FillDatasetPositionRecordIndexCallback implements CacheRecordIndexCallback
2069     {
2070         protected static void setRecordIndex(FillDatasetPosition position, int recordIndex)
2071         {
2072             position.addAttribute("rowIndex", recordIndex);
2073         }
2074         
2075         private final FillDatasetPosition position;
2076
2077         public FillDatasetPositionRecordIndexCallback(FillDatasetPosition position)
2078         {
2079             this.position = position;
2080         }
2081
2082         @Override
2083         public void cacheRecordIndexAvailable(int recordIndex)
2084         {
2085             setRecordIndex(position, recordIndex);
2086         }
2087     }
2088     
2089     protected static class CacheRecordIndexChainedCallback implements CacheRecordIndexCallback
2090     {
2091         private final CacheRecordIndexCallback first;
2092         private final CacheRecordIndexCallback second;
2093
2094         public CacheRecordIndexChainedCallback(CacheRecordIndexCallback first,
2095                 CacheRecordIndexCallback second)
2096         {
2097             this.first = first;
2098             this.second = second;
2099         }
2100
2101         @Override
2102         public void cacheRecordIndexAvailable(int recordIndex)
2103         {
2104             first.cacheRecordIndexAvailable(recordIndex);
2105             second.cacheRecordIndexAvailable(recordIndex);
2106         }
2107     }
2108     
2109     protected void addCacheRecordIndexCallback(int recordIndex, CacheRecordIndexCallback callback)
2110     {
2111         CacheRecordIndexCallback existingCallback = cacheRecordIndexCallbacks.get(recordIndex);
2112         if (existingCallback == null)
2113         {
2114             cacheRecordIndexCallbacks.put(recordIndex, callback);
2115         }
2116         else
2117         {
2118             CacheRecordIndexChainedCallback chainedCallback = new CacheRecordIndexChainedCallback(
2119                     existingCallback, callback);
2120             cacheRecordIndexCallbacks.put(recordIndex, chainedCallback);
2121         }
2122     }
2123     
2124     public void setCacheRecordIndex(FillDatasetPosition position, byte evaluationType)
2125     {
2126         int recordIndex;
2127         switch (evaluationType)
2128         {
2129         case JRExpression.EVALUATION_OLD:
2130             recordIndex = previousCacheRecordIndex;
2131             break;
2132         default:
2133             recordIndex = currentCacheRecordIndex;
2134             break;
2135         }
2136         
2137         if (sortedDataSource && dataRecorder != null)
2138         {
2139             // when recording a sorted data source, the record indexes are computed at the end
2140             // and we need to store a callback
2141             FillDatasetPositionRecordIndexCallback callback = new FillDatasetPositionRecordIndexCallback(position);
2142             addCacheRecordIndexCallback(recordIndex, callback);
2143             
2144             if (log.isDebugEnabled())
2145             {
2146                 log.debug("registered cache callback for index " + recordIndex);
2147             }
2148         }
2149         else
2150         {
2151             FillDatasetPositionRecordIndexCallback.setRecordIndex(position, recordIndex);
2152         }
2153     }
2154 }
2155