/*
 * BIRules.java
 *
 * Copyright © 1994-2004, Oracle. All rights reserved. Oracle grants
 * you ("Licensee") a non-exclusive, royalty-free, worldwide license to use,
 * modify and redistribute this software in source and binary code form, provided
 * that (i) this copyright notice and license appear on all copies of the
 * software; (ii) Licensee does not utilize the software in a manner disparaging
 * to Oracle; and (iii) Licensee does not utilize the software in a manner that
 * violates the terms of any Licensee agreement with Oracle.
 */
package samples.javaclient;

import java.awt.BorderLayout;
import java.awt.Color;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowAdapter;

import java.net.URL;

import java.util.Enumeration;
import java.util.Vector;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;

import oracle.dss.crosstab.Crosstab;

import oracle.dss.dataSource.client.QueryManager;

import oracle.dss.dataSource.common.Query;

import oracle.dss.dataView.managers.ViewFormat;
import oracle.dss.dataView.managers.ViewStyle;

import oracle.dss.rules.DiscriminatorRule;
import oracle.dss.rules.Rule;
import oracle.dss.rules.RuleBundle;

import oracle.dss.rules.discriminator.CompositeDiscriminator;
import oracle.dss.rules.discriminator.NumberValueDiscriminator;
import oracle.dss.rules.discriminator.QDRDiscriminator;

import oracle.dss.util.DataAccess;
import oracle.dss.util.DataDirector;
import oracle.dss.util.DataDirectorException;
import oracle.dss.util.EdgeOutOfRangeException;
import oracle.dss.util.LayerMetadataMap;
import oracle.dss.util.LayerOutOfRangeException;
import oracle.dss.util.QDR;
import oracle.dss.util.QDRMember;
import oracle.dss.util.SliceOutOfRangeException;

/**
 * This sample explores the use of rules to specify custom formatting for a 
 * Crosstab.
 * <P>
 * BIRules provides UI options to create and close 
 * Crosstabs, and to toggle several different rules on and off.  The available
 * rules enable a user to:
 * <ol>
 * <li>format values in the Sales measure as currency</li>
 * <li>format a single databody cell with a border and font style</li>
 * <li>format a metadata member with particular colors and font style</li>
 * <li>format databody cells belonging to divisions in the Product layer
 * with bold and italic font styles</li>
 * <li>stoplight format Sales values greater than ten million in green</li>
 * <li>stoplight format Sales values less than or equal to ten million
 * in yellow</li>
 * <li>stoplight format Sales values less than or equal to two million
 * in red</li>
 * </ol>
 * <P>
 * These examples demonstrate:
 * <ol>
 * <li>how to create rules based on QDRs, values, and combinations of multiple
 * criteria</li>
 * <li>how to create number formatting rules for the databody</li>
 * <li>how to create style rules for the databody and the headers</li>
 * <li>the relationship between the order of rules in a manager and rule 
 * precedence</li>
 * </ol>
 * 
 * @see BIFrame
 */
public class BIRules extends JFrame{

  /**
   * Names for RuleBundles that may be added to the 
   * GridViewDatabodyStyleManager.  These names are used to identify the
   * RuleBundles.  
   */
  /**
   * Name for the RuleBundle containing databody style rules not related to
   * stoplight formatting.
   */
  protected static final String DIVISION_BUNDLE_NAME  = "Division";
  /**
   * Name for the RuleBundle containing databody style rules related to 
   * stoplight formatting.
   */
  protected static final String STOPLIGHT_BUNDLE_NAME = "Stoplight";

  /**
   * IDs for RuleBundles that may be added to the GridViewDatabodyStyleManager.
   * The values of these IDs are used to determine the relative order in which
   * the RuleBundles should be maintained in the manager, with higher values
   * taking precedence.  
   */
  /**
   * ID for the RuleBundle containing databody style rules not related to
   * stoplight formatting.
   */
  protected static final int DIVISION_BUNDLE  = 0;
  /**
   * ID for the RuleBundle containing databody style rules related to
   * stoplight formatting.
   */
  protected static final int STOPLIGHT_BUNDLE = 1;

  /**
   * IDs for GridViewDatabodyStyleManager Rules not related to stoplight
   * formatting.  These Rules belong to the DIVISION_BUNDLE_NAME RuleBundle.
   * The values of these IDs are used to determine the relative order in which
   * the Rules should be maintained in the RuleBundle, with higher values
   * taking precedence.
   * 
   * @see #DIVISION_BUNDLE_NAME
   */
  /**
   * ID for the Rule to format cells associated with the Accessory, Audio, 
   * and Video Division members of the Product layer.  
   */
  protected static final int DIVISION_RULE    = 0;
  /**
   * ID for the Rule to format the cell associated with the World member of the
   * Geography layer, the Audio Division member of the Product layer, and the
   * Sales measure.  
   */
  protected static final int SINGLE_CELL_RULE = 1;

  /**
   * IDs for GridViewDatabodyStyleManager Rules for stoplight formatting.
   * These Rules belong to the STOPLIGHT_BUNDLE_NAME RuleBundle.  The values of
   * these IDs are used to determine the relative order in which the Rules 
   * should be maintained in the RuleBundle, with higher values taking 
   * precedence.
   * 
   * @see #STOPLIGHT_BUNDLE_NAME
   */
  /**
   * ID for the Rule to format Sales cells whose values are greater than ten
   * million with a green background.  
   */
  protected static final int GREEN_STOPLIGHT_RULE  = 0;
  /**
   * ID for the Rule to format Sales cells whose values are less than or equal
   * to ten million with a yellow background.
   */
  protected static final int YELLOW_STOPLIGHT_RULE = 1;
  /**
   * ID for the Rule to format Sales cells whose values are less than or equal
   * to two million with a red background.
   */
  protected static final int RED_STOPLIGHT_RULE    = 2;

  private BIFrame m_frame;
  private JPanel m_panel;
  private JMenuItem m_sampleMenu;

  // toggle currency formatting
  private JCheckBoxMenuItem m_currencyFormattingCheckBox;
  // toggle single cell formatting
  private JCheckBoxMenuItem m_singleCellFormattingCheckBox;
  // toggle header cell formatting
  private JCheckBoxMenuItem m_headerFormattingCheckBox;
  // toggle division formatting
  private JCheckBoxMenuItem m_divisionFormattingCheckBox;
  // toggle green stoplight formatting
  private JCheckBoxMenuItem m_greenStoplightCheckBox;
  // toggle yellow stoplight formatting
  private JCheckBoxMenuItem m_yellowStoplightCheckBox;
  // toggle red stoplight formatting
  private JCheckBoxMenuItem m_redStoplightCheckBox;

  // toggle currency formatting
  private JToggleButton m_currencyFormattingButton;
  // toggle single cell formatting
  private JToggleButton m_singleCellFormattingButton;
  // toggle header cell formatting
  private JToggleButton m_headerFormattingButton;
  // toggle division formatting
  private JToggleButton m_divisionFormattingButton;
  // toggle green stoplight formatting
  private JToggleButton m_greenStoplightButton;
  // toggle yellow stoplight formatting
  private JToggleButton m_yellowStoplightButton;
  // toggle red stoplight formatting
  private JToggleButton m_redStoplightButton;

  // flags to track toggle states of menu items and toolbar buttons
  private boolean m_currencyFormattingFlag;     // currency formatting flag
  private boolean m_singleCellFormattingFlag;   // single cell formatting flag
  private boolean m_headerFormattingFlag;       // header formatting flag
  private boolean m_divisionFormattingFlag;     // division formatting flag
  private boolean m_greenStoplightFlag;         // green stoplight flag
  private boolean m_yellowStoplightFlag;        // yellow stoplight flag
  private boolean m_redStoplightFlag;           // red stoplight flag

  private Crosstab m_crosstab;  // Crosstab instance

  // flags to track which databody style RuleBundles are set on the Crosstab
  private boolean[] m_databodyStyleRuleBundles;

  // flags to track which databody style rules are set on the Crosstab
  private boolean[][] m_databodyStyleRules;

  /** 
   * Constructor for BIRules.
   *
   * @param title The title of the JFrame.
   */
  public BIRules(BIFrame parentFrame, String title) {
    super(title);
    m_frame = parentFrame;
    init();
  }

