duke@1: /*
ohair@158: * Copyright (c) 1998, 2009, Oracle and/or its affiliates. All rights reserved.
duke@1: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
duke@1: *
duke@1: * This code is free software; you can redistribute it and/or modify it
duke@1: * under the terms of the GNU General Public License version 2 only, as
ohair@158: * published by the Free Software Foundation. Oracle designates this
duke@1: * particular file as subject to the "Classpath" exception as provided
ohair@158: * by Oracle in the LICENSE file that accompanied this code.
duke@1: *
duke@1: * This code is distributed in the hope that it will be useful, but WITHOUT
duke@1: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
duke@1: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
duke@1: * version 2 for more details (a copy is included in the LICENSE file that
duke@1: * accompanied this code).
duke@1: *
duke@1: * You should have received a copy of the GNU General Public License version
duke@1: * 2 along with this work; if not, write to the Free Software Foundation,
duke@1: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
duke@1: *
ohair@158: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
ohair@158: * or visit www.oracle.com if you need additional information or have any
ohair@158: * questions.
duke@1: */
duke@1: /*
duke@1: * Licensed Materials - Property of IBM
duke@1: * RMI-IIOP v1.0
duke@1: * Copyright IBM Corp. 1998 1999 All Rights Reserved
duke@1: *
duke@1: */
duke@1:
duke@1: package com.sun.corba.se.impl.io;
duke@1:
duke@1: import java.security.MessageDigest;
duke@1: import java.security.NoSuchAlgorithmException;
duke@1: import java.security.DigestOutputStream;
duke@1: import java.security.AccessController;
duke@1: import java.security.PrivilegedExceptionAction;
duke@1: import java.security.PrivilegedActionException;
duke@1: import java.security.PrivilegedAction;
duke@1:
duke@1: import java.lang.reflect.Modifier;
duke@1: import java.lang.reflect.Array;
duke@1: import java.lang.reflect.Field;
duke@1: import java.lang.reflect.Member;
duke@1: import java.lang.reflect.Method;
duke@1: import java.lang.reflect.Constructor;
duke@1: import java.lang.reflect.Proxy;
duke@1: import java.lang.reflect.InvocationTargetException;
duke@1:
duke@1: import java.io.IOException;
duke@1: import java.io.DataOutputStream;
duke@1: import java.io.ByteArrayOutputStream;
duke@1: import java.io.InvalidClassException;
duke@1: import java.io.Serializable;
duke@1:
duke@1: import java.util.Arrays;
duke@1: import java.util.Comparator;
duke@1: import java.util.Hashtable;
duke@1:
duke@1: import com.sun.corba.se.impl.util.RepositoryId;
duke@1:
duke@1: import org.omg.CORBA.ValueMember;
duke@1:
duke@1: import sun.corba.Bridge;
duke@1:
duke@1: /**
duke@1: * A ObjectStreamClass describes a class that can be serialized to a stream
duke@1: * or a class that was serialized to a stream. It contains the name
duke@1: * and the serialVersionUID of the class.
duke@1: *
duke@1: * The ObjectStreamClass for a specific class loaded in this Java VM can
duke@1: * be found using the lookup method.
duke@1: *
duke@1: * @author Roger Riggs
duke@1: * @since JDK1.1
duke@1: */
duke@1: public class ObjectStreamClass implements java.io.Serializable {
duke@1: private static final boolean DEBUG_SVUID = false ;
duke@1:
duke@1: public static final long kDefaultUID = -1;
duke@1:
duke@1: private static Object noArgsList[] = {};
duke@1: private static Class noTypesList[] = {};
duke@1:
duke@1: private static Hashtable translatedFields;
duke@1:
miroslawzn@235: /** true if represents enum type */
miroslawzn@235: private boolean isEnum;
miroslawzn@235:
duke@1: private static final Bridge bridge =
duke@1: (Bridge)AccessController.doPrivileged(
duke@1: new PrivilegedAction() {
duke@1: public Object run() {
duke@1: return Bridge.get() ;
duke@1: }
duke@1: }
duke@1: ) ;
duke@1:
duke@1: /** Find the descriptor for a class that can be serialized. Null
duke@1: * is returned if the specified class does not implement
duke@1: * java.io.Serializable or java.io.Externalizable.
duke@1: */
duke@1: static final ObjectStreamClass lookup(Class cl)
duke@1: {
duke@1: ObjectStreamClass desc = lookupInternal(cl);
duke@1: if (desc.isSerializable() || desc.isExternalizable())
duke@1: return desc;
duke@1: return null;
duke@1: }
duke@1:
duke@1: /*
duke@1: * Find the class descriptor for the specified class.
duke@1: * Package access only so it can be called from ObjectIn/OutStream.
duke@1: */
duke@1: static ObjectStreamClass lookupInternal(Class cl)
duke@1: {
duke@1: /* Synchronize on the hashtable so no two threads will do
duke@1: * this at the same time.
duke@1: */
duke@1: ObjectStreamClass desc = null;
duke@1: synchronized (descriptorFor) {
duke@1: /* Find the matching descriptor if it already known */
duke@1: desc = findDescriptorFor(cl);
duke@1: if (desc == null) {
duke@1: /* Check if it's serializable */
duke@1: boolean serializable = classSerializable.isAssignableFrom(cl);
duke@1:
duke@1: /* If the class is only Serializable,
duke@1: * lookup the descriptor for the superclass.
duke@1: */
duke@1: ObjectStreamClass superdesc = null;
duke@1: if (serializable) {
duke@1: Class superclass = cl.getSuperclass();
duke@1: if (superclass != null)
duke@1: superdesc = lookup(superclass);
duke@1: }
duke@1:
duke@1: /* Check if its' externalizable.
duke@1: * If it's Externalizable, clear the serializable flag.
duke@1: * Only one or the other may be set in the protocol.
duke@1: */
duke@1: boolean externalizable = false;
duke@1: if (serializable) {
duke@1: externalizable =
duke@1: ((superdesc != null) && superdesc.isExternalizable()) ||
duke@1: classExternalizable.isAssignableFrom(cl);
duke@1: if (externalizable) {
duke@1: serializable = false;
duke@1: }
duke@1: }
duke@1:
duke@1: /* Create a new version descriptor,
duke@1: * it put itself in the known table.
duke@1: */
duke@1: desc = new ObjectStreamClass(cl, superdesc,
duke@1: serializable, externalizable);
duke@1: }
tbell@68: // Must always call init. See bug 4488137. This code was
tbell@68: // incorrectly changed to return immediately on a non-null
tbell@68: // cache result. That allowed threads to gain access to
tbell@68: // unintialized instances.
tbell@68: //
tbell@68: // History: Note, the following init() call was originally within
tbell@68: // the synchronization block, as it currently is now. Later, the
tbell@68: // init() call was moved outside the synchronization block, and
tbell@68: // the init() method used a private member variable lock, to
tbell@68: // avoid performance problems. See bug 4165204. But that lead to
tbell@68: // a deadlock situation, see bug 5104239. Hence, the init() method
tbell@68: // has now been moved back into the synchronization block. The
tbell@68: // right approach to solving these problems would be to rewrite
tbell@68: // this class, based on the latest java.io.ObjectStreamClass.
tbell@68: desc.init();
duke@1: }
duke@1: return desc;
duke@1: }
duke@1:
duke@1: /**
duke@1: * The name of the class described by this descriptor.
duke@1: */
duke@1: public final String getName() {
duke@1: return name;
duke@1: }
duke@1:
duke@1: /**
duke@1: * Return the serialVersionUID for this class.
duke@1: * The serialVersionUID defines a set of classes all with the same name
duke@1: * that have evolved from a common root class and agree to be serialized
duke@1: * and deserialized using a common format.
duke@1: */
duke@1: public static final long getSerialVersionUID( java.lang.Class clazz) {
duke@1: ObjectStreamClass theosc = ObjectStreamClass.lookup( clazz );
duke@1: if( theosc != null )
duke@1: {
duke@1: return theosc.getSerialVersionUID( );
duke@1: }
duke@1: return 0;
duke@1: }
duke@1:
duke@1: /**
duke@1: * Return the serialVersionUID for this class.
duke@1: * The serialVersionUID defines a set of classes all with the same name
duke@1: * that have evolved from a common root class and agree to be serialized
duke@1: * and deserialized using a common format.
duke@1: */
duke@1: public final long getSerialVersionUID() {
duke@1: return suid;
duke@1: }
duke@1:
duke@1: /**
duke@1: * Return the serialVersionUID string for this class.
duke@1: * The serialVersionUID defines a set of classes all with the same name
duke@1: * that have evolved from a common root class and agree to be serialized
duke@1: * and deserialized using a common format.
duke@1: */
duke@1: public final String getSerialVersionUIDStr() {
duke@1: if (suidStr == null)
duke@1: suidStr = Long.toHexString(suid).toUpperCase();
duke@1: return suidStr;
duke@1: }
duke@1:
duke@1: /**
duke@1: * Return the actual (computed) serialVersionUID for this class.
duke@1: */
duke@1: public static final long getActualSerialVersionUID( java.lang.Class clazz )
duke@1: {
duke@1: ObjectStreamClass theosc = ObjectStreamClass.lookup( clazz );
duke@1: if( theosc != null )
duke@1: {
duke@1: return theosc.getActualSerialVersionUID( );
duke@1: }
duke@1: return 0;
duke@1: }
duke@1:
duke@1: /**
duke@1: * Return the actual (computed) serialVersionUID for this class.
duke@1: */
duke@1: public final long getActualSerialVersionUID() {
duke@1: return actualSuid;
duke@1: }
duke@1:
duke@1: /**
duke@1: * Return the actual (computed) serialVersionUID for this class.
duke@1: */
duke@1: public final String getActualSerialVersionUIDStr() {
duke@1: if (actualSuidStr == null)
duke@1: actualSuidStr = Long.toHexString(actualSuid).toUpperCase();
duke@1: return actualSuidStr;
duke@1: }
duke@1:
duke@1: /**
duke@1: * Return the class in the local VM that this version is mapped to.
duke@1: * Null is returned if there is no corresponding local class.
duke@1: */
duke@1: public final Class forClass() {
duke@1: return ofClass;
duke@1: }
duke@1:
duke@1: /**
duke@1: * Return an array of the fields of this serializable class.
duke@1: * @return an array containing an element for each persistent
duke@1: * field of this class. Returns an array of length zero if
duke@1: * there are no fields.
duke@1: * @since JDK1.2
duke@1: */
duke@1: public ObjectStreamField[] getFields() {
duke@1: // Return a copy so the caller can't change the fields.
duke@1: if (fields.length > 0) {
duke@1: ObjectStreamField[] dup = new ObjectStreamField[fields.length];
duke@1: System.arraycopy(fields, 0, dup, 0, fields.length);
duke@1: return dup;
duke@1: } else {
duke@1: return fields;
duke@1: }
duke@1: }
duke@1:
duke@1: public boolean hasField(ValueMember field)
duke@1: {
duke@1: try {
duke@1: for (int i = 0; i < fields.length; i++) {
duke@1: if (fields[i].getName().equals(field.name)) {
duke@1: if (fields[i].getSignature().equals(
duke@1: ValueUtility.getSignature(field)))
duke@1: return true;
duke@1: }
duke@1: }
duke@1: } catch (Exception exc) {
duke@1: // Ignore this; all we want to do is return false
duke@1: // Note that ValueUtility.getSignature can throw checked exceptions.
duke@1: }
duke@1:
duke@1: return false;
duke@1: }
duke@1:
duke@1: /* Avoid unnecessary allocations. */
duke@1: final ObjectStreamField[] getFieldsNoCopy() {
duke@1: return fields;
duke@1: }
duke@1:
duke@1: /**
duke@1: * Get the field of this class by name.
duke@1: * @return The ObjectStreamField object of the named field or null if there
duke@1: * is no such named field.
duke@1: */
duke@1: public final ObjectStreamField getField(String name) {
duke@1: /* Binary search of fields by name.
duke@1: */
duke@1: for (int i = fields.length-1; i >= 0; i--) {
duke@1: if (name.equals(fields[i].getName())) {
duke@1: return fields[i];
duke@1: }
duke@1: }
duke@1: return null;
duke@1: }
duke@1:
duke@1: public Serializable writeReplace(Serializable value) {
duke@1: if (writeReplaceObjectMethod != null) {
duke@1: try {
duke@1: return (Serializable) writeReplaceObjectMethod.invoke(value,noArgsList);
duke@1: } catch(Throwable t) {
duke@1: throw new RuntimeException(t);
duke@1: }
duke@1: }
duke@1: else return value;
duke@1: }
duke@1:
duke@1: public Object readResolve(Object value) {
duke@1: if (readResolveObjectMethod != null) {
duke@1: try {
duke@1: return readResolveObjectMethod.invoke(value,noArgsList);
duke@1: } catch(Throwable t) {
duke@1: throw new RuntimeException(t);
duke@1: }
duke@1: }
duke@1: else return value;
duke@1: }
duke@1:
duke@1: /**
duke@1: * Return a string describing this ObjectStreamClass.
duke@1: */
duke@1: public final String toString() {
duke@1: StringBuffer sb = new StringBuffer();
duke@1:
duke@1: sb.append(name);
duke@1: sb.append(": static final long serialVersionUID = ");
duke@1: sb.append(Long.toString(suid));
duke@1: sb.append("L;");
duke@1: return sb.toString();
duke@1: }
duke@1:
duke@1: /*
duke@1: * Create a new ObjectStreamClass from a loaded class.
duke@1: * Don't call this directly, call lookup instead.
duke@1: */
duke@1: private ObjectStreamClass(java.lang.Class cl, ObjectStreamClass superdesc,
duke@1: boolean serial, boolean extern)
duke@1: {
duke@1: ofClass = cl; /* created from this class */
duke@1:
duke@1: if (Proxy.isProxyClass(cl)) {
duke@1: forProxyClass = true;
duke@1: }
duke@1:
duke@1: name = cl.getName();
miroslawzn@235: isEnum = Enum.class.isAssignableFrom(cl);
duke@1: superclass = superdesc;
duke@1: serializable = serial;
duke@1: if (!forProxyClass) {
duke@1: // proxy classes are never externalizable
duke@1: externalizable = extern;
duke@1: }
duke@1:
duke@1: /*
duke@1: * Enter this class in the table of known descriptors.
duke@1: * Otherwise, when the fields are read it may recurse
duke@1: * trying to find the descriptor for itself.
duke@1: */
duke@1: insertDescriptorFor(this);
duke@1:
duke@1: /*
duke@1: * The remainder of initialization occurs in init(), which is called
duke@1: * after the lock on the global class descriptor table has been
duke@1: * released.
duke@1: */
duke@1: }
duke@1:
duke@1: /*
duke@1: * Initialize class descriptor. This method is only invoked on class
duke@1: * descriptors created via calls to lookupInternal(). This method is kept
duke@1: * separate from the ObjectStreamClass constructor so that lookupInternal
duke@1: * does not have to hold onto a global class descriptor table lock while the
duke@1: * class descriptor is being initialized (see bug 4165204).
duke@1: */
duke@1:
duke@1:
duke@1: private void init() {
duke@1: synchronized (lock) {
duke@1:
duke@1: // See description at definition of initialized.
duke@1: if (initialized)
duke@1: return;
duke@1:
duke@1: final Class cl = ofClass;
duke@1:
duke@1: if (!serializable ||
duke@1: externalizable ||
duke@1: forProxyClass ||
miroslawzn@235: name.equals("java.lang.String") ||
miroslawzn@235: isEnum) {
duke@1: fields = NO_FIELDS;
duke@1: } else if (serializable) {
duke@1: /* Ask for permission to override field access checks.
duke@1: */
duke@1: AccessController.doPrivileged(new PrivilegedAction() {
duke@1: public Object run() {
duke@1: /* Fill in the list of persistent fields.
duke@1: * If it is declared, use the declared serialPersistentFields.
duke@1: * Otherwise, extract the fields from the class itself.
duke@1: */
duke@1: try {
duke@1: Field pf = cl.getDeclaredField("serialPersistentFields");
duke@1: // serial bug 7; the serialPersistentFields were not
duke@1: // being read and stored as Accessible bit was not set
duke@1: pf.setAccessible(true);
duke@1: // serial bug 7; need to find if the field is of type
duke@1: // java.io.ObjectStreamField
duke@1: java.io.ObjectStreamField[] f =
duke@1: (java.io.ObjectStreamField[])pf.get(cl);
duke@1: int mods = pf.getModifiers();
duke@1: if ((Modifier.isPrivate(mods)) &&
duke@1: (Modifier.isStatic(mods)) &&
duke@1: (Modifier.isFinal(mods)))
duke@1: {
duke@1: fields = (ObjectStreamField[])translateFields((Object[])pf.get(cl));
duke@1: }
duke@1: } catch (NoSuchFieldException e) {
duke@1: fields = null;
duke@1: } catch (IllegalAccessException e) {
duke@1: fields = null;
duke@1: } catch (IllegalArgumentException e) {
duke@1: fields = null;
duke@1: } catch (ClassCastException e) {
duke@1: /* Thrown if a field serialPersistentField exists
duke@1: * but it is not of type ObjectStreamField.
duke@1: */
duke@1: fields = null;
duke@1: }
duke@1:
duke@1:
duke@1: if (fields == null) {
duke@1: /* Get all of the declared fields for this
duke@1: * Class. setAccessible on all fields so they
duke@1: * can be accessed later. Create a temporary
duke@1: * ObjectStreamField array to hold each
duke@1: * non-static, non-transient field. Then copy the
duke@1: * temporary array into an array of the correct
duke@1: * size once the number of fields is known.
duke@1: */
duke@1: Field[] actualfields = cl.getDeclaredFields();
duke@1:
duke@1: int numFields = 0;
duke@1: ObjectStreamField[] tempFields =
duke@1: new ObjectStreamField[actualfields.length];
duke@1: for (int i = 0; i < actualfields.length; i++) {
duke@1: Field fld = actualfields[i] ;
duke@1: int modifiers = fld.getModifiers();
duke@1: if (!Modifier.isStatic(modifiers) &&
duke@1: !Modifier.isTransient(modifiers)) {
duke@1: fld.setAccessible(true) ;
duke@1: tempFields[numFields++] = new ObjectStreamField(fld);
duke@1: }
duke@1: }
duke@1:
duke@1: fields = new ObjectStreamField[numFields];
duke@1: System.arraycopy(tempFields, 0, fields, 0, numFields);
duke@1:
duke@1: } else {
duke@1: // For each declared persistent field, look for an actual
duke@1: // reflected Field. If there is one, make sure it's the correct
duke@1: // type and cache it in the ObjectStreamClass for that field.
duke@1: for (int j = fields.length-1; j >= 0; j--) {
duke@1: try {
duke@1: Field reflField = cl.getDeclaredField(fields[j].getName());
duke@1: if (fields[j].getType() == reflField.getType()) {
duke@1: reflField.setAccessible(true);
duke@1: fields[j].setField(reflField);
duke@1: }
duke@1: } catch (NoSuchFieldException e) {
duke@1: // Nothing to do
duke@1: }
duke@1: }
duke@1: }
duke@1: return null;
duke@1: }
duke@1: });
duke@1:
duke@1: if (fields.length > 1)
duke@1: Arrays.sort(fields);
duke@1:
duke@1: /* Set up field data for use while writing using the API api. */
duke@1: computeFieldInfo();
duke@1: }
duke@1:
duke@1: /* Get the serialVersionUID from the class.
duke@1: * It uses the access override mechanism so make sure
duke@1: * the field objects is only used here.
duke@1: *
duke@1: * NonSerializable classes have a serialVerisonUID of 0L.
duke@1: */
miroslawzn@235: if (isNonSerializable() || isEnum) {
duke@1: suid = 0L;
duke@1: } else {
duke@1: // Lookup special Serializable members using reflection.
duke@1: AccessController.doPrivileged(new PrivilegedAction() {
duke@1: public Object run() {
duke@1: if (forProxyClass) {
duke@1: // proxy classes always have serialVersionUID of 0L
duke@1: suid = 0L;
duke@1: } else {
duke@1: try {
duke@1: final Field f = cl.getDeclaredField("serialVersionUID");
duke@1: int mods = f.getModifiers();
duke@1: // SerialBug 5: static final SUID should be read
duke@1: if (Modifier.isStatic(mods) && Modifier.isFinal(mods) ) {
duke@1: f.setAccessible(true);
duke@1: suid = f.getLong(cl);
duke@1: // SerialBug 2: should be computed after writeObject
duke@1: // actualSuid = computeStructuralUID(cl);
duke@1: } else {
duke@1: suid = _computeSerialVersionUID(cl);
duke@1: // SerialBug 2: should be computed after writeObject
duke@1: // actualSuid = computeStructuralUID(cl);
duke@1: }
duke@1: } catch (NoSuchFieldException ex) {
duke@1: suid = _computeSerialVersionUID(cl);
duke@1: // SerialBug 2: should be computed after writeObject
duke@1: // actualSuid = computeStructuralUID(cl);
duke@1: } catch (IllegalAccessException ex) {
duke@1: suid = _computeSerialVersionUID(cl);
duke@1: }
duke@1: }
duke@1:
duke@1: writeReplaceObjectMethod = ObjectStreamClass.getInheritableMethod(cl,
duke@1: "writeReplace", noTypesList, Object.class);
duke@1:
duke@1: readResolveObjectMethod = ObjectStreamClass.getInheritableMethod(cl,
duke@1: "readResolve", noTypesList, Object.class);
duke@1:
duke@1: if (externalizable)
duke@1: cons = getExternalizableConstructor(cl) ;
duke@1: else
duke@1: cons = getSerializableConstructor(cl) ;
duke@1:
duke@1: if (serializable && !forProxyClass) {
duke@1: /* Look for the writeObject method
duke@1: * Set the accessible flag on it here. ObjectOutputStream
duke@1: * will call it as necessary.
duke@1: */
duke@1: writeObjectMethod = getPrivateMethod( cl, "writeObject",
duke@1: new Class[] { java.io.ObjectOutputStream.class }, Void.TYPE ) ;
duke@1: readObjectMethod = getPrivateMethod( cl, "readObject",
duke@1: new Class[] { java.io.ObjectInputStream.class }, Void.TYPE ) ;
duke@1: }
duke@1: return null;
duke@1: }
duke@1: });
duke@1: }
duke@1:
duke@1: // This call depends on a lot of information computed above!
duke@1: actualSuid = ObjectStreamClass.computeStructuralUID(this, cl);
duke@1:
duke@1: // If we have a write object method, precompute the
duke@1: // RMI-IIOP stream format version 2 optional data
duke@1: // repository ID.
duke@1: if (hasWriteObject())
duke@1: rmiiiopOptionalDataRepId = computeRMIIIOPOptionalDataRepId();
duke@1:
duke@1: // This must be done last.
duke@1: initialized = true;
duke@1: }
duke@1: }
duke@1:
duke@1: /**
duke@1: * Returns non-static private method with given signature defined by given
duke@1: * class, or null if none found. Access checks are disabled on the
duke@1: * returned method (if any).
duke@1: */
duke@1: private static Method getPrivateMethod(Class cl, String name,
duke@1: Class[] argTypes,
duke@1: Class returnType)
duke@1: {
duke@1: try {
duke@1: Method meth = cl.getDeclaredMethod(name, argTypes);
duke@1: meth.setAccessible(true);
duke@1: int mods = meth.getModifiers();
duke@1: return ((meth.getReturnType() == returnType) &&
duke@1: ((mods & Modifier.STATIC) == 0) &&
duke@1: ((mods & Modifier.PRIVATE) != 0)) ? meth : null;
duke@1: } catch (NoSuchMethodException ex) {
duke@1: return null;
duke@1: }
duke@1: }
duke@1:
duke@1: // Specific to RMI-IIOP
duke@1: /**
duke@1: * Java to IDL ptc-02-01-12 1.5.1
duke@1: *
duke@1: * "The rep_id string passed to the start_value method must be
duke@1: * 'RMI:org.omg.custom.class:hashcode:suid' where class is the
duke@1: * fully-qualified name of the class whose writeObject method
duke@1: * is being invoked and hashcode and suid are the class's hashcode
duke@1: * and SUID."
duke@1: */
duke@1: private String computeRMIIIOPOptionalDataRepId() {
duke@1:
duke@1: StringBuffer sbuf = new StringBuffer("RMI:org.omg.custom.");
duke@1: sbuf.append(RepositoryId.convertToISOLatin1(this.getName()));
duke@1: sbuf.append(':');
duke@1: sbuf.append(this.getActualSerialVersionUIDStr());
duke@1: sbuf.append(':');
duke@1: sbuf.append(this.getSerialVersionUIDStr());
duke@1:
duke@1: return sbuf.toString();
duke@1: }
duke@1:
duke@1: /**
duke@1: * This will return null if there is no writeObject method.
duke@1: */
duke@1: public final String getRMIIIOPOptionalDataRepId() {
duke@1: return rmiiiopOptionalDataRepId;
duke@1: }
duke@1:
duke@1: /*
duke@1: * Create an empty ObjectStreamClass for a class about to be read.
duke@1: * This is separate from read so ObjectInputStream can assign the
duke@1: * wire handle early, before any nested ObjectStreamClass might
duke@1: * be read.
duke@1: */
duke@1: ObjectStreamClass(String n, long s) {
duke@1: name = n;
duke@1: suid = s;
duke@1: superclass = null;
duke@1: }
duke@1:
duke@1: private static Object[] translateFields(Object objs[])
duke@1: throws NoSuchFieldException {
duke@1: try{
duke@1: java.io.ObjectStreamField fields[] = (java.io.ObjectStreamField[])objs;
duke@1: Object translation[] = null;
duke@1:
duke@1: if (translatedFields == null)
duke@1: translatedFields = new Hashtable();
duke@1:
duke@1: translation = (Object[])translatedFields.get(fields);
duke@1:
duke@1: if (translation != null)
duke@1: return translation;
duke@1: else {
duke@1: Class osfClass = Class.forName("com.sun.corba.se.impl.io.ObjectStreamField");
duke@1: translation = (Object[])java.lang.reflect.Array.newInstance(osfClass, objs.length);
duke@1: Object arg[] = new Object[2];
duke@1: Class types[] = {String.class, Class.class};
duke@1: Constructor constructor = osfClass.getDeclaredConstructor(types);
duke@1: for (int i = fields.length -1; i >= 0; i--){
duke@1: arg[0] = fields[i].getName();
duke@1: arg[1] = fields[i].getType();
duke@1:
duke@1: translation[i] = constructor.newInstance(arg);
duke@1: }
duke@1: translatedFields.put(fields, translation);
duke@1:
duke@1: }
duke@1:
duke@1: return (Object[])translation;
duke@1: }
duke@1: catch(Throwable t){
duke@1: NoSuchFieldException nsfe = new NoSuchFieldException();
duke@1: nsfe.initCause( t ) ;
duke@1: throw nsfe ;
duke@1: }
duke@1: }
duke@1:
duke@1: /*
duke@1: * Set the class this version descriptor matches.
duke@1: * The base class name and serializable hash must match.
duke@1: * Fill in the reflected Fields that will be used
duke@1: * for reading.
duke@1: */
duke@1: final void setClass(Class cl) throws InvalidClassException {
duke@1:
duke@1: if (cl == null) {
duke@1: localClassDesc = null;
duke@1: ofClass = null;
duke@1: computeFieldInfo();
duke@1: return;
duke@1: }
duke@1:
duke@1: localClassDesc = lookupInternal(cl);
duke@1: if (localClassDesc == null)
duke@1: // XXX I18N, logging needed
duke@1: throw new InvalidClassException(cl.getName(),
duke@1: "Local class not compatible");
duke@1: if (suid != localClassDesc.suid) {
duke@1:
duke@1: /* Check for exceptional cases that allow mismatched suid. */
duke@1:
duke@1: /* Allow adding Serializable or Externalizable
duke@1: * to a later release of the class.
duke@1: */
duke@1: boolean addedSerialOrExtern =
duke@1: isNonSerializable() || localClassDesc.isNonSerializable();
duke@1:
duke@1: /* Disregard the serialVersionUID of an array
duke@1: * when name and cl.Name differ. If resolveClass() returns
duke@1: * an array with a different package name,
duke@1: * the serialVersionUIDs will not match since the fully
duke@1: * qualified array class is used in the
duke@1: * computation of the array's serialVersionUID. There is
duke@1: * no way to set a permanent serialVersionUID for an array type.
duke@1: */
duke@1:
duke@1: boolean arraySUID = (cl.isArray() && ! cl.getName().equals(name));
duke@1:
duke@1: if (! arraySUID && ! addedSerialOrExtern ) {
duke@1: // XXX I18N, logging needed
duke@1: throw new InvalidClassException(cl.getName(),
duke@1: "Local class not compatible:" +
duke@1: " stream classdesc serialVersionUID=" + suid +
duke@1: " local class serialVersionUID=" + localClassDesc.suid);
duke@1: }
duke@1: }
duke@1:
duke@1: /* compare the class names, stripping off package names. */
duke@1: if (! compareClassNames(name, cl.getName(), '.'))
duke@1: // XXX I18N, logging needed
duke@1: throw new InvalidClassException(cl.getName(),
duke@1: "Incompatible local class name. " +
duke@1: "Expected class name compatible with " +
duke@1: name);
duke@1:
duke@1: /*
duke@1: * Test that both implement either serializable or externalizable.
duke@1: */
duke@1:
duke@1: // The next check is more generic, since it covers the
duke@1: // Proxy case, the JDK 1.3 serialization code has
duke@1: // both checks
duke@1: //if ((serializable && localClassDesc.externalizable) ||
duke@1: // (externalizable && localClassDesc.serializable))
duke@1: // throw new InvalidClassException(localCl.getName(),
duke@1: // "Serializable is incompatible with Externalizable");
duke@1:
duke@1: if ((serializable != localClassDesc.serializable) ||
duke@1: (externalizable != localClassDesc.externalizable) ||
duke@1: (!serializable && !externalizable))
duke@1:
duke@1: // XXX I18N, logging needed
duke@1: throw new InvalidClassException(cl.getName(),
duke@1: "Serialization incompatible with Externalization");
duke@1:
duke@1: /* Set up the reflected Fields in the class where the value of each
duke@1: * field in this descriptor should be stored.
duke@1: * Each field in this ObjectStreamClass (the source) is located (by
duke@1: * name) in the ObjectStreamClass of the class(the destination).
duke@1: * In the usual (non-versioned case) the field is in both
duke@1: * descriptors and the types match, so the reflected Field is copied.
duke@1: * If the type does not match, a InvalidClass exception is thrown.
duke@1: * If the field is not present in the class, the reflected Field
duke@1: * remains null so the field will be read but discarded.
duke@1: * If extra fields are present in the class they are ignored. Their
duke@1: * values will be set to the default value by the object allocator.
duke@1: * Both the src and dest field list are sorted by type and name.
duke@1: */
duke@1:
duke@1: ObjectStreamField[] destfield =
duke@1: (ObjectStreamField[])localClassDesc.fields;
duke@1: ObjectStreamField[] srcfield =
duke@1: (ObjectStreamField[])fields;
duke@1:
duke@1: int j = 0;
duke@1: nextsrc:
duke@1: for (int i = 0; i < srcfield.length; i++ ) {
duke@1: /* Find this field in the dest*/
duke@1: for (int k = j; k < destfield.length; k++) {
duke@1: if (srcfield[i].getName().equals(destfield[k].getName())) {
duke@1: /* found match */
duke@1: if (srcfield[i].isPrimitive() &&
duke@1: !srcfield[i].typeEquals(destfield[k])) {
duke@1: // XXX I18N, logging needed
duke@1: throw new InvalidClassException(cl.getName(),
duke@1: "The type of field " +
duke@1: srcfield[i].getName() +
duke@1: " of class " + name +
duke@1: " is incompatible.");
duke@1: }
duke@1:
duke@1: /* Skip over any fields in the dest that are not in the src */
duke@1: j = k;
duke@1:
duke@1: srcfield[i].setField(destfield[j].getField());
duke@1: // go on to the next source field
duke@1: continue nextsrc;
duke@1: }
duke@1: }
duke@1: }
duke@1:
duke@1: /* Set up field data for use while reading from the input stream. */
duke@1: computeFieldInfo();
duke@1:
duke@1: /* Remember the class this represents */
duke@1: ofClass = cl;
duke@1:
duke@1: /* get the cache of these methods from the local class
duke@1: * implementation.
duke@1: */
duke@1: readObjectMethod = localClassDesc.readObjectMethod;
duke@1: readResolveObjectMethod = localClassDesc.readResolveObjectMethod;
duke@1: }
duke@1:
duke@1: /* Compare the base class names of streamName and localName.
duke@1: *
duke@1: * @return Return true iff the base class name compare.
duke@1: * @parameter streamName Fully qualified class name.
duke@1: * @parameter localName Fully qualified class name.
duke@1: * @parameter pkgSeparator class names use either '.' or '/'.
duke@1: *
duke@1: * Only compare base class name to allow package renaming.
duke@1: */
duke@1: static boolean compareClassNames(String streamName,
duke@1: String localName,
duke@1: char pkgSeparator) {
duke@1: /* compare the class names, stripping off package names. */
duke@1: int streamNameIndex = streamName.lastIndexOf(pkgSeparator);
duke@1: if (streamNameIndex < 0)
duke@1: streamNameIndex = 0;
duke@1:
duke@1: int localNameIndex = localName.lastIndexOf(pkgSeparator);
duke@1: if (localNameIndex < 0)
duke@1: localNameIndex = 0;
duke@1:
duke@1: return streamName.regionMatches(false, streamNameIndex,
duke@1: localName, localNameIndex,
duke@1: streamName.length() - streamNameIndex);
duke@1: }
duke@1:
duke@1: /*
duke@1: * Compare the types of two class descriptors.
duke@1: * They match if they have the same class name and suid
duke@1: */
duke@1: final boolean typeEquals(ObjectStreamClass other) {
duke@1: return (suid == other.suid) &&
duke@1: compareClassNames(name, other.name, '.');
duke@1: }
duke@1:
duke@1: /*
duke@1: * Return the superclass descriptor of this descriptor.
duke@1: */
duke@1: final void setSuperclass(ObjectStreamClass s) {
duke@1: superclass = s;
duke@1: }
duke@1:
duke@1: /*
duke@1: * Return the superclass descriptor of this descriptor.
duke@1: */
duke@1: final ObjectStreamClass getSuperclass() {
duke@1: return superclass;
duke@1: }
duke@1:
duke@1: /**
duke@1: * Return whether the class has a readObject method
duke@1: */
duke@1: final boolean hasReadObject() {
duke@1: return readObjectMethod != null;
duke@1: }
duke@1:
duke@1: /*
duke@1: * Return whether the class has a writeObject method
duke@1: */
duke@1: final boolean hasWriteObject() {
duke@1: return writeObjectMethod != null ;
duke@1: }
duke@1:
duke@1: /**
duke@1: * Returns when or not this class should be custom
duke@1: * marshaled (use chunking). This should happen if
duke@1: * it is Externalizable OR if it or
duke@1: * any of its superclasses has a writeObject method,
duke@1: */
duke@1: final boolean isCustomMarshaled() {
duke@1: return (hasWriteObject() || isExternalizable())
duke@1: || (superclass != null && superclass.isCustomMarshaled());
duke@1: }
duke@1:
duke@1: /*
duke@1: * Return true if all instances of 'this' Externalizable class
duke@1: * are written in block-data mode from the stream that 'this' was read
duke@1: * from.
duke@1: *
duke@1: * In JDK 1.1, all Externalizable instances are not written
duke@1: * in block-data mode.
duke@1: * In JDK 1.2, all Externalizable instances, by default, are written
duke@1: * in block-data mode and the Externalizable instance is terminated with
duke@1: * tag TC_ENDBLOCKDATA. Change enabled the ability to skip Externalizable
duke@1: * instances.
duke@1: *
duke@1: * IMPLEMENTATION NOTE:
duke@1: * This should have been a mode maintained per stream; however,
duke@1: * for compatibility reasons, it was only possible to record
duke@1: * this change per class. All Externalizable classes within
duke@1: * a given stream should either have this mode enabled or
duke@1: * disabled. This is enforced by not allowing the PROTOCOL_VERSION
duke@1: * of a stream to he changed after any objects have been written.
duke@1: *
duke@1: * @see ObjectOutputStream#useProtocolVersion
duke@1: * @see ObjectStreamConstants#PROTOCOL_VERSION_1
duke@1: * @see ObjectStreamConstants#PROTOCOL_VERSION_2
duke@1: *
duke@1: * @since JDK 1.2
duke@1: */
duke@1: boolean hasExternalizableBlockDataMode() {
duke@1: return hasExternalizableBlockData;
duke@1: }
duke@1:
duke@1: /**
duke@1: * Creates a new instance of the represented class. If the class is
duke@1: * externalizable, invokes its public no-arg constructor; otherwise, if the
duke@1: * class is serializable, invokes the no-arg constructor of the first
duke@1: * non-serializable superclass. Throws UnsupportedOperationException if
duke@1: * this class descriptor is not associated with a class, if the associated
duke@1: * class is non-serializable or if the appropriate no-arg constructor is
duke@1: * inaccessible/unavailable.
duke@1: */
duke@1: Object newInstance()
duke@1: throws InstantiationException, InvocationTargetException,
duke@1: UnsupportedOperationException
duke@1: {
duke@1: if (cons != null) {
duke@1: try {
duke@1: return cons.newInstance(new Object[0]);
duke@1: } catch (IllegalAccessException ex) {
duke@1: // should not occur, as access checks have been suppressed
duke@1: InternalError ie = new InternalError();
duke@1: ie.initCause( ex ) ;
duke@1: throw ie ;
duke@1: }
duke@1: } else {
duke@1: throw new UnsupportedOperationException();
duke@1: }
duke@1: }
duke@1:
duke@1: /**
duke@1: * Returns public no-arg constructor of given class, or null if none found.
duke@1: * Access checks are disabled on the returned constructor (if any), since
duke@1: * the defining class may still be non-public.
duke@1: */
duke@1: private static Constructor getExternalizableConstructor(Class cl) {
duke@1: try {
duke@1: Constructor cons = cl.getDeclaredConstructor(new Class[0]);
duke@1: cons.setAccessible(true);
duke@1: return ((cons.getModifiers() & Modifier.PUBLIC) != 0) ?
duke@1: cons : null;
duke@1: } catch (NoSuchMethodException ex) {
duke@1: return null;
duke@1: }
duke@1: }
duke@1:
duke@1: /**
duke@1: * Returns subclass-accessible no-arg constructor of first non-serializable
duke@1: * superclass, or null if none found. Access checks are disabled on the
duke@1: * returned constructor (if any).
duke@1: */
duke@1: private static Constructor getSerializableConstructor(Class cl) {
duke@1: Class initCl = cl;
duke@1: while (Serializable.class.isAssignableFrom(initCl)) {
duke@1: if ((initCl = initCl.getSuperclass()) == null) {
duke@1: return null;
duke@1: }
duke@1: }
duke@1: try {
duke@1: Constructor cons = initCl.getDeclaredConstructor(new Class[0]);
duke@1: int mods = cons.getModifiers();
duke@1: if ((mods & Modifier.PRIVATE) != 0 ||
duke@1: ((mods & (Modifier.PUBLIC | Modifier.PROTECTED)) == 0 &&
duke@1: !packageEquals(cl, initCl)))
duke@1: {
duke@1: return null;
duke@1: }
duke@1: cons = bridge.newConstructorForSerialization(cl, cons);
duke@1: cons.setAccessible(true);
duke@1: return cons;
duke@1: } catch (NoSuchMethodException ex) {
duke@1: return null;
duke@1: }
duke@1: }
duke@1:
duke@1: /*
duke@1: * Return the ObjectStreamClass of the local class this one is based on.
duke@1: */
duke@1: final ObjectStreamClass localClassDescriptor() {
duke@1: return localClassDesc;
duke@1: }
duke@1:
duke@1: /*
duke@1: * Get the Serializability of the class.
duke@1: */
duke@1: boolean isSerializable() {
duke@1: return serializable;
duke@1: }
duke@1:
duke@1: /*
duke@1: * Get the externalizability of the class.
duke@1: */
duke@1: boolean isExternalizable() {
duke@1: return externalizable;
duke@1: }
duke@1:
duke@1: boolean isNonSerializable() {
duke@1: return ! (externalizable || serializable);
duke@1: }
duke@1:
duke@1: /*
duke@1: * Calculate the size of the array needed to store primitive data and the
duke@1: * number of object references to read when reading from the input
duke@1: * stream.
duke@1: */
duke@1: private void computeFieldInfo() {
duke@1: primBytes = 0;
duke@1: objFields = 0;
duke@1:
duke@1: for (int i = 0; i < fields.length; i++ ) {
duke@1: switch (fields[i].getTypeCode()) {
duke@1: case 'B':
duke@1: case 'Z':
duke@1: primBytes += 1;
duke@1: break;
duke@1: case 'C':
duke@1: case 'S':
duke@1: primBytes += 2;
duke@1: break;
duke@1:
duke@1: case 'I':
duke@1: case 'F':
duke@1: primBytes += 4;
duke@1: break;
duke@1: case 'J':
duke@1: case 'D' :
duke@1: primBytes += 8;
duke@1: break;
duke@1:
duke@1: case 'L':
duke@1: case '[':
duke@1: objFields += 1;
duke@1: break;
duke@1: }
duke@1: }
duke@1: }
duke@1:
duke@1: private static void msg( String str )
duke@1: {
duke@1: System.out.println( str ) ;
duke@1: }
duke@1:
duke@1: /* JDK 1.5 has introduced some new modifier bits (such as SYNTHETIC)
duke@1: * that can affect the SVUID computation (see bug 4897937). These bits
duke@1: * must be ignored, as otherwise interoperability with ORBs in earlier
duke@1: * JDK versions can be compromised. I am adding these masks for this
duke@1: * purpose as discussed in the CCC for this bug (see http://ccc.sfbay/4897937).
duke@1: */
duke@1:
duke@1: public static final int CLASS_MASK = Modifier.PUBLIC | Modifier.FINAL |
duke@1: Modifier.INTERFACE | Modifier.ABSTRACT ;
duke@1: public static final int FIELD_MASK = Modifier.PUBLIC | Modifier.PRIVATE |
duke@1: Modifier.PROTECTED | Modifier.STATIC | Modifier.FINAL |
duke@1: Modifier.TRANSIENT | Modifier.VOLATILE ;
duke@1: public static final int METHOD_MASK = Modifier.PUBLIC | Modifier.PRIVATE |
duke@1: Modifier.PROTECTED | Modifier.STATIC | Modifier.FINAL |
duke@1: Modifier.SYNCHRONIZED | Modifier.NATIVE | Modifier.ABSTRACT |
duke@1: Modifier.STRICT ;
duke@1:
duke@1: /*
duke@1: * Compute a hash for the specified class. Incrementally add
duke@1: * items to the hash accumulating in the digest stream.
duke@1: * Fold the hash into a long. Use the SHA secure hash function.
duke@1: */
duke@1: private static long _computeSerialVersionUID(Class cl) {
duke@1: if (DEBUG_SVUID)
duke@1: msg( "Computing SerialVersionUID for " + cl ) ;
duke@1: ByteArrayOutputStream devnull = new ByteArrayOutputStream(512);
duke@1:
duke@1: long h = 0;
duke@1: try {
duke@1: MessageDigest md = MessageDigest.getInstance("SHA");
duke@1: DigestOutputStream mdo = new DigestOutputStream(devnull, md);
duke@1: DataOutputStream data = new DataOutputStream(mdo);
duke@1:
duke@1: if (DEBUG_SVUID)
duke@1: msg( "\twriteUTF( \"" + cl.getName() + "\" )" ) ;
duke@1: data.writeUTF(cl.getName());
duke@1:
duke@1: int classaccess = cl.getModifiers();
duke@1: classaccess &= (Modifier.PUBLIC | Modifier.FINAL |
duke@1: Modifier.INTERFACE | Modifier.ABSTRACT);
duke@1:
duke@1: /* Workaround for javac bug that only set ABSTRACT for
duke@1: * interfaces if the interface had some methods.
duke@1: * The ABSTRACT bit reflects that the number of methods > 0.
duke@1: * This is required so correct hashes can be computed
duke@1: * for existing class files.
duke@1: * Previously this hack was previously present in the VM.
duke@1: */
duke@1: Method[] method = cl.getDeclaredMethods();
duke@1: if ((classaccess & Modifier.INTERFACE) != 0) {
duke@1: classaccess &= (~Modifier.ABSTRACT);
duke@1: if (method.length > 0) {
duke@1: classaccess |= Modifier.ABSTRACT;
duke@1: }
duke@1: }
duke@1:
duke@1: // Mask out any post-1.4 attributes
duke@1: classaccess &= CLASS_MASK ;
duke@1:
duke@1: if (DEBUG_SVUID)
duke@1: msg( "\twriteInt( " + classaccess + " ) " ) ;
duke@1: data.writeInt(classaccess);
duke@1:
duke@1: /*
duke@1: * Get the list of interfaces supported,
duke@1: * Accumulate their names their names in Lexical order
duke@1: * and add them to the hash
duke@1: */
duke@1: if (!cl.isArray()) {
duke@1: /* In 1.2fcs, getInterfaces() was modified to return
duke@1: * {java.lang.Cloneable, java.io.Serializable} when
duke@1: * called on array classes. These values would upset
duke@1: * the computation of the hash, so we explicitly omit
duke@1: * them from its computation.
duke@1: */
duke@1:
duke@1: Class interfaces[] = cl.getInterfaces();
duke@1: Arrays.sort(interfaces, compareClassByName);
duke@1:
duke@1: for (int i = 0; i < interfaces.length; i++) {
duke@1: if (DEBUG_SVUID)
duke@1: msg( "\twriteUTF( \"" + interfaces[i].getName() + "\" ) " ) ;
duke@1: data.writeUTF(interfaces[i].getName());
duke@1: }
duke@1: }
duke@1:
duke@1: /* Sort the field names to get a deterministic order */
duke@1: Field[] field = cl.getDeclaredFields();
duke@1: Arrays.sort(field, compareMemberByName);
duke@1:
duke@1: for (int i = 0; i < field.length; i++) {
duke@1: Field f = field[i];
duke@1:
duke@1: /* Include in the hash all fields except those that are
duke@1: * private transient and private static.
duke@1: */
duke@1: int m = f.getModifiers();
duke@1: if (Modifier.isPrivate(m) &&
duke@1: (Modifier.isTransient(m) || Modifier.isStatic(m)))
duke@1: continue;
duke@1:
duke@1: if (DEBUG_SVUID)
duke@1: msg( "\twriteUTF( \"" + f.getName() + "\" ) " ) ;
duke@1: data.writeUTF(f.getName());
duke@1:
duke@1: // Mask out any post-1.4 bits
duke@1: m &= FIELD_MASK ;
duke@1:
duke@1: if (DEBUG_SVUID)
duke@1: msg( "\twriteInt( " + m + " ) " ) ;
duke@1: data.writeInt(m);
duke@1:
duke@1: if (DEBUG_SVUID)
duke@1: msg( "\twriteUTF( \"" + getSignature(f.getType()) + "\" ) " ) ;
duke@1: data.writeUTF(getSignature(f.getType()));
duke@1: }
duke@1:
duke@1: if (hasStaticInitializer(cl)) {
duke@1: if (DEBUG_SVUID)
duke@1: msg( "\twriteUTF( \"