001/* ObjectOutputStream.java -- Class used to write serialized objects
002   Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2008
003   Free Software Foundation, Inc.
004
005This file is part of GNU Classpath.
006
007GNU Classpath is free software; you can redistribute it and/or modify
008it under the terms of the GNU General Public License as published by
009the Free Software Foundation; either version 2, or (at your option)
010any later version.
011
012GNU Classpath is distributed in the hope that it will be useful, but
013WITHOUT ANY WARRANTY; without even the implied warranty of
014MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
015General Public License for more details.
016
017You should have received a copy of the GNU General Public License
018along with GNU Classpath; see the file COPYING.  If not, write to the
019Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02002110-1301 USA.
021
022Linking this library statically or dynamically with other modules is
023making a combined work based on this library.  Thus, the terms and
024conditions of the GNU General Public License cover the whole
025combination.
026
027As a special exception, the copyright holders of this library give you
028permission to link this library with independent modules to produce an
029executable, regardless of the license terms of these independent
030modules, and to copy and distribute the resulting executable under
031terms of your choice, provided that you also meet, for each linked
032independent module, the terms and conditions of the license of that
033module.  An independent module is a module which is not derived from
034or based on this library.  If you modify this library, you may extend
035this exception to your version of the library, but you are not
036obligated to do so.  If you do not wish to do so, delete this
037exception statement from your version. */
038
039
040package java.io;
041
042import gnu.java.io.ObjectIdentityMap2Int;
043import gnu.java.lang.reflect.TypeSignature;
044import gnu.java.security.action.SetAccessibleAction;
045
046import java.lang.reflect.Array;
047import java.lang.reflect.Field;
048import java.lang.reflect.InvocationTargetException;
049import java.lang.reflect.Method;
050
051
052/**
053 * An <code>ObjectOutputStream</code> can be used to write objects
054 * as well as primitive data in a platform-independent manner to an
055 * <code>OutputStream</code>.
056 *
057 * The data produced by an <code>ObjectOutputStream</code> can be read
058 * and reconstituted by an <code>ObjectInputStream</code>.
059 *
060 * <code>writeObject (Object)</code> is used to write Objects, the
061 * <code>write&lt;type&gt;</code> methods are used to write primitive
062 * data (as in <code>DataOutputStream</code>). Strings can be written
063 * as objects or as primitive data.
064 *
065 * Not all objects can be written out using an
066 * <code>ObjectOutputStream</code>.  Only those objects that are an
067 * instance of <code>java.io.Serializable</code> can be written.
068 *
069 * Using default serialization, information about the class of an
070 * object is written, all of the non-transient, non-static fields of
071 * the object are written, if any of these fields are objects, they are
072 * written out in the same manner.
073 *
074 * An object is only written out the first time it is encountered.  If
075 * the object is encountered later, a reference to it is written to
076 * the underlying stream.  Thus writing circular object graphs
077 * does not present a problem, nor are relationships between objects
078 * in a graph lost.
079 *
080 * Example usage:
081 * <pre>
082 * Hashtable map = new Hashtable ();
083 * map.put ("one", new Integer (1));
084 * map.put ("two", new Integer (2));
085 *
086 * ObjectOutputStream oos =
087 * new ObjectOutputStream (new FileOutputStream ("numbers"));
088 * oos.writeObject (map);
089 * oos.close ();
090 *
091 * ObjectInputStream ois =
092 * new ObjectInputStream (new FileInputStream ("numbers"));
093 * Hashtable newmap = (Hashtable)ois.readObject ();
094 *
095 * System.out.println (newmap);
096 * </pre>
097 *
098 * The default serialization can be overriden in two ways.
099 *
100 * By defining a method <code>private void
101 * writeObject (ObjectOutputStream)</code>, a class can dictate exactly
102 * how information about itself is written.
103 * <code>defaultWriteObject ()</code> may be called from this method to
104 * carry out default serialization.  This method is not
105 * responsible for dealing with fields of super-classes or subclasses.
106 *
107 * By implementing <code>java.io.Externalizable</code>.  This gives
108 * the class complete control over the way it is written to the
109 * stream.  If this approach is used the burden of writing superclass
110 * and subclass data is transfered to the class implementing
111 * <code>java.io.Externalizable</code>.
112 *
113 * @see java.io.DataOutputStream
114 * @see java.io.Externalizable
115 * @see java.io.ObjectInputStream
116 * @see java.io.Serializable
117 * @author Tom Tromey (tromey@redhat.com)
118 * @author Jeroen Frijters (jeroen@frijters.net)
119 * @author Guilhem Lavaux (guilhem@kaffe.org)
120 * @author Michael Koch (konqueror@gmx.de)
121 * @author Andrew John Hughes (gnu_andrew@member.fsf.org)
122 */
123public class ObjectOutputStream extends OutputStream
124  implements ObjectOutput, ObjectStreamConstants
125{
126  /**
127   * Creates a new <code>ObjectOutputStream</code> that will do all of
128   * its writing onto <code>out</code>.  This method also initializes
129   * the stream by writing the header information (stream magic number
130   * and stream version).
131   *
132   * @exception IOException Writing stream header to underlying
133   * stream cannot be completed.
134   *
135   * @see #writeStreamHeader()
136   */
137  public ObjectOutputStream (OutputStream out) throws IOException
138  {
139    realOutput = new DataOutputStream(out);
140    blockData = new byte[ BUFFER_SIZE ];
141    blockDataCount = 0;
142    blockDataOutput = new DataOutputStream(this);
143    setBlockDataMode(true);
144    replacementEnabled = false;
145    isSerializing = false;
146    nextOID = baseWireHandle;
147    OIDLookupTable = new ObjectIdentityMap2Int();
148    protocolVersion = defaultProtocolVersion;
149    useSubclassMethod = false;
150    writeStreamHeader();
151
152    if (DEBUG)
153      {
154        String val = System.getProperty("gcj.dumpobjects");
155        if (val != null && !val.equals(""))
156          dump = true;
157      }
158  }
159
160  /**
161   * Writes a representation of <code>obj</code> to the underlying
162   * output stream by writing out information about its class, then
163   * writing out each of the objects non-transient, non-static
164   * fields.  If any of these fields are other objects,
165   * they are written out in the same manner.
166   *
167   * This method can be overriden by a class by implementing
168   * <code>private void writeObject (ObjectOutputStream)</code>.
169   *
170   * If an exception is thrown from this method, the stream is left in
171   * an undefined state.
172   *
173   * @param obj the object to serialize.
174   * @exception NotSerializableException An attempt was made to
175   * serialize an <code>Object</code> that is not serializable.
176   *
177   * @exception InvalidClassException Somebody tried to serialize
178   * an object which is wrongly formatted.
179   *
180   * @exception IOException Exception from underlying
181   * <code>OutputStream</code>.
182   * @see #writeUnshared(Object)
183   */
184  public final void writeObject(Object obj) throws IOException
185  {
186    writeObject(obj, true);
187  }
188
189  /**
190   * Writes an object to the stream in the same manner as
191   * {@link #writeObject(Object)}, but without the use of
192   * references.  As a result, the object is always written
193   * to the stream in full.  Likewise, if an object is written
194   * by this method and is then later written again by
195   * {@link #writeObject(Object)}, both calls will write out
196   * the object in full, as the later call to
197   * {@link #writeObject(Object)} will know nothing of the
198   * earlier use of {@link #writeUnshared(Object)}.
199   *
200   * @param obj the object to serialize.
201   * @throws NotSerializableException if the object being
202   *                                  serialized does not implement
203   *                                  {@link Serializable}.
204   * @throws InvalidClassException if a problem occurs with
205   *                               the class of the object being
206   *                               serialized.
207   * @throws IOException if an I/O error occurs on the underlying
208   *                     <code>OutputStream</code>.
209   * @since 1.4
210   * @see #writeObject(Object)
211   */
212  public void writeUnshared(Object obj)
213    throws IOException
214  {
215    writeObject(obj, false);
216  }
217
218  /**
219   * Writes a representation of <code>obj</code> to the underlying
220   * output stream by writing out information about its class, then
221   * writing out each of the objects non-transient, non-static
222   * fields.  If any of these fields are other objects,
223   * they are written out in the same manner.
224   *
225   * This method can be overriden by a class by implementing
226   * <code>private void writeObject (ObjectOutputStream)</code>.
227   *
228   * If an exception is thrown from this method, the stream is left in
229   * an undefined state.
230   *
231   * @param obj the object to serialize.
232   * @param shared true if the serialized object should be
233   *               shared with later calls.
234   * @exception NotSerializableException An attempt was made to
235   * serialize an <code>Object</code> that is not serializable.
236   *
237   * @exception InvalidClassException Somebody tried to serialize
238   * an object which is wrongly formatted.
239   *
240   * @exception IOException Exception from underlying
241   * <code>OutputStream</code>.
242   * @see #writeUnshared(Object)
243   */
244  private final void writeObject(Object obj, boolean shared)
245    throws IOException
246  {
247    if (useSubclassMethod)
248      {
249        if (dump)
250          dumpElementln ("WRITE OVERRIDE: " + obj);
251
252        writeObjectOverride(obj);
253        return;
254      }
255
256    if (dump)
257      dumpElementln ("WRITE: ", obj);
258
259    depth += 2;
260
261    boolean was_serializing = isSerializing;
262    boolean old_mode = setBlockDataMode(false);
263    try
264      {
265        isSerializing = true;
266        boolean replaceDone = false;
267        Object replacedObject = null;
268
269        while (true)
270          {
271            if (obj == null)
272              {
273                realOutput.writeByte(TC_NULL);
274                break;
275              }
276
277            int handle = findHandle(obj);
278            if (handle >= 0 && shared)
279              {
280                realOutput.writeByte(TC_REFERENCE);
281                realOutput.writeInt(handle);
282                break;
283              }
284
285            if (obj instanceof Class)
286              {
287                Class cl = (Class)obj;
288                ObjectStreamClass osc = ObjectStreamClass.lookupForClassObject(cl);
289                realOutput.writeByte(TC_CLASS);
290                if (!osc.isProxyClass)
291                  {
292                    writeObject (osc);
293                  }
294                else
295                  {System.err.println("1");
296                    realOutput.writeByte(TC_PROXYCLASSDESC);
297                    Class[] intfs = cl.getInterfaces();
298                    realOutput.writeInt(intfs.length);
299                    for (int i = 0; i < intfs.length; i++)
300                      realOutput.writeUTF(intfs[i].getName());
301
302                    boolean oldmode = setBlockDataMode(true);
303                    annotateProxyClass(cl);
304                    setBlockDataMode(oldmode);
305                    realOutput.writeByte(TC_ENDBLOCKDATA);
306
307                    writeObject(osc.getSuper());
308                  }
309                if (shared)
310                  assignNewHandle(obj);
311                break;
312              }
313
314            if (obj instanceof ObjectStreamClass)
315              {
316                writeClassDescriptor((ObjectStreamClass) obj);
317                break;
318              }
319
320            Class clazz = obj.getClass();
321            ObjectStreamClass osc = ObjectStreamClass.lookupForClassObject(clazz);
322            if (osc == null)
323              throw new NotSerializableException(clazz.getName());
324
325            if (osc.isEnum())
326              {
327                /* TC_ENUM classDesc newHandle enumConstantName */
328                realOutput.writeByte(TC_ENUM);
329                writeObject(osc);
330                if (shared)
331                  assignNewHandle(obj);
332                writeObject(((Enum) obj).name());
333                break;
334              }
335
336            if ((replacementEnabled || obj instanceof Serializable)
337                && ! replaceDone)
338              {
339                replacedObject = obj;
340
341                if (obj instanceof Serializable)
342                  {
343                    try
344                      {
345                        Method m = osc.writeReplaceMethod;
346                        if (m != null)
347                            obj = m.invoke(obj, new Object[0]);
348                      }
349                    catch (IllegalAccessException ignore)
350                      {
351                      }
352                    catch (InvocationTargetException ignore)
353                      {
354                      }
355                  }
356
357                if (replacementEnabled)
358                  obj = replaceObject(obj);
359
360                replaceDone = true;
361                continue;
362              }
363
364            if (obj instanceof String)
365              {
366                String s = (String)obj;
367                long l = realOutput.getUTFlength(s, 0, 0);
368                if (l <= 65535)
369                  {
370                    realOutput.writeByte(TC_STRING);
371                    if (shared)
372                      assignNewHandle(obj);
373                    realOutput.writeUTFShort(s, (int)l);
374                  }
375                else
376                  {
377                    realOutput.writeByte(TC_LONGSTRING);
378                    if (shared)
379                      assignNewHandle(obj);
380                    realOutput.writeUTFLong(s, l);
381                  }
382                break;
383              }
384
385            if (clazz.isArray ())
386              {
387                realOutput.writeByte(TC_ARRAY);
388                writeObject(osc);
389                if (shared)
390                  assignNewHandle(obj);
391                writeArraySizeAndElements(obj, clazz.getComponentType());
392                break;
393              }
394
395            realOutput.writeByte(TC_OBJECT);
396            writeObject(osc);
397
398            if (shared)
399              if (replaceDone)
400                assignNewHandle(replacedObject);
401              else
402                assignNewHandle(obj);
403
404            if (obj instanceof Externalizable)
405              {
406                if (protocolVersion == PROTOCOL_VERSION_2)
407                  setBlockDataMode(true);
408
409                ((Externalizable)obj).writeExternal(this);
410
411                if (protocolVersion == PROTOCOL_VERSION_2)
412                  {
413                    setBlockDataMode(false);
414                    realOutput.writeByte(TC_ENDBLOCKDATA);
415                  }
416
417                break;
418              }
419
420            if (obj instanceof Serializable)
421              {
422                Object prevObject = this.currentObject;
423                ObjectStreamClass prevObjectStreamClass = this.currentObjectStreamClass;
424                currentObject = obj;
425                ObjectStreamClass[] hierarchy = osc.hierarchy();
426
427                for (int i = 0; i < hierarchy.length; i++)
428                  {
429                    currentObjectStreamClass = hierarchy[i];
430
431                    fieldsAlreadyWritten = false;
432                    if (currentObjectStreamClass.hasWriteMethod())
433                      {
434                        if (dump)
435                          dumpElementln ("WRITE METHOD CALLED FOR: ", obj);
436                        setBlockDataMode(true);
437                        callWriteMethod(obj, currentObjectStreamClass);
438                        setBlockDataMode(false);
439                        realOutput.writeByte(TC_ENDBLOCKDATA);
440                        if (dump)
441                          dumpElementln ("WRITE ENDBLOCKDATA FOR: ", obj);
442                      }
443                    else
444                      {
445                        if (dump)
446                          dumpElementln ("WRITE FIELDS CALLED FOR: ", obj);
447                        writeFields(obj, currentObjectStreamClass);
448                      }
449                  }
450
451                this.currentObject = prevObject;
452                this.currentObjectStreamClass = prevObjectStreamClass;
453                currentPutField = null;
454                break;
455              }
456
457            throw new NotSerializableException(clazz.getName()
458                                               + " in "
459                                               + obj.getClass());
460          } // end pseudo-loop
461      }
462    catch (ObjectStreamException ose)
463      {
464        // Rethrow these are fatal.
465        throw ose;
466      }
467    catch (IOException e)
468      {
469        realOutput.writeByte(TC_EXCEPTION);
470        reset(true);
471
472        setBlockDataMode(false);
473        try
474          {
475            if (DEBUG)
476              {
477                e.printStackTrace(System.out);
478              }
479            writeObject(e);
480          }
481        catch (IOException ioe)
482          {
483            StreamCorruptedException ex =
484              new StreamCorruptedException
485              (ioe + " thrown while exception was being written to stream.");
486            if (DEBUG)
487              {
488                ex.printStackTrace(System.out);
489              }
490            throw ex;
491          }
492
493        reset (true);
494
495      }
496    finally
497      {
498        isSerializing = was_serializing;
499        setBlockDataMode(old_mode);
500        depth -= 2;
501
502        if (dump)
503          dumpElementln ("END: ", obj);
504      }
505  }
506
507  protected void writeClassDescriptor(ObjectStreamClass osc) throws IOException
508  {
509    if (osc.isProxyClass)
510      {
511        realOutput.writeByte(TC_PROXYCLASSDESC);
512        Class[] intfs = osc.forClass().getInterfaces();
513        realOutput.writeInt(intfs.length);
514        for (int i = 0; i < intfs.length; i++)
515          realOutput.writeUTF(intfs[i].getName());
516
517        assignNewHandle(osc);
518
519        boolean oldmode = setBlockDataMode(true);
520        annotateProxyClass(osc.forClass());
521        setBlockDataMode(oldmode);
522        realOutput.writeByte(TC_ENDBLOCKDATA);
523      }
524    else
525      {
526        realOutput.writeByte(TC_CLASSDESC);
527        realOutput.writeUTF(osc.getName());
528        if (osc.isEnum())
529          realOutput.writeLong(0L);
530        else
531          realOutput.writeLong(osc.getSerialVersionUID());
532        assignNewHandle(osc);
533
534        int flags = osc.getFlags();
535
536        if (protocolVersion == PROTOCOL_VERSION_2
537            && osc.isExternalizable())
538        flags |= SC_BLOCK_DATA;
539
540        realOutput.writeByte(flags);
541
542        ObjectStreamField[] fields = osc.fields;
543
544        if (fields == ObjectStreamClass.INVALID_FIELDS)
545          throw new InvalidClassException
546                  (osc.getName(), "serialPersistentFields is invalid");
547
548        realOutput.writeShort(fields.length);
549
550        ObjectStreamField field;
551        for (int i = 0; i < fields.length; i++)
552          {
553            field = fields[i];
554            realOutput.writeByte(field.getTypeCode ());
555            realOutput.writeUTF(field.getName ());
556
557            if (! field.isPrimitive())
558              writeObject(field.getTypeString());
559          }
560
561        boolean oldmode = setBlockDataMode(true);
562        annotateClass(osc.forClass());
563        setBlockDataMode(oldmode);
564        realOutput.writeByte(TC_ENDBLOCKDATA);
565      }
566
567    if (osc.isSerializable() || osc.isExternalizable())
568      writeObject(osc.getSuper());
569    else
570      writeObject(null);
571  }
572
573  /**
574   * Writes the current objects non-transient, non-static fields from
575   * the current class to the underlying output stream.
576   *
577   * This method is intended to be called from within a object's
578   * <code>private void writeObject (ObjectOutputStream)</code>
579   * method.
580   *
581   * @exception NotActiveException This method was called from a
582   * context other than from the current object's and current class's
583   * <code>private void writeObject (ObjectOutputStream)</code>
584   * method.
585   *
586   * @exception IOException Exception from underlying
587   * <code>OutputStream</code>.
588   */
589  public void defaultWriteObject()
590    throws IOException, NotActiveException
591  {
592    markFieldsWritten();
593    writeFields(currentObject, currentObjectStreamClass);
594  }
595
596
597  private void markFieldsWritten() throws IOException
598  {
599    if (currentObject == null || currentObjectStreamClass == null)
600      throw new NotActiveException
601        ("defaultWriteObject called by non-active class and/or object");
602
603    if (fieldsAlreadyWritten)
604      throw new IOException
605        ("Only one of writeFields and defaultWriteObject may be called, and it may only be called once");
606
607    fieldsAlreadyWritten = true;
608  }
609
610  /**
611   * Resets stream to state equivalent to the state just after it was
612   * constructed.
613   *
614   * Causes all objects previously written to the stream to be
615   * forgotten.  A notification of this reset is also written to the
616   * underlying stream.
617   *
618   * @exception IOException Exception from underlying
619   * <code>OutputStream</code> or reset called while serialization is
620   * in progress.
621   */
622  public void reset() throws IOException
623  {
624    reset(false);
625  }
626
627
628  private void reset(boolean internal) throws IOException
629  {
630    if (!internal)
631      {
632        if (isSerializing)
633          throw new IOException("Reset called while serialization in progress");
634
635        realOutput.writeByte(TC_RESET);
636      }
637
638    clearHandles();
639  }
640
641
642  /**
643   * Informs this <code>ObjectOutputStream</code> to write data
644   * according to the specified protocol.  There are currently two
645   * different protocols, specified by <code>PROTOCOL_VERSION_1</code>
646   * and <code>PROTOCOL_VERSION_2</code>.  This implementation writes
647   * data using <code>PROTOCOL_VERSION_2</code> by default, as is done
648   * since the JDK 1.2.
649   * <p>
650   * For an explanation of the differences between the two protocols
651   * see the Java Object Serialization Specification.
652   * </p>
653   *
654   * @param version the version to use.
655   *
656   * @throws IllegalArgumentException if <code>version</code> is not a valid
657   * protocol.
658   * @throws IllegalStateException if called after the first the first object
659   * was serialized.
660   * @throws IOException if an I/O error occurs.
661   *
662   * @see ObjectStreamConstants#PROTOCOL_VERSION_1
663   * @see ObjectStreamConstants#PROTOCOL_VERSION_2
664   *
665   * @since 1.2
666   */
667  public void useProtocolVersion(int version) throws IOException
668  {
669    if (version != PROTOCOL_VERSION_1 && version != PROTOCOL_VERSION_2)
670      throw new IllegalArgumentException("Invalid protocol version requested.");
671
672    if (nextOID != baseWireHandle)
673      throw new IllegalStateException("Protocol version cannot be changed "
674                                      + "after serialization started.");
675
676    protocolVersion = version;
677  }
678
679  /**
680   * An empty hook that allows subclasses to write extra information
681   * about classes to the stream.  This method is called the first
682   * time each class is seen, and after all of the standard
683   * information about the class has been written.
684   *
685   * @exception IOException Exception from underlying
686   * <code>OutputStream</code>.
687   *
688   * @see ObjectInputStream#resolveClass(java.io.ObjectStreamClass)
689   */
690  protected void annotateClass(Class<?> cl) throws IOException
691  {
692  }
693
694  protected void annotateProxyClass(Class<?> cl) throws IOException
695  {
696  }
697
698  /**
699   * Allows subclasses to replace objects that are written to the
700   * stream with other objects to be written in their place.  This
701   * method is called the first time each object is encountered
702   * (modulo reseting of the stream).
703   *
704   * This method must be enabled before it will be called in the
705   * serialization process.
706   *
707   * @exception IOException Exception from underlying
708   * <code>OutputStream</code>.
709   *
710   * @see #enableReplaceObject(boolean)
711   */
712  protected Object replaceObject(Object obj) throws IOException
713  {
714    return obj;
715  }
716
717
718  /**
719   * If <code>enable</code> is <code>true</code> and this object is
720   * trusted, then <code>replaceObject (Object)</code> will be called
721   * in subsequent calls to <code>writeObject (Object)</code>.
722   * Otherwise, <code>replaceObject (Object)</code> will not be called.
723   *
724   * @exception SecurityException This class is not trusted.
725   */
726  protected boolean enableReplaceObject(boolean enable)
727    throws SecurityException
728  {
729    if (enable)
730      {
731        SecurityManager sm = System.getSecurityManager();
732        if (sm != null)
733          sm.checkPermission(new SerializablePermission("enableSubstitution"));
734      }
735
736    boolean old_val = replacementEnabled;
737    replacementEnabled = enable;
738    return old_val;
739  }
740
741
742  /**
743   * Writes stream magic and stream version information to the
744   * underlying stream.
745   *
746   * @exception IOException Exception from underlying
747   * <code>OutputStream</code>.
748   */
749  protected void writeStreamHeader() throws IOException
750  {
751    realOutput.writeShort(STREAM_MAGIC);
752    realOutput.writeShort(STREAM_VERSION);
753  }
754
755  /**
756   * Protected constructor that allows subclasses to override
757   * serialization.  This constructor should be called by subclasses
758   * that wish to override <code>writeObject (Object)</code>.  This
759   * method does a security check <i>NOTE: currently not
760   * implemented</i>, then sets a flag that informs
761   * <code>writeObject (Object)</code> to call the subclasses
762   * <code>writeObjectOverride (Object)</code> method.
763   *
764   * @see #writeObjectOverride(Object)
765   */
766  protected ObjectOutputStream() throws IOException, SecurityException
767  {
768    SecurityManager sec_man = System.getSecurityManager ();
769    if (sec_man != null)
770      sec_man.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
771    useSubclassMethod = true;
772  }
773
774
775  /**
776   * This method allows subclasses to override the default
777   * serialization mechanism provided by
778   * <code>ObjectOutputStream</code>.  To make this method be used for
779   * writing objects, subclasses must invoke the 0-argument
780   * constructor on this class from there constructor.
781   *
782   * @see #ObjectOutputStream()
783   *
784   * @exception NotActiveException Subclass has arranged for this
785   * method to be called, but did not implement this method.
786   */
787  protected void writeObjectOverride(Object obj) throws NotActiveException,
788    IOException
789  {
790    throw new NotActiveException
791      ("Subclass of ObjectOutputStream must implement writeObjectOverride");
792  }
793
794
795  /**
796   * @see DataOutputStream#write(int)
797   */
798  public void write (int data) throws IOException
799  {
800    if (writeDataAsBlocks)
801      {
802        if (blockDataCount == BUFFER_SIZE)
803          drain();
804
805        blockData[ blockDataCount++ ] = (byte)data;
806      }
807    else
808      realOutput.write(data);
809  }
810
811
812  /**
813   * @see DataOutputStream#write(byte[])
814   */
815  public void write(byte[] b) throws IOException
816  {
817    write(b, 0, b.length);
818  }
819
820
821  /**
822   * @see DataOutputStream#write(byte[],int,int)
823   */
824  public void write(byte[] b, int off, int len) throws IOException
825  {
826    if (writeDataAsBlocks)
827      {
828        if (len < 0)
829          throw new IndexOutOfBoundsException();
830
831        if (blockDataCount + len < BUFFER_SIZE)
832          {
833            System.arraycopy(b, off, blockData, blockDataCount, len);
834            blockDataCount += len;
835          }
836        else
837          {
838            drain();
839            writeBlockDataHeader(len);
840            realOutput.write(b, off, len);
841          }
842      }
843    else
844      realOutput.write(b, off, len);
845  }
846
847
848  /**
849   * @see DataOutputStream#flush()
850   */
851  public void flush () throws IOException
852  {
853    drain();
854    realOutput.flush();
855  }
856
857
858  /**
859   * Causes the block-data buffer to be written to the underlying
860   * stream, but does not flush underlying stream.
861   *
862   * @exception IOException Exception from underlying
863   * <code>OutputStream</code>.
864   */
865  protected void drain() throws IOException
866  {
867    if (blockDataCount == 0)
868      return;
869
870    if (writeDataAsBlocks)
871      writeBlockDataHeader(blockDataCount);
872    realOutput.write(blockData, 0, blockDataCount);
873    blockDataCount = 0;
874  }
875
876
877  /**
878   * @see java.io.DataOutputStream#close ()
879   */
880  public void close() throws IOException
881  {
882    flush();
883    realOutput.close();
884  }
885
886
887  /**
888   * @see java.io.DataOutputStream#writeBoolean (boolean)
889   */
890  public void writeBoolean(boolean data) throws IOException
891  {
892    blockDataOutput.writeBoolean(data);
893  }
894
895
896  /**
897   * @see java.io.DataOutputStream#writeByte (int)
898   */
899  public void writeByte(int data) throws IOException
900  {
901    blockDataOutput.writeByte(data);
902  }
903
904
905  /**
906   * @see java.io.DataOutputStream#writeShort (int)
907   */
908  public void writeShort (int data) throws IOException
909  {
910    blockDataOutput.writeShort(data);
911  }
912
913
914  /**
915   * @see java.io.DataOutputStream#writeChar (int)
916   */
917  public void writeChar(int data) throws IOException
918  {
919    blockDataOutput.writeChar(data);
920  }
921
922
923  /**
924   * @see java.io.DataOutputStream#writeInt (int)
925   */
926  public void writeInt(int data) throws IOException
927  {
928    blockDataOutput.writeInt(data);
929  }
930
931
932  /**
933   * @see java.io.DataOutputStream#writeLong (long)
934   */
935  public void writeLong(long data) throws IOException
936  {
937    blockDataOutput.writeLong(data);
938  }
939
940
941  /**
942   * @see java.io.DataOutputStream#writeFloat (float)
943   */
944  public void writeFloat(float data) throws IOException
945  {
946    blockDataOutput.writeFloat(data);
947  }
948
949
950  /**
951   * @see java.io.DataOutputStream#writeDouble (double)
952   */
953  public void writeDouble(double data) throws IOException
954  {
955    blockDataOutput.writeDouble(data);
956  }
957
958
959  /**
960   * @see java.io.DataOutputStream#writeBytes (java.lang.String)
961   */
962  public void writeBytes(String data) throws IOException
963  {
964    blockDataOutput.writeBytes(data);
965  }
966
967
968  /**
969   * @see java.io.DataOutputStream#writeChars (java.lang.String)
970   */
971  public void writeChars(String data) throws IOException
972  {
973    dataOutput.writeChars(data);
974  }
975
976
977  /**
978   * @see java.io.DataOutputStream#writeUTF (java.lang.String)
979   */
980  public void writeUTF(String data) throws IOException
981  {
982    dataOutput.writeUTF(data);
983  }
984
985
986  /**
987   * This class allows a class to specify exactly which fields should
988   * be written, and what values should be written for these fields.
989   *
990   * XXX: finish up comments
991   */
992  public abstract static class PutField
993  {
994    public abstract void put (String name, boolean value);
995    public abstract void put (String name, byte value);
996    public abstract void put (String name, char value);
997    public abstract void put (String name, double value);
998    public abstract void put (String name, float value);
999    public abstract void put (String name, int value);
1000    public abstract void put (String name, long value);
1001    public abstract void put (String name, short value);
1002    public abstract void put (String name, Object value);
1003
1004    /**
1005     * @deprecated
1006     */
1007    public abstract void write (ObjectOutput out) throws IOException;
1008  }
1009
1010  public PutField putFields() throws IOException
1011  {
1012    if (currentPutField != null)
1013      return currentPutField;
1014
1015    currentPutField = new PutField()
1016      {
1017        private byte[] prim_field_data
1018          = new byte[currentObjectStreamClass.primFieldSize];
1019        private Object[] objs
1020          = new Object[currentObjectStreamClass.objectFieldCount];
1021
1022        private ObjectStreamField getField (String name)
1023        {
1024          ObjectStreamField field
1025            = currentObjectStreamClass.getField(name);
1026
1027          if (field == null)
1028            throw new IllegalArgumentException("no such serializable field " + name);
1029
1030          return field;
1031        }
1032
1033        public void put(String name, boolean value)
1034        {
1035          ObjectStreamField field = getField(name);
1036
1037          checkType(field, 'Z');
1038          prim_field_data[field.getOffset ()] = (byte)(value ? 1 : 0);
1039        }
1040
1041        public void put(String name, byte value)
1042        {
1043          ObjectStreamField field = getField(name);
1044
1045          checkType(field, 'B');
1046          prim_field_data[field.getOffset()] = value;
1047        }
1048
1049        public void put(String name, char value)
1050        {
1051          ObjectStreamField field = getField(name);
1052
1053          checkType(field, 'C');
1054          int off = field.getOffset();
1055          prim_field_data[off++] = (byte)(value >>> 8);
1056          prim_field_data[off] = (byte)value;
1057        }
1058
1059        public void put(String name, double value)
1060        {
1061          ObjectStreamField field = getField (name);
1062
1063          checkType(field, 'D');
1064          int off = field.getOffset();
1065          long l_value = Double.doubleToLongBits (value);
1066          prim_field_data[off++] = (byte)(l_value >>> 52);
1067          prim_field_data[off++] = (byte)(l_value >>> 48);
1068          prim_field_data[off++] = (byte)(l_value >>> 40);
1069          prim_field_data[off++] = (byte)(l_value >>> 32);
1070          prim_field_data[off++] = (byte)(l_value >>> 24);
1071          prim_field_data[off++] = (byte)(l_value >>> 16);
1072          prim_field_data[off++] = (byte)(l_value >>> 8);
1073          prim_field_data[off] = (byte)l_value;
1074        }
1075
1076        public void put(String name, float value)
1077        {
1078          ObjectStreamField field = getField(name);
1079
1080          checkType(field, 'F');
1081          int off = field.getOffset();
1082          int i_value = Float.floatToIntBits(value);
1083          prim_field_data[off++] = (byte)(i_value >>> 24);
1084          prim_field_data[off++] = (byte)(i_value >>> 16);
1085          prim_field_data[off++] = (byte)(i_value >>> 8);
1086          prim_field_data[off] = (byte)i_value;
1087        }
1088
1089        public void put(String name, int value)
1090        {
1091          ObjectStreamField field = getField(name);
1092          checkType(field, 'I');
1093          int off = field.getOffset();
1094          prim_field_data[off++] = (byte)(value >>> 24);
1095          prim_field_data[off++] = (byte)(value >>> 16);
1096          prim_field_data[off++] = (byte)(value >>> 8);
1097          prim_field_data[off] = (byte)value;
1098        }
1099
1100        public void put(String name, long value)
1101        {
1102          ObjectStreamField field = getField(name);
1103          checkType(field, 'J');
1104          int off = field.getOffset();
1105          prim_field_data[off++] = (byte)(value >>> 52);
1106          prim_field_data[off++] = (byte)(value >>> 48);
1107          prim_field_data[off++] = (byte)(value >>> 40);
1108          prim_field_data[off++] = (byte)(value >>> 32);
1109          prim_field_data[off++] = (byte)(value >>> 24);
1110          prim_field_data[off++] = (byte)(value >>> 16);
1111          prim_field_data[off++] = (byte)(value >>> 8);
1112          prim_field_data[off] = (byte)value;
1113        }
1114
1115        public void put(String name, short value)
1116        {
1117          ObjectStreamField field = getField(name);
1118          checkType(field, 'S');
1119          int off = field.getOffset();
1120          prim_field_data[off++] = (byte)(value >>> 8);
1121          prim_field_data[off] = (byte)value;
1122        }
1123
1124        public void put(String name, Object value)
1125        {
1126          ObjectStreamField field = getField(name);
1127
1128          if (value != null &&
1129              ! field.getType().isAssignableFrom(value.getClass ()))
1130            throw new IllegalArgumentException("Class " + value.getClass() +
1131                                               " cannot be cast to " + field.getType());
1132          objs[field.getOffset()] = value;
1133        }
1134
1135        public void write(ObjectOutput out) throws IOException
1136        {
1137          // Apparently Block data is not used with PutField as per
1138          // empirical evidence against JDK 1.2.  Also see Mauve test
1139          // java.io.ObjectInputOutput.Test.GetPutField.
1140          boolean oldmode = setBlockDataMode(false);
1141          out.write(prim_field_data);
1142          for (int i = 0; i < objs.length; ++ i)
1143            out.writeObject(objs[i]);
1144          setBlockDataMode(oldmode);
1145        }
1146
1147        private void checkType(ObjectStreamField field, char type)
1148          throws IllegalArgumentException
1149        {
1150          if (TypeSignature.getEncodingOfClass(field.getType()).charAt(0)
1151              != type)
1152            throw new IllegalArgumentException();
1153        }
1154      };
1155    // end PutFieldImpl
1156
1157    return currentPutField;
1158  }
1159
1160
1161  public void writeFields() throws IOException
1162  {
1163    if (currentPutField == null)
1164      throw new NotActiveException("writeFields can only be called after putFields has been called");
1165
1166    markFieldsWritten();
1167    currentPutField.write(this);
1168  }
1169
1170
1171  // write out the block-data buffer, picking the correct header
1172  // depending on the size of the buffer
1173  private void writeBlockDataHeader(int size) throws IOException
1174  {
1175    if (size < 256)
1176      {
1177        realOutput.writeByte(TC_BLOCKDATA);
1178        realOutput.write(size);
1179      }
1180    else
1181      {
1182        realOutput.writeByte(TC_BLOCKDATALONG);
1183        realOutput.writeInt(size);
1184      }
1185  }
1186
1187
1188  // lookup the handle for OBJ, return null if OBJ doesn't have a
1189  // handle yet
1190  private int findHandle(Object obj)
1191  {
1192    return OIDLookupTable.get(obj);
1193  }
1194
1195
1196  // assigns the next availible handle to OBJ
1197  private int assignNewHandle(Object obj)
1198  {
1199    OIDLookupTable.put(obj, nextOID);
1200    return nextOID++;
1201  }
1202
1203
1204  // resets mapping from objects to handles
1205  private void clearHandles()
1206  {
1207    nextOID = baseWireHandle;
1208    OIDLookupTable.clear();
1209  }
1210
1211
1212  // write out array size followed by each element of the array
1213  private void writeArraySizeAndElements(Object array, Class clazz)
1214    throws IOException
1215  {
1216    int length = Array.getLength(array);
1217
1218    if (clazz.isPrimitive())
1219      {
1220        if (clazz == Boolean.TYPE)
1221          {
1222            boolean[] cast_array = (boolean[])array;
1223            realOutput.writeInt (length);
1224            for (int i = 0; i < length; i++)
1225              realOutput.writeBoolean(cast_array[i]);
1226            return;
1227          }
1228        if (clazz == Byte.TYPE)
1229          {
1230            byte[] cast_array = (byte[])array;
1231            realOutput.writeInt(length);
1232            realOutput.write(cast_array, 0, length);
1233            return;
1234          }
1235        if (clazz == Character.TYPE)
1236          {
1237            char[] cast_array = (char[])array;
1238            realOutput.writeInt(length);
1239            for (int i = 0; i < length; i++)
1240              realOutput.writeChar(cast_array[i]);
1241            return;
1242          }
1243        if (clazz == Double.TYPE)
1244          {
1245            double[] cast_array = (double[])array;
1246            realOutput.writeInt(length);
1247            for (int i = 0; i < length; i++)
1248              realOutput.writeDouble(cast_array[i]);
1249            return;
1250          }
1251        if (clazz == Float.TYPE)
1252          {
1253            float[] cast_array = (float[])array;
1254            realOutput.writeInt(length);
1255            for (int i = 0; i < length; i++)
1256              realOutput.writeFloat(cast_array[i]);
1257            return;
1258          }
1259        if (clazz == Integer.TYPE)
1260          {
1261            int[] cast_array = (int[])array;
1262            realOutput.writeInt(length);
1263            for (int i = 0; i < length; i++)
1264              realOutput.writeInt(cast_array[i]);
1265            return;
1266          }
1267        if (clazz == Long.TYPE)
1268          {
1269            long[] cast_array = (long[])array;
1270            realOutput.writeInt (length);
1271            for (int i = 0; i < length; i++)
1272              realOutput.writeLong(cast_array[i]);
1273            return;
1274          }
1275        if (clazz == Short.TYPE)
1276          {
1277            short[] cast_array = (short[])array;
1278            realOutput.writeInt (length);
1279            for (int i = 0; i < length; i++)
1280              realOutput.writeShort(cast_array[i]);
1281            return;
1282          }
1283      }
1284    else
1285      {
1286        Object[] cast_array = (Object[])array;
1287        realOutput.writeInt(length);
1288        for (int i = 0; i < length; i++)
1289          writeObject(cast_array[i]);
1290      }
1291  }
1292
1293
1294/* GCJ LOCAL */
1295  // writes out FIELDS of OBJECT for the specified ObjectStreamClass.
1296  // FIELDS are already supposed already to be in canonical order, but
1297  // under some circumstances (to do with Proxies) this isn't the
1298  // case, so we call ensureFieldsSet().
1299  private void writeFields(Object obj, ObjectStreamClass osc)
1300    throws IOException
1301  {
1302    osc.ensureFieldsSet(osc.forClass());
1303/* END GCJ LOCAL */
1304
1305    ObjectStreamField[] fields = osc.fields;
1306    boolean oldmode = setBlockDataMode(false);
1307
1308    try
1309      {
1310        writeFields(obj,fields);
1311      }
1312    catch (IllegalArgumentException _)
1313      {
1314        InvalidClassException e = new InvalidClassException
1315          ("writing fields of class " + osc.forClass().getName());
1316        e.initCause(_);
1317        throw e;
1318      }
1319    catch (IOException e)
1320      {
1321        throw e;
1322      }
1323    catch (Exception _)
1324      {
1325        IOException e = new IOException("Unexpected exception " + _);
1326        e.initCause(_);
1327        throw(e);
1328      }
1329
1330    setBlockDataMode(oldmode);
1331  }
1332
1333
1334  /**
1335   * Helper function for writeFields(Object,ObjectStreamClass): write
1336   * fields from given fields array.  Pass exception on.
1337   *
1338   * @param obj the object to be written
1339   *
1340   * @param fields the fields of obj to be written.
1341   */
1342  private void writeFields(Object obj, ObjectStreamField[] fields)
1343    throws
1344      IllegalArgumentException, IllegalAccessException, IOException
1345  {
1346    for (int i = 0; i < fields.length; i++)
1347      {
1348        ObjectStreamField osf = fields[i];
1349        Field field = osf.field;
1350
1351        if (DEBUG && dump)
1352          dumpElementln ("WRITE FIELD: " + osf.getName() + " type=" + osf.getType());
1353
1354        switch (osf.getTypeCode())
1355          {
1356          case 'Z': realOutput.writeBoolean(field.getBoolean(obj)); break;
1357          case 'B': realOutput.writeByte   (field.getByte   (obj)); break;
1358          case 'S': realOutput.writeShort  (field.getShort  (obj)); break;
1359          case 'C': realOutput.writeChar   (field.getChar   (obj)); break;
1360          case 'I': realOutput.writeInt    (field.getInt    (obj)); break;
1361          case 'F': realOutput.writeFloat  (field.getFloat  (obj)); break;
1362          case 'J': realOutput.writeLong   (field.getLong   (obj)); break;
1363          case 'D': realOutput.writeDouble (field.getDouble (obj)); break;
1364          case 'L':
1365          case '[':            writeObject (field.get       (obj)); break;
1366          default:
1367            throw new IOException("Unexpected type code " + osf.getTypeCode());
1368          }
1369      }
1370  }
1371
1372
1373  // Toggles writing primitive data to block-data buffer.
1374  // Package-private to avoid a trampoline constructor.
1375  boolean setBlockDataMode(boolean on) throws IOException
1376  {
1377    if (on == writeDataAsBlocks)
1378      return on;
1379
1380    drain();
1381    boolean oldmode = writeDataAsBlocks;
1382    writeDataAsBlocks = on;
1383
1384    if (on)
1385      dataOutput = blockDataOutput;
1386    else
1387      dataOutput = realOutput;
1388
1389    return oldmode;
1390  }
1391
1392
1393  private void callWriteMethod(Object obj, ObjectStreamClass osc)
1394    throws IOException
1395  {
1396    currentPutField = null;
1397    try
1398      {
1399        Object args[] = {this};
1400        osc.writeObjectMethod.invoke(obj, args);
1401      }
1402    catch (InvocationTargetException x)
1403      {
1404        /* Rethrow if possible. */
1405        Throwable exception = x.getTargetException();
1406        if (exception instanceof RuntimeException)
1407          throw (RuntimeException) exception;
1408        if (exception instanceof IOException)
1409          throw (IOException) exception;
1410
1411        IOException ioe
1412          = new IOException("Exception thrown from writeObject() on " +
1413                            osc.forClass().getName() + ": " +
1414                            exception.getClass().getName());
1415        ioe.initCause(exception);
1416        throw ioe;
1417      }
1418    catch (Exception x)
1419      {
1420        IOException ioe
1421          = new IOException("Failure invoking writeObject() on " +
1422                            osc.forClass().getName() + ": " +
1423                            x.getClass().getName());
1424        ioe.initCause(x);
1425        throw ioe;
1426      }
1427  }
1428
1429  private void dumpElementln (String msg, Object obj)
1430  {
1431    try
1432      {
1433        for (int i = 0; i < depth; i++)
1434          System.out.print (" ");
1435        System.out.print (Thread.currentThread() + ": ");
1436        System.out.print (msg);
1437        if (java.lang.reflect.Proxy.isProxyClass(obj.getClass()))
1438          System.out.print (obj.getClass());
1439        else
1440          System.out.print (obj);
1441      }
1442    catch (Exception _)
1443      {
1444      }
1445    finally
1446      {
1447        System.out.println ();
1448      }
1449  }
1450
1451  private void dumpElementln (String msg)
1452  {
1453    for (int i = 0; i < depth; i++)
1454      System.out.print (" ");
1455    System.out.print (Thread.currentThread() + ": ");
1456    System.out.println(msg);
1457  }
1458
1459  // this value comes from 1.2 spec, but is used in 1.1 as well
1460  private static final int BUFFER_SIZE = 1024;
1461
1462  private static int defaultProtocolVersion = PROTOCOL_VERSION_2;
1463
1464  private DataOutputStream dataOutput;
1465  private boolean writeDataAsBlocks;
1466  private DataOutputStream realOutput;
1467  private DataOutputStream blockDataOutput;
1468  private byte[] blockData;
1469  private int blockDataCount;
1470  private Object currentObject;
1471  // Package-private to avoid a trampoline.
1472  ObjectStreamClass currentObjectStreamClass;
1473  private PutField currentPutField;
1474  private boolean fieldsAlreadyWritten;
1475  private boolean replacementEnabled;
1476  private boolean isSerializing;
1477  private int nextOID;
1478  private ObjectIdentityMap2Int OIDLookupTable;
1479  private int protocolVersion;
1480  private boolean useSubclassMethod;
1481  private SetAccessibleAction setAccessible = new SetAccessibleAction();
1482
1483  // The nesting depth for debugging output
1484  private int depth = 0;
1485
1486  // Set if we're generating debugging dumps
1487  private boolean dump = false;
1488
1489  private static final boolean DEBUG = false;
1490}