001    /* ObjectStreamClass.java -- Class used to write class information
002       about serialized objects.
003       Copyright (C) 1998, 1999, 2000, 2001, 2003, 2005  Free Software Foundation, Inc.
004    
005    This file is part of GNU Classpath.
006    
007    GNU Classpath is free software; you can redistribute it and/or modify
008    it under the terms of the GNU General Public License as published by
009    the Free Software Foundation; either version 2, or (at your option)
010    any later version.
011     
012    GNU Classpath is distributed in the hope that it will be useful, but
013    WITHOUT ANY WARRANTY; without even the implied warranty of
014    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015    General Public License for more details.
016    
017    You should have received a copy of the GNU General Public License
018    along with GNU Classpath; see the file COPYING.  If not, write to the
019    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
020    02110-1301 USA.
021    
022    Linking this library statically or dynamically with other modules is
023    making a combined work based on this library.  Thus, the terms and
024    conditions of the GNU General Public License cover the whole
025    combination.
026    
027    As a special exception, the copyright holders of this library give you
028    permission to link this library with independent modules to produce an
029    executable, regardless of the license terms of these independent
030    modules, and to copy and distribute the resulting executable under
031    terms of your choice, provided that you also meet, for each linked
032    independent module, the terms and conditions of the license of that
033    module.  An independent module is a module which is not derived from
034    or based on this library.  If you modify this library, you may extend
035    this exception to your version of the library, but you are not
036    obligated to do so.  If you do not wish to do so, delete this
037    exception statement from your version. */
038    
039    
040    package java.io;
041    
042    import gnu.java.io.NullOutputStream;
043    import gnu.java.lang.reflect.TypeSignature;
044    import gnu.java.security.action.SetAccessibleAction;
045    import gnu.java.security.provider.Gnu;
046    
047    import java.lang.reflect.Constructor;
048    import java.lang.reflect.Field;
049    import java.lang.reflect.Member;
050    import java.lang.reflect.Method;
051    import java.lang.reflect.Modifier;
052    import java.lang.reflect.Proxy;
053    import java.security.AccessController;
054    import java.security.DigestOutputStream;
055    import java.security.MessageDigest;
056    import java.security.NoSuchAlgorithmException;
057    import java.security.PrivilegedAction;
058    import java.security.Security;
059    import java.util.Arrays;
060    import java.util.Comparator;
061    import java.util.Hashtable;
062    
063    /**
064     * @author Tom Tromey (tromey@redhat.com)
065     * @author Jeroen Frijters (jeroen@frijters.net)
066     * @author Guilhem Lavaux (guilhem@kaffe.org)
067     * @author Michael Koch (konqueror@gmx.de)
068     * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
069     */
070    public class ObjectStreamClass implements Serializable
071    {
072      static final ObjectStreamField[] INVALID_FIELDS = new ObjectStreamField[0];
073    
074      /**
075       * Returns the <code>ObjectStreamClass</code> for <code>cl</code>.
076       * If <code>cl</code> is null, or is not <code>Serializable</code>,
077       * null is returned.  <code>ObjectStreamClass</code>'s are memorized;
078       * later calls to this method with the same class will return the
079       * same <code>ObjectStreamClass</code> object and no recalculation
080       * will be done.
081       *
082       * Warning: If this class contains an invalid serialPersistentField arrays
083       * lookup will not throw anything. However {@link #getFields()} will return
084       * an empty array and {@link java.io.ObjectOutputStream#writeObject} will throw an 
085       * {@link java.io.InvalidClassException}.
086       *
087       * @see java.io.Serializable
088       */
089      public static ObjectStreamClass lookup(Class<?> cl)
090      {
091        if (cl == null)
092          return null;
093        if (! (Serializable.class).isAssignableFrom(cl))
094          return null;
095    
096        return lookupForClassObject(cl);
097      }
098    
099      /**
100       * This lookup for internal use by ObjectOutputStream.  Suppose
101       * we have a java.lang.Class object C for class A, though A is not
102       * serializable, but it's okay to serialize C.
103       */
104      static ObjectStreamClass lookupForClassObject(Class cl)
105      {
106        if (cl == null)
107          return null;
108    
109        ObjectStreamClass osc = classLookupTable.get(cl);
110    
111        if (osc != null)
112          return osc;
113        else
114          {
115            osc = new ObjectStreamClass(cl);
116            classLookupTable.put(cl, osc);
117            return osc;
118          }
119      }
120    
121      /**
122       * Returns the name of the class that this
123       * <code>ObjectStreamClass</code> represents.
124       *
125       * @return the name of the class.
126       */
127      public String getName()
128      {
129        return name;
130      }
131    
132      /**
133       * Returns the class that this <code>ObjectStreamClass</code>
134       * represents.  Null could be returned if this
135       * <code>ObjectStreamClass</code> was read from an
136       * <code>ObjectInputStream</code> and the class it represents cannot
137       * be found or loaded.
138       *
139       * @see java.io.ObjectInputStream
140       */
141      public Class<?> forClass()
142      {
143        return clazz;
144      }
145    
146      /**
147       * Returns the serial version stream-unique identifier for the class
148       * represented by this <code>ObjectStreamClass</code>.  This SUID is
149       * either defined by the class as <code>static final long
150       * serialVersionUID</code> or is calculated as specified in
151       * Javasoft's "Object Serialization Specification" XXX: add reference
152       *
153       * @return the serial version UID.
154       */
155      public long getSerialVersionUID()
156      {
157        return uid;
158      }
159    
160      /**
161       * Returns the serializable (non-static and non-transient) Fields
162       * of the class represented by this ObjectStreamClass.  The Fields
163       * are sorted by name.
164       * If fields were obtained using serialPersistentFields and this array
165       * is faulty then the returned array of this method will be empty.
166       *
167       * @return the fields.
168       */
169      public ObjectStreamField[] getFields()
170      {
171        ObjectStreamField[] copy = new ObjectStreamField[ fields.length ];
172        System.arraycopy(fields, 0, copy, 0, fields.length);
173        return copy;
174      }
175    
176      // XXX doc
177      // Can't do binary search since fields is sorted by name and
178      // primitiveness.
179      public ObjectStreamField getField (String name)
180      {
181        for (int i = 0; i < fields.length; i++)
182          if (fields[i].getName().equals(name))
183            return fields[i];
184        return null;
185      }
186    
187      /**
188       * Returns a textual representation of this
189       * <code>ObjectStreamClass</code> object including the name of the
190       * class it represents as well as that class's serial version
191       * stream-unique identifier.
192       *
193       * @see #getSerialVersionUID()
194       * @see #getName()
195       */
196      public String toString()
197      {
198        return "java.io.ObjectStreamClass< " + name + ", " + uid + " >";
199      }
200    
201      // Returns true iff the class that this ObjectStreamClass represents
202      // has the following method:
203      //
204      // private void writeObject (ObjectOutputStream)
205      //
206      // This method is used by the class to override default
207      // serialization behavior.
208      boolean hasWriteMethod()
209      {
210        return (flags & ObjectStreamConstants.SC_WRITE_METHOD) != 0;
211      }
212    
213      // Returns true iff the class that this ObjectStreamClass represents
214      // implements Serializable but does *not* implement Externalizable.
215      boolean isSerializable()
216      {
217        return (flags & ObjectStreamConstants.SC_SERIALIZABLE) != 0;
218      }
219    
220    
221      // Returns true iff the class that this ObjectStreamClass represents
222      // implements Externalizable.
223      boolean isExternalizable()
224      {
225        return (flags & ObjectStreamConstants.SC_EXTERNALIZABLE) != 0;
226      }
227    
228      // Returns true iff the class that this ObjectStreamClass represents
229      // implements Externalizable.
230      boolean isEnum()
231      {
232        return (flags & ObjectStreamConstants.SC_ENUM) != 0;
233      }
234    
235      // Returns the <code>ObjectStreamClass</code> that represents the
236      // class that is the superclass of the class this
237      // <code>ObjectStreamClass</code> represents.  If the superclass is
238      // not Serializable, null is returned.
239      ObjectStreamClass getSuper()
240      {
241        return superClass;
242      }
243    
244      /**
245       * returns an array of ObjectStreamClasses that represent the super
246       * classes of the class represented by this and the class
247       * represented by this itself in order from most super to this.
248       * ObjectStreamClass[0] is the highest superclass of this that is
249       * serializable.
250       *
251       * The result of consecutive calls this hierarchy() will be the same
252       * array instance.
253       *
254       * @return an array of ObjectStreamClass representing the
255       * super-class hierarchy of serializable classes.
256       */
257      ObjectStreamClass[] hierarchy()
258      {
259        ObjectStreamClass[] result = hierarchy; 
260        if (result == null)
261            {
262            int d = 0; 
263      
264            for(ObjectStreamClass osc = this; osc != null; osc = osc.getSuper())
265              d++;
266      
267            result = new ObjectStreamClass[d];
268      
269            for (ObjectStreamClass osc = this; osc != null; osc = osc.getSuper())
270              {
271                result[--d] = osc;
272              }
273      
274            hierarchy = result; 
275          }
276        return result; 
277      }
278    
279      /**
280       * Cache for hierarchy() result.
281       */
282      private ObjectStreamClass[] hierarchy = null;
283    
284      // Returns an integer that consists of bit-flags that indicate
285      // properties of the class represented by this ObjectStreamClass.
286      // The bit-flags that could be present are those defined in
287      // ObjectStreamConstants that begin with `SC_'
288      int getFlags()
289      {
290        return flags;
291      }
292    
293    
294      ObjectStreamClass(String name, long uid, byte flags,
295                        ObjectStreamField[] fields)
296      {
297        this.name = name;
298        this.uid = uid;
299        this.flags = flags;
300        this.fields = fields;
301      }
302    
303      /**
304       * This method builds the internal description corresponding to a Java Class.
305       * As the constructor only assign a name to the current ObjectStreamClass instance,
306       * that method sets the serial UID, chose the fields which will be serialized,
307       * and compute the position of the fields in the serialized stream.
308       *
309       * @param cl The Java class which is used as a reference for building the descriptor.
310       * @param superClass The descriptor of the super class for this class descriptor.
311       * @throws InvalidClassException if an incompatibility between computed UID and
312       * already set UID is found.
313       */
314      void setClass(Class cl, ObjectStreamClass superClass) throws InvalidClassException
315      {hierarchy = null;
316        this.clazz = cl;
317    
318        cacheMethods();
319    
320        long class_uid = getClassUID(cl);
321        if (uid == 0)
322          uid = class_uid;
323        else
324          {
325            // Check that the actual UID of the resolved class matches the UID from 
326            // the stream. Mismatches for array classes are ignored.
327            if (!cl.isArray() && uid != class_uid)
328              {
329                String msg = cl + 
330                  ": Local class not compatible: stream serialVersionUID="
331                  + uid + ", local serialVersionUID=" + class_uid;
332                throw new InvalidClassException (msg);
333              }
334          }
335    
336        isProxyClass = clazz != null && Proxy.isProxyClass(clazz);
337        this.superClass = superClass;
338        calculateOffsets();
339        
340        try
341          {
342            ObjectStreamField[] exportedFields = getSerialPersistentFields (clazz);  
343    
344            if (exportedFields == null)
345              return;
346    
347            ObjectStreamField[] newFieldList = new ObjectStreamField[exportedFields.length + fields.length];
348            int i, j, k;
349    
350            /* We now check the import fields against the exported fields.
351             * There should not be contradiction (e.g. int x and String x)
352             * but extra virtual fields can be added to the class.
353             */
354    
355            Arrays.sort(exportedFields);
356    
357            i = 0; j = 0; k = 0;
358            while (i < fields.length && j < exportedFields.length)
359              {
360                int comp = fields[i].compareTo(exportedFields[j]);
361    
362                if (comp < 0)
363                  {
364                    newFieldList[k] = fields[i];
365                    fields[i].setPersistent(false);
366                    fields[i].setToSet(false);
367                    i++;
368                  }
369                else if (comp > 0)
370                  {
371                    /* field not found in imported fields. We add it
372                     * in the list of supported fields.
373                     */
374                    newFieldList[k] = exportedFields[j];
375                    newFieldList[k].setPersistent(true);
376                    newFieldList[k].setToSet(false);
377                    try
378                      {
379                        newFieldList[k].lookupField(clazz);
380                        newFieldList[k].checkFieldType();
381                      }
382                    catch (NoSuchFieldException _)
383                      {
384                      }
385                    j++;
386                  }
387                else
388                  {
389                    try
390                      {
391                        exportedFields[j].lookupField(clazz);
392                        exportedFields[j].checkFieldType();
393                      }
394                    catch (NoSuchFieldException _)
395                      {
396                      }
397    
398                    if (!fields[i].getType().equals(exportedFields[j].getType()))
399                      throw new InvalidClassException
400                        ("serialPersistentFields must be compatible with" +
401                         " imported fields (about " + fields[i].getName() + ")");
402                    newFieldList[k] = fields[i];
403                    fields[i].setPersistent(true);
404                    i++;
405                    j++;
406                  }
407                k++;
408              }
409    
410            if (i < fields.length)
411              for (;i<fields.length;i++,k++)
412                {
413                  fields[i].setPersistent(false);
414                  fields[i].setToSet(false);
415                  newFieldList[k] = fields[i];
416                }
417            else
418              if (j < exportedFields.length)
419                for (;j<exportedFields.length;j++,k++)
420                  {
421                    exportedFields[j].setPersistent(true);
422                    exportedFields[j].setToSet(false);
423                    newFieldList[k] = exportedFields[j];
424                  }
425            
426            fields = new ObjectStreamField[k];
427            System.arraycopy(newFieldList, 0, fields, 0, k);
428          }
429        catch (NoSuchFieldException ignore)
430          {
431            return;
432          }
433        catch (IllegalAccessException ignore)
434          {
435            return;
436          }
437      }
438    
439      void setSuperclass (ObjectStreamClass osc)
440      {
441        superClass = osc;
442        hierarchy = null;
443      }
444    
445      void calculateOffsets()
446      {
447        int i;
448        ObjectStreamField field;
449        primFieldSize = 0;
450        int fcount = fields.length;
451        for (i = 0; i < fcount; ++ i)
452          {
453            field = fields[i];
454    
455            if (! field.isPrimitive())
456              break;
457    
458            field.setOffset(primFieldSize);
459            switch (field.getTypeCode())
460              {
461              case 'B':
462              case 'Z':
463                ++ primFieldSize;
464                break;
465              case 'C':
466              case 'S':
467                primFieldSize += 2;
468                break;
469              case 'I':
470              case 'F':
471                primFieldSize += 4;
472                break;
473              case 'D':
474              case 'J':
475                primFieldSize += 8;
476                break;
477              }
478          }
479    
480        for (objectFieldCount = 0; i < fcount; ++ i)
481          fields[i].setOffset(objectFieldCount++);
482      }
483    
484      private Method findMethod(Method[] methods, String name, Class[] params,
485                                Class returnType, boolean mustBePrivate)
486      {
487    outer:
488        for (int i = 0; i < methods.length; i++)
489        {
490            final Method m = methods[i];
491            int mods = m.getModifiers();
492            if (Modifier.isStatic(mods)
493                || (mustBePrivate && !Modifier.isPrivate(mods)))
494            {
495                continue;
496            }
497    
498            if (m.getName().equals(name)
499               && m.getReturnType() == returnType)
500            {
501                Class[] mp = m.getParameterTypes();
502                if (mp.length == params.length)
503                {
504                    for (int j = 0; j < mp.length; j++)
505                    {
506                        if (mp[j] != params[j])
507                        {
508                            continue outer;
509                        }
510                    }
511                    AccessController.doPrivileged(new SetAccessibleAction(m));
512                    return m;
513                }
514            }
515        }
516        return null;
517      }
518    
519      private static boolean inSamePackage(Class c1, Class c2)
520      {
521        String name1 = c1.getName();
522        String name2 = c2.getName();
523    
524        int id1 = name1.lastIndexOf('.');
525        int id2 = name2.lastIndexOf('.');
526    
527        // Handle the default package
528        if (id1 == -1 || id2 == -1)
529          return id1 == id2;
530    
531        String package1 = name1.substring(0, id1);
532        String package2 = name2.substring(0, id2);
533    
534        return package1.equals(package2);
535      }
536    
537      final static Class[] noArgs = new Class[0];
538    
539      private static Method findAccessibleMethod(String name, Class from)
540      {
541        for (Class c = from; c != null; c = c.getSuperclass())
542          {
543            try
544              {
545                Method res = c.getDeclaredMethod(name, noArgs);
546                int mods = res.getModifiers();
547                
548                if (c == from  
549                    || Modifier.isProtected(mods)
550                    || Modifier.isPublic(mods)
551                    || (! Modifier.isPrivate(mods) && inSamePackage(c, from)))
552                  {
553                    AccessController.doPrivileged(new SetAccessibleAction(res));
554                    return res;
555                  }
556              }
557            catch (NoSuchMethodException e)
558              {
559              }
560          }
561    
562        return null;
563      }
564    
565      /**
566       * Helper routine to check if a class was loaded by boot or
567       * application class loader.  Classes for which this is not the case
568       * should not be cached since caching prevent class file garbage
569       * collection.
570       *
571       * @param cl a class
572       *
573       * @return true if cl was loaded by boot or application class loader,
574       *         false if cl was loaded by a user class loader.
575       */
576      private static boolean loadedByBootOrApplicationClassLoader(Class cl)
577      {
578        ClassLoader l = cl.getClassLoader();
579        return 
580          (   l == null                             /* boot loader */       ) 
581          || (l == ClassLoader.getSystemClassLoader() /* application loader */);
582      } 
583    
584      static Hashtable methodCache = new Hashtable(); 
585      
586      static final Class[] readObjectSignature  = { ObjectInputStream.class };
587      static final Class[] writeObjectSignature = { ObjectOutputStream.class };
588    
589      private void cacheMethods()
590      {
591        Class cl = forClass(); 
592        Method[] cached = (Method[]) methodCache.get(cl); 
593        if (cached == null)
594          {
595            cached = new Method[4];
596            Method[] methods = cl.getDeclaredMethods();
597            
598            cached[0] = findMethod(methods, "readObject",
599                                   readObjectSignature, 
600                                   Void.TYPE, true);
601            cached[1] = findMethod(methods, "writeObject",
602                                   writeObjectSignature, 
603                                   Void.TYPE, true);
604    
605            // readResolve and writeReplace can be in parent classes, as long as they
606            // are accessible from this class.
607            cached[2] = findAccessibleMethod("readResolve", cl);
608            cached[3] = findAccessibleMethod("writeReplace", cl);
609            
610            /* put in cache if classes not loaded by user class loader.
611             * For a user class loader, the cache may otherwise grow
612             * without limit.
613             */
614            if (loadedByBootOrApplicationClassLoader(cl))
615              methodCache.put(cl,cached);
616          }
617        readObjectMethod   = cached[0];
618        writeObjectMethod  = cached[1];
619        readResolveMethod  = cached[2];
620        writeReplaceMethod = cached[3];
621      }
622    
623      private ObjectStreamClass(Class cl)
624      {
625        uid = 0;
626        flags = 0;
627        isProxyClass = Proxy.isProxyClass(cl);
628    
629        clazz = cl;
630        cacheMethods();
631        name = cl.getName();
632        setFlags(cl);
633        setFields(cl);
634        // to those class nonserializable, its uid field is 0
635        if ( (Serializable.class).isAssignableFrom(cl) && !isProxyClass)
636          uid = getClassUID(cl);
637        superClass = lookup(cl.getSuperclass());
638      }
639    
640    
641      // Sets bits in flags according to features of CL.
642      private void setFlags(Class cl)
643      {
644        if ((java.io.Externalizable.class).isAssignableFrom(cl))
645          flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
646        else if ((java.io.Serializable.class).isAssignableFrom(cl))
647          // only set this bit if CL is NOT Externalizable
648          flags |= ObjectStreamConstants.SC_SERIALIZABLE;
649    
650        if (writeObjectMethod != null)
651          flags |= ObjectStreamConstants.SC_WRITE_METHOD;
652    
653        if (cl.isEnum() || cl == Enum.class)
654          flags |= ObjectStreamConstants.SC_ENUM;
655      }
656    
657    /* GCJ LOCAL */
658      // FIXME: This is a workaround for a fairly obscure bug that happens
659      // when reading a Proxy and then writing it back out again.  The
660      // result is that the ObjectStreamClass doesn't have its fields set,
661      // generating a NullPointerException.  Rather than this kludge we
662      // should probably fix the real bug, but it would require a fairly
663      // radical reorganization to do so.
664      final void ensureFieldsSet(Class cl)
665      {
666        if (! fieldsSet)
667          setFields(cl);
668      }
669    /* END GCJ LOCAL */
670    
671    
672      // Sets fields to be a sorted array of the serializable fields of
673      // clazz.
674      private void setFields(Class cl)
675      {
676    /* GCJ LOCAL */
677        fieldsSet = true;
678    /* END GCJ LOCAL */
679    
680        SetAccessibleAction setAccessible = new SetAccessibleAction();
681    
682        if (!isSerializable() || isExternalizable() || isEnum())
683          {
684            fields = NO_FIELDS;
685            return;
686          }
687    
688        try
689          {
690            final Field f =
691              cl.getDeclaredField("serialPersistentFields");
692            setAccessible.setMember(f);
693            AccessController.doPrivileged(setAccessible);
694            int modifiers = f.getModifiers();
695    
696            if (Modifier.isStatic(modifiers)
697                && Modifier.isFinal(modifiers)
698                && Modifier.isPrivate(modifiers))
699              {
700                fields = getSerialPersistentFields(cl);
701                if (fields != null)
702                  {
703                    ObjectStreamField[] fieldsName = new ObjectStreamField[fields.length];
704                    System.arraycopy(fields, 0, fieldsName, 0, fields.length);
705    
706                    Arrays.sort (fieldsName, new Comparator() {
707                            public int compare(Object o1, Object o2)
708                            {
709                              ObjectStreamField f1 = (ObjectStreamField)o1;
710                              ObjectStreamField f2 = (ObjectStreamField)o2;
711                                
712                              return f1.getName().compareTo(f2.getName());
713                            }
714                        });
715                    
716                    for (int i=1; i < fields.length; i++)
717                      {
718                        if (fieldsName[i-1].getName().equals(fieldsName[i].getName()))
719                            {
720                                fields = INVALID_FIELDS;
721                                return;
722                            }
723                      }
724    
725                    Arrays.sort (fields);
726                    // Retrieve field reference.
727                    for (int i=0; i < fields.length; i++)
728                      {
729                        try
730                          {
731                            fields[i].lookupField(cl);
732                          }
733                        catch (NoSuchFieldException _)
734                          {
735                            fields[i].setToSet(false);
736                          }
737                      }
738                    
739                    calculateOffsets();
740                    return;
741                  }
742              }
743          }
744        catch (NoSuchFieldException ignore)
745          {
746          }
747        catch (IllegalAccessException ignore)
748          {
749          }
750    
751        int num_good_fields = 0;
752        Field[] all_fields = cl.getDeclaredFields();
753    
754        int modifiers;
755        // set non-serializable fields to null in all_fields
756        for (int i = 0; i < all_fields.length; i++)
757          {
758            modifiers = all_fields[i].getModifiers();
759            if (Modifier.isTransient(modifiers)
760                || Modifier.isStatic(modifiers))
761              all_fields[i] = null;
762            else
763              num_good_fields++;
764          }
765    
766        // make a copy of serializable (non-null) fields
767        fields = new ObjectStreamField[ num_good_fields ];
768        for (int from = 0, to = 0; from < all_fields.length; from++)
769          if (all_fields[from] != null)
770            {
771              final Field f = all_fields[from];
772              setAccessible.setMember(f);
773              AccessController.doPrivileged(setAccessible);
774              fields[to] = new ObjectStreamField(all_fields[from]);
775              to++;
776            }
777    
778        Arrays.sort(fields);
779        // Make sure we don't have any duplicate field names
780        // (Sun JDK 1.4.1. throws an Internal Error as well)
781        for (int i = 1; i < fields.length; i++)
782          {
783            if(fields[i - 1].getName().equals(fields[i].getName()))
784                throw new InternalError("Duplicate field " + 
785                            fields[i].getName() + " in class " + cl.getName());
786          }
787        calculateOffsets();
788      }
789    
790      static Hashtable uidCache = new Hashtable();
791    
792      // Returns the serial version UID defined by class, or if that
793      // isn't present, calculates value of serial version UID.
794      private long getClassUID(Class cl)
795      {
796        long result = 0;
797        Long cache = (Long) uidCache.get(cl);
798        if (cache != null)
799          result = cache.longValue(); 
800        else
801          {
802            // Note that we can't use Class.isEnum() here, because that returns
803            // false for java.lang.Enum and enum value sub classes.
804            if (Enum.class.isAssignableFrom(cl) || Proxy.isProxyClass(cl))
805              {
806                // Spec says that enums and dynamic proxies have
807                // a serialVersionUID of 0L.
808                return 0L;
809              }
810            try
811              {
812                result = getClassUIDFromField(cl);
813              }
814            catch (NoSuchFieldException ignore)
815              {
816                try
817                  {
818                    result = calculateClassUID(cl);
819                  }
820                catch (NoSuchAlgorithmException e)
821                  {
822                    throw new RuntimeException
823                      ("The SHA algorithm was not found to use in computing the Serial Version UID for class "
824                       + cl.getName(), e);
825                  }
826                catch (IOException ioe)
827                  {
828                    throw new RuntimeException(ioe);
829                  }
830              }
831    
832            if (loadedByBootOrApplicationClassLoader(cl))
833              uidCache.put(cl,Long.valueOf(result));
834          }
835        return result;
836      }
837    
838      /**
839       * Search for a serialVersionUID field in the given class and read
840       * its value.
841       *
842       * @return the contents of the serialVersionUID field
843       *
844       * @throws NoSuchFieldException if such a field does not exist or is
845       * not static, not final, not of type Long or not accessible.
846       */
847      long getClassUIDFromField(Class cl) 
848        throws NoSuchFieldException
849      {
850        long result;
851        
852        try
853          {
854            // Use getDeclaredField rather than getField, since serialVersionUID
855            // may not be public AND we only want the serialVersionUID of this
856            // class, not a superclass or interface.
857            final Field suid = cl.getDeclaredField("serialVersionUID");
858            SetAccessibleAction setAccessible = new SetAccessibleAction(suid);
859            AccessController.doPrivileged(setAccessible);
860            int modifiers = suid.getModifiers();
861            
862            if (Modifier.isStatic(modifiers)
863                && Modifier.isFinal(modifiers)
864                && suid.getType() == Long.TYPE)
865              result = suid.getLong(null);
866            else
867              throw new NoSuchFieldException();
868          }
869        catch (IllegalAccessException ignore)
870          {
871            throw new NoSuchFieldException();
872          }
873    
874        return result;
875      }
876    
877      /**
878       * Calculate class serial version UID for a class that does not
879       * define serialVersionUID:
880       *
881       * @param cl a class
882       *
883       * @return the calculated serial varsion UID.
884       *
885       * @throws NoSuchAlgorithmException if SHA algorithm not found
886       *
887       * @throws IOException if writing to the DigestOutputStream causes
888       * an IOException.
889       */
890      long calculateClassUID(Class cl) 
891        throws NoSuchAlgorithmException, IOException
892      {
893        long result; 
894        MessageDigest md;
895        try 
896          {
897            md = MessageDigest.getInstance("SHA");
898          }
899        catch (NoSuchAlgorithmException e)
900          {
901            // If a provider already provides SHA, use it; otherwise, use this.
902            Gnu gnuProvider = new Gnu();
903            Security.addProvider(gnuProvider);
904            md = MessageDigest.getInstance("SHA");
905          }
906        
907        DigestOutputStream digest_out =
908          new DigestOutputStream(nullOutputStream, md);
909        DataOutputStream data_out = new DataOutputStream(digest_out);
910        
911        data_out.writeUTF(cl.getName());
912        
913        int modifiers = cl.getModifiers();
914        // just look at interesting bits
915        modifiers = modifiers & (Modifier.ABSTRACT | Modifier.FINAL
916                                 | Modifier.INTERFACE | Modifier.PUBLIC);
917        data_out.writeInt(modifiers);
918        
919        // Pretend that an array has no interfaces, because when array
920        // serialization was defined (JDK 1.1), arrays didn't have it.
921        if (! cl.isArray())
922          {
923            Class[] interfaces = cl.getInterfaces();
924            Arrays.sort(interfaces, interfaceComparator);
925            for (int i = 0; i < interfaces.length; i++)
926              data_out.writeUTF(interfaces[i].getName());
927          }
928        
929        Field field;
930        Field[] fields = cl.getDeclaredFields();
931        Arrays.sort(fields, memberComparator);
932        for (int i = 0; i < fields.length; i++)
933          {
934            field = fields[i];
935            modifiers = field.getModifiers();
936            if (Modifier.isPrivate(modifiers)
937                && (Modifier.isStatic(modifiers)
938                    || Modifier.isTransient(modifiers)))
939              continue;
940            
941            data_out.writeUTF(field.getName());
942            data_out.writeInt(modifiers);
943            data_out.writeUTF(TypeSignature.getEncodingOfClass (field.getType()));
944          }
945        
946        // write class initializer method if present
947        if (VMObjectStreamClass.hasClassInitializer(cl))
948          {
949            data_out.writeUTF("<clinit>");
950            data_out.writeInt(Modifier.STATIC);
951            data_out.writeUTF("()V");
952          }
953        
954        Constructor constructor;
955        Constructor[] constructors = cl.getDeclaredConstructors();
956        Arrays.sort (constructors, memberComparator);
957        for (int i = 0; i < constructors.length; i++)
958          {
959            constructor = constructors[i];
960            modifiers = constructor.getModifiers();
961            if (Modifier.isPrivate(modifiers))
962              continue;
963            
964            data_out.writeUTF("<init>");
965            data_out.writeInt(modifiers);
966            
967            // the replacement of '/' with '.' was needed to make computed
968            // SUID's agree with those computed by JDK
969            data_out.writeUTF 
970              (TypeSignature.getEncodingOfConstructor(constructor).replace('/','.'));
971          }
972        
973        Method method;
974        Method[] methods = cl.getDeclaredMethods();
975        Arrays.sort(methods, memberComparator);
976        for (int i = 0; i < methods.length; i++)
977          {
978            method = methods[i];
979            modifiers = method.getModifiers();
980            if (Modifier.isPrivate(modifiers))
981              continue;
982            
983            data_out.writeUTF(method.getName());
984            data_out.writeInt(modifiers);
985            
986            // the replacement of '/' with '.' was needed to make computed
987            // SUID's agree with those computed by JDK
988            data_out.writeUTF
989              (TypeSignature.getEncodingOfMethod(method).replace('/', '.'));
990          }
991        
992        data_out.close();
993        byte[] sha = md.digest();
994        result = 0;
995        int len = sha.length < 8 ? sha.length : 8;
996        for (int i = 0; i < len; i++)
997          result += (long) (sha[i] & 0xFF) << (8 * i);
998    
999        return result;
1000      }
1001    
1002      /**
1003       * Returns the value of CLAZZ's private static final field named
1004       * `serialPersistentFields'. It performs some sanity checks before
1005       * returning the real array. Besides, the returned array is a clean
1006       * copy of the original. So it can be modified.
1007       *
1008       * @param clazz Class to retrieve 'serialPersistentFields' from.
1009       * @return The content of 'serialPersistentFields'.
1010       */
1011      private ObjectStreamField[] getSerialPersistentFields(Class clazz) 
1012        throws NoSuchFieldException, IllegalAccessException
1013      {
1014        ObjectStreamField[] fieldsArray = null;
1015        ObjectStreamField[] o;
1016    
1017        // Use getDeclaredField rather than getField for the same reason
1018        // as above in getDefinedSUID.
1019        Field f = clazz.getDeclaredField("serialPersistentFields");
1020        f.setAccessible(true);
1021    
1022        int modifiers = f.getModifiers();
1023        if (!(Modifier.isStatic(modifiers) &&
1024              Modifier.isFinal(modifiers) &&
1025              Modifier.isPrivate(modifiers)))
1026          return null;
1027        
1028        o = (ObjectStreamField[]) f.get(null);
1029        
1030        if (o == null)
1031          return null;
1032    
1033        fieldsArray = new ObjectStreamField[ o.length ];
1034        System.arraycopy(o, 0, fieldsArray, 0, o.length);
1035    
1036        return fieldsArray;
1037      }
1038    
1039      /**
1040       * Returns a new instance of the Class this ObjectStreamClass corresponds
1041       * to.
1042       * Note that this should only be used for Externalizable classes.
1043       *
1044       * @return A new instance.
1045       */
1046      Externalizable newInstance() throws InvalidClassException
1047      {
1048        synchronized(this)
1049        {
1050            if (constructor == null)
1051            {
1052                try
1053                {
1054                    final Constructor c = clazz.getConstructor(new Class[0]);
1055    
1056                    AccessController.doPrivileged(new PrivilegedAction()
1057                    {
1058                        public Object run()
1059                        {
1060                            c.setAccessible(true);
1061                            return null;
1062                        }
1063                    });
1064    
1065                    constructor = c;
1066                }
1067                catch(NoSuchMethodException x)
1068                {
1069                    throw new InvalidClassException(clazz.getName(),
1070                        "No public zero-argument constructor");
1071                }
1072            }
1073        }
1074    
1075        try
1076        {
1077            return (Externalizable)constructor.newInstance();
1078        }
1079        catch(Exception x)
1080        {
1081            throw (InvalidClassException)
1082                new InvalidClassException(clazz.getName(),
1083                         "Unable to instantiate").initCause(x);
1084        }
1085      }
1086    
1087      public static final ObjectStreamField[] NO_FIELDS = {};
1088    
1089      private static Hashtable<Class,ObjectStreamClass> classLookupTable
1090        = new Hashtable<Class,ObjectStreamClass>();
1091      private static final NullOutputStream nullOutputStream = new NullOutputStream();
1092      private static final Comparator interfaceComparator = new InterfaceComparator();
1093      private static final Comparator memberComparator = new MemberComparator();
1094      private static final
1095        Class[] writeMethodArgTypes = { java.io.ObjectOutputStream.class };
1096    
1097      private ObjectStreamClass superClass;
1098      private Class<?> clazz;
1099      private String name;
1100      private long uid;
1101      private byte flags;
1102    
1103      // this field is package protected so that ObjectInputStream and
1104      // ObjectOutputStream can access it directly
1105      ObjectStreamField[] fields;
1106    
1107      // these are accessed by ObjectIn/OutputStream
1108      int primFieldSize = -1;  // -1 if not yet calculated
1109      int objectFieldCount;
1110    
1111      Method readObjectMethod;
1112      Method readResolveMethod;
1113      Method writeReplaceMethod;
1114      Method writeObjectMethod;
1115      boolean realClassIsSerializable;
1116      boolean realClassIsExternalizable;
1117      ObjectStreamField[] fieldMapping;
1118      Constructor firstNonSerializableParentConstructor;
1119      private Constructor constructor;  // default constructor for Externalizable
1120    
1121      boolean isProxyClass = false;
1122    
1123    /* GCJ LOCAL */
1124      // True after setFields() has been called
1125      private boolean fieldsSet = false;
1126    /* END GCJ LOCAL */
1127    
1128      // This is probably not necessary because this class is special cased already
1129      // but it will avoid showing up as a discrepancy when comparing SUIDs.
1130      private static final long serialVersionUID = -6120832682080437368L;
1131    
1132    
1133      // interfaces are compared only by name
1134      private static final class InterfaceComparator implements Comparator
1135      {
1136        public int compare(Object o1, Object o2)
1137        {
1138          return ((Class) o1).getName().compareTo(((Class) o2).getName());
1139        }
1140      }
1141    
1142    
1143      // Members (Methods and Constructors) are compared first by name,
1144      // conflicts are resolved by comparing type signatures
1145      private static final class MemberComparator implements Comparator
1146      {
1147        public int compare(Object o1, Object o2)
1148        {
1149          Member m1 = (Member) o1;
1150          Member m2 = (Member) o2;
1151    
1152          int comp = m1.getName().compareTo(m2.getName());
1153    
1154          if (comp == 0)
1155            return TypeSignature.getEncodingOfMember(m1).
1156              compareTo(TypeSignature.getEncodingOfMember(m2));
1157          else
1158            return comp;
1159        }
1160      }
1161    }