package net.sf.jasperreports.crosstabs.design;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.Serializable;
import java.net.URLStreamHandlerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.TimeZone;
import net.sf.jasperreports.crosstabs.JRCellContents;
import net.sf.jasperreports.crosstabs.JRCrosstab;
import net.sf.jasperreports.crosstabs.JRCrosstabBucket;
import net.sf.jasperreports.crosstabs.JRCrosstabCell;
import net.sf.jasperreports.crosstabs.JRCrosstabColumnGroup;
import net.sf.jasperreports.crosstabs.JRCrosstabDataset;
import net.sf.jasperreports.crosstabs.JRCrosstabMeasure;
import net.sf.jasperreports.crosstabs.JRCrosstabParameter;
import net.sf.jasperreports.crosstabs.JRCrosstabRowGroup;
import net.sf.jasperreports.crosstabs.base.JRBaseCrosstab;
import net.sf.jasperreports.engine.JRCommonElement;
import net.sf.jasperreports.engine.JRDefaultStyleProvider;
import net.sf.jasperreports.engine.JRElement;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExpression;
import net.sf.jasperreports.engine.JRExpressionCollector;
import net.sf.jasperreports.engine.JRVariable;
import net.sf.jasperreports.engine.JRVisitor;
import net.sf.jasperreports.engine.design.JRDesignElement;
import net.sf.jasperreports.engine.design.JRDesignVariable;
import net.sf.jasperreports.engine.util.FileResolver;
import net.sf.jasperreports.engine.util.FormatFactory;
import net.sf.jasperreports.engine.util.JRStyleResolver;
import net.sf.jasperreports.engine.util.Pair;
import org.apache.commons.collections.SequencedHashMap;

public class JRDesignCrosstab extends JRDesignElement implements JRCrosstab {
  private static final long serialVersionUID = 10200L;
  
  public static final String PROPERTY_COLUMN_BREAK_OFFSET = "columnBreakOffset";
  
  public static final String PROPERTY_DATASET = "dataset";
  
  public static final String PROPERTY_HEADER_CELL = "headerCell";
  
  public static final String PROPERTY_PARAMETERS_MAP_EXPRESSION = "parametersMapExpression";
  
  public static final String PROPERTY_REPEAT_COLUMN_HEADERS = "repeatColumnHeaders";
  
  public static final String PROPERTY_REPEAT_ROW_HEADERS = "repeatRowHeaders";
  
  public static final String PROPERTY_WHEN_NO_DATA_CELL = "whenNoDataCell";
  
  public static final String PROPERTY_CELLS = "cells";
  
  public static final String PROPERTY_ROW_GROUPS = "rowGroups";
  
  public static final String PROPERTY_COLUMN_GROUPS = "columnGroups";
  
  public static final String PROPERTY_MEASURES = "measures";
  
  public static final String PROPERTY_PARAMETERS = "parameters";
  
  protected List parametersList;
  
  protected Map parametersMap;
  
  protected SequencedHashMap variablesList;
  
  protected JRExpression parametersMapExpression;
  
  protected JRDesignCrosstabDataset dataset;
  
  protected List rowGroups;
  
  protected List columnGroups;
  
  protected List measures;
  
  protected Map rowGroupsMap;
  
  protected Map columnGroupsMap;
  
  protected Map measuresMap;
  
  protected int columnBreakOffset = 10;
  
  protected boolean repeatColumnHeaders = true;
  
  protected boolean repeatRowHeaders = true;
  
  protected byte runDirection;
  
  protected List cellsList;
  
  protected Map cellsMap;
  
  protected JRDesignCrosstabCell[][] crossCells;
  
  protected JRDesignCellContents whenNoDataCell;
  
  protected JRDesignCellContents headerCell;
  
  private class MeasureClassChangeListener implements PropertyChangeListener, Serializable {
    private static final long serialVersionUID = 10200L;
    
    private final JRDesignCrosstab this$0;
    
    private MeasureClassChangeListener() {}
    
    public void propertyChange(PropertyChangeEvent evt) {
      JRDesignCrosstab.this.measureClassChanged((JRDesignCrosstabMeasure)evt.getSource(), (String)evt.getNewValue());
    }
  }
  
  private PropertyChangeListener measureClassChangeListener = new MeasureClassChangeListener();
  
  private static final Object[] BUILT_IN_PARAMETERS = new Object[] { 
      "REPORT_PARAMETERS_MAP", Map.class, "REPORT_LOCALE", Locale.class, "REPORT_RESOURCE_BUNDLE", ResourceBundle.class, "REPORT_TIME_ZONE", TimeZone.class, "REPORT_FORMAT_FACTORY", FormatFactory.class, 
      "REPORT_CLASS_LOADER", ClassLoader.class, "REPORT_URL_HANDLER_FACTORY", URLStreamHandlerFactory.class, "REPORT_FILE_RESOLVER", FileResolver.class };
  
  private static final Object[] BUILT_IN_VARIABLES = new Object[] { "ROW_COUNT", Integer.class, "COLUMN_COUNT", Integer.class };
  
