/*
 * @(#)ObjectStreamClass_1_3_1.java	1.2 01/12/03
 *
 * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
 * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Licensed Materials - Property of IBM
 * RMI-IIOP v1.0
 * Copyright IBM Corp. 1998 1999  All Rights Reserved
 *
 * US Government Users Restricted Rights - Use, duplication or
 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
 */

package com.sun.corba.se.internal.orbutil;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.DigestOutputStream;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedAction;

import java.lang.reflect.Modifier;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.lang.reflect.Proxy;
import java.lang.reflect.InvocationTargetException;

import java.io.IOException;
import java.io.DataOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.InvalidClassException;
import java.io.Serializable;

import java.util.Arrays;
import java.util.Comparator;
import java.util.Hashtable;

import org.omg.CORBA.ValueMember;

import com.sun.corba.se.internal.io.ValueUtility;
import com.sun.corba.se.internal.io.ObjectStreamClass;

/**
 * This is duplicated here to preserve the JDK 1.3.1FCS behavior
 * of calculating the OMG hash code incorrectly when serialPersistentFields
 * is used, but some of the fields no longer exist in the class itself.
 *
 * We have to duplicate it since we aren't allowed to modify the
 * com.sun.corba.se.internal.io version further, and can't make it
 * public outside of its package for security reasons.
 */
/**
 * A ObjectStreamClass_1_3_1 describes a class that can be serialized to a stream
 * or a class that was serialized to a stream.  It contains the name
 * and the serialVersionUID of the class.
 * <br>
 * The ObjectStreamClass_1_3_1 for a specific class loaded in this Java VM can
 * be found using the lookup method.
 *
 * @author  Roger Riggs
 * @(#)ObjectStreamClass_1_3_1.java	1.17 99/06/07
 * @since   JDK1.1
 */
public class ObjectStreamClass_1_3_1 implements java.io.Serializable {

    public static final long kDefaultUID = -1;

    private static Object noArgsList[] = {};
    private static Class noTypesList[] = {};
	
    private static Hashtable translatedFields;

    static {
	try{
	    Class.forName("com.sun.corba.se.internal.io.LibraryManager").getDeclaredMethod("load",null).invoke(null,null);
	}
	catch(Throwable t){}
    }

    /** Find the descriptor for a class that can be serialized.  Null
     * is returned if the specified class does not implement
     * java.io.Serializable or java.io.Externalizable.
     */
    static final ObjectStreamClass_1_3_1 lookup(Class cl)
    {
	ObjectStreamClass_1_3_1 desc = lookupInternal(cl);
	if (desc.isSerializable() || desc.isExternalizable())
	    return desc;
	return null;
    }

    /*
     * Find the class descriptor for the specified class.
     * Package access only so it can be called from ObjectIn/OutStream.
     */
    static ObjectStreamClass_1_3_1 lookupInternal(Class cl)
    {
	/* Synchronize on the hashtable so no two threads will do
	 * this at the same time.
	 */
	ObjectStreamClass_1_3_1 desc = null;
	synchronized (descriptorFor) {
	    /* Find the matching descriptor if it already known */
	    desc = findDescriptorFor(cl);
	    if (desc != null) {
		return desc;
	    }

                /* Check if it's serializable */
                boolean serializable = classSerializable.isAssignableFrom(cl);
                /* If the class is only Serializable,
                 * lookup the descriptor for the superclass.
                 */
                ObjectStreamClass_1_3_1 superdesc = null;
                if (serializable) {
                    Class superclass = cl.getSuperclass();
                    if (superclass != null)
                        superdesc = lookup(superclass);
                }

                /* Check if its' externalizable.
                 * If it's Externalizable, clear the serializable flag.
                 * Only one or the other may be set in the protocol.
                 */
                boolean externalizable = false;
                if (serializable) {
                    externalizable =
                        ((superdesc != null) && superdesc.isExternalizable()) ||
                        classExternalizable.isAssignableFrom(cl);
                    if (externalizable) {
                        serializable = false;
                    }
                }

	    /* Create a new version descriptor,
	     * it put itself in the known table.
	     */
	    desc = new ObjectStreamClass_1_3_1(cl, superdesc,
					 serializable, externalizable);
	}
	desc.init();
	return desc;
    }

    /**
     * The name of the class described by this descriptor.
     */
    public final String getName() {
	return name;
    }

    /**
     * Return the serialVersionUID for this class.
     * The serialVersionUID defines a set of classes all with the same name
     * that have evolved from a common root class and agree to be serialized
     * and deserialized using a common format.
     */
    public static final long getSerialVersionUID( java.lang.Class clazz) {
	ObjectStreamClass_1_3_1 theosc = ObjectStreamClass_1_3_1.lookup( clazz );
        if( theosc != null )
	{
		return theosc.getSerialVersionUID( );
	}
	return 0;
    }

