/*
 * @(#)EnumSyntax.java	1.5 04/01/07
 *
 * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */


package javax.print.attribute;

import java.io.InvalidObjectException;
import java.io.ObjectStreamException;
import java.io.Serializable;

/**
 * Class EnumSyntax is an abstract base class providing the common 
 * implementation of all "type safe enumeration" objects. An enumeration class 
 * (which extends class EnumSyntax) provides a group of enumeration values 
 * (objects) that are singleton instances of the enumeration class; for example: 
 * <PRE>
 *     public class Bach extends EnumSyntax {
 *         public static final Bach JOHANN_SEBASTIAN     = new Bach(0);
 *         public static final Bach WILHELM_FRIEDEMANN   = new Bach(1);
 *         public static final Bach CARL_PHILIP_EMMANUEL = new Bach(2);
 *         public static final Bach JOHANN_CHRISTIAN     = new Bach(3);
 *         public static final Bach P_D_Q                = new Bach(4);
 *
 *         private static final String[] stringTable = {
 *             "Johann Sebastian Bach",
 *              "Wilhelm Friedemann Bach",
 *              "Carl Philip Emmanuel Bach",
 *              "Johann Christian Bach",
 *              "P.D.Q. Bach"
 *         };
 *
 *         protected String[] getStringTable() {
 *             return stringTable;
 *         }
 *
 *         private static final Bach[] enumValueTable = {
 *             JOHANN_SEBASTIAN,
 *              WILHELM_FRIEDEMANN,
 *              CARL_PHILIP_EMMANUEL,
 *              JOHANN_CHRISTIAN,
 *              P_D_Q
 *         };
 *
 *         protected EnumSyntax[] getEnumValueTable() {
 *             return enumValueTable;
 *         }
 *     }
 * </PRE>
 * You can then write code that uses the <CODE>==</CODE> and <CODE>!=</CODE> 
 * operators to test enumeration values; for example: 
 * <PRE>
 *     Bach theComposer;
 *     . . .
 *     if (theComposer == Bach.JOHANN_SEBASTIAN) {
 *         System.out.println ("The greatest composer of all time!");
 *     }
 * </PRE>
 * The <CODE>equals()</CODE> method for an enumeration class just does a test 
 * for identical objects (<CODE>==</CODE>). 
 * <P>
 * You can convert an enumeration value to a string by calling {@link 
 * #toString() <CODE>toString()</CODE>}. The string is obtained from a table 
 * supplied by the enumeration class.
 * <P>
 * Under the hood, an enumeration value is just an integer, a different integer 
 * for each enumeration value within an enumeration class. You can get an 
 * enumeration value's integer value by calling {@link #getValue() 
 * <CODE>getValue()</CODE>}. An enumeration value's integer value is established 
 * when it is constructed (see {@link #EnumSyntax(int) 
 * <CODE>EnumSyntax(int)</CODE>}). Since the constructor is protected, the only 
 * possible enumeration values are the singleton objects declared in the 
 * enumeration class; additional enumeration values cannot be created at run 
 * time. 
 * <P>
 * You can define a subclass of an enumeration class that extends it with 
 * additional enumeration values. The subclass's enumeration values' integer 
 * values need not be distinct from the superclass's enumeration values' integer 
 * values; the <CODE>==</CODE>, <CODE>!=</CODE>, <CODE>equals()</CODE>, and 
 * <CODE>toString()</CODE> methods will still work properly even if the subclass 
 * uses some of the same integer values as the superclass. However, the 
 * application in which the enumeration class and subclass are used may need to 
 * have distinct integer values in the superclass and subclass. 
 * <P>
 *
 * @author  David Mendenhall
 * @author  Alan Kaminsky
 */
public abstract class EnumSyntax implements Serializable, Cloneable {

    private static final long serialVersionUID = -2739521845085831642L; 

    /**
     * This enumeration value's integer value.
     * @serial
     */
    private int value;

    /**
     * Construct a new enumeration value with the given integer value.
     *
     * @param  value  Integer value.
     */
    protected EnumSyntax(int value) {
	this.value = value;
    }

    /**
     * Returns this enumeration value's integer value.
     * @return the value
     */
    public int getValue() {
	return value;
    }

    /**
     * Returns a clone of this enumeration value, which to preserve the 
     * semantics of enumeration values is the same object as this enumeration 
     * value. 
     */
    public Object clone() {
	return this;
    }

    /**
     * Returns a hash code value for this enumeration value. The hash code is
     * just this enumeration value's integer value.
     */
    public int hashCode() {
	return value;
    }

    /**
     * Returns a string value corresponding to this enumeration value.
     */
    public String toString() {

	String[] theTable = getStringTable();
	int theIndex = value - getOffset();
	return
	    theTable != null && theIndex >= 0 && theIndex < theTable.length ?
	    theTable[theIndex] :
	    Integer.toString (value);
    }
        
