ObjectStreamClass.java 25.1 KB
Newer Older
Tom Tromey committed
1 2
/* ObjectStreamClass.java -- Class used to write class information
   about serialized objects.
Mark Wielaard committed
3
   Copyright (C) 1998, 1999, 2000, 2001, 2003  Free Software Foundation, Inc.
Tom Tromey committed
4

5
   This file is part of GNU Classpath.
Tom Tromey committed
6

7 8 9 10
   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.
Tom Tromey committed
11
 
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
   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.

   Linking this library statically or dynamically with other modules is
   making a combined work based on this library.  Thus, the terms and
   conditions of the GNU General Public License cover the whole
   combination.

   As a special exception, the copyright holders of this library give you
   permission to link this library with independent modules to produce an
   executable, regardless of the license terms of these independent
   modules, and to copy and distribute the resulting executable under
   terms of your choice, provided that you also meet, for each linked
   independent module, the terms and conditions of the license of that
   module.  An independent module is a module which is not derived from
   or based on this library.  If you modify this library, you may extend
   this exception to your version of the library, but you are not
   obligated to do so.  If you do not wish to do so, delete this
   exception statement from your version. */
Tom Tromey committed
38 39 40 41 42 43 44 45 46


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;
Mark Wielaard committed
47
import java.lang.reflect.Proxy;
48
import java.security.AccessController;
Tom Tromey committed
49 50 51
import java.security.DigestOutputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
52
import java.security.PrivilegedAction;
Warren Levy committed
53
import java.security.Security;
Tom Tromey committed
54 55 56 57 58 59
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;
60
import gnu.java.security.action.SetAccessibleAction;
Warren Levy committed
61
import gnu.java.security.provider.Gnu;
Tom Tromey committed
62 63 64 65 66


public class ObjectStreamClass implements Serializable
{
  /**
67 68 69 70 71 72 73 74 75 76
   * 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)
Tom Tromey committed
77 78 79
  {
    if (cl == null)
      return null;
80
    if (! (Serializable.class).isAssignableFrom(cl))
81
      return null;
Tom Tromey committed
82

83
    return lookupForClassObject(cl);
Mark Wielaard committed
84 85 86 87 88 89 90
  }

  /**
   * This lookup for internal use by ObjectOutputStream.  Suppose
   * we have a java.lang.Class object C for class A, though A is not
   * serializable, but it's okay to serialize C.
   */
91
  static ObjectStreamClass lookupForClassObject(Class cl)
Mark Wielaard committed
92 93 94 95
  {
    if (cl == null)
      return null;

96
    ObjectStreamClass osc = (ObjectStreamClass) classLookupTable.get(cl);
Tom Tromey committed
97 98 99 100

    if (osc != null)
      return osc;
    else
101 102 103 104 105
      {
	osc = new ObjectStreamClass(cl);
	classLookupTable.put(cl, osc);
	return osc;
      }
Tom Tromey committed
106 107 108
  }

  /**
109 110
   * Returns the name of the class that this
   * <code>ObjectStreamClass</code> represents.
111 112
   *
   * @return the name of the class.
113 114
   */
  public String getName()
Tom Tromey committed
115 116 117 118 119
  {
    return name;
  }

  /**
120 121 122 123 124 125 126 127 128
   * 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()
Tom Tromey committed
129 130 131 132 133
  {
    return clazz;
  }

  /**
134 135 136 137 138
   * 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
139 140
   *
   * @return the serial version UID.
141 142
   */
  public long getSerialVersionUID()
Tom Tromey committed
143 144 145 146
  {
    return uid;
  }

147 148 149 150 151 152 153
  /**
   * Returns the serializable (non-static and non-transient) Fields
   * of the class represented by this ObjectStreamClass.  The Fields
   * are sorted by name.
   *
   * @return the fields.
   */
154
  public ObjectStreamField[] getFields()
Tom Tromey committed
155 156
  {
    ObjectStreamField[] copy = new ObjectStreamField[ fields.length ];
157
    System.arraycopy(fields, 0, copy, 0, fields.length);
Tom Tromey committed
158 159 160 161 162 163 164 165
    return copy;
  }

