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:
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
Reference in New Issue
Block a user