Merge pull request #94 from dscho/serialization

Implement Java-compatible serialization
This commit is contained in:
Joshua Warner 2013-11-06 08:49:14 -08:00
commit d0d4f600dc
8 changed files with 1350 additions and 312 deletions

View File

@ -0,0 +1,234 @@
/* 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 avian.VMClass;
import java.util.HashMap;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class LegacyObjectInputStream extends InputStream {
private final InputStream in;
private final PushbackReader r;
public LegacyObjectInputStream(InputStream in) {
this.in = in;
this.r = new PushbackReader(new InputStreamReader(in));
}
public int read() throws IOException {
return in.read();
}
public int read(byte[] b, int offset, int length) throws IOException {
return in.read(b, offset, length);
}
public void close() throws IOException {
in.close();
}
public Object readObject() throws IOException, ClassNotFoundException {
return readObject(new HashMap());
}
public boolean readBoolean() throws IOException {
read('z');
return readLongToken() != 0;
}
public byte readByte() throws IOException {
read('b');
return (byte) readLongToken();
}
public char readChar() throws IOException {
read('c');
return (char) readLongToken();
}
public short readShort() throws IOException {
read('s');
return (short) readLongToken();
}
public int readInt() throws IOException {
read('i');
return (int) readLongToken();
}
public long readLong() throws IOException {
read('j');
return readLongToken();
}
public float readFloat() throws IOException {
read('f');
return (float) readDoubleToken();
}
public double readDouble() throws IOException {
read('d');
return readDoubleToken();
}
public void defaultReadObject() throws IOException {
throw new UnsupportedOperationException();
}
private void skipSpace() throws IOException {
int c;
while ((c = r.read()) != -1 && Character.isWhitespace((char) c));
if (c != -1) {
r.unread(c);
}
}
private void read(char v) throws IOException {
skipSpace();
int c = r.read();
if (c != v) {
if (c == -1) {
throw new EOFException();
} else {
throw new StreamCorruptedException();
}
}
}
private String readStringToken() throws IOException {
skipSpace();
StringBuilder sb = new StringBuilder();
int c;
while ((c = r.read()) != -1 && ! Character.isWhitespace((char) c) && c != ')') {
sb.append((char) c);
}
if (c != -1) {
r.unread(c);
}
return sb.toString();
}
private long readLongToken() throws IOException {
return Long.parseLong(readStringToken());
}
private double readDoubleToken() throws IOException {
return Double.parseDouble(readStringToken());
}
private Object readObject(HashMap<Integer, Object> map)
throws IOException, ClassNotFoundException
{
skipSpace();
switch (r.read()) {
case 'a':
return deserializeArray(map);
case 'l':
return deserializeObject(map);
case 'n':
return null;
case -1:
throw new EOFException();
default:
throw new StreamCorruptedException();
}
}
private Object deserialize(HashMap<Integer, Object> map)
throws IOException, ClassNotFoundException
{
skipSpace();
switch (r.read()) {
case 'a':
return deserializeArray(map);
case 'l':
return deserializeObject(map);
case 'r':
return map.get((int) readLongToken());
case 'n':
return null;
case 'z':
return (readLongToken() != 0);
case 'b':
return (byte) readLongToken();
case 'c':
return (char) readLongToken();
case 's':
return (short) readLongToken();
case 'i':
return (int) readLongToken();
case 'j':
return readLongToken();
case 'f':
return (float) readDoubleToken();
case 'd':
return readDoubleToken();
case -1:
throw new EOFException();
default:
throw new StreamCorruptedException();
}
}
private Object deserializeArray(HashMap<Integer, Object> map)
throws IOException, ClassNotFoundException
{
read('(');
int id = (int) readLongToken();
Class c = Class.forName(readStringToken());
int length = (int) readLongToken();
Class t = c.getComponentType();
Object o = Array.newInstance(t, length);
map.put(id, o);
for (int i = 0; i < length; ++i) {
Array.set(o, i, deserialize(map));
}
read(')');
return o;
}
private static native Object makeInstance(VMClass c);
private Object deserializeObject(HashMap<Integer, Object> map)
throws IOException, ClassNotFoundException
{
read('(');
int id = (int) readLongToken();
Class c = Class.forName(readStringToken());
Object o = makeInstance(c.vmClass);
map.put(id, o);
for (Field f: c.getAllFields()) {
int modifiers = f.getModifiers();
if ((modifiers & (Modifier.TRANSIENT | Modifier.STATIC)) == 0) {
try {
f.set(o, deserialize(map));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
read(')');
return o;
}
}

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

@ -0,0 +1,51 @@
/* 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;
public abstract class FilterReader extends Reader {
protected Reader in;
protected FilterReader(Reader in) {
this.in = in;
}
public int read() throws IOException {
return in.read();
}
public int read(char[] buffer, int offset, int length) throws IOException {
return in.read(buffer, offset, length);
}
public boolean ready() throws IOException {
throw new UnsupportedOperationException();
}
public long skip(long n) throws IOException {
throw new UnsupportedOperationException();
}
public void close() throws IOException {
in.close();
}
public boolean markSupported() {
return in.markSupported();
}
public void mark(int readAheadLimit) throws IOException {
in.mark(readAheadLimit);
}
public void reset() throws IOException {
in.reset();
}
}

View File

@ -10,225 +10,434 @@
package java.io;
import static java.io.ObjectOutputStream.STREAM_MAGIC;
import static java.io.ObjectOutputStream.STREAM_VERSION;
import static java.io.ObjectOutputStream.TC_NULL;
import static java.io.ObjectOutputStream.TC_REFERENCE;
import static java.io.ObjectOutputStream.TC_CLASSDESC;
import static java.io.ObjectOutputStream.TC_OBJECT;
import static java.io.ObjectOutputStream.TC_STRING;
import static java.io.ObjectOutputStream.TC_ARRAY;
import static java.io.ObjectOutputStream.TC_CLASS;
import static java.io.ObjectOutputStream.TC_BLOCKDATA;
import static java.io.ObjectOutputStream.TC_ENDBLOCKDATA;
import static java.io.ObjectOutputStream.TC_RESET;
import static java.io.ObjectOutputStream.TC_BLOCKDATALONG;
import static java.io.ObjectOutputStream.TC_EXCEPTION;
import static java.io.ObjectOutputStream.TC_LONGSTRING;
import static java.io.ObjectOutputStream.TC_PROXYCLASSDESC;
import static java.io.ObjectOutputStream.TC_ENUM;
import static java.io.ObjectOutputStream.SC_WRITE_METHOD;
import static java.io.ObjectOutputStream.SC_BLOCK_DATA;
import static java.io.ObjectOutputStream.SC_SERIALIZABLE;
import static java.io.ObjectOutputStream.SC_EXTERNALIZABLE;
import static java.io.ObjectOutputStream.SC_ENUM;
import static java.io.ObjectOutputStream.getReadOrWriteMethod;
import avian.VMClass;
import java.util.ArrayList;
import java.util.HashMap;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class ObjectInputStream extends InputStream {
private final InputStream in;
private final PushbackReader r;
public class ObjectInputStream extends InputStream implements DataInput {
private final static int HANDLE_OFFSET = 0x7e0000;
public ObjectInputStream(InputStream in) {
private final InputStream in;
private final ArrayList references;
public ObjectInputStream(InputStream in) throws IOException {
this.in = in;
this.r = new PushbackReader(new InputStreamReader(in));
short signature = (short)rawShort();
if (signature != STREAM_MAGIC) {
throw new IOException("Unrecognized signature: 0x"
+ Integer.toHexString(signature));
}
int version = rawShort();
if (version != STREAM_VERSION) {
throw new IOException("Unsupported version: " + version);
}
references = new ArrayList();
}
public int read() throws IOException {
return in.read();
}
private int rawByte() throws IOException {
int c = read();
if (c < 0) {
throw new EOFException();
}
return c;
}
private int rawShort() throws IOException {
return (rawByte() << 8) | rawByte();
}
private int rawInt() throws IOException {
return (rawShort() << 16) | rawShort();
}
private long rawLong() throws IOException {
return ((rawInt() & 0xffffffffl) << 32) | rawInt();
}
private String rawString() throws IOException {
int length = rawShort();
byte[] array = new byte[length];
readFully(array);
return new String(array);
}
public int read(byte[] b, int offset, int length) throws IOException {
return in.read(b, offset, length);
}
public void readFully(byte[] b) throws IOException {
readFully(b, 0, b.length);
}
public void readFully(byte[] b, int offset, int length) throws IOException {
while (length > 0) {
int count = read(b, offset, length);
if (count < 0) {
throw new EOFException("Reached EOF " + length + " bytes too early");
}
offset += count;
length -= count;
}
}
public String readLine() throws IOException {
int c = read();
if (c < 0) {
return null;
} else if (c == '\n') {
return "";
}
StringBuilder builder = new StringBuilder();
for (;;) {
builder.append((char)c);
c = read();
if (c < 0 || c == '\n') {
return builder.toString();
}
}
}
public void close() throws IOException {
in.close();
}
public Object readObject() throws IOException, ClassNotFoundException {
return readObject(new HashMap());
private int remainingBlockData;
private int rawBlockDataByte() throws IOException {
while (remainingBlockData <= 0) {
int b = rawByte();
if (b == TC_BLOCKDATA) {
remainingBlockData = rawByte();
} else {
throw new UnsupportedOperationException("Unknown token: 0x"
+ Integer.toHexString(b));
}
}
--remainingBlockData;
return rawByte();
}
private int rawBlockDataShort() throws IOException {
return (rawBlockDataByte() << 8) | rawBlockDataByte();
}
private int rawBlockDataInt() throws IOException {
return (rawBlockDataShort() << 16) | rawBlockDataShort();
}
private long rawBlockDataLong() throws IOException {
return ((rawBlockDataInt() & 0xffffffffl) << 32) | rawBlockDataInt();
}
public boolean readBoolean() throws IOException {
read('z');
return readLongToken() != 0;
return rawBlockDataByte() != 0;
}
public byte readByte() throws IOException {
read('b');
return (byte) readLongToken();
return (byte)rawBlockDataByte();
}
public char readChar() throws IOException {
read('c');
return (char) readLongToken();
return (char)rawBlockDataShort();
}
public short readShort() throws IOException {
read('s');
return (short) readLongToken();
return (short)rawBlockDataShort();
}
public int readInt() throws IOException {
read('i');
return (int) readLongToken();
return rawBlockDataInt();
}
public long readLong() throws IOException {
read('j');
return readLongToken();
return rawBlockDataLong();
}
public float readFloat() throws IOException {
read('f');
return (float) readDoubleToken();
return Float.intBitsToFloat(rawBlockDataInt());
}
public double readDouble() throws IOException {
read('d');
return readDoubleToken();
}
public void defaultReadObject() throws IOException {
throw new UnsupportedOperationException();
return Double.longBitsToDouble(rawBlockDataLong());
}
private void skipSpace() throws IOException {
int c;
while ((c = r.read()) != -1 && Character.isWhitespace((char) c));
if (c != -1) {
r.unread(c);
public int readUnsignedByte() throws IOException {
return rawBlockDataByte();
}
public int readUnsignedShort() throws IOException {
return rawBlockDataShort();
}
public String readUTF() throws IOException {
int length = rawBlockDataShort();
if (remainingBlockData < length) {
throw new IOException("Short block data: "
+ remainingBlockData + " < " + length);
}
byte[] bytes = new byte[length];
readFully(bytes);
remainingBlockData -= length;
return new String(bytes, "UTF-8");
}
public int skipBytes(int count) throws IOException {
int i = 0;
while (i < count) {
if (read() < 0) {
return i;
}
++i;
}
return count;
}
private static Class charToPrimitiveType(int c) {
if (c == 'B') {
return Byte.TYPE;
} else if (c == 'C') {
return Character.TYPE;
} else if (c == 'D') {
return Double.TYPE;
} else if (c == 'F') {
return Float.TYPE;
} else if (c == 'I') {
return Integer.TYPE;
} else if (c == 'J') {
return Long.TYPE;
} else if (c == 'S') {
return Short.TYPE;
} else if (c == 'Z') {
return Boolean.TYPE;
}
throw new RuntimeException("Unhandled char: " + (char)c);
}
private void expectToken(int token) throws IOException {
int c = rawByte();
if (c != token) {
throw new UnsupportedOperationException("Unexpected token: 0x"
+ Integer.toHexString(c));
}
}
private void read(char v) throws IOException {
skipSpace();
int c = r.read();
if (c != v) {
if (c == -1) {
throw new EOFException();
private void field(Field field, Object o)
throws IOException, IllegalArgumentException, IllegalAccessException,
ClassNotFoundException
{
Class type = field.getType();
if (!type.isPrimitive()) {
field.set(o, readObject());
} else {
if (type == Byte.TYPE) {
field.setByte(o, (byte)rawByte());
} else if (type == Character.TYPE) {
field.setChar(o, (char)rawShort());
} else if (type == Double.TYPE) {
field.setDouble(o, Double.longBitsToDouble(rawLong()));
} else if (type == Float.TYPE) {
field.setFloat(o, Float.intBitsToFloat(rawInt()));
} else if (type == Integer.TYPE) {
field.setInt(o, rawInt());
} else if (type == Long.TYPE) {
field.setLong(o, rawLong());
} else if (type == Short.TYPE) {
field.setShort(o, (short)rawShort());
} else if (type == Boolean.TYPE) {
field.setBoolean(o, rawByte() != 0);
} else {
throw new StreamCorruptedException();
throw new IOException("Unhandled type: " + type);
}
}
}
private String readStringToken() throws IOException {
skipSpace();
StringBuilder sb = new StringBuilder();
int c;
while ((c = r.read()) != -1 && ! Character.isWhitespace((char) c) && c != ')') {
sb.append((char) c);
}
if (c != -1) {
r.unread(c);
}
return sb.toString();
}
private long readLongToken() throws IOException {
return Long.parseLong(readStringToken());
}
private double readDoubleToken() throws IOException {
return Double.parseDouble(readStringToken());
}
private Object readObject(HashMap<Integer, Object> map)
throws IOException, ClassNotFoundException
{
skipSpace();
switch (r.read()) {
case 'a':
return deserializeArray(map);
case 'l':
return deserializeObject(map);
case 'n':
public Object readObject() throws IOException, ClassNotFoundException {
int c = rawByte();
if (c == TC_NULL) {
return null;
case -1:
throw new EOFException();
default:
throw new StreamCorruptedException();
}
if (c == TC_STRING) {
int length = rawShort();
byte[] bytes = new byte[length];
readFully(bytes);
String s = new String(bytes, "UTF-8");
references.add(s);
return s;
}
if (c == TC_REFERENCE) {
int handle = rawInt();
return references.get(handle - HANDLE_OFFSET);
}
if (c != TC_OBJECT) {
throw new IOException("Unexpected token: 0x"
+ Integer.toHexString(c));
}
// class desc
c = rawByte();
ClassDesc classDesc;
if (c == TC_REFERENCE) {
int handle = rawInt() - HANDLE_OFFSET;
classDesc = (ClassDesc)references.get(handle);
} else if (c == TC_CLASSDESC) {
classDesc = classDesc();
} else {
throw new UnsupportedOperationException("Unexpected token: 0x"
+ Integer.toHexString(c));
}
try {
Object o = makeInstance(classDesc.clazz.vmClass);
references.add(o);
do {
Object o1 = classDesc.clazz.cast(o);
boolean customized = (classDesc.flags & SC_WRITE_METHOD) != 0;
Method readMethod = customized ?
getReadOrWriteMethod(o, "readObject") : null;
if (readMethod == null) {
if (customized) {
throw new IOException("Could not find required readObject method "
+ "in " + classDesc.clazz);
}
defaultReadObject(o, classDesc.fields);
} else {
current = o1;
currentFields = classDesc.fields;
readMethod.invoke(o, this);
current = null;
currentFields = null;
expectToken(TC_ENDBLOCKDATA);
}
} while ((classDesc = classDesc.superClassDesc) != null);
return o;
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException(e);
}
}
private Object deserialize(HashMap<Integer, Object> map)
throws IOException, ClassNotFoundException
{
skipSpace();
switch (r.read()) {
case 'a':
return deserializeArray(map);
case 'l':
return deserializeObject(map);
case 'r':
return map.get((int) readLongToken());
case 'n':
return null;
case 'z':
return (readLongToken() != 0);
case 'b':
return (byte) readLongToken();
case 'c':
return (char) readLongToken();
case 's':
return (short) readLongToken();
case 'i':
return (int) readLongToken();
case 'j':
return readLongToken();
case 'f':
return (float) readDoubleToken();
case 'd':
return readDoubleToken();
case -1:
throw new EOFException();
default:
throw new StreamCorruptedException();
}
private static class ClassDesc {
Class clazz;
int flags;
Field[] fields;
ClassDesc superClassDesc;
}
private Object deserializeArray(HashMap<Integer, Object> map)
throws IOException, ClassNotFoundException
{
read('(');
int id = (int) readLongToken();
Class c = Class.forName(readStringToken());
int length = (int) readLongToken();
Class t = c.getComponentType();
Object o = Array.newInstance(t, length);
private ClassDesc classDesc() throws ClassNotFoundException, IOException {
ClassDesc result = new ClassDesc();
String className = rawString();
ClassLoader loader = Thread.currentThread().getContextClassLoader();
result.clazz = loader.loadClass(className);
long serialVersionUID = rawLong();
try {
Field field = result.clazz.getField("serialVersionUID");
long expected = field.getLong(null);
if (expected != serialVersionUID) {
throw new IOException("Incompatible serial version UID: 0x"
+ Long.toHexString(serialVersionUID) + " != 0x"
+ Long.toHexString(expected));
}
} catch (Exception ignored) { }
references.add(result);
map.put(id, o);
for (int i = 0; i < length; ++i) {
Array.set(o, i, deserialize(map));
result.flags = rawByte();
if ((result.flags & ~(SC_SERIALIZABLE | SC_WRITE_METHOD)) != 0) {
throw new UnsupportedOperationException("Cannot handle flags: 0x"
+ Integer.toHexString(result.flags));
}
read(')');
int fieldCount = rawShort();
result.fields = new Field[fieldCount];
for (int i = 0; i < result.fields.length; i++) {
int typeChar = rawByte();
String fieldName = rawString();
try {
result.fields[i] = result.clazz.getDeclaredField(fieldName);
} catch (Exception e) {
throw new IOException(e);
}
Class type;
if (typeChar == '[' || typeChar == 'L') {
String typeName = (String)readObject();
if (typeName.startsWith("L") && typeName.endsWith(";")) {
typeName = typeName.substring(1, typeName.length() - 1)
.replace('/', '.');
}
type = loader.loadClass(typeName);
} else {
type = charToPrimitiveType(typeChar);
}
if (result.fields[i].getType() != type) {
throw new IOException("Unexpected type of field " + fieldName
+ ": expected " + result.fields[i].getType() + " but got " + type);
}
}
expectToken(TC_ENDBLOCKDATA);
int c = rawByte();
if (c == TC_CLASSDESC) {
result.superClassDesc = classDesc();
} else if (c != TC_NULL) {
throw new UnsupportedOperationException("Unexpected token: 0x"
+ Integer.toHexString(c));
}
return o;
return result;
}
private Object current;
private Field[] currentFields;
public void defaultReadObject() throws IOException {
defaultReadObject(current, currentFields);
}
private void defaultReadObject(Object o, Field[] fields) throws IOException {
try {
for (Field field : fields) {
field(field, o);
}
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException(e);
}
}
private static native Object makeInstance(VMClass c);
private Object deserializeObject(HashMap<Integer, Object> map)
throws IOException, ClassNotFoundException
{
read('(');
int id = (int) readLongToken();
Class c = Class.forName(readStringToken());
Object o = makeInstance(c.vmClass);
map.put(id, o);
for (Field f: c.getAllFields()) {
int modifiers = f.getModifiers();
if ((modifiers & (Modifier.TRANSIENT | Modifier.STATIC)) == 0) {
try {
f.set(o, deserialize(map));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
read(')');
return o;
}
}

View File

@ -10,18 +10,44 @@
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.Method;
import java.lang.reflect.Modifier;
public class ObjectOutputStream extends OutputStream {
private final PrintStream out;
public class ObjectOutputStream extends OutputStream implements DataOutput {
final static short STREAM_MAGIC = (short)0xaced;
final static short STREAM_VERSION = 5;
final static byte TC_NULL = (byte)0x70;
final static byte TC_REFERENCE = (byte)0x71;
final static byte TC_CLASSDESC = (byte)0x72;
final static byte TC_OBJECT = (byte)0x73;
final static byte TC_STRING = (byte)0x74;
final static byte TC_ARRAY = (byte)0x75;
final static byte TC_CLASS = (byte)0x76;
final static byte TC_BLOCKDATA = (byte)0x77;
final static byte TC_ENDBLOCKDATA = (byte)0x78;
final static byte TC_RESET = (byte)0x79;
final static byte TC_BLOCKDATALONG = (byte)0x7a;
final static byte TC_EXCEPTION = (byte)0x7b;
final static byte TC_LONGSTRING = (byte)0x7c;
final static byte TC_PROXYCLASSDESC = (byte)0x7d;
final static byte TC_ENUM = (byte)0x7e;
final static byte SC_WRITE_METHOD = 0x01; //if SC_SERIALIZABLE
final static byte SC_BLOCK_DATA = 0x08; //if SC_EXTERNALIZABLE
final static byte SC_SERIALIZABLE = 0x02;
final static byte SC_EXTERNALIZABLE = 0x04;
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 +64,274 @@ 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, int scFlags) 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 | scFlags);
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});
if (o == null) {
rawByte(TC_NULL);
return;
}
if (o instanceof String) {
byte[] bytes = ((String)o).getBytes("UTF-8");
rawByte(TC_STRING);
rawShort(bytes.length);
write(bytes);
return;
}
rawByte(TC_OBJECT);
Method writeObject = getReadOrWriteMethod(o, "writeObject");
if (writeObject == null) {
classDesc(o.getClass(), 0);
defaultWriteObject(o);
} else try {
classDesc(o.getClass(), SC_WRITE_METHOD);
current = o;
writeObject.invoke(o, this);
current = null;
rawByte(TC_ENDBLOCKDATA);
} catch (Exception e) {
throw new IOException(e);
}
}
public void writeBoolean(boolean v) {
out.print("z");
out.print((v ? 1 : 0));
static Method getReadOrWriteMethod(Object o, String methodName) {
try {
Method method = o.getClass().getDeclaredMethod(methodName,
new Class[] { methodName.startsWith("write") ?
ObjectOutputStream.class : ObjectInputStream.class });
method.setAccessible(true);
int modifiers = method.getModifiers();
if ((modifiers & Modifier.STATIC) == 0 ||
(modifiers & Modifier.PRIVATE) != 0) {
return method;
}
} catch (NoSuchMethodException ignored) { }
return null;
}
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);
}
private Object current;
public void defaultWriteObject() throws IOException {
throw new UnsupportedOperationException();
defaultWriteObject(current);
}
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 defaultWriteObject(Object o) throws IOException {
for (Field field : getFields(o.getClass())) {
field(o, field);
}
}
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

@ -99,7 +99,7 @@ public class Field<T> extends AccessibleObject {
case LongField:
return Long.valueOf
((int) getPrimitive(target, vmField.code, vmField.offset));
(getPrimitive(target, vmField.code, vmField.offset));
case FloatField:
return Float.valueOf
@ -215,6 +215,85 @@ public class Field<T> extends AccessibleObject {
}
}
private void set(Object instance, long value)
throws IllegalAccessException
{
Object target;
if ((vmField.flags & Modifier.STATIC) != 0) {
target = vmField.class_.staticTable;
} else if (Class.isInstance(vmField.class_, instance)) {
target = instance;
} else {
throw new IllegalArgumentException();
}
switch (vmField.code) {
case ByteField:
case BooleanField:
case CharField:
case ShortField:
case IntField:
case LongField:
case FloatField:
case DoubleField:
setPrimitive(target, vmField.code, vmField.offset, value);
break;
default:
throw new IllegalArgumentException
("needed " + getType() + ", got primitive type when setting "
+ Class.getName(vmField.class_) + "." + getName());
}
}
public void setByte(Object instance, byte value)
throws IllegalAccessException
{
set(instance, value & 0xff);
}
public void setBoolean(Object instance, boolean value)
throws IllegalAccessException
{
set(instance, value ? 1 : 0);
}
public void setChar(Object instance, char value)
throws IllegalAccessException
{
set(instance, value & 0xffff);
}
public void setShort(Object instance, short value)
throws IllegalAccessException
{
set(instance, value & 0xffff);
}
public void setInt(Object instance, int value)
throws IllegalAccessException
{
set(instance, value & 0xffffffffl);
}
public void setLong(Object instance, long value)
throws IllegalAccessException
{
set(instance, value);
}
public void setFloat(Object instance, float value)
throws IllegalAccessException
{
set(instance, Float.floatToIntBits(value));
}
public void setDouble(Object instance, double value)
throws IllegalAccessException
{
set(instance, Double.doubleToLongBits(value));
}
private Annotation getAnnotation(Object[] a) {
if (a[0] == null) {
a[0] = Proxy.newProxyInstance

View File

@ -307,6 +307,13 @@ Avian_java_io_ObjectInputStream_makeInstance
return reinterpret_cast<int64_t>(make(t, c));
}
extern "C" JNIEXPORT int64_t JNICALL
Avian_avian_LegacyObjectInputStream_makeInstance
(Thread* t, object, uintptr_t* arguments)
{
return Avian_java_io_ObjectInputStream_makeInstance(t, NULL, arguments);
}
extern "C" JNIEXPORT int64_t JNICALL
Avian_java_lang_reflect_Field_getPrimitive
(Thread* t, object, uintptr_t* arguments)

116
test/Serialize.java Normal file
View File

@ -0,0 +1,116 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Serialize implements Serializable {
public static final long serialVersionUID = 1l;
public int dummy = 0x12345678;
private static void expect(boolean v) {
if (! v) throw new RuntimeException();
}
private static void expectEqual(boolean a, boolean b) {
expect(a == b);
}
private static void expectEqual(int a, int b) {
expect(a == b);
}
private static void expectEqual(String a, String b) {
expect(a.equals(b));
}
protected static void hexdump(byte[] a) {
for (int i = 0; i < a.length; i++) {
if ((i & 0xf) == 0) {
System.err.println();
}
String hex = Integer.toHexString(a[i] & 0xff);
System.err.print(" " + (hex.length() == 1 ? "0" : "") + hex);
}
System.err.println();
}
private static void expectEqual(byte[] a, int[] b) {
expect(a.length == b.length);
for (int i = 0; i < a.length; ++i) {
expect(a[i] == (byte)b[i]);
}
}
public static void main(String[] args) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream out2 = new ObjectOutputStream(out);
out2.writeBoolean(true);
out2.flush();
out2.writeByte(17);
out2.flush();
out2.writeInt(0xcafebabe);
out2.flush();
out2.writeUTF("Max & Möritz");
out2.flush();
out2.writeChar('ɛ');
out2.flush();
out2.writeObject(new Serialize());
out2.close();
byte[] array = out.toByteArray();
expectEqual(array, new int[] {
// magic
0xac, 0xed,
// version
0x00, 0x05,
// blockdata, length
0x77, 0x1,
// true
1,
// blockdata, length
0x77, 0x1,
// (byte)17
17,
// blockdata, length
0x77, 0x4,
// 0xcafebabe
0xca, 0xfe, 0xba, 0xbe,
// blockdata, length
0x77, 0xf,
// "Max & Möritz"
0x0, 0xd, 'M', 'a', 'x', ' ', '&', ' ', 'M', 0xc3, 0xb6, 'r', 'i', 't', 'z',
// blockdata, length
0x77, 0x2,
// 'ö'
0x02, 0x5b,
// object
0x73,
// class desc, "Serialize"
0x72, 0, 9, 'S', 'e', 'r', 'i', 'a', 'l', 'i', 'z', 'e',
// serialVersionUID
0, 0, 0, 0, 0, 0, 0, 1,
// flags (SC_SERIALIZABLE)
2,
// field count
0x0, 0x1,
// int dummy
'I', 0x0, 0x5, 'd', 'u', 'm', 'm', 'y',
// class annotation
0x78,
// super class desc
0x70,
// classdata[]
0x12, 0x34, 0x56, 0x78
});
ByteArrayInputStream in = new ByteArrayInputStream(array);
ObjectInputStream in2 = new ObjectInputStream(in);
expectEqual(true, in2.readBoolean());
expectEqual(17, in2.readByte());
expectEqual(0xcafebabe, in2.readInt());
expectEqual("Max & Möritz", in2.readUTF());
expectEqual('ɛ', in2.readChar());
Serialize unserialized = (Serialize) in2.readObject();
expectEqual(0x12345678, unserialized.dummy);
in2.close();
}
}