ObjectInputStream.java 57.1 KB
Newer Older
Tom Tromey committed
1
/* ObjectInputStream.java -- Class used to read serialized objects
2 3
   Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2005
   Free Software Foundation, Inc.
Tom Tromey committed
4 5 6 7 8 9 10

This file is part of GNU Classpath.

GNU Classpath is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
11

Tom Tromey committed
12 13 14 15 16 17 18
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
Kelley Cook committed
19 20
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301 USA.
Tom Tromey committed
21

22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
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


package java.io;

42 43 44
import gnu.classpath.Configuration;
import gnu.java.io.ObjectIdentityWrapper;

Tom Tromey committed
45
import java.lang.reflect.Array;
Bryce McKinlay committed
46
import java.lang.reflect.Constructor;
47 48 49
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
Tom Tromey committed
50
import java.lang.reflect.Modifier;
51
import java.lang.reflect.Proxy;
52
import java.security.AccessController;
53
import java.security.PrivilegedAction;
Tom Tromey committed
54 55 56 57 58 59 60 61
import java.util.Arrays;
import java.util.Hashtable;
import java.util.Vector;

public class ObjectInputStream extends InputStream
  implements ObjectInput, ObjectStreamConstants
{
  /**
62 63 64 65 66 67 68 69 70 71 72 73 74
   * Creates a new <code>ObjectInputStream</code> that will do all of
   * its reading from <code>in</code>.  This method also checks
   * the stream by reading the header information (stream magic number
   * and stream version).
   *
   * @exception IOException Reading stream header from underlying
   * stream cannot be completed.
   *
   * @exception StreamCorruptedException An invalid stream magic
   * number or stream version was read from the stream.
   *
   * @see #readStreamHeader()
   */
75
  public ObjectInputStream(InputStream in)
Tom Tromey committed
76 77
    throws IOException, StreamCorruptedException
  {
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
    if (Configuration.DEBUG)
      {
	String val = System.getProperty("gcj.dumpobjects");
	if (dump == false && val != null && !val.equals(""))
	  {
	    dump = true;
	    System.out.println ("Serialization debugging enabled");
	  }
	else if (dump == true && (val == null || val.equals("")))
	  {
	    dump = false;
	    System.out.println ("Serialization debugging disabled");
	  }
      }

Tom Tromey committed
93 94 95 96 97
    this.resolveEnabled = false;
    this.isDeserializing = false;
    this.blockDataPosition = 0;
    this.blockDataBytes = 0;
    this.blockData = new byte[BUFFER_SIZE];
98 99
    this.blockDataInput = new DataInputStream(this);
    this.realInputStream = new DataInputStream(in);
Tom Tromey committed
100
    this.nextOID = baseWireHandle;
101 102
    this.objectLookupTable = new Hashtable();
    this.validators = new Vector();
103
    this.classLookupTable = new Hashtable();
104 105
    setBlockDataMode(true);
    readStreamHeader();
Tom Tromey committed
106 107 108 109
  }


  /**
110 111 112 113 114 115
   * Returns the next deserialized object read from the underlying stream.
   *
   * This method can be overriden by a class by implementing
   * <code>private void readObject (ObjectInputStream)</code>.
   *
   * If an exception is thrown from this method, the stream is left in
Mark Wielaard committed
116 117 118 119
   * an undefined state. This method can also throw Errors and 
   * RuntimeExceptions if caused by existing readResolve() user code.
   * 
   * @return The object read from the underlying stream.
120 121 122 123 124 125 126
   *
   * @exception ClassNotFoundException The class that an object being
   * read in belongs to cannot be found.
   *
   * @exception IOException Exception from underlying
   * <code>InputStream</code>.
   */
127
  public final Object readObject() throws ClassNotFoundException, IOException
