New object streaming code is born!

git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@1605 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Michael Bayne
2002-07-23 05:42:34 +00:00
parent 2601ab2321
commit 2330afc3cd
11 changed files with 2203 additions and 0 deletions
+73
View File
@@ -0,0 +1,73 @@
//
// $Id: ArrayMask.java,v 1.1 2002/07/23 05:42:34 mdb Exp $
package com.threerings.io;
import java.io.IOException;
/**
* Used to keep track of which entries in an array are null and which are
* not. <em>Note:</em> only arrays up to 262,144 elements in length can be
* handled by this class.
*/
public class ArrayMask
{
/**
* Creates an array mask suitable for unserializing.
*/
public ArrayMask ()
{
}
/**
* Creates an array mask for an array of the specified length.
*/
public ArrayMask (int length)
{
int mlength = length/8;
if (length % 8 != 0) {
mlength++;
}
_mask = new byte[mlength];
}
/**
* Sets the bit indicating that the specified array index is non-null.
*/
public void set (int index)
{
_mask[index/8] |= (1 << (index%8));
}
/**
* Returns true if the specified array index should be non-null.
*/
public boolean isSet (int index)
{
return (_mask[index/8] & (1 << (index%8))) != 0;
}
/**
* Writes this mask to the specified output stream.
*/
public void writeTo (ObjectOutputStream out)
throws IOException
{
out.writeShort(_mask.length);
out.write(_mask);
}
/**
* Reads this mask from the specified input stream.
*/
public void readFrom (ObjectInputStream in)
throws IOException
{
int length = in.readShort();
_mask = new byte[length];
in.read(_mask);
}
/** A byte array with bits for every entry in the source array. */
protected byte[] _mask;
}
@@ -0,0 +1,629 @@
//
// $Id: BasicStreamers.java,v 1.1 2002/07/23 05:42:34 mdb Exp $
package com.threerings.io;
import java.io.EOFException;
import java.io.IOException;
import java.util.HashMap;
/**
* Code to read and write basic object types (like arrays of primitives,
* {@link Integer} instances, {@link Double} instances, etc.).
*/
public class BasicStreamers
{
/** An array of types for all of the basic streamers. */
public static Class[] BSTREAMER_TYPES = {
Boolean.class,
Byte.class,
Short.class,
Character.class,
Integer.class,
Long.class,
Float.class,
Double.class,
String.class,
(new boolean[0]).getClass(),
(new byte[0]).getClass(),
(new short[0]).getClass(),
(new char[0]).getClass(),
(new int[0]).getClass(),
(new long[0]).getClass(),
(new float[0]).getClass(),
(new double[0]).getClass(),
(new Object[0]).getClass(),
};
/** An array of instances of all of the basic streamers. */
public static Streamer[] BSTREAMER_INSTANCES = {
new BooleanStreamer(),
new ByteStreamer(),
new ShortStreamer(),
new CharacterStreamer(),
new IntegerStreamer(),
new LongStreamer(),
new FloatStreamer(),
new DoubleStreamer(),
new StringStreamer(),
new BooleanArrayStreamer(),
new ByteArrayStreamer(),
new ShortArrayStreamer(),
new CharArrayStreamer(),
new IntArrayStreamer(),
new LongArrayStreamer(),
new FloatArrayStreamer(),
new DoubleArrayStreamer(),
new ObjectArrayStreamer(),
};
/** Streams {@link Boolean} instances. */
public static class BooleanStreamer extends Streamer
{
// documentation inherited
public Object createObject (ObjectInputStream in)
throws IOException
{
return new Boolean(in.readBoolean());
}
// documentation inherited
public void writeObject (
Object object, ObjectOutputStream out, boolean useWriter)
throws IOException
{
out.writeBoolean(((Boolean)object).booleanValue());
}
// documentation inherited
public void readObject (
Object object, ObjectInputStream in, boolean useReader)
throws IOException, ClassNotFoundException
{
// nothing to do here
}
}
/** Streams {@link Byte} instances. */
public static class ByteStreamer extends Streamer
{
// documentation inherited
public Object createObject (ObjectInputStream in)
throws IOException
{
return new Byte(in.readByte());
}
// documentation inherited
public void writeObject (
Object object, ObjectOutputStream out, boolean useWriter)
throws IOException
{
out.writeByte(((Byte)object).byteValue());
}
// documentation inherited
public void readObject (
Object object, ObjectInputStream in, boolean useReader)
throws IOException, ClassNotFoundException
{
// nothing to do here
}
}
/** Streams {@link Short} instances. */
public static class ShortStreamer extends Streamer
{
// documentation inherited
public Object createObject (ObjectInputStream in)
throws IOException
{
return new Short(in.readShort());
}
// documentation inherited
public void writeObject (
Object object, ObjectOutputStream out, boolean useWriter)
throws IOException
{
out.writeShort(((Short)object).shortValue());
}
// documentation inherited
public void readObject (
Object object, ObjectInputStream in, boolean useReader)
throws IOException, ClassNotFoundException
{
// nothing to do here
}
}
/** Streams {@link Character} instances. */
public static class CharacterStreamer extends Streamer
{
// documentation inherited
public Object createObject (ObjectInputStream in)
throws IOException
{
return new Character(in.readChar());
}
// documentation inherited
public void writeObject (
Object object, ObjectOutputStream out, boolean useWriter)
throws IOException
{
out.writeChar(((Character)object).charValue());
}
// documentation inherited
public void readObject (
Object object, ObjectInputStream in, boolean useReader)
throws IOException, ClassNotFoundException
{
// nothing to do here
}
}
/** Streams {@link Integer} instances. */
public static class IntegerStreamer extends Streamer
{
// documentation inherited
public Object createObject (ObjectInputStream in)
throws IOException
{
return new Integer(in.readInt());
}
// documentation inherited
public void writeObject (
Object object, ObjectOutputStream out, boolean useWriter)
throws IOException
{
out.writeInt(((Integer)object).intValue());
}
// documentation inherited
public void readObject (
Object object, ObjectInputStream in, boolean useReader)
throws IOException, ClassNotFoundException
{
// nothing to do here
}
}
/** Streams {@link Long} instances. */
public static class LongStreamer extends Streamer
{
// documentation inherited
public Object createObject (ObjectInputStream in)
throws IOException
{
return new Long(in.readLong());
}
// documentation inherited
public void writeObject (
Object object, ObjectOutputStream out, boolean useWriter)
throws IOException
{
out.writeLong(((Long)object).longValue());
}
// documentation inherited
public void readObject (
Object object, ObjectInputStream in, boolean useReader)
throws IOException, ClassNotFoundException
{
// nothing to do here
}
}
/** Streams {@link Float} instances. */
public static class FloatStreamer extends Streamer
{
// documentation inherited
public Object createObject (ObjectInputStream in)
throws IOException
{
return new Float(in.readFloat());
}
// documentation inherited
public void writeObject (
Object object, ObjectOutputStream out, boolean useWriter)
throws IOException
{
out.writeFloat(((Float)object).floatValue());
}
// documentation inherited
public void readObject (
Object object, ObjectInputStream in, boolean useReader)
throws IOException, ClassNotFoundException
{
// nothing to do here
}
}
/** Streams {@link Double} instances. */
public static class DoubleStreamer extends Streamer
{
// documentation inherited
public Object createObject (ObjectInputStream in)
throws IOException
{
return new Double(in.readDouble());
}
// documentation inherited
public void writeObject (
Object object, ObjectOutputStream out, boolean useWriter)
throws IOException
{
out.writeDouble(((Double)object).doubleValue());
}
// documentation inherited
public void readObject (
Object object, ObjectInputStream in, boolean useReader)
throws IOException, ClassNotFoundException
{
// nothing to do here
}
}
/** Streams {@link String} instances. */
public static class StringStreamer extends Streamer
{
// documentation inherited
public Object createObject (ObjectInputStream in)
throws IOException
{
return in.readUTF();
}
// documentation inherited
public void writeObject (
Object object, ObjectOutputStream out, boolean useWriter)
throws IOException
{
out.writeUTF((String)object);
}
// documentation inherited
public void readObject (
Object object, ObjectInputStream in, boolean useReader)
throws IOException, ClassNotFoundException
{
// nothing to do here
}
}
/** Streams arrays of booleans. */
public static class BooleanArrayStreamer extends Streamer
{
// documentation inherited
public Object createObject (ObjectInputStream in)
throws IOException
{
return new boolean[in.readInt()];
}
// documentation inherited
public void writeObject (
Object object, ObjectOutputStream out, boolean useWriter)
throws IOException
{
boolean[] value = (boolean[])object;
int ecount = value.length;
out.writeInt(ecount);
for (int ii = 0; ii < ecount; ii++) {
out.writeBoolean(value[ii]);
}
}
// documentation inherited
public void readObject (
Object object, ObjectInputStream in, boolean useReader)
throws IOException, ClassNotFoundException
{
boolean[] value = (boolean[])object;
int ecount = value.length;
for (int ii = 0; ii < ecount; ii++) {
value[ii] = in.readBoolean();
}
}
}
/** Streams arrays of bytes. */
public static class ByteArrayStreamer extends Streamer
{
// documentation inherited
public Object createObject (ObjectInputStream in)
throws IOException
{
return new byte[in.readInt()];
}
// documentation inherited
public void writeObject (
Object object, ObjectOutputStream out, boolean useWriter)
throws IOException
{
byte[] value = (byte[])object;
int ecount = value.length;
out.writeInt(ecount);
out.write(value);
}
// documentation inherited
public void readObject (
Object object, ObjectInputStream in, boolean useReader)
throws IOException, ClassNotFoundException
{
byte[] value = (byte[])object;
int remain = value.length, offset = 0, read;
while (remain > 0) {
if ((read = in.read(value, offset, remain)) > 0) {
remain -= read;
offset += read;
} else {
throw new EOFException();
}
}
}
}
/** Streams arrays of shorts. */
public static class ShortArrayStreamer extends Streamer
{
// documentation inherited
public Object createObject (ObjectInputStream in)
throws IOException
{
return new short[in.readInt()];
}
// documentation inherited
public void writeObject (
Object object, ObjectOutputStream out, boolean useWriter)
throws IOException
{
short[] value = (short[])object;
int ecount = value.length;
out.writeInt(ecount);
for (int ii = 0; ii < ecount; ii++) {
out.writeShort(value[ii]);
}
}
// documentation inherited
public void readObject (
Object object, ObjectInputStream in, boolean useReader)
throws IOException, ClassNotFoundException
{
short[] value = (short[])object;
int ecount = value.length;
for (int ii = 0; ii < ecount; ii++) {
value[ii] = in.readShort();
}
}
}
/** Streams arrays of chars. */
public static class CharArrayStreamer extends Streamer
{
// documentation inherited
public Object createObject (ObjectInputStream in)
throws IOException
{
return new char[in.readInt()];
}
// documentation inherited
public void writeObject (
Object object, ObjectOutputStream out, boolean useWriter)
throws IOException
{
char[] value = (char[])object;
int ecount = value.length;
out.writeInt(ecount);
for (int ii = 0; ii < ecount; ii++) {
out.writeChar(value[ii]);
}
}
// documentation inherited
public void readObject (
Object object, ObjectInputStream in, boolean useReader)
throws IOException, ClassNotFoundException
{
char[] value = (char[])object;
int ecount = value.length;
for (int ii = 0; ii < ecount; ii++) {
value[ii] = in.readChar();
}
}
}
/** Streams arrays of ints. */
public static class IntArrayStreamer extends Streamer
{
// documentation inherited
public Object createObject (ObjectInputStream in)
throws IOException
{
return new int[in.readInt()];
}
// documentation inherited
public void writeObject (
Object object, ObjectOutputStream out, boolean useWriter)
throws IOException
{
int[] value = (int[])object;
int ecount = value.length;
out.writeInt(ecount);
for (int ii = 0; ii < ecount; ii++) {
out.writeInt(value[ii]);
}
}
// documentation inherited
public void readObject (
Object object, ObjectInputStream in, boolean useReader)
throws IOException, ClassNotFoundException
{
int[] value = (int[])object;
int ecount = value.length;
for (int ii = 0; ii < ecount; ii++) {
value[ii] = in.readInt();
}
}
}
/** Streams arrays of longs. */
public static class LongArrayStreamer extends Streamer
{
// documentation inherited
public Object createObject (ObjectInputStream in)
throws IOException
{
return new long[in.readInt()];
}
// documentation inherited
public void writeObject (
Object object, ObjectOutputStream out, boolean useWriter)
throws IOException
{
long[] value = (long[])object;
int ecount = value.length;
out.writeInt(ecount);
for (int ii = 0; ii < ecount; ii++) {
out.writeLong(value[ii]);
}
}
// documentation inherited
public void readObject (
Object object, ObjectInputStream in, boolean useReader)
throws IOException, ClassNotFoundException
{
long[] value = (long[])object;
int ecount = value.length;
for (int ii = 0; ii < ecount; ii++) {
value[ii] = in.readLong();
}
}
}
/** Streams arrays of floats. */
public static class FloatArrayStreamer extends Streamer
{
// documentation inherited
public Object createObject (ObjectInputStream in)
throws IOException
{
return new float[in.readInt()];
}
// documentation inherited
public void writeObject (
Object object, ObjectOutputStream out, boolean useWriter)
throws IOException
{
float[] value = (float[])object;
int ecount = value.length;
out.writeInt(ecount);
for (int ii = 0; ii < ecount; ii++) {
out.writeFloat(value[ii]);
}
}
// documentation inherited
public void readObject (
Object object, ObjectInputStream in, boolean useReader)
throws IOException, ClassNotFoundException
{
float[] value = (float[])object;
int ecount = value.length;
for (int ii = 0; ii < ecount; ii++) {
value[ii] = in.readFloat();
}
}
}
/** Streams arrays of doubles. */
public static class DoubleArrayStreamer extends Streamer
{
// documentation inherited
public Object createObject (ObjectInputStream in)
throws IOException
{
return new double[in.readInt()];
}
// documentation inherited
public void writeObject (
Object object, ObjectOutputStream out, boolean useWriter)
throws IOException
{
double[] value = (double[])object;
int ecount = value.length;
out.writeInt(ecount);
for (int ii = 0; ii < ecount; ii++) {
out.writeDouble(value[ii]);
}
}
// documentation inherited
public void readObject (
Object object, ObjectInputStream in, boolean useReader)
throws IOException, ClassNotFoundException
{
double[] value = (double[])object;
int ecount = value.length;
for (int ii = 0; ii < ecount; ii++) {
value[ii] = in.readDouble();
}
}
}
/** Streams arrays of Object instances. */
public static class ObjectArrayStreamer extends Streamer
{
// documentation inherited
public Object createObject (ObjectInputStream in)
throws IOException
{
return new Object[in.readInt()];
}
// documentation inherited
public void writeObject (
Object object, ObjectOutputStream out, boolean useWriter)
throws IOException
{
Object[] value = (Object[])object;
int ecount = value.length;
out.writeInt(ecount);
for (int ii = 0; ii < ecount; ii++) {
out.writeObject(value[ii]);
}
}
// documentation inherited
public void readObject (
Object object, ObjectInputStream in, boolean useReader)
throws IOException, ClassNotFoundException
{
Object[] value = (Object[])object;
int ecount = value.length;
for (int ii = 0; ii < ecount; ii++) {
value[ii] = in.readObject();
}
}
}
}
@@ -0,0 +1,27 @@
//
// $Id: ClassMapping.java,v 1.1 2002/07/23 05:42:34 mdb Exp $
package com.threerings.io;
/**
* Used by the {@link ObjectOutputStream} and {@link ObjectInputStream} to
* map classes to codes during a session.
*/
class ClassMapping
{
public short code;
public Class sclass;
public Streamer streamer;
public ClassMapping (short code, Class sclass, Streamer streamer)
{
this.code = code;
this.sclass = sclass;
this.streamer = streamer;
}
public String toString ()
{
return "[code=" + code + ", class=" + sclass.getName() + "]";
}
}
@@ -0,0 +1,244 @@
//
// $Id: FieldMarshaller.java,v 1.1 2002/07/23 05:42:34 mdb Exp $
package com.threerings.io;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.HashMap;
import com.threerings.presents.Log;
/**
* Used to read and write a single field of a {@link Streamable} instance.
*/
public abstract class FieldMarshaller
{
/**
* Reads the contents of the supplied field from the supplied stream
* and sets it in the supplied object.
*/
public abstract void readField (
Field field, Object target, ObjectInputStream in) throws Exception;
/**
* Writes the contents of the supplied field in the supplied object to
* the supplied stream.
*/
public abstract void writeField (
Field field, Object source, ObjectOutputStream out) throws Exception;
/**
* Returns a field marshaller appropriate for the supplied field or
* null if no marshaller exists for the type contained by the field in
* question.
*/
public static FieldMarshaller getFieldMarshaller (Field field)
{
if (_marshallers == null) {
createMarshallers();
}
// if we have an exact match, use that
Class ftype = field.getType();
FieldMarshaller fm = (FieldMarshaller)_marshallers.get(ftype);
// otherwise if the class is a streamable, use the streamable
// marshaller
if (fm == null && Streamer.isStreamable(ftype)) {
fm = (FieldMarshaller)_marshallers.get(Streamable.class);
}
return fm;
}
/**
* Used to marshall and unmarshall classes for which we have a basic
* {@link Streamer}.
*/
protected static class StreamerMarshaller extends FieldMarshaller
{
public StreamerMarshaller (Streamer streamer)
{
_streamer = streamer;
}
// documentation inherited
public void readField (
Field field, Object target, ObjectInputStream in)
throws Exception
{
if (in.readBoolean()) {
Object value = _streamer.createObject(in);
_streamer.readObject(value, in, true);
field.set(target, value);
} else {
field.set(target, null);
}
}
// documentation inherited
public void writeField (
Field field, Object source, ObjectOutputStream out)
throws Exception
{
Object value = field.get(source);
if (value == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
_streamer.writeObject(value, out, true);
}
}
/** The streamer we use to read and write our field. */
protected Streamer _streamer;
}
/**
* Creates instances of all known field marshaller types and populates
* the {@link _marshallers} table with them. This is called the first
* time a marshaller is requested.
*/
protected static void createMarshallers ()
{
// create our table
_marshallers = new HashMap();
// create a marshaller for streamable instances
_marshallers.put(Streamable.class, new FieldMarshaller() {
public void readField (
Field field, Object target, ObjectInputStream in)
throws Exception {
field.set(target, in.readObject());
}
public void writeField (
Field field, Object source, ObjectOutputStream out)
throws Exception {
out.writeObject(field.get(source));
}
});
// create marshallers for the primitive types
_marshallers.put(Boolean.TYPE, new FieldMarshaller() {
public void readField (
Field field, Object target, ObjectInputStream in)
throws Exception {
field.setBoolean(target, in.readBoolean());
}
public void writeField (
Field field, Object source, ObjectOutputStream out)
throws Exception {
out.writeBoolean(field.getBoolean(source));
}
});
_marshallers.put(Byte.TYPE, new FieldMarshaller() {
public void readField (
Field field, Object target, ObjectInputStream in)
throws Exception {
field.setByte(target, in.readByte());
}
public void writeField (
Field field, Object source, ObjectOutputStream out)
throws Exception {
out.writeByte(field.getByte(source));
}
});
_marshallers.put(Character.TYPE, new FieldMarshaller() {
public void readField (
Field field, Object target, ObjectInputStream in)
throws Exception {
field.setChar(target, in.readChar());
}
public void writeField (
Field field, Object source, ObjectOutputStream out)
throws Exception {
out.writeChar(field.getChar(source));
}
});
_marshallers.put(Short.TYPE, new FieldMarshaller() {
public void readField (
Field field, Object target, ObjectInputStream in)
throws Exception {
field.setShort(target, in.readShort());
}
public void writeField (
Field field, Object source, ObjectOutputStream out)
throws Exception {
out.writeShort(field.getShort(source));
}
});
_marshallers.put(Integer.TYPE, new FieldMarshaller() {
public void readField (
Field field, Object target, ObjectInputStream in)
throws Exception {
field.setInt(target, in.readInt());
}
public void writeField (
Field field, Object source, ObjectOutputStream out)
throws Exception {
out.writeInt(field.getInt(source));
}
});
_marshallers.put(Long.TYPE, new FieldMarshaller() {
public void readField (
Field field, Object target, ObjectInputStream in)
throws Exception {
field.setLong(target, in.readLong());
}
public void writeField (
Field field, Object source, ObjectOutputStream out)
throws Exception {
out.writeLong(field.getLong(source));
}
});
_marshallers.put(Float.TYPE, new FieldMarshaller() {
public void readField (
Field field, Object target, ObjectInputStream in)
throws Exception {
field.setFloat(target, in.readFloat());
}
public void writeField (
Field field, Object source, ObjectOutputStream out)
throws Exception {
out.writeFloat(field.getFloat(source));
}
});
_marshallers.put(Double.TYPE, new FieldMarshaller() {
public void readField (
Field field, Object target, ObjectInputStream in)
throws Exception {
field.setDouble(target, in.readDouble());
}
public void writeField (
Field field, Object source, ObjectOutputStream out)
throws Exception {
out.writeDouble(field.getDouble(source));
}
});
_marshallers.put(String.class, new FieldMarshaller() {
public void readField (
Field field, Object target, ObjectInputStream in)
throws Exception {
field.set(target, in.readUTF());
}
public void writeField (
Field field, Object source, ObjectOutputStream out)
throws Exception {
out.writeUTF((String)field.get(source));
}
});
// create field marshallers for all of the basic types
int bscount = BasicStreamers.BSTREAMER_TYPES.length;
for (int ii = 0; ii < bscount; ii++) {
_marshallers.put(BasicStreamers.BSTREAMER_TYPES[ii],
new StreamerMarshaller(
BasicStreamers.BSTREAMER_INSTANCES[ii]));
}
}
/** Contains a mapping from field type to field marshaller instance
* for that type. */
protected static HashMap _marshallers;
}
@@ -0,0 +1,261 @@
//
// $Id: FramedInputStream.java,v 1.1 2002/07/23 05:42:34 mdb Exp $
package com.threerings.io;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import com.samskivert.util.StringUtil;
import com.threerings.presents.Log;
/**
* The framed input stream reads input that was framed by a framing output
* stream. Framing in this case simply means writing the length of the
* frame followed by the data associated with the frame so that an entire
* frame can be loaded from the network layer before any higher layer
* attempts to process it. Additionally, any failure in decoding a frame
* won't result in the entire stream being skewed due to the remainder of
* the undecoded frame remaining in the input stream.
*
* <p>The framed input stream reads an entire frame worth of data into its
* internal buffer when <code>readFrame()</code> is called. It then
* behaves as if this is the only data available on the stream (meaning
* that when the data in the frame is exhausted, it will behave as if the
* end of the stream has been reached). The buffer can only contain a
* single frame at a time, so any data left over from a previous frame
* will disappear when <code>readFrame()</code> is called again.
*
* <p><em>Note:</em> The framing input stream does not synchronize reads
* from its internal buffer. It is intended to only be accessed from a
* single thread.
*
* <p>Implementation note: maybe this should derive from
* <code>FilterInputStream</code> and be tied to a single
* <code>InputStream</code> for its lifetime.
*/
public class FramedInputStream extends InputStream
{
public FramedInputStream ()
{
_header = new byte[HEADER_SIZE];
_buffer = new byte[INITIAL_BUFFER_SIZE];
}
/**
* Reads a frame from the provided input stream, or appends to a
* partially read frame. Appends the read data to the existing data
* available via the framed input stream's read methods. If the entire
* frame data is not yet available, <code>readFrame</code> will return
* false, otherwise true.
*
* <p> The code assumes that it will be able to read the entire frame
* header in a single read. The header is only four bytes and should
* always arrive at the beginning of a packet, so unless something is
* very funky with the networking layer, this should be a safe
* assumption.
*
* @return true if the entire frame has been read, false if the buffer
* contains only a partial frame.
*/
public boolean readFrame (InputStream source)
throws IOException
{
// if the buffer currently contains a complete frame, that means
// we're not halfway through reading a frame and that we can start
// anew.
if (_count == _length) {
// read in the frame length
int got = source.read(_header, 0, HEADER_SIZE);
if (got < 0) {
throw new EOFException();
} else if (got == 0) {
// TBD: don't log this for now, but look into it later
// Log.info("Woke up to read data, but there ain't none. Sigh.");
return false;
} else if (got < HEADER_SIZE) {
String errmsg = "FramedInputStream does not support " +
"partially reading the header. Needed " + HEADER_SIZE +
" bytes, got " + got + " bytes.";
throw new RuntimeException(errmsg);
}
// now that we've read our new frame length, we can clear out
// any prior data
_pos = 0;
_count = 0;
// decode the frame length
_length = (_header[0] & 0xFF) << 24;
_length += (_header[1] & 0xFF) << 16;
_length += (_header[2] & 0xFF) << 8;
_length += (_header[3] & 0xFF);
// if necessary, expand our buffer to accomodate the frame
if (_length > _buffer.length) {
// increase the buffer size in large increments
_buffer = new byte[Math.max(_buffer.length << 1, _length)];
}
}
// read the data into the buffer
int got = source.read(_buffer, _count, _length-_count);
if (got < 0) {
throw new EOFException();
}
_count += got;
// System.err.println("Read frame " + _count +
// " (want " + _length + " pos " + _pos + ")");
return (_count == _length);
}
/**
* Reads the next byte of data from this input stream. The value byte
* is returned as an <code>int</code> in the range <code>0</code> to
* <code>255</code>. If no byte is available because the end of the
* stream has been reached, the value <code>-1</code> is returned.
*
* <p>This <code>read</code> method cannot block.
*
* @return the next byte of data, or <code>-1</code> if the end of the
* stream has been reached.
*/
public int read ()
{
return (_pos < _count) ? (_buffer[_pos++] & 0xFF) : -1;
}
/**
* Reads up to <code>len</code> bytes of data into an array of bytes
* from this input stream. If <code>pos</code> equals
* <code>count</code>, then <code>-1</code> is returned to indicate
* end of file. Otherwise, the number <code>k</code> of bytes read is
* equal to the smaller of <code>len</code> and
* <code>count-pos</code>. If <code>k</code> is positive, then bytes
* <code>buf[pos]</code> through <code>buf[pos+k-1]</code> are copied
* into <code>b[off]</code> through <code>b[off+k-1]</code> in the
* manner performed by <code>System.arraycopy</code>. The value
* <code>k</code> is added into <code>pos</code> and <code>k</code> is
* returned.
*
* <p>This <code>read</code> method cannot block.
*
* @param b the buffer into which the data is read.
* @param off the start offset of the data.
* @param len the maximum number of bytes read.
*
* @return the total number of bytes read into the buffer, or
* <code>-1</code> if there is no more data because the end of the
* stream has been reached.
*/
public int read (byte[] b, int off, int len)
{
// sanity check the arguments
if (b == null) {
throw new NullPointerException();
} else if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
}
// figure out how much data we'll return
if (_pos >= _count) {
// if they asked to read zero bytes and we have no bytes
// remaining; we're supposed to return 0 rather than EOF
return (len == 0) ? 0 : -1;
}
if (_pos + len > _count) {
len = _count - _pos;
}
if (len <= 0) {
return 0;
}
// copy and advance
System.arraycopy(_buffer, _pos, b, off, len);
_pos += len;
return len;
}
/**
* Skips <code>n</code> bytes of input from this input stream. Fewer
* bytes might be skipped if the end of the input stream is reached.
* The actual number <code>k</code> of bytes to be skipped is equal to
* the smaller of <code>n</code> and <code>count-pos</code>. The value
* <code>k</code> is added into <code>pos</code> and <code>k</code> is
* returned.
*
* @param n the number of bytes to be skipped.
*
* @return the actual number of bytes skipped.
*/
public long skip (long n)
{
if (_pos + n > _count) {
n = _count - _pos;
}
if (n <= 0) {
return 0;
}
_pos += n;
return n;
}
/**
* Returns the number of bytes that can be read from this input stream
* without blocking. The value returned is <code>count - pos</code>,
* which is the number of bytes remaining to be read from the input
* buffer.
*
* @return the number of bytes remaining to be read from the buffered
* frames.
*/
public int available ()
{
return _count - _pos;
}
/**
* Always returns false as framed input streams do not support
* marking.
*/
public boolean markSupported ()
{
return false;
}
/**
* Does nothing, as marking is not supported.
*/
public void mark (int readAheadLimit)
{
// not supported; do nothing
}
/**
* Resets the buffer to the beginning of the buffered frames.
*/
public void reset ()
{
_pos = 0;
}
protected byte[] _header;
protected int _length;
protected byte[] _buffer;
protected int _pos;
protected int _count;
/** The size of the frame header (a 32-bit integer). */
protected static final int HEADER_SIZE = 4;
/** The default initial size of the internal buffer. */
protected static final int INITIAL_BUFFER_SIZE = 32;
}
@@ -0,0 +1,125 @@
//
// $Id: FramingOutputStream.java,v 1.1 2002/07/23 05:42:34 mdb Exp $
package com.threerings.io;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
/**
* The framing output stream accumulates output into a byte array just
* like the byte array output stream, but can then be instructed to send
* its contents down another output stream, prefixed by the length
* (written as an integer) of those contents. It does this efficiently so
* that data is copied as little as possible and so that the output stream
* to which the data is written need not be buffered because the framed
* output is written in a single call to <code>write()</code>.
*
* <p><em>Note:</em> The framing output stream does not synchronize writes
* to its internal buffer. It is intended to only be accessed from a
* single thread.
*
* <p>Implementation note: maybe this should derive from
* <code>FilterOutputStream</code> and be tied to a single
* <code>OutputStream</code> for its lifetime.
*/
public class FramingOutputStream extends OutputStream
{
public FramingOutputStream ()
{
_buffer = new byte[INITIAL_BUFFER_SIZE];
_count = 4; // leave room for the frame size at the beginning
}
/**
* Writes the specified byte to this framing output stream.
*
* @param b the byte to be written.
*/
public void write (int b)
{
// expand our buffer if necessary
int newcount = _count + 1;
if (newcount > _buffer.length) {
// increase the buffer size in large increments
byte[] newbuf = new byte[Math.max(_buffer.length << 1, newcount)];
System.arraycopy(_buffer, 0, newbuf, 0, _count);
_buffer = newbuf;
}
// copy and advance
_buffer[_count] = (byte)b;
_count = newcount;
}
/**
* Writes <code>len</code> bytes from the specified byte array
* starting at offset <code>off</code> to this framing output stream.
*
* @param b the data.
* @param off the start offset in the data.
* @param len the number of bytes to write.
*/
public void write (byte[] b, int off, int len)
{
// sanity check the arguments
if ((off < 0) || (off > b.length) || (len < 0) ||
((off + len) > b.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
// expand the buffer if necessary
int newcount = _count + len;
if (newcount > _buffer.length) {
// increase the buffer size in large increments
byte[] newbuf = new byte[Math.max(_buffer.length << 1, newcount)];
System.arraycopy(_buffer, 0, newbuf, 0, _count);
_buffer = newbuf;
}
// copy and advance
System.arraycopy(b, off, _buffer, _count, len);
_count = newcount;
}
/**
* Writes the contents of this framing output stream to the target
* output stream, prefixed by an integer with value equal to the
* number of bytes written folling that integer. It then resets the
* framing output stream to prepare for another framed message.
*/
public void writeFrameAndReset (OutputStream target)
throws IOException
{
// prefix the frame with the byte count in network byte order (the
// format used by DataOutputStream)
int count = _count - 4;
_buffer[0] = (byte)((count >>> 24) & 0xFF);
_buffer[1] = (byte)((count >>> 16) & 0xFF);
_buffer[2] = (byte)((count >>> 8) & 0xFF);
_buffer[3] = (byte)((count >>> 0) & 0xFF);
// write the data
target.write(_buffer, 0, _count);
// System.err.println("Wrote frame " + (_count-4));
// reset our internal buffer
reset();
}
public void reset ()
{
// leave room for the frame size at the beginning
_count = 4;
}
protected byte[] _buffer;
protected int _count;
/** The default initial size of the internal buffer. */
protected static final int INITIAL_BUFFER_SIZE = 32;
}
@@ -0,0 +1,169 @@
//
// $Id: ObjectInputStream.java,v 1.1 2002/07/23 05:42:34 mdb Exp $
package com.threerings.io;
import java.io.InputStream;
import java.io.DataInputStream;
import java.io.IOException;
import com.samskivert.util.HashIntMap;
import com.threerings.presents.Log;
/**
* Used to read {@link Streamable} objects from an {@link InputStream}.
* Other common object types are supported as well (@see {@link
* ObjectOutputStream}).
*
* @see Streamable
*/
public class ObjectInputStream extends DataInputStream
{
/**
* Constructs an object input stream which will read its data from the
* supplied source stream.
*/
public ObjectInputStream (InputStream source)
{
super(source);
}
/**
* Reads a {@link Streamable} instance or one of the supported object
* types from the input stream.
*/
public Object readObject ()
throws IOException, ClassNotFoundException
{
ClassMapping cmap;
// create our classmap if necessary
if (_classmap == null) {
_classmap = new HashIntMap();
}
try {
// read in the class code for this instance
short code = readShort();
// a zero code indicates a null value
if (code == 0) {
return null;
// if the code is negative, that means that we've never
// seen it before and class metadata follows
} else if (code < 0) {
// first swap the code into positive-land
code *= -1;
// read in the class metadata, create a class mapping record
// for the class, and cache it
String cname = readUTF();
Class sclass = Class.forName(cname);
Streamer streamer = Streamer.getStreamer(sclass);
// sanity check
if (streamer == null) {
String errmsg = "Aiya! Unable to create streamer for " +
"newly seen class [code=" + code +
", class=" + cname + "]";
throw new RuntimeException(errmsg);
}
cmap = new ClassMapping(code, sclass, streamer);
_classmap.put(code, cmap);
} else {
cmap = (ClassMapping)_classmap.get(code);
// sanity check
if (cmap == null) {
String errmsg = "Aiya! Read object code for which we " +
"have no registered class metadata [code=" + code + "]";
throw new RuntimeException(errmsg);
}
}
// create an instance of the appropriate object
_streamer = cmap.streamer;
Object target = _streamer.createObject(this);
_current = target;
// now read the instance data
_streamer.readObject(target, this, true);
// and return the newly read object
return target;
} finally {
// clear out our current object references
_current = null;
_streamer = null;
}
}
/**
* Reads an object from the input stream that was previously written
* with {@link ObjectOutputStream#writeBareObject}.
*
* @param object the object to be populated from data on the stream.
* It cannot be <code>null</code>.
*/
public void readBareObject (Object object)
throws IOException, ClassNotFoundException
{
try {
// obtain the streamer for objects of this class
_streamer = Streamer.getStreamer(object.getClass());
_current = object;
// now read the instance data
_streamer.readObject(object, this, true);
} finally {
// clear out our current object references
_current = null;
_streamer = null;
}
}
/**
* Reads the fields of the specified {@link Streamable} instance from
* the input stream using the default object streaming mechanisms (a
* call is not made to <code>readObject()</code>, even if such a
* method exists).
*/
public void defaultReadObject ()
throws IOException, ClassNotFoundException
{
// sanity check
if (_current == null) {
throw new RuntimeException(
"defaultReadObject() called illegally.");
}
// read the instance data
_streamer.readObject(_current, this, false);
}
/**
* Used by a {@link Streamer} that is reading an array of {@link
* Streamable} instances.
*/
protected void setCurrent (Streamer streamer, Object current)
{
_streamer = streamer;
_current = current;
}
/** Used to map classes to numeric codes and the {@link Streamer}
* instance used to write them. */
protected HashIntMap _classmap;
/** The object currently being read from the stream. */
protected Object _current;
/** The streamer being used currently. */
protected Streamer _streamer;
}
@@ -0,0 +1,183 @@
//
// $Id: ObjectOutputStream.java,v 1.1 2002/07/23 05:42:34 mdb Exp $
package com.threerings.io;
import java.io.OutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.HashMap;
import com.threerings.presents.Log;
/**
* Used to write {@link Streamable} objects to an {@link OutputStream}.
* Other common object types are supported as well:
*
* <pre>
* Boolean
* Byte
* Character
* Short
* Integer
* Long
* Float
* Double
* boolean[]
* byte[]
* char[]
* short[]
* int[]
* long[]
* float[]
* double[]
* Object[]
* </pre>
*
* @see Streamable
*/
public class ObjectOutputStream extends DataOutputStream
{
/**
* Constructs an object output stream which will write its data to the
* supplied target stream.
*/
public ObjectOutputStream (OutputStream target)
{
super(target);
}
/**
* Writes a {@link Streamable} instance or one of the support object
* types to the output stream.
*/
public void writeObject (Object object)
throws IOException
{
// if the object to be written is null, simply write a zero
if (object == null) {
writeShort(0);
return;
}
// create our classmap if necessary
if (_classmap == null) {
_classmap = new HashMap();
}
try {
// otherwise, look up the class mapping record
Class sclass = object.getClass();
ClassMapping cmap = (ClassMapping)_classmap.get(sclass);
_current = object;
// create a class mapping for this class if we've not got one
if (cmap == null) {
// create a streamer instance and assign a code to this class
Streamer streamer = Streamer.getStreamer(sclass);
// we specifically do not inline the getStreamer() call
// into the ClassMapping constructor because we want to be
// sure not to call _nextCode++ if getStreamer() throws an
// exception
cmap = new ClassMapping(_nextCode++, sclass, streamer);
_classmap.put(sclass, cmap);
// make sure we didn't blow past our maximum class count
if (_nextCode <= 0) {
throw new RuntimeException("Too many unique classes " +
"written to ObjectOutputStream");
}
// writing a negative class code indicates that the class
// name will follow
writeShort(-cmap.code);
writeUTF(sclass.getName());
} else {
writeShort(cmap.code);
}
// now write the instance data
_streamer = cmap.streamer;
_streamer.writeObject(object, this, true);
} finally {
// clear out our current object references
_current = null;
_streamer = null;
}
}
/**
* Writes a {@link Streamable} instance or one of the support object
* types <em>without associated class metadata</em> to the output
* stream. The caller is responsible for knowing the exact class of
* the written object, creating an instance of such and calling {@link
* ObjectInputStream#readBareObject} to read its data from the stream.
*
* @param object the object to be written. It cannot be
* <code>null</code>.
*/
public void writeBareObject (Object object)
throws IOException
{
try {
// get the streamer for objects of this type
_streamer = Streamer.getStreamer(object.getClass());
_current = object;
// now write the instance data
_streamer.writeObject(object, this, true);
} finally {
// clear out our current object references
_current = null;
_streamer = null;
}
}
/**
* Uses the default streamable mechanism to write the contents of the
* object currently being streamed. This can only be called from
* within a <code>writeObject</code> implementation in a {@link
* Streamable} object.
*/
public void defaultWriteObject ()
throws IOException
{
// sanity check
if (_current == null) {
throw new RuntimeException(
"defaultWriteObject() called illegally.");
}
// Log.info("Writing default [cmap=" + _streamer +
// ", current=" + _current + "].");
// write the instance data
_streamer.writeObject(_current, this, false);
}
/**
* Used by a {@link Streamer} that is writing an array of {@link
* Streamable} instances.
*/
protected void setCurrent (Streamer streamer, Object current)
{
_streamer = streamer;
_current = current;
}
/** Used to map classes to numeric codes and the {@link Streamer}
* instance used to write them. */
protected HashMap _classmap;
/** A counter used to assign codes to streamed classes. */
protected short _nextCode = 1;
/** The object currently being written to the stream. */
protected Object _current;
/** The streamer being used currently. */
protected Streamer _streamer;
}
@@ -0,0 +1,33 @@
//
// $Id: SimpleStreamableObject.java,v 1.1 2002/07/23 05:42:34 mdb Exp $
package com.threerings.io;
import com.samskivert.util.StringUtil;
/**
* A simple serializable object implements the {@link Streamable}
* interface and provides a default {@link #toString} implementation which
* outputs all public members.
*/
public class SimpleStreamableObject implements Streamable
{
/**
* Generates a string representation of this instance.
*/
public String toString ()
{
StringBuffer buf = new StringBuffer("[");
toString(buf);
return buf.append("]").toString();
}
/**
* Handles the toString-ification of all public members. Derived
* classes can override and include non-public members if desired.
*/
protected void toString (StringBuffer buf)
{
StringUtil.fieldsToString(buf, this);
}
}
@@ -0,0 +1,29 @@
//
// $Id: Streamable.java,v 1.1 2002/07/23 05:42:34 mdb Exp $
package com.threerings.io;
/**
* Marks an object as streamable, meaning that it can be written to {@link
* ObjectOutputStream} instances and read from {@link ObjectInputStream}
* instances.
*
* <p> All non-<code>transient</code> public fields will be automatically
* written and restored for a {@link Streamable} instance. Classes that
* wish to stream non-public fields or customize the streaming process
* should implement methods with the following signatures:
*
* <p><code>
* public void writeObject ({@link ObjectOutputStream} out);
* public void readObject ({@link ObjectInputStream} in);
* </code>
*
* <p> They can then handle the entirety of the streaming process, or call
* {@link ObjectOutputStream#defaultWriteObject} and {@link
* ObjectInputStream#defaultReadObject} from within their
* <code>writeObject</code> and <code>readObject</code> methods to perform
* the standard streaming in addition to their customized behavior.
*/
public interface Streamable
{
}
+430
View File
@@ -0,0 +1,430 @@
//
// $Id: Streamer.java,v 1.1 2002/07/23 05:42:34 mdb Exp $
package com.threerings.io;
import java.lang.NoSuchMethodException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import com.samskivert.io.NestableIOException;
import com.threerings.presents.Log;
/**
* Handles the streaming of {@link Streamable} instances as well as a set
* of basic object types (see {@link ObjectOutputStream}). An instance of
* {@link Streamer} is created for each distinct class that implements
* {@link Streamable}. The {@link Streamer} reflects on the streamed class
* and caches the information necessary to efficiently read and write
* objects of the class in question.
*/
public class Streamer
{
/**
* Returns true if the supplied target class can be streamed using a
* streamer.
*/
public synchronized static boolean isStreamable (Class target)
{
// if we have a streamer registered for this class, then it's
// definitely streamable
if (_streamers.containsKey(target)) {
return true;
}
// return true if it is a streamable or already registered type,
// or an array of same
Class uclass = target.isArray() ? target.getComponentType() : target;
return (_streamers.containsKey(uclass) ||
Streamable.class.isAssignableFrom(uclass));
}
/**
* Obtains a {@link Streamer} that can be used to read and write
* objects of the specified target class. {@link Streamer} instances
* are shared among all {@link ObjectInputStream}s and {@link
* ObjectOutputStream}s.
*
* @throws IOException when a streamer is requested for an object that
* does not implement {@link Streamable} and is not one of the basic
* object types (@see {@link ObjectOutputStream}).
*/
public synchronized static Streamer getStreamer (Class target)
throws IOException
{
// if we have not yet initialized ourselves, do so now
if (_streamers == null) {
createStreamers();
}
Streamer stream = (Streamer)_streamers.get(target);
if (stream == null) {
// make sure this is a streamable class
if (!isStreamable(target)) {
throw new IOException("Requested to stream invalid class '" +
target.getName() + "'");
}
// create a streamer for this class and cache it
// Log.info("Creating a streamer for '" + target.getName() + "'.");
stream = new Streamer(target);
_streamers.put(target, stream);
}
return stream;
}
/**
* Writes the supplied object to the specified stream.
*
* @param object the instance to be written to the stream.
* @param out the stream to which to write the instance.
* @param useWriter whether or not to use the custom
* <code>writeObject</code> if one exists.
*/
public void writeObject (
Object object, ObjectOutputStream out, boolean useWriter)
throws IOException
{
// if we're supposed to and one exists, use the writer method
if (useWriter && _writer != null) {
try {
// Log.info("Writing with writer " +
// "[class=" + _target.getName() + "].");
_writer.invoke(object, new Object[] { out });
} catch (Throwable t) {
if (t instanceof InvocationTargetException) {
t = ((InvocationTargetException)t).getTargetException();
}
if (t instanceof IOException) {
throw (IOException)t;
}
String errmsg = "Failure invoking streamable writer " +
"[class=" + _target.getName() + "]";
throw new NestableIOException(errmsg, t);
}
return;
}
// if we're writing an array, do some special business
if (_target.isArray()) {
int length = Array.getLength(object);
out.writeInt(length);
// if the component class is final, we can be sure that all
// instances in the array will be of the same class and thus
// can serialize things more efficiently
int cmods = _target.getComponentType().getModifiers();
if ((cmods & Modifier.FINAL) != 0) {
// compute a mask indicating which elements are null and
// which are populated
ArrayMask mask = new ArrayMask(length);
for (int ii = 0; ii < length; ii++) {
if (Array.get(object, ii) != null) {
mask.set(ii);
}
}
// write that mask out to the stream
mask.writeTo(out);
// now write out the populated elements
for (int ii = 0; ii < length; ii++) {
Object element = Array.get(object, ii);
if (element == null) {
continue;
}
out.setCurrent(_delegate, element);
_delegate.writeObject(element, out, useWriter);
}
} else {
// otherwise we've got to write each array element with
// its own class identifier because it could be any
// derived class of the array element type
for (int ii = 0; ii < length; ii++) {
out.writeObject(Array.get(object, ii));
}
}
return;
}
// otherwise simply write out the fields via our field marshallers
int fcount = _fields.length;
for (int ii = 0; ii < fcount; ii++) {
Field field = _fields[ii];
FieldMarshaller fm = _marshallers[ii];
if (fm == null) {
String errmsg = "Unable to marshall field " +
"[class=" + _target.getName() +
", field=" + field.getName() +
", type=" + field.getType().getName() + "]";
throw new IOException(errmsg);
}
try {
// Log.info("Writing field [class=" + _target.getName() +
// ", field=" + field.getName() + "].");
fm.writeField(field, object, out);
} catch (Exception e) {
String errmsg = "Failure writing streamable field " +
"[class=" + _target.getName() +
", field=" + field.getName() + "]";
throw new NestableIOException(errmsg, e);
}
}
}
/**
* Creates a blank object that can subsequently be read by this
* streamer. Data may be read from the input stream as a result of
* this method (in the case of arrays, the length of the array must be
* read before creating the array).
*/
public Object createObject (ObjectInputStream in)
throws IOException
{
try {
// if our target class is an array type, read in the element
// count and create an array instance of the appropriate type
// and size
if (_target.isArray()) {
return Array.newInstance(
_target.getComponentType(), in.readInt());
} else {
return _target.newInstance();
}
} catch (InstantiationException ie) {
String errmsg = "Error instantiating object " +
"[type=" + _target.getName() + "]";
throw new NestableIOException(errmsg, ie);
} catch (IllegalAccessException iae) {
String errmsg = "Error instantiating object " +
"[type=" + _target.getName() + "]";
throw new NestableIOException(errmsg, iae);
}
}
/**
* Reads and populates the fields of the supplied object from the
* specified stream.
*
* @param object the instance to be read from the stream.
* @param out the stream from which to read the instance.
* @param useWriter whether or not to use the custom
* <code>readObject</code> if one exists.
*/
public void readObject (
Object object, ObjectInputStream in, boolean useReader)
throws IOException, ClassNotFoundException
{
// if we're supposed to and one exists, use the reader method
if (useReader && _reader != null) {
try {
// Log.info("Reading with reader " +
// "[class=" + _target.getName() + "].");
_reader.invoke(object, new Object[] { in });
} catch (Throwable t) {
if (t instanceof InvocationTargetException) {
t = ((InvocationTargetException)t).getTargetException();
}
if (t instanceof IOException) {
throw (IOException)t;
}
String errmsg = "Failure invoking streamable reader " +
"[class=" + _target.getName() + "]";
throw new NestableIOException(errmsg, t);
}
return;
}
// if we're reading in an array, do some special business
if (_target.isArray()) {
int length = Array.getLength(object);
// if the component class is final, we can be sure that all
// instances in the array will be of the same class and thus
// have serialized things more efficiently
int cmods = _target.getComponentType().getModifiers();
if ((cmods & Modifier.FINAL) != 0) {
// read in the nullness mask
ArrayMask mask = new ArrayMask();
mask.readFrom(in);
// now read in the array elements given that we know which
// elements to read
for (int ii = 0; ii < length; ii++) {
if (mask.isSet(ii)) {
Object element = _delegate.createObject(in);
in.setCurrent(_delegate, element);
_delegate.readObject(element, in, useReader);
Array.set(object, ii, element);
}
}
} else {
// otherwise we had to write each object out individually
for (int ii = 0; ii < length; ii++) {
Array.set(object, ii, in.readObject());
}
}
return;
}
// otherwise simply read the fields via our field marshallers
int fcount = _fields.length;
for (int ii = 0; ii < fcount; ii++) {
Field field = _fields[ii];
FieldMarshaller fm = _marshallers[ii];
if (fm == null) {
String errmsg = "Unable to marshall field " +
"[class=" + _target.getName() +
", field=" + field.getName() +
", type=" + field.getType().getName() + "]";
throw new IOException(errmsg);
}
try {
fm.readField(field, object, in);
} catch (Exception e) {
String errmsg = "Failure reading streamable field " +
"[class=" + _target.getName() +
", field=" + field.getName() + "]";
throw new NestableIOException(errmsg, e);
}
}
}
/**
* The constructor used by the basic streamers.
*/
protected Streamer ()
{
}
/**
* Constructs a streamer for the specified target class.
*/
protected Streamer (Class target)
throws IOException
{
// keep a handle on the class
_target = target;
// if our target class is an array, we want to get a handle on a
// streamer delegate that we'll use to stream our elements
if (_target.isArray()) {
_delegate = Streamer.getStreamer(_target.getComponentType());
// sanity check
if (_delegate == null) {
String errmsg = "Aiya! Streamer created for array type " +
"but we have no registered streamer for the element " +
"type [type=" + _target.getName() + "]";
throw new RuntimeException(errmsg);
}
// and that's all we'll need
return;
}
// reflect on all the public fields
Field[] fields = target.getFields();
ArrayList flist = new ArrayList();
int fcount = fields.length;
for (int ii = 0; ii < fcount; ii++) {
Field field = fields[ii];
int mods = field.getModifiers();
// skip static, transient and non-public fields
if (((mods & Modifier.STATIC) != 0) ||
((mods & Modifier.PUBLIC) == 0) ||
((mods & Modifier.TRANSIENT) != 0)) {
continue;
}
// otherwise add the field to the list
flist.add(field);
}
// convert the list of fields into an array
fcount = flist.size();
_fields = new Field[fcount];
flist.toArray(_fields);
// obtain field marshallers for all of our fields
_marshallers = new FieldMarshaller[fcount];
for (int ii = 0; ii < fcount; ii++) {
_marshallers[ii] = FieldMarshaller.getFieldMarshaller(_fields[ii]);
// Log.info("Using " + _marshallers[ii] + " for " +
// _fields[ii].getName() + ".");
}
// look up the reader and writer methods
try {
_reader = target.getMethod(READER_METHOD_NAME, READER_ARGS);
} catch (NoSuchMethodException nsme) {
// nothing to worry about, we just don't have one
}
try {
_writer = target.getMethod(WRITER_METHOD_NAME, WRITER_ARGS);
} catch (NoSuchMethodException nsme) {
// nothing to worry about, we just don't have one
}
}
/**
* Creates our streamers table and registers streamers for all of the
* basic types.
*/
protected static void createStreamers ()
{
_streamers = new HashMap();
// register all of the basic streamers
int bscount = BasicStreamers.BSTREAMER_TYPES.length;
for (int ii = 0; ii < bscount; ii++) {
_streamers.put(BasicStreamers.BSTREAMER_TYPES[ii],
BasicStreamers.BSTREAMER_INSTANCES[ii]);
}
}
/** The class for which this streamer instance is configured. */
protected Class _target;
/** If our target class is an array, this is a reference to a streamer
* that can stream our array elements, otherwise it is null. */
protected Streamer _delegate;
/** The non-transient, non-static public fields that we will stream
* when requested. */
protected Field[] _fields;
/** Field marshallers for each field that will be read or written in
* our objects. */
protected FieldMarshaller[] _marshallers;
/** A reference to the <code>readObject</code> method if one is
* defined by our target class. */
protected Method _reader;
/** A reference to the <code>writeObject</code> method if one is
* defined by our target class. */
protected Method _writer;
/** Contains the mapping from class names to configured streamer
* instances. */
protected static HashMap _streamers;
/** The name of the custom reader method. */
protected static final String READER_METHOD_NAME = "readObject";
/** The argument list for the custom reader method. */
protected static final Class[] READER_ARGS = { ObjectInputStream.class };
/** The name of the custom writer method. */
protected static final String WRITER_METHOD_NAME = "writeObject";
/** The argument list for the custom writer method. */
protected static final Class[] WRITER_ARGS = { ObjectOutputStream.class };
}