/*!
* 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();
}
}
}