Package net.sf.jasperreports.crosstabs.fill.calculation

Source Code of net.sf.jasperreports.crosstabs.fill.calculation.BucketingService$OrderedCollectedList

/*
* JasperReports - Free Java Reporting Library.
* Copyright (C) 2001 - 2009 Jaspersoft Corporation. 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 <http://www.gnu.org/licenses/>.
*/
package net.sf.jasperreports.crosstabs.fill.calculation;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Map.Entry;

import net.sf.jasperreports.crosstabs.fill.calculation.BucketDefinition.Bucket;
import net.sf.jasperreports.crosstabs.fill.calculation.MeasureDefinition.MeasureValue;
import net.sf.jasperreports.crosstabs.type.CrosstabTotalPositionEnum;
import net.sf.jasperreports.engine.JRConstants;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRRuntimeException;
import net.sf.jasperreports.engine.fill.JRCalculable;
import net.sf.jasperreports.engine.fill.JRFillCrosstab;
import net.sf.jasperreports.engine.type.CalculationEnum;
import net.sf.jasperreports.engine.util.JRProperties;

/**
* Crosstab bucketing engine.
*
* @author Lucian Chirita (lucianc@users.sourceforge.net)
* @version $Id: BucketingService.java 3940 2010-08-20 10:35:15Z teodord $
*/
public class BucketingService
{
 
  public static final String PROPERTY_BUCKET_MEASURE_LIMIT = JRProperties.PROPERTY_PREFIX + "crosstab.bucket.measure.limit";
 
  protected static final byte DIMENSION_ROW = 0;

  protected static final byte DIMENSION_COLUMN = 1;

  protected static final int DIMENSIONS = 2;

  private final JRFillCrosstab fillCrosstab;

  protected final BucketDefinition[] allBuckets;
  protected final BucketDefinition[][] buckets;

  protected final int rowBucketCount;
  protected final int colBucketCount;

  protected final boolean[][] retrieveTotal;
  private boolean[] rowRetrTotals;
  private int rowRetrTotalMin;
  private int rowRetrTotalMax;
  private int[] rowRetrColMax;

  protected final MeasureDefinition[] measures;
  protected final int origMeasureCount;
  protected final int[] measureIndexes;

  protected final boolean sorted;

  protected final BucketMap bucketValueMap;
  protected long dataCount;
  protected boolean processed;
 
  protected HeaderCell[][] colHeaders;
  protected HeaderCell[][] rowHeaders;
  protected CrosstabCell[][] cells;

  private final MeasureValue[] zeroUserMeasureValues;

  private final int bucketMeasureLimit;
  private int runningBucketMeasureCount;
 
  /**
   * Creates a crosstab bucketing engine.
   *
   * @param fillCrosstab
   * @param rowBuckets the row bucket definitions
   * @param columnBuckets the column bucket definitions
   * @param measures the measure definitions
   * @param sorted whether the data is presorted
   * @param retrieveTotal totals to retrieve along with the cell values
   */
  public BucketingService(JRFillCrosstab fillCrosstab, List rowBuckets, List columnBuckets, List measures, boolean sorted, boolean[][] retrieveTotal)
  {
    this.fillCrosstab = fillCrosstab;
   
    this.sorted = sorted;

    buckets = new BucketDefinition[DIMENSIONS][];
   
    rowBucketCount = rowBuckets.size();
    buckets[DIMENSION_ROW] = new BucketDefinition[rowBucketCount];
    rowBuckets.toArray(buckets[DIMENSION_ROW]);
   
    colBucketCount = columnBuckets.size();
    buckets[DIMENSION_COLUMN] = new BucketDefinition[colBucketCount];
    columnBuckets.toArray(buckets[DIMENSION_COLUMN]);

    allBuckets = new BucketDefinition[rowBucketCount + colBucketCount];
    System.arraycopy(buckets[DIMENSION_ROW], 0, allBuckets, 0, rowBucketCount);
    System.arraycopy(buckets[DIMENSION_COLUMN], 0, allBuckets, rowBucketCount, colBucketCount);

    origMeasureCount = measures.size();
    List measuresList = new ArrayList(measures.size() * 2);
    List measureIndexList = new ArrayList(measures.size() * 2);
    for (int i = 0; i < measures.size(); ++i)
    {
      MeasureDefinition measure = (MeasureDefinition) measures.get(i);
      addMeasure(measure, i, measuresList, measureIndexList);
    }
    this.measures = new MeasureDefinition[measuresList.size()];
    measuresList.toArray(this.measures);
    this.measureIndexes = new int[measureIndexList.size()];
    for (int i = 0; i < measureIndexes.length; ++i)
    {
      measureIndexes[i] = ((Integer) measureIndexList.get(i)).intValue();
    }

    this.retrieveTotal = retrieveTotal;
    checkTotals();
   
    bucketValueMap = createBucketMap(0);
   
    zeroUserMeasureValues = initUserMeasureValues();
   
    bucketMeasureLimit = JRProperties.getIntegerProperty(PROPERTY_BUCKET_MEASURE_LIMIT, 0);
  }