  /**
   * Add a Rule to a RuleBundle in its appropriate place.  This method only
   * works for RuleBundles associated with the GridViewDatabodyStyleManager.
   * 
   * @param ruleID the ID of the Rule to be added
   * @param rule the Rule to be added
   * @param ruleBundleID the ID of the RuleBundle to which the Rule will be
   *                     added
   * @param bundle the RuleBundle to which the Rule will be added
   * 
   * @see #DIVISION_BUNDLE
   * @see #STOPLIGHT_BUNDLE
   * 
   * @see #DIVISION_RULE
   * @see #SINGLE_CELL_RULE
   * @see #GREEN_STOPLIGHT_RULE
   * @see #YELLOW_STOPLIGHT_RULE
   * @see #RED_STOPLIGHT_RULE
   */
  protected void addDatabodyStyleRule(int ruleID, Rule rule, 
    int ruleBundleID, RuleBundle bundle) {
    // Count the flags of Rules that should precede the new Rule to find 
    // the index at which the new Rule is to be inserted in the bundle.
    int index = 0;
    boolean[] rules = m_databodyStyleRules[ruleBundleID];
    if (bundle.getCount() > 0) {
      for (int i = 0; i < rules.length && i < ruleID; i++) {
        if (rules[i]) {
          index++;
        }
      }
    }

    // Insert the Rule at the determined location, and mark the flag
    // indicating that the Rule has been set on the Crosstab.
    bundle.addRule(rule, index);
    rules[ruleID] = true;
  }

  /**
   * Apply or remove currency formatting in the Sales measure in the 
   * databody.
   * If <code>bApply</code> is <code>true</code>, this method creates a Rule to
   * format data in the Sales measure as currency, and adds the Rule to 
   * the GridViewFormatManager.
   * If <code>bApply</code> is <code>false</code>, this method removes from the 
   * GridViewFormatManager the previously created Rule to format Sales
   * data as currency.  
   * 
   * The GridViewFormatManager is used to specify number and date formatting
   * in the databody.
   * 
   * @param bApply <code>true</code> to apply currency formatting, 
   *               <code>false</code> to remove it
   */
  protected void applyCurrencyFormatting(boolean bApply) {
    // Get the Vector of RuleBundles from the GridViewFormatManager.
    Vector ruleBundles = m_crosstab.getGridViewFormatManager().getBundles();

    // If applying formatting, create a Rule, add it to a RuleBundle, and 
    // add the RuleBundle to the Vector.
    if (bApply) {
      // If the Vector is null, create a new one.
      if (ruleBundles == null) {
        ruleBundles = new Vector();
      }

      // Create a new RuleBundle which will be added to the Vector.  
      // For the purposes of this sample application, there will be only one 
      // Rule in one RuleBundle in the Vector on the GridViewFormatManager.
      RuleBundle bundle = new RuleBundle();

      // Key Concept
      // Create a QDR for the Sales measure, to which the Rule will apply.
      QDR salesQDR = new QDR("MDM!MEASUREDIMENSION", "MDM!MEASUREDIMENSION",
        "MDM!M_CS_OLAP.SHAWSALES.AMOUNT_SOLD");

      // Key Concept
      // Create a QDRDiscriminator based on the Sales QDR.  The operator for 
      // the discriminator is set to SUPERSET.  This discriminator will apply
      // to any databody cell associated with the Sales measure, no matter how 
      // many other layers the cell is associated with.  Only databody cells
      // will be affected, because the default implementation of the
      // GridViewFormatManager does not format metadata in the headers.
      QDRDiscriminator qdrDisc = new QDRDiscriminator(salesQDR,
        QDRDiscriminator.SUPERSET);

      // Key Concept
      // Create the ViewFormat that will affect the cells for which the
      // discriminator applies.  Only the properties that should take effect
      // in the Rule should be set.  This ViewFormat will format data as
      // currency.  
      ViewFormat viewFormat = new ViewFormat();
      viewFormat.setNumberType(ViewFormat.NUMTYPE_CURRENCY);

      // Key Concept
      // Create the DiscriminatorRule and set its discriminator and Mergeable.
      // The ViewFormat is a Mergeable, which means that it can be 
      // merged with other ViewFormats and used in Rules.  
      // Only properties that have been explicitly set on
      // the ViewFormat will be merged as part of the rule resolution process.
      // ViewFormat is the only type of Mergeable that may be used with Rules
      // in the GridViewFormatManager.
      DiscriminatorRule discRule = new DiscriminatorRule();
      discRule.setDiscriminator(qdrDisc);
      discRule.setFixedMergeable(viewFormat);

      // Key Concept
      // Add the Rule to the RuleBundle.  Every Rule must belong to a 
      // RuleBundle, and the RuleBundle added to the Vector on the manager, 
      // in order for the Rule to run.  
      bundle.addRule(discRule);

      // Key Concept
      // Add the RuleBundle to the Vector, which will be set on the manager.
      ruleBundles.addElement(bundle);
    }
    // If removing formatting, remove the whole RuleBundle containing the 
    // Rule from the Vector, because it only contains this one Rule.
    else {
      if (ruleBundles != null && !ruleBundles.isEmpty()) {
        ruleBundles.removeElementAt(0);
      }
    }

    // Key Concept
    // Set the updated Vector of RuleBundles on the GridViewFormatManager.  
    // Since the Vector is passed by value, no modifications have been made
    // to the instance maintained by the manager so far.
    // It is necessary to call setBundles() to update the manager
    // with the new Vector.  
    m_crosstab.getGridViewFormatManager().setBundles(m_crosstab, ruleBundles);
  }

  /**
   * Apply or remove formatting for the databody cells associated with divisions
   * in the Product layer.  The divisions are Accessory, Audio, and Video.  
   * If <code>bApply</code> is <code>true</code>, this method creates a Rule to
   * format the cells, and adds the Rule to the GridViewDatabodyStyleManager.
   * If <code>bApply</code> is <code>false</code>, this method removes from the 
   * GridViewDatabodStyleManager the previously created Rule to format the 
   * cells.  
   * The Rule will format the cells with a bold and italic font.
   * 
   * The GridViewDatabodyStyleManager is used to specify style formatting
   * in the databody.
   * 
   * @param bApply <code>true</code> to apply formatting, 
   *               <code>false</code> to remove it
   */
  protected void applyDivisionFormatting(boolean bApply) {
    // Get the Vector of RuleBundles from the GridViewDatabodyStyleManager.
    Vector ruleBundles = 
      m_crosstab.getGridViewDatabodyStyleManager().getBundles();

    // If applying formatting, create a Rule, add it to a RuleBundle, and 
    // add the RuleBundle to the Vector.
    if (bApply) {
      // If the Vector is null, create a new one.
      if (ruleBundles == null) {
        ruleBundles = new Vector();
      }

      // Key Concept
      // Get from the Vector the RuleBundle that will contain the new Rule.  
      // Because the GridViewDatabodyStyleManager Vector will contain multiple
      // RuleBundles in this sample, each RuleBundle it contains is named.
      // The RuleBundle to which this Rule will be added is identified by
      // DIVISION_BUNDLE_NAME.  If there is no such named RuleBundle in the
      // Vector already, the named bundle is created.  
      RuleBundle bundle = getDatabodyStyleRuleBundle(DIVISION_BUNDLE_NAME,
        ruleBundles);
      if (bundle == null) {
        bundle = new RuleBundle();
        bundle.setID(DIVISION_BUNDLE_NAME);
      }

      // Key Concept
      // Create a QDR for the cells to which the Rule will apply.
      // The cells are those associated with the Accessory, Audio, and Video
      // Division memebers of the Product layer.
      QDR divisionQDR = new QDR("MDM!MEASUREDIMENSION");
      Vector members = new Vector();
      members.addElement("201");  // IDs of subcategories of products
      members.addElement("202");
      members.addElement("203");
      QDRMember qdrMember = new QDRMember(QDRMember.LIST, members);
      divisionQDR.addDimMemberPair("MDM!D_CS_OLAP.SHAWPRODUCTS", qdrMember);

      // Key Concept
      // Create a QDRDiscriminator based on the above QDR.  The operator for 
      // the discriminator is set to SUPERSET.  This discriminator will apply
      // to any databody cell associated with the above QDR, no matter how 
      // many other layers the cell is associated with.  For instance, a
      // cell may belong to any member of the Geography layer, which is
      // initially on the column edge.
      QDRDiscriminator qdrDisc = new QDRDiscriminator(divisionQDR,
        QDRDiscriminator.SUPERSET);

      // Key Concept
      // Create the ViewStyle that will affect the cells for which the
      // discriminator applies.  Only the properties that should take effect
      // in the Rule should be set.  This ViewStyle will make the font bold
      // and italic.  
      ViewStyle viewStyle = new ViewStyle();
      viewStyle.setFontBold(true);
      viewStyle.setFontItalic(true);

      // Key Concept
      // Create the DiscriminatorRule and set its discriminator and Mergeable.
      // The ViewStyle is a Mergeable, which means that it can be 
      // merged with other ViewStyles and used in Rules.  
      // Only properties that have been explicitly set on
      // the ViewStyle will be merged as part of the rule resolution process.
      // ViewStyle is the only type of Mergeable that may be used with Rules
      // in the GridViewDatabodyStyleManager.
      DiscriminatorRule discRule = new DiscriminatorRule();
      discRule.setDiscriminator(qdrDisc);
      discRule.setFixedMergeable(viewStyle);

      // Key Concept
      // Add the Rule to the RuleBundle.  Every Rule must belong to a 
      // RuleBundle, and the RuleBundle added to the Vector on the manager, 
      // in order for the Rule to run.  The order of Rules within a RuleBundle
      // and RuleBundles within a Vector matters.  Rules are applied first to 
      // last, so later Rules take precedence.  Because each RuleBundle in 
      // the Vector of bundles on the GridViewDatabodyStyleManager may contain 
      // multiple Rules in this sample, this particular Rule must be added in 
      // the correct position relative to other Rules in the RuleBundle. 
      addDatabodyStyleRule(DIVISION_RULE, discRule, DIVISION_BUNDLE, bundle);

      // Key Concept
      // Add the RuleBundle to the Vector, which will be set on the manager.
      // Because in this sample the Vector of bundles on the
      // GridViewDatabodyStyleManager may contain multiple RuleBundles, this
      // particular RuleBundle must be added to the Vector in the correct
      // position relative to the other RuleBundles in the Vector.  
      setDatabodyStyleRuleBundle(DIVISION_BUNDLE, bundle, ruleBundles);
    }
    // Key Concept
    // If removing formatting, remove the Rule from its RuleBundle.  If that 
    // leaves the RuleBundle empty, then remove the entire RuleBundle from
    // the Vector.  
    else {
      if (ruleBundles != null && !ruleBundles.isEmpty()) {
        // Get the RuleBundle that contains this Rule.
        RuleBundle ruleBundle = 
          getDatabodyStyleRuleBundle(DIVISION_BUNDLE_NAME, ruleBundles);

        // If the RuleBundle is in the Vector, remove this Rule from it.  If
        // the RuleBundle contains other Rules, reset it in the Vector.  If the
        // RuleBundle is empty, remove it from the Vector completely.  
        if (ruleBundle != null) {
          if (removeDatabodyStyleRule(DIVISION_RULE, DIVISION_BUNDLE, 
            ruleBundle)) {
            if (ruleBundle.getCount() > 0) {
              setDatabodyStyleRuleBundle(DIVISION_BUNDLE, ruleBundle, 
                ruleBundles);
            }
            else {
              removeDatabodyStyleRuleBundle(DIVISION_BUNDLE, ruleBundle.getID(),
                ruleBundles);
            }
          }
        }
      }
    }

    // Key Concept
    // Set the updated Vector of RuleBundles on the 
    // GridViewDatabodyStyleManager.  Since the Vector is passed by value, no 
    // modifications have been made to the instance maintained by the manager 
    // so far.  It is necessary to call setBundles() to update the manager
    // with the new Vector.
    m_crosstab.getGridViewDatabodyStyleManager().setBundles(m_crosstab,
      ruleBundles);
  }

