Some import reorganizing by Eclipse
[online-glom:gwt-glom.git] / src / main / java / org / glom / web / server / ReportGenerator.java
1 /*
2  * Copyright (C) 2012 Openismus GmbH
3  *
4  * This file is part of GWT-Glom.
5  *
6  * GWT-Glom is free software: you can redistribute it and/or modify it
7  * under the terms of the GNU Lesser General Public License as published by the
8  * Free Software Foundation, either version 3 of the License, or (at your
9  * option) any later version.
10  *
11  * GWT-Glom is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
14  * for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with GWT-Glom.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 package org.glom.web.server;
21
22 import java.io.ByteArrayOutputStream;
23 import java.sql.Connection;
24 import java.text.DateFormat;
25 import java.text.DecimalFormat;
26 import java.text.SimpleDateFormat;
27 import java.util.ArrayList;
28 import java.util.HashMap;
29 import java.util.List;
30 import java.util.Locale;
31
32 import net.sf.jasperreports.engine.JRException;
33 import net.sf.jasperreports.engine.JRExporterParameter;
34 import net.sf.jasperreports.engine.JasperCompileManager;
35 import net.sf.jasperreports.engine.JasperFillManager;
36 import net.sf.jasperreports.engine.JasperPrint;
37 import net.sf.jasperreports.engine.JasperReport;
38 import net.sf.jasperreports.engine.design.JRDesignBand;
39 import net.sf.jasperreports.engine.design.JRDesignExpression;
40 import net.sf.jasperreports.engine.design.JRDesignField;
41 import net.sf.jasperreports.engine.design.JRDesignGroup;
42 import net.sf.jasperreports.engine.design.JRDesignLine;
43 import net.sf.jasperreports.engine.design.JRDesignQuery;
44 import net.sf.jasperreports.engine.design.JRDesignSection;
45 import net.sf.jasperreports.engine.design.JRDesignStaticText;
46 import net.sf.jasperreports.engine.design.JRDesignStyle;
47 import net.sf.jasperreports.engine.design.JRDesignTextField;
48 import net.sf.jasperreports.engine.design.JasperDesign;
49 import net.sf.jasperreports.engine.export.JRHtmlExporterParameter;
50 import net.sf.jasperreports.engine.export.JRXhtmlExporter;
51
52 import org.apache.commons.lang3.StringUtils;
53 import org.glom.web.shared.TypedDataItem;
54 import org.glom.web.shared.libglom.Document;
55 import org.glom.web.shared.libglom.Field.GlomFieldType;
56 import org.glom.web.shared.libglom.NumericFormat;
57 import org.glom.web.shared.libglom.Report;
58 import org.glom.web.shared.libglom.layout.Formatting;
59 import org.glom.web.shared.libglom.layout.LayoutGroup;
60 import org.glom.web.shared.libglom.layout.LayoutItem;
61 import org.glom.web.shared.libglom.layout.LayoutItemField;
62 import org.glom.web.shared.libglom.layout.SortClause;
63 import org.glom.web.shared.libglom.layout.UsesRelationship;
64 import org.glom.web.shared.libglom.layout.reportparts.LayoutItemGroupBy;
65 import org.glom.web.shared.libglom.layout.reportparts.LayoutItemVerticalGroup;
66 import org.jooq.Condition;
67
68 /**
69  * @author Murray Cumming <murrayc@openimus.com>
70  * 
71  */
72 public class ReportGenerator {
73
74         private class Position {
75                 public Position(final int x, final int y) {
76                         this.x = x;
77                         this.y = y;
78                 }
79
80                 public Position(final Position pos) {
81                         this.x = pos.x;
82                         this.y = pos.y;
83                 }
84
85                 public int x = 0;
86                 public int y = 0;
87         }
88
89         final int height = 30; // Points, as specified later.
90         // An arbitrary width, because we must specify _some_ width:
91         final int width = 100; // Points, as specified later.
92
93         List<LayoutItemField> fieldsToGet = new ArrayList<LayoutItemField>();
94         SortClause sortClause = new SortClause();
95         String localeID;
96
97         final JasperDesign design = new JasperDesign();
98         JRDesignStyle titleStyle = new JRDesignStyle();
99         JRDesignStyle normalStyle = new JRDesignStyle();
100         JRDesignStyle fieldTitleStyle = new JRDesignStyle();
101
102         ReportGenerator(final String localeID) {
103                 this.localeID = StringUtils.defaultString(localeID);
104         }
105
106         /**
107          */
108         public String generateReport(final Document glomDocument, final String tableName, final Report report,
109                         final Connection connection, final String quickFind) {
110
111                 final LayoutGroup layout_group = report.get_layout_group();
112
113                 design.setName(report.get_title(localeID)); // TODO: Actually, we want the title.
114
115                 titleStyle.setName("Sans_Title");
116                 titleStyle.setFontName("DejaVu Sans");
117                 titleStyle.setFontSize(24);
118                 normalStyle.setName("Sans_Normal");
119                 normalStyle.setDefault(true);
120                 normalStyle.setFontName("DejaVu Sans");
121                 normalStyle.setFontSize(12);
122                 normalStyle.setBlankWhenNull(true); // Avoid "null" appearing in reports.
123                 fieldTitleStyle.setName("Sans_Bold");
124                 fieldTitleStyle.setFontName("DejaVu Sans");
125                 fieldTitleStyle.setFontSize(12);
126                 fieldTitleStyle.setBold(true);
127                 fieldTitleStyle.setBlankWhenNull(true); // Avoid "null" appearing in reports when this is used for a GroupBy
128                                                                                                 // title.
129                 try {
130                         design.addStyle(titleStyle);
131                         design.addStyle(normalStyle);
132                         design.addStyle(fieldTitleStyle);
133                 } catch (final JRException ex) {
134                         // TODO Auto-generated catch block
135                         ex.printStackTrace();
136                 }
137
138                 final JRDesignBand titleBand = new JRDesignBand();
139                 titleBand.setHeight(height);
140                 final JRDesignStaticText staticTitle = new JRDesignStaticText();
141                 staticTitle.setText(report.get_title(localeID));
142                 staticTitle.setY(0);
143                 staticTitle.setX(0);
144                 staticTitle.setWidth(width * 5); // No data will be shown without this.
145                 // staticTitle.setStretchWithOverflow(true);
146                 staticTitle.setHeight(height); // We must specify _some_ height.
147                 staticTitle.setStyle(titleStyle);
148                 titleBand.addElement(staticTitle);
149                 design.setTitle(titleBand);
150
151                 final JRDesignBand detailBand = new JRDesignBand();
152                 detailBand.setHeight(height + 20);
153
154                 final JRDesignBand headerBand = new JRDesignBand();
155                 headerBand.setHeight(height + 20);
156
157                 fieldsToGet = new ArrayList<LayoutItemField>();
158                 final int x = 0;
159                 addGroupToReport(layout_group, detailBand, x, headerBand, 0);
160
161                 design.setColumnHeader(headerBand);
162                 ((JRDesignSection) design.getDetailSection()).addBand(detailBand);
163
164                 // Later versions of libglom actually return an empty SqlExpr when quickFindValue is empty,
165                 // but let's be sure:
166                 Condition whereClause = null;
167                 if (!StringUtils.isEmpty(quickFind)) {
168                         final TypedDataItem quickFindValue = new TypedDataItem();
169                         quickFindValue.setText(quickFind);
170                         whereClause = SqlUtils.get_find_where_clause_quick(glomDocument, tableName, quickFindValue);
171                 }
172
173                 String sqlQuery = "";
174                 if (!fieldsToGet.isEmpty()) {
175                         sqlQuery = SqlUtils.build_sql_select_with_where_clause(connection, tableName, fieldsToGet, whereClause,
176                                         sortClause);
177                 } else {
178                         Log.info("generateReport(): fieldsToGet is empty.");
179                 }
180
181                 final JRDesignQuery query = new JRDesignQuery();
182                 query.setText(sqlQuery); // TODO: Extra sort clause to sort the rows within the groups.
183                 design.setQuery(query);
184
185                 JasperReport jasperreport;
186                 try {
187                         jasperreport = JasperCompileManager.compileReport(design);
188                 } catch (final JRException ex) {
189                         ex.printStackTrace();
190                         return "Failed to Generate HTML: compileReport() failed.";
191                 }
192
193                 JasperPrint print;
194                 try {
195                         final HashMap<String, Object> parameters = new HashMap<String, Object>();
196                         parameters.put("ReportTitle", report.get_title(localeID)); // TODO: Use the title, not the name.
197                         print = JasperFillManager.fillReport(jasperreport, parameters, connection);
198                 } catch (final JRException ex) {
199                         ex.printStackTrace();
200                         return "Failed to Generate HTML: fillReport() failed.";
201                 }
202
203                 final ByteArrayOutputStream output = new ByteArrayOutputStream();
204
205                 // We use this because there is no JasperExportManager.exportReportToHtmlStream() method.
206                 // JasperExportManager.exportReportToXmlStream(print, output);
207                 try {
208                         final JRXhtmlExporter exporter = new JRXhtmlExporter();
209                         exporter.setParameter(JRExporterParameter.JASPER_PRINT, print);
210                         exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, output);
211
212                         // Use points instead of pixels for sizes, because pixels are silly
213                         // in HTML:
214                         exporter.setParameter(JRHtmlExporterParameter.SIZE_UNIT, "pt");
215
216                         // Avoid the page breaks, because they do not make sense for HTML:
217                         // TODO: These do not seem to preven the page break gap.
218                         exporter.setParameter(JRHtmlExporterParameter.IS_REMOVE_EMPTY_SPACE_BETWEEN_ROWS, Boolean.TRUE);
219                         exporter.setParameter(JRHtmlExporterParameter.BETWEEN_PAGES_HTML, "");
220
221                         exporter.exportReport();
222                 } catch (final JRException ex) {
223                         ex.printStackTrace();
224                         return "Failed to Generate HTML: exportReport() failed.";
225                 }
226
227                 // System.out.print(output.toString() + "\n");
228                 final String html = output.toString();
229
230                 // This does not work because jasperReports does not put individual rows in separate table rows.
231                 // jasperReports just puts the whole thing in one table row.
232                 // Remove the arbitrary width and height that JasperReports forced us to specify:
233                 // html = html.replaceAll("position:absolute;", "");
234                 // html = html.replaceAll("top:(\\d*)pt;", "");
235                 // html = html.replaceAll("left:(\\d*)pt;", "");
236                 // html = html.replaceAll("height:(\\d*)pt;", "");
237                 // html = html.replaceAll("width:(\\d*)pt;", "");
238                 // html = html.replaceAll("overflow: hidden;", "");
239
240                 return html;
241         }
242
243         /**
244          * A vertical group lays the fields out vertically instead of horizontally, with titles to the left.
245          * 
246          * @param layout_group
247          * @param parentBand
248          * @param x
249          * @param fieldTitlesY
250          *            TODO
251          * @param height
252          */
253         private Position addVerticalGroupToReport(final LayoutItemVerticalGroup layout_group,
254                         final JRDesignBand parentBand, final Position pos) {
255                 Position pos_result = new Position(pos);
256
257                 final List<LayoutItem> layoutItemsVec = layout_group.get_items();
258                 final int numItems = Utils.safeLongToInt(layoutItemsVec.size());
259                 for (int i = 0; i < numItems; i++) {
260                         final LayoutItem libglomLayoutItem = layoutItemsVec.get(i);
261
262                         if (libglomLayoutItem instanceof LayoutItemField) {
263                                 final LayoutItemField libglomLayoutItemField = (LayoutItemField) libglomLayoutItem;
264                                 pos_result = addFieldToDetailBandVertical(parentBand, pos_result, libglomLayoutItemField);
265                                 pos_result.x = pos.x;
266                         } else {
267
268                                 // TODO: Handle other item types.
269
270                                 // Recurse into sub-groups:
271                                 // TODO: x = addGroupToReport(libglomLayoutGroup, parentBand, x, fieldTitlesBand, thisFieldTitlesY);
272                         }
273                 }
274
275                 pos_result.x += width * 2;
276                 return pos_result;
277         }
278
279         /**
280          * @param layout_group
281          * @param parentBand
282          * @param x
283          * @param fieldTitlesY
284          *            TODO
285          * @param height
286          */
287         private Position addGroupToReport(final LayoutGroup layout_group, final JRDesignBand parentBand, final int x,
288                         final JRDesignBand headerBand, final int fieldTitlesY) {
289
290                 Position pos_result = new Position(x, 0);
291
292                 /**
293                  * * If this is a vertical group then we will lay the fields out vertically instead of horizontally.
294                  */
295                 if (layout_group instanceof LayoutItemVerticalGroup) {
296                         final LayoutItemVerticalGroup verticalGroup = (LayoutItemVerticalGroup) layout_group;
297                         return addVerticalGroupToReport(verticalGroup, parentBand, pos_result);
298                 }
299
300                 // Where we put the field titles depends on whether we are in a group-by:
301                 JRDesignBand fieldTitlesBand = headerBand;
302                 int thisFieldTitlesY = fieldTitlesY; // If they are in a group title then they must be lower.
303
304                 final List<LayoutItem> layoutItemsVec = layout_group.get_items();
305                 final int numItems = Utils.safeLongToInt(layoutItemsVec.size());
306                 for (int i = 0; i < numItems; i++) {
307                         final LayoutItem libglomLayoutItem = layoutItemsVec.get(i);
308
309                         if (libglomLayoutItem instanceof LayoutItemField) {
310                                 final LayoutItemField libglomLayoutItemField = (LayoutItemField) libglomLayoutItem;
311                                 pos_result = addFieldToDetailBand(parentBand, headerBand, pos_result.x, libglomLayoutItemField,
312                                                 thisFieldTitlesY, pos_result.y);
313                         } else if (libglomLayoutItem instanceof LayoutGroup) {
314                                 final LayoutGroup libglomLayoutGroup = (LayoutGroup) libglomLayoutItem;
315
316                                 if (libglomLayoutGroup instanceof LayoutItemGroupBy) {
317                                         final LayoutItemGroupBy libglomGroupBy = (LayoutItemGroupBy) libglomLayoutGroup;
318                                         final LayoutItemField fieldGroupBy = libglomGroupBy.get_field_group_by();
319                                         if (fieldGroupBy == null)
320                                                 continue;
321
322                                         final String fieldName = addField(fieldGroupBy);
323                                         if (StringUtils.isEmpty(fieldName)) {
324                                                 continue;
325                                         }
326
327                                         // We must sort by the group field,
328                                         // so that JasperReports can start a new group when its value changes.
329                                         // Note that this is not like a SQL GROUP BY.
330                                         final SortClause.SortField pair = new SortClause.SortField();
331                                         pair.field = fieldGroupBy;
332                                         pair.ascending = true;
333                                         sortClause.add(pair);
334
335                                         final JRDesignGroup group = new JRDesignGroup();
336                                         group.setName(fieldName);
337
338                                         // Show the field value:
339                                         final JRDesignExpression expression = createFieldExpression(fieldGroupBy);
340                                         group.setExpression(expression);
341
342                                         try {
343                                                 design.addGroup(group);
344                                         } catch (final JRException e) {
345                                                 // TODO Auto-generated catch block
346                                                 e.printStackTrace();
347                                         }
348
349                                         // Show the group-by field:
350                                         final JRDesignBand groupBand = new JRDesignBand();
351
352                                         // TODO: Use height instead of height*2 if there are no child fields,
353                                         // for instance if the only child is a sub group-by.
354                                         groupBand.setHeight(height * 2); // Enough height for the title and the field titles.
355                                         ((JRDesignSection) group.getGroupHeaderSection()).addBand(groupBand);
356
357                                         // Put the field titles inside the group-by instead of just at the top of the page.
358                                         // (or instead of just in the parent group-by):
359                                         fieldTitlesBand = groupBand;
360                                         thisFieldTitlesY = height;
361
362                                         /*
363                                          * final JRDesignBand footerBand = new JRDesignBand(); footerBand.setHeight(height);
364                                          * ((JRDesignSection) group.getGroupFooterSection()).addBand(footerBand);
365                                          */
366
367                                         int groupX = addFieldToGroupBand(groupBand, x, fieldGroupBy);
368
369                                         // Show the secondary fields:
370                                         final LayoutGroup groupSecondaries = libglomGroupBy.get_secondary_fields();
371                                         if (groupSecondaries != null)
372                                                 groupX = addSecondaryFieldsToGroupBand(groupSecondaries, groupBand, groupX);
373
374                                         final JRDesignLine line = new JRDesignLine();
375                                         final int lineheight = 1;
376                                         line.setX(0);
377
378                                         // TODO: Automatically place it below the text, though that needs us to know how much height the
379                                         // text really needs.
380                                         line.setY(height - 15);
381
382                                         // TODO: Make it as wide as needed by the details band.
383                                         line.setWidth(groupX);
384                                         line.setHeight(lineheight);
385                                         groupBand.addElement(line);
386                                 }
387
388                                 // Recurse into sub-groups:
389                                 pos_result = addGroupToReport(libglomLayoutGroup, parentBand, pos_result.x, fieldTitlesBand,
390                                                 thisFieldTitlesY);
391                         }
392                 }
393
394                 return pos_result;
395         }
396
397         private int addSecondaryFieldsToGroupBand(final LayoutGroup layout_group, final JRDesignBand groupBand, int x) {
398                 final List<LayoutItem> layoutItemsVec = layout_group.get_items();
399                 final int numItems = Utils.safeLongToInt(layoutItemsVec.size());
400                 for (int i = 0; i < numItems; i++) {
401                         final LayoutItem libglomLayoutItem = layoutItemsVec.get(i);
402
403                         if (libglomLayoutItem instanceof LayoutItemField) {
404                                 final LayoutItemField libglomLayoutItemField = (LayoutItemField) libglomLayoutItem;
405                                 x = addFieldToGroupBand(groupBand, x, libglomLayoutItemField);
406                         } else if (libglomLayoutItem instanceof LayoutGroup) {
407                                 final LayoutGroup libglomLayoutGroup = (LayoutGroup) libglomLayoutItem;
408
409                                 // We do not expect LayoutItem_GroupBy in the secondary fields:
410                                 // final LayoutItem_GroupBy libglomGroupBy = LayoutItem_GroupBy.cast_dynamic(libglomLayoutGroup);
411
412                                 // Recurse into sub-groups:
413                                 x = addSecondaryFieldsToGroupBand(libglomLayoutGroup, groupBand, x);
414                         }
415                 }
416
417                 return x;
418         }
419
420         /**
421          * @param libglomLayoutItemField
422          * @return
423          */
424         private JRDesignExpression createFieldExpression(final LayoutItemField libglomLayoutItemField) {
425                 final JRDesignExpression expression = new JRDesignExpression();
426
427                 final String fieldName = libglomLayoutItemField.get_name(); // TODO: Is this enough for related fields?
428
429                 // TODO: Where is this format documented?
430                 expression.setText("$F{" + fieldName + "}");
431                 return expression;
432         }
433
434         /**
435          * @param parentBand
436          * @param x
437          * @param libglomLayoutItemField
438          * @param fieldTitlesY
439          *            TODO
440          * @return
441          */
442         private Position addFieldToDetailBand(final JRDesignBand parentBand, final JRDesignBand headerBand, final int x,
443                         final LayoutItemField libglomLayoutItemField, final int fieldTitlesY, final int fieldY) {
444                 addField(libglomLayoutItemField);
445
446                 // Show the field title:
447                 final JRDesignStaticText textFieldColumn = createFieldTitleElement(new Position(x, fieldTitlesY),
448                                 libglomLayoutItemField, false);
449                 textFieldColumn.setStyle(fieldTitleStyle);
450                 headerBand.addElement(textFieldColumn);
451
452                 // Show an instance of the field (the field value):
453                 final JRDesignTextField textField = createFieldValueElement(new Position(x, 0), libglomLayoutItemField);
454                 textField.setStyle(normalStyle);
455                 parentBand.addElement(textField);
456
457                 return new Position(x + width, 0);
458         }
459
460         /**
461          * @param parentBand
462          * @param x
463          * @param libglomLayoutItemField
464          * @param fieldTitlesY
465          *            TODO
466          * @return
467          */
468         private Position addFieldToDetailBandVertical(final JRDesignBand parentBand, final Position pos,
469                         final LayoutItemField libglomLayoutItemField) {
470                 addField(libglomLayoutItemField);
471
472                 final Position pos_result = new Position(pos);
473
474                 // Make the band high enough if necessary:
475                 if (parentBand.getHeight() < (pos_result.y + height))
476                         parentBand.setHeight(pos_result.y + height + 20);
477
478                 // Show the field title:
479                 final JRDesignStaticText textFieldColumn = createFieldTitleElement(pos_result, libglomLayoutItemField, true);
480                 textFieldColumn.setStyle(fieldTitleStyle);
481                 parentBand.addElement(textFieldColumn);
482                 pos_result.x += width;
483
484                 // Show an instance of the field (the field value):
485                 final JRDesignTextField textField = createFieldValueElement(pos_result, libglomLayoutItemField);
486                 textField.setStyle(normalStyle);
487                 parentBand.addElement(textField);
488
489                 pos_result.x += width;
490
491                 pos_result.y += height;
492
493                 return pos_result;
494         }
495
496         private int addFieldToGroupBand(final JRDesignBand parentBand, final int x,
497                         final LayoutItemField libglomLayoutItemField) {
498                 addField(libglomLayoutItemField);
499
500                 final Position pos = new Position(x, 0);
501
502                 // Show the field title:
503                 final JRDesignStaticText textFieldColumn = createFieldTitleElement(pos, libglomLayoutItemField, true);
504
505                 // Instead, the field value will be bold, because here it is like a title.
506                 textFieldColumn.setStyle(normalStyle);
507
508                 parentBand.addElement(textFieldColumn);
509                 pos.x += width;
510
511                 // Show an instance of the field (the field value):
512                 final JRDesignTextField textField = createFieldValueElement(pos, libglomLayoutItemField);
513                 parentBand.addElement(textField);
514                 textField.setStyle(fieldTitleStyle);
515
516                 pos.x += width;
517                 return pos.x;
518         }
519
520         /**
521          * @param x
522          * @param libglomLayoutItemField
523          * @return
524          */
525         private JRDesignTextField createFieldValueElement(final Position pos, final LayoutItemField libglomLayoutItemField) {
526                 final JRDesignTextField textField = new JRDesignTextField();
527
528                 // Make sure this field starts at the right of the previous field,
529                 // because JasperReports uses absolute positioning.
530                 textField.setY(pos.y);
531                 textField.setX(pos.x);
532                 textField.setWidth(width); // No data will be shown without this.
533
534                 // This only stretches vertically, but that is better than
535                 // nothing.
536                 textField.setStretchWithOverflow(true);
537                 textField.setHeight(height); // We must specify _some_ height.
538
539                 final JRDesignExpression expression = createFieldExpression(libglomLayoutItemField);
540                 textField.setExpression(expression);
541
542                 if (libglomLayoutItemField.get_glom_type() == GlomFieldType.TYPE_NUMERIC) {
543                         // Numeric formatting:
544                         final Formatting formatting = libglomLayoutItemField.get_formatting_used();
545                         final NumericFormat numericFormat = formatting.getNumericFormat();
546
547                         final DecimalFormat format = new DecimalFormat();
548                         format.setMaximumFractionDigits(numericFormat.getDecimalPlaces());
549                         format.setGroupingUsed(numericFormat.getUseThousandsSeparator());
550
551                         // TODO: Use numericFormat.get_currency_symbol(), possibly via format.setCurrency().
552                         textField.setPattern(format.toPattern());
553                 } else if (libglomLayoutItemField.get_glom_type() == GlomFieldType.TYPE_DATE) {
554                         // Date formatting
555                         // TODO: Use a 4-digit-year short form, somehow.
556                         try // We use a try block because getDateInstance() is not guaranteed to return a SimpleDateFormat.
557                         {
558                                 final SimpleDateFormat format = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT,
559                                                 Locale.ROOT);
560
561                                 textField.setPattern(format.toPattern());
562                         } catch (final Exception ex) {
563                                 Log.info("ReportGenerator: The cast of SimpleDateFormat failed.");
564                         }
565                 } else if (libglomLayoutItemField.get_glom_type() == GlomFieldType.TYPE_TIME) {
566                         // Time formatting
567                         try // We use a try block because getDateInstance() is not guaranteed to return a SimpleDateFormat.
568                         {
569                                 final SimpleDateFormat format = (SimpleDateFormat) DateFormat.getTimeInstance(DateFormat.SHORT,
570                                                 Locale.ROOT);
571
572                                 textField.setPattern(format.toPattern());
573                         } catch (final Exception ex) {
574                                 Log.info("ReportGenerator: The cast of SimpleDateFormat failed.");
575                         }
576                 }
577
578                 return textField;
579         }
580
581         /**
582          * @param x
583          * @param y
584          *            TODO
585          * @param libglomLayoutItemField
586          * @return
587          */
588         private JRDesignStaticText createFieldTitleElement(final Position pos,
589                         final LayoutItemField libglomLayoutItemField, final boolean withColon) {
590                 final JRDesignStaticText textFieldColumn = new JRDesignStaticText();
591
592                 String title = StringUtils.defaultString(libglomLayoutItemField.get_title(this.localeID));
593
594                 // If the title is at the left, instead of above, we need a : to show that it's a title.
595                 if (withColon)
596                         title += ":";
597
598                 textFieldColumn.setText(title);
599                 textFieldColumn.setY(pos.y);
600                 textFieldColumn.setX(pos.x);
601                 textFieldColumn.setWidth(width); // No data will be shown without this.
602                 // textFieldColumn.setStretchWithOverflow(true);
603                 textFieldColumn.setHeight(height); // We must specify _some_ height.
604                 return textFieldColumn;
605         }
606
607         /**
608          * @param libglomLayoutItemField
609          * @return
610          */
611         private String addField(final LayoutItemField libglomLayoutItemField) {
612
613                 final String fieldName = libglomLayoutItemField.get_name(); // TODO: Is this enough for related fields?
614
615                 // Avoid an unnamed field:
616                 if (StringUtils.isEmpty(fieldName)) {
617                         Log.info("addField(): Ignoring LayoutItemField with no field name");
618                         return fieldName;
619                 }
620
621                 // Avoid adding duplicate fields,
622                 // because JasperDesign.addField() throws a "Duplicate declaration of field" exception.
623                 for (int i = 0; i < fieldsToGet.size(); ++i) {
624                         final UsesRelationship thisField = fieldsToGet.get(i);
625                         if (thisField.equals(libglomLayoutItemField))
626                                 return fieldName;
627                 }
628
629                 fieldsToGet.add(libglomLayoutItemField);
630
631                 // System.out.print("fieldName=" + fieldName + "\n");
632
633                 // Tell the JasperDesign about the database field that will be in the SQL query,
634                 // specified later:
635                 final JRDesignField field = new JRDesignField();
636                 field.setName(fieldName); // TODO: Related fields.
637
638                 final Class<?> klass = getClassTypeForGlomType(libglomLayoutItemField);
639                 if (klass != null) {
640                         field.setValueClass(klass);
641                 } else {
642                         Log.info("getClassTypeForGlomType() returned null");
643                 }
644
645                 try {
646                         design.addField(field);
647                 } catch (final JRException e2) {
648                         // TODO Auto-generated catch block
649                         e2.printStackTrace();
650                 }
651
652                 return fieldName;
653         }
654
655         /**
656          * @param libglomLayoutItemField
657          * @return
658          */
659         private Class<?> getClassTypeForGlomType(final LayoutItemField libglomLayoutItemField) {
660                 // Choose a suitable java class type for the SQL field:
661                 Class<?> klass = null;
662
663                 final GlomFieldType glom_type = libglomLayoutItemField.get_glom_type();
664                 switch (glom_type) {
665                 case TYPE_TEXT:
666                         klass = java.lang.String.class;
667                         break;
668                 case TYPE_BOOLEAN:
669                         klass = java.lang.Boolean.class;
670                         break;
671                 case TYPE_NUMERIC:
672                         klass = java.lang.Double.class;
673                         break;
674                 case TYPE_DATE:
675                         klass = java.util.Date.class;
676                         break;
677                 case TYPE_TIME:
678                         klass = java.sql.Time.class;
679                         break;
680                 case TYPE_IMAGE:
681                         klass = java.sql.Blob.class; // TODO: This does not work.
682                         break;
683                 case TYPE_INVALID:
684                         Log.info("getClassTypeForGlomType() returning null for TYPE_INVALID glom type. Field name="
685                                         + libglomLayoutItemField.get_layout_display_name());
686                 default:
687                         Log.info("getClassTypeForGlomType() returning null for glom type: " + glom_type + ". Field name="
688                                         + libglomLayoutItemField.get_layout_display_name());
689                         break;
690                 }
691                 return klass;
692         }
693 }