  protected void checkTotals()
  {
    rowRetrTotalMin = rowBucketCount + 1;
    rowRetrTotalMax = -1;
    rowRetrTotals = new boolean[rowBucketCount + 1];
    rowRetrColMax = new int[rowBucketCount + 1];
    for (int row = 0; row <= rowBucketCount; ++row)
    {
      rowRetrColMax[row] = -1;
      boolean total = false;
      for (int col = 0; col <= colBucketCount; ++col)
      {
        if (retrieveTotal[row][col])
        {
          total = true;
          rowRetrColMax[row] = col;
        }
      }
     
      rowRetrTotals[row] = total;
      if (total)
      {
        if (row < rowRetrTotalMin)
        {
          rowRetrTotalMin = row;
        }
        rowRetrTotalMax = row;
       
        if (row < rowBucketCount)
        {
          allBuckets[row].setComputeTotal();
        }
      }
    }
   
    for (int col = 0; col < colBucketCount; ++col)
    {
      BucketDefinition colBucket = allBuckets[rowBucketCount + col];
      if (!colBucket.computeTotal())
      {
        boolean total = false;
        for (int row = 0; !total && row <= rowBucketCount; ++row)
        {
          total = retrieveTotal[row][col];
        }
       
        if (total)
        {
          colBucket.setComputeTotal();
        }
      }
    }
   
    for (int d = 0; d < DIMENSIONS; ++d)
    {
      boolean dTotal = false;
     
      for (int i = 0; i < buckets[d].length; ++i)
      {
        if (dTotal)
        {
          buckets[d][i].setComputeTotal();
        }
        else
        {
          dTotal = buckets[d][i].computeTotal();
        }
      }
    }
  }


  /**
   * Clears all the accumulated and computed data.
   */
  public void clear()
  {
    bucketValueMap.clear();
    processed = false;
    dataCount = 0;
    runningBucketMeasureCount = 0;
  }
 
  protected BucketMap createBucketMap(int level)
  {
    BucketMap map;
    if (sorted)
    {
      map = new BucketListMap(level, false);
    }
    else
    {
      map = new BucketTreeMap(level);
    }
    return map;
  }
 
  protected BucketListMap createCollectBucketMap(int level)
  {
    return new BucketListMap(level, true);
  }

