Improvements to Collection streaming support.

Any Collection can now be streamed. You can send Collections.emptyMap()
back to a client, for example.
(It will unstream as a HashMap on the other side.)

This actually streams everything to the exact same bytes as before.

Using specific classes is discouraged: ArrayList, StreamableArrayList...
If a Streamer is created for a class that has a field of that type,
a warning will be generated, urging you to change the field definition
to the interface.

Also: that setup is now deferred until needed, because in clyde/projectx
there are a number of Streamable classes that have fields of specific
classes, like ArrayIntSet for example, but don't use standard streaming
to transmit them. In that case it's totally cool for them to have a more
specific type.

Since that setup is potentially deferred, one change may be observed
for a class that uses custom readObject() and writeObject() methods
but still calls defaultReadObject(). The field marshallers will now
be configured a little later in the game than before.

But, we're actually saving a bit of memory by not setting up those
marshallers for streamable classes that never use them. Woo.

Please let me know if this causes any space shuttle explosions or
spikes the punch at your quinceanera.


git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6348 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
Ray Greenwell
2010-12-09 02:39:01 +00:00
parent e91e11a765
commit 0b77c89fa7
6 changed files with 114 additions and 40 deletions
@@ -67,12 +67,9 @@ public class BasicStreamers
.put(double[].class, new DoubleArrayStreamer())
.put(Object[].class, new ObjectArrayStreamer())
.put(List.class, ListStreamer.INSTANCE)
.put(ArrayList.class, ListStreamer.INSTANCE)
.put(Collection.class, ListStreamer.INSTANCE)
.put(Set.class, SetStreamer.INSTANCE)
.put(HashSet.class, SetStreamer.INSTANCE)
.put(Map.class, MapStreamer.INSTANCE)
.put(HashMap.class, MapStreamer.INSTANCE)
.build();
/** Streams {@link Boolean} instances. */
@@ -26,7 +26,6 @@ import java.lang.reflect.Method;
import java.lang.reflect.ReflectPermission;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import com.google.common.collect.Maps;
@@ -117,10 +116,20 @@ public abstract class FieldMarshaller
// if we have an exact match, use that
FieldMarshaller fm = _marshallers.get(ftype);
if (fm == null) {
Class<?> collClass = Streamer.getCollectionClass(ftype);
if (collClass != null && !collClass.equals(ftype)) {
log.warning("Specific field types are discouraged for Collections and Maps.",
"class", field.getDeclaringClass(), "field", field.getName(),
"type", ftype, "shouldBe", collClass);
fm = _marshallers.get(collClass);
}
// otherwise if the class is a pure interface or streamable, use the streamable marshaller
if (fm == null && (ftype.isInterface() || Streamer.isStreamable(ftype))) {
fm = _marshallers.get(Streamable.class);
// otherwise if the class is a pure interface or streamable,
// use the streamable marshaller
if (fm == null && (ftype.isInterface() || Streamer.isStreamable(ftype))) {
fm = _marshallers.get(Streamable.class);
}
}
return fm;
@@ -395,7 +404,7 @@ public abstract class FieldMarshaller
}
/** Contains a mapping from field type to field marshaller instance for that type. */
protected static HashMap<Class<?>, FieldMarshaller> _marshallers;
protected static Map<Class<?>, FieldMarshaller> _marshallers;
/** The field marshaller for pooled strings. */
protected static FieldMarshaller _internMarshaller;
@@ -21,8 +21,8 @@
package com.threerings.io;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
@@ -337,10 +337,10 @@ public class ObjectInputStream extends DataInputStream
/** Used to map classes to numeric codes and the {@link Streamer} instance used to write
* them. */
protected ArrayList<ClassMapping> _classmap;
protected List<ClassMapping> _classmap;
/** Maps numeric codes to pooled strings. */
protected ArrayList<String> _internmap;
protected List<String> _internmap;
/** The object currently being read from the stream. */
protected Object _current;
@@ -352,7 +352,7 @@ public class ObjectInputStream extends DataInputStream
protected ClassLoader _loader;
/** An optional set of class name translations to use when unserializing objects. */
protected HashMap<String, String> _translations;
protected Map<String, String> _translations;
/** Used to activate verbose debug logging. */
protected static final boolean STREAM_DEBUG = false;
@@ -174,6 +174,14 @@ public class ObjectOutputStream extends DataOutputStream
// create a class mapping for this class if we've not got one
if (cmap == null) {
// see if we just want to use an existing class mapping
Class<?> collClass = Streamer.getCollectionClass(sclass);
if (collClass != null && !collClass.equals(sclass)) {
cmap = writeClassMapping(collClass);
_classmap.put(sclass, cmap);
return cmap;
}
// 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
+83 -23
View File
@@ -30,8 +30,10 @@ import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.io.IOException;
@@ -74,9 +76,11 @@ public class Streamer
return true;
}
// if it's not an array, it must be streamable
// if it's not an array, it must be streamable, or a Collection or Map
if (!target.isArray()) {
return Streamable.class.isAssignableFrom(target);
return Streamable.class.isAssignableFrom(target) ||
Collection.class.isAssignableFrom(target) ||
Map.class.isAssignableFrom(target);
}
// otherwise extract the component type and loop back around for another check...
@@ -95,6 +99,31 @@ public class Streamer
((Enum<?>)object).getDeclaringClass() : object.getClass();
}
/**
* If the specified class is not Streamable and is a Collection type, return the
* most specific supported Collection interface type; otherwise return null.
*/
public static Class<?> getCollectionClass (Class<?> clazz)
{
if (Streamable.class.isAssignableFrom(clazz)) {
// the class is natively streamable, let's ignore it
return null;
}
if (Map.class.isAssignableFrom(clazz)) {
return Map.class;
}
if (Set.class.isAssignableFrom(clazz)) {
return Set.class;
}
if (List.class.isAssignableFrom(clazz)) {
return List.class;
}
if (Collection.class.isAssignableFrom(clazz)) {
return Collection.class;
}
return null;
}
/**
* 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
@@ -115,28 +144,37 @@ public class Streamer
Streamer stream = _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() + "'");
// see if it's a collection type
Class<?> collClass = getCollectionClass(target);
if (collClass != null) {
stream = getStreamer(collClass);
} else {
// otherwise 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
if (ObjectInputStream.STREAM_DEBUG) {
log.info("Creating a streamer for '" + target.getName() + "'.");
}
// create our streamer in a privileged block so that it can introspect on the to be
// streamed class
try {
stream = AccessController.doPrivileged(
new PrivilegedExceptionAction<Streamer>() {
public Streamer run () throws IOException {
return new Streamer(target);
}
});
} catch (PrivilegedActionException pae) {
throw (IOException) pae.getCause();
}
}
// create a streamer for this class and cache it
if (ObjectInputStream.STREAM_DEBUG) {
log.info("Creating a streamer for '" + target.getName() + "'.");
}
// create our streamer in a privileged block so that it can introspect on the to be
// streamed class
try {
stream = AccessController.doPrivileged(new PrivilegedExceptionAction<Streamer>() {
public Streamer run () throws IOException {
return new Streamer(target);
}
});
} catch (PrivilegedActionException pae) {
throw (IOException) pae.getCause();
}
// cache the streamer by the class type
_streamers.put(target, stream);
}
return stream;
@@ -224,6 +262,9 @@ public class Streamer
}
// otherwise simply write out the fields via our field marshallers
if (_marshallers == null) {
initMarshallers();
}
int fcount = _fields.length;
for (int ii = 0; ii < fcount; ii++) {
Field field = _fields[ii];
@@ -373,6 +414,9 @@ public class Streamer
}
// otherwise simply read the fields via our field marshallers
if (_marshallers == null) {
initMarshallers();
}
int fcount = _fields.length;
for (int ii = 0; ii < fcount; ii++) {
Field field = _fields[ii];
@@ -457,6 +501,10 @@ public class Streamer
}
// and that's all we'll need
return;
} else if (_target.isEnum()) {
// nothing extra for enums
return;
}
// look up the reader and writer methods
@@ -471,9 +519,21 @@ public class Streamer
// nothing to worry about, we just don't have one
}
// let's try to fail fast and initialize the marshallers now if we know
// we're going to need them.
if ((_reader == null) || (_writer == null)) {
initMarshallers();
}
}
/**
* Initialize the marshallers.
*/
protected void initMarshallers ()
{
// reflect on all the object's fields and remove all marked with NotStreamable
List<Field> fields = Lists.newArrayList();
ClassUtil.getFields(target, fields);
ClassUtil.getFields(_target, fields);
_fields = Iterables.toArray(Iterables.filter(fields, _isStreamableFieldPred), Field.class);
int fcount = _fields.length;
@@ -23,7 +23,7 @@ package com.threerings.presents.dobj;
import java.util.List;
import com.threerings.util.StreamableArrayList;
import com.google.common.collect.Lists;
import com.threerings.presents.net.Transport;
@@ -57,7 +57,7 @@ public class CompoundEvent extends DEvent
_omgr = omgr;
_target = target;
_events = StreamableArrayList.newList();
_events = Lists.newArrayList();
}
/**
@@ -176,7 +176,7 @@ public class CompoundEvent extends DEvent
{
buf.append("COMPOUND:");
super.toString(buf);
for (int ii = 0; ii < _events.size(); ii++) {
for (int ii = 0, nn = _events.size(); ii < nn; ii++) {
buf.append(", ").append(_events.get(ii));
}
}
@@ -188,5 +188,5 @@ public class CompoundEvent extends DEvent
protected transient DObject _target;
/** A list of the events associated with this compound event. */
protected StreamableArrayList<DEvent> _events;
protected List<DEvent> _events;
}