  /**
   * Apply or remove green stoplight formatting for the databody cells 
   * associated with the Sales measure whose value is greater than ten million.  
   * If <code>bApply</code> is <code>true</code>, this method creates a Rule to
   * format the cells, and adds the Rule to the GridViewDatabodyStyleManager.
   * If <code>bApply</code> is <code>false</code>, this method removes from the 
   * GridViewDatabodStyleManager the previously created Rule to format the 
   * cells.  
   * The Rule will format cells where Sales is greater than ten million with a 
   * green background.
   * 
   * The GridViewDatabodyStyleManager is used to specify style formatting
   * in the databody.
   * 
   * @param bApply <code>true</code> to apply formatting, 
   *               <code>false</code> to remove it
   */
  protected void applyGreenStoplightFormatting(boolean bApply) {
    // Variables for the value discriminator and ViewStyle that will be used
    // in the Rule.  
    NumberValueDiscriminator nvDisc = null;
    ViewStyle viewStyle = null;

    // If formatting is being applied, initialize the value discriminator and
    // ViewStyle.  If formatting is being removed, these objects do not need
    // to be initialized.
    if (bApply) {
      // Key Concept
      // Create a NumberValueDiscriminator that will apply to any cell whose 
      // value is greater than ten million.
      nvDisc = new NumberValueDiscriminator(10000000, 
        NumberValueDiscriminator.GT);

      // Key Concept
      // Create the ViewStyle that will affect the cells for which the
      // discriminator applies.  Only the properties that should take effect
      // in the Rule should be set.  This ViewStyle will make the 
      // background green.  
      viewStyle = new ViewStyle();
      viewStyle.setBackground(Color.green);
    }

    // Call the helper method that will actually create the Rule to do the
    // green stoplight formatting, passing in the relevant information.  
    applyStoplightFormatting(bApply, GREEN_STOPLIGHT_RULE, viewStyle, nvDisc);
  }

  /**
   * Apply or remove formatting in the Americas member of the Geography layer
   * in the header.  
   * If <code>bApply</code> is <code>true</code>, this method creates a Rule to
   * format metadata for the Americas member of the Geography layer in the 
   * header, and adds the Rule to the GridViewHeaderStyleManager.
   * If <code>bApply</code> is <code>false</code>, this method removes from the 
   * GridViewHeaderStyleManager the previously created Rule to format the 
   * Americas member of the Geography layer.
   * The Rule will format the metadata with a blue foreground, cyan 
   * background, and bold and italic font.  
   * 
   * The GridViewHeaderStyleManager is used to specify style formatting
   * in the column, row, and page headers.
   * 
   * @param bApply <code>true</code> to apply header formatting, 
   *               <code>false</code> to remove it
   */
  protected void applyHeaderFormatting(boolean bApply) {
    // Get the Vector of RuleBundles from the GridViewHeaderStyleManager.
    Vector ruleBundles = 
      m_crosstab.getGridViewHeaderStyleManager().getBundles();

    // If applying formatting, create a Rule, add it to a RuleBundle, and 
    // add the RuleBundle to the Vector.
    if (bApply) {
      // If the Vector is null, create a new one.
      if (ruleBundles == null) {
        ruleBundles = new Vector();
      }
      
      // Create a new RuleBundle which will be added to the Vector.  
      // For the purposes of this sample application, there will be only one 
      // Rule in one RuleBundle in the Vector on the GridViewHeaderStyleManager.
      RuleBundle bundle = new RuleBundle();

      // Key Concept
      // Create a QDR for the Electronics Division member of the Product layer, to which
      // the Rule will apply.
      QDR elecQDR = new QDR("MDM!MEASUREDIMENSION", 
        "MDM!D_CS_OLAP.SHAWPRODUCTS", "201");

      // Key Concept
      // Create a QDRDiscriminator based on the Electronics QDR.  The operator for 
      // the discriminator is set to SUPERSET.  This discriminator will apply
      // to any cell associated with the Electronics member, no matter how many 
      // other dimensions the cell is associated with.  
      QDRDiscriminator qdrDisc1 = new QDRDiscriminator(elecQDR,
        QDRDiscriminator.SUPERSET);

      // Key Concept
      // Create a QDR for the Product layer itself.  This QDR will be used to
      // restrict the cells to which the rule will apply to those within the
      // Product layer, so that cells associated with Elec Division that belong
      // to other layers will not be affected.  
      QDR prodLayerQDR = new QDR("MDM!MEASUREDIMENSION");
      QDRMember qdrMember = new QDRMember(QDRMember.VARIES);
      prodLayerQDR.addDimMemberPair("MDM!D_CS_OLAP.SHAWPRODUCTS", qdrMember);

      // Key Concept
      // Create a QDRDiscriminator based on the Product QDR.  The operator
      // for the discriminator is set to EQUALS.  This discriminator will 
      // apply to any cell in the Product layer.  
      QDRDiscriminator qdrDisc2 = new QDRDiscriminator(prodLayerQDR, 
        QDRDiscriminator.EQUALS);

      // Key Concept
      // Create a CompositeDiscriminator from the two QDRDiscriminators.  By
      // ANDing the two QDRDiscriminators, the CompositeDiscriminator will 
      // only apply to those cells that meet both of the QDRDiscriminators'
      // requirements.  Basically, this limits the Rule to cells for the 
      // Audio member within the Product layer itself.  
      CompositeDiscriminator compDisc = new CompositeDiscriminator();
      compDisc.setCondOperator(CompositeDiscriminator.AND);
      compDisc.setRight(qdrDisc1);
      compDisc.setLeft(qdrDisc2);

      // Key Concept
      // Create the ViewStyle that will affect the cells for which the
      // discriminator applies.  Only the properties that should take effect
      // in the Rule should be set.  This ViewStyle will format metadata with
      // a blue foreground, cyan background, and bold and italic font.
      ViewStyle viewStyle = new ViewStyle();
      viewStyle.setBackground(Color.cyan);
      viewStyle.setForeground(Color.blue);
      viewStyle.setFontItalic(true);
      viewStyle.setFontBold(true);

      // Key Concept
      // Create the DiscriminatorRule and set its discriminator and Mergeable.
      // The ViewStyle is a Mergeable, which means that it can be 
      // merged with other ViewStyles and used in Rules.  
      // Only properties that have been explicitly set on
      // the ViewStyle will be merged as part of the rule resolution process.
      // ViewStyle is the only type of Mergeable that may be used with Rules
      // in the GridViewHeaderStyleManager.
      DiscriminatorRule discRule = new DiscriminatorRule();
      discRule.setDiscriminator(compDisc);
      discRule.setFixedMergeable(viewStyle);

      // Key Concept
      // Add the Rule to the RuleBundle.  Every Rule must belong to a 
      // RuleBundle, and the RuleBundle added to the Vector on the manager, 
      // in order for the Rule to run.  
      bundle.addRule(discRule);

      // Key Concept
      // Add the RuleBundle to the Vector, which will be set on the manager.
      ruleBundles.addElement(bundle);
    }
    // If removing formatting, remove the whole RuleBundle containing the 
    // Rule from the Vector, because it only contains this one Rule.
    else {
      if (ruleBundles != null && !ruleBundles.isEmpty()) {
        ruleBundles.removeElementAt(0);
      }
    }

    // Key Concept
    // Set the updated Vector of RuleBundles on the GridViewHeaderStyleManager.  
    // Since the Vector is passed by value, no modifications have been made
    // to the instance maintained by the manager so far.  
    // It is necessary to call setBundles() to update the manager
    // with the new Vector.
    m_crosstab.getGridViewHeaderStyleManager().setBundles(m_crosstab,
      ruleBundles);
  }