  public JRDesignCrosstab(JRDefaultStyleProvider defaultStyleProvider) {
    super(defaultStyleProvider);
    this.parametersList = new ArrayList();
    this.parametersMap = new HashMap();
    this.rowGroupsMap = new HashMap();
    this.rowGroups = new ArrayList();
    this.columnGroupsMap = new HashMap();
    this.columnGroups = new ArrayList();
    this.measuresMap = new HashMap();
    this.measures = new ArrayList();
    this.cellsMap = new HashMap();
    this.cellsList = new ArrayList();
    addBuiltinParameters();
    this.variablesList = new SequencedHashMap();
    addBuiltinVariables();
    this.dataset = new JRDesignCrosstabDataset();
  }
  
  private void addBuiltinParameters() {
    for (int i = 0; i < BUILT_IN_PARAMETERS.length; i++) {
      JRDesignCrosstabParameter parameter = new JRDesignCrosstabParameter();
      parameter.setName((String)BUILT_IN_PARAMETERS[i++]);
      parameter.setValueClass((Class)BUILT_IN_PARAMETERS[i]);
      parameter.setSystemDefined(true);
      try {
        addParameter(parameter);
      } catch (JRException e) {}
    } 
  }
  
  private void addBuiltinVariables() {
    for (int i = 0; i < BUILT_IN_VARIABLES.length; i++) {
      JRDesignVariable variable = new JRDesignVariable();
      variable.setName((String)BUILT_IN_VARIABLES[i]);
      variable.setValueClass((Class)BUILT_IN_VARIABLES[++i]);
      variable.setCalculation((byte)8);
      variable.setSystemDefined(true);
      addVariable((JRVariable)variable);
    } 
  }
  
  public JRDesignCrosstab() {
    this((JRDefaultStyleProvider)null);
  }
  
  public int getId() {
    return 0;
  }
  
  public JRCrosstabDataset getDataset() {
    return this.dataset;
  }
  
  public JRDesignCrosstabDataset getDesignDataset() {
    return this.dataset;
  }
  
  public JRCrosstabRowGroup[] getRowGroups() {
    JRCrosstabRowGroup[] groups = new JRCrosstabRowGroup[this.rowGroups.size()];
    this.rowGroups.toArray((Object[])groups);
    return groups;
  }
  
  public JRCrosstabColumnGroup[] getColumnGroups() {
    JRCrosstabColumnGroup[] groups = new JRCrosstabColumnGroup[this.columnGroups.size()];
    this.columnGroups.toArray((Object[])groups);
    return groups;
  }
  
  public JRCrosstabMeasure[] getMeasures() {
    JRCrosstabMeasure[] measureArray = new JRCrosstabMeasure[this.measures.size()];
    this.measures.toArray((Object[])measureArray);
    return measureArray;
  }
  
  public void collectExpressions(JRExpressionCollector collector) {
    collector.collect(this);
  }
  
  public void visit(JRVisitor visitor) {
    visitor.visitCrosstab(this);
  }
  
  public void setDataset(JRDesignCrosstabDataset dataset) {
    Object old = this.dataset;
    this.dataset = dataset;
    getEventSupport().firePropertyChange("dataset", old, this.dataset);
  }
  
  public void addRowGroup(JRDesignCrosstabRowGroup group) throws JRException {
    String groupName = group.getName();
    if (this.rowGroupsMap.containsKey(groupName) || this.columnGroupsMap.containsKey(groupName) || this.measuresMap.containsKey(groupName))
      throw new JRException("A group or measure having the same name already exists in the crosstab."); 
    this.rowGroupsMap.put(groupName, new Integer(this.rowGroups.size()));
    this.rowGroups.add(group);
    addRowGroupVars(group);
    setParent(group);
    getEventSupport().fireCollectionElementAddedEvent("rowGroups", group, this.rowGroups.size() - 1);
  }
  
  protected void addRowGroupVars(JRDesignCrosstabRowGroup rowGroup) {
    addVariable(rowGroup.getVariable());
    for (Iterator measureIt = this.measures.iterator(); measureIt.hasNext(); ) {
      JRCrosstabMeasure measure = measureIt.next();
      addTotalVar(measure, rowGroup, (JRCrosstabColumnGroup)null);
      for (Iterator colIt = this.columnGroups.iterator(); colIt.hasNext(); ) {
        JRCrosstabColumnGroup colGroup = colIt.next();
        addTotalVar(measure, rowGroup, colGroup);
      } 
    } 
  }
  
  public void addColumnGroup(JRDesignCrosstabColumnGroup group) throws JRException {
    String groupName = group.getName();
    if (this.rowGroupsMap.containsKey(groupName) || this.columnGroupsMap.containsKey(groupName) || this.measuresMap.containsKey(groupName))
      throw new JRException("A group or measure having the same name already exists in the crosstab."); 
    this.columnGroupsMap.put(groupName, new Integer(this.columnGroups.size()));
    this.columnGroups.add(group);
    addColGroupVars(group);
    setParent(group);
    getEventSupport().fireCollectionElementAddedEvent("columnGroups", group, this.columnGroups.size() - 1);
  }
  
  protected void addColGroupVars(JRDesignCrosstabColumnGroup colGroup) {
    addVariable(colGroup.getVariable());
    for (Iterator measureIt = this.measures.iterator(); measureIt.hasNext(); ) {
      JRCrosstabMeasure measure = measureIt.next();
      addTotalVar(measure, (JRCrosstabRowGroup)null, colGroup);
      for (Iterator rowIt = this.rowGroups.iterator(); rowIt.hasNext(); ) {
        JRCrosstabRowGroup rowGroup = rowIt.next();
        addTotalVar(measure, rowGroup, colGroup);
      } 
    } 
  }
  
