Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
net.sf.jasperreports.engine.fill.JRFillCrosstab Maven / Gradle / Ivy
/*
* JasperReports - Free Java Reporting Library.
* Copyright (C) 2001 - 2023 Cloud Software Group, Inc. All rights reserved.
* http://www.jaspersoft.com
*
* Unless you have purchased a commercial license agreement from Jaspersoft,
* the following license terms apply:
*
* This program is part of JasperReports.
*
* JasperReports is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* JasperReports 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.
*
* You should have received a copy of the GNU Lesser General Public License
* along with JasperReports. If not, see .
*/
package net.sf.jasperreports.engine.fill;
import java.awt.Color;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import net.sf.jasperreports.annotations.properties.Property;
import net.sf.jasperreports.annotations.properties.PropertyScope;
import net.sf.jasperreports.components.iconlabel.IconLabelComponent;
import net.sf.jasperreports.components.iconlabel.IconLabelComponentUtil;
import net.sf.jasperreports.components.table.fill.TableReport;
import net.sf.jasperreports.crosstabs.CrosstabColumnCell;
import net.sf.jasperreports.crosstabs.CrosstabDeepVisitor;
import net.sf.jasperreports.crosstabs.JRCellContents;
import net.sf.jasperreports.crosstabs.JRCrosstab;
import net.sf.jasperreports.crosstabs.JRCrosstabBucket;
import net.sf.jasperreports.crosstabs.JRCrosstabCell;
import net.sf.jasperreports.crosstabs.JRCrosstabColumnGroup;
import net.sf.jasperreports.crosstabs.JRCrosstabDataset;
import net.sf.jasperreports.crosstabs.JRCrosstabGroup;
import net.sf.jasperreports.crosstabs.JRCrosstabMeasure;
import net.sf.jasperreports.crosstabs.JRCrosstabParameter;
import net.sf.jasperreports.crosstabs.JRCrosstabRowGroup;
import net.sf.jasperreports.crosstabs.base.JRBaseCrosstab;
import net.sf.jasperreports.crosstabs.design.JRDesignCrosstab;
import net.sf.jasperreports.crosstabs.fill.BucketExpressionOrderer;
import net.sf.jasperreports.crosstabs.fill.BucketOrderer;
import net.sf.jasperreports.crosstabs.fill.IconLabelFillObjectFactory;
import net.sf.jasperreports.crosstabs.fill.JRCrosstabExpressionEvaluator;
import net.sf.jasperreports.crosstabs.fill.JRFillCrosstabCell;
import net.sf.jasperreports.crosstabs.fill.JRFillCrosstabColumnGroup;
import net.sf.jasperreports.crosstabs.fill.JRFillCrosstabGroup;
import net.sf.jasperreports.crosstabs.fill.JRFillCrosstabMeasure;
import net.sf.jasperreports.crosstabs.fill.JRFillCrosstabObjectFactory;
import net.sf.jasperreports.crosstabs.fill.JRFillCrosstabParameter;
import net.sf.jasperreports.crosstabs.fill.JRFillCrosstabRowGroup;
import net.sf.jasperreports.crosstabs.fill.calculation.BucketDefinition;
import net.sf.jasperreports.crosstabs.fill.calculation.BucketDefinition.Bucket;
import net.sf.jasperreports.crosstabs.fill.calculation.BucketingServiceContext;
import net.sf.jasperreports.crosstabs.fill.calculation.ColumnValueInfo;
import net.sf.jasperreports.crosstabs.fill.calculation.CrosstabBucketingService;
import net.sf.jasperreports.crosstabs.fill.calculation.CrosstabCell;
import net.sf.jasperreports.crosstabs.fill.calculation.HeaderCell;
import net.sf.jasperreports.crosstabs.fill.calculation.MeasureDefinition;
import net.sf.jasperreports.crosstabs.fill.calculation.MeasureDefinition.MeasureValue;
import net.sf.jasperreports.crosstabs.fill.calculation.OrderByColumnInfo;
import net.sf.jasperreports.crosstabs.fill.calculation.OrderByColumnOrderer;
import net.sf.jasperreports.crosstabs.interactive.CrosstabInteractiveJsonHandler;
import net.sf.jasperreports.crosstabs.interactive.DataColumnInfo;
import net.sf.jasperreports.crosstabs.interactive.RowGroupInteractiveInfo;
import net.sf.jasperreports.crosstabs.type.CrosstabColumnPositionEnum;
import net.sf.jasperreports.crosstabs.type.CrosstabPercentageEnum;
import net.sf.jasperreports.crosstabs.type.CrosstabRowPositionEnum;
import net.sf.jasperreports.engine.JRChild;
import net.sf.jasperreports.engine.JRElement;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExpression;
import net.sf.jasperreports.engine.JRExpressionChunk;
import net.sf.jasperreports.engine.JRExpressionCollector;
import net.sf.jasperreports.engine.JRGenericElementType;
import net.sf.jasperreports.engine.JRLineBox;
import net.sf.jasperreports.engine.JROrigin;
import net.sf.jasperreports.engine.JRParameter;
import net.sf.jasperreports.engine.JRPrintElement;
import net.sf.jasperreports.engine.JRPrintFrame;
import net.sf.jasperreports.engine.JRPropertiesUtil;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.JRStaticText;
import net.sf.jasperreports.engine.JRTextElement;
import net.sf.jasperreports.engine.JRTextField;
import net.sf.jasperreports.engine.JRVariable;
import net.sf.jasperreports.engine.JRVisitor;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.JasperReportsContext;
import net.sf.jasperreports.engine.analytics.dataset.BucketOrder;
import net.sf.jasperreports.engine.design.JRDesignComponentElement;
import net.sf.jasperreports.engine.design.JRDesignTextField;
import net.sf.jasperreports.engine.export.HtmlExporter;
import net.sf.jasperreports.engine.export.MatcherExporterFilter;
import net.sf.jasperreports.engine.type.HorizontalPosition;
import net.sf.jasperreports.engine.type.ModeEnum;
import net.sf.jasperreports.engine.type.RunDirectionEnum;
import net.sf.jasperreports.engine.type.SortOrderEnum;
import net.sf.jasperreports.engine.util.ElementsVisitorUtils;
import net.sf.jasperreports.engine.util.JRValueStringUtils;
import net.sf.jasperreports.engine.xml.JRXmlConstants;
import net.sf.jasperreports.export.AccessibilityUtil;
import net.sf.jasperreports.export.type.AccessibilityTagEnum;
import net.sf.jasperreports.properties.PropertyConstants;
import net.sf.jasperreports.util.JacksonUtil;
/**
* Fill-time implementation of a {@link net.sf.jasperreports.crosstabs.JRCrosstab crosstab}.
*
* @author Lucian Chirita ([email protected] )
*/
public class JRFillCrosstab extends JRFillElement implements JRCrosstab, JROriginProvider, BucketingServiceContext
{
private final static Log log = LogFactory.getLog(JRFillCrosstab.class);
public static final String EXCEPTION_MESSAGE_KEY_BUCKETING_SERVICE_ERROR = "crosstabs.bucketing.service.error";
public static final String EXCEPTION_MESSAGE_KEY_EVALUATOR_LOADING_ERROR = "crosstabs.evaluator.loading.error";
public static final String EXCEPTION_MESSAGE_KEY_INFINITE_LOOP = "crosstabs.infinite.loop";
public static final String EXCEPTION_MESSAGE_KEY_NOT_ENOUGH_SPACE = "crosstabs.not.enough.space";
/**
* Property that enables/disables the interactivity in the crosstab component.
*
*
* It can be set:
*
* globally
* at report level
* at component level
*
*
*
*
* The default global value of this property is true
*
*/
@Property(
category = PropertyConstants.CATEGORY_CROSSTAB,
defaultValue = PropertyConstants.BOOLEAN_TRUE,
scopes = {PropertyScope.CONTEXT, PropertyScope.REPORT, PropertyScope.CROSSTAB},
sinceVersion = PropertyConstants.VERSION_5_5_0,
valueType = Boolean.class
)
public static final String PROPERTY_INTERACTIVE = JRPropertiesUtil.PROPERTY_PREFIX + "crosstab.interactive";
/**
* Property that enables/disables the floating headers in the crosstab component when scrolling.
* If the interactivity has been disabled by setting {@link #PROPERTY_INTERACTIVE} to false
, then
* setting this property will have no effect.
*
*
* It can be set:
*
* globally
* at report level
* at component level
*
*
*
*
* The default global value of this property is true
*
*/
@Property(
category = PropertyConstants.CATEGORY_CROSSTAB,
defaultValue = PropertyConstants.BOOLEAN_TRUE,
scopes = {PropertyScope.CONTEXT, PropertyScope.REPORT, PropertyScope.CROSSTAB},
sinceVersion = PropertyConstants.VERSION_6_0_0,
valueType = Boolean.class
)
public static final String PROPERTY_FLOATING_HEADERS = JRPropertiesUtil.PROPERTY_PREFIX + "crosstab.floating.headers";
public static final String PROPERTY_ORDER_BY_COLUMN = JRPropertiesUtil.PROPERTY_PREFIX + "crosstab.order.by.column";
@Property(
category = PropertyConstants.CATEGORY_CROSSTAB,
scopes = {PropertyScope.ELEMENT},
sinceVersion = PropertyConstants.VERSION_5_5_0,
valueType = Integer.class
)
public static final String PROPERTY_ROW_GROUP_COLUMN_HEADER = JRPropertiesUtil.PROPERTY_PREFIX + "crosstab.row.group.column.header";
@Property(
category = PropertyConstants.CATEGORY_CROSSTAB,
scopes = {PropertyScope.ELEMENT},
sinceVersion = PropertyConstants.VERSION_5_5_0,
valueType = Integer.class
)
public static final String PROPERTY_COLUMN_HEADER_SORT_MEASURE_INDEX = JRPropertiesUtil.PROPERTY_PREFIX + "crosstab.column.header.sort.measure.index";
public static final String CROSSTAB_INTERACTIVE_ELEMENT_NAME = "crosstabInteractiveElement";
public static final String HTML_CLASS_COLUMN_FLOATING = "jrxtcolfloating";
public static final String HTML_CLASS_ROW_FLOATING = "jrxtrowfloating";
public static final String HTML_CLASS_CROSS_FLOATING = "jrxtcrossfloating";
public static final JRGenericElementType CROSSTAB_INTERACTIVE_ELEMENT_TYPE =
new JRGenericElementType(JRXmlConstants.JASPERREPORTS_NAMESPACE, CROSSTAB_INTERACTIVE_ELEMENT_NAME);
protected static final String FILL_CACHE_KEY_CROSSTAB_CHUNK_COUNTER = JRFillCrosstab.class.getName() + "#chunkCounter";
private final JRFillObjectFactory fillFactory;
final protected JRCrosstab parentCrosstab;
protected final JRLineBox lineBox;
protected JRFillCrosstabDataset dataset;
protected JRFillCrosstabRowGroup[] rowGroups;
protected Map rowGroupsMap;
protected JRFillCrosstabColumnGroup[] columnGroups;
protected Map columnGroupsMap;
protected JRFillCrosstabMeasure[] measures;
private OrderByColumnInfo orderByColumnInfo;
private List orderByColumnBucketValues;
protected CrosstabBucketingService bucketingService;
protected JRFillVariable[] variables;
protected Map variablesMap;
protected JRFillVariable[][][] totalVariables;
protected boolean[][] retrieveTotal;
protected JRFillCrosstabParameter[] parameters;
protected Map parametersMap;
protected boolean ignoreWidth;
protected JRCrosstabExpressionEvaluator crosstabEvaluator;
protected JRFillCrosstabCell[][] crossCells;
protected JRFillCellContents titleCellContents;
protected JRFillCellContents headerCell;
protected JRFillCellContents whenNoDataCell;
protected boolean hasData;
protected HeaderCell[][] columnHeadersData;
protected HeaderCell[][] rowHeadersData;
protected CrosstabCell[][] cellData;
protected MeasureValue[] grandTotals;
private boolean percentage;
private CrosstabFiller crosstabFiller;
private int overflowStartPage;
private List printFrames;
private int printFramesMaxWidth;
private boolean interactive;
private boolean floatingHeaders;
private int lastColumnGroupWithHeaderIndex = -1;
public JRFillCrosstab(JRBaseFiller filler, JRCrosstab crosstab, JRFillObjectFactory factory)
{
super(filler, crosstab, factory);
this.fillFactory = factory;
parentCrosstab = crosstab;
lineBox = crosstab.getLineBox().clone(this);
loadEvaluator(filler.getJasperReport());
JRFillCrosstabObjectFactory crosstabFactory = new JRFillCrosstabObjectFactory(
factory, crosstabEvaluator);
crosstabFactory.setParentOriginProvider(this);
if (crosstab.getTitleCell() != null && crosstab.getTitleCell().getCellContents() != null)
{
titleCellContents = crosstabFactory.getCell(crosstab.getTitleCell().getCellContents(),
JRCellContents.TYPE_CROSSTAB_TITLE);
}
headerCell = crosstabFactory.getCell(crosstab.getHeaderCell(),
JRCellContents.TYPE_CROSSTAB_HEADER);
copyRowGroups(crosstab, crosstabFactory);
copyColumnGroups(crosstab, crosstabFactory);
copyMeasures(crosstab, crosstabFactory);
copyCells(crosstab, crosstabFactory);
whenNoDataCell = crosstabFactory.getCell(crosstab.getWhenNoDataCell(),
JRCellContents.TYPE_NO_DATA_CELL);
dataset = factory.getCrosstabDataset(crosstab.getDataset(), this);
crosstabEvaluator.setFillDataset(dataset.getFillDataset());
copyParameters(crosstab, factory);
copyVariables(crosstab, crosstabFactory);
lastColumnGroupWithHeaderIndex = determineLastColumnGroupWithHeaderIndex();
crosstabFiller = new CrosstabFiller();
}
@Override
protected void setBand(JRFillBand band)
{
super.setBand(band);
dataset.setBand(band);
}
private boolean isIgnoreWidth(JRBaseFiller filler, JRCrosstab crosstab)
{
Boolean crosstabIgnoreWidth = crosstab.getIgnoreWidth();
// crosstab attribute overrides all
if (crosstabIgnoreWidth != null)
{
return crosstabIgnoreWidth;
}
// report level property
String reportProperty = JRPropertiesUtil.getOwnProperty(filler.getMainDataset(), PROPERTY_IGNORE_WIDTH); // because we read the "own" property here, without inheritance,
// it is most likely that this would not work for crosstab in table component because the filler.getMainDataset() would be the one from TableJasperReport (subreport),
// and not the master report dataset where the property would be set
if (reportProperty != null)
{
return JRPropertiesUtil.asBoolean(reportProperty);
}
// report pagination flag from the filler
if (filler.isIgnorePagination())
{
return true;
}
// global property
return filler.getPropertiesUtil().getBooleanProperty(PROPERTY_IGNORE_WIDTH);
}
@Override
public ModeEnum getModeValue()
{
return getStyleResolver().getMode(this, ModeEnum.TRANSPARENT);
}
private void copyRowGroups(JRCrosstab crosstab, JRFillCrosstabObjectFactory factory)
{
JRCrosstabRowGroup[] groups = crosstab.getRowGroups();
rowGroups = new JRFillCrosstabRowGroup[groups.length];
rowGroupsMap = new HashMap<>();
for (int i = 0; i < groups.length; ++i)
{
JRFillCrosstabRowGroup group = factory.getCrosstabRowGroup(groups[i]);
group.getFillHeader().setVerticalPositionType(groups[i].getPositionValue());
rowGroups[i] = group;
rowGroupsMap.put(group.getName(), i);
}
}
private void copyColumnGroups(JRCrosstab crosstab, JRFillCrosstabObjectFactory factory)
{
JRCrosstabColumnGroup[] groups = crosstab.getColumnGroups();
columnGroups = new JRFillCrosstabColumnGroup[groups.length];
columnGroupsMap = new HashMap<>();
for (int i = 0; i < groups.length; ++i)
{
JRFillCrosstabColumnGroup group = factory.getCrosstabColumnGroup(groups[i]);
columnGroups[i] = group;
columnGroupsMap.put(group.getName(), i);
}
}
private void copyMeasures(JRCrosstab crosstab, JRFillCrosstabObjectFactory factory)
{
JRCrosstabMeasure[] crossMeasures = crosstab.getMeasures();
measures = new JRFillCrosstabMeasure[crossMeasures.length];
for (int i = 0; i < crossMeasures.length; i++)
{
measures[i] = factory.getCrosstabMeasure(crossMeasures[i]);
}
}
private void copyParameters(JRCrosstab crosstab, JRFillObjectFactory factory)
{
JRCrosstabParameter[] crossParams = crosstab.getParameters();
parameters = new JRFillCrosstabParameter[crossParams.length];
parametersMap = new HashMap<>();
for (int i = 0; i < crossParams.length; i++)
{
parameters[i] = factory.getCrosstabParameter(crossParams[i]);
parametersMap.put(parameters[i].getName(), parameters[i]);
}
}
private void copyCells(JRCrosstab crosstab, JRFillCrosstabObjectFactory factory)
{
JRCrosstabCell[][] crosstabCells = crosstab.getCells();
crossCells = new JRFillCrosstabCell[rowGroups.length + 1][columnGroups.length + 1];
for (int i = 0; i <= rowGroups.length; ++i)
{
for (int j = 0; j <= columnGroups.length; ++j)
{
if (crosstabCells[i][j] != null)
{
crossCells[i][j] = factory.getCrosstabCell(crosstabCells[i][j]);
}
}
}
}
private void copyVariables(JRCrosstab crosstab, JRFillObjectFactory factory)
{
JRVariable[] vars = crosstab.getVariables();
variables = new JRFillVariable[vars.length];
variablesMap = new HashMap<>();
for (int i = 0; i < variables.length; i++)
{
variables[i] = factory.getVariable(vars[i]);
variablesMap.put(variables[i].getName(), variables[i]);
}
Map totalVarPos = new HashMap<>();
totalVariables = new JRFillVariable[rowGroups.length + 1][columnGroups.length + 1][measures.length];
for (int row = 0; row <= rowGroups.length; ++row)
{
JRCrosstabRowGroup rowGroup = row == rowGroups.length ? null : rowGroups[row];
for (int col = 0; col <= columnGroups.length; ++col)
{
JRCrosstabColumnGroup colGroup = col == columnGroups.length ? null : columnGroups[col];
if (row < rowGroups.length || col < columnGroups.length)
{
for (int m = 0; m < measures.length; m++)
{
String totalVariableName = JRDesignCrosstab.getTotalVariableName(measures[m], rowGroup, colGroup);
totalVariables[row][col][m] = variablesMap.get(totalVariableName);
totalVarPos.put(totalVariableName, new int[]{row, col});
}
}
}
}
Set measureVars = new HashSet<>();
for (JRFillCrosstabMeasure measure : measures)
{
measureVars.add(measure.getFillVariable().getName());
}
retrieveTotal = new boolean[rowGroups.length + 1][columnGroups.length + 1];
//FIXME avoid this
JRExpressionCollector collector = JRExpressionCollector.collector(filler.getJasperReportsContext(), filler.getJasperReport(), crosstab);
List expressions = collector.getExpressions(crosstab);
for (Iterator iter = expressions.iterator(); iter.hasNext();)
{
JRExpression expression = iter.next();
Object expressionContext = collector.getExpressionContext(expression);
boolean groupHeaderExpression = expressionContext instanceof JRCrosstabGroup;
JRExpressionChunk[] chunks = expression.getChunks();
if (chunks != null)
{
for (int i = 0; i < chunks.length; i++)
{
JRExpressionChunk chunk = chunks[i];
if (chunk.getType() == JRExpressionChunk.TYPE_VARIABLE)
{
String varName = chunk.getText();
int[] pos = totalVarPos.get(varName);
if (pos != null)
{
retrieveTotal[pos[0]][pos[1]] = true;
}
// if a measure variable is used inside a group header, compute all totals.
// in theory we could have a finer grained rule here, but it complicates
// the logic without a singnificant gain.
if (groupHeaderExpression && (pos != null || measureVars.contains(varName)))
{
retrieveTotal[0][0] = true;
}
}
}
}
}
}
@Override
public JRBaseFiller getFiller()
{
return filler;
}
protected void loadEvaluator(JasperReport jasperReport)
{
try
{
JREvaluator evaluator = JasperCompileManager.getInstance(filler.getJasperReportsContext()).getEvaluator(jasperReport, parentCrosstab);
crosstabEvaluator = new JRCrosstabExpressionEvaluator(evaluator);
}
catch (JRException e)
{
throw
new JRRuntimeException(
EXCEPTION_MESSAGE_KEY_EVALUATOR_LOADING_ERROR,
(Object[])null,
e);
}
}
private CrosstabBucketingService createService(byte evaluation) throws JRException
{
boolean hasOrderByExpression = false;
List rowBuckets = new ArrayList<>(rowGroups.length);
for (int i = 0; i < rowGroups.length; ++i)
{
JRFillCrosstabRowGroup group = rowGroups[i];
rowBuckets.add(createServiceBucket(group, i, evaluation));
hasOrderByExpression |= group.getBucket().getOrderByExpression() != null;
}
List colBuckets = new ArrayList<>(columnGroups.length);
for (int i = 0; i < columnGroups.length; ++i)
{
JRFillCrosstabColumnGroup group = columnGroups[i];
colBuckets.add(createServiceBucket(group, i, evaluation));
hasOrderByExpression |= group.getBucket().getOrderByExpression() != null;
}
percentage = false;
List measureList = new ArrayList<>(measures.length);
for (int i = 0; i < measures.length; ++i)
{
measureList.add(createServiceMeasure(measures[i]));
percentage |= measures[i].getPercentageType() == CrosstabPercentageEnum.GRAND_TOTAL;
}
// if a group has order by expression, compute totals as they might be used
// in the expression
// TODO refine this
if (percentage || hasOrderByExpression)
{
rowBuckets.get(0).setComputeTotal();
colBuckets.get(0).setComputeTotal();
}
return new CrosstabBucketingService(this, rowBuckets, colBuckets, measureList, dataset.isDataPreSorted(), retrieveTotal);
}
private BucketDefinition createServiceBucket(JRCrosstabGroup group, int groupIndex, byte evaluation) throws JRException
{
JRCrosstabBucket bucket = group.getBucket();
Comparator comparator = null;
JRExpression comparatorExpression = bucket.getComparatorExpression();
if (comparatorExpression != null)
{
comparator = (Comparator) evaluateExpression(comparatorExpression, evaluation);
}
BucketOrderer orderer = createOrderer(group, groupIndex, comparator);
BucketDefinition bucketDefinition = new BucketDefinition(bucket.getValueClass(),
orderer, comparator, bucket.getOrder(),
group.getTotalPositionValue());
Boolean mergeHeaderCells = group.getMergeHeaderCells();
// by default the header cells are merged
bucketDefinition.setMergeHeaderCells(mergeHeaderCells == null || mergeHeaderCells);
return bucketDefinition;
}
protected BucketOrderer createOrderer(JRCrosstabGroup group, int groupIndex, Comparator bucketComparator)
{
BucketOrderer orderer = null;
if (group instanceof JRCrosstabRowGroup
&& orderByColumnInfo != null && orderByColumnInfo.getOrder() != null
// ordering by column only applies to nesting groups is they are not already ordered
&& (groupIndex == rowGroups.length - 1 || group.getBucket().getOrder() == BucketOrder.NONE))
{
orderer = new OrderByColumnOrderer(orderByColumnInfo);
}
if (orderer == null)
{
JRCrosstabBucket bucket = group.getBucket();
JRExpression orderByExpression = bucket.getOrderByExpression();
if (orderByExpression != null && bucket.getOrder() != BucketOrder.NONE)
{
if (log.isDebugEnabled())
{
log.debug("using order by expression to order group " + group.getName());
}
// when we have an order by expression, the comparator is applied to order values
Comparator orderValueComparator = BucketDefinition.createOrderComparator(bucketComparator, bucket.getOrder());
orderer = new BucketExpressionOrderer(orderByExpression, orderValueComparator);
}
}
return orderer;
}
private MeasureDefinition createServiceMeasure(JRFillCrosstabMeasure measure)
{
return new MeasureDefinition(
measure.getValueClass(),
measure.getCalculationValue(),
measure.getIncrementerFactory());
}
@Override
public Object evaluateMeasuresExpression(JRExpression expression, MeasureValue[] measureValues)
throws JRException
{
for (int i = 0; i < measures.length; i++)
{
Object value = measureValues[i].getValue();
measures[i].getFillVariable().setValue(value);
}
return crosstabEvaluator.evaluate(expression, JRExpression.EVALUATION_DEFAULT);
}
@Override
protected void reset()
{
super.reset();
for (int i = 0; i < variables.length; i++)
{
variables[i].setValue(null);
variables[i].setInitialized(true);
}
printFrames = null;
printFramesMaxWidth = 0;
}
@Override
protected void evaluate(byte evaluation) throws JRException
{
reset();
evaluatePrintWhenExpression(evaluation);
if (isPrintWhenExpressionNull() || isPrintWhenTrue())
{
evaluateProperties(evaluation);
evaluateStyle(evaluation);
dataset.evaluateDatasetRun(evaluation);
initEvaluator(evaluation);
bucketingService.processData();
bucketingService.createCrosstab();
hasData = bucketingService.hasData();
if (hasData)
{
columnHeadersData = bucketingService.getColumnHeaders();
rowHeadersData = bucketingService.getRowHeaders();
cellData = bucketingService.getCrosstabCells();
if (percentage)
{
grandTotals = bucketingService.getGrandTotals();
}
crosstabFiller.initCrosstab();
}
overflowStartPage = 0;
ignoreWidth = isIgnoreWidth(filler, parentCrosstab);
interactive = filler.getPropertiesUtil().getBooleanProperty(this, PROPERTY_INTERACTIVE, true);
floatingHeaders = filler.getPropertiesUtil().getBooleanProperty(this, PROPERTY_FLOATING_HEADERS, true);
}
}
protected void initEvaluator(byte evaluation) throws JRException
{
Map parameterValues =
JRFillSubreport.getParameterValues(
filler,
getParametersMapExpression(),
getParameters(),
evaluation,
true,
false,//hasResourceBundle
false//hasFormatFactory
);
ResourceBundle resBdl = (ResourceBundle) parameterValues.get(JRParameter.REPORT_RESOURCE_BUNDLE);
if (resBdl == null)
{
JRFillParameter resourceBundleParam = filler.getParametersMap().get(JRParameter.REPORT_RESOURCE_BUNDLE);
parameterValues.put(JRParameter.REPORT_RESOURCE_BUNDLE, resourceBundleParam.getValue());
}
parameterValues.put(JRParameter.REPORT_PARAMETERS_MAP, parameterValues);
for (int i = 0; i < parameters.length; i++)
{
Object value = parameterValues.get(parameters[i].getName());
parameters[i].setValue(value);
}
boolean ignoreNPE = filler.getPropertiesUtil().getBooleanProperty(this, JREvaluator.PROPERTY_IGNORE_NPE, true);
crosstabEvaluator.init(parametersMap, variablesMap, filler.getWhenResourceMissingType(), ignoreNPE);
}
protected void initBucketingService()
{
if (bucketingService == null)
{
setOrderByColumnInfo();
try
{
bucketingService = createService(JRExpression.EVALUATION_DEFAULT);
}
catch (JRException e)
{
throw
new JRRuntimeException(
EXCEPTION_MESSAGE_KEY_BUCKETING_SERVICE_ERROR,
(Object[])null,
e);
}
setOrderByColumnBucketValues();
}
else
{
bucketingService.clear();
}
}
protected void setOrderByColumnInfo()
{
orderByColumnInfo = null;
// should we read this from evaluated properties?
String orderByProperty = parentCrosstab.getPropertiesMap().getProperty(PROPERTY_ORDER_BY_COLUMN);
if (orderByProperty == null || orderByProperty.isEmpty())
{
return;
}
orderByColumnInfo = JacksonUtil.getInstance(filler.getJasperReportsContext()).loadObject(
orderByProperty, OrderByColumnInfo.class);
}
protected void setOrderByColumnBucketValues()
{
if (orderByColumnInfo != null && orderByColumnInfo.getOrder() != null)
{
// creating an orderer for convenience
OrderByColumnOrderer orderer = new OrderByColumnOrderer(orderByColumnInfo);
orderer.init(bucketingService);
orderByColumnBucketValues = orderer.getBucketValues();
}
}
@Override
protected boolean prepare(int availableHeight, boolean isOverflow) throws JRException
{
super.prepare(availableHeight, isOverflow);
if (!isToPrint())
{
return false;
}
if (availableHeight < getRelativeY() + getHeight())
{
setToPrint(false);
return true;
}
if (isOverflow && crosstabFiller.ended() && isAlreadyPrinted())
{
if (isPrintWhenDetailOverflows())
{
rewind();
setReprinted(true);
}
else
{
setPrepareHeight(getHeight());
setToPrint(false);
return false;
}
}
if (isOverflow && isPrintWhenDetailOverflows())
{
setReprinted(true);
}
printFrames = new ArrayList<>();
printFramesMaxWidth = 0;
crosstabFiller.fill(availableHeight - getRelativeY());
if (!printFrames.isEmpty())
{
// crosstab content has been filled, reset overflowPage
overflowStartPage = 0;
}
else
{
int pageCount = filler.getCurrentPageCount();
if (overflowStartPage == 0)
{
// first empty page
overflowStartPage = pageCount;
}
else if (pageCount >= overflowStartPage + 2)
{
throw
new JRRuntimeException(
EXCEPTION_MESSAGE_KEY_INFINITE_LOOP,
(Object[])null
);
}
}
boolean willOverflow = crosstabFiller.willOverflow();
if (willOverflow)
{
setPrepareHeight(availableHeight - getRelativeY());
}
else if (!printFrames.isEmpty())
{
JRTemplatePrintFrame lastFrame = printFrames.get(printFrames.size() - 1);
int usedHeight = lastFrame.getY() + lastFrame.getHeight();
setPrepareHeight(usedHeight);
}
return willOverflow;
}
protected void addCrosstabChunk(List elements, int yOffset)
{
JRTemplatePrintFrame printFrame = new JRTemplatePrintFrame(getTemplateFrame(), printElementOriginator);
//printFrame.setUUID(getUUID());
printFrame.setX(0);
printFrame.setY(yOffset);
Collections.sort(elements, new JRYXComparator());//FIXME make singleton comparator; same for older comparator
int xLimit = Integer.MIN_VALUE;
int yLimit = Integer.MIN_VALUE;
for (Iterator it = elements.iterator(); it.hasNext();)
{
JRPrintElement element = it.next();
if (element.getX() + element.getWidth() > xLimit)
{
xLimit = element.getX() + element.getWidth();
}
if (element.getY() + element.getHeight() > yLimit)
{
yLimit = element.getY() + element.getHeight();
}
}
JRLineBox lineBox = getLineBox();
int width = xLimit + lineBox.getLeftPadding() + lineBox.getRightPadding();
printFrame.setWidth(width);
HorizontalPosition position = concreteHorizontalPosition();
switch (position)
{
case RIGHT:
// the position does not apply when the crosstab is bigger than the element (ignoreWidth is set)
// still, it applies if the crosstab is RTL
if (width < getWidth() || getRunDirectionValue() == RunDirectionEnum.RTL)
{
// move to the right
printFrame.setX(getWidth() - width);
}
break;
case CENTER:
// the position does not apply when the crosstab is bigger than the element (ignoreWidth is set)
if (width < getWidth())
{
int centeredX = (getWidth() - width) / 2;
printFrame.setX(centeredX);
}
break;
case LEFT:
default:
// x = 0 already set
break;
}
int height = yLimit + lineBox.getTopPadding() + lineBox.getBottomPadding();
printFrame.setHeight(height);
if (getRunDirectionValue() == RunDirectionEnum.RTL)
{
mirrorPrintElements(elements, xLimit);
}
int chunkIndex = getChunkIndex();
String chunkId = getUUID().toString() + "." + chunkIndex;
printFrame.getPropertiesMap().setProperty(AccessibilityUtil.PROPERTY_ACCESSIBILITY_TAG, AccessibilityTagEnum.TABLE.getName());
if (interactive)
{
printFrame.getPropertiesMap().setProperty(CrosstabInteractiveJsonHandler.PROPERTY_CROSSTAB_ID,
chunkId);
JRTemplateGenericPrintElement genericElement = createInteractiveElement(chunkId, floatingHeaders);
printFrame.addElement(genericElement);
}
// dump all elements into the print frame
printFrame.addElements(elements);
// add this frame to the list to the list of crosstab chunks
printFrames.add(printFrame);
if (printFrame.getX() + printFrame.getWidth() > printFramesMaxWidth)
{
printFramesMaxWidth = printFrame.getX() + printFrame.getWidth();
}
}
protected int getChunkIndex()
{
JRFillContext fillerContext = filler.getFillContext();
AtomicInteger counter = (AtomicInteger) fillerContext.getFillCache(FILL_CACHE_KEY_CROSSTAB_CHUNK_COUNTER);
if (counter == null)
{
// we just need a mutable integer, there's no actual concurrency here
counter = new AtomicInteger();
fillerContext.setFillCache(FILL_CACHE_KEY_CROSSTAB_CHUNK_COUNTER, counter);
}
int chunkIndex = counter.getAndIncrement();
return chunkIndex;
}
protected HorizontalPosition concreteHorizontalPosition()
{
HorizontalPosition position = getHorizontalPosition();
if (position == null)
{
position = getRunDirectionValue() == RunDirectionEnum.RTL
? HorizontalPosition.RIGHT : HorizontalPosition.LEFT;
}
return position;
}
protected JRTemplateGenericPrintElement createInteractiveElement(String chunkId, boolean floatingHeaders)
{
// TODO lucianc cache
JRTemplateGenericElement genericElementTemplate = new JRTemplateGenericElement(
getElementOrigin(), defaultStyleProvider, CROSSTAB_INTERACTIVE_ELEMENT_TYPE);
JRTemplateGenericPrintElement genericElement = new JRTemplateGenericPrintElement(
genericElementTemplate, printElementOriginator);//use a different source id?
genericElement.setX(0);
genericElement.setY(0);
genericElement.setWidth(1);
genericElement.setHeight(1);
genericElement.setParameterValue(CrosstabInteractiveJsonHandler.ELEMENT_PARAMETER_CROSSTAB_ID, getUUID().toString());
genericElement.setParameterValue(CrosstabInteractiveJsonHandler.ELEMENT_PARAMETER_CROSSTAB_FRAGMENT_ID, chunkId);
genericElement.setParameterValue(CrosstabInteractiveJsonHandler.ELEMENT_PARAMETER_START_COLUMN_INDEX, crosstabFiller.startColumnIndex);
genericElement.setParameterValue(CrosstabInteractiveJsonHandler.ELEMENT_PARAMETER_FLOATING_HEADERS, floatingHeaders);
BucketDefinition[] rowBuckets = bucketingService.getRowBuckets();
List rowGroups = new ArrayList<>(rowBuckets.length);
for (BucketDefinition bucket : rowBuckets)
{
RowGroupInteractiveInfo groupInfo = new RowGroupInteractiveInfo();
groupInfo.setSortable(true);// TODO lucianc
BucketOrder order = bucketOrder(bucket);
groupInfo.setOrder(order);
rowGroups.add(groupInfo);
}
genericElement.setParameterValue(CrosstabInteractiveJsonHandler.ELEMENT_PARAMETER_ROW_GROUPS, rowGroups);
int dataColumnCount = crosstabFiller.lastColumnIndex - crosstabFiller.startColumnIndex;
List dataColumns = new ArrayList<>(dataColumnCount);
for (int colIdx = crosstabFiller.startColumnIndex; colIdx < crosstabFiller.lastColumnIndex; ++colIdx)
{
List bucketValues = null;
// getting the values from the most detailed column header
for (int level = columnHeadersData.length - 1; level >= 0 && bucketValues == null; --level)
{
HeaderCell header = columnHeadersData[level][colIdx];
bucketValues = header == null ? null : bucketValuesList(header);
}
DataColumnInfo dataColumn = new DataColumnInfo();
int sortMeasureIndex = measures.length <= 1 ? 0
: (crosstabFiller.dataColumnSortMeasures == null ? 0
: crosstabFiller.dataColumnSortMeasures[colIdx - crosstabFiller.startColumnIndex]);
if (sortMeasureIndex >= 0)
{
// the column is sortable
// TODO lucianc do not repeat this if not necessary + do not serialize nulls
dataColumn.setSortMeasureIndex(sortMeasureIndex);
List columnValues = toColumnValues(bucketValues);
dataColumn.setColumnValues(columnValues);
}
BucketOrder columnOrder = null;
if (orderByColumnBucketValues != null && orderByColumnBucketValues.equals(bucketValues))
{
columnOrder = BucketOrder.fromSortOrderEnum(orderByColumnInfo.getOrder());
}
dataColumn.setOrder(columnOrder);
dataColumns.add(dataColumn);
}
genericElement.setParameterValue(CrosstabInteractiveJsonHandler.ELEMENT_PARAMETER_DATA_COLUMNS, dataColumns);
return genericElement;
}
protected BucketOrder bucketOrder(BucketDefinition bucket)
{
return (dataset.isDataPreSorted() || bucket.getOrderer() != null)
? BucketOrder.NONE : bucket.getOrder();
}
protected boolean matchesOrderByColumn(HeaderCell cell)
{
if (orderByColumnBucketValues == null)
{
return false;
}
List cellValues = bucketValuesList(cell);
if (cellValues.size() > orderByColumnBucketValues.size())
{
return false;
}
// when the last column group is empty, we will only match a part of orderByColumnBucketValues
Iterator orderValueIt = orderByColumnBucketValues.iterator();
for (Bucket cellValue : cellValues)
{
Bucket orderValue = orderValueIt.next();
if (!cellValue.equals(orderValue))
{
return false;
}
}
return true;
}
protected List bucketValuesList(HeaderCell cell)
{
Bucket[] values = cell.getBucketValues();
ArrayList valuesList = new ArrayList<>(values.length);
for (Bucket bucket : values)
{
if (bucket != null)
{
valuesList.add(bucket);
}
}
return valuesList;
}
protected List toColumnValues(List bucketValues)
{
List columnValues = new ArrayList<>(bucketValues.size());
for (Bucket bucket : bucketValues)
{
if (bucket != null)
{
ColumnValueInfo columnValue = new ColumnValueInfo();
if (bucket.isTotal())
{
columnValue.setTotal(true);
}
else
{
Object value = bucket.getValue();
if (value != null)
{
String valueType = value.getClass().getName();
columnValue.setValueType(valueType);
columnValue.setValue(JRValueStringUtils.serialize(valueType, value));
}
}
columnValues.add(columnValue);
}
}
return columnValues;
}
@Override
protected JRPrintElement fill()
{
// don't return anything, see getPrintElements()
return null;
}
protected JRTemplateFrame getTemplateFrame()
{
return (JRTemplateFrame) getElementTemplate();
}
@Override
protected JRTemplateElement createElementTemplate()
{
JRTemplateFrame template = new JRTemplateFrame(getElementOrigin(),
filler.getJasperPrint().getDefaultStyleProvider());
template.setElement(this);
template.copyBox(getLineBox());
return template;
}
@Override
protected void rewind()
{
crosstabFiller.initCrosstab();
overflowStartPage = 0;
}
protected List extends JRPrintElement> getPrintElements()
{
return printFrames;
}
protected int getPrintElementsWidth()
{
return printFramesMaxWidth;
}
protected void mirrorPrintElements(List printElements, int width)
{
for (Iterator it = printElements.iterator(); it.hasNext();)
{
JRPrintElement element = it.next();
int mirrorX = width - element.getX() - element.getWidth();
element.setX(mirrorX);
}
}
@Override
protected void resolveElement(JRPrintElement element, byte evaluation)
{
// nothing
}
@Override
public void collectExpressions(JRExpressionCollector collector)
{
collector.collect(this);
}
@Override
public void visit(JRVisitor visitor)
{
visitor.visitCrosstab(this);
if (ElementsVisitorUtils.visitDeepElements(visitor))
{
new CrosstabDeepVisitor(visitor).deepVisitCrosstab(this);
}
}
@Override
public int getId()
{
return parentCrosstab.getId();
}
@Override
public JRCrosstabDataset getDataset()
{
return dataset;
}
@Override
public JRCrosstabRowGroup[] getRowGroups()
{
return rowGroups;
}
@Override
public JRCrosstabColumnGroup[] getColumnGroups()
{
return columnGroups;
}
@Override
public JRCrosstabMeasure[] getMeasures()
{
return measures;
}
private int determineLastColumnGroupWithHeaderIndex()
{
int colGroupIdx = columnGroups.length -1;
while (colGroupIdx >= 0)
{
JRCellContents header = columnGroups[colGroupIdx].getHeader();
if (header != null && !header.getChildren().isEmpty())
{
break;
}
--colGroupIdx;
}
int lastGroupIndex = colGroupIdx >= 0 ? colGroupIdx : 0;
if (log.isDebugEnabled())
{
log.debug("last column group with header is " + lastGroupIndex);
}
return lastGroupIndex;
}
/**
* Fill-time crosstab input dataset implementation.
*
* @author Lucian Chirita ([email protected] )
*/
public class JRFillCrosstabDataset extends JRFillElementDataset implements JRCrosstabDataset
{
public static final String EXCEPTION_MESSAGE_KEY_DATASET_INCREMENTING_ERROR = "crosstabs.dataset.incrementing.error";
private Object[] bucketValues;
private Object[] measureValues;
public JRFillCrosstabDataset(JRCrosstabDataset dataset, JRFillObjectFactory factory)
{
super(dataset, factory);
this.bucketValues = new Object[rowGroups.length + columnGroups.length];
this.measureValues = new Object[measures.length];
}
@Override
protected void customInitialize()
{
initBucketingService();
}
@Override
protected void customEvaluate(JRCalculator calculator) throws JRExpressionEvalException
{
for (int i = 0; i < rowGroups.length; i++)
{
bucketValues[i] = calculator.evaluate(rowGroups[i].getBucket().getExpression());
}
for (int i = 0; i < columnGroups.length; ++i)
{
bucketValues[i + rowGroups.length] = calculator.evaluate(columnGroups[i].getBucket().getExpression());
}
for (int i = 0; i < measures.length; i++)
{
measureValues[i] = calculator.evaluate(measures[i].getValueExpression());
}
}
@Override
protected void customIncrement()
{
try
{
bucketingService.addData(bucketValues, measureValues);
}
catch (JRException e)
{
throw
new JRRuntimeException(
EXCEPTION_MESSAGE_KEY_DATASET_INCREMENTING_ERROR,
(Object[])null,
e);
}
}
@Override
public void collectExpressions(JRExpressionCollector collector)
{
}
@Override
public boolean isDataPreSorted()
{
return ((JRCrosstabDataset) parent).isDataPreSorted();
}
}
/**
* Crosstab filler class.
*
* @author Lucian Chirita ([email protected] )
*/
protected class CrosstabFiller
{
private int yOffset;
private int yChunkOffset;
private boolean willOverflow;
private int[] rowHeadersXOffsets;
private boolean[] columnBreakable;
private boolean[] rowBreakable;
private int[] columnCount;
private int[] rowCount;
private int[] columnXOffsets;
private boolean noDataCellPrinted;
private boolean titlePrinted;
private int startRowIndex;
private int startColumnIndex;
private int lastColumnIndex;
private List columnHeaders;
private List> printRows;
private int[] dataColumnSortMeasures;
private HeaderCell[] spanHeaders;
private int[] spanHeadersStart;
private List rowYs = new ArrayList<>();
private int rowIdx;
private List preparedRow = new ArrayList<>();
private int preparedRowHeight;
private boolean printRowHeaders;
private boolean printColumnHeaders;
private JRFillVariable rowCountVar;
private JRFillVariable evenRowVar;
private JRFillVariable colCountVar;
private JRFillVariable evenColVar;
protected CrosstabFiller()
{
setRowHeadersXOffsets();
printRows = new ArrayList<>();
rowCountVar = variablesMap.get(JRCrosstab.VARIABLE_ROW_COUNT);
evenRowVar = variablesMap.get(JRCrosstab.VARIABLE_IS_EVEN_ROW);
colCountVar = variablesMap.get(JRCrosstab.VARIABLE_COLUMN_COUNT);
evenColVar = variablesMap.get(JRCrosstab.VARIABLE_IS_EVEN_COLUMN);
}
protected void initCrosstab()
{
columnXOffsets = computeOffsets(columnHeadersData, columnGroups, true);
columnBreakable = computeBreakableHeaders(columnHeadersData, columnGroups, columnXOffsets, true, true);
columnCount = computeCounts(columnHeadersData);
int[] rowYOffsets = computeOffsets(rowHeadersData, rowGroups, false);
rowBreakable = computeBreakableHeaders(rowHeadersData, rowGroups, rowYOffsets, false, false);
rowCount = computeCounts(rowHeadersData);
spanHeaders = new HeaderCell[rowGroups.length - 1];
spanHeadersStart = new int[rowGroups.length - 1];
startRowIndex = 0;
startColumnIndex = 0;
lastColumnIndex = 0;
noDataCellPrinted = false;
titlePrinted = false;
}
protected void setRowHeadersXOffsets()
{
rowHeadersXOffsets = new int[rowGroups.length + 1];
rowHeadersXOffsets[0] = 0;
for (int i = 0; i < rowGroups.length; i++)
{
rowHeadersXOffsets[i + 1] = rowHeadersXOffsets[i] + rowGroups[i].getWidth();
}
}
protected int[] computeOffsets(HeaderCell[][] headersData, JRFillCrosstabGroup[] groups, boolean width)
{
int[] offsets = new int[headersData[0].length + 1];
offsets[0] = 0;
for (int i = 0; i < headersData[0].length; i++)
{
int size = 0;
for (int j = groups.length - 1; j >= 0; --j)
{
if (headersData[j][i] != null)
{
JRFillCellContents cell = headersData[j][i].isTotal() ? groups[j].getFillTotalHeader() : groups[j].getFillHeader();
size = cell == null ? 0 : (width ? cell.getWidth() : cell.getHeight());
break;
}
}
offsets[i + 1] = offsets[i] + size;
}
return offsets;
}
protected boolean[] computeBreakableHeaders(HeaderCell[][] headersData, JRFillCrosstabGroup[] groups, int[] offsets, boolean width, boolean startHeaders)
{
boolean[] breakable = new boolean[headersData[0].length];
for (int i = 0; i < breakable.length; i++)
{
breakable[i] = true;
}
for (int j = 0; j < groups.length; ++j)
{
JRFillCellContents fillHeader = groups[j].getFillHeader();
if (fillHeader != null)
{
int size = width ? fillHeader.getWidth() : fillHeader.getHeight();
for (int i = 0; i < headersData[0].length; i++)
{
HeaderCell header = headersData[j][i];
if (header != null && !header.isTotal() && header.getLevelSpan() > 1)
{
int span = header.getLevelSpan();
if (startHeaders)
{
for (int k = i + 1; k < i + span && offsets[k] - offsets[i] < size; ++k)
{
breakable[k] = false;
}
}
for (int k = i + span - 1; k > i && offsets[i + span] - offsets[k] < size; --k)
{
breakable[k] = false;
}
}
}
}
}
return breakable;
}
private int[] computeCounts(HeaderCell[][] headersData)
{
int[] counts = new int[headersData[0].length];
HeaderCell[] lastHeaders = headersData[headersData.length - 1];
for (int i = 0, c = 0; i < counts.length; ++i)
{
HeaderCell lastHeader = lastHeaders[i];
if (lastHeader != null && !lastHeader.isTotal())
{
++c;
}
counts[i] = c;
}
return counts;
}
protected void fill(int availableHeight) throws JRException
{
printRows.clear();
yOffset = 0;
yChunkOffset = 0;
willOverflow = false;
fillVerticalCrosstab(availableHeight);
}
protected boolean willOverflow()
{
return willOverflow;
}
protected boolean ended()
{
return hasData ? (startRowIndex >= rowHeadersData[0].length && startColumnIndex >= columnHeadersData[0].length) : noDataCellPrinted;
}
protected void fillVerticalCrosstab(int availableHeight) throws JRException
{
JRLineBox lineBox = getLineBox();
int padding = lineBox.getTopPadding() + lineBox.getBottomPadding();
int contentsHeight = availableHeight - padding;
if (contentsHeight < 0)
{
willOverflow = true;
return;
}
if (!hasData)
{
fillNoDataCell(contentsHeight);
if (!printRows.isEmpty())
{
addFilledRows();
}
return;
}
printRowHeaders = startColumnIndex == 0 || isRepeatRowHeaders();
int rowHeadersXOffset = printRowHeaders ? rowHeadersXOffsets[rowGroups.length] : 0;
if (startColumnIndex == lastColumnIndex)
{
int availableWidth;
if (ignoreWidth)
{
//FIXME lucianc this assumes that the crosstab is not placed in a subreport or table
availableWidth = filler.maxPageWidth - filler.leftMargin - filler.rightMargin
- getX() - lineBox.getLeftPadding() - lineBox.getRightPadding();
}
else
{
availableWidth = getWidth() - lineBox.getLeftPadding() - lineBox.getRightPadding();
}
columnHeaders = getGroupHeaders(availableWidth - rowHeadersXOffset, columnXOffsets, columnBreakable, startColumnIndex, columnHeadersData, columnGroups);
lastColumnIndex = startColumnIndex + columnHeaders.size();
if (startColumnIndex == lastColumnIndex)
{
throw
new JRRuntimeException(
EXCEPTION_MESSAGE_KEY_NOT_ENOUGH_SPACE,
(Object[])null
);
}
}
List titleRow = null;
if (startRowIndex == 0 && startColumnIndex == 0 && titleCellContents != null && !titlePrinted)
{
titleRow = fillTitleCell(contentsHeight - yOffset);
if (willOverflow)
{
return;
}
titlePrinted = true;
}
if (interactive && measures.length > 1)
{
dataColumnSortMeasures = new int[lastColumnIndex - startColumnIndex];
Arrays.fill(dataColumnSortMeasures, -1);
}
printColumnHeaders = startRowIndex == 0 || isRepeatColumnHeaders();
List> columnHeaderRows = null;
if (printColumnHeaders)
{
columnHeaderRows = fillColumnHeaders(rowHeadersXOffset, contentsHeight - yOffset);
if (willOverflow)
{
//FIXME avoid repeating this
if (titleRow != null)
{
addPrintRow(titleRow);
addFilledRows();
}
return;
}
}
int lastRowIndex = fillRows(rowHeadersXOffset, contentsHeight - yOffset);
if (lastRowIndex == startRowIndex)
{
willOverflow = true;
if (titleRow != null)
{
addPrintRow(titleRow);
addFilledRows();
}
return;
}
if (titleRow != null)
{
addPrintRow(titleRow);
}
if (columnHeaderRows != null)
{
printRows.addAll(columnHeaderRows);
}
if (!printRows.isEmpty())
{
addFilledRows();
}
if (lastRowIndex >= rowHeadersData[0].length)
{
startColumnIndex = lastColumnIndex;
if (startColumnIndex < columnHeadersData[0].length)
{
startRowIndex = lastRowIndex = 0;
// set the chunk offset and compute the remaining height
int yAdvance = yOffset + getColumnBreakOffset() + padding;
yChunkOffset += yAdvance;
int remainingHeight = availableHeight - yAdvance;
// reset the elements offset
yOffset = 0;
// fill a new chunk
fillVerticalCrosstab(remainingHeight);
return;
}
}
//FIXME is this needed? we do setPrepareHeight in prepare()
boolean fillEnded = lastRowIndex >= rowHeadersData[0].length && lastColumnIndex >= columnHeadersData[0].length;
if (fillEnded)
{
setPrepareHeight(yOffset);
}
else
{
setPrepareHeight(availableHeight);
}
startRowIndex = lastRowIndex;
willOverflow = !fillEnded;
}
protected void addFilledRows()
{
List prints = new ArrayList<>();
for (Iterator> it = printRows.iterator(); it.hasNext();)
{
List rowPrints = it.next();
prints.addAll(rowPrints);
}
// add the crosstab chunk to the element
addCrosstabChunk(prints, yChunkOffset);
// clear the added rows
printRows.clear();
}
protected List getGroupHeaders(
int available,
int[] offsets,
boolean[] breakable,
int firstIndex,
HeaderCell[][] headersData,
JRFillCrosstabGroup[] groups
)
{
List headers = new ArrayList<>();
int maxOffset = available + offsets[firstIndex];
int lastIndex;
for (lastIndex = firstIndex;
lastIndex < headersData[0].length
&& offsets[lastIndex + 1] <= maxOffset;
++lastIndex)
{
HeaderCell[] groupHeaders = new HeaderCell[groups.length];
for (int j = 0; j < groups.length; ++j)
{
groupHeaders[j] = headersData[j][lastIndex];
}
headers.add(groupHeaders);
}
if (lastIndex < headersData[0].length)
{
while(lastIndex > firstIndex && !breakable[lastIndex])
{
--lastIndex;
headers.remove(headers.size() - 1);
}
}
if (lastIndex > firstIndex)
{
if (firstIndex > 0)
{
HeaderCell[] firstHeaders = headers.get(0);
for (int j = 0; j < groups.length; ++j)
{
HeaderCell header = headersData[j][firstIndex];
if (header == null)
{
int spanIndex = getSpanIndex(firstIndex, j, headersData);
if (spanIndex >= 0)
{
HeaderCell spanCell = headersData[j][spanIndex];
int headerEndIdx = spanCell.getLevelSpan() + spanIndex;
if (headerEndIdx > lastIndex)
{
headerEndIdx = lastIndex;
}
firstHeaders[j] = HeaderCell.createLevelSpanCopy(spanCell, headerEndIdx - firstIndex);
}
}
}
}
if (lastIndex < headersData[0].length)
{
for (int j = 0; j < groups.length; ++j)
{
HeaderCell header = headersData[j][lastIndex];
if (header == null)
{
int spanIndex = getSpanIndex(lastIndex, j, headersData);
if (spanIndex >= firstIndex)
{
HeaderCell spanCell = headersData[j][spanIndex];
HeaderCell[] headerCells = headers.get(spanIndex - firstIndex);
headerCells[j] = HeaderCell.createLevelSpanCopy(spanCell, lastIndex - spanIndex);
}
}
}
}
}
return headers;
}
protected int getSpanIndex(int i, int j, HeaderCell[][] headersData)
{
int spanIndex = i - 1;
while (spanIndex >= 0 && headersData[j][spanIndex] == null)
{
--spanIndex;
}
if (spanIndex >= 0)
{
HeaderCell spanCell = headersData[j][spanIndex];
int span = spanCell.getLevelSpan();
if (span > i - spanIndex)
{
return spanIndex;
}
}
return -1;
}
protected void fillNoDataCell(int availableHeight) throws JRException
{
if (whenNoDataCell == null)
{
noDataCellPrinted = true;
}
else
{
if (availableHeight < whenNoDataCell.getHeight())
{
willOverflow = true;
}
else
{
whenNoDataCell.evaluate(JRExpression.EVALUATION_DEFAULT);
whenNoDataCell.prepare(availableHeight);
willOverflow = whenNoDataCell.willOverflow();
if (!willOverflow)
{
whenNoDataCell.setX(0);
whenNoDataCell.setY(0);
JRPrintFrame printCell = whenNoDataCell.fill();
List noDataRow = new ArrayList<>(1);
noDataRow.add(printCell);
addPrintRow(noDataRow);
yOffset += whenNoDataCell.getPrintHeight();
noDataCellPrinted = true;
}
}
}
}
protected List> fillColumnHeaders(int rowHeadersXOffset, int availableHeight) throws JRException
{
JRFillCellContents[][] columnHeaderRows = new JRFillCellContents[columnGroups.length][lastColumnIndex - startColumnIndex + 2];
rowYs.clear();
rowYs.add(0);
if (printRowHeaders && headerCell != null)
{
JRFillCellContents contents = fillHeader(availableHeight);
if (willOverflow)
{
return null;
}
columnHeaderRows[columnGroups.length - 1][0] = contents;
}
rows:
for (rowIdx = 0; rowIdx < columnGroups.length; rowIdx++)
{
if (printRowHeaders)
{
JRFillCellContents cell = prepareColumnCrosstabHeader(availableHeight);
columnHeaderRows[rowIdx][1] = cell;
if (willOverflow)
{
break rows;
}
}
for (int columnIdx = startColumnIndex; columnIdx < lastColumnIndex; ++columnIdx)
{
HeaderCell[] headers = columnHeaders.get(columnIdx - startColumnIndex);
HeaderCell cell = headers[rowIdx];
if (cell != null)
{
JRFillCellContents contents = prepareColumnHeader(cell, rowIdx, columnIdx, rowHeadersXOffset, availableHeight);
columnHeaderRows[rowIdx + cell.getDepthSpan() - 1][columnIdx - startColumnIndex + 2] = contents;
if (willOverflow)
{
break rows;
}
}
}
int rowStretchHeight = stretchColumnHeadersRow(columnHeaderRows[rowIdx]);
rowYs.add(rowYs.get(rowIdx) + rowStretchHeight);
}
List> headerRows;
if (willOverflow)
{
headerRows = null;
releaseColumnHeaderCells(columnHeaderRows);
}
else
{
headerRows = fillColumnHeaders(columnHeaderRows);
// reversing so that the overall header cell comes before the column crosstab header
Collections.reverse(headerRows);
yOffset += rowYs.get(columnGroups.length);
}
resetVariables();
return headerRows;
}
private void setCountVars(int rowIdx, int colIdx)
{
if (rowIdx == -1)
{
rowCountVar.setValue(null);
evenRowVar.setValue(null);
}
else
{
rowCountVar.setValue(rowCount[rowIdx]);
evenRowVar.setValue(rowCount[rowIdx] % 2 == 0);
}
if (colIdx == -1)
{
colCountVar.setValue(null);
evenColVar.setValue(null);
}
else
{
colCountVar.setValue(columnCount[colIdx]);
evenColVar.setValue(columnCount[colIdx] % 2 == 0);
}
}
private List fillTitleCell(int availableHeight) throws JRException
{
setCountVars(-1, -1);
JRFillCellContents cell = titleCellContents;
int width = rowHeadersXOffsets[rowGroups.length] + columnXOffsets[lastColumnIndex] - columnXOffsets[startColumnIndex];
cell = cell.getTransformedContents(width, cell.getHeight(), getTitleCell().getContentsPosition(), CrosstabRowPositionEnum.TOP);
JRFillCellContents contents = cell.getWorkingClone();
contents.evaluate(JRExpression.EVALUATION_DEFAULT);
contents.prepare(availableHeight);
willOverflow = contents.willOverflow();
if (willOverflow)
{
return null;
}
contents.setX(0);
contents.setY(yOffset);
contents.setHorizontalSpan(rowGroups.length + lastColumnIndex - startColumnIndex);
contents.setVerticalSpan(1);
JRPrintFrame printCell = contents.fill();
List titleRow = new ArrayList<>(1);
titleRow.add(printCell);
yOffset += contents.getPrintHeight();
return titleRow;
}
private JRFillCellContents fillHeader(int availableHeight) throws JRException
{
setCountVars(-1, -1);
JRFillCellContents cell = headerCell;
if (!headerCell.getChildren().isEmpty() && interactive)
{
// look for row group column headers
cell = decorateCellWithRowGroupIconLabel(cell);
}
JRFillCellContents contents = cell.getWorkingClone();
contents.evaluate(JRExpression.EVALUATION_DEFAULT);
contents.prepare(availableHeight);
if (interactive)
{
contents.addHtmlClass(HTML_CLASS_CROSS_FLOATING);
}
willOverflow = contents.willOverflow();
if (!willOverflow)
{
contents.setX(0);
contents.setY(yOffset);
contents.setVerticalSpan(columnGroups.length);
contents.setHorizontalSpan(rowGroups.length);
}
return contents;
}
private JRFillCellContents decorateCellWithRowGroupIconLabel(JRFillCellContents cell)
{
List cellElements = cell.getChildren();
BucketDefinition[] rowBuckets = bucketingService.getRowBuckets();
int[] headerTextIndices = new int[rowBuckets.length];
Arrays.fill(headerTextIndices, -1);
boolean[] alignedText = new boolean[rowBuckets.length];
boolean foundHeader = false;
for (int bucketIdx = 0, bucketXOffset = 0; bucketIdx < rowBuckets.length;
bucketXOffset += rowGroups[bucketIdx].getWidth(), bucketIdx++)
{
int headerIndex = findRowGroupColumHeaderElementIndex(bucketIdx, cellElements);
if (headerIndex < 0)
{
continue;
}
JRElement headerElement = (JRElement) cellElements.get(headerIndex);
boolean aligned = headerElement.getX() == bucketXOffset
&& headerElement.getWidth() == rowGroups[bucketIdx].getWidth();
BucketDefinition rowBucket = rowBuckets[bucketIdx];
if (bucketOrder(rowBucket) == BucketOrder.NONE && !aligned)
{
continue;
}
headerTextIndices[bucketIdx] = headerIndex;
alignedText[bucketIdx] = aligned;
foundHeader = true;
}
if (!foundHeader)
{
return cell;
}
JRFillCellContents decoratedCell = (JRFillCellContents) cell.createClone();
List clonedElements = decoratedCell.getChildren();
BuiltinExpressionEvaluatorFactory builtinExpressions = new BuiltinExpressionEvaluatorFactory();
JRFillExpressionEvaluator decoratedEvaluator = builtinExpressions.decorate(cell.expressionEvaluator);
NavigableMap iconLabelElements = new TreeMap<>();
for (int bucketIdx = 0; bucketIdx < headerTextIndices.length; bucketIdx++)
{
int textElementIndex = headerTextIndices[bucketIdx];
if (textElementIndex >= 0)
{
JRFillTextElement textElement = (JRFillTextElement) clonedElements.get(textElementIndex);
BucketOrder bucketOrder = bucketOrder(rowBuckets[bucketIdx]);
if (bucketOrder == BucketOrder.NONE)
{
if (alignedText[bucketIdx])
{
// adding properties so that the text is selected as part of the row group column
textElement.setExpressionEvaluator(decoratedEvaluator);
textElement.addDynamicProperty(CrosstabInteractiveJsonHandler.PROPERTY_COLUMN_INDEX,
builtinExpressions.createConstantExpression(Integer.toString(bucketIdx)));
textElement.addDynamicProperty(HtmlExporter.PROPERTY_HTML_CLASS,
builtinExpressions.createConstantExpression("jrxtrowheader jrxtinteractive " + HTML_CLASS_ROW_FLOATING));
}
}
else
{
textElement.setExpressionEvaluator(decoratedEvaluator);
textElement.addDynamicProperty(MatcherExporterFilter.PROPERTY_MATCHER_EXPORT_FILTER_KEY,
builtinExpressions.createConstantExpression(TableReport.TABLE_HEADER_LABEL_MATCHER_EXPORT_KEY));
SortOrderEnum order = BucketOrder.toSortOrderEnum(bucketOrder);
if (log.isDebugEnabled())
{
log.debug("wrapping header element " + textElement.getUUID() + " in iconLabel for row group " + bucketIdx);
}
JRDesignComponentElement iconLabelElement = createIconLabelElement(order,
textElement, builtinExpressions);
if (alignedText[bucketIdx])
{
iconLabelElement.getPropertiesMap().setProperty(CrosstabInteractiveJsonHandler.PROPERTY_COLUMN_INDEX,
Integer.toString(bucketIdx));
iconLabelElement.getPropertiesMap().setProperty(HtmlExporter.PROPERTY_HTML_CLASS, "jrxtrowheader jrxtinteractive " + HTML_CLASS_ROW_FLOATING);
}
iconLabelElements.put(textElementIndex, iconLabelElement);
}
}
}
IconLabelFillObjectFactory factory = new IconLabelFillObjectFactory(fillFactory, decoratedEvaluator);
for (Entry entry : iconLabelElements.descendingMap().entrySet())
{
int elementIndex = entry.getKey();
JRDesignComponentElement iconLabelElement = entry.getValue();
JRFillComponentElement fillIconLabelElement = new JRFillComponentElement(filler, iconLabelElement, factory);
decoratedCell.addElement(elementIndex + 1, fillIconLabelElement);
}
return decoratedCell;
}
private int findRowGroupColumHeaderElementIndex(int rowGroupIndex, List cellElements)
{
String rowGropIndexStr = Integer.toString(rowGroupIndex);
int colHeaderTextIndex = -1;
for (ListIterator elemIt = cellElements.listIterator(cellElements.size()); elemIt.hasPrevious();)
{
JRChild child = elemIt.previous();
if (child instanceof JRTextElement)
{
JRFillTextElement textElement = (JRFillTextElement) child;
JRElement parentElement = textElement.getParent();
String prop = parentElement.hasProperties()
? parentElement.getPropertiesMap().getProperty(PROPERTY_ROW_GROUP_COLUMN_HEADER) : null;
if (prop != null && prop.equals(rowGropIndexStr))
{
// found it
colHeaderTextIndex = elemIt.nextIndex();
break;
}
}
}
return colHeaderTextIndex;
}
private JRFillCellContents prepareColumnHeader(HeaderCell cell, int rowIdx, int columnIdx, int xOffset, int availableHeight) throws JRException
{
JRFillCrosstabColumnGroup group = columnGroups[rowIdx];
JRFillCellContents contents = cell.isTotal() ? group.getFillTotalHeader() : group.getFillHeader();
int width = columnXOffsets[columnIdx + cell.getLevelSpan()] - columnXOffsets[columnIdx];
int height = contents.getHeight();
if (width <= 0 || height <= 0)
{
return null;
}
// column header is a cell that is on the last level with a column header and has no row span
boolean headerLabel = rowIdx + cell.getDepthSpan() == lastColumnGroupWithHeaderIndex + 1
&& cell.getLevelSpan() == 1;
// a column header is a cell that comes after the last level and has no row span
boolean header = rowIdx + cell.getDepthSpan() >= lastColumnGroupWithHeaderIndex + 1
&& cell.getLevelSpan() == 1;
JRFillCellContents preparedContents = null;
int rowY = rowYs.get(rowIdx);
if (availableHeight >= rowY + height)
{
setCountVars(-1, columnIdx);
setGroupVariables(columnGroups, cell.getBucketValues());
setGroupMeasureVariables(cell, false);
contents = contents.getTransformedContents(width, height, group.getPositionValue(), CrosstabRowPositionEnum.TOP);
boolean firstOnRow = columnIdx == startColumnIndex && (!printRowHeaders || headerCell == null);
contents = contents.getBoxContents(
firstOnRow && getRunDirectionValue() == RunDirectionEnum.LTR,
firstOnRow && getRunDirectionValue() == RunDirectionEnum.RTL,
false);
// check if the column is sorted
if (interactive && headerLabel && matchesOrderByColumn(cell))
{
contents = decorateWithSortIcon(contents, orderByColumnInfo.getOrder());
}
contents = contents.getWorkingClone();
contents.evaluate(JRExpression.EVALUATION_DEFAULT);
contents.prepare(availableHeight - rowY);
if (interactive)
{
contents.addHtmlClass(HTML_CLASS_COLUMN_FLOATING);
boolean sortHeader = false;
if (headerLabel && measures.length > 1)
{
if (measures.length <= 1)
{
sortHeader = true;
}
else
{
// looking for the sorting measure index property in the column header
int sortMeasureIdx = determineColumnSortMeasure(contents);
dataColumnSortMeasures[columnIdx - startColumnIndex] = sortMeasureIdx;
sortHeader = sortMeasureIdx >= 0;
}
}
if (header)
{
contents.setPrintProperty(CrosstabInteractiveJsonHandler.PROPERTY_COLUMN_INDEX, Integer.toString(columnIdx));
contents.addHtmlClass("jrxtcolheader");
if (sortHeader)
{
contents.addHtmlClass("jrxtinteractive");
}
}
}
if (contents.willOverflow())
{
willOverflow = true;
}
else
{
contents.setX(columnXOffsets[columnIdx] - columnXOffsets[startColumnIndex] + xOffset);
contents.setY(rowY + yOffset);
contents.setVerticalSpan(cell.getDepthSpan());
contents.setHorizontalSpan(cell.getLevelSpan());
preparedContents = contents;
}
}
else
{
willOverflow = true;
}
return preparedContents;
}
private int determineColumnSortMeasure(JRFillCellContents contents)
{
int sortMeasureIdx = -1;
JRElement[] cellElements = contents.getElements();
for (JRElement element : cellElements)
{
String sortMeasureIdxProp = element.hasProperties()
? element.getPropertiesMap().getProperty(PROPERTY_COLUMN_HEADER_SORT_MEASURE_INDEX) : null;
if (sortMeasureIdxProp != null)
{
sortMeasureIdx = JRPropertiesUtil.asInteger(sortMeasureIdxProp);
break;
}
}
return sortMeasureIdx;
}
private JRFillCellContents decorateWithSortIcon(JRFillCellContents cell, SortOrderEnum order)
{
// check whether the contents contain a single text element
JRElement[] elements = cell.getElements();
if (elements.length != 1)
{
if (log.isDebugEnabled())
{
log.debug("order by column header has " + elements.length + " elements");
}
return cell;
}
if (!(elements[0] instanceof JRTextElement))
{
if (log.isDebugEnabled())
{
log.debug("order by column header has element " + elements[0].getClass().getName());
}
return cell;
}
// TODO lucianc cache
JRFillTextElement textElement = (JRFillTextElement) elements[0];
if (log.isDebugEnabled())
{
log.debug("wrapping column header element " + textElement.getUUID() + " in iconLabel");
}
BuiltinExpressionEvaluatorFactory builtinExpressions = new BuiltinExpressionEvaluatorFactory();
JRDesignComponentElement iconLabelElement = createIconLabelElement(order, textElement, builtinExpressions);
JRFillExpressionEvaluator decoratedEvaluator = builtinExpressions.decorate(cell.expressionEvaluator);
IconLabelFillObjectFactory factory = new IconLabelFillObjectFactory(fillFactory, decoratedEvaluator);
JRFillComponentElement fillIconLabelElement = new JRFillComponentElement(filler, iconLabelElement, factory);
JRFillCellContents clonedCell = (JRFillCellContents) cell.createClone();
clonedCell.addElement(1, fillIconLabelElement);
JRFillElement clonedTextElement = (JRFillElement) clonedCell.getElements()[0];
clonedTextElement.setExpressionEvaluator(decoratedEvaluator);
clonedTextElement.addDynamicProperty(MatcherExporterFilter.PROPERTY_MATCHER_EXPORT_FILTER_KEY,
builtinExpressions.createConstantExpression(TableReport.TABLE_HEADER_LABEL_MATCHER_EXPORT_KEY));
return clonedCell;
}
protected JRDesignComponentElement createIconLabelElement(SortOrderEnum order, JRFillTextElement textElement,
BuiltinExpressionEvaluatorFactory builtinExpressions)
{
JRTextElement parentElement = (JRTextElement) textElement.getParent();
JRDesignComponentElement designIconLabelElement =
IconLabelComponentUtil.getInstance(filler.getJasperReportsContext()).createIconLabelComponentElement(parentElement, textElement);
IconLabelComponent iconLabelComponent = (IconLabelComponent)designIconLabelElement.getComponent();
JRDesignTextField labelTextField = (JRDesignTextField)iconLabelComponent.getLabelTextField();
JRDesignTextField iconTextField = (JRDesignTextField)iconLabelComponent.getIconTextField();
designIconLabelElement.setStyle(textElement.getInitStyle());
labelTextField.setStyle(textElement.getInitStyle());
iconTextField.setStyle(textElement.getInitStyle());
if (textElement instanceof JRTextField)
{
labelTextField.setExpression(((JRTextField) textElement).getExpression());
}
else if (textElement instanceof JRStaticText)
{
String text = ((JRStaticText) textElement).getText();
labelTextField.setExpression(builtinExpressions.createConstantExpression(text));
}
String iconText =
order == SortOrderEnum.ASCENDING ? filler.getPropertiesUtil().getProperty(TableReport.PROPERTY_UP_ARROW_CHAR)
: (order == SortOrderEnum.DESCENDING ? filler.getPropertiesUtil().getProperty(TableReport.PROPERTY_DOWN_ARROW_CHAR) : "");
iconTextField.setExpression(builtinExpressions.createConstantExpression(" " + iconText));
designIconLabelElement.getPropertiesMap().setProperty(
MatcherExporterFilter.PROPERTY_MATCHER_EXPORT_FILTER_KEY,
TableReport.TABLE_HEADER_ICON_LABEL_MATCHER_EXPORT_KEY
);
return designIconLabelElement;
}
protected JRFillCellContents prepareColumnCrosstabHeader(int availableHeight) throws JRException
{
JRFillCellContents header = columnGroups[rowIdx].getFillCrosstabHeader();
if (header == null)
{
return null;
}
int width = header.getWidth();
int height = header.getHeight();
if (width <= 0 || height <= 0)
{
return null;
}
int rowY = rowYs.get(rowIdx);
if (availableHeight < rowY + height)
{
willOverflow = true;
return null;
}
if (rowIdx == columnGroups.length - 1 && !header.getChildren().isEmpty() && interactive)
{
// look for row group column headers in the last column group's header
header = decorateCellWithRowGroupIconLabel(header);
}
JRFillCellContents preparedCell = header.getWorkingClone();
setCountVars(-1, -1);
preparedCell.evaluate(JRExpression.EVALUATION_DEFAULT);
preparedCell.prepare(availableHeight - rowY);
if (interactive)
{
preparedCell.addHtmlClass(HTML_CLASS_CROSS_FLOATING);
}
if (preparedCell.willOverflow())
{
willOverflow = true;
return null;
}
preparedCell.setX(0);
preparedCell.setY(rowY + yOffset);
preparedCell.setHorizontalSpan(rowGroups.length);
preparedCell.setVerticalSpan(1);
return preparedCell;
}
private int stretchColumnHeadersRow(JRFillCellContents[] headers)
{
int rowY = rowYs.get(rowIdx);
int rowStretchHeight = 0;
for (int j = 0; j < headers.length; j++)
{
JRFillCellContents contents = headers[j];
if (contents != null)
{
int startRowY = rowY;
if (contents.getVerticalSpan() > 1)
{
startRowY = rowYs.get(rowIdx - contents.getVerticalSpan() + 1);
}
int height = contents.getPrintHeight() - rowY + startRowY;
if (height > rowStretchHeight)
{
rowStretchHeight = height;
}
}
}
for (int j = 0; j < headers.length; j++)
{
JRFillCellContents contents = headers[j];
if (contents != null)
{
int startRowY = rowY;
if (contents.getVerticalSpan() > 1)
{
startRowY = rowYs.get(rowIdx - contents.getVerticalSpan() + 1);
}
contents.stretchTo(rowStretchHeight + rowY - startRowY);
}
}
return rowStretchHeight;
}
private List> fillColumnHeaders(JRFillCellContents[][] columnHeaderRows) throws JRException
{
List> headerRows = new ArrayList<>(columnGroups.length);
for (int i = 0; i < columnHeaderRows.length; ++i)
{
List headerRow = new ArrayList<>(lastColumnIndex - startColumnIndex);
headerRows.add(headerRow);
for (int j = 0; j < columnHeaderRows[i].length; j++)
{
JRFillCellContents contents = columnHeaderRows[i][j];
if (contents != null)
{
headerRow.add(contents.fill());
contents.releaseWorkingClone();
}
}
}
return headerRows;
}
private void releaseColumnHeaderCells(JRFillCellContents[][] columnHeaderRows) throws JRException
{
for (int i = 0; i < columnHeaderRows.length; ++i)
{
for (int j = 0; j < columnHeaderRows[i].length; j++)
{
JRFillCellContents contents = columnHeaderRows[i][j];
if (contents != null)
{
contents.rewind();
contents.releaseWorkingClone();
}
}
}
}
protected int fillRows(int xOffset, int availableHeight) throws JRException
{
rowYs.clear();
rowYs.add(0);
for (rowIdx = 0; rowIdx < cellData.length - startRowIndex; ++rowIdx)
{
initPreparedRow();
prepareRow(xOffset, availableHeight);
if (willOverflow)
{
break;
}
fillRow();
rowYs.add(rowYs.get(rowIdx) + preparedRowHeight);
}
if (rowIdx < cellData.length - startRowIndex)//overflow
{
releasePreparedRow();
if (printRowHeaders)
{
fillContinuingRowHeaders(xOffset, availableHeight);
}
}
yOffset += rowYs.get(rowIdx);
return rowIdx + startRowIndex;
}
private void initPreparedRow()
{
preparedRow.clear();
preparedRowHeight = 0;
}
private void removeFilledRows(int rowsToRemove)
{
if (rowsToRemove > 0)
{
for (int i = 0; i < rowsToRemove; ++i)
{
printRows.remove(printRows.size() - 1);
rowYs.remove(rowYs.size() - 1);
}
rowIdx -= rowsToRemove;
}
}
private void releasePreparedRow() throws JRException
{
for (Iterator it = preparedRow.iterator(); it.hasNext();)
{
JRFillCellContents cell = it.next();
cell.rewind();
cell.releaseWorkingClone();
}
preparedRow.clear();
}
private void fillRow() throws JRException
{
int rowY = rowYs.get(rowIdx);
List rowPrints = new ArrayList<>(preparedRow.size());
for (Iterator it = preparedRow.iterator(); it.hasNext();)
{
JRFillCellContents cell = it.next();
int spanHeight = 0;
if (cell.getVerticalSpan() > 1)
{
spanHeight = rowY - rowYs.get(rowIdx - cell.getVerticalSpan() + 1);
}
cell.stretchTo(preparedRowHeight + spanHeight);
rowPrints.add(cell.fill());
cell.releaseWorkingClone();
}
addPrintRow(rowPrints);
}
private void prepareRow(int xOffset, int availableHeight) throws JRException
{
for (int col = startColumnIndex; col < lastColumnIndex; ++col)
{
CrosstabCell data = cellData[rowIdx + startRowIndex][col];
boolean overflow = prepareDataCell(data, col, availableHeight, xOffset);
if (overflow)
{
willOverflow = true;
return;
}
}
resetVariables();
if (printRowHeaders)
{
for (int j = 0; j < rowGroups.length; j++)
{
HeaderCell cell = rowHeadersData[j][rowIdx + startRowIndex];
int vSpan = 0;
if (cell == null)
{
// if we have a span header
if (toCloseRowHeader(j))
{
cell = spanHeaders[j];
vSpan = cell.getLevelSpan();
if (spanHeadersStart[j] < startRowIndex)//continuing from the prev page
{
vSpan += spanHeadersStart[j] - startRowIndex;
}
}
}
else
{
if (cell.getLevelSpan() > 1)
{
spanHeaders[j] = cell;
spanHeadersStart[j] = rowIdx + startRowIndex;
continue;
}
vSpan = 1;
}
if (cell != null)
{
boolean overflow = prepareRowHeader(j, cell, vSpan, availableHeight);
if (overflow)
{
willOverflow = true;
return;
}
}
}
// successfully prepared a row, reset the span headers
for (int j = 0; j < rowGroups.length; j++)
{
// if we have a span header
if (rowHeadersData[j][rowIdx + startRowIndex] == null
&& toCloseRowHeader(j))
{
// reset it
spanHeaders[j] = null;
}
}
resetVariables();
}
}
private boolean prepareDataCell(CrosstabCell data, int column, int availableHeight, int xOffset) throws JRException
{
int rowY = rowYs.get(rowIdx);
JRFillCrosstabCell cell = crossCells[data.getRowTotalGroupIndex()][data.getColumnTotalGroupIndex()];
JRFillCellContents contents = cell == null ? null : cell.getFillContents();
if (contents == null || contents.getWidth() <= 0 || contents.getHeight() <= 0)
{
return false;
}
boolean overflow = availableHeight < rowY + contents.getHeight();
if (!overflow)
{
boolean leftEmpty = startColumnIndex != 0 && !isRepeatRowHeaders();
boolean topEmpty = startRowIndex != 0 && !isRepeatColumnHeaders();
setCountVars(rowIdx + startRowIndex, column);
setGroupVariables(rowGroups, data.getRowBucketValues());
setGroupVariables(columnGroups, data.getColumnBucketValues());
setMeasureVariables(data);
boolean firstOnRow = leftEmpty && column == startColumnIndex;
contents = contents.getBoxContents(
firstOnRow && getRunDirectionValue() == RunDirectionEnum.LTR,
firstOnRow && getRunDirectionValue() == RunDirectionEnum.RTL,
topEmpty && rowIdx == 0);
contents = contents.getWorkingClone();
contents.evaluate(JRExpression.EVALUATION_DEFAULT);
contents.prepare(availableHeight - rowY);
if (interactive)
{
contents.setPrintProperty(CrosstabInteractiveJsonHandler.PROPERTY_COLUMN_INDEX, Integer.toString(column));
contents.addHtmlClass("jrxtdatacell");
}
preparedRow.add(contents);
overflow = contents.willOverflow();
if (!overflow)
{
contents.setX(columnXOffsets[column] - columnXOffsets[startColumnIndex] + xOffset);
contents.setY(rowY + yOffset);
int rowCellHeight =
contents.isLegacyElementStretchEnabled() // this test is probably not needed, but using it just to be safe
? contents.getPrintHeight()
: Math.max(contents.getPrintHeight(), contents.getHeight());
if (rowCellHeight > preparedRowHeight)
{
preparedRowHeight = rowCellHeight;
}
}
}
return overflow;
}
private boolean prepareRowHeader(int rowGroup, HeaderCell cell, int vSpan, int availableHeight) throws JRException
{
JRFillCrosstabRowGroup group = rowGroups[rowGroup];
JRFillCellContents contents = cell.isTotal() ? group.getFillTotalHeader() : group.getFillHeader();
if (contents.getWidth() <= 0 || contents.getHeight() <= 0)
{
return false;
}
int spanHeight = 0;
int headerY = rowYs.get(rowIdx - vSpan + 1);
if (vSpan > 1)
{
spanHeight += rowYs.get(rowIdx) - headerY;
}
int rowHeight = spanHeight + preparedRowHeight;
boolean stretchContents = group.getPositionValue() == CrosstabRowPositionEnum.STRETCH;
int contentsHeight = stretchContents ? rowHeight : contents.getHeight();
boolean headerOverflow = availableHeight < headerY + contentsHeight || rowHeight < contents.getHeight();
if (!headerOverflow)
{
setCountVars(rowIdx + startRowIndex - vSpan + 1, -1);
setGroupVariables(rowGroups, cell.getBucketValues());
setGroupMeasureVariables(cell, true);
if (stretchContents)
{
contents = contents.getTransformedContents(contents.getWidth(), rowHeight, CrosstabColumnPositionEnum.LEFT, CrosstabRowPositionEnum.STRETCH);
}
contents = contents.getBoxContents(false, false, rowIdx + 1 == vSpan && (!printColumnHeaders || headerCell == null));
contents.getWorkingClone();
contents.evaluate(JRExpression.EVALUATION_DEFAULT);
contents.prepare(availableHeight - headerY);
if (interactive)
{
contents.addHtmlClass(HTML_CLASS_ROW_FLOATING);
contents.setPrintProperty(CrosstabInteractiveJsonHandler.PROPERTY_COLUMN_INDEX, Integer.toString(rowGroup));
if (cell.getDepthSpan() == 1)
{
// marking only unspanned headers for HTML selection
contents.addHtmlClass("jrxtrowheader");
}
}
preparedRow.add(contents);
headerOverflow = contents.willOverflow();
if (!headerOverflow)
{
contents.setX(rowHeadersXOffsets[rowGroup]);
contents.setY(headerY + yOffset);
contents.setVerticalSpan(vSpan);
contents.setHorizontalSpan(cell.getDepthSpan());
int rowCellHeight = contents.getPrintHeight() - spanHeight;
if (rowCellHeight > preparedRowHeight)
{
preparedRowHeight = rowCellHeight;
}
}
}
if (headerOverflow)
{
removeFilledRows(vSpan - 1);
}
return headerOverflow;
}
protected boolean toCloseRowHeader(int rowGroup)
{
return rowGroup < rowGroups.length - 1 &&
spanHeaders[rowGroup] != null &&
spanHeaders[rowGroup].getLevelSpan() + spanHeadersStart[rowGroup] ==
rowIdx + startRowIndex + 1;
}
private void removeExceedingSpanHeaders()
{
for (int j = rowGroups.length - 2; j >= 0; --j)
{
if (spanHeaders[j] != null && spanHeadersStart[j] >= rowIdx + startRowIndex)
{
spanHeaders[j] = null;
}
}
}
private void setBackSpanHeaders()
{
for (int j = rowGroups.length - 2; j >= 0 && spanHeaders[j] == null; --j)
{
int spanIndex = getSpanIndex(rowIdx + startRowIndex, j, rowHeadersData);
if (spanIndex >= 0)
{
spanHeaders[j] = rowHeadersData[j][spanIndex];
spanHeadersStart[j] = spanIndex;
}
}
}
private void fillContinuingRowHeaders(int xOffset, int availableHeight) throws JRException
{
boolean done = false;
breakCrosstab:
do
{
removeExceedingSpanHeaders();
if (!rowBreakable[rowIdx + startRowIndex])
{
removeFilledRows(1);
setBackSpanHeaders();
continue;
}
initPreparedRow();
//fill continuing headers
for (int j = 0; j < rowGroups.length - 1; ++j)
{
if (spanHeaders[j] != null)
{
boolean headerOverflow = prepareContinuingRowHeader(j, availableHeight);
if (headerOverflow)
{
releasePreparedRow();
continue breakCrosstab;
}
}
}
if (!preparedRow.isEmpty())
{
int lastRowHeight = rowYs.get(rowIdx) - rowYs.get(rowIdx - 1);
if (preparedRowHeight > lastRowHeight)//need to stretch already filled row by refilling
{
refillLastRow(xOffset, availableHeight);
}
else
{
fillContinuingHeaders(lastRowHeight);
}
}
done = true;
}
while (!done && rowIdx > 0);
}
private void fillContinuingHeaders(int lastRowHeight) throws JRException
{
int nextToLastHeaderY = rowYs.get(rowIdx - 1);
List lastPrintRow = getLastPrintRow();
for (int j = 0; j < preparedRow.size(); ++j)
{
JRFillCellContents contents = preparedRow.get(j);
int headerY = rowYs.get(rowIdx - contents.getVerticalSpan());
contents.stretchTo(nextToLastHeaderY - headerY + lastRowHeight);
lastPrintRow.add(contents.fill());
contents.releaseWorkingClone();
}
}
private void refillLastRow(int xOffset, int availableHeight) throws JRException
{
removeFilledRows(1);
setBackSpanHeaders();
prepareRow(xOffset, availableHeight);
fillRow();
rowYs.add(rowYs.get(rowIdx) + preparedRowHeight);
++rowIdx;
}
private boolean prepareContinuingRowHeader(int rowGroup, int availableHeight) throws JRException
{
HeaderCell cell = spanHeaders[rowGroup];
int vSpan = rowIdx + startRowIndex - spanHeadersStart[rowGroup];
if (spanHeadersStart[rowGroup] < startRowIndex)//continuing from the prev page
{
vSpan += spanHeadersStart[rowGroup] - startRowIndex;
}
int headerY = rowYs.get(rowIdx - vSpan);
int lastHeaderY = rowYs.get(rowIdx);
int headerHeight = lastHeaderY - headerY;
int nextToLastHeaderY = rowYs.get(rowIdx - 1);
int stretchHeight = nextToLastHeaderY - headerY;
JRFillCrosstabRowGroup group = rowGroups[rowGroup];
JRFillCellContents contents = cell.isTotal() ? group.getFillTotalHeader() : group.getFillHeader();
boolean stretchContents = group.getPositionValue() == CrosstabRowPositionEnum.STRETCH;
int contentsHeight = stretchContents ? headerHeight : contents.getHeight();
boolean headerOverflow = availableHeight < headerY + contentsHeight || headerHeight < contents.getHeight();
if (!headerOverflow)
{
setCountVars(rowIdx + startRowIndex - vSpan, -1);
setGroupVariables(rowGroups, cell.getBucketValues());
setGroupMeasureVariables(cell, true);
if (stretchContents)
{
contents = contents.getTransformedContents(contents.getWidth(), headerHeight, CrosstabColumnPositionEnum.LEFT, CrosstabRowPositionEnum.STRETCH);
}
contents = contents.getBoxContents(false, false, rowIdx == vSpan && (!printColumnHeaders || headerCell == null));
contents.getWorkingClone();
contents.evaluate(JRExpression.EVALUATION_DEFAULT);
contents.prepare(availableHeight - headerY);
if (interactive)
{
contents.addHtmlClass(HTML_CLASS_ROW_FLOATING);
contents.setPrintProperty(CrosstabInteractiveJsonHandler.PROPERTY_COLUMN_INDEX, Integer.toString(rowGroup));
if (cell.getDepthSpan() == 1)
{
// marking only unspanned headers for HTML selection
contents.addHtmlClass("jrxtrowheader");
}
}
preparedRow.add(contents);
headerOverflow = contents.willOverflow();
if (!headerOverflow)
{
contents.setX(rowHeadersXOffsets[rowGroup]);
contents.setY(headerY + yOffset);
contents.setVerticalSpan(vSpan);//FIXME is this correct?
contents.setHorizontalSpan(cell.getDepthSpan());
int rowHeight = contents.getPrintHeight() - stretchHeight;
if (rowHeight > preparedRowHeight)
{
preparedRowHeight = rowHeight;
}
}
}
if (headerOverflow)
{
removeFilledRows(vSpan);
}
return headerOverflow;
}
protected void addPrintRow(List printRow)
{
printRows.add(printRow);
}
protected List getLastPrintRow()
{
return printRows.get(printRows.size() - 1);
}
protected void setGroupVariables(JRFillCrosstabGroup[] groups, Bucket[] bucketValues)
{
for (int i = 0; i < groups.length; i++)
{
Object value = null;
if (bucketValues[i] != null && !bucketValues[i].isTotal())
{
value = bucketValues[i].getValue();
}
groups[i].getFillVariable().setValue(value);
}
}
protected void setGroupMeasureVariables(HeaderCell cell, boolean rowGroup)
{
MeasureValue[][] totals = cell.getTotals();
for (int m = 0; m < measures.length; m++)
{
for (int row = 0; row <= rowGroups.length; row++)
{
for (int col = 0; col <= columnGroups.length; col++)
{
// setting the same values for all row/column groups when filling column/row headers
MeasureValue[] vals = rowGroup ? totals[row] : totals[col];
if (row == rowGroups.length && col == columnGroups.length)
{
// setting the base measure variable
Object value = measureValue(vals, m);
measures[m].getFillVariable().setValue(value);
}
else if (retrieveTotal[row][col])
{
JRFillVariable totalVar = totalVariables[row][col][m];
Object value = measureValue(vals, m);
totalVar.setValue(value);
}
}
}
}
}
protected void setMeasureVariables(CrosstabCell cell)
{
MeasureValue[] values = cell.getMesureValues();
for (int i = 0; i < measures.length; i++)
{
Object value = measureValue(values, i);
measures[i].getFillVariable().setValue(value);
}
MeasureValue[][][] totals = cell.getTotals();
for (int row = 0; row <= rowGroups.length; row++)
{
for (int col = 0; col <= columnGroups.length; col++)
{
MeasureValue[] vals = totals[row][col];
if (retrieveTotal[row][col])
{
for (int m = 0; m < measures.length; m++)
{
JRFillVariable totalVar = totalVariables[row][col][m];
Object value = measureValue(vals, m);
totalVar.setValue(value);
}
}
}
}
}
protected Object measureValue(MeasureValue[] values, int measureIdx)
{
if (values == null)
{
return null;
}
Object value;
if (measures[measureIdx].getPercentageType() == CrosstabPercentageEnum.GRAND_TOTAL)
{
if (values[measureIdx].isInitialized())
{
value = values[measureIdx].getValue();
}
else
{
value = measures[measureIdx].getPercentageCalculator().calculatePercentage(values[measureIdx], grandTotals[measureIdx]);
}
}
else
{
value = values[measureIdx].getValue();
}
return value;
}
protected void resetVariables()
{
for (int i = 0; i < rowGroups.length; i++)
{
rowGroups[i].getFillVariable().setValue(null);
}
for (int i = 0; i < columnGroups.length; i++)
{
columnGroups[i].getFillVariable().setValue(null);
}
for (int i = 0; i < measures.length; i++)
{
measures[i].getFillVariable().setValue(null);
}
for (int row = 0; row <= rowGroups.length; ++row)
{
for (int col = 0; col <= columnGroups.length; ++col)
{
if (retrieveTotal[row][col])
{
for (int i = 0; i < measures.length; i++)
{
totalVariables[row][col][i].setValue(null);
}
}
}
}
}
}
@Override
public int getColumnBreakOffset()
{
return parentCrosstab.getColumnBreakOffset();
}
@Override
public boolean isRepeatColumnHeaders()
{
return parentCrosstab.isRepeatColumnHeaders();
}
@Override
public boolean isRepeatRowHeaders()
{
return parentCrosstab.isRepeatRowHeaders();
}
@Override
public JRCrosstabCell[][] getCells()
{
return crossCells;
}
@Override
public JRCellContents getWhenNoDataCell()
{
return whenNoDataCell;
}
@Override
public JRCrosstabParameter[] getParameters()
{
return parameters;
}
@Override
public JRExpression getParametersMapExpression()
{
return parentCrosstab.getParametersMapExpression();
}
@Override
public JRElement getElementByKey(String elementKey)
{
return JRBaseCrosstab.getElementByKey(this, elementKey);
}
@Override
public JRFillCloneable createClone(JRFillCloneFactory factory)
{
//not needed
return null;
}
@Override
public CrosstabColumnCell getTitleCell()
{
return parentCrosstab.getTitleCell();
}
@Override
public JRCellContents getHeaderCell()
{
return headerCell;
}
@Override
public JRVariable[] getVariables()
{
return variables;
}
@Override
public RunDirectionEnum getRunDirectionValue()
{
return parentCrosstab.getRunDirectionValue();
}
@Override
public void setRunDirection(RunDirectionEnum runDirection)
{
throw new UnsupportedOperationException();
}
@Override
public HorizontalPosition getHorizontalPosition()
{
return parentCrosstab.getHorizontalPosition();
}
@Override
public void setHorizontalPosition(HorizontalPosition horizontalPosition)
{
throw new UnsupportedOperationException();
}
@Override
public JROrigin getOrigin()
{
return getElementOrigin();
}
@Override
public Boolean getIgnoreWidth()
{
return parentCrosstab.getIgnoreWidth();
}
@Override
public void setIgnoreWidth(Boolean ignoreWidth)
{
throw new UnsupportedOperationException();
}
@Override
public Color getDefaultLineColor()
{
return parentCrosstab.getDefaultLineColor();
}
@Override
public JRLineBox getLineBox()
{
return lineBox;
}
@Override
public JasperReportsContext getJasperReportsContext()
{
return filler.getJasperReportsContext();
}
@Override
public JRFillExpressionEvaluator getExpressionEvaluator()
{
return expressionEvaluator;
}
}