  /**
   * Apply or remove red stoplight formatting for the databody cells 
   * associated with the Sales measure whose value is less than or equal to
   * two million.  
   * If <code>bApply</code> is <code>true</code>, this method creates a Rule to
   * format the cells, and adds the Rule to the GridViewDatabodyStyleManager.
   * If <code>bApply</code> is <code>false</code>, this method removes from the 
   * GridViewDatabodStyleManager the previously created Rule to format the 
   * cells.  
   * The Rule will format cells where Sales is less than or equal to two million
   * with a red background.
   * 
   * The GridViewDatabodyStyleManager is used to specify style formatting
   * in the databody.
   * 
   * Note that if both yellow and red stoplight formatting are applied to the
   * Crosstab, then Sales cells whose values are less than two million will be
   * red, not yellow.  This is because the Rule for red stoplight formatting
   * comes after the Rule for yellow stoplight formatting in the manager, so
   * it takes precedence.  If the order were reversed, then when both yellow
   * and red stoplight formatting were applied to the Crosstab, the yellow
   * formatting would mask the red.  
   * 
   * @param bApply <code>true</code> to apply formatting, 
   *               <code>false</code> to remove it
   */
  protected void applyRedStoplightFormatting(boolean bApply) {
    // Variables for the value discriminator and ViewStyle that will be used
    // in the Rule.  
    NumberValueDiscriminator nvDisc = null;
    ViewStyle viewStyle = null;

    // If formatting is being applied, initialize the value discriminator and
    // ViewStyle.  If formatting is being removed, these objects do not need
    // to be initialized.
    if (bApply) {
      // Key Concept
      // Create a NumberValueDiscriminator that will apply to any cell whose 
      // value is less than or equal to two million.
      nvDisc = new NumberValueDiscriminator(2000000, 
        NumberValueDiscriminator.LE);

      // Key Concept
      // Create the ViewStyle that will affect the cells for which the
      // discriminator applies.  Only the properties that should take effect
      // in the Rule should be set.  This ViewStyle will make the 
      // background red. 
      viewStyle = new ViewStyle();
      viewStyle.setBackground(Color.red);
    }

    // Call the helper method that will actually create the Rule to do the
    // red stoplight formatting, passing in the relevant information.
    applyStoplightFormatting(bApply, RED_STOPLIGHT_RULE, viewStyle, nvDisc);
  }

  /**
   * Apply or remove formatting for a single cell in the databody.  
   * The cell is at the intersection of Sales, Regions of the World,
   * and Audio Division.  
   * If <code>bApply</code> is <code>true</code>, this method creates a Rule to
   * format the cell, and adds the Rule to the GridViewDatabodyStyleManager.
   * If <code>bApply</code> is <code>false</code>, this method removes from the 
   * GridViewDatabodStyleManager the previously created Rule to format the cell.  
   * The Rule will format the cell with a blue double border and magenta text.
   * 
   * The GridViewDatabodyStyleManager is used to specify style formatting
   * in the databody.
   * 
   * @param bApply <code>true</code> to apply formatting, 
   *               <code>false</code> to remove it
   */
  protected void applySingleCellFormatting(boolean bApply) {
    // Get the Vector of RuleBundles from the GridViewDatabodyStyleManager.
    Vector ruleBundles = 
      m_crosstab.getGridViewDatabodyStyleManager().getBundles();

    // If applying formatting, create a Rule, add it to a RuleBundle, and 
    // add the RuleBundle to the Vector.
    if (bApply) {
      // If the Vector is null, create a new one.
      if (ruleBundles == null) {
        ruleBundles = new Vector();
      }

      // Key Concept
      // Get from the Vector the RuleBundle that will contain the new Rule.  
      // Because the GridViewDatabodyStyleManager Vector will contain multiple
      // RuleBundles in this sample, each RuleBundle it contains is named.
      // The RuleBundle to which this Rule will be added is identified by
      // DIVISION_BUNDLE_NAME.  If there is no such named RuleBundle in the
      // Vector already, the named bundle is created.  
      RuleBundle bundle = getDatabodyStyleRuleBundle(DIVISION_BUNDLE_NAME,
        ruleBundles);
      if (bundle == null) {
        bundle = new RuleBundle();
        bundle.setID(DIVISION_BUNDLE_NAME);
      }

      // Key Concept
      // Create a QDR for the cell to which the Rule will apply.
      // The cell is associated with the Sales member of the measure layer,
      // the Direct member of the Channel layer, and the Electronics Division member
      // of the Product layer.  
      QDR cellQDR = new QDR("MDM!MEASUREDIMENSION");
      cellQDR.addDimMemberPair("MDM!MEASUREDIMENSION",
        "MDM!M_CS_OLAP.SHAWSALES.AMOUNT_SOLD");
      cellQDR.addDimMemberPair("MDM!D_CS_OLAP.SHAWCHANNELS", "12");
      cellQDR.addDimMemberPair("MDM!D_CS_OLAP.SHAWPRODUCTS", "201");

      // Key Concept
      // Create a QDRDiscriminator based on the cell's QDR.  The operator for 
      // the discriminator is set to SUPERSET.  This discriminator will apply
      // to any databody cell associated with the above QDR, no matter how 
      // many other layers the cell is associated with.  For instance, the
      // cell may belong to any members of the Geography and Time layers, which
      // are initially on the page edge.  
      QDRDiscriminator qdrDisc = new QDRDiscriminator(cellQDR,
        QDRDiscriminator.SUPERSET);

      // Key Concept
      // Create the ViewStyle that will affect the cells for which the
      // discriminator applies.  Only the properties that should take effect
      // in the Rule should be set.  This ViewStyle will add a double blue 
      // border around the cell and set the text color to magenta.  
      ViewStyle viewStyle = new ViewStyle();
      viewStyle.setBorderColor(ViewStyle.ALL_BORDERS, Color.blue);
      viewStyle.setBorderStyle(ViewStyle.ALL_BORDERS, 
        ViewStyle.BORDER_STYLE_DOUBLE);
      viewStyle.setBorderWidth(ViewStyle.ALL_BORDERS, 2);
      viewStyle.setForeground(Color.magenta);

      // Key Concept
      // Create the DiscriminatorRule and set its discriminator and Mergeable.
      // The ViewStyle is a Mergeable, which means that it can be 
      // merged with other ViewStyles and used in Rules.  
      // Only properties that have been explicitly set on
      // the ViewStyle will be merged as part of the rule resolution process.
      // ViewStyle is the only type of Mergeable that may be used with Rules
      // in the GridViewDatabodyStyleManager.
      DiscriminatorRule discRule = new DiscriminatorRule();
      discRule.setDiscriminator(qdrDisc);
      discRule.setFixedMergeable(viewStyle);

      // Key Concept
      // Add the Rule to the RuleBundle.  Every Rule must belong to a 
      // RuleBundle, and the RuleBundle added to the Vector on the manager, 
      // in order for the Rule to run.  The order of Rules within a RuleBundle
      // and RuleBundles within a Vector matters.  Rules are applied first to 
      // last, so later Rules take precedence.  Because each RuleBundle in 
      // the Vector of bundles on the GridViewDatabodyStyleManager may contain 
      // multiple Rules in this sample, this particular Rule must be added in 
      // the correct position relative to other Rules in the RuleBundle.  
      addDatabodyStyleRule(SINGLE_CELL_RULE, discRule, DIVISION_BUNDLE, bundle);

      // Key Concept
      // Add the RuleBundle to the Vector, which will be set on the manager.
      // Because in this sample the Vector of bundles on the
      // GridViewDatabodyStyleManager may contain multiple RuleBundles, this
      // particular RuleBundle must be added to the Vector in the correct
      // position relative to the other RuleBundles in the Vector.  
      setDatabodyStyleRuleBundle(DIVISION_BUNDLE, bundle, ruleBundles);
    }
    // Key Concept
    // If removing formatting, remove the Rule from its RuleBundle.  If that 
    // leaves the RuleBundle empty, then remove the entire RuleBundle from
    // the Vector.  
    else {
      if (ruleBundles != null && !ruleBundles.isEmpty()) {
        // Get the RuleBundle that contains this Rule.
        RuleBundle ruleBundle = 
          getDatabodyStyleRuleBundle(DIVISION_BUNDLE_NAME, ruleBundles);

        // If the RuleBundle is in the Vector, remove this Rule from it.  If
        // the RuleBundle contains other Rules, reset it in the Vector.  If the
        // RuleBundle is empty, remove it from the Vector completely.  
        if (ruleBundle != null) {
          if (removeDatabodyStyleRule(SINGLE_CELL_RULE, DIVISION_BUNDLE, 
            ruleBundle)) {
            if (ruleBundle.getCount() > 0) {
              setDatabodyStyleRuleBundle(DIVISION_BUNDLE, 
                ruleBundle, ruleBundles);
            }
            else {
              removeDatabodyStyleRuleBundle(DIVISION_BUNDLE, ruleBundle.getID(),
                ruleBundles);
            }
          }
        }
      }
    }

    // Key Concept
    // Set the updated Vector of RuleBundles on the 
    // GridViewDatabodyStyleManager.  Since the Vector is passed by value, no 
    // modifications have been made to the instance maintained by the manager 
    // so far.  It is necessary to call setBundles() to update the manager
    // with the new Vector.
    m_crosstab.getGridViewDatabodyStyleManager().setBundles(m_crosstab, 
      ruleBundles);
  }

