/* * 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.states.datarow; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.pentaho.reporting.engine.classic.core.DataFactory; import org.pentaho.reporting.engine.classic.core.DataRow; import org.pentaho.reporting.engine.classic.core.ReportProcessingException; import org.pentaho.reporting.engine.classic.core.ResourceBundleFactory; import org.pentaho.reporting.engine.classic.core.event.ReportEvent; 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.Function; import org.pentaho.reporting.engine.classic.core.function.ProcessingContext; import org.pentaho.reporting.engine.classic.core.states.DefaultGroupingState; import org.pentaho.reporting.engine.classic.core.states.GroupingState; import org.pentaho.reporting.engine.classic.core.states.LayoutProcess; import org.pentaho.reporting.engine.classic.core.states.ReportState; import org.pentaho.reporting.engine.classic.core.wizard.DataSchema; import org.pentaho.reporting.libraries.base.config.Configuration; import javax.swing.table.TableModel; public final class ExpressionDataRow extends ExpressionEventHelper { private static final Log logger = LogFactory.getLog( ExpressionDataRow.class ); private static final Expression[] EMPTY_EXPRESSIONS = new Expression[0]; private static class DataRowRuntime implements ExpressionRuntime { private ExpressionDataRow expressionDataRow; private GroupingState state; private boolean structuralComplex; private boolean crosstabActive; protected DataRowRuntime( final ExpressionDataRow dataRow ) { this.expressionDataRow = dataRow; this.state = DefaultGroupingState.EMPTY; } public DataSchema getDataSchema() { return expressionDataRow.getMasterRow().getDataSchema(); } public DataRow getDataRow() { return expressionDataRow.getMasterRow().getGlobalView(); } public Configuration getConfiguration() { return getProcessingContext().getConfiguration(); } public ResourceBundleFactory getResourceBundleFactory() { return expressionDataRow.getMasterRow().getResourceBundleFactory(); } public DataFactory getDataFactory() { return expressionDataRow.getMasterRow().getDataFactory(); } /** * Access to the tablemodel was granted using report properties, now direct. */ public TableModel getData() { return expressionDataRow.getMasterRow().getReportData(); } /** * Where are we in the current processing. */ public int getCurrentRow() { return expressionDataRow.getMasterRow().getCursor(); } public int getCurrentDataItem() { return expressionDataRow.getMasterRow().getRawDataCursor(); } /** * The output descriptor is a simple string collections consisting of the following components: * exportclass/type/subtype * <p/> * For example, the PDF export would be: pageable/pdf The StreamHTML export would return table/html/stream * * @return the export descriptor. */ public String getExportDescriptor() { return getProcessingContext().getExportDescriptor(); } public ProcessingContext getProcessingContext() { return expressionDataRow.getProcessingContext(); } public int getCurrentGroup() { return state.getCurrentGroup(); } public int getGroupStartRow( final String groupName ) { return state.getGroupStartRow( groupName ); } public int getGroupStartRow( final int groupIndex ) { return state.getGroupStartRow( groupIndex ); } public boolean isStructuralComplexReport() { return structuralComplex; } public boolean isCrosstabActive() { return crosstabActive; } public GroupingState getState() { return state; } public void setState( final GroupingState state ) { if ( state == null ) { throw new NullPointerException(); } this.state = state; } public void setCrosstabInfo( final boolean structuralComplex, final boolean crosstabActive ) { this.structuralComplex = structuralComplex; this.crosstabActive = crosstabActive; } } private MasterDataRowChangeHandler masterRowChangeHandler; private MasterDataRow masterRow; private ProcessingContext processingContext; private int length; private Expression[] expressions; private LevelStorageBackend[] levelData; private DataRowRuntime runtime; private boolean includeStructuralProcessing; public ExpressionDataRow( final MasterDataRowChangeHandler masterRowChangeHandler, final MasterDataRow masterRow, final ProcessingContext processingContext ) { if ( masterRow == null ) { throw new NullPointerException(); } if ( processingContext == null ) { throw new NullPointerException(); } this.processingContext = processingContext; this.masterRow = masterRow; this.masterRowChangeHandler = masterRowChangeHandler; this.expressions = ExpressionDataRow.EMPTY_EXPRESSIONS; this.runtime = new DataRowRuntime( this ); this.revalidate(); } public boolean isIncludeStructuralProcessing() { return includeStructuralProcessing; } public void setIncludeStructuralProcessing( final boolean includeStructuralProcessing ) { this.includeStructuralProcessing = includeStructuralProcessing; revalidate(); } private void revalidate() { this.levelData = LevelStorageBackend.revalidate( this.expressions, length, includeStructuralProcessing ); } private ExpressionDataRow( final MasterDataRowChangeHandler masterRowChangeHandler, final MasterDataRow masterRow, final ExpressionDataRow previousRow, final boolean updateGlobalView ) throws CloneNotSupportedException { final MasterDataRowChangeEvent chEvent = masterRowChangeHandler.getReusableEvent(); chEvent.reuse( MasterDataRowChangeEvent.COLUMN_UPDATED, "", "" ); this.processingContext = previousRow.processingContext; this.masterRow = masterRow; this.masterRowChangeHandler = masterRowChangeHandler; this.expressions = new Expression[previousRow.expressions.length]; this.length = previousRow.length; this.levelData = previousRow.levelData; this.runtime = new DataRowRuntime( this ); this.runtime.setState( previousRow.runtime.getState() ); this.includeStructuralProcessing = previousRow.includeStructuralProcessing; for ( int i = 0; i < length; i++ ) { final Expression expression = previousRow.expressions[i]; if ( expression == null ) { ExpressionDataRow.logger.debug( "Error: Expression is null..." ); throw new IllegalStateException(); } if ( expression instanceof Function ) { expressions[i] = (Expression) expression.clone(); } else { expressions[i] = expression; } if ( updateGlobalView == false ) { continue; } final String name = expression.getName(); if ( name != null ) { chEvent.setColumnName( name ); } Object value; final ExpressionRuntime oldRuntime = expression.getRuntime(); try { expression.setRuntime( runtime ); if ( runtime.getProcessingContext().getProcessingLevel() <= expression.getDependencyLevel() ) { value = expression.getValue(); } else { value = null; } } catch ( Exception e ) { if ( ExpressionDataRow.logger.isDebugEnabled() ) { ExpressionDataRow.logger.warn( "Failed to evaluate expression '" + name + '\'', e ); } else { ExpressionDataRow.logger.warn( "Failed to evaluate expression '" + name + '\'' ); } value = null; } finally { expression.setRuntime( oldRuntime ); } if ( name != null ) { chEvent.setColumnValue( value ); masterRowChangeHandler.dataRowChanged( chEvent ); } } } /** * This adds the expression to the data-row and queries the expression for the first time. * * @param expressionSlot * the expression that should be added. * @param preserveState * a flag indicating whether the expression is statefull and should preserve its internal state. * @throws ReportProcessingException * if the processing failed due to invalid function implementations. */ private void pushExpression( final Expression expressionSlot, final boolean preserveState ) throws ReportProcessingException { if ( expressionSlot == null ) { throw new NullPointerException(); } ensureCapacity( length + 1 ); if ( preserveState == false ) { this.expressions[length] = expressionSlot.getInstance(); } else { try { this.expressions[length] = (Expression) expressionSlot.clone(); } catch ( final CloneNotSupportedException e ) { throw new ReportProcessingException( "Failed to clone the expression.", e ); } } final String name = expressionSlot.getName(); length += 1; // A manual advance to initialize the function. if ( name != null ) { final MasterDataRowChangeEvent event = masterRowChangeHandler.getReusableEvent(); event.reuse( MasterDataRowChangeEvent.COLUMN_ADDED, name, null ); masterRowChangeHandler.dataRowChanged( event ); } } public void pushExpressions( final Expression[] expressionSlots, final boolean preserveState ) throws ReportProcessingException { if ( expressionSlots == null ) { throw new NullPointerException(); } ensureCapacity( length + expressionSlots.length ); for ( int i = 0; i < expressionSlots.length; i++ ) { final Expression expression = expressionSlots[i]; if ( expression == null ) { continue; } pushExpression( expression, preserveState ); } revalidate(); } public void popExpressions( final int counter ) { for ( int i = 0; i < counter; i++ ) { popExpression(); } revalidate(); } private void popExpression() { if ( length == 0 ) { return; } final Expression removedExpression = this.expressions[length - 1]; final String originalName = removedExpression.getName(); removedExpression.setRuntime( null ); this.expressions[length - 1] = null; this.length -= 1; if ( originalName != null ) { if ( removedExpression.isPreserve() == false ) { final MasterDataRowChangeEvent event = masterRowChangeHandler.getReusableEvent(); event.reuse( MasterDataRowChangeEvent.COLUMN_REMOVED, originalName, null ); masterRowChangeHandler.dataRowChanged( event ); } } } private void ensureCapacity( final int requestedSize ) { final int capacity = this.expressions.length; if ( capacity > requestedSize ) { return; } final int newSize = Math.max( capacity * 2, requestedSize + 10 ); final Expression[] newExpressions = new Expression[newSize]; System.arraycopy( expressions, 0, newExpressions, 0, length ); this.expressions = newExpressions; } /** * Returns the number of columns, expressions and functions and marked ReportProperties in the report. * * @return the item count. */ public int getColumnCount() { return length; } public void fireReportEvent( final ReportEvent event ) { final ReportState reportState = event.getState(); runtime.setState( reportState.createGroupingState() ); runtime.setCrosstabInfo( reportState.isStructuralPreprocessingNeeded(), reportState.isCrosstabActive() ); super.fireReportEvent( event ); super.reactivateExpressions( event.isDeepTraversing() ); } protected void updateMasterDataRow( final String name, final Object value ) { final MasterDataRowChangeEvent event = masterRowChangeHandler.getReusableEvent(); event.reuse( MasterDataRowChangeEvent.COLUMN_UPDATED, name, value ); masterRowChangeHandler.dataRowChanged( event ); } protected ExpressionRuntime getRuntime() { return runtime; } protected int getProcessingLevel() { final int activeLevel; final int rawLevel = runtime.getProcessingContext().getProcessingLevel(); if ( rawLevel == LayoutProcess.LEVEL_STRUCTURAL_PREPROCESSING ) { // we are in the data-pre-processing stage. Include all common expressions, in case they // compute a group-break. if ( levelData.length > 0 ) { activeLevel = levelData[0].getLevelNumber(); } else { activeLevel = rawLevel; } } else { activeLevel = rawLevel; } return activeLevel; } public ExpressionDataRow derive( final MasterDataRowChangeHandler masterRowChangeHandler, final MasterDataRow masterRow, final boolean update ) { try { return new ExpressionDataRow( masterRowChangeHandler, masterRow, this, update ); } catch ( final CloneNotSupportedException e ) { logger.error( "Error on derive(..): ", e ); throw new IllegalStateException( "Cannot clone? Cannot survive!" ); } } public boolean isValid() { return levelData != null; } public Expression[] getExpressions() { final Expression[] retval = new Expression[length]; System.arraycopy( expressions, 0, retval, 0, length ); return retval; } /** * Returns the current master-row instance to inner-classes. * * @return a reference to the master-row (to be used in inner classes). * @noinspection ProtectedMemberInFinalClass */ protected MasterDataRow getMasterRow() { return masterRow; } /** * Returns the current processing context to inner-classes. * * @return a reference to the processing context (to be used in inner classes). * @noinspection ProtectedMemberInFinalClass */ protected ProcessingContext getProcessingContext() { return processingContext; } public void refresh() { reactivateExpressions( false ); } protected int getRunLevelCount() { return levelData.length; } protected LevelStorage getRunLevel( final int index ) { final LevelStorageBackend backend = levelData[index]; return LevelStorageBackend.getLevelStorage( backend, expressions ); } }