    /**
     * Return the serialVersionUID for this class.
     * The serialVersionUID defines a set of classes all with the same name
     * that have evolved from a common root class and agree to be serialized
     * and deserialized using a common format.
     */
    public final long getSerialVersionUID() {
	return suid;
    }

    /**
     * Return the serialVersionUID string for this class.
     * The serialVersionUID defines a set of classes all with the same name
     * that have evolved from a common root class and agree to be serialized
     * and deserialized using a common format.
     */
    public final String getSerialVersionUIDStr() {
	if (suidStr == null)
	    suidStr = Long.toHexString(suid).toUpperCase();
	return suidStr;
    }

    /**
     * Return the actual (computed) serialVersionUID for this class.
     */
    public static final long getActualSerialVersionUID( java.lang.Class clazz )
    {
	ObjectStreamClass_1_3_1 theosc = ObjectStreamClass_1_3_1.lookup( clazz );
	if( theosc != null )
	{
		return theosc.getActualSerialVersionUID( );
	}
	return 0;
    }

    /**
     * Return the actual (computed) serialVersionUID for this class.
     */
    public final long getActualSerialVersionUID() {
	return actualSuid;
    }

    /**
     * Return the actual (computed) serialVersionUID for this class.
     */
    public final String getActualSerialVersionUIDStr() {
	if (actualSuidStr == null)
	    actualSuidStr = Long.toHexString(actualSuid).toUpperCase();
	return actualSuidStr;
    }

    /**
     * Return the class in the local VM that this version is mapped to.
     * Null is returned if there is no corresponding local class.
     */
    public final Class forClass() {
	return ofClass;
    }

    /**
     * Return an array of the fields of this serializable class.
     * @return an array containing an element for each persistent
     * field of this class. Returns an array of length zero if
     * there are no fields.
     * @since JDK1.2
     */
    public ObjectStreamField[] getFields() {
    	// Return a copy so the caller can't change the fields.
	if (fields.length > 0) {
	    ObjectStreamField[] dup = new ObjectStreamField[fields.length];
	    System.arraycopy(fields, 0, dup, 0, fields.length);
	    return dup;
	} else {
	    return fields;
	}
    }

    public boolean hasField(ValueMember field){

	for (int i = 0; i < fields.length; i++){
	    try{
		if (fields[i].getName().equals(field.name)) {

		    if (fields[i].getSignature().equals(ValueUtility.getSignature(field)))
			return true;
		}
	    }
	    catch(Throwable t){}
	}
	return false;
    }

    /* Avoid unnecessary allocations. */
    final ObjectStreamField[] getFieldsNoCopy() {
	return fields;
    }

    /**
     * Get the field of this class by name.
     * @return The ObjectStreamField object of the named field or null if there
     * is no such named field.
     */
    public final ObjectStreamField getField(String name) {
    	/* Binary search of fields by name.
	 */
    	for (int i = fields.length-1; i >= 0; i--) {
    	    if (name.equals(fields[i].getName())) {
    	    	return fields[i];
	    }
    	}
	return null;
    }

    public Serializable writeReplace(Serializable value) {
	if (writeReplaceObjectMethod != null) {
	    try {
		return (Serializable) writeReplaceObjectMethod.invoke(value,noArgsList);
	    }
	    catch(Throwable t) {
		throw new RuntimeException(t.getMessage());
	    }
	}
	else return value;
    }

    public Object readResolve(Object value) {
	if (readResolveObjectMethod != null) {
	    try {
		return readResolveObjectMethod.invoke(value,noArgsList);
	    }
	    catch(Throwable t) {
		throw new RuntimeException(t.getMessage());
	    }
	}
	else return value;
    }

    /**
     * Return a string describing this ObjectStreamClass_1_3_1.
     */
    public final String toString() {
	StringBuffer sb = new StringBuffer();

	sb.append(name);
	sb.append(": static final long serialVersionUID = ");
	sb.append(Long.toString(suid));
	sb.append("L;");
	return sb.toString();
    }

    /*
     * Create a new ObjectStreamClass_1_3_1 from a loaded class.
     * Don't call this directly, call lookup instead.
     */
    private ObjectStreamClass_1_3_1(java.lang.Class cl, ObjectStreamClass_1_3_1 superdesc,
			      boolean serial, boolean extern)
    {
	ofClass = cl;		/* created from this class */

        if (Proxy.isProxyClass(cl)) {
            forProxyClass = true;
        }

	name = cl.getName();
	superclass = superdesc;
	serializable = serial;
        if (!forProxyClass) {
            // proxy classes are never externalizable
            externalizable = extern;
        }

	/*
	 * Enter this class in the table of known descriptors.
	 * Otherwise, when the fields are read it may recurse
	 * trying to find the descriptor for itself.
	 */
	insertDescriptorFor(this);

        /*
         * The remainder of initialization occurs in init(), which is called
         * after the lock on the global class descriptor table has been
         * released.
         */
    }