  // XXX doc
  // Can't do binary search since fields is sorted by name and
  // primitiveness.
  public ObjectStreamField getField (String name)
  {
166 167
    for (int i = 0; i < fields.length; i++)
      if (fields[i].getName().equals(name))
Tom Tromey committed
168 169 170 171 172
	return fields[i];
    return null;
  }

  /**
173 174 175 176 177 178 179 180
   * 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()
   */
181
  public String toString()
Tom Tromey committed
182 183 184 185 186 187 188 189 190 191
  {
    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
192
  // serialization behavior.
193
  boolean hasWriteMethod()
Tom Tromey committed
194 195 196 197
  {
    return (flags & ObjectStreamConstants.SC_WRITE_METHOD) != 0;
  }

198
  // Returns true iff the class that this ObjectStreamClass represents
Tom Tromey committed
199
  // implements Serializable but does *not* implement Externalizable.
200
  boolean isSerializable()
Tom Tromey committed
201 202 203 204 205 206 207
  {
    return (flags & ObjectStreamConstants.SC_SERIALIZABLE) != 0;
  }


  // Returns true iff the class that this ObjectStreamClass represents
  // implements Externalizable.
208
  boolean isExternalizable()
Tom Tromey committed
209 210 211 212 213 214 215
  {
    return (flags & ObjectStreamConstants.SC_EXTERNALIZABLE) != 0;
  }


  // Returns the <code>ObjectStreamClass</code> that represents the
  // class that is the superclass of the class this
Anthony Green committed
216
  // <code>ObjectStreamClass</code> represents.  If the superclass is
Tom Tromey committed
217
  // not Serializable, null is returned.
218
  ObjectStreamClass getSuper()
Tom Tromey committed
219 220 221 222 223 224 225 226 227
  {
    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.
228
  static ObjectStreamClass[] getObjectStreamClasses(Class clazz)
Tom Tromey committed
229
  {
230
    ObjectStreamClass osc = ObjectStreamClass.lookup(clazz);
Tom Tromey committed
231 232 233 234 235

    if (osc == null)
      return new ObjectStreamClass[0];
    else
      {
236
	Vector oscs = new Vector();
Tom Tromey committed
237

238 239 240 241 242 243 244 245
	while (osc != null)
	  {
	    oscs.addElement (osc);
	    osc = osc.getSuper();
	  }

	int count = oscs.size();
	ObjectStreamClass[] sorted_oscs = new ObjectStreamClass[ count ];
Tom Tromey committed
246

247 248
	for (int i = count - 1; i >= 0; i--)
	  sorted_oscs[ count - i - 1 ] = (ObjectStreamClass) oscs.elementAt(i);
Tom Tromey committed
249

250 251
	return sorted_oscs;
      }
Tom Tromey committed
252 253 254 255 256 257 258
  }


  // 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_'
259
  int getFlags()
Tom Tromey committed
260 261 262 263 264
  {
    return flags;
  }


265 266
  ObjectStreamClass(String name, long uid, byte flags,
		    ObjectStreamField[] fields)
Tom Tromey committed
267 268 269 270 271 272 273
  {
    this.name = name;
    this.uid = uid;
    this.flags = flags;
    this.fields = fields;
  }

274 275 276 277 278 279 280 281 282 283 284
  /**
   * This method builds the internal description corresponding to a Java Class.
   * As the constructor only assign a name to the current ObjectStreamClass instance,
   * that method sets the serial UID, chose the fields which will be serialized,
   * and compute the position of the fields in the serialized stream.
   *
   * @param cl The Java class which is used as a reference for building the descriptor.
   * @param superClass The descriptor of the super class for this class descriptor.
   * @throws InvalidClassException if an incompatibility between computed UID and
   * already set UID is found.
   */
285
  void setClass(Class cl, ObjectStreamClass superClass) throws InvalidClassException
Tom Tromey committed
286
  {
287
    this.clazz = cl;
Mark Wielaard committed
288

289 290
    cacheMethods();

291
    long class_uid = getClassUID(cl);
292
    if (uid == 0)
Mark Wielaard committed
293 294
      uid = class_uid;
    else
295
      {
Mark Wielaard committed
296 297 298 299 300 301 302 303 304
	// 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);
	  }
305
      }
Mark Wielaard committed
306

307
    isProxyClass = clazz != null && Proxy.isProxyClass(clazz);
308
    this.superClass = superClass;
309 310
    calculateOffsets();
    
311 312
    try
      {
313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
	ObjectStreamField[] exportedFields = getSerialPersistentFields (clazz);  

	if (exportedFields == null)
	  return;

	ObjectStreamField[] newFieldList = new ObjectStreamField[exportedFields.length + fields.length];
	int i, j, k;

	/* We now check the import fields against the exported fields.
	 * There should not be contradiction (e.g. int x and String x)
	 * but extra virtual fields can be added to the class.
	 */

	Arrays.sort(exportedFields);

	i = 0; j = 0; k = 0;
	while (i < fields.length && j < exportedFields.length)
	  {
331
	    int comp = fields[i].compareTo(exportedFields[j]);
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347

	    if (comp < 0)
	      {
		newFieldList[k] = fields[i];
		fields[i].setPersistent(false);
		fields[i].setToSet(false);
		i++;
	      }
	    else if (comp > 0)
	      {
		/* field not found in imported fields. We add it
		 * in the list of supported fields.
		 */
		newFieldList[k] = exportedFields[j];
		newFieldList[k].setPersistent(true);
		newFieldList[k].setToSet(false);
348 349 350 351 352 353 354 355
		try
		  {
		    newFieldList[k].lookupField(clazz);
		    newFieldList[k].checkFieldType();
		  }
		catch (NoSuchFieldException _)
		  {
		  }
356 357 358 359
		j++;
	      }
	    else
	      {
360 361 362 363 364 365 366 367 368
		try
		  {
		    exportedFields[j].lookupField(clazz);
		    exportedFields[j].checkFieldType();
		  }
		catch (NoSuchFieldException _)
		  {
		  }

369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398
		if (!fields[i].getType().equals(exportedFields[j].getType()))
		  throw new InvalidClassException
		    ("serialPersistentFields must be compatible with" +
		     " imported fields (about " + fields[i].getName() + ")");
		newFieldList[k] = fields[i];
		fields[i].setPersistent(true);
		i++;
		j++;
	      }
	    k++;
	  }

	if (i < fields.length)
	  for (;i<fields.length;i++,k++)
	    {
	      fields[i].setPersistent(false);
	      fields[i].setToSet(false);
	      newFieldList[k] = fields[i];
	    }
	else
	  if (j < exportedFields.length)
	    for (;j<exportedFields.length;j++,k++)
	      {
		exportedFields[j].setPersistent(true);
		exportedFields[j].setToSet(false);
		newFieldList[k] = exportedFields[j];
	      }
	
	fields = new ObjectStreamField[k];
	System.arraycopy(newFieldList, 0, fields, 0, k);
399 400 401
      }
    catch (NoSuchFieldException ignore)
      {
402
	return;
403 404 405
      }
    catch (IllegalAccessException ignore)
      {
406
	return;
407
      }
Tom Tromey committed
408 409 410 411 412 413 414
  }

  void setSuperclass (ObjectStreamClass osc)
  {
    superClass = osc;
  }

415
  void calculateOffsets()
Tom Tromey committed
416 417 418 419 420 421 422
  {
    int i;
    ObjectStreamField field;
    primFieldSize = 0;
    int fcount = fields.length;
    for (i = 0; i < fcount; ++ i)
      {
423 424 425
	field = fields[i];

	if (! field.isPrimitive())
Tom Tromey committed
426
	  break;
427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447

	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;
	  }
Tom Tromey committed
448 449 450
      }

    for (objectFieldCount = 0; i < fcount; ++ i)
451
      fields[i].setOffset(objectFieldCount++);
Tom Tromey committed
452 453
  }

454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473
  private Method findMethod(Method[] methods, String name, Class[] params,
			    Class returnType)
  {
outer:
    for(int i = 0; i < methods.length; i++)
    {
	if(methods[i].getName().equals(name) &&
	   methods[i].getReturnType() == returnType)
	{
	    Class[] mp = methods[i].getParameterTypes();
	    if(mp.length == params.length)
	    {
		for(int j = 0; j < mp.length; j++)
		{
		    if(mp[j] != params[j])
		    {
			continue outer;
		    }
		}
		final Method m = methods[i];
474 475
		SetAccessibleAction setAccessible = new SetAccessibleAction(m);
		AccessController.doPrivileged(setAccessible);
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
		return m;
	    }
	}
    }
    return null;
  }