Tom Tromey committed
128 129
  {
    if (this.useSubclassMethod)
130
      return readObjectOverride();
Tom Tromey committed
131 132 133 134 135 136

    boolean was_deserializing;

    Object ret_val;
    was_deserializing = this.isDeserializing;

137
    boolean is_consumed = false;
138
    boolean old_mode = setBlockDataMode(false);
Tom Tromey committed
139 140 141

    this.isDeserializing = true;

142
    byte marker = this.realInputStream.readByte();
143 144 145

    depth += 2;

146
    if(dump) dumpElement("MARKER: 0x" + Integer.toHexString(marker) + " ");
Tom Tromey committed
147

148
    try
Tom Tromey committed
149
      {
150
	switch (marker)
Tom Tromey committed
151
	  {
152 153 154 155 156 157
	  case TC_ENDBLOCKDATA:
	    {
	      ret_val = null;
	      is_consumed = true;
	      break;
	    }
158

159 160 161
	  case TC_BLOCKDATA:
	  case TC_BLOCKDATALONG:
	    {
162
	      if (marker == TC_BLOCKDATALONG)
163
		{ if(dump) dumpElementln("BLOCKDATALONG"); }
164
	      else
165
		{ if(dump) dumpElementln("BLOCKDATA"); }
166 167
	      readNextBlock(marker);
	      throw new StreamCorruptedException("Unexpected blockData");
168 169 170 171
	    }

	  case TC_NULL:
	    {
172
	      if(dump) dumpElementln("NULL");
173 174 175
	      ret_val = null;
	      break;
	    }
176

177 178
	  case TC_REFERENCE:
	    {
179
	      if(dump) dumpElement("REFERENCE ");
180
	      Integer oid = new Integer(this.realInputStream.readInt());
181
	      if(dump) dumpElementln(Integer.toHexString(oid.intValue()));
182
	      ret_val = ((ObjectIdentityWrapper)
183
			 this.objectLookupTable.get(oid)).object;
184 185
	      break;
	    }
186

187 188
	  case TC_CLASS:
	    {
189
	      if(dump) dumpElementln("CLASS");
190 191 192
	      ObjectStreamClass osc = (ObjectStreamClass)readObject();
	      Class clazz = osc.forClass();
	      assignNewHandle(clazz);
193 194 195 196 197 198
	      ret_val = clazz;
	      break;
	    }

	  case TC_PROXYCLASSDESC:
	    {
199
	      if(dump) dumpElementln("PROXYCLASS");
200 201 202 203 204 205 206
	      int n_intf = this.realInputStream.readInt();
	      String[] intfs = new String[n_intf];
	      for (int i = 0; i < n_intf; i++)
		{
		  intfs[i] = this.realInputStream.readUTF();
		}
	      
207
	      boolean oldmode = setBlockDataMode(true);
208 209 210
	      Class cl = resolveProxyClass(intfs);
	      setBlockDataMode(oldmode);
	      
211
	      ObjectStreamClass osc = lookupClass(cl);
Mark Wielaard committed
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226
	      if (osc.firstNonSerializableParentConstructor == null)
		{
		  osc.realClassIsSerializable = true;
		  osc.fields = osc.fieldMapping = new ObjectStreamField[0];
		  try
		    {
		      osc.firstNonSerializableParentConstructor =
		        Object.class.getConstructor(new Class[0]);
		    }
		  catch (NoSuchMethodException x)
		    {
		      throw (InternalError)
			new InternalError("Object ctor missing").initCause(x);
		    }
		}
227
	      assignNewHandle(osc);
228 229 230
	      
	      if (!is_consumed)
		{
231
		  byte b = this.realInputStream.readByte();
232
		  if (b != TC_ENDBLOCKDATA)
233
		    throw new IOException("Data annotated to class was not consumed." + b);
234 235 236
		}
	      else
		is_consumed = false;
237 238
	      ObjectStreamClass superosc = (ObjectStreamClass)readObject();
	      osc.setSuperclass(superosc);
239 240 241 242 243 244
	      ret_val = osc;
	      break;
	    }

	  case TC_CLASSDESC:
	    {
245
	      ObjectStreamClass osc = readClassDescriptor();
246 247 248
	      
	      if (!is_consumed)
		{
249
		  byte b = this.realInputStream.readByte();
250
		  if (b != TC_ENDBLOCKDATA)
251
		    throw new IOException("Data annotated to class was not consumed." + b);
252 253 254 255
		}
	      else
		is_consumed = false;
	      
256
	      osc.setSuperclass ((ObjectStreamClass)readObject());
257 258 259
	      ret_val = osc;
	      break;
	    }
260

261 262 263
	  case TC_STRING:
	  case TC_LONGSTRING:
	    {
264
	      if(dump) dumpElement("STRING=");
265
	      String s = this.realInputStream.readUTF();
266 267
	      if(dump) dumpElementln(s);
	      ret_val = processResolution(null, s, assignNewHandle(s));
268 269 270 271 272
	      break;
	    }

	  case TC_ARRAY:
	    {
273
	      if(dump) dumpElementln("ARRAY");
274 275
	      ObjectStreamClass osc = (ObjectStreamClass)readObject();
	      Class componentType = osc.forClass().getComponentType();
276
	      if(dump) dumpElement("ARRAY LENGTH=");
277
	      int length = this.realInputStream.readInt();
278
	      if(dump) dumpElementln (length + "; COMPONENT TYPE=" + componentType);
279 280 281
	      Object array = Array.newInstance(componentType, length);
	      int handle = assignNewHandle(array);
	      readArrayElements(array, componentType);
282 283
	      if(dump)
	        for (int i = 0, len = Array.getLength(array); i < len; i++)
Bryce McKinlay committed
284
		  dumpElementln("  ELEMENT[" + i + "]=" + Array.get(array, i));
285
	      ret_val = processResolution(null, array, handle);
286 287 288 289 290
	      break;
	    }

	  case TC_OBJECT:
	    {
291
	      if(dump) dumpElementln("OBJECT");
292 293
	      ObjectStreamClass osc = (ObjectStreamClass)readObject();
	      Class clazz = osc.forClass();
294
	      
295
	      if (!osc.realClassIsSerializable)
296 297
		throw new NotSerializableException
		  (clazz + " is not Serializable, and thus cannot be deserialized.");
298
	      
299
	      if (osc.realClassIsExternalizable)
300
		{
301
		  Externalizable obj = osc.newInstance();
302
		  
303
		  int handle = assignNewHandle(obj);
304
		  
305
		  boolean read_from_blocks = ((osc.getFlags() & SC_BLOCK_DATA) != 0);
306 307 308
		  
		  boolean oldmode = this.readDataFromBlock;
		  if (read_from_blocks)
309
		    setBlockDataMode(true);
310
		  
311
		  obj.readExternal(this);
312 313
		  
		  if (read_from_blocks)
Bryce McKinlay committed
314 315 316 317 318 319
                    {
		      setBlockDataMode(oldmode);
                      if (!oldmode)
			if (this.realInputStream.readByte() != TC_ENDBLOCKDATA)
			    throw new IOException("No end of block data seen for class with readExternal (ObjectInputStream) method.");
                    }
320
		  
321
		  ret_val = processResolution(osc, obj, handle);
322
		  break;
323 324
		} // end if (osc.realClassIsExternalizable)

Bryce McKinlay committed
325
	      Object obj = newObject(clazz, osc.firstNonSerializableParentConstructor);
326
	      
327
	      int handle = assignNewHandle(obj);
328 329 330
	      Object prevObject = this.currentObject;
	      ObjectStreamClass prevObjectStreamClass = this.currentObjectStreamClass;
	      
331 332
	      this.currentObject = obj;
	      ObjectStreamClass[] hierarchy =
333
		inputGetObjectStreamClasses(clazz);
334
	      
335
	      for (int i = 0; i < hierarchy.length; i++)
336 337 338
		{
		  this.currentObjectStreamClass = hierarchy[i];
		  
339
		  if(dump) dumpElementln("Reading fields of " + this.currentObjectStreamClass.getName ());
340 341 342 343 344

		  // XXX: should initialize fields in classes in the hierarchy
		  // that aren't in the stream
		  // should skip over classes in the stream that aren't in the
		  // real classes hierarchy
345
		  
346 347
		  Method readObjectMethod = this.currentObjectStreamClass.readObjectMethod;
		  if (readObjectMethod != null)
348
		    {
349
		      fieldsAlreadyRead = false;
350
		      boolean oldmode = setBlockDataMode(true);
351
		      callReadMethod(readObjectMethod, this.currentObjectStreamClass.forClass(), obj);
352
		      setBlockDataMode(oldmode);
353 354 355 356 357 358 359 360
		    }
		  else
		    {
		      readFields(obj, currentObjectStreamClass);
		    }

		  if (this.currentObjectStreamClass.hasWriteMethod())
		    {
361
		      if(dump) dumpElement("ENDBLOCKDATA? ");
362 363
		      try
			{
364 365 366 367 368
			  // FIXME: XXX: This try block is to
			  // catch EOF which is thrown for some
			  // objects.  That indicates a bug in
			  // the logic.

369 370 371
			  if (this.realInputStream.readByte() != TC_ENDBLOCKDATA)
			    throw new IOException
			      ("No end of block data seen for class with readObject (ObjectInputStream) method.");
372
			  if(dump) dumpElementln("yes");
373
			}
374 375 376 377
// 		      catch (EOFException e)
// 			{
// 			  if(dump) dumpElementln("no, got EOFException");
// 			}
378 379
		      catch (IOException e)
			{
380
			  if(dump) dumpElementln("no, got IOException");
381 382 383 384
			}
		    }
		}

385 386
	      this.currentObject = prevObject;
	      this.currentObjectStreamClass = prevObjectStreamClass;
387
	      ret_val = processResolution(osc, obj, handle);
388
		  
389 390
	      break;
	    }
391

392
	  case TC_RESET:
393
	    if(dump) dumpElementln("RESET");
394 395
	    clearHandles();
	    ret_val = readObject();
396
	    break;
397

398 399
	  case TC_EXCEPTION:
	    {
400
	      if(dump) dumpElement("EXCEPTION=");
401
	      Exception e = (Exception)readObject();
402
	      if(dump) dumpElementln(e.toString());
403 404
	      clearHandles();
	      throw new WriteAbortedException("Exception thrown during writing of stream", e);
405
	    }
406

407
	  default:
408
	    throw new IOException("Unknown marker on stream: " + marker);
Tom Tromey committed
409 410
	  }
      }
411
    finally
Tom Tromey committed
412
      {
413
	setBlockDataMode(old_mode);
414 415 416
	
	this.isDeserializing = was_deserializing;
	
417 418
	depth -= 2;
	
419 420
	if (! was_deserializing)
	  {
421 422
	    if (validators.size() > 0)
	      invokeValidators();
423
	  }
Tom Tromey committed
424
      }
425
    
Tom Tromey committed
426 427 428
    return ret_val;
  }

429
  /**
430 431 432 433 434 435 436 437 438 439 440 441 442 443
   * This method makes a partial check of types for the fields
   * contained given in arguments. It checks primitive types of
   * fields1 against non primitive types of fields2. This method 
   * assumes the two lists has already been sorted according to 
   * the Java specification.
   *
   * @param name Name of the class owning the given fields.
   * @param fields1 First list to check.
   * @param fields2 Second list to check.
   * @throws InvalidClassException if a field in fields1, which has a primitive type, is a present
   * in the non primitive part in fields2.
   */
  private void checkTypeConsistency(String name, ObjectStreamField[] fields1, ObjectStreamField[] fields2)
    throws InvalidClassException
444
  {
445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488
    int nonPrimitive = 0;
    
    for (nonPrimitive = 0; 
	 nonPrimitive < fields1.length
	   && fields1[nonPrimitive].isPrimitive(); nonPrimitive++)
      {
      }

    if (nonPrimitive == fields1.length)
      return;
    
    int i = 0;
    ObjectStreamField f1;
    ObjectStreamField f2;
    
    while (i < fields2.length
	   && nonPrimitive < fields1.length)
      {
	f1 = fields1[nonPrimitive];
	f2 = fields2[i];
	
	if (!f2.isPrimitive())
	  break;

	int compVal = f1.getName().compareTo (f2.getName());

	if (compVal < 0)
	  {
	    nonPrimitive++;
	  }
	else if (compVal > 0)
	  {
	    i++;
	  }
	else
	  {
	    throw new InvalidClassException
	      ("invalid field type for " + f2.getName() +
	       " in class " + name);
	  }
      }
  }

  /**
489 490 491 492 493 494 495 496 497 498 499 500 501 502 503
   * This method reads a class descriptor from the real input stream
   * and use these data to create a new instance of ObjectStreamClass.
   * Fields are sorted and ordered for the real read which occurs for
   * each instance of the described class. Be aware that if you call that
   * method you must ensure that the stream is synchronized, in the other
   * case it may be completely desynchronized.
   *
   * @return A new instance of ObjectStreamClass containing the freshly
   * created descriptor.
   * @throws ClassNotFoundException if the required class to build the
   * descriptor has not been found in the system.
   * @throws IOException An input/output error occured.
   * @throws InvalidClassException If there was a compatibility problem
   * between the class present in the system and the serialized class.
   */
504
  protected ObjectStreamClass readClassDescriptor()
