home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Java 1.2 How-To
/
JavaHowTo.iso
/
3rdParty
/
jbuilder
/
unsupported
/
JDK1.2beta3
/
SOURCE
/
SRC.ZIP
/
java
/
io
/
ObjectStreamClass.java
< prev
next >
Encoding:
Amiga
Atari
Commodore
DOS
FM Towns/JPY
Macintosh
Macintosh JP
NeXTSTEP
RISC OS/Acorn
UTF-8
Wrap
Java Source
|
1998-03-20
|
34.1 KB
|
1,173 lines
/*
* @(#)ObjectStreamClass.java 1.65 98/03/18
*
* Copyright 1996-1998 by Sun Microsystems, Inc.,
* 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
* 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.
*/
package java.io;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.DigestOutputStream;
import java.security.AccessController;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Modifier;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
/**
* A ObjectStreamClass 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 for a specific class loaded in this Java VM can
* be found using the lookup method.
*
* @author Roger Riggs
* @version @(#)ObjectStreamClass.java 1.45 97/08/03
* @since JDK1.1
*/
public class ObjectStreamClass implements java.io.Serializable {
/** 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.
*/
public static ObjectStreamClass lookup(Class cl)
{
ObjectStreamClass 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 lookupInternal(Class cl)
{
/* Synchronize on the hashtable so no two threads will do
* this at the same time.
*/
ObjectStreamClass 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 or externalizable.
* Test if it's Externalizable, clear the serializable flag.
* Only one or the other may be set in the protocol.
*/
boolean serializable = classSerializable.isAssignableFrom(cl);
boolean externalizable = classExternalizable.isAssignableFrom(cl);
if (externalizable)
serializable = false;
/* If the class is only Serializable,
* lookup the descriptor for the superclass.
*/
ObjectStreamClass superdesc = null;
if (serializable) {
Class superclass = cl.getSuperclass();
if (superclass != null)
superdesc = lookup(superclass);
}
/* Create a new version descriptor,
* it put itself in the known table.
*/
desc = new ObjectStreamClass(cl, superdesc,
serializable, externalizable);
}
return desc;
}
/**
* The name of the class described by this descriptor.
*/
public 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 long getSerialVersionUID() {
return suid;
}
/**
* Return the class in the local VM that this version is mapped to.
* Null is returned if there is no corresponding local class.
*/
public 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;
}
}
/* 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 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;
}
/**
* Return a string describing this ObjectStreamClass.
*/
public 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 from a loaded class.
* Don't call this directly, call lookup instead.
*/
private ObjectStreamClass(java.lang.Class cl, ObjectStreamClass superdesc,
boolean serial, boolean extern)
{
ofClass = cl; /* created from this class */
name = cl.getName();
superclass = superdesc;
serializable = serial;
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);
if (!serializable ||
externalizable ||
name.equals("java.lang.String")) {
fields = new ObjectStreamField[0];
} else if (serializable) {
try {
/* Ask for permission to override field access checks.
*/
AccessController.beginPrivileged();
/* 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");
pf.setAccessible(true);
ObjectStreamField[] f = (ObjectStreamField[])pf.get(cl);
fields = f;
} catch (NoSuchFieldException e) {
fields = null;
} catch (IllegalAccessException e) {
fields = null;
} catch (IllegalArgumentException e) {
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();
AccessibleObject.setAccessible(actualfields, true);
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 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);
} else {
// TBD: Should this be flagged as an error?
}
} catch (NoSuchFieldException e) {
// Nothing to do
}
}
}
} finally {
AccessController.endPrivileged();
}
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.
*/
try {
AccessController.beginPrivileged();
Field f = cl.getDeclaredField("serialVersionUID");
int mods = f.getModifiers();
if (Modifier.isStatic(mods) && Modifier.isFinal(mods)) {
f.setAccessible(true);
suid = f.getLong(cl);
} else {
suid = computeSerialVersionUID(cl);
}
} catch (NoSuchFieldException ex) {
suid = computeSerialVersionUID(cl);
} catch (IllegalAccessException ex) {
suid = computeSerialVersionUID(cl);
} finally {
AccessController.endPrivileged();
}
/* Look for the writeObject method
* Set the accessible flag on it here. ObjectOutputStream
* will call it as necessary.
*/
try {
Class[] args = {ObjectOutputStream.class};
AccessController.beginPrivileged();
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;
} else {
writeObjectMethod.setAccessible(true);
}
} catch (NoSuchMethodException e) {
} finally {
AccessController.endPrivileged();
}
}
/*
* Create an empty ObjectStreamClass for a class about to be read.
* This is separate from read so ObjectInputStream can assign the
* wire handle early, before any nested ObjectStreamClass might
* be read.
*/
ObjectStreamClass(String n, long s) {
name = n;
suid = s;
superclass = null;
}
/*
* Set the class this version descriptor matches.
* The base class name and serializable hash must match.
* Fill in the reflected Fields that will be used
* for reading.
*/
void setClass(Class cl) throws InvalidClassException {
if (cl == null) {
localClassDesc = null;
ofClass = null;
computeFieldInfo();
return;
}
localClassDesc = lookupInternal(cl);
if (localClassDesc == null)
throw new InvalidClassException(cl.getName(),
"Local class not compatible");
if (suid != localClassDesc.suid) {
/* Disregard the serialVersionUID of an array
* when name and cl.Name differ. If resolveClass() returns
* an array with a different package name,
* the serialVersionUIDs will not match since the fully
* qualified array class is used in the
* computation of the array's serialVersionUID. There is
* no way to set a permanent serialVersionUID for an array type.
*/
if (! (cl.isArray() && ! cl.getName().equals(name)))
throw new InvalidClassException(cl.getName(),
"Local class not compatible:" +
" stream classdesc serialVersionUID=" + suid +
" local class serialVersionUID=" + localClassDesc.suid);
}
/* compare the class names, stripping off package names. */
if (! compareClassNames(name, cl.getName(), '.'))
throw new InvalidClassException(name,
"Incompatible local class name: " +
cl.getName());
/*
* Test that both implement either serializable or externalizable.
*/
if (serializable != localClassDesc.serializable ||
externalizable != localClassDesc.externalizable)
throw new InvalidClassException(cl.getName(),
"Serialization incompatible with Externalization");
/* Set up the reflected Fields in the class where the value of each
* field in this descriptor should be stored.
* Each field in this ObjectStreamClass (the source) is located (by
* name) in the ObjectStreamClass of the class(the destination).
* In the usual (non-versioned case) the field is in both
* descriptors and the types match, so the reflected Field is copied.
* If the type does not match, a InvalidClass exception is thrown.
* If the field is not present in the class, the reflected Field
* remains null so the field will be read but discarded.
* If extra fields are present in the class they are ignored. Their
* values will be set to the default value by the object allocator.
* Both the src and dest field list are sorted by type and name.
*/
ObjectStreamField[] destfield =
(ObjectStreamField[])localClassDesc.fields;
ObjectStreamField[] srcfield =
(ObjectStreamField[])fields;
int j = 0;
nextsrc:
for (int i = 0; i < srcfield.length; i++ ) {
/* Find this field in the dest*/
for (int k = j; k < destfield.length; k++) {
if (srcfield[i].getName().equals(destfield[k].getName())) {
/* found match */
if (srcfield[i].isPrimitive() &&
!srcfield[i].typeEquals(destfield[k])) {
throw new InvalidClassException(cl.getName(),
"The type of field " +
srcfield[i].getName() +
" of class " + name +
" is incompatible.");
}
/* Skip over any fields in the dest that are not in the src */
j = k;
srcfield[i].setField(destfield[j].getField());
// go on to the next source field
continue nextsrc;
}
}
}
/* Set up field data for use while reading from the input stream. */
computeFieldInfo();
/* Remember the class this represents */
ofClass = cl;
/* 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};
AccessController.beginPrivileged();
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;
} else {
readObjectMethod.setAccessible(true);
}
} catch (NoSuchMethodException e) {
} finally {
AccessController.endPrivileged();
}
}
/* 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
*/
boolean typeEquals(ObjectStreamClass other) {
return (suid == other.suid) &&
compareClassNames(name, other.name, '.');
}
/*
* Return the superclass descriptor of this descriptor.
*/
void setSuperclass(ObjectStreamClass s) {
superclass = s;
}
/*
* Return the superclass descriptor of this descriptor.
*/
ObjectStreamClass getSuperclass() {
return superclass;
}
/*
* Return whether the class has a writeObject method
*/
boolean hasWriteObject() {
return hasWriteObjectMethod;
}
/*
* 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 of the local class this one is based on.
*/
ObjectStreamClass localClassDescriptor() {
return localClassDesc;
}
/*
* Get the Serializability of the class.
*/
boolean isSerializable() {
return serializable;
}
/*
* Get the externalizability of the class.
*/
boolean isExternalizable() {
return externalizable;
}
/*
* 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':
fields[i].setOffset(primBytes);
primBytes += 1;
break;
case 'C':
case 'S':
fields[i].setOffset(primBytes);
primBytes += 2;
break;
case 'I':
case 'F':
fields[i].setOffset(primBytes);
primBytes += 4;
break;
case 'J':
case 'D' :
fields[i].setOffset(primBytes);
primBytes += 8;
break;
case 'L':
case '[':
fields[i].setOffset(objFields);
objFields += 1;
break;
}
}
}
/*
* Compute a hash for the specified class. Incrementally add
* items to the hash accumulating in the digest stream.
* Fold the hash into a long. Use the SHA secure hash function.
*/
private static long computeSerialVersionUID(Class cl) {
ByteArrayOutputStream devnull = new ByteArrayOutputStream(512);
long h = 0;
try {
MessageDigest md = MessageDigest.getInstance("SHA");
DigestOutputStream mdo = new DigestOutputStream(devnull, md);
DataOutputStream data = new DataOutputStream(mdo);
data.writeUTF(cl.getName());
int classaccess = cl.getModifiers();
classaccess &= (Modifier.PUBLIC | Modifier.FINAL |
Modifier.INTERFACE | Modifier.ABSTRACT);
/* Workaround for javac bug that only set ABSTRACT for
* interfaces if the interface had some methods.
* The ABSTRACT bit reflects that the number of methods > 0.
* This is required so correct hashes can be computed
* for existing class files.
* Previously this hack was previously present in the VM.
*/
Method[] method = cl.getDeclaredMethods();
if ((classaccess & Modifier.INTERFACE) != 0) {
classaccess &= (~Modifier.ABSTRACT);
if (method.length > 0) {
classaccess |= Modifier.ABSTRACT;
}
}
data.writeInt(classaccess);
/*
* Get the list of interfaces supported,
* Accumulate their names their names in Lexical order
* and add them to the hash
*/
Class interfaces[] = cl.getInterfaces();
Arrays.sort(interfaces, compareClassByName);
for (int i = 0; i < interfaces.length; i++) {
data.writeUTF(interfaces[i].getName());
}
/* Sort the field names to get a deterministic order */
Field[] field = cl.getDeclaredFields();
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
* private transient and private static.
*/
int m = f.getModifiers();
if (Modifier.isPrivate(m) &&
(Modifier.isTransient(m) || Modifier.isStatic(m)))
continue;
data.writeUTF(f.getName());
data.writeInt(m);
data.writeUTF(getSignature(f.getType()));
}
if (hasStaticInitializer(cl)) {
data.writeUTF("<clinit>");
data.writeInt(Modifier.STATIC); // TBD: what modifiers does it have
data.writeUTF("()V");
}
/*
* Get the list of constructors including name and signature
* Sort lexically, add all except the private constructors
* to the hash with their access flags
*/
MethodSignature[] constructors =
MethodSignature.removePrivateAndSort(cl.getDeclaredConstructors());
for (int i = 0; i < constructors.length; i++) {
MethodSignature c = constructors[i];
String mname = "<init>";
String desc = c.signature;
desc = desc.replace('/', '.');
data.writeUTF(mname);
data.writeInt(c.member.getModifiers());
data.writeUTF(desc);
}
/* Include in the hash all methods except those that are
* private transient and private static.
*/
MethodSignature[] methods =
MethodSignature.removePrivateAndSort(method);
for (int i = 0; i < methods.length; i++ ) {
MethodSignature m = methods[i];
String desc = m.signature;
desc = desc.replace('/', '.');
data.writeUTF(m.member.getName());
data.writeInt(m.member.getModifiers());
data.writeUTF(desc);
}
/* Compute the hash value for this class.
* Use only the first 64 bits of the hash.
*/
data.flush();
byte hasharray[] = md.digest();
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();
}
/*
* locate the ObjectStreamClass for this class and write it to the stream.
*/
void write(ObjectOutputStream s) throws IOException {
/* write the flag indicating that this class has write/read object methods */
int flags = 0;
if (hasWriteObjectMethod)
flags |= ObjectStreamConstants.SC_WRITE_METHOD;
if (serializable)
flags |= ObjectStreamConstants.SC_SERIALIZABLE;
if (externalizable) {
flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
/* Enabling the SC_BLOCK_DATA flag indicates PROTCOL_VERSION_2.*/
if (! s.useDeprecatedExternalizableFormat)
flags |= ObjectStreamConstants.SC_BLOCK_DATA;
}
s.writeByte(flags);
// If there are no fields, write a null and return
if (fields == null) {
s.writeShort(0);
return;
}
/* write the total number of fields */
s.writeShort(fields.length);
/* Write out the descriptors of the primitive fields Each
* descriptor consists of the UTF fieldname, a short for the
* access modes, and the first byte of the signature byte.
* For the object types, ('[' and 'L'), a reference to the
* type of the field follows.
*/
/* disable replacement of String objects written
* by ObjectStreamClass. */
boolean prevEnableReplace = s.enableReplace;
s.enableReplace = false;
try {
for (int i = 0; i < fields.length; i++ ) {
ObjectStreamField f = fields[i];
s.writeByte(f.getTypeCode());
s.writeUTF(f.getName());
if (!f.isPrimitive()) {
s.writeObject(f.getTypeString());
}
}
} finally {
s.enableReplace = prevEnableReplace;
}
}
/*
* Read the version descriptor from the stream.
* Write the count of field descriptors
* for each descriptor write the first character of its type,
* the name of the field.
* If the type is for an object either array or object, write
* the type typedescriptor for the type
*/
void read(ObjectInputStream s) throws IOException, ClassNotFoundException {
/* read flags and determine whether the source class had
* write/read methods.
*/
byte flags = s.readByte();
serializable = (flags & ObjectStreamConstants.SC_SERIALIZABLE) != 0;
externalizable = (flags & ObjectStreamConstants.SC_EXTERNALIZABLE) != 0;
hasWriteObjectMethod = serializable ?
(flags & ObjectStreamConstants.SC_WRITE_METHOD) != 0 :
false;
hasExternalizableBlockData = externalizable ?
(flags & ObjectStreamConstants.SC_BLOCK_DATA) != 0 :
false;
/* Read the number of fields described.
* For each field read the type byte, the name.
*/
int count = s.readShort();
fields = new ObjectStreamField[count];
/* disable replacement of String objects read
* by ObjectStreamClass. */
boolean prevEnableResolve = s.enableResolve;
s.enableResolve = false;
try {
for (int i = 0; i < count; i++ ) {
char type = (char)s.readByte();
String name = s.readUTF();
String ftype = null;
if (type == '[' || type == 'L') {
ftype = (String)s.readObject();
}
fields[i] =
new ObjectStreamField(name, type, null, ftype);
}
} finally {
s.enableResolve = prevEnableResolve;
}
}
/*
* 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 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 desc = (ObjectStreamClass)(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 mapping.
*/
private static void insertDescriptorFor(ObjectStreamClass 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;
}
/*
* The name of this descriptor
*/
private String name;
/*
* The descriptor of the supertype.
*/
private ObjectStreamClass 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;
/*
* SerialVersionUID for this class.
*/
private long suid;
/*
* The total number of bytes of primitive fields.
* The total number of object fields.
*/
int primBytes;
int objFields;
/* 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;
/*
* ObjectStreamClass that this one was built from.
*/
private ObjectStreamClass localClassDesc;
/* Find out if the class has a static class initializer <clinit> */
private static native boolean hasStaticInitializer(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;
/*
* Entries held in the Cache of known ObjectStreamClass objects.
* Entries are chained together with the same hash value (modulo array size).
*/
private static class ObjectStreamClassEntry extends java.lang.ref.SoftReference
{
ObjectStreamClassEntry(ObjectStreamClass c) {
super(c);
}
ObjectStreamClassEntry next;
}
/*
* 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;
}
private boolean isConstructor() {
return member instanceof Constructor;
}
private MethodSignature(Member m) {
member = m;
if (isConstructor()) {
signature = ObjectStreamClass.getSignature((Constructor)m);
} else {
signature = ObjectStreamClass.getSignature((Method)m);
}
}
}
}