From 9cd146a43c6ede5be4b207c63e974e6ebf813356 Mon Sep 17 00:00:00 2001 From: Michael Bayne Date: Wed, 30 May 2001 00:16:00 +0000 Subject: [PATCH] Created the facilities for transmitting distributed objects and derived classes over the wire (by inspecting their fields and sending those directly). git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@8 542714f4-19e9-0310-aa3c-eee0fc999fb1 --- .../presents/dobj/io/DObjectFactory.java | 48 ++++++-- .../presents/io/BooleanFieldMarshaller.java | 29 +++++ .../presents/io/DoubleFieldMarshaller.java | 29 +++++ .../presents/io/FieldMarshaller.java | 25 ++++ .../presents/io/FieldMarshallerRegistry.java | 67 +++++++++++ .../presents/io/FloatFieldMarshaller.java | 29 +++++ .../presents/io/IntFieldMarshaller.java | 29 +++++ .../presents/io/LongFieldMarshaller.java | 29 +++++ .../threerings/presents/io/Marshaller.java | 113 ++++++++++++++++++ .../presents/io/ShortFieldMarshaller.java | 29 +++++ .../presents/io/StringFieldMarshaller.java | 29 +++++ .../threerings/presents/net/AuthResponse.java | 4 +- .../presents/net/AuthResponseData.java | 15 +-- .../presents/net/ObjectResponse.java | 4 +- 14 files changed, 456 insertions(+), 23 deletions(-) create mode 100644 src/java/com/threerings/presents/io/BooleanFieldMarshaller.java create mode 100644 src/java/com/threerings/presents/io/DoubleFieldMarshaller.java create mode 100644 src/java/com/threerings/presents/io/FieldMarshaller.java create mode 100644 src/java/com/threerings/presents/io/FieldMarshallerRegistry.java create mode 100644 src/java/com/threerings/presents/io/FloatFieldMarshaller.java create mode 100644 src/java/com/threerings/presents/io/IntFieldMarshaller.java create mode 100644 src/java/com/threerings/presents/io/LongFieldMarshaller.java create mode 100644 src/java/com/threerings/presents/io/Marshaller.java create mode 100644 src/java/com/threerings/presents/io/ShortFieldMarshaller.java create mode 100644 src/java/com/threerings/presents/io/StringFieldMarshaller.java diff --git a/src/java/com/threerings/presents/dobj/io/DObjectFactory.java b/src/java/com/threerings/presents/dobj/io/DObjectFactory.java index 6371cf9ac..a24995bc6 100644 --- a/src/java/com/threerings/presents/dobj/io/DObjectFactory.java +++ b/src/java/com/threerings/presents/dobj/io/DObjectFactory.java @@ -1,12 +1,15 @@ // -// $Id: DObjectFactory.java,v 1.1 2001/05/29 03:27:59 mdb Exp $ +// $Id: DObjectFactory.java,v 1.2 2001/05/30 00:16:00 mdb Exp $ -package com.samskivert.cocktail.cher.dobj; +package com.samskivert.cocktail.cher.dobj.net; import java.io.IOException; import java.io.DataInputStream; import java.io.DataOutputStream; +import java.util.HashMap; +import com.samskivert.cocktail.cher.Log; +import com.samskivert.cocktail.cher.dobj.DObject; import com.samskivert.cocktail.cher.io.ObjectStreamException; /** @@ -16,22 +19,40 @@ import com.samskivert.cocktail.cher.io.ObjectStreamException; */ public class DObjectFactory { + /** + * Writes the supplied distributed object out to the specified data + * output stream. + */ public static void writeTo (DataOutputStream out, DObject dobj) throws IOException { - // first we write the class of the object to the stream - out.writeUTF(dobj.getClass().getName()); - // then we write the object itself - dobj.writeTo(out); + Log.info("Marshalling object: " + dobj); + // look up the marshaller for this object + Class clazz = dobj.getClass(); + Marshaller marsh = getMarshaller(clazz); + // then write the class of the object to the stream + out.writeUTF(clazz.getName()); + // then use the marshaller to write the object itself + marsh.writeTo(out, dobj); } + /** + * Reads a distributed object from the specified input stream. + */ public static DObject readFrom (DataInputStream in) throws IOException { try { + // read in the class name and create an instance of that class Class clazz = Class.forName(in.readUTF()); DObject dobj = (DObject)clazz.newInstance(); - dobj.readFrom(in); + Log.info("Unmarshalling object: " + dobj); + + // look up the marshaller for that class + Marshaller marsh = getMarshaller(clazz); + // use it to reconstitute the object from the stream + marsh.readFrom(in, dobj); + return dobj; } catch (Exception e) { @@ -39,4 +60,17 @@ public class DObjectFactory throw new ObjectStreamException(errmsg); } } + + protected static Marshaller getMarshaller (Class clazz) + throws IOException + { + Marshaller marsh = (Marshaller)_marshallers.get(clazz); + // create a new marshaller if we don't already have one + if (marsh == null) { + _marshallers.put(clazz, marsh = new Marshaller(clazz)); + } + return marsh; + } + + protected static HashMap _marshallers = new HashMap(); } diff --git a/src/java/com/threerings/presents/io/BooleanFieldMarshaller.java b/src/java/com/threerings/presents/io/BooleanFieldMarshaller.java new file mode 100644 index 000000000..4908579cf --- /dev/null +++ b/src/java/com/threerings/presents/io/BooleanFieldMarshaller.java @@ -0,0 +1,29 @@ +// +// $Id: BooleanFieldMarshaller.java,v 1.1 2001/05/30 00:16:00 mdb Exp $ + +package com.samskivert.cocktail.cher.dobj.net; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; + +import com.samskivert.cocktail.cher.dobj.DObject; + +public class BooleanFieldMarshaller implements FieldMarshaller +{ + /** This is the sort of field that we marshall. */ + public boolean prototype; + + public void writeTo (DataOutputStream out, Field field, DObject dobj) + throws IOException, IllegalAccessException + { + out.writeBoolean(field.getBoolean(dobj)); + } + + public void readFrom (DataInputStream in, Field field, DObject dobj) + throws IOException, IllegalAccessException + { + field.setBoolean(dobj, in.readBoolean()); + } +} diff --git a/src/java/com/threerings/presents/io/DoubleFieldMarshaller.java b/src/java/com/threerings/presents/io/DoubleFieldMarshaller.java new file mode 100644 index 000000000..c2753594f --- /dev/null +++ b/src/java/com/threerings/presents/io/DoubleFieldMarshaller.java @@ -0,0 +1,29 @@ +// +// $Id: DoubleFieldMarshaller.java,v 1.1 2001/05/30 00:16:00 mdb Exp $ + +package com.samskivert.cocktail.cher.dobj.net; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; + +import com.samskivert.cocktail.cher.dobj.DObject; + +public class DoubleFieldMarshaller implements FieldMarshaller +{ + /** This is the sort of field that we marshall. */ + public double prototype; + + public void writeTo (DataOutputStream out, Field field, DObject dobj) + throws IOException, IllegalAccessException + { + out.writeDouble(field.getDouble(dobj)); + } + + public void readFrom (DataInputStream in, Field field, DObject dobj) + throws IOException, IllegalAccessException + { + field.setDouble(dobj, in.readDouble()); + } +} diff --git a/src/java/com/threerings/presents/io/FieldMarshaller.java b/src/java/com/threerings/presents/io/FieldMarshaller.java new file mode 100644 index 000000000..d27699952 --- /dev/null +++ b/src/java/com/threerings/presents/io/FieldMarshaller.java @@ -0,0 +1,25 @@ +// +// $Id: FieldMarshaller.java,v 1.1 2001/05/30 00:16:00 mdb Exp $ + +package com.samskivert.cocktail.cher.dobj.net; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; + +import com.samskivert.cocktail.cher.dobj.DObject; + +/** + * A field marshaller knows how to marshall and unmarshall a particular + * data type. It is called upon to do the actual on the wire + * encoding/decoding of a field. + */ +public interface FieldMarshaller +{ + public void writeTo (DataOutputStream out, Field field, DObject dobj) + throws IOException, IllegalAccessException; + + public void readFrom (DataInputStream in, Field field, DObject dobj) + throws IOException, IllegalAccessException; +} diff --git a/src/java/com/threerings/presents/io/FieldMarshallerRegistry.java b/src/java/com/threerings/presents/io/FieldMarshallerRegistry.java new file mode 100644 index 000000000..ce4f219fa --- /dev/null +++ b/src/java/com/threerings/presents/io/FieldMarshallerRegistry.java @@ -0,0 +1,67 @@ +// +// $Id: FieldMarshallerRegistry.java,v 1.1 2001/05/30 00:16:00 mdb Exp $ + +package com.samskivert.cocktail.cher.dobj.net; + +import java.lang.reflect.Field; +import java.util.Hashtable; +import com.samskivert.cocktail.cher.Log; + +/** + * Field marshaller instances are registered for each type of field that + * is supported in a distributed object. + */ +public class FieldMarshallerRegistry +{ + /** + * Returns a field marshaller that can marshall and unmarshall the + * specified type of field. If no marshaller is registered for that + * field type, an illegal argument exception will be thrown. + * Distributed objects must not contain fields that cannot be + * transmitted on the wire. + */ + public static FieldMarshaller getMarshaller (Field field) + { + Class clazz = field.getType(); + FieldMarshaller marsh = (FieldMarshaller)_registry.get(clazz); + if (marsh == null) { + String errmsg = "No field marshaller registered for fields " + + "of type " + clazz.getName() + "."; + throw new IllegalArgumentException(errmsg); + } + return marsh; + } + + /** + * Registers the specified field marshaller as the one to use for + * fields of the specified class. This assumes that the field + * marshaller implementation provides a prototype field of the type it + * knows how to marshall by the name of prototype. + * + * @param clazz the field marshaller class to be registered. + */ + protected static void registerMarshaller (Class clazz) + { + try { + _registry.put(clazz.getField("prototype").getType(), + clazz.newInstance()); + } catch (Exception e) { + Log.warning("Unable to register field marshaller " + + "[class=" + clazz.getName() + "]."); + Log.logStackTrace(e); + } + } + + protected static Hashtable _registry = new Hashtable(); + + static { + // register our field marshallers + registerMarshaller(BooleanFieldMarshaller.class); + registerMarshaller(ShortFieldMarshaller.class); + registerMarshaller(IntFieldMarshaller.class); + registerMarshaller(LongFieldMarshaller.class); + registerMarshaller(FloatFieldMarshaller.class); + registerMarshaller(DoubleFieldMarshaller.class); + registerMarshaller(StringFieldMarshaller.class); + } +} diff --git a/src/java/com/threerings/presents/io/FloatFieldMarshaller.java b/src/java/com/threerings/presents/io/FloatFieldMarshaller.java new file mode 100644 index 000000000..721b8f766 --- /dev/null +++ b/src/java/com/threerings/presents/io/FloatFieldMarshaller.java @@ -0,0 +1,29 @@ +// +// $Id: FloatFieldMarshaller.java,v 1.1 2001/05/30 00:16:00 mdb Exp $ + +package com.samskivert.cocktail.cher.dobj.net; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; + +import com.samskivert.cocktail.cher.dobj.DObject; + +public class FloatFieldMarshaller implements FieldMarshaller +{ + /** This is the sort of field that we marshall. */ + public float prototype; + + public void writeTo (DataOutputStream out, Field field, DObject dobj) + throws IOException, IllegalAccessException + { + out.writeFloat(field.getFloat(dobj)); + } + + public void readFrom (DataInputStream in, Field field, DObject dobj) + throws IOException, IllegalAccessException + { + field.setFloat(dobj, in.readFloat()); + } +} diff --git a/src/java/com/threerings/presents/io/IntFieldMarshaller.java b/src/java/com/threerings/presents/io/IntFieldMarshaller.java new file mode 100644 index 000000000..f46575663 --- /dev/null +++ b/src/java/com/threerings/presents/io/IntFieldMarshaller.java @@ -0,0 +1,29 @@ +// +// $Id: IntFieldMarshaller.java,v 1.1 2001/05/30 00:16:00 mdb Exp $ + +package com.samskivert.cocktail.cher.dobj.net; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; + +import com.samskivert.cocktail.cher.dobj.DObject; + +public class IntFieldMarshaller implements FieldMarshaller +{ + /** This is the sort of field that we marshall. */ + public int prototype; + + public void writeTo (DataOutputStream out, Field field, DObject dobj) + throws IOException, IllegalAccessException + { + out.writeInt(field.getInt(dobj)); + } + + public void readFrom (DataInputStream in, Field field, DObject dobj) + throws IOException, IllegalAccessException + { + field.setInt(dobj, in.readInt()); + } +} diff --git a/src/java/com/threerings/presents/io/LongFieldMarshaller.java b/src/java/com/threerings/presents/io/LongFieldMarshaller.java new file mode 100644 index 000000000..07a2665ac --- /dev/null +++ b/src/java/com/threerings/presents/io/LongFieldMarshaller.java @@ -0,0 +1,29 @@ +// +// $Id: LongFieldMarshaller.java,v 1.1 2001/05/30 00:16:00 mdb Exp $ + +package com.samskivert.cocktail.cher.dobj.net; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; + +import com.samskivert.cocktail.cher.dobj.DObject; + +public class LongFieldMarshaller implements FieldMarshaller +{ + /** This is the sort of field that we marshall. */ + public long prototype; + + public void writeTo (DataOutputStream out, Field field, DObject dobj) + throws IOException, IllegalAccessException + { + out.writeLong(field.getLong(dobj)); + } + + public void readFrom (DataInputStream in, Field field, DObject dobj) + throws IOException, IllegalAccessException + { + field.setLong(dobj, in.readLong()); + } +} diff --git a/src/java/com/threerings/presents/io/Marshaller.java b/src/java/com/threerings/presents/io/Marshaller.java new file mode 100644 index 000000000..714d98556 --- /dev/null +++ b/src/java/com/threerings/presents/io/Marshaller.java @@ -0,0 +1,113 @@ +// +// $Id: Marshaller.java,v 1.1 2001/05/30 00:16:00 mdb Exp $ + +package com.samskivert.cocktail.cher.dobj.net; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Comparator; + +import com.samskivert.cocktail.cher.Log; +import com.samskivert.cocktail.cher.dobj.DObject; +import com.samskivert.cocktail.cher.io.ObjectStreamException; + +/** + * The marshaller inspects the class with which it is constructed and + * caches the reflection information it needs to marshall and unmarshall + * instances of that class. + */ +public class Marshaller +{ + public Marshaller (Class clazz) + { + // we introspect on the class and cache the public data members + _fields = clazz.getFields(); + // sort the fields so that they are written and read in the same + // order on all VMs + Arrays.sort(_fields, FIELD_COMP); + + // now select field marshallers for each of the fields based on + // their type + _marshallers = new FieldMarshaller[_fields.length]; + for (int i = 0; i < _fields.length; i++) { + _marshallers[i] = + FieldMarshallerRegistry.getMarshaller(_fields[i]); + // Log.info("Assigned [field=" + _fields[i] + + // ", marshaller=" + _marshallers[i] + "]."); + } + } + + /** + * Writes out all of the fields of the specified distributed object to + * the supplied data output stream. + */ + public void writeTo (DataOutputStream out, DObject dobj) + throws IOException + { + // we simply iterate over our marshallers, writing each field + // out in succession + for (int i = 0; i < _fields.length; i++) { + try { + _marshallers[i].writeTo(out, _fields[i], dobj); + + } catch (IllegalAccessException iae) { + // this shouldn't happen because we only attempt to marshall + // public fields, but we'll pass it on none the less + String errmsg = "Unable to marshall dobj field " + + "[field=" + _fields[i].getName() + + ", dobj=" + dobj + "]."; + throw new ObjectStreamException(errmsg); + } + } + } + + /** + * Reads in all of the fields of the specified distributed object from + * the supplied data input stream. + */ + public void readFrom (DataInputStream in, DObject dobj) + throws IOException + { + // we simply iterate over our marshallers, reading each field in + // in succession + for (int i = 0; i < _fields.length; i++) { + try { + _marshallers[i].readFrom(in, _fields[i], dobj); + + } catch (IllegalAccessException iae) { + // this shouldn't happen because we only attempt to + // unmarshall public fields, but we'll pass it on anyway + String errmsg = "Unable to unmarshall dobj field " + + "[field=" + _fields[i].getName() + + ", dobj=" + dobj + "]."; + throw new ObjectStreamException(errmsg); + } + } + } + + /** + * Used to sort object fields into a predictable order. + */ + protected static class FieldComparator implements Comparator + { + public int compare (Object o1, Object o2) + { + return ((Field)o1).getName().compareTo(((Field)o2).getName()); + } + + public boolean equals (Object obj) + { + // we don't care about comparing this comparator to others + return obj == this; + } + } + + protected Field[] _fields; + protected FieldMarshaller[] _marshallers; + + /** We use this to sort the fields into a predictable order. */ + protected static final FieldComparator FIELD_COMP = new FieldComparator(); +} diff --git a/src/java/com/threerings/presents/io/ShortFieldMarshaller.java b/src/java/com/threerings/presents/io/ShortFieldMarshaller.java new file mode 100644 index 000000000..9d3a09bc3 --- /dev/null +++ b/src/java/com/threerings/presents/io/ShortFieldMarshaller.java @@ -0,0 +1,29 @@ +// +// $Id: ShortFieldMarshaller.java,v 1.1 2001/05/30 00:16:00 mdb Exp $ + +package com.samskivert.cocktail.cher.dobj.net; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; + +import com.samskivert.cocktail.cher.dobj.DObject; + +public class ShortFieldMarshaller implements FieldMarshaller +{ + /** This is the sort of field that we marshall. */ + public short prototype; + + public void writeTo (DataOutputStream out, Field field, DObject dobj) + throws IOException, IllegalAccessException + { + out.writeShort(field.getShort(dobj)); + } + + public void readFrom (DataInputStream in, Field field, DObject dobj) + throws IOException, IllegalAccessException + { + field.setShort(dobj, in.readShort()); + } +} diff --git a/src/java/com/threerings/presents/io/StringFieldMarshaller.java b/src/java/com/threerings/presents/io/StringFieldMarshaller.java new file mode 100644 index 000000000..cea6b335d --- /dev/null +++ b/src/java/com/threerings/presents/io/StringFieldMarshaller.java @@ -0,0 +1,29 @@ +// +// $Id: StringFieldMarshaller.java,v 1.1 2001/05/30 00:16:00 mdb Exp $ + +package com.samskivert.cocktail.cher.dobj.net; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; +import java.lang.reflect.Field; + +import com.samskivert.cocktail.cher.dobj.DObject; + +public class StringFieldMarshaller implements FieldMarshaller +{ + /** This is the sort of field that we marshall. */ + public String prototype; + + public void writeTo (DataOutputStream out, Field field, DObject dobj) + throws IOException, IllegalAccessException + { + out.writeUTF((String)field.get(dobj)); + } + + public void readFrom (DataInputStream in, Field field, DObject dobj) + throws IOException, IllegalAccessException + { + field.set(dobj, in.readUTF()); + } +} diff --git a/src/java/com/threerings/presents/net/AuthResponse.java b/src/java/com/threerings/presents/net/AuthResponse.java index b903944af..b1cd182f5 100644 --- a/src/java/com/threerings/presents/net/AuthResponse.java +++ b/src/java/com/threerings/presents/net/AuthResponse.java @@ -1,5 +1,5 @@ // -// $Id: AuthResponse.java,v 1.3 2001/05/29 03:27:59 mdb Exp $ +// $Id: AuthResponse.java,v 1.4 2001/05/30 00:16:00 mdb Exp $ package com.samskivert.cocktail.cher.net; @@ -7,7 +7,7 @@ import java.io.IOException; import java.io.DataInputStream; import java.io.DataOutputStream; -import com.samskivert.cocktail.cher.dobj.DObjectFactory; +import com.samskivert.cocktail.cher.dobj.net.DObjectFactory; /** * The auth response communicates authentication success or failure as diff --git a/src/java/com/threerings/presents/net/AuthResponseData.java b/src/java/com/threerings/presents/net/AuthResponseData.java index 6a941fff9..66414b0f4 100644 --- a/src/java/com/threerings/presents/net/AuthResponseData.java +++ b/src/java/com/threerings/presents/net/AuthResponseData.java @@ -1,5 +1,5 @@ // -// $Id: AuthResponseData.java,v 1.2 2001/05/29 03:27:59 mdb Exp $ +// $Id: AuthResponseData.java,v 1.3 2001/05/30 00:16:00 mdb Exp $ package com.samskivert.cocktail.cher.net; @@ -23,17 +23,8 @@ public class AuthResponseData extends DObject */ public String code; - public void writeTo (DataOutputStream out) - throws IOException + public String toString () { - super.writeTo(out); - out.writeUTF(code); - } - - public void readFrom (DataInputStream in) - throws IOException - { - super.readFrom(in); - code = in.readUTF(); + return "[code=" + code + "]"; } } diff --git a/src/java/com/threerings/presents/net/ObjectResponse.java b/src/java/com/threerings/presents/net/ObjectResponse.java index 7ad026b03..b8f378996 100644 --- a/src/java/com/threerings/presents/net/ObjectResponse.java +++ b/src/java/com/threerings/presents/net/ObjectResponse.java @@ -1,5 +1,5 @@ // -// $Id: ObjectResponse.java,v 1.3 2001/05/29 03:28:50 mdb Exp $ +// $Id: ObjectResponse.java,v 1.4 2001/05/30 00:16:00 mdb Exp $ package com.samskivert.cocktail.cher.net; @@ -8,7 +8,7 @@ import java.io.DataInputStream; import java.io.DataOutputStream; import com.samskivert.cocktail.cher.dobj.DObject; -import com.samskivert.cocktail.cher.dobj.DObjectFactory; +import com.samskivert.cocktail.cher.dobj.net.DObjectFactory; public class ObjectResponse extends DownstreamMessage {