  /**
   * Apply or remove stoplight formatting for the databody cells 
   * associated with the Sales measure.  
   * If <code>bApply</code> is <code>true</code>, this method creates a Rule to
   * format the cells, and adds the Rule to the GridViewDatabodyStyleManager.
   * If <code>bApply</code> is <code>false</code>, this method removes from the 
   * GridViewDatabodStyleManager the previously created Rule to format the 
   * cells.  
   * The Rule will format cells where Sales in the range specified by the
   * given NumberValueDiscriminator, applying properties specified in the
   * given ViewStyle.
   * 
   * The GridViewDatabodyStyleManager is used to specify style formatting
   * in the databody.
   * 
   * @param bApply <code>true</code> to apply formatting, 
   *               <code>false</code> to remove it
   * @param ruleID the ID of the Rule being applied
   * @param viewStyle the ViewStyle specifying properties to be applied in the
   *                  Rule
   * @param nvDisc the NumberValueDiscriminator to which the value of a cell
   *               will be compared to see if the Rule applies
   * 
   * @see #GREEN_STOPLIGHT_RULE
   * @see #YELLOW_STOPLIGHT_RULE
   * @see #RED_STOPLIGHT_RULE
   */
  protected void applyStoplightFormatting(boolean bApply, int ruleID, 
    ViewStyle viewStyle, NumberValueDiscriminator nvDisc) {
    // Get the Vector of RuleBundles from the GridViewDatabodyStyleManager.
    Vector ruleBundles = 
      m_crosstab.getGridViewDatabodyStyleManager().getBundles();

    // If applying formatting, create a Rule, add it to a RuleBundle, and 
    // add the RuleBundle to the Vector.
    if (bApply) {
      // If the Vector is null, create a new one.
      if (ruleBundles == null) {
        ruleBundles = new Vector();
      }

      // Key Concept
      // Get from the Vector the RuleBundle that will contain the new Rule.  
      // Because the GridViewDatabodyStyleManager Vector will contain multiple
      // RuleBundles in this sample, each RuleBundle it contains is named.
      // The RuleBundle to which this Rule will be added is identified by
      // STOPLIGHT_BUNDLE_NAME.  If there is no such named RuleBundle in the
      // Vector already, the named bundle is created.  
      RuleBundle bundle = getDatabodyStyleRuleBundle(STOPLIGHT_BUNDLE_NAME,
        ruleBundles);
      if (bundle == null) {
        bundle = new RuleBundle();
        bundle.setID(STOPLIGHT_BUNDLE_NAME);
      }

      // Key Concept
      // Create a QDR for the Sales measure, to which the Rule will apply.
      QDR salesQDR = new QDR("MDM!MEASUREDIMENSION", "MDM!MEASUREDIMENSION",
        "MDM!M_CS_OLAP.SHAWSALES.AMOUNT_SOLD");

      // Key Concept
      // Create a QDRDiscriminator based on the Sales QDR.  The operator for 
      // the discriminator is set to SUPERSET.  This discriminator will apply
      // to any databody cell associated with the Sales measure, no matter how 
      // many other layers the cell is associated with.  
      QDRDiscriminator qdrDisc = new QDRDiscriminator(salesQDR,
        QDRDiscriminator.SUPERSET);

      // Key Concept
      // Create a CompositeDiscriminator from the QDRDiscriminator and the
      // NumberValueDiscriminator.  By ANDing the two QDRDiscriminators, the 
      // CompositeDiscriminator will only apply to those cells that meet both 
      // of the other discriminators' requirements.  Basically, this limits the
      // Rule to Sales cells whose values are in the range specified by the
      // NumberValueDiscriminator.  
      CompositeDiscriminator compDisc = new CompositeDiscriminator();
      compDisc.setCondOperator(CompositeDiscriminator.AND);
      compDisc.setLeft(qdrDisc);
      compDisc.setRight(nvDisc);

      // Key Concept
      // Create the DiscriminatorRule and set its discriminator and Mergeable.
      // The ViewStyle is a Mergeable, which means that it can be 
      // merged with other ViewStyles and used in Rules.  
      // Only properties that have been explicitly set on
      // the ViewStyle will be merged as part of the rule resolution process.
      // ViewStyle is the only type of Mergeable that may be used with Rules
      // in the GridViewDatabodyStyleManager.
      DiscriminatorRule discRule = new DiscriminatorRule();
      discRule.setDiscriminator(compDisc);
      discRule.setFixedMergeable(viewStyle);

      // Key Concept
      // Add the Rule to the RuleBundle.  Every Rule must belong to a 
      // RuleBundle, and the RuleBundle added to the Vector on the manager, 
      // in order for the Rule to run.  The order of Rules within a RuleBundle
      // and RuleBundles within a Vector matters.  Rules are applied first to 
      // last, so later Rules take precedence.  Because each RuleBundle in 
      // the Vector of bundles on the GridViewDatabodyStyleManager may contain 
      // multiple Rules in this sample, this particular Rule must be added in 
      // the correct position relative to other Rules in the RuleBundle. 
      addDatabodyStyleRule(ruleID, discRule, STOPLIGHT_BUNDLE, bundle);

      // Key Concept
      // Add the RuleBundle to the Vector, which will be set on the manager.
      // Because in this sample the Vector of bundles on the
      // GridViewDatabodyStyleManager may contain multiple RuleBundles, this
      // particular RuleBundle must be added to the Vector in the correct
      // position relative to the other RuleBundles in the Vector.  
      setDatabodyStyleRuleBundle(STOPLIGHT_BUNDLE, bundle, ruleBundles);
    }
    // Key Concept
    // If removing formatting, remove the Rule from its RuleBundle.  If that 
    // leaves the RuleBundle empty, then remove the entire RuleBundle from
    // the Vector.  
    else {
      if (ruleBundles != null && !ruleBundles.isEmpty()) {
        // Get the RuleBundle that contains this Rule.
        RuleBundle ruleBundle = 
          getDatabodyStyleRuleBundle(STOPLIGHT_BUNDLE_NAME, ruleBundles);

        // If the RuleBundle is in the Vector, remove this Rule from it.  If
        // the RuleBundle contains other Rules, reset it in the Vector.  If the
        // RuleBundle is empty, remove it from the Vector completely.
        if (ruleBundle != null) {
          if (removeDatabodyStyleRule(ruleID, STOPLIGHT_BUNDLE, ruleBundle)) {
            if (ruleBundle.getCount() > 0) {
              setDatabodyStyleRuleBundle(STOPLIGHT_BUNDLE, ruleBundle, 
                ruleBundles);
            }
            else {
              removeDatabodyStyleRuleBundle(STOPLIGHT_BUNDLE, 
                ruleBundle.getID(), ruleBundles);
            }
          }
        }
      }
    }

    // Key Concept
    // Set the updated Vector of RuleBundles on the 
    // GridViewDatabodyStyleManager.  Since the Vector is passed by value, no 
    // modifications have been made to the instance maintained by the manager 
    // so far.  It is necessary to call setBundles() to update the manager
    // with the new Vector.
    m_crosstab.getGridViewDatabodyStyleManager().setBundles(m_crosstab,
      ruleBundles);
  }