505 506
    throws ClassNotFoundException, IOException
  {
507
    if(dump) dumpElement("CLASSDESC NAME=");
508
    String name = this.realInputStream.readUTF();
509
    if(dump) dumpElement(name + "; UID=");
510
    long uid = this.realInputStream.readLong ();
511
    if(dump) dumpElement(Long.toHexString(uid) + "; FLAGS=");
512
    byte flags = this.realInputStream.readByte ();
513
    if(dump) dumpElement(Integer.toHexString(flags) + "; FIELD COUNT=");
514
    short field_count = this.realInputStream.readShort();
515
    if(dump) dumpElementln(Short.toString(field_count));
516
    ObjectStreamField[] fields = new ObjectStreamField[field_count];
517 518 519
    ObjectStreamClass osc = new ObjectStreamClass(name, uid,
						  flags, fields);
    assignNewHandle(osc);
520

Bryce McKinlay committed
521 522
    if (callersClassLoader == null)
      callersClassLoader = currentLoader();
523
	      
524
    for (int i = 0; i < field_count; i++)
525
      {
526
	if(dump) dumpElement("  TYPE CODE=");
527
	char type_code = (char)this.realInputStream.readByte();
528
	if(dump) dumpElement(type_code + "; FIELD NAME=");
529
	String field_name = this.realInputStream.readUTF();
530
	if(dump) dumpElementln(field_name);
531 532
	String class_name;
		  
533 534 535 536
	// If the type code is an array or an object we must
	// decode a String here. In the other case we convert
	// the type code and pass it to ObjectStreamField.
	// Type codes are decoded by gnu.java.lang.reflect.TypeSignature.
537
	if (type_code == 'L' || type_code == '[')
538
	  class_name = (String)readObject();
539
	else
540
	  class_name = String.valueOf(type_code);
541 542
		  
	fields[i] =
Bryce McKinlay committed
543
	  new ObjectStreamField(field_name, class_name, callersClassLoader);
544 545
      }
	      
546 547
    /* Now that fields have been read we may resolve the class
     * (and read annotation if needed). */
Bryce McKinlay committed
548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576
    Class clazz;
    try
      {
	clazz = resolveClass(osc);
      }
    catch (ClassNotFoundException cnfe)
      {
	// Maybe it was an primitive class?
	if (name.equals("void"))
	  clazz = Void.TYPE;
	else if (name.equals("boolean"))
	  clazz = Boolean.TYPE;
	else if (name.equals("byte"))
	  clazz = Byte.TYPE;
	else if (name.equals("short"))
	  clazz = Short.TYPE;
	else if (name.equals("char"))
	  clazz = Character.TYPE;
	else if (name.equals("int"))
	  clazz = Integer.TYPE;
	else if (name.equals("long"))
	  clazz = Long.TYPE;
	else if (name.equals("float"))
	  clazz = Float.TYPE;
	else if (name.equals("double"))
	  clazz = Double.TYPE;
	else
	  throw cnfe;
      }
577 578 579 580 581 582 583 584 585

    boolean oldmode = setBlockDataMode(true);
    osc.setClass(clazz, lookupClass(clazz.getSuperclass()));
    classLookupTable.put(clazz, osc);
    setBlockDataMode(oldmode);

    // find the first non-serializable, non-abstract
    // class in clazz's inheritance hierarchy
    Class first_nonserial = clazz.getSuperclass();
Bryce McKinlay committed
586 587 588 589 590 591 592 593 594
    // Maybe it is a primitive class, those don't have a super class,
    // or Object itself.  Otherwise we can keep getting the superclass
    // till we hit the Object class, or some other non-serializable class.

    if (first_nonserial == null)
      first_nonserial = clazz;
    else
      while (Serializable.class.isAssignableFrom(first_nonserial)
	     || Modifier.isAbstract(first_nonserial.getModifiers()))
595 596
	first_nonserial = first_nonserial.getSuperclass();

Bryce McKinlay committed
597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619
    final Class local_constructor_class = first_nonserial;

    osc.firstNonSerializableParentConstructor =
        (Constructor)AccessController.doPrivileged(new PrivilegedAction()
          {
            public Object run()
            {
              try
                {
                  Constructor c = local_constructor_class.
                                    getDeclaredConstructor(new Class[0]);
                  if (Modifier.isPrivate(c.getModifiers()))
                    return null;
                  return c;
                }
              catch (NoSuchMethodException e)
                {
                  // error will be reported later, in newObject()
                  return null;
                }
            }
          });

620 621 622 623
    osc.realClassIsSerializable = Serializable.class.isAssignableFrom(clazz);
    osc.realClassIsExternalizable = Externalizable.class.isAssignableFrom(clazz);

    ObjectStreamField[] stream_fields = osc.fields;
Bryce McKinlay committed
624
    ObjectStreamField[] real_fields = ObjectStreamClass.lookupForClassObject(clazz).fields;
625 626 627 628 629 630
    ObjectStreamField[] fieldmapping = new ObjectStreamField[2 * Math.max(stream_fields.length, real_fields.length)];

    int stream_idx = 0;
    int real_idx = 0;
    int map_idx = 0;

631 632 633 634 635 636 637 638 639
    /*
     * Check that there is no type inconsistencies between the lists.
     * A special checking must be done for the two groups: primitive types and
     * not primitive types. 
     */
    checkTypeConsistency(name, real_fields, stream_fields);
    checkTypeConsistency(name, stream_fields, real_fields);

    
640 641
    while (stream_idx < stream_fields.length
	   || real_idx < real_fields.length)
642
      {
643 644 645 646
	ObjectStreamField stream_field = null;
	ObjectStreamField real_field = null;

	if (stream_idx == stream_fields.length)
647
	  {
648
	    real_field = real_fields[real_idx++];
649
	  }
650
	else if (real_idx == real_fields.length)
651
	  {
652
	    stream_field = stream_fields[stream_idx++];
653
	  }
654 655 656
	else
	  {
	    int comp_val =
657
	      real_fields[real_idx].compareTo (stream_fields[stream_idx]);
658

659 660 661 662 663 664 665 666 667 668 669 670
	    if (comp_val < 0)
	      {
		real_field = real_fields[real_idx++];
	      }
	    else if (comp_val > 0)
	      {
		stream_field = stream_fields[stream_idx++];
	      }
	    else
	      {
		stream_field = stream_fields[stream_idx++];
		real_field = real_fields[real_idx++];
671 672 673 674
		if (stream_field.getType() != real_field.getType())
		  throw new InvalidClassException
		    ("invalid field type for " + real_field.getName() +
		     " in class " + name);
675 676
	      }
	  }
677

678 679 680 681 682 683 684 685
	/* If some of stream_fields does not correspond to any of real_fields,
	 * or the opposite, then fieldmapping will go short.
	 */
	if (map_idx == fieldmapping.length)
	  {
	    ObjectStreamField[] newfieldmapping =
	      new ObjectStreamField[fieldmapping.length + 2];
	    System.arraycopy(fieldmapping, 0,
686
			     newfieldmapping, 0, fieldmapping.length);
687 688
	    fieldmapping = newfieldmapping;
	  }
689 690 691
	fieldmapping[map_idx++] = stream_field;
	fieldmapping[map_idx++] = real_field;
      }
692
    osc.fieldMapping = fieldmapping;
693

694 695 696
    return osc;
  }

Tom Tromey committed
697
  /**
698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715
   * Reads the current objects non-transient, non-static fields from
   * the current class from the underlying output stream.
   *
   * This method is intended to be called from within a object's
   * <code>private void readObject (ObjectInputStream)</code>
   * method.
   *
   * @exception ClassNotFoundException The class that an object being
   * read in belongs to cannot be found.
   *
   * @exception NotActiveException This method was called from a
   * context other than from the current object's and current class's
   * <code>private void readObject (ObjectInputStream)</code>
   * method.
   *
   * @exception IOException Exception from underlying
   * <code>OutputStream</code>.
   */
716
  public void defaultReadObject()
Tom Tromey committed
717 718 719
    throws ClassNotFoundException, IOException, NotActiveException
  {
    if (this.currentObject == null || this.currentObjectStreamClass == null)
720 721
      throw new NotActiveException("defaultReadObject called by non-active"
				   + " class and/or object");
Tom Tromey committed
722 723

    if (fieldsAlreadyRead)
724 725 726
      throw new NotActiveException("defaultReadObject called but fields "
				   + "already read from stream (by "
				   + "defaultReadObject or readFields)");
Tom Tromey committed
727

728
    boolean oldmode = setBlockDataMode(false);
729
    readFields(this.currentObject, this.currentObjectStreamClass);
730
    setBlockDataMode(oldmode);
Tom Tromey committed
731 732 733 734 735 736

    fieldsAlreadyRead = true;
  }


  /**
737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753
   * Registers a <code>ObjectInputValidation</code> to be carried out
   * on the object graph currently being deserialized before it is
   * returned to the original caller of <code>readObject ()</code>.
   * The order of validation for multiple
   * <code>ObjectInputValidation</code>s can be controled using
   * <code>priority</code>.  Validators with higher priorities are
   * called first.
   *
   * @see java.io.ObjectInputValidation
   *
   * @exception InvalidObjectException <code>validator</code> is
   * <code>null</code>
   *
   * @exception NotActiveException an attempt was made to add a
   * validator outside of the <code>readObject</code> method of the
   * object currently being deserialized
   */
754 755
  public void registerValidation(ObjectInputValidation validator,
				 int priority)
Tom Tromey committed
756 757 758
    throws InvalidObjectException, NotActiveException
  {
    if (this.currentObject == null || this.currentObjectStreamClass == null)
759 760
      throw new NotActiveException("registerValidation called by non-active "
				   + "class and/or object");
Tom Tromey committed
761 762

    if (validator == null)
763 764
      throw new InvalidObjectException("attempt to add a null "
				       + "ObjectInputValidation object");
Tom Tromey committed
765

766 767
    this.validators.addElement(new ValidatorAndPriority (validator,
							 priority));
Tom Tromey committed
768 769 770 771
  }


  /**
772 773 774 775 776 777 778 779 780 781 782 783 784 785 786
   * Called when a class is being deserialized.  This is a hook to
   * allow subclasses to read in information written by the
   * <code>annotateClass (Class)</code> method of an
   * <code>ObjectOutputStream</code>.
   *
   * This implementation looks up the active call stack for a
   * <code>ClassLoader</code>; if a <code>ClassLoader</code> is found,
   * it is used to load the class associated with <code>osc</code>,
   * otherwise, the default system <code>ClassLoader</code> is used.
   *
   * @exception IOException Exception from underlying
   * <code>OutputStream</code>.
   *
   * @see java.io.ObjectOutputStream#annotateClass (java.lang.Class)
   */
