/*
* 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;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
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.Group;
import org.pentaho.reporting.engine.classic.core.GroupBody;
import org.pentaho.reporting.engine.classic.core.InvalidReportStateException;
import org.pentaho.reporting.engine.classic.core.PerformanceTags;
import org.pentaho.reporting.engine.classic.core.ReportDefinition;
import org.pentaho.reporting.engine.classic.core.ReportProcessingException;
import org.pentaho.reporting.engine.classic.core.function.ExpressionRuntime;
import org.pentaho.reporting.engine.classic.core.function.ProcessingContext;
import org.pentaho.reporting.engine.classic.core.layout.build.LayoutModelBuilder;
import org.pentaho.reporting.engine.classic.core.layout.build.RenderModelBuilder;
import org.pentaho.reporting.engine.classic.core.layout.build.ReportRenderModelBuilder;
import org.pentaho.reporting.engine.classic.core.layout.model.LogicalPageBox;
import org.pentaho.reporting.engine.classic.core.layout.output.ContentProcessingException;
import org.pentaho.reporting.engine.classic.core.layout.output.LayoutPagebreakHandler;
import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessor;
import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessorFeature;
import org.pentaho.reporting.engine.classic.core.layout.output.OutputProcessorMetaData;
import org.pentaho.reporting.engine.classic.core.layout.output.ValidateSafeToStoreStateStep;
import org.pentaho.reporting.engine.classic.core.layout.process.ApplyAutoCommitStep;
import org.pentaho.reporting.engine.classic.core.layout.process.ApplyCachedValuesStep;
import org.pentaho.reporting.engine.classic.core.layout.process.ApplyCommitStep;
import org.pentaho.reporting.engine.classic.core.layout.process.CanvasMajorAxisLayoutStep;
import org.pentaho.reporting.engine.classic.core.layout.process.CanvasMinorAxisLayoutStep;
import org.pentaho.reporting.engine.classic.core.layout.process.CommitStep;
import org.pentaho.reporting.engine.classic.core.layout.process.ComputeStaticPropertiesProcessStep;
import org.pentaho.reporting.engine.classic.core.layout.process.InfiniteMajorAxisLayoutStep;
import org.pentaho.reporting.engine.classic.core.layout.process.InfiniteMinorAxisLayoutStep;
import org.pentaho.reporting.engine.classic.core.layout.process.ParagraphLineBreakStep;
import org.pentaho.reporting.engine.classic.core.layout.process.RollbackStep;
import org.pentaho.reporting.engine.classic.core.layout.process.TableValidationStep;
import org.pentaho.reporting.engine.classic.core.layout.process.ValidateModelStep;
import org.pentaho.reporting.engine.classic.core.states.PerformanceMonitorContext;
import org.pentaho.reporting.engine.classic.core.states.ReportStateKey;
import org.pentaho.reporting.engine.classic.core.states.process.ProcessState;
import org.pentaho.reporting.engine.classic.core.util.InstanceID;
import org.pentaho.reporting.libraries.base.util.ArgumentNullException;
import org.pentaho.reporting.libraries.base.util.PerformanceLoggingStopWatch;
/**
* The LayoutSystem is a simplified version of the LibLayout-rendering system.
*
* @author Thomas Morgner
* @noinspection HardCodedStringLiteral
*/
public abstract class AbstractRenderer implements Renderer {
private class CloseListener implements ChangeListener {
public void stateChanged( final ChangeEvent e ) {
close();
}
}
private static final Log logger = LogFactory.getLog( AbstractRenderer.class );
private RenderModelBuilder renderModelBuilder;
private ValidateModelStep validateModelStep;
private ComputeStaticPropertiesProcessStep staticPropertiesStep;
private ParagraphLineBreakStep paragraphLineBreakStep;
private InfiniteMinorAxisLayoutStep minorAxisLayoutStep;
private CanvasMinorAxisLayoutStep canvasMinorAxisLayoutStep;
private InfiniteMajorAxisLayoutStep majorAxisLayoutStep;
private CanvasMajorAxisLayoutStep canvasMajorAxisLayoutStep;
private ValidateSafeToStoreStateStep validateSafeToStoreStateStep;
private TableValidationStep tableValidationStep;
private CommitStep commitStep;
private ApplyCommitStep applyCommitStep;
private RollbackStep rollbackStep;
private ApplyAutoCommitStep applyAutoCommitStep;
private OutputProcessor outputProcessor;
private int pagebreaks;
private boolean dirty;
private ReportStateKey lastStateKey;
private ApplyCachedValuesStep applyCachedValuesStep;
private boolean readOnly;
private boolean paranoidChecks;
private boolean wrapProgressMarkerInSection;
private LayoutResult lastValidateResult;
private PerformanceLoggingStopWatch validateStopWatch;
private PerformanceLoggingStopWatch paginateStopWatch;
private PerformanceMonitorContext performanceMonitorContext;
protected AbstractRenderer( final OutputProcessor outputProcessor ) {
this.outputProcessor = outputProcessor;
this.validateModelStep = new ValidateModelStep();
this.staticPropertiesStep = new ComputeStaticPropertiesProcessStep();
this.paragraphLineBreakStep = new ParagraphLineBreakStep();
this.minorAxisLayoutStep = new InfiniteMinorAxisLayoutStep();
this.canvasMinorAxisLayoutStep = new CanvasMinorAxisLayoutStep();
this.majorAxisLayoutStep = new InfiniteMajorAxisLayoutStep();
this.canvasMajorAxisLayoutStep = new CanvasMajorAxisLayoutStep();
this.validateSafeToStoreStateStep = new ValidateSafeToStoreStateStep();
this.applyCachedValuesStep = new ApplyCachedValuesStep();
this.commitStep = new CommitStep();
this.applyAutoCommitStep = new ApplyAutoCommitStep();
this.applyCommitStep = new ApplyCommitStep();
this.rollbackStep = new RollbackStep();
this.tableValidationStep = new TableValidationStep();
}
protected void initialize() {
this.renderModelBuilder = createRenderModelBuilder();
}
protected ReportRenderModelBuilder createRenderModelBuilder() {
return new ReportRenderModelBuilder( createComponentFactory() );
}
protected RenderComponentFactory createComponentFactory() {
return new DefaultRenderComponentFactory();
}
public LayoutModelBuilder getNormalFlowLayoutModelBuilder() {
return renderModelBuilder.getNormalFlowLayoutModelBuilder();
}
public int getPageCount() {
return 0;
}
protected RenderModelBuilder getRenderModelBuilder() {
return renderModelBuilder;
}
protected LogicalPageBox getPageBox() {
return renderModelBuilder.getPageBox();
}
public boolean isSafeToStore() {
final LogicalPageBox pageBox = getPageBox();
if ( pageBox == null ) {
return true;
}
return validateSafeToStoreStateStep.isSafeToStore( pageBox );
}
protected OutputProcessorMetaData getMetaData() {
return getOutputProcessor().getMetaData();
}
public void setStateKey( final ReportStateKey stateKey ) {
renderModelBuilder.updateStateKey( stateKey );
}
public OutputProcessor getOutputProcessor() {
return outputProcessor;
}
protected boolean isWidowOrphanDefinitionsEncountered() {
return staticPropertiesStep.isWidowOrphanDefinitionsEncountered();
}
public void startReport( final ReportDefinition report, final ProcessingContext processingContext,
final PerformanceMonitorContext performanceMonitorContext ) {
ArgumentNullException.validate( "report", report );
ArgumentNullException.validate( "processingContext", processingContext );
ArgumentNullException.validate( "performanceMonitorContext", performanceMonitorContext );
if ( readOnly ) {
throw new IllegalStateException();
}
this.performanceMonitorContext = performanceMonitorContext;
this.performanceMonitorContext.addChangeListener( new CloseListener() );
this.validateStopWatch = performanceMonitorContext.createStopWatch( PerformanceTags.REPORT_LAYOUT_VALIDATE );
this.paginateStopWatch = performanceMonitorContext.createStopWatch( PerformanceTags.REPORT_LAYOUT_PROCESS );
this.majorAxisLayoutStep.initializePerformanceMonitoring( performanceMonitorContext );
this.canvasMajorAxisLayoutStep.initializePerformanceMonitoring( performanceMonitorContext );
this.minorAxisLayoutStep.initializePerformanceMonitoring( performanceMonitorContext );
this.canvasMinorAxisLayoutStep.initializePerformanceMonitoring( performanceMonitorContext );
outputProcessor.processingStarted( report, processingContext );
initializeRendererOnStartReport( processingContext );
renderModelBuilder.startReport( report, processingContext );
markDirty();
}
protected void initializeRendererOnStartReport( final ProcessingContext processingContext ) {
final OutputProcessorMetaData metaData = getMetaData();
this.paranoidChecks =
"true".equals( metaData.getConfiguration().getConfigProperty(
"org.pentaho.reporting.engine.classic.core.layout.ParanoidChecks" ) )
&& processingContext.getOutputProcessorMetaData().isFeatureSupported( OutputProcessorFeature.DESIGNTIME ) == false;
this.wrapProgressMarkerInSection =
"true".equals( metaData.getConfiguration().getConfigProperty(
"org.pentaho.reporting.engine.classic.core.legacy.WrapProgressMarkerInSection" ) );
staticPropertiesStep.initialize( metaData, processingContext );
canvasMinorAxisLayoutStep.initialize( metaData, processingContext );
minorAxisLayoutStep.initialize( metaData );
canvasMajorAxisLayoutStep.initialize( metaData );
majorAxisLayoutStep.initialize( metaData );
}
public void startSubReport( final ReportDefinition report, final InstanceID insertationPoint ) {
if ( readOnly ) {
throw new IllegalStateException( "Renderer is marked read-only" );
}
renderModelBuilder.startSubReport( report, insertationPoint );
}
public void startGroup( final Group group, final Integer predictedStateCount ) {
if ( readOnly ) {
throw new IllegalStateException();
}
renderModelBuilder.startGroup( group, 5 );
}
public void startGroupBody( final GroupBody groupBody, final Integer predictedStateCount ) {
if ( logger.isDebugEnabled() ) {
logger.debug( "Group-Body: Predicted size: " + predictedStateCount );
}
if ( readOnly ) {
throw new IllegalStateException();
}
renderModelBuilder.startGroupBody( groupBody, predictedStateCount );
markDirty();
}
public void startSection( final SectionType type ) {
if ( readOnly ) {
throw new IllegalStateException();
}
renderModelBuilder.startSection( type );
}
public InlineSubreportMarker[] endSection() {
if ( readOnly ) {
throw new IllegalStateException();
}
// todo: Cheap hack for now. Convert this into a real check that figures out whether real changes have been done.
final RenderModelBuilder.SectionResult result = renderModelBuilder.endSection();
if ( result.isEmpty() == false ) {
markDirty();
}
return result.getSubreportMarkers();
}
public void endGroupBody() {
if ( readOnly ) {
throw new IllegalStateException();
}
renderModelBuilder.endGroupBody();
markDirty();
}
public void endGroup() {
if ( readOnly ) {
throw new IllegalStateException();
}
renderModelBuilder.endGroup();
markDirty();
}
public void endSubReport() {
if ( readOnly ) {
throw new IllegalStateException();
}
renderModelBuilder.endSubReport();
markDirty();
}
public void endReport() {
if ( readOnly ) {
throw new IllegalStateException();
}
renderModelBuilder.endReport();
markDirty();
}
public void addEmptyRootLevelBand() throws ReportProcessingException {
if ( readOnly ) {
throw new IllegalStateException();
}
renderModelBuilder.addEmptyRootLevelBand();
}
public void addProgressBox() throws ReportProcessingException {
if ( readOnly ) {
throw new IllegalStateException();
}
if ( wrapProgressMarkerInSection ) {
renderModelBuilder.startSection( SectionType.NORMALFLOW );
renderModelBuilder.addProgressBox();
renderModelBuilder.endSection();
} else {
renderModelBuilder.addProgressBox();
}
}
public void add( final Band band, final ExpressionRuntime runtime ) throws ReportProcessingException {
if ( readOnly ) {
throw new IllegalStateException();
}
renderModelBuilder.add( runtime, band );
}
public void addToNormalFlow( final Band band, final ExpressionRuntime runtime ) throws ReportProcessingException {
if ( readOnly ) {
throw new IllegalStateException();
}
renderModelBuilder.addToNormalFlow( runtime, band );
}
public LayoutResult validatePages() throws ContentProcessingException {
if ( readOnly ) {
throw new IllegalStateException();
}
try {
validateStopWatch.start();
final LogicalPageBox pageBox = getPageBox();
if ( pageBox == null ) {
// StartReport has not been called yet ..
lastValidateResult = LayoutResult.LAYOUT_UNVALIDATABLE;
return LayoutResult.LAYOUT_UNVALIDATABLE;
}
if ( !dirty && lastValidateResult != null ) {
return lastValidateResult;
}
setLastStateKey( null );
setPagebreaks( 0 );
if ( validateModelStep.isLayoutable( pageBox ) == false ) { // STRUCT
if ( logger.isDebugEnabled() ) {
logger.debug( "Content-Ref# " + pageBox.getContentRefCount() );
}
lastValidateResult = LayoutResult.LAYOUT_UNVALIDATABLE;
return LayoutResult.LAYOUT_UNVALIDATABLE;
}
// These structural processors will skip old nodes. These beasts cannot be cached otherwise.
tableValidationStep.validate( pageBox ); // STRUCT
paragraphLineBreakStep.compute( pageBox ); // STRUCT
staticPropertiesStep.compute( pageBox ); // STRUCT
minorAxisLayoutStep.compute( pageBox ); // VISUAL
canvasMinorAxisLayoutStep.compute( pageBox ); // VISUAL
majorAxisLayoutStep.compute( pageBox ); // VISUAL
canvasMajorAxisLayoutStep.compute( pageBox ); // VISUAL
if ( preparePagination( pageBox ) == false ) {
return LayoutResult.LAYOUT_UNVALIDATABLE;
}
applyCachedValuesStep.compute( pageBox ); // STRUCT
if ( isPageFinished() ) {
lastValidateResult = LayoutResult.LAYOUT_PAGEBREAK;
return LayoutResult.LAYOUT_PAGEBREAK;
} else {
lastValidateResult = LayoutResult.LAYOUT_NO_PAGEBREAK;
return LayoutResult.LAYOUT_NO_PAGEBREAK;
}
} finally {
validateStopWatch.stop( true );
}
}
protected boolean preparePagination( final LogicalPageBox pageBox ) {
return true;
}
protected void clearDirty() {
dirty = false;
}
protected abstract boolean isPageFinished();
public void processIncrementalUpdate( final boolean performOutput ) throws ContentProcessingException {
// dirty = false;
}
public boolean processPage( final LayoutPagebreakHandler handler, final Object commitMarker,
final boolean performOutput ) throws ContentProcessingException {
if ( readOnly ) {
throw new IllegalStateException();
}
try {
paginateStopWatch.start();
final LogicalPageBox pageBox = getPageBox();
if ( pageBox == null ) {
// StartReport has not been called yet ..
// Log.debug ("PageBox null");
return false;
}
if ( dirty == false ) {
// Log.debug ("Not dirty");
return false;
}
setLastStateKey( null );
setPagebreaks( 0 );
if ( validateModelStep.isLayoutable( pageBox ) == false ) {
logger.debug( "Not layoutable" );
return false;
}
// processes the current page
boolean repeat = true;
while ( repeat ) {
if ( handler != null ) {
// make sure we generate an up-to-date page-footer. This also implies that there
// are more page-finished than page-started events generated during the report processing.
handler.pageFinished();
}
if ( outputProcessor.getMetaData().isFeatureSupported( OutputProcessorFeature.PAGEBREAKS ) ) {
createRollbackInformation();
applyRollbackInformation();
performParanoidModelCheck();
}
tableValidationStep.validate( pageBox ); // STRUCT
paragraphLineBreakStep.compute( pageBox ); // STRUCT
staticPropertiesStep.compute( pageBox ); // VISUAL
minorAxisLayoutStep.compute( pageBox );
canvasMinorAxisLayoutStep.compute( pageBox );
majorAxisLayoutStep.compute( pageBox );
canvasMajorAxisLayoutStep.compute( pageBox );
if ( preparePagination( pageBox ) == false ) {
return ( pagebreaks > 0 );
}
applyCachedValuesStep.compute( pageBox );
repeat = performPagination( handler, performOutput );
}
clearDirty();
return ( pagebreaks > 0 );
} finally {
validateStopWatch.stop( isOpen() );
paginateStopWatch.stop( isOpen() );
}
}
protected abstract boolean performPagination( LayoutPagebreakHandler handler, final boolean performOutput )
throws ContentProcessingException;
/**
* A hook to allow easier debugging.
*
* @param pageBox
* the current page box.
* @noinspection NoopMethodInAbstractClass
*/
protected void debugPrint( final LogicalPageBox pageBox ) {
}
public ReportStateKey getLastStateKey() {
return lastStateKey;
}
protected void setLastStateKey( final ReportStateKey lastStateKey ) {
this.lastStateKey = lastStateKey;
}
protected void setPagebreaks( final int pagebreaks ) {
this.pagebreaks = pagebreaks;
}
public int getPagebreaks() {
return pagebreaks;
}
public boolean isOpen() {
final LogicalPageBox pageBox = getPageBox();
if ( pageBox == null ) {
return false;
}
return pageBox.isOpen();
}
public boolean isValid() {
return readOnly == false;
}
public Renderer deriveForStorage() {
try {
final AbstractRenderer renderer = (AbstractRenderer) clone();
renderer.readOnly = false;
renderer.renderModelBuilder = renderModelBuilder.deriveForStorage();
return renderer;
} catch ( CloneNotSupportedException cne ) {
throw new InvalidReportStateException( "Failed to derive Renderer", cne );
}
}
public Renderer deriveForPagebreak() {
try {
final AbstractRenderer renderer = (AbstractRenderer) clone();
renderer.readOnly = true;
renderer.renderModelBuilder = renderModelBuilder.deriveForPageBreak();
return renderer;
} catch ( CloneNotSupportedException cne ) {
throw new InvalidReportStateException( "Failed to derive Renderer", cne );
}
}
public void performParanoidModelCheck() {
if ( paranoidChecks ) {
renderModelBuilder.performParanoidModelCheck();
}
}
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
public void addPagebreak() {
if ( readOnly ) {
throw new IllegalStateException();
}
renderModelBuilder.addPageBreak();
}
public boolean clearPendingPageStart( final LayoutPagebreakHandler layoutPagebreakHandler ) {
// intentionally left empty.
return false;
}
public boolean isCurrentPageEmpty() {
return false;
}
public boolean isPageStartPending() {
return false;
}
public boolean isDirty() {
return dirty;
}
public void createRollbackInformation() {
final LogicalPageBox pageBox = getPageBox();
if ( pageBox != null ) {
commitStep.compute( pageBox );
}
}
public void applyRollbackInformation() {
final LogicalPageBox pageBox = getPageBox();
if ( pageBox != null ) {
applyCommitStep.compute( pageBox );
}
}
public void validateAfterCommit() {
if ( paranoidChecks ) {
renderModelBuilder.validateAfterCommit();
}
}
public void rollback() {
readOnly = false;
final LogicalPageBox pageBox = getPageBox();
if ( pageBox != null ) {
rollbackStep.compute( pageBox );
renderModelBuilder.restoreStateAfterRollback();
validateAfterCommit();
}
}
public void applyAutoCommit() {
final LogicalPageBox pageBox = getPageBox();
if ( pageBox != null ) {
applyAutoCommitStep.compute( pageBox );
}
}
public boolean isPendingPageHack() {
return false;
}
protected void markDirty() {
dirty = true;
lastValidateResult = null;
}
public void print() {
if ( renderModelBuilder.getPageBox() == null ) {
logger.info( "Printing impossible - Page-Box empty" );
} else {
ModelPrinter.INSTANCE.print( renderModelBuilder.getPageBox() );
}
}
public void newPageStarted() {
if ( logger.isDebugEnabled() ) {
logger.debug( "================================ CLEAR HEADER AND FOOTER ==================================: "
+ getPageCount() );
}
final LogicalPageBox pageBox = getPageBox();
pageBox.getFooterArea().clear();
pageBox.getRepeatFooterArea().clear();
pageBox.getHeaderArea().clear();
pageBox.getWatermarkArea().clear();
}
/**
* This is a debug helper function. It is not used in normal report runs. It helps debug layouter states and the
* roll-back system by dumping all layouts into a directory on the file system for automated diffs.
*
* @param state
* @param print
* @param rollback
*/
@SuppressWarnings( "UnusedDeclaration" )
public void printLayoutStateToFile( final ProcessState state, final boolean print, final boolean rollback ) {
/*
* if (((state.getSequenceCounter() <= 14440 || state.getSequenceCounter() >= 14445)) || (state.getSequenceCounter()
* % 1) != 0) { return; }
*/
String fileName = "test-output/" + state.getSequenceCounter();
fileName += print ? "-print" : "-paginate";
fileName += rollback ? "-rb" : "";
fileName += ".xml";
FileModelPrinter.print( fileName, getPageBox() );
}
protected PerformanceLoggingStopWatch getValidateStopWatch() {
return validateStopWatch;
}
protected PerformanceLoggingStopWatch getPaginateStopWatch() {
return paginateStopWatch;
}
protected PerformanceMonitorContext getPerformanceMonitorContext() {
return performanceMonitorContext;
}
protected void close() {
this.majorAxisLayoutStep.close();
this.canvasMajorAxisLayoutStep.close();
this.minorAxisLayoutStep.close();
this.canvasMinorAxisLayoutStep.close();
validateStopWatch.close();
paginateStopWatch.close();
}
}