1 /*
   2  * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved.
   3  * Copyright (c) 2024, Alibaba Group Holding Limited. All Rights Reserved.
   4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   5  *
   6  * This code is free software; you can redistribute it and/or modify it
   7  * under the terms of the GNU General Public License version 2 only, as
   8  * published by the Free Software Foundation.  Oracle designates this
   9  * particular file as subject to the "Classpath" exception as provided
  10  * by Oracle in the LICENSE file that accompanied this code.
  11  *
  12  * This code is distributed in the hope that it will be useful, but WITHOUT
  13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  15  * version 2 for more details (a copy is included in the LICENSE file that
  16  * accompanied this code).
  17  *
  18  * You should have received a copy of the GNU General Public License version
  19  * 2 along with this work; if not, write to the Free Software Foundation,
  20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  21  *
  22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  23  * or visit www.oracle.com if you need additional information or have any
  24  * questions.
  25  */
  26 
  27 package java.io;
  28 
  29 import java.util.ArrayList;
  30 import java.util.Arrays;
  31 import java.util.List;
  32 import java.util.Objects;
  33 import java.util.StringJoiner;
  34 
  35 import jdk.internal.util.ByteArray;
  36 import jdk.internal.access.JavaLangAccess;
  37 import jdk.internal.access.SharedSecrets;
  38 
  39 import static jdk.internal.util.ModifiedUtf.putChar;
  40 import static jdk.internal.util.ModifiedUtf.utfLen;
  41 
  42 /**
  43  * An ObjectOutputStream writes primitive data types and graphs of Java objects
  44  * to an OutputStream.  The objects can be read (reconstituted) using an
  45  * ObjectInputStream.  Persistent storage of objects can be accomplished by
  46  * using a file for the stream.  If the stream is a network socket stream, the
  47  * objects can be reconstituted on another host or in another process.
  48  *
  49  * <p>Only objects that support the java.io.Serializable interface can be
  50  * written to streams.  The class of each serializable object is encoded
  51  * including the class name and signature of the class, the values of the
  52  * object's fields and arrays, and the closure of any other objects referenced
  53  * from the initial objects.
  54  *
  55  * <p>The method writeObject is used to write an object to the stream.  Any
  56  * object, including Strings and arrays, is written with writeObject. Multiple
  57  * objects or primitives can be written to the stream.  The objects must be
  58  * read back from the corresponding ObjectInputstream with the same types and
  59  * in the same order as they were written.
  60  *
  61  * <p>Primitive data types can also be written to the stream using the
  62  * appropriate methods from DataOutput. Strings can also be written using the
  63  * writeUTF method.
  64  *
  65  * <p>The default serialization mechanism for an object writes the class of the
  66  * object, the class signature, and the values of all non-transient and
  67  * non-static fields.  References to other objects (except in transient or
  68  * static fields) cause those objects to be written also. Multiple references
  69  * to a single object are encoded using a reference sharing mechanism so that
  70  * graphs of objects can be restored to the same shape as when the original was
  71  * written.
  72  *
  73  * <p>For example to write an object that can be read by the example in
  74  * {@link ObjectInputStream}:
  75  * {@snippet lang="java":
  76  *      try (FileOutputStream fos = new FileOutputStream("t.tmp");
  77  *           ObjectOutputStream oos = new ObjectOutputStream(fos)) {
  78  *          oos.writeObject("Today");
  79  *          oos.writeObject(LocalDateTime.now());
  80  *      } catch (Exception ex) {
  81  *          // handle exception
  82  *      }
  83  * }
  84  *
  85  * <p>Serializable classes that require special handling during the
  86  * serialization and deserialization process should implement methods
  87  * with the following signatures:
  88  *
  89  * {@snippet lang="java":
  90  *     private void readObject(java.io.ObjectInputStream stream)
  91  *         throws IOException, ClassNotFoundException;
  92  *     private void writeObject(java.io.ObjectOutputStream stream)
  93  *         throws IOException;
  94  *     private void readObjectNoData()
  95  *         throws ObjectStreamException;
  96  * }
  97  *
  98  * <p>The method name, modifiers, return type, and number and type of
  99  * parameters must match exactly for the method to be used by
 100  * serialization or deserialization. The methods should only be
 101  * declared to throw checked exceptions consistent with these
 102  * signatures.
 103  *
 104  * <p>The writeObject method is responsible for writing the state of the object
 105  * for its particular class so that the corresponding readObject method can
 106  * restore it.  The method does not need to concern itself with the state
 107  * belonging to the object's superclasses or subclasses.  State is saved by
 108  * writing the individual fields to the ObjectOutputStream using the
 109  * writeObject method or by using the methods for primitive data types
 110  * supported by DataOutput.
 111  *
 112  * <p>Serialization does not write out the fields of any object that does not
 113  * implement the java.io.Serializable interface.  Subclasses of Objects that
 114  * are not serializable can be serializable. In this case the non-serializable
 115  * class must have a no-arg constructor to allow its fields to be initialized.
 116  * In this case it is the responsibility of the subclass to save and restore
 117  * the state of the non-serializable class. It is frequently the case that the
 118  * fields of that class are accessible (public, package, or protected) or that
 119  * there are get and set methods that can be used to restore the state.
 120  *
 121  * <p>Serialization of an object can be prevented by implementing writeObject
 122  * and readObject methods that throw the NotSerializableException.  The
 123  * exception will be caught by the ObjectOutputStream and abort the
 124  * serialization process.
 125  *
 126  * <p>Implementing the Externalizable interface allows the object to assume
 127  * complete control over the contents and format of the object's serialized
 128  * form.  The methods of the Externalizable interface, writeExternal and
 129  * readExternal, are called to save and restore the objects state.  When
 130  * implemented by a class they can write and read their own state using all of
 131  * the methods of ObjectOutput and ObjectInput.  It is the responsibility of
 132  * the objects to handle any versioning that occurs.
 133  *
 134  * <p>Enum constants are serialized differently than ordinary serializable or
 135  * externalizable objects.  The serialized form of an enum constant consists
 136  * solely of its name; field values of the constant are not transmitted.  To
 137  * serialize an enum constant, ObjectOutputStream writes the string returned by
 138  * the constant's name method.  Like other serializable or externalizable
 139  * objects, enum constants can function as the targets of back references
 140  * appearing subsequently in the serialization stream.  The process by which
 141  * enum constants are serialized cannot be customized; any class-specific
 142  * writeObject and writeReplace methods defined by enum types are ignored
 143  * during serialization.  Similarly, any serialPersistentFields or
 144  * serialVersionUID field declarations are also ignored--all enum types have a
 145  * fixed serialVersionUID of 0L.
 146  *
 147  * <p>Primitive data, excluding serializable fields and externalizable data, is
 148  * written to the ObjectOutputStream in block-data records. A block data record
 149  * is composed of a header and data. The block data header consists of a marker
 150  * and the number of bytes to follow the header.  Consecutive primitive data
 151  * writes are merged into one block-data record.  The blocking factor used for
 152  * a block-data record will be 1024 bytes.  Each block-data record will be
 153  * filled up to 1024 bytes, or be written whenever there is a termination of
 154  * block-data mode.  Calls to the ObjectOutputStream methods writeObject,
 155  * defaultWriteObject and writeFields initially terminate any existing
 156  * block-data record.
 157  *
 158  * <p>Records are serialized differently than ordinary serializable or externalizable
 159  * objects, see <a href="ObjectInputStream.html#record-serialization">record serialization</a>.
 160  *
 161  * @spec serialization/index.html Java Object Serialization Specification
 162  * @author      Mike Warres
 163  * @author      Roger Riggs
 164  * @see java.io.DataOutput
 165  * @see java.io.ObjectInputStream
 166  * @see java.io.Serializable
 167  * @see java.io.Externalizable
 168  * @see <a href="{@docRoot}/../specs/serialization/output.html">
 169  *      <cite>Java Object Serialization Specification,</cite> Section 2, "Object Output Classes"</a>
 170  * @since       1.1
 171  */
 172 public class ObjectOutputStream
 173     extends OutputStream implements ObjectOutput, ObjectStreamConstants
 174 {
 175     private static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
 176 
 177     private static class Caches {
 178         /** cache of subclass security audit results */
 179         static final ClassValue<Boolean> subclassAudits =
 180             new ClassValue<>() {
 181                 @Override
 182                 protected Boolean computeValue(Class<?> type) {
 183                     return auditSubclass(type);
 184                 }
 185             };
 186     }
 187 
 188     /** filter stream for handling block data conversion */
 189     private final BlockDataOutputStream bout;
 190     /** obj -> wire handle map */
 191     private final HandleTable handles;
 192     /** obj -> replacement obj map */
 193     private final ReplaceTable subs;
 194     /** stream protocol version */
 195     private int protocol = PROTOCOL_VERSION_2;
 196     /** recursion depth */
 197     private int depth;
 198 
 199     /** buffer for writing primitive field values */
 200     private byte[] primVals;
 201 
 202     /** if true, invoke writeObjectOverride() instead of writeObject() */
 203     private final boolean enableOverride;
 204     /** if true, invoke replaceObject() */
 205     private boolean enableReplace;
 206 
 207     // values below valid only during upcalls to writeObject()/writeExternal()
 208     /**
 209      * Context during upcalls to class-defined writeObject methods; holds
 210      * object currently being serialized and descriptor for current class.
 211      * Null when not during writeObject upcall.
 212      */
 213     private SerialCallbackContext curContext;
 214     /** current PutField object */
 215     private PutFieldImpl curPut;
 216 
 217     /** custom storage for debug trace info */
 218     private final DebugTraceInfoStack debugInfoStack;
 219 
 220     /**
 221      * value of "sun.io.serialization.extendedDebugInfo" property,
 222      * as true or false for extended information about exception's place
 223      */
 224     private static final boolean extendedDebugInfo =
 225             Boolean.getBoolean("sun.io.serialization.extendedDebugInfo");
 226 
 227     /**
 228      * Creates an ObjectOutputStream that writes to the specified OutputStream.
 229      * This constructor writes the serialization stream header to the
 230      * underlying stream; callers may wish to flush the stream immediately to
 231      * ensure that constructors for receiving ObjectInputStreams will not block
 232      * when reading the header.
 233      *
 234      * @param   out output stream to write to
 235      * @throws  IOException if an I/O error occurs while writing stream header
 236      * @throws  NullPointerException if {@code out} is {@code null}
 237      * @since   1.4
 238      * @see     ObjectOutputStream#ObjectOutputStream()
 239      * @see     ObjectOutputStream#putFields()
 240      * @see     ObjectInputStream#ObjectInputStream(InputStream)
 241      */
 242     @SuppressWarnings("this-escape")
 243     public ObjectOutputStream(OutputStream out) throws IOException {
 244         bout = new BlockDataOutputStream(out);
 245         handles = new HandleTable(10, (float) 3.00);
 246         subs = new ReplaceTable(10, (float) 3.00);
 247         enableOverride = false;
 248         writeStreamHeader();
 249         bout.setBlockDataMode(true);
 250         if (extendedDebugInfo) {
 251             debugInfoStack = new DebugTraceInfoStack();
 252         } else {
 253             debugInfoStack = null;
 254         }
 255     }
 256 
 257     /**
 258      * Provide a way for subclasses that are completely reimplementing
 259      * ObjectOutputStream to not have to allocate private data just used by
 260      * this implementation of ObjectOutputStream.
 261      *
 262      * @throws  IOException if an I/O error occurs while creating this stream
 263      */
 264     protected ObjectOutputStream() throws IOException {
 265         bout = null;
 266         handles = null;
 267         subs = null;
 268         enableOverride = true;
 269         debugInfoStack = null;
 270     }
 271 
 272     /**
 273      * Specify stream protocol version to use when writing the stream.
 274      *
 275      * <p>This routine provides a hook to enable the current version of
 276      * Serialization to write in a format that is backwards compatible to a
 277      * previous version of the stream format.
 278      *
 279      * <p>Every effort will be made to avoid introducing additional
 280      * backwards incompatibilities; however, sometimes there is no
 281      * other alternative.
 282      *
 283      * @param   version use ProtocolVersion from java.io.ObjectStreamConstants.
 284      * @throws  IllegalStateException if called after any objects
 285      *          have been serialized.
 286      * @throws  IllegalArgumentException if invalid version is passed in.
 287      * @throws  IOException if I/O errors occur
 288      * @see java.io.ObjectStreamConstants#PROTOCOL_VERSION_1
 289      * @see java.io.ObjectStreamConstants#PROTOCOL_VERSION_2
 290      * @since   1.2
 291      */
 292     public void useProtocolVersion(int version) throws IOException {
 293         if (handles.size() != 0) {
 294             // REMIND: implement better check for pristine stream?
 295             throw new IllegalStateException("stream non-empty");
 296         }
 297         switch (version) {
 298             case PROTOCOL_VERSION_1:
 299             case PROTOCOL_VERSION_2:
 300                 protocol = version;
 301                 break;
 302 
 303             default:
 304                 throw new IllegalArgumentException(
 305                     "unknown version: " + version);
 306         }
 307     }
 308 
 309     /**
 310      * Write the specified object to the ObjectOutputStream.  The class of the
 311      * object, the signature of the class, and the values of the non-transient
 312      * and non-static fields of the class and all of its supertypes are
 313      * written.  Default serialization for a class can be overridden using the
 314      * writeObject and the readObject methods.  Objects referenced by this
 315      * object are written transitively so that a complete equivalent graph of
 316      * objects can be reconstructed by an ObjectInputStream.
 317      *
 318      * <p>Exceptions are thrown for problems with the OutputStream and for
 319      * classes that should not be serialized.  All exceptions are fatal to the
 320      * OutputStream, which is left in an indeterminate state, and it is up to
 321      * the caller to ignore or recover the stream state.
 322      *
 323      * @throws  InvalidClassException Something is wrong with a class used by
 324      *          serialization.
 325      * @throws  NotSerializableException Some object to be serialized does not
 326      *          implement the java.io.Serializable interface.
 327      * @throws  IOException Any exception thrown by the underlying
 328      *          OutputStream.
 329      */
 330     public final void writeObject(Object obj) throws IOException {
 331         if (enableOverride) {
 332             writeObjectOverride(obj);
 333             return;
 334         }
 335         try {
 336             writeObject0(obj, false);
 337         } catch (IOException ex) {
 338             if (depth == 0) {
 339                 writeFatalException(ex);
 340             }
 341             throw ex;
 342         }
 343     }
 344 
 345     /**
 346      * Method used by subclasses to override the default writeObject method.
 347      * This method is called by trusted subclasses of ObjectOutputStream that
 348      * constructed ObjectOutputStream using the protected no-arg constructor.
 349      * The subclass is expected to provide an override method with the modifier
 350      * "final".
 351      *
 352      * @param   obj object to be written to the underlying stream
 353      * @throws  IOException if there are I/O errors while writing to the
 354      *          underlying stream
 355      * @see #ObjectOutputStream()
 356      * @see #writeObject(Object)
 357      * @since 1.2
 358      */
 359     protected void writeObjectOverride(Object obj) throws IOException {
 360     }
 361 
 362     /**
 363      * Writes an "unshared" object to the ObjectOutputStream.  This method is
 364      * identical to writeObject, except that it always writes the given object
 365      * as a new, unique object in the stream (as opposed to a back-reference
 366      * pointing to a previously serialized instance).  Specifically:
 367      * <ul>
 368      *   <li>An object written via writeUnshared is always serialized in the
 369      *       same manner as a newly appearing object (an object that has not
 370      *       been written to the stream yet), regardless of whether or not the
 371      *       object has been written previously.
 372      *
 373      *   <li>If writeObject is used to write an object that has been previously
 374      *       written with writeUnshared, the previous writeUnshared operation
 375      *       is treated as if it were a write of a separate object.  In other
 376      *       words, ObjectOutputStream will never generate back-references to
 377      *       object data written by calls to writeUnshared.
 378      * </ul>
 379      * While writing an object via writeUnshared does not in itself guarantee a
 380      * unique reference to the object when it is deserialized, it allows a
 381      * single object to be defined multiple times in a stream, so that multiple
 382      * calls to readUnshared by the receiver will not conflict.  Note that the
 383      * rules described above only apply to the base-level object written with
 384      * writeUnshared, and not to any transitively referenced sub-objects in the
 385      * object graph to be serialized.
 386      *
 387      * @param   obj object to write to stream
 388      * @throws  NotSerializableException if an object in the graph to be
 389      *          serialized does not implement the Serializable interface
 390      * @throws  InvalidClassException if a problem exists with the class of an
 391      *          object to be serialized
 392      * @throws  IOException if an I/O error occurs during serialization
 393      * @since 1.4
 394      */
 395     public void writeUnshared(Object obj) throws IOException {
 396         try {
 397             writeObject0(obj, true);
 398         } catch (IOException ex) {
 399             if (depth == 0) {
 400                 writeFatalException(ex);
 401             }
 402             throw ex;
 403         }
 404     }
 405 
 406     /**
 407      * Write the non-static and non-transient fields of the current class to
 408      * this stream.  This may only be called from the writeObject method of the
 409      * class being serialized. It will throw the NotActiveException if it is
 410      * called otherwise.
 411      *
 412      * @throws  IOException if I/O errors occur while writing to the underlying
 413      *          {@code OutputStream}
 414      */
 415     public void defaultWriteObject() throws IOException {
 416         SerialCallbackContext ctx = curContext;
 417         if (ctx == null) {
 418             throw new NotActiveException("not in call to writeObject");
 419         }
 420         Object curObj = ctx.getObj();
 421         ObjectStreamClass curDesc = ctx.getDesc();
 422         bout.setBlockDataMode(false);
 423         defaultWriteFields(curObj, curDesc);
 424         bout.setBlockDataMode(true);
 425     }
 426 
 427     /**
 428      * Retrieve the object used to buffer persistent fields to be written to
 429      * the stream.  The fields will be written to the stream when writeFields
 430      * method is called.
 431      *
 432      * @return  an instance of the class Putfield that holds the serializable
 433      *          fields
 434      * @throws  IOException if I/O errors occur
 435      * @since 1.2
 436      */
 437     public ObjectOutputStream.PutField putFields() throws IOException {
 438         if (curPut == null) {
 439             SerialCallbackContext ctx = curContext;
 440             if (ctx == null) {
 441                 throw new NotActiveException("not in call to writeObject");
 442             }
 443             ctx.checkAndSetUsed();
 444             ObjectStreamClass curDesc = ctx.getDesc();
 445             curPut = new PutFieldImpl(curDesc);
 446         }
 447         return curPut;
 448     }
 449 
 450     /**
 451      * Write the buffered fields to the stream.
 452      *
 453      * @throws  IOException if I/O errors occur while writing to the underlying
 454      *          stream
 455      * @throws  NotActiveException Called when a classes writeObject method was
 456      *          not called to write the state of the object.
 457      * @since 1.2
 458      */
 459     public void writeFields() throws IOException {
 460         if (curPut == null) {
 461             throw new NotActiveException("no current PutField object");
 462         }
 463         bout.setBlockDataMode(false);
 464         curPut.writeFields();
 465         bout.setBlockDataMode(true);
 466     }
 467 
 468     /**
 469      * Reset will disregard the state of any objects already written to the
 470      * stream.  The state is reset to be the same as a new ObjectOutputStream.
 471      * The current point in the stream is marked as reset so the corresponding
 472      * ObjectInputStream will be reset at the same point.  Objects previously
 473      * written to the stream will not be referred to as already being in the
 474      * stream.  They will be written to the stream again.
 475      *
 476      * @throws  IOException if reset() is invoked while serializing an object.
 477      */
 478     public void reset() throws IOException {
 479         if (depth != 0) {
 480             throw new IOException("stream active");
 481         }
 482         bout.setBlockDataMode(false);
 483         bout.writeByte(TC_RESET);
 484         clear();
 485         bout.setBlockDataMode(true);
 486     }
 487 
 488     /**
 489      * Subclasses may implement this method to allow class data to be stored in
 490      * the stream. By default this method does nothing.  The corresponding
 491      * method in ObjectInputStream is resolveClass.  This method is called
 492      * exactly once for each unique class in the stream.  The class name and
 493      * signature will have already been written to the stream.  This method may
 494      * make free use of the ObjectOutputStream to save any representation of
 495      * the class it deems suitable (for example, the bytes of the class file).
 496      * The resolveClass method in the corresponding subclass of
 497      * ObjectInputStream must read and use any data or objects written by
 498      * annotateClass.
 499      *
 500      * @param   cl the class to annotate custom data for
 501      * @throws  IOException Any exception thrown by the underlying
 502      *          OutputStream.
 503      */
 504     protected void annotateClass(Class<?> cl) throws IOException {
 505     }
 506 
 507     /**
 508      * Subclasses may implement this method to store custom data in the stream
 509      * along with descriptors for dynamic proxy classes.
 510      *
 511      * <p>This method is called exactly once for each unique proxy class
 512      * descriptor in the stream.  The default implementation of this method in
 513      * {@code ObjectOutputStream} does nothing.
 514      *
 515      * <p>The corresponding method in {@code ObjectInputStream} is
 516      * {@code resolveProxyClass}.  For a given subclass of
 517      * {@code ObjectOutputStream} that overrides this method, the
 518      * {@code resolveProxyClass} method in the corresponding subclass of
 519      * {@code ObjectInputStream} must read any data or objects written by
 520      * {@code annotateProxyClass}.
 521      *
 522      * @param   cl the proxy class to annotate custom data for
 523      * @throws  IOException any exception thrown by the underlying
 524      *          {@code OutputStream}
 525      * @see ObjectInputStream#resolveProxyClass(String[])
 526      * @since   1.3
 527      */
 528     protected void annotateProxyClass(Class<?> cl) throws IOException {
 529     }
 530 
 531     /**
 532      * This method will allow trusted subclasses of ObjectOutputStream to
 533      * substitute one object for another during serialization. Replacing
 534      * objects is disabled until enableReplaceObject is called. The
 535      * enableReplaceObject method checks that the stream requesting to do
 536      * replacement can be trusted.  The first occurrence of each object written
 537      * into the serialization stream is passed to replaceObject.  Subsequent
 538      * references to the object are replaced by the object returned by the
 539      * original call to replaceObject.  To ensure that the private state of
 540      * objects is not unintentionally exposed, only trusted streams may use
 541      * replaceObject.
 542      *
 543      * <p>The ObjectOutputStream.writeObject method takes a parameter of type
 544      * Object (as opposed to type Serializable) to allow for cases where
 545      * non-serializable objects are replaced by serializable ones.
 546      *
 547      * <p>When a subclass is replacing objects it must ensure that either a
 548      * complementary substitution must be made during deserialization or that
 549      * the substituted object is compatible with every field where the
 550      * reference will be stored.  Objects whose type is not a subclass of the
 551      * type of the field or array element abort the serialization by raising an
 552      * exception and the object is not be stored.
 553      *
 554      * <p>This method is called only once when each object is first
 555      * encountered.  All subsequent references to the object will be redirected
 556      * to the new object. This method should return the object to be
 557      * substituted or the original object.
 558      *
 559      * <p>Null can be returned as the object to be substituted, but may cause
 560      * {@link NullPointerException} in classes that contain references to the
 561      * original object since they may be expecting an object instead of
 562      * null.
 563      *
 564      * @param   obj the object to be replaced
 565      * @return  the alternate object that replaced the specified one
 566      * @throws  IOException Any exception thrown by the underlying
 567      *          OutputStream.
 568      */
 569     protected Object replaceObject(Object obj) throws IOException {
 570         return obj;
 571     }
 572 
 573     /**
 574      * Enables the stream to do replacement of objects written to the stream.  When
 575      * enabled, the {@link #replaceObject} method is called for every object being
 576      * serialized.
 577      *
 578      * @param   enable true for enabling use of {@code replaceObject} for
 579      *          every object being serialized
 580      * @return  the previous setting before this method was invoked
 581      */
 582     protected boolean enableReplaceObject(boolean enable) {
 583         if (enable == enableReplace) {
 584             return enable;
 585         }
 586         enableReplace = enable;
 587         return !enableReplace;
 588     }
 589 
 590     /**
 591      * The writeStreamHeader method is provided so subclasses can append or
 592      * prepend their own header to the stream.  It writes the magic number and
 593      * version to the stream.
 594      *
 595      * @throws  IOException if I/O errors occur while writing to the underlying
 596      *          stream
 597      */
 598     protected void writeStreamHeader() throws IOException {
 599         bout.writeShort(STREAM_MAGIC);
 600         bout.writeShort(STREAM_VERSION);
 601     }
 602 
 603     /**
 604      * Write the specified class descriptor to the ObjectOutputStream.  Class
 605      * descriptors are used to identify the classes of objects written to the
 606      * stream.  Subclasses of ObjectOutputStream may override this method to
 607      * customize the way in which class descriptors are written to the
 608      * serialization stream.  The corresponding method in ObjectInputStream,
 609      * {@link ObjectInputStream#readClassDescriptor readClassDescriptor}, should then be
 610      * overridden to reconstitute the class descriptor from its custom stream representation.
 611      * By default, this method writes class descriptors according to the format
 612      * defined in the <a href="{@docRoot}/../specs/serialization/index.html">
 613      * <cite>Java Object Serialization Specification</cite></a>.
 614      *
 615      * <p>Note that this method will only be called if the ObjectOutputStream
 616      * is not using the old serialization stream format (set by calling
 617      * ObjectOutputStream's {@code useProtocolVersion} method).  If this
 618      * serialization stream is using the old format
 619      * ({@code PROTOCOL_VERSION_1}), the class descriptor will be written
 620      * internally in a manner that cannot be overridden or customized.
 621      *
 622      * @param   desc class descriptor to write to the stream
 623      * @throws  IOException If an I/O error has occurred.
 624      * @spec serialization/index.html Java Object Serialization Specification
 625      * @see java.io.ObjectInputStream#readClassDescriptor()
 626      * @see #useProtocolVersion(int)
 627      * @see java.io.ObjectStreamConstants#PROTOCOL_VERSION_1
 628      * @since 1.3
 629      */
 630     protected void writeClassDescriptor(ObjectStreamClass desc)
 631         throws IOException
 632     {
 633         desc.writeNonProxy(this);
 634     }
 635 
 636     /**
 637      * Writes a byte. This method will block until the byte is actually
 638      * written.
 639      *
 640      * @param   val the byte to be written to the stream
 641      * @throws  IOException If an I/O error has occurred.
 642      */
 643     @Override
 644     public void write(int val) throws IOException {
 645         bout.write(val);
 646     }
 647 
 648     /**
 649      * Writes an array of bytes. This method will block until the bytes are
 650      * actually written.
 651      *
 652      * @param   buf the data to be written
 653      * @throws  IOException If an I/O error has occurred.
 654      */
 655     @Override
 656     public void write(byte[] buf) throws IOException {
 657         bout.write(buf, 0, buf.length, false);
 658     }
 659 
 660     /**
 661      * Writes a sub array of bytes.
 662      *
 663      * @param   buf the data to be written
 664      * @param   off the start offset in the data
 665      * @param   len the number of bytes that are written
 666      * @throws  IOException {@inheritDoc}
 667      * @throws  IndexOutOfBoundsException {@inheritDoc}
 668      */
 669     @Override
 670     public void write(byte[] buf, int off, int len) throws IOException {
 671         if (buf == null) {
 672             throw new NullPointerException();
 673         }
 674         Objects.checkFromIndexSize(off, len, buf.length);
 675         bout.write(buf, off, len, false);
 676     }
 677 
 678     /**
 679      * Flushes the stream. This will write any buffered output bytes and flush
 680      * through to the underlying stream.
 681      *
 682      * @throws  IOException {@inheritDoc}
 683      */
 684     @Override
 685     public void flush() throws IOException {
 686         bout.flush();
 687     }
 688 
 689     /**
 690      * Drain any buffered data in ObjectOutputStream.  Similar to flush but
 691      * does not propagate the flush to the underlying stream.
 692      *
 693      * @throws  IOException if I/O errors occur while writing to the underlying
 694      *          stream
 695      */
 696     protected void drain() throws IOException {
 697         bout.drain();
 698     }
 699 
 700     /**
 701      * Closes the stream. This method must be called to release any resources
 702      * associated with the stream.
 703      *
 704      * @throws  IOException If an I/O error has occurred.
 705      */
 706     @Override
 707     public void close() throws IOException {
 708         flush();
 709         clear();
 710         bout.close();
 711     }
 712 
 713     /**
 714      * Writes a boolean.
 715      *
 716      * @param   val the boolean to be written
 717      * @throws  IOException if I/O errors occur while writing to the underlying
 718      *          stream
 719      */
 720     public void writeBoolean(boolean val) throws IOException {
 721         bout.writeBoolean(val);
 722     }
 723 
 724     /**
 725      * Writes an 8-bit byte.
 726      *
 727      * @param   val the byte value to be written
 728      * @throws  IOException if I/O errors occur while writing to the underlying
 729      *          stream
 730      */
 731     public void writeByte(int val) throws IOException  {
 732         bout.writeByte(val);
 733     }
 734 
 735     /**
 736      * Writes a 16-bit short.
 737      *
 738      * @param   val the short value to be written
 739      * @throws  IOException if I/O errors occur while writing to the underlying
 740      *          stream
 741      */
 742     public void writeShort(int val)  throws IOException {
 743         bout.writeShort(val);
 744     }
 745 
 746     /**
 747      * Writes a 16-bit char.
 748      *
 749      * @param   val the char value to be written
 750      * @throws  IOException if I/O errors occur while writing to the underlying
 751      *          stream
 752      */
 753     public void writeChar(int val)  throws IOException {
 754         bout.writeChar(val);
 755     }
 756 
 757     /**
 758      * Writes a 32-bit int.
 759      *
 760      * @param   val the integer value to be written
 761      * @throws  IOException if I/O errors occur while writing to the underlying
 762      *          stream
 763      */
 764     public void writeInt(int val)  throws IOException {
 765         bout.writeInt(val);
 766     }
 767 
 768     /**
 769      * Writes a 64-bit long.
 770      *
 771      * @param   val the long value to be written
 772      * @throws  IOException if I/O errors occur while writing to the underlying
 773      *          stream
 774      */
 775     public void writeLong(long val)  throws IOException {
 776         bout.writeLong(val);
 777     }
 778 
 779     /**
 780      * Writes a 32-bit float.
 781      *
 782      * @param   val the float value to be written
 783      * @throws  IOException if I/O errors occur while writing to the underlying
 784      *          stream
 785      */
 786     public void writeFloat(float val) throws IOException {
 787         bout.writeFloat(val);
 788     }
 789 
 790     /**
 791      * Writes a 64-bit double.
 792      *
 793      * @param   val the double value to be written
 794      * @throws  IOException if I/O errors occur while writing to the underlying
 795      *          stream
 796      */
 797     public void writeDouble(double val) throws IOException {
 798         bout.writeDouble(val);
 799     }
 800 
 801     /**
 802      * Writes a String as a sequence of bytes.
 803      *
 804      * @param   str the String of bytes to be written
 805      * @throws  IOException if I/O errors occur while writing to the underlying
 806      *          stream
 807      */
 808     public void writeBytes(String str) throws IOException {
 809         bout.writeBytes(str);
 810     }
 811 
 812     /**
 813      * Writes a String as a sequence of chars.
 814      *
 815      * @param   str the String of chars to be written
 816      * @throws  IOException if I/O errors occur while writing to the underlying
 817      *          stream
 818      */
 819     public void writeChars(String str) throws IOException {
 820         bout.writeChars(str);
 821     }
 822 
 823     /**
 824      * Primitive data write of this String in
 825      * <a href="DataInput.html#modified-utf-8">modified UTF-8</a>
 826      * format.  Note that there is a
 827      * significant difference between writing a String into the stream as
 828      * primitive data or as an Object. A String instance written by writeObject
 829      * is written into the stream as a String initially. Future writeObject()
 830      * calls write references to the string into the stream.
 831      *
 832      * @param   str the String to be written
 833      * @throws  IOException if I/O errors occur while writing to the underlying
 834      *          stream
 835      */
 836     public void writeUTF(String str) throws IOException {
 837         bout.writeUTFInternal(str, false);
 838     }
 839 
 840     /**
 841      * Provide programmatic access to the persistent fields to be written
 842      * to ObjectOutput.
 843      *
 844      * @since 1.2
 845      */
 846     public abstract static class PutField {
 847         /**
 848          * Constructor for subclasses to call.
 849          */
 850         public PutField() {}
 851 
 852         /**
 853          * Put the value of the named boolean field into the persistent field.
 854          *
 855          * @param  name the name of the serializable field
 856          * @param  val the value to assign to the field
 857          * @throws IllegalArgumentException if {@code name} does not
 858          * match the name of a serializable field for the class whose fields
 859          * are being written, or if the type of the named field is not
 860          * {@code boolean}
 861          */
 862         public abstract void put(String name, boolean val);
 863 
 864         /**
 865          * Put the value of the named byte field into the persistent field.
 866          *
 867          * @param  name the name of the serializable field
 868          * @param  val the value to assign to the field
 869          * @throws IllegalArgumentException if {@code name} does not
 870          * match the name of a serializable field for the class whose fields
 871          * are being written, or if the type of the named field is not
 872          * {@code byte}
 873          */
 874         public abstract void put(String name, byte val);
 875 
 876         /**
 877          * Put the value of the named char field into the persistent field.
 878          *
 879          * @param  name the name of the serializable field
 880          * @param  val the value to assign to the field
 881          * @throws IllegalArgumentException if {@code name} does not
 882          * match the name of a serializable field for the class whose fields
 883          * are being written, or if the type of the named field is not
 884          * {@code char}
 885          */
 886         public abstract void put(String name, char val);
 887 
 888         /**
 889          * Put the value of the named short field into the persistent field.
 890          *
 891          * @param  name the name of the serializable field
 892          * @param  val the value to assign to the field
 893          * @throws IllegalArgumentException if {@code name} does not
 894          * match the name of a serializable field for the class whose fields
 895          * are being written, or if the type of the named field is not
 896          * {@code short}
 897          */
 898         public abstract void put(String name, short val);
 899 
 900         /**
 901          * Put the value of the named int field into the persistent field.
 902          *
 903          * @param  name the name of the serializable field
 904          * @param  val the value to assign to the field
 905          * @throws IllegalArgumentException if {@code name} does not
 906          * match the name of a serializable field for the class whose fields
 907          * are being written, or if the type of the named field is not
 908          * {@code int}
 909          */
 910         public abstract void put(String name, int val);
 911 
 912         /**
 913          * Put the value of the named long field into the persistent field.
 914          *
 915          * @param  name the name of the serializable field
 916          * @param  val the value to assign to the field
 917          * @throws IllegalArgumentException if {@code name} does not
 918          * match the name of a serializable field for the class whose fields
 919          * are being written, or if the type of the named field is not
 920          * {@code long}
 921          */
 922         public abstract void put(String name, long val);
 923 
 924         /**
 925          * Put the value of the named float field into the persistent field.
 926          *
 927          * @param  name the name of the serializable field
 928          * @param  val the value to assign to the field
 929          * @throws IllegalArgumentException if {@code name} does not
 930          * match the name of a serializable field for the class whose fields
 931          * are being written, or if the type of the named field is not
 932          * {@code float}
 933          */
 934         public abstract void put(String name, float val);
 935 
 936         /**
 937          * Put the value of the named double field into the persistent field.
 938          *
 939          * @param  name the name of the serializable field
 940          * @param  val the value to assign to the field
 941          * @throws IllegalArgumentException if {@code name} does not
 942          * match the name of a serializable field for the class whose fields
 943          * are being written, or if the type of the named field is not
 944          * {@code double}
 945          */
 946         public abstract void put(String name, double val);
 947 
 948         /**
 949          * Put the value of the named Object field into the persistent field.
 950          *
 951          * @param  name the name of the serializable field
 952          * @param  val the value to assign to the field
 953          *         (which may be {@code null})
 954          * @throws IllegalArgumentException if {@code name} does not
 955          * match the name of a serializable field for the class whose fields
 956          * are being written, or if the type of the named field is not a
 957          * reference type
 958          */
 959         public abstract void put(String name, Object val);
 960 
 961         /**
 962          * Write the data and fields to the specified ObjectOutput stream,
 963          * which must be the same stream that produced this
 964          * {@code PutField} object.
 965          *
 966          * @param  out the stream to write the data and fields to
 967          * @throws IOException if I/O errors occur while writing to the
 968          *         underlying stream
 969          * @throws IllegalArgumentException if the specified stream is not
 970          *         the same stream that produced this {@code PutField}
 971          *         object
 972          * @deprecated This method does not write the values contained by this
 973          *         {@code PutField} object in a proper format, and may
 974          *         result in corruption of the serialization stream.  The
 975          *         correct way to write {@code PutField} data is by
 976          *         calling the {@link java.io.ObjectOutputStream#writeFields()}
 977          *         method.
 978          */
 979         @Deprecated(forRemoval = true, since = "1.4")
 980         public abstract void write(ObjectOutput out) throws IOException;
 981     }
 982 
 983 
 984     /**
 985      * Returns protocol version in use.
 986      */
 987     int getProtocolVersion() {
 988         return protocol;
 989     }
 990 
 991     /**
 992      * Writes string without allowing it to be replaced in stream.  Used by
 993      * ObjectStreamClass to write class descriptor type strings.
 994      */
 995     void writeTypeString(String str) throws IOException {
 996         int handle;
 997         if (str == null) {
 998             writeNull();
 999         } else if ((handle = handles.lookup(str)) != -1) {
1000             writeHandle(handle);
1001         } else {
1002             writeString(str, false);
1003         }
1004     }
1005 
1006     /**
1007      * Performs reflective checks on given subclass to verify that it doesn't
1008      * override security-sensitive non-final methods.  Returns TRUE if subclass
1009      * is "safe", FALSE otherwise.
1010      */
1011     private static Boolean auditSubclass(Class<?> subcl) {
1012         for (Class<?> cl = subcl;
1013              cl != ObjectOutputStream.class;
1014              cl = cl.getSuperclass())
1015         {
1016             try {
1017                 cl.getDeclaredMethod(
1018                     "writeUnshared", new Class<?>[] { Object.class });
1019                 return Boolean.FALSE;
1020             } catch (NoSuchMethodException ex) {
1021             }
1022             try {
1023                 cl.getDeclaredMethod("putFields", (Class<?>[]) null);
1024                 return Boolean.FALSE;
1025             } catch (NoSuchMethodException ex) {
1026             }
1027         }
1028         return Boolean.TRUE;
1029     }
1030 
1031     /**
1032      * Clears internal data structures.
1033      */
1034     private void clear() {
1035         subs.clear();
1036         handles.clear();
1037     }
1038 
1039     /**
1040      * Underlying writeObject/writeUnshared implementation.
1041      */
1042     private void writeObject0(Object obj, boolean unshared)
1043         throws IOException
1044     {
1045         boolean oldMode = bout.setBlockDataMode(false);
1046         depth++;
1047         try {
1048             // handle previously written and non-replaceable objects
1049             int h;
1050             if ((obj = subs.lookup(obj)) == null) {
1051                 writeNull();
1052                 return;
1053             } else if (!unshared && (h = handles.lookup(obj)) != -1) {
1054                 writeHandle(h);
1055                 return;
1056             } else if (obj instanceof Class) {
1057                 writeClass((Class) obj, unshared);
1058                 return;
1059             } else if (obj instanceof ObjectStreamClass) {
1060                 writeClassDesc((ObjectStreamClass) obj, unshared);
1061                 return;
1062             }
1063 
1064             // check for replacement object
1065             Object orig = obj;
1066             Class<?> cl = obj.getClass();
1067             ObjectStreamClass desc;
1068             for (;;) {
1069                 // REMIND: skip this check for strings/arrays?
1070                 Class<?> repCl;
1071                 desc = ObjectStreamClass.lookup(cl, true);
1072                 if (!desc.hasWriteReplaceMethod() ||
1073                     (obj = desc.invokeWriteReplace(obj)) == null ||
1074                     (repCl = obj.getClass()) == cl)
1075                 {
1076                     break;
1077                 }
1078                 cl = repCl;
1079             }
1080             if (enableReplace) {
1081                 Object rep = replaceObject(obj);
1082                 if (rep != obj && rep != null) {
1083                     cl = rep.getClass();
1084                     desc = ObjectStreamClass.lookup(cl, true);
1085                 }
1086                 obj = rep;
1087             }
1088 
1089             // if object replaced, run through original checks a second time
1090             if (obj != orig) {
1091                 subs.assign(orig, obj);
1092                 if (obj == null) {
1093                     writeNull();
1094                     return;
1095                 } else if (!unshared && (h = handles.lookup(obj)) != -1) {
1096                     writeHandle(h);
1097                     return;
1098                 } else if (obj instanceof Class) {
1099                     writeClass((Class) obj, unshared);
1100                     return;
1101                 } else if (obj instanceof ObjectStreamClass) {
1102                     writeClassDesc((ObjectStreamClass) obj, unshared);
1103                     return;
1104                 }
1105             }
1106 
1107             // remaining cases
1108             if (obj instanceof String) {
1109                 writeString((String) obj, unshared);
1110             } else if (cl.isArray()) {
1111                 writeArray(obj, desc, unshared);
1112             } else if (obj instanceof Enum) {
1113                 writeEnum((Enum<?>) obj, desc, unshared);
1114             } else if (obj instanceof Serializable) {
1115                 writeOrdinaryObject(obj, desc, unshared);
1116             } else {
1117                 if (extendedDebugInfo) {
1118                     throw new NotSerializableException(
1119                         cl.getName() + "\n" + debugInfoStack.toString());
1120                 } else {
1121                     throw new NotSerializableException(cl.getName());
1122                 }
1123             }
1124         } finally {
1125             depth--;
1126             bout.setBlockDataMode(oldMode);
1127         }
1128     }
1129 
1130     /**
1131      * Writes null code to stream.
1132      */
1133     private void writeNull() throws IOException {
1134         bout.writeByte(TC_NULL);
1135     }
1136 
1137     /**
1138      * Writes given object handle to stream.
1139      */
1140     private void writeHandle(int handle) throws IOException {
1141         bout.writeByte(TC_REFERENCE);
1142         bout.writeInt(baseWireHandle + handle);
1143     }
1144 
1145     /**
1146      * Writes representation of given class to stream.
1147      */
1148     private void writeClass(Class<?> cl, boolean unshared) throws IOException {
1149         bout.writeByte(TC_CLASS);
1150         writeClassDesc(ObjectStreamClass.lookup(cl, true), false);
1151         handles.assign(unshared ? null : cl);
1152     }
1153 
1154     /**
1155      * Writes representation of given class descriptor to stream.
1156      */
1157     private void writeClassDesc(ObjectStreamClass desc, boolean unshared)
1158         throws IOException
1159     {
1160         int handle;
1161         if (desc == null) {
1162             writeNull();
1163         } else if (!unshared && (handle = handles.lookup(desc)) != -1) {
1164             writeHandle(handle);
1165         } else if (desc.isProxy()) {
1166             writeProxyDesc(desc, unshared);
1167         } else {
1168             writeNonProxyDesc(desc, unshared);
1169         }
1170     }
1171 
1172     /**
1173      * Writes class descriptor representing a dynamic proxy class to stream.
1174      */
1175     private void writeProxyDesc(ObjectStreamClass desc, boolean unshared)
1176         throws IOException
1177     {
1178         bout.writeByte(TC_PROXYCLASSDESC);
1179         handles.assign(unshared ? null : desc);
1180 
1181         Class<?> cl = desc.forClass();
1182         Class<?>[] ifaces = cl.getInterfaces();
1183         bout.writeInt(ifaces.length);
1184         for (int i = 0; i < ifaces.length; i++) {
1185             bout.writeUTF(ifaces[i].getName());
1186         }
1187 
1188         bout.setBlockDataMode(true);
1189         annotateProxyClass(cl);
1190         bout.setBlockDataMode(false);
1191         bout.writeByte(TC_ENDBLOCKDATA);
1192 
1193         writeClassDesc(desc.getSuperDesc(), false);
1194     }
1195 
1196     /**
1197      * Writes class descriptor representing a standard (i.e., not a dynamic
1198      * proxy) class to stream.
1199      */
1200     private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared)
1201         throws IOException
1202     {
1203         bout.writeByte(TC_CLASSDESC);
1204         handles.assign(unshared ? null : desc);
1205 
1206         if (protocol == PROTOCOL_VERSION_1) {
1207             // do not invoke class descriptor write hook with old protocol
1208             desc.writeNonProxy(this);
1209         } else {
1210             writeClassDescriptor(desc);
1211         }
1212 
1213         Class<?> cl = desc.forClass();
1214         bout.setBlockDataMode(true);
1215         annotateClass(cl);
1216         bout.setBlockDataMode(false);
1217         bout.writeByte(TC_ENDBLOCKDATA);
1218 
1219         writeClassDesc(desc.getSuperDesc(), false);
1220     }
1221 
1222     /**
1223      * Writes given string to stream, using standard or long UTF format
1224      * depending on string length.
1225      */
1226     private void writeString(String str, boolean unshared) throws IOException {
1227         handles.assign(unshared ? null : str);
1228         bout.writeUTFInternal(str, true);
1229     }
1230 
1231     /**
1232      * Writes given array object to stream.
1233      */
1234     private void writeArray(Object array,
1235                             ObjectStreamClass desc,
1236                             boolean unshared)
1237         throws IOException
1238     {
1239         bout.writeByte(TC_ARRAY);
1240         writeClassDesc(desc, false);
1241         handles.assign(unshared ? null : array);
1242 
1243         Class<?> ccl = desc.forClass().getComponentType();
1244         if (ccl.isPrimitive()) {
1245             if (ccl == Integer.TYPE) {
1246                 int[] ia = (int[]) array;
1247                 bout.writeInt(ia.length);
1248                 bout.writeInts(ia, 0, ia.length);
1249             } else if (ccl == Byte.TYPE) {
1250                 byte[] ba = (byte[]) array;
1251                 bout.writeInt(ba.length);
1252                 bout.write(ba, 0, ba.length, true);
1253             } else if (ccl == Long.TYPE) {
1254                 long[] ja = (long[]) array;
1255                 bout.writeInt(ja.length);
1256                 bout.writeLongs(ja, 0, ja.length);
1257             } else if (ccl == Float.TYPE) {
1258                 float[] fa = (float[]) array;
1259                 bout.writeInt(fa.length);
1260                 bout.writeFloats(fa, 0, fa.length);
1261             } else if (ccl == Double.TYPE) {
1262                 double[] da = (double[]) array;
1263                 bout.writeInt(da.length);
1264                 bout.writeDoubles(da, 0, da.length);
1265             } else if (ccl == Short.TYPE) {
1266                 short[] sa = (short[]) array;
1267                 bout.writeInt(sa.length);
1268                 bout.writeShorts(sa, 0, sa.length);
1269             } else if (ccl == Character.TYPE) {
1270                 char[] ca = (char[]) array;
1271                 bout.writeInt(ca.length);
1272                 bout.writeChars(ca, 0, ca.length);
1273             } else if (ccl == Boolean.TYPE) {
1274                 boolean[] za = (boolean[]) array;
1275                 bout.writeInt(za.length);
1276                 bout.writeBooleans(za, 0, za.length);
1277             } else {
1278                 throw new InternalError();
1279             }
1280         } else {
1281             Object[] objs = (Object[]) array;
1282             int len = objs.length;
1283             bout.writeInt(len);
1284             if (extendedDebugInfo) {
1285                 debugInfoStack.push(
1286                     "array (class \"" + array.getClass().getName() +
1287                     "\", size: " + len  + ")");
1288             }
1289             try {
1290                 for (int i = 0; i < len; i++) {
1291                     if (extendedDebugInfo) {
1292                         debugInfoStack.push(
1293                             "element of array (index: " + i + ")");
1294                     }
1295                     try {
1296                         writeObject0(objs[i], false);
1297                     } finally {
1298                         if (extendedDebugInfo) {
1299                             debugInfoStack.pop();
1300                         }
1301                     }
1302                 }
1303             } finally {
1304                 if (extendedDebugInfo) {
1305                     debugInfoStack.pop();
1306                 }
1307             }
1308         }
1309     }
1310 
1311     /**
1312      * Writes given enum constant to stream.
1313      */
1314     private void writeEnum(Enum<?> en,
1315                            ObjectStreamClass desc,
1316                            boolean unshared)
1317         throws IOException
1318     {
1319         bout.writeByte(TC_ENUM);
1320         ObjectStreamClass sdesc = desc.getSuperDesc();
1321         writeClassDesc((sdesc.forClass() == Enum.class) ? desc : sdesc, false);
1322         handles.assign(unshared ? null : en);
1323         writeString(en.name(), false);
1324     }
1325 
1326     /**
1327      * Writes representation of an "ordinary" (i.e., not a String, Class,
1328      * ObjectStreamClass, array, or enum constant) serializable object to the
1329      * stream.
1330      */
1331     private void writeOrdinaryObject(Object obj,
1332                                      ObjectStreamClass desc,
1333                                      boolean unshared)
1334         throws IOException
1335     {
1336         if (extendedDebugInfo) {
1337             debugInfoStack.push(
1338                 (depth == 1 ? "root " : "") + "object (class \"" +
1339                 obj.getClass().getName() + "\", " + obj.toString() + ")");
1340         }
1341         try {
1342             desc.checkSerialize();
1343 
1344             bout.writeByte(TC_OBJECT);
1345             writeClassDesc(desc, false);
1346             handles.assign(unshared ? null : obj);
1347 
1348             if (desc.isRecord()) {
1349                 writeRecordData(obj, desc);
1350             } else if (desc.isExternalizable() && !desc.isProxy()) {
1351                 writeExternalData((Externalizable) obj);
1352             } else {
1353                 writeSerialData(obj, desc);
1354             }
1355         } finally {
1356             if (extendedDebugInfo) {
1357                 debugInfoStack.pop();
1358             }
1359         }
1360     }
1361 
1362     /**
1363      * Writes externalizable data of given object by invoking its
1364      * writeExternal() method.
1365      */
1366     private void writeExternalData(Externalizable obj) throws IOException {
1367         PutFieldImpl oldPut = curPut;
1368         curPut = null;
1369 
1370         if (extendedDebugInfo) {
1371             debugInfoStack.push("writeExternal data");
1372         }
1373         SerialCallbackContext oldContext = curContext;
1374         try {
1375             curContext = null;
1376             if (protocol == PROTOCOL_VERSION_1) {
1377                 obj.writeExternal(this);
1378             } else {
1379                 bout.setBlockDataMode(true);
1380                 obj.writeExternal(this);
1381                 bout.setBlockDataMode(false);
1382                 bout.writeByte(TC_ENDBLOCKDATA);
1383             }
1384         } finally {
1385             curContext = oldContext;
1386             if (extendedDebugInfo) {
1387                 debugInfoStack.pop();
1388             }
1389         }
1390 
1391         curPut = oldPut;
1392     }
1393 
1394     /** Writes the record component values for the given record object. */
1395     private void writeRecordData(Object obj, ObjectStreamClass desc)
1396         throws IOException
1397     {
1398         assert obj.getClass().isRecord();
1399         ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
1400         if (slots.length != 1) {
1401             throw new InvalidClassException(
1402                     "expected a single record slot length, but found: " + slots.length);
1403         }
1404 
1405         defaultWriteFields(obj, desc);  // #### seems unnecessary to use the accessors
1406     }
1407 
1408     /**
1409      * Writes instance data for each serializable class of given object, from
1410      * superclass to subclass.
1411      */
1412     private void writeSerialData(Object obj, ObjectStreamClass desc)
1413         throws IOException
1414     {
1415         ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();
1416         for (int i = 0; i < slots.length; i++) {
1417             ObjectStreamClass slotDesc = slots[i].desc;
1418             if (slotDesc.hasWriteObjectMethod()) {
1419                 PutFieldImpl oldPut = curPut;
1420                 curPut = null;
1421                 SerialCallbackContext oldContext = curContext;
1422 
1423                 if (extendedDebugInfo) {
1424                     debugInfoStack.push(
1425                         "custom writeObject data (class \"" +
1426                         slotDesc.getName() + "\")");
1427                 }
1428                 try {
1429                     curContext = new SerialCallbackContext(obj, slotDesc);
1430                     bout.setBlockDataMode(true);
1431                     slotDesc.invokeWriteObject(obj, this);
1432                     bout.setBlockDataMode(false);
1433                     bout.writeByte(TC_ENDBLOCKDATA);
1434                 } finally {
1435                     curContext.setUsed();
1436                     curContext = oldContext;
1437                     if (extendedDebugInfo) {
1438                         debugInfoStack.pop();
1439                     }
1440                 }
1441 
1442                 curPut = oldPut;
1443             } else {
1444                 defaultWriteFields(obj, slotDesc);
1445             }
1446         }
1447     }
1448 
1449     /**
1450      * Fetches and writes values of serializable fields of given object to
1451      * stream.  The given class descriptor specifies which field values to
1452      * write, and in which order they should be written.
1453      */
1454     private void defaultWriteFields(Object obj, ObjectStreamClass desc)
1455         throws IOException
1456     {
1457         Class<?> cl = desc.forClass();
1458         if (cl != null && obj != null && !cl.isInstance(obj)) {
1459             throw new ClassCastException();
1460         }
1461 
1462         desc.checkDefaultSerialize();
1463 
1464         int primDataSize = desc.getPrimDataSize();
1465         if (primDataSize > 0) {
1466             if (primVals == null || primVals.length < primDataSize) {
1467                 primVals = new byte[primDataSize];
1468             }
1469             desc.getPrimFieldValues(obj, primVals);
1470             bout.write(primVals, 0, primDataSize, false);
1471         }
1472 
1473         int numObjFields = desc.getNumObjFields();
1474         if (numObjFields > 0) {
1475             ObjectStreamField[] fields = desc.getFields(false);
1476             Object[] objVals = new Object[numObjFields];
1477             int numPrimFields = fields.length - objVals.length;
1478             desc.getObjFieldValues(obj, objVals);
1479             for (int i = 0; i < objVals.length; i++) {
1480                 if (extendedDebugInfo) {
1481                     debugInfoStack.push(
1482                         "field (class \"" + desc.getName() + "\", name: \"" +
1483                         fields[numPrimFields + i].getName() + "\", type: \"" +
1484                         fields[numPrimFields + i].getType() + "\")");
1485                 }
1486                 try {
1487                     writeObject0(objVals[i],
1488                                  fields[numPrimFields + i].isUnshared());
1489                 } finally {
1490                     if (extendedDebugInfo) {
1491                         debugInfoStack.pop();
1492                     }
1493                 }
1494             }
1495         }
1496     }
1497 
1498     /**
1499      * Attempts to write to stream fatal IOException that has caused
1500      * serialization to abort.
1501      */
1502     private void writeFatalException(IOException ex) throws IOException {
1503         /*
1504          * Note: the serialization specification states that if a second
1505          * IOException occurs while attempting to serialize the original fatal
1506          * exception to the stream, then a StreamCorruptedException should be
1507          * thrown (section 2.1).  However, due to a bug in previous
1508          * implementations of serialization, StreamCorruptedExceptions were
1509          * rarely (if ever) actually thrown--the "root" exceptions from
1510          * underlying streams were thrown instead.  This historical behavior is
1511          * followed here for consistency.
1512          */
1513         clear();
1514         boolean oldMode = bout.setBlockDataMode(false);
1515         try {
1516             bout.writeByte(TC_EXCEPTION);
1517             writeObject0(ex, false);
1518             clear();
1519         } finally {
1520             bout.setBlockDataMode(oldMode);
1521         }
1522     }
1523 
1524     /**
1525      * Default PutField implementation.
1526      */
1527     private class PutFieldImpl extends PutField {
1528 
1529         /** class descriptor describing serializable fields */
1530         private final ObjectStreamClass desc;
1531         /** primitive field values */
1532         private final byte[] primVals;
1533         /** object field values */
1534         private final Object[] objVals;
1535 
1536         /**
1537          * Creates PutFieldImpl object for writing fields defined in given
1538          * class descriptor.
1539          */
1540         PutFieldImpl(ObjectStreamClass desc) {
1541             this.desc = desc;
1542             primVals = new byte[desc.getPrimDataSize()];
1543             objVals = new Object[desc.getNumObjFields()];
1544         }
1545 
1546         public void put(String name, boolean val) {
1547             ByteArray.setBoolean(primVals, getFieldOffset(name, Boolean.TYPE), val);
1548         }
1549 
1550         public void put(String name, byte val) {
1551             primVals[getFieldOffset(name, Byte.TYPE)] = val;
1552         }
1553 
1554         public void put(String name, char val) {
1555             ByteArray.setChar(primVals, getFieldOffset(name, Character.TYPE), val);
1556         }
1557 
1558         public void put(String name, short val) {
1559             ByteArray.setShort(primVals, getFieldOffset(name, Short.TYPE), val);
1560         }
1561 
1562         public void put(String name, int val) {
1563             ByteArray.setInt(primVals, getFieldOffset(name, Integer.TYPE), val);
1564         }
1565 
1566         public void put(String name, float val) {
1567             ByteArray.setFloat(primVals, getFieldOffset(name, Float.TYPE), val);
1568         }
1569 
1570         public void put(String name, long val) {
1571             ByteArray.setLong(primVals, getFieldOffset(name, Long.TYPE), val);
1572         }
1573 
1574         public void put(String name, double val) {
1575             ByteArray.setDouble(primVals, getFieldOffset(name, Double.TYPE), val);
1576         }
1577 
1578         public void put(String name, Object val) {
1579             objVals[getFieldOffset(name, Object.class)] = val;
1580         }
1581 
1582         // deprecated in ObjectOutputStream.PutField
1583         public void write(ObjectOutput out) throws IOException {
1584             /*
1585              * Applications should *not* use this method to write PutField
1586              * data, as it will lead to stream corruption if the PutField
1587              * object writes any primitive data (since block data mode is not
1588              * unset/set properly, as is done in OOS.writeFields()).  This
1589              * broken implementation is being retained solely for behavioral
1590              * compatibility, in order to support applications which use
1591              * OOS.PutField.write() for writing only non-primitive data.
1592              *
1593              * Serialization of unshared objects is not implemented here since
1594              * it is not necessary for backwards compatibility; also, unshared
1595              * semantics may not be supported by the given ObjectOutput
1596              * instance.  Applications which write unshared objects using the
1597              * PutField API must use OOS.writeFields().
1598              */
1599             if (ObjectOutputStream.this != out) {
1600                 throw new IllegalArgumentException("wrong stream");
1601             }
1602             out.write(primVals, 0, primVals.length);
1603 
1604             ObjectStreamField[] fields = desc.getFields(false);
1605             int numPrimFields = fields.length - objVals.length;
1606             // REMIND: warn if numPrimFields > 0?
1607             for (int i = 0; i < objVals.length; i++) {
1608                 if (fields[numPrimFields + i].isUnshared()) {
1609                     throw new IOException("cannot write unshared object");
1610                 }
1611                 out.writeObject(objVals[i]);
1612             }
1613         }
1614 
1615         /**
1616          * Writes buffered primitive data and object fields to stream.
1617          */
1618         void writeFields() throws IOException {
1619             bout.write(primVals, 0, primVals.length, false);
1620 
1621             ObjectStreamField[] fields = desc.getFields(false);
1622             int numPrimFields = fields.length - objVals.length;
1623             for (int i = 0; i < objVals.length; i++) {
1624                 if (extendedDebugInfo) {
1625                     debugInfoStack.push(
1626                         "field (class \"" + desc.getName() + "\", name: \"" +
1627                         fields[numPrimFields + i].getName() + "\", type: \"" +
1628                         fields[numPrimFields + i].getType() + "\")");
1629                 }
1630                 try {
1631                     writeObject0(objVals[i],
1632                                  fields[numPrimFields + i].isUnshared());
1633                 } finally {
1634                     if (extendedDebugInfo) {
1635                         debugInfoStack.pop();
1636                     }
1637                 }
1638             }
1639         }
1640 
1641         /**
1642          * Returns offset of field with given name and type.  A specified type
1643          * of null matches all types, Object.class matches all non-primitive
1644          * types, and any other non-null type matches assignable types only.
1645          * Throws IllegalArgumentException if no matching field found.
1646          */
1647         private int getFieldOffset(String name, Class<?> type) {
1648             ObjectStreamField field = desc.getField(name, type);
1649             if (field == null) {
1650                 throw new IllegalArgumentException("no such field " + name +
1651                                                    " with type " + type);
1652             }
1653             return field.getOffset();
1654         }
1655     }
1656 
1657     /**
1658      * Buffered output stream with two modes: in default mode, outputs data in
1659      * same format as DataOutputStream; in "block data" mode, outputs data
1660      * bracketed by block data markers (see object serialization specification
1661      * for details).
1662      */
1663     private static final class BlockDataOutputStream
1664         extends OutputStream implements DataOutput
1665     {
1666         /** maximum data block length */
1667         private static final int MAX_BLOCK_SIZE = 1024;
1668         /** maximum data block header length */
1669         private static final int MAX_HEADER_SIZE = 5;
1670         /** (tunable) length of char buffer (for writing strings) */
1671         private static final int CHAR_BUF_SIZE = 256;
1672 
1673         /** buffer for writing general/block data */
1674         private final byte[] buf = new byte[MAX_BLOCK_SIZE];
1675         /** buffer for writing block data headers */
1676         private final byte[] hbuf = new byte[MAX_HEADER_SIZE];
1677         /** char buffer for fast string writes */
1678         private final char[] cbuf = new char[CHAR_BUF_SIZE];
1679 
1680         /** block data mode */
1681         private boolean blkmode = false;
1682         /** current offset into buf */
1683         private int pos = 0;
1684 
1685         /** underlying output stream */
1686         private final OutputStream out;
1687         /** loopback stream (for data writes that span data blocks) */
1688         private final DataOutputStream dout;
1689 
1690         /**
1691          * Creates new BlockDataOutputStream on top of given underlying stream.
1692          * Block data mode is turned off by default.
1693          */
1694         BlockDataOutputStream(OutputStream out) {
1695             this.out = out;
1696             dout = new DataOutputStream(this);
1697         }
1698 
1699         /**
1700          * Sets block data mode to the given mode (true == on, false == off)
1701          * and returns the previous mode value.  If the new mode is the same as
1702          * the old mode, no action is taken.  If the new mode differs from the
1703          * old mode, any buffered data is flushed before switching to the new
1704          * mode.
1705          */
1706         boolean setBlockDataMode(boolean mode) throws IOException {
1707             if (blkmode == mode) {
1708                 return blkmode;
1709             }
1710             drain();
1711             blkmode = mode;
1712             return !blkmode;
1713         }
1714 
1715         /**
1716          * Returns true if the stream is currently in block data mode, false
1717          * otherwise.
1718          */
1719         boolean getBlockDataMode() {
1720             return blkmode;
1721         }
1722 
1723         /* ----------------- generic output stream methods ----------------- */
1724         /*
1725          * The following methods are equivalent to their counterparts in
1726          * OutputStream, except that they partition written data into data
1727          * blocks when in block data mode.
1728          */
1729 
1730         public void write(int b) throws IOException {
1731             if (pos >= MAX_BLOCK_SIZE) {
1732                 drain();
1733             }
1734             buf[pos++] = (byte) b;
1735         }
1736 
1737         public void write(byte[] b) throws IOException {
1738             write(b, 0, b.length, false);
1739         }
1740 
1741         public void write(byte[] b, int off, int len) throws IOException {
1742             write(b, off, len, false);
1743         }
1744 
1745         public void flush() throws IOException {
1746             drain();
1747             out.flush();
1748         }
1749 
1750         public void close() throws IOException {
1751             flush();
1752             out.close();
1753         }
1754 
1755         /**
1756          * Writes specified span of byte values from given array.  If copy is
1757          * true, copies the values to an intermediate buffer before writing
1758          * them to underlying stream (to avoid exposing a reference to the
1759          * original byte array).
1760          */
1761         void write(byte[] b, int off, int len, boolean copy)
1762             throws IOException
1763         {
1764             if (!(copy || blkmode)) {           // write directly
1765                 drain();
1766                 out.write(b, off, len);
1767                 return;
1768             }
1769 
1770             while (len > 0) {
1771                 if (pos >= MAX_BLOCK_SIZE) {
1772                     drain();
1773                 }
1774                 if (len >= MAX_BLOCK_SIZE && !copy && pos == 0) {
1775                     // avoid unnecessary copy
1776                     writeBlockHeader(MAX_BLOCK_SIZE);
1777                     out.write(b, off, MAX_BLOCK_SIZE);
1778                     off += MAX_BLOCK_SIZE;
1779                     len -= MAX_BLOCK_SIZE;
1780                 } else {
1781                     int wlen = Math.min(len, MAX_BLOCK_SIZE - pos);
1782                     System.arraycopy(b, off, buf, pos, wlen);
1783                     pos += wlen;
1784                     off += wlen;
1785                     len -= wlen;
1786                 }
1787             }
1788         }
1789 
1790         /**
1791          * Writes all buffered data from this stream to the underlying stream,
1792          * but does not flush underlying stream.
1793          */
1794         void drain() throws IOException {
1795             if (pos == 0) {
1796                 return;
1797             }
1798             if (blkmode) {
1799                 writeBlockHeader(pos);
1800             }
1801             out.write(buf, 0, pos);
1802             pos = 0;
1803         }
1804 
1805         /**
1806          * Writes block data header.  Data blocks shorter than 256 bytes are
1807          * prefixed with a 2-byte header; all others start with a 5-byte
1808          * header.
1809          */
1810         private void writeBlockHeader(int len) throws IOException {
1811             if (len <= 0xFF) {
1812                 hbuf[0] = TC_BLOCKDATA;
1813                 hbuf[1] = (byte) len;
1814                 out.write(hbuf, 0, 2);
1815             } else {
1816                 hbuf[0] = TC_BLOCKDATALONG;
1817                 ByteArray.setInt(hbuf, 1, len);
1818                 out.write(hbuf, 0, 5);
1819             }
1820         }
1821 
1822 
1823         /* ----------------- primitive data output methods ----------------- */
1824         /*
1825          * The following methods are equivalent to their counterparts in
1826          * DataOutputStream, except that they partition written data into data
1827          * blocks when in block data mode.
1828          */
1829 
1830         public void writeBoolean(boolean v) throws IOException {
1831             if (pos >= MAX_BLOCK_SIZE) {
1832                 drain();
1833             }
1834             ByteArray.setBoolean(buf, pos++, v);
1835         }
1836 
1837         public void writeByte(int v) throws IOException {
1838             if (pos >= MAX_BLOCK_SIZE) {
1839                 drain();
1840             }
1841             buf[pos++] = (byte) v;
1842         }
1843 
1844         public void writeChar(int v) throws IOException {
1845             if (pos + 2 <= MAX_BLOCK_SIZE) {
1846                 ByteArray.setChar(buf, pos, (char) v);
1847                 pos += 2;
1848             } else {
1849                 dout.writeChar(v);
1850             }
1851         }
1852 
1853         public void writeShort(int v) throws IOException {
1854             if (pos + 2 <= MAX_BLOCK_SIZE) {
1855                 ByteArray.setShort(buf, pos, (short) v);
1856                 pos += 2;
1857             } else {
1858                 dout.writeShort(v);
1859             }
1860         }
1861 
1862         public void writeInt(int v) throws IOException {
1863             if (pos + 4 <= MAX_BLOCK_SIZE) {
1864                 ByteArray.setInt(buf, pos, v);
1865                 pos += 4;
1866             } else {
1867                 dout.writeInt(v);
1868             }
1869         }
1870 
1871         public void writeFloat(float v) throws IOException {
1872             if (pos + 4 <= MAX_BLOCK_SIZE) {
1873                 ByteArray.setFloat(buf, pos, v);
1874                 pos += 4;
1875             } else {
1876                 dout.writeFloat(v);
1877             }
1878         }
1879 
1880         public void writeLong(long v) throws IOException {
1881             if (pos + 8 <= MAX_BLOCK_SIZE) {
1882                 ByteArray.setLong(buf, pos, v);
1883                 pos += 8;
1884             } else {
1885                 dout.writeLong(v);
1886             }
1887         }
1888 
1889         public void writeDouble(double v) throws IOException {
1890             if (pos + 8 <= MAX_BLOCK_SIZE) {
1891                 ByteArray.setDouble(buf, pos, v);
1892                 pos += 8;
1893             } else {
1894                 dout.writeDouble(v);
1895             }
1896         }
1897 
1898         @SuppressWarnings("deprecation")
1899         void writeBytes(String s, int len) throws IOException {
1900             int pos = this.pos;
1901             for (int strpos = 0; strpos < len;) {
1902                 int rem = MAX_BLOCK_SIZE - pos;
1903                 int csize = Math.min(len - strpos, rem);
1904                 s.getBytes(strpos, strpos + csize, buf, pos);
1905                 pos += csize;
1906                 strpos += csize;
1907 
1908                 if (pos == MAX_BLOCK_SIZE) {
1909                     this.pos = pos;
1910                     drain();
1911                     pos = 0;
1912                 }
1913             }
1914             this.pos = pos;
1915         }
1916 
1917         public void writeBytes(String s) throws IOException {
1918             writeBytes(s, s.length());
1919         }
1920 
1921         public void writeChars(String s) throws IOException {
1922             int endoff = s.length();
1923             for (int off = 0; off < endoff; ) {
1924                 int csize = Math.min(endoff - off, CHAR_BUF_SIZE);
1925                 s.getChars(off, off + csize, cbuf, 0);
1926                 writeChars(cbuf, 0, csize);
1927                 off += csize;
1928             }
1929         }
1930 
1931         public void writeUTF(String str) throws IOException {
1932             writeUTFInternal(str, false);
1933         }
1934 
1935         private void writeUTFInternal(String str, boolean writeHeader) throws IOException {
1936             int strlen = str.length();
1937             int countNonZeroAscii = JLA.countNonZeroAscii(str);
1938             int utflen = utfLen(str, countNonZeroAscii);
1939             if (utflen <= 0xFFFF) {
1940                 if(writeHeader) {
1941                     writeByte(TC_STRING);
1942                 }
1943                 writeShort(utflen);
1944             } else {
1945                 if(writeHeader) {
1946                     writeByte(TC_LONGSTRING);
1947                 }
1948                 writeLong(utflen);
1949             }
1950 
1951             if (countNonZeroAscii != 0) {
1952                 writeBytes(str, countNonZeroAscii);
1953             }
1954             if (countNonZeroAscii != strlen) {
1955                 writeMoreUTF(str, countNonZeroAscii);
1956             }
1957         }
1958 
1959         private void writeMoreUTF(String str, int stroff) throws IOException {
1960             int pos = this.pos;
1961             for (int strlen = str.length(); stroff < strlen;) {
1962                 char c = str.charAt(stroff++);
1963                 int csize = c != 0 && c < 0x80 ? 1 : c >= 0x800 ? 3 : 2;
1964                 if (pos + csize >= MAX_BLOCK_SIZE) {
1965                     this.pos = pos;
1966                     drain();
1967                     pos = 0;
1968                 }
1969                 pos = putChar(buf, pos, c);
1970             }
1971             this.pos = pos;
1972         }
1973 
1974 
1975         /* -------------- primitive data array output methods -------------- */
1976         /*
1977          * The following methods write out spans of primitive data values.
1978          * Though equivalent to calling the corresponding primitive write
1979          * methods repeatedly, these methods are optimized for writing groups
1980          * of primitive data values more efficiently.
1981          */
1982 
1983         void writeBooleans(boolean[] v, int off, int len) throws IOException {
1984             int endoff = off + len;
1985             while (off < endoff) {
1986                 if (pos >= MAX_BLOCK_SIZE) {
1987                     drain();
1988                 }
1989                 int stop = Math.min(endoff, off + (MAX_BLOCK_SIZE - pos));
1990                 while (off < stop) {
1991                     ByteArray.setBoolean(buf, pos++, v[off++]);
1992                 }
1993             }
1994         }
1995 
1996         void writeChars(char[] v, int off, int len) throws IOException {
1997             int limit = MAX_BLOCK_SIZE - 2;
1998             int endoff = off + len;
1999             while (off < endoff) {
2000                 if (pos <= limit) {
2001                     int avail = (MAX_BLOCK_SIZE - pos) >> 1;
2002                     int stop = Math.min(endoff, off + avail);
2003                     while (off < stop) {
2004                         ByteArray.setChar(buf, pos, v[off++]);
2005                         pos += 2;
2006                     }
2007                 } else {
2008                     dout.writeChar(v[off++]);
2009                 }
2010             }
2011         }
2012 
2013         void writeShorts(short[] v, int off, int len) throws IOException {
2014             int limit = MAX_BLOCK_SIZE - 2;
2015             int endoff = off + len;
2016             while (off < endoff) {
2017                 if (pos <= limit) {
2018                     int avail = (MAX_BLOCK_SIZE - pos) >> 1;
2019                     int stop = Math.min(endoff, off + avail);
2020                     while (off < stop) {
2021                         ByteArray.setShort(buf, pos, v[off++]);
2022                         pos += 2;
2023                     }
2024                 } else {
2025                     dout.writeShort(v[off++]);
2026                 }
2027             }
2028         }
2029 
2030         void writeInts(int[] v, int off, int len) throws IOException {
2031             int limit = MAX_BLOCK_SIZE - 4;
2032             int endoff = off + len;
2033             while (off < endoff) {
2034                 if (pos <= limit) {
2035                     int avail = (MAX_BLOCK_SIZE - pos) >> 2;
2036                     int stop = Math.min(endoff, off + avail);
2037                     while (off < stop) {
2038                         ByteArray.setInt(buf, pos, v[off++]);
2039                         pos += 4;
2040                     }
2041                 } else {
2042                     dout.writeInt(v[off++]);
2043                 }
2044             }
2045         }
2046 
2047         void writeFloats(float[] v, int off, int len) throws IOException {
2048             int limit = MAX_BLOCK_SIZE - 4;
2049             int endoff = off + len;
2050             while (off < endoff) {
2051                 if (pos <= limit) {
2052                     int avail = (MAX_BLOCK_SIZE - pos) >> 2;
2053                     int stop = Math.min(endoff, off + avail);
2054                     while (off < stop) {
2055                         ByteArray.setFloat(buf, pos, v[off++]);
2056                         pos += 4;
2057                     }
2058                 } else {
2059                     dout.writeFloat(v[off++]);
2060                 }
2061             }
2062         }
2063 
2064         void writeLongs(long[] v, int off, int len) throws IOException {
2065             int limit = MAX_BLOCK_SIZE - 8;
2066             int endoff = off + len;
2067             while (off < endoff) {
2068                 if (pos <= limit) {
2069                     int avail = (MAX_BLOCK_SIZE - pos) >> 3;
2070                     int stop = Math.min(endoff, off + avail);
2071                     while (off < stop) {
2072                         ByteArray.setLong(buf, pos, v[off++]);
2073                         pos += 8;
2074                     }
2075                 } else {
2076                     dout.writeLong(v[off++]);
2077                 }
2078             }
2079         }
2080 
2081         void writeDoubles(double[] v, int off, int len) throws IOException {
2082             int limit = MAX_BLOCK_SIZE - 8;
2083             int endoff = off + len;
2084             while (off < endoff) {
2085                 if (pos <= limit) {
2086                     int avail = (MAX_BLOCK_SIZE - pos) >> 3;
2087                     int stop = Math.min(endoff, off + avail);
2088                     while (off < stop) {
2089                         ByteArray.setDouble(buf, pos, v[off++]);
2090                         pos += 8;
2091                     }
2092                 } else {
2093                     dout.writeDouble(v[off++]);
2094                 }
2095             }
2096         }
2097     }
2098 
2099     /**
2100      * Lightweight identity hash table which maps objects to integer handles,
2101      * assigned in ascending order.
2102      */
2103     private static final class HandleTable {
2104 
2105         /* number of mappings in table/next available handle */
2106         private int size;
2107         /* size threshold determining when to expand hash spine */
2108         private int threshold;
2109         /* factor for computing size threshold */
2110         private final float loadFactor;
2111         /* maps hash value -> candidate handle value */
2112         private int[] spine;
2113         /* maps handle value -> next candidate handle value */
2114         private int[] next;
2115         /* maps handle value -> associated object */
2116         private Object[] objs;
2117 
2118         /**
2119          * Creates new HandleTable with given capacity and load factor.
2120          */
2121         HandleTable(int initialCapacity, float loadFactor) {
2122             this.loadFactor = loadFactor;
2123             spine = new int[initialCapacity];
2124             next = new int[initialCapacity];
2125             objs = new Object[initialCapacity];
2126             threshold = (int) (initialCapacity * loadFactor);
2127             clear();
2128         }
2129 
2130         /**
2131          * Assigns next available handle to given object, and returns handle
2132          * value.  Handles are assigned in ascending order starting at 0.
2133          */
2134         int assign(Object obj) {
2135             if (size >= next.length) {
2136                 growEntries();
2137             }
2138             if (size >= threshold) {
2139                 growSpine();
2140             }
2141             insert(obj, size);
2142             return size++;
2143         }
2144 
2145         /**
2146          * Looks up and returns handle associated with given object, or -1 if
2147          * no mapping found.
2148          */
2149         int lookup(Object obj) {
2150             if (size == 0) {
2151                 return -1;
2152             }
2153             int index = hash(obj) % spine.length;
2154             for (int i = spine[index]; i >= 0; i = next[i]) {
2155                 if (objs[i] == obj) {
2156                     return i;
2157                 }
2158             }
2159             return -1;
2160         }
2161 
2162         /**
2163          * Resets table to its initial (empty) state.
2164          */
2165         void clear() {
2166             Arrays.fill(spine, -1);
2167             Arrays.fill(objs, 0, size, null);
2168             size = 0;
2169         }
2170 
2171         /**
2172          * Returns the number of mappings currently in table.
2173          */
2174         int size() {
2175             return size;
2176         }
2177 
2178         /**
2179          * Inserts mapping object -> handle mapping into table.  Assumes table
2180          * is large enough to accommodate new mapping.
2181          */
2182         private void insert(Object obj, int handle) {
2183             int index = hash(obj) % spine.length;
2184             objs[handle] = obj;
2185             next[handle] = spine[index];
2186             spine[index] = handle;
2187         }
2188 
2189         /**
2190          * Expands the hash "spine" -- equivalent to increasing the number of
2191          * buckets in a conventional hash table.
2192          */
2193         private void growSpine() {
2194             spine = new int[(spine.length << 1) + 1];
2195             threshold = (int) (spine.length * loadFactor);
2196             Arrays.fill(spine, -1);
2197             for (int i = 0; i < size; i++) {
2198                 insert(objs[i], i);
2199             }
2200         }
2201 
2202         /**
2203          * Increases hash table capacity by lengthening entry arrays.
2204          */
2205         private void growEntries() {
2206             int newLength = (next.length << 1) + 1;
2207             int[] newNext = new int[newLength];
2208             System.arraycopy(next, 0, newNext, 0, size);
2209             next = newNext;
2210 
2211             Object[] newObjs = new Object[newLength];
2212             System.arraycopy(objs, 0, newObjs, 0, size);
2213             objs = newObjs;
2214         }
2215 
2216         /**
2217          * Returns hash value for given object.
2218          */
2219         private int hash(Object obj) {
2220             return System.identityHashCode(obj) & 0x7FFFFFFF;
2221         }
2222     }
2223 
2224     /**
2225      * Lightweight identity hash table which maps objects to replacement
2226      * objects.
2227      */
2228     private static final class ReplaceTable {
2229 
2230         /* maps object -> index */
2231         private final HandleTable htab;
2232         /* maps index -> replacement object */
2233         private Object[] reps;
2234 
2235         /**
2236          * Creates new ReplaceTable with given capacity and load factor.
2237          */
2238         ReplaceTable(int initialCapacity, float loadFactor) {
2239             htab = new HandleTable(initialCapacity, loadFactor);
2240             reps = new Object[initialCapacity];
2241         }
2242 
2243         /**
2244          * Enters mapping from object to replacement object.
2245          */
2246         void assign(Object obj, Object rep) {
2247             int index = htab.assign(obj);
2248             while (index >= reps.length) {
2249                 grow();
2250             }
2251             reps[index] = rep;
2252         }
2253 
2254         /**
2255          * Looks up and returns replacement for given object.  If no
2256          * replacement is found, returns the lookup object itself.
2257          */
2258         Object lookup(Object obj) {
2259             int index = htab.lookup(obj);
2260             return (index >= 0) ? reps[index] : obj;
2261         }
2262 
2263         /**
2264          * Resets table to its initial (empty) state.
2265          */
2266         void clear() {
2267             Arrays.fill(reps, 0, htab.size(), null);
2268             htab.clear();
2269         }
2270 
2271         /**
2272          * Returns the number of mappings currently in table.
2273          */
2274         int size() {
2275             return htab.size();
2276         }
2277 
2278         /**
2279          * Increases table capacity.
2280          */
2281         private void grow() {
2282             Object[] newReps = new Object[(reps.length << 1) + 1];
2283             System.arraycopy(reps, 0, newReps, 0, reps.length);
2284             reps = newReps;
2285         }
2286     }
2287 
2288     /**
2289      * Stack to keep debug information about the state of the
2290      * serialization process, for embedding in exception messages.
2291      */
2292     private static final class DebugTraceInfoStack {
2293         private final List<String> stack;
2294 
2295         DebugTraceInfoStack() {
2296             stack = new ArrayList<>();
2297         }
2298 
2299         /**
2300          * Removes all of the elements from enclosed list.
2301          */
2302         void clear() {
2303             stack.clear();
2304         }
2305 
2306         /**
2307          * Removes the object at the top of enclosed list.
2308          */
2309         void pop() {
2310             stack.remove(stack.size()-1);
2311         }
2312 
2313         /**
2314          * Pushes a String onto the top of enclosed list.
2315          */
2316         void push(String entry) {
2317             stack.add("\t- " + entry);
2318         }
2319 
2320         /**
2321          * Returns a string representation of this object
2322          */
2323         public String toString() {
2324             StringJoiner sj = new StringJoiner("\n");
2325             for (int i = stack.size() - 1; i >= 0; i--) {
2326                 sj.add(stack.get(i));
2327             }
2328             return sj.toString();
2329         }
2330     }
2331 
2332 }