  private void cacheMethods()
  {
    Method[] methods = forClass().getDeclaredMethods();
    readObjectMethod = findMethod(methods, "readObject",
				  new Class[] { ObjectInputStream.class },
				  Void.TYPE);
    readResolveMethod = findMethod(methods, "readResolve",
				   new Class[0], Object.class);
  }
Tom Tromey committed
492

493
  private ObjectStreamClass(Class cl)
Tom Tromey committed
494 495 496
  {
    uid = 0;
    flags = 0;
497
    isProxyClass = Proxy.isProxyClass(cl);
Tom Tromey committed
498 499

    clazz = cl;
500
    cacheMethods();
501 502 503
    name = cl.getName();
    setFlags(cl);
    setFields(cl);
Mark Wielaard committed
504
    // to those class nonserializable, its uid field is 0
505 506 507
    if ( (Serializable.class).isAssignableFrom(cl) && !isProxyClass)
      uid = getClassUID(cl);
    superClass = lookup(cl.getSuperclass());
Tom Tromey committed
508 509 510 511
  }


  // Sets bits in flags according to features of CL.
512
  private void setFlags(Class cl)
Tom Tromey committed
513
  {
514
    if ((java.io.Externalizable.class).isAssignableFrom(cl))
Tom Tromey committed
515
      flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;
516
    else if ((java.io.Serializable.class).isAssignableFrom(cl))
Tom Tromey committed
517 518 519 520
      // only set this bit if CL is NOT Externalizable
      flags |= ObjectStreamConstants.SC_SERIALIZABLE;

    try
521 522 523 524 525 526 527 528 529 530 531 532 533
      {
	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)
      {
      }
Tom Tromey committed
534 535 536 537 538
  }


