From c2a6f4a7262ab4dfa76a4f300565e8733c32a596 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Tue, 29 Oct 2013 10:20:55 -0500 Subject: [PATCH] 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 --- classpath/avian/LegacyObjectOutputStream.java | 211 +++++++++ classpath/java/io/ObjectOutputStream.java | 421 +++++++++++------- 2 files changed, 467 insertions(+), 165 deletions(-) create mode 100644 classpath/avian/LegacyObjectOutputStream.java diff --git a/classpath/avian/LegacyObjectOutputStream.java b/classpath/avian/LegacyObjectOutputStream.java new file mode 100644 index 0000000000..7538538322 --- /dev/null +++ b/classpath/avian/LegacyObjectOutputStream.java @@ -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 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 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 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(")"); + } + +} diff --git a/classpath/java/io/ObjectOutputStream.java b/classpath/java/io/ObjectOutputStream.java index 0e707e485a..2ef6f87461 100644 --- a/classpath/java/io/ObjectOutputStream.java +++ b/classpath/java/io/ObjectOutputStream.java @@ -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 list = new ArrayList(); + 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 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 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 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(")"); - } - }