/*! * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.reporting.engine.classic.core.states.datarow; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.reporting.engine.classic.core.DataRow; import org.pentaho.reporting.engine.classic.core.states.crosstab.CrosstabSpecification; import org.pentaho.reporting.libraries.base.util.ObjectUtilities; import javax.swing.table.TableModel; import java.util.Arrays; public class DataProcessor implements Cloneable { private static final Log logger = LogFactory.getLog( DataProcessor.class ); private PaddingController paddingDataRow; private ReportDataRow reportDataRow; private int paddingCount; private int cursor; private boolean prepadding; public DataProcessor() { cursor = 0; } public ReportDataRow getReportDataRow() { return reportDataRow; } public void clearReportDataRow( final MasterDataRowChangeHandler changeHandler ) { if ( this.reportDataRow == null ) { throw new IllegalStateException(); } final MasterDataRowChangeEvent event = changeHandler.getReusableEvent(); event.reuse( MasterDataRowChangeEvent.COLUMN_REMOVED, "", "" ); final int dataColCount = this.reportDataRow.getColumnCount(); for ( int i = dataColCount - 1; i >= 0; i-- ) { final String columnName = this.reportDataRow.getColumnName( i ); if ( columnName != null ) { event.setColumnName( columnName ); changeHandler.dataRowChanged( event ); } } this.reportDataRow = null; } public void setReportDataRow( final ReportDataRow reportDataRow, final MasterDataRowChangeHandler changeHandler ) { if ( reportDataRow == null ) { throw new NullPointerException(); } if ( this.reportDataRow != null ) { throw new IllegalStateException(); } this.reportDataRow = reportDataRow; final MasterDataRowChangeEvent event = changeHandler.getReusableEvent(); event.reuse( MasterDataRowChangeEvent.COLUMN_ADDED, "", null ); final boolean readable = reportDataRow.isReadable(); final int dataColCount = reportDataRow.getColumnCount(); for ( int i = 0; i < dataColCount; i++ ) { final String columnName = reportDataRow.getColumnName( i ); if ( columnName != null ) { event.setColumnName( columnName ); if ( readable ) { event.setColumnValue( reportDataRow.get( i ) ); } else { event.setColumnValue( null ); } changeHandler.dataRowChanged( event ); } } } public int getCursor() { return cursor; } public int getRawDataCursor() { return reportDataRow.getCursor(); } public TableModel getRawData() { return reportDataRow.getReportData(); } public DataProcessor advance( final boolean deepTraversingOnly, final FastGlobalView globalView ) { if ( deepTraversingOnly ) { return this; } final DataProcessor dataRow = derive(); dataRow.cursor += 1; if ( paddingDataRow != null ) { dataRow.paddingDataRow = paddingDataRow.advance(); if ( paddingCount > 0 ) { if ( logger.isDebugEnabled() ) { logger.debug( "Padding = " + dataRow.paddingCount + "; Cursor = " + dataRow.cursor ); } dataRow.paddingCount -= 1; dataRow.paddingDataRow.activate( globalView ); dataRow.paddingDataRow.refreshPaddedRow( globalView, dataRow.reportDataRow ); } else if ( dataRow.prepadding ) { dataRow.paddingDataRow.activate( globalView ); dataRow.paddingDataRow.refreshRow( globalView, reportDataRow ); refreshData( globalView, reportDataRow ); if ( logger.isDebugEnabled() ) { logger.debug( "Pre-Padding finished; Using dataset at cursor = " + dataRow.cursor + "; padding = " + dataRow.paddingCount ); } dataRow.prepadding = false; } else { final ReportDataRow tempReportDataRow = reportDataRow.advance(); final FastGlobalView tempGlobalView = globalView.derive(); refreshData( tempGlobalView, tempReportDataRow ); final Object[] tempRowKey = dataRow.paddingDataRow.createRowKey( tempGlobalView ); final Object[] oldRowKey = paddingDataRow.createRowKey( globalView ); // if the row key has changed ... if ( ObjectUtilities.equalArray( tempRowKey, oldRowKey ) == false ) { if ( logger.isDebugEnabled() ) { logger.debug( "TempRowKey=" + Arrays.asList( tempRowKey ) + "; RowKey=" + Arrays.asList( oldRowKey ) + " Cursor=" + dataRow.cursor ); } // .. check whether the current row has processed all column dimensions ... if ( dataRow.paddingDataRow.getCurrentCursorPosition() < dataRow.paddingDataRow.getCrosstabColumnCount() ) { // post padding mode. Ignore the advance, mark the next few advances as paddings until we completed all // column dimensions dataRow.paddingCount = dataRow.paddingDataRow.getCrosstabColumnCount() - dataRow.paddingDataRow.getCurrentCursorPosition() - 1; if ( logger.isDebugEnabled() ) { logger.debug( "RowKey Changed - Need post-padding; Cursor = " + dataRow.cursor + "; padding = " + dataRow.paddingCount ); } dataRow.paddingDataRow.activate( globalView ); dataRow.paddingDataRow.refreshPaddedRow( globalView, dataRow.reportDataRow ); } else { dataRow.paddingDataRow = dataRow.paddingDataRow.resetRowCursor(); dataRow.paddingCount = dataRow.paddingDataRow.getPrePaddingRows( tempGlobalView ); dataRow.reportDataRow = tempReportDataRow; if ( dataRow.paddingCount > 0 ) { if ( logger.isDebugEnabled() ) { logger.debug( "RowKey Changed, but detected need for Pre-Padding = " + dataRow.paddingCount + "; Cursor = " + dataRow.cursor ); } dataRow.paddingCount -= 1; dataRow.paddingDataRow.activate( globalView ); dataRow.paddingDataRow.refreshPaddedRow( globalView, dataRow.reportDataRow ); dataRow.prepadding = true; } else { if ( logger.isDebugEnabled() ) { logger.debug( "RowKey Changed; Advance; Cursor = " + dataRow.cursor + "; padding = " + dataRow.paddingCount ); } dataRow.paddingDataRow.activate( globalView ); dataRow.paddingDataRow.refreshRow( globalView, tempReportDataRow ); // refreshData(globalView, tempReportDataRow); } } } else { final Object[] tempColKey = dataRow.paddingDataRow.createColumnKey( tempGlobalView ); final Object[] oldColKey = dataRow.paddingDataRow.createColumnKey( globalView ); if ( ObjectUtilities.equalArray( tempColKey, oldColKey ) ) { if ( logger.isDebugEnabled() ) { logger.debug( "Row- and Column-Key still the same, staying in current crosstab-position = " + dataRow.paddingCount + "; Cursor = " + dataRow.cursor ); } // undo the advance for the padding data-row, but advance the report-data row dataRow.paddingDataRow = paddingDataRow; dataRow.reportDataRow = tempReportDataRow; dataRow.paddingDataRow.activate( globalView ); dataRow.paddingDataRow.refreshRow( globalView, tempReportDataRow ); // refreshData(globalView, tempReportDataRow); } else { // rowkey is still the same, so check for pre-paddings ... dataRow.paddingCount = dataRow.paddingDataRow.getPrePaddingRows( tempGlobalView ); if ( dataRow.paddingCount > 0 ) { if ( logger.isDebugEnabled() ) { logger.debug( "RowKey same, but detected need for Padding = " + dataRow.paddingCount + "; Cursor = " + dataRow.cursor ); } dataRow.paddingCount -= 1; dataRow.paddingDataRow.activate( globalView ); dataRow.paddingDataRow.refreshPaddedRow( globalView, dataRow.reportDataRow ); } else { if ( logger.isDebugEnabled() ) { logger.debug( "RowKey Same; Advance; Cursor = " + dataRow.cursor + "; padding = " + dataRow.paddingCount ); } dataRow.reportDataRow = tempReportDataRow; dataRow.paddingDataRow.activate( globalView ); dataRow.paddingDataRow.refreshRow( globalView, tempReportDataRow ); // refreshData(globalView, tempReportDataRow); } } } } } else if ( reportDataRow != null ) { dataRow.reportDataRow = reportDataRow.advance(); refreshData( globalView, dataRow.reportDataRow ); } return dataRow; } public boolean isAdvanceable( final DataRow globalView ) { if ( paddingDataRow != null ) { if ( paddingCount > 0 ) { return true; } if ( paddingDataRow.getPrePaddingRows( globalView ) > 0 ) { return true; } } if ( reportDataRow != null ) { if ( reportDataRow.isAdvanceable() ) { return true; } // at the end of the report, we should be also at the end of the columns ... if ( paddingDataRow != null ) { final int colsToGo = ( paddingDataRow.getCrosstabColumnCount() - paddingDataRow.getCurrentCursorPosition() ) - 1; if ( colsToGo > 0 ) { return true; } } } return false; } private static void refreshData( final MasterDataRowChangeHandler changeHandler, final ReportDataRow reportDataRow ) { if ( reportDataRow == null ) { return; } logger.debug( "Refreshing data" ); final MasterDataRowChangeEvent event = changeHandler.getReusableEvent(); event.reuse( MasterDataRowChangeEvent.COLUMN_UPDATED, "", null ); final int dataColCount = reportDataRow.getColumnCount(); final boolean readable = reportDataRow.isReadable(); for ( int i = 0; i < dataColCount; i++ ) { final String columnName = reportDataRow.getColumnName( i ); if ( columnName != null ) { event.setColumnName( columnName ); if ( readable ) { event.setColumnValue( reportDataRow.get( i ) ); } else { event.setColumnValue( null ); } changeHandler.dataRowChanged( event ); } } } public DataProcessor startCrosstabMode( final CrosstabSpecification crosstabSpecification, final FastGlobalView globalView ) { logger.debug( "Starting crosstab mode" ); final DataProcessor dataRow = derive(); dataRow.paddingDataRow = new PaddingController( crosstabSpecification ); final int prePaddingRows = dataRow.paddingDataRow.getPrePaddingRows( globalView ); if ( prePaddingRows > 0 ) { // The current position of the first data-row of this crosstab does point to the first computed column. // this means, we have to insert one or more artificial rows now. dataRow.paddingCount = prePaddingRows - 1; if ( logger.isDebugEnabled() ) { logger.debug( "Starting crosstab mode: cursor=" + dataRow.cursor + " pre-padding=" + dataRow.paddingCount ); } dataRow.paddingDataRow.activate( globalView ); dataRow.paddingDataRow.refreshPaddedRow( globalView, dataRow.reportDataRow ); dataRow.prepadding = true; } else { if ( logger.isDebugEnabled() ) { logger.debug( "Starting crosstab mode: cursor=" + dataRow.cursor + " pre-padding=" + dataRow.paddingCount ); } } return dataRow; } public void refresh( final FastGlobalView globalView ) { if ( paddingDataRow != null && reportDataRow != null ) { paddingDataRow.activate( globalView ); if ( paddingCount > 0 ) { paddingDataRow.refreshPaddedRow( globalView, reportDataRow ); } else { paddingDataRow.refreshRow( globalView, reportDataRow ); } } else if ( reportDataRow != null ) { refreshData( globalView, reportDataRow ); } } public DataProcessor endCrosstabMode() { logger.debug( "Ending crosstab mode" ); final DataProcessor retval = derive(); retval.paddingDataRow = null; return retval; } public DataProcessor resetRowCursor() { if ( paddingDataRow != null ) { final DataProcessor dataRow = derive(); dataRow.paddingDataRow = dataRow.paddingDataRow.resetRowCursor(); return dataRow; } return this; } public DataProcessor derive() { return this.clone(); } public DataProcessor clone() { try { final DataProcessor clone = (DataProcessor) super.clone(); if ( paddingDataRow != null ) { clone.paddingDataRow = this.paddingDataRow.clone(); } return clone; } catch ( CloneNotSupportedException e ) { throw new IllegalStateException(); } } public CrosstabSpecification getCrosstabSpecification() { if ( paddingDataRow == null ) { return null; } return paddingDataRow.getCrosstabSpecification(); } public boolean isCrosstabActive() { return paddingDataRow != null; } public boolean isSameState( final DataProcessor processor ) { if ( processor.prepadding != prepadding ) { return false; } if ( processor.cursor != cursor ) { return false; } if ( processor.paddingCount != paddingCount ) { return false; } return true; } }