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