net.sf.jasperreports.crosstabs.fill.calculation.CrosstabBucketingService Maven / Gradle / Ivy
Show all versions of jasperreports Show documentation
/*
* JasperReports - Free Java Reporting Library.
* Copyright (C) 2001 - 2022 TIBCO Software 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.crosstabs.fill.calculation;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import net.sf.jasperreports.crosstabs.fill.BucketOrderer;
import net.sf.jasperreports.crosstabs.fill.calculation.BucketDefinition.Bucket;
import net.sf.jasperreports.crosstabs.fill.calculation.BucketValueOrderDecorator.OrderPosition;
import net.sf.jasperreports.crosstabs.fill.calculation.MeasureDefinition.MeasureValue;
import net.sf.jasperreports.crosstabs.type.CrosstabTotalPositionEnum;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRRuntimeException;
/**
* @author Lucian Chirita ([email protected])
*/
public class CrosstabBucketingService extends BucketingService implements BucketingData
{
private static final Log log = LogFactory.getLog(CrosstabBucketingService.class);
public static final String EXCEPTION_MESSAGE_KEY_DATA_NOT_PROCESSED = "crosstabs.calculation.data.not.processed";
protected HeaderCell[][] colHeaders;
protected HeaderCell[][] rowHeaders;
protected CrosstabCell[][] cells;
public CrosstabBucketingService(BucketingServiceContext serviceContext,
List rowBuckets,
List columnBuckets,
List measures,
boolean sorted, boolean[][] retrieveTotal)
{
super(serviceContext, rowBuckets, columnBuckets, measures,
sorted, retrieveTotal);
}
public void createCrosstab() throws JRException
{
if (!processed)
{
throw
new JRRuntimeException(
EXCEPTION_MESSAGE_KEY_DATA_NOT_PROCESSED,
(Object[])null);
}
if (!hasData())
{
// nothing to do
return;
}
CollectedList[] collectedHeaders = new CollectedList[BucketingService.DIMENSIONS];
collectedHeaders[DIMENSION_ROW] = createHeadersList(DIMENSION_ROW, bucketValueMap, 0, false);
BucketMap columnTotalsMap = null;
if (allBuckets[0].computeTotal())
{
columnTotalsMap = bucketValueMap;
for (int i = 0; i < rowBucketCount; ++i)
{
columnTotalsMap = (BucketMap) columnTotalsMap.getTotalEntry().getValue();
}
}
collectedHeaders[DIMENSION_COLUMN] = createHeadersList(DIMENSION_COLUMN,
// using column totals for sorting if needed
columnTotalsMap == null ? columnBucketMap : columnTotalsMap,
0, false);
int rowBuckets = collectedHeaders[BucketingService.DIMENSION_ROW].span;
int colBuckets = collectedHeaders[BucketingService.DIMENSION_COLUMN].span;
int bucketMeasureCount = rowBuckets * colBuckets * origMeasureCount;
if (log.isDebugEnabled())
{
log.debug("crosstab has " + rowBuckets + " row buckets, "
+ colBuckets + " column buckets and "
+ origMeasureCount + " measures");
log.debug("bucket measure count is " + bucketMeasureCount
+ ", limit " + bucketMeasureLimit);
}
checkBucketMeasureCount(bucketMeasureCount);
colHeaders = createHeaders(BucketingService.DIMENSION_COLUMN, collectedHeaders, columnTotalsMap);
rowHeaders = createHeaders(BucketingService.DIMENSION_ROW, collectedHeaders, bucketValueMap);
cells = new CrosstabCell[rowBuckets][colBuckets];
fillCells(collectedHeaders, bucketValueMap, 0, new int[]{0, 0}, new ArrayList<>(), new ArrayList<>());
}
protected HeaderCell[][] createHeaders(byte dimension, CollectedList[] headersLists, BucketMap totalsMap)
{
HeaderCell[][] headers = new HeaderCell[buckets[dimension].length][headersLists[dimension].span];
List vals = new ArrayList<>();
fillHeaders(dimension, headers, 0, 0, headersLists[dimension], vals, totalsMap);
return headers;
}
protected CollectedList createHeadersList(byte dimension, BucketMap bucketMap, int level, boolean total)
throws JRException
{
BucketDefinition bucketDefinition = allBuckets[bucketMap.level];
CrosstabTotalPositionEnum totalPosition = bucketDefinition.getTotalPosition();
BucketOrderer bucketOrderer = bucketDefinition.getOrderer();
CollectedList headers;
if (bucketOrderer != null)
{
headers = new OrderedCollectedList(bucketDefinition);
bucketOrderer.init(this);
}
else
{
headers = new SequentialCollectedList(totalPosition);
}
for (Iterator> it = bucketMap.entryIterator(); it.hasNext();)
{
Map.Entry entry = it.next();
Bucket bucketValue = entry.getKey();
boolean totalBucket = bucketValue.isTotal();
boolean createHeader = !totalBucket || total || totalPosition != CrosstabTotalPositionEnum.NONE;
if (createHeader)
{
CollectedList nextHeaders;
if (level + 1 < buckets[dimension].length)
{
BucketMap nextMap = (BucketMap) entry.getValue();
nextHeaders = createHeadersList(dimension, nextMap, level + 1, total || totalBucket);
}
else
{
nextHeaders = new SequentialCollectedList(CrosstabTotalPositionEnum.NONE);
nextHeaders.span = 1;
}
nextHeaders.key = bucketValue;
if (bucketOrderer != null)
{
Object orderValue = bucketOrderer.getOrderValue(bucketMap, bucketValue);
nextHeaders.orderValue = orderValue;
}
headers.add(nextHeaders);
}
}
if (headers.span == 0)
{
headers.span = 1;
}
return headers;
}
@Override
public MeasureValue[] getMeasureTotals(BucketMap bucketMap, Bucket bucket)
{
Object bucketValue = bucketMap.get(bucket);
for (int idx = bucketMap.level + 1; idx < rowBucketCount + colBucketCount; ++idx)
{
bucketValue = ((BucketMap) bucketValue).getTotalEntry().getValue();
}
MeasureValue[] totals = (MeasureValue[]) bucketValue;
MeasureValue[] userTotals = getUserMeasureValues(totals);
return userTotals;
}
protected void fillHeaders(byte dimension, HeaderCell[][] headers, int level, int col, CollectedList list,
List vals, BucketMap totalsMap)
{
if (level == buckets[dimension].length)
{
return;
}
for (Iterator it = list.iterator(); it.hasNext();)
{
CollectedList subList = it.next();
vals.add(subList.key);
if (!subList.key.isTotal())
{
fillHeaders(dimension, headers, level + 1, col, subList, vals, totalsMap);
}
int depthSpan = subList.key.isTotal() ? buckets[dimension].length - level : 1;
Bucket[] values = new Bucket[buckets[dimension].length];
vals.toArray(values);
MeasureValue[][] totals = retrieveHeaderTotals(dimension, values, totalsMap);
if (subList.key.isTotal()
|| buckets[dimension][level].isMergeHeaderCells()
|| level >= buckets[dimension].length - 1)
{
// a single merged header
headers[level][col] = new HeaderCell(values, subList.span, depthSpan, totals);
}
else
{
// creating one header for each header on the next level
for (int c = col; c < col + subList.span; ++c)
{
HeaderCell subheader = headers[level + 1][c];
if (subheader != null)
{
headers[level][c] = new HeaderCell(values, subheader.getLevelSpan(), depthSpan, totals);
}
}
}
col += subList.span;
vals.remove(vals.size() - 1);
}
}
protected MeasureValue[][] retrieveHeaderTotals(byte dimension, Bucket[] values, BucketMap totalsMap)
{
// an array to advance on bucket levels with values and totals
int levelCount = buckets[dimension].length;
Object[] levelBuckets = new Object[levelCount + 1];
levelBuckets[0] = totalsMap;
for (int idx = 0; idx < levelCount; ++idx)
{
// save this as it gets modified
Object valueBucket = levelBuckets[idx];
// advance with totals
for (int lIdx = 0; lIdx <= idx; ++lIdx)
{
if (levelBuckets[lIdx] != null)
{
MapEntry entry = ((BucketMap) levelBuckets[lIdx]).getTotalEntry();
levelBuckets[lIdx] = entry == null ? null : entry.getValue();
}
}
// advance with value if it exists, or total otherwise
if (valueBucket != null)
{
if (idx < values.length && values[idx] != null)
{
levelBuckets[idx + 1] = ((BucketMap) valueBucket).get(values[idx]);
}
else
{
// this is the total computed in the previous loop
levelBuckets[idx + 1] = levelBuckets[idx];
}
}
}
if (dimension == DIMENSION_ROW)
{
// we need to advance through column totals
for (int idx = 0; idx < colBucketCount; ++idx)
{
for (int lIdx = 0; lIdx <= levelCount; ++lIdx)
{
if (levelBuckets[lIdx] != null)
{
MapEntry entry = ((BucketMap) levelBuckets[lIdx]).getTotalEntry();
levelBuckets[lIdx] = entry == null ? null : entry.getValue();
}
}
}
}
MeasureValue[][] totals = new MeasureValue[levelCount + 1][];
for (int lIdx = 0; lIdx <= levelCount; ++lIdx)
{
MeasureValue[] measureValues = (MeasureValue[]) levelBuckets[lIdx];
if (measureValues != null)
{
totals[lIdx] = getUserMeasureValues(measureValues);
}
}
return totals;
}
protected void fillCells(CollectedList[] collectedHeaders, BucketMap bucketMap, int level, int[] pos, List vals, List bucketMaps)
{
bucketMaps.add(bucketMap);
byte dimension = level < rowBucketCount ? DIMENSION_ROW : DIMENSION_COLUMN;
boolean last = level == allBuckets.length - 1;
CollectedList[] nextCollected = null;
if (!last)
{
nextCollected = new CollectedList[DIMENSIONS];
for (int d = 0; d < DIMENSIONS; ++d)
{
if (d != dimension)
{
nextCollected[d] = collectedHeaders[d];
}
}
}
boolean incrementRow = level == buckets[BucketingService.DIMENSION_ROW].length - 1;
CollectedList collectedList = collectedHeaders[dimension];
for (Iterator it = collectedList.iterator(); it.hasNext();)
{
CollectedList list = it.next();
Object bucketValue = bucketMap == null ? null : bucketMap.get(list.key);
vals.add(list.key);
if (last)
{
fillCell(pos, vals, bucketMaps, (MeasureValue[]) bucketValue);
}
else
{
nextCollected[dimension] = list;
BucketMap nextMap = bucketValue == null ? null : (BucketMap) bucketValue;
fillCells(nextCollected, nextMap, level + 1, pos, vals, bucketMaps);
}
vals.remove(vals.size() - 1);
if (incrementRow)
{
++pos[0];
pos[1] = 0;
}
}
bucketMaps.remove(bucketMaps.size() - 1);
}
protected void fillCell(int[] pos, List vals, List bucketMaps, MeasureValue[] values)
{
Iterator valsIt = vals.iterator();
Bucket[] rowValues = new Bucket[buckets[BucketingService.DIMENSION_ROW].length];
for (int i = 0; i < rowValues.length; i++)
{
rowValues[i] = valsIt.next();
}
Bucket[] columnValues = new Bucket[buckets[BucketingService.DIMENSION_COLUMN].length];
for (int i = 0; i < columnValues.length; i++)
{
columnValues[i] = valsIt.next();
}
MeasureValue[] measureVals = values == null ? zeroUserMeasureValues : getUserMeasureValues(values);
MeasureValue[][][] totals = retrieveTotals(vals, bucketMaps);
cells[pos[0]][pos[1]] = new CrosstabCell(rowValues, columnValues, measureVals, totals);
++pos[1];
}
protected MeasureValue[][][] retrieveTotals(List vals, List bucketMaps)
{
MeasureValue[][][] totals = new MeasureValue[rowBucketCount + 1][colBucketCount + 1][];
for (int row = rowRetrTotalMax; row >= rowRetrTotalMin; --row)
{
if (!rowRetrTotals[row])
{
continue;
}
BucketMap rowMap = bucketMaps.get(row);
for (int i = row; rowMap != null && i < rowBucketCount; ++i)
{
MapEntry totalEntry = rowMap.getTotalEntry();
rowMap = totalEntry == null ? null : (BucketMap) totalEntry.getValue();
}
for (int col = 0; col <= rowRetrColMax[row]; ++col)
{
BucketMap colMap = rowMap;
if (col < colBucketCount - 1)
{
if (row == rowBucketCount)
{
rowMap = bucketMaps.get(rowBucketCount + col + 1);
}
else if (rowMap != null)
{
rowMap = (BucketMap) rowMap.get(vals.get(rowBucketCount + col));
}
}
if (!retrieveTotal[row][col])
{
continue;
}
for (int i = col + 1; colMap != null && i < colBucketCount; ++i)
{
colMap = (BucketMap) colMap.getTotalEntry().getValue();
}
if (colMap != null)
{
if (col == colBucketCount)
{
MeasureValue[] measureValues = (MeasureValue[]) colMap.get(vals.get(rowBucketCount + colBucketCount - 1));
if (measureValues != null)
{
totals[row][col] = getUserMeasureValues(measureValues);
}
}
else
{
MapEntry totalEntry = colMap.getTotalEntry();
if (totalEntry != null)
{
MeasureValue[] totalValues = (MeasureValue[]) totalEntry.getValue();
totals[row][col] = getUserMeasureValues(totalValues);
}
}
}
if (totals[row][col] == null)
{
totals[row][col] = zeroUserMeasureValues;
}
}
}
return totals;
}
/**
* Returns the crosstab column headers.
*
* {@link #processData() processData()} has to be called before this.
*
* @return the crosstab column headers
*/
public HeaderCell[][] getColumnHeaders()
{
return colHeaders;
}
/**
* Returns the crosstab row headers.
*
* {@link #processData() processData()} has to be called before this.
*
* @return the crosstab row headers
*/
public HeaderCell[][] getRowHeaders()
{
return rowHeaders;
}
/**
* Returns the crosstab data cells.
*
* {@link #processData() processData()} has to be called before this.
*
* @return the crosstab data cells
*/
public CrosstabCell[][] getCrosstabCells()
{
return cells;
}
protected static abstract class CollectedList
{
int span;
Bucket key;
Object orderValue;
CollectedList()
{
span = 0;
}
public abstract Iterator iterator();
public void add(CollectedList sublist)
{
addSublist(sublist);
incrementSpan(sublist);
}
protected abstract void addSublist(CollectedList sublist);
private void incrementSpan(CollectedList sublist)
{
if (sublist != null)
{
span += sublist.span;
}
else
{
span += 1;
}
}
@Override
public String toString()
{
return key + "/" + span + ": " + super.toString();
}
}
protected static class SequentialCollectedList extends CollectedList
{
final CrosstabTotalPositionEnum totalPosition;
final LinkedList list;
SequentialCollectedList(CrosstabTotalPositionEnum totalPosition)
{
this.totalPosition = totalPosition;
list = new LinkedList<>();
}
@Override
public Iterator iterator()
{
return list.iterator();
}
@Override
protected void addSublist(CollectedList sublist)
{
if (sublist.key.isTotal() && totalPosition == CrosstabTotalPositionEnum.START)
{
list.addFirst(sublist);
}
else
{
list.add(sublist);
}
}
}
protected static class OrderedCollectedList extends CollectedList
{
final TreeSet list;
OrderedCollectedList(BucketDefinition bucketDefinition)
{
super();
CollectedListComparator comparator =
new CollectedListComparator(bucketDefinition);
list = new TreeSet<>(comparator);
}
@Override
public Iterator iterator()
{
return list.iterator();
}
@Override
protected void addSublist(CollectedList sublist)
{
list.add(sublist);
}
}
protected static class CollectedListComparator implements Comparator
{
final String EXCEPTION_MESSAGE_KEY_TWO_TOTAL_KEYS_IN_SAME_LIST = "crosstabs.calculation.two.total.keys.in.same.list";
final BucketOrderer bucketOrderer;
final boolean totalFirst;
CollectedListComparator(BucketDefinition bucketDefinition)
{
this.bucketOrderer = bucketDefinition.getOrderer();
this.totalFirst = bucketDefinition.getTotalPosition()
== CrosstabTotalPositionEnum.START;
}
@Override
public int compare(CollectedList l1, CollectedList l2)
{
if (l1 == l2)
{
return 0;
}
int order;
if (l1.key.isTotal())
{
if (l2.key.isTotal())
{
// this should not happen
throw
new JRRuntimeException(
EXCEPTION_MESSAGE_KEY_TWO_TOTAL_KEYS_IN_SAME_LIST,
(Object[])null);
}
order = totalFirst ? -1 : 1;
}
else if (l2.key.isTotal())
{
order = totalFirst ? 1 : -1;
}
else
{
// check order positions
OrderPosition orderPosition1 = l1.key.getOrderPosition();
OrderPosition orderPosition2 = l2.key.getOrderPosition();
if (orderPosition1 != orderPosition2)
{
order = orderPosition1.comparePosition(orderPosition2);
}
else
{
// compare the order values
order = bucketOrderer.compareOrderValues(
l1.orderValue, l2.orderValue);
if (order == 0)
{
// if order values are equal, fallback to bucket value order
order = l1.key.compareTo(l2.key);
}
}
}
return order;
}
}
@Override
public BucketingServiceContext getServiceContext()
{
return serviceContext;
}
@Override
public Bucket getColumnTotalBucket(int columnGroupIndex)
{
if (columnGroupIndex < 0 || columnGroupIndex >= colBucketCount)
{
throw new IndexOutOfBoundsException("Column group index " + columnGroupIndex
+ " out of range, column group count is " + colBucketCount);
}
return allBuckets[rowBucketCount + columnGroupIndex].VALUE_TOTAL;
}
@Override
public Bucket getColumnBucket(int columnGroupIndex, Object value)
{
if (columnGroupIndex < 0 || columnGroupIndex >= colBucketCount)
{
throw new IndexOutOfBoundsException("Column group index " + columnGroupIndex
+ " out of range, column group count is " + colBucketCount);
}
BucketDefinition bucket = allBuckets[rowBucketCount + columnGroupIndex];
return value == null ? bucket.VALUE_NULL : bucket.create(value);
}
@Override
public MeasureValue[] getMeasureValues(BucketMap bucketMap, Bucket bucket,
List columnValues)
{
Object bucketValue = bucketMap.get(bucket);
// advance to row totals
for (int idx = bucketMap.level + 1; idx < rowBucketCount; ++idx)
{
bucketValue = ((BucketMap) bucketValue).getTotalEntry().getValue();
}
// advance to column values
Iterator colValuesIt = columnValues.iterator();
for (int idx = 0; idx < colBucketCount && bucketValue != null; ++idx)
{
if (colValuesIt.hasNext())
{
Bucket columnBucket = colValuesIt.next();
bucketValue = ((BucketMap) bucketValue).get(columnBucket);
}
else
{
// if we don't have a value for the column group, use the total
bucketValue = ((BucketMap) bucketValue).getTotalEntry().getValue();
}
}
MeasureValue[] measureValues;
if (bucketValue == null)
{
// the column values were not present for the row, using zero values
measureValues = zeroUserMeasureValues;
}
else
{
measureValues = getUserMeasureValues((MeasureValue[]) bucketValue);
}
return measureValues;
}
}