/*
* @(#)ObjectStreamClass.java 1.56 05/09/13
*
* Copyright 2005 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.impl.io;
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 com.sun.corba.se.impl.util.RepositoryId;
import sun.misc.SoftCache;
import org.omg.CORBA.ValueMember;
import sun.corba.Bridge;
/**
* 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.
*
* The ObjectStreamClass for a specific class loaded in this Java VM can
* be found using the lookup method.
*
* @author Roger Riggs
* @(#)ObjectStreamClass.java 1.17 99/06/07
* @since JDK1.1
*/
public class ObjectStreamClass implements java.io.Serializable {
private static final boolean DEBUG_SVUID = false ;
public static final long kDefaultUID = -1;
private static Object noArgsList[] = {};
private static Class noTypesList[] = {};
private static Hashtable translatedFields;
private static final Bridge bridge =
(Bridge)AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
return Bridge.get() ;
}
}
) ;
/** 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 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 = (ObjectStreamClass)descriptorFor.get(cl);
if (desc == null) {
/* Check if it's serializable */
boolean serializable = classSerializable.isAssignableFrom(cl);
/* 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);
}
/* 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(cl, superdesc,
serializable, externalizable);
}
// Must always call init. See bug 4488137. This code was
// incorrectly changed to return immediately on a non-null
// cache result. That allowed threads to gain access to
// unintialized instances.
//
// History: Note, the following init() call was originally within
// the synchronization block, as it currently is now. Later, the
// init() call was moved outside the synchronization block, and
// the init() method used a private member variable lock, to
// avoid performance problems. See bug 4165204. But that lead to
// a deadlock situation, see bug 5104239. Hence, the init() method
// has now been moved back into the synchronization block. The
// right approach to solving these problems would be to rewrite
// this class, based on the latest java.io.ObjectStreamClass.
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 theosc = ObjectStreamClass.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 theosc = ObjectStreamClass.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)
{
try {
for (int i = 0; i < fields.length; i++) {
if (fields[i].getName().equals(field.name)) {
if (fields[i].getSignature().equals(
ValueUtility.getSignature(field)))
return true;
}
}
} catch (Exception exc) {
// Ignore this; all we want to do is return false
// Note that ValueUtility.getSignature can throw checked exceptions.
}
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);
}
}
else return value;
}
public Object readResolve(Object value) {
if (readResolveObjectMethod != null) {
try {
return readResolveObjectMethod.invoke(value,noArgsList);
} catch(Throwable t) {
throw new RuntimeException(t);
}
}
else return value;
}
/**
* Return a string describing this ObjectStreamClass.
*/
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 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 */
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.
*/
descriptorFor.put(cl, 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 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) {
// See description at definition of initialized.
if (initialized)
return;
final Class cl = ofClass;
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++) {
Field fld = actualfields[i] ;
int modifiers = fld.getModifiers();
if (!Modifier.isStatic(modifiers) &&
!Modifier.isTransient(modifiers)) {
fld.setAccessible(true) ;
tempFields[numFields++] = new ObjectStreamField(fld);
}
}
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);
}
} 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);
// SerialBug 2: should be computed after writeObject
// actualSuid = computeStructuralUID(cl);
} else {
suid = _computeSerialVersionUID(cl);
// SerialBug 2: should be computed after writeObject
// actualSuid = computeStructuralUID(cl);
}
} catch (NoSuchFieldException ex) {
suid = _computeSerialVersionUID(cl);
// SerialBug 2: should be computed after writeObject
// actualSuid = computeStructuralUID(cl);
} catch (IllegalAccessException ex) {
suid = _computeSerialVersionUID(cl);
}
}
writeReplaceObjectMethod = ObjectStreamClass.getInheritableMethod(cl,
"writeReplace", noTypesList, Object.class);
readResolveObjectMethod = ObjectStreamClass.getInheritableMethod(cl,
"readResolve", noTypesList, Object.class);
if (externalizable)
cons = getExternalizableConstructor(cl) ;
else
cons = getSerializableConstructor(cl) ;
if (serializable && !forProxyClass) {
/* Look for the writeObject method
* Set the accessible flag on it here. ObjectOutputStream
* will call it as necessary.
*/
writeObjectMethod = getPrivateMethod( cl, "writeObject",
new Class[] { java.io.ObjectOutputStream.class }, Void.TYPE ) ;
readObjectMethod = getPrivateMethod( cl, "readObject",
new Class[] { java.io.ObjectInputStream.class }, Void.TYPE ) ;
}
return null;
}
});
}
// This call depends on a lot of information computed above!
actualSuid = ObjectStreamClass.computeStructuralUID(this, cl);
// If we have a write object method, precompute the
// RMI-IIOP stream format version 2 optional data
// repository ID.
if (hasWriteObject())
rmiiiopOptionalDataRepId = computeRMIIIOPOptionalDataRepId();
// This must be done last.
initialized = true;
}
}
/**
* Returns non-static private method with given signature defined by given
* class, or null if none found. Access checks are disabled on the
* returned method (if any).
*/
private static Method getPrivateMethod(Class cl, String name,
Class[] argTypes,
Class returnType)
{
try {
Method meth = cl.getDeclaredMethod(name, argTypes);
meth.setAccessible(true);
int mods = meth.getModifiers();
return ((meth.getReturnType() == returnType) &&
((mods & Modifier.STATIC) == 0) &&
((mods & Modifier.PRIVATE) != 0)) ? meth : null;
} catch (NoSuchMethodException ex) {
return null;
}
}
// Specific to RMI-IIOP
/**
* Java to IDL ptc-02-01-12 1.5.1
*
* "The rep_id string passed to the start_value method must be
* 'RMI:org.omg.custom.class:hashcode:suid' where class is the
* fully-qualified name of the class whose writeObject method
* is being invoked and hashcode and suid are the class's hashcode
* and SUID."
*/
private String computeRMIIIOPOptionalDataRepId() {
StringBuffer sbuf = new StringBuffer("RMI:org.omg.custom.");
sbuf.append(RepositoryId.convertToISOLatin1(this.getName()));
sbuf.append(':');
sbuf.append(this.getActualSerialVersionUIDStr());
sbuf.append(':');
sbuf.append(this.getSerialVersionUIDStr());
return sbuf.toString();
}
/**
* This will return null if there is no writeObject method.
*/
public final String getRMIIIOPOptionalDataRepId() {
return rmiiiopOptionalDataRepId;
}
/*
* 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;
}
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 = Class.forName("com.sun.corba.se.impl.io.ObjectStreamField");
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){
NoSuchFieldException nsfe = new NoSuchFieldException();
nsfe.initCause( t ) ;
throw nsfe ;
}
}
/*
* 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.
*/
final void setClass(Class cl) throws InvalidClassException {
if (cl == null) {
localClassDesc = null;
ofClass = null;
computeFieldInfo();
return;
}
localClassDesc = lookupInternal(cl);
if (localClassDesc == null)
// XXX I18N, logging needed
throw new InvalidClassException(cl.getName(),
"Local class not compatible");
if (suid != localClassDesc.suid) {
/* Check for exceptional cases that allow mismatched suid. */
/* Allow adding Serializable or Externalizable
* to a later release of the class.
*/
boolean addedSerialOrExtern =
isNonSerializable() || localClassDesc.isNonSerializable();
/* 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.
*/
boolean arraySUID = (cl.isArray() && ! cl.getName().equals(name));
if (! arraySUID && ! addedSerialOrExtern ) {
// XXX I18N, logging needed
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(), '.'))
// XXX I18N, logging needed
throw new InvalidClassException(cl.getName(),
"Incompatible local class name. " +
"Expected class name compatible with " +
name);
/*
* Test that both implement either serializable or externalizable.
*/
// The next check is more generic, since it covers the
// Proxy case, the JDK 1.3 serialization code has
// both checks
//if ((serializable && localClassDesc.externalizable) ||
// (externalizable && localClassDesc.serializable))
// throw new InvalidClassException(localCl.getName(),
// "Serializable is incompatible with Externalizable");
if ((serializable != localClassDesc.serializable) ||
(externalizable != localClassDesc.externalizable) ||
(!serializable && !externalizable))
// XXX I18N, logging needed
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])) {
// XXX I18N, logging needed
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;
/* get the cache of these methods from the local class
* implementation.
*/
readObjectMethod = localClassDesc.readObjectMethod;
readResolveObjectMethod = localClassDesc.readResolveObjectMethod;
}
/* 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 other) {
return (suid == other.suid) &&
compareClassNames(name, other.name, '.');
}
/*
* Return the superclass descriptor of this descriptor.
*/
final void setSuperclass(ObjectStreamClass s) {
superclass = s;
}
/*
* Return the superclass descriptor of this descriptor.
*/
final ObjectStreamClass getSuperclass() {
return superclass;
}
/**
* Return whether the class has a readObject method
*/
final boolean hasReadObject() {
return readObjectMethod != null;
}
/*
* Return whether the class has a writeObject method
*/
final boolean hasWriteObject() {
return writeObjectMethod != null ;
}
/**
* Returns when or not this class should be custom
* marshaled (use chunking). This should happen if
* it is Externalizable OR if it or
* any of its superclasses has a writeObject method,
*/
final boolean isCustomMarshaled() {
return (hasWriteObject() || isExternalizable())
|| (superclass != null && superclass.isCustomMarshaled());
}
/*
* Return true if all instances of 'this' Externalizable class
* are written in block-data mode from the stream that 'this' was read
* from.
*
* 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 ObjectOutputStream#useProtocolVersion
* @see ObjectStreamConstants#PROTOCOL_VERSION_1
* @see ObjectStreamConstants#PROTOCOL_VERSION_2
*
* @since JDK 1.2
*/
boolean hasExternalizableBlockDataMode() {
return hasExternalizableBlockData;
}
/**
* Creates a new instance of the represented class. If the class is
* externalizable, invokes its public no-arg constructor; otherwise, if the
* class is serializable, invokes the no-arg constructor of the first
* non-serializable superclass. Throws UnsupportedOperationException if
* this class descriptor is not associated with a class, if the associated
* class is non-serializable or if the appropriate no-arg constructor is
* inaccessible/unavailable.
*/
Object newInstance()
throws InstantiationException, InvocationTargetException,
UnsupportedOperationException
{
if (cons != null) {
try {
return cons.newInstance(new Object[0]);
} catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed
InternalError ie = new InternalError();
ie.initCause( ex ) ;
throw ie ;
}
} else {
throw new UnsupportedOperationException();
}
}
/**
* Returns public no-arg constructor of given class, or null if none found.
* Access checks are disabled on the returned constructor (if any), since
* the defining class may still be non-public.
*/
private static Constructor getExternalizableConstructor(Class cl) {
try {
Constructor cons = cl.getDeclaredConstructor(new Class[0]);
cons.setAccessible(true);
return ((cons.getModifiers() & Modifier.PUBLIC) != 0) ?
cons : null;
} catch (NoSuchMethodException ex) {
return null;
}
}
/**
* Returns subclass-accessible no-arg constructor of first non-serializable
* superclass, or null if none found. Access checks are disabled on the
* returned constructor (if any).
*/
private static Constructor getSerializableConstructor(Class cl) {
Class initCl = cl;
while (Serializable.class.isAssignableFrom(initCl)) {
if ((initCl = initCl.getSuperclass()) == null) {
return null;
}
}
try {
Constructor cons = initCl.getDeclaredConstructor(new Class[0]);
int mods = cons.getModifiers();
if ((mods & Modifier.PRIVATE) != 0 ||
((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) == 0 &&
!packageEquals(cl, initCl)))
{
return null;
}
cons = bridge.newConstructorForSerialization(cl, cons);
cons.setAccessible(true);
return cons;
} catch (NoSuchMethodException ex) {
return null;
}
}
/*
* Return the ObjectStreamClass of the local class this one is based on.
*/
final ObjectStreamClass 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 void msg( String str )
{
System.out.println( str ) ;
}
/* JDK 1.5 has introduced some new modifier bits (such as SYNTHETIC)
* that can affect the SVUID computation (see bug 4897937). These bits
* must be ignored, as otherwise interoperability with ORBs in earlier
* JDK versions can be compromised. I am adding these masks for this
* purpose as discussed in the CCC for this bug (see http://ccc.sfbay/4897937).
*/
public static final int CLASS_MASK = Modifier.PUBLIC | Modifier.FINAL |
Modifier.INTERFACE | Modifier.ABSTRACT ;
public static final int FIELD_MASK = Modifier.PUBLIC | Modifier.PRIVATE |
Modifier.PROTECTED | Modifier.STATIC | Modifier.FINAL |
Modifier.TRANSIENT | Modifier.VOLATILE ;
public static final int METHOD_MASK = Modifier.PUBLIC | Modifier.PRIVATE |
Modifier.PROTECTED | Modifier.STATIC | Modifier.FINAL |
Modifier.SYNCHRONIZED | Modifier.NATIVE | Modifier.ABSTRACT |
Modifier.STRICT ;
/*
* 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) {
if (DEBUG_SVUID)
msg( "Computing SerialVersionUID for " + 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);
if (DEBUG_SVUID)
msg( "\twriteUTF( \"" + cl.getName() + "\" )" ) ;
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;
}
}
// Mask out any post-1.4 attributes
classaccess &= CLASS_MASK ;
if (DEBUG_SVUID)
msg( "\twriteInt( " + classaccess + " ) " ) ;
data.writeInt(classaccess);
/*
* Get the list of interfaces supported,
* Accumulate their names their names in Lexical order
* and add them to the hash
*/
if (!cl.isArray()) {
/* In 1.2fcs, getInterfaces() was modified to return
* {java.lang.Cloneable, java.io.Serializable} when
* called on array classes. These values would upset
* the computation of the hash, so we explicitly omit
* them from its computation.
*/
Class interfaces[] = cl.getInterfaces();
Arrays.sort(interfaces, compareClassByName);
for (int i = 0; i < interfaces.length; i++) {
if (DEBUG_SVUID)
msg( "\twriteUTF( \"" + interfaces[i].getName() + "\" ) " ) ;
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;
if (DEBUG_SVUID)
msg( "\twriteUTF( \"" + f.getName() + "\" ) " ) ;
data.writeUTF(f.getName());
// Mask out any post-1.4 bits
m &= FIELD_MASK ;
if (DEBUG_SVUID)
msg( "\twriteInt( " + m + " ) " ) ;
data.writeInt(m);
if (DEBUG_SVUID)
msg( "\twriteUTF( \"" + getSignature(f.getType()) + "\" ) " ) ;
data.writeUTF(getSignature(f.getType()));
}
if (hasStaticInitializer(cl)) {
if (DEBUG_SVUID)
msg( "\twriteUTF( \"