Streaming changes.
- Fix the bug with streaming an object that had a field typed as merely 'List'. This will result in an incompatible change. Notably, BootstrapData has a List field, but this shouldn't be accessed until after versions are checked. The bug also meant that such fields could only hold an ArrayList. Now they may hold any List type and will turn into an ArrayList on the other end... - Support Map and Set streaming in the same way. - Support using unmodifiedUTF if the "com.threerings.io.unmodifiedUTFStreaming" system property is set to the String "true". We can roll this back if it freaks something out, but I'd rather push forward. git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@6116 542714f4-19e9-0310-aa3c-eee0fc999fb1
This commit is contained in:
@@ -22,15 +22,20 @@
|
|||||||
package com.threerings.io;
|
package com.threerings.io;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.RandomAccess;
|
import java.util.Set;
|
||||||
|
|
||||||
import java.io.EOFException;
|
import java.io.EOFException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.google.common.collect.Maps;
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Code to read and write basic object types (like arrays of primitives, {@link Integer} instances,
|
* Code to read and write basic object types (like arrays of primitives, {@link Integer} instances,
|
||||||
@@ -38,30 +43,37 @@ import com.google.common.collect.Maps;
|
|||||||
*/
|
*/
|
||||||
public class BasicStreamers
|
public class BasicStreamers
|
||||||
{
|
{
|
||||||
public static Map<Class<?>, Streamer> BSTREAMERS = Maps.newLinkedHashMap();
|
public static final Map<Class<?>, Streamer> BSTREAMERS =
|
||||||
static {
|
ImmutableMap.<Class<?>, Streamer>builder()
|
||||||
BSTREAMERS.put(Boolean.class, new BooleanStreamer());
|
.put(Boolean.class, new BooleanStreamer())
|
||||||
BSTREAMERS.put(Byte.class, new ByteStreamer());
|
.put(Byte.class, new ByteStreamer())
|
||||||
BSTREAMERS.put(Short.class, new ShortStreamer());
|
.put(Short.class, new ShortStreamer())
|
||||||
BSTREAMERS.put(Character.class, new CharacterStreamer());
|
.put(Character.class, new CharacterStreamer())
|
||||||
BSTREAMERS.put(Integer.class, new IntegerStreamer());
|
.put(Integer.class, new IntegerStreamer())
|
||||||
BSTREAMERS.put(Long.class, new LongStreamer());
|
.put(Long.class, new LongStreamer())
|
||||||
BSTREAMERS.put(Float.class, new FloatStreamer());
|
.put(Float.class, new FloatStreamer())
|
||||||
BSTREAMERS.put(Double.class, new DoubleStreamer());
|
.put(Double.class, new DoubleStreamer())
|
||||||
BSTREAMERS.put(Class.class, new ClassStreamer());
|
.put(Class.class, new ClassStreamer())
|
||||||
BSTREAMERS.put(String.class, new StringStreamer());
|
.put(String.class, Boolean.getBoolean("com.threerings.io.unmodifiedUTFStreaming")
|
||||||
BSTREAMERS.put(boolean[].class, new BooleanArrayStreamer());
|
? new UnmodifiedUTFStringStreamer()
|
||||||
BSTREAMERS.put(byte[].class, new ByteArrayStreamer());
|
: new StringStreamer())
|
||||||
BSTREAMERS.put(short[].class, new ShortArrayStreamer());
|
.put(boolean[].class, new BooleanArrayStreamer())
|
||||||
BSTREAMERS.put(char[].class, new CharArrayStreamer());
|
.put(byte[].class, new ByteArrayStreamer())
|
||||||
BSTREAMERS.put(int[].class, new IntArrayStreamer());
|
.put(short[].class, new ShortArrayStreamer())
|
||||||
BSTREAMERS.put(long[].class, new LongArrayStreamer());
|
.put(char[].class, new CharArrayStreamer())
|
||||||
BSTREAMERS.put(float[].class, new FloatArrayStreamer());
|
.put(int[].class, new IntArrayStreamer())
|
||||||
BSTREAMERS.put(double[].class, new DoubleArrayStreamer());
|
.put(long[].class, new LongArrayStreamer())
|
||||||
BSTREAMERS.put(Object[].class, new ObjectArrayStreamer());
|
.put(float[].class, new FloatArrayStreamer())
|
||||||
BSTREAMERS.put(List.class, new ListStreamer());
|
.put(double[].class, new DoubleArrayStreamer())
|
||||||
BSTREAMERS.put(ArrayList.class, new ListStreamer());
|
.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. */
|
/** Streams {@link Boolean} instances. */
|
||||||
public static class BooleanStreamer extends BasicStreamer
|
public static class BooleanStreamer extends BasicStreamer
|
||||||
@@ -225,7 +237,7 @@ public class BasicStreamers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Streams {@link String} instances. */
|
/** Streams {@link String} instances, using modifiedUTF. */
|
||||||
public static class StringStreamer extends BasicStreamer
|
public static class StringStreamer extends BasicStreamer
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
@@ -243,6 +255,26 @@ public class BasicStreamers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Streams {@link String} instances, without using modifiedUTF. */
|
||||||
|
public static class UnmodifiedUTFStringStreamer extends BasicStreamer
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public Object createObject (ObjectInputStream in)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
System.err.println("Read unmodifiedUTF");
|
||||||
|
return in.readUnmodifiedUTF();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeObject (Object object, ObjectOutputStream out, boolean useWriter)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
out.writeUnmodifiedUTF((String)object);
|
||||||
|
System.err.println("Wrote unmodifiedUTF");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Streams arrays of booleans. */
|
/** Streams arrays of booleans. */
|
||||||
public static class BooleanArrayStreamer extends BasicStreamer
|
public static class BooleanArrayStreamer extends BasicStreamer
|
||||||
{
|
{
|
||||||
@@ -405,24 +437,114 @@ public class BasicStreamers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Streams {@link List} instances. */
|
/** A building-block class for streaming Collections. */
|
||||||
public static class ListStreamer extends BasicStreamer
|
protected abstract static class CollectionStreamer extends BasicStreamer
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public Object createObject (ObjectInputStream in)
|
public Object createObject (ObjectInputStream in)
|
||||||
throws IOException, ClassNotFoundException
|
throws IOException, ClassNotFoundException
|
||||||
{
|
{
|
||||||
return readList(in);
|
int size = in.readInt();
|
||||||
|
Collection<Object> coll = createCollection(size);
|
||||||
|
for (int ii = 0; ii < size; ii++) {
|
||||||
|
coll.add(in.readObject());
|
||||||
|
}
|
||||||
|
return coll;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeObject (Object object, ObjectOutputStream out, boolean useWriter)
|
public void writeObject (Object object, ObjectOutputStream out, boolean useWriter)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
writeList(out, (List<?>)object);
|
Collection<?> coll = (Collection<?>)object;
|
||||||
|
out.writeInt(coll.size());
|
||||||
|
for (Object o : coll) {
|
||||||
|
out.writeObject(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to create the collection being read.
|
||||||
|
*
|
||||||
|
* @param size the exact size of the collection.
|
||||||
|
*/
|
||||||
|
protected abstract Collection<Object> createCollection (int size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Streams {@link List} instances. */
|
||||||
|
public static class ListStreamer extends CollectionStreamer
|
||||||
|
{
|
||||||
|
/** A singleton instance. */
|
||||||
|
public static final ListStreamer INSTANCE = new ListStreamer();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Collection<Object> createCollection (int size)
|
||||||
|
{
|
||||||
|
return Lists.newArrayListWithCapacity(size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Streams {@link Set} instances. */
|
||||||
|
public static class SetStreamer extends CollectionStreamer
|
||||||
|
{
|
||||||
|
/** A singleton instance. */
|
||||||
|
public static final SetStreamer INSTANCE = new SetStreamer();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Collection<Object> createCollection (int size)
|
||||||
|
{
|
||||||
|
return Sets.newHashSetWithExpectedSize(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Streams {@link Map} instances. */
|
||||||
|
public static class MapStreamer extends BasicStreamer
|
||||||
|
{
|
||||||
|
/** A singleton instance. */
|
||||||
|
public static final MapStreamer INSTANCE = new MapStreamer();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object createObject (ObjectInputStream in)
|
||||||
|
throws IOException, ClassNotFoundException
|
||||||
|
{
|
||||||
|
int size = in.readInt();
|
||||||
|
Map<Object, Object> map = createMap(size);
|
||||||
|
for (int ii = 0; ii < size; ii++) {
|
||||||
|
map.put(in.readObject(), in.readObject());
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeObject (Object object, ObjectOutputStream out, boolean useWriter)
|
||||||
|
throws IOException
|
||||||
|
{
|
||||||
|
Map<?, ?> map = (Map<?, ?>)object;
|
||||||
|
out.writeInt(map.size());
|
||||||
|
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||||
|
out.writeObject(entry.getKey());
|
||||||
|
out.writeObject(entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overrideable if a subclass is desired to stream a different kind of map.
|
||||||
|
*/
|
||||||
|
protected Map<Object, Object> createMap (int size)
|
||||||
|
{
|
||||||
|
return Maps.newHashMapWithExpectedSize(size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// // TODO : We can't create an EnumMap of zero size- we need to know the key class
|
||||||
|
// public static class EnumMapStreamer extends MapStreamer
|
||||||
|
// {
|
||||||
|
// }
|
||||||
|
// // TODO : We can't create an EnumSet of zero size- we need to know the key class
|
||||||
|
// public static class EnumSetStreamer extends CollectionStreamer
|
||||||
|
// {
|
||||||
|
// }
|
||||||
|
|
||||||
/** Streams {@link String} instances. */
|
/** Streams {@link String} instances. */
|
||||||
public static class BasicStreamer extends Streamer
|
public static class BasicStreamer extends Streamer
|
||||||
{
|
{
|
||||||
@@ -538,17 +660,6 @@ public class BasicStreamers
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ArrayList<?> readList (ObjectInputStream ins)
|
|
||||||
throws IOException, ClassNotFoundException
|
|
||||||
{
|
|
||||||
int ecount = ins.readInt();
|
|
||||||
ArrayList<Object> list = Lists.newArrayListWithCapacity(ecount);
|
|
||||||
for (int ii = 0; ii < ecount; ii++) {
|
|
||||||
list.add(ins.readObject());
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void writeBooleanArray (ObjectOutputStream out, boolean[] value)
|
public static void writeBooleanArray (ObjectOutputStream out, boolean[] value)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
@@ -636,22 +747,4 @@ public class BasicStreamers
|
|||||||
out.writeObject(value[ii]);
|
out.writeObject(value[ii]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void writeList (ObjectOutputStream out, List<?> value)
|
|
||||||
throws IOException
|
|
||||||
{
|
|
||||||
int ecount = value.size();
|
|
||||||
out.writeInt(ecount);
|
|
||||||
if (value instanceof RandomAccess) {
|
|
||||||
// if RandomAccess, it's faster and less garbagey this way
|
|
||||||
for (int ii = 0; ii < ecount; ii++) {
|
|
||||||
out.writeObject(value.get(ii));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// if not RandomAccess, go ahead and make an Iterator...
|
|
||||||
for (Object o : value) {
|
|
||||||
out.writeObject(o);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,10 +109,6 @@ public abstract class FieldMarshaller
|
|||||||
}
|
}
|
||||||
|
|
||||||
Class<?> ftype = field.getType();
|
Class<?> ftype = field.getType();
|
||||||
if (ftype.isInterface()) {
|
|
||||||
// if the class is a pure interface, use Object.
|
|
||||||
ftype = Object.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
// use the intern marshaller for pooled strings
|
// use the intern marshaller for pooled strings
|
||||||
if (ftype == String.class && field.isAnnotationPresent(Intern.class)) {
|
if (ftype == String.class && field.isAnnotationPresent(Intern.class)) {
|
||||||
@@ -122,8 +118,8 @@ public abstract class FieldMarshaller
|
|||||||
// if we have an exact match, use that
|
// if we have an exact match, use that
|
||||||
FieldMarshaller fm = _marshallers.get(ftype);
|
FieldMarshaller fm = _marshallers.get(ftype);
|
||||||
|
|
||||||
// otherwise if the class is a streamable, use the streamable marshaller
|
// otherwise if the class is a pure interface or streamable, use the streamable marshaller
|
||||||
if (fm == null && Streamer.isStreamable(ftype)) {
|
if (fm == null && (ftype.isInterface() || Streamer.isStreamable(ftype))) {
|
||||||
fm = _marshallers.get(Streamable.class);
|
fm = _marshallers.get(Streamable.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user