1 /*
2  * JasperReports - Free Java Reporting Library.
3  * Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved.
4  * http://www.jaspersoft.com
5  *
6  * Unless you have purchased a commercial license agreement from Jaspersoft,
7  * the following license terms apply:
8  *
9  * This program is part of JasperReports.
10  *
11  * JasperReports is free software: you can redistribute it and/or modify
12  * it under the terms of the GNU Lesser General Public License as published by
13  * the Free Software Foundation, either version 3 of the License, or
14  * (at your option) any later version.
15  *
16  * JasperReports is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU Lesser General Public License for more details.
20  *
21  * You should have received a copy of the GNU Lesser General Public License
22  * along with JasperReports. If not, see <http://www.gnu.org/licenses/>.
23  */

24 package net.sf.jasperreports.components.table;
25
26 import java.util.ArrayList;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.ListIterator;
30 import java.util.Set;
31
32 import net.sf.jasperreports.engine.JRDatasetRun;
33 import net.sf.jasperreports.engine.JRElement;
34 import net.sf.jasperreports.engine.JRExpressionCollector;
35 import net.sf.jasperreports.engine.JRGroup;
36 import net.sf.jasperreports.engine.base.JRBaseObjectFactory;
37 import net.sf.jasperreports.engine.component.Component;
38 import net.sf.jasperreports.engine.component.ComponentCompiler;
39 import net.sf.jasperreports.engine.design.JRDesignDataset;
40 import net.sf.jasperreports.engine.design.JRVerifier;
41
42 /**
43  * 
44  * 
45  * @author Lucian Chirita (lucianc@users.sourceforge.net)
46  */

