/* 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 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 { 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(); } }