787
  protected Class resolveClass(ObjectStreamClass osc)
Tom Tromey committed
788 789
    throws ClassNotFoundException, IOException
  {
Bryce McKinlay committed
790 791 792 793 794 795 796 797 798
    if (callersClassLoader == null)
      {
	callersClassLoader = currentLoader ();
	if (Configuration.DEBUG && dump)
	  {
	    dumpElementln ("CallersClassLoader = " + callersClassLoader);
	  }
      }

799
    return Class.forName(osc.getName(), true, callersClassLoader);
800 801
  }

802
  /**
803 804
   * Returns the most recent user defined ClassLoader on the execution stack
   * or null if none is found.
805
   */
806 807
  // GCJ LOCAL: native method.
  private native ClassLoader currentLoader();
Tom Tromey committed
808

809 810 811 812 813 814 815 816 817 818 819
  /**
   * Lookup a class stored in the local hashtable. If it is not
   * use the global lookup function in ObjectStreamClass to build
   * the ObjectStreamClass. This method is requested according to
   * the behaviour detected in the JDK by Kaffe's team.
   *
   * @param clazz Class to lookup in the hash table or for which
   * we must build a descriptor.
   * @return A valid instance of ObjectStreamClass corresponding
   * to the specified class.
   */
820
  private ObjectStreamClass lookupClass(Class clazz)
821
  {
Bryce McKinlay committed
822 823
    if (clazz == null)
      return null;
Tom Tromey committed
824

Bryce McKinlay committed
825
    ObjectStreamClass oclazz;
826
    oclazz = (ObjectStreamClass)classLookupTable.get(clazz);
827
    if (oclazz == null)
828
      return ObjectStreamClass.lookup(clazz);
829
    else
830 831 832 833 834 835 836
      return oclazz;
  }

  /**
   * Reconstruct class hierarchy the same way
   * {@link java.io.ObjectStreamClass.getObjectStreamClasses(java.lang.Class)} does
   * but using lookupClass instead of ObjectStreamClass.lookup. This
837 838
   * dup is necessary localize the lookup table. Hopefully some future
   * rewritings will be able to prevent this.
839 840 841 842 843 844
   *
   * @param clazz This is the class for which we want the hierarchy.
   *
   * @return An array of valid {@link java.io.ObjectStreamClass} instances which
   * represent the class hierarchy for clazz.
   */
845
  private ObjectStreamClass[] inputGetObjectStreamClasses(Class clazz)
846
  {
847
    ObjectStreamClass osc = lookupClass(clazz);
848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868

    if (osc == null)
      return new ObjectStreamClass[0];
    else
      {
        Vector oscs = new Vector();

        while (osc != null)
          {
            oscs.addElement(osc);
            osc = osc.getSuper();
	  }

        int count = oscs.size();
	ObjectStreamClass[] sorted_oscs = new ObjectStreamClass[count];

        for (int i = count - 1; i >= 0; i--)
          sorted_oscs[count - i - 1] = (ObjectStreamClass) oscs.elementAt(i);

        return sorted_oscs;
      }
Tom Tromey committed
869 870 871
  }

  /**
872 873 874 875 876 877 878 879 880 881 882 883
   * Allows subclasses to resolve objects that are read from the
   * stream with other objects to be returned in their place.  This
   * method is called the first time each object is encountered.
   *
   * This method must be enabled before it will be called in the
   * serialization process.
   *
   * @exception IOException Exception from underlying
   * <code>OutputStream</code>.
   *
   * @see #enableResolveObject(boolean)
   */
884
  protected Object resolveObject(Object obj) throws IOException
Tom Tromey committed
885 886 887 888 889
  {
    return obj;
  }


890
  protected Class resolveProxyClass(String[] intfs)
891 892
    throws IOException, ClassNotFoundException
  {
893
    ClassLoader cl = currentLoader();
894 895
    
    Class[] clss = new Class[intfs.length];
896 897 898 899 900 901
    if(cl == null)
      {
	for (int i = 0; i < intfs.length; i++)
	  clss[i] = Class.forName(intfs[i]);
	cl = ClassLoader.getSystemClassLoader();
      }
902 903 904
    else
      for (int i = 0; i < intfs.length; i++)
	clss[i] = cl.loadClass(intfs[i]);
905 906 907 908 909 910 911 912
    try 
      {
	return Proxy.getProxyClass(cl, clss);
      } 
    catch (IllegalArgumentException e) 
      {
	throw new ClassNotFoundException(null, e);
      }
913 914
  }
  
Tom Tromey committed
915
  /**
916 917 918 919 920 921 922
   * If <code>enable</code> is <code>true</code> and this object is
   * trusted, then <code>resolveObject (Object)</code> will be called
   * in subsequent calls to <code>readObject (Object)</code>.
   * Otherwise, <code>resolveObject (Object)</code> will not be called.
   *
   * @exception SecurityException This class is not trusted.
   */
Tom Tromey committed
923 924 925 926
  protected boolean enableResolveObject (boolean enable)
    throws SecurityException
  {
    if (enable)
927
      {
928
	SecurityManager sm = System.getSecurityManager();
929
	if (sm != null)
930
	  sm.checkPermission(new SerializablePermission("enableSubstitution"));
931
      }
Tom Tromey committed
932 933 934 935 936 937 938

    boolean old_val = this.resolveEnabled;
    this.resolveEnabled = enable;
    return old_val;
  }

  /**
939 940 941 942 943 944 945 946
   * Reads stream magic and stream version information from the
   * underlying stream.
   *
   * @exception IOException Exception from underlying stream.
   *
   * @exception StreamCorruptedException An invalid stream magic
   * number or stream version was read from the stream.
   */
947
  protected void readStreamHeader()
Tom Tromey committed
948 949
    throws IOException, StreamCorruptedException
  {
950
    if(dump) dumpElement("STREAM MAGIC ");
951 952
    if (this.realInputStream.readShort() != STREAM_MAGIC)
      throw new StreamCorruptedException("Invalid stream magic number");
Tom Tromey committed
953

954
    if(dump) dumpElementln("STREAM VERSION ");
955 956
    if (this.realInputStream.readShort() != STREAM_VERSION)
      throw new StreamCorruptedException("Invalid stream version number");
Tom Tromey committed
957 958
  }

959
  public int read() throws IOException
Tom Tromey committed
960 961
  {
    if (this.readDataFromBlock)
962 963
      {
	if (this.blockDataPosition >= this.blockDataBytes)
964
	  readNextBlock();
965 966
	return (this.blockData[this.blockDataPosition++] & 0xff);
      }
Tom Tromey committed
967
    else
968
      return this.realInputStream.read();
Tom Tromey committed
969 970
  }

971
  public int read(byte[] data, int offset, int length) throws IOException
Tom Tromey committed
972 973
  {
    if (this.readDataFromBlock)
974 975 976 977 978 979
      {
	if (this.blockDataPosition + length > this.blockDataBytes)
	  {
	    int remain = this.blockDataBytes - this.blockDataPosition;
	    if (remain != 0)
	      {
980 981
		System.arraycopy(this.blockData, this.blockDataPosition,
				 data, offset, remain);
982 983 984 985 986
		offset += remain;
		length -= remain;
	      }
	    readNextBlock ();
	  }
Tom Tromey committed
987

988 989
	System.arraycopy(this.blockData, this.blockDataPosition,
			 data, offset, length);
990
	this.blockDataPosition += length;
991

992 993
	return length;
      }
Tom Tromey committed
994
    else
995
      return this.realInputStream.read(data, offset, length);
Tom Tromey committed
996 997
  }

998
  public int available() throws IOException
Tom Tromey committed
999 1000
  {
    if (this.readDataFromBlock)
1001 1002 1003
      {
	if (this.blockDataPosition >= this.blockDataBytes)
	  readNextBlock ();
Tom Tromey committed
1004

1005 1006
	return this.blockDataBytes - this.blockDataPosition;
      }
Tom Tromey committed
1007
    else
1008
      return this.realInputStream.available();
Tom Tromey committed
1009 1010
  }

1011
  public void close() throws IOException
Tom Tromey committed
1012
  {
1013
    this.realInputStream.close();
Tom Tromey committed
1014 1015
  }

1016
  public boolean readBoolean() throws IOException
Tom Tromey committed
1017
  {
1018 1019 1020 1021 1022 1023 1024 1025 1026 1027
    boolean switchmode = true;
    boolean oldmode = this.readDataFromBlock;
    if (!oldmode || this.blockDataBytes - this.blockDataPosition >= 1)
      switchmode = false;
    if (switchmode)
      oldmode = setBlockDataMode (true);
    boolean value = this.dataInputStream.readBoolean ();
    if (switchmode)
      setBlockDataMode (oldmode);
    return value;
Tom Tromey committed
1028 1029
  }

