Package org.pentaho.reporting.engine.classic.core.layout.output

Source Code of org.pentaho.reporting.engine.classic.core.layout.output.DefaultOutputFunction

/*
* 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;
  }
}
TOP

Related Classes of org.pentaho.reporting.engine.classic.core.layout.output.DefaultOutputFunction

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.