0001: /*
0002: * ============================================================================
0003: * GNU Lesser General Public License
0004: * ============================================================================
0005: *
0006: * JasperReports - Free Java report-generating library.
0007: * Copyright (C) 2001-2006 JasperSoft Corporation http://www.jaspersoft.com
0008: *
0009: * This library is free software; you can redistribute it and/or
0010: * modify it under the terms of the GNU Lesser General Public
0011: * License as published by the Free Software Foundation; either
0012: * version 2.1 of the License, or (at your option) any later version.
0013: *
0014: * This library is distributed in the hope that it will be useful,
0015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0017: * Lesser General Public License for more details.
0018: *
0019: * You should have received a copy of the GNU Lesser General Public
0020: * License along with this library; if not, write to the Free Software
0021: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
0022: *
0023: * JasperSoft Corporation
0024: * 303 Second Street, Suite 450 North
0025: * San Francisco, CA 94107
0026: * http://www.jaspersoft.com
0027: */
0028: package net.sf.jasperreports.crosstabs.fill.calculation;
0029:
0030: import java.util.ArrayList;
0031: import java.util.Collections;
0032: import java.util.Iterator;
0033: import java.util.LinkedList;
0034: import java.util.List;
0035: import java.util.ListIterator;
0036: import java.util.Map;
0037: import java.util.TreeMap;
0038: import java.util.Map.Entry;
0039:
0040: import net.sf.jasperreports.crosstabs.fill.calculation.BucketDefinition.Bucket;
0041: import net.sf.jasperreports.crosstabs.fill.calculation.MeasureDefinition.MeasureValue;
0042: import net.sf.jasperreports.engine.JRException;
0043: import net.sf.jasperreports.engine.JRRuntimeException;
0044: import net.sf.jasperreports.engine.JRVariable;
0045: import net.sf.jasperreports.engine.fill.JRCalculable;
0046: import net.sf.jasperreports.engine.util.JRProperties;
0047:
0048: /**
0049: * Crosstab bucketing engine.
0050: *
0051: * @author Lucian Chirita (lucianc@users.sourceforge.net)
0052: * @version $Id: BucketingService.java 1730 2007-05-29 16:10:55Z lucianc $
0053: */
0054: public class BucketingService {
0055:
0056: public static final String PROPERTY_BUCKET_MEASURE_LIMIT = JRProperties.PROPERTY_PREFIX
0057: + "crosstab.bucket.measure.limit";
0058:
0059: protected static final byte DIMENSION_ROW = 0;
0060:
0061: protected static final byte DIMENSION_COLUMN = 1;
0062:
0063: protected static final int DIMENSIONS = 2;
0064:
0065: protected final BucketDefinition[] allBuckets;
0066: protected final BucketDefinition[][] buckets;
0067:
0068: protected final int rowBucketCount;
0069: protected final int colBucketCount;
0070:
0071: protected final boolean[][] retrieveTotal;
0072: private boolean[] rowRetrTotals;
0073: private int rowRetrTotalMin;
0074: private int rowRetrTotalMax;
0075: private int[] rowRetrColMax;
0076:
0077: protected final MeasureDefinition[] measures;
0078: protected final int origMeasureCount;
0079: protected final int[] measureIndexes;
0080:
0081: protected final boolean sorted;
0082:
0083: protected final BucketMap bucketValueMap;
0084: protected long dataCount;
0085: protected boolean processed;
0086:
0087: protected HeaderCell[][] colHeaders;
0088: protected HeaderCell[][] rowHeaders;
0089: protected CrosstabCell[][] cells;
0090:
0091: private final MeasureValue[] zeroUserMeasureValues;
0092:
0093: private final int bucketMeasureLimit;
0094: private int runningBucketMeasureCount = 0;
0095:
0096: /**
0097: * Creates a crosstab bucketing engine.
0098: *
0099: * @param rowBuckets the row bucket definitions
0100: * @param columnBuckets the column bucket definitions
0101: * @param measures the measure definitions
0102: * @param sorted whether the data is presorted
0103: * @param retrieveTotal totals to retrieve along with the cell values
0104: */
0105: public BucketingService(List rowBuckets, List columnBuckets,
0106: List measures, boolean sorted, boolean[][] retrieveTotal) {
0107: this .sorted = sorted;
0108:
0109: buckets = new BucketDefinition[DIMENSIONS][];
0110:
0111: rowBucketCount = rowBuckets.size();
0112: buckets[DIMENSION_ROW] = new BucketDefinition[rowBucketCount];
0113: rowBuckets.toArray(buckets[DIMENSION_ROW]);
0114:
0115: colBucketCount = columnBuckets.size();
0116: buckets[DIMENSION_COLUMN] = new BucketDefinition[colBucketCount];
0117: columnBuckets.toArray(buckets[DIMENSION_COLUMN]);
0118:
0119: allBuckets = new BucketDefinition[rowBucketCount
0120: + colBucketCount];
0121: System.arraycopy(buckets[DIMENSION_ROW], 0, allBuckets, 0,
0122: rowBucketCount);
0123: System.arraycopy(buckets[DIMENSION_COLUMN], 0, allBuckets,
0124: rowBucketCount, colBucketCount);
0125:
0126: origMeasureCount = measures.size();
0127: List measuresList = new ArrayList(measures.size() * 2);
0128: List measureIndexList = new ArrayList(measures.size() * 2);
0129: for (int i = 0; i < measures.size(); ++i) {
0130: MeasureDefinition measure = (MeasureDefinition) measures
0131: .get(i);
0132: addMeasure(measure, i, measuresList, measureIndexList);
0133: }
0134: this .measures = new MeasureDefinition[measuresList.size()];
0135: measuresList.toArray(this .measures);
0136: this .measureIndexes = new int[measureIndexList.size()];
0137: for (int i = 0; i < measureIndexes.length; ++i) {
0138: measureIndexes[i] = ((Integer) measureIndexList.get(i))
0139: .intValue();
0140: }
0141:
0142: this .retrieveTotal = retrieveTotal;
0143: checkTotals();
0144:
0145: bucketValueMap = createBucketMap(0);
0146:
0147: zeroUserMeasureValues = initUserMeasureValues();
0148:
0149: bucketMeasureLimit = JRProperties.getIntegerProperty(
0150: PROPERTY_BUCKET_MEASURE_LIMIT, 0);
0151: }
0152:
0153: protected void checkTotals() {
0154: rowRetrTotalMin = rowBucketCount + 1;
0155: rowRetrTotalMax = -1;
0156: rowRetrTotals = new boolean[rowBucketCount + 1];
0157: rowRetrColMax = new int[rowBucketCount + 1];
0158: for (int row = 0; row <= rowBucketCount; ++row) {
0159: rowRetrColMax[row] = -1;
0160: boolean total = false;
0161: for (int col = 0; col <= colBucketCount; ++col) {
0162: if (retrieveTotal[row][col]) {
0163: total = true;
0164: rowRetrColMax[row] = col;
0165: }
0166: }
0167:
0168: rowRetrTotals[row] = total;
0169: if (total) {
0170: if (row < rowRetrTotalMin) {
0171: rowRetrTotalMin = row;
0172: }
0173: rowRetrTotalMax = row;
0174:
0175: if (row < rowBucketCount) {
0176: allBuckets[row].setComputeTotal();
0177: }
0178: }
0179: }
0180:
0181: for (int col = 0; col < colBucketCount; ++col) {
0182: BucketDefinition colBucket = allBuckets[rowBucketCount
0183: + col];
0184: if (!colBucket.computeTotal()) {
0185: boolean total = false;
0186: for (int row = 0; !total && row <= rowBucketCount; ++row) {
0187: total = retrieveTotal[row][col];
0188: }
0189:
0190: if (total) {
0191: colBucket.setComputeTotal();
0192: }
0193: }
0194: }
0195:
0196: for (int d = 0; d < DIMENSIONS; ++d) {
0197: boolean dTotal = false;
0198:
0199: for (int i = 0; i < buckets[d].length; ++i) {
0200: if (dTotal) {
0201: buckets[d][i].setComputeTotal();
0202: } else {
0203: dTotal = buckets[d][i].computeTotal();
0204: }
0205: }
0206: }
0207: }
0208:
0209: /**
0210: * Clears all the accumulated and computed data.
0211: */
0212: public void clear() {
0213: bucketValueMap.clear();
0214: processed = false;
0215: dataCount = 0;
0216: runningBucketMeasureCount = 0;
0217: }
0218:
0219: protected BucketMap createBucketMap(int level) {
0220: BucketMap map;
0221: if (sorted) {
0222: map = new BucketListMap(level, false);
0223: } else {
0224: map = new BucketTreeMap(level);
0225: }
0226: return map;
0227: }
0228:
0229: protected BucketListMap createCollectBucketMap(int level) {
0230: return new BucketListMap(level, true);
0231: }
0232:
0233: protected void addMeasure(MeasureDefinition measure, int index,
0234: List measuresList, List measureIndexList) {
0235: switch (measure.getCalculation()) {
0236: case JRVariable.CALCULATION_AVERAGE:
0237: case JRVariable.CALCULATION_VARIANCE: {
0238: MeasureDefinition sumMeasure = MeasureDefinition
0239: .createHelperMeasure(measure,
0240: JRVariable.CALCULATION_SUM);
0241: addMeasure(sumMeasure, index, measuresList,
0242: measureIndexList);
0243: MeasureDefinition countMeasure = MeasureDefinition
0244: .createHelperMeasure(measure,
0245: JRVariable.CALCULATION_COUNT);
0246: addMeasure(countMeasure, index, measuresList,
0247: measureIndexList);
0248: break;
0249: }
0250: case JRVariable.CALCULATION_STANDARD_DEVIATION: {
0251: MeasureDefinition varianceMeasure = MeasureDefinition
0252: .createHelperMeasure(measure,
0253: JRVariable.CALCULATION_VARIANCE);
0254: addMeasure(varianceMeasure, index, measuresList,
0255: measureIndexList);
0256: break;
0257: }
0258: case JRVariable.CALCULATION_DISTINCT_COUNT: {
0259: MeasureDefinition countMeasure = MeasureDefinition
0260: .createDistinctCountHelperMeasure(measure);
0261: addMeasure(countMeasure, index, measuresList,
0262: measureIndexList);
0263: break;
0264: }
0265: }
0266:
0267: measuresList.add(measure);
0268: measureIndexList.add(new Integer(index));
0269: }
0270:
0271: /**
0272: * Feeds data to the engine.
0273: *
0274: * @param bucketValues the bucket values
0275: * @param measureValues the measure values
0276: * @throws JRException
0277: */
0278: public void addData(Object[] bucketValues, Object[] measureValues)
0279: throws JRException {
0280: if (processed) {
0281: throw new JRException(
0282: "Crosstab data has already been processed.");
0283: }
0284:
0285: ++dataCount;
0286:
0287: Bucket[] bucketVals = getBucketValues(bucketValues);
0288:
0289: MeasureValue[] values = bucketValueMap
0290: .insertMeasureValues(bucketVals);
0291:
0292: for (int i = 0; i < measures.length; ++i) {
0293: values[i].addValue(measureValues[measureIndexes[i]]);
0294: }
0295: }
0296:
0297: protected void bucketMeasuresCreated() {
0298: runningBucketMeasureCount += origMeasureCount;
0299:
0300: checkBucketMeasureCount(runningBucketMeasureCount);
0301: }
0302:
0303: protected Bucket[] getBucketValues(Object[] bucketValues) {
0304: Bucket[] bucketVals = new Bucket[allBuckets.length];
0305:
0306: for (int i = 0; i < allBuckets.length; ++i) {
0307: BucketDefinition bucket = allBuckets[i];
0308: Object value = bucketValues[i];
0309: bucketVals[i] = bucket.create(value);
0310: }
0311:
0312: return bucketVals;
0313: }
0314:
0315: protected MeasureValue[] initMeasureValues() {
0316: MeasureValue[] values;
0317: values = new MeasureValue[measures.length];
0318:
0319: for (int i = 0; i < measures.length; ++i) {
0320: MeasureDefinition measure = measures[i];
0321: values[i] = measure.new MeasureValue();
0322:
0323: switch (measure.getCalculation()) {
0324: case JRVariable.CALCULATION_AVERAGE:
0325: case JRVariable.CALCULATION_VARIANCE: {
0326: values[i].setHelper(values[i - 2],
0327: JRCalculable.HELPER_SUM);
0328: values[i].setHelper(values[i - 1],
0329: JRCalculable.HELPER_COUNT);
0330: break;
0331: }
0332: case JRVariable.CALCULATION_STANDARD_DEVIATION: {
0333: values[i].setHelper(values[i - 1],
0334: JRCalculable.HELPER_VARIANCE);
0335: }
0336: case JRVariable.CALCULATION_DISTINCT_COUNT: {
0337: values[i].setHelper(values[i - 1],
0338: JRCalculable.HELPER_COUNT);
0339: }
0340: }
0341: }
0342: return values;
0343: }
0344:
0345: protected MeasureValue[] initUserMeasureValues() {
0346: MeasureValue[] vals = new MeasureValue[origMeasureCount];
0347:
0348: for (int c = 0, i = 0; i < measures.length; ++i) {
0349: if (!measures[i].isSystemDefined()) {
0350: vals[c] = measures[i].new MeasureValue();
0351: ++c;
0352: }
0353: }
0354:
0355: return vals;
0356: }
0357:
0358: /**
0359: * Processes the data which was fed to the engine.
0360: * <p>
0361: * This method should be called after the data has been exhausted.
0362: * The processing consists of total calculations and crosstab table creation.
0363: *
0364: * @throws JRException
0365: */
0366: public void processData() throws JRException {
0367: if (!processed) {
0368: if (dataCount > 0) {
0369: if (allBuckets[rowBucketCount - 1].computeTotal()
0370: || allBuckets[allBuckets.length - 1]
0371: .computeTotal()) {
0372: computeTotals(bucketValueMap);
0373: }
0374:
0375: createCrosstab();
0376: }
0377:
0378: processed = true;
0379: }
0380: }
0381:
0382: /**
0383: * Checks whether there is any data accumulated by the engine.
0384: *
0385: * @return <code>true</code> iff the engine has any accumulated data
0386: */
0387: public boolean hasData() {
0388: return dataCount > 0;
0389: }
0390:
0391: /**
0392: * Returns the crosstab column headers.
0393: * <p>
0394: * {@link #processData() processData()} has to be called before this.
0395: *
0396: * @return the crosstab column headers
0397: */
0398: public HeaderCell[][] getColumnHeaders() {
0399: return colHeaders;
0400: }
0401:
0402: /**
0403: * Returns the crosstab row headers.
0404: * <p>
0405: * {@link #processData() processData()} has to be called before this.
0406: *
0407: * @return the crosstab row headers
0408: */
0409: public HeaderCell[][] getRowHeaders() {
0410: return rowHeaders;
0411: }
0412:
0413: /**
0414: * Returns the crosstab data cells.
0415: * <p>
0416: * {@link #processData() processData()} has to be called before this.
0417: *
0418: * @return the crosstab data cells
0419: */
0420: public CrosstabCell[][] getCrosstabCells() {
0421: return cells;
0422: }
0423:
0424: /**
0425: * Returns the measure values for a set of bucket values.
0426: *
0427: * @param bucketValues the bucket values
0428: * @return the measure values corresponding to the bucket values
0429: */
0430: public MeasureValue[] getMeasureValues(Bucket[] bucketValues) {
0431: BucketMap map = bucketValueMap;
0432:
0433: for (int i = 0; map != null && i < allBuckets.length - 1; ++i) {
0434: map = (BucketMap) map.get(bucketValues[i]);
0435: }
0436:
0437: return map == null ? null : (MeasureValue[]) map
0438: .get(bucketValues[allBuckets.length - 1]);
0439: }
0440:
0441: protected MeasureValue[] getUserMeasureValues(MeasureValue[] values) {
0442: MeasureValue[] vals = new MeasureValue[origMeasureCount];
0443:
0444: for (int c = 0, i = 0; i < measures.length; ++i) {
0445: if (!measures[i].isSystemDefined()) {
0446: vals[c] = values[i];
0447: ++c;
0448: }
0449: }
0450:
0451: return vals;
0452: }
0453:
0454: /**
0455: * Returns the grand total measure values.
0456: *
0457: * @return the grand total measure values
0458: */
0459: public MeasureValue[] getGrandTotals() {
0460: BucketMap map = bucketValueMap;
0461:
0462: for (int i = 0; map != null && i < allBuckets.length - 1; ++i) {
0463: map = (BucketMap) map.getTotalEntry().getValue();
0464: }
0465:
0466: return map == null ? null : (MeasureValue[]) map
0467: .getTotalEntry().getValue();
0468: }
0469:
0470: protected void computeTotals(BucketMap bucketMap)
0471: throws JRException {
0472: byte dimension = bucketMap.level < rowBucketCount ? DIMENSION_ROW
0473: : DIMENSION_COLUMN;
0474:
0475: if (dimension == DIMENSION_COLUMN
0476: && !allBuckets[allBuckets.length - 1].computeTotal()) {
0477: return;
0478: }
0479:
0480: if (!bucketMap.last) {
0481: for (Iterator it = bucketMap.entryIterator(); it.hasNext();) {
0482: Map.Entry entry = (Map.Entry) it.next();
0483:
0484: computeTotals((BucketMap) entry.getValue());
0485: }
0486: }
0487:
0488: if (allBuckets[bucketMap.level].computeTotal()) {
0489: if (dimension == DIMENSION_COLUMN) {
0490: computeColumnTotal(bucketMap);
0491: } else {
0492: computeRowTotals(bucketMap);
0493: }
0494: }
0495: }
0496:
0497: protected void sumVals(MeasureValue[] totals, MeasureValue[] vals)
0498: throws JRException {
0499: for (int i = 0; i < measures.length; i++) {
0500: totals[i].addValue(vals[i]);
0501: }
0502: }
0503:
0504: protected void computeColumnTotal(BucketMap bucketMap)
0505: throws JRException {
0506: MeasureValue[] totals = initMeasureValues();
0507:
0508: for (Iterator it = bucketMap.entryIterator(); it.hasNext();) {
0509: Map.Entry entry = (Map.Entry) it.next();
0510:
0511: for (int i = bucketMap.level + 1; i < allBuckets.length; ++i) {
0512: entry = ((BucketMap) entry.getValue()).getTotalEntry();
0513: }
0514:
0515: sumVals(totals, (MeasureValue[]) entry.getValue());
0516: }
0517:
0518: for (int i = bucketMap.level + 1; i < allBuckets.length; ++i) {
0519: bucketMap = bucketMap.addTotalNextMap();
0520: }
0521:
0522: bucketMap.addTotalEntry(totals);
0523: }
0524:
0525: protected void computeRowTotals(BucketMap bucketMap)
0526: throws JRException {
0527: BucketListMap totals = createCollectBucketMap(rowBucketCount);
0528:
0529: for (Iterator it = bucketMap.entryIterator(); it.hasNext();) {
0530: Map.Entry entry = (Map.Entry) it.next();
0531:
0532: for (int i = bucketMap.level + 1; i < rowBucketCount; ++i) {
0533: entry = ((BucketMap) entry.getValue()).getTotalEntry();
0534: }
0535:
0536: totals.collectVals((BucketMap) entry.getValue(), true);
0537: }
0538:
0539: BucketMap totalBucketMap = bucketMap;
0540: for (int i = bucketMap.level + 1; i < rowBucketCount; ++i) {
0541: totalBucketMap = totalBucketMap.addTotalNextMap();
0542: }
0543:
0544: totalBucketMap.addTotalEntry(totals);
0545: }
0546:
0547: static protected class MapEntry implements Map.Entry, Comparable {
0548: final Bucket key;
0549:
0550: final Object value;
0551:
0552: MapEntry(Bucket key, Object value) {
0553: this .key = key;
0554: this .value = value;
0555: }
0556:
0557: public Object getKey() {
0558: return key;
0559: }
0560:
0561: public Object getValue() {
0562: return value;
0563: }
0564:
0565: public Object setValue(Object value) {
0566: throw new UnsupportedOperationException();
0567: }
0568:
0569: public int compareTo(Object o) {
0570: return key.compareTo(((MapEntry) o).key);
0571: }
0572:
0573: public String toString() {
0574: return key + ":" + value;
0575: }
0576: }
0577:
0578: protected abstract class BucketMap {
0579: final int level;
0580: final boolean last;
0581: final Bucket totalKey;
0582:
0583: BucketMap(int level) {
0584: this .level = level;
0585: this .last = level == allBuckets.length - 1;
0586: totalKey = allBuckets[level].VALUE_TOTAL;
0587: }
0588:
0589: BucketMap addTotalNextMap() {
0590: BucketMap nextMap = createBucketMap(level + 1);
0591: addTotalEntry(nextMap);
0592: return nextMap;
0593: }
0594:
0595: abstract void set(Bucket key, Object value);
0596:
0597: abstract void clear();
0598:
0599: abstract Iterator entryIterator();
0600:
0601: abstract Object get(Bucket key);
0602:
0603: abstract MeasureValue[] insertMeasureValues(
0604: Bucket[] bucketValues);
0605:
0606: /* abstract void fillKeys(Collection collectedKeys);*/
0607:
0608: abstract void addTotalEntry(Object val);
0609:
0610: abstract int size();
0611:
0612: abstract Map.Entry getTotalEntry();
0613: }
0614:
0615: protected class BucketTreeMap extends BucketMap {
0616: TreeMap map;
0617:
0618: BucketTreeMap(int level) {
0619: super (level);
0620:
0621: map = new TreeMap();
0622: }
0623:
0624: void clear() {
0625: map.clear();
0626: }
0627:
0628: Iterator entryIterator() {
0629: return map.entrySet().iterator();
0630: }
0631:
0632: Object get(Bucket key) {
0633: return map.get(key);
0634: }
0635:
0636: MeasureValue[] insertMeasureValues(Bucket[] bucketValues) {
0637: BucketTreeMap levelMap = (BucketTreeMap) bucketValueMap;
0638: for (int i = 0; i < bucketValues.length - 1; i++) {
0639: BucketTreeMap nextMap = (BucketTreeMap) levelMap
0640: .get(bucketValues[i]);
0641: if (nextMap == null) {
0642: nextMap = new BucketTreeMap(i + 1);
0643: levelMap.map.put(bucketValues[i], nextMap);
0644: }
0645:
0646: levelMap = nextMap;
0647: }
0648:
0649: MeasureValue[] values = (MeasureValue[]) levelMap
0650: .get(bucketValues[bucketValues.length - 1]);
0651: if (values == null) {
0652: values = initMeasureValues();
0653: levelMap.map.put(bucketValues[bucketValues.length - 1],
0654: values);
0655:
0656: bucketMeasuresCreated();
0657: }
0658:
0659: return values;
0660: }
0661:
0662: int size() {
0663: return map.size();
0664: }
0665:
0666: void addTotalEntry(Object value) {
0667: map.put(totalKey, value);
0668: }
0669:
0670: Map.Entry getTotalEntry() {
0671: Object value = get(totalKey);
0672: return value == null ? null : new MapEntry(totalKey, value);
0673: }
0674:
0675: public String toString() {
0676: return map.toString();
0677: }
0678:
0679: void set(Bucket key, Object value) {
0680: map.put(key, value);
0681: }
0682: }
0683:
0684: protected class BucketListMap extends BucketMap {
0685: List entries;
0686:
0687: BucketListMap(int level, boolean linked) {
0688: super (level);
0689:
0690: if (linked) {
0691: entries = new LinkedList();
0692: } else {
0693: entries = new ArrayList();
0694: }
0695: }
0696:
0697: void clear() {
0698: entries.clear();
0699: }
0700:
0701: Iterator entryIterator() {
0702: return entries.iterator();
0703: }
0704:
0705: private void add(Bucket key, Object value) {
0706: entries.add(new MapEntry(key, value));
0707: }
0708:
0709: Object get(Bucket key) {
0710: int idx = Collections.binarySearch(entries, new MapEntry(
0711: key, null));
0712: return idx >= 0 ? ((MapEntry) entries.get(idx)).value
0713: : null;
0714: }
0715:
0716: MeasureValue[] insertMeasureValues(Bucket[] bucketValues) {
0717: int i = 0;
0718: Object levelObj = this ;
0719: BucketListMap map = null;
0720: while (i < allBuckets.length) {
0721: map = (BucketListMap) levelObj;
0722: int size = map.entries.size();
0723: if (size == 0) {
0724: break;
0725: }
0726:
0727: MapEntry lastEntry = (MapEntry) map.entries
0728: .get(size - 1);
0729: if (!lastEntry.key.equals(bucketValues[i])) {
0730: break;
0731: }
0732:
0733: ++i;
0734: levelObj = lastEntry.value;
0735: }
0736:
0737: if (i == allBuckets.length) {
0738: return (MeasureValue[]) levelObj;
0739: }
0740:
0741: while (i < allBuckets.length - 1) {
0742: BucketListMap nextMap = new BucketListMap(i + 1, false);
0743: map.add(bucketValues[i], nextMap);
0744: map = nextMap;
0745: ++i;
0746: }
0747:
0748: MeasureValue[] values = initMeasureValues();
0749: map.add(bucketValues[i], values);
0750:
0751: bucketMeasuresCreated();
0752:
0753: return values;
0754: }
0755:
0756: int size() {
0757: return entries.size();
0758: }
0759:
0760: void addTotalEntry(Object value) {
0761: add(totalKey, value);
0762: }
0763:
0764: Map.Entry getTotalEntry() {
0765: MapEntry lastEntry = (MapEntry) entries
0766: .get(entries.size() - 1);
0767: if (lastEntry.key.isTotal()) {
0768: return lastEntry;
0769: }
0770:
0771: return null;
0772: }
0773:
0774: void set(Bucket key, Object value) {
0775: MapEntry mapEntry = new MapEntry(key, value);
0776: int idx = Collections.binarySearch(entries, mapEntry);
0777: int ins = -idx - 1;
0778: entries.add(ins, mapEntry);
0779: }
0780:
0781: void collectVals(BucketMap map, boolean sum) throws JRException {
0782: ListIterator totalIt = entries.listIterator();
0783: MapEntry totalItEntry = totalIt.hasNext() ? (MapEntry) totalIt
0784: .next()
0785: : null;
0786:
0787: Iterator it = map.entryIterator();
0788: Map.Entry entry = it.hasNext() ? (Map.Entry) it.next()
0789: : null;
0790: while (entry != null) {
0791: Bucket key = (Bucket) entry.getKey();
0792:
0793: int compare = totalItEntry == null ? -1 : key
0794: .compareTo(totalItEntry.key);
0795: if (compare <= 0) {
0796: Object addVal = null;
0797:
0798: if (last) {
0799: if (sum) {
0800: MeasureValue[] totalVals = compare == 0 ? (MeasureValue[]) totalItEntry.value
0801: : null;
0802:
0803: if (totalVals == null) {
0804: totalVals = initMeasureValues();
0805: addVal = totalVals;
0806: }
0807:
0808: sumVals(totalVals, (MeasureValue[]) entry
0809: .getValue());
0810: }
0811: } else {
0812: BucketListMap nextTotals = compare == 0 ? (BucketListMap) totalItEntry.value
0813: : null;
0814:
0815: if (nextTotals == null) {
0816: nextTotals = createCollectBucketMap(level + 1);
0817: addVal = nextTotals;
0818: }
0819:
0820: nextTotals.collectVals((BucketMap) entry
0821: .getValue(), sum);
0822: }
0823:
0824: if (compare < 0) {
0825: if (totalItEntry != null) {
0826: totalIt.previous();
0827: }
0828: totalIt.add(new MapEntry(key, addVal));
0829: if (totalItEntry != null) {
0830: totalIt.next();
0831: }
0832: }
0833:
0834: entry = it.hasNext() ? (Map.Entry) it.next() : null;
0835: }
0836:
0837: if (compare >= 0) {
0838: totalItEntry = totalIt.hasNext() ? (MapEntry) totalIt
0839: .next()
0840: : null;
0841: }
0842: }
0843: }
0844: }
0845:
0846: protected void createCrosstab() throws JRException {
0847: CollectedList[] collectedHeaders = new CollectedList[BucketingService.DIMENSIONS];
0848: collectedHeaders[DIMENSION_ROW] = createHeadersList(
0849: DIMENSION_ROW, bucketValueMap, 0, false);
0850:
0851: BucketListMap collectedCols;
0852: if (allBuckets[0].computeTotal()) {
0853: BucketMap map = bucketValueMap;
0854: for (int i = 0; i < rowBucketCount; ++i) {
0855: map = (BucketMap) map.getTotalEntry().getValue();
0856: }
0857: collectedCols = (BucketListMap) map;
0858: } else {
0859: collectedCols = createCollectBucketMap(rowBucketCount);
0860: collectCols(collectedCols, bucketValueMap);
0861: }
0862: collectedHeaders[DIMENSION_COLUMN] = createHeadersList(
0863: DIMENSION_COLUMN, collectedCols, 0, false);
0864:
0865: int rowBuckets = collectedHeaders[BucketingService.DIMENSION_ROW].span;
0866: int colBuckets = collectedHeaders[BucketingService.DIMENSION_COLUMN].span;
0867:
0868: int bucketMeasureCount = rowBuckets * colBuckets
0869: * origMeasureCount;
0870: checkBucketMeasureCount(bucketMeasureCount);
0871:
0872: colHeaders = createHeaders(BucketingService.DIMENSION_COLUMN,
0873: collectedHeaders);
0874: rowHeaders = createHeaders(BucketingService.DIMENSION_ROW,
0875: collectedHeaders);
0876:
0877: cells = new CrosstabCell[rowBuckets][colBuckets];
0878: fillCells(collectedHeaders, bucketValueMap, 0,
0879: new int[] { 0, 0 }, new ArrayList(), new ArrayList());
0880: }
0881:
0882: protected void checkBucketMeasureCount(int bucketMeasureCount) {
0883: if (bucketMeasureLimit > 0
0884: && bucketMeasureCount > bucketMeasureLimit) {
0885: throw new JRRuntimeException(
0886: "Crosstab bucket/measure limit ("
0887: + bucketMeasureLimit + ") exceeded.");
0888: }
0889: }
0890:
0891: protected void collectCols(BucketListMap collectedCols,
0892: BucketMap bucketMap) throws JRException {
0893: if (allBuckets[bucketMap.level].computeTotal()) {
0894: BucketMap map = bucketMap;
0895: for (int i = bucketMap.level; i < rowBucketCount; ++i) {
0896: map = (BucketMap) map.getTotalEntry().getValue();
0897: }
0898: collectedCols.collectVals(map, false);
0899:
0900: return;
0901: }
0902:
0903: for (Iterator it = bucketMap.entryIterator(); it.hasNext();) {
0904: Map.Entry entry = (Map.Entry) it.next();
0905: BucketMap nextMap = (BucketMap) entry.getValue();
0906: if (bucketMap.level == rowBucketCount - 1) {
0907: collectedCols.collectVals(nextMap, false);
0908: } else {
0909: collectCols(collectedCols, nextMap);
0910: }
0911: }
0912: }
0913:
0914: protected CollectedList createHeadersList(byte dimension,
0915: BucketMap bucketMap, int level, boolean total) {
0916: CollectedList headers = new CollectedList();
0917:
0918: for (Iterator it = bucketMap.entryIterator(); it.hasNext();) {
0919: Map.Entry entry = (Map.Entry) it.next();
0920: Bucket bucketValue = (Bucket) entry.getKey();
0921:
0922: boolean totalBucket = bucketValue.isTotal();
0923: byte totalPosition = allBuckets[bucketMap.level]
0924: .getTotalPosition();
0925: boolean createHeader = !totalBucket
0926: || total
0927: || totalPosition != BucketDefinition.TOTAL_POSITION_NONE;
0928:
0929: if (createHeader) {
0930: CollectedList nextHeaders;
0931: if (level + 1 < buckets[dimension].length) {
0932: BucketMap nextMap = (BucketMap) entry.getValue();
0933: nextHeaders = createHeadersList(dimension, nextMap,
0934: level + 1, total || totalBucket);
0935: } else {
0936: nextHeaders = new CollectedList();
0937: nextHeaders.span = 1;
0938: }
0939: nextHeaders.key = bucketValue;
0940:
0941: if (totalBucket) {
0942: if (totalPosition == BucketDefinition.TOTAL_POSITION_START) {
0943: headers.addFirst(nextHeaders);
0944: } else {
0945: headers.add(nextHeaders);
0946: }
0947: } else {
0948: headers.add(nextHeaders);
0949: }
0950: }
0951: }
0952:
0953: if (headers.span == 0) {
0954: headers.span = 1;
0955: }
0956:
0957: return headers;
0958: }
0959:
0960: protected HeaderCell[][] createHeaders(byte dimension,
0961: CollectedList[] headersLists) {
0962: HeaderCell[][] headers = new HeaderCell[buckets[dimension].length][headersLists[dimension].span];
0963:
0964: List vals = new ArrayList();
0965: fillHeaders(dimension, headers, 0, 0, headersLists[dimension],
0966: vals);
0967:
0968: return headers;
0969: }
0970:
0971: protected void fillHeaders(byte dimension, HeaderCell[][] headers,
0972: int level, int col, CollectedList list, List vals) {
0973: if (level == buckets[dimension].length) {
0974: return;
0975: }
0976:
0977: for (Iterator it = list.iterator(); it.hasNext();) {
0978: CollectedList subList = (CollectedList) it.next();
0979:
0980: vals.add(subList.key);
0981:
0982: int depthSpan = subList.key.isTotal() ? buckets[dimension].length
0983: - level
0984: : 1;
0985: Bucket[] values = new Bucket[buckets[dimension].length];
0986: vals.toArray(values);
0987:
0988: headers[level][col] = new HeaderCell(values, subList.span,
0989: depthSpan);
0990:
0991: if (!subList.key.isTotal()) {
0992: fillHeaders(dimension, headers, level + 1, col,
0993: subList, vals);
0994: }
0995:
0996: col += subList.span;
0997: vals.remove(vals.size() - 1);
0998: }
0999: }
1000:
1001: protected void fillCells(CollectedList[] collectedHeaders,
1002: BucketMap bucketMap, int level, int[] pos, List vals,
1003: List bucketMaps) {
1004: bucketMaps.add(bucketMap);
1005:
1006: byte dimension = level < rowBucketCount ? DIMENSION_ROW
1007: : DIMENSION_COLUMN;
1008: boolean last = level == allBuckets.length - 1;
1009:
1010: CollectedList[] nextCollected = null;
1011: if (!last) {
1012: nextCollected = new CollectedList[DIMENSIONS];
1013: for (int d = 0; d < DIMENSIONS; ++d) {
1014: if (d != dimension) {
1015: nextCollected[d] = collectedHeaders[d];
1016: }
1017: }
1018: }
1019:
1020: boolean incrementRow = level == buckets[BucketingService.DIMENSION_ROW].length - 1;
1021:
1022: CollectedList collectedList = collectedHeaders[dimension];
1023:
1024: Iterator bucketIt = bucketMap == null ? null : bucketMap
1025: .entryIterator();
1026: Map.Entry bucketItEntry = bucketIt != null
1027: && bucketIt.hasNext() ? (Map.Entry) bucketIt.next()
1028: : null;
1029: for (Iterator it = collectedList.iterator(); it.hasNext();) {
1030: CollectedList list = (CollectedList) it.next();
1031:
1032: Map.Entry bucketEntry = null;
1033: if (list.key.isTotal()) {
1034: if (bucketMap != null) {
1035: bucketEntry = bucketMap.getTotalEntry();
1036: }
1037: } else {
1038: if (bucketItEntry != null
1039: && bucketItEntry.getKey().equals(list.key)) {
1040: bucketEntry = bucketItEntry;
1041: bucketItEntry = bucketIt.hasNext() ? (Map.Entry) bucketIt
1042: .next()
1043: : null;
1044: }
1045: }
1046:
1047: vals.add(list.key);
1048: if (last) {
1049: fillCell(pos, vals, bucketMaps, bucketEntry);
1050: } else {
1051: nextCollected[dimension] = list;
1052: BucketMap nextMap = bucketEntry == null ? null
1053: : (BucketMap) bucketEntry.getValue();
1054:
1055: fillCells(nextCollected, nextMap, level + 1, pos, vals,
1056: bucketMaps);
1057: }
1058: vals.remove(vals.size() - 1);
1059:
1060: if (incrementRow) {
1061: ++pos[0];
1062: pos[1] = 0;
1063: }
1064: }
1065:
1066: bucketMaps.remove(bucketMaps.size() - 1);
1067: }
1068:
1069: protected void fillCell(int[] pos, List vals, List bucketMaps,
1070: Map.Entry bucketEntry) {
1071: Iterator valsIt = vals.iterator();
1072: Bucket[] rowValues = new Bucket[buckets[BucketingService.DIMENSION_ROW].length];
1073: for (int i = 0; i < rowValues.length; i++) {
1074: rowValues[i] = (Bucket) valsIt.next();
1075: }
1076:
1077: Bucket[] columnValues = new Bucket[buckets[BucketingService.DIMENSION_COLUMN].length];
1078: for (int i = 0; i < columnValues.length; i++) {
1079: columnValues[i] = (Bucket) valsIt.next();
1080: }
1081:
1082: MeasureValue[] measureVals = bucketEntry == null ? zeroUserMeasureValues
1083: : getUserMeasureValues((MeasureValue[]) bucketEntry
1084: .getValue());
1085: MeasureValue[][][] totals = retrieveTotals(vals, bucketMaps);
1086: cells[pos[0]][pos[1]] = new CrosstabCell(rowValues,
1087: columnValues, measureVals, totals);
1088: ++pos[1];
1089: }
1090:
1091: protected MeasureValue[][][] retrieveTotals(List vals,
1092: List bucketMaps) {
1093: MeasureValue[][][] totals = new MeasureValue[rowBucketCount + 1][colBucketCount + 1][];
1094:
1095: for (int row = rowRetrTotalMax; row >= rowRetrTotalMin; --row) {
1096: if (!rowRetrTotals[row]) {
1097: continue;
1098: }
1099:
1100: BucketMap rowMap = (BucketMap) bucketMaps.get(row);
1101: for (int i = row; rowMap != null && i < rowBucketCount; ++i) {
1102: Entry totalEntry = rowMap.getTotalEntry();
1103: rowMap = totalEntry == null ? null
1104: : (BucketMap) totalEntry.getValue();
1105: }
1106:
1107: for (int col = 0; col <= rowRetrColMax[row]; ++col) {
1108: BucketMap colMap = rowMap;
1109:
1110: if (col < colBucketCount - 1) {
1111: if (row == rowBucketCount) {
1112: rowMap = (BucketMap) bucketMaps
1113: .get(rowBucketCount + col + 1);
1114: } else if (rowMap != null) {
1115: rowMap = (BucketMap) rowMap.get((Bucket) vals
1116: .get(rowBucketCount + col));
1117: }
1118: }
1119:
1120: if (!retrieveTotal[row][col]) {
1121: continue;
1122: }
1123:
1124: for (int i = col + 1; colMap != null
1125: && i < colBucketCount; ++i) {
1126: colMap = (BucketMap) colMap.getTotalEntry()
1127: .getValue();
1128: }
1129:
1130: if (colMap != null) {
1131: if (col == colBucketCount) {
1132: MeasureValue[] measureValues = (MeasureValue[]) colMap
1133: .get((Bucket) vals.get(rowBucketCount
1134: + colBucketCount - 1));
1135: totals[row][col] = getUserMeasureValues(measureValues);
1136: } else {
1137: Map.Entry totalEntry = colMap.getTotalEntry();
1138: if (totalEntry != null) {
1139: MeasureValue[] totalValues = (MeasureValue[]) totalEntry
1140: .getValue();
1141: totals[row][col] = getUserMeasureValues(totalValues);
1142: }
1143: }
1144: }
1145:
1146: if (totals[row][col] == null) {
1147: totals[row][col] = zeroUserMeasureValues;
1148: }
1149: }
1150: }
1151:
1152: return totals;
1153: }
1154:
1155: protected static class CollectedList extends LinkedList {
1156: int span;
1157: Bucket key;
1158:
1159: CollectedList() {
1160: super ();
1161:
1162: span = 0;
1163: }
1164:
1165: public boolean add(Object o) {
1166: boolean added = super .add(o);
1167:
1168: incrementSpan(o);
1169:
1170: return added;
1171: }
1172:
1173: public void addFirst(Object o) {
1174: super .addFirst(o);
1175:
1176: incrementSpan(o);
1177: }
1178:
1179: public void addLast(Object o) {
1180: super .add(o);
1181:
1182: incrementSpan(o);
1183: }
1184:
1185: private void incrementSpan(Object o) {
1186: if (o != null && o instanceof CollectedList) {
1187: span += ((CollectedList) o).span;
1188: } else {
1189: span += 1;
1190: }
1191: }
1192:
1193: public String toString() {
1194: return key + "/" + span + ": " + super.toString();
1195: }
1196: }
1197: }
|