Implement a Java-compatible ObjectOutputStream

The Java Language Specification documents the serialization protocol
implemented by this change set:

http://docs.oracle.com/javase/7/docs/platform/serialization/spec/protocol.html#10258

This change is intended to make it easier to use Avian VM as a drop-in
replacement for the Oracle JVM when serializing objects.

The previous serialization code is still available as
avian.LegacyObjectOutputStream.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
This commit is contained in:
Johannes Schindelin 2013-10-29 10:20:55 -05:00
parent f2dd4add26
commit c2a6f4a726
2 changed files with 467 additions and 165 deletions

View File

@ -0,0 +1,211 @@
/* Copyright (c) 2008-2013, Avian Contributors
Permission to use, copy, modify, and/or distribute this software
for any purpose with or without fee is hereby granted, provided
that the above copyright notice and this permission notice appear
in all copies.
There is NO WARRANTY for this software. See license.txt for
details. */
package avian;
import java.util.IdentityHashMap;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.Serializable;
import java.io.NotSerializableException;
public class LegacyObjectOutputStream extends OutputStream {
private final PrintStream out;
public LegacyObjectOutputStream(OutputStream out) {
this.out = new PrintStream(out);
}
public void write(int c) throws IOException {
out.write(c);
}
public void write(byte[] b, int offset, int length) throws IOException {
out.write(b, offset, length);
}
public void flush() throws IOException {
out.flush();
}
public void close() throws IOException {
out.close();
}
public void writeObject(Object o) throws IOException {
writeObject(o, new IdentityHashMap(), new int[] {0});
}
public void writeBoolean(boolean v) {
out.print("z");
out.print((v ? 1 : 0));
}
public void writeByte(byte v) {
out.print("b");
out.print((int) v);
}
public void writeChar(char v) {
out.print("c");
out.print((int) v);
}
public void writeShort(short v) {
out.print("s");
out.print((int) v);
}
public void writeInt(int v) {
out.print("i");
out.print(v);
}
public void writeLong(long v) {
out.print("j");
out.print(v);
}
public void writeFloat(float v) {
out.print("f");
out.print(v);
}
public void writeDouble(double v) {
out.print("d");
out.print(v);
}
public void defaultWriteObject() throws IOException {
throw new UnsupportedOperationException();
}
private void writeObject(Object o, IdentityHashMap<Object, Integer> map,
int[] nextId)
throws IOException
{
if (o == null) {
out.print("n");
} else {
Integer id = map.get(o);
if (id == null) {
map.put(o, nextId[0]);
Class c = o.getClass();
if (c.isArray()) {
serializeArray(o, map, nextId);
} else if (Serializable.class.isAssignableFrom(c)) {
serializeObject(o, map, nextId);
} else {
throw new NotSerializableException(c.getName());
}
} else {
out.print("r");
out.print(id.intValue());
}
}
}
private void serializeArray(Object o, IdentityHashMap<Object, Integer> map,
int[] nextId)
throws IOException
{
Class c = o.getClass();
Class t = c.getComponentType();
int length = Array.getLength(o);
out.print("a(");
out.print(nextId[0]++);
out.print(" ");
out.print(c.getName());
out.print(" ");
out.print(length);
for (int i = 0; i < length; ++i) {
out.print(" ");
Object v = Array.get(o, i);
if (t.equals(boolean.class)) {
writeBoolean((Boolean) v);
} else if (t.equals(byte.class)) {
writeByte((Byte) v);
} else if (t.equals(char.class)) {
writeChar((Character) v);
} else if (t.equals(short.class)) {
writeShort((Short) v);
} else if (t.equals(int.class)) {
writeInt((Integer) v);
} else if (t.equals(long.class)) {
writeLong((Long) v);
} else if (t.equals(float.class)) {
writeFloat((Float) v);
} else if (t.equals(double.class)) {
writeDouble((Double) v);
} else {
writeObject(v, map, nextId);
}
}
out.print(")");
}
private void serializeObject(Object o, IdentityHashMap<Object, Integer> map,
int[] nextId)
throws IOException
{
Class c = o.getClass();
out.print("l(");
out.print(nextId[0]++);
out.print(" ");
out.print(c.getName());
for (Field f: c.getAllFields()) {
int modifiers = f.getModifiers();
if ((modifiers & (Modifier.TRANSIENT | Modifier.STATIC)) == 0) {
out.print(" ");
Object v;
try {
v = f.get(o);
} catch (Exception e) {
throw new RuntimeException(e);
}
Class t = f.getType();
if (t.equals(boolean.class)) {
writeBoolean((Boolean) v);
} else if (t.equals(byte.class)) {
writeByte((Byte) v);
} else if (t.equals(char.class)) {
writeChar((Character) v);
} else if (t.equals(short.class)) {
writeShort((Short) v);
} else if (t.equals(int.class)) {
writeInt((Integer) v);
} else if (t.equals(long.class)) {
writeLong((Long) v);
} else if (t.equals(float.class)) {
writeFloat((Float) v);
} else if (t.equals(double.class)) {
writeDouble((Double) v);
} else {
writeObject(v, map, nextId);
}
}
}
out.print(")");
}
}

