/*
 * @(#)ObjectInputStream.java	1.27 97/03/03
 * 
 * Copyright (c) 1995, 1996 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * This software is the confidential and proprietary information of Sun
 * Microsystems, Inc. ("Confidential Information").  You shall not
 * disclose such Confidential Information and shall use it only in
 * accordance with the terms of the license agreement you entered into
 * with Sun.
 * 
 * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
 * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
 * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
 * THIS SOFTWARE OR ITS DERIVATIVES.
 * 
 * CopyrightVersion 1.1_beta
 */

package java.io;

import java.util.Vector;
import java.util.Stack;
import java.util.Hashtable;

/**
 * An ObjectInputStream deserializes primitive data and objects previously
 * written using an ObjectOutputStream.
 * 
 * ObjectOutputStream and ObjectInputStream can provide an application
 * with persistent storage for graphs of objects when used with a
 * FileOutputStream and FileInputStream respectively.
 * ObjectInputStream is used to recover those objects previously
 * serialized. Other uses include passing objects between hosts using
 * a socket stream or for marshaling and unmarshaling arguments and
 * parameters in a remote communication system.<p>
 *
 * ObjectInputStream ensures that the types of all objects in the
 * graph created from the stream match the classes present in the
 * Java Virtual Machine.  Classes are loaded as required using the
 * standard mechanisms. <p>
 *
 * Only objects that support the java.io.Serializable or
 * java.io.Externalizable interface can be read from streams.
 *
 * The method <STRONG>readObject</STRONG> is used to read an object
 * from the stream.  Java's safe casting should be used to get the
 * desired type.  In Java, strings and arrays are objects and are
 * treated as objects during serialization. When read they need to be
 * cast to the expected type.<p>
 *
 * Primitive data types can be read from the stream using the appropriate
 * method on DataInput. <p>
 * 
 * The default deserialization mechanism for objects restores the
 * contents of each field to the value and type it had when it was written.
 * Fields declared as transient or static are ignored by the
 * deserialization process.  References to other objects cause those
 * objects to be read from the stream as necessary.  Graphs of objects
 * are restored correctly using a reference sharing mechanism.  New
 * objects are always allocated when deserializing, which prevents
 * existing objects from being overwritten. <p>
 *
 * Reading an object is analogous to running the constructors of a new
 * object.  Memory is allocated for the object and initialized to zero
 * (NULL).  No-arg constructors are invoked for the non-serializable
 * classes and then the fields of the serializable classes are
 * restored from the stream starting with the serializable class closest to
 * java.lang.object and finishing with the object's most specifiec
 * class. <p>
 *
 * For example to read from a stream as written by the example in
 * ObjectOutputStream: <br>
 *
 * <PRE>
 *	FileInputStream istream = new FileInputStream("t.tmp");
 *	ObjectInputStream p = new ObjectInputStream(istream);
 *
 *	int i = p.readInt();
 *	String today = (String)p.readObject();
 *	Date date = (Date)p.readObject();
 *
 *	istream.close();
 * </PRE>
 *
 * Classes control how they are serialized by implementing either the
 * java.io.Serializable or java.io.Externalizable interfaces.<P>
 *
 * Implementing the Serializable interface allows object serialization
 * to save and restore the entire state of the object and it allows
 * classes to evolve between the time the stream is written and the time it is
 * read.  It automatically traverses references between objects,
 * saving and restoring entire graphs.
 *
 * Serializable classes that require special handling during the
 * serialization and deserialization process should implement both
 * of these methods:<p>
 *
 * <PRE>
 * private void writeObject(java.io.ObjectOutputStream stream)
 *     throws IOException;
 * private void readObject(java.io.ObjectInputStream stream)
 *     throws IOException, ClassNotFoundException; 
 * </PRE><p>
 *
 * The readObject method is responsible for reading and restoring the
 * state of the object for its particular class using data written to
 * the stream by the corresponding writeObject method.  The method
 * does not need to concern itself with the state belonging to its
 * superclasses or subclasses.  State is restored by reading data from
 * the ObjectInputStream for the individual fields and making
 * assignments to the appropriate fields of the object.  Reading
 * primitive data types is supported by DataInput. <p>
 *
 * Serialization does not read or assign values to the fields of any
 * object that does not implement the java.io.Serializable interface.
 * Subclasses of Objects that are not serializable can be
 * serializable. In this case the non-serializable class must have a
 * no-arg constructor to allow its fields to be initialized.  In this
 * case it is the responsibility of the subclass to save and restore
 * the state of the non-serializable class. It is frequently the case that
 * the fields of that class are accessible (public, package, or
 * protected) or that there are get and set methods that can be used
 * to restore the state. <p>
 *
 * Any exception that occurs while deserializing an object will be
 * caught by the ObjectInputStream and abort the reading process. <p>
 *
 * Implementing the Externalizable interface allows the object to
 * assume complete control over the contents and format of the object's
 * serialized form.  The methods of the Externalizable interface,
 * writeExternal and readExternal, are called to save and restore the
 * objects state.  When implemented by a class they can write and read
 * their own state using all of the methods of ObjectOutput and
 * ObjectInput.  It is the responsibility of the objects to handle any
 * versioning that occurs.
 *
 * @author  Roger Riggs
 * @version 1.27, 03 Mar 1997
 * @see java.io.DataInput
 * @see java.io.ObjectOutputStream
 * @see java.io.Serializable
 * @since   JDK1.1
 */
