60b7365f52
* java/io/ObjectInputStream.java (read): AND byte with 0xff to make result unsigned. (read (byte[], int, int)): Only call readNextBlock() if the block buffer would actually be overrun. Increment blockDataPosition. (callReadMethod): Propagate exceptions from invocation target. * java/io/ObjectOutputStream.java (callWriteMethod): Propagate exceptions from invocation target. From-SVN: r39293
1372 lines
36 KiB
Java
1372 lines
36 KiB
Java
/* ObjectOutputStream.java -- Class used to write serialized objects
|
|
Copyright (C) 1998, 1999, 2000 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.lang.reflect.InvocationTargetException;
|
|
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, they 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,
|
|
they 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 Serializable)
|
|
&& ! replaceDone)
|
|
{
|
|
replacedObject = obj;
|
|
|
|
if (obj instanceof Serializable)
|
|
{
|
|
Method m = null;
|
|
try
|
|
{
|
|
Class classArgs[] = {};
|
|
m = obj.getClass ().getDeclaredMethod ("writeReplace",
|
|
classArgs);
|
|
// m can't be null by definition since an exception would
|
|
// have been thrown so a check for null is not needed.
|
|
obj = m.invoke (obj, new Object[] {});
|
|
}
|
|
catch (NoSuchMethodException ignore)
|
|
{
|
|
}
|
|
catch (IllegalAccessException ignore)
|
|
{
|
|
}
|
|
catch (InvocationTargetException ignore)
|
|
{
|
|
}
|
|
}
|
|
|
|
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 (clazz.getName ());
|
|
|
|
if (clazz.isArray ())
|
|
{
|
|
realOutput.writeByte (TC_ARRAY);
|
|
writeObject (osc);
|
|
assignNewHandle (obj);
|
|
writeArraySizeAndElements (obj, clazz.getComponentType ());
|
|
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];
|
|
|
|
fieldsAlreadyWritten = false;
|
|
has_write = currentObjectStreamClass.hasWriteMethod ();
|
|
|
|
writeFields (obj, currentObjectStreamClass.fields,
|
|
has_write);
|
|
|
|
if (has_write)
|
|
{
|
|
drain ();
|
|
realOutput.writeByte (TC_ENDBLOCKDATA);
|
|
}
|
|
}
|
|
|
|
currentObject = null;
|
|
currentObjectStreamClass = null;
|
|
currentPutField = null;
|
|
break;
|
|
}
|
|
|
|
throw new NotSerializableException (clazz.getName ());
|
|
} // 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 defaultWriteObject 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, 'C');
|
|
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, 'D');
|
|
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, 'F');
|
|
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, 'I');
|
|
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, 'J');
|
|
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, 'S');
|
|
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 (value != null &&
|
|
! field.getType ().isAssignableFrom (value.getClass ()))
|
|
throw new IllegalArgumentException ();
|
|
objs[field.getOffset ()] = value;
|
|
}
|
|
|
|
public void write (ObjectOutput out) throws IOException
|
|
{
|
|
// Apparently Block data is not used with PutField as per
|
|
// empirical evidence against JDK 1.2. Also see Mauve test
|
|
// java.io.ObjectInputOutput.Test.GetPutField.
|
|
setBlockDataMode (false);
|
|
out.write (prim_field_data);
|
|
for (int i = 0; i < objs.length; ++ i)
|
|
out.writeObject (objs[i]);
|
|
setBlockDataMode (true);
|
|
}
|
|
|
|
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
|
|
{
|
|
Class klass = obj.getClass ();
|
|
try
|
|
{
|
|
Class classArgs[] = {ObjectOutputStream.class};
|
|
Method m = getMethod (klass, "writeObject", classArgs);
|
|
if (m == null)
|
|
return;
|
|
Object args[] = {this};
|
|
m.invoke (obj, args);
|
|
}
|
|
catch (InvocationTargetException x)
|
|
{
|
|
/* Rethrow if possible. */
|
|
Throwable exception = x.getTargetException();
|
|
if (exception instanceof RuntimeException)
|
|
throw (RuntimeException) exception;
|
|
if (exception instanceof IOException)
|
|
throw (IOException) exception;
|
|
|
|
throw new IOException ("Exception thrown from writeObject() on " +
|
|
klass + ": " + exception.getClass().getName());
|
|
}
|
|
catch (Exception x)
|
|
{
|
|
throw new IOException ("Failure invoking writeObject() on " +
|
|
klass + ": " + x.getClass().getName());
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|