corda/classpath/java/io/ObjectOutputStream.java
Johannes Schindelin c2a6f4a726 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>
2013-11-06 09:10:50 -06:00

298 lines
8.3 KiB
Java

/* 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 java.io;
import java.util.ArrayList;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
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;
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);
}
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();
}
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 {
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();
}
}