/*
* 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) 2001 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.layout.output;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.reporting.engine.classic.core.Band;
import org.pentaho.reporting.engine.classic.core.DataRow;
import org.pentaho.reporting.engine.classic.core.DetailsFooter;
import org.pentaho.reporting.engine.classic.core.DetailsHeader;
import org.pentaho.reporting.engine.classic.core.Group;
import org.pentaho.reporting.engine.classic.core.GroupFooter;
import org.pentaho.reporting.engine.classic.core.GroupHeader;
import org.pentaho.reporting.engine.classic.core.InvalidReportStateException;
import org.pentaho.reporting.engine.classic.core.PageFooter;
import org.pentaho.reporting.engine.classic.core.PageHeader;
import org.pentaho.reporting.engine.classic.core.RelationalGroup;
import org.pentaho.reporting.engine.classic.core.ReportDefinition;
import org.pentaho.reporting.engine.classic.core.ReportProcessingException;
import org.pentaho.reporting.engine.classic.core.Watermark;
import org.pentaho.reporting.engine.classic.core.event.PageEventListener;
import org.pentaho.reporting.engine.classic.core.event.ReportEvent;
import org.pentaho.reporting.engine.classic.core.function.AbstractFunction;
import org.pentaho.reporting.engine.classic.core.function.Expression;
import org.pentaho.reporting.engine.classic.core.function.ExpressionRuntime;
import org.pentaho.reporting.engine.classic.core.function.OutputFunction;
import org.pentaho.reporting.engine.classic.core.function.ProcessingContext;
import org.pentaho.reporting.engine.classic.core.layout.InlineSubreportMarker;
import org.pentaho.reporting.engine.classic.core.layout.Renderer;
import org.pentaho.reporting.engine.classic.core.layout.output.crosstab.CrosstabRowOutputHandler;
import org.pentaho.reporting.engine.classic.core.layout.output.crosstab.RenderedCrosstabLayout;
import org.pentaho.reporting.engine.classic.core.layout.output.crosstab.RenderedCrosstabOutputHandlerFactory;
import org.pentaho.reporting.engine.classic.core.states.ReportState;
import org.pentaho.reporting.engine.classic.core.states.datarow.MasterDataRow;
import org.pentaho.reporting.engine.classic.core.states.process.SubReportProcessType;
import org.pentaho.reporting.engine.classic.core.style.BandStyleKeys;
import org.pentaho.reporting.engine.classic.core.style.StyleSheet;
import org.pentaho.reporting.engine.classic.core.util.InstanceID;
import org.pentaho.reporting.libraries.base.util.FastStack;
import javax.swing.table.TableModel;
import java.util.ArrayList;
public class DefaultOutputFunction extends AbstractFunction implements OutputFunction, PageEventListener {
private static final Log logger = LogFactory.getLog( DefaultOutputFunction.class );
private static final LayouterLevel[] EMPTY_LAYOUTER_LEVEL = new LayouterLevel[0];
public static final InlineSubreportMarker[] EMPTY_INLINE_SUBREPORT_MARKERS = new InlineSubreportMarker[0];
private ReportEvent currentEvent;
private Renderer renderer;
private boolean lastPagebreak;
private DefaultLayoutPagebreakHandler pagebreakHandler;
private ArrayList<InlineSubreportMarker> inlineSubreports;
private FastStack<GroupOutputHandler> outputHandlers;
private int beginOfRow;
private FastStack<RenderedCrosstabLayout> renderedCrosstabLayouts;
private GroupOutputHandlerFactory groupOutputHandlerFactory;
private ElementChangeChecker elementChangeChecker;
private int printedFooter;
private int printedRepeatingFooter;
private int avoidedFooter;
private int avoidedRepeatingFooter;
private RepeatingFooterValidator repeatingFooterValidator;
private boolean clearedFooter;
private ArrayList<InstanceID> subReportFooterTracker;
/**
* Creates an unnamed function. Make sure the name of the function is set using {@link #setName} before the function
* is added to the report's function collection.
*/
public DefaultOutputFunction() {
this.subReportFooterTracker = new ArrayList<InstanceID>();
this.repeatingFooterValidator = new RepeatingFooterValidator();
this.pagebreakHandler = new DefaultLayoutPagebreakHandler();
this.inlineSubreports = new ArrayList<InlineSubreportMarker>();
this.outputHandlers = new FastStack<GroupOutputHandler>();
this.renderedCrosstabLayouts = new FastStack<RenderedCrosstabLayout>();
this.groupOutputHandlerFactory = new RenderedCrosstabOutputHandlerFactory();
this.elementChangeChecker = new ElementChangeChecker();
}
protected OutputProcessorMetaData getMetaData() {
return getRenderer().getOutputProcessor().getMetaData();
}
/**
* Return the current expression value.
* <P>
* The value depends (obviously) on the expression implementation.
*
* @return the value of the function.
*/
public Object getValue() {
return null;
}
public void reportInitialized( final ReportEvent event ) {
// there can be no pending page-start, we just have started ...
if ( event.getState().getParentSubReportState() != null ) {
// except if we are a subreport, of course ..
clearPendingPageStart( event );
}
// activating this state after the page has ended is invalid.
setCurrentEvent( event );
try {
// activating this state after the page has ended is invalid.
final ReportDefinition report = event.getReport();
if ( event.getState().isSubReportEvent() == false ) {
renderer.startReport( report, getRuntime().getProcessingContext(), event.getState()
.getPerformanceMonitorContext() );
final ReportState reportState = event.getState();
final ExpressionRuntime runtime = getRuntime();
try {
reportState.firePageStartedEvent( reportState.getEventCode() );
} finally {
// restore the current event, as the page-started event will clear it ..
setRuntime( runtime );
setCurrentEvent( event );
}
} else {
renderer.startSubReport( report, event.getState().getCurrentSubReportMarker().getInsertationPointId() );
}
} catch ( final InvalidReportStateException fe ) {
throw fe;
} catch ( final Exception e ) {
throw new InvalidReportStateException( "ReportInitialized failed", e );
} finally {
clearCurrentEvent();
}
}
/**
* Receives notification that the report has started. Also invokes the start of the first page ...
* <P>
* Layout and draw the report header after the PageStartEvent was fired.
*
* @param event
* the event.
*/
public void reportStarted( final ReportEvent event ) {
clearPendingPageStart( event );
// activating this state after the page has ended is invalid.
setCurrentEvent( event );
try {
// activating this state after the page has ended is invalid.
updateFooterArea( event );
final ReportDefinition report = event.getReport();
renderer.startSection( Renderer.SectionType.NORMALFLOW );
print( getRuntime(), report.getReportHeader() );
addSubReportMarkers( renderer.endSection() );
printDesigntimeHeader( event );
} catch ( final InvalidReportStateException fe ) {
throw fe;
} catch ( final Exception e ) {
throw new InvalidReportStateException( "ReportStarted failed", e );
} finally {
clearCurrentEvent();
}
}
protected void printDesigntimeHeader( final ReportEvent event ) throws ReportProcessingException {
}
public void addSubReportMarkers( final InlineSubreportMarker[] markers ) {
for ( int i = 0; i < markers.length; i++ ) {
final InlineSubreportMarker marker = markers[i];
inlineSubreports.add( marker );
}
}
/**
* Receives notification that a group has started.
* <P>
* Prints the GroupHeader
*
* @param event
* Information about the event.
*/
public void groupStarted( final ReportEvent event ) {
final int type = event.getType();
final GroupOutputHandler groupOutputHandler = groupOutputHandlerFactory.getOutputHandler( event, beginOfRow );
outputHandlers.push( groupOutputHandler );
if ( ( type & ReportEvent.CROSSTABBING_ROW ) == ReportEvent.CROSSTABBING_ROW ) {
beginOfRow = event.getState().getCurrentRow();
}
clearPendingPageStart( event );
// activating this state after the page has ended is invalid.
setCurrentEvent( event );
try {
final GroupOutputHandler handler = outputHandlers.peek();
handler.groupStarted( this, event );
} catch ( final InvalidReportStateException fe ) {
throw fe;
} catch ( final Exception e ) {
throw new InvalidReportStateException( "GroupStarted failed", e );
} finally {
clearCurrentEvent();
}
}
/**
* Receives notification that a group of item bands is about to be processed.
* <P>
* The next events will be itemsAdvanced events until the itemsFinished event is raised.
*
* @param event
* The event.
*/
public void itemsStarted( final ReportEvent event ) {
clearPendingPageStart( event );
setCurrentEvent( event );
try {
final GroupOutputHandler handler = outputHandlers.peek();
handler.itemsStarted( this, event );
} catch ( final InvalidReportStateException fe ) {
throw fe;
} catch ( final Exception e ) {
throw new InvalidReportStateException( "ItemsStarted failed", e );
} finally {
clearCurrentEvent();
}
}
/**
* Receives notification that a row of data is being processed.
* <P>
* prints the ItemBand.
*
* @param event
* Information about the event.
*/
public void itemsAdvanced( final ReportEvent event ) {
clearPendingPageStart( event );
setCurrentEvent( event );
try {
final GroupOutputHandler handler = outputHandlers.peek();
handler.itemsAdvanced( this, event );
} catch ( final InvalidReportStateException fe ) {
throw fe;
} catch ( final Exception e ) {
throw new InvalidReportStateException( "ItemsAdvanced failed", e );
} finally {
clearCurrentEvent();
}
}
/**
* Receives notification that a group of item bands has been completed.
* <P>
* The itemBand is finished, the report starts to close open groups.
*
* @param event
* The event.
*/
public void itemsFinished( final ReportEvent event ) {
clearPendingPageStart( event );
setCurrentEvent( event );
try {
final GroupOutputHandler handler = outputHandlers.peek();
handler.itemsFinished( this, event );
} catch ( final InvalidReportStateException fe ) {
throw fe;
} catch ( final Exception e ) {
throw new InvalidReportStateException( "ItemsFinished failed", e );
} finally {
clearCurrentEvent();
}
}
public void groupBodyFinished( final ReportEvent event ) {
clearPendingPageStart( event );
setCurrentEvent( event );
try {
final GroupOutputHandler handler = outputHandlers.peek();
handler.groupBodyFinished( this, event );
} catch ( final InvalidReportStateException fe ) {
throw fe;
} catch ( final Exception e ) {
throw new InvalidReportStateException( "GroupBody failed", e );
} finally {
clearCurrentEvent();
}
}
/**
* Receives notification that a group has finished.
* <P>
* Prints the GroupFooter.
*
* @param event
* Information about the event.
*/
public void groupFinished( final ReportEvent event ) {
clearPendingPageStart( event );
setCurrentEvent( event );
try {
final GroupOutputHandler handler = outputHandlers.pop();
handler.groupFinished( this, event );
} catch ( final InvalidReportStateException fe ) {
throw fe;
} catch ( final Exception e ) {
throw new InvalidReportStateException( "GroupFinished failed", e );
} finally {
clearCurrentEvent();
}
}
public void summaryRowSelection( final ReportEvent event ) {
clearPendingPageStart( event );
setCurrentEvent( event );
try {
if ( ( event.getType() & ReportEvent.SUMMARY_ROW_START ) == ReportEvent.SUMMARY_ROW_START ) {
final GroupOutputHandler handler = new CrosstabRowOutputHandler();
outputHandlers.push( handler );
handler.summaryRowStart( this, event );
} else if ( ( event.getType() & ReportEvent.SUMMARY_ROW_END ) == ReportEvent.SUMMARY_ROW_END ) {
final GroupOutputHandler handler = outputHandlers.pop();
handler.summaryRowEnd( this, event );
} else {
final GroupOutputHandler handler = outputHandlers.peek();
handler.summaryRow( this, event );
}
} catch ( final InvalidReportStateException fe ) {
throw fe;
} catch ( final Exception e ) {
throw new InvalidReportStateException( "Summary Row Selection event failed", e );
} finally {
clearCurrentEvent();
}
}
/**
* Receives notification that the report has finished.
* <P>
* Prints the ReportFooter and forces the last pagebreak.
*
* @param event
* Information about the event.
*/
public void reportFinished( final ReportEvent event ) {
clearPendingPageStart( event );
setCurrentEvent( event );
try {
// a deep traversing event means, we are in a subreport ..
// force that this last pagebreak ... (This is an indicator for the
// pagefooter's print-on-last-page) This is highly unclean and may or
// may not work ..
renderer.startSection( Renderer.SectionType.NORMALFLOW );
print( getRuntime(), event.getReport().getReportFooter() );
addSubReportMarkers( renderer.endSection() );
if ( event.isDeepTraversing() == false ) {
lastPagebreak = true;
}
updateFooterArea( event );
printDesigntimeFooter( event );
} catch ( final InvalidReportStateException fe ) {
throw fe;
} catch ( final Exception e ) {
throw new InvalidReportStateException( "ReportFinished failed", e );
} finally {
clearCurrentEvent();
}
}
protected void printDesigntimeFooter( final ReportEvent event ) throws ReportProcessingException {
}
/**
* Receives notification that report generation has completed, the report footer was printed, no more output is done.
* This is a helper event to shut down the output service.
*
* @param event
* The event.
*/
public void reportDone( final ReportEvent event ) {
if ( event.getState().isSubReportEvent() == false ) {
renderer.endReport();
} else {
renderer.endSubReport();
}
printPerformanceStats();
}
protected void printPerformanceStats() {
elementChangeChecker.reportCachePerformance();
logger.info( String.format(
"Performance: footer-printed=%d footer-avoided=%d repeating-footer-printed=%d repeating-footer-avoided=%d",
printedFooter, avoidedFooter, printedRepeatingFooter, avoidedRepeatingFooter ) );
}
private static LayoutExpressionRuntime createRuntime( final MasterDataRow masterRow, final ReportState state,
final ProcessingContext processingContext ) {
final TableModel reportDataModel = masterRow.getReportData();
return new LayoutExpressionRuntime( masterRow.getGlobalView(), masterRow.getDataSchema(), state, reportDataModel,
processingContext );
}
private static LayouterLevel[] collectSubReportStates( final ReportState state,
final ProcessingContext processingContext ) {
if ( processingContext == null ) {
throw new NullPointerException();
}
ReportState parentState = state.getParentSubReportState();
if ( parentState == null ) {
return EMPTY_LAYOUTER_LEVEL;
}
MasterDataRow dataRow = state.getFlowController().getMasterRow();
dataRow = dataRow.getParentDataRow();
if ( dataRow == null ) {
throw new IllegalStateException( "Parent-DataRow in a subreport-state must be defined." );
}
final ArrayList<LayouterLevel> stack = new ArrayList<LayouterLevel>();
while ( parentState != null ) {
if ( parentState.isInlineProcess() == false ) {
final LayoutExpressionRuntime runtime = createRuntime( dataRow, parentState, processingContext );
stack.add( new LayouterLevel( parentState.getReport(), parentState.getPresentationGroupIndex(), runtime,
parentState.isInItemGroup() ) );
}
parentState = parentState.getParentSubReportState();
dataRow = dataRow.getParentDataRow();
if ( dataRow == null ) {
throw new IllegalStateException( "Parent-DataRow in a subreport-state must be defined." );
}
}
return stack.toArray( new LayouterLevel[stack.size()] );
}
private int computeCurrentPage() {
return renderer.getPageCount() + 1;
}
private boolean isPageHeaderPrinting( final Band b, final boolean testSticky ) {
final StyleSheet resolverStyleSheet = b.getComputedStyle();
if ( resolverStyleSheet == null ) {
throw new InvalidReportStateException( "Inv" );
}
if ( isDesignTime() ) {
return true;
}
if ( testSticky && resolverStyleSheet.getBooleanStyleProperty( BandStyleKeys.STICKY ) == false ) {
return false;
}
final boolean displayOnFirstPage = resolverStyleSheet.getBooleanStyleProperty( BandStyleKeys.DISPLAY_ON_FIRSTPAGE );
if ( computeCurrentPage() == 1 && displayOnFirstPage == false ) {
return false;
}
final boolean displayOnLastPage = resolverStyleSheet.getBooleanStyleProperty( BandStyleKeys.DISPLAY_ON_LASTPAGE );
if ( isLastPagebreak() && ( displayOnLastPage == false ) ) {
return false;
}
return true;
}
protected boolean isLastPagebreak() {
return lastPagebreak;
}
/**
* Receives notification that a page has started.
* <P>
* This prints the PageHeader. If this is the first page, the header is not printed if the pageheader style-flag
* DISPLAY_ON_FIRSTPAGE is set to false. If this event is known to be the last pageStarted event, the
* DISPLAY_ON_LASTPAGE is evaluated and the header is printed only if this flag is set to TRUE.
* <p/>
* If there is an active repeating GroupHeader, print the last one. The GroupHeader is searched for the current group
* and all parent groups, starting at the current group and ascending to the parents. The first goupheader that has
* the StyleFlag REPEAT_HEADER set to TRUE is printed.
* <p/>
* The PageHeader and the repeating GroupHeader are spooled until the first real content is printed. This way, the
* LogicalPage remains empty until an other band is printed.
*
* @param event
* Information about the event.
*/
public void pageStarted( final ReportEvent event ) {
// activating this state after the page has ended is invalid.
setCurrentEvent( event );
try {
final int mask = ReportEvent.REPORT_INITIALIZED | ReportEvent.NO_PARENT_PASSING_EVENT;
if ( event.getState().isSubReportEvent() && ( event.getType() & mask ) == mask ) {
// if this is the artificial subreport-page-start event that is fired from the
// init-report event handler, then do not rebuild the header if the page is not empty.
if ( renderer.isCurrentPageEmpty() == false
|| renderer.validatePages() == Renderer.LayoutResult.LAYOUT_UNVALIDATABLE ) {
return;
}
}
renderer.newPageStarted();
clearedFooter = true;
updateHeaderArea( event.getState() );
} catch ( final InvalidReportStateException fe ) {
throw fe;
} catch ( final Exception e ) {
throw new InvalidReportStateException( "PageStarted failed", e );
} finally {
clearCurrentEvent();
}
}
protected void updateHeaderArea( final ReportState givenState ) throws ReportProcessingException {
ReportState state = givenState;
while ( state != null && state.isInlineProcess() ) {
state = state.getParentSubReportState();
}
if ( state == null ) {
return;
}
final ProcessingContext processingContext = getRuntime().getProcessingContext();
final ReportDefinition report = state.getReport();
LayouterLevel[] levels = null;
ExpressionRuntime runtime = null;
final OutputProcessorMetaData metaData = renderer.getOutputProcessor().getMetaData();
if ( metaData.isFeatureSupported( OutputProcessorFeature.WATERMARK_SECTION ) ) {
renderer.startSection( Renderer.SectionType.WATERMARK );
// a new page has started, so reset the cursor ...
// Check the subreport for sticky watermarks ...
levels = DefaultOutputFunction.collectSubReportStates( state, processingContext );
runtime = updateWatermark( state, processingContext, report, levels, runtime );
addSubReportMarkers( renderer.endSection() );
}
if ( metaData.isFeatureSupported( OutputProcessorFeature.PAGE_SECTIONS ) ) {
renderer.startSection( Renderer.SectionType.HEADER );
// after printing the watermark, we are still at the top of the page.
if ( levels == null ) {
levels = DefaultOutputFunction.collectSubReportStates( state, processingContext );
}
runtime = updatePageHeader( state, processingContext, report, levels, runtime );
runtime = updateRepeatingGroupHeader( state, processingContext, report, levels, runtime );
updateDetailsHeader( state, processingContext, report, runtime );
addSubReportMarkers( renderer.endSection() );
}
// mark the current position to calculate the maxBand-Height
}
protected ExpressionRuntime updateWatermark( final ReportState state, final ProcessingContext processingContext,
final ReportDefinition report, final LayouterLevel[] levels, ExpressionRuntime runtime )
throws ReportProcessingException {
for ( int i = levels.length - 1; i >= 0; i -= 1 ) {
final LayouterLevel level = levels[i];
final ReportDefinition def = level.getReportDefinition();
final Watermark watermark = def.getWatermark();
if ( isPageHeaderPrinting( watermark, true ) ) {
print( level.getRuntime(), watermark );
}
}
// and finally print the watermark of the subreport itself ..
final Band watermark = report.getWatermark();
if ( isPageHeaderPrinting( watermark, false ) ) {
runtime = createRuntime( state.getFlowController().getMasterRow(), state, processingContext );
print( runtime, watermark );
}
return runtime;
}
protected ExpressionRuntime updatePageHeader( final ReportState state, final ProcessingContext processingContext,
final ReportDefinition report, final LayouterLevel[] levels, ExpressionRuntime runtime )
throws ReportProcessingException {
for ( int i = levels.length - 1; i >= 0; i -= 1 ) {
// This is propably wrong (or at least incomplete) in case a subreport uses header or footer which should
// not be printed with the report-footer or header ..
final LayouterLevel level = levels[i];
final ReportDefinition def = level.getReportDefinition();
final PageHeader header = def.getPageHeader();
if ( isPageHeaderPrinting( header, true ) ) {
print( level.getRuntime(), header );
}
}
// and print the ordinary page header ..
final Band b = report.getPageHeader();
if ( isPageHeaderPrinting( b, false ) ) {
if ( runtime == null ) {
runtime = createRuntime( state.getFlowController().getMasterRow(), state, processingContext );
}
print( runtime, b );
}
return runtime;
}
protected ExpressionRuntime updateRepeatingGroupHeader( final ReportState state,
final ProcessingContext processingContext, final ReportDefinition report, final LayouterLevel[] levels,
ExpressionRuntime runtime ) throws ReportProcessingException {
if ( isDesignTime() ) {
return runtime;
}
/**
* Dive into the pending group to print the group header ...
*/
for ( int i = levels.length - 1; i >= 0; i -= 1 ) {
final LayouterLevel level = levels[i];
final ReportDefinition def = level.getReportDefinition();
for ( int gidx = 0; gidx <= level.getGroupIndex(); gidx++ ) {
final Group g = def.getGroup( gidx );
if ( g instanceof RelationalGroup ) {
final RelationalGroup rg = (RelationalGroup) g;
final GroupHeader header = rg.getHeader();
if ( isGroupSectionPrintableInternal( header, true, true ) ) {
print( level.getRuntime(), header );
}
}
}
if ( level.isInItemGroup() ) {
final DetailsHeader detailsHeader = def.getDetailsHeader();
if ( detailsHeader != null && isGroupSectionPrintableInternal( detailsHeader, true, true ) ) {
print( level.getRuntime(), detailsHeader );
}
}
}
final int groupsPrinted = state.getPresentationGroupIndex();
for ( int gidx = 0; gidx <= groupsPrinted; gidx++ ) {
final Group g = report.getGroup( gidx );
if ( g instanceof RelationalGroup ) {
final RelationalGroup rg = (RelationalGroup) g;
final GroupHeader header = rg.getHeader();
if ( isGroupSectionPrintableInternal( header, false, true ) ) {
if ( runtime == null ) {
runtime = createRuntime( state.getFlowController().getMasterRow(), state, processingContext );
}
print( runtime, header );
}
}
}
return runtime;
}
protected ExpressionRuntime updateDetailsHeader( final ReportState state, final ProcessingContext processingContext,
final ReportDefinition report, ExpressionRuntime runtime ) throws ReportProcessingException {
if ( isDesignTime() ) {
return runtime;
}
if ( state.isInItemGroup() ) {
final DetailsHeader detailsHeader = report.getDetailsHeader();
if ( detailsHeader != null && isGroupSectionPrintableInternal( detailsHeader, false, true ) ) {
if ( runtime == null ) {
runtime = createRuntime( state.getFlowController().getMasterRow(), state, processingContext );
}
print( runtime, detailsHeader );
}
}
return runtime;
}
/**
* Receives notification that a page has ended.
* <p/>
* This prints the PageFooter. If this is the first page, the footer is not printed if the pagefooter style-flag
* DISPLAY_ON_FIRSTPAGE is set to false. If this event is known to be the last pageFinished event, the
* DISPLAY_ON_LASTPAGE is evaluated and the footer is printed only if this flag is set to TRUE.
* <p/>
*
* @param event
* the report event.
*/
public void pageFinished( final ReportEvent event ) {
setCurrentEvent( event );
try {
updateFooterArea( event );
} catch ( final InvalidReportStateException fe ) {
throw fe;
} catch ( final Exception e ) {
throw new InvalidReportStateException( "PageFinished failed", e );
} finally {
clearCurrentEvent();
}
}
public void updateFooterArea( final ReportEvent event ) throws ReportProcessingException {
final OutputProcessorMetaData metaData = renderer.getOutputProcessor().getMetaData();
if ( metaData.isFeatureSupported( OutputProcessorFeature.PAGE_SECTIONS ) == false ) {
return;
}
if ( event.getState().isInlineProcess() ) {
return;
}
final LayouterLevel[] levels =
DefaultOutputFunction.collectSubReportStates( event.getState(), getRuntime().getProcessingContext() );
if ( isSubReportConfigurationChanged( levels ) ) {
clearedFooter = true;
refreshSubReportFooterConfiguration( levels );
}
updateRepeatingFooters( event, levels );
updatePageFooter( event, levels );
clearedFooter = false;
}
private void refreshSubReportFooterConfiguration( final LayouterLevel[] levels ) {
subReportFooterTracker.clear();
for ( final LayouterLevel level : levels ) {
subReportFooterTracker.add( level.getReportDefinition().getObjectID() );
}
}
private boolean isSubReportConfigurationChanged( final LayouterLevel[] levels ) {
if ( levels.length != subReportFooterTracker.size() ) {
return true;
}
for ( int i = 0; i < subReportFooterTracker.size(); i++ ) {
InstanceID instanceID = subReportFooterTracker.get( i );
if ( levels[i].getReportDefinition().getObjectID() != instanceID ) {
return true;
}
}
return false;
}
protected boolean updatePageFooter( final ReportEvent event, final LayouterLevel[] levels )
throws ReportProcessingException {
final ReportDefinition report = event.getReport();
final int levelCount = levels.length;
final DataRow dataRow = event.getDataRow();
final PageFooter pageFooter = report.getPageFooter();
boolean needPrinting = isPageFooterPrinting( levels, levelCount, dataRow, pageFooter );
if ( needPrinting == false ) {
avoidedFooter += 1;
return false;
}
renderer.startSection( Renderer.SectionType.FOOTER );
if ( isPageFooterPrintable( pageFooter, false ) ) {
print( getRuntime(), pageFooter );
} else {
printEmptyRootLevelBand();
}
for ( int i = 0; i < levelCount; i++ ) {
final LayouterLevel level = levels[i];
final ReportDefinition def = level.getReportDefinition();
final PageFooter b = def.getPageFooter();
if ( isPageFooterPrintable( b, true ) ) {
print( level.getRuntime(), b );
} else {
printEmptyRootLevelBand();
}
}
addSubReportMarkers( renderer.endSection() );
printedFooter += 1;
return true;
}
private boolean isPageFooterPrinting( final LayouterLevel[] levels, final int levelCount, final DataRow dataRow,
final PageFooter pageFooter ) {
if ( isDesignTime() ) {
return true;
}
if ( clearedFooter ) {
return true;
}
if ( isPageFooterPrintable( pageFooter, false ) && elementChangeChecker.isBandChanged( pageFooter, dataRow ) ) {
return true;
}
for ( int i = 0; i < levelCount; i++ ) {
final LayouterLevel level = levels[i];
final ReportDefinition def = level.getReportDefinition();
final PageFooter b = def.getPageFooter();
if ( isPageFooterPrintable( b, true ) && elementChangeChecker.isBandChanged( b, dataRow ) ) {
return true;
}
}
return false;
}
protected boolean updateRepeatingFooters( final ReportEvent event, final LayouterLevel[] levels )
throws ReportProcessingException {
final ReportDefinition report = event.getReport();
final ReportState state = event.getState();
final int groupsPrinted = state.getPresentationGroupIndex();
final int levelCount = levels.length;
final boolean needPrinting = isNeedPrintRepeatingFooter( event, levels );
if ( needPrinting == false ) {
avoidedRepeatingFooter += 1;
return false;
}
renderer.startSection( Renderer.SectionType.REPEAT_FOOTER );
if ( state.isInItemGroup() ) {
final DetailsFooter footer = report.getDetailsFooter();
if ( isGroupSectionPrintableInternal( footer, false, true ) ) {
print( getRuntime(), footer );
}
}
/**
* Repeating group header are only printed while ItemElements are processed.
*/
for ( int gidx = groupsPrinted; gidx >= 0; gidx -= 1 ) {
final Group g = report.getGroup( gidx );
if ( g instanceof RelationalGroup ) {
final RelationalGroup rg = (RelationalGroup) g;
final GroupFooter footer = rg.getFooter();
if ( isGroupSectionPrintableInternal( footer, false, true ) ) {
print( getRuntime(), footer );
}
}
}
for ( int i = 0; i < levelCount; i++ ) {
final LayouterLevel level = levels[i];
final ReportDefinition def = level.getReportDefinition();
if ( level.isInItemGroup() ) {
final DetailsFooter detailsFooter = def.getDetailsFooter();
if ( detailsFooter != null ) {
if ( isGroupSectionPrintableInternal( detailsFooter, true, true ) ) {
print( level.getRuntime(), detailsFooter );
}
}
}
for ( int gidx = level.getGroupIndex(); gidx >= 0; gidx -= 1 ) {
final Group g = def.getGroup( gidx );
if ( g instanceof RelationalGroup ) {
final RelationalGroup rg = (RelationalGroup) g;
final GroupFooter footer = rg.getFooter();
if ( isGroupSectionPrintableInternal( footer, true, true ) ) {
print( level.getRuntime(), footer );
}
}
}
}
addSubReportMarkers( renderer.endSection() );
printedRepeatingFooter += 1;
return true;
}
protected boolean isNeedPrintRepeatingFooter( final ReportEvent event, final LayouterLevel[] levels ) {
final ReportDefinition report = event.getReport();
final ReportState state = event.getState();
final int groupsPrinted = state.getPresentationGroupIndex();
final int levelCount = levels.length;
final DataRow dataRow = event.getDataRow();
if ( repeatingFooterValidator.isRepeatFooterValid( event, levels ) == false ) {
return true;
}
boolean needPrinting = clearedFooter;
if ( needPrinting == false && state.isInItemGroup() ) {
final DetailsFooter footer = report.getDetailsFooter();
if ( isGroupSectionPrintableInternal( footer, false, true )
&& elementChangeChecker.isBandChanged( footer, dataRow ) ) {
needPrinting = true;
}
}
/**
* Repeating group header are only printed while ItemElements are processed.
*/
if ( needPrinting == false ) {
for ( int gidx = groupsPrinted; gidx >= 0; gidx -= 1 ) {
final Group g = report.getGroup( gidx );
if ( g instanceof RelationalGroup ) {
final RelationalGroup rg = (RelationalGroup) g;
final GroupFooter footer = rg.getFooter();
if ( isGroupSectionPrintableInternal( footer, false, true )
&& elementChangeChecker.isBandChanged( footer, dataRow ) ) {
needPrinting = true;
}
}
}
}
if ( needPrinting == false ) {
for ( int i = 0; i < levelCount; i++ ) {
final LayouterLevel level = levels[i];
final ReportDefinition def = level.getReportDefinition();
if ( level.isInItemGroup() ) {
final DetailsFooter detailsFooter = def.getDetailsFooter();
if ( detailsFooter != null ) {
if ( isGroupSectionPrintableInternal( detailsFooter, true, true )
&& elementChangeChecker.isBandChanged( detailsFooter, dataRow ) ) {
needPrinting = true;
}
}
}
if ( needPrinting == false ) {
for ( int gidx = level.getGroupIndex(); gidx >= 0; gidx -= 1 ) {
final Group g = def.getGroup( gidx );
if ( g instanceof RelationalGroup ) {
final RelationalGroup rg = (RelationalGroup) g;
final GroupFooter footer = rg.getFooter();
if ( isGroupSectionPrintableInternal( footer, true, true )
&& elementChangeChecker.isBandChanged( footer, dataRow ) ) {
needPrinting = true;
}
}
}
}
}
}
return needPrinting;
}
protected boolean isGroupSectionPrintableInternal( final Band b, final boolean testSticky, final boolean testRepeat ) {
return isGroupSectionPrintable( b, testSticky, testRepeat );
}
public static boolean isGroupSectionPrintable( final Band b, final boolean testSticky, final boolean testRepeat ) {
final StyleSheet resolverStyleSheet = b.getComputedStyle();
if ( testSticky && resolverStyleSheet.getBooleanStyleProperty( BandStyleKeys.STICKY ) == false ) {
return false;
}
if ( testRepeat && resolverStyleSheet.getBooleanStyleProperty( BandStyleKeys.REPEAT_HEADER ) == false ) {
return false;
}
return true;
}
protected boolean isPageFooterPrintable( final Band b, final boolean testSticky ) {
final StyleSheet resolverStyleSheet = b.getComputedStyle();
if ( testSticky && resolverStyleSheet.getBooleanStyleProperty( BandStyleKeys.STICKY ) == false ) {
return false;
}
if ( computeCurrentPage() == 1 ) {
if ( resolverStyleSheet.getBooleanStyleProperty( BandStyleKeys.DISPLAY_ON_FIRSTPAGE ) == true ) {
return true;
} else {
return false;
}
} else if ( isLastPagebreak() ) {
if ( resolverStyleSheet.getBooleanStyleProperty( BandStyleKeys.DISPLAY_ON_LASTPAGE ) == true ) {
return true;
} else {
return false;
}
} else {
return true;
}
}
/**
* Returns the current report event.
*
* @return the event.
*/
protected ReportEvent getCurrentEvent() {
return currentEvent;
}
/**
* Sets the current event (also updates the report reference).
*
* @param currentEvent
* event.
*/
protected void setCurrentEvent( final ReportEvent currentEvent ) {
if ( currentEvent == null ) {
throw new NullPointerException( "Event must not be null." );
}
this.currentEvent = currentEvent;
this.pagebreakHandler.setReportState( currentEvent.getState() );
this.renderer.setStateKey( currentEvent.getState().getProcessKey() );
}
/**
* Clears the current event.
*/
protected void clearCurrentEvent() {
if ( currentEvent == null ) {
throw new IllegalStateException( "ClearCurrentEvent called without Event set:" );
}
this.currentEvent = null;
this.pagebreakHandler.setReportState( null );
this.renderer.setStateKey( null );
}
/**
* Clones the function.
* <P>
* Be aware, this does not create a deep copy. If you have complex strucures contained in objects, you have to
* override this function.
*
* @return a clone of this function.
* @throws CloneNotSupportedException
* this should never happen.
*/
public final Object clone() throws CloneNotSupportedException {
final DefaultOutputFunction sl = (DefaultOutputFunction) super.clone();
sl.repeatingFooterValidator = repeatingFooterValidator.clone();
sl.currentEvent = null;
sl.inlineSubreports = (ArrayList<InlineSubreportMarker>) inlineSubreports.clone();
sl.outputHandlers = outputHandlers.clone();
sl.renderedCrosstabLayouts = renderedCrosstabLayouts.clone();
sl.renderedCrosstabLayouts.clear();
final int rSize = renderedCrosstabLayouts.size();
for ( int i = 0; i < rSize; i++ ) {
final RenderedCrosstabLayout o = renderedCrosstabLayouts.get( i );
sl.renderedCrosstabLayouts.push( (RenderedCrosstabLayout) o.clone() );
}
return sl;
}
public Expression getInstance() {
return deriveForStorage();
}
/**
* Creates a storage-copy of the output function. A storage copy must create a deep clone of all referenced objects so
* that it is guaranteed that changes to either the original or the clone do not affect the other instance.
* <p/>
* Any failure to implement this method correctly will be a great source of very subtle bugs.
*
* @return the deep clone.
*/
public OutputFunction deriveForStorage() {
try {
final DefaultOutputFunction sl = (DefaultOutputFunction) super.clone();
sl.repeatingFooterValidator = repeatingFooterValidator.clone();
sl.renderer = renderer.deriveForStorage();
sl.inlineSubreports = (ArrayList<InlineSubreportMarker>) inlineSubreports.clone();
sl.currentEvent = null;
sl.pagebreakHandler = (DefaultLayoutPagebreakHandler) pagebreakHandler.clone();
sl.pagebreakHandler.setReportState( null );
sl.outputHandlers = outputHandlers.clone();
sl.renderedCrosstabLayouts = renderedCrosstabLayouts.clone();
sl.renderedCrosstabLayouts.clear();
final int rSize = renderedCrosstabLayouts.size();
for ( int i = 0; i < rSize; i++ ) {
final RenderedCrosstabLayout o = renderedCrosstabLayouts.get( i );
sl.renderedCrosstabLayouts.push( o.derive() );
}
return sl;
} catch ( final CloneNotSupportedException e ) {
throw new IllegalStateException();
}
}
/**
* Creates a cheaper version of the deep-copy of the output function. A pagebreak-derivate is created on every
* possible pagebreak position and must contain all undo/rollback information to restore the state of any shared
* object when a roll-back is requested.
* <p/>
* Any failure to implement this method correctly will be a great source of very subtle bugs.
*
* @return the deep clone.
*/
public OutputFunction deriveForPagebreak() {
try {
final DefaultOutputFunction sl = (DefaultOutputFunction) super.clone();
sl.repeatingFooterValidator = repeatingFooterValidator.clone();
sl.renderer = renderer.deriveForPagebreak();
sl.inlineSubreports = (ArrayList<InlineSubreportMarker>) inlineSubreports.clone();
sl.currentEvent = null;
sl.pagebreakHandler = (DefaultLayoutPagebreakHandler) pagebreakHandler.clone();
sl.outputHandlers = outputHandlers.clone();
sl.renderedCrosstabLayouts = renderedCrosstabLayouts.clone();
sl.renderedCrosstabLayouts.clear();
final int rSize = renderedCrosstabLayouts.size();
for ( int i = 0; i < rSize; i++ ) {
final RenderedCrosstabLayout o = renderedCrosstabLayouts.get( i );
sl.renderedCrosstabLayouts.push( o.derive() );
}
return sl;
} catch ( final CloneNotSupportedException e ) {
throw new IllegalStateException();
}
}
public void setRenderer( final Renderer renderer ) {
this.renderer = renderer;
}
protected boolean isDesignTime() {
return false;
}
public Renderer getRenderer() {
return renderer;
}
/**
* Prints the given band at the current cursor position.
*
* @param dataRow
* the datarow for evaluating the band's value-expressions.
* @param band
* the band to be printed.
* @throws ReportProcessingException
* if an error occured during the layout computation.
*/
public void print( final ExpressionRuntime dataRow, final Band band ) throws ReportProcessingException {
renderer.add( band, dataRow );
}
protected void printEmptyRootLevelBand() throws ReportProcessingException {
renderer.addEmptyRootLevelBand();
}
private void clearPendingPageStart( final ReportEvent event ) {
clearPendingPageStart( event, false );
}
private void clearPendingPageStart( final ReportEvent event, final boolean force ) {
pagebreakHandler.setReportState( event.getState() );
try {
if ( renderer.clearPendingPageStart( pagebreakHandler ) ) {
// page started has been fired ...
return;
}
if ( !force ) {
final boolean currentPageEmpty = renderer.isCurrentPageEmpty();
if ( currentPageEmpty == false ) {
return;
}
final boolean validateResult = renderer.validatePages() != Renderer.LayoutResult.LAYOUT_UNVALIDATABLE;
if ( validateResult == false ) {
return;
}
}
try {
setCurrentEvent( event );
renderer.newPageStarted();
clearedFooter = true;
updateHeaderArea( event.getState() );
} finally {
clearCurrentEvent();
}
} catch ( final ReportProcessingException e ) {
throw new InvalidReportStateException( "Failed to update the page-header", e );
} catch ( final ContentProcessingException e ) {
throw new InvalidReportStateException( "Failed to update the page-header", e );
} finally {
pagebreakHandler.setReportState( null );
}
}
public InlineSubreportMarker[] getInlineSubreports() {
if ( inlineSubreports.isEmpty() ) {
return EMPTY_INLINE_SUBREPORT_MARKERS;
}
return inlineSubreports.toArray( new InlineSubreportMarker[inlineSubreports.size()] );
}
public void clearInlineSubreports( final SubReportProcessType inlineExecution ) {
final InlineSubreportMarker[] subreports = getInlineSubreports();
for ( int i = subreports.length - 1; i >= 0; i-- ) {
final InlineSubreportMarker subreport = subreports[i];
if ( inlineExecution == subreport.getProcessType() ) {
inlineSubreports.remove( i );
}
}
}
public RenderedCrosstabLayout startRenderedCrosstabLayout() {
final RenderedCrosstabLayout layout = new RenderedCrosstabLayout();
renderedCrosstabLayouts.push( layout );
return layout;
}
public RenderedCrosstabLayout getCurrentRenderedCrosstabLayout() {
return renderedCrosstabLayouts.peek();
}
public void endRenderedCrosstabLayout() {
renderedCrosstabLayouts.pop();
}
public void restart( final ReportState state ) throws ReportProcessingException {
final ReportEvent event = new ReportEvent( state, state.getEventCode() );
clearPendingPageStart( event, true );
}
public boolean createRollbackInformation() {
final Renderer commitableRenderer = getRenderer();
commitableRenderer.createRollbackInformation();
return true;
}
}