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