    /**
     * During object input, convert this deserialized enumeration instance to
     * the proper enumeration value defined in the enumeration attribute class.
     *
     * @return  The enumeration singleton value stored at index
     *          <I>i</I>-<I>L</I> in the enumeration value table returned by 
     *          {@link #getEnumValueTable() <CODE>getEnumValueTable()</CODE>}, 
     *          where <I>i</I> is this enumeration value's integer value and 
     *          <I>L</I> is the value returned by {@link #getOffset() 
     *          <CODE>getOffset()</CODE>}. 
     *
     * @throws ObjectStreamException if the stream can't be deserialised
     * @throws  InvalidObjectException
     *     Thrown if the enumeration value table is null, this enumeration 
     *     value's integer value does not correspond to an element in the 
     *     enumeration value table, or the corresponding element in the 
     *     enumeration value table is null. (Note: {@link 
     *     java.io.InvalidObjectException InvalidObjectException} is a subclass 
     *     of {@link java.io.ObjectStreamException ObjectStreamException}, which 
     *     <CODE>readResolve()</CODE> is declared to throw.) 
     */
    protected Object readResolve() throws ObjectStreamException {

	EnumSyntax[] theTable = getEnumValueTable();

	if (theTable == null) {
	    throw new InvalidObjectException(
				"Null enumeration value table for class " +
				getClass());
	}

	int theOffset = getOffset();
	int theIndex = value - theOffset;

	if (0 > theIndex || theIndex >= theTable.length) {
	    throw new InvalidObjectException
		("Integer value = " +  value + " not in valid range " +
		 theOffset + ".." + (theOffset + theTable.length - 1) +
		 "for class " + getClass());
	}

	EnumSyntax result = theTable[theIndex];
	if (result == null) {
	    throw new InvalidObjectException
		("No enumeration value for integer value = " +
		 value + "for class " + getClass());
	}
	return result;
    }

    // Hidden operations to be implemented in a subclass.

    /**
     * Returns the string table for this enumeration value's enumeration class. 
     * The enumeration class's integer values are assumed to lie in the range 
     * <I>L</I>..<I>L</I>+<I>N</I>-1, where <I>L</I> is the value returned by 
     * {@link #getOffset() <CODE>getOffset()</CODE>} and <I>N</I> is the length 
     * of the string table. The element in the string table at index 
     * <I>i</I>-<I>L</I> is the value returned by {@link #toString() 
     * <CODE>toString()</CODE>} for the enumeration value whose integer value 
     * is <I>i</I>. If an integer within the above range is not used by any 
     * enumeration value, leave the corresponding table element null. 
     * <P>
     * The default implementation returns null. If the enumeration class (a 
     * subclass of class EnumSyntax) does not override this method to return a 
     * non-null string table, and the subclass does not override the {@link 
     * #toString() <CODE>toString()</CODE>} method, the base class {@link 
     * #toString() <CODE>toString()</CODE>} method will return just a string 
     * representation of this enumeration value's integer value. 
     * @return the string table
     */
    protected String[] getStringTable() {
	return null;
    }

    /**
     * Returns the enumeration value table for this enumeration value's 
     * enumeration class. The enumeration class's integer values are assumed to 
     * lie in the range <I>L</I>..<I>L</I>+<I>N</I>-1, where <I>L</I> is the 
     * value returned by {@link #getOffset() <CODE>getOffset()</CODE>} and 
     * <I>N</I> is the length of the enumeration value table. The element in the 
     * enumeration value table at index <I>i</I>-<I>L</I> is the enumeration 
     * value object whose integer value is <I>i</I>; the {@link #readResolve() 
     * <CODE>readResolve()</CODE>} method needs this to preserve singleton 
     * semantics during deserialization of an enumeration instance. If an 
     * integer within the above range is not used by any enumeration value, 
     * leave the corresponding table element null. 
     * <P>
     * The default implementation returns null. If the enumeration class (a 
     * subclass of class EnumSyntax) does not override this method to return 
     * a non-null enumeration value table, and the subclass does not override 
     * the {@link #readResolve() <CODE>readResolve()</CODE>} method, the base 
     * class {@link #readResolve() <CODE>readResolve()</CODE>} method will throw 
     * an exception whenever an enumeration instance is deserialized from an 
     * object input stream. 
     * @return the value table
     */
    protected EnumSyntax[] getEnumValueTable() {
	return null;
    }

    /**
     * Returns the lowest integer value used by this enumeration value's
     * enumeration class.
     * <P>
     * The default implementation returns 0. If the enumeration class (a 
     * subclass of class EnumSyntax) uses integer values starting at other than 
     * 0, override this method in the subclass. 
     * @return the offset of the lowest enumeration value.
     */
    protected int getOffset() {
	return 0;
    } 
    
}