    /*
     * Initialize class descriptor.  This method is only invoked on class
     * descriptors created via calls to lookupInternal().  This method is kept
     * separate from the ObjectStreamClass_1_3_1 constructor so that lookupInternal
     * does not have to hold onto a global class descriptor table lock while the
     * class descriptor is being initialized (see bug 4165204).
     */


    private void init() {
      synchronized (lock) {

	final Class cl = ofClass;
 
	if (fields != null) // already initialized
                return;


	if (!serializable ||
	    externalizable ||
	    forProxyClass ||
	    name.equals("java.lang.String")) {
	    fields = NO_FIELDS;
	} else if (serializable) {

            /* Ask for permission to override field access checks.
             */
            AccessController.doPrivileged(new PrivilegedAction() {
                public Object run() {
		/* Fill in the list of persistent fields.
		 * If it is declared, use the declared serialPersistentFields.
		 * Otherwise, extract the fields from the class itself.
		 */
		try {
                    Field pf = cl.getDeclaredField("serialPersistentFields");
	            // serial bug 7; the serialPersistentFields were not
	            // being read and stored as Accessible bit was not set
		    pf.setAccessible(true);
	            // serial bug 7; need to find if the field is of type
	            // java.io.ObjectStreamField
                    java.io.ObjectStreamField[] f =
                           (java.io.ObjectStreamField[])pf.get(cl);
		    int mods = pf.getModifiers();
		    if ((Modifier.isPrivate(mods)) &&
			(Modifier.isStatic(mods)) &&
			(Modifier.isFinal(mods)))
	            {
			fields = (ObjectStreamField[])translateFields((Object[])pf.get(cl));
		    }
		} catch (NoSuchFieldException e) {
		    fields = null;
		} catch (IllegalAccessException e) {
		    fields = null;
		} catch (IllegalArgumentException e) {
		    fields = null;
                } catch (ClassCastException e) {
                    /* Thrown if a field serialPersistentField exists
                     * but it is not of type ObjectStreamField.
                     */
                    fields = null;
                }


		if (fields == null) {
		    /* Get all of the declared fields for this
		     * Class. setAccessible on all fields so they
		     * can be accessed later.  Create a temporary
		     * ObjectStreamField array to hold each
		     * non-static, non-transient field. Then copy the
		     * temporary array into an array of the correct
		     * size once the number of fields is known.
		     */
                    Field[] actualfields = cl.getDeclaredFields();

		    int numFields = 0;
		    ObjectStreamField[] tempFields =
			new ObjectStreamField[actualfields.length];
		    for (int i = 0; i < actualfields.length; i++) {
			int modifiers = actualfields[i].getModifiers();
			if (!Modifier.isStatic(modifiers) &&
			    !Modifier.isTransient(modifiers)) {
			    tempFields[numFields++] =
				new ObjectStreamField(actualfields[i]);
			}
		    }
		    fields = new ObjectStreamField[numFields];
		    System.arraycopy(tempFields, 0, fields, 0, numFields);

		} else {
		    // For each declared persistent field, look for an actual
		    // reflected Field. If there is one, make sure it's the correct
		    // type and cache it in the ObjectStreamClass_1_3_1 for that field.
		    for (int j = fields.length-1; j >= 0; j--) {
			try {
                            Field reflField = cl.getDeclaredField(fields[j].getName());
			    if (fields[j].getType() == reflField.getType()) {
				// reflField.setAccessible(true);
				fields[j].setField(reflField);
			    }
			} catch (NoSuchFieldException e) {
			    // Nothing to do
			}
		    }
		}
	        return null;
	    }
	    });

	    if (fields.length > 1)
		Arrays.sort(fields);

	    /* Set up field data for use while writing using the API api. */
	    computeFieldInfo();
	}

        /* Get the serialVersionUID from the class.
         * It uses the access override mechanism so make sure
         * the field objects is only used here.
         *
         * NonSerializable classes have a serialVerisonUID of 0L.
         */
         if (isNonSerializable()) {
             suid = 0L;
         } else {
             // Lookup special Serializable members using reflection.
             AccessController.doPrivileged(new PrivilegedAction() {
                public Object run() {
                if (forProxyClass) {
                    // proxy classes always have serialVersionUID of 0L
                    suid = 0L;
                } else {
	            try {
                        final Field f = cl.getDeclaredField("serialVersionUID");
	                int mods = f.getModifiers();
		    // SerialBug 5:  static final SUID should be read
	                if (Modifier.isStatic(mods) && 
                            Modifier.isFinal(mods) ) {
                            f.setAccessible(true);
		            suid = f.getLong(cl);
	                    // get rid of native code
	                    // suid = getSerialVersionUIDField(cl);
	            // SerialBug 2: should be computed after writeObject
	            // actualSuid = computeStructuralUID(cl);
	                } else {
		            suid = ObjectStreamClass.getSerialVersionUID(cl);
	                    // SerialBug 2: should be computed after writeObject
	                    // actualSuid = computeStructuralUID(cl);
	                }
	            } catch (NoSuchFieldException ex) {
	                suid = ObjectStreamClass.getSerialVersionUID(cl);
	                // SerialBug 2: should be computed after writeObject
	                // actualSuid = computeStructuralUID(cl);
                    } catch (IllegalAccessException ex) {
                        suid = ObjectStreamClass.getSerialVersionUID(cl);
                    }
	        }


	        try {
                    writeReplaceObjectMethod = cl.getDeclaredMethod("writeReplace", noTypesList);
	            if (Modifier.isStatic(writeReplaceObjectMethod.getModifiers())) {
	                writeReplaceObjectMethod = null;
                    } else {
                        writeReplaceObjectMethod.setAccessible(true);
                    } 

	        } catch (NoSuchMethodException e2) {

	        }
	
	        try {
                    readResolveObjectMethod = cl.getDeclaredMethod("readResolve", noTypesList);
	            if (Modifier.isStatic(readResolveObjectMethod.getModifiers())) {
	               readResolveObjectMethod = null;
                    } else {
                       readResolveObjectMethod.setAccessible(true);
                    }

	        } catch (NoSuchMethodException e2) {

	        }
	
                /* Cache lookup of writeObject and readObject for
                 * Serializable classes. (Do not lookup for
                 * Externalizable)
                 */

                if (serializable && !forProxyClass) {

	            /* Look for the writeObject method
	             * Set the accessible flag on it here. ObjectOutputStream
	             * will call it as necessary.
	             */
	            try {
	              Class[] args = {java.io.ObjectOutputStream.class};
                      writeObjectMethod = cl.getDeclaredMethod("writeObject", args);
	              hasWriteObjectMethod = true;
	              int mods = writeObjectMethod.getModifiers();

	              // Method must be private and non-static
	              if (!Modifier.isPrivate(mods) ||
		        Modifier.isStatic(mods)) {
		        writeObjectMethod = null;
		        hasWriteObjectMethod = false;
	              }

	            } catch (NoSuchMethodException e) {
	            }

	            /* Look for the readObject method
	             * set the access override and save the reference for
	             * ObjectInputStream so it can all the method directly.
	             */
	            try {
	              Class[] args = {java.io.ObjectInputStream.class};
                      readObjectMethod = cl.getDeclaredMethod("readObject", args);
	              int mods = readObjectMethod.getModifiers();

	              // Method must be private and non-static
	              if (!Modifier.isPrivate(mods) ||
		        Modifier.isStatic(mods)) {
		        readObjectMethod = null;
	              }
	            } catch (NoSuchMethodException e) {
                    }
                    // Compute the structural UID.  This must be done after the
                    // calculation for writeObject.  Fixed 4/20/2000, eea1
	            // SerialBug 2: to have correct value in RepId
	        }
	        return null;
	    }
	  });
	}

        actualSuid = computeStructuralUID(this, cl);
      }

    }