  public void addMeasure(JRDesignCrosstabMeasure measure) throws JRException {
    String measureName = measure.getName();
    if (this.rowGroupsMap.containsKey(measureName) || this.columnGroupsMap.containsKey(measureName) || this.measuresMap.containsKey(measureName))
      throw new JRException("A group or measure having the same name already exists in the crosstab."); 
    measure.addPropertyChangeListener("valueClassName", this.measureClassChangeListener);
    this.measuresMap.put(measureName, new Integer(this.measures.size()));
    this.measures.add(measure);
    addMeasureVars(measure);
    getEventSupport().fireCollectionElementAddedEvent("measures", measure, this.measures.size() - 1);
  }
  
  protected void addMeasureVars(JRDesignCrosstabMeasure measure) {
    addVariable(measure.getVariable());
    for (Iterator colIt = this.columnGroups.iterator(); colIt.hasNext(); ) {
      JRCrosstabColumnGroup colGroup = colIt.next();
      addTotalVar((JRCrosstabMeasure)measure, (JRCrosstabRowGroup)null, colGroup);
    } 
    for (Iterator rowIt = this.rowGroups.iterator(); rowIt.hasNext(); ) {
      JRCrosstabRowGroup rowGroup = rowIt.next();
      addTotalVar((JRCrosstabMeasure)measure, rowGroup, (JRCrosstabColumnGroup)null);
      for (Iterator iterator = this.columnGroups.iterator(); iterator.hasNext(); ) {
        JRCrosstabColumnGroup colGroup = iterator.next();
        addTotalVar((JRCrosstabMeasure)measure, rowGroup, colGroup);
      } 
    } 
  }
  
  protected void addTotalVar(JRCrosstabMeasure measure, JRCrosstabRowGroup rowGroup, JRCrosstabColumnGroup colGroup) {
    JRDesignVariable var = new JRDesignVariable();
    var.setCalculation((byte)8);
    var.setSystemDefined(true);
    var.setName(getTotalVariableName(measure, rowGroup, colGroup));
    var.setValueClassName(measure.getValueClassName());
    addVariable((JRVariable)var);
  }
  
  protected void removeTotalVar(JRCrosstabMeasure measure, JRCrosstabRowGroup rowGroup, JRCrosstabColumnGroup colGroup) {
    String varName = getTotalVariableName(measure, rowGroup, colGroup);
    removeVariable(varName);
  }
  
  public static String getTotalVariableName(JRCrosstabMeasure measure, JRCrosstabRowGroup rowGroup, JRCrosstabColumnGroup colGroup) {
    StringBuffer name = new StringBuffer();
    name.append(measure.getName());
    if (rowGroup != null) {
      name.append('_');
      name.append(rowGroup.getName());
    } 
    if (colGroup != null) {
      name.append('_');
      name.append(colGroup.getName());
    } 
    name.append("_ALL");
    return name.toString();
  }
  
  public JRCrosstabRowGroup removeRowGroup(String groupName) {
    JRCrosstabRowGroup removed = null;
    Integer idx = (Integer)this.rowGroupsMap.remove(groupName);
    if (idx != null) {
      removed = this.rowGroups.remove(idx.intValue());
      for (ListIterator listIterator = this.rowGroups.listIterator(idx.intValue()); listIterator.hasNext(); ) {
        JRCrosstabRowGroup group = listIterator.next();
        this.rowGroupsMap.put(group.getName(), new Integer(listIterator.previousIndex()));
      } 
      for (Iterator it = this.cellsList.iterator(); it.hasNext(); ) {
        JRCrosstabCell cell = it.next();
        String rowTotalGroup = cell.getRowTotalGroup();
        if (rowTotalGroup != null && rowTotalGroup.equals(groupName)) {
          it.remove();
          this.cellsMap.remove(new Pair(rowTotalGroup, cell.getColumnTotalGroup()));
          getEventSupport().fireCollectionElementRemovedEvent("cells", cell, -1);
        } 
      } 
      removeRowGroupVars(removed);
      getEventSupport().fireCollectionElementRemovedEvent("rowGroups", removed, idx.intValue());
    } 
    return removed;
  }
  
  protected void removeRowGroupVars(JRCrosstabRowGroup rowGroup) {
    removeVariable(rowGroup.getVariable());
    for (Iterator measureIt = this.measures.iterator(); measureIt.hasNext(); ) {
      JRCrosstabMeasure measure = measureIt.next();
      removeTotalVar(measure, rowGroup, (JRCrosstabColumnGroup)null);
      for (Iterator colIt = this.columnGroups.iterator(); colIt.hasNext(); ) {
        JRCrosstabColumnGroup colGroup = colIt.next();
        removeTotalVar(measure, rowGroup, colGroup);
      } 
    } 
  }
  
  public JRCrosstabRowGroup removeRowGroup(JRCrosstabRowGroup group) {
    return removeRowGroup(group.getName());
  }
  
