corda/classpath/java/io/ObjectOutputStream.java
2014-04-23 15:33:41 -06:00

338 lines
9.3 KiB
Java

/* Copyright (c) 2008-2014, 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.Method;
import java.lang.reflect.Modifier;
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;
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, 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 {
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);
}
}
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;
}
private Object current;
public void defaultWriteObject() throws IOException {
defaultWriteObject(current);
}
private void defaultWriteObject(Object o) throws IOException {
for (Field field : getFields(o.getClass())) {
field(o, field);
}
}
}