1030
  public byte readByte() throws IOException
Tom Tromey committed
1031
  {
1032 1033 1034 1035 1036
    boolean switchmode = true;
    boolean oldmode = this.readDataFromBlock;
    if (!oldmode || this.blockDataBytes - this.blockDataPosition >= 1)
      switchmode = false;
    if (switchmode)
1037 1038
      oldmode = setBlockDataMode(true);
    byte value = this.dataInputStream.readByte();
1039
    if (switchmode)
1040
      setBlockDataMode(oldmode);
1041
    return value;
Tom Tromey committed
1042 1043
  }

1044
  public int readUnsignedByte() throws IOException
Tom Tromey committed
1045
  {
1046 1047 1048 1049 1050
    boolean switchmode = true;
    boolean oldmode = this.readDataFromBlock;
    if (!oldmode || this.blockDataBytes - this.blockDataPosition >= 1)
      switchmode = false;
    if (switchmode)
1051 1052
      oldmode = setBlockDataMode(true);
    int value = this.dataInputStream.readUnsignedByte();
1053
    if (switchmode)
1054
      setBlockDataMode(oldmode);
1055
    return value;
Tom Tromey committed
1056 1057
  }

1058
  public short readShort() throws IOException
Tom Tromey committed
1059
  {
1060 1061 1062 1063 1064
    boolean switchmode = true;
    boolean oldmode = this.readDataFromBlock;
    if (!oldmode || this.blockDataBytes - this.blockDataPosition >= 2)
      switchmode = false;
    if (switchmode)
1065 1066
      oldmode = setBlockDataMode(true);
    short value = this.dataInputStream.readShort();
1067
    if (switchmode)
1068
      setBlockDataMode(oldmode);
1069
    return value;
Tom Tromey committed
1070 1071
  }

1072
  public int readUnsignedShort() throws IOException
Tom Tromey committed
1073
  {
1074 1075 1076 1077 1078
    boolean switchmode = true;
    boolean oldmode = this.readDataFromBlock;
    if (!oldmode || this.blockDataBytes - this.blockDataPosition >= 2)
      switchmode = false;
    if (switchmode)
1079 1080
      oldmode = setBlockDataMode(true);
    int value = this.dataInputStream.readUnsignedShort();
1081
    if (switchmode)
1082
      setBlockDataMode(oldmode);
1083
    return value;
Tom Tromey committed
1084 1085
  }

1086
  public char readChar() throws IOException
Tom Tromey committed
1087
  {
1088 1089 1090 1091 1092
    boolean switchmode = true;
    boolean oldmode = this.readDataFromBlock;
    if (!oldmode || this.blockDataBytes - this.blockDataPosition >= 2)
      switchmode = false;
    if (switchmode)
1093 1094
      oldmode = setBlockDataMode(true);
    char value = this.dataInputStream.readChar();
1095
    if (switchmode)
1096
      setBlockDataMode(oldmode);
1097
    return value;
Tom Tromey committed
1098 1099
  }

1100
  public int readInt() throws IOException
Tom Tromey committed
1101
  {
1102 1103 1104 1105 1106
    boolean switchmode = true;
    boolean oldmode = this.readDataFromBlock;
    if (!oldmode || this.blockDataBytes - this.blockDataPosition >= 4)
      switchmode = false;
    if (switchmode)
1107 1108
      oldmode = setBlockDataMode(true);
    int value = this.dataInputStream.readInt();
1109
    if (switchmode)
1110
      setBlockDataMode(oldmode);
1111
    return value;
Tom Tromey committed
1112 1113
  }

1114
  public long readLong() throws IOException
Tom Tromey committed
1115
  {
1116 1117 1118 1119 1120
    boolean switchmode = true;
    boolean oldmode = this.readDataFromBlock;
    if (!oldmode || this.blockDataBytes - this.blockDataPosition >= 8)
      switchmode = false;
    if (switchmode)
1121 1122
      oldmode = setBlockDataMode(true);
    long value = this.dataInputStream.readLong();
1123
    if (switchmode)
1124
      setBlockDataMode(oldmode);
1125
    return value;
Tom Tromey committed
1126 1127
  }

1128
  public float readFloat() throws IOException
Tom Tromey committed
1129
  {
1130 1131 1132 1133 1134
    boolean switchmode = true;
    boolean oldmode = this.readDataFromBlock;
    if (!oldmode || this.blockDataBytes - this.blockDataPosition >= 4)
      switchmode = false;
    if (switchmode)
1135 1136
      oldmode = setBlockDataMode(true);
    float value = this.dataInputStream.readFloat();
1137
    if (switchmode)
1138
      setBlockDataMode(oldmode);
1139
    return value;
Tom Tromey committed
1140 1141
  }

1142
  public double readDouble() throws IOException
Tom Tromey committed
1143
  {
1144 1145 1146 1147 1148
    boolean switchmode = true;
    boolean oldmode = this.readDataFromBlock;
    if (!oldmode || this.blockDataBytes - this.blockDataPosition >= 8)
      switchmode = false;
    if (switchmode)
1149 1150
      oldmode = setBlockDataMode(true);
    double value = this.dataInputStream.readDouble();
1151
    if (switchmode)
1152
      setBlockDataMode(oldmode);
1153
    return value;
Tom Tromey committed
1154 1155
  }

1156
  public void readFully(byte data[]) throws IOException
Tom Tromey committed
1157
  {
1158
    this.dataInputStream.readFully(data);
Tom Tromey committed
1159 1160
  }

1161
  public void readFully(byte data[], int offset, int size)
Tom Tromey committed
1162 1163
    throws IOException
  {
1164
    this.dataInputStream.readFully(data, offset, size);
Tom Tromey committed
1165 1166
  }

1167
  public int skipBytes(int len) throws IOException
Tom Tromey committed
1168
  {
1169
    return this.dataInputStream.skipBytes(len);
Tom Tromey committed
1170 1171 1172
  }

  /**
1173 1174 1175
   * @deprecated
   * @see java.io.DataInputStream#readLine ()
   */
1176
  public String readLine() throws IOException
Tom Tromey committed
1177
  {
1178
    return this.dataInputStream.readLine();
Tom Tromey committed
1179 1180
  }

1181
  public String readUTF() throws IOException
Tom Tromey committed
1182
  {
1183
    return this.dataInputStream.readUTF();
Tom Tromey committed
1184 1185 1186
  }

  /**
1187 1188 1189 1190 1191
   * This class allows a class to specify exactly which fields should
   * be read, and what values should be read for these fields.
   *
   * XXX: finish up comments
   */
1192
  public abstract static class GetField
Tom Tromey committed
1193
  {
1194
    public abstract ObjectStreamClass getObjectStreamClass();
Tom Tromey committed
1195

1196
    public abstract boolean defaulted(String name)
Tom Tromey committed
1197 1198
      throws IOException, IllegalArgumentException;

1199
    public abstract boolean get(String name, boolean defvalue)
Tom Tromey committed
1200 1201
      throws IOException, IllegalArgumentException;

1202
    public abstract char get(String name, char defvalue)
Tom Tromey committed
1203 1204
      throws IOException, IllegalArgumentException;

1205
    public abstract byte get(String name, byte defvalue)
Tom Tromey committed
1206 1207
      throws IOException, IllegalArgumentException;

1208
    public abstract short get(String name, short defvalue)
Tom Tromey committed
1209 1210
      throws IOException, IllegalArgumentException;

1211
    public abstract int get(String name, int defvalue)
Tom Tromey committed
1212 1213
      throws IOException, IllegalArgumentException;

1214
    public abstract long get(String name, long defvalue)
Tom Tromey committed
1215 1216
      throws IOException, IllegalArgumentException;

1217
    public abstract float get(String name, float defvalue)
Tom Tromey committed
1218 1219
      throws IOException, IllegalArgumentException;

1220
    public abstract double get(String name, double defvalue)
Tom Tromey committed
1221 1222
      throws IOException, IllegalArgumentException;

1223
    public abstract Object get(String name, Object defvalue)
Tom Tromey committed
1224 1225 1226
      throws IOException, IllegalArgumentException;
  }

1227 1228 1229 1230 1231
  /**
   * This method should be called by a method called 'readObject' in the
   * deserializing class (if present). It cannot (and should not)be called
   * outside of it. Its goal is to read all fields in the real input stream
   * and keep them accessible through the {@link #GetField} class. Calling
1232
   * this method will not alter the deserializing object.
1233 1234 1235 1236 1237 1238 1239
   *
   * @return A valid freshly created 'GetField' instance to get access to
   * the deserialized stream.
   * @throws IOException An input/output exception occured. 
   * @throws ClassNotFoundException 
   * @throws NotActiveException
   */
1240
  public GetField readFields()