    /*
     * Create an empty ObjectStreamClass_1_3_1 for a class about to be read.
     * This is separate from read so ObjectInputStream can assign the
     * wire handle early, before any nested ObjectStreamClass_1_3_1 might
     * be read.
     */
    ObjectStreamClass_1_3_1(String n, long s) {
	name = n;
	suid = s;
	superclass = null;
    }

    private static Object[] translateFields(Object objs[])
        throws NoSuchFieldException {
        try{
            java.io.ObjectStreamField fields[] = (java.io.ObjectStreamField[])objs;
            Object translation[] = null;
                       
            if (translatedFields == null)
                translatedFields = new Hashtable();
                       
            translation = (Object[])translatedFields.get(fields);
                       
            if (translation != null)
                return translation;
            else {
                Class osfClass = com.sun.corba.se.internal.orbutil.ObjectStreamField.class;

                translation = (Object[])java.lang.reflect.Array.newInstance(osfClass, objs.length);
                Object arg[] = new Object[2];
                Class types[] = {String.class, Class.class};
                Constructor constructor = osfClass.getDeclaredConstructor(types);
                for (int i = fields.length -1; i >= 0; i--){
                    arg[0] = fields[i].getName();
                    arg[1] = fields[i].getType();
                       
                    translation[i] = constructor.newInstance(arg);
                }
                translatedFields.put(fields, translation);
                       
            }

            return (Object[])translation;
        }
        catch(Throwable t){
            throw new NoSuchFieldException();
        }
    }

