1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.io;
19
20import java.lang.reflect.Field;
21import java.lang.reflect.InvocationTargetException;
22import java.lang.reflect.Method;
23import java.lang.reflect.Proxy;
24import java.nio.ByteOrder;
25import java.nio.charset.ModifiedUtf8;
26import java.util.List;
27import libcore.io.Memory;
28import libcore.io.SizeOf;
29
30/**
31 * A specialized {@link OutputStream} that is able to write (serialize) Java
32 * objects as well as primitive data types (int, byte, char etc.). The data can
33 * later be loaded using an ObjectInputStream.
34 *
35 * @see ObjectInputStream
36 * @see ObjectOutput
37 * @see Serializable
38 * @see Externalizable
39 */
40public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants {
41
42    /*
43     * Mask to zero SC_BLOC_DATA bit.
44     */
45    private static final byte NOT_SC_BLOCK_DATA = (byte) (SC_BLOCK_DATA ^ 0xFF);
46
47    /*
48     * How many nested levels to writeObject.
49     */
50    private int nestedLevels;
51
52    /*
53     * Where we write to
54     */
55    private DataOutputStream output;
56
57    /*
58     * If object replacement is enabled or not
59     */
60    private boolean enableReplace;
61
62    /*
63     * Where we write primitive types to
64     */
65    private DataOutputStream primitiveTypes;
66
67    /*
68     * Where the write primitive types are actually written to
69     */
70    private ByteArrayOutputStream primitiveTypesBuffer;
71
72    /*
73     * Table mapping Object -> Integer (handle)
74     */
75    private SerializationHandleMap objectsWritten;
76
77    /*
78     * All objects are assigned an ID (integer handle)
79     */
80    private int currentHandle;
81
82    /*
83     * Used by defaultWriteObject
84     */
85    private Object currentObject;
86
87    /*
88     * Used by defaultWriteObject
89     */
90    private ObjectStreamClass currentClass;
91
92    /*
93     * Either ObjectStreamConstants.PROTOCOL_VERSION_1 or
94     * ObjectStreamConstants.PROTOCOL_VERSION_2
95     */
96    private int protocolVersion;
97
98    /*
99     * Used to keep track of the PutField object for the class/object being
100     * written
101     */
102    private EmulatedFieldsForDumping currentPutField;
103
104    /*
105     * Allows the receiver to decide if it needs to call writeObjectOverride
106     */
107    private boolean subclassOverridingImplementation;
108
109    /*
110     * Descriptor for java.lang.reflect.Proxy
111     */
112    private final ObjectStreamClass proxyClassDesc = ObjectStreamClass.lookup(Proxy.class);
113
114    /**
115     * PutField is an inner class to provide access to the persistent fields
116     * that are written to the target stream.
117     */
118    public static abstract class PutField {
119        /**
120         * Puts the value of the boolean field identified by {@code name} to the
121         * persistent field.
122         *
123         * @param name
124         *            the name of the field to serialize.
125         * @param value
126         *            the value that is put to the persistent field.
127         */
128        public abstract void put(String name, boolean value);
129
130        /**
131         * Puts the value of the character field identified by {@code name} to
132         * the persistent field.
133         *
134         * @param name
135         *            the name of the field to serialize.
136         * @param value
137         *            the value that is put to the persistent field.
138         */
139        public abstract void put(String name, char value);
140
141        /**
142         * Puts the value of the byte field identified by {@code name} to the
143         * persistent field.
144         *
145         * @param name
146         *            the name of the field to serialize.
147         * @param value
148         *            the value that is put to the persistent field.
149         */
150        public abstract void put(String name, byte value);
151
152        /**
153         * Puts the value of the short field identified by {@code name} to the
154         * persistent field.
155         *
156         * @param name
157         *            the name of the field to serialize.
158         * @param value
159         *            the value that is put to the persistent field.
160         */
161        public abstract void put(String name, short value);
162
163        /**
164         * Puts the value of the integer field identified by {@code name} to the
165         * persistent field.
166         *
167         * @param name
168         *            the name of the field to serialize.
169         * @param value
170         *            the value that is put to the persistent field.
171         */
172        public abstract void put(String name, int value);
173
174        /**
175         * Puts the value of the long field identified by {@code name} to the
176         * persistent field.
177         *
178         * @param name
179         *            the name of the field to serialize.
180         * @param value
181         *            the value that is put to the persistent field.
182         */
183        public abstract void put(String name, long value);
184
185        /**
186         * Puts the value of the float field identified by {@code name} to the
187         * persistent field.
188         *
189         * @param name
190         *            the name of the field to serialize.
191         * @param value
192         *            the value that is put to the persistent field.
193         */
194        public abstract void put(String name, float value);
195
196        /**
197         * Puts the value of the double field identified by {@code name} to the
198         * persistent field.
199         *
200         * @param name
201         *            the name of the field to serialize.
202         * @param value
203         *            the value that is put to the persistent field.
204         */
205        public abstract void put(String name, double value);
206
207        /**
208         * Puts the value of the Object field identified by {@code name} to the
209         * persistent field.
210         *
211         * @param name
212         *            the name of the field to serialize.
213         * @param value
214         *            the value that is put to the persistent field.
215         */
216        public abstract void put(String name, Object value);
217
218        /**
219         * Writes the fields to the target stream {@code out}.
220         *
221         * @param out
222         *            the target stream
223         * @throws IOException
224         *             if an error occurs while writing to the target stream.
225         * @deprecated This method is unsafe and may corrupt the target stream.
226         *             Use ObjectOutputStream#writeFields() instead.
227         */
228        @Deprecated
229        public abstract void write(ObjectOutput out) throws IOException;
230    }
231
232    /**
233     * Constructs a new {@code ObjectOutputStream}. This default constructor can
234     * be used by subclasses that do not want to use the public constructor if
235     * it allocates unneeded data.
236     *
237     * @throws IOException
238     *             if an error occurs when creating this stream.
239     */
240    protected ObjectOutputStream() throws IOException {
241        /*
242         * WARNING - we should throw IOException if not called from a subclass
243         * according to the JavaDoc. Add the test.
244         */
245        this.subclassOverridingImplementation = true;
246    }
247
248    /**
249     * Constructs a new ObjectOutputStream that writes to the OutputStream
250     * {@code output}.
251     *
252     * @param output
253     *            the non-null OutputStream to filter writes on.
254     *
255     * @throws IOException
256     *             if an error occurs while writing the object stream
257     *             header
258     */
259    public ObjectOutputStream(OutputStream output) throws IOException {
260        this.output = (output instanceof DataOutputStream) ? (DataOutputStream) output
261                : new DataOutputStream(output);
262        this.enableReplace = false;
263        this.protocolVersion = PROTOCOL_VERSION_2;
264        this.subclassOverridingImplementation = false;
265
266        resetState();
267        // So write...() methods can be used by
268        // subclasses during writeStreamHeader()
269        primitiveTypes = this.output;
270        // Has to be done here according to the specification
271        writeStreamHeader();
272        primitiveTypes = null;
273    }
274
275    /**
276     * Writes optional information for class {@code aClass} to the output
277     * stream. This optional data can be read when deserializing the class
278     * descriptor (ObjectStreamClass) for this class from an input stream. By
279     * default, no extra data is saved.
280     *
281     * @param aClass
282     *            the class to annotate.
283     * @throws IOException
284     *             if an error occurs while writing to the target stream.
285     * @see ObjectInputStream#resolveClass(ObjectStreamClass)
286     */
287    protected void annotateClass(Class<?> aClass) throws IOException {
288        // By default no extra info is saved. Subclasses can override
289    }
290
291    /**
292     * Writes optional information for a proxy class to the target stream. This
293     * optional data can be read when deserializing the proxy class from an
294     * input stream. By default, no extra data is saved.
295     *
296     * @param aClass
297     *            the proxy class to annotate.
298     * @throws IOException
299     *             if an error occurs while writing to the target stream.
300     * @see ObjectInputStream#resolveProxyClass(String[])
301     */
302    protected void annotateProxyClass(Class<?> aClass) throws IOException {
303        // By default no extra info is saved. Subclasses can override
304    }
305
306    /**
307     * Do the necessary work to see if the receiver can be used to write
308     * primitive types like int, char, etc.
309     */
310    private void checkWritePrimitiveTypes() {
311        if (primitiveTypes == null) {
312            // If we got here we have no Stream previously created
313            // WARNING - if the stream does not grow, this code is wrong
314            primitiveTypesBuffer = new ByteArrayOutputStream(128);
315            primitiveTypes = new DataOutputStream(primitiveTypesBuffer);
316        }
317    }
318
319    /**
320     * Closes this stream. Any buffered data is flushed. This implementation
321     * closes the target stream.
322     *
323     * @throws IOException
324     *             if an error occurs while closing this stream.
325     */
326    @Override
327    public void close() throws IOException {
328        // First flush what is needed (primitive data, etc)
329        flush();
330        output.close();
331    }
332
333    /**
334     * Computes the collection of emulated fields that users can manipulate to
335     * store a representation different than the one declared by the class of
336     * the object being dumped.
337     *
338     * @see #writeFields
339     * @see #writeFieldValues(EmulatedFieldsForDumping)
340     */
341    private void computePutField() {
342        currentPutField = new EmulatedFieldsForDumping(this, currentClass);
343    }
344
345    /**
346     * Default method to write objects to this stream. Serializable fields
347     * defined in the object's class and superclasses are written to the output
348     * stream.
349     *
350     * @throws IOException
351     *             if an error occurs while writing to the target stream.
352     * @throws NotActiveException
353     *             if this method is not called from {@code writeObject()}.
354     * @see ObjectInputStream#defaultReadObject
355     */
356    public void defaultWriteObject() throws IOException {
357        if (currentObject == null) {
358            throw new NotActiveException();
359        }
360        writeFieldValues(currentObject, currentClass);
361    }
362
363    /**
364     * Writes buffered data to the target stream. This is similar to {@code
365     * flush} but the flush is not propagated to the target stream.
366     *
367     * @throws IOException
368     *             if an error occurs while writing to the target stream.
369     */
370    protected void drain() throws IOException {
371        if (primitiveTypes == null || primitiveTypesBuffer == null) {
372            return;
373        }
374
375        // If we got here we have a Stream previously created
376        int offset = 0;
377        byte[] written = primitiveTypesBuffer.toByteArray();
378        // Normalize the primitive data
379        while (offset < written.length) {
380            int toWrite = written.length - offset > 1024 ? 1024
381                    : written.length - offset;
382            if (toWrite < 256) {
383                output.writeByte(TC_BLOCKDATA);
384                output.writeByte((byte) toWrite);
385            } else {
386                output.writeByte(TC_BLOCKDATALONG);
387                output.writeInt(toWrite);
388            }
389
390            // write primitive types we had and the marker of end-of-buffer
391            output.write(written, offset, toWrite);
392            offset += toWrite;
393        }
394
395        // and now we're clean to a state where we can write an object
396        primitiveTypes = null;
397        primitiveTypesBuffer = null;
398    }
399
400    /**
401     * Dumps the parameter {@code obj} only if it is {@code null}
402     * or an object that has already been dumped previously.
403     *
404     * @param obj
405     *            Object to check if an instance previously dumped by this
406     *            stream.
407     * @return -1 if it is an instance which has not been dumped yet (and this
408     *         method does nothing). The handle if {@code obj} is an
409     *         instance which has been dumped already.
410     *
411     * @throws IOException
412     *             If an error occurs attempting to save {@code null} or
413     *             a cyclic reference.
414     */
415    private int dumpCycle(Object obj) throws IOException {
416        // If the object has been saved already, save its handle only
417        int handle = objectsWritten.get(obj);
418        if (handle != -1) {
419            writeCyclicReference(handle);
420            return handle;
421        }
422        return -1;
423    }
424
425    /**
426     * Enables object replacement for this stream. By default this is not
427     * enabled. Only trusted subclasses (loaded with system class loader) are
428     * allowed to change this status.
429     *
430     * @param enable
431     *            {@code true} to enable object replacement; {@code false} to
432     *            disable it.
433     * @return the previous setting.
434     * @see #replaceObject
435     * @see ObjectInputStream#enableResolveObject
436     */
437    protected boolean enableReplaceObject(boolean enable) {
438        boolean originalValue = enableReplace;
439        enableReplace = enable;
440        return originalValue;
441    }
442
443    /**
444     * Writes buffered data to the target stream and calls the {@code flush}
445     * method of the target stream.
446     *
447     * @throws IOException
448     *             if an error occurs while writing to or flushing the output
449     *             stream.
450     */
451    @Override
452    public void flush() throws IOException {
453        drain();
454        output.flush();
455    }
456
457    /**
458     * Return the next handle to be used to indicate cyclic
459     * references being saved to the stream.
460     *
461     * @return the next handle to represent the next cyclic reference
462     */
463    private int nextHandle() {
464        return currentHandle++;
465    }
466
467    /**
468     * Gets this stream's {@code PutField} object. This object provides access
469     * to the persistent fields that are eventually written to the output
470     * stream. It is used to transfer the values from the fields of the object
471     * that is currently being written to the persistent fields.
472     *
473     * @return the PutField object from which persistent fields can be accessed
474     *         by name.
475     * @throws IOException
476     *             if an I/O error occurs.
477     * @throws NotActiveException
478     *             if this method is not called from {@code writeObject()}.
479     * @see ObjectInputStream#defaultReadObject
480     */
481    public PutField putFields() throws IOException {
482        if (currentObject == null) {
483            throw new NotActiveException();
484        }
485        if (currentPutField == null) {
486            computePutField();
487        }
488        return currentPutField;
489    }
490
491    private int registerObjectWritten(Object obj) {
492        int handle = nextHandle();
493        objectsWritten.put(obj, handle);
494        return handle;
495    }
496
497    /**
498     * Remove the unshared object from the table, and restore any previous
499     * handle.
500     *
501     * @param obj
502     *            Non-null object being dumped.
503     * @param previousHandle
504     *            The handle of the previous identical object dumped
505     */
506    private void removeUnsharedReference(Object obj, int previousHandle) {
507        if (previousHandle != -1) {
508            objectsWritten.put(obj, previousHandle);
509        } else {
510            objectsWritten.remove(obj);
511        }
512    }
513
514    /**
515     * Allows trusted subclasses to substitute the specified original {@code
516     * object} with a new object. Object substitution has to be activated first
517     * with calling {@code enableReplaceObject(true)}. This implementation just
518     * returns {@code object}.
519     *
520     * @param object
521     *            the original object for which a replacement may be defined.
522     * @return the replacement object for {@code object}.
523     * @throws IOException
524     *             if any I/O error occurs while creating the replacement
525     *             object.
526     * @see #enableReplaceObject
527     * @see ObjectInputStream#enableResolveObject
528     * @see ObjectInputStream#resolveObject
529     */
530    protected Object replaceObject(Object object) throws IOException {
531        // By default no object replacement. Subclasses can override
532        return object;
533    }
534
535    /**
536     * Resets the state of this stream. A marker is written to the stream, so
537     * that the corresponding input stream will also perform a reset at the same
538     * point. Objects previously written are no longer remembered, so they will
539     * be written again (instead of a cyclical reference) if found in the object
540     * graph.
541     *
542     * @throws IOException
543     *             if {@code reset()} is called during the serialization of an
544     *             object.
545     */
546    public void reset() throws IOException {
547        // First we flush what we have
548        drain();
549        /*
550         * And dump a reset marker, so that the ObjectInputStream can reset
551         * itself at the same point
552         */
553        output.writeByte(TC_RESET);
554        // Now we reset ourselves
555        resetState();
556    }
557
558    /**
559     * Reset the collection of objects already dumped by the receiver. If the
560     * objects are found again in the object graph, the receiver will dump them
561     * again, instead of a handle (cyclic reference).
562     *
563     */
564    private void resetSeenObjects() {
565        objectsWritten = new SerializationHandleMap();
566        currentHandle = baseWireHandle;
567    }
568
569    /**
570     * Reset the receiver. The collection of objects already dumped by the
571     * receiver is reset, and internal structures are also reset so that the
572     * receiver knows it is in a fresh clean state.
573     *
574     */
575    private void resetState() {
576        resetSeenObjects();
577        nestedLevels = 0;
578    }
579
580    /**
581     * Sets the specified protocol version to be used by this stream.
582     *
583     * @param version
584     *            the protocol version to be used. Use a {@code
585     *            PROTOCOL_VERSION_x} constant from {@code
586     *            java.io.ObjectStreamConstants}.
587     * @throws IllegalArgumentException
588     *             if an invalid {@code version} is specified.
589     * @throws IOException
590     *             if an I/O error occurs.
591     * @see ObjectStreamConstants#PROTOCOL_VERSION_1
592     * @see ObjectStreamConstants#PROTOCOL_VERSION_2
593     */
594    public void useProtocolVersion(int version) throws IOException {
595        if (!objectsWritten.isEmpty()) {
596            throw new IllegalStateException("Cannot set protocol version when stream in use");
597        }
598        if (version != ObjectStreamConstants.PROTOCOL_VERSION_1
599                && version != ObjectStreamConstants.PROTOCOL_VERSION_2) {
600            throw new IllegalArgumentException("Unknown protocol: " + version);
601        }
602        protocolVersion = version;
603    }
604
605    /**
606     * Writes {@code count} bytes from the byte array {@code buffer} starting at
607     * offset {@code index} to the target stream. Blocks until all bytes are
608     * written.
609     *
610     * @param buffer
611     *            the buffer to write.
612     * @param offset
613     *            the index of the first byte in {@code buffer} to write.
614     * @param length
615     *            the number of bytes from {@code buffer} to write to the output
616     *            stream.
617     * @throws IOException
618     *             if an error occurs while writing to the target stream.
619     */
620    @Override
621    public void write(byte[] buffer, int offset, int length) throws IOException {
622        checkWritePrimitiveTypes();
623        primitiveTypes.write(buffer, offset, length);
624    }
625
626    /**
627     * Writes a single byte to the target stream. Only the least significant
628     * byte of the integer {@code value} is written to the stream. Blocks until
629     * the byte is actually written.
630     *
631     * @param value
632     *            the byte to write.
633     * @throws IOException
634     *             if an error occurs while writing to the target stream.
635     */
636    @Override
637    public void write(int value) throws IOException {
638        checkWritePrimitiveTypes();
639        primitiveTypes.write(value);
640    }
641
642    /**
643     * Writes a boolean to the target stream.
644     *
645     * @param value
646     *            the boolean value to write to the target stream.
647     * @throws IOException
648     *             if an error occurs while writing to the target stream.
649     */
650    public void writeBoolean(boolean value) throws IOException {
651        checkWritePrimitiveTypes();
652        primitiveTypes.writeBoolean(value);
653    }
654
655    /**
656     * Writes a byte (8 bit) to the target stream.
657     *
658     * @param value
659     *            the byte to write to the target stream.
660     * @throws IOException
661     *             if an error occurs while writing to the target stream.
662     */
663    public void writeByte(int value) throws IOException {
664        checkWritePrimitiveTypes();
665        primitiveTypes.writeByte(value);
666    }
667
668    /**
669     * Writes the string {@code value} as a sequence of bytes to the target
670     * stream. Only the least significant byte of each character in the string
671     * is written.
672     *
673     * @param value
674     *            the string to write to the target stream.
675     * @throws IOException
676     *             if an error occurs while writing to the target stream.
677     */
678    public void writeBytes(String value) throws IOException {
679        checkWritePrimitiveTypes();
680        primitiveTypes.writeBytes(value);
681    }
682
683    /**
684     * Writes a character (16 bit) to the target stream.
685     *
686     * @param value
687     *            the character to write to the target stream.
688     * @throws IOException
689     *             if an error occurs while writing to the target stream.
690     */
691    public void writeChar(int value) throws IOException {
692        checkWritePrimitiveTypes();
693        primitiveTypes.writeChar(value);
694    }
695
696    /**
697     * Writes the string {@code value} as a sequence of characters to the target
698     * stream.
699     *
700     * @param value
701     *            the string to write to the target stream.
702     * @throws IOException
703     *             if an error occurs while writing to the target stream.
704     */
705    public void writeChars(String value) throws IOException {
706        checkWritePrimitiveTypes();
707        primitiveTypes.writeChars(value);
708    }
709
710    /**
711     * Write a class descriptor {@code classDesc} (an
712     * {@code ObjectStreamClass}) to the stream.
713     *
714     * @param classDesc
715     *            The class descriptor (an {@code ObjectStreamClass}) to
716     *            be dumped
717     * @param unshared
718     *            Write the object unshared
719     * @return the handle assigned to the class descriptor
720     *
721     * @throws IOException
722     *             If an IO exception happened when writing the class
723     *             descriptor.
724     */
725    private int writeClassDesc(ObjectStreamClass classDesc, boolean unshared) throws IOException {
726        if (classDesc == null) {
727            writeNull();
728            return -1;
729        }
730        int handle = -1;
731        if (!unshared) {
732            handle = dumpCycle(classDesc);
733        }
734        if (handle == -1) {
735            Class<?> classToWrite = classDesc.forClass();
736            int previousHandle = -1;
737            if (unshared) {
738                previousHandle = objectsWritten.get(classDesc);
739            }
740            // If we got here, it is a new (non-null) classDesc that will have
741            // to be registered as well
742            handle = registerObjectWritten(classDesc);
743
744            if (classDesc.isProxy()) {
745                output.writeByte(TC_PROXYCLASSDESC);
746                Class<?>[] interfaces = classToWrite.getInterfaces();
747                output.writeInt(interfaces.length);
748                for (int i = 0; i < interfaces.length; i++) {
749                    output.writeUTF(interfaces[i].getName());
750                }
751                annotateProxyClass(classToWrite);
752                output.writeByte(TC_ENDBLOCKDATA);
753                writeClassDesc(proxyClassDesc, false);
754                if (unshared) {
755                    // remove reference to unshared object
756                    removeUnsharedReference(classDesc, previousHandle);
757                }
758                return handle;
759            }
760
761            output.writeByte(TC_CLASSDESC);
762            if (protocolVersion == PROTOCOL_VERSION_1) {
763                writeNewClassDesc(classDesc);
764            } else {
765                // So write...() methods can be used by
766                // subclasses during writeClassDescriptor()
767                primitiveTypes = output;
768                writeClassDescriptor(classDesc);
769                primitiveTypes = null;
770            }
771            // Extra class info (optional)
772            annotateClass(classToWrite);
773            drain(); // flush primitive types in the annotation
774            output.writeByte(TC_ENDBLOCKDATA);
775            writeClassDesc(classDesc.getSuperclass(), unshared);
776            if (unshared) {
777                // remove reference to unshared object
778                removeUnsharedReference(classDesc, previousHandle);
779            }
780        }
781        return handle;
782    }
783
784    /**
785     * Writes a handle representing a cyclic reference (object previously
786     * dumped).
787     */
788    private void writeCyclicReference(int handle) throws IOException {
789        output.writeByte(TC_REFERENCE);
790        output.writeInt(handle);
791    }
792
793    /**
794     * Writes a double (64 bit) to the target stream.
795     *
796     * @param value
797     *            the double to write to the target stream.
798     * @throws IOException
799     *             if an error occurs while writing to the target stream.
800     */
801    public void writeDouble(double value) throws IOException {
802        checkWritePrimitiveTypes();
803        primitiveTypes.writeDouble(value);
804    }
805
806    /**
807     * Writes a collection of field descriptors (name, type name, etc) for the
808     * class descriptor {@code classDesc} (an
809     * {@code ObjectStreamClass})
810     *
811     * @param classDesc
812     *            The class descriptor (an {@code ObjectStreamClass})
813     *            for which to write field information
814     * @param externalizable
815     *            true if the descriptors are externalizable
816     *
817     * @throws IOException
818     *             If an IO exception happened when writing the field
819     *             descriptors.
820     *
821     * @see #writeObject(Object)
822     */
823    private void writeFieldDescriptors(ObjectStreamClass classDesc, boolean externalizable) throws IOException {
824        Class<?> loadedClass = classDesc.forClass();
825        ObjectStreamField[] fields = null;
826        int fieldCount = 0;
827
828        // The fields of String are not needed since Strings are treated as
829        // primitive types
830        if (!externalizable && loadedClass != ObjectStreamClass.STRINGCLASS) {
831            fields = classDesc.fields();
832            fieldCount = fields.length;
833        }
834
835        // Field count
836        output.writeShort(fieldCount);
837        // Field names
838        for (int i = 0; i < fieldCount; i++) {
839            ObjectStreamField f = fields[i];
840            boolean wasPrimitive = f.writeField(output);
841            if (!wasPrimitive) {
842                writeObject(f.getTypeString());
843            }
844        }
845    }
846
847    /**
848     * Writes the fields of the object currently being written to the target
849     * stream. The field values are buffered in the currently active {@code
850     * PutField} object, which can be accessed by calling {@code putFields()}.
851     *
852     * @throws IOException
853     *             if an error occurs while writing to the target stream.
854     * @throws NotActiveException
855     *             if there are no fields to write to the target stream.
856     * @see #putFields
857     */
858    public void writeFields() throws IOException {
859        // Has to have fields to write
860        if (currentPutField == null) {
861            throw new NotActiveException();
862        }
863        writeFieldValues(currentPutField);
864    }
865
866    /**
867     * Writes a collection of field values for the emulated fields
868     * {@code emulatedFields}
869     *
870     * @param emulatedFields
871     *            an {@code EmulatedFieldsForDumping}, concrete subclass
872     *            of {@code PutField}
873     *
874     * @throws IOException
875     *             If an IO exception happened when writing the field values.
876     *
877     * @see #writeFields
878     * @see #writeObject(Object)
879     */
880    private void writeFieldValues(EmulatedFieldsForDumping emulatedFields) throws IOException {
881        // Access internal fields which we can set/get. Users can't do this.
882        EmulatedFields accessibleSimulatedFields = emulatedFields.emulatedFields();
883        for (EmulatedFields.ObjectSlot slot : accessibleSimulatedFields.slots()) {
884            Object fieldValue = slot.getFieldValue();
885            Class<?> type = slot.getField().getType();
886            if (type == int.class) {
887                output.writeInt(fieldValue != null ? ((Integer) fieldValue).intValue() : 0);
888            } else if (type == byte.class) {
889                output.writeByte(fieldValue != null ? ((Byte) fieldValue).byteValue() : 0);
890            } else if (type == char.class) {
891                output.writeChar(fieldValue != null ? ((Character) fieldValue).charValue() : 0);
892            } else if (type == short.class) {
893                output.writeShort(fieldValue != null ? ((Short) fieldValue).shortValue() : 0);
894            } else if (type == boolean.class) {
895                output.writeBoolean(fieldValue != null ? ((Boolean) fieldValue).booleanValue() : false);
896            } else if (type == long.class) {
897                output.writeLong(fieldValue != null ? ((Long) fieldValue).longValue() : 0);
898            } else if (type == float.class) {
899                output.writeFloat(fieldValue != null ? ((Float) fieldValue).floatValue() : 0);
900            } else if (type == double.class) {
901                output.writeDouble(fieldValue != null ? ((Double) fieldValue).doubleValue() : 0);
902            } else {
903                // Either array or Object
904                writeObject(fieldValue);
905            }
906        }
907    }
908
909    /**
910     * Writes a collection of field values for the fields described by class
911     * descriptor {@code classDesc} (an {@code ObjectStreamClass}).
912     * This is the default mechanism, when emulated fields (an
913     * {@code PutField}) are not used. Actual values to dump are fetched
914     * directly from object {@code obj}.
915     *
916     * @param obj
917     *            Instance from which to fetch field values to dump.
918     * @param classDesc
919     *            A class descriptor (an {@code ObjectStreamClass})
920     *            defining which fields should be dumped.
921     *
922     * @throws IOException
923     *             If an IO exception happened when writing the field values.
924     *
925     * @see #writeObject(Object)
926     */
927    private void writeFieldValues(Object obj, ObjectStreamClass classDesc) throws IOException {
928        for (ObjectStreamField fieldDesc : classDesc.fields()) {
929            try {
930                Class<?> type = fieldDesc.getTypeInternal();
931                Field field = classDesc.checkAndGetReflectionField(fieldDesc);
932                if (field == null) {
933                    throw new InvalidClassException(classDesc.getName()
934                            + " doesn't have a serializable field " + fieldDesc.getName()
935                            + " of type " + type);
936                }
937                if (type == byte.class) {
938                    output.writeByte(field.getByte(obj));
939                } else if (type == char.class) {
940                    output.writeChar(field.getChar(obj));
941                } else if (type == double.class) {
942                    output.writeDouble(field.getDouble(obj));
943                } else if (type == float.class) {
944                    output.writeFloat(field.getFloat(obj));
945                } else if (type == int.class) {
946                    output.writeInt(field.getInt(obj));
947                } else if (type == long.class) {
948                    output.writeLong(field.getLong(obj));
949                } else if (type == short.class) {
950                    output.writeShort(field.getShort(obj));
951                } else if (type == boolean.class) {
952                    output.writeBoolean(field.getBoolean(obj));
953                } else {
954                    // Reference types (including arrays).
955                    Object objField = field.get(obj);
956                    if (fieldDesc.isUnshared()) {
957                        writeUnshared(objField);
958                    } else {
959                        writeObject(objField);
960                    }
961                }
962            } catch (IllegalAccessException iae) {
963                // ObjectStreamField should have called setAccessible(true).
964                throw new AssertionError(iae);
965            } catch (NoSuchFieldError nsf) {
966                // The user defined serialPersistentFields but did not provide
967                // the glue to transfer values in writeObject, so we ended up using
968                // the default mechanism but failed to set the emulated field.
969                throw new InvalidClassException(classDesc.getName());
970            }
971        }
972    }
973
974    /**
975     * Writes a float (32 bit) to the target stream.
976     *
977     * @param value
978     *            the float to write to the target stream.
979     * @throws IOException
980     *             if an error occurs while writing to the target stream.
981     */
982    public void writeFloat(float value) throws IOException {
983        checkWritePrimitiveTypes();
984        primitiveTypes.writeFloat(value);
985    }
986
987    /**
988     * Walks the hierarchy of classes described by class descriptor
989     * {@code classDesc} and writes the field values corresponding to
990     * fields declared by the corresponding class descriptor. The instance to
991     * fetch field values from is {@code object}. If the class
992     * (corresponding to class descriptor {@code classDesc}) defines
993     * private instance method {@code writeObject} it will be used to
994     * dump field values.
995     *
996     * @param object
997     *            Instance from which to fetch field values to dump.
998     * @param classDesc
999     *            A class descriptor (an {@code ObjectStreamClass})
1000     *            defining which fields should be dumped.
1001     *
1002     * @throws IOException
1003     *             If an IO exception happened when writing the field values in
1004     *             the hierarchy.
1005     * @throws NotActiveException
1006     *             If the given object is not active
1007     *
1008     * @see #defaultWriteObject
1009     * @see #writeObject(Object)
1010     */
1011    private void writeHierarchy(Object object, ObjectStreamClass classDesc)
1012            throws IOException, NotActiveException {
1013        if (object == null) {
1014            throw new NotActiveException();
1015        }
1016
1017        // Fields are written from class closest to Object to leaf class
1018        // (down the chain)
1019        List<ObjectStreamClass> hierarchy = classDesc.getHierarchy();
1020        for (int i = 0, end = hierarchy.size(); i < end; ++i) {
1021            ObjectStreamClass osc = hierarchy.get(i);
1022            // Have to do this before calling defaultWriteObject or anything
1023            // that calls defaultWriteObject
1024            currentObject = object;
1025            currentClass = osc;
1026
1027            // See if the object has a writeObject method. If so, run it
1028            try {
1029                boolean executed = false;
1030                if (osc.hasMethodWriteObject()) {
1031                    final Method method = osc.getMethodWriteObject();
1032                    try {
1033                        method.invoke(object, new Object[] { this });
1034                        executed = true;
1035                    } catch (InvocationTargetException e) {
1036                        Throwable ex = e.getTargetException();
1037                        if (ex instanceof RuntimeException) {
1038                            throw (RuntimeException) ex;
1039                        } else if (ex instanceof Error) {
1040                            throw (Error) ex;
1041                        }
1042                        throw (IOException) ex;
1043                    } catch (IllegalAccessException e) {
1044                        throw new RuntimeException(e.toString());
1045                    }
1046                }
1047
1048                if (executed) {
1049                    drain();
1050                    output.writeByte(TC_ENDBLOCKDATA);
1051                } else {
1052                    // If the object did not have a writeMethod, call
1053                    // defaultWriteObject
1054                    defaultWriteObject();
1055                }
1056            } finally {
1057                // Cleanup, needs to run always so that we can later detect
1058                // invalid calls to defaultWriteObject
1059                currentObject = null;
1060                currentClass = null;
1061                currentPutField = null;
1062            }
1063        }
1064    }
1065
1066    /**
1067     * Writes an integer (32 bit) to the target stream.
1068     *
1069     * @param value
1070     *            the integer to write to the target stream.
1071     * @throws IOException
1072     *             if an error occurs while writing to the target stream.
1073     */
1074    public void writeInt(int value) throws IOException {
1075        checkWritePrimitiveTypes();
1076        primitiveTypes.writeInt(value);
1077    }
1078
1079    /**
1080     * Writes a long (64 bit) to the target stream.
1081     *
1082     * @param value
1083     *            the long to write to the target stream.
1084     * @throws IOException
1085     *             if an error occurs while writing to the target stream.
1086     */
1087    public void writeLong(long value) throws IOException {
1088        checkWritePrimitiveTypes();
1089        primitiveTypes.writeLong(value);
1090    }
1091
1092    /**
1093     * Write array {@code array} of class {@code arrayClass} with
1094     * component type {@code componentType} into the receiver. It is
1095     * assumed the array has not been dumped yet. Returns
1096     * the handle for this object (array) which is dumped here.
1097     *
1098     * @param array
1099     *            The array object to dump
1100     * @param arrayClass
1101     *            A {@code java.lang.Class} representing the class of the
1102     *            array
1103     * @param componentType
1104     *            A {@code java.lang.Class} representing the array
1105     *            component type
1106     * @return the handle assigned to the array
1107     *
1108     * @throws IOException
1109     *             If an IO exception happened when writing the array.
1110     */
1111    private int writeNewArray(Object array, Class<?> arrayClass, ObjectStreamClass arrayClDesc,
1112            Class<?> componentType, boolean unshared) throws IOException {
1113        output.writeByte(TC_ARRAY);
1114        writeClassDesc(arrayClDesc, false);
1115
1116        int handle = nextHandle();
1117        if (!unshared) {
1118            objectsWritten.put(array, handle);
1119        }
1120
1121        // Now we have code duplication just because Java is typed. We have to
1122        // write N elements and assign to array positions, but we must typecast
1123        // the array first, and also call different methods depending on the
1124        // elements.
1125
1126        if (componentType.isPrimitive()) {
1127            if (componentType == int.class) {
1128                int[] intArray = (int[]) array;
1129                output.writeInt(intArray.length);
1130                for (int i = 0; i < intArray.length; i++) {
1131                    output.writeInt(intArray[i]);
1132                }
1133            } else if (componentType == byte.class) {
1134                byte[] byteArray = (byte[]) array;
1135                output.writeInt(byteArray.length);
1136                output.write(byteArray, 0, byteArray.length);
1137            } else if (componentType == char.class) {
1138                char[] charArray = (char[]) array;
1139                output.writeInt(charArray.length);
1140                for (int i = 0; i < charArray.length; i++) {
1141                    output.writeChar(charArray[i]);
1142                }
1143            } else if (componentType == short.class) {
1144                short[] shortArray = (short[]) array;
1145                output.writeInt(shortArray.length);
1146                for (int i = 0; i < shortArray.length; i++) {
1147                    output.writeShort(shortArray[i]);
1148                }
1149            } else if (componentType == boolean.class) {
1150                boolean[] booleanArray = (boolean[]) array;
1151                output.writeInt(booleanArray.length);
1152                for (int i = 0; i < booleanArray.length; i++) {
1153                    output.writeBoolean(booleanArray[i]);
1154                }
1155            } else if (componentType == long.class) {
1156                long[] longArray = (long[]) array;
1157                output.writeInt(longArray.length);
1158                for (int i = 0; i < longArray.length; i++) {
1159                    output.writeLong(longArray[i]);
1160                }
1161            } else if (componentType == float.class) {
1162                float[] floatArray = (float[]) array;
1163                output.writeInt(floatArray.length);
1164                for (int i = 0; i < floatArray.length; i++) {
1165                    output.writeFloat(floatArray[i]);
1166                }
1167            } else if (componentType == double.class) {
1168                double[] doubleArray = (double[]) array;
1169                output.writeInt(doubleArray.length);
1170                for (int i = 0; i < doubleArray.length; i++) {
1171                    output.writeDouble(doubleArray[i]);
1172                }
1173            } else {
1174                throw new InvalidClassException("Wrong base type in " + arrayClass.getName());
1175            }
1176        } else {
1177            // Array of Objects
1178            Object[] objectArray = (Object[]) array;
1179            output.writeInt(objectArray.length);
1180            for (int i = 0; i < objectArray.length; i++) {
1181                // TODO: This place is the opportunity for enhancement
1182                //      We can implement writing elements through fast-path,
1183                //      without setting up the context (see writeObject()) for
1184                //      each element with public API
1185                writeObject(objectArray[i]);
1186            }
1187        }
1188        return handle;
1189    }
1190
1191    /**
1192     * Write class {@code object} into the receiver. It is assumed the
1193     * class has not been dumped yet. Classes are not really dumped, but a class
1194     * descriptor ({@code ObjectStreamClass}) that corresponds to them.
1195     * Returns the handle for this object (class) which is dumped here.
1196     *
1197     * @param object
1198     *            The {@code java.lang.Class} object to dump
1199     * @return the handle assigned to the class being dumped
1200     *
1201     * @throws IOException
1202     *             If an IO exception happened when writing the class.
1203     */
1204    private int writeNewClass(Class<?> object, boolean unshared) throws IOException {
1205        output.writeByte(TC_CLASS);
1206
1207        // Instances of java.lang.Class are always Serializable, even if their
1208        // instances aren't (e.g. java.lang.Object.class).
1209        // We cannot call lookup because it returns null if the parameter
1210        // represents instances that cannot be serialized, and that is not what
1211        // we want.
1212        ObjectStreamClass clDesc = ObjectStreamClass.lookupStreamClass(object);
1213
1214        // The handle for the classDesc is NOT the handle for the class object
1215        // being dumped. We must allocate a new handle and return it.
1216        if (clDesc.isEnum()) {
1217            writeEnumDesc(clDesc, unshared);
1218        } else {
1219            writeClassDesc(clDesc, unshared);
1220        }
1221
1222        int handle = nextHandle();
1223        if (!unshared) {
1224            objectsWritten.put(object, handle);
1225        }
1226
1227        return handle;
1228    }
1229
1230    /**
1231     * Write class descriptor {@code classDesc} into the receiver. It is
1232     * assumed the class descriptor has not been dumped yet. The class
1233     * descriptors for the superclass chain will be dumped as well. Returns
1234     * the handle for this object (class descriptor) which is dumped here.
1235     *
1236     * @param classDesc
1237     *            The {@code ObjectStreamClass} object to dump
1238     *
1239     * @throws IOException
1240     *             If an IO exception happened when writing the class
1241     *             descriptor.
1242     */
1243    private void writeNewClassDesc(ObjectStreamClass classDesc)
1244            throws IOException {
1245        output.writeUTF(classDesc.getName());
1246        output.writeLong(classDesc.getSerialVersionUID());
1247        byte flags = classDesc.getFlags();
1248
1249        boolean externalizable = classDesc.isExternalizable();
1250
1251        if (externalizable) {
1252            if (protocolVersion == PROTOCOL_VERSION_1) {
1253                flags &= NOT_SC_BLOCK_DATA;
1254            } else {
1255                // Change for 1.2. Objects can be saved in old format
1256                // (PROTOCOL_VERSION_1) or in the 1.2 format (PROTOCOL_VERSION_2).
1257                flags |= SC_BLOCK_DATA;
1258            }
1259        }
1260        output.writeByte(flags);
1261        if ((SC_ENUM | SC_SERIALIZABLE) != classDesc.getFlags()) {
1262            writeFieldDescriptors(classDesc, externalizable);
1263        } else {
1264            // enum write no fields
1265            output.writeShort(0);
1266        }
1267    }
1268
1269    /**
1270     * Writes a class descriptor to the target stream.
1271     *
1272     * @param classDesc
1273     *            the class descriptor to write to the target stream.
1274     * @throws IOException
1275     *             if an error occurs while writing to the target stream.
1276     */
1277    protected void writeClassDescriptor(ObjectStreamClass classDesc)
1278            throws IOException {
1279        writeNewClassDesc(classDesc);
1280    }
1281
1282    /**
1283     * Write exception {@code ex} into the receiver. It is assumed the
1284     * exception has not been dumped yet. Returns
1285     * the handle for this object (exception) which is dumped here.
1286     * This is used to dump the exception instance that happened (if any) when
1287     * dumping the original object graph. The set of seen objects will be reset
1288     * just before and just after dumping this exception object.
1289     *
1290     * When exceptions are found normally in the object graph, they are dumped
1291     * as a regular object, and not by this method. In that case, the set of
1292     * "known objects" is not reset.
1293     *
1294     * @param ex
1295     *            Exception object to dump
1296     *
1297     * @throws IOException
1298     *             If an IO exception happened when writing the exception
1299     *             object.
1300     */
1301    private void writeNewException(Exception ex) throws IOException {
1302        output.writeByte(TC_EXCEPTION);
1303        resetSeenObjects();
1304        writeObjectInternal(ex, false, false, false); // No replacements
1305        resetSeenObjects();
1306    }
1307
1308    /**
1309     * Write object {@code object} of class {@code theClass} into
1310     * the receiver. It is assumed the object has not been dumped yet.
1311     * Return the handle for this object which
1312     * is dumped here.
1313     *
1314     * If the object implements {@code Externalizable} its
1315     * {@code writeExternal} is called. Otherwise, all fields described
1316     * by the class hierarchy is dumped. Each class can define how its declared
1317     * instance fields are dumped by defining a private method
1318     * {@code writeObject}
1319     *
1320     * @param object
1321     *            The object to dump
1322     * @param theClass
1323     *            A {@code java.lang.Class} representing the class of the
1324     *            object
1325     * @param unshared
1326     *            Write the object unshared
1327     * @return the handle assigned to the object
1328     *
1329     * @throws IOException
1330     *             If an IO exception happened when writing the object.
1331     */
1332    private int writeNewObject(Object object, Class<?> theClass, ObjectStreamClass clDesc,
1333            boolean unshared) throws IOException {
1334        // Not String, not null, not array, not cyclic reference
1335
1336        EmulatedFieldsForDumping originalCurrentPutField = currentPutField; // save
1337        currentPutField = null; // null it, to make sure one will be computed if
1338        // needed
1339
1340        boolean externalizable = clDesc.isExternalizable();
1341        boolean serializable = clDesc.isSerializable();
1342        if (!externalizable && !serializable) {
1343            // Object is neither externalizable nor serializable. Error
1344            throw new NotSerializableException(theClass.getName());
1345        }
1346
1347        // Either serializable or externalizable, now we can save info
1348        output.writeByte(TC_OBJECT);
1349        writeClassDesc(clDesc, false);
1350        int previousHandle = -1;
1351        if (unshared) {
1352            previousHandle = objectsWritten.get(object);
1353        }
1354
1355        int handle = registerObjectWritten(object);
1356
1357        // This is how we know what to do in defaultWriteObject. And it is also
1358        // used by defaultWriteObject to check if it was called from an invalid
1359        // place.
1360        // It allows writeExternal to call defaultWriteObject and have it work.
1361        currentObject = object;
1362        currentClass = clDesc;
1363        try {
1364            if (externalizable) {
1365                boolean noBlockData = protocolVersion == PROTOCOL_VERSION_1;
1366                if (noBlockData) {
1367                    primitiveTypes = output;
1368                }
1369                // Object is externalizable, just call its own method
1370                ((Externalizable) object).writeExternal(this);
1371                if (noBlockData) {
1372                    primitiveTypes = null;
1373                } else {
1374                    // Similar to the code in writeHierarchy when object
1375                    // implements writeObject.
1376                    // Any primitive data has to be flushed and a tag must be
1377                    // written
1378                    drain();
1379                    output.writeByte(TC_ENDBLOCKDATA);
1380                }
1381            } else { // If it got here, it has to be Serializable
1382                // Object is serializable. Walk the class chain writing the
1383                // fields
1384                writeHierarchy(object, currentClass);
1385            }
1386        } finally {
1387            // Cleanup, needs to run always so that we can later detect invalid
1388            // calls to defaultWriteObject
1389            if (unshared) {
1390                // remove reference to unshared object
1391                removeUnsharedReference(object, previousHandle);
1392            }
1393            currentObject = null;
1394            currentClass = null;
1395            currentPutField = originalCurrentPutField;
1396        }
1397
1398        return handle;
1399    }
1400
1401    /**
1402     * Write String {@code object} into the receiver. It is assumed the
1403     * String has not been dumped yet. Returns the handle for this object (String) which is dumped here.
1404     * Strings are saved encoded with {@link DataInput modified UTF-8}.
1405     *
1406     * @param object
1407     *            the string to dump.
1408     * @return the handle assigned to the String being dumped
1409     *
1410     * @throws IOException
1411     *             If an IO exception happened when writing the String.
1412     */
1413    private int writeNewString(String object, boolean unshared) throws IOException {
1414        long count = ModifiedUtf8.countBytes(object, false);
1415        byte[] buffer;
1416        int offset = 0;
1417        if (count <= 0xffff) {
1418            buffer = new byte[1 + SizeOf.SHORT + (int) count];
1419            buffer[offset++] = TC_STRING;
1420            Memory.pokeShort(buffer, offset, (short) count, ByteOrder.BIG_ENDIAN);
1421            offset += SizeOf.SHORT;
1422        } else {
1423            buffer = new byte[1 + SizeOf.LONG + (int) count];
1424            buffer[offset++] = TC_LONGSTRING;
1425            Memory.pokeLong(buffer, offset, count, ByteOrder.BIG_ENDIAN);
1426            offset += SizeOf.LONG;
1427        }
1428        ModifiedUtf8.encode(buffer, offset, object);
1429        output.write(buffer, 0, buffer.length);
1430
1431        int handle = nextHandle();
1432        if (!unshared) {
1433            objectsWritten.put(object, handle);
1434        }
1435
1436        return handle;
1437    }
1438
1439    /**
1440     * Write a special tag that indicates the value {@code null} into the
1441     * receiver.
1442     *
1443     * @throws IOException
1444     *             If an IO exception happened when writing the tag for
1445     *             {@code null}.
1446     */
1447    private void writeNull() throws IOException {
1448        output.writeByte(TC_NULL);
1449    }
1450
1451    /**
1452     * Writes an object to the target stream.
1453     *
1454     * @param object
1455     *            the object to write to the target stream.
1456     * @throws IOException
1457     *             if an error occurs while writing to the target stream.
1458     * @see ObjectInputStream#readObject()
1459     */
1460    public final void writeObject(Object object) throws IOException {
1461        writeObject(object, false);
1462    }
1463
1464    /**
1465     * Writes an unshared object to the target stream. This method is identical
1466     * to {@code writeObject}, except that it always writes a new object to the
1467     * stream versus the use of back-referencing for identical objects by
1468     * {@code writeObject}.
1469     *
1470     * @param object
1471     *            the object to write to the target stream.
1472     * @throws IOException
1473     *             if an error occurs while writing to the target stream.
1474     * @see ObjectInputStream#readUnshared()
1475     */
1476    public void writeUnshared(Object object) throws IOException {
1477        writeObject(object, true);
1478    }
1479
1480    private void writeObject(Object object, boolean unshared) throws IOException {
1481        boolean setOutput = (primitiveTypes == output);
1482        if (setOutput) {
1483            primitiveTypes = null;
1484        }
1485        // This is the specified behavior in JDK 1.2. Very bizarre way to allow
1486        // behavior overriding.
1487        if (subclassOverridingImplementation && !unshared) {
1488            writeObjectOverride(object);
1489            return;
1490        }
1491
1492        try {
1493            // First we need to flush primitive types if they were written
1494            drain();
1495            // Actual work, and class-based replacement should be computed
1496            // if needed.
1497            writeObjectInternal(object, unshared, true, true);
1498            if (setOutput) {
1499                primitiveTypes = output;
1500            }
1501        } catch (IOException ioEx1) {
1502            // This will make it pass through until the top caller. Only the top caller writes the
1503            // exception (where it can).
1504            if (nestedLevels == 0) {
1505                try {
1506                    writeNewException(ioEx1);
1507                } catch (IOException ioEx2) {
1508                    // If writing the exception to the output stream causes another exception there
1509                    // is no need to propagate the second exception or generate a third exception,
1510                    // both of which might obscure details of the root cause.
1511                }
1512            }
1513            throw ioEx1; // and then we propagate the original exception
1514        }
1515    }
1516
1517    /**
1518     * Write object {@code object} into the receiver's underlying stream.
1519     *
1520     * @param object
1521     *            The object to write
1522     * @param unshared
1523     *            Write the object unshared
1524     * @param computeClassBasedReplacement
1525     *            A boolean indicating if class-based replacement should be
1526     *            computed (if supported) for the object.
1527     * @param computeStreamReplacement
1528     *            A boolean indicating if stream-based replacement should be
1529     *            computed (if supported) for the object.
1530     * @return the handle assigned to the final object being dumped
1531     *
1532     * @throws IOException
1533     *             If an IO exception happened when writing the object
1534     *
1535     * @see ObjectInputStream#readObject()
1536     */
1537    private int writeObjectInternal(Object object, boolean unshared,
1538            boolean computeClassBasedReplacement,
1539            boolean computeStreamReplacement) throws IOException {
1540
1541        if (object == null) {
1542            writeNull();
1543            return -1;
1544        }
1545        if (!unshared) {
1546            int handle = dumpCycle(object);
1547            if (handle != -1) {
1548                return handle; // cyclic reference
1549            }
1550        }
1551
1552        // Non-null object, first time seen...
1553        Class<?> objClass = object.getClass();
1554        ObjectStreamClass clDesc = ObjectStreamClass.lookupStreamClass(objClass);
1555
1556        nestedLevels++;
1557        try {
1558
1559            if (!(enableReplace && computeStreamReplacement)) {
1560                // Is it a Class ?
1561                if (objClass == ObjectStreamClass.CLASSCLASS) {
1562                    return writeNewClass((Class<?>) object, unshared);
1563                }
1564                // Is it an ObjectStreamClass ?
1565                if (objClass == ObjectStreamClass.OBJECTSTREAMCLASSCLASS) {
1566                    return writeClassDesc((ObjectStreamClass) object, unshared);
1567                }
1568            }
1569
1570            if (clDesc.isSerializable() && computeClassBasedReplacement) {
1571                if (clDesc.hasMethodWriteReplace()){
1572                    Method methodWriteReplace = clDesc.getMethodWriteReplace();
1573                    Object replObj;
1574                    try {
1575                        replObj = methodWriteReplace.invoke(object, (Object[]) null);
1576                    } catch (IllegalAccessException iae) {
1577                        replObj = object;
1578                    } catch (InvocationTargetException ite) {
1579                        // WARNING - Not sure this is the right thing to do
1580                        // if we can't run the method
1581                        Throwable target = ite.getTargetException();
1582                        if (target instanceof ObjectStreamException) {
1583                            throw (ObjectStreamException) target;
1584                        } else if (target instanceof Error) {
1585                            throw (Error) target;
1586                        } else {
1587                            throw (RuntimeException) target;
1588                        }
1589                    }
1590                    if (replObj != object) {
1591                        // All over, class-based replacement off this time.
1592                        int replacementHandle = writeObjectInternal(replObj, false, false,
1593                                computeStreamReplacement);
1594                        // Make the original object also map to the same
1595                        // handle.
1596                        if (replacementHandle != -1) {
1597                            objectsWritten.put(object, replacementHandle);
1598                        }
1599                        return replacementHandle;
1600                    }
1601                }
1602
1603            }
1604
1605            // We get here either if class-based replacement was not needed or
1606            // if it was needed but produced the same object or if it could not
1607            // be computed.
1608            if (enableReplace && computeStreamReplacement) {
1609                // Now we compute the stream-defined replacement.
1610                Object streamReplacement = replaceObject(object);
1611                if (streamReplacement != object) {
1612                    // All over, class-based replacement off this time.
1613                    int replacementHandle = writeObjectInternal(streamReplacement, false,
1614                            computeClassBasedReplacement, false);
1615                    // Make the original object also map to the same handle.
1616                    if (replacementHandle != -1) {
1617                        objectsWritten.put(object, replacementHandle);
1618                    }
1619                    return replacementHandle;
1620                }
1621            }
1622
1623            // We get here if stream-based replacement produced the same object
1624
1625            // Is it a Class ?
1626            if (objClass == ObjectStreamClass.CLASSCLASS) {
1627                return writeNewClass((Class<?>) object, unshared);
1628            }
1629
1630            // Is it an ObjectStreamClass ?
1631            if (objClass == ObjectStreamClass.OBJECTSTREAMCLASSCLASS) {
1632                return writeClassDesc((ObjectStreamClass) object, unshared);
1633            }
1634
1635            // Is it a String ? (instanceof, but == is faster)
1636            if (objClass == ObjectStreamClass.STRINGCLASS) {
1637                return writeNewString((String) object, unshared);
1638            }
1639
1640            // Is it an Array ?
1641            if (objClass.isArray()) {
1642                return writeNewArray(object, objClass, clDesc, objClass
1643                        .getComponentType(), unshared);
1644            }
1645
1646            if (object instanceof Enum) {
1647                return writeNewEnum(object, objClass, unshared);
1648            }
1649
1650            // Not a String or Class or Array. Default procedure.
1651            return writeNewObject(object, objClass, clDesc, unshared);
1652        } finally {
1653            nestedLevels--;
1654        }
1655    }
1656
1657    // write for Enum Class Desc only, which is different from other classes
1658    private ObjectStreamClass writeEnumDesc(ObjectStreamClass classDesc, boolean unshared)
1659            throws IOException {
1660        // write classDesc, classDesc for enum is different
1661
1662        // set flag for enum, the flag is (SC_SERIALIZABLE | SC_ENUM)
1663        classDesc.setFlags((byte) (SC_SERIALIZABLE | SC_ENUM));
1664        int previousHandle = -1;
1665        if (unshared) {
1666            previousHandle = objectsWritten.get(classDesc);
1667        }
1668        int handle = -1;
1669        if (!unshared) {
1670            handle = dumpCycle(classDesc);
1671        }
1672        if (handle == -1) {
1673            Class<?> classToWrite = classDesc.forClass();
1674            // If we got here, it is a new (non-null) classDesc that will have
1675            // to be registered as well
1676            registerObjectWritten(classDesc);
1677
1678            output.writeByte(TC_CLASSDESC);
1679            if (protocolVersion == PROTOCOL_VERSION_1) {
1680                writeNewClassDesc(classDesc);
1681            } else {
1682                // So write...() methods can be used by
1683                // subclasses during writeClassDescriptor()
1684                primitiveTypes = output;
1685                writeClassDescriptor(classDesc);
1686                primitiveTypes = null;
1687            }
1688            // Extra class info (optional)
1689            annotateClass(classToWrite);
1690            drain(); // flush primitive types in the annotation
1691            output.writeByte(TC_ENDBLOCKDATA);
1692            // write super class
1693            ObjectStreamClass superClassDesc = classDesc.getSuperclass();
1694            if (superClassDesc != null) {
1695                // super class is also enum
1696                superClassDesc.setFlags((byte) (SC_SERIALIZABLE | SC_ENUM));
1697                writeEnumDesc(superClassDesc, unshared);
1698            } else {
1699                output.writeByte(TC_NULL);
1700            }
1701            if (unshared) {
1702                // remove reference to unshared object
1703                removeUnsharedReference(classDesc, previousHandle);
1704            }
1705        }
1706        return classDesc;
1707    }
1708
1709    private int writeNewEnum(Object object, Class<?> theClass, boolean unshared) throws IOException {
1710        // write new Enum
1711        EmulatedFieldsForDumping originalCurrentPutField = currentPutField; // save
1712        // null it, to make sure one will be computed if needed
1713        currentPutField = null;
1714
1715        output.writeByte(TC_ENUM);
1716        while (theClass != null && !theClass.isEnum()) {
1717            // write enum only
1718            theClass = theClass.getSuperclass();
1719        }
1720        ObjectStreamClass classDesc = ObjectStreamClass.lookup(theClass);
1721        writeEnumDesc(classDesc, unshared);
1722
1723        int previousHandle = -1;
1724        if (unshared) {
1725            previousHandle = objectsWritten.get(object);
1726        }
1727        int handle = registerObjectWritten(object);
1728
1729        ObjectStreamField[] fields = classDesc.getSuperclass().fields();
1730        // Only write field "name" for enum class, which is the second field of
1731        // enum, that is fields[1]. Ignore all non-fields and fields.length < 2
1732        if (fields != null && fields.length > 1) {
1733            Field field = classDesc.getSuperclass().checkAndGetReflectionField(fields[1]);
1734            if (field == null) {
1735                throw new NoSuchFieldError();
1736            }
1737            try {
1738                String str = (String) field.get(object);
1739                int strHandle = -1;
1740                if (!unshared) {
1741                    strHandle = dumpCycle(str);
1742                }
1743                if (strHandle == -1) {
1744                    writeNewString(str, unshared);
1745                }
1746            } catch (IllegalAccessException iae) {
1747                throw new AssertionError(iae);
1748            }
1749        }
1750
1751        if (unshared) {
1752            // remove reference to unshared object
1753            removeUnsharedReference(object, previousHandle);
1754        }
1755        currentPutField = originalCurrentPutField;
1756        return handle;
1757    }
1758
1759    /**
1760     * Method to be overridden by subclasses to write {@code object} to the
1761     * target stream.
1762     *
1763     * @param object
1764     *            the object to write to the target stream.
1765     * @throws IOException
1766     *             if an error occurs while writing to the target stream.
1767     */
1768    protected void writeObjectOverride(Object object) throws IOException {
1769        if (!subclassOverridingImplementation) {
1770            // Subclasses must override.
1771            throw new IOException();
1772        }
1773    }
1774
1775    /**
1776     * Writes a short (16 bit) to the target stream.
1777     *
1778     * @param value
1779     *            the short to write to the target stream.
1780     * @throws IOException
1781     *             if an error occurs while writing to the target stream.
1782     */
1783    public void writeShort(int value) throws IOException {
1784        checkWritePrimitiveTypes();
1785        primitiveTypes.writeShort(value);
1786    }
1787
1788    /**
1789     * Writes the {@link ObjectOutputStream} header to the target stream.
1790     *
1791     * @throws IOException
1792     *             if an error occurs while writing to the target stream.
1793     */
1794    protected void writeStreamHeader() throws IOException {
1795        output.writeShort(STREAM_MAGIC);
1796        output.writeShort(STREAM_VERSION);
1797    }
1798
1799    /**
1800     * Writes a string encoded with {@link DataInput modified UTF-8} to the
1801     * target stream.
1802     *
1803     * @param value
1804     *            the string to write to the target stream.
1805     * @throws IOException
1806     *             if an error occurs while writing to the target stream.
1807     */
1808    public void writeUTF(String value) throws IOException {
1809        checkWritePrimitiveTypes();
1810        primitiveTypes.writeUTF(value);
1811    }
1812}
1813