diff options
Diffstat (limited to 'libjava/java/io/ObjectOutputStream.java')
-rw-r--r-- | libjava/java/io/ObjectOutputStream.java | 1335 |
1 files changed, 1335 insertions, 0 deletions
diff --git a/libjava/java/io/ObjectOutputStream.java b/libjava/java/io/ObjectOutputStream.java new file mode 100644 index 0000000..a98b55ba --- /dev/null +++ b/libjava/java/io/ObjectOutputStream.java @@ -0,0 +1,1335 @@ +/* ObjectOutputStream.java -- Class used to write serialized objects + Copyright (C) 1998, 1999 Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA +02111-1307 USA. + +As a special exception, if you link this library with other files to +produce an executable, this library does not by itself cause the +resulting executable to be covered by the GNU General Public License. +This exception does not however invalidate any other reasons why the +executable file might be covered by the GNU General Public License. */ + + +package java.io; + +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Hashtable; + +import gnu.java.io.ObjectIdentityWrapper; +import gnu.java.lang.reflect.TypeSignature; + +/** + An <code>ObjectOutputStream</code> can be used to write objects + as well as primitive data in a platform-independent manner to an + <code>OutputStream</code>. + + The data produced by an <code>ObjectOutputStream</code> can be read + and reconstituted by an <code>ObjectInputStream</code>. + + <code>writeObject (Object)</code> is used to write Objects, the + <code>write<type></code> methods are used to write primitive + data (as in <code>DataOutputStream</code>). Strings can be written + as objects or as primitive data. + + Not all objects can be written out using an + <code>ObjectOutputStream</code>. Only those objects that are an + instance of <code>java.io.Serializable</code> can be written. + + Using default serialization, information about the class of an + object is written, all of the non-transient, non-static fields of + the object are written, if any of these fields are objects, the are + written out in the same manner. + + An object is only written out the first time it is encountered. If + the object is encountered later, a reference to it is written to + the underlying stream. Thus writing circular object graphs + does not present a problem, nor are relationships between objects + in a graph lost. + + Example usage: + <pre> + Hashtable map = new Hashtable (); + map.put ("one", new Integer (1)); + map.put ("two", new Integer (2)); + + ObjectOutputStream oos = + new ObjectOutputStream (new FileOutputStream ("numbers")); + oos.writeObject (map); + oos.close (); + + ObjectInputStream ois = + new ObjectInputStream (new FileInputStream ("numbers")); + Hashtable newmap = (Hashtable)ois.readObject (); + + System.out.println (newmap); + </pre> + + The default serialization can be overriden in two ways. + + By defining a method <code>private void + writeObject (ObjectOutputStream)</code>, a class can dictate exactly + how information about itself is written. + <code>defaultWriteObject ()</code> may be called from this method to + carry out default serialization. This method is not + responsible for dealing with fields of super-classes or subclasses. + + By implementing <code>java.io.Externalizable</code>. This gives + the class complete control over the way it is written to the + stream. If this approach is used the burden of writing superclass + and subclass data is transfered to the class implementing + <code>java.io.Externalizable</code>. + + @see java.io.DataOutputStream + @see java.io.Externalizable + @see java.io.ObjectInputStream + @see java.io.Serializable + @see XXX: java serialization spec +*/ +public class ObjectOutputStream extends OutputStream + implements ObjectOutput, ObjectStreamConstants +{ + /** + Creates a new <code>ObjectOutputStream</code> that will do all of + its writing onto <code>out</code>. This method also initializes + the stream by writing the header information (stream magic number + and stream version). + + @exception IOException Writing stream header to underlying + stream cannot be completed. + + @see writeStreamHeader () + */ + public ObjectOutputStream (OutputStream out) throws IOException + { + realOutput = new DataOutputStream (out); + blockData = new byte[ BUFFER_SIZE ]; + blockDataCount = 0; + blockDataOutput = new DataOutputStream (this); + setBlockDataMode (true); + replacementEnabled = false; + isSerializing = false; + nextOID = baseWireHandle; + OIDLookupTable = new Hashtable (); + protocolVersion = defaultProtocolVersion; + useSubclassMethod = false; + writeStreamHeader (); + } + + + /** + Writes a representation of <code>obj</code> to the underlying + output stream by writing out information about its class, then + writing out each of the objects non-transient, non-static + fields. If any of these fields are other objects, + the are written out in the same manner. + + This method can be overriden by a class by implementing + <code>private void writeObject (ObjectOutputStream)</code>. + + If an exception is thrown from this method, the stream is left in + an undefined state. + + @exception NotSerializableException An attempt was made to + serialize an <code>Object</code> that is not serializable. + + @exception IOException Exception from underlying + <code>OutputStream</code>. + */ + public final void writeObject (Object obj) throws IOException + { + if (useSubclassMethod) + { + writeObjectOverride (obj); + return; + } + + boolean was_serializing = isSerializing; + + if (! was_serializing) + setBlockDataMode (false); + + try + { + isSerializing = true; + boolean replaceDone = false; + + drain (); + + while (true) + { + if (obj == null) + { + realOutput.writeByte (TC_NULL); + break; + } + + Integer handle = findHandle (obj); + if (handle != null) + { + realOutput.writeByte (TC_REFERENCE); + realOutput.writeInt (handle.intValue ()); + break; + } + + if (obj instanceof Class) + { + realOutput.writeByte (TC_CLASS); + writeObject (ObjectStreamClass.lookup ((Class)obj)); + assignNewHandle (obj); + break; + } + + if (obj instanceof ObjectStreamClass) + { + ObjectStreamClass osc = (ObjectStreamClass)obj; + realOutput.writeByte (TC_CLASSDESC); + realOutput.writeUTF (osc.getName ()); + realOutput.writeLong (osc.getSerialVersionUID ()); + assignNewHandle (obj); + + int flags = osc.getFlags (); + + if (protocolVersion == PROTOCOL_VERSION_2 + && osc.isExternalizable ()) + flags |= SC_BLOCK_DATA; + + realOutput.writeByte (flags); + + ObjectStreamField[] fields = osc.fields; + realOutput.writeShort (fields.length); + + ObjectStreamField field; + for (int i=0; i < fields.length; i++) + { + field = fields[i]; + realOutput.writeByte (field.getTypeCode ()); + realOutput.writeUTF (field.getName ()); + + if (! field.isPrimitive ()) + writeObject (field.getTypeString ()); + } + + setBlockDataMode (true); + annotateClass (osc.forClass ()); + setBlockDataMode (false); + realOutput.writeByte (TC_ENDBLOCKDATA); + + if (osc.isSerializable ()) + writeObject (osc.getSuper ()); + else + writeObject (null); + break; + } + + + Object replacedObject = null; + + if ((replacementEnabled || obj instanceof Replaceable) + && ! replaceDone) + { + replacedObject = obj; + + if (obj instanceof Replaceable) + obj = ((Replaceable)obj).writeReplace (); + + if (replacementEnabled) + obj = replaceObject (obj); + + replaceDone = true; + continue; + } + + if (obj instanceof String) + { + realOutput.writeByte (TC_STRING); + assignNewHandle (obj); + realOutput.writeUTF ((String)obj); + break; + } + + Class clazz = obj.getClass (); + ObjectStreamClass osc = ObjectStreamClass.lookup (clazz); + if (osc == null) + throw new NotSerializableException ("The class " + + clazz.getName () + + " is not Serializable"); + + if (clazz.isArray ()) + { + realOutput.writeByte (TC_ARRAY); + writeObject (osc); + assignNewHandle (obj); + writeArraySizeAndElements (obj, clazz); + break; + } + + realOutput.writeByte (TC_OBJECT); + writeObject (osc); + + if (replaceDone) + assignNewHandle (replacedObject); + else + assignNewHandle (obj); + + if (obj instanceof Externalizable) + { + if (protocolVersion == PROTOCOL_VERSION_2) + setBlockDataMode (true); + + ((Externalizable)obj).writeExternal (this); + + if (protocolVersion == PROTOCOL_VERSION_2) + { + setBlockDataMode (false); + drain (); + } + + break; + } + + if (obj instanceof Serializable) + { + currentObject = obj; + ObjectStreamClass[] hierarchy = + ObjectStreamClass.getObjectStreamClasses (clazz); + + boolean has_write; + for (int i=0; i < hierarchy.length; i++) + { + currentObjectStreamClass = hierarchy[i]; + + has_write = currentObjectStreamClass.hasWriteMethod (); + writeFields (obj, currentObjectStreamClass.fields, + has_write); + + fieldsAlreadyWritten = false; + + if (has_write) + { + drain (); + realOutput.writeByte (TC_ENDBLOCKDATA); + } + } + + currentObject = null; + currentObjectStreamClass = null; + currentPutField = null; + break; + } + + throw new NotSerializableException ("Instances of the class " + + clazz.getName () + + " are not Serializable"); + } // end pseudo-loop + } + catch (IOException e) + { + realOutput.writeByte (TC_EXCEPTION); + reset (true); + + try + { + writeObject (e); + } + catch (IOException ioe) + { + throw new StreamCorruptedException ("Exception " + ioe + " thrown while exception was being written to stream."); + } + + reset (true); + } + finally + { + isSerializing = was_serializing; + + if (! was_serializing) + setBlockDataMode (true); + } + } + + + /** + Writes the current objects non-transient, non-static fields from + the current class to the underlying output stream. + + This method is intended to be called from within a object's + <code>private void writeObject (ObjectOutputStream)</code> + method. + + @exception NotActiveException This method was called from a + context other than from the current object's and current class's + <code>private void writeObject (ObjectOutputStream)</code> + method. + + @exception IOException Exception from underlying + <code>OutputStream</code>. + */ + public void defaultWriteObject () + throws IOException, NotActiveException + { + markFieldsWritten (); + writeFields (currentObject, currentObjectStreamClass.fields, false); + } + + + private void markFieldsWritten () throws IOException + { + if (currentObject == null || currentObjectStreamClass == null) + throw new NotActiveException ("defaultWriteObject called by non-active class and/or object"); + + if (fieldsAlreadyWritten) + throw new IOException ("Only one of putFields and defalutWriteObject may be called, and it may only be called once"); + + fieldsAlreadyWritten = true; + } + + + /** + Resets stream to state equivalent to the state just after it was + constructed. + + Causes all objects previously written to the stream to be + forgotten. A notification of this reset is also written to the + underlying stream. + + @exception IOException Exception from underlying + <code>OutputStream</code> or reset called while serialization is + in progress. + */ + public void reset () throws IOException + { + reset (false); + } + + + private void reset (boolean internal) throws IOException + { + if (!internal) + { + if (isSerializing) + throw new IOException ("Reset called while serialization in progress"); + + realOutput.writeByte (TC_RESET); + } + + clearHandles (); + } + + + /** + Informs this <code>ObjectOutputStream</code> to write data + according to the specified protocol. There are currently two + different protocols, specified by <code>PROTOCOL_VERSION_1</code> + and <code>PROTOCOL_VERSION_2</code>. This implementation writes + data using <code>PROTOCOL_VERSION_1</code> by default, as is done + by the JDK 1.1. + + A non-portable method, <code>setDefaultProtocolVersion (int + version)</code> is provided to change the default protocol + version. + + For an explination of the differences beween the two protocols + see XXX: the Java ObjectSerialization Specification. + + @exception IOException if <code>version</code> is not a valid + protocol + + @see setDefaultProtocolVersion (int) + */ + public void useProtocolVersion (int version) throws IOException + { + if (version != PROTOCOL_VERSION_1 && version != PROTOCOL_VERSION_2) + throw new IOException ("Invalid protocol version requested."); + + protocolVersion = version; + } + + + /** + <em>GNU $classpath specific</em> + + Changes the default stream protocol used by all + <code>ObjectOutputStream</code>s. There are currently two + different protocols, specified by <code>PROTOCOL_VERSION_1</code> + and <code>PROTOCOL_VERSION_2</code>. The default default is + <code>PROTOCOL_VERSION_1</code>. + + @exception IOException if <code>version</code> is not a valid + protocol + + @see useProtocolVersion (int) + */ + public static void setDefaultProtocolVersion (int version) + throws IOException + { + if (version != PROTOCOL_VERSION_1 && version != PROTOCOL_VERSION_2) + throw new IOException ("Invalid protocol version requested."); + + defaultProtocolVersion = version; + } + + + /** + An empty hook that allows subclasses to write extra information + about classes to the stream. This method is called the first + time each class is seen, and after all of the standard + information about the class has been written. + + @exception IOException Exception from underlying + <code>OutputStream</code>. + + @see java.io.ObjectInputStream#resolveClass (java.io.ObjectStreamClass) + */ + protected void annotateClass (Class cl) throws IOException + {} + + + /** + Allows subclasses to replace objects that are written to the + stream with other objects to be written in their place. This + method is called the first time each object is encountered + (modulo reseting of the stream). + + This method must be enabled before it will be called in the + serialization process. + + @exception IOException Exception from underlying + <code>OutputStream</code>. + + @see enableReplaceObject (boolean) + */ + protected Object replaceObject (Object obj) throws IOException + { + return obj; + } + + + /** + If <code>enable</code> is <code>true</code> and this object is + trusted, then <code>replaceObject (Object)</code> will be called + in subsequent calls to <code>writeObject (Object)</code>. + Otherwise, <code>replaceObject (Object)</code> will not be called. + + @exception SecurityException This class is not trusted. + */ + protected boolean enableReplaceObject (boolean enable) + throws SecurityException + { + if (enable) + if (getClass ().getClassLoader () != null) + throw new SecurityException ("Untrusted ObjectOutputStream subclass attempted to enable object replacement"); + + boolean old_val = replacementEnabled; + replacementEnabled = enable; + return old_val; + } + + + /** + Writes stream magic and stream version information to the + underlying stream. + + @exception IOException Exception from underlying + <code>OutputStream</code>. + */ + protected void writeStreamHeader () throws IOException + { + realOutput.writeShort (STREAM_MAGIC); + realOutput.writeShort (STREAM_VERSION); + } + + + + /** + Protected constructor that allows subclasses to override + serialization. This constructor should be called by subclasses + that wish to override <code>writeObject (Object)</code>. This + method does a security check <i>NOTE: currently not + implemented</i>, then sets a flag that informs + <code>writeObject (Object)</code> to call the subclasses + <code>writeObjectOverride (Object)</code> method. + + @see writeObjectOverride (Object) + */ + protected ObjectOutputStream () throws IOException, SecurityException + { + SecurityManager sec_man = System.getSecurityManager (); + if (sec_man != null) + sec_man.checkPermission (SUBCLASS_IMPLEMENTATION_PERMISSION); + useSubclassMethod = true; + } + + + /** + This method allows subclasses to override the default + serialization mechanism provided by + <code>ObjectOutputStream</code>. To make this method be used for + writing objects, subclasses must invoke the 0-argument + constructor on this class from there constructor. + + @see ObjectOutputStream () + + @exception NotActiveException Subclass has arranged for this + method to be called, but did not implement this method. + */ + protected void writeObjectOverride (Object obj) throws NotActiveException, + IOException + { + throw new NotActiveException ("Subclass of ObjectOutputStream must implement writeObjectOverride"); + } + + + /** + @see java.io.DataOutputStream#write (int) + */ + public void write (int data) throws IOException + { + if (writeDataAsBlocks) + { + if (blockDataCount == BUFFER_SIZE) + drain (); + + blockData[ blockDataCount++ ] = (byte)data; + } + else + realOutput.write (data); + } + + + /** + @see java.io.DataOutputStream#write (byte[]) + */ + public void write (byte b[]) throws IOException + { + write (b, 0, b.length); + } + + + /** + @see java.io.DataOutputStream#write (byte[],int,int) + */ + public void write (byte b[], int off, int len) throws IOException + { + if (writeDataAsBlocks) + { + if (len < 0) + throw new IndexOutOfBoundsException (); + + if (blockDataCount + len < BUFFER_SIZE) + { + System.arraycopy (b, off, blockData, blockDataCount, len); + blockDataCount += len; + } + else + { + drain (); + writeBlockDataHeader (len); + realOutput.write (b, off, len); + } + } + else + realOutput.write (b, off, len); + } + + + /** + @see java.io.DataOutputStream#flush () + */ + public void flush () throws IOException + { + drain (); + realOutput.flush (); + } + + + /** + Causes the block-data buffer to be written to the underlying + stream, but does not flush underlying stream. + + @exception IOException Exception from underlying + <code>OutputStream</code>. + */ + protected void drain () throws IOException + { + if (blockDataCount == 0) + return; + + writeBlockDataHeader (blockDataCount); + realOutput.write (blockData, 0, blockDataCount); + blockDataCount = 0; + } + + + /** + @see java.io.DataOutputStream#close () + */ + public void close () throws IOException + { + drain (); + realOutput.close (); + } + + + /** + @see java.io.DataOutputStream#writeBoolean (boolean) + */ + public void writeBoolean (boolean data) throws IOException + { + dataOutput.writeBoolean (data); + } + + + /** + @see java.io.DataOutputStream#writeByte (int) + */ + public void writeByte (int data) throws IOException + { + dataOutput.writeByte (data); + } + + + /** + @see java.io.DataOutputStream#writeShort (int) + */ + public void writeShort (int data) throws IOException + { + dataOutput.writeShort (data); + } + + + /** + @see java.io.DataOutputStream#writeChar (int) + */ + public void writeChar (int data) throws IOException + { + dataOutput.writeChar (data); + } + + + /** + @see java.io.DataOutputStream#writeInt (int) + */ + public void writeInt (int data) throws IOException + { + dataOutput.writeInt (data); + } + + + /** + @see java.io.DataOutputStream#writeLong (long) + */ + public void writeLong (long data) throws IOException + { + dataOutput.writeLong (data); + } + + + /** + @see java.io.DataOutputStream#writeFloat (float) + */ + public void writeFloat (float data) throws IOException + { + dataOutput.writeFloat (data); + } + + + /** + @see java.io.DataOutputStream#writeDouble (double) + */ + public void writeDouble (double data) throws IOException + { + dataOutput.writeDouble (data); + } + + + /** + @see java.io.DataOutputStream#writeBytes (java.lang.String) + */ + public void writeBytes (String data) throws IOException + { + dataOutput.writeBytes (data); + } + + + /** + @see java.io.DataOutputStream#writeChars (java.lang.String) + */ + public void writeChars (String data) throws IOException + { + dataOutput.writeChars (data); + } + + + /** + @see java.io.DataOutputStream#writeUTF (java.lang.String) + */ + public void writeUTF (String data) throws IOException + { + dataOutput.writeUTF (data); + } + + + /** + This class allows a class to specify exactly which fields should + be written, and what values should be written for these fields. + + XXX: finish up comments + */ + public static abstract class PutField + { + public abstract void put (String name, boolean value) + throws IOException, IllegalArgumentException; + public abstract void put (String name, byte value) + throws IOException, IllegalArgumentException; + public abstract void put (String name, char value) + throws IOException, IllegalArgumentException; + public abstract void put (String name, double value) + throws IOException, IllegalArgumentException; + public abstract void put (String name, float value) + throws IOException, IllegalArgumentException; + public abstract void put (String name, int value) + throws IOException, IllegalArgumentException; + public abstract void put (String name, long value) + throws IOException, IllegalArgumentException; + public abstract void put (String name, short value) + throws IOException, IllegalArgumentException; + public abstract void put (String name, Object value) + throws IOException, IllegalArgumentException; + public abstract void write (ObjectOutput out) throws IOException; + } + + + public PutField putFields () throws IOException + { + markFieldsWritten (); + + currentPutField = new PutField () + { + private byte[] prim_field_data + = new byte[currentObjectStreamClass.primFieldSize]; + private Object[] objs + = new Object[currentObjectStreamClass.objectFieldCount]; + + public void put (String name, boolean value) + throws IOException, IllegalArgumentException + { + ObjectStreamField field + = currentObjectStreamClass.getField (name); + checkType (field, 'Z'); + prim_field_data[field.getOffset ()] = (byte)(value ? 1 : 0); + } + + public void put (String name, byte value) + throws IOException, IllegalArgumentException + { + ObjectStreamField field + = currentObjectStreamClass.getField (name); + checkType (field, 'B'); + prim_field_data[field.getOffset ()] = value; + } + + public void put (String name, char value) + throws IOException, IllegalArgumentException + { + ObjectStreamField field + = currentObjectStreamClass.getField (name); + checkType (field, 'B'); + int off = field.getOffset (); + prim_field_data[off++] = (byte)(value >>> 8); + prim_field_data[off] = (byte)value; + } + + public void put (String name, double value) + throws IOException, IllegalArgumentException + { + ObjectStreamField field + = currentObjectStreamClass.getField (name); + checkType (field, 'B'); + int off = field.getOffset (); + long l_value = Double.doubleToLongBits (value); + prim_field_data[off++] = (byte)(l_value >>> 52); + prim_field_data[off++] = (byte)(l_value >>> 48); + prim_field_data[off++] = (byte)(l_value >>> 40); + prim_field_data[off++] = (byte)(l_value >>> 32); + prim_field_data[off++] = (byte)(l_value >>> 24); + prim_field_data[off++] = (byte)(l_value >>> 16); + prim_field_data[off++] = (byte)(l_value >>> 8); + prim_field_data[off] = (byte)l_value; + } + + public void put (String name, float value) + throws IOException, IllegalArgumentException + { + ObjectStreamField field + = currentObjectStreamClass.getField (name); + checkType (field, 'B'); + int off = field.getOffset (); + int i_value = Float.floatToIntBits (value); + prim_field_data[off++] = (byte)(i_value >>> 24); + prim_field_data[off++] = (byte)(i_value >>> 16); + prim_field_data[off++] = (byte)(i_value >>> 8); + prim_field_data[off] = (byte)i_value; + } + + public void put (String name, int value) + throws IOException, IllegalArgumentException + { + ObjectStreamField field + = currentObjectStreamClass.getField (name); + checkType (field, 'B'); + int off = field.getOffset (); + prim_field_data[off++] = (byte)(value >>> 24); + prim_field_data[off++] = (byte)(value >>> 16); + prim_field_data[off++] = (byte)(value >>> 8); + prim_field_data[off] = (byte)value; + } + + public void put (String name, long value) + throws IOException, IllegalArgumentException + { + ObjectStreamField field + = currentObjectStreamClass.getField (name); + checkType (field, 'B'); + int off = field.getOffset (); + prim_field_data[off++] = (byte)(value >>> 52); + prim_field_data[off++] = (byte)(value >>> 48); + prim_field_data[off++] = (byte)(value >>> 40); + prim_field_data[off++] = (byte)(value >>> 32); + prim_field_data[off++] = (byte)(value >>> 24); + prim_field_data[off++] = (byte)(value >>> 16); + prim_field_data[off++] = (byte)(value >>> 8); + prim_field_data[off] = (byte)value; + } + + public void put (String name, short value) + throws IOException, IllegalArgumentException + { + ObjectStreamField field + = currentObjectStreamClass.getField (name); + checkType (field, 'B'); + int off = field.getOffset (); + prim_field_data[off++] = (byte)(value >>> 8); + prim_field_data[off] = (byte)value; + } + + public void put (String name, Object value) + throws IOException, IllegalArgumentException + { + ObjectStreamField field + = currentObjectStreamClass.getField (name); + if (! field.getType ().isAssignableFrom (value.getClass ())) + throw new IllegalArgumentException (); + objs[field.getOffset ()] = value; + } + + public void write (ObjectOutput out) throws IOException + { + out.write (prim_field_data); + for (int i = 0; i < objs.length; ++ i) + out.writeObject (objs[i]); + } + + private void checkType (ObjectStreamField field, char type) + throws IllegalArgumentException + { + if (TypeSignature.getEncodingOfClass (field.getType ()).charAt (0) != type) + throw new IllegalArgumentException (); + } + }; + // end PutFieldImpl + + return currentPutField; + } + + + public void writeFields () throws IOException + { + if (currentPutField == null) + throw new NotActiveException ("writeFields can only be called after putFields has been called"); + + currentPutField.write (this); + } + + + // write out the block-data buffer, picking the correct header + // depending on the size of the buffer + private void writeBlockDataHeader (int size) throws IOException + { + if (size < 256) + { + realOutput.writeByte (TC_BLOCKDATA); + realOutput.write (size); + } + else + { + realOutput.writeByte (TC_BLOCKDATALONG); + realOutput.writeInt (size); + } + } + + + // lookup the handle for OBJ, return null if OBJ doesn't have a + // handle yet + private Integer findHandle (Object obj) + { + return (Integer)OIDLookupTable.get (new ObjectIdentityWrapper (obj)); + } + + + // assigns the next availible handle to OBJ + private int assignNewHandle (Object obj) + { + OIDLookupTable.put (new ObjectIdentityWrapper (obj), + new Integer (nextOID)); + return nextOID++; + } + + + // resets mapping from objects to handles + private void clearHandles () + { + nextOID = baseWireHandle; + OIDLookupTable.clear (); + } + + + // write out array size followed by each element of the array + private void writeArraySizeAndElements (Object array, Class clazz) + throws IOException + { + int length = Array.getLength (array); + + if (clazz.isPrimitive ()) + { + if (clazz == Boolean.TYPE) + { + boolean[] cast_array = (boolean[])array; + realOutput.writeInt (length); + for (int i=0; i < length; i++) + realOutput.writeBoolean (cast_array[i]); + return; + } + if (clazz == Byte.TYPE) + { + byte[] cast_array = (byte[])array; + realOutput.writeInt (length); + for (int i=0; i < length; i++) + realOutput.writeByte (cast_array[i]); + return; + } + if (clazz == Character.TYPE) + { + char[] cast_array = (char[])array; + realOutput.writeInt (length); + for (int i=0; i < length; i++) + realOutput.writeChar (cast_array[i]); + return; + } + if (clazz == Double.TYPE) + { + double[] cast_array = (double[])array; + realOutput.writeInt (length); + for (int i=0; i < length; i++) + realOutput.writeDouble (cast_array[i]); + return; + } + if (clazz == Float.TYPE) + { + float[] cast_array = (float[])array; + realOutput.writeInt (length); + for (int i=0; i < length; i++) + realOutput.writeFloat (cast_array[i]); + return; + } + if (clazz == Integer.TYPE) + { + int[] cast_array = (int[])array; + realOutput.writeInt (length); + for (int i=0; i < length; i++) + realOutput.writeInt (cast_array[i]); + return; + } + if (clazz == Long.TYPE) + { + long[] cast_array = (long[])array; + realOutput.writeInt (length); + for (int i=0; i < length; i++) + realOutput.writeLong (cast_array[i]); + return; + } + if (clazz == Short.TYPE) + { + short[] cast_array = (short[])array; + realOutput.writeInt (length); + for (int i=0; i < length; i++) + realOutput.writeShort (cast_array[i]); + return; + } + } + else + { + Object[] cast_array = (Object[])array; + realOutput.writeInt (length); + for (int i=0; i < length; i++) + writeObject (cast_array[i]); + } + } + + + // writes out FIELDS of OBJECT. If CALL_WRITE_METHOD is true, use + // object's writeObject (ObjectOutputStream), otherwise use default + // serialization. FIELDS are already in canonical order. + private void writeFields (Object obj, + ObjectStreamField[] fields, + boolean call_write_method) throws IOException + { + if (call_write_method) + { + setBlockDataMode (true); + callWriteMethod (obj); + setBlockDataMode (false); + return; + } + + String field_name; + Class type; + for (int i=0; i < fields.length; i++) + { + field_name = fields[i].getName (); + type = fields[i].getType (); + + if (type == Boolean.TYPE) + realOutput.writeBoolean (getBooleanField (obj, field_name)); + else if (type == Byte.TYPE) + realOutput.writeByte (getByteField (obj, field_name)); + else if (type == Character.TYPE) + realOutput.writeChar (getCharField (obj, field_name)); + else if (type == Double.TYPE) + realOutput.writeDouble (getDoubleField (obj, field_name)); + else if (type == Float.TYPE) + realOutput.writeFloat (getFloatField (obj, field_name)); + else if (type == Integer.TYPE) + realOutput.writeInt (getIntField (obj, field_name)); + else if (type == Long.TYPE) + realOutput.writeLong (getLongField (obj, field_name)); + else if (type == Short.TYPE) + realOutput.writeShort (getShortField (obj, field_name)); + else + writeObject (getObjectField (obj, field_name, + TypeSignature.getEncodingOfClass (type))); + } + } + + + // Toggles writing primitive data to block-data buffer. + private void setBlockDataMode (boolean on) + { + writeDataAsBlocks = on; + + if (on) + dataOutput = blockDataOutput; + else + dataOutput = realOutput; + } + + + private void callWriteMethod (Object obj) throws IOException + { + try + { + Class classArgs[] = {Class.forName ("java.io.ObjectOutputStream")}; + Class klass = obj.getClass (); + Method m = getMethod (klass, "writeObject", classArgs); + if (m == null) + return; + Object args[] = {this}; + m.invoke (obj, args); + } + catch (Exception _) + { + throw new IOException (); + } + } + + private boolean getBooleanField (Object obj, String field_name) throws IOException + { + try + { + Class klass = obj.getClass (); + Field f = getField (klass, field_name); + boolean b = f.getBoolean (obj); + return b; + } + catch (Exception _) + { + throw new IOException (); + } + } + + private byte getByteField (Object obj, String field_name) throws IOException + { + try + { + Class klass = obj.getClass (); + Field f = getField (klass, field_name); + byte b = f.getByte (obj); + return b; + } + catch (Exception _) + { + throw new IOException (); + } + } + + private char getCharField (Object obj, String field_name) throws IOException + { + try + { + Class klass = obj.getClass (); + Field f = getField (klass, field_name); + char b = f.getChar (obj); + return b; + } + catch (Exception _) + { + throw new IOException (); + } + } + + private double getDoubleField (Object obj, String field_name) throws IOException + { + try + { + Class klass = obj.getClass (); + Field f = getField (klass, field_name); + double b = f.getDouble (obj); + return b; + } + catch (Exception _) + { + throw new IOException (); + } + } + + private float getFloatField (Object obj, String field_name) throws IOException + { + try + { + Class klass = obj.getClass (); + Field f = getField (klass, field_name); + float b = f.getFloat (obj); + return b; + } + catch (Exception _) + { + throw new IOException (); + } + } + + private int getIntField (Object obj, String field_name) throws IOException + { + try + { + Class klass = obj.getClass (); + Field f = getField (klass, field_name); + int b = f.getInt (obj); + return b; + } + catch (Exception _) + { + throw new IOException (); + } + } + + private long getLongField (Object obj, String field_name) throws IOException + { + try + { + Class klass = obj.getClass (); + Field f = getField (klass, field_name); + long b = f.getLong (obj); + return b; + } + catch (Exception _) + { + throw new IOException (); + } + } + + private short getShortField (Object obj, String field_name) throws IOException + { + try + { + Class klass = obj.getClass (); + Field f = getField (klass, field_name); + short b = f.getShort (obj); + return b; + } + catch (Exception _) + { + throw new IOException (); + } + } + + private Object getObjectField (Object obj, String field_name, + String type_code) throws IOException + { + try + { + Class klass = obj.getClass (); + Field f = getField (klass, field_name); + Object o = f.get (obj); + // FIXME: We should check the type_code here + return o; + } + catch (Exception _) + { + throw new IOException (); + } + } + + private static native Field getField (Class klass, String name) + throws java.lang.NoSuchFieldException; + + private static native Method getMethod (Class klass, String name, Class args[]) + throws java.lang.NoSuchMethodException; + + // this value comes from 1.2 spec, but is used in 1.1 as well + private final static int BUFFER_SIZE = 1024; + + private static int defaultProtocolVersion = PROTOCOL_VERSION_1; + + private DataOutputStream dataOutput; + private boolean writeDataAsBlocks; + private DataOutputStream realOutput; + private DataOutputStream blockDataOutput; + private byte[] blockData; + private int blockDataCount; + private Object currentObject; + private ObjectStreamClass currentObjectStreamClass; + private PutField currentPutField; + private boolean fieldsAlreadyWritten; + private boolean replacementEnabled; + private boolean isSerializing; + private int nextOID; + private Hashtable OIDLookupTable; + private int protocolVersion; + private boolean useSubclassMethod; +} |