    /* Compare the base class names of streamName and localName.
     *
     * @return  Return true iff the base class name compare.
     * @parameter streamName	Fully qualified class name.
     * @parameter localName	Fully qualified class name.
     * @parameter pkgSeparator	class names use either '.' or '/'.
     *
     * Only compare base class name to allow package renaming.
     */
    static boolean compareClassNames(String streamName,
				     String localName,
				     char pkgSeparator) {
	/* compare the class names, stripping off package names. */
	int streamNameIndex = streamName.lastIndexOf(pkgSeparator);
	if (streamNameIndex < 0)
	    streamNameIndex = 0;

	int localNameIndex = localName.lastIndexOf(pkgSeparator);
	if (localNameIndex < 0)
	    localNameIndex = 0;

	return streamName.regionMatches(false, streamNameIndex,
					localName, localNameIndex,
					streamName.length() - streamNameIndex);
    }

    /*
     * Compare the types of two class descriptors.
     * They match if they have the same class name and suid
     */
    final boolean typeEquals(ObjectStreamClass_1_3_1 other) {
	return (suid == other.suid) &&
	    compareClassNames(name, other.name, '.');
    }

    /*
     * Return the superclass descriptor of this descriptor.
     */
    final void setSuperclass(ObjectStreamClass_1_3_1 s) {
	superclass = s;
    }

    /*
     * Return the superclass descriptor of this descriptor.
     */
    final ObjectStreamClass_1_3_1 getSuperclass() {
	return superclass;
    }

    /*
     * Return whether the class has a writeObject method
     */
    final boolean hasWriteObject() {
	return hasWriteObjectMethod;
    }

    final boolean isCustomMarshaled() {
	return (hasWriteObject() || isExternalizable());
    }

    /*
     * Return true if all instances of 'this' Externalizable class
     * are written in block-data mode from the stream that 'this' was read
     * from. <p>
     *
     * In JDK 1.1, all Externalizable instances are not written
     * in block-data mode.
     * In JDK 1.2, all Externalizable instances, by default, are written
     * in block-data mode and the Externalizable instance is terminated with
     * tag TC_ENDBLOCKDATA. Change enabled the ability to skip Externalizable
     * instances.
     *
     * IMPLEMENTATION NOTE:
     *   This should have been a mode maintained per stream; however,
     *   for compatibility reasons, it was only possible to record
     *   this change per class. All Externalizable classes within
     *   a given stream should either have this mode enabled or
     *   disabled. This is enforced by not allowing the PROTOCOL_VERSION
     *   of a stream to he changed after any objects have been written.
     *
     * @see ObjectOuputStream#useProtocolVersion
     * @see ObjectStreamConstants#PROTOCOL_VERSION_1
     * @see ObjectStreamConstants#PROTOCOL_VERSION_2
     *
     * @since JDK 1.2
     */
    boolean hasExternalizableBlockDataMode() {
	return hasExternalizableBlockData;
    }

    /*
     * Return the ObjectStreamClass_1_3_1 of the local class this one is based on.
     */
    final ObjectStreamClass_1_3_1 localClassDescriptor() {
	return localClassDesc;
    }

    /*
     * Get the Serializability of the class.
     */
    boolean isSerializable() {
	return serializable;
    }

    /*
     * Get the externalizability of the class.
     */
    boolean isExternalizable() {
	return externalizable;
    }

    boolean isNonSerializable() {
        return ! (externalizable || serializable);
    }

    /*
     * Calculate the size of the array needed to store primitive data and the
     * number of object references to read when reading from the input
     * stream.
     */
    private void computeFieldInfo() {
	primBytes = 0;
	objFields = 0;

	for (int i = 0; i < fields.length; i++ ) {
	    switch (fields[i].getTypeCode()) {
	    case 'B':
	    case 'Z':
	    	primBytes += 1;
	    	break;
	    case 'C':
	    case 'S':
	    	primBytes += 2;
	    	break;

	    case 'I':
	    case 'F':
	    	primBytes += 4;
	    	break;
	    case 'J':
	    case 'D' :
	    	primBytes += 8;
	    	break;

	    case 'L':
	    case '[':
	    	objFields += 1;
	    	break;
	    }
	}
    }