  // Sets fields to be a sorted array of the serializable fields of
  // clazz.
539
  private void setFields(Class cl)
Tom Tromey committed
540
  {
541 542
    SetAccessibleAction setAccessible = new SetAccessibleAction();

543 544 545 546 547
    if (!isSerializable() || isExternalizable())
      {
	fields = NO_FIELDS;
	return;
      }
Tom Tromey committed
548 549 550

    try
      {
551
	final Field f =
552
	  cl.getDeclaredField("serialPersistentFields");
553 554 555
	setAccessible.setMember(f);
	AccessController.doPrivileged(setAccessible);
	int modifiers = f.getModifiers();
556 557 558 559

	if (Modifier.isStatic(modifiers)
	    && Modifier.isFinal(modifiers)
	    && Modifier.isPrivate(modifiers))
560
	  {
561 562 563 564
	    fields = getSerialPersistentFields(cl);
	    if (fields != null)
	      {
		Arrays.sort (fields);
565 566 567 568 569 570 571 572 573 574 575 576 577
		// Retrieve field reference.
		for (int i=0; i < fields.length; i++)
		  {
		    try
		      {
			fields[i].lookupField(cl);
		      }
		    catch (NoSuchFieldException _)
		      {
			fields[i].setToSet(false);
		      }
		  }
		
578 579 580
		calculateOffsets();
		return;
	      }
581
	  }
Tom Tromey committed
582 583
      }
    catch (NoSuchFieldException ignore)
584 585
      {
      }
586 587 588
    catch (IllegalAccessException ignore)
      {
      }
Tom Tromey committed
589 590

    int num_good_fields = 0;
591
    Field[] all_fields = cl.getDeclaredFields();
Tom Tromey committed
592 593 594

    int modifiers;
    // set non-serializable fields to null in all_fields
595 596 597 598 599 600 601 602 603
    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++;
      }
Tom Tromey committed
604 605 606