  /**
   * Apply or remove yellow stoplight formatting for the databody cells 
   * associated with the Sales measure whose value is less than or equal to
   * ten million.  
   * If <code>bApply</code> is <code>true</code>, this method creates a Rule to
   * format the cells, and adds the Rule to the GridViewDatabodyStyleManager.
   * If <code>bApply</code> is <code>false</code>, this method removes from the 
   * GridViewDatabodStyleManager the previously created Rule to format the 
   * cells.  
   * The Rule will format cells where Sales is less than or equal to ten million
   * with a yellow background.
   * 
   * The GridViewDatabodyStyleManager is used to specify style formatting
   * in the databody.
   * 
   * Note that if both yellow and red stoplight formatting are applied to the
   * Crosstab, then Sales cells whose values are less than two million will be
   * red, not yellow.  This is because the Rule for red stoplight formatting
   * comes after the Rule for yellow stoplight formatting in the manager, so
   * it takes precedence.  If the order were reversed, then when both yellow
   * and red stoplight formatting were applied to the Crosstab, the yellow
   * formatting would mask the red.  
   * 
   * @param bApply <code>true</code> to apply formatting, 
   *               <code>false</code> to remove it
   */
  protected void applyYellowStoplightFormatting(boolean bApply) {
    // Variables for the value discriminator and ViewStyle that will be used
    // in the Rule.  
    NumberValueDiscriminator nvDisc = null;
    ViewStyle viewStyle = null;

    // If formatting is being applied, initialize the value discriminator and
    // ViewStyle.  If formatting is being removed, these objects do not need
    // to be initialized.
    if (bApply) {
      // Key Concept
      // Create a NumberValueDiscriminator that will apply to any cell whose 
      // value is less than or equal to ten million.
      nvDisc = new NumberValueDiscriminator(10000000, 
        NumberValueDiscriminator.LE);

      // Key Concept
      // Create the ViewStyle that will affect the cells for which the
      // discriminator applies.  Only the properties that should take effect
      // in the Rule should be set.  This ViewStyle will make the 
      // background yellow. 
      viewStyle = new ViewStyle();
      viewStyle.setBackground(Color.yellow);
    }

    // Call the helper method that will actually create the Rule to do the
    // yellow stoplight formatting, passing in the relevant information.
    applyStoplightFormatting(bApply, YELLOW_STOPLIGHT_RULE, viewStyle, nvDisc);
  }

  /**
   * Create and initialize a new Crosstab.  
   */
  protected void createCrosstab() {
    // Create a new Crosstab
    m_crosstab = new Crosstab();
    m_crosstab.setPagingControlVisible(true);

    // Initialize the flags indicating which RuleBundles are set on
    // the Crosstab to false.  These flags are used for maintaining the order
    // of the RuleBundles set on the GridViewDatabodyStyleManager.
    m_databodyStyleRuleBundles = new boolean[2];
    for (int i = 0; i < m_databodyStyleRuleBundles.length; i++) {
      m_databodyStyleRuleBundles[i] = false;
    }

    // Initialize the flags indicating which Rules in the RuleBundles are
    // set on the Crosstab to false.  These flags are used for maintaining the
    // order of the Rules in the RuleBundles set on the 
    // GridViewDatabodyStyleManager.
    m_databodyStyleRules = new boolean[2][];
    m_databodyStyleRules[DIVISION_BUNDLE] = new boolean[2];
    for (int i = 0; i < m_databodyStyleRules[DIVISION_BUNDLE].length; i++) {
      m_databodyStyleRules[DIVISION_BUNDLE][i] = false;
    }

    m_databodyStyleRules[STOPLIGHT_BUNDLE] = new boolean[3];
    for (int i = 0; i < m_databodyStyleRules[STOPLIGHT_BUNDLE].length; i++) {
      m_databodyStyleRules[STOPLIGHT_BUNDLE][i] = false;
    }
    
    // Create a default Query
    Query query = m_frame.getDefaultQuery();
    m_crosstab.setDataSource(query);
    
    // Modify the query to make the initial Crosstab more visually interesting
    // for the purposes of this sample.  The default query places Product and 
    // Time on the page edge, Sales above Geography on the column edge, and 
    // Channel on the row edge, with no layers drilled.  The following 
    // modifications produce the desired query for this sample, which has
    // Channel and Time on the page edge, Sales above Geography on the
    // column edge, and Product on the row edge, with Geography and Product
    // each drilled one level.  
    DataDirector dataDirector = m_crosstab.getCrosstabModel().getDataDirector();
    DataAccess dataAccess = m_crosstab.getCrosstabModel().getDataAccess();
    try {
      // Determine the index of the Product layer on the page edge, so that
      // it can be swapped with the Channel layer on the row edge.  
      int pageLayerCount = dataAccess.getEdgeExtent(DataDirector.PAGE_EDGE);
      String productLayerName = "MDM!D_CS_OLAP.SHAWPRODUCTS";
      int productLayer = 0;
      for (int layer = 0; layer < pageLayerCount; layer++) {
        Object layerObj = dataAccess.getLayerMetadata(DataDirector.PAGE_EDGE,
          layer, LayerMetadataMap.LAYER_METADATA_NAME);
        String layerName = "";
        if (layerObj != null) {
          layerName = layerObj.toString();
        }
        if (layerName != null && layerName.equals(productLayerName)) {
          productLayer = layer;
          break;
        }
      }
      
      // Swap the Product layer on the page edge with the Channel layer on
      // the row edge.
      if (dataDirector.pivotOK(DataDirector.PAGE_EDGE, DataDirector.ROW_EDGE,
        productLayer, 0, DataDirector.PIVOT_SWAP)) {
        dataDirector.pivot(DataDirector.PAGE_EDGE, DataDirector.ROW_EDGE,
          productLayer, 0, DataDirector.PIVOT_SWAP);
      }

      // Expand the Geography members on the column edge.
      if (dataDirector.drillOK(DataDirector.COLUMN_EDGE, 1, 0, 0)) {
        dataDirector.drill(DataDirector.COLUMN_EDGE, 1, 0, 0);
      }

      // Expand the Product members on the row edge.
      if (dataDirector.drillOK(DataDirector.ROW_EDGE, 0, 0, 0)) {
        dataDirector.drill(DataDirector.ROW_EDGE, 0, 0, 0);
      }
    }
    catch (EdgeOutOfRangeException eEdge) {
      // Could not pivot or drill
      m_frame.showExceptionDialog(this, eEdge);
    }
    catch (LayerOutOfRangeException eLayer) {
      // Could not pivot or drill
      m_frame.showExceptionDialog(this, eLayer);
    }
    catch (SliceOutOfRangeException eSlice) {
      // Could not drill
      m_frame.showExceptionDialog(this, eSlice);
    }
    catch (DataDirectorException eData) {
      // Could not pivot or drill
      m_frame.showExceptionDialog(this, eData);
    }
    
    // Add the Crosstab to the container
    m_panel.add(m_crosstab, BorderLayout.CENTER);
    m_panel.invalidate();
    m_panel.validate();
    m_panel.repaint();
    
    // Initialize the flags maintaining the state of the rules options to false.
    m_currencyFormattingFlag = false;
    m_singleCellFormattingFlag = false;
    m_headerFormattingFlag = false;
    m_divisionFormattingFlag = false;
    m_greenStoplightFlag = false;
    m_yellowStoplightFlag = false;
    m_redStoplightFlag = false;
  }
  
