
package java_cup;

import java.util.BitSet;

/**
 * A set of terminals implemented as a bitset.
 * 
 * @version last updated: 11/25/95
 * @author Scott Hudson
 */
public class terminal_set {

  /*-----------------------------------------------------------*/
  /*--- Constructor(s) ----------------------------------------*/
  /*-----------------------------------------------------------*/

  /** Constructor for an empty set. */
  public terminal_set() {
    /* allocate the bitset at what is probably the right size */
    _elements = new BitSet(terminal.number());
  }

  /* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */

  /**
   * Constructor for cloning from another set.
   * 
   * @param other the set we are cloning from.
   */
  public terminal_set(terminal_set other) throws internal_error {
    not_null(other);
    _elements = (BitSet) other._elements.clone();
  }

  /*-----------------------------------------------------------*/
  /*--- (Access to) Static (Class) Variables ------------------*/
  /*-----------------------------------------------------------*/

  /** Constant for the empty set. */
  public static final terminal_set EMPTY = new terminal_set();

  /*-----------------------------------------------------------*/
  /*--- (Access to) Instance Variables ------------------------*/
  /*-----------------------------------------------------------*/

  /** Bitset to implement the actual set. */
  protected BitSet _elements;

  /*-----------------------------------------------------------*/
  /*--- General Methods ----------------------------------------*/
  /*-----------------------------------------------------------*/

  /**
   * Helper function to test for a null object and throw an exception if one is
   * found.
   * 
   * @param obj the object we are testing.
   */
  protected void not_null(Object obj) throws internal_error {
    if (obj == null)
      throw new internal_error("Null object used in set operation");
  }

  /* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */

  /** Determine if the set is empty. */
  public boolean empty() {
    return equals(EMPTY);
  }

  /* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */

  /**
   * Determine if the set contains a particular terminal.
   * 
   * @param sym the terminal symbol we are looking for.
   */
  public boolean contains(terminal sym) throws internal_error {
    not_null(sym);
    return _elements.get(sym.index());
  }

  /* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */

  /**
   * Given its index determine if the set contains a particular terminal.
   * 
   * @param indx the index of the terminal in question.
   */
  public boolean contains(int indx) {
    return _elements.get(indx);
  }

  /* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */

  /**
   * Determine if this set is an (improper) subset of another.
   * 
   * @param other the set we are testing against.
   */
  public boolean is_subset_of(terminal_set other) throws internal_error {
    not_null(other);

    /* make a copy of the other set */
    BitSet copy_other = (BitSet) other._elements.clone();

    /* and or in */
    copy_other.or(_elements);

    /* if it hasn't changed, we were a subset */
    return copy_other.equals(other._elements);
  }

  /* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */

  /**
   * Determine if this set is an (improper) superset of another.
   * 
   * @param other the set we are testing against.
   */
  public boolean is_superset_of(terminal_set other) throws internal_error {
    not_null(other);
    return other.is_subset_of(this);
  }

  /* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */

  /**
   * Add a single terminal to the set.
   * 
   * @param sym the terminal being added.
   * @return true if this changes the set.
   */
  public boolean add(terminal sym) throws internal_error {
    boolean result;

    not_null(sym);

    /* see if we already have this */
    result = _elements.get(sym.index());

    /* if not we add it */
    if (!result)
      _elements.set(sym.index());

    return result;
  }

  /* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */

  /**
   * Remove a terminal if it is in the set.
   * 
   * @param sym the terminal being removed.
   */
  public void remove(terminal sym) throws internal_error {
    not_null(sym);
    _elements.clear(sym.index());
  }

  /* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */

  /**
   * Add (union) in a complete set.
   * 
   * @param other the set being added.
   * @return true if this changes the set.
   */
  public boolean add(terminal_set other) throws internal_error {
    not_null(other);

    /* make a copy */
    BitSet copy = (BitSet) _elements.clone();

    /* or in the other set */
    _elements.or(other._elements);

    /* changed if we are not the same as the copy */
    return !_elements.equals(copy);
  }

  /* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */

  /**
   * Determine if this set intersects another.
   * 
   * @param other the other set in question.
   */
  public boolean intersects(terminal_set other) throws internal_error {
    not_null(other);
    return _elements.intersects(other._elements);
  }

  /* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */

  /** Equality comparison. */
  public boolean equals(terminal_set other) {
    if (other == null)
      return false;
    else
      return _elements.equals(other._elements);
  }

  /* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */

  /** Generic equality comparison. */
  @Override
  public boolean equals(Object other) {
    if (!(other instanceof terminal_set))
      return false;
    else
      return equals((terminal_set) other);
  }

  /* . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . */

  @Override
  public int hashCode() {
    return _elements.hashCode();
  }

  /** Convert to string. */
  @Override
  public String toString() {
    String result;
    boolean comma_flag;

    result = "{";
    comma_flag = false;
    for (int t = 0; t < terminal.number(); t++) {
      if (_elements.get(t)) {
        if (comma_flag)
          result += ", ";
        else
          comma_flag = true;

        result += terminal.find(t).name();
      }
    }
    result += "}";

    return result;
  }

  /*-----------------------------------------------------------*/

}
