java: Add the Java Syncbase core API

The only unimplemented methods are the factory methods that take names and
not an Id.

This change moves from internal/ to core/ the struct-type classes that
are shared between the internal/ and core/.

MultiPart: 2/2
Change-Id: Ib3bf8d68af7c2e960e688c86dbae80ed161c285d
diff --git a/syncbase/src/main/java/io/v/syncbase/core/BatchDatabase.java b/syncbase/src/main/java/io/v/syncbase/core/BatchDatabase.java
new file mode 100644
index 0000000..8c26394
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/core/BatchDatabase.java
@@ -0,0 +1,39 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.core;
+
+import java.util.List;
+
+public class BatchDatabase extends DatabaseHandle {
+    protected String batchHandle;
+
+    protected BatchDatabase(Id id, String batchHandle) {
+        super(id);
+        this.batchHandle = batchHandle;
+    }
+
+    @Override
+    public Collection collection(Id id) {
+        return new Collection(this.fullName, id, this.batchHandle);
+    }
+
+    @Override
+    public List<Id> listCollections() throws VError {
+        return io.v.syncbase.internal.Database.ListCollections(fullName, batchHandle);
+    }
+
+    @Override
+    public byte[] getResumeMarker() throws VError {
+        return io.v.syncbase.internal.Database.GetResumeMarker(fullName, batchHandle);
+    }
+
+    public void commit() throws VError {
+        io.v.syncbase.internal.Database.Commit(fullName, batchHandle);
+    }
+
+    public void abort() throws VError {
+        io.v.syncbase.internal.Database.Abort(fullName, batchHandle);
+    }
+}
diff --git a/syncbase/src/main/java/io/v/syncbase/internal/Permissions.java b/syncbase/src/main/java/io/v/syncbase/core/BatchOptions.java
similarity index 60%
copy from syncbase/src/main/java/io/v/syncbase/internal/Permissions.java
copy to syncbase/src/main/java/io/v/syncbase/core/BatchOptions.java
index 5e471c2..ab44dd5 100644
--- a/syncbase/src/main/java/io/v/syncbase/internal/Permissions.java
+++ b/syncbase/src/main/java/io/v/syncbase/core/BatchOptions.java
@@ -2,8 +2,9 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package io.v.syncbase.internal;
+package io.v.syncbase.core;
 
