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