/*!
* 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.output.crosstab;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.engine.classic.core.CrosstabCell;
import org.pentaho.reporting.engine.classic.core.CrosstabCellBody;
import org.pentaho.reporting.engine.classic.core.CrosstabColumnGroup;
import org.pentaho.reporting.engine.classic.core.CrosstabColumnGroupBody;
import org.pentaho.reporting.engine.classic.core.CrosstabRowGroup;
import org.pentaho.reporting.engine.classic.core.CrosstabRowGroupBody;
import org.pentaho.reporting.engine.classic.core.Group;
import org.pentaho.reporting.engine.classic.core.GroupBody;
import org.pentaho.reporting.engine.classic.core.ReportProcessingException;
import org.pentaho.reporting.engine.classic.core.event.ReportEvent;
import org.pentaho.reporting.engine.classic.core.layout.Renderer;
import org.pentaho.reporting.engine.classic.core.layout.build.LayoutModelBuilder;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.RenderNode;
import org.pentaho.reporting.engine.classic.core.layout.model.table.TableCellRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.model.table.TableSectionRenderBox;
import org.pentaho.reporting.engine.classic.core.layout.output.DefaultOutputFunction;
import org.pentaho.reporting.engine.classic.core.layout.output.GroupOutputHandler;
import org.pentaho.reporting.engine.classic.core.style.BandStyleKeys;
import org.pentaho.reporting.engine.classic.core.util.InstanceID;
public class CrosstabRowOutputHandler implements GroupOutputHandler
{
private static final Log logger = LogFactory.getLog (CrosstabRowOutputHandler.class);
public CrosstabRowOutputHandler()
{
}
public void groupStarted(final DefaultOutputFunction outputFunction,
final ReportEvent event) throws ReportProcessingException
{
final RenderedCrosstabLayout crosstabLayout = outputFunction.getCurrentRenderedCrosstabLayout();
final LayoutModelBuilder layoutModelBuilder = outputFunction.getRenderer().getNormalFlowLayoutModelBuilder();
final int gidx = event.getState().getCurrentGroupIndex();
final CrosstabRowGroup group = (CrosstabRowGroup) event.getReport().getGroup(gidx);
if (crosstabLayout.isCrosstabTableOpen() == false)
{
buildHeaderPlaceholder(crosstabLayout, layoutModelBuilder);
crosstabLayout.setFirstRowGroupIndex(gidx);
crosstabLayout.setCrosstabTableOpen(true);
}
if (crosstabLayout.isCrosstabHeaderOpen())
{
layoutModelBuilder.startSubFlow(crosstabLayout.getRowTitleHeaderId());
CrosstabOutputHelper.createAutomaticCell(layoutModelBuilder);
outputFunction.getRenderer().add(group.getTitleHeader(), outputFunction.getRuntime());
layoutModelBuilder.finishBox();
layoutModelBuilder.suspendSubFlow();
}
if (crosstabLayout.isCrosstabRowOpen() == false)
{
// start a new row if needed ..
layoutModelBuilder.startBox(CrosstabOutputHelper.createTableRow());
crosstabLayout.setCrosstabRowOpen(true);
// flag not empty is needed to connect the new node with the rest of the layout model.
layoutModelBuilder.legacyFlagNotEmpty();
final TableSectionRenderBox rowRenderNode = CrosstabOutputHelper.findTableSection(layoutModelBuilder.dangerousRawAccess());
for (int i = crosstabLayout.getFirstRowGroupIndex(), count = 0; i < gidx; i += 1, count += 1)
{
final InstanceID rowHeader = crosstabLayout.getRowHeader(i - crosstabLayout.getFirstRowGroupIndex());
final RenderNode cell = CrosstabOutputHelper.findNode(rowRenderNode, rowHeader);
if (cell instanceof TableCellRenderBox)
{
final TableCellRenderBox cellBox = (TableCellRenderBox) cell;
cellBox.update(cellBox.getRowSpan() + 1, cellBox.getColSpan());
}
else
{
throw new IllegalStateException("Unable to find previously defined row header. Aborting report processing.");
}
}
}
final int firstRowGroupIndex = crosstabLayout.getFirstRowGroupIndex();
if (gidx == firstRowGroupIndex)
{
RenderNode renderNode = layoutModelBuilder.dangerousRawAccess();
RenderBox parentNode = (RenderBox) CrosstabOutputHelper.findParentNode(renderNode, crosstabLayout.getCrosstabId());
parentNode.setPreventPagination(true);
}
CrosstabOutputHelper.createAutomaticCell(layoutModelBuilder, 1, 1, group.getHeader());
crosstabLayout.setRowHeader(gidx - crosstabLayout.getFirstRowGroupIndex(), layoutModelBuilder.dangerousRawAccess().getInstanceId());
outputFunction.getRenderer().add(group.getHeader(), outputFunction.getRuntime());
layoutModelBuilder.finishBox();
}
public void groupFinished(final DefaultOutputFunction outputFunction,
final ReportEvent event) throws ReportProcessingException
{
CrosstabOutputHelper.printCrosstabSummary(outputFunction, event);
final RenderedCrosstabLayout crosstabLayout = outputFunction.getCurrentRenderedCrosstabLayout();
final LayoutModelBuilder layoutModelBuilder = outputFunction.getRenderer().getNormalFlowLayoutModelBuilder();
if (crosstabLayout.isCrosstabRowOpen())
{
layoutModelBuilder.finishBox(); // Table-Row
crosstabLayout.setCrosstabRowOpen(false);
}
if (crosstabLayout.isCrosstabHeaderOpen())
{
// close all header rows
final InstanceID[] columnHeaders = crosstabLayout.getColumnHeaderSubFlows();
for (int i = 0; i < columnHeaders.length; i++)
{
final InstanceID columnHeader = columnHeaders[i];
layoutModelBuilder.startSubFlow(columnHeader);
layoutModelBuilder.endSubFlow();
}
crosstabLayout.setCrosstabHeaderOpen(false);
}
final int gidx = event.getState().getCurrentGroupIndex();
final int firstRowGroupIndex = crosstabLayout.getFirstRowGroupIndex();
if (gidx == firstRowGroupIndex)
{
RenderNode renderNode = layoutModelBuilder.dangerousRawAccess();
RenderBox parentNode = (RenderBox) CrosstabOutputHelper.findParentNode(renderNode, crosstabLayout.getCrosstabId());
parentNode.setPreventPagination(false);
}
}
public void groupBodyFinished(final DefaultOutputFunction outputFunction,
final ReportEvent event) throws ReportProcessingException
{
}
private void buildHeaderPlaceholder(final RenderedCrosstabLayout crosstabLayout,
final LayoutModelBuilder layoutModelBuilder)
{
crosstabLayout.setCrosstabId
(layoutModelBuilder.startBox(CrosstabOutputHelper.createTable(crosstabLayout.getTableLayout())));
layoutModelBuilder.startBox(CrosstabOutputHelper.createTableBand(BandStyleKeys.LAYOUT_TABLE_HEADER));
// create column group placeholder rows. We subsequently add content as sub-flows into these groups.
int columnGroupCount = crosstabLayout.getColumnGroups();
if (crosstabLayout.isGenerateColumnTitleHeaders())
{
columnGroupCount += crosstabLayout.getColumnGroups();
}
if (crosstabLayout.isGenerateMeasureHeaders())
{
columnGroupCount += 1;
}
final InstanceID[] columnHeaders = new InstanceID[Math.max(1, columnGroupCount)];
for (int i = 0; i < columnHeaders.length; i += 1)
{
columnHeaders[i] = layoutModelBuilder.createSubflowPlaceholder(CrosstabOutputHelper.createTableRow());
}
if (columnHeaders.length > 1)
{
// Adds a empty cell that consumes the area above the row-header
layoutModelBuilder.startSubFlow(columnHeaders[0]);
CrosstabOutputHelper.createAutomaticCell(layoutModelBuilder, crosstabLayout.getRowGroups(), columnHeaders.length - 1);
layoutModelBuilder.legacyFlagNotEmpty();
layoutModelBuilder.finishBox();
layoutModelBuilder.suspendSubFlow();
}
layoutModelBuilder.finishBox(); // BandStyleKeys.LAYOUT_TABLE_HEADER
layoutModelBuilder.startBox(CrosstabOutputHelper.createTableBand(BandStyleKeys.LAYOUT_TABLE_BODY));
crosstabLayout.setCrosstabHeaderOpen(true);
crosstabLayout.setColumnHeaderRowIds(columnHeaders);
}
public void itemsStarted(final DefaultOutputFunction outputFunction,
final ReportEvent event) throws ReportProcessingException
{
throw new ReportProcessingException("A crosstab-row cannot contain a detail band. Never.");
}
public void itemsAdvanced(final DefaultOutputFunction outputFunction,
final ReportEvent event) throws ReportProcessingException
{
throw new ReportProcessingException("A crosstab-row cannot contain a detail band. Never.");
}
public void itemsFinished(final DefaultOutputFunction outputFunction,
final ReportEvent event) throws ReportProcessingException
{
throw new ReportProcessingException("A crosstab-row cannot contain a detail band. Never.");
}
public void summaryRowStart(final DefaultOutputFunction outputFunction,
final ReportEvent event) throws ReportProcessingException
{
final RenderedCrosstabLayout crosstabLayout = outputFunction.getCurrentRenderedCrosstabLayout();
final LayoutModelBuilder layoutModelBuilder = outputFunction.getRenderer().getNormalFlowLayoutModelBuilder();
if (crosstabLayout.isCrosstabRowOpen())
{
throw new IllegalStateException("Event Order Error: A summary row cannot be printed while a row is still open.");
}
final int gidx = event.getState().getCurrentGroupIndex() + 1;
final CrosstabRowGroup group = (CrosstabRowGroup) event.getReport().getGroup(gidx);
if (group.isPrintSummary() == false)
{
crosstabLayout.startSummaryRowProcessing(false, gidx, null);
return;
}
if (group.getField() == null)
{
crosstabLayout.startSummaryRowProcessing(false, gidx, null);
return;
}
final CrosstabCellBody dataBody = event.getReport().getCrosstabCellBody();
final CrosstabCell element = dataBody.findElement(group.getField(), null);
if (element == null)
{
crosstabLayout.startSummaryRowProcessing(false, gidx, null);
return;
}
if (crosstabLayout.isCrosstabRowOpen() == false)
{
// start a new row if needed ..
layoutModelBuilder.startBox(CrosstabOutputHelper.createTableRow());
crosstabLayout.setCrosstabRowOpen(true);
// flag not empty is needed to connect the new node with the rest of the layout model.
layoutModelBuilder.legacyFlagNotEmpty();
final TableSectionRenderBox rowRenderNode = CrosstabOutputHelper.findTableSection(layoutModelBuilder.dangerousRawAccess());
for (int i = crosstabLayout.getFirstRowGroupIndex(), count = 0; i < gidx; i += 1, count += 1)
{
final InstanceID rowHeader = crosstabLayout.getRowHeader(i - crosstabLayout.getFirstRowGroupIndex());
final RenderNode cell = CrosstabOutputHelper.findNode(rowRenderNode, rowHeader);
if (cell instanceof TableCellRenderBox)
{
final TableCellRenderBox cellBox = (TableCellRenderBox) cell;
cellBox.update(cellBox.getRowSpan() + 1, cellBox.getColSpan());
}
else
{
throw new IllegalStateException("Unable to find previously defined row header. Aborting report processing.");
}
}
}
// An outer row-group's summary cell spans across all inner row-group header-columns up to the start
// of the data area.
final int colSpan = crosstabLayout.getFirstColGroupIndex() - gidx;
CrosstabOutputHelper.createAutomaticCell(layoutModelBuilder, colSpan, 1, group.getSummaryHeader());
crosstabLayout.setRowHeader(gidx - crosstabLayout.getFirstRowGroupIndex(), layoutModelBuilder.dangerousRawAccess().getInstanceId());
outputFunction.getRenderer().add(group.getSummaryHeader(), outputFunction.getRuntime());
layoutModelBuilder.finishBox();
crosstabLayout.startSummaryRowProcessing(true, gidx, group.getField());
crosstabLayout.setDetailsRendered(false);
crosstabLayout.setProcessingCrosstabHeader(false);
}
public void summaryRowEnd(final DefaultOutputFunction outputFunction,
final ReportEvent event) throws ReportProcessingException
{
final RenderedCrosstabLayout crosstabLayout = outputFunction.getCurrentRenderedCrosstabLayout();
if (crosstabLayout.isSummaryRowPrintable() == false)
{
return;
}
final LayoutModelBuilder layoutModelBuilder = outputFunction.getRenderer().getNormalFlowLayoutModelBuilder();
if (crosstabLayout.isCrosstabRowOpen())
{
printSummaryCell(outputFunction, event);
layoutModelBuilder.finishBox(); // Table-Row
crosstabLayout.setCrosstabRowOpen(false);
}
}
public void summaryRow(final DefaultOutputFunction outputFunction,
final ReportEvent event) throws ReportProcessingException
{
final RenderedCrosstabLayout crosstabLayout = outputFunction.getCurrentRenderedCrosstabLayout();
if (crosstabLayout.isSummaryRowPrintable() == false)
{
return;
}
printSummaryCell(outputFunction, event);
}
private void printSummaryCell(final DefaultOutputFunction outputFunction,
final ReportEvent event) throws ReportProcessingException
{
final RenderedCrosstabLayout crosstabLayout = outputFunction.getCurrentRenderedCrosstabLayout();
final int gidx = event.getState().getCurrentGroupIndex();
final Group group = event.getReport().getGroup(gidx);
final String columnField;
final GroupBody groupBody = group.getBody();
if (groupBody instanceof CrosstabColumnGroupBody)
{
final CrosstabColumnGroupBody columnGroupBody = (CrosstabColumnGroupBody) groupBody;
final CrosstabColumnGroup next = columnGroupBody.getGroup();
if (next.isPrintSummary() == false)
{
return;
}
columnField = next.getField();
}
else if (groupBody instanceof CrosstabRowGroupBody)
{
// the final sum of the row. The column field will be the first column group
final CrosstabColumnGroup colGroup = (CrosstabColumnGroup)
event.getReport().getGroup(crosstabLayout.getFirstColGroupIndex());
if (colGroup.isPrintSummary() == false)
{
return;
}
columnField = colGroup.getField();
}
else
{
// a detail level summary row cell.
columnField = null;
}
final LayoutModelBuilder layoutModelBuilder = outputFunction.getRenderer().getNormalFlowLayoutModelBuilder();
final CrosstabCellBody dataBody = event.getReport().getCrosstabCellBody();
final CrosstabCell element = dataBody.findElement(crosstabLayout.getSummaryRowField(), columnField);
if (element != null)
{
CrosstabOutputHelper.createAutomaticCell(layoutModelBuilder);
layoutModelBuilder.legacyFlagNotEmpty();
outputFunction.getRenderer().startSection(Renderer.SectionType.NORMALFLOW);
outputFunction.getRenderer().add(element, outputFunction.getRuntime());
outputFunction.addSubReportMarkers(outputFunction.getRenderer().endSection());
layoutModelBuilder.finishBox();
}
else
{
CrosstabOutputHelper.createAutomaticCell(layoutModelBuilder);
layoutModelBuilder.legacyFlagNotEmpty();
logger.debug(String.format("Unable to find summary cell: %s - %s", // NON-NLS
crosstabLayout.getSummaryRowField(), columnField));
layoutModelBuilder.finishBox();
}
}
}