| /* ObjectStreamClass.java -- Class used to write class information | |
| about serialized objects. | |
| Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. | |
| This file is part of GNU Classpath. | |
| GNU Classpath is free software; you can redistribute it and/or modify | |
| it under the terms of the GNU General Public License as published by | |
| the Free Software Foundation; either version 2, or (at your option) | |
| any later version. | |
| GNU Classpath is distributed in the hope that it will be useful, but | |
| WITHOUT ANY WARRANTY; without even the implied warranty of | |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| General Public License for more details. | |
| You should have received a copy of the GNU General Public License | |
| along with GNU Classpath; see the file COPYING. If not, write to the | |
| Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA | |
| 02111-1307 USA. | |
| As a special exception, if you link this library with other files to | |
| produce an executable, this library does not by itself cause the | |
| resulting executable to be covered by the GNU General Public License. | |
| This exception does not however invalidate any other reasons why the | |
| executable file might be covered by the GNU General Public License. */ | |
| package java.io; | |
| import java.lang.reflect.Constructor; | |
| import java.lang.reflect.Field; | |
| import java.lang.reflect.Member; | |
| import java.lang.reflect.Method; | |
| import java.lang.reflect.Modifier; | |
| import java.security.DigestOutputStream; | |
| import java.security.MessageDigest; | |
| import java.security.NoSuchAlgorithmException; | |
| import java.security.Security; | |
| import java.util.Arrays; | |
| import java.util.Comparator; | |
| import java.util.Hashtable; | |
| import java.util.Vector; | |
| import gnu.java.io.NullOutputStream; | |
| import gnu.java.lang.reflect.TypeSignature; | |
| import gnu.java.security.provider.Gnu; | |
| public class ObjectStreamClass implements Serializable | |
| { | |
| /** | |
| Returns the <code>ObjectStreamClass</code> for <code>cl</code>. | |
| If <code>cl</code> is null, or is not <code>Serializable</code>, | |
| null is returned. <code>ObjectStreamClass</code>'s are memorized; | |
| later calls to this method with the same class will return the | |
| same <code>ObjectStreamClass</code> object and no recalculation | |
| will be done. | |
| @see java.io.Serializable | |
| */ | |
| public static ObjectStreamClass lookup (Class cl) | |
| { | |
| if (cl == null) | |
| return null; | |
| if (! (Serializable.class).isAssignableFrom (cl)) | |
| return null; | |
| ObjectStreamClass osc = (ObjectStreamClass)classLookupTable.get (cl); | |
| if (osc != null) | |
| return osc; | |
| else | |
| { | |
| osc = new ObjectStreamClass (cl); | |
| classLookupTable.put (cl, osc); | |
| return osc; | |
| } | |
| } | |
| /** | |
| Returns the name of the class that this | |
| <code>ObjectStreamClass</code> represents. | |
| */ | |
| public String getName () | |
| { | |
| return name; | |
| } | |
| /** | |
| Returns the class that this <code>ObjectStreamClass</code> | |
| represents. Null could be returned if this | |
| <code>ObjectStreamClass</code> was read from an | |
| <code>ObjectInputStream</code> and the class it represents cannot | |
| be found or loaded. | |
| @see java.io.ObjectInputStream | |
| */ | |
| public Class forClass () | |
| { | |
| return clazz; | |
| } | |
| /** | |
| Returns the serial version stream-unique identifier for the class | |
| represented by this <code>ObjectStreamClass</code>. This SUID is | |
| either defined by the class as <code>static final long | |
| serialVersionUID</code> or is calculated as specified in | |
| Javasoft's "Object Serialization Specification" XXX: add reference | |
| */ | |
| public long getSerialVersionUID () | |
| { | |
| return uid; | |
| } | |
| // Returns the serializable (non-static and non-transient) Fields | |
| // of the class represented by this ObjectStreamClass. The Fields | |
| // are sorted by name. | |
| // XXX doc | |
| public ObjectStreamField[] getFields () | |
| { | |
| ObjectStreamField[] copy = new ObjectStreamField[ fields.length ]; | |
| System.arraycopy (fields, 0, copy, 0, fields.length); | |
| return copy; | |
| } | |
| // XXX doc | |
| // Can't do binary search since fields is sorted by name and | |
| // primitiveness. | |
| public ObjectStreamField getField (String name) | |
| { | |
| for (int i=0; i < fields.length; i++) | |
| if (fields[i].getName ().equals (name)) | |
| return fields[i]; | |
| return null; | |
| } | |
| /** | |
| Returns a textual representation of this | |
| <code>ObjectStreamClass</code> object including the name of the | |
| class it represents as well as that class's serial version | |
| stream-unique identifier. | |
| @see getSerialVersionUID () | |
| @see getName () | |
| */ | |
| public String toString () | |
| { | |
| return "java.io.ObjectStreamClass< " + name + ", " + uid + " >"; | |
| } | |
| // Returns true iff the class that this ObjectStreamClass represents | |
| // has the following method: | |
| // | |
| // private void writeObject (ObjectOutputStream) | |
| // | |
| // This method is used by the class to override default | |
| // serialization behavior. | |
| boolean hasWriteMethod () | |
| { | |
| return (flags & ObjectStreamConstants.SC_WRITE_METHOD) != 0; | |
| } | |
| // Returns true iff the class that this ObjectStreamClass represents | |
| // implements Serializable but does *not* implement Externalizable. | |
| boolean isSerializable () | |
| { | |
| return (flags & ObjectStreamConstants.SC_SERIALIZABLE) != 0; | |
| } | |
| // Returns true iff the class that this ObjectStreamClass represents | |
| // implements Externalizable. | |
| boolean isExternalizable () | |
| { | |
| return (flags & ObjectStreamConstants.SC_EXTERNALIZABLE) != 0; | |
| } | |
| // Returns the <code>ObjectStreamClass</code> that represents the | |
| // class that is the superclass of the class this | |
| // <code>ObjectStreamClass</code> represents. If the superclass is | |
| // not Serializable, null is returned. | |
| ObjectStreamClass getSuper () | |
| { | |
| return superClass; | |
| } | |
| // returns an array of ObjectStreamClasses that represent the super | |
| // classes of CLAZZ and CLAZZ itself in order from most super to | |
| // CLAZZ. ObjectStreamClass[0] is the highest superclass of CLAZZ | |
| // that is serializable. | |
| static ObjectStreamClass[] getObjectStreamClasses (Class clazz) | |
| { | |
| ObjectStreamClass osc = ObjectStreamClass.lookup (clazz); | |
| ObjectStreamClass[] ret_val; | |
| if (osc == null) | |
| return new ObjectStreamClass[0]; | |
| else | |
| { | |
| Vector oscs = new Vector (); | |
| while (osc != null) | |
| { | |
| oscs.addElement (osc); | |
| osc = osc.getSuper (); | |
| } | |
| int count = oscs.size (); | |
| ObjectStreamClass[] sorted_oscs = new ObjectStreamClass[ count ]; | |
| for (int i = count - 1; i >= 0; i--) | |
| sorted_oscs[ count - i - 1 ] = (ObjectStreamClass)oscs.elementAt (i); | |
| return sorted_oscs; | |
| } | |
| } | |
| // Returns an integer that consists of bit-flags that indicate | |
| // properties of the class represented by this ObjectStreamClass. | |
| // The bit-flags that could be present are those defined in | |
| // ObjectStreamConstants that begin with `SC_' | |
| int getFlags () | |
| { | |
| return flags; | |
| } | |
| ObjectStreamClass (String name, long uid, byte flags, | |
| ObjectStreamField[] fields) | |
| { | |
| this.name = name; | |
| this.uid = uid; | |
| this.flags = flags; | |
| this.fields = fields; | |
| } | |
| void setClass (Class cl) throws InvalidClassException | |
| { | |
| this.clazz = cl; | |
| long class_uid = getClassUID (cl); | |
| if (uid == 0) | |
| { | |
| uid = class_uid; | |
| return; | |
| } | |
| // Check that the actual UID of the resolved class matches the UID from | |
| // the stream. | |
| if (uid != class_uid) | |
| { | |
| String msg = cl + | |
| ": Local class not compatible: stream serialVersionUID=" | |
| + uid + ", local serialVersionUID=" + class_uid; | |
| throw new InvalidClassException (msg); | |
| } | |
| } | |
| void setSuperclass (ObjectStreamClass osc) | |
| { | |
| superClass = osc; | |
| } | |
| void calculateOffsets () | |
| { | |
| int i; | |
| ObjectStreamField field; | |
| primFieldSize = 0; | |
| int fcount = fields.length; | |
| for (i = 0; i < fcount; ++ i) | |
| { | |
| field = fields[i]; | |
| if (! field.isPrimitive ()) | |
| break; | |
| field.setOffset (primFieldSize); | |
| switch (field.getTypeCode ()) | |
| { | |
| case 'B': | |
| case 'Z': | |
| ++ primFieldSize; | |
| break; | |
| case 'C': | |
| case 'S': | |
| primFieldSize += 2; | |
| break; | |
| case 'I': | |
| case 'F': | |
| primFieldSize += 4; | |
| break; | |
| case 'D': | |
| case 'J': | |
| primFieldSize += 8; | |
| break; | |
| } | |
| } | |
| for (objectFieldCount = 0; i < fcount; ++ i) | |
| fields[i].setOffset (objectFieldCount++); | |
| } | |
| private ObjectStreamClass (Class cl) | |
| { | |
| uid = 0; | |
| flags = 0; | |
| clazz = cl; | |
| name = cl.getName (); | |
| setFlags (cl); | |
| setFields (cl); | |
| uid = getClassUID (cl); | |
| superClass = lookup (cl.getSuperclass ()); | |
| } | |
| // Sets bits in flags according to features of CL. | |
| private void setFlags (Class cl) | |
| { | |
| if ((java.io.Externalizable.class).isAssignableFrom (cl)) | |
| flags |= ObjectStreamConstants.SC_EXTERNALIZABLE; | |
| else if ((java.io.Serializable.class).isAssignableFrom (cl)) | |
| // only set this bit if CL is NOT Externalizable | |
| flags |= ObjectStreamConstants.SC_SERIALIZABLE; | |
| try | |
| { | |
| Method writeMethod = cl.getDeclaredMethod ("writeObject", | |
| writeMethodArgTypes); | |
| int modifiers = writeMethod.getModifiers (); | |
| if (writeMethod.getReturnType () == Void.TYPE | |
| && Modifier.isPrivate (modifiers) | |
| && !Modifier.isStatic (modifiers)) | |
| flags |= ObjectStreamConstants.SC_WRITE_METHOD; | |
| } | |
| catch (NoSuchMethodException oh_well) | |
| {} | |
| } | |
| // Sets fields to be a sorted array of the serializable fields of | |
| // clazz. | |
| private void setFields (Class cl) | |
| { | |
| if (! isSerializable () || isExternalizable ()) | |
| { | |
| fields = NO_FIELDS; | |
| return; | |
| } | |
| try | |
| { | |
| Field serialPersistentFields | |
| = cl.getDeclaredField ("serialPersistentFields"); | |
| int modifiers = serialPersistentFields.getModifiers (); | |
| if (Modifier.isStatic (modifiers) | |
| && Modifier.isFinal (modifiers) | |
| && Modifier.isPrivate (modifiers)) | |
| { | |
| fields = getSerialPersistentFields (cl); | |
| Arrays.sort (fields); | |
| calculateOffsets (); | |
| return; | |
| } | |
| } | |
| catch (NoSuchFieldException ignore) | |
| {} | |
| int num_good_fields = 0; | |
| Field[] all_fields = cl.getDeclaredFields (); | |
| int modifiers; | |
| // set non-serializable fields to null in all_fields | |
| for (int i=0; i < all_fields.length; i++) | |
| { | |
| modifiers = all_fields[i].getModifiers (); | |
| if (Modifier.isTransient (modifiers) | |
| || Modifier.isStatic (modifiers)) | |
| all_fields[i] = null; | |
| else | |
| num_good_fields++; | |
| } | |
| // make a copy of serializable (non-null) fields | |
| fields = new ObjectStreamField[ num_good_fields ]; | |
| for (int from=0, to=0; from < all_fields.length; from++) | |
| if (all_fields[from] != null) | |
| { | |
| Field f = all_fields[from]; | |
| fields[to] = new ObjectStreamField (f.getName (), f.getType ()); | |
| to++; | |
| } | |
| Arrays.sort (fields); | |
| calculateOffsets (); | |
| } | |
| // Returns the serial version UID defined by class, or if that | |
| // isn't present, calculates value of serial version UID. | |
| private long getClassUID (Class cl) | |
| { | |
| try | |
| { | |
| Field suid = cl.getDeclaredField ("serialVersionUID"); | |
| int modifiers = suid.getModifiers (); | |
| if (Modifier.isStatic (modifiers) && Modifier.isFinal (modifiers)) | |
| return suid.getLong (null); | |
| } | |
| catch (NoSuchFieldException ignore) | |
| { | |
| } | |
| catch (IllegalAccessException ignore) | |
| { | |
| } | |
| // cl didn't define serialVersionUID, so we have to compute it | |
| try | |
| { | |
| MessageDigest md = null; | |
| DigestOutputStream digest_out = null; | |
| DataOutputStream data_out = null; | |
| try | |
| { | |
| md = MessageDigest.getInstance ("SHA"); | |
| } | |
| catch (NoSuchAlgorithmException e) | |
| { | |
| // If a provider already provides SHA, use it; otherwise, use this. | |
| Gnu gnuProvider = new Gnu(); | |
| Security.addProvider(gnuProvider); | |
| md = MessageDigest.getInstance ("SHA"); | |
| } | |
| digest_out = new DigestOutputStream (nullOutputStream, md); | |
| data_out = new DataOutputStream (digest_out); | |
| data_out.writeUTF (cl.getName ()); | |
| int modifiers = cl.getModifiers (); | |
| // just look at interesting bits | |
| modifiers = modifiers & (Modifier.ABSTRACT | Modifier.FINAL | |
| | Modifier.INTERFACE | Modifier.PUBLIC); | |
| data_out.writeInt (modifiers); | |
| // Pretend that an array has no interfaces, because when array | |
| // serialization was defined (JDK 1.1), arrays didn't have it. | |
| if (! cl.isArray ()) | |
| { | |
| Class[] interfaces = cl.getInterfaces (); | |
| Arrays.sort (interfaces, interfaceComparator); | |
| for (int i=0; i < interfaces.length; i++) | |
| data_out.writeUTF (interfaces[i].getName ()); | |
| } | |
| Field field; | |
| Field[] fields = cl.getDeclaredFields (); | |
| Arrays.sort (fields, memberComparator); | |
| for (int i=0; i < fields.length; i++) | |
| { | |
| field = fields[i]; | |
| modifiers = field.getModifiers (); | |
| if (Modifier.isPrivate (modifiers) | |
| && (Modifier.isStatic (modifiers) | |
| || Modifier.isTransient (modifiers))) | |
| continue; | |
| data_out.writeUTF (field.getName ()); | |
| data_out.writeInt (modifiers); | |
| data_out.writeUTF (TypeSignature.getEncodingOfClass (field.getType ())); | |
| } | |
| // write class initializer method if present | |
| boolean has_init; | |
| try | |
| { | |
| has_init = hasClassInitializer (cl); | |
| } | |
| catch (NoSuchMethodError e) | |
| { | |
| has_init = false; | |
| } | |
| if (has_init) | |
| { | |
| data_out.writeUTF ("<clinit>"); | |
| data_out.writeInt (Modifier.STATIC); | |
| data_out.writeUTF ("()V"); | |
| } | |
| Constructor constructor; | |
| Constructor[] constructors = cl.getDeclaredConstructors (); | |
| Arrays.sort (constructors, memberComparator); | |
| for (int i=0; i < constructors.length; i++) | |
| { | |
| constructor = constructors[i]; | |
| modifiers = constructor.getModifiers (); | |
| if (Modifier.isPrivate (modifiers)) | |
| continue; | |
| data_out.writeUTF ("<init>"); | |
| data_out.writeInt (modifiers); | |
| // the replacement of '/' with '.' was needed to make computed | |
| // SUID's agree with those computed by JDK | |
| data_out.writeUTF ( | |
| TypeSignature.getEncodingOfConstructor (constructor).replace ('/','.')); | |
| } | |
| Method method; | |
| Method[] methods = cl.getDeclaredMethods (); | |
| Arrays.sort (methods, memberComparator); | |
| for (int i=0; i < methods.length; i++) | |
| { | |
| method = methods[i]; | |
| modifiers = method.getModifiers (); | |
| if (Modifier.isPrivate (modifiers)) | |
| continue; | |
| data_out.writeUTF (method.getName ()); | |
| data_out.writeInt (modifiers); | |
| // the replacement of '/' with '.' was needed to make computed | |
| // SUID's agree with those computed by JDK | |
| data_out.writeUTF ( | |
| TypeSignature.getEncodingOfMethod (method).replace ('/', '.')); | |
| } | |
| data_out.close (); | |
| byte[] sha = md.digest (); | |
| long result = 0; | |
| int len = sha.length < 8 ? sha.length : 8; | |
| for (int i=0; i < len; i++) | |
| result += (long)(sha[i] & 0xFF) << (8 * i); | |
| return result; | |
| } | |
| catch (NoSuchAlgorithmException e) | |
| { | |
| throw new RuntimeException ("The SHA algorithm was not found to use in computing the Serial Version UID for class " | |
| + cl.getName ()); | |
| } | |
| catch (IOException ioe) | |
| { | |
| throw new RuntimeException (ioe.getMessage ()); | |
| } | |
| } | |
| // Returns the value of CLAZZ's private static final field named | |
| // `serialPersistentFields'. | |
| private ObjectStreamField[] getSerialPersistentFields (Class clazz) | |
| { | |
| ObjectStreamField[] o = null; | |
| try | |
| { | |
| // Use getDeclaredField rather than getField for the same reason | |
| // as above in getDefinedSUID. | |
| Field f = clazz.getDeclaredField ("getSerialPersistentFields"); | |
| o = (ObjectStreamField[])f.get (null); | |
| } | |
| catch (java.lang.NoSuchFieldException e) | |
| { | |
| } | |
| catch (java.lang.IllegalAccessException e) | |
| { | |
| } | |
| return o; | |
| } | |
| // Returns true if CLAZZ has a static class initializer | |
| // (a.k.a. <clinit>). | |
| // | |
| // A NoSuchMethodError is raised if CLAZZ has no such method. | |
| private static boolean hasClassInitializer (Class clazz) | |
| throws java.lang.NoSuchMethodError | |
| { | |
| Method m = null; | |
| try | |
| { | |
| Class classArgs[] = {}; | |
| m = clazz.getDeclaredMethod ("<clinit>", classArgs); | |
| } | |
| catch (java.lang.NoSuchMethodException e) | |
| { | |
| throw new java.lang.NoSuchMethodError (); | |
| } | |
| return m != null; | |
| } | |
| public static final ObjectStreamField[] NO_FIELDS = {}; | |
| private static Hashtable classLookupTable = new Hashtable (); | |
| private static final NullOutputStream nullOutputStream = new NullOutputStream (); | |
| private static final Comparator interfaceComparator = new InterfaceComparator (); | |
| private static final Comparator memberComparator = new MemberComparator (); | |
| private static final | |
| Class[] writeMethodArgTypes = { java.io.ObjectOutputStream.class }; | |
| private ObjectStreamClass superClass; | |
| private Class clazz; | |
| private String name; | |
| private long uid; | |
| private byte flags; | |
| // this field is package protected so that ObjectInputStream and | |
| // ObjectOutputStream can access it directly | |
| ObjectStreamField[] fields; | |
| // these are accessed by ObjectIn/OutputStream | |
| int primFieldSize = -1; // -1 if not yet calculated | |
| int objectFieldCount; | |
| // This is probably not necessary because this class is special cased already | |
| // but it will avoid showing up as a discrepancy when comparing SUIDs. | |
| private static final long serialVersionUID = -6120832682080437368L; | |
| } | |
| // interfaces are compared only by name | |
| class InterfaceComparator implements Comparator | |
| { | |
| public int compare (Object o1, Object o2) | |
| { | |
| return ((Class)o1).getName ().compareTo (((Class)o2).getName ()); | |
| } | |
| } | |
| // Members (Methods and Constructors) are compared first by name, | |
| // conflicts are resolved by comparing type signatures | |
| class MemberComparator implements Comparator | |
| { | |
| public int compare (Object o1, Object o2) | |
| { | |
| Member m1 = (Member)o1; | |
| Member m2 = (Member)o2; | |
| int comp = m1.getName ().compareTo (m2.getName ()); | |
| if (comp == 0) | |
| return TypeSignature.getEncodingOfMember (m1). | |
| compareTo (TypeSignature.getEncodingOfMember (m2)); | |
| else | |
| return comp; | |
| } | |
| } |