/*
* 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 javax.swing.table.TableModel;
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;
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);
}
}