  public JRCrosstabColumnGroup removeColumnGroup(String groupName) {
    JRCrosstabColumnGroup removed = null;
    Integer idx = (Integer)this.columnGroupsMap.remove(groupName);
    if (idx != null) {
      removed = this.columnGroups.remove(idx.intValue());
      for (ListIterator listIterator = this.columnGroups.listIterator(idx.intValue()); listIterator.hasNext(); ) {
        JRCrosstabColumnGroup group = listIterator.next();
        this.columnGroupsMap.put(group.getName(), new Integer(listIterator.previousIndex()));
      } 
      for (Iterator it = this.cellsList.iterator(); it.hasNext(); ) {
        JRCrosstabCell cell = it.next();
        String columnTotalGroup = cell.getColumnTotalGroup();
        if (columnTotalGroup != null && columnTotalGroup.equals(groupName)) {
          it.remove();
          this.cellsMap.remove(new Pair(cell.getRowTotalGroup(), columnTotalGroup));
          getEventSupport().fireCollectionElementRemovedEvent("cells", cell, -1);
        } 
      } 
      removeColGroupVars(removed);
      getEventSupport().fireCollectionElementRemovedEvent("columnGroups", removed, idx.intValue());
    } 
    return removed;
  }
  
  protected void removeColGroupVars(JRCrosstabColumnGroup colGroup) {
    removeVariable(colGroup.getVariable());
    for (Iterator measureIt = this.measures.iterator(); measureIt.hasNext(); ) {
      JRCrosstabMeasure measure = measureIt.next();
      removeTotalVar(measure, (JRCrosstabRowGroup)null, colGroup);
      for (Iterator rowIt = this.rowGroups.iterator(); rowIt.hasNext(); ) {
        JRCrosstabRowGroup rowGroup = rowIt.next();
        removeTotalVar(measure, rowGroup, colGroup);
      } 
    } 
  }
  
  public JRCrosstabColumnGroup removeColumnGroup(JRCrosstabColumnGroup group) {
    return removeColumnGroup(group.getName());
  }
  
  public JRCrosstabMeasure removeMeasure(String measureName) {
    JRDesignCrosstabMeasure removed = null;
    Integer idx = (Integer)this.measuresMap.remove(measureName);
    if (idx != null) {
      removed = this.measures.remove(idx.intValue());
      for (ListIterator it = this.measures.listIterator(idx.intValue()); it.hasNext(); ) {
        JRCrosstabMeasure group = it.next();
        this.measuresMap.put(group.getName(), new Integer(it.previousIndex()));
      } 
      removeMeasureVars(removed);
      removed.removePropertyChangeListener("valueClassName", this.measureClassChangeListener);
      getEventSupport().fireCollectionElementRemovedEvent("measures", removed, idx.intValue());
    } 
    return (JRCrosstabMeasure)removed;
  }
  
  protected void removeMeasureVars(JRDesignCrosstabMeasure measure) {
    removeVariable(measure.getVariable());
    for (Iterator colIt = this.columnGroups.iterator(); colIt.hasNext(); ) {
      JRCrosstabColumnGroup colGroup = colIt.next();
      removeTotalVar((JRCrosstabMeasure)measure, (JRCrosstabRowGroup)null, colGroup);
    } 
    for (Iterator rowIt = this.rowGroups.iterator(); rowIt.hasNext(); ) {
      JRCrosstabRowGroup rowGroup = rowIt.next();
      removeTotalVar((JRCrosstabMeasure)measure, rowGroup, (JRCrosstabColumnGroup)null);
      for (Iterator iterator = this.columnGroups.iterator(); iterator.hasNext(); ) {
        JRCrosstabColumnGroup colGroup = iterator.next();
        removeTotalVar((JRCrosstabMeasure)measure, rowGroup, colGroup);
      } 
    } 
  }
  
  public JRCrosstabMeasure removeMeasure(JRCrosstabMeasure measure) {
    return removeMeasure(measure.getName());
  }
  
  public boolean isRepeatColumnHeaders() {
    return this.repeatColumnHeaders;
  }
  
  public void setRepeatColumnHeaders(boolean repeatColumnHeaders) {
    boolean old = this.repeatColumnHeaders;
    this.repeatColumnHeaders = repeatColumnHeaders;
    getEventSupport().firePropertyChange("repeatColumnHeaders", old, this.repeatColumnHeaders);
  }
  
  public boolean isRepeatRowHeaders() {
    return this.repeatRowHeaders;
  }
  
  public void setRepeatRowHeaders(boolean repeatRowHeaders) {
    boolean old = this.repeatRowHeaders;
    this.repeatRowHeaders = repeatRowHeaders;
    getEventSupport().firePropertyChange("repeatRowHeaders", old, this.repeatRowHeaders);
  }
  
  public JRCrosstabCell[][] getCells() {
    return (JRCrosstabCell[][])this.crossCells;
  }
  
  public List getCellsList() {
    return this.cellsList;
  }
  
  public Map getCellsMap() {
    return this.cellsMap;
  }
  
