From 7a4171f3ba204177d8bf9da7342447e04b545d8b Mon Sep 17 00:00:00 2001 From: akwizgran <akwizgran@users.sourceforge.net> Date: Mon, 18 Jul 2011 16:06:09 +0100 Subject: [PATCH] A more efficient encoding for short strings, raws, lists and maps. Now we can encode a list of three small integers in 4 bytes like MessagePack does, should that ever turn out to be useful. --- .../net/sf/briar/serial/ReaderImpl.java | 125 +++++++++----- .../net/sf/briar/serial/WriterImpl.java | 50 ++++-- test/net/sf/briar/serial/ReaderImplTest.java | 73 +++++--- test/net/sf/briar/serial/WriterImplTest.java | 161 +++++++++++++----- 4 files changed, 292 insertions(+), 117 deletions(-) diff --git a/components/net/sf/briar/serial/ReaderImpl.java b/components/net/sf/briar/serial/ReaderImpl.java index eb447459b1..3d417d047f 100644 --- a/components/net/sf/briar/serial/ReaderImpl.java +++ b/components/net/sf/briar/serial/ReaderImpl.java @@ -15,6 +15,8 @@ import net.sf.briar.api.serial.Tag; class ReaderImpl implements Reader { + private static final byte[] EMPTY_BUFFER = new byte[] {}; + private final InputStream in; private Consumer[] consumers = new Consumer[] {}; private boolean started = false, eof = false; @@ -143,18 +145,21 @@ class ReaderImpl implements Reader { } private void readIntoBuffer(int length) throws IOException { - assert length > 0; if(buf == null || buf.length < length) buf = new byte[length]; - buf[0] = next; + readIntoBuffer(buf, length); + } + + private void readIntoBuffer(byte[] b, int length) throws IOException { + b[0] = next; int offset = 1; while(offset < length) { - int read = in.read(buf, offset, length - offset); + int read = in.read(b, offset, length - offset); if(read == -1) break; offset += read; } if(offset < length) throw new FormatException(); // Feed the hungry mouths - for(Consumer c : consumers) c.write(buf, 0, length); + for(Consumer c : consumers) c.write(b, 0, length); // Read the lookahead byte int i = in.read(); if(i == -1) eof = true; @@ -226,26 +231,29 @@ class ReaderImpl implements Reader { public boolean hasString() throws IOException { if(!started) readNext(true); if(eof) return false; - return next == Tag.STRING; + return next == Tag.STRING + || (next & Tag.SHORT_MASK) == Tag.SHORT_STRING; } public String readString() throws IOException { if(!hasString()) throw new FormatException(); - readNext(false); - int length = readLength(); + if(next == Tag.STRING) { + readNext(false); + return readString(readLength()); + } else { + int length = 0xFF & next ^ Tag.SHORT_STRING; + readNext(length == 0); + return readString(length); + } + } + + private String readString(int length) throws IOException { + assert length >= 0; if(length == 0) return ""; - checkLimit(length); readIntoBuffer(length); return new String(buf, 0, length, "UTF-8"); } - private boolean hasLength() throws IOException { - if(!started) readNext(true); - if(eof) return false; - return next >= 0 || next == Tag.INT8 || next == Tag.INT16 - || next == Tag.INT32; - } - private int readLength() throws IOException { if(!hasLength()) throw new FormatException(); if(next >= 0) return readUint7(); @@ -255,32 +263,44 @@ class ReaderImpl implements Reader { throw new IllegalStateException(); } - private void checkLimit(long bytes) throws FormatException { - // FIXME + private boolean hasLength() throws IOException { + if(!started) readNext(true); + if(eof) return false; + return next >= 0 || next == Tag.INT8 || next == Tag.INT16 + || next == Tag.INT32; } public boolean hasRaw() throws IOException { if(!started) readNext(true); if(eof) return false; - return next == Tag.RAW; + return next == Tag.RAW || (next & Tag.SHORT_MASK) == Tag.SHORT_RAW; } public byte[] readRaw() throws IOException { if(!hasRaw()) throw new FormatException(); - readNext(false); - int length = readLength(); - if(length == 0) return new byte[] {}; - checkLimit(length); - readIntoBuffer(length); - byte[] b = buf; - buf = null; + if(next == Tag.RAW) { + readNext(false); + return readRaw(readLength()); + } else { + int length = 0xFF & next ^ Tag.SHORT_RAW; + readNext(length == 0); + return readRaw(length); + } + } + + private byte[] readRaw(int length) throws IOException { + assert length >= 0; + if(length == 0) return EMPTY_BUFFER; + byte[] b = new byte[length]; + readIntoBuffer(b, length); return b; } public boolean hasList() throws IOException { if(!started) readNext(true); if(eof) return false; - return next == Tag.LIST || next == Tag.LIST_START; + return next == Tag.LIST || next == Tag.LIST_START + || (next & Tag.SHORT_MASK) == Tag.SHORT_LIST; } public List<Object> readList() throws IOException { @@ -289,16 +309,26 @@ class ReaderImpl implements Reader { public <E> List<E> readList(Class<E> e) throws IOException { if(!hasList()) throw new FormatException(); - boolean definite = next == Tag.LIST; - readNext(false); - List<E> list = new ArrayList<E>(); - if(definite) { - int length = readLength(); - for(int i = 0; i < length; i++) list.add(readObject(e)); - } else { + if(next == Tag.LIST) { + readNext(false); + return readList(e, readLength()); + } else if(next == Tag.LIST_START) { + readNext(false); + List<E> list = new ArrayList<E>(); while(!hasEnd()) list.add(readObject(e)); readEnd(); + return list; + } else { + int length = 0xFF & next ^ Tag.SHORT_LIST; + readNext(length == 0); + return readList(e, length); } + } + + private <E> List<E> readList(Class<E> e, int length) throws IOException { + assert length >= 0; + List<E> list = new ArrayList<E>(); + for(int i = 0; i < length; i++) list.add(readObject(e)); return list; } @@ -315,7 +345,6 @@ class ReaderImpl implements Reader { } private Object readObject() throws IOException { - // FIXME: Use a switch statement if(!started) throw new IllegalStateException(); if(hasBoolean()) return Boolean.valueOf(readBoolean()); if(hasUint7()) return Byte.valueOf(readUint7()); @@ -367,7 +396,8 @@ class ReaderImpl implements Reader { public boolean hasMap() throws IOException { if(!started) readNext(true); if(eof) return false; - return next == Tag.MAP || next == Tag.MAP_START; + return next == Tag.MAP || next == Tag.MAP_START + || (next & Tag.SHORT_MASK) == Tag.SHORT_MAP; } public Map<Object, Object> readMap() throws IOException { @@ -376,16 +406,27 @@ class ReaderImpl implements Reader { public <K, V> Map<K, V> readMap(Class<K> k, Class<V> v) throws IOException { if(!hasMap()) throw new FormatException(); - boolean definite = next == Tag.MAP; - readNext(false); - Map<K, V> m = new HashMap<K, V>(); - if(definite) { - int length = readLength(); - for(int i = 0; i < length; i++) m.put(readObject(k), readObject(v)); - } else { + if(next == Tag.MAP) { + readNext(false); + return readMap(k, v, readLength()); + } else if(next == Tag.MAP_START) { + readNext(false); + Map<K, V> m = new HashMap<K, V>(); while(!hasEnd()) m.put(readObject(k), readObject(v)); readEnd(); + return m; + } else { + int size = 0xFF & next ^ Tag.SHORT_MAP; + readNext(size == 0); + return readMap(k, v, size); } + } + + private <K, V> Map<K, V> readMap(Class<K> k, Class<V> v, int size) + throws IOException { + assert size >= 0; + Map<K, V> m = new HashMap<K, V>(); + for(int i = 0; i < size; i++) m.put(readObject(k), readObject(v)); return m; } diff --git a/components/net/sf/briar/serial/WriterImpl.java b/components/net/sf/briar/serial/WriterImpl.java index dd3d301f0e..42083227a1 100644 --- a/components/net/sf/briar/serial/WriterImpl.java +++ b/components/net/sf/briar/serial/WriterImpl.java @@ -108,16 +108,38 @@ class WriterImpl implements Writer { } public void writeString(String s) throws IOException { - out.write(Tag.STRING); byte[] b = s.getBytes("UTF-8"); - writeIntAny(b.length); + if(b.length < 16) out.write(intToByte(Tag.SHORT_STRING | b.length)); + else { + out.write(Tag.STRING); + writeLength(b.length); + } out.write(b); bytesWritten += b.length + 1; } + private byte intToByte(int i) { + assert i >= 0; + assert i <= 255; + return (byte) (i > 127 ? i - 256 : i); + } + + private void writeLength(int i) throws IOException { + if(i >= 0 && i <= Byte.MAX_VALUE) + writeUint7((byte) i); + else if(i >= Byte.MIN_VALUE && i <= Byte.MAX_VALUE) + writeInt8((byte) i); + else if(i >= Short.MIN_VALUE && i <= Short.MAX_VALUE) + writeInt16((short) i); + else writeInt32(i); + } + public void writeRaw(byte[] b) throws IOException { - out.write(Tag.RAW); - writeIntAny(b.length); + if(b.length < 16) out.write(intToByte(Tag.SHORT_RAW | b.length)); + else { + out.write(Tag.RAW); + writeLength(b.length); + } out.write(b); bytesWritten += b.length + 1; } @@ -127,10 +149,14 @@ class WriterImpl implements Writer { } public void writeList(List<?> l) throws IOException { - out.write(Tag.LIST); - bytesWritten++; - writeIntAny(l.size()); + int length = l.size(); + if(length < 16) out.write(intToByte(Tag.SHORT_LIST | length)); + else { + out.write(Tag.LIST); + writeLength(length); + } for(Object o : l) writeObject(o); + bytesWritten++; } private void writeObject(Object o) throws IOException { @@ -160,13 +186,17 @@ class WriterImpl implements Writer { } public void writeMap(Map<?, ?> m) throws IOException { - out.write(Tag.MAP); - bytesWritten++; - writeIntAny(m.size()); + int length = m.size(); + if(length < 16) out.write(intToByte(Tag.SHORT_MAP | length)); + else { + out.write(Tag.MAP); + writeLength(length); + } for(Entry<?, ?> e : m.entrySet()) { writeObject(e.getKey()); writeObject(e.getValue()); } + bytesWritten++; } public void writeMapStart() throws IOException { diff --git a/test/net/sf/briar/serial/ReaderImplTest.java b/test/net/sf/briar/serial/ReaderImplTest.java index c88765c8a6..ff1d592118 100644 --- a/test/net/sf/briar/serial/ReaderImplTest.java +++ b/test/net/sf/briar/serial/ReaderImplTest.java @@ -120,25 +120,39 @@ public class ReaderImplTest extends TestCase { @Test public void testReadString() throws IOException { - setContents("F703666F6F" + "F703666F6F" + "F700"); + setContents("F703666F6F" + "83666F6F" + "F700" + "80"); assertEquals("foo", r.readString()); assertEquals("foo", r.readString()); assertEquals("", r.readString()); + assertEquals("", r.readString()); assertTrue(r.eof()); } @Test public void testReadRaw() throws IOException { - setContents("F603010203" + "F603010203" + "F600"); + setContents("F603010203" + "93010203" + "F600" + "90"); assertTrue(Arrays.equals(new byte[] {1, 2, 3}, r.readRaw())); assertTrue(Arrays.equals(new byte[] {1, 2, 3}, r.readRaw())); assertTrue(Arrays.equals(new byte[] {}, r.readRaw())); + assertTrue(Arrays.equals(new byte[] {}, r.readRaw())); + assertTrue(r.eof()); + } + + @Test + public void testReadShortList() throws IOException { + setContents("A" + "3" + "01" + "83666F6F" + "FC0080"); + List<Object> l = r.readList(Object.class); + assertNotNull(l); + assertEquals(3, l.size()); + assertEquals((byte) 1, l.get(0)); + assertEquals("foo", l.get(1)); + assertEquals((short) 128, l.get(2)); assertTrue(r.eof()); } @Test - public void testReadDefiniteList() throws IOException { - setContents("F5" + "03" + "01" + "F703666F6F" + "FC0080"); + public void testReadList() throws IOException { + setContents("F5" + "03" + "01" + "83666F6F" + "FC0080"); List<Object> l = r.readList(Object.class); assertNotNull(l); assertEquals(3, l.size()); @@ -149,8 +163,8 @@ public class ReaderImplTest extends TestCase { } @Test - public void testReadDefiniteListTypeSafe() throws IOException { - setContents("F5" + "03" + "01" + "02" + "03"); + public void testReadListTypeSafe() throws IOException { + setContents("A" + "3" + "01" + "02" + "03"); List<Byte> l = r.readList(Byte.class); assertNotNull(l); assertEquals(3, l.size()); @@ -161,8 +175,21 @@ public class ReaderImplTest extends TestCase { } @Test - public void testReadDefiniteMap() throws IOException { - setContents("F4" + "02" + "F703666F6F" + "7B" + "F600" + "F0"); + public void testReadShortMap() throws IOException { + setContents("B" + "2" + "83666F6F" + "7B" + "90" + "F0"); + Map<Object, Object> m = r.readMap(Object.class, Object.class); + assertNotNull(m); + assertEquals(2, m.size()); + assertEquals((byte) 123, m.get("foo")); + Raw raw = new RawByteArray(new byte[] {}); + assertTrue(m.containsKey(raw)); + assertNull(m.get(raw)); + assertTrue(r.eof()); + } + + @Test + public void testReadMap() throws IOException { + setContents("F4" + "02" + "83666F6F" + "7B" + "90" + "F0"); Map<Object, Object> m = r.readMap(Object.class, Object.class); assertNotNull(m); assertEquals(2, m.size()); @@ -174,8 +201,8 @@ public class ReaderImplTest extends TestCase { } @Test - public void testReadDefiniteMapTypeSafe() throws IOException { - setContents("F4" + "02" + "F703666F6F" + "7B" + "F700" + "F0"); + public void testReadMapTypeSafe() throws IOException { + setContents("B" + "2" + "83666F6F" + "7B" + "80" + "F0"); Map<String, Byte> m = r.readMap(String.class, Byte.class); assertNotNull(m); assertEquals(2, m.size()); @@ -186,8 +213,8 @@ public class ReaderImplTest extends TestCase { } @Test - public void testReadIndefiniteList() throws IOException { - setContents("F3" + "01" + "F703666F6F" + "FC0080" + "F1"); + public void testReadDelimitedList() throws IOException { + setContents("F3" + "01" + "83666F6F" + "FC0080" + "F1"); List<Object> l = r.readList(Object.class); assertNotNull(l); assertEquals(3, l.size()); @@ -198,8 +225,8 @@ public class ReaderImplTest extends TestCase { } @Test - public void testReadIndfiniteListElements() throws IOException { - setContents("F3" + "01" + "F703666F6F" + "FC0080" + "F1"); + public void testReadDelimitedListElements() throws IOException { + setContents("F3" + "01" + "83666F6F" + "FC0080" + "F1"); assertTrue(r.hasListStart()); r.readListStart(); assertFalse(r.hasListEnd()); @@ -214,7 +241,7 @@ public class ReaderImplTest extends TestCase { } @Test - public void testReadIndefiniteListTypeSafe() throws IOException { + public void testReadDelimitedListTypeSafe() throws IOException { setContents("F3" + "01" + "02" + "03" + "F1"); List<Byte> l = r.readList(Byte.class); assertNotNull(l); @@ -226,8 +253,8 @@ public class ReaderImplTest extends TestCase { } @Test - public void testReadIndefiniteMap() throws IOException { - setContents("F2" + "F703666F6F" + "7B" + "F600" + "F0" + "F1"); + public void testReadDelimitedMap() throws IOException { + setContents("F2" + "83666F6F" + "7B" + "90" + "F0" + "F1"); Map<Object, Object> m = r.readMap(Object.class, Object.class); assertNotNull(m); assertEquals(2, m.size()); @@ -239,8 +266,8 @@ public class ReaderImplTest extends TestCase { } @Test - public void testReadIndefiniteMapEntries() throws IOException { - setContents("F2" + "F703666F6F" + "7B" + "F600" + "F0" + "F1"); + public void testReadDelimitedMapEntries() throws IOException { + setContents("F2" + "83666F6F" + "7B" + "90" + "F0" + "F1"); assertTrue(r.hasMapStart()); r.readMapStart(); assertFalse(r.hasMapEnd()); @@ -258,8 +285,8 @@ public class ReaderImplTest extends TestCase { } @Test - public void testReadIndefiniteMapTypeSafe() throws IOException { - setContents("F2" + "F703666F6F" + "7B" + "F700" + "F0" + "F1"); + public void testReadDelimitedMapTypeSafe() throws IOException { + setContents("F2" + "83666F6F" + "7B" + "80" + "F0" + "F1"); Map<String, Byte> m = r.readMap(String.class, Byte.class); assertNotNull(m); assertEquals(2, m.size()); @@ -272,8 +299,8 @@ public class ReaderImplTest extends TestCase { @Test @SuppressWarnings("unchecked") public void testReadNestedMapsAndLists() throws IOException { - setContents("F4" + "01" + "F4" + "01" + "F703666F6F" + "7B" + - "F5" + "01" + "01"); + setContents("B" + "1" + "B" + "1" + "83666F6F" + "7B" + + "A" + "1" + "01"); Map<Object, Object> m = r.readMap(Object.class, Object.class); assertNotNull(m); assertEquals(1, m.size()); diff --git a/test/net/sf/briar/serial/WriterImplTest.java b/test/net/sf/briar/serial/WriterImplTest.java index 1abbcaf471..b5b91a427f 100644 --- a/test/net/sf/briar/serial/WriterImplTest.java +++ b/test/net/sf/briar/serial/WriterImplTest.java @@ -30,13 +30,15 @@ public class WriterImplTest extends TestCase { public void testWriteBoolean() throws IOException { w.writeBoolean(true); w.writeBoolean(false); - checkContents("FEFF"); + // TRUE tag, FALSE tag + checkContents("FE" + "FF"); } @Test public void testWriteUint7() throws IOException { w.writeUint7((byte) 0); w.writeUint7((byte) 127); + // 0, 127 checkContents("00" + "7F"); } @@ -46,7 +48,8 @@ public class WriterImplTest extends TestCase { w.writeInt8((byte) -1); w.writeInt8((byte) -128); w.writeInt8((byte) 127); - checkContents("FD00" + "FDFF" + "FD80" + "FD7F"); + // INT8 tag, 0, INT8 tag, -1, INT8 tag, -128, INT8 tag, 127 + checkContents("FD" + "00" + "FD" + "FF" + "FD" + "80" + "FD" + "7F"); } @Test @@ -55,7 +58,9 @@ public class WriterImplTest extends TestCase { w.writeInt16((short) -1); w.writeInt16((short) -32768); w.writeInt16((short) 32767); - checkContents("FC0000" + "FCFFFF" + "FC8000" + "FC7FFF"); + // INT16 tag, 0, INT16 tag, -1, INT16 tag, -32768, INT16 tag, 32767 + checkContents("FC" + "0000" + "FC" + "FFFF" + "FC" + "8000" + + "FC" + "7FFF"); } @Test @@ -64,8 +69,9 @@ public class WriterImplTest extends TestCase { w.writeInt32(-1); w.writeInt32(-2147483648); w.writeInt32(2147483647); - checkContents("FB00000000" + "FBFFFFFFFF" + - "FB80000000" + "FB7FFFFFFF"); + // INT32 tag, 0, INT32 tag, -1, etc + checkContents("FB" + "00000000" + "FB" + "FFFFFFFF" + "FB" + "80000000" + + "FB" + "7FFFFFFF"); } @Test @@ -74,8 +80,9 @@ public class WriterImplTest extends TestCase { w.writeInt64(-1L); w.writeInt64(-9223372036854775808L); w.writeInt64(9223372036854775807L); - checkContents("FA0000000000000000" + "FAFFFFFFFFFFFFFFFF" + - "FA8000000000000000" + "FA7FFFFFFFFFFFFFFF"); + // INT64 tag, 0, INT64 tag, -1, etc + checkContents("FA" + "0000000000000000" + "FA" + "FFFFFFFFFFFFFFFF" + + "FA" + "8000000000000000" + "FA" + "7FFFFFFFFFFFFFFF"); } @Test @@ -88,8 +95,8 @@ public class WriterImplTest extends TestCase { w.writeIntAny(32768L); // int32 w.writeIntAny(2147483647L); // int32 w.writeIntAny(2147483648L); // int64 - checkContents("00" + "7F" + "FDFF" + "FC0080" + "FC7FFF" + - "FB00008000" + "FB7FFFFFFF" + "FA0000000080000000"); + checkContents("00" + "7F" + "FDFF" + "FC0080" + "FC7FFF" + + "FB00008000" + "FB7FFFFFFF" + "FA0000000080000000"); } @Test @@ -105,9 +112,9 @@ public class WriterImplTest extends TestCase { w.writeFloat32(Float.NEGATIVE_INFINITY); // 1 255 0 -> 0xFF800000 w.writeFloat32(Float.POSITIVE_INFINITY); // 0 255 0 -> 0x7F800000 w.writeFloat32(Float.NaN); // 0 255 1 -> 0x7FC00000 - checkContents("F900000000" + "F93F800000" + "F940000000" + - "F9BF800000" + "F980000000" + "F9FF800000" + - "F97F800000" + "F97FC00000"); + checkContents("F9" + "00000000" + "F9" + "3F800000" + "F9" + "40000000" + + "F9" + "BF800000" + "F9" + "80000000" + "F9" + "FF800000" + + "F9" + "7F800000" + "F9" + "7FC00000"); } @Test @@ -121,70 +128,138 @@ public class WriterImplTest extends TestCase { w.writeFloat64(Double.NEGATIVE_INFINITY); // 1 2047 0 -> 0xFFF00000... w.writeFloat64(Double.POSITIVE_INFINITY); // 0 2047 0 -> 0x7FF00000... w.writeFloat64(Double.NaN); // 0 2047 1 -> 0x7FF8000000000000 - checkContents("F80000000000000000" + "F83FF0000000000000" + - "F84000000000000000" + "F8BFF0000000000000" + - "F88000000000000000" + "F8FFF0000000000000" + - "F87FF0000000000000" + "F87FF8000000000000"); + checkContents("F8" + "0000000000000000" + "F8" + "3FF0000000000000" + + "F8" + "4000000000000000" + "F8" + "BFF0000000000000" + + "F8" + "8000000000000000" + "F8" + "FFF0000000000000" + + "F8" + "7FF0000000000000" + "F8" + "7FF8000000000000"); } @Test - public void testWriteUtf8() throws IOException { - w.writeString("foo"); - // UTF-8 tag, length as uint7, UTF-8 bytes - checkContents("F7" + "03" + "666F6F"); + public void testWriteShortString() throws IOException { + w.writeString("foo bar baz bam"); + // SHORT_STRING tag, length 15, UTF-8 bytes + checkContents("8" + "F" + "666F6F206261722062617A2062616D"); + } + + @Test + public void testWriteString() throws IOException { + w.writeString("foo bar baz bam "); + // STRING tag, length 16 as uint7, UTF-8 bytes + checkContents("F7" + "10" + "666F6F206261722062617A2062616D20"); + } + + @Test + public void testWriteShortRawBytes() throws IOException { + w.writeRaw(new byte[] { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 + }); + // SHORT_RAW tag, length 15, raw bytes + checkContents("9" + "F" + "000102030405060708090A0B0C0D0E"); + } + + @Test + public void testWriteShortRawObject() throws IOException { + w.writeRaw(new RawByteArray(new byte[] { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14 + })); + // SHORT_RAW tag, length 15, raw bytes + checkContents("9" + "F" + "000102030405060708090A0B0C0D0E"); } @Test public void testWriteRawBytes() throws IOException { - w.writeRaw(new byte[] {0, 1, -1, 127, -128}); - checkContents("F6" + "05" + "0001FF7F80"); + w.writeRaw(new byte[] { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + }); + // RAW tag, length 16 as uint7, raw bytes + checkContents("F6" + "10" + "000102030405060708090A0B0C0D0E0F"); } @Test public void testWriteRawObject() throws IOException { - w.writeRaw(new RawByteArray(new byte[] {0, 1, -1, 127, -128})); - checkContents("F6" + "05" + "0001FF7F80"); + w.writeRaw(new RawByteArray(new byte[] { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 + })); + // RAW tag, length 16 as uint7, raw bytes + checkContents("F6" + "10" + "000102030405060708090A0B0C0D0E0F"); } @Test - public void testWriteDefiniteList() throws IOException { + public void testWriteShortList() throws IOException { List<Object> l = new ArrayList<Object>(); - l.add(Byte.valueOf((byte) 1)); // Written as a uint7 - l.add("foo"); - l.add(Long.valueOf(128L)); // Written as an int16 + for(int i = 0; i < 15; i++) l.add(i); w.writeList(l); - checkContents("F5" + "03" + "01" + "F703666F6F" + "FC0080"); + // SHORT_LIST tag, length, elements as uint7 + checkContents("A" + "F" + "000102030405060708090A0B0C0D0E"); + } + + @Test + public void testWriteList() throws IOException { + List<Object> l = new ArrayList<Object>(); + for(int i = 0; i < 16; i++) l.add(i); + w.writeList(l); + // LIST tag, length as uint7, elements as uint7 + checkContents("F5" + "10" + "000102030405060708090A0B0C0D0E0F"); + } + + @Test + public void testListCanContainNull() throws IOException { + List<Object> l = new ArrayList<Object>(); + l.add(1); + l.add(null); + l.add(2); + w.writeList(l); + // SHORT_LIST tag, length, 1 as uint7, null, 2 as uint7 + checkContents("A" + "3" + "01" + "F0" + "02"); + } + + @Test + public void testWriteShortMap() throws IOException { + // Use LinkedHashMap to get predictable iteration order + Map<Object, Object> m = new LinkedHashMap<Object, Object>(); + for(int i = 0; i < 15; i++) m.put(i, i + 1); + w.writeMap(m); + // SHORT_MAP tag, size, entries as uint7 + checkContents("B" + "F" + "0001" + "0102" + "0203" + "0304" + "0405" + + "0506" + "0607" + "0708" + "0809" + "090A" + "0A0B" + "0B0C" + + "0C0D" + "0D0E" + "0E0F"); } @Test - public void testWriteDefiniteMap() throws IOException { + public void testWriteMap() throws IOException { // Use LinkedHashMap to get predictable iteration order Map<Object, Object> m = new LinkedHashMap<Object, Object>(); - m.put("foo", Integer.valueOf(123)); // Written as a uint7 - m.put(new RawByteArray(new byte[] {}), null); // Empty array != null + for(int i = 0; i < 16; i++) m.put(i, i + 1); w.writeMap(m); - checkContents("F4" + "02" + "F703666F6F" + "7B" + "F600" + "F0"); + // MAP tag, size as uint7, entries as uint7 + checkContents("F4" + "10" + "0001" + "0102" + "0203" + "0304" + "0405" + + "0506" + "0607" + "0708" + "0809" + "090A" + "0A0B" + "0B0C" + + "0C0D" + "0D0E" + "0E0F" + "0F10"); } @Test - public void testWriteIndefiniteList() throws IOException { + public void testWriteDelimitedList() throws IOException { w.writeListStart(); w.writeIntAny((byte) 1); // Written as uint7 - w.writeString("foo"); + w.writeString("foo"); // Written as short string w.writeIntAny(128L); // Written as an int16 w.writeListEnd(); - checkContents("F3" + "01" + "F703666F6F" + "FC0080" + "F1"); + // LIST_START tag, 1 as uint7, "foo" as short string, 128 as int16, + // END tag + checkContents("F3" + "01" + "83666F6F" + "FC0080" + "F1"); } @Test - public void testWriteIndefiniteMap() throws IOException { + public void testWriteDelimitedMap() throws IOException { w.writeMapStart(); - w.writeString("foo"); + w.writeString("foo"); // Written as short string w.writeIntAny(123); // Written as a uint7 - w.writeRaw(new byte[] {}); + w.writeRaw(new byte[] {}); // Written as short raw w.writeNull(); w.writeMapEnd(); - checkContents("F2" + "F703666F6F" + "7B" + "F600" + "F0" + "F1"); + // MAP_START tag, "foo" as short string, 123 as uint7, + // byte[] {} as short raw, NULL tag, END tag + checkContents("F2" + "83666F6F" + "7B" + "90" + "F0" + "F1"); } @Test @@ -196,8 +271,10 @@ public class WriterImplTest extends TestCase { Map<Object, Object> m1 = new LinkedHashMap<Object, Object>(); m1.put(m, l); w.writeMap(m1); - checkContents("F4" + "01" + "F4" + "01" + "F703666F6F" + "7B" + - "F5" + "01" + "01"); + // SHORT_MAP tag, length 1, SHORT_MAP tag, length 1, + // "foo" as short string, 123 as uint7, SHORT_LIST tag, length 1, + // 1 as uint7 + checkContents("B" + "1" + "B" + "1" + "83666F6F" + "7B" + "A1" + "01"); } @Test -- GitLab