1
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
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
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
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