    private static long computeStructuralUID(ObjectStreamClass_1_3_1 osc, Class cl) {
	ByteArrayOutputStream devnull = new ByteArrayOutputStream(512);
		
	long h = 0;
	try {

	    if ((!java.io.Serializable.class.isAssignableFrom(cl)) ||
		(cl.isInterface())){
		return 0;
	    }
			
	    if (java.io.Externalizable.class.isAssignableFrom(cl)) {
		return 1;
	    }

	    MessageDigest md = MessageDigest.getInstance("SHA");
	    DigestOutputStream mdo = new DigestOutputStream(devnull, md);
	    DataOutputStream data = new DataOutputStream(mdo);

	    // Get SUID of parent
	    Class parent = cl.getSuperclass();
	    if ((parent != null))  
	    // SerialBug 1; acc. to spec the one for 
	    // java.lang.object
	    // should be computed and put
	    //     && (parent != java.lang.Object.class)) 
	    {
				//data.writeLong(computeSerialVersionUID(null,parent));
		data.writeLong(computeStructuralUID(lookup(parent), parent));
	    }

	    if (osc.hasWriteObject())
		data.writeInt(2);
	    else
		data.writeInt(1);

	    /* Sort the field names to get a deterministic order */
            // Field[] field = ObjectStreamClass_1_3_1.getDeclaredFields(cl);

            ObjectStreamField[] fields = osc.getFields();

            // Must make sure that the Field array we allocate
            // below is exactly the right size.  Bug fix for
            // 4397133.
            int numNonNullFields = 0;
            for (int i = 0; i < fields.length; i++)
                if (fields[i].getField() != null)
                    numNonNullFields++;

            Field [] field = new java.lang.reflect.Field[numNonNullFields];
            for (int i = 0, fieldNum = 0; i < fields.length; i++) {
	        if (fields[i].getField() != null) {
                    field[fieldNum++] = fields[i].getField();
	        }
            }

	    if (field.length > 1)
                Arrays.sort(field, compareMemberByName);

	    for (int i = 0; i < field.length; i++) {
		Field f = field[i];
				
				/* Include in the hash all fields except those that are
				 * transient 
				 */
		int m = f.getModifiers();
	        //Serial 6
		//if (Modifier.isTransient(m) || Modifier.isStatic(m))
	        // spec reference 00-01-06.pdf, 1.3.5.6, states non-static
	        // non-transient, public fields are mapped to Java IDL.
	        //
                // Here's the quote from the first paragraph:
	        // Java non-static non-transient public fields are mapped to 
	        // OMG IDL public data members, and other Java fields are 
	        // not mapped.

		// if (Modifier.isTransient(m) || Modifier.isStatic(m))
		//     continue;
				
		data.writeUTF(f.getName());
		data.writeUTF(getSignature(f.getType()));
	    }
			
	    /* Compute the hash value for this class.
	     * Use only the first 64 bits of the hash.
	     */
	    data.flush();
	    byte hasharray[] = md.digest();
	    // int minimum = Math.min(8, hasharray.length);
	    // SerialBug 3: SHA computation is wrong; for loop reversed
	    //for (int i = minimum; i > 0; i--) 
	    for (int i = 0; i < Math.min(8, hasharray.length); i++) {
		h += (long)(hasharray[i] & 255) << (i * 8);
	    }
	} catch (IOException ignore) {
	    /* can't happen, but be deterministic anyway. */
	    h = -1;
	} catch (NoSuchAlgorithmException complain) {
	    throw new SecurityException(complain.getMessage());
	}
	return h;
    }

    /**
     * Compute the JVM signature for the class.
     */
    static String getSignature(Class clazz) {
	String type = null;
	if (clazz.isArray()) {
	    Class cl = clazz;
	    int dimensions = 0;
	    while (cl.isArray()) {
		dimensions++;
		cl = cl.getComponentType();
	    }
	    StringBuffer sb = new StringBuffer();
	    for (int i = 0; i < dimensions; i++) {
		sb.append("[");
	    }
	    sb.append(getSignature(cl));
	    type = sb.toString();
	} else if (clazz.isPrimitive()) {
	    if (clazz == Integer.TYPE) {
		type = "I";
	    } else if (clazz == Byte.TYPE) {
		type = "B";
	    } else if (clazz == Long.TYPE) {
		type = "J";
	    } else if (clazz == Float.TYPE) {
		type = "F";
	    } else if (clazz == Double.TYPE) {
		type = "D";
	    } else if (clazz == Short.TYPE) {
		type = "S";
	    } else if (clazz == Character.TYPE) {
		type = "C";
	    } else if (clazz == Boolean.TYPE) {
		type = "Z";
	    } else if (clazz == Void.TYPE) {
		type = "V";
	    }
	} else {
	    type = "L" + clazz.getName().replace('.', '/') + ";";
	}
	return type;
    }