View File

@ -10,18 +10,43 @@
package java.io;
import java.util.IdentityHashMap;
import java.util.ArrayList;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class ObjectOutputStream extends OutputStream {
private final PrintStream out;
public class ObjectOutputStream extends OutputStream implements DataOutput {
private final static short STREAM_MAGIC = (short)0xaced;
private final static short STREAM_VERSION = 5;
private final static byte TC_NULL = (byte)0x70;
private final static byte TC_REFERENCE = (byte)0x71;
private final static byte TC_CLASSDESC = (byte)0x72;
private final static byte TC_OBJECT = (byte)0x73;
private final static byte TC_STRING = (byte)0x74;
private final static byte TC_ARRAY = (byte)0x75;
private final static byte TC_CLASS = (byte)0x76;
private final static byte TC_BLOCKDATA = (byte)0x77;
private final static byte TC_ENDBLOCKDATA = (byte)0x78;
private final static byte TC_RESET = (byte)0x79;
private final static byte TC_BLOCKDATALONG = (byte)0x7a;
private final static byte TC_EXCEPTION = (byte)0x7b;
private final static byte TC_LONGSTRING = (byte)0x7c;
private final static byte TC_PROXYCLASSDESC = (byte)0x7d;
private final static byte TC_ENUM = (byte)0x7e;
private final static byte SC_WRITE_METHOD = 0x01; //if SC_SERIALIZABLE
private final static byte SC_BLOCK_DATA = 0x08; //if SC_EXTERNALIZABLE
private final static byte SC_SERIALIZABLE = 0x02;
private final static byte SC_EXTERNALIZABLE = 0x04;
private final static byte SC_ENUM = 0x10;
public ObjectOutputStream(OutputStream out) {
this.out = new PrintStream(out);
private final OutputStream out;
public ObjectOutputStream(OutputStream out) throws IOException {
this.out = out;
rawShort(STREAM_MAGIC);
rawShort(STREAM_VERSION);
}
public void write(int c) throws IOException {
out.write(c);
}
@ -38,169 +63,235 @@ public class ObjectOutputStream extends OutputStream {
out.close();
}
private void rawByte(int v) throws IOException {
out.write((byte)(v & 0xff));
}
private void rawShort(int v) throws IOException {
rawByte(v >> 8);
rawByte(v);
}
private void rawInt(int v) throws IOException {
rawShort(v >> 16);
rawShort(v);
}
private void rawLong(long v) throws IOException {
rawInt((int)(v >> 32));
rawInt((int)(v & 0xffffffffl));
}
private void blockData(int... bytes) throws IOException {
blockData(bytes, null, null);
}
private void blockData(int[] bytes, byte[] bytes2, char[] chars) throws IOException {
int count = (bytes == null ? 0 : bytes.length)
+ (bytes2 == null ? 0 : bytes2.length)
+ (chars == null ? 0 : chars.length * 2);
if (count < 0x100) {
rawByte(TC_BLOCKDATA);
rawByte(count);
} else {
rawByte(TC_BLOCKDATALONG);
rawInt(count);
}
if (bytes != null) {
for (int b : bytes) {
rawByte(b);
}
}
if (bytes2 != null) {
for (byte b : bytes2) {
rawByte(b & 0xff);
}
}
if (chars != null) {
for (char c : chars) {
rawShort((short)c);
}
}
}
public void writeBoolean(boolean v) throws IOException {
blockData(v ? 1 : 0);
}
public void writeByte(int v) throws IOException {
blockData(v);
}
public void writeShort(int v) throws IOException {
blockData(v >> 8, v);
}
public void writeChar(int v) throws IOException {
blockData(v >> 8, v);
}
public void writeInt(int v) throws IOException {
blockData(v >> 24, v >> 16, v >> 8, v);
}
public void writeLong(long v) throws IOException {
int u = (int)(v >> 32), l = (int)(v & 0xffffffff);
blockData(u >> 24, u >> 16, u >> 8, u, l >> 24, l >> 16, l >> 8, l);
}
public void writeFloat(float v) throws IOException {
writeInt(Float.floatToIntBits(v));
}
public void writeDouble(double v) throws IOException {
writeLong(Double.doubleToLongBits(v));
}
public void writeBytes(String s) throws IOException {
blockData(null, s.getBytes(), null);
}
public void writeChars(String s) throws IOException {
blockData(null, null, s.toCharArray());
}
public void writeUTF(String s) throws IOException {
byte[] bytes = s.getBytes();
int length = bytes.length;
blockData(new int[] { length >> 8, length }, bytes, null);
}
private int classHandle;
private void string(String s) throws IOException {
int length = s.length();
rawShort(length);
for (byte b : s.getBytes()) {
rawByte(b);
}
}
private static char primitiveTypeChar(Class type) {
if (type == Byte.TYPE) {
return 'B';
} else if (type == Character.TYPE) {
return 'C';
} else if (type == Double.TYPE) {
return 'D';
} else if (type == Float.TYPE) {
return 'F';
} else if (type == Integer.TYPE) {
return 'I';
} else if (type == Long.TYPE) {
return 'J';
} else if (type == Short.TYPE) {
return 'S';
} else if (type == Boolean.TYPE) {
return 'Z';
}
throw new RuntimeException("Unhandled primitive type: " + type);
}
private void classDesc(Class clazz) throws IOException {
rawByte(TC_CLASSDESC);
// class name
string(clazz.getName());
// serial version UID
long serialVersionUID = 1l;
try {
Field field = clazz.getField("serialVersionUID");
serialVersionUID = field.getLong(null);
} catch (Exception ignored) {}
rawLong(serialVersionUID);
// handle
rawByte(SC_SERIALIZABLE);
Field[] fields = getFields(clazz);
rawShort(fields.length);
for (Field field : fields) {
Class fieldType = field.getType();
if (fieldType.isPrimitive()) {
rawByte(primitiveTypeChar(fieldType));
string(field.getName());
} else {
rawByte(fieldType.isArray() ? '[' : 'L');
string(field.getName());
rawByte(TC_STRING);
string("L" + fieldType.getName().replace('.', '/') + ";");
}
}
rawByte(TC_ENDBLOCKDATA); // TODO: write annotation
rawByte(TC_NULL); // super class desc
}
private void field(Object o, Field field) throws IOException {
try {
field.setAccessible(true);
Class type = field.getType();
if (!type.isPrimitive()) {
writeObject(field.get(o));
} else if (type == Byte.TYPE) {
rawByte(field.getByte(o));
} else if (type == Character.TYPE) {
char c = field.getChar(o);
rawShort((short)c);
} else if (type == Double.TYPE) {
double d = field.getDouble(o);
rawLong(Double.doubleToLongBits(d));
} else if (type == Float.TYPE) {
float f = field.getFloat(o);
rawInt(Float.floatToIntBits(f));
} else if (type == Integer.TYPE) {
int i = field.getInt(o);
rawInt(i);
} else if (type == Long.TYPE) {
long l = field.getLong(o);
rawLong(l);
} else if (type == Short.TYPE) {
short s = field.getShort(o);
rawShort(s);
} else if (type == Boolean.TYPE) {
boolean b = field.getBoolean(o);
rawByte(b ? 1 : 0);
} else {
throw new UnsupportedOperationException("Field '" + field.getName()
+ "' has unsupported type: " + type);
}
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException(e);
}
}
private static Field[] getFields(Class clazz) {
ArrayList<Field> list = new ArrayList<Field>();
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (0 == (field.getModifiers() &
(Modifier.STATIC | Modifier.TRANSIENT))) {
list.add(field);
}
}
return list.toArray(new Field[list.size()]);
}
public void writeObject(Object o) throws IOException {
writeObject(o, new IdentityHashMap(), new int[] {0});
}
public void writeBoolean(boolean v) {
out.print("z");
out.print((v ? 1 : 0));
}
public void writeByte(byte v) {
out.print("b");
out.print((int) v);
}
public void writeChar(char v) {
out.print("c");
out.print((int) v);
}
public void writeShort(short v) {
out.print("s");
out.print((int) v);
}
public void writeInt(int v) {
out.print("i");
out.print(v);
}
public void writeLong(long v) {
out.print("j");
out.print(v);
}
public void writeFloat(float v) {
out.print("f");
out.print(v);
}
public void writeDouble(double v) {
out.print("d");
out.print(v);
if (o == null) {
rawByte(TC_NULL);
return;
}
rawByte(TC_OBJECT);
classDesc(o.getClass());
for (Field field : getFields(o.getClass())) {
field(o, field);
}
}
public void defaultWriteObject() throws IOException {
throw new UnsupportedOperationException();
}
private void writeObject(Object o, IdentityHashMap<Object, Integer> map,
int[] nextId)
throws IOException
{
if (o == null) {
out.print("n");
} else {
Integer id = map.get(o);
if (id == null) {
map.put(o, nextId[0]);
Class c = o.getClass();
if (c.isArray()) {
serializeArray(o, map, nextId);
} else if (Serializable.class.isAssignableFrom(c)) {
serializeObject(o, map, nextId);
} else {
throw new NotSerializableException(c.getName());
}
} else {
out.print("r");
out.print(id.intValue());
}
}
}
private void serializeArray(Object o, IdentityHashMap<Object, Integer> map,
int[] nextId)
throws IOException
{
Class c = o.getClass();
Class t = c.getComponentType();
int length = Array.getLength(o);
out.print("a(");
out.print(nextId[0]++);
out.print(" ");
out.print(c.getName());
out.print(" ");
out.print(length);
for (int i = 0; i < length; ++i) {
out.print(" ");
Object v = Array.get(o, i);
if (t.equals(boolean.class)) {
writeBoolean((Boolean) v);
} else if (t.equals(byte.class)) {
writeByte((Byte) v);
} else if (t.equals(char.class)) {
writeChar((Character) v);
} else if (t.equals(short.class)) {
writeShort((Short) v);
} else if (t.equals(int.class)) {
writeInt((Integer) v);
} else if (t.equals(long.class)) {
writeLong((Long) v);
} else if (t.equals(float.class)) {
writeFloat((Float) v);
} else if (t.equals(double.class)) {
writeDouble((Double) v);
} else {
writeObject(v, map, nextId);
}
}
out.print(")");
}
private void serializeObject(Object o, IdentityHashMap<Object, Integer> map,
int[] nextId)
throws IOException
{
Class c = o.getClass();
out.print("l(");
out.print(nextId[0]++);
out.print(" ");
out.print(c.getName());
for (Field f: c.getAllFields()) {
int modifiers = f.getModifiers();
if ((modifiers & (Modifier.TRANSIENT | Modifier.STATIC)) == 0) {
out.print(" ");
Object v;
try {
v = f.get(o);
} catch (Exception e) {
throw new RuntimeException(e);
}
Class t = f.getType();
if (t.equals(boolean.class)) {
writeBoolean((Boolean) v);
} else if (t.equals(byte.class)) {
writeByte((Byte) v);
} else if (t.equals(char.class)) {
writeChar((Character) v);
} else if (t.equals(short.class)) {
writeShort((Short) v);
} else if (t.equals(int.class)) {
writeInt((Integer) v);
} else if (t.equals(long.class)) {
writeLong((Long) v);
} else if (t.equals(float.class)) {
writeFloat((Float) v);
} else if (t.equals(double.class)) {
writeDouble((Double) v);
} else {
writeObject(v, map, nextId);
}
}
}
out.print(")");
}
}