  protected void addMeasure(MeasureDefinition measure, int index, List measuresList, List measureIndexList)
  {
    switch (measure.getCalculation())
    {
      case AVERAGE:
      case VARIANCE:
      {
        MeasureDefinition sumMeasure = MeasureDefinition.createHelperMeasure(measure, CalculationEnum.SUM);
        addMeasure(sumMeasure, index, measuresList, measureIndexList);
        MeasureDefinition countMeasure = MeasureDefinition.createHelperMeasure(measure, CalculationEnum.COUNT);
        addMeasure(countMeasure, index, measuresList, measureIndexList);
        break;
      }
      case STANDARD_DEVIATION:
      {
        MeasureDefinition varianceMeasure = MeasureDefinition.createHelperMeasure(measure, CalculationEnum.VARIANCE);
        addMeasure(varianceMeasure, index, measuresList, measureIndexList);
        break;
      }
      case DISTINCT_COUNT:
      {
        MeasureDefinition countMeasure = MeasureDefinition.createDistinctCountHelperMeasure(measure);
        addMeasure(countMeasure, index, measuresList, measureIndexList);
        break;
      }
    }

    measuresList.add(measure);
    measureIndexList.add(Integer.valueOf(index));
  }

 
  /**
   * 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)
    {
      Object measureValue = measureValues[measureIndexes[i]];
      values[i].addValue(measureValue);
    }
  }
 
  protected void bucketMeasuresCreated()
  {
    runningBucketMeasureCount += origMeasureCount;
   
    checkBucketMeasureCount(runningBucketMeasureCount);
  }

  protected Bucket[] getBucketValues(Object[] bucketValues)
  {
    Bucket[] bucketVals = new Bucket[allBuckets.length];

    for (int i = 0; i < allBuckets.length; ++i)
    {
      BucketDefinition bucket = allBuckets[i];
      Object value = bucketValues[i];
      bucketVals[i] = bucket.create(value);
    }
   
    return bucketVals;
  }

  protected MeasureValue[] initMeasureValues()
  {
    MeasureValue[] values;
    values = new MeasureValue[measures.length];

    for (int i = 0; i < measures.length; ++i)
    {
      MeasureDefinition measure = measures[i];
      values[i] = measure.new MeasureValue();

      switch (measure.getCalculation())
      {
        case AVERAGE:
        case VARIANCE:
        {
          values[i].setHelper(values[i - 2], JRCalculable.HELPER_SUM);
          values[i].setHelper(values[i - 1], JRCalculable.HELPER_COUNT);
          break;
        }
        case STANDARD_DEVIATION:
        {
          values[i].setHelper(values[i - 1], JRCalculable.HELPER_VARIANCE);
        }
        case DISTINCT_COUNT:
        {
          values[i].setHelper(values[i - 1], JRCalculable.HELPER_COUNT);
        }
      }
    }
    return values;
  }

  protected MeasureValue[] initUserMeasureValues()
  {
    MeasureValue[] vals = new MeasureValue[origMeasureCount];
   
    for (int c = 0, i = 0; i < measures.length; ++i)
    {
      if (!measures[i].isSystemDefined())
      {
        vals[c] = measures[i].new MeasureValue();
        ++c;
      }
    }
   
    return vals;
  }

 
  /**
   * Processes the data which was fed to the engine.
   * <p>
   * This method should be called after the data has been exhausted.
   * The processing consists of total calculations and crosstab table creation.
   *
   * @throws JRException
   */
  public void processData() throws JRException
  {
    if (!processed)
    {
      if (dataCount > 0)
      {
        if (allBuckets[rowBucketCount - 1].computeTotal() || allBuckets[allBuckets.length - 1].computeTotal())
        {
          computeTotals(bucketValueMap);
        }

        createCrosstab();
      }
     
      processed = true;
    }
  }

 
  /**
   * Checks whether there is any data accumulated by the engine.
   *
   * @return <code>true</code> if and only if the engine has any accumulated data
   */
  public boolean hasData()
  {
    return dataCount > 0;
  }
 
 
  /**
   * Returns the crosstab column headers.
   * <p>
   * {@link #processData() processData()} has to be called before this.
   *
   * @return the crosstab column headers
   */
  public HeaderCell[][] getColumnHeaders()
  {
    return colHeaders;
  }
 
 
  /**
   * Returns the crosstab row headers.
   * <p>
   * {@link #processData() processData()} has to be called before this.
   *
   * @return the crosstab row headers
   */
  public HeaderCell[][] getRowHeaders()
  {
    return rowHeaders;
  }
 
 
  /**
   * Returns the crosstab data cells.
   * <p>
   * {@link #processData() processData()} has to be called before this.
   *
   * @return the crosstab data cells
   */
  public CrosstabCell[][] getCrosstabCells()
  {
    return cells;
  }
 
 
  /**
   * 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]);
  }

  protected MeasureValue[] getUserMeasureValues(MeasureValue[] values)
  {
    MeasureValue[] vals = new MeasureValue[origMeasureCount];
   
    for (int c = 0, i = 0; i < measures.length; ++i)
    {
      if (!measures[i].isSystemDefined())
      {
        vals[c] = values[i];
        ++c;
      }
    }
   
    return vals;
  }

 
  /**
   * 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();
  }

 
  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 sumVals(MeasureValue[] totals, MeasureValue[] vals) throws JRException
  {
    for (int i = 0; i < measures.length; i++)
    {
      totals[i].addValue(vals[i]);
    }
  }
 
  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
  {
    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);
  }

 
  static protected class MapEntry implements Map.Entry, Comparable
  {
    final Bucket key;

    final Object value;
   
    MapEntry(Bucket key, Object value)
    {
      this.key = key;
      this.value = value;
    }

    public Object getKey()
    {
      return key;
    }

    public Object getValue()
    {
      return value;
    }

    public Object setValue(Object value)
    {
      throw new UnsupportedOperationException();
    }

    public int compareTo(Object o)
    {
      return key.compareTo(((MapEntry) o).key);
    }
   
    public String toString()
    {
      return key + "=" + value;
    }
  }
 
  protected abstract class BucketMap
  {
    final int level;
    final boolean last;
    final Bucket totalKey;

    BucketMap(int level)
    {
      this.level = level;
      this.last = level == allBuckets.length - 1;
      totalKey = allBuckets[level].VALUE_TOTAL;
    }

    BucketMap addTotalNextMap()
    {
      BucketMap nextMap = createBucketMap(level + 1);
      addTotalEntry(nextMap);
      return nextMap;
    }

    abstract void clear();

    abstract Iterator entryIterator();

    abstract Object get(Bucket key);

    abstract MeasureValue[] insertMeasureValues(Bucket[] bucketValues);

/*    abstract void fillKeys(Collection collectedKeys);*/