    /*
     * Compute the JVM method descriptor for the method.
     */
    static String getSignature(Method meth) {
	StringBuffer sb = new StringBuffer();

	sb.append("(");

	Class[] params = meth.getParameterTypes(); // avoid clone
	for (int j = 0; j < params.length; j++) {
	    sb.append(getSignature(params[j]));
	}
	sb.append(")");
	sb.append(getSignature(meth.getReturnType()));
	return sb.toString();
    }

    /*
     * Compute the JVM constructor descriptor for the constructor.
     */
    static String getSignature(Constructor cons) {
	StringBuffer sb = new StringBuffer();

	sb.append("(");

	Class[] params = cons.getParameterTypes(); // avoid clone
	for (int j = 0; j < params.length; j++) {
	    sb.append(getSignature(params[j]));
	}
	sb.append(")V");
	return sb.toString();
    }

    /*
     * Cache of Class -> ClassDescriptor Mappings.
     */
    static private ObjectStreamClassEntry[] descriptorFor = new ObjectStreamClassEntry[61];

    /*
     * findDescriptorFor a Class.  This looks in the cache for a
     * mapping from Class -> ObjectStreamClass mappings.  The hashCode
     * of the Class is used for the lookup since the Class is the key.
     * The entries are extended from java.lang.ref.SoftReference so the
     * gc will be able to free them if needed.
     */
    private static ObjectStreamClass_1_3_1 findDescriptorFor(Class cl) {

	int hash = cl.hashCode();
	int index = (hash & 0x7FFFFFFF) % descriptorFor.length;
	ObjectStreamClassEntry e;
	ObjectStreamClassEntry prev;

	/* Free any initial entries whose refs have been cleared */
	while ((e = descriptorFor[index]) != null && e.get() == null) {
	    descriptorFor[index] = e.next;
	}

	/* Traverse the chain looking for a descriptor with ofClass == cl.
	 * unlink entries that are unresolved.
	 */
	prev = e;
	while (e != null ) {
	    ObjectStreamClass_1_3_1 desc = (ObjectStreamClass_1_3_1)(e.get());
	    if (desc == null) {
		// This entry has been cleared,  unlink it
		prev.next = e.next;
	    } else {
		if (desc.ofClass == cl)
		    return desc;
		prev = e;
	    }
	    e = e.next;
	}
	return null;
    }

    /*
     * insertDescriptorFor a Class -> ObjectStreamClass_1_3_1 mapping.
     */
    private static void insertDescriptorFor(ObjectStreamClass_1_3_1 desc) {
	// Make sure not already present
	if (findDescriptorFor(desc.ofClass) != null) {
	    return;
	}

	int hash = desc.ofClass.hashCode();
	int index = (hash & 0x7FFFFFFF) % descriptorFor.length;
	ObjectStreamClassEntry e = new ObjectStreamClassEntry(desc);
	e.next = descriptorFor[index];
       	descriptorFor[index] = e;
    }