  /**
   * Cleaup to close and release the Crosstab. 
   * This method calls the destroyCrosstab method.
   * After the connection is closed, the "New Crosstab" option is disabled.
   */
  protected void cleanUp() {
  
    // Clear the flags indicating which RuleBundles are set on
    // the Crosstab.  
    m_databodyStyleRuleBundles = null;

    // Clear the flags indicating which Rules in the RuleBundles are
    // set on the Crosstab.
    if (m_databodyStyleRules != null) {
      for (int i = 0; i < m_databodyStyleRules.length; i++) {
        m_databodyStyleRules[i] = null;
      }
    }
    m_databodyStyleRules = null;
    
    if(m_crosstab != null) {
      m_crosstab.setDataSource(null);
      m_panel.remove(m_crosstab);
      m_crosstab = null;
    }
    dispose();
    }

  /**
   * Get the RuleBundle with the given ID from the Vector of bundles.  
   * If no RuleBundle with the given ID exists in the Vector, null is returned.
   * 
   * @param name the name of the RuleBundle to get from the Vector
   * @param bundles the Vector of bundles from which to get the RuleBundle with
   *                the given ID
   * 
   * @return the RuleBundle with the given ID from the Vector of bundles, 
   *         or null if no such RuleBundle exists
   * 
   * @see #DIVISION_BUNDLE_NAME
   * @see #STOPLIGHT_BUNDLE_NAME
   */
  protected RuleBundle getDatabodyStyleRuleBundle(String name, Vector bundles) {
    // If the Vector is not empty, iterate over its contents and return the 
    // RuleBundle that has the given name.
    if (!bundles.isEmpty()) {
      Enumeration enum = bundles.elements();
      while (enum.hasMoreElements()) {
        RuleBundle rb = (RuleBundle)enum.nextElement();
        if (rb.getID().equals(name)) {
          return rb;
        }
      }
    }

    // If the Vector is empty, or if no RuleBundle with the given name was 
    // found, return null.
    return null;
  }

