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