Implement a rudimentary Java-compatible ObjectInputStream

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.LegacyObjectInputStream.

This commit only implements the non-object parts of the deserialization
specification.

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 35ecf5025c
commit 4b8285e597
2 changed files with 387 additions and 161 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

@ -10,6 +10,29 @@
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 avian.VMClass;
import java.util.HashMap;
@ -17,218 +40,187 @@ import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
public class ObjectInputStream extends InputStream {
public class ObjectInputStream extends InputStream implements DataInput {
private final InputStream in;
private final PushbackReader r;
public ObjectInputStream(InputStream in) {
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);
}
}
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());
throw new UnsupportedOperationException();
}
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");
}
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();
public int skipBytes(int count) throws IOException {
int i = 0;
while (i < count) {
if (read() < 0) {
return i;
}
++i;
}
return count;
}
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;
}
}