  /**
   * Initialize the user interface.
   */
  protected void init() {

    // Text for menu items and button tooltips
    String currencyFormatText = "Currency Formatting";
    String singleCellFormatText = "Single Cell Formatting";
    String headerFormatText = "Header Cell Formatting";
    String divisionFormatText = "Row Formatting";
    String greenStoplightText = "Sales > 10,000,000";
    String yellowStoplightText = "Sales <= 10,000,000";
    String redStoplightText = "Sales <= 2,000,000";

    // Create icons for the menu items and toolbar buttons
    URL url = BIRules.class.getResource("images/currencyFormat.gif");
    ImageIcon currencyFormattingIcon = new ImageIcon(url);
    url = BIRules.class.getResource("images/yes.gif");
    ImageIcon singleCellFormattingIcon = new ImageIcon(url);
    url = BIRules.class.getResource("images/domainIndex.gif");
    ImageIcon headerFormattingIcon = new ImageIcon(url);
    url = BIRules.class.getResource("images/updateAt.gif");
    ImageIcon divisionFormattingIcon = new ImageIcon(url);
    url = BIRules.class.getResource("images/green-ball.gif");
    ImageIcon greenStoplightIcon = new ImageIcon(url);
    url = BIRules.class.getResource("images/yellow-ball.gif");
    ImageIcon yellowStoplightIcon = new ImageIcon(url);
    url = BIRules.class.getResource("images/red-ball.gif");
    ImageIcon redStoplightIcon = new ImageIcon(url);

    ActionListener currencyFormattingListener = new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        // Toggle the UI flag for the rule, set the selection states of the 
        // toolbar button and the menu item to match, and process the format 
        // request.
        m_currencyFormattingFlag = !m_currencyFormattingFlag;
        m_currencyFormattingCheckBox.setSelected(m_currencyFormattingFlag);
        m_currencyFormattingButton.setSelected(m_currencyFormattingFlag);
        applyCurrencyFormatting(m_currencyFormattingFlag);
      }
    };

    ActionListener singleCellFormattingListener = new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        // Toggle the UI flag for the rule, set the selection states of the 
        // toolbar button and the menu item to match, and process the format 
        // request.
        m_singleCellFormattingFlag = !m_singleCellFormattingFlag;
        m_singleCellFormattingCheckBox.setSelected(m_singleCellFormattingFlag);
        m_singleCellFormattingButton.setSelected(m_singleCellFormattingFlag);
        applySingleCellFormatting(m_singleCellFormattingFlag);
      }
    };

    ActionListener headerFormattingListener = new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        // Toggle the UI flag for the rule, set the selection states of the 
        // toolbar button and the menu item to match, and process the format 
        // request.
        m_headerFormattingFlag = !m_headerFormattingFlag;
        m_headerFormattingCheckBox.setSelected(m_headerFormattingFlag);
        m_headerFormattingButton.setSelected(m_headerFormattingFlag);
        applyHeaderFormatting(m_headerFormattingFlag);
      }
    };

    ActionListener divisionFormattingListener = new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        // Toggle the UI flag for the rule, set the selection states of the 
        // toolbar button and the menu item to match, and process the format 
        // request.
        m_divisionFormattingFlag = !m_divisionFormattingFlag;
        m_divisionFormattingCheckBox.setSelected(m_divisionFormattingFlag);
        m_divisionFormattingButton.setSelected(m_divisionFormattingFlag);
        applyDivisionFormatting(m_divisionFormattingFlag);
      }
    };

    ActionListener greenStoplightListener = new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        // Toggle the UI flag for the rule, set the selection states of the 
        // toolbar button and the menu item to match, and process the format 
        // request.
        m_greenStoplightFlag = !m_greenStoplightFlag;
        m_greenStoplightCheckBox.setSelected(m_greenStoplightFlag);
        m_greenStoplightButton.setSelected(m_greenStoplightFlag);
        applyGreenStoplightFormatting(m_greenStoplightFlag);
      }
    };
    
    ActionListener yellowStoplightListener = new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        // Toggle the UI flag for the rule, set the selection states of the 
        // toolbar button and the menu item to match, and process the format 
        // request.
        m_yellowStoplightFlag = !m_yellowStoplightFlag;
        m_yellowStoplightCheckBox.setSelected(m_yellowStoplightFlag);
        m_yellowStoplightButton.setSelected(m_yellowStoplightFlag);
        applyYellowStoplightFormatting(m_yellowStoplightFlag);
      }
    };
    
    ActionListener redStoplightListener = new ActionListener() {
      public void actionPerformed(ActionEvent event) {
        // Toggle the UI flag for the rule, set the selection states of the 
        // toolbar button and the menu item to match, and process the format 
        // request.
        m_redStoplightFlag = !m_redStoplightFlag;
        m_redStoplightCheckBox.setSelected(m_redStoplightFlag);
        m_redStoplightButton.setSelected(m_redStoplightFlag);
        applyRedStoplightFormatting(m_redStoplightFlag);
      }
    };

    // Create additional menu items
    JMenuBar m_menuBar = new JMenuBar();
    m_sampleMenu = new JMenu("Formatting crosstabs");
    m_menuBar.add(m_sampleMenu);

    // Create menu items for the Rules menu
    m_currencyFormattingCheckBox = new JCheckBoxMenuItem(currencyFormatText,
      currencyFormattingIcon);
    m_currencyFormattingCheckBox.setMnemonic('u');
    m_singleCellFormattingCheckBox = new JCheckBoxMenuItem(singleCellFormatText,
      singleCellFormattingIcon);
    m_singleCellFormattingCheckBox.setMnemonic('i');
    m_headerFormattingCheckBox = new JCheckBoxMenuItem(headerFormatText,
      headerFormattingIcon);
    m_headerFormattingCheckBox.setMnemonic('d');
    m_divisionFormattingCheckBox = new JCheckBoxMenuItem(divisionFormatText,
      divisionFormattingIcon);
    m_divisionFormattingCheckBox.setMnemonic('w');
    m_greenStoplightCheckBox = new JCheckBoxMenuItem(greenStoplightText, 
      greenStoplightIcon);
    m_greenStoplightCheckBox.setMnemonic('S');
    m_yellowStoplightCheckBox =new JCheckBoxMenuItem(yellowStoplightText, 
      yellowStoplightIcon);
    m_yellowStoplightCheckBox.setMnemonic('l');
    m_redStoplightCheckBox =new JCheckBoxMenuItem(redStoplightText, 
      redStoplightIcon);
    m_redStoplightCheckBox.setMnemonic('e');

    // Add items to the Rules menu item
    m_sampleMenu.add(m_currencyFormattingCheckBox);
    m_sampleMenu.add(m_singleCellFormattingCheckBox);
    m_sampleMenu.add(m_headerFormattingCheckBox);
    m_sampleMenu.add(m_divisionFormattingCheckBox);

    // Create the Stoplight submenu of the Rules menu, and add items to it
    JMenu stoplightMenu = new JMenu("Stoplight Formatting");
    stoplightMenu.setMnemonic('t');
    stoplightMenu.add(m_greenStoplightCheckBox);
    stoplightMenu.add(m_yellowStoplightCheckBox);
    stoplightMenu.add(m_redStoplightCheckBox);
    m_sampleMenu.add(stoplightMenu);
    
    // Add listeners to the Rules menu items
    m_currencyFormattingCheckBox.addActionListener(currencyFormattingListener);
    m_singleCellFormattingCheckBox.addActionListener(
      singleCellFormattingListener);
    m_headerFormattingCheckBox.addActionListener(headerFormattingListener);
    m_divisionFormattingCheckBox.addActionListener(divisionFormattingListener);
    m_greenStoplightCheckBox.addActionListener(greenStoplightListener);
    m_yellowStoplightCheckBox.addActionListener(yellowStoplightListener);
    m_redStoplightCheckBox.addActionListener(redStoplightListener);

    // Create additional toolbar buttons, with tooltips

    m_currencyFormattingButton = new JToggleButton(currencyFormattingIcon);
    m_currencyFormattingButton.setToolTipText(currencyFormatText);

    m_singleCellFormattingButton = new JToggleButton(singleCellFormattingIcon);
    m_singleCellFormattingButton.setToolTipText(singleCellFormatText);
 
    m_headerFormattingButton = new JToggleButton(headerFormattingIcon);
    m_headerFormattingButton.setToolTipText(headerFormatText);
 
    m_divisionFormattingButton = new JToggleButton(divisionFormattingIcon);
    m_divisionFormattingButton.setToolTipText(divisionFormatText);
  
    m_greenStoplightButton = new JToggleButton(greenStoplightIcon);
    m_greenStoplightButton.setToolTipText(greenStoplightText);
  
    m_yellowStoplightButton = new JToggleButton(yellowStoplightIcon);
    m_yellowStoplightButton.setToolTipText(yellowStoplightText);
  
    m_redStoplightButton = new JToggleButton(redStoplightIcon);
    m_redStoplightButton.setToolTipText(redStoplightText);

    JToolBar m_toolBar = new JToolBar();

    // Add buttons to toolbar
    
    m_toolBar.add(m_currencyFormattingButton);
    m_toolBar.add(m_singleCellFormattingButton);
    m_toolBar.add(m_headerFormattingButton);
    m_toolBar.add(m_divisionFormattingButton);
    m_toolBar.addSeparator();
    m_toolBar.add(m_greenStoplightButton);
    m_toolBar.add(m_yellowStoplightButton);
    m_toolBar.add(m_redStoplightButton);

    
    // Add listeners to toolbar buttons
    m_currencyFormattingButton.addActionListener(currencyFormattingListener);
    m_singleCellFormattingButton.addActionListener(
      singleCellFormattingListener);
    m_headerFormattingButton.addActionListener(headerFormattingListener);
    m_divisionFormattingButton.addActionListener(divisionFormattingListener);
    m_greenStoplightButton.addActionListener(greenStoplightListener);
    m_yellowStoplightButton.addActionListener(yellowStoplightListener);
    m_redStoplightButton.addActionListener(redStoplightListener);

    m_panel = new JPanel();
    m_panel.setLayout(new BorderLayout());
    
    // put all of these into the frame
    setJMenuBar(m_menuBar);
    getContentPane().removeAll();
    getContentPane().add(m_toolBar, BorderLayout.NORTH);
    getContentPane().add(m_panel, BorderLayout.CENTER);
  
    // add a listener to listen for the window closing event
    addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        cleanUp();
      }
    });
    
    // Create the crosstab for formatting
    createCrosstab();
  }

  /**
   * Remove a Rule from a RuleBundle.  This method only
   * works for RuleBundles associated with the GridViewDatabodyStyleManager.
   * 
   * @param ruleID the ID of the Rule to remove from the bundle
   * @param ruleBundleID the ID of the RuleBundle from which to remove the Rule
   * @param bundle the RuleBundle from which to remove the Rule
   * 
   * @return true if the Rule was removed from the bundle, false if not
   * 
   * @see #DIVISION_BUNDLE
   * @see #STOPLIGHT_BUNDLE
   * 
   * @see #DIVISION_RULE
   * @see #SINGLE_CELL_RULE
   * @see #GREEN_STOPLIGHT_RULE
   * @see #YELLOW_STOPLIGHT_RULE
   * @see #RED_STOPLIGHT_RULE
   */
  protected boolean removeDatabodyStyleRule(int ruleID, int ruleBundleID, 
    RuleBundle bundle) {
    // If there are Rules in the bundle, count the Rules that precede the
    // one to be removed to find its index in the bundle.
    if (bundle != null && bundle.getCount() > 0) {
      int index = 0;
      boolean[] rules = m_databodyStyleRules[ruleBundleID];
      for (int i = 0; i < rules.length && i < ruleID; i++) {
        if (rules[i]) {
          index++;
        }
      }

      // Remove the Rule at the determined index from the bundle, and clear
      // the flag indicating the the Rule is not set on the Crosstab.
      bundle.removeRule(index);
      rules[ruleID] = false;

      // Return true to indicate that the RuleBundle was changed.
      return true;
    }

    // Return false to indicate that the RuleBundle was not changed.
    return false;
  }

  /**
   * Remove the RuleBundle with the given name from the Vector of bundles.
   * If no such RuleBundle exists, the Vector will not be modified.
   * 
   * @param id the ID of the RuleBundle
   * @param name the name of the RuleBundle to remove from the Vector
   * @param bundles the Vector of bundles from which to remove the RuleBundle 
   *                with the given ID
   * 
   * @see #DIVISION_BUNDLE
   * @see #STOPLIGHT_BUNDLE
   * @see #DIVISION_BUNDLE_NAME
   * @see #STOPLIGHT_BUNDLE_NAME
   */
  protected void removeDatabodyStyleRuleBundle(int id, String name, 
    Vector bundles) {
    // Get the RuleBundle with the given ID from the Vector.
    RuleBundle bundle = getDatabodyStyleRuleBundle(name, bundles);

    // If such a RuleBundle exists, remove it from the Vector and clear the
    // flag indicating that the bundle is not set on the Crosstab.
    if (bundle != null) {
      bundles.removeElement(bundle);
      m_databodyStyleRuleBundles[id] = false;
    }
  }

  /**
   * Set the given RuleBundle in the Vector of bundles.  If a RuleBundle
   * with the same ID as the one passed in already exists in the Vector,
   * then it will be replaced with the one passed in.  If no RuleBundle with
   * the same ID exists in the Vector, then the given RuleBundle will be 
   * inserted into the Vector, maintaining established order among the 
   * RuleBundles.
   * 
   * @param id the ID of the RuleBundle being set
   * @param bundle the RuleBundle to set in the Vector of bundles
   * @param bundles the Vector of RulesBundles on which the given RuleBundle
   *                will be set
   * 
   * @see #DIVISION_BUNDLE
   * @see #STOPLIGHT_BUNDLE
   */
  protected void setDatabodyStyleRuleBundle(int id, RuleBundle bundle, 
    Vector bundles) {
    // The index of the existing bundle with the same ID as the new bundle,
    // and also the index at which to insert the new bundle
    int index = -1;

    // Get the index of the existing bundle with the same ID as the new one, 
    // if it exists.
    RuleBundle rb = getDatabodyStyleRuleBundle(bundle.getID(), bundles);
    if (rb != null) {
      index = bundles.indexOf(rb);
    }

    // If the index is greater than or equal to zero, then there already exists
    // a bundle in the Vector with the same ID as the new one.  In that case,
    // replace the old bundle with the new one.
    if (index >= 0) {
      bundles.removeElementAt(index);
      bundles.insertElementAt(bundle, index);
    }
    // If the index is less than zero, no bundle with the same ID as the new 
    // one exists in the Vector.  Insert the new bundle into its proper place 
    // in the order.
    else {
      // Count the flags of bundles that should precede the new bundle to find 
      // the index at which the new bundle is to be inserted in the Vector.  
      index = 0;
      for (int i = 0; i < m_databodyStyleRuleBundles.length; i++) {
        if (m_databodyStyleRuleBundles[i]) {
          index++;
        }
      }

      // Insert the new bundle at the determined index, and mark the flag 
      // indicating that the bundle has been set on the Crosstab.
      bundles.insertElementAt(bundle, index);
      m_databodyStyleRuleBundles[id] = true;
    }
  }
}