  public void addCell(JRDesignCrosstabCell cell) throws JRException {
    String rowTotalGroup = cell.getRowTotalGroup();
    if (rowTotalGroup != null && !this.rowGroupsMap.containsKey(rowTotalGroup))
      throw new JRException("Row group " + rowTotalGroup + " does not exist."); 
    String columnTotalGroup = cell.getColumnTotalGroup();
    if (columnTotalGroup != null && !this.columnGroupsMap.containsKey(columnTotalGroup))
      throw new JRException("Row group " + columnTotalGroup + " does not exist."); 
    Object cellKey = new Pair(rowTotalGroup, columnTotalGroup);
    if (this.cellsMap.containsKey(cellKey))
      throw new JRException("Duplicate cell in crosstab."); 
    this.cellsMap.put(cellKey, cell);
    this.cellsList.add(cell);
    setCellOrigin(cell.getContents(), new JRCrosstabOrigin(this, (byte)7, rowTotalGroup, columnTotalGroup));
    getEventSupport().fireCollectionElementAddedEvent("cells", cell, this.cellsList.size() - 1);
  }
  
  public JRCrosstabCell removeCell(String rowTotalGroup, String columnTotalGroup) {
    Object cellKey = new Pair(rowTotalGroup, columnTotalGroup);
    JRCrosstabCell cell = (JRCrosstabCell)this.cellsMap.remove(cellKey);
    if (cell != null) {
      this.cellsList.remove(cell);
      getEventSupport().fireCollectionElementRemovedEvent("cells", cell, -1);
    } 
    return cell;
  }
  
  public JRCrosstabCell removeCell(JRCrosstabCell cell) {
    return removeCell(cell.getRowTotalGroup(), cell.getColumnTotalGroup());
  }
  
  public JRCrosstabParameter[] getParameters() {
    JRCrosstabParameter[] parameters = new JRCrosstabParameter[this.parametersList.size()];
    this.parametersList.toArray((Object[])parameters);
    return parameters;
  }
  
  public List getParametersList() {
    return this.parametersList;
  }
  
  public Map getParametersMap() {
    return this.parametersMap;
  }
  
  public JRExpression getParametersMapExpression() {
    return this.parametersMapExpression;
  }
  
  public void addParameter(JRCrosstabParameter parameter) throws JRException {
    if (this.parametersMap.containsKey(parameter.getName()))
      if (this.parametersMap.containsKey(parameter.getName()))
        throw new JRException("Duplicate declaration of parameter : " + parameter.getName());  
    this.parametersMap.put(parameter.getName(), parameter);
    this.parametersList.add(parameter);
    getEventSupport().fireCollectionElementAddedEvent("parameters", parameter, this.parametersList.size() - 1);
  }
  
  public JRCrosstabParameter removeParameter(String parameterName) {
    JRCrosstabParameter param = (JRCrosstabParameter)this.parametersMap.remove(parameterName);
    if (param != null) {
      int idx = this.parametersList.indexOf(param);
      if (idx >= 0)
        this.parametersList.remove(idx); 
      getEventSupport().fireCollectionElementRemovedEvent("parameters", param, idx);
    } 
    return param;
  }
  
  public JRCrosstabParameter removeParameter(JRCrosstabParameter parameter) {
    return removeParameter(parameter.getName());
  }
  
  public void setParametersMapExpression(JRExpression expression) {
    Object old = this.parametersMapExpression;
    this.parametersMapExpression = expression;
    getEventSupport().firePropertyChange("parametersMapExpression", old, this.parametersMapExpression);
  }
  
  public Map getVariablesMap() {
    JRVariable[] variables = getVariables();
    Map variablesMap = new HashMap();
    for (int i = 0; i < variables.length; i++)
      variablesMap.put(variables[i].getName(), variables[i]); 
    return variablesMap;
  }
  
  public JRVariable[] getVariables() {
    JRVariable[] variables = new JRVariable[this.variablesList.size()];
    this.variablesList.values().toArray((Object[])variables);
    return variables;
  }
  
  public int getColumnBreakOffset() {
    return this.columnBreakOffset;
  }
  
  public void setColumnBreakOffset(int columnBreakOffset) {
    int old = this.columnBreakOffset;
    this.columnBreakOffset = columnBreakOffset;
    getEventSupport().firePropertyChange("columnBreakOffset", old, this.columnBreakOffset);
  }
  
  public void preprocess() {
    setGroupVariablesClass(this.rowGroups);
    setGroupVariablesClass(this.columnGroups);
    calculateSizes();
  }
  
  protected void setGroupVariablesClass(List groups) {
    for (Iterator it = groups.iterator(); it.hasNext(); ) {
      JRDesignCrosstabGroup group = it.next();
      JRCrosstabBucket bucket = group.getBucket();
      if (bucket != null) {
        JRExpression expression = bucket.getExpression();
        if (expression != null)
          group.designVariable.setValueClassName(expression.getValueClassName()); 
      } 
    } 
  }
  
  protected void calculateSizes() {
    setWhenNoDataCellSize();
    createCellMatrix();
    int rowHeadersWidth = calculateRowHeadersSizes();
    int colHeadersHeight = calculateColumnHeadersSizes();
    if (this.headerCell != null) {
      this.headerCell.setWidth(rowHeadersWidth);
      this.headerCell.setHeight(colHeadersHeight);
    } 
  }
  
  protected void setWhenNoDataCellSize() {
    if (this.whenNoDataCell != null) {
      this.whenNoDataCell.setWidth(getWidth());
      this.whenNoDataCell.setHeight(getHeight());
    } 
  }
  
