/*
* 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 java.util.ArrayList;
import javax.swing.table.TableModel;
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;
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;
}
}