public class ObjectInputStream extends InputStream
	implements ObjectInput, ObjectStreamConstants
{ 
    /**
     * Create an ObjectInputStream that reads from the specified InputStream.
     * The stream header containing the magic number and version number
     * are read from the stream and verified. This method will block
     * until the corresponding ObjectOutputStream has written and flushed the header.
     * @exception StreamCorruptedException The version or magic number are incorrect.
     * @exception IOException An exception occurred in the underlying stream.
     * @since     JDK1.1
     */
    public ObjectInputStream(InputStream in)
	throws IOException, StreamCorruptedException
  {
  	/*
  	 * Save the input stream to read bytes from
  	 * Create a DataInputStream used to read primitive types.
  	 * Setup the DataInputStream to read from this ObjectInputStream
  	 */
	this.in = in;
	dis  = new DataInputStream(this); 
	readStreamHeader();
	resetStream();
    }

    /**
     * Read an object from the ObjectInputStream.
     * The class of the object, the signature of the class, and the values
     * of the non-transient and non-static fields of the class and all
     * of its supertypes are read.  Default deserializing for a class can be
     * overriden using the writeObject and readObject methods.
     * Objects referenced by this object are read transitively so
     * that a complete equivalent graph of objects is reconstructed by readObject. <p>
     *
     * The root object is completly restored when all of its fields
     * and the objects it references are completely restored.  At this
     * point the object validation callbacks are executed in order
     * based on their registered priorities. The callbacks are
     * registered by objects (in the readObject special methods)
     * as they are individually restored.
     *
     * Exceptions are thrown for problems with the InputStream and for classes
     * that should not be deserialized.  All exceptions are fatal to the 
     * InputStream and leave it in an indeterminate state; it is up to the caller
     * to ignore or recover the stream state.
     * @exception java.lang.ClassNotFoundException Class of a serialized object
     *      cannot be found.
     * @exception InvalidClassException Something is wrong with a class used by
     *     serialization.
     * @exception StreamCorruptedException Control information in the
     *     stream is inconsistent.
     * @exception OptionalDataException Primitive data was found in the 
     * stream instead of objects.
     * @exception IOException Any of the usual Input/Output related exceptions.
     * @since     JDK1.1
     */
    public final Object readObject()
	throws OptionalDataException, ClassNotFoundException, IOException
    {
	/* If the stream is in blockData mode and there's any data
	 * left throw an exception to report how much there is.
	 */
	int n;
	if (blockDataMode && (n = available()) > 0) {
	    throw new OptionalDataException(n);
	}
	
	/*
	 * Look ahead now to absorb any pending reset's.
	 * Before changing the state.
	 */
	peekCode();

	/* Save the current state and get ready to read an object. */
	Object prevObject = currentObject;
	ObjectStreamClass prevClass = currentClassDesc;
	boolean prevBlockDataMode = setBlockData(false);
	
	recursionDepth++;	// Entering
	Object obj = null;

	/* 
	 * Check for reset, handle it before reading an object.
	 */

	byte rcode;
	rcode = readCode();
	try {
	    /*
	     * Dispatch on the next code in the stream.
	     */
	    int wireoffset = -1;

	    switch (rcode) {
		
	    case TC_NULL:
		obj = null;
		break;
		
	    case TC_REFERENCE: 
		/* This is a reference to a pre-existing object */
		wireoffset = readInt() - baseWireHandle; 
		
		try {
		    obj = wireHandle2Object.elementAt(wireoffset);
		} catch (ArrayIndexOutOfBoundsException e) {
		    throw new StreamCorruptedException("Reference to object never serialized.");
		}
		break;
		
	    case TC_STRING:
		obj = readUTF(); 
		assignWireOffset(obj);
		break;
		
	    case TC_CLASS:
		ObjectStreamClass v = (ObjectStreamClass)readObject();
		if (v == null) {
		    /*
		     * No class descriptor in stream or class not serializable
		     */
		    throw new StreamCorruptedException("Class not in stream");
		}
		obj = v.forClass();
		assignWireOffset(obj);
		break;
		
	    case TC_CLASSDESC:
		obj = inputClassDescriptor();
		break;
		
	    case TC_ARRAY:
		obj = inputArray();
		break;
		
	    case TC_OBJECT:
		wireoffset = inputObject();
		obj = currentObject;
		if (enableResolve) {
		    /* Hook for alternate object */
		    obj = resolveObject(obj);
		    wireHandle2Object.setElementAt(obj, wireoffset);
		}
		break;
		
	    case TC_ENDBLOCKDATA:
		if (!prevBlockDataMode)
		    throw new StreamCorruptedException("Unexpected end of block data");
		pushbackCode(TC_ENDBLOCKDATA);
		count = -1;	/* Flag EOF */ 
		throw new OptionalDataException(true);
		
	    case TC_BLOCKDATA:
	    case TC_BLOCKDATALONG:
		if (rcode == TC_BLOCKDATALONG) { /* long block: 32 bit size */
		    int b3 = in.read();
		    int b2 = in.read();
		    int b1 = in.read();
		    int b0 = in.read();
		    if ((b3 | b2 | b1 | b0) < 0)
			throw new StreamCorruptedException("EOF expecting count");
		    count = (b3 << 24) | (b2 << 16) | (b1 << 8) | b0;
		    if (count < 0)
			throw new StreamCorruptedException("Negative block data size");
		} else {			/* normal block: 8 bit size */
		    count = in.read();
		    if (count < 0)
			throw new StreamCorruptedException("EOF expecting count");
		}

    		if (!prevBlockDataMode)
		    throw new StreamCorruptedException("Unexpected blockdata");
		
		throw new OptionalDataException(count);
		
	    case TC_EXCEPTION:
		/* An exception happened during writing, reset the
		 * stream, read the exception, reset the stream and
		 * throw a writeAbortedException with the exception
		 * that was read.
		 */
		resetStream();
		IOException ee = (IOException)readObject();
		resetStream();
		throw new WriteAbortedException("Writing aborted by exception", ee);

	    default:
		throw new StreamCorruptedException("Unknown code in readObject " + rcode);
	    }
	} catch (OptionalDataException optdata) {
	    /* OptionalDataExceptions won't terminate everything.
	     * so just rethrow it.
	     */
	    throw optdata;
	} catch(IOException ee) {
	    if (abortIOException == null && abortClassNotFoundException == null)
		abortIOException = ee;
	} catch(ClassNotFoundException ee) {
	    if (abortIOException == null && abortClassNotFoundException == null)
		abortClassNotFoundException = ee;
	} finally {
	    recursionDepth --;
	    currentObject = prevObject;
	    currentClassDesc = prevClass;
	    setBlockData(prevBlockDataMode);
	}
	
	/* Check for thrown exceptions and re-throw them, clearing them if
	 * this is the last recursive call .
	 */
	IOException exIOE = abortIOException;
	if (recursionDepth == 0)
	    abortIOException = null;
	if (exIOE != null)
	    throw exIOE;

	
	ClassNotFoundException exCNF = abortClassNotFoundException;
	if (recursionDepth == 0)
	    abortClassNotFoundException = null;
	if (exCNF != null) {
	    throw exCNF;
	}
	
	// Check if this is the last nested read, if so
	// Call the validations
	if (recursionDepth == 0) {
	    doValidations();
	}

	return obj;
    }

    /**
     * Read the non-static and non-transient fields of the current class
     * from this stream.  This may only be called from the readObject method
     * of the class being deserialized. It will throw the NotActiveException
     * if it is called otherwise.
     *
     * @exception java.lang.ClassNotFoundException if the class of a serialized
     *              object could not be found.
     * @exception IOException        if an I/O error occurs.
     * @exception NotActiveException if the stream is not currently reading
     *              objects.
     * @since     JDK1.1
     */
    public final void defaultReadObject()
	throws IOException, ClassNotFoundException, NotActiveException
    {
	if (currentObject == null || currentClassDesc == null)
	    throw new NotActiveException("defaultReadObject");
	

	if (currentClassDesc.getFieldSequence() != null) {
	    boolean prevmode = setBlockData(false);
	    inputClassFields(currentObject, currentClass,
			     currentClassDesc.getFieldSequence());
	    setBlockData(prevmode);
	}
    }
    
    /**
     * Register an object to be validated before the graph is
     * returned.  While similar to resolveObject these validations are
     * called after the entire graph has been reconstituted.
     * Typically, a readObject method will register the object with
     * the stream so that when all of the objects are restored a final
     * set of validations can be performed.
     * @param obj the object to receive the validation callback.
     * @param prio controls the order of callbacks;zero is a good default.
     * Use higher numbers to be called back earlier, lower numbers for later
     * callbacks. Within a priority, callbacks are processed in no
     * particular order.
     *
     * @exception NotActiveException The stream is not currently reading objects
     * so it is invalid to register a callback.
     * @exception InvalidObjectException The validation object is null.
     * @since     JDK1.1
     */
    public synchronized void registerValidation(ObjectInputValidation obj,
						int prio)
	throws NotActiveException, InvalidObjectException
    {
	if (recursionDepth == 0) {
	    throw new NotActiveException("readObject not Active");
	}
	if (obj == null) {
	    throw new InvalidObjectException("Null is not a valid callback object");
	}

	ValidationCallback cb = new ValidationCallback(obj, prio);

	if (callbacks == null) {
	    callbacks = new Vector(100,100);
	}
	// insert at the end if the priority is less than or equal to
	// the last element.
	if (callbacks.isEmpty() ||
	    ((ValidationCallback)(callbacks.lastElement())).priority >= prio) {
	    callbacks.addElement(cb);
	    return;
	}

	// search for the element with priority that is <= to the new
	// priority, insert before it. 
	int size = callbacks.size();
	for (int i = 0; i < size; i++) {
	    ValidationCallback curr = (ValidationCallback)callbacks.elementAt(i);
	    if (curr.priority <= prio) {
		callbacks.insertElementAt(cb, i);
		break;
	    }
	}
    }

    /*
     * If any validations are pending, do them and cleanup the validation vector
     * if an exception is raised, it is passed on to abort the deserialization.
     */
    private void doValidations() throws InvalidObjectException {
	if (callbacks == null)
	    return;
	
	int size = callbacks.size();
	for (int i = 0; i < size; i++) {
	    ValidationCallback curr = (ValidationCallback)callbacks.elementAt(i);
	    curr.callback.validateObject();
	}
    }

    /**
     * Subclasses may implement this method to allow classes to be
     * fetched from an alternate source. 
     *
     * The corresponding method in ObjectOutputStream is
     * annotateClass.  This method will be invoked only once for each
     * unique class in the stream.  This method can be implemented by
     * subclasses to use an alternate loading mechanism but must
     * return a Class object.  Once returned, the serialVersionUID of the
     * class is compared to the serialVersionUID of the serialized class.
     * If there is a mismatch, the deserialization fails and an exception
     * is raised. <p>
     *
     * By default the class name is resolved relative to the class
     * that called readObject. <p>
     *
     * @exception ClassNotFoundException If class of
     * a serialized object cannot be found.
     * @since     JDK1.1
     */
    protected Class resolveClass(ObjectStreamClass v)
	throws IOException, ClassNotFoundException
    {
	/* Resolve by looking up the stack for a non-zero class
	 * loader. If not found use the system loader.
	 */
	return loadClass0(null, v.getName());
    }

    /* Resolve a class name relative to the specified class.  If the
     * class is null find the first available class loader up the
     * stack.  This will resolve classes relative to the caller of
     * ObjectInputStream instead of the itself. Classes must be
     * loaded/resolved relative to the application.
     */
    private native Class loadClass0(Class cl, String classname)
	throws ClassNotFoundException;

    /**
     * This method will allow trusted subclasses of ObjectInputStream
     * to substitute one object for another during
     * deserialization. Replacing objects is disabled until
     * enableResolveObject is called. The enableResolveObject method
     * checks that the stream requesting to resolve object can be
     * trusted. Every reference to serializable objects is passed to
     * resolveObject.  To insure that the private state of objects is
     * not unintentionally exposed only trusted streams may use
     * resolveObject. <p>
     *
     * This method is called after an object has been read but before it is
     * returned from readObject.  The default resolveObject method
     * just returns the new object. <p>
     *
     * When a subclass is replacing objects it must insure that the
     * substituted object is compatible with every field where the
     * reference will be stored.  Objects whose type is not a subclass
     * of the type of the field or array element abort the
     * serialization by raising an exception and the object is not be
     * stored. <p>
     *
     * This method is called only once when each object is first encountered.
     * All subsequent references to the object will be redirected to the
     * new object. <P>
     *
     * @exception IOException Any of the usual Input/Output exceptions.
     * @since     JDK1.1
     */
    protected Object resolveObject(Object obj)
    	throws IOException
    {
	return obj;
    }

    /**
     * Enable the stream to allow objects read from the stream to be replaced.
     * If the stream is a trusted class it is allowed to enable replacment.
     * Trusted classes are those classes with a classLoader equals null. <p>
     * 
     * When enabled the resolveObject method is called for every object
     * being deserialized.
     * 
     * @exception SecurityException The classloader of this stream object is non-null.
     * @since     JDK1.1
     */
    protected final boolean enableResolveObject(boolean enable)
	throws SecurityException
    {
	boolean previous = enableResolve;
	if (enable) {
	    ClassLoader loader = this.getClass().getClassLoader();
	    if (loader == null) {
		enableResolve = true;
		return previous;
	    }
	    throw new SecurityException("Not trusted class");
	} else {
	    enableResolve = false;
	}
	return previous;
    }


    /**
     * The readStreamHeader method is provided to allow subclasses to
     * read and verify their own stream headers. It reads and
     * verifies the magic number and version number.
     * @since     JDK1.1
     */
    protected void readStreamHeader()
	throws IOException, StreamCorruptedException
    {
	short incoming_magic = readShort();
	short incoming_version = readShort();
	if (incoming_magic != STREAM_MAGIC)
	    throw new StreamCorruptedException("InputStream does not contain a serialized object");
	
	if (incoming_version != STREAM_VERSION)
	    throw new StreamCorruptedException("Version Mismatch, Expected " +
					       STREAM_VERSION + " and got " +
					       incoming_version);
    }

    /*
     * Read a ObjectStreamClasss from the stream, it may recursively
     * create other ObjectStreamClasss for the classes it references.
     */
    private ObjectStreamClass inputClassDescriptor()
	throws IOException, InvalidClassException, ClassNotFoundException
    {

	/* Read the class name and hash */
	Class aclass;
	String classname = readUTF(); 
	long hash = readLong();

	/* Read a new class version descriptor from the stream */
	ObjectStreamClass v = new ObjectStreamClass(classname, hash);

	/* Assign the wire handle for this ObjectStreamClass and read it */
	int wireoffset = assignWireOffset(v); 
	v.read(this);

	/* Switch to BlockDataMode and call resolveClass.
	 * It may raise ClassNotFoundException.
	 * Consume any extra data or objects left by resolve class and
	 * read the endOfBlockData. Then switch out of BlockDataMode.
	 */
	boolean prevMode = setBlockData(true);
	aclass = resolveClass((ObjectStreamClass)v);
	SkipToEndOfBlockData();
	prevMode = setBlockData(prevMode);


	/* Verify that the class returned is "compatible" with
	 * the class description.  i.e. the name and hash match.
	 * Set the class this ObjectStreamClass will use to create 
	 * instances.
	 */
	if (aclass == null) {
	    throw new ClassNotFoundException(v.getName());
	}
	v.setClass(aclass);

	/* Get the superdescriptor of this one and it set it.
	 */
	ObjectStreamClass superdesc = (ObjectStreamClass)readObject();
	v.setSuperclass(superdesc);

	return v;
    }

    /* Private routine to read in an array. Called from inputObject
     * after the typecode has been read from the stream.
     */
    private Object inputArray()
	throws IOException, ClassNotFoundException
    {
	/* May raise ClassNotFoundException */
	ObjectStreamClass v = (ObjectStreamClass)readObject();
	
	Class arrayclass = v.forClass();

	/* This can't be done with new because only the top level array
	 * is needed and the type must be set properly.
	 * the lower level arrays will be created when they are read.
	 */
	int length = readInt();
	currentObject = allocateNewArray(arrayclass, length);
	assignWireOffset(currentObject);
	
	/* Now extract the array values
	 */
	inputArrayValues(currentObject, arrayclass);

	return currentObject;
    }

    /*
     * Read an instance of a class from the stream
     * The new object typecode has already been read and used to dispatch to here.
     * The ObjectStreamClass for the class is read and the class
     * of the object retrieved from it.
     * A new object is created of the specified class and
     * each serializable class is processed using either
     * the default serialization methods or class defined special methods
     * if they have been defined.
     * The handle for the object is returned, the object itself is in currentObject.
     */
    private int inputObject()
	throws IOException, ClassNotFoundException
    {
	int handle = -1;
	/*
	 * Get the descriptor and then class of the incomming object.
	 */
	currentClassDesc = (ObjectStreamClass)readObject();
	currentClass = currentClassDesc.forClass();
	
	
	/* If Externalizable,
	 *  Create an instance and tell it to read its data.
	 * else,
	 *  Handle it as a serializable class.
	 */
	if (currentClassDesc.isExternalizable()) {
	    try {
		currentObject = allocateNewObject(currentClass, currentClass);
		handle = assignWireOffset(currentObject);
		Externalizable ext = (Externalizable)currentObject;
		ext.readExternal(this);
	    } catch (IllegalAccessException e) {
		throw new InvalidClassException(currentClass.getName(),
					    "IllegalAccessException");
	    } catch (InstantiationException e) {
		throw new InvalidClassException(currentClass.getName(),
					    "InstantiationException");
	    }
	} else {
	    /* Count number of classes and descriptors we might have
	     * to work on.
	     */
	    ObjectStreamClass currdesc = currentClassDesc;
	    Class currclass = currentClass;

	    int spBase = spClass;	// current top of stack

	    /* The object's classes should be processed from supertype to subtype
	     * Push all the clases of the current object onto a stack.
	     * Note that only the serializable classes are represented
	     * in the descriptor list. 
	     *
	     * Handle versioning where one or more supertypes of
	     * have been inserted or removed.  The stack will
	     * contain pairs of descriptors and the corresponding
	     * class.  If the object has a class that did not occur in
	     * the original the descriptor will be null.  If the
	     * original object had a descriptor for a class not
	     * present in the local hierarchy of the object the class will be
	     * null.
	     *
	     */

	    /*
	     * This is your basic diff pattern, made simpler
	     * because reordering is not allowed.
	     */
	    for (currdesc = currentClassDesc, currclass = currentClass;
		 currdesc != null;
		 currdesc = currdesc.getSuperclass()) {

		/*
		 * Search the classes to see if thisthe class of this
		 * descriptor appears further up the hierarchy. Until
		 * it's found assume its an inserted class.  If it's
		 * not found, its the descriptor's class that has been
		 * removed.
		 */
		Class cc = currdesc.forClass();
		Class cl;
		for (cl = currclass; cl != null; cl = cl.getSuperclass()) {
		    if (cc == cl) {
			// found a superclass that matches this descriptor
			break;
		    } else {
			/* Ignore a class that doesn't match.  No
			 * action is needed since it is already
			 * initialized.
			 */
		    }
		}
		/* Test if there is room for this new entry.
		 * If not, double the size of the arrays and copy the contents.
		 */
		spClass++;
		if (spClass >= classes.length) {
		    int newlen = classes.length * 2;
		    Class[] newclasses = new Class[newlen];
		    ObjectStreamClass[] newclassdesc = new ObjectStreamClass[newlen];

		    System.arraycopy(classes, 0,
			     newclasses, 0,
			     classes.length);
		    System.arraycopy(classdesc, 0,
				     newclassdesc, 0,
				     classes.length);

		    classes = newclasses;
		    classdesc = newclassdesc;
		}

		if (cl == null) {
		    /* Class not found corresponding to this descriptor.
		     * Pop off all the extra classes pushed.
		     * Push the descriptor and a null class.
		     */
		    classdesc[spClass] = currdesc;
		    classes[spClass] = null;
		} else {
       		    /* Current class descriptor matches current class.
		     * Some classes may have been inserted.
		     * Record the match and advance the class, continue
		     * with the next descriptor.
		     */
		    classdesc[spClass] = currdesc;
		    classes[spClass] = cl;
		    currclass = cl.getSuperclass();
		}
	    }

	    /* Allocate a new object.  The object is only constructed
	     * above the highest serializable class and is set to
	     * default values for all more specialized classes.
	     * Remember the next wirehandle goes with the new object
	     */
	    try {
		currentObject = allocateNewObject(currentClass, currclass);
	    } catch (NoSuchMethodError e) {
		throw new InvalidClassException(currclass.getName(),
					    "NoSuchMethodError");
	    } catch (IllegalAccessException e) {
		throw new InvalidClassException(currclass.getName(),
					    "IllegalAccessException");
	    } catch (InstantiationException e) {
		throw new InvalidClassException(currclass.getName(),
					    "InstantiationException");
	    }
	    handle = assignWireOffset(currentObject);
	    
	    /* 
	     * For all the pushed descriptors and classes.
	     * If there is a descriptor but no class, skip the
	     * data for that class.
	     * If there is a class but no descriptor, just advance,
	     * The classes fields have already been initialized to default
	     * values.
	     * Otherwise, there is both a descriptor and class,
	     * 	if the class has its own writeObject and readObject methods
	     *	    set blockData = true; and call the readObject method
	     *	else
	     *	    invoke the defaultReadObject method
	     *	if the stream was written by class specific methods
	     *	    skip any remaining data a objects until TC_ENDBLOCKDATA
	     * Avoid setting BlockData=true unless necessary becase it flushes
	     * the buffer.
	     */
	    try {
		for (spClass = spClass; spClass > spBase; spClass--) {
		    /*
		     * Set current descriptor and corresponding class
		     */
		    currentClassDesc = classdesc[spClass];
		    currentClass = classes[spClass];

		    if (classes[spClass] != null) {
			/* Read the data from the stream described by the
			 * descriptor and store into the matching class.
			 */
			setBlockData(true);	/* any reads are from datablocks */
			ObjectStreamClass localDesc = currentClassDesc.localClassDescriptor();
			if (!invokeObjectReader(currentObject, currentClass)) {
			    defaultReadObject();
			}
		    } else {
			/* No class for this descriptor,
			 * Skip over the data for this class.
			 * like defaultReadObject with a null currentObject.
			 * The native code will read the values but discard them.
			 */
			if (currentClassDesc.getFieldSequence() != null) {
			    boolean prevmode = setBlockData(false);
			    inputClassFields(null, currentClass,
					     currentClassDesc.getFieldSequence());
			    setBlockData(prevmode);
			}
		    }

		    /*
		     * If the source class (stream) had a write object method
		     * it may have written more data and will have written the
		     * TC_ENDBLOCKDATA.  Skip anything up to that and read it.
		     */
		    if (currentClassDesc.hasWriteObject()) {
			SkipToEndOfBlockData();
		    }
		    setBlockData(false);
		}
	    } finally {
		// Make sure we exit at the same stack level as when we started.
		spClass = spBase;
	    }
	}
	return handle;
    }

    /*
     * Skip any unread block data and objects up to the next
     * TC_ENDBLOCKDATA.  Anybody can do this because readObject
     * handles the details of reporting if there is data left.
     * Try reading objects.  If it throws optional data
     * skip over it and try again. 
     */
    private void SkipToEndOfBlockData()
	throws IOException, ClassNotFoundException
    {
	while (peekCode() != TC_ENDBLOCKDATA) {
	    try {
		Object ignore = readObject();
	    } catch (OptionalDataException data) {
		if (data.length > 0)
		    skip(data.length);
	    }
	}
	readCode();			/* Consume TC_ENDBLOCKDATA */
    }
    
    /*
     * Reset the stream to be just like it was after the constructor.
     */
    private void resetStream() throws IOException {
	wireHandle2Object = new Vector(100,100);
	nextWireOffset = 0;
	classes = new Class[20];
	classdesc = new ObjectStreamClass[20];
	spClass = 0;
	setBlockData(true);		// Re-enable buffering
	callbacks = null;		// discard any pending callbacks
    }

    /* Allocate a handle for an object.
     * The Vector is indexed by the wireHandleOffset
     * and contains the object.
     */
    private int assignWireOffset(Object obj)
	throws IOException
    {
	wireHandle2Object.addElement(obj);
	if (++nextWireOffset != wireHandle2Object.size())
	  throw new StreamCorruptedException("Elements not assigned in order");;
	return nextWireOffset-1;
    }

    /*
     * Peek at the next control code in the stream.
     * If the code has not been peeked at yet, read it from the stream.
     */
    private byte peekCode() throws IOException, StreamCorruptedException{
	while (currCode == 0) {

	    int newcode = in.read();	// Read byte from the underlying stream
	    if (newcode < 0) 
		throw new EOFException("Expecting code");
	    
	    currCode = (byte)newcode;
	    if (currCode < TC_BASE || currCode > TC_MAX)
		throw new StreamCorruptedException("Type code out of range, is " + currCode);

	    /* 
	     * Handle reset as a hidden code and reset the stream.
	     */
	    if (currCode == TC_RESET) {
		if (recursionDepth != 0 ||
		    currentObject != null ||
		    currentClassDesc != null)
		    throw new StreamCorruptedException("Illegal stream state for reset");
	    
		/* Reset the stream, and repeat the peek at the next code
		 */
		resetStream();
		currCode = 0;
	    }
	}
	return currCode;
    }
    
    /*
     * Return the next control code in the stream.
     * peekCode gets the next code.  readCode just consumes it.
     */
    private byte readCode()
	throws IOException, StreamCorruptedException
    {
	byte tc = peekCode();
	currCode = 0;
	return tc;
    }
    
    /*
     * Put back the specified code to be peeked at next time.
     */
    private void pushbackCode(byte code) {
	currCode = code;
    }
    /* -----------------------------------------------------*/
    /*
     * Implement the InputStream methods.  The stream has
     * two modes used internally to ObjectInputStream.  When
     * in BlockData mode, all reads are only from datablocks
     * as original written. End of data (-1) is returned
     * if something other than a datablock is next in the stream.
     * When not in BlockData mode (false), reads pass directly
     * through to the underlying stream.
     * The BlockData mode is used to encapsulate data written
     * by class specific writeObject methods that is intended
     * only to be read by corresponding readObject method of the 
     * same class.  The blocking of data allows it to be skipped
     * if necessary.
     *
     * The setBlockData method is used to switch buffering
     * on and off.  When switching between on and off 
     * there must be no data pending to be read. This is 
     * an internal consistency check that will throw an exception. 
     *
     */
    private InputStream in;	/* TBD: make this an java.lang.Input */

    /*
     * Count of bytes available from blockData, if zero, call refill
     * to look for more.  If -1 always return eof (-1)
     */
    private int count;
	    
    private boolean blockDataMode;

    /*
     * Set the blockdata buffering mode.
     * If it is being set to false after being true there must
     * be no pending data. If count > 0 a corrupted exception is thrown.
     */
    private boolean setBlockData(boolean mode) throws IOException {
	if (blockDataMode == mode)
	    return mode;
	if (blockDataMode && count > 0)
	    throw new StreamCorruptedException("Unread data");

	/* Set count to allow reading or not */
	count =  mode ? 0 : -1;

	blockDataMode = mode;
	return !mode;
    }
    
    /**
     * Reads a byte of data. This method will block if no input is 
     * available.
     * @return 	the byte read, or -1 if the end of the
     *		stream is reached.
     * @exception IOException If an I/O error has occurred.
     * @since     JDK1.1
     */
    public int read() throws IOException {
	int data;
	if (blockDataMode) {
	    while (count == 0)
		refill();
	    if (count < 0)
		return -1;			/* EOF */
	    data = in.read();
	    if (data >= 0)
		count--;
	} else {
	    data = in.read();		/* read directly from input stream */
	}
	return data;
    }

    /*
     * Expect the next thing in the stream is a datablock, If its a
     * datablock, extract the count of bytes to allow.  If data is not
     * available set the count to zero.  On error or EOF, set count to -1.
     */
    private void refill() throws IOException {
	count = -1;		/*  No more data to read, EOF */
	byte code;
	try {
	    code = peekCode();
	} catch (EOFException e) {
	    return;
	}
	if (code == TC_BLOCKDATA) {
	    code = readCode();			/* Consume the code */
	    int c = in.read();
	    if (c < 0)
		throw new StreamCorruptedException("EOF expecting count");
	    
	    count = c & 0xff;
	} else if (code == TC_BLOCKDATALONG) {
	    code = readCode();
	    int b3 = in.read();
	    int b2 = in.read();
	    int b1 = in.read();
	    int b0 = in.read();
	    if ((b3 | b2 | b1 | b0) < 0)
		throw new StreamCorruptedException("EOF expecting count");
	    int c = (b3 << 24) | (b2 << 16) | (b1 << 8) | b0;
	    /*
	     * The 32 bit integer size in the long block data format is
	     * signed (unlike like the normal block data format), and
	     * negative values are invalid.
	     */
	    if (c < 0)
		throw new StreamCorruptedException("Negative block data size");

	    count = c;
	}
    }
    
    /**
     * Reads into an array of bytes.  This method will
     * block until some input is available.
     * @param b	the buffer into which the data is read
     * @param off the start offset of the data
     * @param len the maximum number of bytes read
     * @return  the actual number of bytes read, -1 is
     * 		returned when the end of the stream is reached.
     * @exception IOException If an I/O error has occurred.
     * @since     JDK1.1
     */
    public int read(byte[] data, int offset, int length) throws IOException {
	int v;
	int i;

	if (length < 0)
	    throw new IndexOutOfBoundsException();

	if (blockDataMode) {
	    while (count == 0)
		refill();
	    if (count < 0)
		return -1;
	    int l = Math.min(length, count);
	    i = in.read(data, offset, l);
	    if (i > 0)
		count -= i;
	    return i;			/* return number of bytes read */
	} else {
	    /* read directly from input stream */
	    return in.read(data, offset, length);
	}
    }

    /**
     * Returns the number of bytes that can be read without blocking.
     * @return the number of available bytes.
     * @since     JDK1.1
     */
    /*
     * If in blockdataMode returns the number of bytes in the
     * current block. If that is zero, it will try to read
     * another blockdata from the stream if any data is available from the
     * underlying stream..
     * If not in blockdata mode it returns zero.
     */
    public int available() throws IOException {
	if (blockDataMode) {
	    if (count == 0 && in.available() > 0)
		refill();
	    if (count >= 0) {
		return count;
	    } else
		return 0;	/* EOF is no bytes available */
	} else {
	    return 0;		/* Not blockdata, no bytes available */
	}
    }

    /**
     * Closes the input stream. Must be called
     * to release any resources associated with
     * the stream.
     * @exception IOException If an I/O error has occurred.
     * @since     JDK1.1
     */
    public void close() throws IOException {
	in.close();
    }

    /* -----------------------------------------------------*/
    /*
     * Provide the methods to implement DataInput.
     * They delegate to an Instance of DataInputStream that
     * reads its input from the ObjectInputStream.
     * This allows this stream to manage the blocked data the data
     * as necessary.
     */
    private DataInputStream dis;
    
    /**
     * Reads in a boolean.
     * @return the boolean read.
     * @exception EOFException If end of file is reached.
     * @exception IOException If other I/O error has occurred.
     * @since     JDK1.1
     */
    public boolean readBoolean() throws IOException {
	return dis.readBoolean();
    }

    /**
     * Reads an 8 bit byte.
     * @return the 8 bit byte read.
     * @exception EOFException If end of file is reached.
     * @exception IOException If other I/O error has occurred.
     * @since     JDK1.1
     */
    public byte readByte() throws IOException  {
	return dis.readByte();
    }

    /**
     * Reads an unsigned 8 bit byte.
     * @return the 8 bit byte read.
     * @exception EOFException If end of file is reached.
     * @exception IOException If other I/O error has occurred.
     * @since     JDK1.1
     */
    public int readUnsignedByte()  throws IOException {
	return dis.readUnsignedByte();
    }

    /**
     * Reads a 16 bit short.
     * @return the 16 bit short read.
     * @exception EOFException If end of file is reached.
     * @exception IOException If other I/O error has occurred.
     * @since     JDK1.1
     */
    public short readShort()  throws IOException {
	return dis.readShort();
    }

    /**
     * Reads an unsigned 16 bit short.
     * @return the 16 bit short read.
     * @exception EOFException If end of file is reached.
     * @exception IOException If other I/O error has occurred.
     * @since     JDK1.1
     */
    public int readUnsignedShort() throws IOException {
	return dis.readUnsignedShort();
    }

    /**
     * Reads a 16 bit char.
     * @return the 16 bit char read. 
     * @exception EOFException If end of file is reached.
     * @exception IOException If other I/O error has occurred.
     * @since     JDK1.1
     */
    public char readChar()  throws IOException {
	return dis.readChar();
    }

    /**
     * Reads a 32 bit int.
     * @return the 32 bit integer read.
     * @exception EOFException If end of file is reached.
     * @exception IOException If other I/O error has occurred.
     * @since     JDK1.1
     */
    public int readInt()  throws IOException {
	return dis.readInt();
    }

    /**
     * Reads a 64 bit long.
     * @return the read 64 bit long.
     * @exception EOFException If end of file is reached.
     * @exception IOException If other I/O error has occurred.
     * @since     JDK1.1
     */
    public long readLong()  throws IOException {
	return dis.readLong();
    }

    /**
     * Reads a 32 bit float.
     * @return the 32 bit float read.
     * @exception EOFException If end of file is reached.
     * @exception IOException If other I/O error has occurred.
     * @since     JDK1.1
     */
    public float readFloat() throws IOException {
	return dis.readFloat();
    }

    /**
     * Reads a 64 bit double.
     * @return the 64 bit double read.
     * @exception EOFException If end of file is reached.
     * @exception IOException If other I/O error has occurred.
     * @since     JDK1.1
     */
    public double readDouble() throws IOException {
	return dis.readDouble();
    }

    /**
     * Reads bytes, blocking until all bytes are read.
     * @param b	the buffer into which the data is read
     * @exception EOFException If end of file is reached.
     * @exception IOException If other I/O error has occurred.
     * @since     JDK1.1
     */
    public void readFully(byte[] data) throws IOException {
	dis.readFully(data);
    }

    /**
     * Reads bytes, blocking until all bytes are read.
     * @param b	the buffer into which the data is read
     * @param off the start offset of the data
     * @param len the maximum number of bytes to read
     * @exception EOFException If end of file is reached.
     * @exception IOException If other I/O error has occurred.
     * @since     JDK1.1
     */
    public void readFully(byte[] data, int offset, int size) throws IOException {
	if (size < 0)
	    throw new IndexOutOfBoundsException();
	
	dis.readFully(data, offset, size);
    }

    /**
     * Skips bytes, block until all bytes are skipped.
     * @param n the number of bytes to be skipped
     * @return	the actual number of bytes skipped.
     * @exception EOFException If end of file is reached.
     * @exception IOException If other I/O error has occurred.
     * @since     JDK1.1
     */
    public int skipBytes(int len) throws IOException {
	return dis.skipBytes(len);
    }

    /**
     * Reads in a line that has been terminated by a \n, \r, 
     * \r\n or EOF.
     * @return a String copy of the line.
     * @since     JDK1.1
     */
    public String readLine() throws IOException {
	return dis.readLine();
    }

    /**
     * Reads a UTF format String.
     * @return the String.
     * @since     JDK1.1
     */
     public String readUTF() throws IOException {
	return dis.readUTF();
    }
    
    /* Remember the first exception that stopped this stream. */
    private IOException abortIOException = null;
    private ClassNotFoundException abortClassNotFoundException = null;

    /* Read the fields of the specified class.
     * The native implementation sorts the field names to put them
     * in cononical order, ignores transient and static fields
     * and invokes the appropriate write* method on this class.
     */
    private native void inputClassFields(Object o, Class cl, int[] fieldSequence)
	throws InvalidClassException,
	    StreamCorruptedException, ClassNotFoundException, IOException;

    /* Read in the values from the array,
     * It dispatches using the type and read using the read* methods.
     */
    private native void inputArrayValues(Object o, Class currclass)
	throws InvalidClassException,
	    StreamCorruptedException, ClassNotFoundException, IOException;

    /* Allocate a new object for the specified class
     * Native since newInstance may not be able to find a zero arg constructor.
     */
    private static native Object allocateNewObject(Class aclass, Class initclass)
	throws InstantiationException, IllegalAccessException;
    
    /* Allocate a new array for the specified class
     * Native since the type of the array needs to be set to the class
     */
    private static native Object allocateNewArray(Class aclass, int length);
    
    /* Test if readObject/Writer methods are present, if so
     * invoke the reader and return true.
     */
    private native boolean invokeObjectReader(Object o, Class aclass)
	throws InvalidClassException,
	    StreamCorruptedException, ClassNotFoundException, IOException;

    
    /* The object is the current object and class is the the current
     * subclass of the object being read. Nesting information is kept
     * on the stack.
     */
    private Object currentObject;
    private ObjectStreamClass currentClassDesc;
    private Class currentClass;

    /* Arrays used to keep track of classes and ObjectStreamClasses
     * as they are being merged; used in inputObject.
     * spClass is the stack pointer for both.
     */
    ObjectStreamClass[] classdesc;
    Class[] classes;
    int spClass;

    /* During deserialization the objects in the stream are represented by
     * handles (ints), they need to be mapped to the objects.
     * The vector is indexed by the offset between baseWireHandle and the
     * wire handle in the stream.
     */
    private Vector wireHandle2Object;
    private int nextWireOffset;

    /* Vector of validation callback objects
     * The vector is created as needed, and ValidationCallback objects added
     * for each call to registerObject. The vector is maintained in
     * order of highest (first) priority to lowest
     */
    private Vector callbacks;

    /* Recursion level, starts at zero and is incremented for each entry
     * to readObject.  Decremented before exit.
     */ 
    private int recursionDepth;

    /* Last code Peek'ed, if any */
    private byte currCode;

    /* 
     * Flag set to true to allow resolveObject to be called.
     * Set by enableResolveObject.
     */
    private boolean enableResolve;
}

// Internal class to hold the Callback object and priority
class ValidationCallback {
    ValidationCallback(ObjectInputValidation cb, int prio) {
	callback = cb;
	priority = prio;
    }

    int priority;			// priority of this callback
    ObjectInputValidation callback; // object to be called back
}