  protected void createCellMatrix() {
    this.crossCells = new JRDesignCrosstabCell[this.rowGroups.size() + 1][this.columnGroups.size() + 1];
    for (Iterator it = this.cellsList.iterator(); it.hasNext(); ) {
      JRDesignCrosstabCell crosstabCell = it.next();
      JRDesignCellContents contents = (JRDesignCellContents)crosstabCell.getContents();
      String rowTotalGroup = crosstabCell.getRowTotalGroup();
      int rowGroupIndex = (rowTotalGroup == null) ? (rowGroupIndex = this.rowGroups.size()) : ((Integer)this.rowGroupsMap.get(rowTotalGroup)).intValue();
      Integer cellWidth = crosstabCell.getWidth();
      if (cellWidth != null)
        contents.setWidth(cellWidth.intValue()); 
      String columnTotalGroup = crosstabCell.getColumnTotalGroup();
      int columnGroupIndex = (columnTotalGroup == null) ? (columnGroupIndex = this.columnGroups.size()) : ((Integer)this.columnGroupsMap.get(columnTotalGroup)).intValue();
      Integer cellHeight = crosstabCell.getHeight();
      if (cellHeight != null)
        contents.setHeight(cellHeight.intValue()); 
      this.crossCells[rowGroupIndex][columnGroupIndex] = crosstabCell;
    } 
    inheritCells();
  }
  
  protected JRDesignCrosstabRowGroup getRowGroup(int rowGroupIndex) {
    return this.rowGroups.get(rowGroupIndex);
  }
  
  protected JRDesignCrosstabColumnGroup getColumnGroup(int columnGroupIndex) {
    return this.columnGroups.get(columnGroupIndex);
  }
  
  protected void inheritCells() {
    for (int i = this.rowGroups.size(); i >= 0; i--) {
      for (int j = this.columnGroups.size(); j >= 0; j--) {
        boolean used = ((i == this.rowGroups.size() || getRowGroup(i).hasTotal()) && (j == this.columnGroups.size() || getColumnGroup(j).hasTotal()));
        if (used) {
          if (this.crossCells[i][j] == null) {
            inheritCell(i, j);
            if (this.crossCells[i][j] == null) {
              this.crossCells[i][j] = emptyCell(i, j);
              inheritCellSize(i, j);
            } 
          } else {
            inheritCellSize(i, j);
          } 
        } else {
          this.crossCells[i][j] = null;
        } 
      } 
    } 
  }
  
  private JRDesignCrosstabCell emptyCell(int i, int j) {
    JRDesignCrosstabCell emptyCell = new JRDesignCrosstabCell();
    if (i < this.rowGroups.size())
      emptyCell.setRowTotalGroup(((JRCrosstabRowGroup)this.rowGroups.get(i)).getName()); 
    if (j < this.columnGroups.size())
      emptyCell.setColumnTotalGroup(((JRCrosstabColumnGroup)this.columnGroups.get(j)).getName()); 
    return emptyCell;
  }
  
  protected void inheritCellSize(int i, int j) {
    JRDesignCrosstabCell cell = this.crossCells[i][j];
    JRDesignCellContents contents = (JRDesignCellContents)cell.getContents();
    if (contents.getWidth() == Integer.MIN_VALUE)
      if (i < this.rowGroups.size()) {
        JRDesignCrosstabCell rowCell = this.crossCells[this.rowGroups.size()][j];
        if (rowCell != null)
          contents.setWidth(rowCell.getContents().getWidth()); 
      } else {
        for (int k = j + 1; k <= this.columnGroups.size(); k++) {
          if (this.crossCells[i][k] != null) {
            contents.setWidth(this.crossCells[i][k].getContents().getWidth());
            break;
          } 
        } 
      }  
    if (contents.getHeight() == Integer.MIN_VALUE)
      if (j < this.columnGroups.size()) {
        JRDesignCrosstabCell colCell = this.crossCells[i][this.columnGroups.size()];
        if (colCell != null)
          contents.setHeight(colCell.getContents().getHeight()); 
      } else {
        for (int k = i + 1; k <= this.rowGroups.size(); k++) {
          if (this.crossCells[k][j] != null)
            contents.setHeight(this.crossCells[k][j].getContents().getHeight()); 
        } 
      }  
  }
  
  protected void inheritCell(int i, int j) {
    JRDesignCrosstabCell inheritedCell = null;
    if (j < this.columnGroups.size()) {
      JRDesignCrosstabCell colCell = this.crossCells[this.rowGroups.size()][j];
      JRDesignCellContents colContents = (colCell == null) ? null : (JRDesignCellContents)colCell.getContents();
      for (int k = j + 1; inheritedCell == null && k <= this.columnGroups.size(); k++) {
        JRDesignCrosstabCell cell = this.crossCells[i][k];
        if (cell != null) {
          JRDesignCellContents contents = (JRDesignCellContents)cell.getContents();
          if (colContents == null || contents.getWidth() == colContents.getWidth())
            inheritedCell = cell; 
        } 
      } 
    } 
    if (inheritedCell == null && i < this.rowGroups.size()) {
      JRDesignCrosstabCell rowCell = this.crossCells[i][this.columnGroups.size()];
      JRDesignCellContents rowContents = (rowCell == null) ? null : (JRDesignCellContents)rowCell.getContents();
      for (int k = i + 1; inheritedCell == null && k <= this.rowGroups.size(); k++) {
        JRDesignCrosstabCell cell = this.crossCells[k][j];
        if (cell != null) {
          JRDesignCellContents contents = (JRDesignCellContents)cell.getContents();
          if (rowContents == null || contents.getHeight() == rowContents.getHeight())
            inheritedCell = cell; 
        } 
      } 
    } 
    this.crossCells[i][j] = inheritedCell;
  }
  
