package java_cup;

import java.util.Hashtable;
import java.util.Enumeration;

/** This class represents a set of symbols and provides a series of 
 *  set operations to manipulate them.
 *
 * @see     java_cup.symbol
 * @version last updated: 11/25/95
 * @author  Scott Hudson
 */
public class symbol_set {

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

	/** Constructor for an empty set. */
	public symbol_set() {
	}

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

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

	/** A hash table to hold the set. Symbols are keyed using their name string. 
	 */
	protected Hashtable _all = new Hashtable(11);

	/** Access to all elements of the set. */
	public Enumeration all() {
		return _all.elements();
	}

	/** size of the set */
	public int size() {
		return _all.size();
	}

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

	/** 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 contains a particular symbol. 
	 * @param sym the symbol we are looking for.
	 */
	public boolean contains(symbol sym) {
		return _all.containsKey(sym.name());
	}

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

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

		/* walk down our set and make sure every element is in the other */
		for (Enumeration e = all(); e.hasMoreElements();)
			if (!other.contains((symbol) e.nextElement()))
				return false;

		/* they were all there */
		return true;
	}

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

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

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

	/** Add a single symbol to the set.  
	 * @param sym the symbol we are adding.
	 * @return true if this changes the set.
	 */
	public boolean add(symbol sym) throws internal_error
	{
		Object previous;

		not_null(sym);

		/* put the object in */
		previous = _all.put(sym.name(), sym);

		/* if we had a previous, this is no change */
		return previous == null;
	}

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

	/** Remove a single symbol if it is in the set. 
	 * @param sym the symbol we are removing.
	 */
	public void remove(symbol sym) throws internal_error
	{
		not_null(sym);
		_all.remove(sym.name());
	}

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

	/** Add (union) in a complete set.  
	 * @param other the set we are adding in.
	 * @return true if this changes the set. 
	 */
	public boolean add(symbol_set other) throws internal_error
	{
		boolean result = false;

		not_null(other);

		/* walk down the other set and do the adds individually */
		for (Enumeration e = other.all(); e.hasMoreElements();)
			result = add((symbol) e.nextElement()) || result;

		return result;
	}

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

	/** Remove (set subtract) a complete set. 
	 * @param other the set we are removing.
	 */
	public void remove(symbol_set other) throws internal_error
	{
		not_null(other);

		/* walk down the other set and do the removes individually */
		for (Enumeration e = other.all(); e.hasMoreElements();)
			remove((symbol) e.nextElement());
	}

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

	/** Equality comparison. */
	public boolean equals(symbol_set other)
	{
		if (other == null || other.size() != size())
			return false;

		/* once we know they are the same size, then improper subset does test */
		try {
			return is_subset_of(other);
		} catch (internal_error e) {
			/* can't throw the error (because super class doesn't), so we crash */
			e.crash();
			return false;
		}
	}

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

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

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

	/** Compute a hash code. */
	public int hashCode()
	{
		int result = 0;
		int cnt;
		Enumeration e;

		/* hash together codes from at most first 5 elements */
		for (e = all(), cnt = 0; e.hasMoreElements() && cnt < 5; cnt++)
			result ^= ((symbol) e.nextElement()).hashCode();

		return result;
	}

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

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

		result = "{";
		comma_flag = false;
		for (Enumeration e = all(); e.hasMoreElements();)
		{
			if (comma_flag)
				result += ", ";
			else
				comma_flag = true;

			result += ((symbol) e.nextElement()).name();
		}
		result += "}";

		return result;
	}

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

}