-public class Permissions {
-    byte[] json;
-}
\ No newline at end of file
+public class BatchOptions {
+    public String hint;
+    public boolean readOnly;
+}
diff --git a/syncbase/src/main/java/io/v/syncbase/core/Collection.java b/syncbase/src/main/java/io/v/syncbase/core/Collection.java
new file mode 100644
index 0000000..09bf49b
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/core/Collection.java
@@ -0,0 +1,71 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.core;
+
+import java.util.Arrays;
+
+import io.v.syncbase.internal.Util;
+import io.v.v23.syncbase.RowRange;
+
+public class Collection {
+    protected String batchHandle;
+    private Id id;
+    protected String fullName;
+
+    protected Collection(String parentFullName, Id id, String batchHandle) {
+        this.batchHandle = batchHandle;
+        this.id = id;
+        this.fullName = Util.NamingJoin(Arrays.asList(parentFullName, id.encode()));
+    }
+
+    public Id id() {
+        return id;
+    }
+
+    public Permissions getPermissions() throws VError {
+        return io.v.syncbase.internal.Collection.GetPermissions(fullName, batchHandle);
+    }
+
+    public void setPermissions(Permissions permissions) throws VError {
+        io.v.syncbase.internal.Collection.SetPermissions(fullName, batchHandle, permissions);
+    }
+
+    public void create(Permissions permissions) throws VError {
+        io.v.syncbase.internal.Collection.Create(fullName, batchHandle, permissions);
+    }
+
+    public void destroy() throws VError {}
+
+    public boolean exists() throws VError {
+        return io.v.syncbase.internal.Collection.Exists(fullName, batchHandle);
+    }
+
+    public Row row(String key) {
+        return new Row(this.fullName, key, this.batchHandle);
+    }
+
+    public byte[] get(String key) throws VError {
+        return new Row(this.fullName, key, this.batchHandle).get();
+    }
+
+    public void put(String key, byte[] value) throws VError {
+        new Row(this.fullName, key, this.batchHandle).put(value);
+    }
+
+    public void delete(String key) throws VError {
+        new Row(this.fullName, key, this.batchHandle).delete();
+    }
+
+    public void deleteRange(byte[] start, byte[] limit) throws VError {
+        io.v.syncbase.internal.Collection.DeleteRange(fullName, batchHandle, start, limit);
+    }
+
+    public interface ScanCallbacks {
+        void onValue(KeyValue keyValue);
+        void onDone(VError vError);
+    }
+
+    public void scan(RowRange range, ScanCallbacks scanCallbacks) {}
+}
diff --git a/syncbase/src/main/java/io/v/syncbase/core/CollectionRowPattern.java b/syncbase/src/main/java/io/v/syncbase/core/CollectionRowPattern.java
new file mode 100644
index 0000000..1306b2b
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/core/CollectionRowPattern.java
@@ -0,0 +1,11 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.core;
+
+public class CollectionRowPattern {
+    public String collectionBlessing;
+    public String collectionName;
+    public String rowKey;
+}
\ No newline at end of file
diff --git a/syncbase/src/main/java/io/v/syncbase/core/Database.java b/syncbase/src/main/java/io/v/syncbase/core/Database.java
new file mode 100644
index 0000000..61f8ca2
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/core/Database.java
@@ -0,0 +1,59 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.core;
+
+import java.util.List;
+
+public class Database extends DatabaseHandle {
+
+    protected Database(Id id) {
+        super(id);
+    }
+
+    public VersionedPermissions getPermissions() throws VError {
+        return io.v.syncbase.internal.Database.GetPermissions(fullName);
+    }
+
+    public void setPermissions(VersionedPermissions permissions) throws VError {
+        io.v.syncbase.internal.Database.SetPermissions(fullName, permissions);
+    }
+
+    public void create(Permissions permissions) throws VError {
+        io.v.syncbase.internal.Database.Create(fullName, permissions);
+    }
+
+    public void destroy() throws VError {
+        io.v.syncbase.internal.Database.Destroy(fullName);
+    }
+
+    public boolean exists() throws VError {
+        return io.v.syncbase.internal.Database.Exists(fullName);
+    }
+
+    public BatchDatabase beginBatch(BatchOptions options) throws VError {
+        String batchHandle = io.v.syncbase.internal.Database.BeginBatch(fullName, options);
+        return new BatchDatabase(this.id, batchHandle);
+    }
+
+    public Syncgroup syncgroup(String name) throws VError {
+        return syncgroup(new Id(io.v.syncbase.internal.Blessings.AppBlessingFromContext(), name));
+    }
+
+    public Syncgroup syncgroup(Id id) {
+        return new Syncgroup(this, id);
+    }
+
+    public List<Id> listSyncgroups() throws VError {
+        return io.v.syncbase.internal.Database.ListSyncgroups(fullName);
+    }
+
+    public interface WatchPatternsCallbacks {
+        void onChange(WatchChange watchChange);
+        void onError(VError vError);
+    }
+
+    public void watch(byte[] resumeMarker, List<CollectionRowPattern> patterns,
+                      WatchPatternsCallbacks callbacks) throws VError {}
+}
\ No newline at end of file
diff --git a/syncbase/src/main/java/io/v/syncbase/core/DatabaseHandle.java b/syncbase/src/main/java/io/v/syncbase/core/DatabaseHandle.java
new file mode 100644
index 0000000..c57a119
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/core/DatabaseHandle.java
@@ -0,0 +1,37 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.core;
+
+import java.util.List;
+
+public class DatabaseHandle {
+    protected Id id;
+    protected String fullName;
+
+    protected DatabaseHandle(Id id) {
+        this.id = id;
+        fullName = id.encode();
+    }
+
+    public Id id() {
+        return id;
+    }
+
+    public Collection collection(String name) throws VError {
+        return collection(new Id(io.v.syncbase.internal.Blessings.AppBlessingFromContext(), name));
+    }
+
+    public Collection collection(Id id) {
+        return new Collection(new BatchDatabase(this.id, "").fullName, id, "");
+    }
+
+    public List<Id> listCollections() throws VError {
+        return io.v.syncbase.internal.Database.ListCollections(fullName, "");
+    }
+
+    public byte[] getResumeMarker() throws VError {
+        return io.v.syncbase.internal.Database.GetResumeMarker(fullName, "");
+    }
+}
diff --git a/syncbase/src/main/java/io/v/syncbase/internal/Id.java b/syncbase/src/main/java/io/v/syncbase/core/Id.java
similarity index 85%
rename from syncbase/src/main/java/io/v/syncbase/internal/Id.java
rename to syncbase/src/main/java/io/v/syncbase/core/Id.java
index c876ea3..9ba0d51 100644
--- a/syncbase/src/main/java/io/v/syncbase/internal/Id.java
+++ b/syncbase/src/main/java/io/v/syncbase/core/Id.java
@@ -2,11 +2,13 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package io.v.syncbase.internal;
+package io.v.syncbase.core;
+
+import io.v.syncbase.internal.Util;
 
 public class Id {
-    String blessing;
-    String name;
+    public String blessing;
+    public String name;
 
     public Id() {
         // This empty constructor makes the JNI code a little bit simpler by making this class
diff --git a/syncbase/src/main/java/io/v/syncbase/internal/Permissions.java b/syncbase/src/main/java/io/v/syncbase/core/KeyValue.java
similarity index 62%
copy from syncbase/src/main/java/io/v/syncbase/internal/Permissions.java
copy to syncbase/src/main/java/io/v/syncbase/core/KeyValue.java
index 5e471c2..e332c54 100644
--- a/syncbase/src/main/java/io/v/syncbase/internal/Permissions.java
+++ b/syncbase/src/main/java/io/v/syncbase/core/KeyValue.java
@@ -2,8 +2,9 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package io.v.syncbase.internal;
+package io.v.syncbase.core;
 
-public class Permissions {
-    byte[] json;
+public class KeyValue {
+    public String key;
+    public byte[] value;
 }
\ No newline at end of file
diff --git a/syncbase/src/main/java/io/v/syncbase/internal/Permissions.java b/syncbase/src/main/java/io/v/syncbase/core/Permissions.java
similarity index 78%
rename from syncbase/src/main/java/io/v/syncbase/internal/Permissions.java
rename to syncbase/src/main/java/io/v/syncbase/core/Permissions.java
index 5e471c2..690d834 100644
--- a/syncbase/src/main/java/io/v/syncbase/internal/Permissions.java
+++ b/syncbase/src/main/java/io/v/syncbase/core/Permissions.java
@@ -2,8 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package io.v.syncbase.internal;
+package io.v.syncbase.core;
 
 public class Permissions {
-    byte[] json;
+    public byte[] json;
 }
\ No newline at end of file
diff --git a/syncbase/src/main/java/io/v/syncbase/core/Row.java b/syncbase/src/main/java/io/v/syncbase/core/Row.java
new file mode 100644
index 0000000..d1628ad
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/core/Row.java
@@ -0,0 +1,41 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.core;
+
+import java.util.Arrays;
+
+import io.v.syncbase.internal.Util;
+
+public class Row {
+    private String batchHandle;
+    private String key;
+    private String fullName;
+
+    protected Row(String parentFullName, String key, String batchHandle) {
+        this.batchHandle = batchHandle;
+        this.key = key;
+        this.fullName = Util.NamingJoin(Arrays.asList(parentFullName, key));
+    }
+
+    public String key() {
+        return key;
+    }
+
+    public boolean exists() throws VError {
+        return io.v.syncbase.internal.Row.Exists(fullName, batchHandle);
+    }
+
+    public byte[] get() throws VError {
+        return io.v.syncbase.internal.Row.Get(fullName, batchHandle);
+    }
+
+    public void put(byte[] value) throws VError {
+        io.v.syncbase.internal.Row.Put(fullName, batchHandle, value);
+    }
+
+    public void delete() throws VError {
+        io.v.syncbase.internal.Row.Delete(fullName, batchHandle);
+    }
+}
diff --git a/syncbase/src/main/java/io/v/syncbase/core/Service.java b/syncbase/src/main/java/io/v/syncbase/core/Service.java
new file mode 100644
index 0000000..4e7b231
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/core/Service.java
@@ -0,0 +1,29 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.core;
+
+import java.util.List;
+
+public class Service {
+    public static VersionedPermissions getPermissions() {
+        return io.v.syncbase.internal.Service.GetPermissions();
+    }
+
+    public static void setPermissions(VersionedPermissions permissions) throws VError {
+        io.v.syncbase.internal.Service.SetPermissions(permissions);
+    }
+
+    public static Database database(String name) throws VError {
+        return database(new Id(io.v.syncbase.internal.Blessings.AppBlessingFromContext(), name));
+    }
+
+    public static Database database(Id id) {
+        return new Database(id);
+    }
+
+    public static List<Id> listDatabases() throws Error {
+        return io.v.syncbase.internal.Service.ListDatabases();
+    }
+}
\ No newline at end of file
diff --git a/syncbase/src/main/java/io/v/syncbase/core/Syncgroup.java b/syncbase/src/main/java/io/v/syncbase/core/Syncgroup.java
new file mode 100644
index 0000000..995efaf
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/core/Syncgroup.java
@@ -0,0 +1,53 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.core;
+
+import java.util.List;
+import java.util.Map;
+
+public class Syncgroup {
+    private String dbFullName;
+    private Id id;
+
+    protected Syncgroup(Database database, Id id) {
+        dbFullName = database.fullName;
+        this.id = id;
+    }
+
+    public void create(SyncgroupSpec spec, SyncgroupMemberInfo info) throws VError {
+        io.v.syncbase.internal.Database.CreateSyncgroup(dbFullName, id, spec, info);
+    }
+
+    public SyncgroupSpec join(String remoteSyncbaseName, List<String> expectedSyncbaseBlessings,
+                              SyncgroupMemberInfo info)
+            throws VError {
+        return io.v.syncbase.internal.Database.JoinSyncgroup(
+                dbFullName, remoteSyncbaseName, expectedSyncbaseBlessings, id, info);
+    }
+
+    public void leave() throws VError {
+        io.v.syncbase.internal.Database.LeaveSyncgroup(dbFullName, id);
+    }
+
+    public void destroy() throws VError {
+        io.v.syncbase.internal.Database.DestroySyncgroup(dbFullName, id);
+    }
+
+    public void eject(String member) throws VError {
+        io.v.syncbase.internal.Database.EjectFromSyncgroup(dbFullName, id, member);
+    }
+
+    public VersionedSyncgroupSpec getSpec() throws VError {
+        return io.v.syncbase.internal.Database.GetSyncgroupSpec(dbFullName, id);
+    }
+
+    public void setSpec(VersionedSyncgroupSpec spec) throws VError {
+        io.v.syncbase.internal.Database.SetSyncgroupSpec(dbFullName, id, spec);
+    }
+
+    public Map<String,SyncgroupMemberInfo> getMembers() throws VError {
+        return io.v.syncbase.internal.Database.GetSyncgroupMembers(dbFullName, id);
+    }
+}
diff --git a/syncbase/src/main/java/io/v/syncbase/core/SyncgroupMemberInfo.java b/syncbase/src/main/java/io/v/syncbase/core/SyncgroupMemberInfo.java
new file mode 100644
index 0000000..e7d1b8a
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/core/SyncgroupMemberInfo.java
@@ -0,0 +1,10 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.core;
+
+public class SyncgroupMemberInfo {
+    public int syncPriority;
+    public int blobDevType;
+}
\ No newline at end of file
diff --git a/syncbase/src/main/java/io/v/syncbase/core/SyncgroupSpec.java b/syncbase/src/main/java/io/v/syncbase/core/SyncgroupSpec.java
new file mode 100644
index 0000000..4bdf5a4
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/core/SyncgroupSpec.java
@@ -0,0 +1,16 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.core;
+
+import java.util.List;
+
+public class SyncgroupSpec {
+    public String description;
+    public String publishSyncbaseName;
+    public Permissions permissions;
+    public List<Id> collections;
+    public List<String> mountTables;
+    public boolean isPrivate;
+}
\ No newline at end of file
diff --git a/syncbase/src/main/java/io/v/syncbase/internal/VError.java b/syncbase/src/main/java/io/v/syncbase/core/VError.java
similarity index 75%
rename from syncbase/src/main/java/io/v/syncbase/internal/VError.java
rename to syncbase/src/main/java/io/v/syncbase/core/VError.java
index 721c2ca..6fb54c5 100644
--- a/syncbase/src/main/java/io/v/syncbase/internal/VError.java
+++ b/syncbase/src/main/java/io/v/syncbase/core/VError.java
@@ -2,13 +2,13 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package io.v.syncbase.internal;
+package io.v.syncbase.core;
 
 public class VError extends Exception {
-    String id;
-    long actionCode;
-    String message;
-    String stack;
+    public String id;
+    public long actionCode;
+    public String message;
+    public String stack;
 
     public String toString() {
         return String.format("{\n  id: \"%s\"\n  actionCode: %d\n  message: \"%s\"\n  stack: \"%s\"}",
diff --git a/syncbase/src/main/java/io/v/syncbase/internal/VersionedPermissions.java b/syncbase/src/main/java/io/v/syncbase/core/VersionedPermissions.java
similarity index 69%
rename from syncbase/src/main/java/io/v/syncbase/internal/VersionedPermissions.java
rename to syncbase/src/main/java/io/v/syncbase/core/VersionedPermissions.java
index 1b82416..6d1d495 100644
--- a/syncbase/src/main/java/io/v/syncbase/internal/VersionedPermissions.java
+++ b/syncbase/src/main/java/io/v/syncbase/core/VersionedPermissions.java
@@ -2,9 +2,9 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package io.v.syncbase.internal;
+package io.v.syncbase.core;
 
 public class VersionedPermissions {
-    String version;
-    Permissions permissions;
+    public String version;
+    public Permissions permissions;
 }
diff --git a/syncbase/src/main/java/io/v/syncbase/core/VersionedSyncgroupSpec.java b/syncbase/src/main/java/io/v/syncbase/core/VersionedSyncgroupSpec.java
new file mode 100644
index 0000000..73fd171
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/core/VersionedSyncgroupSpec.java
@@ -0,0 +1,10 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.core;
+
+public class VersionedSyncgroupSpec {
+    public String version;
+    public SyncgroupSpec syncgroupSpec;
+}
\ No newline at end of file
diff --git a/syncbase/src/main/java/io/v/syncbase/core/WatchChange.java b/syncbase/src/main/java/io/v/syncbase/core/WatchChange.java
new file mode 100644
index 0000000..4ef08e1
--- /dev/null
+++ b/syncbase/src/main/java/io/v/syncbase/core/WatchChange.java
@@ -0,0 +1,17 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.core;
+
+public class WatchChange {
+    public enum ChangeType { PUT, DELETE }
+
+    public Id collection;
+    public String row;
+    public ChangeType changeType;
+    public byte[] value;
+    public String resumeMarker;
+    public boolean fromSync;
+    public boolean continued;
+}
\ No newline at end of file
diff --git a/syncbase/src/main/java/io/v/syncbase/internal/Blessings.java b/syncbase/src/main/java/io/v/syncbase/internal/Blessings.java
index 9302393..0a3d0d2 100644
--- a/syncbase/src/main/java/io/v/syncbase/internal/Blessings.java
+++ b/syncbase/src/main/java/io/v/syncbase/internal/Blessings.java
@@ -4,6 +4,8 @@
 
 package io.v.syncbase.internal;
 
+import io.v.syncbase.core.VError;
+
 public class Blessings {
     public static native String DebugString();
     public static native String AppBlessingFromContext() throws VError;
diff --git a/syncbase/src/main/java/io/v/syncbase/internal/Collection.java b/syncbase/src/main/java/io/v/syncbase/internal/Collection.java
index 4ba5e69..3b13840 100644
--- a/syncbase/src/main/java/io/v/syncbase/internal/Collection.java
+++ b/syncbase/src/main/java/io/v/syncbase/internal/Collection.java
@@ -4,6 +4,10 @@
 
 package io.v.syncbase.internal;
 
+import io.v.syncbase.core.KeyValue;
+import io.v.syncbase.core.Permissions;
+import io.v.syncbase.core.VError;
+
 public class Collection {
     public static native Permissions GetPermissions(String name, String batchHandle) throws VError;
     public static native void SetPermissions(String name, String batchHandle, Permissions permissions) throws VError;
@@ -13,11 +17,6 @@
     public static native boolean Exists(String name, String batchHandle) throws VError;
     public static native void DeleteRange(String name, String batchHandle, byte[] start, byte[] limit) throws VError;
 
-    public static class KeyValue {
-        String key;
-        byte[] value;
-    }
-
     public interface ScanCallbacks {
         void onKeyValue(KeyValue keyValue);
         void onDone(VError vError);
diff --git a/syncbase/src/main/java/io/v/syncbase/internal/Database.java b/syncbase/src/main/java/io/v/syncbase/internal/Database.java
index f863327..86c2516 100644
--- a/syncbase/src/main/java/io/v/syncbase/internal/Database.java
+++ b/syncbase/src/main/java/io/v/syncbase/internal/Database.java
@@ -7,6 +7,17 @@
 import java.util.List;
 import java.util.Map;
 
+import io.v.syncbase.core.BatchOptions;
+import io.v.syncbase.core.CollectionRowPattern;
+import io.v.syncbase.core.Id;
+import io.v.syncbase.core.Permissions;
+import io.v.syncbase.core.SyncgroupMemberInfo;
+import io.v.syncbase.core.SyncgroupSpec;
+import io.v.syncbase.core.VError;
+import io.v.syncbase.core.VersionedPermissions;
+import io.v.syncbase.core.VersionedSyncgroupSpec;
+import io.v.syncbase.core.WatchChange;
+
 public class Database {
     public static native VersionedPermissions GetPermissions(String name) throws VError;
     public static native void SetPermissions(String name, VersionedPermissions permissions) throws VError;
@@ -15,36 +26,12 @@
     public static native void Destroy(String name) throws VError;
     public static native boolean Exists(String name) throws VError;
 
-    public static class BatchOptions {
-        String hint;
-        boolean readOnly;
-    }
-
     public static native String BeginBatch(String name, BatchOptions options) throws VError;
     public static native List<Id> ListCollections(String name, String batchHandle) throws VError;
     public static native void Commit(String name, String batchHandle) throws VError;
     public static native void Abort(String name, String batchHandle) throws VError;
     public static native byte[] GetResumeMarker(String name, String batchHandle) throws VError;
 
-    public static class SyncgroupSpec {
-        String description;
-        String publishSyncbaseName;
-        Permissions permissions;
-        List<Id> collections;
-        List<String> mountTables;
-        boolean isPrivate;
-    }
-
-    public static class VersionedSyncgroupSpec {
-        String version;
-        SyncgroupSpec syncgroupSpec;
-    }
-
-    public static class SyncgroupMemberInfo {
-        int syncPriority;
-        int blobDevType;
-    }
-
     public static native List<Id> ListSyncgroups(String name) throws VError;
     public static native void CreateSyncgroup(String name, Id syncgroupId, SyncgroupSpec spec, SyncgroupMemberInfo info) throws VError;
     public static native SyncgroupSpec JoinSyncgroup(String name, String remoteSyncbaseName, List<String> expectedSyncbaseBlessings, Id syncgroupId, SyncgroupMemberInfo info) throws VError;
@@ -55,24 +42,6 @@
     public static native void SetSyncgroupSpec(String name, Id syncgroupId, VersionedSyncgroupSpec spec) throws VError;
     public static native Map<String, SyncgroupMemberInfo> GetSyncgroupMembers(String name, Id syncgroupId) throws VError;
 
-    public static class CollectionRowPattern {
-        String collectionBlessing;
-        String collectionName;
-        String rowKey;
-    }
-
-    public enum ChangeType { PUT, DELETE }
-
-    public static class WatchChange {
-        Id collection;
-        String row;
-        ChangeType changeType;
-        byte[] value;
-        String resumeMarker;
-        boolean fromSync;
-        boolean continued;
-    }
-
     public interface WatchPatternsCallbacks {
         void onChange(WatchChange watchChange);
         void onError(VError vError);
diff --git a/syncbase/src/main/java/io/v/syncbase/internal/Row.java b/syncbase/src/main/java/io/v/syncbase/internal/Row.java
index 76f0c09..dcfce3f 100644
--- a/syncbase/src/main/java/io/v/syncbase/internal/Row.java
+++ b/syncbase/src/main/java/io/v/syncbase/internal/Row.java
@@ -4,6 +4,8 @@
 
 package io.v.syncbase.internal;
 
+import io.v.syncbase.core.VError;
+
 public class Row {
     public static native boolean Exists(String name, String batchHandle) throws VError;
     public static native byte[] Get(String name, String batchHandle) throws VError;
diff --git a/syncbase/src/main/java/io/v/syncbase/internal/Service.java b/syncbase/src/main/java/io/v/syncbase/internal/Service.java
index 3ad493a..0a4ed82 100644
--- a/syncbase/src/main/java/io/v/syncbase/internal/Service.java
+++ b/syncbase/src/main/java/io/v/syncbase/internal/Service.java
@@ -6,6 +6,10 @@
 
 import java.util.List;
 
+import io.v.syncbase.core.Id;
+import io.v.syncbase.core.VError;
+import io.v.syncbase.core.VersionedPermissions;
+
 public class Service {
     public static native VersionedPermissions GetPermissions();
     public static native void SetPermissions(VersionedPermissions permissions) throws VError;
diff --git a/syncbase/src/main/java/io/v/syncbase/internal/Util.java b/syncbase/src/main/java/io/v/syncbase/internal/Util.java
index 48da808..03aaa1c 100644
--- a/syncbase/src/main/java/io/v/syncbase/internal/Util.java
+++ b/syncbase/src/main/java/io/v/syncbase/internal/Util.java
@@ -6,6 +6,8 @@
 
 import java.util.List;
 
+import io.v.syncbase.core.Id;
+
 public class Util {
     public static native String Encode(String s);
     public static native String EncodeId(Id id);
diff --git a/syncbase/src/test/java/io/v/syncbase/core/BatchDatabaseTest.java b/syncbase/src/test/java/io/v/syncbase/core/BatchDatabaseTest.java
new file mode 100644
index 0000000..891f3cf
--- /dev/null
+++ b/syncbase/src/test/java/io/v/syncbase/core/BatchDatabaseTest.java
@@ -0,0 +1,41 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.core;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static io.v.syncbase.core.TestConstants.anyCollectionPermissions;
+import static io.v.syncbase.core.TestConstants.anyDbPermissions;
+import static org.junit.Assert.fail;
+
+public class BatchDatabaseTest {
+    @Before
+    public void setUp() {
+        System.loadLibrary("syncbase");
+    }
+
+    @Test
+    public void commitAndAbort() {
+        Id dbId = new Id("idp:a:angrybirds", "core_abort_db");
+        Id collectionId = new Id("...", "collection");
+        try {
+            Database db = Service.database(dbId);
+            db.create(anyDbPermissions());
+            BatchDatabase batchDb = db.beginBatch(null);
+            Collection collection = batchDb.collection(collectionId);
+            collection.create(anyCollectionPermissions());
+            batchDb.abort();
+            batchDb = db.beginBatch(null);
+            collection = batchDb.collection(collectionId);
+            // This should work because we abort the previous batch.
+            collection.create(anyCollectionPermissions());
+            batchDb.commit();
+        } catch (VError vError) {
+            vError.printStackTrace();
+            fail(vError.toString());
+        }
+    }
+}
diff --git a/syncbase/src/test/java/io/v/syncbase/core/CollectionTest.java b/syncbase/src/test/java/io/v/syncbase/core/CollectionTest.java
new file mode 100644
index 0000000..46daa54
--- /dev/null
+++ b/syncbase/src/test/java/io/v/syncbase/core/CollectionTest.java
@@ -0,0 +1,134 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.core;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+import static io.v.syncbase.core.TestConstants.anyCollectionPermissions;
+import static io.v.syncbase.core.TestConstants.anyDbPermissions;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class CollectionTest {
+    @Before
+    public void setUp() {
+        System.loadLibrary("syncbase");
+    }
+
+    @Test
+    public void create() {
+        Id dbId = new Id("idp:a:angrybirds", "core_create_collection");
+        Id collectionId1 = new Id("...", "collection1");
+        Id collectionId2 = new Id("...", "collection2");
+        try {
+            Database db = Service.database(dbId);
+            db.create(anyDbPermissions());
+            BatchDatabase batchDb = db.beginBatch(null);
+
+            batchDb.collection(collectionId1).create(anyCollectionPermissions());
+            batchDb.collection(collectionId2).create(anyCollectionPermissions());
+
+            List<Id> collections = batchDb.listCollections();
+            assertNotNull(collections);
+            assertEquals(2, collections.size());
+            assertEquals(collectionId1.encode(), collections.get(0).encode());
+            assertEquals(collectionId2.encode(), collections.get(1).encode());
+        } catch (VError vError) {
+            vError.printStackTrace();
+            fail(vError.toString());
+        }
+    }
+
+    @Test
+    public void destroy() {
+        Id dbId = new Id("idp:a:angrybirds", "core_destroy_collection");
+        Id collectionId = new Id("...", "collection");
+        try {
+            Database db = Service.database(dbId);
+            db.create(anyDbPermissions());
+            BatchDatabase batchDb = db.beginBatch(null);
+            batchDb.collection(collectionId).create(anyCollectionPermissions());
+            batchDb.commit();
+
+            batchDb = db.beginBatch(null);
+            batchDb.collection(collectionId).destroy();
+        } catch (VError vError) {
+            vError.printStackTrace();
+            fail(vError.toString());
+        }
+    }
+
+    @Test
+    public void exists() {
+        Id dbId = new Id("idp:a:angrybirds", "core_exists_collection");
+        Id collectionId1 = new Id("...", "collection1");
+        Id collectionId2 = new Id("...", "collection2");
+        try {
+            Database db = Service.database(dbId);
+            db.create(anyDbPermissions());
+            BatchDatabase batchDb = db.beginBatch(null);
+            Collection collection1 = batchDb.collection(collectionId1);
+            collection1.create(anyCollectionPermissions());
+            // We have not committed the batch yet so exists() should fail.
+            assertFalse(collection1.exists());
+            batchDb.commit();
+            assertTrue(db.collection(collectionId1).exists());
+            assertFalse(db.collection(collectionId2).exists());
+        } catch (VError vError) {
+            vError.printStackTrace();
+            fail(vError.toString());
+        }
+    }
+
+    @Test
+    public void permissions() {
+        Id dbId = new Id("idp:a:angrybirds", "core_permissions_collection");
+        Id collectionId = new Id("...", "collection");
+        try {
+            Database db = Service.database(dbId);
+            db.create(anyDbPermissions());
+            Collection collection = db.collection(collectionId);
+            collection.create(anyCollectionPermissions());
+            Permissions permissions = collection.getPermissions();
+            assertNotNull(permissions);
+            String json = new String(permissions.json);
+            assertTrue(json.contains("Admin"));
+
+            collection.setPermissions(permissions);
+        } catch (VError vError) {
+            vError.printStackTrace();
+            fail(vError.toString());
+        }
+    }
+
+    @Test
+    public void deleteRangeCollection() {
+        Id dbId = new Id("idp:a:angrybirds", "core_delete_range_collection");
+        Id collectionId = new Id("...", "collection");
+        String key = "key";
+        // Reference: release/go/src/v.io/v23/vom/testdata/data81/vomdata.vdl
+        byte[] vomValue = {(byte)0x81, 0x06, 0x03, 'a', 'b', 'c'};
+        try {
+            Database db = Service.database(dbId);
+            db.create(anyDbPermissions());
+            Collection collection = db.collection(collectionId);
+            collection.create(anyCollectionPermissions());
+            collection.put(key, vomValue);
+            assertTrue(collection.row(key).exists());
+
+            collection.deleteRange(new byte[]{}, new byte[]{});
+            assertFalse(collection.row(key).exists());
+        } catch (VError vError) {
+            vError.printStackTrace();
+            fail(vError.toString());
+        }
+    }
+}
diff --git a/syncbase/src/test/java/io/v/syncbase/core/DatabaseHandleTest.java b/syncbase/src/test/java/io/v/syncbase/core/DatabaseHandleTest.java
new file mode 100644
index 0000000..08bd888
--- /dev/null
+++ b/syncbase/src/test/java/io/v/syncbase/core/DatabaseHandleTest.java
@@ -0,0 +1,55 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.core;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+import static io.v.syncbase.core.TestConstants.anyDbPermissions;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class DatabaseHandleTest {
+    @Before
+    public void setUp() {
+        System.loadLibrary("syncbase");
+    }
+
+    @Test
+    public void listCollections() {
+        Id dbId = new Id("idp:a:angrybirds", "core_list_db");
+        try {
+            Database db = Service.database(dbId);
+            db.create(anyDbPermissions());
+            BatchDatabase batchDb = db.beginBatch(null);
+            List<Id> collections = batchDb.listCollections();
+            assertNotNull(collections);
+            assertEquals(0, collections.size());
+        } catch (VError vError) {
+            vError.printStackTrace();
+            fail(vError.toString());
+        }
+    }
+
+    @Test
+    public void getResumeMarker() {
+        Id dbId = new Id("idp:a:angrybirds", "core_get_resume_marker");
+        try {
+            Database db = Service.database(dbId);
+            db.create(anyDbPermissions());
+            BatchDatabase batchDb = db.beginBatch(null);
+            byte[] marker = batchDb.getResumeMarker();
+            assertNotNull(marker);
+            assertTrue(marker.length > 0);
+        } catch (VError vError) {
+            vError.printStackTrace();
+            fail(vError.toString());
+        }
+    }
+}
diff --git a/syncbase/src/test/java/io/v/syncbase/core/DatabaseTest.java b/syncbase/src/test/java/io/v/syncbase/core/DatabaseTest.java
new file mode 100644
index 0000000..1ab7585
--- /dev/null
+++ b/syncbase/src/test/java/io/v/syncbase/core/DatabaseTest.java
@@ -0,0 +1,117 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.core;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+import static io.v.syncbase.core.TestConstants.anyDbPermissions;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class DatabaseTest {
+    @Before
+    public void setUp() {
+        System.loadLibrary("syncbase");
+    }
+
+    @Test
+    public void create() {
+        Id dbId = new Id("idp:a:angrybirds", "core_create_db");
+        // The instance is empty so creating of a database should succeed.
+        try {
+            Service.database(dbId).create(anyDbPermissions());
+        } catch (VError vError) {
+            vError.printStackTrace();
+            fail(vError.toString());
+        }
+
+        // Creating the same database should raise an exception.
+        boolean exceptionThrown = false;
+        try {
+            Service.database(dbId).create(anyDbPermissions());
+        } catch (VError vError) {
+            assertEquals("v.io/v23/verror.Exist", vError.id);
+            assertNotNull(vError.message);
+            assertNotNull(vError.stack);
+            assertEquals(0, vError.actionCode);
+            exceptionThrown = true;
+        }
+        assertTrue(exceptionThrown);
+    }
+
+    @Test
+    public void destroy() {
+        Id dbId = new Id("idp:a:angrybirds", "core_destroy_db");
+        try {
+            Database db = Service.database(dbId);
+            db.create(anyDbPermissions());
+            db.destroy();
+        } catch (VError vError) {
+            vError.printStackTrace();
+            fail(vError.toString());
+        }
+    }
+
+    @Test
+    public void exists() {
+        Id dbId = new Id("idp:a:angrybirds", "core_exists_db");
+        try {
+            Database db = Service.database(dbId);
+            // We have not created the database yet so Exists should fail.
+            assertFalse(db.exists());
+            // The instance is empty so creating of a database should succeed.
+            db.create(anyDbPermissions());
+            // Exists should succeed now.
+            assertTrue(db.exists());
+        } catch (VError vError) {
+            vError.printStackTrace();
+            fail(vError.toString());
+        }
+    }
+
+    @Test
+    public void permissions() {
+        Id dbId = new Id("idp:a:angrybirds", "core_permissions_db");
+        try {
+            Database db = Service.database(dbId);
+            db.create(anyDbPermissions());
+            VersionedPermissions versionedPermissions1 = db.getPermissions();
+            assertNotNull(versionedPermissions1);
+            assertTrue(versionedPermissions1.version.length() > 0);
+            String json = new String(versionedPermissions1.permissions.json);
+            assertTrue(json.contains("Admin"));
+
+            db.setPermissions(versionedPermissions1);
+            VersionedPermissions versionedPermissions2 = db.getPermissions();
+            assertNotEquals(versionedPermissions1.version, versionedPermissions2.version);
+            assertEquals(json, new String(versionedPermissions2.permissions.json));
+        } catch (VError vError) {
+            vError.printStackTrace();
+            fail(vError.toString());
+        }
+    }
+
+    @Test
+    public void listSyncgroups() {
+        Id dbId = new Id("idp:a:angrybirds", "core_list_syncgroups");
+        try {
+            Database db = Service.database(dbId);
+            db.create(anyDbPermissions());
+            List<Id> syncgroups = db.listSyncgroups();
+            assertNotNull(syncgroups);
+            assertEquals(0, syncgroups.size());
+        } catch (VError vError) {
+            vError.printStackTrace();
+            fail(vError.toString());
+        }
+    }
+}
diff --git a/syncbase/src/test/java/io/v/syncbase/core/RowTest.java b/syncbase/src/test/java/io/v/syncbase/core/RowTest.java
new file mode 100644
index 0000000..48cb8f8
--- /dev/null
+++ b/syncbase/src/test/java/io/v/syncbase/core/RowTest.java
@@ -0,0 +1,46 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.core;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static io.v.syncbase.core.TestConstants.anyCollectionPermissions;
+import static io.v.syncbase.core.TestConstants.anyDbPermissions;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class RowTest {
+    @Before
+    public void setUp() {
+        System.loadLibrary("syncbase");
+    }
+
+    @Test
+    public void all() {
+        Id dbId = new Id("idp:a:angrybirds", "core_collection");
+        Id collectionId = new Id("...", "collection");
+        String key = "key";
+        // Reference: release/go/src/v.io/v23/vom/testdata/data81/vomdata.vdl
+        byte[] vomValue = {(byte)0x81, 0x06, 0x03, 'a', 'b', 'c'};
+        try {
+            Database db = Service.database(dbId);
+            db.create(anyDbPermissions());
+            Collection collection = db.collection(collectionId);
+            collection.create(anyCollectionPermissions());
+            Row row = collection.row(key);
+            row.put(vomValue);
+            assertArrayEquals(vomValue, row.get());
+            assertTrue(row.exists());
+            row.delete();
+            assertFalse(row.exists());
+        } catch (VError vError) {
+            vError.printStackTrace();
+            fail(vError.toString());
+        }
+    }
+}
diff --git a/syncbase/src/test/java/io/v/syncbase/core/ServiceTest.java b/syncbase/src/test/java/io/v/syncbase/core/ServiceTest.java
new file mode 100644
index 0000000..ddd32ce
--- /dev/null
+++ b/syncbase/src/test/java/io/v/syncbase/core/ServiceTest.java
@@ -0,0 +1,46 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.core;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class ServiceTest {
+    @Before
+    public void setUp() {
+        System.loadLibrary("syncbase");
+    }
+
+    @Test
+    public void listDatabases() {
+        assertTrue(Service.listDatabases().isEmpty());
+        // TODO(razvanm): Add proper testing after listing starts working.
+    }
+
+    @Test
+    public void permissions() {
+        VersionedPermissions versionedPermissions1 = Service.getPermissions();
+        assertNotNull(versionedPermissions1);
+        assertTrue(versionedPermissions1.version.length() > 0);
+        String json = new String(versionedPermissions1.permissions.json);
+        assertTrue(json.contains("Admin"));
+
+        try {
+            Service.setPermissions(versionedPermissions1);
+            VersionedPermissions versionedPermissions2 = Service.getPermissions();
+            assertNotEquals(versionedPermissions1.version, versionedPermissions2.version);
+            assertEquals(json, new String(versionedPermissions2.permissions.json));
+        } catch (VError vError) {
+            vError.printStackTrace();
+            fail(vError.toString());
+        }
+    }
+}
diff --git a/syncbase/src/test/java/io/v/syncbase/core/SyncgroupTest.java b/syncbase/src/test/java/io/v/syncbase/core/SyncgroupTest.java
new file mode 100644
index 0000000..4d2fcf7
--- /dev/null
+++ b/syncbase/src/test/java/io/v/syncbase/core/SyncgroupTest.java
@@ -0,0 +1,147 @@
+// Copyright 2016 The Vanadium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package io.v.syncbase.core;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import static io.v.syncbase.core.TestConstants.anyCollectionPermissions;
+import static io.v.syncbase.core.TestConstants.anyDbPermissions;
+import static io.v.syncbase.core.TestConstants.anySyncgroupPermissions;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+public class SyncgroupTest {
+    @Test
+    public void create() {
+        Id dbId = new Id("idp:a:angrybirds", "core_create_syncgroups");
+        Id sgId = new Id("...", "syncgroup");
+        Id collectionId = new Id("...", "collection");
+        try {
+            Database db = Service.database(dbId);
+            db.create(anyDbPermissions());
+            db.collection(collectionId).create(anyCollectionPermissions());
+            SyncgroupSpec spec = new SyncgroupSpec();
+            spec.collections = Arrays.asList(collectionId);
+            spec.permissions = anySyncgroupPermissions();
+            SyncgroupMemberInfo info = new SyncgroupMemberInfo();
+            // TODO(razvanm): Pick some meaningful values.
+            info.syncPriority = 1;
+            info.blobDevType = 2;
+            Syncgroup syncgroup = db.syncgroup(sgId);
+            syncgroup.create(spec, info);
+
+            List<Id> syncgroups = db.listSyncgroups();
+            assertEquals(1, syncgroups.size());
+            Id actual = syncgroups.get(0);
+            assertEquals(sgId.blessing, actual.blessing);
+            assertEquals(sgId.name, actual.name);
+
+            VersionedSyncgroupSpec verSpec = syncgroup.getSpec();
+            assertNotNull(verSpec.version);
+            assertTrue(verSpec.version.length() > 0);
+            assertNotNull(verSpec.syncgroupSpec);
+            assertEquals(1, verSpec.syncgroupSpec.collections.size());
+            // The trim is used to remove a new line.
+            assertEquals(
+                    new String(spec.permissions.json),
+                    new String(verSpec.syncgroupSpec.permissions.json).trim());
+            actual = syncgroups.get(0);
+            assertEquals(sgId.blessing, actual.blessing);
+            assertEquals(sgId.name, actual.name);
+
+            verSpec.syncgroupSpec.description = "Dummy description";
+            syncgroup.setSpec(verSpec);
+            assertEquals(
+                    verSpec.syncgroupSpec.description,
+                    syncgroup.getSpec().syncgroupSpec.description);
+
+            Map<String, SyncgroupMemberInfo> members = syncgroup.getMembers();
+            assertNotNull(members);
+            assertEquals(1, members.size());
+            assertTrue(members.keySet().iterator().next().length() > 0);
+            assertEquals(info.syncPriority, members.values().iterator().next().syncPriority);
+            assertEquals(info.blobDevType, members.values().iterator().next().blobDevType);
+        } catch (VError vError) {
+            vError.printStackTrace();
+            fail(vError.toString());
+        }
+    }
+
+    @Test
+    public void destroy() {
+        Id dbId = new Id("idp:a:angrybirds", "destroy_syncgroup");
+        Id sgId = new Id("idp:u:alice", "syncgroup");
+        // TODO(razvanm): We'll have to update this after the destroy lands.
+        boolean exceptionThrown = false;
+        try {
+            Database db = Service.database(dbId);
+            db.create(anyDbPermissions());
+            db.syncgroup(sgId).destroy();
+        } catch (VError vError) {
+            assertEquals("v.io/v23/verror.NotImplemented", vError.id);
+            exceptionThrown = true;
+        }
+        assertTrue(exceptionThrown);
+    }
+
+    @Test
+    public void join() {
+        Id dbId = new Id("idp:a:angrybirds", "core_join_syncgroup");
+        Id sgId = new Id("idp:u:alice", "syncgroup");
+        boolean exceptionThrown = false;
+        try {
+            Database db = Service.database(dbId);
+            db.syncgroup(sgId).join("", new ArrayList<String>(), new SyncgroupMemberInfo());
+        } catch (VError vError) {
+            assertEquals("v.io/v23/verror.NoExist", vError.id);
+            assertNotNull(vError.message);
+            assertNotNull(vError.stack);
+            assertEquals(0, vError.actionCode);
+            exceptionThrown = true;
+        }
+        assertTrue(exceptionThrown);
+    }
+
+    @Test
+    public void leave() {
+        Id dbId = new Id("idp:a:angrybirds", "core_leave_syncgroups");
+        String dbName = dbId.encode();
+        Id sgId = new Id("idp:u:alice", "syncgroup");
+        boolean exceptionThrown = false;
+        try {
+            Database db = Service.database(dbId);
+            db.create(anyDbPermissions());
+            db.syncgroup(sgId).leave();
+        }  catch (VError vError) {
+            assertEquals("v.io/v23/verror.NotImplemented", vError.id);
+            exceptionThrown = true;
+        }
+        assertTrue(exceptionThrown);
+    }
+
+    @Test
+    public void eject() {
+        Id dbId = new Id("idp:a:angrybirds", "core_eject_from_syncgroup");
+        String dbName = dbId.encode();
+        Id sgId = new Id("idp:u:alice", "syncgroup");
+        boolean exceptionThrown = false;
+        try {
+            Database db = Service.database(dbId);
+            db.create(anyDbPermissions());
+            db.syncgroup(sgId).eject("");
+        }  catch (VError vError) {
+            assertEquals("v.io/v23/verror.NotImplemented", vError.id);
+            exceptionThrown = true;
+        }
+        assertTrue(exceptionThrown);
+    }
+}
diff --git a/syncbase/src/test/java/io/v/syncbase/internal/TestConstants.java b/syncbase/src/test/java/io/v/syncbase/core/TestConstants.java
similarity index 97%
rename from syncbase/src/test/java/io/v/syncbase/internal/TestConstants.java
rename to syncbase/src/test/java/io/v/syncbase/core/TestConstants.java
index a0b7615..ec701a0 100644
--- a/syncbase/src/test/java/io/v/syncbase/internal/TestConstants.java
+++ b/syncbase/src/test/java/io/v/syncbase/core/TestConstants.java
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-package io.v.syncbase.internal;
+package io.v.syncbase.core;
 
 public class TestConstants {
     public static Permissions anyDbPermissions() {
diff --git a/syncbase/src/test/java/io/v/syncbase/internal/BlessingsTest.java b/syncbase/src/test/java/io/v/syncbase/internal/BlessingsTest.java
index a6de221..dc9aeb7 100644
--- a/syncbase/src/test/java/io/v/syncbase/internal/BlessingsTest.java
+++ b/syncbase/src/test/java/io/v/syncbase/internal/BlessingsTest.java
@@ -7,6 +7,8 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import io.v.syncbase.core.VError;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
diff --git a/syncbase/src/test/java/io/v/syncbase/internal/CollectionTest.java b/syncbase/src/test/java/io/v/syncbase/internal/CollectionTest.java
index 4a07d4f..0216303 100644
--- a/syncbase/src/test/java/io/v/syncbase/internal/CollectionTest.java
+++ b/syncbase/src/test/java/io/v/syncbase/internal/CollectionTest.java
@@ -13,8 +13,13 @@
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 
-import static io.v.syncbase.internal.TestConstants.anyCollectionPermissions;
-import static io.v.syncbase.internal.TestConstants.anyDbPermissions;
+import io.v.syncbase.core.Id;
+import io.v.syncbase.core.KeyValue;
+import io.v.syncbase.core.Permissions;
+import io.v.syncbase.core.VError;
+
+import static io.v.syncbase.core.TestConstants.anyCollectionPermissions;
+import static io.v.syncbase.core.TestConstants.anyDbPermissions;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -173,7 +178,7 @@
             Collection.Scan(collectionName, batchHandle, new byte[]{}, new byte[]{},
                     new Collection.ScanCallbacks() {
                 @Override
-                public void onKeyValue(Collection.KeyValue keyValue) {
+                public void onKeyValue(KeyValue keyValue) {
                     assertEquals("key", keyValue.key);
                     assertArrayEquals(vomValue, keyValue.value);
                 }
diff --git a/syncbase/src/test/java/io/v/syncbase/internal/DatabaseTest.java b/syncbase/src/test/java/io/v/syncbase/internal/DatabaseTest.java
index d1b3955..46faeff 100644
--- a/syncbase/src/test/java/io/v/syncbase/internal/DatabaseTest.java
+++ b/syncbase/src/test/java/io/v/syncbase/internal/DatabaseTest.java
@@ -17,9 +17,18 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
 
-import static io.v.syncbase.internal.TestConstants.anyCollectionPermissions;
-import static io.v.syncbase.internal.TestConstants.anyDbPermissions;
-import static io.v.syncbase.internal.TestConstants.anySyncgroupPermissions;
+import io.v.syncbase.core.CollectionRowPattern;
+import io.v.syncbase.core.Id;
+import io.v.syncbase.core.SyncgroupMemberInfo;
+import io.v.syncbase.core.SyncgroupSpec;
+import io.v.syncbase.core.VError;
+import io.v.syncbase.core.VersionedPermissions;
+import io.v.syncbase.core.VersionedSyncgroupSpec;
+import io.v.syncbase.core.WatchChange;
+
+import static io.v.syncbase.core.TestConstants.anyCollectionPermissions;
+import static io.v.syncbase.core.TestConstants.anyDbPermissions;
+import static io.v.syncbase.core.TestConstants.anySyncgroupPermissions;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -179,10 +188,10 @@
             String batchHandle = Database.BeginBatch(dbId.encode(), null);
             Collection.Create(collectionName, batchHandle, anyCollectionPermissions());
             Database.Commit(dbName, batchHandle);
-            Database.SyncgroupSpec spec = new Database.SyncgroupSpec();
+            SyncgroupSpec spec = new SyncgroupSpec();
             spec.collections = Arrays.asList(collectionId);
             spec.permissions = anySyncgroupPermissions();
-            Database.SyncgroupMemberInfo info = new Database.SyncgroupMemberInfo();
+            SyncgroupMemberInfo info = new SyncgroupMemberInfo();
             // TODO(razvanm): Pick some meaningful values.
             info.syncPriority = 1;
             info.blobDevType = 2;
@@ -193,7 +202,7 @@
             assertEquals(sgId.blessing, actual.blessing);
             assertEquals(sgId.name, actual.name);
 
-            Database.VersionedSyncgroupSpec verSpec = Database.GetSyncgroupSpec(dbName, sgId);
+            VersionedSyncgroupSpec verSpec = Database.GetSyncgroupSpec(dbName, sgId);
             assertNotNull(verSpec.version);
             assertTrue(verSpec.version.length() > 0);
             assertNotNull(verSpec.syncgroupSpec);
@@ -210,7 +219,7 @@
             Database.SetSyncgroupSpec(dbName, sgId, verSpec);
             assertEquals(verSpec.syncgroupSpec.description, Database.GetSyncgroupSpec(dbName, sgId).syncgroupSpec.description);
 
-            Map<String, Database.SyncgroupMemberInfo> members = Database.GetSyncgroupMembers(dbName, sgId);
+            Map<String, SyncgroupMemberInfo> members = Database.GetSyncgroupMembers(dbName, sgId);
             assertNotNull(members);
             assertEquals(1, members.size());
             assertTrue(members.keySet().iterator().next().length() > 0);
@@ -261,8 +270,8 @@
         Id sgId = new Id("idp:u:alice", "syncgroup");
         boolean exceptionThrown = false;
         try {
-            Database.SyncgroupSpec spec = Database.JoinSyncgroup(
-                    dbName, "", new ArrayList<String>(), sgId, new Database.SyncgroupMemberInfo());
+            SyncgroupSpec spec = Database.JoinSyncgroup(
+                    dbName, "", new ArrayList<String>(), sgId, new SyncgroupMemberInfo());
         } catch (VError vError) {
             assertEquals("v.io/v23/verror.NoExist", vError.id);
             assertNotNull(vError.message);
@@ -314,11 +323,10 @@
             Database.Create(dbName, anyDbPermissions());
             String batchHandle = Database.BeginBatch(dbName, null);
             byte[] marker = Database.GetResumeMarker(dbName, batchHandle);
-            List<Database.CollectionRowPattern> patterns =
-                    Arrays.asList(new Database.CollectionRowPattern());
+            List<CollectionRowPattern> patterns = Arrays.asList(new CollectionRowPattern());
             Database.WatchPatterns(dbName, marker, patterns, new Database.WatchPatternsCallbacks() {
                 @Override
-                public void onChange(Database.WatchChange watchChange) {
+                public void onChange(WatchChange watchChange) {
                     fail("Unexpected onChange: " + watchChange);
                 }
 
@@ -353,14 +361,14 @@
             Database.Create(dbName, anyDbPermissions());
             String batchHandle = Database.BeginBatch(dbName, null);
             Collection.Create(collectionName, batchHandle, anyCollectionPermissions());
-            Database.CollectionRowPattern pattern = new Database.CollectionRowPattern();
+            CollectionRowPattern pattern = new CollectionRowPattern();
             pattern.collectionBlessing = collectionId.blessing;
             pattern.collectionName = collectionId.name;
-            List<Database.CollectionRowPattern> patterns = Arrays.asList(pattern);
+            List<CollectionRowPattern> patterns = Arrays.asList(pattern);
             Database.WatchPatterns(dbName, new byte[]{}, patterns,
                     new Database.WatchPatternsCallbacks() {
                 @Override
-                public void onChange(Database.WatchChange watchChange) {
+                public void onChange(WatchChange watchChange) {
                     // TODO(razvanm): Really check the answer once the onChange starts working.
                     fail("Unexpected onChange: " + watchChange);
                 }
diff --git a/syncbase/src/test/java/io/v/syncbase/internal/RowTest.java b/syncbase/src/test/java/io/v/syncbase/internal/RowTest.java
index aaca664..a622543 100644
--- a/syncbase/src/test/java/io/v/syncbase/internal/RowTest.java
+++ b/syncbase/src/test/java/io/v/syncbase/internal/RowTest.java
@@ -9,8 +9,11 @@
 
 import java.util.Arrays;
 
-import static io.v.syncbase.internal.TestConstants.anyCollectionPermissions;
-import static io.v.syncbase.internal.TestConstants.anyDbPermissions;
+import io.v.syncbase.core.Id;
+import io.v.syncbase.core.VError;
+
+import static io.v.syncbase.core.TestConstants.anyCollectionPermissions;
+import static io.v.syncbase.core.TestConstants.anyDbPermissions;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
diff --git a/syncbase/src/test/java/io/v/syncbase/internal/ServiceTest.java b/syncbase/src/test/java/io/v/syncbase/internal/ServiceTest.java
index b02c423..6b629fa 100644
--- a/syncbase/src/test/java/io/v/syncbase/internal/ServiceTest.java
+++ b/syncbase/src/test/java/io/v/syncbase/internal/ServiceTest.java
@@ -7,6 +7,9 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import io.v.syncbase.core.VError;
+import io.v.syncbase.core.VersionedPermissions;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
@@ -21,6 +24,7 @@
     @Test
     public void listDatabases() {
         assertTrue(Service.ListDatabases().isEmpty());
+        // TODO(razvanm): Add proper testing after listing starts working.
     }
 
     @Test
diff --git a/syncbase/src/test/java/io/v/syncbase/internal/UtilTest.java b/syncbase/src/test/java/io/v/syncbase/internal/UtilTest.java
index ac7902c..1f6929a 100644
--- a/syncbase/src/test/java/io/v/syncbase/internal/UtilTest.java
+++ b/syncbase/src/test/java/io/v/syncbase/internal/UtilTest.java
@@ -9,6 +9,8 @@
 
 import java.util.Arrays;
 
+import io.v.syncbase.core.Id;
+
 import static org.junit.Assert.assertEquals;
 
 public class UtilTest {