  protected int calculateRowHeadersSizes() {
    int widthSum = 0;
    for (int i = this.rowGroups.size() - 1, heightSum = 0; i >= 0; i--) {
      JRDesignCrosstabRowGroup group = this.rowGroups.get(i);
      widthSum += group.getWidth();
      JRDesignCrosstabCell cell = this.crossCells[i + 1][this.columnGroups.size()];
      if (cell != null)
        heightSum += cell.getContents().getHeight(); 
      JRDesignCellContents header = (JRDesignCellContents)group.getHeader();
      header.setHeight(heightSum);
      header.setWidth(group.getWidth());
      if (group.hasTotal()) {
        JRDesignCellContents totalHeader = (JRDesignCellContents)group.getTotalHeader();
        totalHeader.setWidth(widthSum);
        JRDesignCrosstabCell totalCell = this.crossCells[i][this.columnGroups.size()];
        if (totalCell != null)
          totalHeader.setHeight(totalCell.getContents().getHeight()); 
      } 
    } 
    return widthSum;
  }
  
  protected int calculateColumnHeadersSizes() {
    int heightSum = 0;
    for (int i = this.columnGroups.size() - 1, widthSum = 0; i >= 0; i--) {
      JRDesignCrosstabColumnGroup group = this.columnGroups.get(i);
      heightSum += group.getHeight();
      JRDesignCrosstabCell cell = this.crossCells[this.rowGroups.size()][i + 1];
      if (cell != null)
        widthSum += cell.getContents().getWidth(); 
      JRDesignCellContents header = (JRDesignCellContents)group.getHeader();
      header.setHeight(group.getHeight());
      header.setWidth(widthSum);
      if (group.hasTotal()) {
        JRDesignCellContents totalHeader = (JRDesignCellContents)group.getTotalHeader();
        totalHeader.setHeight(heightSum);
        JRDesignCrosstabCell totalCell = this.crossCells[this.rowGroups.size()][i];
        if (totalCell != null)
          totalHeader.setWidth(totalCell.getContents().getWidth()); 
      } 
    } 
    return heightSum;
  }
  
  public JRCellContents getWhenNoDataCell() {
    return this.whenNoDataCell;
  }
  
  public void setWhenNoDataCell(JRDesignCellContents whenNoDataCell) {
    Object old = this.whenNoDataCell;
    this.whenNoDataCell = whenNoDataCell;
    setCellOrigin(this.whenNoDataCell, new JRCrosstabOrigin(this, (byte)2));
    getEventSupport().firePropertyChange("whenNoDataCell", old, this.whenNoDataCell);
  }
  
  public JRElement getElementByKey(String elementKey) {
    return JRBaseCrosstab.getElementByKey(this, elementKey);
  }
  
  public byte getMode() {
    return JRStyleResolver.getMode((JRCommonElement)this, (byte)2);
  }
  
  public JRCellContents getHeaderCell() {
    return this.headerCell;
  }
  
  public void setHeaderCell(JRDesignCellContents headerCell) {
    Object old = this.headerCell;
    this.headerCell = headerCell;
    setCellOrigin(this.headerCell, new JRCrosstabOrigin(this, (byte)1));
    getEventSupport().firePropertyChange("headerCell", old, this.headerCell);
  }
  
  protected void measureClassChanged(JRDesignCrosstabMeasure measure, String valueClassName) {
    for (Iterator colIt = this.columnGroups.iterator(); colIt.hasNext(); ) {
      JRCrosstabColumnGroup colGroup = colIt.next();
      setTotalVarClass((JRCrosstabMeasure)measure, (JRCrosstabRowGroup)null, colGroup, valueClassName);
    } 
    for (Iterator rowIt = this.rowGroups.iterator(); rowIt.hasNext(); ) {
      JRCrosstabRowGroup rowGroup = rowIt.next();
      setTotalVarClass((JRCrosstabMeasure)measure, rowGroup, (JRCrosstabColumnGroup)null, valueClassName);
      for (Iterator iterator = this.columnGroups.iterator(); iterator.hasNext(); ) {
        JRCrosstabColumnGroup colGroup = iterator.next();
        setTotalVarClass((JRCrosstabMeasure)measure, rowGroup, colGroup, valueClassName);
      } 
    } 
  }
  
  protected void setTotalVarClass(JRCrosstabMeasure measure, JRCrosstabRowGroup rowGroup, JRCrosstabColumnGroup colGroup, String valueClassName) {
    JRDesignVariable variable = getVariable(getTotalVariableName(measure, rowGroup, colGroup));
    variable.setValueClassName(valueClassName);
  }
  
  private void addVariable(JRVariable variable) {
    this.variablesList.put(variable.getName(), variable);
  }
  