47 public class TableCompiler implements ComponentCompiler
48 {
49
50     @Override
51     public void collectExpressions(Component component,
52             JRExpressionCollector collector)
53     {
54         TableComponent table = (TableComponent) component;
55         
56         JRDatasetRun datasetRun = table.getDatasetRun();
57         collector.collect(datasetRun);
58         
59         JRExpressionCollector datasetCollector = collector.getDatasetCollector(
60                 datasetRun.getDatasetName());
61         ColumnExpressionCollector columnCollector = new ColumnExpressionCollector(
62                 collector, datasetCollector);
63         columnCollector.collectColumns(table.getColumns());
64         
65         RowExpressionCollector rowCollector = new RowExpressionCollector(datasetCollector);
66         rowCollector.collectRow(table.getTableHeader());
67         rowCollector.collectRow(table.getTableFooter());
68         rowCollector.collectGroupRows(table.getGroupHeaders());
69         rowCollector.collectGroupRows(table.getGroupFooters());
70         rowCollector.collectRow(table.getColumnHeader());
71         rowCollector.collectRow(table.getColumnFooter());
72         rowCollector.collectRow(table.getDetail());
73         
74         columnCollector.collectCell(table.getNoData());
75     }
76
77     @Override
78     public Component toCompiledComponent(Component component,
79             JRBaseObjectFactory baseFactory)
80     {
81         TableComponent table = (TableComponent) component;
82         return new StandardTable(table, baseFactory);
83     }
84
85     @Override
86     public void verify(Component component, JRVerifier verifier)
87     {
88         TableComponent table = (TableComponent) component;
89
90         JRDatasetRun datasetRun = table.getDatasetRun();
91         if (datasetRun == null)
92         {
93             verifier.addBrokenRule("No list subdataset run set", table);
94         }
95         else
96         {
97             verifier.verifyDatasetRun(datasetRun);
98         }
99         
100         List<BaseColumn> columns = table.getColumns();
101         if (columns == null || columns.isEmpty())
102         {
103             verifier.addBrokenRule("No columns defined in the table", table);
104         }
105         else
106         {
107             if (!detectLoops(verifier, columns))
108             {
109                 String subdataset = datasetRun == null ? null : datasetRun.getDatasetName();
110                 if (subdataset != null)
111                 {
112                     verifier.pushSubdatasetContext(subdataset);
113                 }
114                 try
115                 {
116                     verifyColumns(table, verifier);
117                 }
118                 finally
119                 {
120                     if (subdataset != null)
121                     {
122                         verifier.popSubdatasetContext();
123                     }
124                 }
125                 
126                 verifyColumnHeights(table, verifier);
127             }
128         }
129     }
130
131     protected boolean detectLoops(JRVerifier verifier, List<BaseColumn> columns)
132     {
133         Set<BaseColumn> parents = new HashSet<BaseColumn>();
134         return detectLoops(verifier, columns, parents);
135     }
136
137     protected boolean detectLoops(final JRVerifier verifier, List<BaseColumn> columns, 
138             final Set<BaseColumn> parents)
139     {
140         boolean loop = false;
141         for (BaseColumn column : columns)
142         {
143             if (parents.contains(column))
144             {
145                 verifier.addBrokenRule("Table column is its own ancestor", column);
146                 loop = true;
147             }
148             else
149             {
150                 loop = column.visitColumn(new ColumnVisitor<Boolean>()
151                 {
152                     @Override
153                     public Boolean visitColumn(Column column)
154                     {
155                         return false;
156                     }
157
158                     @Override
159                     public Boolean visitColumnGroup(ColumnGroup columnGroup)
160                     {
161                         parents.add(columnGroup);
162                         boolean loopDetected = detectLoops(verifier, 
163                                 columnGroup.getColumns(), parents);
164                         parents.remove(columnGroup);
165                         return loopDetected;
166                     }
167                 });
168             }
169             
170             if (loop)
171             {
172                 break;
173             }
174         }
175         
176         return false;
177     }
178
179     protected void verifyColumns(final TableComponent table, final JRVerifier verifier)
180     {
181         ColumnVisitor<Void> columnVerifier = new ColumnVisitor<Void>()
182         {
183             @Override
184             public Void visitColumn(Column column)
185             {
186                 verifyColumn(table, column, verifier);
187                 return null;
188             }
189
190             @Override
191             public Void visitColumnGroup(ColumnGroup columnGroup)
192             {
193                 verifyBaseColumn(table, columnGroup, verifier);
194                 
195                 List<BaseColumn> subcolumns = columnGroup.getColumns();
196                 if (subcolumns == null || subcolumns.isEmpty())
197                 {
198                     verifier.addBrokenRule("No columns defined in column group", columnGroup);
199                 }
200                 else
201                 {
202                     int subwidth = 0;
203                     boolean subwidthValid = true;
204                     for (BaseColumn column : columnGroup.getColumns())
205                     {
206                         column.visitColumn(this);
207                         
208                         Integer width = column.getWidth();
209                         if (width == null)
210                         {
211                             subwidthValid = false;
212                         }
213                         else
214                         {
215                             subwidth += width;
216                         }
217                     }
218                     
219                     if (subwidthValid && columnGroup.getWidth() != null
220                             && columnGroup.getWidth() != subwidth)
221                     {
222                         verifier.addBrokenRule("Column group width " + columnGroup.getWidth() 
223                                 + " does not match sum of subcolumn widths " + subwidth, columnGroup);
224                     }
225                 }
226                 return null;
227             }
228         };
229         
230         for (BaseColumn column : table.getColumns())
231         {
232             column.visitColumn(columnVerifier);
233         }
234     }
235     
236     protected void verifyBaseColumn(TableComponent table, BaseColumn column, JRVerifier verifier)
237     {
238         Integer width = column.getWidth();
239         if (width == null)
240         {
241             verifier.addBrokenRule("Column width not set", column);
242         }
243         else if (width < 0)
244         {
245             verifier.addBrokenRule("Negative column width", column);
246         }
247         else
248         {
249             verifyCell(column.getTableHeader(), width, "table header", verifier);
250             verifyCell(column.getTableFooter(), width, "table footer", verifier);
251             verifyGroupCells(table, column.getGroupHeaders(), width, "group header", verifier);
252             verifyGroupCells(table, column.getGroupFooters(), width, "group footer", verifier);
253             verifyCell(column.getColumnHeader(), width, "column header", verifier);
254             verifyCell(column.getColumnFooter(), width, "column footer", verifier);
255         }
256     }
257     
258     protected void verifyGroupCells(TableComponent table, List<GroupCell> cells, int width, 
259             String cellName, JRVerifier verifier)
260     {
261         if (cells != null)
262         {
263             Set<String> groupNames = new HashSet<String>();
264             for (GroupCell groupCell : cells)
265             {
266                 String groupName = groupCell.getGroupName();
267                 if (groupName == null)
268                 {
269                     verifier.addBrokenRule("No group name set for table column group cell", groupCell);
270                 }
271                 else
272                 {
273                     if (!groupNames.add(groupName))
274                     {
275                         verifier.addBrokenRule("Duplicate " + cellName + for group \"" + groupName + "\""
276                                 groupCell);
277                     }
278                     
279                     JRDatasetRun datasetRun = table.getDatasetRun();
280                     if (datasetRun != null)
281                     {
282                         JRDesignDataset dataset = (JRDesignDataset) verifier.getReportDesign().getDatasetMap().get(
283                                 datasetRun.getDatasetName());
284                         if (dataset != null && dataset.getGroupsMap().get(groupName) == null)
285                         {
286                             verifier.addBrokenRule("No group named \"" + groupName 
287                                     + "\" found in subdataset " + datasetRun.getDatasetName(), 
288                                     groupCell);
289                         }
290                     }
291                 }
292                 
293                 verifyCell(groupCell.getCell(), width, cellName, verifier);
294             }
295         }
296     }
297     
298     protected void verifyCell(Cell cell, int width, String cellName, JRVerifier verifier)
299     {
300         if (cell == null)
301         {
302             return;
303         }
304         
305         if (cell.getRowSpan() != null && cell.getRowSpan() < 1)
306         {
307             verifier.addBrokenRule("Negative or zero cell row span", cell);
308         }
309         
310         Integer height = cell.getHeight();
311         if (height == null)
312         {
313             verifier.addBrokenRule("Cell height not set", cell);
314         }
315         else if (height < 0)
316         {
317             verifier.addBrokenRule("Negative cell height", cell);
318         }
319         else
320         {
321             JRElement[] elements = cell.getElements();
322             if (elements != null && elements.length > 0)
323             {
324                 int topPadding = cell.getLineBox().getTopPadding();
325                 int leftPadding = cell.getLineBox().getLeftPadding();
326                 int bottomPadding = cell.getLineBox().getBottomPadding();
327                 int rightPadding = cell.getLineBox().getRightPadding();
328
329                 int avlblWidth = width - leftPadding - rightPadding;
330                 int avlblHeight = height - topPadding - bottomPadding;
331                 
332                 for (JRElement element : elements)
333                 {
334                     verifier.verifyElement(element);
335                     
336                     if (element.getX() < 0 || element.getY() < 0)
337                     {
338                         verifier.addBrokenRule("Element must be placed at positive coordinates."
339                                 element);
340                     }
341                     
342                     if (element.getY() + element.getHeight() > avlblHeight)
343                     {
344                         verifier.addBrokenRule("Element reaches outside table " + cellName + " contents height: y = " 
345                                 + element.getY() + ", height = " + element.getHeight() 
346                                 + ", cell available height = " + avlblHeight + ".", element);
347                     }
348                     
349                     if (element.getX() + element.getWidth() > avlblWidth)
350                     {
351                         verifier.addBrokenRule("Element reaches outside table " + cellName + " contents width: x = " 
352                                 + element.getX() + ", width = " + element.getWidth() 
353                                 + ", cell available width = " + avlblWidth + ".", element);
354                     }
355                     
356                 }
357             }
358         }
359     }
360     
361     protected void verifyColumn(TableComponent table, Column column, JRVerifier verifier)
362     {
363         verifyBaseColumn(table, column, verifier);
364         
365         if (column.getWidth() != null)
366         {
367             Cell detailCell = column.getDetailCell();
368             verifyCell(detailCell, column.getWidth(), "detail", verifier);
369         }
370     }
371     
372     protected interface ColumnCellSelector
373     {
374         Cell getCell(Column column);
375         
376         Cell getCell(ColumnGroup group);
377         
378         String getCellName();
379     }
380     
381     protected void verifyColumnHeights(TableComponent table, 
382             JRVerifier verifier)
383     {
384         verifyColumnHeights(table, verifier, new BaseColumnCellSelector()
385         {
386             @Override
387             protected Cell getCell(BaseColumn column)
388             {
389                 return column.getTableHeader();
390             }
391
392             @Override
393             public String getCellName()
394             {
395                 return "table header";
396             }
397         });
398         
399         verifyColumnHeights(table, verifier, new BaseColumnCellSelector()
400         {
401             @Override
402             protected Cell getCell(BaseColumn column)
403             {
404                 return column.getTableFooter();
405             }
406
407             @Override
408             public String getCellName()
409             {
410                 return "table footer";
411             }
412         });
413         
414         JRDatasetRun datasetRun = table.getDatasetRun();
415         if (datasetRun != null)
416         {
417             JRDesignDataset dataset = (JRDesignDataset) verifier.getReportDesign().getDatasetMap().get(
418                     datasetRun.getDatasetName());
419             if (dataset != null)
420             {
421                 JRGroup[] groups = dataset.getGroups();
422                 if (groups != null)
423                 {
424                     for (int i = 0; i < groups.length; i++)
425                     {
426                         final String groupName = groups[i].getName();
427                         
428                         verifyColumnHeights(table, verifier, new BaseColumnCellSelector()
429                         {
430                             @Override
431                             protected Cell getCell(BaseColumn column)
432                             {
433                                 return column.getGroupHeader(groupName);
434                             }
435
436                             @Override
437                             public String getCellName()
438                             {
439                                 return "group " + groupName + " header";
440                             }
441                         });
442                         
443                         verifyColumnHeights(table, verifier, new BaseColumnCellSelector()
444                         {
445                             @Override
446                             protected Cell getCell(BaseColumn column)
447                             {
448                                 return column.getGroupFooter(groupName);
449                             }
450
451                             @Override
452                             public String getCellName()
453                             {
454                                 return "group " + groupName + " footer";
455                             }
456                         });
457                     }
458                 }
459             }
460         }
461         
462         verifyColumnHeights(table, verifier, new BaseColumnCellSelector()
463         {
464             @Override
465             protected Cell getCell(BaseColumn column)
466             {
467                 return column.getColumnHeader();
468             }
469
470             @Override
471             public String getCellName()
472             {
473                 return "column header";
474             }
475         });
476         
477         verifyColumnHeights(table, verifier, new BaseColumnCellSelector()
478         {
479             @Override
480             protected Cell getCell(BaseColumn column)
481             {
482                 return column.getColumnFooter();
483             }
484
485             @Override
486             public String getCellName()
487             {
488                 return "column footer";
489             }
490         });
491         
492         verifyColumnHeights(table, verifier, new ColumnCellSelector()
493         {
494             @Override
495             public Cell getCell(Column column)
496             {
497                 return column.getDetailCell();
498             }
499
500             @Override
501             public Cell getCell(ColumnGroup group)
502             {
503                 return null;
504             }
505             
506             @Override
507             public String getCellName()
508             {
509                 return "detail";
510             }
511         });        
512     }
513     
514     protected abstract class BaseColumnCellSelector implements ColumnCellSelector
515     {
516
517         @Override
518         public Cell getCell(Column column)
519         {
520             return getCell((BaseColumn) column);
521         }
522
523         @Override
524         public Cell getCell(ColumnGroup group)
525         {
526             return getCell((BaseColumn) group);
527         }
528         
529         protected abstract Cell getCell(BaseColumn column);
530     }
531     
532     protected void verifyColumnHeights(TableComponent table, 
533             JRVerifier verifier, 
534             final ColumnCellSelector cellSelector)
535     {
536         final List<List<Cell>> tableCellRows = new ArrayList<List<Cell>>();
537         
538         ColumnVisitor<Void> cellCollector = new ColumnVisitor<Void>()
539         {
540             int rowIdx = 0;
541             
542             protected List<Cell> getRow()
543             {
544                 int currentRowCount = tableCellRows.size();
545                 if (rowIdx >= currentRowCount)
546                 {
547                     for (int i = currentRowCount; i <= rowIdx; i++)
548                     {
549                         tableCellRows.add(new ArrayList<Cell>());
550                     }
551                 }
552                 return tableCellRows.get(rowIdx);
553             }
554             
555             @Override
556             public Void visitColumn(Column column)
557             {
558                 Cell cell = cellSelector.getCell(column);
559                 if (cell != null)
560                 {
561                     getRow().add(cell);
562                 }
563                 return null;
564             }
565
566             @Override
567             public Void visitColumnGroup(ColumnGroup columnGroup)
568             {
569                 Cell cell = cellSelector.getCell(columnGroup);
570                 if (cell != null)
571                 {
572                     getRow().add(cell);
573                 }
574                 
575                 int span = cell == null ? 0 : 1;
576                 if (cell != null && cell.getRowSpan() != null && cell.getRowSpan() > 1)
577                 {
578                     span = cell.getRowSpan();
579                 }
580                 
581                 rowIdx += span;
582                 for (BaseColumn subcolumn : columnGroup.getColumns())
583                 {
584                     subcolumn.visitColumn(this);
585                 }
586                 rowIdx -= span;
587                 
588                 return null;
589             }
590         };
591         
592         for (BaseColumn column : table.getColumns())
593         {
594             column.visitColumn(cellCollector);
595         }
596         
597         boolean validRowHeights = true;
598         
599         List<Integer> rowHeights = new ArrayList<Integer>(tableCellRows.size());
600         for (int rowIdx = 0; rowIdx < tableCellRows.size(); ++rowIdx)
601         {
602             Integer rowHeight = null;
603             // going back on rows in order to determine row height
604             int spanHeight = 0;
605             prevRowLoop:
606             for (int idx = rowIdx; idx >= 0; --idx)
607             {
608                 for (Cell cell : tableCellRows.get(idx))
609                 {
610                     int rowSpan = cell.getRowSpan() == null ? 1 : cell.getRowSpan();
611                     if (idx + rowSpan - 1 == rowIdx && cell.getHeight() != null)
612                     {
613                         rowHeight = cell.getHeight() - spanHeight;
614                         break prevRowLoop;
615                     }
616                 }
617                 
618                 if (rowIdx > 0)
619                 {
620                     spanHeight += rowHeights.get(rowIdx - 1);
621                 }
622             }
623             
624             if (rowHeight == null)
625             {
626                 verifier.addBrokenRule("Unable to determine " + cellSelector.getCellName() 
627                         + " row #" + (rowIdx + 1) + " height."
628                         table);
629                 validRowHeights = false;
630             }
631             else
632             {
633                 rowHeights.add(rowHeight);
634             }
635         }
636         
637         // don't do any more verifications if row heights could not be determined
638         if (validRowHeights)
639         {
640             for (ListIterator<List<Cell>> rowIt = tableCellRows.listIterator(); rowIt.hasNext();)
641             {
642                 List<Cell> row = rowIt.next();
643                 int rowIdx = rowIt.previousIndex();
644                 int rowHeight = rowHeights.get(rowIdx);
645                 
646                 for (Cell cell : row)
647                 {
648                     Integer rowSpan = cell.getRowSpan();
649                     Integer height = cell.getHeight();
650                     if ((rowSpan == null || rowSpan >= 1) 
651                             && height != null)
652                     {
653                         int span = rowSpan == null ? 1 : rowSpan;
654                         if (rowIdx + span > tableCellRows.size())
655                         {
656                             verifier.addBrokenRule("Row span of " + cellSelector.getCellName() 
657                                     + " exceeds number of rows", cell);
658                         }
659                         else
660                         {
661                             int spanHeight = rowHeight;
662                             for (int idx = 1; idx < span; ++idx)
663                             {
664                                 spanHeight += rowHeights.get(rowIdx + idx);
665                             }
666                             
667                             if (cell.getHeight() != spanHeight)
668                             {
669                                 verifier.addBrokenRule("Height " + cell.getHeight() + " of " + cellSelector.getCellName() 
670                                         + " does not match computed row height of " + spanHeight, cell);
671                             }
672                         }
673                     }
674                 }
675             }
676         }
677     }
678 }
679