/*!
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program 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.
*
* Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.layout.process;
import org.pentaho.reporting.engine.classic.core.layout.model.BlockRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.Border;
import org.pentaho.reporting.engine.classic.core.layout.model.CanvasRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.InlineRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.LayoutNodeTypes;
import org.pentaho.reporting.engine.classic.core.layout.model.LogicalPageBox;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderLength;
import org.pentaho.reporting.engine.classic.core.layout.model.context.BoxDefinition;
import org.pentaho.reporting.engine.classic.core.layout.model.table.TableCellRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.table.TableColumnGroupNode;
import org.pentaho.reporting.engine.classic.core.layout.model.table.TableColumnNode;
import org.pentaho.reporting.engine.classic.core.layout.model.table.TableRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.table.TableRowRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.table.TableSectionRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.table.columns.TableColumn;
import org.pentaho.reporting.engine.classic.core.layout.model.table.columns.TableColumnGroup;
import org.pentaho.reporting.engine.classic.core.layout.model.table.columns.TableColumnModel;
import org.pentaho.reporting.engine.classic.core.layout.model.table.rows.TableRowModel;
import org.pentaho.reporting.engine.classic.core.layout.process.util.ProcessUtility;
import org.pentaho.reporting.engine.classic.core.util.IntList;
import org.pentaho.reporting.engine.classic.core.util.geom.StrictGeomUtility;
/**
* Another static processing step which validates the table structure and computes the cell positions
* within the table.
*
* @author Thomas Morgner
*/
public class TableValidationStep extends IterateStructuralProcessStep
{
private static final long MAX_AUTO = StrictGeomUtility.toInternalValue(0x80000000000L);
private static class TableInfoStructure
{
private TableRenderBox table;
private TableInfoStructure parent;
private TableColumnModel columnModel;
private TableSectionRenderBox sectionRenderBox;
private IntList rowSpans;
private TableRowModel rowModel;
protected int tableCellPosition;
protected int rowIndex;
private boolean bodySection;
private boolean headerOrFooterSection;
public TableInfoStructure(final TableRenderBox table, final TableInfoStructure parent)
{
this.table = table;
this.parent = parent;
this.columnModel = table.getColumnModel();
this.rowSpans = new IntList(10);
}
public void resetCellPosition()
{
this.tableCellPosition = 0;
}
private boolean isCellAreaClear(final int pos, final int colSpan)
{
final int maxIdx = Math.min(pos + colSpan, rowSpans.size());
for (int i = pos; i < maxIdx; i++)
{
if (rowSpans.get(tableCellPosition) > 0)
{
return false;
}
}
return true;
}
public int increaseCellPosition(final int colSpan, final int rowSpan)
{
// find insert-position for the cell. This skips cells that block the location via a row-span.
while (true)
{
// we are past the point of defined cells. Adding new cells is guaranteed to not have row-spans.
if (tableCellPosition >= rowSpans.size())
{
break;
}
if (isCellAreaClear(tableCellPosition, colSpan))
{
break;
}
tableCellPosition += 1;
}
final int retval = tableCellPosition;
// set the cell...
for (int i = tableCellPosition; i < tableCellPosition + colSpan; i++)
{
if (i < rowSpans.size())
{
rowSpans.set(i, Math.max(rowSpan, rowSpans.get(i)));
}
else
{
rowSpans.add(rowSpan);
}
}
tableCellPosition += colSpan;
return retval;
}
public TableInfoStructure pop()
{
return parent;
}
public TableSectionRenderBox getSectionRenderBox()
{
return sectionRenderBox;
}
public void setSectionRenderBox(final TableSectionRenderBox sectionRenderBox)
{
this.rowSpans.clear();
this.sectionRenderBox = sectionRenderBox;
if (this.sectionRenderBox != null)
{
this.rowModel = sectionRenderBox.getRowModel();
this.bodySection = (sectionRenderBox.getDisplayRole() == TableSectionRenderBox.Role.BODY);
this.headerOrFooterSection = !bodySection;
}
else
{
this.rowModel = null;
this.bodySection = false;
}
this.rowIndex = -1;
}
public boolean isHeaderOrFooterSection()
{
return headerOrFooterSection;
}
public void setHeaderOrFooterSection(final boolean headerOrFooterSection)
{
this.headerOrFooterSection = headerOrFooterSection;
}
public boolean isBodySection()
{
return bodySection;
}
public TableRenderBox getTable()
{
return table;
}
public TableColumnModel getColumnModel()
{
return columnModel;
}
public void updateDefinedSize(final int rowSpan, final long preferredSize)
{
rowModel.updateDefinedSize(rowIndex, rowSpan, preferredSize);
}
}
private TableInfoStructure currentTable;
private TableColumnGroup currentColumnGroup;
public TableValidationStep()
{
}
public void validate(final LogicalPageBox box)
{
currentTable = null;
startProcessing(box);
if (currentTable != null)
{
throw new IllegalStateException();
}
}
private boolean abortIfNoTable(final RenderBox box)
{
if (box.getTableRefCount() == 0)
{
return false;
}
if (box.getTableValidationAge() == box.getChangeTracker())
{
return false;
}
box.setTableValidationAge(box.getChangeTracker());
return true;
}
protected boolean startCanvasBox(final CanvasRenderBox box)
{
return abortIfNoTable(box);
}
protected boolean startBlockBox(final BlockRenderBox box)
{
return abortIfNoTable(box);
}
protected boolean startInlineBox(final InlineRenderBox box)
{
return abortIfNoTable(box);
}
protected boolean startOtherBox(final RenderBox box)
{
return abortIfNoTable(box);
}
protected boolean startRowBox(final RenderBox box)
{
return abortIfNoTable(box);
}
protected boolean startAutoBox(final RenderBox box)
{
if (currentTable != null)
{
if (box.getParent().getLayoutNodeType() == LayoutNodeTypes.TYPE_BOX_TABLE)
{
currentTable.setHeaderOrFooterSection(false);
}
return true;
}
return abortIfNoTable(box);
}
protected void finishAutoBox(final RenderBox box)
{
if (currentTable != null)
{
box.setContainsReservedContent(currentTable.isHeaderOrFooterSection());
}
}
protected boolean startTableBox(final TableRenderBox table)
{
final long changeTracker = table.getChangeTracker();
final long age = table.getTableValidationAge();
if (changeTracker == age)
{
return false;
}
currentTable = new TableInfoStructure(table, currentTable);
return true;
}
protected void finishTableBox(final TableRenderBox table)
{
final long changeTracker = table.getChangeTracker();
final long age = table.getTableValidationAge();
if (changeTracker == age)
{
return;
}
// currentTable.columnModel.validatePreferredSizes(table);
table.setTableValidationAge(age);
table.setPredefinedColumnsValidated(true);
currentTable = currentTable.pop();
}
protected boolean startTableColumnGroupBox(final TableColumnGroupNode box)
{
if (currentTable == null)
{
return false;
}
if (currentTable.table.isPredefinedColumnsValidated())
{
return false;
}
currentColumnGroup = new TableColumnGroup(box.getBoxDefinition().getBorder());
currentColumnGroup.setColSpan(box.getColSpan());
return true;
}
protected void processTableColumn(final TableColumnNode node)
{
if (currentTable == null)
{
return;
}
if (currentTable.table.isPredefinedColumnsValidated())
{
return;
}
final Border border = node.getBoxDefinition().getBorder();
final RenderLength length = node.getBoxDefinition().getMinimumWidth();
if (currentColumnGroup != null)
{
currentColumnGroup.addColumn(new TableColumn(border, length, false));
}
else
{
final TableColumnGroup currentColumnGroup = new TableColumnGroup(BoxDefinition.EMPTY.getBorder());
currentColumnGroup.addColumn(new TableColumn(border, length, false));
currentTable.columnModel.addColumnGroup(currentColumnGroup);
}
}
protected void finishTableColumnGroupBox(final TableColumnGroupNode box)
{
if (currentTable == null)
{
return;
}
if (currentTable.table.isPredefinedColumnsValidated())
{
return;
}
while (currentColumnGroup.getColumnCount() < box.getColSpan())
{
currentColumnGroup.addColumn(new TableColumn(currentColumnGroup.getBorder(), RenderLength.AUTO, false));
}
currentTable.columnModel.addColumnGroup(currentColumnGroup);
currentColumnGroup = null;
}
protected boolean startTableSectionBox(final TableSectionRenderBox box)
{
if (currentTable == null)
{
return false;
}
if (currentTable.getSectionRenderBox() != null)
{
return true;
}
currentTable.setSectionRenderBox(box);
box.setContainsReservedContent(box.getDisplayRole() != TableSectionRenderBox.Role.BODY);
box.getRowModel().initialize(currentTable.getTable());
return true;
}
protected void finishTableSectionBox(final TableSectionRenderBox box)
{
if (currentTable == null)
{
return;
}
if (currentTable.getSectionRenderBox() != box)
{
return;
}
final IntList rowSpans = currentTable.rowSpans;
int missingRows = 0;
for (int i = 0; i < rowSpans.size(); i++)
{
final int value = rowSpans.get(i);
if (missingRows < value)
{
missingRows = value;
}
}
for (int i = 0; i < missingRows; i += 1)
{
currentTable.rowModel.addRow();
}
box.getRowModel().validatePreferredSizes();
currentTable.setSectionRenderBox(null);
}
protected boolean startTableRowBox(final TableRowRenderBox box)
{
if (currentTable == null)
{
return false;
}
if (currentTable.getSectionRenderBox() == null)
{
return false;
}
currentTable.resetCellPosition();
box.setBodySection(currentTable.isBodySection());
// check if this is the first row ...
if (currentTable.rowIndex == -1)
{
if (box.getRowIndex() != -1)
{
currentTable.rowIndex = box.getRowIndex();
return true;
}
}
currentTable.rowIndex += 1;
box.setRowIndex(currentTable.rowIndex);
if (currentTable.rowModel.getRowCount() <= currentTable.rowIndex)
{
currentTable.rowModel.addRow();
}
return true;
}
protected void finishTableRowBox(final TableRowRenderBox box)
{
if (currentTable == null)
{
return;
}
if (currentTable.getSectionRenderBox() == null)
{
return;
}
final IntList rowSpans = currentTable.rowSpans;
int maxRowSpan = 0;
for (int i = 0; i < rowSpans.size(); i++)
{
final int value = rowSpans.get(i);
maxRowSpan = Math.max(maxRowSpan, value);
}
for (int i = 0; i < rowSpans.size(); i++)
{
final int value = rowSpans.get(i);
rowSpans.set(i, Math.max(0, value - 1));
}
}
protected boolean startTableCellBox(final TableCellRenderBox box)
{
if (currentTable == null)
{
return false;
}
if (currentTable.getSectionRenderBox() == null)
{
return false;
}
final int rowSpan = box.getRowSpan();
final int colSpan = box.getColSpan();
final int startPos = currentTable.increaseCellPosition(colSpan, rowSpan);
while (currentTable.columnModel.getColumnCount() < (startPos + colSpan))
{
currentTable.columnModel.addAutoColumn();
}
box.setColumnIndex(startPos);
box.setBodySection(currentTable.isBodySection());
final BoxDefinition boxDefinition = box.getBoxDefinition();
final long preferredHeight = boxDefinition.getPreferredHeight().resolve(0);
final long minHeight = boxDefinition.getMinimumHeight().resolve(0);
final long maxHeight = boxDefinition.getMaximumHeight().resolve(0, MAX_AUTO);
final long preferredSize = ProcessUtility.computeLength(minHeight, maxHeight, preferredHeight);
currentTable.updateDefinedSize(rowSpan, preferredSize);
return true;
}
}