  private void removeVariable(JRVariable variable) {
    removeVariable(variable.getName());
  }
  
  private void removeVariable(String varName) {
    this.variablesList.remove(varName);
  }
  
  private JRDesignVariable getVariable(String varName) {
    return (JRDesignVariable)this.variablesList.get(varName);
  }
  
  public byte getRunDirection() {
    return this.runDirection;
  }
  
  public void setRunDirection(byte runDirection) {
    byte old = this.runDirection;
    this.runDirection = runDirection;
    getEventSupport().firePropertyChange("runDirection", old, this.runDirection);
  }
  
  protected void setCellOrigin(JRCellContents cell, JRCrosstabOrigin origin) {
    if (cell instanceof JRDesignCellContents)
      setCellOrigin((JRDesignCellContents)cell, origin); 
  }
  
  protected void setCellOrigin(JRDesignCellContents cell, JRCrosstabOrigin origin) {
    if (cell != null)
      cell.setOrigin(origin); 
  }
  
  protected void setParent(JRDesignCrosstabGroup group) {
    if (group != null)
      group.setParent(this); 
  }
  
  public Object clone() {
    JRDesignCrosstab clone = (JRDesignCrosstab)super.clone();
    if (this.parametersList != null) {
      clone.parametersList = new ArrayList(this.parametersList.size());
      clone.parametersMap = new HashMap(this.parametersList.size());
      for (int i = 0; i < this.parametersList.size(); i++) {
        JRCrosstabParameter parameter = (JRCrosstabParameter)((JRCrosstabParameter)this.parametersList.get(i)).clone();
        clone.parametersList.add(parameter);
        clone.parametersMap.put(parameter.getName(), parameter);
      } 
    } 
    if (this.variablesList != null) {
      clone.variablesList = new SequencedHashMap(this.variablesList.size());
      for (Iterator it = this.variablesList.sequence().iterator(); it.hasNext(); ) {
        Object key = it.next();
        JRVariable variable = (JRVariable)this.variablesList.get(key);
        clone.variablesList.put(variable.getName(), variable);
      } 
    } 
    if (this.parametersMapExpression != null)
      clone.parametersMapExpression = (JRExpression)this.parametersMapExpression.clone(); 
    if (this.dataset != null)
      clone.dataset = (JRDesignCrosstabDataset)this.dataset.clone(); 
    if (this.rowGroups != null) {
      clone.rowGroups = new ArrayList(this.rowGroups.size());
      clone.rowGroupsMap = new HashMap(this.rowGroups.size());
      for (int i = 0; i < this.rowGroups.size(); i++) {
        JRCrosstabRowGroup group = (JRCrosstabRowGroup)((JRCrosstabRowGroup)this.rowGroups.get(i)).clone();
        clone.rowGroups.add(group);
        clone.rowGroupsMap.put(group.getName(), new Integer(i));
      } 
    } 
    if (this.columnGroups != null) {
      clone.columnGroups = new ArrayList(this.columnGroups.size());
      clone.columnGroupsMap = new HashMap(this.columnGroups.size());
      for (int i = 0; i < this.columnGroups.size(); i++) {
        JRCrosstabColumnGroup group = (JRCrosstabColumnGroup)((JRCrosstabColumnGroup)this.columnGroups.get(i)).clone();
        clone.columnGroups.add(group);
        clone.columnGroupsMap.put(group.getName(), new Integer(i));
      } 
    } 
    if (this.measures != null) {
      clone.measures = new ArrayList(this.measures.size());
      clone.measuresMap = new HashMap(this.measures.size());
      for (int i = 0; i < this.measures.size(); i++) {
        JRCrosstabMeasure measure = (JRCrosstabMeasure)((JRCrosstabMeasure)this.measures.get(i)).clone();
        clone.measures.add(measure);
        clone.measuresMap.put(measure.getName(), new Integer(i));
      } 
    } 
    if (this.cellsList != null) {
      clone.cellsList = new ArrayList(this.cellsList.size());
      clone.cellsMap = new HashMap(this.cellsList.size());
      for (int i = 0; i < this.cellsList.size(); i++) {
        JRDesignCrosstabCell cell = (JRDesignCrosstabCell)((JRDesignCrosstabCell)this.cellsList.get(i)).clone();
        clone.cellsList.add(cell);
        clone.cellsMap.put(new Pair(cell.getRowTotalGroup(), cell.getColumnTotalGroup()), cell);
      } 
    } 
    if (this.whenNoDataCell != null)
      clone.whenNoDataCell = (JRDesignCellContents)this.whenNoDataCell.clone(); 
    if (this.headerCell != null)
      clone.headerCell = (JRDesignCellContents)this.headerCell.clone(); 
    return clone;
  }
  
  public List getRowGroupsList() {
    return this.rowGroups;
  }
  
  public Map getRowGroupIndicesMap() {
    return this.rowGroupsMap;
  }
  
  public List getColumnGroupsList() {
    return this.columnGroups;
  }
  
  public Map getColumnGroupIndicesMap() {
    return this.columnGroupsMap;
  }
  
  public List getMesuresList() {
    return this.measures;
  }
  
  public Map getMeasureIndicesMap() {
    return this.measuresMap;
  }
}