Tom Tromey committed
1241 1242 1243
    throws IOException, ClassNotFoundException, NotActiveException
  {
    if (this.currentObject == null || this.currentObjectStreamClass == null)
1244
      throw new NotActiveException("readFields called by non-active class and/or object");
Tom Tromey committed
1245

1246 1247 1248
    if (prereadFields != null)
      return prereadFields;

Tom Tromey committed
1249
    if (fieldsAlreadyRead)
1250 1251
      throw new NotActiveException("readFields called but fields already read from"
				   + " stream (by defaultReadObject or readFields)");
Tom Tromey committed
1252 1253 1254 1255

    final ObjectStreamClass clazz = this.currentObjectStreamClass;
    final byte[] prim_field_data = new byte[clazz.primFieldSize];
    final Object[] objs = new Object[clazz.objectFieldCount];
1256 1257 1258 1259

    // Apparently Block data is not used with GetField as per
    // empirical evidence against JDK 1.2.  Also see Mauve test
    // java.io.ObjectInputOutput.Test.GetPutField.
1260 1261
    boolean oldmode = setBlockDataMode(false);
    readFully(prim_field_data);
Tom Tromey committed
1262
    for (int i = 0; i < objs.length; ++ i)
1263 1264
      objs[i] = readObject();
    setBlockDataMode(oldmode);
Tom Tromey committed
1265

1266
    prereadFields = new GetField()
Tom Tromey committed
1267
      {
1268
	public ObjectStreamClass getObjectStreamClass()
1269 1270 1271
	{
	  return clazz;
	}
Tom Tromey committed
1272

1273
	public boolean defaulted(String name)
1274 1275
	  throws IOException, IllegalArgumentException
	{
1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298
	  ObjectStreamField f = clazz.getField(name);
	  
	  /* First if we have a serialized field use the descriptor */
	  if (f != null)
	    {
	      /* It is in serialPersistentFields but setClass tells us
	       * it should not be set. This value is defaulted.
	       */
	      if (f.isPersistent() && !f.isToSet())
		return true;
	      
	      return false;
	    }

	  /* This is not a serialized field. There should be
	   * a default value only if the field really exists.
	   */
	  try
	    {
	      return (clazz.forClass().getDeclaredField (name) != null);
	    }
	  catch (NoSuchFieldException e)
	    {
1299
	      throw new IllegalArgumentException(e.getMessage());
1300
	    }
1301
	}
Tom Tromey committed
1302

1303
	public boolean get(String name, boolean defvalue)
1304 1305
	  throws IOException, IllegalArgumentException
	{
1306
	  ObjectStreamField field = getField(name, Boolean.TYPE);
Tom Tromey committed
1307

1308 1309
	  if (field == null)
	    return defvalue;
Tom Tromey committed
1310

1311
	  return prim_field_data[field.getOffset()] == 0 ? false : true;
1312
	}
Tom Tromey committed
1313

1314
	public char get(String name, char defvalue)
1315 1316
	  throws IOException, IllegalArgumentException
	{
1317
	  ObjectStreamField field = getField(name, Character.TYPE);
Tom Tromey committed
1318

1319 1320
	  if (field == null)
	    return defvalue;
Tom Tromey committed
1321

1322
	  int off = field.getOffset();
Tom Tromey committed
1323

1324 1325 1326
	  return (char)(((prim_field_data[off++] & 0xFF) << 8)
			| (prim_field_data[off] & 0xFF));
	}
Tom Tromey committed
1327

1328
	public byte get(String name, byte defvalue)
1329 1330
	  throws IOException, IllegalArgumentException
	{
1331
	  ObjectStreamField field = getField(name, Byte.TYPE);
Tom Tromey committed
1332

1333 1334
	  if (field == null)
	    return defvalue;
Tom Tromey committed
1335

1336
	  return prim_field_data[field.getOffset()];
1337
	}
Tom Tromey committed
1338

1339
	public short get(String name, short defvalue)
1340 1341
	  throws IOException, IllegalArgumentException
	{
1342
	  ObjectStreamField field = getField(name, Short.TYPE);
Tom Tromey committed
1343

1344 1345
	  if (field == null)
	    return defvalue;
Tom Tromey committed
1346

1347
	  int off = field.getOffset();
Tom Tromey committed
1348

1349 1350 1351
	  return (short)(((prim_field_data[off++] & 0xFF) << 8)
			 | (prim_field_data[off] & 0xFF));
	}
Tom Tromey committed
1352

1353
	public int get(String name, int defvalue)
1354 1355
	  throws IOException, IllegalArgumentException
	{
1356
	  ObjectStreamField field = getField(name, Integer.TYPE);
Tom Tromey committed
1357

1358 1359
	  if (field == null)
	    return defvalue;
Tom Tromey committed
1360

1361
	  int off = field.getOffset();
Tom Tromey committed
1362

1363 1364 1365 1366 1367
	  return ((prim_field_data[off++] & 0xFF) << 24)
	    | ((prim_field_data[off++] & 0xFF) << 16)
	    | ((prim_field_data[off++] & 0xFF) << 8)
	    | (prim_field_data[off] & 0xFF);
	}
Tom Tromey committed
1368

1369
	public long get(String name, long defvalue)
1370 1371
	  throws IOException, IllegalArgumentException
	{
1372
	  ObjectStreamField field = getField(name, Long.TYPE);
1373 1374 1375

	  if (field == null)
	    return defvalue;
Tom Tromey committed
1376

1377
	  int off = field.getOffset();
Tom Tromey committed
1378

1379 1380 1381 1382
	  return (long)(((prim_field_data[off++] & 0xFFL) << 56)
			| ((prim_field_data[off++] & 0xFFL) << 48)
			| ((prim_field_data[off++] & 0xFFL) << 40)
			| ((prim_field_data[off++] & 0xFFL) << 32)
1383 1384 1385 1386 1387 1388
			| ((prim_field_data[off++] & 0xFF) << 24)
			| ((prim_field_data[off++] & 0xFF) << 16)
			| ((prim_field_data[off++] & 0xFF) << 8)
			| (prim_field_data[off] & 0xFF));
	}

1389
	public float get(String name, float defvalue)
1390 1391
	  throws IOException, IllegalArgumentException
	{
1392
	  ObjectStreamField field = getField(name, Float.TYPE);
1393 1394 1395 1396

	  if (field == null)
	    return defvalue;

1397
	  int off = field.getOffset();
1398

1399 1400 1401 1402
	  return Float.intBitsToFloat(((prim_field_data[off++] & 0xFF) << 24)
				      | ((prim_field_data[off++] & 0xFF) << 16)
				      | ((prim_field_data[off++] & 0xFF) << 8)
				      | (prim_field_data[off] & 0xFF));
1403 1404
	}

1405
	public double get(String name, double defvalue)
1406 1407
	  throws IOException, IllegalArgumentException
	{
1408
	  ObjectStreamField field = getField(name, Double.TYPE);
1409 1410 1411

	  if (field == null)
	    return defvalue;
Tom Tromey committed
1412

1413
	  int off = field.getOffset();
1414 1415

	  return Double.longBitsToDouble
1416 1417 1418 1419
	    ( (long) (((prim_field_data[off++] & 0xFFL) << 56)
		      | ((prim_field_data[off++] & 0xFFL) << 48)
		      | ((prim_field_data[off++] & 0xFFL) << 40)
		      | ((prim_field_data[off++] & 0xFFL) << 32)
Tom Tromey committed
1420 1421 1422
		      | ((prim_field_data[off++] & 0xFF) << 24)
		      | ((prim_field_data[off++] & 0xFF) << 16)
		      | ((prim_field_data[off++] & 0xFF) << 8)
1423 1424
		      | (prim_field_data[off] & 0xFF)));
	}
Tom Tromey committed
1425

1426
	public Object get(String name, Object defvalue)
1427 1428 1429
	  throws IOException, IllegalArgumentException
	{
	  ObjectStreamField field =
1430
	    getField(name, defvalue == null ? null : defvalue.getClass ());
Tom Tromey committed
1431

1432 1433
	  if (field == null)
	    return defvalue;
Tom Tromey committed
1434

1435
	  return objs[field.getOffset()];
1436
	}
Tom Tromey committed
1437

1438
	private ObjectStreamField getField(String name, Class type)
1439 1440
	  throws IllegalArgumentException
	{
1441
	  ObjectStreamField field = clazz.getField(name);
1442
	  boolean illegal = false;
Tom Tromey committed
1443

1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 1478 1479 1480 1481 1482 1483 1484 1485 1486 1487 1488 1489 1490 1491
	  try
	    {
	      try
		{
		  Class field_type = field.getType();
		  
		  if (type == field_type ||
		      (type == null && !field_type.isPrimitive()))
		    {
		      /* See defaulted */
		      return field;
		    }
	 
		  illegal = true;
		  throw new IllegalArgumentException
		    ("Field requested is of type "
		     + field_type.getName()
		     + ", but requested type was "
		     + (type == null ?  "Object" : type.getName()));
		}
	      catch (NullPointerException _)
		{
		  /* Here we catch NullPointerException, because it may
		     only come from the call 'field.getType()'. If field
		     is null, we have to return null and classpath ethic
		     say we must try to avoid 'if (xxx == null)'.
		  */
		}
	      catch (IllegalArgumentException e)
		{
		  throw e;
		}
	      
	      return null;
	    }
	  finally
	    {
	      /* If this is an unassigned field we should return
	       * the default value.
	       */
	      if (!illegal && field != null && !field.isToSet() && field.isPersistent())
		return null;

	      /* We do not want to modify transient fields. They should
	       * be left to 0.
	       */
	      try
		{
1492
		  Field f = clazz.forClass().getDeclaredField(name);
1493 1494 1495 1496 1497 1498 1499 1500 1501 1502 1503 1504 1505 1506
		  if (Modifier.isTransient(f.getModifiers()))
		    throw new IllegalArgumentException
		      ("no such field (non transient) " + name);
		  if (field == null && f.getType() != type)
		    throw new IllegalArgumentException
		      ("Invalid requested type for field " + name);
		}
	      catch (NoSuchFieldException e)
		{
		  if (field == null)
		    throw new IllegalArgumentException(e.getMessage());
		}
	       
	    }
1507 1508
	}
      };
Tom Tromey committed
1509

1510 1511
    fieldsAlreadyRead = true;
    return prereadFields;
Tom Tromey committed
1512 1513 1514
  }

  /**
1515 1516 1517 1518 1519 1520 1521 1522 1523 1524
   * Protected constructor that allows subclasses to override
   * deserialization.  This constructor should be called by subclasses
   * that wish to override <code>readObject (Object)</code>.  This
   * method does a security check <i>NOTE: currently not
   * implemented</i>, then sets a flag that informs
   * <code>readObject (Object)</code> to call the subclasses
   * <code>readObjectOverride (Object)</code> method.
   *
   * @see #readObjectOverride()
   */