    private static Field[] getDeclaredFields(final Class clz) {
        return (Field[]) AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {
                return clz.getDeclaredFields();
            }
        });
    }


    /*
     * The name of this descriptor
     */
    private String name;

    /*
     * The descriptor of the supertype.
     */
    private ObjectStreamClass_1_3_1 superclass;

    /*
     * Flags for Serializable and Externalizable.
     */
    private boolean serializable;
    private boolean externalizable;

    /*
     * Array of persistent fields of this class, sorted by
     * type and name.
     */
    private ObjectStreamField[] fields;

    /*
     * Class that is a descriptor for in this virtual machine.
     */
    private Class ofClass;

    /*
     * True if descriptor for a proxy class.
     */
    boolean forProxyClass;


    /*
     * SerialVersionUID for this class.
     */
    private long suid = kDefaultUID;
    private String suidStr = null;

    /*
     * Actual (computed) SerialVersionUID for this class.
     */
    private long actualSuid = kDefaultUID;
    private String actualSuidStr = null;

    /*
     * The total number of bytes of primitive fields.
     * The total number of object fields.
     */
    int primBytes;
    int objFields;

    /* Internal lock object. */
    private Object lock = new Object();

    /* True if this class has/had a writeObject method */
    private boolean hasWriteObjectMethod;

    /* In JDK 1.1, external data was not written in block mode.
     * As of JDK 1.2, external data is written in block data mode. This
     * flag enables JDK 1.2 to be able to read JDK 1.1 written external data.
     *
     * @since JDK 1.2
     */
    private boolean hasExternalizableBlockData;
    Method writeObjectMethod;
    Method readObjectMethod;
    private Method writeReplaceObjectMethod;
    private Method readResolveObjectMethod;

    /*
     * ObjectStreamClass_1_3_1 that this one was built from.
     */
    private ObjectStreamClass_1_3_1 localClassDesc;

    /* Get the private static final field for serial version UID */
    // private static native long getSerialVersionUIDField(Class cl);

    /* The Class Object for java.io.Serializable */
    private static Class classSerializable = null;
    private static Class classExternalizable = null;

    /*
     * Resolve java.io.Serializable at load time.
     */
    static {
	try {
	    classSerializable = Class.forName("java.io.Serializable");
	    classExternalizable = Class.forName("java.io.Externalizable");
	} catch (Throwable e) {
	    System.err.println("Could not load java.io.Serializable or java.io.Externalizable.");
	}
    }

    /** use serialVersionUID from JDK 1.1. for interoperability */
    private static final long serialVersionUID = -6120832682080437368L;

    /**
     * Set serialPersistentFields of a Serializable class to this value to
     * denote that the class has no Serializable fields.
     */
    public static final ObjectStreamField[] NO_FIELDS =
        new ObjectStreamField[0];

    /*
     * Entries held in the Cache of known ObjectStreamClass_1_3_1 objects.
     * Entries are chained together with the same hash value (modulo array size).
     */
    private static class ObjectStreamClassEntry // extends java.lang.ref.SoftReference
    {
	ObjectStreamClassEntry(ObjectStreamClass_1_3_1 c) {
	    //super(c);
	    this.c = c;
	}
	ObjectStreamClassEntry next;

	public Object get()
	{
	    return c;
	}
	private ObjectStreamClass_1_3_1 c;
    }

    /*
     * Comparator object for Classes and Interfaces
     */
    private static Comparator compareClassByName =
    	new CompareClassByName();

    private static class CompareClassByName implements Comparator {
	public int compare(Object o1, Object o2) {
	    Class c1 = (Class)o1;
	    Class c2 = (Class)o2;
	    return (c1.getName()).compareTo(c2.getName());
	}
    }

    /*
     * Comparator object for Members, Fields, and Methods
     */
    private static Comparator compareMemberByName =
    	new CompareMemberByName();

    private static class CompareMemberByName implements Comparator {
	public int compare(Object o1, Object o2) {
	    String s1 = ((Member)o1).getName();
	    String s2 = ((Member)o2).getName();

	    if (o1 instanceof Method) {
		s1 += getSignature((Method)o1);
		s2 += getSignature((Method)o2);
	    } else if (o1 instanceof Constructor) {
		s1 += getSignature((Constructor)o1);
		s2 += getSignature((Constructor)o2);
	    }
	    return s1.compareTo(s2);
	}
    }

    /* It is expensive to recompute a method or constructor signature
       many times, so compute it only once using this data structure. */
    private static class MethodSignature implements Comparator {
	Member member;
	String signature;      // cached parameter signature

	/* Given an array of Method or Constructor members,
	   return a sorted array of the non-private members.*/
	/* A better implementation would be to implement the returned data
	   structure as an insertion sorted link list.*/
	static MethodSignature[] removePrivateAndSort(Member[] m) {
	    int numNonPrivate = 0;
	    for (int i = 0; i < m.length; i++) {
		if (! Modifier.isPrivate(m[i].getModifiers())) {
		    numNonPrivate++;
		}
	    }
	    MethodSignature[] cm = new MethodSignature[numNonPrivate];
	    int cmi = 0;
	    for (int i = 0; i < m.length; i++) {
		if (! Modifier.isPrivate(m[i].getModifiers())) {
		    cm[cmi] = new MethodSignature(m[i]);
		    cmi++;
		}
	    }
	    if (cmi > 0)
		Arrays.sort(cm, cm[0]);
	    return cm;
	}

	/* Assumes that o1 and o2 are either both methods
	   or both constructors.*/
	public int compare(Object o1, Object o2) {
	    /* Arrays.sort calls compare when o1 and o2 are equal.*/
	    if (o1 == o2)
		return 0;

	    MethodSignature c1 = (MethodSignature)o1;
	    MethodSignature c2 = (MethodSignature)o2;

	    int result;
	    if (isConstructor()) {
		result = c1.signature.compareTo(c2.signature);
	    } else { // is a Method.
		result = c1.member.getName().compareTo(c2.member.getName());
		if (result == 0)
		    result = c1.signature.compareTo(c2.signature);
	    }
	    return result;
	}

	final private boolean isConstructor() {
	    return member instanceof Constructor;
	}
	private MethodSignature(Member m) {
	    member = m;
	    if (isConstructor()) {
		signature = ObjectStreamClass_1_3_1.getSignature((Constructor)m);
	    } else {
		signature = ObjectStreamClass_1_3_1.getSignature((Method)m);
	    }
	}
    }
}