    // make a copy of serializable (non-null) fields
    fields = new ObjectStreamField[ num_good_fields ];
607
    for (int from = 0, to = 0; from < all_fields.length; from++)
Tom Tromey committed
608
      if (all_fields[from] != null)
609
	{
610
	  final Field f = all_fields[from];
611 612
	  setAccessible.setMember(f);
	  AccessController.doPrivileged(setAccessible);
613
	  fields[to] = new ObjectStreamField(all_fields[from]);
614 615
	  to++;
	}
Tom Tromey committed
616

617
    Arrays.sort(fields);
618 619 620 621 622 623 624 625
    // Make sure we don't have any duplicate field names
    // (Sun JDK 1.4.1. throws an Internal Error as well)
    for (int i = 1; i < fields.length; i++)
      {
	if(fields[i - 1].getName().equals(fields[i].getName()))
	    throw new InternalError("Duplicate field " + 
			fields[i].getName() + " in class " + cl.getName());
      }
626
    calculateOffsets();
Tom Tromey committed
627 628
  }

629
  // Returns the serial version UID defined by class, or if that
Tom Tromey committed
630
  // isn't present, calculates value of serial version UID.
631
  private long getClassUID(Class cl)
Tom Tromey committed
632 633
  {
    try
634 635 636 637
      {
	// Use getDeclaredField rather than getField, since serialVersionUID
	// may not be public AND we only want the serialVersionUID of this
	// class, not a superclass or interface.
638
	final Field suid = cl.getDeclaredField("serialVersionUID");
639 640
	SetAccessibleAction setAccessible = new SetAccessibleAction(suid);
	AccessController.doPrivileged(setAccessible);
641 642 643 644 645 646 647
	int modifiers = suid.getModifiers();

	if (Modifier.isStatic(modifiers)
	    && Modifier.isFinal(modifiers)
	    && suid.getType() == Long.TYPE)
	  return suid.getLong(null);
      }
Tom Tromey committed
648
    catch (NoSuchFieldException ignore)
649 650
      {
      }
651
    catch (IllegalAccessException ignore)
652 653
      {
      }
Tom Tromey committed
654 655 656

    // cl didn't define serialVersionUID, so we have to compute it
    try
657 658 659 660 661 662 663 664 665 666 667 668 669
      {
	MessageDigest md;
	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");
	  }
Tom Tromey committed
670

671 672 673
	DigestOutputStream digest_out =
	  new DigestOutputStream(nullOutputStream, md);
	DataOutputStream data_out = new DataOutputStream(digest_out);
Mark Wielaard committed
674

675
	data_out.writeUTF(cl.getName());
Tom Tromey committed
676

677 678 679 680 681
	int modifiers = cl.getModifiers();
	// just look at interesting bits
	modifiers = modifiers & (Modifier.ABSTRACT | Modifier.FINAL
				 | Modifier.INTERFACE | Modifier.PUBLIC);
	data_out.writeInt(modifiers);
Tom Tromey committed
682

683 684 685 686 687 688 689 690 691
	// 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());
	  }
Tom Tromey committed
692

693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708
	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()));
	  }
Tom Tromey committed
709

710 711 712 713 714 715 716
	// write class initializer method if present
	if (VMObjectStreamClass.hasClassInitializer(cl))
	  {
	    data_out.writeUTF("<clinit>");
	    data_out.writeInt(Modifier.STATIC);
	    data_out.writeUTF("()V");
	  }
Tom Tromey committed
717

718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735
	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('/','.'));
	  }
Tom Tromey committed
736

737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754
	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('/', '.'));
	  }
Tom Tromey committed
755

756 757 758 759 760 761
	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);
Tom Tromey committed
762

763 764
	return result;
      }
Tom Tromey committed
765
    catch (NoSuchAlgorithmException e)
766 767 768 769 770
      {
	throw new RuntimeException
	  ("The SHA algorithm was not found to use in computing the Serial Version UID for class "
	   + cl.getName(), e);
      }
Tom Tromey committed
771
    catch (IOException ioe)
772 773 774
      {
	throw new RuntimeException(ioe);
      }
Tom Tromey committed
775 776
  }

