From 0f4ffe9fbcf1ddd1c74e8f8c5aebee633d6cf094 Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Sun, 10 Jul 2011 18:31:18 +0100
Subject: [PATCH] Added type-safe accessors and iterator accessors for lists
 and maps.

---
 api/net/sf/briar/api/serial/Reader.java       |  18 +-
 .../net/sf/briar/serial/ReaderImpl.java       | 254 +++++++++++++++---
 test/net/sf/briar/serial/ReaderImplTest.java  | 207 +++++++++++++-
 3 files changed, 422 insertions(+), 57 deletions(-)

diff --git a/api/net/sf/briar/api/serial/Reader.java b/api/net/sf/briar/api/serial/Reader.java
index ec90dd65f1..652cd94d3d 100644
--- a/api/net/sf/briar/api/serial/Reader.java
+++ b/api/net/sf/briar/api/serial/Reader.java
@@ -1,8 +1,10 @@
 package net.sf.briar.api.serial;
 
 import java.io.IOException;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
 
 public interface Reader {
 
@@ -37,17 +39,17 @@ public interface Reader {
 	byte[] readRaw() throws IOException;
 	byte[] readRaw(int maxLength) throws IOException;
 
-	// FIXME: Add type-safe readers and iterator readers
-
-	boolean hasList(boolean definite) throws IOException;
-	List<?> readList(boolean definite) throws IOException;
 	boolean hasList() throws IOException;
-	List<?> readList() throws IOException;
+	List<Object> readList() throws IOException;
+	Iterator<Object> readListElements() throws IOException;
+	<E> List<E> readList(Class<E> e) throws IOException;
+	<E> Iterator<E> readListElements(Class<E> e) throws IOException;
 
-	boolean hasMap(boolean definite) throws IOException;
-	Map<?, ?> readMap(boolean definite) throws IOException;
 	boolean hasMap() throws IOException;
-	Map<?, ?> readMap() throws IOException;
+	Map<Object, Object> readMap() throws IOException;
+	Iterator<Entry<Object, Object>> readMapEntries() throws IOException;
+	<K, V> Map<K, V> readMap(Class<K> k, Class<V> v) throws IOException;
+	<K, V> Iterator<Entry<K, V>> readMapEntries(Class<K> k, Class<V> v) throws IOException;
 
 	boolean hasNull() throws IOException;
 	void readNull() throws IOException;
diff --git a/components/net/sf/briar/serial/ReaderImpl.java b/components/net/sf/briar/serial/ReaderImpl.java
index a57390f33a..cffaf557a4 100644
--- a/components/net/sf/briar/serial/ReaderImpl.java
+++ b/components/net/sf/briar/serial/ReaderImpl.java
@@ -4,8 +4,11 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Map.Entry;
+import java.util.NoSuchElementException;
 
 import net.sf.briar.api.serial.FormatException;
 import net.sf.briar.api.serial.Reader;
@@ -243,30 +246,45 @@ public class ReaderImpl implements Reader {
 		return b;
 	}
 
-	public boolean hasList(boolean definite) throws IOException {
+	public boolean hasList() throws IOException {
 		if(!started) readNext(true);
 		if(eof) return false;
-		if(definite) return next == Tag.LIST_DEF;
-		else return next == Tag.LIST_INDEF;
+		return next == Tag.LIST_DEF || next == Tag.LIST_INDEF;
+	}
+
+	public List<Object> readList() throws IOException {
+		return readList(Object.class);
+	}
+
+	public Iterator<Object> readListElements() throws IOException {
+		return readListElements(Object.class);
 	}
 
-	@SuppressWarnings({ "unchecked", "rawtypes" })
-	public List<?> readList(boolean definite) throws IOException {
-		if(!hasList(definite)) throw new FormatException();
+	public <E> List<E> readList(Class<E> e) throws IOException {
+		if(!hasList()) throw new FormatException();
+		boolean definite = next == Tag.LIST_DEF;
 		readNext(false);
-		List list = new ArrayList();
+		List<E> list = new ArrayList<E>();
 		if(definite) {
 			long l = readIntAny();
 			if(l < 0 || l > Integer.MAX_VALUE) throw new FormatException();
 			int length = (int) l;
-			for(int i = 0; i < length; i++) list.add(readObject());
+			for(int i = 0; i < length; i++) list.add(readObject(e));
 		} else {
-			while(!hasEnd()) list.add(readObject());
+			while(!hasEnd()) list.add(readObject(e));
 			readEnd();
 		}
 		return list;
 	}
 
+	public <E> Iterator<E> readListElements(Class<E> e) throws IOException {
+		if(!hasList()) throw new FormatException();
+		boolean definite = next == Tag.LIST_DEF;
+		readNext(false);
+		if(definite) return new DefiniteListIterator<E>(e);
+		else return new IndefiniteListIterator<E>(e);
+	}
+
 	private boolean hasEnd() throws IOException {
 		if(!started) readNext(true);
 		if(eof) return false;
@@ -299,52 +317,53 @@ public class ReaderImpl implements Reader {
 		throw new IllegalStateException();
 	}
 
-	public boolean hasList() throws IOException {
+	@SuppressWarnings("unchecked")
+	private <T> T readObject(Class<T> t) throws IOException {
+		try {
+			return (T) readObject();
+		} catch(ClassCastException e) {
+			throw new FormatException();
+		}
+	}
+
+	public boolean hasMap() throws IOException {
 		if(!started) readNext(true);
 		if(eof) return false;
-		return next == Tag.LIST_DEF || next == Tag.LIST_INDEF;
+		return next == Tag.MAP_DEF || next == Tag.MAP_INDEF;
 	}
 
-	public List<?> readList() throws IOException {
-		if(hasList(true)) return readList(true);
-		if(hasList(false)) return readList(false);
-		throw new FormatException();
+	public Map<Object, Object> readMap() throws IOException {
+		return readMap(Object.class, Object.class);
 	}
 
-	public boolean hasMap(boolean definite) throws IOException {
-		if(!started) readNext(true);
-		if(eof) return false;
-		if(definite) return next == Tag.MAP_DEF;
-		else return next == Tag.MAP_INDEF;
+	public Iterator<Entry<Object, Object>> readMapEntries() throws IOException {
+		return readMapEntries(Object.class, Object.class);
 	}
 
-	@SuppressWarnings({ "unchecked", "rawtypes" })
-	public Map<?, ?> readMap(boolean definite) throws IOException {
-		if(!hasMap(definite)) throw new FormatException();
+	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_DEF;
 		readNext(false);
-		Map m = new HashMap();
+		Map<K, V> m = new HashMap<K, V>();
 		if(definite) {
 			long l = readIntAny();
 			if(l < 0 || l > Integer.MAX_VALUE) throw new FormatException();
 			int length = (int) l;
-			for(int i = 0; i < length; i++) m.put(readObject(), readObject());
+			for(int i = 0; i < length; i++) m.put(readObject(k), readObject(v));
 		} else {
-			while(!hasEnd()) m.put(readObject(), readObject());
+			while(!hasEnd()) m.put(readObject(k), readObject(v));
 			readEnd();
 		}
 		return m;
 	}
 
-	public boolean hasMap() throws IOException {
-		if(!started) readNext(true);
-		if(eof) return false;
-		return next == Tag.MAP_DEF || next == Tag.MAP_INDEF;
-	}
-
-	public Map<?, ?> readMap() throws IOException {
-		if(hasMap(true)) return readMap(true);
-		if(hasMap(false)) return readMap(false);
-		throw new FormatException();
+	public <K, V> Iterator<Entry<K, V>> readMapEntries(Class<K> k, Class<V> v)
+	throws IOException {
+		if(!hasMap()) throw new FormatException();
+		boolean definite = next == Tag.MAP_DEF;
+		readNext(false);
+		if(definite) return new DefiniteMapIterator<K, V>(k, v);
+		else return new IndefiniteMapIterator<K, V>(k, v);
 	}
 
 	public boolean hasNull() throws IOException {
@@ -357,4 +376,167 @@ public class ReaderImpl implements Reader {
 		if(!hasNull()) throw new FormatException();
 		readNext(true);
 	}
+
+	private class DefiniteListIterator<E> implements Iterator<E> {
+
+		private final Class<E> e;
+		private int remaining;
+
+		private DefiniteListIterator(Class<E> e) throws IOException {
+			this.e = e;
+			long l = readIntAny();
+			if(l < 0 || l > Integer.MAX_VALUE) throw new FormatException();
+			remaining = (int) l;
+		}
+
+		public boolean hasNext() {
+			return remaining > 0;
+		}
+
+		public E next() {
+			if(remaining == 0) throw new NoSuchElementException();
+			remaining--;
+			try {
+				return readObject(e);
+			} catch(IOException ex) {
+				throw new RuntimeException(ex);
+			}
+		}
+
+		public void remove() {
+			throw new UnsupportedOperationException();
+		}
+	}
+
+	private class IndefiniteListIterator<E> implements Iterator<E> {
+
+		private final Class<E> e;
+		private boolean hasNext = true;
+
+		private IndefiniteListIterator(Class<E> e) throws IOException {
+			this.e = e;
+			if(hasEnd()) {
+				readEnd();
+				hasNext = false;
+			}
+		}
+
+		public boolean hasNext() {
+			return hasNext;
+		}
+
+		public E next() {
+			if(!hasNext) throw new NoSuchElementException();
+			try {
+				E next = readObject(e);
+				if(hasEnd()) {
+					readEnd();
+					hasNext = false;
+				}
+				return next;
+			} catch(IOException ex) {
+				throw new RuntimeException(ex);
+			}
+		}
+
+		public void remove() {
+			throw new UnsupportedOperationException();
+		}
+	}
+
+	private class DefiniteMapIterator<K, V> implements Iterator<Entry<K, V>> {
+
+		private final Class<K> k;
+		private final Class<V> v;
+		private int remaining;
+
+		private DefiniteMapIterator(Class<K> k, Class<V> v) throws IOException {
+			this.k = k;
+			this.v = v;
+			long l = readIntAny();
+			if(l < 0 || l > Integer.MAX_VALUE) throw new FormatException();
+			remaining = (int) l;
+		}
+
+		public boolean hasNext() {
+			return remaining > 0;
+		}
+
+		public Entry<K, V> next() {
+			if(remaining == 0) throw new NoSuchElementException();
+			remaining--;
+			try {
+				return new MapEntry<K, V>(readObject(k), readObject(v));
+			} catch(IOException ex) {
+				throw new RuntimeException(ex);
+			}
+		}
+
+		public void remove() {
+			throw new UnsupportedOperationException();
+		}
+	}
+
+	private class IndefiniteMapIterator<K, V> implements Iterator<Entry<K, V>> {
+
+		private final Class<K> k;
+		private final Class<V> v;
+		private boolean hasNext = true;
+
+		private IndefiniteMapIterator(Class<K> k, Class<V> v)
+		throws IOException {
+			this.k = k;
+			this.v = v;
+			if(hasEnd()) {
+				readEnd();
+				hasNext = false;
+			}
+		}
+
+		public boolean hasNext() {
+			return hasNext;
+		}
+
+		public Entry<K, V> next() {
+			if(!hasNext) throw new NoSuchElementException();
+			try {
+				Entry<K, V> next =
+					new MapEntry<K, V>(readObject(k), readObject(v));
+				if(hasEnd()) {
+					readEnd();
+					hasNext = false;
+				}
+				return next;
+			} catch(IOException ex) {
+				throw new RuntimeException(ex);
+			}
+		}
+
+		public void remove() {
+			throw new UnsupportedOperationException();
+		}
+	}
+
+	private static class MapEntry<K, V> implements Entry<K, V> {
+
+		private final K k;
+		private final V v;
+
+		MapEntry(K k, V v) {
+			this.k = k;
+			this.v = v;
+		}
+
+		public K getKey() {
+			return k;
+		}
+
+		public V getValue() {
+			return v;
+		}
+
+		public V setValue(V value) {
+			throw new UnsupportedOperationException();
+		}
+	}
 }
diff --git a/test/net/sf/briar/serial/ReaderImplTest.java b/test/net/sf/briar/serial/ReaderImplTest.java
index ea9fb88b52..f1826f5fd5 100644
--- a/test/net/sf/briar/serial/ReaderImplTest.java
+++ b/test/net/sf/briar/serial/ReaderImplTest.java
@@ -3,6 +3,7 @@ package net.sf.briar.serial;
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.util.Arrays;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -169,10 +170,9 @@ public class ReaderImplTest extends TestCase {
 	}
 
 	@Test
-	@SuppressWarnings("rawtypes")
 	public void testReadDefiniteList() throws IOException {
 		setContents("F5" + "03" + "01" + "F703666F6F" + "FC0080");
-		List l = r.readList(true);
+		List<Object> l = r.readList();
 		assertNotNull(l);
 		assertEquals(3, l.size());
 		assertEquals((byte) 1, l.get(0));
@@ -182,10 +182,51 @@ public class ReaderImplTest extends TestCase {
 	}
 
 	@Test
-	@SuppressWarnings("rawtypes")
+	public void testReadDefiniteListElements() throws IOException {
+		setContents("F5" + "03" + "01" + "F703666F6F" + "FC0080");
+		Iterator<Object> i = r.readListElements();
+		assertNotNull(i);
+		assertTrue(i.hasNext());
+		assertEquals((byte) 1, i.next());
+		assertTrue(i.hasNext());
+		assertEquals("foo", i.next());
+		assertTrue(i.hasNext());
+		assertEquals((short) 128, i.next());
+		assertFalse(i.hasNext());
+		assertTrue(r.eof());
+	}
+
+	@Test
+	public void testReadDefiniteListTypeSafe() throws IOException {
+		setContents("F5" + "03" + "01" + "02" + "03");
+		List<Byte> l = r.readList(Byte.class);
+		assertNotNull(l);
+		assertEquals(3, l.size());
+		assertEquals(Byte.valueOf((byte) 1), l.get(0));
+		assertEquals(Byte.valueOf((byte) 2), l.get(1));
+		assertEquals(Byte.valueOf((byte) 3), l.get(2));
+		assertTrue(r.eof());
+	}
+
+	@Test
+	public void testReadDefiniteListElementsTypeSafe() throws IOException {
+		setContents("F5" + "03" + "01" + "02" + "03");
+		Iterator<Byte> i = r.readListElements(Byte.class);
+		assertNotNull(i);
+		assertTrue(i.hasNext());
+		assertEquals(Byte.valueOf((byte) 1), i.next());
+		assertTrue(i.hasNext());
+		assertEquals(Byte.valueOf((byte) 2), i.next());
+		assertTrue(i.hasNext());
+		assertEquals(Byte.valueOf((byte) 3), i.next());
+		assertFalse(i.hasNext());
+		assertTrue(r.eof());
+	}
+
+	@Test
 	public void testReadDefiniteMap() throws IOException {
 		setContents("F4" + "02" + "F703666F6F" + "7B" + "F600" + "F0");
-		Map m = r.readMap(true);
+		Map<Object, Object> m = r.readMap();
 		assertNotNull(m);
 		assertEquals(2, m.size());
 		assertEquals((byte) 123, m.get("foo"));
@@ -196,10 +237,58 @@ public class ReaderImplTest extends TestCase {
 	}
 
 	@Test
-	@SuppressWarnings("rawtypes")
+	public void testReadDefiniteMapEntries() throws IOException {
+		setContents("F4" + "02" + "F703666F6F" + "7B" + "F600" + "F0");
+		Iterator<Entry<Object, Object>> i = r.readMapEntries();
+		assertNotNull(i);
+		assertTrue(i.hasNext());
+		Entry<Object, Object> e = i.next();
+		assertNotNull(e);
+		assertEquals("foo", e.getKey());
+		assertEquals((byte) 123, e.getValue());
+		assertTrue(i.hasNext());
+		e = i.next();
+		assertNotNull(e);
+		assertEquals(new RawImpl(new byte[] {}), e.getKey());
+		assertNull(e.getValue());
+		assertTrue(r.eof());
+	}
+
+	@Test
+	public void testReadDefiniteMapTypeSafe() throws IOException {
+		setContents("F4" + "02" + "F703666F6F" + "7B" + "F700" + "F0");
+		Map<String, Byte> m = r.readMap(String.class, Byte.class);
+		assertNotNull(m);
+		assertEquals(2, m.size());
+		assertEquals(Byte.valueOf((byte) 123), m.get("foo"));
+		assertTrue(m.containsKey(""));
+		assertNull(m.get(""));
+		assertTrue(r.eof());
+	}
+
+	@Test
+	public void testReadDefiniteMapEntriesTypeSafe() throws IOException {
+		setContents("F4" + "02" + "F703666F6F" + "7B" + "F700" + "F0");
+		Iterator<Entry<String, Byte>> i =
+			r.readMapEntries(String.class, Byte.class);
+		assertNotNull(i);
+		assertTrue(i.hasNext());
+		Entry<String, Byte> e = i.next();
+		assertNotNull(e);
+		assertEquals("foo", e.getKey());
+		assertEquals(Byte.valueOf((byte) 123), e.getValue());
+		assertTrue(i.hasNext());
+		e = i.next();
+		assertNotNull(e);
+		assertEquals("", e.getKey());
+		assertNull(e.getValue());
+		assertTrue(r.eof());
+	}
+
+	@Test
 	public void testReadIndefiniteList() throws IOException {
 		setContents("F3" + "01" + "F703666F6F" + "FC0080" + "F1");
-		List l = r.readList(false);
+		List<Object> l = r.readList();
 		assertNotNull(l);
 		assertEquals(3, l.size());
 		assertEquals((byte) 1, l.get(0));
@@ -209,10 +298,51 @@ public class ReaderImplTest extends TestCase {
 	}
 
 	@Test
-	@SuppressWarnings("rawtypes")
+	public void testReadIndfiniteListElements() throws IOException {
+		setContents("F3" + "01" + "F703666F6F" + "FC0080" + "F1");
+		Iterator<Object> i = r.readListElements();
+		assertNotNull(i);
+		assertTrue(i.hasNext());
+		assertEquals((byte) 1, i.next());
+		assertTrue(i.hasNext());
+		assertEquals("foo", i.next());
+		assertTrue(i.hasNext());
+		assertEquals((short) 128, i.next());
+		assertFalse(i.hasNext());
+		assertTrue(r.eof());
+	}
+
+	@Test
+	public void testReadIndefiniteListTypeSafe() throws IOException {
+		setContents("F3" + "01" + "02" + "03" + "F1");
+		List<Byte> l = r.readList(Byte.class);
+		assertNotNull(l);
+		assertEquals(3, l.size());
+		assertEquals(Byte.valueOf((byte) 1), l.get(0));
+		assertEquals(Byte.valueOf((byte) 2), l.get(1));
+		assertEquals(Byte.valueOf((byte) 3), l.get(2));
+		assertTrue(r.eof());
+	}
+
+	@Test
+	public void testReadIndefiniteListElementsTypeSafe() throws IOException {
+		setContents("F3" + "01" + "02" + "03" + "F1");
+		Iterator<Byte> i = r.readListElements(Byte.class);
+		assertNotNull(i);
+		assertTrue(i.hasNext());
+		assertEquals(Byte.valueOf((byte) 1), i.next());
+		assertTrue(i.hasNext());
+		assertEquals(Byte.valueOf((byte) 2), i.next());
+		assertTrue(i.hasNext());
+		assertEquals(Byte.valueOf((byte) 3), i.next());
+		assertFalse(i.hasNext());
+		assertTrue(r.eof());
+	}
+
+	@Test
 	public void testReadIndefiniteMap() throws IOException {
 		setContents("F2" + "F703666F6F" + "7B" + "F600" + "F0" + "F1");
-		Map m = r.readMap(false);
+		Map<Object, Object> m = r.readMap();
 		assertNotNull(m);
 		assertEquals(2, m.size());
 		assertEquals((byte) 123, m.get("foo"));
@@ -223,19 +353,70 @@ public class ReaderImplTest extends TestCase {
 	}
 
 	@Test
-	@SuppressWarnings("rawtypes")
+	public void testReadIndefiniteMapEntries() throws IOException {
+		setContents("F2" + "F703666F6F" + "7B" + "F600" + "F0" + "F1");
+		Iterator<Entry<Object, Object>> i = r.readMapEntries();
+		assertNotNull(i);
+		assertTrue(i.hasNext());
+		Entry<Object, Object> e = i.next();
+		assertNotNull(e);
+		assertEquals("foo", e.getKey());
+		assertEquals((byte) 123, e.getValue());
+		assertTrue(i.hasNext());
+		e = i.next();
+		assertNotNull(e);
+		assertEquals(new RawImpl(new byte[] {}), e.getKey());
+		assertNull(e.getValue());
+		assertFalse(i.hasNext());
+		assertTrue(r.eof());
+	}
+
+	@Test
+	public void testReadIndefiniteMapTypeSafe() throws IOException {
+		setContents("F2" + "F703666F6F" + "7B" + "F700" + "F0" + "F1");
+		Map<String, Byte> m = r.readMap(String.class, Byte.class);
+		assertNotNull(m);
+		assertEquals(2, m.size());
+		assertEquals(Byte.valueOf((byte) 123), m.get("foo"));
+		assertTrue(m.containsKey(""));
+		assertNull(m.get(""));
+		assertTrue(r.eof());
+	}
+
+	@Test
+	public void testReadIndefiniteMapEntriesTypeSafe() throws IOException {
+		setContents("F2" + "F703666F6F" + "7B" + "F700" + "F0" + "F1");
+		Iterator<Entry<String, Byte>> i =
+			r.readMapEntries(String.class, Byte.class);
+		assertNotNull(i);
+		assertTrue(i.hasNext());
+		Entry<String, Byte> e = i.next();
+		assertNotNull(e);
+		assertEquals("foo", e.getKey());
+		assertEquals(Byte.valueOf((byte) 123), e.getValue());
+		assertTrue(i.hasNext());
+		e = i.next();
+		assertNotNull(e);
+		assertEquals("", e.getKey());
+		assertNull(e.getValue());
+		assertFalse(i.hasNext());
+		assertTrue(r.eof());
+	}
+
+	@Test
+	@SuppressWarnings("unchecked")
 	public void testReadNestedMapsAndLists() throws IOException {
 		setContents("F4" + "01" + "F4" + "01" + "F703666F6F" + "7B" +
 				"F5" + "01" + "01");
-		Map m = r.readMap();
+		Map<Object, Object> m = r.readMap();
 		assertNotNull(m);
 		assertEquals(1, m.size());
-		Entry e = (Entry) m.entrySet().iterator().next();
-		Map m1 = (Map) e.getKey();
+		Entry<Object, Object> e = m.entrySet().iterator().next();
+		Map<Object, Object> m1 = (Map<Object, Object>) e.getKey();
 		assertNotNull(m1);
 		assertEquals(1, m1.size());
 		assertEquals((byte) 123, m1.get("foo"));
-		List l = (List) e.getValue();
+		List<Object> l = (List<Object>) e.getValue();
 		assertNotNull(l);
 		assertEquals(1, l.size());
 		assertEquals((byte) 1, l.get(0));
-- 
GitLab