protected void createCrosstab() throws JRException {
    CollectedList[] collectedHeaders = new CollectedList[BucketingService.DIMENSIONS];
    collectedHeaders[DIMENSION_ROW] = createHeadersList(DIMENSION_ROW, bucketValueMap, 0, false);

    BucketListMap collectedCols;
    if (allBuckets[0].computeTotal()) {
      BucketMap map = bucketValueMap;
      for (int i = 0; i < rowBucketCount; ++i) {
        map = (BucketMap) map.getTotalEntry().getValue();
      }
      collectedCols = (BucketListMap) map;
    } else {
      collectedCols = createCollectBucketMap(rowBucketCount);
      collectCols(collectedCols, bucketValueMap);
    }
    collectedHeaders[DIMENSION_COLUMN] =
        createHeadersList(DIMENSION_COLUMN, collectedCols, 0, false);

    int rowBuckets = collectedHeaders[BucketingService.DIMENSION_ROW].span;
    int colBuckets = collectedHeaders[BucketingService.DIMENSION_COLUMN].span;

    int bucketMeasureCount = rowBuckets * colBuckets * origMeasureCount;
    checkBucketMeasureCount(bucketMeasureCount);

    colHeaders = createHeaders(BucketingService.DIMENSION_COLUMN, collectedHeaders);
    rowHeaders = createHeaders(BucketingService.DIMENSION_ROW, collectedHeaders);

    cells = new CrosstabCell[rowBuckets][colBuckets];
    fillCells(
        collectedHeaders, bucketValueMap, 0, new int[] {0, 0}, new ArrayList(), new ArrayList());
  }
 /** Clears all the accumulated and computed data. */
 public void clear() {
   bucketValueMap.clear();
   columnBucketMap.clear();
   processed = false;
   dataCount = 0;
   runningBucketMeasureCount = 0;
 }
  /**
   * Returns the grand total measure values.
   *
   * @return the grand total measure values
   */
  public MeasureValue[] getGrandTotals() {
    BucketMap map = bucketValueMap;

    for (int i = 0; map != null && i < allBuckets.length - 1; ++i) {
      map = (BucketMap) map.getTotalEntry().getValue();
    }

    return map == null ? null : (MeasureValue[]) map.getTotalEntry().getValue();
  }
  /**
   * Returns the measure values for a set of bucket values.
   *
   * @param bucketValues the bucket values
   * @return the measure values corresponding to the bucket values
   */
  public MeasureValue[] getMeasureValues(Bucket[] bucketValues) {
    BucketMap map = bucketValueMap;

    for (int i = 0; map != null && i < allBuckets.length - 1; ++i) {
      map = (BucketMap) map.get(bucketValues[i]);
    }

    return map == null ? null : (MeasureValue[]) map.get(bucketValues[allBuckets.length - 1]);
  }
  /**
   * Feeds data to the engine.
   *
   * @param bucketValues the bucket values
   * @param measureValues the measure values
   * @throws JRException
   */
  public void addData(Object[] bucketValues, Object[] measureValues) throws JRException {
    if (processed) {
      throw new JRException(EXCEPTION_MESSAGE_KEY_BUCKET_DATA_PROCESSED, (Object[]) null);
    }

    ++dataCount;

    Bucket[] bucketVals = getBucketValues(bucketValues);

    MeasureValue[] values = bucketValueMap.insertMeasureValues(bucketVals, true, 0);
    for (int i = 0; i < measures.length; ++i) {
      Object measureValue = measureValues[measureIndexes[i]];
      values[i].addValue(measureValue);
    }

    // collect column bucket values
    columnBucketMap.insertMeasureValues(bucketVals, false, rowBucketCount);
  }
  protected void computeColumnTotal(BucketMap bucketMap) throws JRException {
    MeasureValue[] totals = initMeasureValues();

    for (Iterator it = bucketMap.entryIterator(); it.hasNext(); ) {
      Map.Entry entry = (Map.Entry) it.next();

      for (int i = bucketMap.level + 1; i < allBuckets.length; ++i) {
        entry = ((BucketMap) entry.getValue()).getTotalEntry();
      }

      sumVals(totals, (MeasureValue[]) entry.getValue());
    }

    for (int i = bucketMap.level + 1; i < allBuckets.length; ++i) {
      bucketMap = bucketMap.addTotalNextMap();
    }

    bucketMap.addTotalEntry(totals);
  }
  protected void computeRowTotals(BucketMap bucketMap) throws JRException {
    BucketMapMap totals = createRowTotalsBucketMap();

    for (Iterator<Map.Entry<Bucket, Object>> it = bucketMap.entryIterator(); it.hasNext(); ) {
      Map.Entry<Bucket, Object> entry = it.next();

      for (int i = bucketMap.level + 1; i < rowBucketCount; ++i) {
        entry = ((BucketMap) entry.getValue()).getTotalEntry();
      }

      totals.sumValues((BucketMap) entry.getValue());
    }

    BucketMap totalBucketMap = bucketMap;
    for (int i = bucketMap.level + 1; i < rowBucketCount; ++i) {
      totalBucketMap = totalBucketMap.addTotalNextMap();
    }

    totalBucketMap.addTotalEntry(totals);
  }
  protected void computeRowTotals(BucketMap bucketMap) throws JRException {
    BucketListMap totals = createCollectBucketMap(rowBucketCount);

    for (Iterator it = bucketMap.entryIterator(); it.hasNext(); ) {
      Map.Entry entry = (Map.Entry) it.next();

      for (int i = bucketMap.level + 1; i < rowBucketCount; ++i) {
        entry = ((BucketMap) entry.getValue()).getTotalEntry();
      }

      totals.collectVals((BucketMap) entry.getValue(), true);
    }

    BucketMap totalBucketMap = bucketMap;
    for (int i = bucketMap.level + 1; i < rowBucketCount; ++i) {
      totalBucketMap = totalBucketMap.addTotalNextMap();
    }

    totalBucketMap.addTotalEntry(totals);
  }
  protected void collectCols(BucketListMap collectedCols, BucketMap bucketMap) throws JRException {
    if (allBuckets[bucketMap.level].computeTotal()) {
      BucketMap map = bucketMap;
      for (int i = bucketMap.level; i < rowBucketCount; ++i) {
        map = (BucketMap) map.getTotalEntry().getValue();
      }
      collectedCols.collectVals(map, false);

      return;
    }

    for (Iterator it = bucketMap.entryIterator(); it.hasNext(); ) {
      Map.Entry entry = (Map.Entry) it.next();
      BucketMap nextMap = (BucketMap) entry.getValue();
      if (bucketMap.level == rowBucketCount - 1) {
        collectedCols.collectVals(nextMap, false);
      } else {
        collectCols(collectedCols, nextMap);
      }
    }
  }
    void collectVals(BucketMap map, boolean sum) throws JRException {
      ListIterator totalIt = entries.listIterator();
      MapEntry totalItEntry = totalIt.hasNext() ? (MapEntry) totalIt.next() : null;

      Iterator it = map.entryIterator();
      Map.Entry entry = it.hasNext() ? (Map.Entry) it.next() : null;
      while (entry != null) {
        Bucket key = (Bucket) entry.getKey();

        int compare = totalItEntry == null ? -1 : key.compareTo(totalItEntry.key);
        if (compare <= 0) {
          Object addVal = null;

          if (last) {
            if (sum) {
              MeasureValue[] totalVals = compare == 0 ? (MeasureValue[]) totalItEntry.value : null;

              if (totalVals == null) {
                totalVals = initMeasureValues();
                addVal = totalVals;
              }

              sumVals(totalVals, (MeasureValue[]) entry.getValue());
            }
          } else {
            BucketListMap nextTotals = compare == 0 ? (BucketListMap) totalItEntry.value : null;

            if (nextTotals == null) {
              nextTotals = createCollectBucketMap(level + 1);
              addVal = nextTotals;
            }

            nextTotals.collectVals((BucketMap) entry.getValue(), sum);
          }

          if (compare < 0) {
            if (totalItEntry != null) {
              totalIt.previous();
            }
            totalIt.add(new MapEntry(key, addVal));
            if (totalItEntry != null) {
              totalIt.next();
            }
          }

          entry = it.hasNext() ? (Map.Entry) it.next() : null;
        }

        if (compare >= 0) {
          totalItEntry = totalIt.hasNext() ? (MapEntry) totalIt.next() : null;
        }
      }
    }
  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 = (BucketMap) bucketMaps.get(row);
      for (int i = row; rowMap != null && i < rowBucketCount; ++i) {
        Entry 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 = (BucketMap) bucketMaps.get(rowBucketCount + col + 1);
          } else if (rowMap != null) {
            rowMap = (BucketMap) rowMap.get((Bucket) 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((Bucket) vals.get(rowBucketCount + colBucketCount - 1));
            totals[row][col] = getUserMeasureValues(measureValues);
          } else {
            Map.Entry 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;
  }
  /**
   * Feeds data to the engine.
   *
   * @param bucketValues the bucket values
   * @param measureValues the measure values
   * @throws JRException
   */
  public void addData(Object[] bucketValues, Object[] measureValues) throws JRException {
    if (processed) {
      throw new JRException("Crosstab data has already been processed.");
    }

    ++dataCount;

    Bucket[] bucketVals = getBucketValues(bucketValues);

    MeasureValue[] values = bucketValueMap.insertMeasureValues(bucketVals);

    for (int i = 0; i < measures.length; ++i) {
      values[i].addValue(measureValues[measureIndexes[i]]);
    }
  }
    void sumValues(BucketMap bucketMap) throws JRException {
      for (Iterator<Map.Entry<Bucket, Object>> it = bucketMap.entryIterator(); it.hasNext(); ) {
        Map.Entry<Bucket, Object> entry = it.next();

        // find the total entry that matches the map entry.
        // the total map is should contain all collected entries
        // FIXME optimize this for sorted maps where we can assume that the order is the same
        Object value = get(entry.getKey());
        if (last) {
          // last level, sum the values
          sumVals((MeasureValue[]) value, (MeasureValue[]) entry.getValue());
        } else {
          // go to the next level
          ((BucketMapMap) value).sumValues((BucketMap) entry.getValue());
        }
      }
    }
    void copyEntries(BucketMap bucketMap) {
      for (Iterator<Entry<Bucket, Object>> bucketIterator = bucketMap.entryIterator();
          bucketIterator.hasNext(); ) {
        Entry<Bucket, Object> bucketEntry = bucketIterator.next();
        Bucket bucketKey = bucketEntry.getKey();

        Object copyBucketValue;
        if (bucketMap.last) {
          copyBucketValue = initMeasureValues();
        } else {
          BucketMap bucketSubMap = (BucketMap) bucketEntry.getValue();
          BucketMapMap copyBucketSubMap = new BucketMapMap(level + 1, false);
          copyBucketSubMap.copyEntries(bucketSubMap);
          copyBucketValue = copyBucketSubMap;
        }

        map.put(bucketKey, copyBucketValue);
      }
    }
  protected CollectedList createHeadersList(
      byte dimension, BucketMap bucketMap, int level, boolean total) {
    CollectedList headers = new CollectedList();

    for (Iterator it = bucketMap.entryIterator(); it.hasNext(); ) {
      Map.Entry entry = (Map.Entry) it.next();
      Bucket bucketValue = (Bucket) entry.getKey();

      boolean totalBucket = bucketValue.isTotal();
      byte totalPosition = allBuckets[bucketMap.level].getTotalPosition();
      boolean createHeader =
          !totalBucket || total || totalPosition != BucketDefinition.TOTAL_POSITION_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 CollectedList();
          nextHeaders.span = 1;
        }
        nextHeaders.key = bucketValue;

        if (totalBucket) {
          if (totalPosition == BucketDefinition.TOTAL_POSITION_START) {
            headers.addFirst(nextHeaders);
          } else {
            headers.add(nextHeaders);
          }
        } else {
          headers.add(nextHeaders);
        }
      }
    }

    if (headers.span == 0) {
      headers.span = 1;
    }

    return headers;
  }
  protected void computeTotals(BucketMap bucketMap) throws JRException {
    byte dimension = bucketMap.level < rowBucketCount ? DIMENSION_ROW : DIMENSION_COLUMN;

    if (dimension == DIMENSION_COLUMN && !allBuckets[allBuckets.length - 1].computeTotal()) {
      return;
    }

    if (!bucketMap.last) {
      for (Iterator it = bucketMap.entryIterator(); it.hasNext(); ) {
        Map.Entry entry = (Map.Entry) it.next();

        computeTotals((BucketMap) entry.getValue());
      }
    }

    if (allBuckets[bucketMap.level].computeTotal()) {
      if (dimension == DIMENSION_COLUMN) {
        computeColumnTotal(bucketMap);
      } else {
        computeRowTotals(bucketMap);
      }
    }
  }
  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];

    Iterator bucketIt = bucketMap == null ? null : bucketMap.entryIterator();
    Map.Entry bucketItEntry =
        bucketIt != null && bucketIt.hasNext() ? (Map.Entry) bucketIt.next() : null;
    for (Iterator it = collectedList.iterator(); it.hasNext(); ) {
      CollectedList list = (CollectedList) it.next();

      Map.Entry bucketEntry = null;
      if (list.key.isTotal()) {
        if (bucketMap != null) {
          bucketEntry = bucketMap.getTotalEntry();
        }
      } else {
        if (bucketItEntry != null && bucketItEntry.getKey().equals(list.key)) {
          bucketEntry = bucketItEntry;
          bucketItEntry = bucketIt.hasNext() ? (Map.Entry) bucketIt.next() : null;
        }
      }

      vals.add(list.key);
      if (last) {
        fillCell(pos, vals, bucketMaps, bucketEntry);
      } else {
        nextCollected[dimension] = list;
        BucketMap nextMap = bucketEntry == null ? null : (BucketMap) bucketEntry.getValue();

        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);
  }