From 4f5eb21180c8b1e995346b1bb03a0aa090ba852f Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Mon, 11 Jul 2011 12:25:04 +0100
Subject: [PATCH] Builders for batches and bundles.

---
 .../sf/briar/api/db/DatabaseComponent.java    |   3 +-
 api/net/sf/briar/api/protocol/Batch.java      |  10 +-
 .../sf/briar/api/protocol/BatchBuilder.java   |  10 ++
 api/net/sf/briar/api/protocol/Bundle.java     |  21 +---
 .../sf/briar/api/protocol/BundleBuilder.java  |  22 ++++
 .../sf/briar/db/DatabaseComponentImpl.java    |   8 +-
 .../db/ReadWriteLockDatabaseComponent.java    |  25 ++--
 .../db/SynchronizedDatabaseComponent.java     |  24 ++--
 test/build.xml                                |   2 +
 .../briar/db/DatabaseComponentImplTest.java   |  24 ++--
 .../sf/briar/db/DatabaseComponentTest.java    | 110 +++++++++++-------
 .../ReadWriteLockDatabaseComponentTest.java   |  11 +-
 .../db/SynchronizedDatabaseComponentTest.java |  11 +-
 13 files changed, 162 insertions(+), 119 deletions(-)
 create mode 100644 api/net/sf/briar/api/protocol/BatchBuilder.java
 create mode 100644 api/net/sf/briar/api/protocol/BundleBuilder.java

diff --git a/api/net/sf/briar/api/db/DatabaseComponent.java b/api/net/sf/briar/api/db/DatabaseComponent.java
index bea1e9e4ac..dd231be4d4 100644
--- a/api/net/sf/briar/api/db/DatabaseComponent.java
+++ b/api/net/sf/briar/api/db/DatabaseComponent.java
@@ -7,6 +7,7 @@ import net.sf.briar.api.ContactId;
 import net.sf.briar.api.Rating;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.Bundle;
+import net.sf.briar.api.protocol.BundleBuilder;
 import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Message;
 
