Java tutorial
/* * JasperReports - Free Java Reporting Library. * Copyright (C) 2001 - 2019 TIBCO Software Inc. All rights reserved. * http://www.jaspersoft.com * * Unless you have purchased a commercial license agreement from Jaspersoft, * the following license terms apply: * * This program is part of JasperReports. * * JasperReports is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * JasperReports is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with JasperReports. If not, see <http://www.gnu.org/licenses/>. */ package net.sf.jasperreports.engine.export.tabulator; import java.awt.Color; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.NavigableSet; import java.util.SortedSet; import net.sf.jasperreports.engine.JRBoxContainer; import net.sf.jasperreports.engine.JRLineBox; import net.sf.jasperreports.engine.JROrigin; import net.sf.jasperreports.engine.JRPrintElement; import net.sf.jasperreports.engine.JRPrintFrame; import net.sf.jasperreports.engine.JRRuntimeException; import net.sf.jasperreports.engine.export.PrintElementIndex; import net.sf.jasperreports.engine.export.ExporterFilter; import net.sf.jasperreports.engine.type.BandTypeEnum; import net.sf.jasperreports.engine.type.ModeEnum; import net.sf.jasperreports.engine.util.Bounds; import net.sf.jasperreports.engine.util.JRBoxUtil; import net.sf.jasperreports.engine.util.Pair; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * @author Lucian Chirita (lucianc@users.sourceforge.net) */ public class Tabulator { private static final Log log = LogFactory.getLog(Tabulator.class); public static final String EXCEPTION_MESSAGE_KEY_DROPPING_PARENT_ERROR = "export.tabulator.dropping.parent.error"; private final ExporterFilter filter; private final List<? extends JRPrintElement> elements; private Table mainTable; private ParentCheck parentCheck = new ParentCheck(); private SpanRangeCheck spanRangeCheck = new SpanRangeCheck(); private SpanCheck spanCheck = new SpanCheck(); private CollapseCheck collapseCheck = new CollapseCheck(); private TableCellCreator tableCellCreator = new TableCellCreator(); public Tabulator(ExporterFilter filter, List<? extends JRPrintElement> elements) { this.filter = filter; this.elements = elements; this.mainTable = new Table(this); } public void tabulate() { // TODO lucianc force background as different layer layoutElements(elements, mainTable, null, null, 0, 0, null); } protected void layoutElements(List<? extends JRPrintElement> elementList, Table table, FrameCell parentCell, PrintElementIndex parentIndex, int xOffset, int yOffset, Bounds elementBounds) { if (log.isTraceEnabled()) { log.trace("laying out " + elements.size() + " elements for parent " + parentCell + " at offsets " + xOffset + ", " + yOffset); } // iterating the list in reverse order so that background band elements come last for (ListIterator<? extends JRPrintElement> it = elementList.listIterator(elementList.size()); it .hasPrevious();) { JRPrintElement element = it.previous(); if (filter != null && !filter.isToExport(element)) { if (log.isTraceEnabled()) { log.trace("element " + element.getUUID() + " skipped by filter " + element); } continue; } if (element.getWidth() <= 0 || element.getHeight() <= 0) { if (log.isDebugEnabled()) { log.debug("element " + element.getUUID() + " skipped, size " + element.getWidth() + ", " + element.getHeight()); } continue; } if (elementBounds != null && !elementBounds.contains(element.getX(), element.getX() + element.getWidth(), element.getY(), element.getY() + element.getHeight())) { if (log.isDebugEnabled()) { log.debug("element " + element.getUUID() + " at [" + element.getX() + "," + (element.getX() + element.getWidth()) + "),[" + element.getY() + "," + (element.getY() + element.getHeight()) + ") does not fit inside bounds " + elementBounds); } continue; } placeElement(table, parentCell, xOffset, yOffset, element, parentIndex, it.nextIndex(), true); } } protected boolean placeElement(Table table, FrameCell parentCell, int xOffset, int yOffset, JRPrintElement element, PrintElementIndex parentIndex, int elementIndex, boolean allowOverlap) { DimensionRange<Column> colRange = table.columns.getRange(element.getX() + xOffset, element.getX() + element.getWidth() + xOffset); DimensionRange<Row> rowRange = table.rows.getRange(element.getY() + yOffset, element.getY() + element.getHeight() + yOffset); if (log.isTraceEnabled()) { log.trace("placing element " + element.getUUID() + " at " + colRange.start + ", " + colRange.end + ", " + rowRange.start + ", " + rowRange.end); } boolean overlap = false; Bounds overlapBounds = new Bounds(colRange.start, colRange.end, rowRange.start, rowRange.end); JROrigin elementOrigin = element.getOrigin(); if (parentCell == null // top level element && elementOrigin != null && elementOrigin.getReportName() == null // master background element // TODO lucianc do something for subreport background bands as well && elementOrigin.getBandTypeValue() == BandTypeEnum.BACKGROUND) { // create a layer as big as the table for the master background band SortedSet<Column> userColumns = table.columns.getUserEntries(); SortedSet<Row> userRows = table.rows.getUserEntries(); // check if we have something in the table if (!userColumns.isEmpty() && !userRows.isEmpty()) { overlapBounds.grow(userColumns.first().startCoord, userColumns.last().endCoord, userRows.first().startCoord, userRows.last().endCoord); // TODO lucianc avoid the following cell overlap checks } } Bounds covered = null; Bounds originalBounds; overlapLoop: do { originalBounds = overlapBounds.cloneBounds(); if (rowRange.start != overlapBounds.getStartY() || rowRange.end != overlapBounds.getEndY()) { rowRange = table.rows.getRange(overlapBounds.getStartY(), overlapBounds.getEndY()); } if (colRange.start != overlapBounds.getStartX() || colRange.end != overlapBounds.getEndX()) { colRange = table.columns.getRange(overlapBounds.getStartX(), overlapBounds.getEndX()); } for (Row row : rowRange.rangeSet) { for (Column col : colRange.rangeSet) { if (covered != null && covered.contains(col.startCoord, col.endCoord, row.startCoord, row.endCoord)) { //we've been here before continue; } Cell cell = row.getCell(col); if (!canOverwrite(cell, parentCell)) { overlap = true; if (!allowOverlap) { break overlapLoop; } // TODO lucianc see if we can avoid some of these checks Cell overlapParentCell = overlapParentCell(cell, parentCell); Pair<Column, Column> colSpanRange = getColumnSpanRange(table, col, row, overlapParentCell); Pair<Row, Row> rowSpanRange = getRowSpanRange(table, col, row, overlapParentCell); if (log.isTraceEnabled()) { log.trace("found overlap with cell " + cell + ", overlap parent " + overlapParentCell + ", column span range " + colSpanRange.first().startCoord + " to " + colSpanRange.second().startCoord + ", row span range " + rowSpanRange.first().startCoord + " to " + rowSpanRange.second().startCoord); } overlapBounds.grow(colSpanRange.first().startCoord, colSpanRange.second().startCoord, rowSpanRange.first().startCoord, rowSpanRange.second().startCoord); } } } covered = originalBounds; } while (!originalBounds.equals(overlapBounds)); if (!overlap) { setElementCells(table, parentCell, xOffset, yOffset, element, parentIndex, elementIndex, colRange, rowRange); return true; } if (!allowOverlap) { return false; } placeOverlappedElement(table, parentCell, xOffset, yOffset, element, parentIndex, elementIndex, overlapBounds); return true; } protected void placeOverlappedElement(Table table, FrameCell parentCell, int xOffset, int yOffset, JRPrintElement element, PrintElementIndex parentIndex, int elementIndex, Bounds overlapBounds) { DimensionRange<Column> overlapColRange = table.columns.getRange(overlapBounds.getStartX(), overlapBounds.getEndX()); DimensionRange<Row> overlapRowRange = table.rows.getRange(overlapBounds.getStartY(), overlapBounds.getEndY()); DimensionRange<Column> layeredColRange = table.columns.addEntries(overlapColRange); DimensionRange<Row> layeredRowRange = table.rows.addEntries(overlapRowRange); // TODO lucianc expand existing layered cell if smaller than the current element boolean placed = false; Cell firstOverlapCell = overlapRowRange.floor.getCell(overlapColRange.floor); if (firstOverlapCell instanceof LayeredCell) { LayeredCell layeredCell = (LayeredCell) firstOverlapCell; // get the opposite corner cell Column lastCol = table.columns.getEntries().lower(layeredColRange.ceiling); Row lastRow = table.rows.getEntries().lower(layeredRowRange.ceiling); Cell lastCell = lastRow.getCell(lastCol); if (lastCell != null && (layeredCell.equals(lastCell) || layeredCell.accept(spanCheck, lastCell))) { placed = true; placeInLayeredCell(xOffset, yOffset, element, parentIndex, elementIndex, layeredCell, layeredColRange, layeredRowRange); } } if (!placed) { createLayeredCell(table, parentCell, xOffset, yOffset, element, parentIndex, elementIndex, layeredColRange, layeredRowRange); } } protected void placeInLayeredCell(int xOffset, int yOffset, JRPrintElement element, PrintElementIndex parentIndex, int elementIndex, LayeredCell layeredCell, DimensionRange<Column> layeredColRange, DimensionRange<Row> layeredRowRange) { // attempt to place it on the first layer Table firstLayer = layeredCell.getLayers().get(0); boolean placed = placeElement(firstLayer, null, xOffset - layeredColRange.start, yOffset - layeredRowRange.start, element, parentIndex, elementIndex, false); if (placed) { if (log.isTraceEnabled()) { log.trace("placed element on first layer of " + layeredCell); } } else { // create a new layer createOverlappedLayer(xOffset, yOffset, layeredCell, element, parentIndex, elementIndex, layeredColRange, layeredRowRange); } } protected void createLayeredCell(Table table, FrameCell parentCell, int xOffset, int yOffset, JRPrintElement element, PrintElementIndex parentIndex, int elementIndex, DimensionRange<Column> layeredColRange, DimensionRange<Row> layeredRowRange) { if (log.isDebugEnabled()) { log.debug("creating layered cell at " + layeredColRange + ", " + layeredRowRange); } LayeredCell layeredCell = new LayeredCell(parentCell); Table firstLayer = new Table(this); layeredCell.addLayer(firstLayer); moveCellsToLayerTable(parentCell, firstLayer, layeredColRange, layeredRowRange); setElementCells(layeredColRange, layeredRowRange, layeredCell); collapseSpanColumns(table, layeredColRange); collapseSpanRows(table, layeredRowRange); createOverlappedLayer(xOffset, yOffset, layeredCell, element, parentIndex, elementIndex, layeredColRange, layeredRowRange); } protected void createOverlappedLayer(int xOffset, int yOffset, LayeredCell layeredCell, JRPrintElement element, PrintElementIndex parentIndex, int elementIndex, DimensionRange<Column> layeredColRange, DimensionRange<Row> layeredRowRange) { Table overlappedLayer = new Table(this); layeredCell.addLayer(overlappedLayer); overlappedLayer.columns.addEntry(0); //adding the final range entry so that narrower layers do not stretch to 100% in HTML overlappedLayer.columns.addEntry(layeredColRange.end - layeredColRange.start); overlappedLayer.rows.addEntry(0); //not adding final range entry for rows for now, not needed at the moment int layerXOffset = xOffset - layeredColRange.start; int layerYOffset = yOffset - layeredRowRange.start; DimensionRange<Column> layerColRange = overlappedLayer.columns.getRange(element.getX() + layerXOffset, element.getX() + element.getWidth() + layerXOffset); DimensionRange<Row> layerRowRange = overlappedLayer.rows.getRange(element.getY() + layerYOffset, element.getY() + element.getHeight() + layerYOffset); setElementCells(overlappedLayer, null, layerXOffset, layerYOffset, element, parentIndex, elementIndex, layerColRange, layerRowRange); } protected void setElementCells(Table table, FrameCell parentCell, int xOffset, int yOffset, JRPrintElement element, PrintElementIndex parentIndex, int elementIndex, DimensionRange<Column> colRange, DimensionRange<Row> rowRange) { DimensionRange<Column> elementColRange = table.columns.addEntries(colRange); DimensionRange<Row> elementRowRange = table.rows.addEntries(rowRange); if (element instanceof JRPrintFrame) { JRPrintFrame frame = (JRPrintFrame) element; FrameCell frameCell = new FrameCell(parentCell, parentIndex, elementIndex); setElementCells(elementColRange, elementRowRange, frameCell); // go deep in the frame PrintElementIndex frameIndex = new PrintElementIndex(parentIndex, elementIndex); JRLineBox box = frame.getLineBox(); layoutElements(frame.getElements(), table, frameCell, frameIndex, xOffset + frame.getX() + box.getLeftPadding(), yOffset + frame.getY() + box.getTopPadding(), new Bounds(0, frame.getWidth() - box.getLeftPadding() - box.getRightPadding(), 0, frame.getHeight() - box.getTopPadding() - box.getBottomPadding())); } else { ElementCell elementCell = new ElementCell(parentCell, parentIndex, elementIndex); setElementCells(elementColRange, elementRowRange, elementCell); } } protected boolean canOverwrite(Cell existingCell, FrameCell currentParent) { if (existingCell == null) { return true; } if (existingCell instanceof FrameCell) { return isParent((FrameCell) existingCell, currentParent); } return false; } protected boolean isParent(FrameCell parent, FrameCell child) { boolean foundAncestor = false; FrameCell ancestor = child; while (ancestor != null) { if (ancestor.equals(parent)) { foundAncestor = true; break; } ancestor = ancestor.getParent(); } return foundAncestor; } protected Cell overlapParentCell(Cell existingCell, FrameCell currentParent) { LinkedList<FrameCell> existingParents = new LinkedList<FrameCell>(); for (FrameCell parent = existingCell.getParent(); parent != null; parent = parent.getParent()) { existingParents.addFirst(parent); } LinkedList<FrameCell> currentParents = new LinkedList<FrameCell>(); for (FrameCell parent = currentParent; parent != null; parent = parent.getParent()) { currentParents.addFirst(parent); } Iterator<FrameCell> existingIt = existingParents.iterator(); Iterator<FrameCell> currentIt = currentParents.iterator(); while (existingIt.hasNext()) { FrameCell existingParent = existingIt.next(); FrameCell currentCell = currentIt.hasNext() ? currentIt.next() : null; if (currentCell == null || !existingParent.equals(currentCell)) { return existingParent; } } return existingCell; } protected Pair<Column, Column> getColumnSpanRange(Table table, Column col, Row row, Cell spanned) { Column startCol = col; for (Column headCol : table.columns.getEntries().headSet(col, false).descendingSet()) { Cell headCell = row.getCell(headCol); if (headCell == null || !spanned.accept(spanRangeCheck, headCell)) { break; } startCol = headCol; } Column endCol = null; for (Column tailCol : table.columns.getEntries().tailSet(col)) { endCol = tailCol; Cell tailCell = row.getCell(tailCol); if (tailCell == null || !spanned.accept(spanRangeCheck, tailCell)) { break; } } assert endCol != null; assert startCol.startCoord < endCol.startCoord; return new Pair<Column, Column>(startCol, endCol); } protected Pair<Row, Row> getRowSpanRange(Table table, Column col, Row row, Cell spanned) { Row startRow = row; for (Row headRow : table.rows.getEntries().headSet(row, false).descendingSet()) { Cell headCell = headRow.getCell(col); if (headCell == null || !spanned.accept(spanRangeCheck, headCell)) { break; } startRow = headRow; } Row endRow = null; for (Row tailRow : table.rows.getEntries().tailSet(row)) { endRow = tailRow; Cell tailCell = tailRow.getCell(col); if (tailCell == null || !spanned.accept(spanRangeCheck, tailCell)) { break; } } assert endRow != null; assert startRow.startCoord < endRow.startCoord; return new Pair<Row, Row>(startRow, endRow); } protected void moveCellsToLayerTable(FrameCell parentCell, Table layerTable, DimensionRange<Column> colRange, DimensionRange<Row> rowRange) { layerTable.columns.addEntry(0, colRange.end - colRange.start); layerTable.rows.addEntry(0, rowRange.end - rowRange.start); ParentDrop parentDrop = new ParentDrop(); for (Row row : rowRange.rangeSet) { for (Column column : colRange.rangeSet) { Cell cell = row.getCell(column); // drop parentCell from the cell's parent Cell layerCell = cell == null ? null : cell.accept(parentDrop, parentCell); if (layerCell != null) { // determine how much the original cell spans Column lastColSpan = getColumnCellSpan(colRange.rangeSet, column, row, cell).lastEntry; Row lastRowSpan = getRowCellSpan(rowRange.rangeSet, column, row, cell).lastEntry; // create ranges in the layer table DimensionRange<Column> cellColRange = layerTable.columns .getRange(column.startCoord - colRange.start, lastColSpan.endCoord - colRange.start); DimensionRange<Row> cellRowRange = layerTable.rows.getRange(row.startCoord - rowRange.start, lastRowSpan.endCoord - rowRange.start); // add entries for the ranges and set the cell in the layer table DimensionRange<Column> cellFinalColRange = layerTable.columns.addEntries(cellColRange); DimensionRange<Row> cellFinalRowRange = layerTable.rows.addEntries(cellRowRange); setElementCells(cellFinalColRange, cellFinalRowRange, layerCell); } } } } protected void collapseSpanColumns(Table table, DimensionRange<Column> range) { List<Pair<Column, Column>> removeList = new ArrayList<Pair<Column, Column>>(); Column prevColumn = null; for (Column column : range.rangeSet) { if (prevColumn == null) { prevColumn = column; continue; } boolean collapse = true; for (Row row : table.getRows().getEntries()) { Cell prevCell = row.getCell(prevColumn); Cell cell = row.getCell(column); boolean collapseCell = prevCell == null ? cell == null : (cell != null && prevCell.accept(collapseCheck, cell)); if (!collapseCell) { collapse = false; break; } } if (collapse) { // removing outside the iteration so that the iterator is not broken removeList.add(new Pair<Column, Column>(column, prevColumn)); } else { prevColumn = column; } } for (Pair<Column, Column> removePair : removeList) { table.removeColumn(removePair.first(), removePair.second()); } } protected void collapseSpanRows(Table table, DimensionRange<Row> range) { List<Pair<Row, Row>> removeList = new ArrayList<Pair<Row, Row>>(); Row prevRow = null; for (Row row : range.rangeSet) { if (prevRow == null) { prevRow = row; continue; } boolean collapse = true; for (Column column : table.getColumns().getEntries()) { Cell prevCell = prevRow.getCell(column); Cell cell = row.getCell(column); boolean collapseCell = prevCell == null ? cell == null : (cell != null && prevCell.accept(collapseCheck, cell)); if (!collapseCell) { collapse = false; break; } } if (collapse) { // removing outside the iteration so that the iterator is not broken removeList.add(new Pair<Row, Row>(row, prevRow)); } else { prevRow = row; } } for (Pair<Row, Row> removePair : removeList) { table.removeRow(removePair.first(), removePair.second()); } } protected void setElementCells(DimensionRange<Column> elementColRange, DimensionRange<Row> elementRowRange, Cell elementCell) { elementRowRange.floor.setCell(elementColRange.floor, elementCell); for (Column col : elementColRange.rangeSet.tailSet(elementColRange.floor, false)) { elementRowRange.floor.setCell(col, elementCell.split()); } for (Row row : elementRowRange.rangeSet.tailSet(elementRowRange.floor, false)) { for (Column col : elementColRange.rangeSet) { row.setCell(col, elementCell.split()); } } } public void addMargins(int width, int height) { mainTable.columns.addMargins(width); mainTable.rows.addMargins(height); } protected Column columnKey(int startCoord) { // TODO lucianc cache return new Column(startCoord); } protected Row rowKey(int startCoord) { // TODO lucianc cache return new Row(startCoord); } protected void columnSplit(Table table, Column splitCol, Column newCol) { for (Row row : table.rows.getEntries()) { Cell cell = row.getCell(splitCol); if (cell != null) { Cell splitCell = cell.split(); row.setCell(newCol, splitCell); } } } protected void rowSplit(Table table, Row splitRow, Row newRow) { for (Column col : table.columns.getEntries()) { Cell cell = splitRow.getCell(col); if (cell != null) { Cell splitCell = cell.split(); newRow.setCell(col, splitCell); } } } protected boolean isSplitCell(Cell spanned, Cell cell) { return (cell instanceof SplitCell) && ((SplitCell) cell).getSourceCell().equals(spanned); } public Table getTable() { return mainTable; } public JRPrintElement getCellElement(BaseElementCell cell) { return getCellElement(cell.getParentIndex(), cell.getElementIndex()); } protected JRPrintElement getCellElement(PrintElementIndex parentIndex, int index) { // TODO lucianc keep a cache of current element position? JRPrintElement element; if (parentIndex == null) { element = elements.get(index); } else { JRPrintFrame parentFrame = (JRPrintFrame) getCellElement(parentIndex.getParentIndex(), parentIndex.getIndex()); element = parentFrame.getElements().get(index); } return element; } protected boolean isParent(FrameCell parent, Cell child) { if (child == null) { return false; } return child.accept(parentCheck, parent); } protected SpanInfo<Column> getColumnCellSpan(TablePosition position, Cell cell) { return getColumnCellSpan(position.getTable().columns.getEntries(), position.getColumn(), position.getRow(), cell); } protected SpanInfo<Column> getColumnCellSpan(NavigableSet<Column> columns, Column column, Row row, Cell cell) { int span = 1; Column lastCol = column; for (Column tailCol : columns.tailSet(column, false)) { Cell tailCell = row.getCell(tailCol); if (tailCell == null || !cell.accept(spanCheck, tailCell)) { break; } ++span; lastCol = tailCol; } return new SpanInfo<Column>(span, lastCol); } protected SpanInfo<Row> getRowCellSpan(TablePosition position, Cell cell) { return getRowCellSpan(position.getTable().rows.getEntries(), position.getColumn(), position.getRow(), cell); } protected SpanInfo<Row> getRowCellSpan(NavigableSet<Row> rows, Column column, Row row, Cell cell) { int span = 1; Row lastRow = row; for (Row tailRow : rows.tailSet(row, false)) { Cell tailCell = tailRow.getCell(column); if (tailCell == null || !cell.accept(spanCheck, tailCell)) { break; } ++span; lastRow = tailRow; } return new SpanInfo<Row>(span, lastRow); } protected FrameCell droppedParent(FrameCell existingParent, FrameCell parent) { if (existingParent == null) { // should not happen throw new JRRuntimeException(EXCEPTION_MESSAGE_KEY_DROPPING_PARENT_ERROR, (Object[]) null); } if (existingParent.equals(parent)) { return null; } FrameCell droppedGrandParent = droppedParent(existingParent.getParent(), parent); FrameCell droppedParent = new FrameCell(droppedGrandParent, existingParent.getParentIndex(), existingParent.getElementIndex()); return droppedParent; } public TableCell getTableCell(TablePosition position, Cell cell) { return cell.accept(tableCellCreator, position); } protected class ParentCheck implements CellVisitor<FrameCell, Boolean, RuntimeException> { @Override public Boolean visit(ElementCell cell, FrameCell parentCell) { return Tabulator.this.isParent(parentCell, cell.getParent()); } @Override public Boolean visit(SplitCell cell, FrameCell parentCell) { return Tabulator.this.isParent(parentCell, cell.getSourceCell().getParent()); } @Override public Boolean visit(FrameCell frameCell, FrameCell parentCell) { return Tabulator.this.isParent(parentCell, frameCell); } @Override public Boolean visit(LayeredCell layeredCell, FrameCell parentCell) { return Tabulator.this.isParent(parentCell, layeredCell.getParent()); } } protected class SpanRangeCheck implements CellVisitor<Cell, Boolean, RuntimeException> { @Override public Boolean visit(ElementCell spanned, Cell cell) { return spanned.equals(cell) || Tabulator.this.isSplitCell(spanned, cell); } @Override public Boolean visit(SplitCell spanned, Cell cell) { return spanned.getSourceCell().accept(this, cell); } @Override public Boolean visit(FrameCell spanned, Cell cell) { return Tabulator.this.isParent(spanned, cell); } @Override public Boolean visit(LayeredCell spanned, Cell cell) { return spanned.equals(cell) || Tabulator.this.isSplitCell(spanned, cell); } } protected class SpanCheck implements CellVisitor<Cell, Boolean, RuntimeException> { @Override public Boolean visit(ElementCell spanned, Cell cell) { return Tabulator.this.isSplitCell(spanned, cell); } @Override public Boolean visit(SplitCell spanned, Cell cell) { return false; } @Override public Boolean visit(FrameCell spanned, Cell cell) { return false; } @Override public Boolean visit(LayeredCell spanned, Cell cell) { return Tabulator.this.isSplitCell(spanned, cell); } } protected class CollapseCheck implements CellVisitor<Cell, Boolean, RuntimeException> { @Override public Boolean visit(ElementCell spanned, Cell cell) { return Tabulator.this.isSplitCell(spanned, cell); } @Override public Boolean visit(SplitCell spanned, Cell cell) { // all element/layered split cells are the same return spanned.equals(cell); } @Override public Boolean visit(FrameCell spanned, Cell cell) { // all frame split cells are the same return spanned.equals(cell); } @Override public Boolean visit(LayeredCell spanned, Cell cell) { return Tabulator.this.isSplitCell(spanned, cell); } } protected class ParentDrop implements CellVisitor<FrameCell, Cell, RuntimeException> { private final Map<FrameCell, FrameCell> parentMapping = new HashMap<FrameCell, FrameCell>(); protected FrameCell droppedParent(FrameCell existingParent, FrameCell parent) { FrameCell droppedParent; if (parent == null) { droppedParent = existingParent; } else if (existingParent == null) { droppedParent = null; } else if (parentMapping.containsKey(existingParent))// we can have nulls as values { droppedParent = parentMapping.get(existingParent); } else { droppedParent = Tabulator.this.droppedParent(existingParent, parent); parentMapping.put(existingParent, droppedParent); } return droppedParent; } @Override public Cell visit(ElementCell cell, FrameCell parent) { FrameCell droppedParent = droppedParent(cell.getParent(), parent); cell.setParent(droppedParent); return cell; } @Override public Cell visit(SplitCell cell, FrameCell parent) { // we're not explicitly moving split cells return null; } @Override public Cell visit(FrameCell frameCell, FrameCell parent) { FrameCell droppedParent = droppedParent(frameCell, parent); return droppedParent; } @Override public Cell visit(LayeredCell layeredCell, FrameCell parent) { FrameCell droppedParent = droppedParent(layeredCell.getParent(), parent); layeredCell.setParent(droppedParent); return layeredCell; } } protected class TableCellCreator implements CellVisitor<TablePosition, TableCell, RuntimeException> { @Override public TableCell visit(ElementCell cell, TablePosition position) { JRPrintElement element = getCellElement(cell); int colSpan = getColumnCellSpan(position, cell).span; int rowSpan = getRowCellSpan(position, cell).span; Color backcolor = getElementBackcolor(cell); JRLineBox elementBox = (element instanceof JRBoxContainer) ? ((JRBoxContainer) element).getLineBox() : null; JRLineBox box = copyParentBox(cell, element, elementBox, true, true, true, true); TableCell tableCell = new TableCell(Tabulator.this, position, cell, element, colSpan, rowSpan, backcolor, box); return tableCell; } @Override public TableCell visit(SplitCell cell, TablePosition position) { // NOP return null; } @Override public TableCell visit(FrameCell frameCell, TablePosition position) { JRPrintElement element = getCellElement(frameCell); Color backcolor = getElementBackcolor(frameCell); boolean[] borders = getFrameCellBorders(position.getTable(), frameCell, position.getColumn(), position.getColumn(), position.getRow(), position.getRow()); JRLineBox box = copyFrameBox(frameCell, (JRPrintFrame) element, null, borders[0], borders[1], borders[2], borders[3]); return new TableCell(Tabulator.this, position, frameCell, element, 1, 1, backcolor, box); } @Override public TableCell visit(LayeredCell layeredCell, TablePosition position) { SpanInfo<Column> colSpan = getColumnCellSpan(position, layeredCell); SpanInfo<Row> rowSpan = getRowCellSpan(position, layeredCell); Color backcolor = getElementBackcolor(layeredCell.getParent()); JRLineBox box = null; FrameCell parentCell = layeredCell.getParent(); if (parentCell != null) { boolean[] borders = getFrameCellBorders(position.getTable(), parentCell, position.getColumn(), colSpan.lastEntry, position.getRow(), rowSpan.lastEntry); if (borders[0] || borders[1] || borders[2] || borders[3]) { JRPrintFrame parentFrame = (JRPrintFrame) getCellElement(parentCell); box = copyFrameBox(parentCell, parentFrame, null, borders[0], borders[1], borders[2], borders[3]); } } return new TableCell(Tabulator.this, position, layeredCell, null, colSpan.span, rowSpan.span, backcolor, box); } protected Color getElementBackcolor(BaseElementCell cell) { if (cell == null) { return null; } JRPrintElement element = getCellElement(cell); if (element.getModeValue() == ModeEnum.OPAQUE) { return element.getBackcolor(); } return getElementBackcolor(cell.getParent()); } protected JRLineBox copyParentBox(Cell cell, JRPrintElement element, JRLineBox baseBox, boolean keepLeft, boolean keepRight, boolean keepTop, boolean keepBottom) { FrameCell parentCell = cell.getParent(); if (parentCell == null) { return baseBox; } // TODO lucianc check this in the table instead? JRPrintFrame parentFrame = (JRPrintFrame) getCellElement(parentCell); keepLeft &= element.getX() == 0 && parentFrame.getLineBox().getLeftPadding() == 0; keepRight &= (element.getX() + element.getWidth() + parentFrame.getLineBox().getLeftPadding()) == parentFrame.getWidth(); keepTop &= element.getY() == 0 && parentFrame.getLineBox().getTopPadding() == 0; keepBottom &= (element.getY() + element.getHeight() + parentFrame.getLineBox().getTopPadding()) == parentFrame.getHeight(); JRLineBox resultBox = baseBox; if (keepLeft || keepRight || keepTop || keepBottom) { resultBox = copyFrameBox(parentCell, parentFrame, baseBox, keepLeft, keepRight, keepTop, keepBottom); } return resultBox; } protected boolean[] getFrameCellBorders(Table table, FrameCell cell, Column firstCol, Column lastCol, Row firstRow, Row lastRow) { Column prevCol = table.columns.getEntries().lower(firstCol); boolean leftBorder = !isParent(cell, firstRow.getCell(prevCol)); Column nextCol = table.columns.getEntries().higher(lastCol); boolean rightBorder = !isParent(cell, firstRow.getCell(nextCol)); Row prevRow = table.rows.getEntries().lower(firstRow); boolean topBorder = !isParent(cell, prevRow.getCell(firstCol)); Row nextRow = table.rows.getEntries().higher(lastRow); boolean bottomBorder = !isParent(cell, nextRow.getCell(firstCol)); return new boolean[] { leftBorder, rightBorder, topBorder, bottomBorder }; } protected JRLineBox copyFrameBox(FrameCell frameCell, JRPrintFrame frame, JRLineBox baseBox, boolean keepLeft, boolean keepRight, boolean keepTop, boolean keepBottom) { // TODO lucianc cache JRLineBox resultBox = JRBoxUtil.copyBordersNoPadding(frame.getLineBox(), keepLeft, keepRight, keepTop, keepBottom, baseBox); // recurse resultBox = copyParentBox(frameCell, frame, resultBox, keepLeft, keepRight, keepTop, keepBottom); return resultBox; } } protected static class SpanInfo<T extends DimensionEntry> { protected final int span; protected final T lastEntry; public SpanInfo(int span, T lastEntry) { this.span = span; this.lastEntry = lastEntry; } } }