    abstract void addTotalEntry(Object val);

    abstract int size();
   
    abstract Map.Entry getTotalEntry();
  }

  protected class BucketTreeMap extends BucketMap
  {
    TreeMap map;

    BucketTreeMap(int level)
    {
      super(level);

      map = new TreeMap();
    }
   
    void clear()
    {
      map.clear();
    }

    Iterator entryIterator()
    {
      return map.entrySet().iterator();
    }

    Object get(Bucket key)
    {
      return map.get(key);
    }

    MeasureValue[] insertMeasureValues(Bucket[] bucketValues)
    {
      BucketTreeMap levelMap = (BucketTreeMap) bucketValueMap;
      for (int i = 0; i < bucketValues.length - 1; i++)
      {
        BucketTreeMap nextMap = (BucketTreeMap) levelMap.get(bucketValues[i]);
        if (nextMap == null)
        {
          nextMap = new BucketTreeMap(i + 1);
          levelMap.map.put(bucketValues[i], nextMap);
        }

        levelMap = nextMap;
      }

      MeasureValue[] values = (MeasureValue[]) levelMap.get(bucketValues[bucketValues.length - 1]);
      if (values == null)
      {
        values = initMeasureValues();
        levelMap.map.put(bucketValues[bucketValues.length - 1], values);
       
        bucketMeasuresCreated();
      }

      return values;
    }

    int size()
    {
      return map.size();
    }

    void addTotalEntry(Object value)
    {
      map.put(totalKey, value);
    }
   
    Map.Entry getTotalEntry()
    {
      Object value = get(totalKey);
      return value == null ? null : new MapEntry(totalKey, value);
    }
   
   
    public String toString()
    {
      return map.toString();
    }
  }

  protected class BucketListMap extends BucketMap
  {
    List entries;
    // we maintain a map as well in order to have fast search by key
    // TODO implement this in a single structure
    Map entryMap;

    BucketListMap(int level, boolean linked)
    {
      super(level);

      if (linked)
      {
        entries = new LinkedList();
      }
      else
      {
        entries = new ArrayList();
      }
     
      entryMap = new HashMap();
    }

    void clear()
    {
      entries.clear();
      entryMap.clear();
    }
   
    Iterator entryIterator()
    {
      return entries.iterator();
    }

    private void add(Bucket key, Object value)
    {
      entries.add(new MapEntry(key, value));
      entryMap.put(key, value);
    }

    Object get(Bucket key)
    {
      return entryMap.get(key);
    }

    MeasureValue[] insertMeasureValues(Bucket[] bucketValues)
    {
      int i = 0;
      Object levelObj = this;
      BucketListMap map = null;
      while (i < allBuckets.length)
      {
        map = (BucketListMap) levelObj;
        int size = map.entries.size();
        if (size == 0)
        {
          break;
        }

        MapEntry lastEntry = (MapEntry) map.entries.get(size - 1);
        if (!lastEntry.key.equals(bucketValues[i]))
        {
          break;
        }
       
        ++i;
        levelObj = lastEntry.value;
      }

      if (i == allBuckets.length)
      {
        return (MeasureValue[]) levelObj;
      }

      while (i < allBuckets.length - 1)
      {
        BucketListMap nextMap = new BucketListMap(i + 1, false);
        map.add(bucketValues[i], nextMap);
        map = nextMap;
        ++i;
      }

      MeasureValue[] values = initMeasureValues();
      map.add(bucketValues[i], values);
     
      bucketMeasuresCreated();

      return values;
    }

    int size()
    {
      return entries.size();
    }

    void addTotalEntry(Object value)
    {
      add(totalKey, value);
    }

    Map.Entry getTotalEntry()
    {
      MapEntry lastEntry = (MapEntry) entries.get(entries.size() - 1);
      if (lastEntry.key.isTotal())
      {
        return lastEntry;
      }
     
      return null;
    }

   
    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));
            entryMap.put(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;
        }
      }
    }
   
    public String toString()
    {
      StringBuffer sb = new StringBuffer();
      sb.append('{');
      for (Iterator it = entries.iterator(); it.hasNext();)
      {
        MapEntry entry = (MapEntry) it.next();
        sb.append(entry);
        if (it.hasNext())
        {
          sb.append(", ");
        }
      }
      sb.append('}');
      return sb.toString();
    }
  }

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


  protected void checkBucketMeasureCount(int bucketMeasureCount)
  {
    if (bucketMeasureLimit > 0 && bucketMeasureCount > bucketMeasureLimit)
    {
      throw new JRRuntimeException("Crosstab bucket/measure limit (" + bucketMeasureLimit + ") exceeded.");
    }
  }


  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);
      }
    }
  }
 
 
  protected CollectedList createHeadersList(byte dimension, BucketMap bucketMap, int level, boolean total)
      throws JRException
  {
    BucketDefinition bucketDefinition = allBuckets[bucketMap.level];
    CrosstabTotalPositionEnum totalPosition = bucketDefinition.getTotalPosition();
    CollectedList headers;
    if (bucketDefinition.hasOrderValues())
    {
      headers = new OrderedCollectedList(bucketDefinition);
    }
    else
    {
      headers = new SequentialCollectedList(totalPosition);
    }

    for (Iterator it = bucketMap.entryIterator(); it.hasNext();)
    {
      Map.Entry entry = (Map.Entry) it.next();
      Bucket bucketValue = (Bucket) 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 (bucketDefinition.hasOrderValues())
        {
          Object orderValue = evaluateOrderValue(bucketMap, bucketValue);
          nextHeaders.orderValue = orderValue;
        }
        headers.add(nextHeaders);
      }
    }

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

    return headers;
  }
 
 
  protected Object evaluateOrderValue(BucketMap bucketMap, Bucket bucket) throws JRException
  {
    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 fillCrosstab.evaluateExpression(
        allBuckets[bucketMap.level].getOrderByExpression(),
        userTotals);
  }


  protected HeaderCell[][] createHeaders(byte dimension, CollectedList[] headersLists)
  {
    HeaderCell[][] headers = new HeaderCell[buckets[dimension].length][headersLists[dimension].span];
   
    List vals = new ArrayList();
    fillHeaders(dimension, headers, 0, 0, headersLists[dimension], vals);
   
    return headers;
  }

 
  protected void fillHeaders(byte dimension, HeaderCell[][] headers, int level, int col, CollectedList list, List vals)
  {
    if (level == buckets[dimension].length)
    {
      return;
    }
   
    for (Iterator it = list.iterator(); it.hasNext();)
    {
      CollectedList subList = (CollectedList) it.next();
     
      vals.add(subList.key);
     
      int depthSpan = subList.key.isTotal() ? buckets[dimension].length - level : 1;
      Bucket[] values = new Bucket[buckets[dimension].length];
      vals.toArray(values);
     
      headers[level][col] = new HeaderCell(values, subList.span, depthSpan);
     
      if (!subList.key.isTotal())
      {
        fillHeaders(dimension, headers, level + 1, col, subList, vals);
      }
     
      col += subList.span;
      vals.remove(vals.size() - 1);
    }
  }


  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 = (CollectedList) 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] = (Bucket) valsIt.next();
    }
   
    Bucket[] columnValues = new Bucket[buckets[BucketingService.DIMENSION_COLUMN].length];
    for (int i = 0; i < columnValues.length; i++)
    {
      columnValues[i] = (Bucket) 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 = (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));
            if (measureValues != null)
            {
              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;
  }
 
  protected static abstract class CollectedList
  {
    private static final long serialVersionUID = JRConstants.SERIAL_VERSION_UID;

    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;
      }
    }
   
    public String toString()
    {
      return key + "/" + span + ": " + super.toString();
    }
  }
 
  protected static class SequentialCollectedList extends CollectedList
  {
    private static final long serialVersionUID = JRConstants.SERIAL_VERSION_UID;

    final CrosstabTotalPositionEnum totalPosition;
    final LinkedList list;
   
    SequentialCollectedList(CrosstabTotalPositionEnum totalPosition)
    {
      this.totalPosition = totalPosition;
     
      list = new LinkedList();
    }

    public Iterator iterator()
    {
      return list.iterator();
    }

    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
  {
    private static final long serialVersionUID = JRConstants.SERIAL_VERSION_UID;

    final TreeSet list;
   
    OrderedCollectedList(BucketDefinition bucketDefinition)
    {
      super();
     
      CollectedListComparator comparator =
        new CollectedListComparator(bucketDefinition);
      list = new TreeSet(comparator);
    }

    public Iterator iterator()
    {
      return list.iterator();
    }

    protected void addSublist(CollectedList sublist)
    {
      list.add(sublist);
    }
  }
 
  protected static class CollectedListComparator implements Comparator
  {
    final BucketDefinition bucketDefinition;
    final boolean totalFirst;
   
    CollectedListComparator(BucketDefinition bucketDefinition)
    {
      this.bucketDefinition = bucketDefinition;
      this.totalFirst = bucketDefinition.getTotalPosition()
          == CrosstabTotalPositionEnum.START;
    }

    public int compare(Object o1, Object o2)
    {
      if (o1 == o2)
      {
        return 0;
      }
     
      CollectedList l1 = (CollectedList) o1;
      CollectedList l2 = (CollectedList) o2;
     
      int order;
      if (l1.key.isTotal())
      {
        if (l2.key.isTotal())
        {
          // this should not happen
          throw new JRRuntimeException("Two total keys in the same list");
        }
       
        order = totalFirst ? -1 : 1;
      }
      else if (l2.key.isTotal())
      {
        order = totalFirst ? 1 : -1;
      }
      else
      {
        // first compare the order values
        order = bucketDefinition.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;
    }   
  }
}
TOP

Related Classes of net.sf.jasperreports.crosstabs.fill.calculation.BucketingService$OrderedCollectedList

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.