777 778 779 780 781 782 783 784 785
  /**
   * Returns the value of CLAZZ's private static final field named
   * `serialPersistentFields'. It performs some sanity checks before
   * returning the real array. Besides, the returned array is a clean
   * copy of the original. So it can be modified.
   *
   * @param clazz Class to retrieve 'serialPersistentFields' from.
   * @return The content of 'serialPersistentFields'.
   */
786
  private ObjectStreamField[] getSerialPersistentFields(Class clazz) 
787
    throws NoSuchFieldException, IllegalAccessException
Tom Tromey committed
788
  {
789 790 791
    ObjectStreamField[] fieldsArray = null;
    ObjectStreamField[] o;

792 793 794 795
    // Use getDeclaredField rather than getField for the same reason
    // as above in getDefinedSUID.
    Field f = clazz.getDeclaredField("serialPersistentFields");
    f.setAccessible(true);
796 797

    int modifiers = f.getModifiers();
798 799 800
    if (!(Modifier.isStatic(modifiers) &&
	  Modifier.isFinal(modifiers) &&
	  Modifier.isPrivate(modifiers)))
801 802 803 804 805 806 807
      return null;
    
    o = (ObjectStreamField[]) f.get(null);
    
    if (o == null)
      return null;

808
    fieldsArray = new ObjectStreamField[ o.length ];
809
    System.arraycopy(o, 0, fieldsArray, 0, o.length);
810

811
    return fieldsArray;
Tom Tromey committed
812 813
  }

814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861
  /**
   * Returns a new instance of the Class this ObjectStreamClass corresponds
   * to.
   * Note that this should only be used for Externalizable classes.
   *
   * @return A new instance.
   */
  Externalizable newInstance() throws InvalidClassException
  {
    synchronized(this)
    {
	if (constructor == null)
	{
	    try
	    {
		final Constructor c = clazz.getConstructor(new Class[0]);

		AccessController.doPrivileged(new PrivilegedAction()
		{
		    public Object run()
		    {
			c.setAccessible(true);
			return null;
		    }
		});

		constructor = c;
	    }
	    catch(NoSuchMethodException x)
	    {
		throw new InvalidClassException(clazz.getName(),
		    "No public zero-argument constructor");
	    }
	}
    }

    try
    {
	return (Externalizable)constructor.newInstance(null);
    }
    catch(Throwable t)
    {
	throw (InvalidClassException)
	    new InvalidClassException(clazz.getName(),
		     "Unable to instantiate").initCause(t);
    }
  }

Tom Tromey committed
862 863
  public static final ObjectStreamField[] NO_FIELDS = {};

864 865 866 867
  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();
Tom Tromey committed
868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883
  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;
884

885 886 887 888 889 890
  Method readObjectMethod;
  Method readResolveMethod;
  boolean realClassIsSerializable;
  boolean realClassIsExternalizable;
  ObjectStreamField[] fieldMapping;
  Class firstNonSerializableParent;
891
  private Constructor constructor;  // default constructor for Externalizable
892

Mark Wielaard committed
893 894
  boolean isProxyClass = false;

895 896 897
  // 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;
Mark Wielaard committed
898

Tom Tromey committed
899 900 901 902 903 904
}


// interfaces are compared only by name
class InterfaceComparator implements Comparator
{
905
  public int compare(Object o1, Object o2)
Tom Tromey committed
906
  {
907
    return ((Class) o1).getName().compareTo(((Class) o2).getName());
Tom Tromey committed
908 909 910 911 912 913 914 915
  }
}


// Members (Methods and Constructors) are compared first by name,
// conflicts are resolved by comparing type signatures
class MemberComparator implements Comparator
{
916
  public int compare(Object o1, Object o2)
Tom Tromey committed
917
  {
918 919
    Member m1 = (Member) o1;
    Member m2 = (Member) o2;
Tom Tromey committed
920

921
    int comp = m1.getName().compareTo(m2.getName());
Tom Tromey committed
922 923

    if (comp == 0)
924 925
      return TypeSignature.getEncodingOfMember(m1).
	compareTo(TypeSignature.getEncodingOfMember(m2));
Tom Tromey committed
926 927 928 929
    else
      return comp;
  }
}