1525
  protected ObjectInputStream()
Tom Tromey committed
1526 1527
    throws IOException, SecurityException
  {
1528
    SecurityManager sec_man = System.getSecurityManager();
Tom Tromey committed
1529
    if (sec_man != null)
1530
      sec_man.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
Tom Tromey committed
1531 1532 1533 1534
    this.useSubclassMethod = true;
  }

  /**
1535 1536 1537 1538 1539 1540 1541 1542
   * This method allows subclasses to override the default
   * de serialization mechanism provided by
   * <code>ObjectInputStream</code>.  To make this method be used for
   * writing objects, subclasses must invoke the 0-argument
   * constructor on this class from their constructor.
   *
   * @see #ObjectInputStream()
   */
1543
  protected Object readObjectOverride()
Tom Tromey committed
1544 1545
    throws ClassNotFoundException, IOException, OptionalDataException
  {
1546
    throw new IOException("Subclass of ObjectInputStream must implement readObjectOverride");
Tom Tromey committed
1547 1548
  }

1549 1550 1551 1552 1553 1554
  /**
   * Assigns the next available handle to <code>obj</code>.
   *
   * @param obj The object for which we want a new handle.
   * @return A valid handle for the specified object.
   */
1555
  private int assignNewHandle(Object obj)
Tom Tromey committed
1556
  {
1557 1558
    this.objectLookupTable.put(new Integer(this.nextOID),
			       new ObjectIdentityWrapper(obj));
Tom Tromey committed
1559 1560 1561
    return this.nextOID++;
  }

1562
  private Object processResolution(ObjectStreamClass osc, Object obj, int handle)
Tom Tromey committed
1563 1564
    throws IOException
  {
1565
    if (osc != null && obj instanceof Serializable)
1566 1567
      {
	try
1568
	  {
1569 1570 1571 1572 1573
	    Method m = osc.readResolveMethod; 
	    if(m != null)
	    {
		obj = m.invoke(obj, new Object[] {});
	    }
1574
	  }
1575
	catch (IllegalAccessException ignore)
1576 1577
	  {
	  }
Mark Wielaard committed
1578
	catch (InvocationTargetException exception)
1579
	  {
Mark Wielaard committed
1580 1581 1582 1583 1584 1585 1586
	    Throwable cause = exception.getCause();
	    if (cause instanceof ObjectStreamException)
	      throw (ObjectStreamException) cause;
	    else if (cause instanceof RuntimeException)
	      throw (RuntimeException) cause;
	    else if (cause instanceof Error)
	      throw (Error) cause;
1587
	  }
1588
      }
Tom Tromey committed
1589 1590

    if (this.resolveEnabled)
1591
      obj = resolveObject(obj);
Tom Tromey committed
1592

1593 1594
    this.objectLookupTable.put(new Integer(handle),
			       new ObjectIdentityWrapper(obj));
Tom Tromey committed
1595 1596 1597 1598

    return obj;
  }

1599
  private void clearHandles()
Tom Tromey committed
1600
  {
1601
    this.objectLookupTable.clear();
Tom Tromey committed
1602 1603 1604
    this.nextOID = baseWireHandle;
  }

1605
  private void readNextBlock() throws IOException
Tom Tromey committed
1606
  {
1607
    readNextBlock(this.realInputStream.readByte());
Tom Tromey committed
1608 1609
  }

1610
  private void readNextBlock(byte marker) throws IOException
Tom Tromey committed
1611 1612
  {
    if (marker == TC_BLOCKDATA)
1613
      {
1614
	if(dump) dumpElement("BLOCK DATA SIZE=");
1615
	this.blockDataBytes = this.realInputStream.readUnsignedByte();
1616
	if(dump) dumpElementln (Integer.toString(this.blockDataBytes));
1617
      }
Tom Tromey committed
1618
    else if (marker == TC_BLOCKDATALONG)
1619
      {
1620
	if(dump) dumpElement("BLOCK DATA LONG SIZE=");
1621
	this.blockDataBytes = this.realInputStream.readInt();
1622
	if(dump) dumpElementln (Integer.toString(this.blockDataBytes));
1623
      }
Tom Tromey committed
1624
    else
1625
      {
1626
	throw new EOFException("Attempt to read primitive data, but no data block is active.");
1627
      }
Tom Tromey committed
1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638

    if (this.blockData.length < this.blockDataBytes)
      this.blockData = new byte[this.blockDataBytes];

    this.realInputStream.readFully (this.blockData, 0, this.blockDataBytes);
    this.blockDataPosition = 0;
  }

  private void readArrayElements (Object array, Class clazz)
    throws ClassNotFoundException, IOException
  {
1639
    if (clazz.isPrimitive())
Tom Tromey committed
1640
      {
1641 1642 1643 1644
	if (clazz == Boolean.TYPE)
	  {
	    boolean[] cast_array = (boolean[])array;
	    for (int i=0; i < cast_array.length; i++)
1645
	      cast_array[i] = this.realInputStream.readBoolean();
1646 1647 1648 1649 1650 1651
	    return;
	  }
	if (clazz == Byte.TYPE)
	  {
	    byte[] cast_array = (byte[])array;
	    for (int i=0; i < cast_array.length; i++)
1652
	      cast_array[i] = this.realInputStream.readByte();
1653 1654 1655 1656 1657 1658
	    return;
	  }
	if (clazz == Character.TYPE)
	  {
	    char[] cast_array = (char[])array;
	    for (int i=0; i < cast_array.length; i++)
1659
	      cast_array[i] = this.realInputStream.readChar();
1660 1661 1662 1663 1664 1665
	    return;
	  }
	if (clazz == Double.TYPE)
	  {
	    double[] cast_array = (double[])array;
	    for (int i=0; i < cast_array.length; i++)
1666
	      cast_array[i] = this.realInputStream.readDouble();
1667 1668 1669 1670 1671 1672
	    return;
	  }
	if (clazz == Float.TYPE)
	  {
	    float[] cast_array = (float[])array;
	    for (int i=0; i < cast_array.length; i++)
1673
	      cast_array[i] = this.realInputStream.readFloat();
1674 1675 1676 1677 1678 1679
	    return;
	  }
	if (clazz == Integer.TYPE)
	  {
	    int[] cast_array = (int[])array;
	    for (int i=0; i < cast_array.length; i++)
1680
	      cast_array[i] = this.realInputStream.readInt();
1681 1682 1683 1684 1685 1686
	    return;
	  }
	if (clazz == Long.TYPE)
	  {
	    long[] cast_array = (long[])array;
	    for (int i=0; i < cast_array.length; i++)
1687
	      cast_array[i] = this.realInputStream.readLong();
1688 1689 1690 1691 1692 1693
	    return;
	  }
	if (clazz == Short.TYPE)
	  {
	    short[] cast_array = (short[])array;
	    for (int i=0; i < cast_array.length; i++)
1694
	      cast_array[i] = this.realInputStream.readShort();
1695 1696
	    return;
	  }
Tom Tromey committed
1697
      }
1698
    else
Tom Tromey committed
1699
      {
1700
	Object[] cast_array = (Object[])array;
Tom Tromey committed
1701
	for (int i=0; i < cast_array.length; i++)
1702
 	  cast_array[i] = readObject();
1703
      }
Tom Tromey committed
1704 1705
  }

1706
  private void readFields (Object obj, ObjectStreamClass stream_osc)
