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
005This file is part of GNU Classpath.
006
007GNU Classpath is free software; you can redistribute it and/or modify
008it under the terms of the GNU General Public License as published by
009the Free Software Foundation; either version 2, or (at your option)
010any later version.
011 
012GNU Classpath is distributed in the hope that it will be useful, but
013WITHOUT ANY WARRANTY; without even the implied warranty of
014MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015General Public License for more details.
016
017You should have received a copy of the GNU General Public License
018along with GNU Classpath; see the file COPYING.  If not, write to the
019Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02002110-1301 USA.
021
022Linking this library statically or dynamically with other modules is
023making a combined work based on this library.  Thus, the terms and
024conditions of the GNU General Public License cover the whole
025combination.
026
027As a special exception, the copyright holders of this library give you
028permission to link this library with independent modules to produce an
029executable, regardless of the license terms of these independent
030modules, and to copy and distribute the resulting executable under
031terms of your choice, provided that you also meet, for each linked
032independent module, the terms and conditions of the license of that
033module.  An independent module is a module which is not derived from
034or based on this library.  If you modify this library, you may extend
035this exception to your version of the library, but you are not
036obligated to do so.  If you do not wish to do so, delete this
037exception statement from your version. */
038
039
040package java.io;
041
042import gnu.java.io.NullOutputStream;
043import gnu.java.lang.reflect.TypeSignature;
044import gnu.java.security.action.SetAccessibleAction;
045import gnu.java.security.provider.Gnu;
046
047import java.lang.reflect.Constructor;
048import java.lang.reflect.Field;
049import java.lang.reflect.Member;
050import java.lang.reflect.Method;
051import java.lang.reflect.Modifier;
052import java.lang.reflect.Proxy;
053import java.security.AccessController;
054import java.security.DigestOutputStream;
055import java.security.MessageDigest;
056import java.security.NoSuchAlgorithmException;
057import java.security.PrivilegedAction;
058import java.security.Security;
059import java.util.Arrays;
060import java.util.Comparator;
061import 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 */
070public 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  {
487outer:
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}