| /* |
| * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package java.io; |
| |
| import java.io.ObjectStreamClass.WeakClassKey; |
| import java.lang.ref.ReferenceQueue; |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.ConcurrentMap; |
| import static java.io.ObjectStreamClass.processQueue; |
| import java.io.SerialCallbackContext; |
| import sun.reflect.misc.ReflectUtil; |
| |
| /** |
| * An ObjectOutputStream writes primitive data types and graphs of Java objects |
| * to an OutputStream. The objects can be read (reconstituted) using an |
| * ObjectInputStream. Persistent storage of objects can be accomplished by |
| * using a file for the stream. If the stream is a network socket stream, the |
| * objects can be reconstituted on another host or in another process. |
| * |
| * <p>Only objects that support the java.io.Serializable interface can be |
| * written to streams. The class of each serializable object is encoded |
| * including the class name and signature of the class, the values of the |
| * object's fields and arrays, and the closure of any other objects referenced |
| * from the initial objects. |
| * |
| * <p>The method writeObject is used to write an object to the stream. Any |
| * object, including Strings and arrays, is written with writeObject. Multiple |
| * objects or primitives can be written to the stream. The objects must be |
| * read back from the corresponding ObjectInputstream with the same types and |
| * in the same order as they were written. |
| * |
| * <p>Primitive data types can also be written to the stream using the |
| * appropriate methods from DataOutput. Strings can also be written using the |
| * writeUTF method. |
| * |
| * <p>The default serialization mechanism for an object writes the class of the |
| * object, the class signature, and the values of all non-transient and |
| * non-static fields. References to other objects (except in transient or |
| * static fields) cause those objects to be written also. Multiple references |
| * to a single object are encoded using a reference sharing mechanism so that |
| * graphs of objects can be restored to the same shape as when the original was |
| * written. |
| * |
| * <p>For example to write an object that can be read by the example in |
| * ObjectInputStream: |
| * <br> |
| * <pre> |
| * FileOutputStream fos = new FileOutputStream("t.tmp"); |
| * ObjectOutputStream oos = new ObjectOutputStream(fos); |
| * |
| * oos.writeInt(12345); |
| * oos.writeObject("Today"); |
| * oos.writeObject(new Date()); |
| * |
| * oos.close(); |
| * </pre> |
| * |
| * <p>Classes that require special handling during the serialization and |
| * deserialization process must implement special methods with these exact |
| * signatures: |
| * <br> |
| * <pre> |
| * private void readObject(java.io.ObjectInputStream stream) |
| * throws IOException, ClassNotFoundException; |
| * private void writeObject(java.io.ObjectOutputStream stream) |
| * throws IOException |
| * private void readObjectNoData() |
| * throws ObjectStreamException; |
| * </pre> |
| * |
| * <p>The writeObject method is responsible for writing the state of the object |
| * for its particular class so that the corresponding readObject method can |
| * restore it. The method does not need to concern itself with the state |
| * belonging to the object's superclasses or subclasses. State is saved by |
| * writing the individual fields to the ObjectOutputStream using the |
| * writeObject method or by using the methods for primitive data types |
| * supported by DataOutput. |
| * |
| * <p>Serialization does not write out the fields of any object that does not |
| * implement the java.io.Serializable interface. Subclasses of Objects that |
| * are not serializable can be serializable. In this case the non-serializable |
| * class must have a no-arg constructor to allow its fields to be initialized. |
| * In this case it is the responsibility of the subclass to save and restore |
| * the state of the non-serializable class. It is frequently the case that the |
| * fields of that class are accessible (public, package, or protected) or that |
| * there are get and set methods that can be used to restore the state. |
| * |
| * <p>Serialization of an object can be prevented by implementing writeObject |
| * and readObject methods that throw the NotSerializableException. The |
| * exception will be caught by the ObjectOutputStream and abort the |
| * serialization process. |
| * |
| * <p>Implementing the Externalizable interface allows the object to assume |
| * complete control over the contents and format of the object's serialized |
| * form. The methods of the Externalizable interface, writeExternal and |
| * readExternal, are called to save and restore the objects state. When |
| * implemented by a class they can write and read their own state using all of |
| * the methods of ObjectOutput and ObjectInput. It is the responsibility of |
| * the objects to handle any versioning that occurs. |
| * |
| * <p>Enum constants are serialized differently than ordinary serializable or |
| * externalizable objects. The serialized form of an enum constant consists |
| * solely of its name; field values of the constant are not transmitted. To |
| * serialize an enum constant, ObjectOutputStream writes the string returned by |
| * the constant's name method. Like other serializable or externalizable |
| * objects, enum constants can function as the targets of back references |
| * appearing subsequently in the serialization stream. The process by which |
| * enum constants are serialized cannot be customized; any class-specific |
| * writeObject and writeReplace methods defined by enum types are ignored |
| * during serialization. Similarly, any serialPersistentFields or |
| * serialVersionUID field declarations are also ignored--all enum types have a |
| * fixed serialVersionUID of 0L. |
| * |
| * <p>Primitive data, excluding serializable fields and externalizable data, is |
| * written to the ObjectOutputStream in block-data records. A block data record |
| * is composed of a header and data. The block data header consists of a marker |
| * and the number of bytes to follow the header. Consecutive primitive data |
| * writes are merged into one block-data record. The blocking factor used for |
| * a block-data record will be 1024 bytes. Each block-data record will be |
| * filled up to 1024 bytes, or be written whenever there is a termination of |
| * block-data mode. Calls to the ObjectOutputStream methods writeObject, |
| * defaultWriteObject and writeFields initially terminate any existing |
| * block-data record. |
| * |
| * @author Mike Warres |
| * @author Roger Riggs |
| * @see java.io.DataOutput |
| * @see java.io.ObjectInputStream |
| * @see java.io.Serializable |
| * @see java.io.Externalizable |
| * @see <a href="../../../platform/serialization/spec/output.html">Object Serialization Specification, Section 2, Object Output Classes</a> |
| * @since JDK1.1 |
| */ |
| public class ObjectOutputStream |
| extends OutputStream implements ObjectOutput, ObjectStreamConstants |
| { |
| |
| private static class Caches { |
| /** cache of subclass security audit results */ |
| static final ConcurrentMap<WeakClassKey,Boolean> subclassAudits = |
| new ConcurrentHashMap<>(); |
| |
| /** queue for WeakReferences to audited subclasses */ |
| static final ReferenceQueue<Class<?>> subclassAuditsQueue = |
| new ReferenceQueue<>(); |
| } |
| |
| /** filter stream for handling block data conversion */ |
| private final BlockDataOutputStream bout; |
| /** obj -> wire handle map */ |
| private final HandleTable handles; |
| /** obj -> replacement obj map */ |
| private final ReplaceTable subs; |
| /** stream protocol version */ |
| private int protocol = PROTOCOL_VERSION_2; |
| /** recursion depth */ |
| private int depth; |
| |
| /** buffer for writing primitive field values */ |
| private byte[] primVals; |
| |
| /** if true, invoke writeObjectOverride() instead of writeObject() */ |
| private final boolean enableOverride; |
| /** if true, invoke replaceObject() */ |
| private boolean enableReplace; |
| |
| // values below valid only during upcalls to writeObject()/writeExternal() |
| /** |
| * Context during upcalls to class-defined writeObject methods; holds |
| * object currently being serialized and descriptor for current class. |
| * Null when not during writeObject upcall. |
| */ |
| private SerialCallbackContext curContext; |
| /** current PutField object */ |
| private PutFieldImpl curPut; |
| |
| /** custom storage for debug trace info */ |
| private final DebugTraceInfoStack debugInfoStack; |
| |
| /** |
| * value of "sun.io.serialization.extendedDebugInfo" property, |
| * as true or false for extended information about exception's place |
| */ |
| private static final boolean extendedDebugInfo = |
| java.security.AccessController.doPrivileged( |
| new sun.security.action.GetBooleanAction( |
| "sun.io.serialization.extendedDebugInfo")).booleanValue(); |
| |
| /** |
| * Creates an ObjectOutputStream that writes to the specified OutputStream. |
| * This constructor writes the serialization stream header to the |
| * underlying stream; callers may wish to flush the stream immediately to |
| * ensure that constructors for receiving ObjectInputStreams will not block |
| * when reading the header. |
| * |
| * <p>If a security manager is installed, this constructor will check for |
| * the "enableSubclassImplementation" SerializablePermission when invoked |
| * directly or indirectly by the constructor of a subclass which overrides |
| * the ObjectOutputStream.putFields or ObjectOutputStream.writeUnshared |
| * methods. |
| * |
| * @param out output stream to write to |
| * @throws IOException if an I/O error occurs while writing stream header |
| * @throws SecurityException if untrusted subclass illegally overrides |
| * security-sensitive methods |
| * @throws NullPointerException if <code>out</code> is <code>null</code> |
| * @since 1.4 |
| * @see ObjectOutputStream#ObjectOutputStream() |
| * @see ObjectOutputStream#putFields() |
| * @see ObjectInputStream#ObjectInputStream(InputStream) |
| */ |
| public ObjectOutputStream(OutputStream out) throws IOException { |
| verifySubclass(); |
| bout = new BlockDataOutputStream(out); |
| handles = new HandleTable(10, (float) 3.00); |
| subs = new ReplaceTable(10, (float) 3.00); |
| enableOverride = false; |
| writeStreamHeader(); |
| bout.setBlockDataMode(true); |
| if (extendedDebugInfo) { |
| debugInfoStack = new DebugTraceInfoStack(); |
| } else { |
| debugInfoStack = null; |
| } |
| } |
| |
| /** |
| * Provide a way for subclasses that are completely reimplementing |
| * ObjectOutputStream to not have to allocate private data just used by |
| * this implementation of ObjectOutputStream. |
| * |
| * <p>If there is a security manager installed, this method first calls the |
| * security manager's <code>checkPermission</code> method with a |
| * <code>SerializablePermission("enableSubclassImplementation")</code> |
| * permission to ensure it's ok to enable subclassing. |
| * |
| * @throws SecurityException if a security manager exists and its |
| * <code>checkPermission</code> method denies enabling |
| * subclassing. |
| * @throws IOException if an I/O error occurs while creating this stream |
| * @see SecurityManager#checkPermission |
| * @see java.io.SerializablePermission |
| */ |
| protected ObjectOutputStream() throws IOException, SecurityException { |
| SecurityManager sm = System.getSecurityManager(); |
| if (sm != null) { |
| sm.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); |
| } |
| bout = null; |
| handles = null; |
| subs = null; |
| enableOverride = true; |
| debugInfoStack = null; |
| } |
| |
| /** |
| * Specify stream protocol version to use when writing the stream. |
| * |
| * <p>This routine provides a hook to enable the current version of |
| * Serialization to write in a format that is backwards compatible to a |
| * previous version of the stream format. |
| * |
| * <p>Every effort will be made to avoid introducing additional |
| * backwards incompatibilities; however, sometimes there is no |
| * other alternative. |
| * |
| * @param version use ProtocolVersion from java.io.ObjectStreamConstants. |
| * @throws IllegalStateException if called after any objects |
| * have been serialized. |
| * @throws IllegalArgumentException if invalid version is passed in. |
| * @throws IOException if I/O errors occur |
| * @see java.io.ObjectStreamConstants#PROTOCOL_VERSION_1 |
| * @see java.io.ObjectStreamConstants#PROTOCOL_VERSION_2 |
| * @since 1.2 |
| */ |
| public void useProtocolVersion(int version) throws IOException { |
| if (handles.size() != 0) { |
| // REMIND: implement better check for pristine stream? |
| throw new IllegalStateException("stream non-empty"); |
| } |
| switch (version) { |
| case PROTOCOL_VERSION_1: |
| case PROTOCOL_VERSION_2: |
| protocol = version; |
| break; |
| |
| default: |
| throw new IllegalArgumentException( |
| "unknown version: " + version); |
| } |
| } |
| |
| /** |
| * Write the specified object to the ObjectOutputStream. The class of the |
| * object, the signature of the class, and the values of the non-transient |
| * and non-static fields of the class and all of its supertypes are |
| * written. Default serialization for a class can be overridden using the |
| * writeObject and the readObject methods. Objects referenced by this |
| * object are written transitively so that a complete equivalent graph of |
| * objects can be reconstructed by an ObjectInputStream. |
| * |
| * <p>Exceptions are thrown for problems with the OutputStream and for |
| * classes that should not be serialized. All exceptions are fatal to the |
| * OutputStream, which is left in an indeterminate state, and it is up to |
| * the caller to ignore or recover the stream state. |
| * |
| * @throws InvalidClassException Something is wrong with a class used by |
| * serialization. |
| * @throws NotSerializableException Some object to be serialized does not |
| * implement the java.io.Serializable interface. |
| * @throws IOException Any exception thrown by the underlying |
| * OutputStream. |
| */ |
| public final void writeObject(Object obj) throws IOException { |
| if (enableOverride) { |
| writeObjectOverride(obj); |
| return; |
| } |
| try { |
| writeObject0(obj, false); |
| } catch (IOException ex) { |
| if (depth == 0) { |
| writeFatalException(ex); |
| } |
| throw ex; |
| } |
| } |
| |
| /** |
| * Method used by subclasses to override the default writeObject method. |
| * This method is called by trusted subclasses of ObjectInputStream that |
| * constructed ObjectInputStream using the protected no-arg constructor. |
| * The subclass is expected to provide an override method with the modifier |
| * "final". |
| * |
| * @param obj object to be written to the underlying stream |
| * @throws IOException if there are I/O errors while writing to the |
| * underlying stream |
| * @see #ObjectOutputStream() |
| * @see #writeObject(Object) |
| * @since 1.2 |
| */ |
| protected void writeObjectOverride(Object obj) throws IOException { |
| } |
| |
| /** |
| * Writes an "unshared" object to the ObjectOutputStream. This method is |
| * identical to writeObject, except that it always writes the given object |
| * as a new, unique object in the stream (as opposed to a back-reference |
| * pointing to a previously serialized instance). Specifically: |
| * <ul> |
| * <li>An object written via writeUnshared is always serialized in the |
| * same manner as a newly appearing object (an object that has not |
| * been written to the stream yet), regardless of whether or not the |
| * object has been written previously. |
| * |
| * <li>If writeObject is used to write an object that has been previously |
| * written with writeUnshared, the previous writeUnshared operation |
| * is treated as if it were a write of a separate object. In other |
| * words, ObjectOutputStream will never generate back-references to |
| * object data written by calls to writeUnshared. |
| * </ul> |
| * While writing an object via writeUnshared does not in itself guarantee a |
| * unique reference to the object when it is deserialized, it allows a |
| * single object to be defined multiple times in a stream, so that multiple |
| * calls to readUnshared by the receiver will not conflict. Note that the |
| * rules described above only apply to the base-level object written with |
| * writeUnshared, and not to any transitively referenced sub-objects in the |
| * object graph to be serialized. |
| * |
| * <p>ObjectOutputStream subclasses which override this method can only be |
| * constructed in security contexts possessing the |
| * "enableSubclassImplementation" SerializablePermission; any attempt to |
| * instantiate such a subclass without this permission will cause a |
| * SecurityException to be thrown. |
| * |
| * @param obj object to write to stream |
| * @throws NotSerializableException if an object in the graph to be |
| * serialized does not implement the Serializable interface |
| * @throws InvalidClassException if a problem exists with the class of an |
| * object to be serialized |
| * @throws IOException if an I/O error occurs during serialization |
| * @since 1.4 |
| */ |
| public void writeUnshared(Object obj) throws IOException { |
| try { |
| writeObject0(obj, true); |
| } catch (IOException ex) { |
| if (depth == 0) { |
| writeFatalException(ex); |
| } |
| throw ex; |
| } |
| } |
| |
| /** |
| * Write the non-static and non-transient fields of the current class to |
| * this stream. This may only be called from the writeObject method of the |
| * class being serialized. It will throw the NotActiveException if it is |
| * called otherwise. |
| * |
| * @throws IOException if I/O errors occur while writing to the underlying |
| * <code>OutputStream</code> |
| */ |
| public void defaultWriteObject() throws IOException { |
| SerialCallbackContext ctx = curContext; |
| if (ctx == null) { |
| throw new NotActiveException("not in call to writeObject"); |
| } |
| Object curObj = ctx.getObj(); |
| ObjectStreamClass curDesc = ctx.getDesc(); |
| bout.setBlockDataMode(false); |
| defaultWriteFields(curObj, curDesc); |
| bout.setBlockDataMode(true); |
| } |
| |
| /** |
| * Retrieve the object used to buffer persistent fields to be written to |
| * the stream. The fields will be written to the stream when writeFields |
| * method is called. |
| * |
| * @return an instance of the class Putfield that holds the serializable |
| * fields |
| * @throws IOException if I/O errors occur |
| * @since 1.2 |
| */ |
| public ObjectOutputStream.PutField putFields() throws IOException { |
| if (curPut == null) { |
| SerialCallbackContext ctx = curContext; |
| if (ctx == null) { |
| throw new NotActiveException("not in call to writeObject"); |
| } |
| Object curObj = ctx.getObj(); |
| ObjectStreamClass curDesc = ctx.getDesc(); |
| curPut = new PutFieldImpl(curDesc); |
| } |
| return curPut; |
| } |
| |
| /** |
| * Write the buffered fields to the stream. |
| * |
| * @throws IOException if I/O errors occur while writing to the underlying |
| * stream |
| * @throws NotActiveException Called when a classes writeObject method was |
| * not called to write the state of the object. |
| * @since 1.2 |
| */ |
| public void writeFields() throws IOException { |
| if (curPut == null) { |
| throw new NotActiveException("no current PutField object"); |
| } |
| bout.setBlockDataMode(false); |
| curPut.writeFields(); |
| bout.setBlockDataMode(true); |
| } |
| |
| /** |
| * Reset will disregard the state of any objects already written to the |
| * stream. The state is reset to be the same as a new ObjectOutputStream. |
| * The current point in the stream is marked as reset so the corresponding |
| * ObjectInputStream will be reset at the same point. Objects previously |
| * written to the stream will not be referred to as already being in the |
| * stream. They will be written to the stream again. |
| * |
| * @throws IOException if reset() is invoked while serializing an object. |
| */ |
| public void reset() throws IOException { |
| if (depth != 0) { |
| throw new IOException("stream active"); |
| } |
| bout.setBlockDataMode(false); |
| bout.writeByte(TC_RESET); |
| clear(); |
| bout.setBlockDataMode(true); |
| } |
| |
| /** |
| * Subclasses may implement this method to allow class data to be stored in |
| * the stream. By default this method does nothing. The corresponding |
| * method in ObjectInputStream is resolveClass. This method is called |
| * exactly once for each unique class in the stream. The class name and |
| * signature will have already been written to the stream. This method may |
| * make free use of the ObjectOutputStream to save any representation of |
| * the class it deems suitable (for example, the bytes of the class file). |
| * The resolveClass method in the corresponding subclass of |
| * ObjectInputStream must read and use any data or objects written by |
| * annotateClass. |
| * |
| * @param cl the class to annotate custom data for |
| * @throws IOException Any exception thrown by the underlying |
| * OutputStream. |
| */ |
| protected void annotateClass(Class<?> cl) throws IOException { |
| } |
| |
| /** |
| * Subclasses may implement this method to store custom data in the stream |
| * along with descriptors for dynamic proxy classes. |
| * |
| * <p>This method is called exactly once for each unique proxy class |
| * descriptor in the stream. The default implementation of this method in |
| * <code>ObjectOutputStream</code> does nothing. |
| * |
| * <p>The corresponding method in <code>ObjectInputStream</code> is |
| * <code>resolveProxyClass</code>. For a given subclass of |
| * <code>ObjectOutputStream</code> that overrides this method, the |
| * <code>resolveProxyClass</code> method in the corresponding subclass of |
| * <code>ObjectInputStream</code> must read any data or objects written by |
| * <code>annotateProxyClass</code>. |
| * |
| * @param cl the proxy class to annotate custom data for |
| * @throws IOException any exception thrown by the underlying |
| * <code>OutputStream</code> |
| * @see ObjectInputStream#resolveProxyClass(String[]) |
| * @since 1.3 |
| */ |
| protected void annotateProxyClass(Class<?> cl) throws IOException { |
| } |
| |
| /** |
| * This method will allow trusted subclasses of ObjectOutputStream to |
| * substitute one object for another during serialization. Replacing |
| * objects is disabled until enableReplaceObject is called. The |
| * enableReplaceObject method checks that the stream requesting to do |
| * replacement can be trusted. The first occurrence of each object written |
| * into the serialization stream is passed to replaceObject. Subsequent |
| * references to the object are replaced by the object returned by the |
| * original call to replaceObject. To ensure that the private state of |
| * objects is not unintentionally exposed, only trusted streams may use |
| * replaceObject. |
| * |
| * <p>The ObjectOutputStream.writeObject method takes a parameter of type |
| * Object (as opposed to type Serializable) to allow for cases where |
| * non-serializable objects are replaced by serializable ones. |
| * |
| * <p>When a subclass is replacing objects it must insure that either a |
| * complementary substitution must be made during deserialization or that |
| * the substituted object is compatible with every field where the |
| * reference will be stored. Objects whose type is not a subclass of the |
| * type of the field or array element abort the serialization by raising an |
| * exception and the object is not be stored. |
| * |
| * <p>This method is called only once when each object is first |
| * encountered. All subsequent references to the object will be redirected |
| * to the new object. This method should return the object to be |
| * substituted or the original object. |
| * |
| * <p>Null can be returned as the object to be substituted, but may cause |
| * NullReferenceException in classes that contain references to the |
| * original object since they may be expecting an object instead of |
| * null. |
| * |
| * @param obj the object to be replaced |
| * @return the alternate object that replaced the specified one |
| * @throws IOException Any exception thrown by the underlying |
| * OutputStream. |
| */ |
| protected Object replaceObject(Object obj) throws IOException { |
| return obj; |
| } |
| |
| /** |
| * Enable the stream to do replacement of objects in the stream. When |
| * enabled, the replaceObject method is called for every object being |
| * serialized. |
| * |
| * <p>If <code>enable</code> is true, and there is a security manager |
| * installed, this method first calls the security manager's |
| * <code>checkPermission</code> method with a |
| * <code>SerializablePermission("enableSubstitution")</code> permission to |
| * ensure it's ok to enable the stream to do replacement of objects in the |
| * stream. |
| * |
| * @param enable boolean parameter to enable replacement of objects |
| * @return the previous setting before this method was invoked |
| * @throws SecurityException if a security manager exists and its |
| * <code>checkPermission</code> method denies enabling the stream |
| * to do replacement of objects in the stream. |
| * @see SecurityManager#checkPermission |
| * @see java.io.SerializablePermission |
| */ |
| protected boolean enableReplaceObject(boolean enable) |
| throws SecurityException |
| { |
| if (enable == enableReplace) { |
| return enable; |
| } |
| if (enable) { |
| SecurityManager sm = System.getSecurityManager(); |
| if (sm != null) { |
| sm.checkPermission(SUBSTITUTION_PERMISSION); |
| } |
| } |
| enableReplace = enable; |
| return !enableReplace; |
| } |
| |
| /** |
| * The writeStreamHeader method is provided so subclasses can append or |
| * prepend their own header to the stream. It writes the magic number and |
| * version to the stream. |
| * |
| * @throws IOException if I/O errors occur while writing to the underlying |
| * stream |
| */ |
| protected void writeStreamHeader() throws IOException { |
| bout.writeShort(STREAM_MAGIC); |
| bout.writeShort(STREAM_VERSION); |
| } |
| |
| /** |
| * Write the specified class descriptor to the ObjectOutputStream. Class |
| * descriptors are used to identify the classes of objects written to the |
| * stream. Subclasses of ObjectOutputStream may override this method to |
| * customize the way in which class descriptors are written to the |
| * serialization stream. The corresponding method in ObjectInputStream, |
| * <code>readClassDescriptor</code>, should then be overridden to |
| * reconstitute the class descriptor from its custom stream representation. |
| * By default, this method writes class descriptors according to the format |
| * defined in the Object Serialization specification. |
| * |
| * <p>Note that this method will only be called if the ObjectOutputStream |
| * is not using the old serialization stream format (set by calling |
| * ObjectOutputStream's <code>useProtocolVersion</code> method). If this |
| * serialization stream is using the old format |
| * (<code>PROTOCOL_VERSION_1</code>), the class descriptor will be written |
| * internally in a manner that cannot be overridden or customized. |
| * |
| * @param desc class descriptor to write to the stream |
| * @throws IOException If an I/O error has occurred. |
| * @see java.io.ObjectInputStream#readClassDescriptor() |
| * @see #useProtocolVersion(int) |
| * @see java.io.ObjectStreamConstants#PROTOCOL_VERSION_1 |
| * @since 1.3 |
| */ |
| protected void writeClassDescriptor(ObjectStreamClass desc) |
| throws IOException |
| { |
| desc.writeNonProxy(this); |
| } |
| |
| /** |
| * Writes a byte. This method will block until the byte is actually |
| * written. |
| * |
| * @param val the byte to be written to the stream |
| * @throws IOException If an I/O error has occurred. |
| */ |
| public void write(int val) throws IOException { |
| bout.write(val); |
| } |
| |
| /** |
| * Writes an array of bytes. This method will block until the bytes are |
| * actually written. |
| * |
| * @param buf the data to be written |
| * @throws IOException If an I/O error has occurred. |
| */ |
| public void write(byte[] buf) throws IOException { |
| bout.write(buf, 0, buf.length, false); |
| } |
| |
| /** |
| * Writes a sub array of bytes. |
| * |
| * @param buf the data to be written |
| * @param off the start offset in the data |
| * @param len the number of bytes that are written |
| * @throws IOException If an I/O error has occurred. |
| */ |
| public void write(byte[] buf, int off, int len) throws IOException { |
| if (buf == null) { |
| throw new NullPointerException(); |
| } |
| int endoff = off + len; |
| if (off < 0 || len < 0 || endoff > buf.length || endoff < 0) { |
| throw new IndexOutOfBoundsException(); |
| } |
| bout.write(buf, off, len, false); |
| } |
| |
| /** |
| * Flushes the stream. This will write any buffered output bytes and flush |
| * through to the underlying stream. |
| * |
| * @throws IOException If an I/O error has occurred. |
| */ |
| public void flush() throws IOException { |
| bout.flush(); |
| } |
| |
| /** |
| * Drain any buffered data in ObjectOutputStream. Similar to flush but |
| * does not propagate the flush to the underlying stream. |
| * |
| * @throws IOException if I/O errors occur while writing to the underlying |
| * stream |
| */ |
| protected void drain() throws IOException { |
| bout.drain(); |
| } |
| |
| /** |
| * Closes the stream. This method must be called to release any resources |
| * associated with the stream. |
| * |
| * @throws IOException If an I/O error has occurred. |
| */ |
| public void close() throws IOException { |
| flush(); |
| clear(); |
| bout.close(); |
| } |
| |
| /** |
| * Writes a boolean. |
| * |
| * @param val the boolean to be written |
| * @throws IOException if I/O errors occur while writing to the underlying |
| * stream |
| */ |
| public void writeBoolean(boolean val) throws IOException { |
| bout.writeBoolean(val); |
| } |
| |
| /** |
| * Writes an 8 bit byte. |
| * |
| * @param val the byte value to be written |
| * @throws IOException if I/O errors occur while writing to the underlying |
| * stream |
| */ |
| public void writeByte(int val) throws IOException { |
| bout.writeByte(val); |
| } |
| |
| /** |
| * Writes a 16 bit short. |
| * |
| * @param val the short value to be written |
| * @throws IOException if I/O errors occur while writing to the underlying |
| * stream |
| */ |
| public void writeShort(int val) throws IOException { |
| bout.writeShort(val); |
| } |
| |
| /** |
| * Writes a 16 bit char. |
| * |
| * @param val the char value to be written |
| * @throws IOException if I/O errors occur while writing to the underlying |
| * stream |
| */ |
| public void writeChar(int val) throws IOException { |
| bout.writeChar(val); |
| } |
| |
| /** |
| * Writes a 32 bit int. |
| * |
| * @param val the integer value to be written |
| * @throws IOException if I/O errors occur while writing to the underlying |
| * stream |
| */ |
| public void writeInt(int val) throws IOException { |
| bout.writeInt(val); |
| } |
| |
| /** |
| * Writes a 64 bit long. |
| * |
| * @param val the long value to be written |
| * @throws IOException if I/O errors occur while writing to the underlying |
| * stream |
| */ |
| public void writeLong(long val) throws IOException { |
| bout.writeLong(val); |
| } |
| |
| /** |
| * Writes a 32 bit float. |
| * |
| * @param val the float value to be written |
| * @throws IOException if I/O errors occur while writing to the underlying |
| * stream |
| */ |
| public void writeFloat(float val) throws IOException { |
| bout.writeFloat(val); |
| } |
| |
| /** |
| * Writes a 64 bit double. |
| * |
| * @param val the double value to be written |
| * @throws IOException if I/O errors occur while writing to the underlying |
| * stream |
| */ |
| public void writeDouble(double val) throws IOException { |
| bout.writeDouble(val); |
| } |
| |
| /** |
| * Writes a String as a sequence of bytes. |
| * |
| * @param str the String of bytes to be written |
| * @throws IOException if I/O errors occur while writing to the underlying |
| * stream |
| */ |
| public void writeBytes(String str) throws IOException { |
| bout.writeBytes(str); |
| } |
| |
| /** |
| * Writes a String as a sequence of chars. |
| * |
| * @param str the String of chars to be written |
| * @throws IOException if I/O errors occur while writing to the underlying |
| * stream |
| */ |
| public void writeChars(String str) throws IOException { |
| bout.writeChars(str); |
| } |
| |
| /** |
| * Primitive data write of this String in |
| * <a href="DataInput.html#modified-utf-8">modified UTF-8</a> |
| * format. Note that there is a |
| * significant difference between writing a String into the stream as |
| * primitive data or as an Object. A String instance written by writeObject |
| * is written into the stream as a String initially. Future writeObject() |
| * calls write references to the string into the stream. |
| * |
| * @param str the String to be written |
| * @throws IOException if I/O errors occur while writing to the underlying |
| * stream |
| */ |
| public void writeUTF(String str) throws IOException { |
| bout.writeUTF(str); |
| } |
| |
| /** |
| * Provide programmatic access to the persistent fields to be written |
| * to ObjectOutput. |
| * |
| * @since 1.2 |
| */ |
| public static abstract class PutField { |
| |
| /** |
| * Put the value of the named boolean field into the persistent field. |
| * |
| * @param name the name of the serializable field |
| * @param val the value to assign to the field |
| * @throws IllegalArgumentException if <code>name</code> does not |
| * match the name of a serializable field for the class whose fields |
| * are being written, or if the type of the named field is not |
| * <code>boolean</code> |
| */ |
| public abstract void put(String name, boolean val); |
| |
| /** |
| * Put the value of the named byte field into the persistent field. |
| * |
| * @param name the name of the serializable field |
| * @param val the value to assign to the field |
| * @throws IllegalArgumentException if <code>name</code> does not |
| * match the name of a serializable field for the class whose fields |
| * are being written, or if the type of the named field is not |
| * <code>byte</code> |
| */ |
| public abstract void put(String name, byte val); |
| |
| /** |
| * Put the value of the named char field into the persistent field. |
| * |
| * @param name the name of the serializable field |
| * @param val the value to assign to the field |
| * @throws IllegalArgumentException if <code>name</code> does not |
| * match the name of a serializable field for the class whose fields |
| * are being written, or if the type of the named field is not |
| * <code>char</code> |
| */ |
| public abstract void put(String name, char val); |
| |
| /** |
| * Put the value of the named short field into the persistent field. |
| * |
| * @param name the name of the serializable field |
| * @param val the value to assign to the field |
| * @throws IllegalArgumentException if <code>name</code> does not |
| * match the name of a serializable field for the class whose fields |
| * are being written, or if the type of the named field is not |
| * <code>short</code> |
| */ |
| public abstract void put(String name, short val); |
| |
| /** |
| * Put the value of the named int field into the persistent field. |
| * |
| * @param name the name of the serializable field |
| * @param val the value to assign to the field |
| * @throws IllegalArgumentException if <code>name</code> does not |
| * match the name of a serializable field for the class whose fields |
| * are being written, or if the type of the named field is not |
| * <code>int</code> |
| */ |
| public abstract void put(String name, int val); |
| |
| /** |
| * Put the value of the named long field into the persistent field. |
| * |
| * @param name the name of the serializable field |
| * @param val the value to assign to the field |
| * @throws IllegalArgumentException if <code>name</code> does not |
| * match the name of a serializable field for the class whose fields |
| * are being written, or if the type of the named field is not |
| * <code>long</code> |
| */ |
| public abstract void put(String name, long val); |
| |
| /** |
| * Put the value of the named float field into the persistent field. |
| * |
| * @param name the name of the serializable field |
| * @param val the value to assign to the field |
| * @throws IllegalArgumentException if <code>name</code> does not |
| * match the name of a serializable field for the class whose fields |
| * are being written, or if the type of the named field is not |
| * <code>float</code> |
| */ |
| public abstract void put(String name, float val); |
| |
| /** |
| * Put the value of the named double field into the persistent field. |
| * |
| * @param name the name of the serializable field |
| * @param val the value to assign to the field |
| * @throws IllegalArgumentException if <code>name</code> does not |
| * match the name of a serializable field for the class whose fields |
| * are being written, or if the type of the named field is not |
| * <code>double</code> |
| */ |
| public abstract void put(String name, double val); |
| |
| /** |
| * Put the value of the named Object field into the persistent field. |
| * |
| * @param name the name of the serializable field |
| * @param val the value to assign to the field |
| * (which may be <code>null</code>) |
| * @throws IllegalArgumentException if <code>name</code> does not |
| * match the name of a serializable field for the class whose fields |
| * are being written, or if the type of the named field is not a |
| * reference type |
| */ |
| public abstract void put(String name, Object val); |
| |
| /** |
| * Write the data and fields to the specified ObjectOutput stream, |
| * which must be the same stream that produced this |
| * <code>PutField</code> object. |
| * |
| * @param out the stream to write the data and fields to |
| * @throws IOException if I/O errors occur while writing to the |
| * underlying stream |
| * @throws IllegalArgumentException if the specified stream is not |
| * the same stream that produced this <code>PutField</code> |
| * object |
| * @deprecated This method does not write the values contained by this |
| * <code>PutField</code> object in a proper format, and may |
| * result in corruption of the serialization stream. The |
| * correct way to write <code>PutField</code> data is by |
| * calling the {@link java.io.ObjectOutputStream#writeFields()} |
| * method. |
| */ |
| @Deprecated |
| public abstract void write(ObjectOutput out) throws IOException; |
| } |
| |
| |
| /** |
| * Returns protocol version in use. |
| */ |
| int getProtocolVersion() { |
| return protocol; |
| } |
| |
| /** |
| * Writes string without allowing it to be replaced in stream. Used by |
| * ObjectStreamClass to write class descriptor type strings. |
| */ |
| void writeTypeString(String str) throws IOException { |
| int handle; |
| if (str == null) { |
| writeNull(); |
| } else if ((handle = handles.lookup(str)) != -1) { |
| writeHandle(handle); |
| } else { |
| writeString(str, false); |
| } |
| } |
| |
| /** |
| * Verifies that this (possibly subclass) instance can be constructed |
| * without violating security constraints: the subclass must not override |
| * security-sensitive non-final methods, or else the |
| * "enableSubclassImplementation" SerializablePermission is checked. |
| */ |
| private void verifySubclass() { |
| Class<?> cl = getClass(); |
| if (cl == ObjectOutputStream.class) { |
| return; |
| } |
| SecurityManager sm = System.getSecurityManager(); |
| if (sm == null) { |
| return; |
| } |
| processQueue(Caches.subclassAuditsQueue, Caches.subclassAudits); |
| WeakClassKey key = new WeakClassKey(cl, Caches.subclassAuditsQueue); |
| Boolean result = Caches.subclassAudits.get(key); |
| if (result == null) { |
| result = Boolean.valueOf(auditSubclass(cl)); |
| Caches.subclassAudits.putIfAbsent(key, result); |
| } |
| if (result.booleanValue()) { |
| return; |
| } |
| sm.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); |
| } |
| |
| /** |
| * Performs reflective checks on given subclass to verify that it doesn't |
| * override security-sensitive non-final methods. Returns true if subclass |
| * is "safe", false otherwise. |
| */ |
| private static boolean auditSubclass(final Class<?> subcl) { |
| Boolean result = AccessController.doPrivileged( |
| new PrivilegedAction<Boolean>() { |
| public Boolean run() { |
| for (Class<?> cl = subcl; |
| cl != ObjectOutputStream.class; |
| cl = cl.getSuperclass()) |
| { |
| try { |
| cl.getDeclaredMethod( |
| "writeUnshared", new Class<?>[] { Object.class }); |
| return Boolean.FALSE; |
| } catch (NoSuchMethodException ex) { |
| } |
| try { |
| cl.getDeclaredMethod("putFields", (Class<?>[]) null); |
| return Boolean.FALSE; |
| } catch (NoSuchMethodException ex) { |
| } |
| } |
| return Boolean.TRUE; |
| } |
| } |
| ); |
| return result.booleanValue(); |
| } |
| |
| /** |
| * Clears internal data structures. |
| */ |
| private void clear() { |
| subs.clear(); |
| handles.clear(); |
| } |
| |
| /** |
| * Underlying writeObject/writeUnshared implementation. |
| */ |
| private void writeObject0(Object obj, boolean unshared) |
| throws IOException |
| { |
| boolean oldMode = bout.setBlockDataMode(false); |
| depth++; |
| try { |
| // handle previously written and non-replaceable objects |
| int h; |
| if ((obj = subs.lookup(obj)) == null) { |
| writeNull(); |
| return; |
| } else if (!unshared && (h = handles.lookup(obj)) != -1) { |
| writeHandle(h); |
| return; |
| } else if (obj instanceof Class) { |
| writeClass((Class) obj, unshared); |
| return; |
| } else if (obj instanceof ObjectStreamClass) { |
| writeClassDesc((ObjectStreamClass) obj, unshared); |
| return; |
| } |
| |
| // check for replacement object |
| Object orig = obj; |
| Class<?> cl = obj.getClass(); |
| ObjectStreamClass desc; |
| for (;;) { |
| // REMIND: skip this check for strings/arrays? |
| Class<?> repCl; |
| desc = ObjectStreamClass.lookup(cl, true); |
| if (!desc.hasWriteReplaceMethod() || |
| (obj = desc.invokeWriteReplace(obj)) == null || |
| (repCl = obj.getClass()) == cl) |
| { |
| break; |
| } |
| cl = repCl; |
| } |
| if (enableReplace) { |
| Object rep = replaceObject(obj); |
| if (rep != obj && rep != null) { |
| cl = rep.getClass(); |
| desc = ObjectStreamClass.lookup(cl, true); |
| } |
| obj = rep; |
| } |
| |
| // if object replaced, run through original checks a second time |
| if (obj != orig) { |
| subs.assign(orig, obj); |
| if (obj == null) { |
| writeNull(); |
| return; |
| } else if (!unshared && (h = handles.lookup(obj)) != -1) { |
| writeHandle(h); |
| return; |
| } else if (obj instanceof Class) { |
| writeClass((Class) obj, unshared); |
| return; |
| } else if (obj instanceof ObjectStreamClass) { |
| writeClassDesc((ObjectStreamClass) obj, unshared); |
| return; |
| } |
| } |
| |
| // remaining cases |
| if (obj instanceof String) { |
| writeString((String) obj, unshared); |
| } else if (cl.isArray()) { |
| writeArray(obj, desc, unshared); |
| } else if (obj instanceof Enum) { |
| writeEnum((Enum<?>) obj, desc, unshared); |
| } else if (obj instanceof Serializable) { |
| writeOrdinaryObject(obj, desc, unshared); |
| } else { |
| if (extendedDebugInfo) { |
| throw new NotSerializableException( |
| cl.getName() + "\n" + debugInfoStack.toString()); |
| } else { |
| throw new NotSerializableException(cl.getName()); |
| } |
| } |
| } finally { |
| depth--; |
| bout.setBlockDataMode(oldMode); |
| } |
| } |
| |
| /** |
| * Writes null code to stream. |
| */ |
| private void writeNull() throws IOException { |
| bout.writeByte(TC_NULL); |
| } |
| |
| /** |
| * Writes given object handle to stream. |
| */ |
| private void writeHandle(int handle) throws IOException { |
| bout.writeByte(TC_REFERENCE); |
| bout.writeInt(baseWireHandle + handle); |
| } |
| |
| /** |
| * Writes representation of given class to stream. |
| */ |
| private void writeClass(Class<?> cl, boolean unshared) throws IOException { |
| bout.writeByte(TC_CLASS); |
| writeClassDesc(ObjectStreamClass.lookup(cl, true), false); |
| handles.assign(unshared ? null : cl); |
| } |
| |
| /** |
| * Writes representation of given class descriptor to stream. |
| */ |
| private void writeClassDesc(ObjectStreamClass desc, boolean unshared) |
| throws IOException |
| { |
| int handle; |
| if (desc == null) { |
| writeNull(); |
| } else if (!unshared && (handle = handles.lookup(desc)) != -1) { |
| writeHandle(handle); |
| } else if (desc.isProxy()) { |
| writeProxyDesc(desc, unshared); |
| } else { |
| writeNonProxyDesc(desc, unshared); |
| } |
| } |
| |
| private boolean isCustomSubclass() { |
| // Return true if this class is a custom subclass of ObjectOutputStream |
| return getClass().getClassLoader() |
| != ObjectOutputStream.class.getClassLoader(); |
| } |
| |
| /** |
| * Writes class descriptor representing a dynamic proxy class to stream. |
| */ |
| private void writeProxyDesc(ObjectStreamClass desc, boolean unshared) |
| throws IOException |
| { |
| bout.writeByte(TC_PROXYCLASSDESC); |
| handles.assign(unshared ? null : desc); |
| |
| Class<?> cl = desc.forClass(); |
| Class<?>[] ifaces = cl.getInterfaces(); |
| bout.writeInt(ifaces.length); |
| for (int i = 0; i < ifaces.length; i++) { |
| bout.writeUTF(ifaces[i].getName()); |
| } |
| |
| bout.setBlockDataMode(true); |
| if (isCustomSubclass()) { |
| ReflectUtil.checkPackageAccess(cl); |
| } |
| annotateProxyClass(cl); |
| bout.setBlockDataMode(false); |
| bout.writeByte(TC_ENDBLOCKDATA); |
| |
| writeClassDesc(desc.getSuperDesc(), false); |
| } |
| |
| /** |
| * Writes class descriptor representing a standard (i.e., not a dynamic |
| * proxy) class to stream. |
| */ |
| private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared) |
| throws IOException |
| { |
| bout.writeByte(TC_CLASSDESC); |
| handles.assign(unshared ? null : desc); |
| |
| if (protocol == PROTOCOL_VERSION_1) { |
| // do not invoke class descriptor write hook with old protocol |
| desc.writeNonProxy(this); |
| } else { |
| writeClassDescriptor(desc); |
| } |
| |
| Class<?> cl = desc.forClass(); |
| bout.setBlockDataMode(true); |
| if (isCustomSubclass()) { |
| ReflectUtil.checkPackageAccess(cl); |
| } |
| annotateClass(cl); |
| bout.setBlockDataMode(false); |
| bout.writeByte(TC_ENDBLOCKDATA); |
| |
| writeClassDesc(desc.getSuperDesc(), false); |
| } |
| |
| /** |
| * Writes given string to stream, using standard or long UTF format |
| * depending on string length. |
| */ |
| private void writeString(String str, boolean unshared) throws IOException { |
| handles.assign(unshared ? null : str); |
| long utflen = bout.getUTFLength(str); |
| if (utflen <= 0xFFFF) { |
| bout.writeByte(TC_STRING); |
| bout.writeUTF(str, utflen); |
| } else { |
| bout.writeByte(TC_LONGSTRING); |
| bout.writeLongUTF(str, utflen); |
| } |
| } |
| |
| /** |
| * Writes given array object to stream. |
| */ |
| private void writeArray(Object array, |
| ObjectStreamClass desc, |
| boolean unshared) |
| throws IOException |
| { |
| bout.writeByte(TC_ARRAY); |
| writeClassDesc(desc, false); |
| handles.assign(unshared ? null : array); |
| |
| Class<?> ccl = desc.forClass().getComponentType(); |
| if (ccl.isPrimitive()) { |
| if (ccl == Integer.TYPE) { |
| int[] ia = (int[]) array; |
| bout.writeInt(ia.length); |
| bout.writeInts(ia, 0, ia.length); |
| } else if (ccl == Byte.TYPE) { |
| byte[] ba = (byte[]) array; |
| bout.writeInt(ba.length); |
| bout.write(ba, 0, ba.length, true); |
| } else if (ccl == Long.TYPE) { |
| long[] ja = (long[]) array; |
| bout.writeInt(ja.length); |
| bout.writeLongs(ja, 0, ja.length); |
| } else if (ccl == Float.TYPE) { |
| float[] fa = (float[]) array; |
| bout.writeInt(fa.length); |
| bout.writeFloats(fa, 0, fa.length); |
| } else if (ccl == Double.TYPE) { |
| double[] da = (double[]) array; |
| bout.writeInt(da.length); |
| bout.writeDoubles(da, 0, da.length); |
| } else if (ccl == Short.TYPE) { |
| short[] sa = (short[]) array; |
| bout.writeInt(sa.length); |
| bout.writeShorts(sa, 0, sa.length); |
| } else if (ccl == Character.TYPE) { |
| char[] ca = (char[]) array; |
| bout.writeInt(ca.length); |
| bout.writeChars(ca, 0, ca.length); |
| } else if (ccl == Boolean.TYPE) { |
| boolean[] za = (boolean[]) array; |
| bout.writeInt(za.length); |
| bout.writeBooleans(za, 0, za.length); |
| } else { |
| throw new InternalError(); |
| } |
| } else { |
| Object[] objs = (Object[]) array; |
| int len = objs.length; |
| bout.writeInt(len); |
| if (extendedDebugInfo) { |
| debugInfoStack.push( |
| "array (class \"" + array.getClass().getName() + |
| "\", size: " + len + ")"); |
| } |
| try { |
| for (int i = 0; i < len; i++) { |
| if (extendedDebugInfo) { |
| debugInfoStack.push( |
| "element of array (index: " + i + ")"); |
| } |
| try { |
| writeObject0(objs[i], false); |
| } finally { |
| if (extendedDebugInfo) { |
| debugInfoStack.pop(); |
| } |
| } |
| } |
| } finally { |
| if (extendedDebugInfo) { |
| debugInfoStack.pop(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Writes given enum constant to stream. |
| */ |
| private void writeEnum(Enum<?> en, |
| ObjectStreamClass desc, |
| boolean unshared) |
| throws IOException |
| { |
| bout.writeByte(TC_ENUM); |
| ObjectStreamClass sdesc = desc.getSuperDesc(); |
| writeClassDesc((sdesc.forClass() == Enum.class) ? desc : sdesc, false); |
| handles.assign(unshared ? null : en); |
| writeString(en.name(), false); |
| } |
| |
| /** |
| * Writes representation of a "ordinary" (i.e., not a String, Class, |
| * ObjectStreamClass, array, or enum constant) serializable object to the |
| * stream. |
| */ |
| private void writeOrdinaryObject(Object obj, |
| ObjectStreamClass desc, |
| boolean unshared) |
| throws IOException |
| { |
| if (extendedDebugInfo) { |
| debugInfoStack.push( |
| (depth == 1 ? "root " : "") + "object (class \"" + |
| obj.getClass().getName() + "\", " + obj.toString() + ")"); |
| } |
| try { |
| desc.checkSerialize(); |
| |
| bout.writeByte(TC_OBJECT); |
| writeClassDesc(desc, false); |
| handles.assign(unshared ? null : obj); |
| if (desc.isExternalizable() && !desc.isProxy()) { |
| writeExternalData((Externalizable) obj); |
| } else { |
| writeSerialData(obj, desc); |
| } |
| } finally { |
| if (extendedDebugInfo) { |
| debugInfoStack.pop(); |
| } |
| } |
| } |
| |
| /** |
| * Writes externalizable data of given object by invoking its |
| * writeExternal() method. |
| */ |
| private void writeExternalData(Externalizable obj) throws IOException { |
| PutFieldImpl oldPut = curPut; |
| curPut = null; |
| |
| if (extendedDebugInfo) { |
| debugInfoStack.push("writeExternal data"); |
| } |
| SerialCallbackContext oldContext = curContext; |
| try { |
| curContext = null; |
| if (protocol == PROTOCOL_VERSION_1) { |
| obj.writeExternal(this); |
| } else { |
| bout.setBlockDataMode(true); |
| obj.writeExternal(this); |
| bout.setBlockDataMode(false); |
| bout.writeByte(TC_ENDBLOCKDATA); |
| } |
| } finally { |
| curContext = oldContext; |
| if (extendedDebugInfo) { |
| debugInfoStack.pop(); |
| } |
| } |
| |
| curPut = oldPut; |
| } |
| |
| /** |
| * Writes instance data for each serializable class of given object, from |
| * superclass to subclass. |
| */ |
| private void writeSerialData(Object obj, ObjectStreamClass desc) |
| throws IOException |
| { |
| ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout(); |
| for (int i = 0; i < slots.length; i++) { |
| ObjectStreamClass slotDesc = slots[i].desc; |
| if (slotDesc.hasWriteObjectMethod()) { |
| PutFieldImpl oldPut = curPut; |
| curPut = null; |
| SerialCallbackContext oldContext = curContext; |
| |
| if (extendedDebugInfo) { |
| debugInfoStack.push( |
| "custom writeObject data (class \"" + |
| slotDesc.getName() + "\")"); |
| } |
| try { |
| curContext = new SerialCallbackContext(obj, slotDesc); |
| bout.setBlockDataMode(true); |
| slotDesc.invokeWriteObject(obj, this); |
| bout.setBlockDataMode(false); |
| bout.writeByte(TC_ENDBLOCKDATA); |
| } finally { |
| curContext.setUsed(); |
| curContext = oldContext; |
| if (extendedDebugInfo) { |
| debugInfoStack.pop(); |
| } |
| } |
| |
| curPut = oldPut; |
| } else { |
| defaultWriteFields(obj, slotDesc); |
| } |
| } |
| } |
| |
| /** |
| * Fetches and writes values of serializable fields of given object to |
| * stream. The given class descriptor specifies which field values to |
| * write, and in which order they should be written. |
| */ |
| private void defaultWriteFields(Object obj, ObjectStreamClass desc) |
| throws IOException |
| { |
| Class<?> cl = desc.forClass(); |
| if (cl != null && obj != null && !cl.isInstance(obj)) { |
| throw new ClassCastException(); |
| } |
| |
| desc.checkDefaultSerialize(); |
| |
| int primDataSize = desc.getPrimDataSize(); |
| if (primVals == null || primVals.length < primDataSize) { |
| primVals = new byte[primDataSize]; |
| } |
| desc.getPrimFieldValues(obj, primVals); |
| bout.write(primVals, 0, primDataSize, false); |
| |
| ObjectStreamField[] fields = desc.getFields(false); |
| Object[] objVals = new Object[desc.getNumObjFields()]; |
| int numPrimFields = fields.length - objVals.length; |
| desc.getObjFieldValues(obj, objVals); |
| for (int i = 0; i < objVals.length; i++) { |
| if (extendedDebugInfo) { |
| debugInfoStack.push( |
| "field (class \"" + desc.getName() + "\", name: \"" + |
| fields[numPrimFields + i].getName() + "\", type: \"" + |
| fields[numPrimFields + i].getType() + "\")"); |
| } |
| try { |
| writeObject0(objVals[i], |
| fields[numPrimFields + i].isUnshared()); |
| } finally { |
| if (extendedDebugInfo) { |
| debugInfoStack.pop(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Attempts to write to stream fatal IOException that has caused |
| * serialization to abort. |
| */ |
| private void writeFatalException(IOException ex) throws IOException { |
| /* |
| * Note: the serialization specification states that if a second |
| * IOException occurs while attempting to serialize the original fatal |
| * exception to the stream, then a StreamCorruptedException should be |
| * thrown (section 2.1). However, due to a bug in previous |
| * implementations of serialization, StreamCorruptedExceptions were |
| * rarely (if ever) actually thrown--the "root" exceptions from |
| * underlying streams were thrown instead. This historical behavior is |
| * followed here for consistency. |
| */ |
| clear(); |
| boolean oldMode = bout.setBlockDataMode(false); |
| try { |
| bout.writeByte(TC_EXCEPTION); |
| writeObject0(ex, false); |
| clear(); |
| } finally { |
| bout.setBlockDataMode(oldMode); |
| } |
| } |
| |
| /** |
| * Converts specified span of float values into byte values. |
| */ |
| // REMIND: remove once hotspot inlines Float.floatToIntBits |
| private static native void floatsToBytes(float[] src, int srcpos, |
| byte[] dst, int dstpos, |
| int nfloats); |
| |
| /** |
| * Converts specified span of double values into byte values. |
| */ |
| // REMIND: remove once hotspot inlines Double.doubleToLongBits |
| private static native void doublesToBytes(double[] src, int srcpos, |
| byte[] dst, int dstpos, |
| int ndoubles); |
| |
| /** |
| * Default PutField implementation. |
| */ |
| private class PutFieldImpl extends PutField { |
| |
| /** class descriptor describing serializable fields */ |
| private final ObjectStreamClass desc; |
| /** primitive field values */ |
| private final byte[] primVals; |
| /** object field values */ |
| private final Object[] objVals; |
| |
| /** |
| * Creates PutFieldImpl object for writing fields defined in given |
| * class descriptor. |
| */ |
| PutFieldImpl(ObjectStreamClass desc) { |
| this.desc = desc; |
| primVals = new byte[desc.getPrimDataSize()]; |
| objVals = new Object[desc.getNumObjFields()]; |
| } |
| |
| public void put(String name, boolean val) { |
| Bits.putBoolean(primVals, getFieldOffset(name, Boolean.TYPE), val); |
| } |
| |
| public void put(String name, byte val) { |
| primVals[getFieldOffset(name, Byte.TYPE)] = val; |
| } |
| |
| public void put(String name, char val) { |
| Bits.putChar(primVals, getFieldOffset(name, Character.TYPE), val); |
| } |
| |
| public void put(String name, short val) { |
| Bits.putShort(primVals, getFieldOffset(name, Short.TYPE), val); |
| } |
| |
| public void put(String name, int val) { |
| Bits.putInt(primVals, getFieldOffset(name, Integer.TYPE), val); |
| } |
| |
| public void put(String name, float val) { |
| Bits.putFloat(primVals, getFieldOffset(name, Float.TYPE), val); |
| } |
| |
| public void put(String name, long val) { |
| Bits.putLong(primVals, getFieldOffset(name, Long.TYPE), val); |
| } |
| |
| public void put(String name, double val) { |
| Bits.putDouble(primVals, getFieldOffset(name, Double.TYPE), val); |
| } |
| |
| public void put(String name, Object val) { |
| objVals[getFieldOffset(name, Object.class)] = val; |
| } |
| |
| // deprecated in ObjectOutputStream.PutField |
| public void write(ObjectOutput out) throws IOException { |
| /* |
| * Applications should *not* use this method to write PutField |
| * data, as it will lead to stream corruption if the PutField |
| * object writes any primitive data (since block data mode is not |
| * unset/set properly, as is done in OOS.writeFields()). This |
| * broken implementation is being retained solely for behavioral |
| * compatibility, in order to support applications which use |
| * OOS.PutField.write() for writing only non-primitive data. |
| * |
| * Serialization of unshared objects is not implemented here since |
| * it is not necessary for backwards compatibility; also, unshared |
| * semantics may not be supported by the given ObjectOutput |
| * instance. Applications which write unshared objects using the |
| * PutField API must use OOS.writeFields(). |
| */ |
| if (ObjectOutputStream.this != out) { |
| throw new IllegalArgumentException("wrong stream"); |
| } |
| out.write(primVals, 0, primVals.length); |
| |
| ObjectStreamField[] fields = desc.getFields(false); |
| int numPrimFields = fields.length - objVals.length; |
| // REMIND: warn if numPrimFields > 0? |
| for (int i = 0; i < objVals.length; i++) { |
| if (fields[numPrimFields + i].isUnshared()) { |
| throw new IOException("cannot write unshared object"); |
| } |
| out.writeObject(objVals[i]); |
| } |
| } |
| |
| /** |
| * Writes buffered primitive data and object fields to stream. |
| */ |
| void writeFields() throws IOException { |
| bout.write(primVals, 0, primVals.length, false); |
| |
| ObjectStreamField[] fields = desc.getFields(false); |
| int numPrimFields = fields.length - objVals.length; |
| for (int i = 0; i < objVals.length; i++) { |
| if (extendedDebugInfo) { |
| debugInfoStack.push( |
| "field (class \"" + desc.getName() + "\", name: \"" + |
| fields[numPrimFields + i].getName() + "\", type: \"" + |
| fields[numPrimFields + i].getType() + "\")"); |
| } |
| try { |
| writeObject0(objVals[i], |
| fields[numPrimFields + i].isUnshared()); |
| } finally { |
| if (extendedDebugInfo) { |
| debugInfoStack.pop(); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Returns offset of field with given name and type. A specified type |
| * of null matches all types, Object.class matches all non-primitive |
| * types, and any other non-null type matches assignable types only. |
| * Throws IllegalArgumentException if no matching field found. |
| */ |
| private int getFieldOffset(String name, Class<?> type) { |
| ObjectStreamField field = desc.getField(name, type); |
| if (field == null) { |
| throw new IllegalArgumentException("no such field " + name + |
| " with type " + type); |
| } |
| return field.getOffset(); |
| } |
| } |
| |
| /** |
| * Buffered output stream with two modes: in default mode, outputs data in |
| * same format as DataOutputStream; in "block data" mode, outputs data |
| * bracketed by block data markers (see object serialization specification |
| * for details). |
| */ |
| private static class BlockDataOutputStream |
| extends OutputStream implements DataOutput |
| { |
| /** maximum data block length */ |
| private static final int MAX_BLOCK_SIZE = 1024; |
| /** maximum data block header length */ |
| private static final int MAX_HEADER_SIZE = 5; |
| /** (tunable) length of char buffer (for writing strings) */ |
| private static final int CHAR_BUF_SIZE = 256; |
| |
| /** buffer for writing general/block data */ |
| private final byte[] buf = new byte[MAX_BLOCK_SIZE]; |
| /** buffer for writing block data headers */ |
| private final byte[] hbuf = new byte[MAX_HEADER_SIZE]; |
| /** char buffer for fast string writes */ |
| private final char[] cbuf = new char[CHAR_BUF_SIZE]; |
| |
| /** block data mode */ |
| private boolean blkmode = false; |
| /** current offset into buf */ |
| private int pos = 0; |
| |
| /** underlying output stream */ |
| private final OutputStream out; |
| /** loopback stream (for data writes that span data blocks) */ |
| private final DataOutputStream dout; |
| |
| /** |
| * Creates new BlockDataOutputStream on top of given underlying stream. |
| * Block data mode is turned off by default. |
| */ |
| BlockDataOutputStream(OutputStream out) { |
| this.out = out; |
| dout = new DataOutputStream(this); |
| } |
| |
| /** |
| * Sets block data mode to the given mode (true == on, false == off) |
| * and returns the previous mode value. If the new mode is the same as |
| * the old mode, no action is taken. If the new mode differs from the |
| * old mode, any buffered data is flushed before switching to the new |
| * mode. |
| */ |
| boolean setBlockDataMode(boolean mode) throws IOException { |
| if (blkmode == mode) { |
| return blkmode; |
| } |
| drain(); |
| blkmode = mode; |
| return !blkmode; |
| } |
| |
| /** |
| * Returns true if the stream is currently in block data mode, false |
| * otherwise. |
| */ |
| boolean getBlockDataMode() { |
| return blkmode; |
| } |
| |
| /* ----------------- generic output stream methods ----------------- */ |
| /* |
| * The following methods are equivalent to their counterparts in |
| * OutputStream, except that they partition written data into data |
| * blocks when in block data mode. |
| */ |
| |
| public void write(int b) throws IOException { |
| if (pos >= MAX_BLOCK_SIZE) { |
| drain(); |
| } |
| buf[pos++] = (byte) b; |
| } |
| |
| public void write(byte[] b) throws IOException { |
| write(b, 0, b.length, false); |
| } |
| |
| public void write(byte[] b, int off, int len) throws IOException { |
| write(b, off, len, false); |
| } |
| |
| public void flush() throws IOException { |
| drain(); |
| out.flush(); |
| } |
| |
| public void close() throws IOException { |
| flush(); |
| out.close(); |
| } |
| |
| /** |
| * Writes specified span of byte values from given array. If copy is |
| * true, copies the values to an intermediate buffer before writing |
| * them to underlying stream (to avoid exposing a reference to the |
| * original byte array). |
| */ |
| void write(byte[] b, int off, int len, boolean copy) |
| throws IOException |
| { |
| if (!(copy || blkmode)) { // write directly |
| drain(); |
| out.write(b, off, len); |
| return; |
| } |
| |
| while (len > 0) { |
| if (pos >= MAX_BLOCK_SIZE) { |
| drain(); |
| } |
| if (len >= MAX_BLOCK_SIZE && !copy && pos == 0) { |
| // avoid unnecessary copy |
| writeBlockHeader(MAX_BLOCK_SIZE); |
| out.write(b, off, MAX_BLOCK_SIZE); |
| off += MAX_BLOCK_SIZE; |
| len -= MAX_BLOCK_SIZE; |
| } else { |
| int wlen = Math.min(len, MAX_BLOCK_SIZE - pos); |
| System.arraycopy(b, off, buf, pos, wlen); |
| pos += wlen; |
| off += wlen; |
| len -= wlen; |
| } |
| } |
| } |
| |
| /** |
| * Writes all buffered data from this stream to the underlying stream, |
| * but does not flush underlying stream. |
| */ |
| void drain() throws IOException { |
| if (pos == 0) { |
| return; |
| } |
| if (blkmode) { |
| writeBlockHeader(pos); |
| } |
| out.write(buf, 0, pos); |
| pos = 0; |
| } |
| |
| /** |
| * Writes block data header. Data blocks shorter than 256 bytes are |
| * prefixed with a 2-byte header; all others start with a 5-byte |
| * header. |
| */ |
| private void writeBlockHeader(int len) throws IOException { |
| if (len <= 0xFF) { |
| hbuf[0] = TC_BLOCKDATA; |
| hbuf[1] = (byte) len; |
| out.write(hbuf, 0, 2); |
| } else { |
| hbuf[0] = TC_BLOCKDATALONG; |
| Bits.putInt(hbuf, 1, len); |
| out.write(hbuf, 0, 5); |
| } |
| } |
| |
| |
| /* ----------------- primitive data output methods ----------------- */ |
| /* |
| * The following methods are equivalent to their counterparts in |
| * DataOutputStream, except that they partition written data into data |
| * blocks when in block data mode. |
| */ |
| |
| public void writeBoolean(boolean v) throws IOException { |
| if (pos >= MAX_BLOCK_SIZE) { |
| drain(); |
| } |
| Bits.putBoolean(buf, pos++, v); |
| } |
| |
| public void writeByte(int v) throws IOException { |
| if (pos >= MAX_BLOCK_SIZE) { |
| drain(); |
| } |
| buf[pos++] = (byte) v; |
| } |
| |
| public void writeChar(int v) throws IOException { |
| if (pos + 2 <= MAX_BLOCK_SIZE) { |
| Bits.putChar(buf, pos, (char) v); |
| pos += 2; |
| } else { |
| dout.writeChar(v); |
| } |
| } |
| |
| public void writeShort(int v) throws IOException { |
| if (pos + 2 <= MAX_BLOCK_SIZE) { |
| Bits.putShort(buf, pos, (short) v); |
| pos += 2; |
| } else { |
| dout.writeShort(v); |
| } |
| } |
| |
| public void writeInt(int v) throws IOException { |
| if (pos + 4 <= MAX_BLOCK_SIZE) { |
| Bits.putInt(buf, pos, v); |
| pos += 4; |
| } else { |
| dout.writeInt(v); |
| } |
| } |
| |
| public void writeFloat(float v) throws IOException { |
| if (pos + 4 <= MAX_BLOCK_SIZE) { |
| Bits.putFloat(buf, pos, v); |
| pos += 4; |
| } else { |
| dout.writeFloat(v); |
| } |
| } |
| |
| public void writeLong(long v) throws IOException { |
| if (pos + 8 <= MAX_BLOCK_SIZE) { |
| Bits.putLong(buf, pos, v); |
| pos += 8; |
| } else { |
| dout.writeLong(v); |
| } |
| } |
| |
| public void writeDouble(double v) throws IOException { |
| if (pos + 8 <= MAX_BLOCK_SIZE) { |
| Bits.putDouble(buf, pos, v); |
| pos += 8; |
| } else { |
| dout.writeDouble(v); |
| } |
| } |
| |
| public void writeBytes(String s) throws IOException { |
| int endoff = s.length(); |
| int cpos = 0; |
| int csize = 0; |
| for (int off = 0; off < endoff; ) { |
| if (cpos >= csize) { |
| cpos = 0; |
| csize = Math.min(endoff - off, CHAR_BUF_SIZE); |
| s.getChars(off, off + csize, cbuf, 0); |
| } |
| if (pos >= MAX_BLOCK_SIZE) { |
| drain(); |
| } |
| int n = Math.min(csize - cpos, MAX_BLOCK_SIZE - pos); |
| int stop = pos + n; |
| while (pos < stop) { |
| buf[pos++] = (byte) cbuf[cpos++]; |
| } |
| off += n; |
| } |
| } |
| |
| public void writeChars(String s) throws IOException { |
| int endoff = s.length(); |
| for (int off = 0; off < endoff; ) { |
| int csize = Math.min(endoff - off, CHAR_BUF_SIZE); |
| s.getChars(off, off + csize, cbuf, 0); |
| writeChars(cbuf, 0, csize); |
| off += csize; |
| } |
| } |
| |
| public void writeUTF(String s) throws IOException { |
| writeUTF(s, getUTFLength(s)); |
| } |
| |
| |
| /* -------------- primitive data array output methods -------------- */ |
| /* |
| * The following methods write out spans of primitive data values. |
| * Though equivalent to calling the corresponding primitive write |
| * methods repeatedly, these methods are optimized for writing groups |
| * of primitive data values more efficiently. |
| */ |
| |
| void writeBooleans(boolean[] v, int off, int len) throws IOException { |
| int endoff = off + len; |
| while (off < endoff) { |
| if (pos >= MAX_BLOCK_SIZE) { |
| drain(); |
| } |
| int stop = Math.min(endoff, off + (MAX_BLOCK_SIZE - pos)); |
| while (off < stop) { |
| Bits.putBoolean(buf, pos++, v[off++]); |
| } |
| } |
| } |
| |
| void writeChars(char[] v, int off, int len) throws IOException { |
| int limit = MAX_BLOCK_SIZE - 2; |
| int endoff = off + len; |
| while (off < endoff) { |
| if (pos <= limit) { |
| int avail = (MAX_BLOCK_SIZE - pos) >> 1; |
| int stop = Math.min(endoff, off + avail); |
| while (off < stop) { |
| Bits.putChar(buf, pos, v[off++]); |
| pos += 2; |
| } |
| } else { |
| dout.writeChar(v[off++]); |
| } |
| } |
| } |
| |
| void writeShorts(short[] v, int off, int len) throws IOException { |
| int limit = MAX_BLOCK_SIZE - 2; |
| int endoff = off + len; |
| while (off < endoff) { |
| if (pos <= limit) { |
| int avail = (MAX_BLOCK_SIZE - pos) >> 1; |
| int stop = Math.min(endoff, off + avail); |
| while (off < stop) { |
| Bits.putShort(buf, pos, v[off++]); |
| pos += 2; |
| } |
| } else { |
| dout.writeShort(v[off++]); |
| } |
| } |
| } |
| |
| void writeInts(int[] v, int off, int len) throws IOException { |
| int limit = MAX_BLOCK_SIZE - 4; |
| int endoff = off + len; |
| while (off < endoff) { |
| if (pos <= limit) { |
| int avail = (MAX_BLOCK_SIZE - pos) >> 2; |
| int stop = Math.min(endoff, off + avail); |
| while (off < stop) { |
| Bits.putInt(buf, pos, v[off++]); |
| pos += 4; |
| } |
| } else { |
| dout.writeInt(v[off++]); |
| } |
| } |
| } |
| |
| void writeFloats(float[] v, int off, int len) throws IOException { |
| int limit = MAX_BLOCK_SIZE - 4; |
| int endoff = off + len; |
| while (off < endoff) { |
| if (pos <= limit) { |
| int avail = (MAX_BLOCK_SIZE - pos) >> 2; |
| int chunklen = Math.min(endoff - off, avail); |
| floatsToBytes(v, off, buf, pos, chunklen); |
| off += chunklen; |
| pos += chunklen << 2; |
| } else { |
| dout.writeFloat(v[off++]); |
| } |
| } |
| } |
| |
| void writeLongs(long[] v, int off, int len) throws IOException { |
| int limit = MAX_BLOCK_SIZE - 8; |
| int endoff = off + len; |
| while (off < endoff) { |
| if (pos <= limit) { |
| int avail = (MAX_BLOCK_SIZE - pos) >> 3; |
| int stop = Math.min(endoff, off + avail); |
| while (off < stop) { |
| Bits.putLong(buf, pos, v[off++]); |
| pos += 8; |
| } |
| } else { |
| dout.writeLong(v[off++]); |
| } |
| } |
| } |
| |
| void writeDoubles(double[] v, int off, int len) throws IOException { |
| int limit = MAX_BLOCK_SIZE - 8; |
| int endoff = off + len; |
| while (off < endoff) { |
| if (pos <= limit) { |
| int avail = (MAX_BLOCK_SIZE - pos) >> 3; |
| int chunklen = Math.min(endoff - off, avail); |
| doublesToBytes(v, off, buf, pos, chunklen); |
| off += chunklen; |
| pos += chunklen << 3; |
| } else { |
| dout.writeDouble(v[off++]); |
| } |
| } |
| } |
| |
| /** |
| * Returns the length in bytes of the UTF encoding of the given string. |
| */ |
| long getUTFLength(String s) { |
| int len = s.length(); |
| long utflen = 0; |
| for (int off = 0; off < len; ) { |
| int csize = Math.min(len - off, CHAR_BUF_SIZE); |
| s.getChars(off, off + csize, cbuf, 0); |
| for (int cpos = 0; cpos < csize; cpos++) { |
| char c = cbuf[cpos]; |
| if (c >= 0x0001 && c <= 0x007F) { |
| utflen++; |
| } else if (c > 0x07FF) { |
| utflen += 3; |
| } else { |
| utflen += 2; |
| } |
| } |
| off += csize; |
| } |
| return utflen; |
| } |
| |
| /** |
| * Writes the given string in UTF format. This method is used in |
| * situations where the UTF encoding length of the string is already |
| * known; specifying it explicitly avoids a prescan of the string to |
| * determine its UTF length. |
| */ |
| void writeUTF(String s, long utflen) throws IOException { |
| if (utflen > 0xFFFFL) { |
| throw new UTFDataFormatException(); |
| } |
| writeShort((int) utflen); |
| if (utflen == (long) s.length()) { |
| writeBytes(s); |
| } else { |
| writeUTFBody(s); |
| } |
| } |
| |
| /** |
| * Writes given string in "long" UTF format. "Long" UTF format is |
| * identical to standard UTF, except that it uses an 8 byte header |
| * (instead of the standard 2 bytes) to convey the UTF encoding length. |
| */ |
| void writeLongUTF(String s) throws IOException { |
| writeLongUTF(s, getUTFLength(s)); |
| } |
| |
| /** |
| * Writes given string in "long" UTF format, where the UTF encoding |
| * length of the string is already known. |
| */ |
| void writeLongUTF(String s, long utflen) throws IOException { |
| writeLong(utflen); |
| if (utflen == (long) s.length()) { |
| writeBytes(s); |
| } else { |
| writeUTFBody(s); |
| } |
| } |
| |
| /** |
| * Writes the "body" (i.e., the UTF representation minus the 2-byte or |
| * 8-byte length header) of the UTF encoding for the given string. |
| */ |
| private void writeUTFBody(String s) throws IOException { |
| int limit = MAX_BLOCK_SIZE - 3; |
| int len = s.length(); |
| for (int off = 0; off < len; ) { |
| int csize = Math.min(len - off, CHAR_BUF_SIZE); |
| s.getChars(off, off + csize, cbuf, 0); |
| for (int cpos = 0; cpos < csize; cpos++) { |
| char c = cbuf[cpos]; |
| if (pos <= limit) { |
| if (c <= 0x007F && c != 0) { |
| buf[pos++] = (byte) c; |
| } else if (c > 0x07FF) { |
| buf[pos + 2] = (byte) (0x80 | ((c >> 0) & 0x3F)); |
| buf[pos + 1] = (byte) (0x80 | ((c >> 6) & 0x3F)); |
| buf[pos + 0] = (byte) (0xE0 | ((c >> 12) & 0x0F)); |
| pos += 3; |
| } else { |
| buf[pos + 1] = (byte) (0x80 | ((c >> 0) & 0x3F)); |
| buf[pos + 0] = (byte) (0xC0 | ((c >> 6) & 0x1F)); |
| pos += 2; |
| } |
| } else { // write one byte at a time to normalize block |
| if (c <= 0x007F && c != 0) { |
| write(c); |
| } else if (c > 0x07FF) { |
| write(0xE0 | ((c >> 12) & 0x0F)); |
| write(0x80 | ((c >> 6) & 0x3F)); |
| write(0x80 | ((c >> 0) & 0x3F)); |
| } else { |
| write(0xC0 | ((c >> 6) & 0x1F)); |
| write(0x80 | ((c >> 0) & 0x3F)); |
| } |
| } |
| } |
| off += csize; |
| } |
| } |
| } |
| |
| /** |
| * Lightweight identity hash table which maps objects to integer handles, |
| * assigned in ascending order. |
| */ |
| private static class HandleTable { |
| |
| /* number of mappings in table/next available handle */ |
| private int size; |
| /* size threshold determining when to expand hash spine */ |
| private int threshold; |
| /* factor for computing size threshold */ |
| private final float loadFactor; |
| /* maps hash value -> candidate handle value */ |
| private int[] spine; |
| /* maps handle value -> next candidate handle value */ |
| private int[] next; |
| /* maps handle value -> associated object */ |
| private Object[] objs; |
| |
| /** |
| * Creates new HandleTable with given capacity and load factor. |
| */ |
| HandleTable(int initialCapacity, float loadFactor) { |
| this.loadFactor = loadFactor; |
| spine = new int[initialCapacity]; |
| next = new int[initialCapacity]; |
| objs = new Object[initialCapacity]; |
| threshold = (int) (initialCapacity * loadFactor); |
| clear(); |
| } |
| |
| /** |
| * Assigns next available handle to given object, and returns handle |
| * value. Handles are assigned in ascending order starting at 0. |
| */ |
| int assign(Object obj) { |
| if (size >= next.length) { |
| growEntries(); |
| } |
| if (size >= threshold) { |
| growSpine(); |
| } |
| insert(obj, size); |
| return size++; |
| } |
| |
| /** |
| * Looks up and returns handle associated with given object, or -1 if |
| * no mapping found. |
| */ |
| int lookup(Object obj) { |
| if (size == 0) { |
| return -1; |
| } |
| int index = hash(obj) % spine.length; |
| for (int i = spine[index]; i >= 0; i = next[i]) { |
| if (objs[i] == obj) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * Resets table to its initial (empty) state. |
| */ |
| void clear() { |
| Arrays.fill(spine, -1); |
| Arrays.fill(objs, 0, size, null); |
| size = 0; |
| } |
| |
| /** |
| * Returns the number of mappings currently in table. |
| */ |
| int size() { |
| return size; |
| } |
| |
| /** |
| * Inserts mapping object -> handle mapping into table. Assumes table |
| * is large enough to accommodate new mapping. |
| */ |
| private void insert(Object obj, int handle) { |
| int index = hash(obj) % spine.length; |
| objs[handle] = obj; |
| next[handle] = spine[index]; |
| spine[index] = handle; |
| } |
| |
| /** |
| * Expands the hash "spine" -- equivalent to increasing the number of |
| * buckets in a conventional hash table. |
| */ |
| private void growSpine() { |
| spine = new int[(spine.length << 1) + 1]; |
| threshold = (int) (spine.length * loadFactor); |
| Arrays.fill(spine, -1); |
| for (int i = 0; i < size; i++) { |
| insert(objs[i], i); |
| } |
| } |
| |
| /** |
| * Increases hash table capacity by lengthening entry arrays. |
| */ |
| private void growEntries() { |
| int newLength = (next.length << 1) + 1; |
| int[] newNext = new int[newLength]; |
| System.arraycopy(next, 0, newNext, 0, size); |
| next = newNext; |
| |
| Object[] newObjs = new Object[newLength]; |
| System.arraycopy(objs, 0, newObjs, 0, size); |
| objs = newObjs; |
| } |
| |
| /** |
| * Returns hash value for given object. |
| */ |
| private int hash(Object obj) { |
| return System.identityHashCode(obj) & 0x7FFFFFFF; |
| } |
| } |
| |
| /** |
| * Lightweight identity hash table which maps objects to replacement |
| * objects. |
| */ |
| private static class ReplaceTable { |
| |
| /* maps object -> index */ |
| private final HandleTable htab; |
| /* maps index -> replacement object */ |
| private Object[] reps; |
| |
| /** |
| * Creates new ReplaceTable with given capacity and load factor. |
| */ |
| ReplaceTable(int initialCapacity, float loadFactor) { |
| htab = new HandleTable(initialCapacity, loadFactor); |
| reps = new Object[initialCapacity]; |
| } |
| |
| /** |
| * Enters mapping from object to replacement object. |
| */ |
| void assign(Object obj, Object rep) { |
| int index = htab.assign(obj); |
| while (index >= reps.length) { |
| grow(); |
| } |
| reps[index] = rep; |
| } |
| |
| /** |
| * Looks up and returns replacement for given object. If no |
| * replacement is found, returns the lookup object itself. |
| */ |
| Object lookup(Object obj) { |
| int index = htab.lookup(obj); |
| return (index >= 0) ? reps[index] : obj; |
| } |
| |
| /** |
| * Resets table to its initial (empty) state. |
| */ |
| void clear() { |
| Arrays.fill(reps, 0, htab.size(), null); |
| htab.clear(); |
| } |
| |
| /** |
| * Returns the number of mappings currently in table. |
| */ |
| int size() { |
| return htab.size(); |
| } |
| |
| /** |
| * Increases table capacity. |
| */ |
| private void grow() { |
| Object[] newReps = new Object[(reps.length << 1) + 1]; |
| System.arraycopy(reps, 0, newReps, 0, reps.length); |
| reps = newReps; |
| } |
| } |
| |
| /** |
| * Stack to keep debug information about the state of the |
| * serialization process, for embedding in exception messages. |
| */ |
| private static class DebugTraceInfoStack { |
| private final List<String> stack; |
| |
| DebugTraceInfoStack() { |
| stack = new ArrayList<>(); |
| } |
| |
| /** |
| * Removes all of the elements from enclosed list. |
| */ |
| void clear() { |
| stack.clear(); |
| } |
| |
| /** |
| * Removes the object at the top of enclosed list. |
| */ |
| void pop() { |
| stack.remove(stack.size()-1); |
| } |
| |
| /** |
| * Pushes a String onto the top of enclosed list. |
| */ |
| void push(String entry) { |
| stack.add("\t- " + entry); |
| } |
| |
| /** |
| * Returns a string representation of this object |
| */ |
| public String toString() { |
| StringBuilder buffer = new StringBuilder(); |
| if (!stack.isEmpty()) { |
| for(int i = stack.size(); i > 0; i-- ) { |
| buffer.append(stack.get(i-1) + ((i != 1) ? "\n" : "")); |
| } |
| } |
| return buffer.toString(); |
| } |
| } |
| |
| } |