@@ -48,7 +49,7 @@ public interface DatabaseComponent {
 	 * Generates a bundle of acknowledgements, subscriptions, and batches of
 	 * messages for the given contact.
 	 */
-	void generateBundle(ContactId c, Bundle b) throws DbException;
+	Bundle generateBundle(ContactId c, BundleBuilder bundleBuilder) throws DbException;
 
 	/** Returns the IDs of all contacts. */
 	Set<ContactId> getContacts() throws DbException;
diff --git a/api/net/sf/briar/api/protocol/Batch.java b/api/net/sf/briar/api/protocol/Batch.java
index 82c86222cc..eaa0ec2618 100644
--- a/api/net/sf/briar/api/protocol/Batch.java
+++ b/api/net/sf/briar/api/protocol/Batch.java
@@ -5,12 +5,7 @@ public interface Batch {
 
 	public static final long CAPACITY = 1024L * 1024L;
 
-	/** Prepares the batch for transmission and generates its identifier. */
-	public void seal();
-
-	/**
-	 * Returns the batch's unique identifier. Cannot be called before seal().
-	 */
+	/** Returns the batch's unique identifier. */
 	BatchId getId();
 
 	/** Returns the size of the batch in bytes. */
@@ -18,7 +13,4 @@ public interface Batch {
 
 	/** Returns the messages contained in the batch. */
 	Iterable<Message> getMessages();
-
-	/** Adds a message to the batch. Cannot be called after seal(). */
-	void addMessage(Message m);
 }
\ No newline at end of file
diff --git a/api/net/sf/briar/api/protocol/BatchBuilder.java b/api/net/sf/briar/api/protocol/BatchBuilder.java
new file mode 100644
index 0000000000..cd5c345510
--- /dev/null
+++ b/api/net/sf/briar/api/protocol/BatchBuilder.java
@@ -0,0 +1,10 @@
+package net.sf.briar.api.protocol;
+
+public interface BatchBuilder {
+
+	/** Adds a message to the batch. */
+	void addMessage(Message m);
+
+	/** Builds and returns the batch. */
+	Batch build();
+}
diff --git a/api/net/sf/briar/api/protocol/Bundle.java b/api/net/sf/briar/api/protocol/Bundle.java
index 8dc8c806ba..7465c3b56e 100644
--- a/api/net/sf/briar/api/protocol/Bundle.java
+++ b/api/net/sf/briar/api/protocol/Bundle.java
@@ -5,12 +5,7 @@ import java.util.Map;
 /** A bundle of acknowledgements, subscriptions, and batches of messages. */
 public interface Bundle {
 
-	/** Prepares the bundle for transmission and generates its identifier. */
-	public void seal();
-
-	/**
-	 * Returns the bundle's unique identifier. Cannot be called before seal().
-	 */
+	/** Returns the bundle's unique identifier. */
 	BundleId getId();
 
 	/** Returns the bundle's capacity in bytes. */
@@ -22,26 +17,12 @@ public interface Bundle {
 	/** Returns the acknowledgements contained in the bundle. */
 	Iterable<BatchId> getAcks();
 
-	/** Adds an acknowledgement to the bundle. Cannot be called after seal(). */
-	void addAck(BatchId b);
-
 	/** Returns the subscriptions contained in the bundle. */
 	Iterable<GroupId> getSubscriptions();
 
-	/** Adds a subscription to the bundle. Cannot be called after seal(). */
-	void addSubscription(GroupId g);
-
 	/** Returns the transport details contained in the bundle. */
 	Map<String, String> getTransports();
 
-	/** Adds a transport detail to the bundle. Cannot be called after seal(). */
-	void addTransport(String key, String value);
-
 	/** Returns the batches of messages contained in the bundle. */
 	Iterable<Batch> getBatches();
-
-	/**
-	 * Adds a batch of messages to the bundle. Cannot be called after seal().
-	 */
-	void addBatch(Batch b);
 }
diff --git a/api/net/sf/briar/api/protocol/BundleBuilder.java b/api/net/sf/briar/api/protocol/BundleBuilder.java
new file mode 100644
index 0000000000..f95ef602dc
--- /dev/null
+++ b/api/net/sf/briar/api/protocol/BundleBuilder.java
@@ -0,0 +1,22 @@
+package net.sf.briar.api.protocol;
+
+public interface BundleBuilder {
+
+	/** Returns the bundle's capacity in bytes. */
+	long getCapacity();
+
+	/** Adds an acknowledgement to the bundle. */
+	void addAck(BatchId b);
+
+	/** Adds a subscription to the bundle. */
+	void addSubscription(GroupId g);
+
+	/** Adds a transport detail to the bundle. */
+	void addTransport(String key, String value);
+
+	/** Adds a batch of messages to the bundle. */
+	void addBatch(Batch b);
+
+	/** Builds and returns the bundle. */
+	Bundle build();
+}
diff --git a/components/net/sf/briar/db/DatabaseComponentImpl.java b/components/net/sf/briar/db/DatabaseComponentImpl.java
index b2cfa0bdaa..49427ce337 100644
--- a/components/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/components/net/sf/briar/db/DatabaseComponentImpl.java
@@ -9,7 +9,7 @@ import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.Status;
 import net.sf.briar.api.protocol.AuthorId;
-import net.sf.briar.api.protocol.Batch;
+import net.sf.briar.api.protocol.BatchBuilder;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageId;
 
@@ -27,7 +27,7 @@ DatabaseCleaner.Callback {
 
 	protected final Database<Txn> db;
 	protected final DatabaseCleaner cleaner;
-	protected final Provider<Batch> batchProvider;
+	protected final Provider<BatchBuilder> batchBuilderProvider;
 
 	private final Object spaceLock = new Object();
 	private final Object writeLock = new Object();
@@ -36,10 +36,10 @@ DatabaseCleaner.Callback {
 	private volatile boolean writesAllowed = true;
 
 	DatabaseComponentImpl(Database<Txn> db, DatabaseCleaner cleaner,
-			Provider<Batch> batchProvider) {
+			Provider<BatchBuilder> batchBuilderProvider) {
 		this.db = db;
 		this.cleaner = cleaner;
-		this.batchProvider = batchProvider;
+		this.batchBuilderProvider = batchBuilderProvider;
 	}
 
 	public void open(boolean resume) throws DbException {
diff --git a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
index c2e0a9ecc3..a5ec477985 100644
--- a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
+++ b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
@@ -15,8 +15,10 @@ import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.NoSuchContactException;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.Batch;
+import net.sf.briar.api.protocol.BatchBuilder;
 import net.sf.briar.api.protocol.BatchId;
 import net.sf.briar.api.protocol.Bundle;
+import net.sf.briar.api.protocol.BundleBuilder;
 import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageId;
@@ -53,8 +55,8 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 
 	@Inject
 	ReadWriteLockDatabaseComponent(Database<Txn> db, DatabaseCleaner cleaner,
-			Provider<Batch> batchProvider) {
-		super(db, cleaner, batchProvider);
+			Provider<BatchBuilder> batchBuilderProvider) {
+		super(db, cleaner, batchBuilderProvider);
 	}
 
 	public void close() throws DbException {
@@ -184,7 +186,8 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		}
 	}
 
-	public void generateBundle(ContactId c, Bundle b) throws DbException {
+	public Bundle generateBundle(ContactId c, BundleBuilder b)
+	throws DbException {
 		if(LOG.isLoggable(Level.FINE)) LOG.fine("Generating bundle for " + c);
 		// Ack all batches received from c
 		contactLock.readLock().lock();
@@ -277,10 +280,11 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 			// more messages trickling in but we can't wait forever
 			if(size * 2 < Batch.CAPACITY) break;
 		}
-		b.seal();
+		Bundle bundle = b.build();
 		if(LOG.isLoggable(Level.FINE))
-			LOG.fine("Bundle sent, " + b.getSize() + " bytes");
+			LOG.fine("Bundle generated, " + bundle.getSize() + " bytes");
 		System.gc();
+		return bundle;
 	}
 
 	private Batch fillBatch(ContactId c, long capacity) throws DbException {
@@ -290,7 +294,8 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 			messageLock.readLock().lock();
 			try {
 				Set<MessageId> sent;
-				Batch b;
+				BatchBuilder b;
+				Batch batch;
 				messageStatusLock.readLock().lock();
 				try {
 					Txn txn = db.startTransaction();
@@ -303,13 +308,13 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 							return null; // No more messages to send
 						}
 						sent = new HashSet<MessageId>();
-						b = batchProvider.get();
+						b = batchBuilderProvider.get();
 						while(it.hasNext()) {
 							MessageId m = it.next();
 							b.addMessage(db.getMessage(txn, m));
 							sent.add(m);
 						}
-						b.seal();
+						batch = b.build();
 						db.commitTransaction(txn);
 					} catch(DbException e) {
 						db.abortTransaction(txn);
@@ -324,9 +329,9 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 					Txn txn = db.startTransaction();
 					try {
 						assert !sent.isEmpty();
-						db.addOutstandingBatch(txn, c, b.getId(), sent);
+						db.addOutstandingBatch(txn, c, batch.getId(), sent);
 						db.commitTransaction(txn);
-						return b;
+						return batch;
 					} catch(DbException e) {
 						db.abortTransaction(txn);
 						throw e;
diff --git a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
index 9c26e5953a..cacf6cfe92 100644
--- a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
+++ b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
@@ -3,8 +3,8 @@ package net.sf.briar.db;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
-import java.util.Set;
 import java.util.Map.Entry;
+import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -14,8 +14,10 @@ import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.NoSuchContactException;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.Batch;
+import net.sf.briar.api.protocol.BatchBuilder;
 import net.sf.briar.api.protocol.BatchId;
 import net.sf.briar.api.protocol.Bundle;
+import net.sf.briar.api.protocol.BundleBuilder;
 import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageId;
@@ -46,8 +48,8 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 
 	@Inject
 	SynchronizedDatabaseComponent(Database<Txn> db, DatabaseCleaner cleaner,
-			Provider<Batch> batchProvider) {
-		super(db, cleaner, batchProvider);
+			Provider<BatchBuilder> batchBuilderProvider) {
+		super(db, cleaner, batchBuilderProvider);
 	}
 
 	public void close() throws DbException {
@@ -137,7 +139,8 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		}
 	}
 
-	public void generateBundle(ContactId c, Bundle b) throws DbException {
+	public Bundle generateBundle(ContactId c, BundleBuilder b)
+	throws DbException {
 		if(LOG.isLoggable(Level.FINE)) LOG.fine("Generating bundle for " + c);
 		// Ack all batches received from c
 		synchronized(contactLock) {
@@ -212,10 +215,11 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 			// more messages trickling in but we can't wait forever
 			if(size * 2 < Batch.CAPACITY) break;
 		}
-		b.seal();
+		Bundle bundle = b.build();
 		if(LOG.isLoggable(Level.FINE))
-			LOG.fine("Bundle sent, " + b.getSize() + " bytes");
+			LOG.fine("Bundle generated, " + bundle.getSize() + " bytes");
 		System.gc();
+		return bundle;
 	}
 
 	private Batch fillBatch(ContactId c, long capacity) throws DbException {
@@ -232,19 +236,19 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 							db.commitTransaction(txn);
 							return null; // No more messages to send
 						}
-						Batch b = batchProvider.get();
+						BatchBuilder b = batchBuilderProvider.get();
 						Set<MessageId> sent = new HashSet<MessageId>();
 						while(it.hasNext()) {
 							MessageId m = it.next();
 							b.addMessage(db.getMessage(txn, m));
 							sent.add(m);
 						}
-						b.seal();
+						Batch batch = b.build();
 						// Record the contents of the batch
 						assert !sent.isEmpty();
-						db.addOutstandingBatch(txn, c, b.getId(), sent);
+						db.addOutstandingBatch(txn, c, batch.getId(), sent);
 						db.commitTransaction(txn);
-						return b;
+						return batch;
 					} catch(DbException e) {
 						db.abortTransaction(txn);
 						throw e;
diff --git a/test/build.xml b/test/build.xml
index 4b864204d2..d908f95f32 100644
--- a/test/build.xml
+++ b/test/build.xml
@@ -20,6 +20,8 @@
 			<test name='net.sf.briar.i18n.FontManagerTest'/>
 			<test name='net.sf.briar.i18n.I18nTest'/>
 			<test name='net.sf.briar.invitation.InvitationWorkerTest'/>
+			<test name='net.sf.briar.serial.ReaderImplTest'/>
+			<test name='net.sf.briar.serial.WriterImplTest'/>
 			<test name='net.sf.briar.setup.SetupWorkerTest'/>
 			<test name='net.sf.briar.util.FileUtilsTest'/>
 			<test name='net.sf.briar.util.StringUtilsTest'/>
diff --git a/test/net/sf/briar/db/DatabaseComponentImplTest.java b/test/net/sf/briar/db/DatabaseComponentImplTest.java
index e713c5ebad..5532d7a568 100644
--- a/test/net/sf/briar/db/DatabaseComponentImplTest.java
+++ b/test/net/sf/briar/db/DatabaseComponentImplTest.java
@@ -6,7 +6,7 @@ import static net.sf.briar.api.db.DatabaseComponent.MIN_FREE_SPACE;
 import java.util.Collections;
 
 import net.sf.briar.api.db.DbException;
-import net.sf.briar.api.protocol.Batch;
+import net.sf.briar.api.protocol.BatchBuilder;
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.db.DatabaseCleaner.Callback;
 
@@ -24,7 +24,7 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest {
 
 	protected abstract <T> DatabaseComponentImpl<T> createDatabaseComponentImpl(
 			Database<T> database, DatabaseCleaner cleaner,
-			Provider<Batch> batchProvider);
+			Provider<BatchBuilder> batchBuilderProvider);
 
 	@Test
 	public void testNotCleanedIfEnoughFreeSpace() throws DbException {
@@ -33,13 +33,14 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		@SuppressWarnings("unchecked")
-		final Provider<Batch> batchProvider = context.mock(Provider.class);
+		final Provider<BatchBuilder> batchBuilderProvider =
+			context.mock(Provider.class);
 		context.checking(new Expectations() {{
 			oneOf(database).getFreeSpace();
 			will(returnValue(MIN_FREE_SPACE));
 		}});
 		Callback db = createDatabaseComponentImpl(database, cleaner,
-				batchProvider);
+				batchBuilderProvider);
 
 		db.checkFreeSpaceAndClean();
 
@@ -53,7 +54,8 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		@SuppressWarnings("unchecked")
-		final Provider<Batch> batchProvider = context.mock(Provider.class);
+		final Provider<BatchBuilder> batchBuilderProvider =
+			context.mock(Provider.class);
 		context.checking(new Expectations() {{
 			oneOf(database).getFreeSpace();
 			will(returnValue(MIN_FREE_SPACE - 1));
@@ -67,7 +69,7 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest {
 			will(returnValue(MIN_FREE_SPACE));
 		}});
 		Callback db = createDatabaseComponentImpl(database, cleaner,
-				batchProvider);
+				batchBuilderProvider);
 
 		db.checkFreeSpaceAndClean();
 
@@ -82,7 +84,8 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		@SuppressWarnings("unchecked")
-		final Provider<Batch> batchProvider = context.mock(Provider.class);
+		final Provider<BatchBuilder> batchBuilderProvider =
+			context.mock(Provider.class);
 		context.checking(new Expectations() {{
 			oneOf(database).getFreeSpace();
 			will(returnValue(MIN_FREE_SPACE - 1));
@@ -98,7 +101,7 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest {
 			will(returnValue(MIN_FREE_SPACE));
 		}});
 		Callback db = createDatabaseComponentImpl(database, cleaner,
-				batchProvider);
+				batchBuilderProvider);
 
 		db.checkFreeSpaceAndClean();
 
@@ -113,7 +116,8 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		@SuppressWarnings("unchecked")
-		final Provider<Batch> batchProvider = context.mock(Provider.class);
+		final Provider<BatchBuilder> batchBuilderProvider =
+			context.mock(Provider.class);
 		context.checking(new Expectations() {{
 			oneOf(database).getFreeSpace();
 			will(returnValue(MIN_FREE_SPACE - 1));
@@ -131,7 +135,7 @@ public abstract class DatabaseComponentImplTest extends DatabaseComponentTest {
 			will(returnValue(MIN_FREE_SPACE));
 		}});
 		Callback db = createDatabaseComponentImpl(database, cleaner,
-				batchProvider);
+				batchBuilderProvider);
 
 		db.checkFreeSpaceAndClean();
 
diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java
index 120610c15b..0edcdc4be8 100644
--- a/test/net/sf/briar/db/DatabaseComponentTest.java
+++ b/test/net/sf/briar/db/DatabaseComponentTest.java
@@ -14,8 +14,10 @@ import net.sf.briar.api.db.NoSuchContactException;
 import net.sf.briar.api.db.Status;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.Batch;
+import net.sf.briar.api.protocol.BatchBuilder;
 import net.sf.briar.api.protocol.BatchId;
 import net.sf.briar.api.protocol.Bundle;
+import net.sf.briar.api.protocol.BundleBuilder;
 import net.sf.briar.api.protocol.BundleId;
 import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Message;
@@ -62,7 +64,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 
 	protected abstract <T> DatabaseComponent createDatabaseComponent(
 			Database<T> database, DatabaseCleaner cleaner,
-			Provider<Batch> batchProvider);
+			Provider<BatchBuilder> batchBuilderProvider);
 
 	@Test
 	public void testSimpleCalls() throws DbException {
@@ -76,7 +78,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		@SuppressWarnings("unchecked")
-		final Provider<Batch> batchProvider = context.mock(Provider.class);
+		final Provider<BatchBuilder> batchBuilderProvider =
+			context.mock(Provider.class);
 		context.checking(new Expectations() {{
 			allowing(database).startTransaction();
 			will(returnValue(txn));
@@ -116,7 +119,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).close();
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				batchProvider);
+				batchBuilderProvider);
 
 		db.open(false);
 		assertEquals(Rating.UNRATED, db.getRating(authorId));
@@ -141,7 +144,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		@SuppressWarnings("unchecked")
-		final Provider<Batch> batchProvider = context.mock(Provider.class);
+		final Provider<BatchBuilder> batchBuilderProvider =
+			context.mock(Provider.class);
 		context.checking(new Expectations() {{
 			// setRating(Rating.GOOD)
 			allowing(database).startTransaction();
@@ -159,7 +163,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				batchProvider);
+				batchBuilderProvider);
 
 		db.setRating(authorId, Rating.GOOD);
 
@@ -174,7 +178,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		@SuppressWarnings("unchecked")
-		final Provider<Batch> batchProvider = context.mock(Provider.class);
+		final Provider<BatchBuilder> batchBuilderProvider =
+			context.mock(Provider.class);
 		context.checking(new Expectations() {{
 			// setRating(Rating.GOOD)
 			oneOf(database).startTransaction();
@@ -195,7 +200,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				batchProvider);
+				batchBuilderProvider);
 
 		db.setRating(authorId, Rating.GOOD);
 
@@ -211,7 +216,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		@SuppressWarnings("unchecked")
-		final Provider<Batch> batchProvider = context.mock(Provider.class);
+		final Provider<BatchBuilder> batchBuilderProvider =
+			context.mock(Provider.class);
 		context.checking(new Expectations() {{
 			// setRating(Rating.GOOD)
 			oneOf(database).startTransaction();
@@ -236,7 +242,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				batchProvider);
+				batchBuilderProvider);
 
 		db.setRating(authorId, Rating.GOOD);
 
@@ -252,7 +258,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		@SuppressWarnings("unchecked")
-		final Provider<Batch> batchProvider = context.mock(Provider.class);
+		final Provider<BatchBuilder> batchBuilderProvider =
+			context.mock(Provider.class);
 		context.checking(new Expectations() {{
 			// setRating(Rating.GOOD)
 			oneOf(database).startTransaction();
@@ -280,7 +287,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				batchProvider);
+				batchBuilderProvider);
 
 		db.setRating(authorId, Rating.GOOD);
 
@@ -296,7 +303,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		@SuppressWarnings("unchecked")
-		final Provider<Batch> batchProvider = context.mock(Provider.class);
+		final Provider<BatchBuilder> batchBuilderProvider =
+			context.mock(Provider.class);
 		context.checking(new Expectations() {{
 			// setRating(Rating.GOOD)
 			oneOf(database).startTransaction();
@@ -326,7 +334,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				batchProvider);
+				batchBuilderProvider);
 
 		db.setRating(authorId, Rating.GOOD);
 
@@ -341,7 +349,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		@SuppressWarnings("unchecked")
-		final Provider<Batch> batchProvider = context.mock(Provider.class);
+		final Provider<BatchBuilder> batchBuilderProvider =
+			context.mock(Provider.class);
 		context.checking(new Expectations() {{
 			// addLocallyGeneratedMessage(message)
 			oneOf(database).startTransaction();
@@ -351,7 +360,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				batchProvider);
+				batchBuilderProvider);
 
 		db.addLocallyGeneratedMessage(message);
 
@@ -365,7 +374,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		@SuppressWarnings("unchecked")
-		final Provider<Batch> batchProvider = context.mock(Provider.class);
+		final Provider<BatchBuilder> batchBuilderProvider =
+			context.mock(Provider.class);
 		context.checking(new Expectations() {{
 			// addLocallyGeneratedMessage(message)
 			oneOf(database).startTransaction();
@@ -377,7 +387,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				batchProvider);
+				batchBuilderProvider);
 
 		db.addLocallyGeneratedMessage(message);
 
@@ -391,7 +401,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		@SuppressWarnings("unchecked")
-		final Provider<Batch> batchProvider = context.mock(Provider.class);
+		final Provider<BatchBuilder> batchBuilderProvider =
+			context.mock(Provider.class);
 		context.checking(new Expectations() {{
 			// addLocallyGeneratedMessage(message)
 			oneOf(database).startTransaction();
@@ -412,7 +423,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				batchProvider);
+				batchBuilderProvider);
 
 		db.addLocallyGeneratedMessage(message);
 
@@ -427,7 +438,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		@SuppressWarnings("unchecked")
-		final Provider<Batch> batchProvider = context.mock(Provider.class);
+		final Provider<BatchBuilder> batchBuilderProvider =
+			context.mock(Provider.class);
 		context.checking(new Expectations() {{
 			// addLocallyGeneratedMessage(message)
 			oneOf(database).startTransaction();
@@ -451,7 +463,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				batchProvider);
+				batchBuilderProvider);
 
 		db.addLocallyGeneratedMessage(message);
 
@@ -466,8 +478,9 @@ public abstract class DatabaseComponentTest extends TestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		@SuppressWarnings("unchecked")
-		final Provider<Batch> batchProvider = context.mock(Provider.class);
-		final Bundle bundle = context.mock(Bundle.class);
+		final Provider<BatchBuilder> batchBuilderProvider =
+			context.mock(Provider.class);
+		final BundleBuilder bundleBuilder = context.mock(BundleBuilder.class);
 		context.checking(new Expectations() {{
 			// Check that the contact is still in the DB
 			oneOf(database).startTransaction();
@@ -477,10 +490,10 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				batchProvider);
+				batchBuilderProvider);
 
 		try {
-			db.generateBundle(contactId, bundle);
+			db.generateBundle(contactId, bundleBuilder);
 			assertTrue(false);
 		} catch(NoSuchContactException expected) {}
 
@@ -494,9 +507,12 @@ public abstract class DatabaseComponentTest extends TestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		@SuppressWarnings("unchecked")
-		final Provider<Batch> batchProvider = context.mock(Provider.class);
-		final Bundle bundle = context.mock(Bundle.class);
+		final Provider<BatchBuilder> batchBuilderProvider =
+			context.mock(Provider.class);
+		final BundleBuilder bundleBuilder = context.mock(BundleBuilder.class);
+		final BatchBuilder batchBuilder = context.mock(BatchBuilder.class);
 		final Batch batch = context.mock(Batch.class);
+		final Bundle bundle = context.mock(Bundle.class);
 		context.checking(new Expectations() {{
 			allowing(database).startTransaction();
 			will(returnValue(txn));
@@ -506,44 +522,46 @@ public abstract class DatabaseComponentTest extends TestCase {
 			// Add acks to the bundle
 			oneOf(database).removeBatchesToAck(txn, contactId);
 			will(returnValue(Collections.singleton(batchId)));
-			oneOf(bundle).addAck(batchId);
+			oneOf(bundleBuilder).addAck(batchId);
 			// Add subscriptions to the bundle
 			oneOf(database).getSubscriptions(txn);
 			will(returnValue(Collections.singleton(groupId)));
-			oneOf(bundle).addSubscription(groupId);
+			oneOf(bundleBuilder).addSubscription(groupId);
 			// Add transports to the bundle
 			oneOf(database).getTransports(txn);
 			will(returnValue(Collections.singletonMap("foo", "bar")));
-			oneOf(bundle).addTransport("foo", "bar");
+			oneOf(bundleBuilder).addTransport("foo", "bar");
 			// Prepare to add batches to the bundle
-			oneOf(bundle).getCapacity();
+			oneOf(bundleBuilder).getCapacity();
 			will(returnValue((long) ONE_MEGABYTE));
 			// Add messages to the batch
-			oneOf(database).getSendableMessages(txn, contactId, ONE_MEGABYTE);
+			oneOf(database).getSendableMessages(txn, contactId, Batch.CAPACITY);
 			will(returnValue(Collections.singleton(messageId)));
-			oneOf(batchProvider).get();
-			will(returnValue(batch));
+			oneOf(batchBuilderProvider).get();
+			will(returnValue(batchBuilder));
 			oneOf(database).getMessage(txn, messageId);
 			will(returnValue(message));
-			oneOf(batch).addMessage(message);
-			oneOf(batch).seal();
+			oneOf(batchBuilder).addMessage(message);
+			oneOf(batchBuilder).build();
+			will(returnValue(batch));
 			// Record the batch as outstanding
 			oneOf(batch).getId();
 			will(returnValue(batchId));
 			oneOf(database).addOutstandingBatch(txn, contactId, batchId,
 					Collections.singleton(messageId));
 			// Add the batch to the bundle
-			oneOf(bundle).addBatch(batch);
+			oneOf(bundleBuilder).addBatch(batch);
 			// Check whether to add another batch
 			oneOf(batch).getSize();
 			will(returnValue((long) message.getSize()));
-			// Nope
-			oneOf(bundle).seal();
+			// No, just send the bundle
+			oneOf(bundleBuilder).build();
+			will(returnValue(bundle));
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				batchProvider);
+				batchBuilderProvider);
 
-		db.generateBundle(contactId, bundle);
+		db.generateBundle(contactId, bundleBuilder);
 
 		context.assertIsSatisfied();
 	}
@@ -556,7 +574,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		@SuppressWarnings("unchecked")
-		final Provider<Batch> batchProvider = context.mock(Provider.class);
+		final Provider<BatchBuilder> batchBuilderProvider =
+			context.mock(Provider.class);
 		final Bundle bundle = context.mock(Bundle.class);
 		context.checking(new Expectations() {{
 			// Check that the contact is still in the DB
@@ -567,7 +586,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).commitTransaction(txn);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				batchProvider);
+				batchBuilderProvider);
 
 		try {
 			db.receiveBundle(contactId, bundle);
@@ -586,7 +605,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 		final Database<Object> database = context.mock(Database.class);
 		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
 		@SuppressWarnings("unchecked")
-		final Provider<Batch> batchProvider = context.mock(Provider.class);
+		final Provider<BatchBuilder> batchBuilderProvider =
+			context.mock(Provider.class);
 		final Bundle bundle = context.mock(Bundle.class);
 		final Batch batch = context.mock(Batch.class);
 		context.checking(new Expectations() {{
@@ -630,7 +650,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).removeLostBatch(txn, contactId, batchId);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				batchProvider);
+				batchBuilderProvider);
 
 		db.receiveBundle(contactId, bundle);
 
diff --git a/test/net/sf/briar/db/ReadWriteLockDatabaseComponentTest.java b/test/net/sf/briar/db/ReadWriteLockDatabaseComponentTest.java
index 16ff7eae5e..77e32e60f4 100644
--- a/test/net/sf/briar/db/ReadWriteLockDatabaseComponentTest.java
+++ b/test/net/sf/briar/db/ReadWriteLockDatabaseComponentTest.java
@@ -1,7 +1,7 @@
 package net.sf.briar.db;
 
 import net.sf.briar.api.db.DatabaseComponent;
-import net.sf.briar.api.protocol.Batch;
+import net.sf.briar.api.protocol.BatchBuilder;
 
 import com.google.inject.Provider;
 
@@ -11,15 +11,16 @@ extends DatabaseComponentImplTest {
 	@Override
 	protected <T> DatabaseComponent createDatabaseComponent(
 			Database<T> database, DatabaseCleaner cleaner,
-			Provider<Batch> batchProvider) {
-		return createDatabaseComponentImpl(database, cleaner, batchProvider);
+			Provider<BatchBuilder> batchBuilderProvider) {
+		return createDatabaseComponentImpl(database, cleaner,
+				batchBuilderProvider);
 	}
 
 	@Override
 	protected <T> DatabaseComponentImpl<T> createDatabaseComponentImpl(
 			Database<T> database, DatabaseCleaner cleaner,
-			Provider<Batch> batchProvider) {
+			Provider<BatchBuilder> batchBuilderProvider) {
 		return new ReadWriteLockDatabaseComponent<T>(database, cleaner,
-				batchProvider);
+				batchBuilderProvider);
 	}
 }
diff --git a/test/net/sf/briar/db/SynchronizedDatabaseComponentTest.java b/test/net/sf/briar/db/SynchronizedDatabaseComponentTest.java
index 647f50490d..e562db8ba9 100644
--- a/test/net/sf/briar/db/SynchronizedDatabaseComponentTest.java
+++ b/test/net/sf/briar/db/SynchronizedDatabaseComponentTest.java
@@ -1,7 +1,7 @@
 package net.sf.briar.db;
 
 import net.sf.briar.api.db.DatabaseComponent;
-import net.sf.briar.api.protocol.Batch;
+import net.sf.briar.api.protocol.BatchBuilder;
 
 import com.google.inject.Provider;
 
@@ -11,15 +11,16 @@ extends DatabaseComponentImplTest {
 	@Override
 	protected <T> DatabaseComponent createDatabaseComponent(
 			Database<T> database, DatabaseCleaner cleaner,
-			Provider<Batch> batchProvider) {
-		return createDatabaseComponentImpl(database, cleaner, batchProvider);
+			Provider<BatchBuilder> batchBuilderProvider) {
+		return createDatabaseComponentImpl(database, cleaner,
+				batchBuilderProvider);
 	}
 
 	@Override
 	protected <T> DatabaseComponentImpl<T> createDatabaseComponentImpl(
 			Database<T> database, DatabaseCleaner cleaner,
-			Provider<Batch> batchProvider) {
+			Provider<BatchBuilder> batchBuilderProvider) {
 		return new SynchronizedDatabaseComponent<T>(database, cleaner,
-				batchProvider);
+				batchBuilderProvider);
 	}
 }
-- 
GitLab