Tom Tromey committed
1707 1708
    throws ClassNotFoundException, IOException
  {
1709
    ObjectStreamField[] fields = stream_osc.fieldMapping;
Tom Tromey committed
1710

1711
    for (int i = 0; i < fields.length; i += 2)
Tom Tromey committed
1712
      {
1713 1714
	ObjectStreamField stream_field = fields[i];
	ObjectStreamField real_field = fields[i + 1];
1715 1716 1717 1718
	boolean read_value = (stream_field != null && stream_field.getOffset() >= 0 && stream_field.isToSet());
	boolean set_value = (real_field != null && real_field.isToSet());
	String field_name;
	char type;
1719

1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823
	if (stream_field != null)
	  {
	    field_name = stream_field.getName();
	    type = stream_field.getTypeCode();
	  }
	else
	  {
	    field_name = real_field.getName();
	    type = real_field.getTypeCode();
	  }
	
	switch(type)
	  {
	  case 'Z':
	    {
	      boolean value =
		read_value ? this.realInputStream.readBoolean() : false;
	      if (dump && read_value && set_value)
		dumpElementln("  " + field_name + ": " + value);
	      if (set_value)
		real_field.setBooleanField(obj, value);
	      break;
	    }
	  case 'B':
	    {
	      byte value =
		read_value ? this.realInputStream.readByte() : 0;
	      if (dump && read_value && set_value)
		dumpElementln("  " + field_name + ": " + value);
	      if (set_value)
		real_field.setByteField(obj, value);
	      break;
	    }
	  case 'C':
	    {
	      char value =
		read_value ? this.realInputStream.readChar(): 0;
	      if (dump && read_value && set_value)
		dumpElementln("  " + field_name + ": " + value);
	      if (set_value)
		real_field.setCharField(obj, value);
	      break;
	    }
	  case 'D':
	    {
	      double value =
		read_value ? this.realInputStream.readDouble() : 0;
	      if (dump && read_value && set_value)
		dumpElementln("  " + field_name + ": " + value);
	      if (set_value)
		real_field.setDoubleField(obj, value);
	      break;
	    }
	  case 'F':
	    {
	      float value =
		read_value ? this.realInputStream.readFloat() : 0;
	      if (dump && read_value && set_value)
		dumpElementln("  " + field_name + ": " + value);
	      if (set_value)
		real_field.setFloatField(obj, value);
	      break;
	    }
	  case 'I':
	    {
	      int value =
		read_value ? this.realInputStream.readInt() : 0;
	      if (dump && read_value && set_value)
		dumpElementln("  " + field_name + ": " + value);
	      if (set_value)
		real_field.setIntField(obj, value);
	      break;
	    }
	  case 'J':
	    {
	      long value =
		read_value ? this.realInputStream.readLong() : 0;
	      if (dump && read_value && set_value)
		dumpElementln("  " + field_name + ": " + value);
	      if (set_value)
		real_field.setLongField(obj, value);
	      break;
	    }
	  case 'S':
	    {
	      short value =
		read_value ? this.realInputStream.readShort() : 0;
	      if (dump && read_value && set_value)
		dumpElementln("  " + field_name + ": " + value);
	      if (set_value)
		real_field.setShortField(obj, value);
	      break;
	    }
	  case 'L':
	  case '[':
	    {
	      Object value =
		read_value ? readObject() : null;
	      if (set_value)
		real_field.setObjectField(obj, value);
	      break;
	    }
	  default:
	    throw new InternalError("Invalid type code: " + type);
1824 1825 1826
	  }
      }
  }
1827
  
Tom Tromey committed
1828
  // Toggles writing primitive data to block-data buffer.
1829
  private boolean setBlockDataMode (boolean on)
Tom Tromey committed
1830
  {
1831
    boolean oldmode = this.readDataFromBlock;
Tom Tromey committed
1832 1833 1834 1835 1836 1837
    this.readDataFromBlock = on;

    if (on)
      this.dataInputStream = this.blockDataInput;
    else
      this.dataInputStream = this.realInputStream;
1838
    return oldmode;
Tom Tromey committed
1839 1840 1841
  }

  // returns a new instance of REAL_CLASS that has been constructed
Tom Tromey committed
1842
  // only to the level of CONSTRUCTOR_CLASS (a super class of REAL_CLASS)
Bryce McKinlay committed
1843 1844
  private Object newObject (Class real_class, Constructor constructor)
    throws ClassNotFoundException, IOException
Tom Tromey committed
1845
  {
Bryce McKinlay committed
1846 1847
    if (constructor == null)
        throw new InvalidClassException("Missing accessible no-arg base class constructor for " + real_class.getName()); 
Tom Tromey committed
1848
    try
1849
      {
Bryce McKinlay committed
1850
	return allocateObject(real_class, constructor.getDeclaringClass(), constructor);
1851
      }
Tom Tromey committed
1852
    catch (InstantiationException e)
1853
      {
1854 1855
        throw new ClassNotFoundException
		("Instance of " + real_class + " could not be created");
1856
      }
Tom Tromey committed
1857 1858 1859 1860
  }

  // runs all registered ObjectInputValidations in prioritized order
  // on OBJ
1861
  private void invokeValidators() throws InvalidObjectException
Tom Tromey committed
1862
  {
1863
    Object[] validators = new Object[this.validators.size()];
Tom Tromey committed
1864 1865 1866 1867
    this.validators.copyInto (validators);
    Arrays.sort (validators);

    try
1868 1869
      {
	for (int i=0; i < validators.length; i++)
1870
	  ((ObjectInputValidation)validators[i]).validateObject();
1871
      }
Tom Tromey committed
1872
    finally
1873
      {
1874
	this.validators.removeAllElements();
1875
      }
Tom Tromey committed
1876 1877
  }

1878 1879
  private void callReadMethod (Method readObject, Class klass, Object obj)
    throws ClassNotFoundException, IOException
Tom Tromey committed
1880 1881 1882
  {
    try
      {
1883
	readObject.invoke(obj, new Object[] { this });
Tom Tromey committed
1884
      }
1885 1886 1887 1888 1889 1890 1891 1892
    catch (InvocationTargetException x)
      {
        /* Rethrow if possible. */
	Throwable exception = x.getTargetException();
	if (exception instanceof RuntimeException)
	  throw (RuntimeException) exception;
	if (exception instanceof IOException)
	  throw (IOException) exception;
1893 1894
        if (exception instanceof ClassNotFoundException)
          throw (ClassNotFoundException) exception;
1895

1896
	throw new IOException("Exception thrown from readObject() on " +
1897 1898 1899
			       klass + ": " + exception.getClass().getName());
      }
    catch (Exception x)
Tom Tromey committed
1900
      {
1901
	throw new IOException("Failure invoking readObject() on " +
1902
			       klass + ": " + x.getClass().getName());
Tom Tromey committed
1903
      }
1904 1905 1906

    // Invalidate fields which has been read through readFields.
    prereadFields = null;
Tom Tromey committed
1907 1908
  }
    
Bryce McKinlay committed
1909
  private native Object allocateObject(Class clazz, Class constr_clazz, Constructor constructor)
Tom Tromey committed
1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 1927 1928 1929
    throws InstantiationException;

  private static final int BUFFER_SIZE = 1024;

  private DataInputStream realInputStream;
  private DataInputStream dataInputStream;
  private DataInputStream blockDataInput;
  private int blockDataPosition;
  private int blockDataBytes;
  private byte[] blockData;
  private boolean useSubclassMethod;
  private int nextOID;
  private boolean resolveEnabled;
  private Hashtable objectLookupTable;
  private Object currentObject;
  private ObjectStreamClass currentObjectStreamClass;
  private boolean readDataFromBlock;
  private boolean isDeserializing;
  private boolean fieldsAlreadyRead;
  private Vector validators;
1930
  private Hashtable classLookupTable;
1931
  private GetField prereadFields;
Tom Tromey committed
1932

1933
  private ClassLoader callersClassLoader;
Bryce McKinlay committed
1934
  private static boolean dump;
1935 1936 1937 1938

  // The nesting depth for debugging output
  private int depth = 0;

1939
  private void dumpElement (String msg)
1940
  {
1941
    System.out.print(msg);
Tom Tromey committed
1942
  }
1943 1944
  
  private void dumpElementln (String msg)
Tom Tromey committed
1945
  {
1946
    System.out.println(msg);
1947 1948 1949
    for (int i = 0; i < depth; i++)
      System.out.print (" ");
    System.out.print (Thread.currentThread() + ": ");
Tom Tromey committed
1950
  }
1951 1952

  static
1953 1954 1955 1956 1957 1958
  {
    if (Configuration.INIT_LOAD_LIBRARY)
      {
	System.loadLibrary ("javaio");
      }
  }
Tom Tromey committed
1959

1960 1961
  // used to keep a prioritized list of object validators
  private static final class ValidatorAndPriority implements Comparable
Tom Tromey committed
1962
  {
1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976
    int priority;
    ObjectInputValidation validator;

    ValidatorAndPriority (ObjectInputValidation validator, int priority)
    {
      this.priority = priority;
      this.validator = validator;
    }

    public int compareTo (Object o)
    {
      ValidatorAndPriority vap = (ValidatorAndPriority)o;
      return this.priority - vap.priority;
    }
Tom Tromey committed
1977 1978
  }
}
1979