java: Implement 6 more JNI syncbase internal functions

The functions added in this CL are:

  Database.JoinSyncgroup
  Database.LeaveSyncgroup
  Database.EjectFromSyncgroup
  Database.GetSyncgroupSpec
  Database.SetSyncgroupSpec
  Database.GetSyncgroupMembers

The only unimplemented JNI functions are now the two that use
callbacks:

  Database.WatchPatterns
  Collection.Scan

MultiPart: 1/2
Change-Id: Id90f490ebb925e1b6ddf579367865e878cd12f20
diff --git a/services/syncbase/bridge/cgo/jni.go b/services/syncbase/bridge/cgo/jni.go
index 56bd6a8..27b8d5d 100644
--- a/services/syncbase/bridge/cgo/jni.go
+++ b/services/syncbase/bridge/cgo/jni.go
@@ -11,19 +11,31 @@
 	"unsafe"
 )
 
-// #include <stdlib.h>
-// #include <string.h>
-// #include "jni_wrapper.h"
-// #include "lib.h"
+/*
+#include <stdlib.h>
+#include <string.h>
+#include "jni_wrapper.h"
+#include "lib.h"
+
+static jvalue* allocJValueArray(int elements) {
+  return malloc(sizeof(jvalue) * elements);
+}
+
+static void setJValueArrayElement(jvalue* arr, int index, jvalue val) {
+  arr[index] = val;
+}
+*/
 import "C"
 
 var (
-	jVM                      *C.JavaVM
-	arrayListClass           jArrayListClass
-	idClass                  jIdClass
-	syncgroupMemberInfoClass jSyncgroupMemberInfo
-	syncgroupSpecClass       jSyncgroupSpec
-	verrorClass              jVErrorClass
+	jVM                         *C.JavaVM
+	arrayListClass              jArrayListClass
+	hashMapClass                jHashMap
+	idClass                     jIdClass
+	syncgroupMemberInfoClass    jSyncgroupMemberInfo
+	syncgroupSpecClass          jSyncgroupSpec
+	verrorClass                 jVErrorClass
+	versionedSyncgroupSpecClass jVersionedSyncgroupSpec
 )
 
 // JNI_OnLoad is called when System.loadLibrary is called. We need to cache the
@@ -42,10 +54,12 @@
 
 	v23_syncbase_Init(C.v23_syncbase_Bool(1))
 	arrayListClass = newJArrayListClass(env)
+	hashMapClass = newJHashMap(env)
 	idClass = newJIdClass(env)
 	syncgroupMemberInfoClass = newJSyncgroupMemberInfo(env)
 	syncgroupSpecClass = newJSyncgroupSpec(env)
 	verrorClass = newJVErrorClass(env)
+	versionedSyncgroupSpecClass = newJVersionedSyncgroupSpec(env)
 
 	return C.JNI_VERSION_1_6
 }
@@ -190,10 +204,27 @@
 	maybeThrowException(env, &cErr)
 }
 
-func Java_io_v_syncbase_internal_Database_JoinSyncgroup(env *C.JNIEnv, cls C.jclass, name C.jstring, sgId C.jobject, info C.jobject) C.jobject {
-	return nil
+//export Java_io_v_syncbase_internal_Database_JoinSyncgroup
+func Java_io_v_syncbase_internal_Database_JoinSyncgroup(env *C.JNIEnv, cls C.jclass, name C.jstring, remoteSyncbaseName C.jstring, expectedSyncbaseBlessings C.jobject, sgId C.jobject, info C.jobject) C.jobject {
+	cName := newVStringFromJava(env, name)
+	cRemoteSyncbaseName := newVStringFromJava(env, remoteSyncbaseName)
+	cExpectedSyncbaseBlessings := newVStringsFromJava(env, expectedSyncbaseBlessings)
+	cSgId := newVIdFromJava(env, sgId)
+	cMyInfo := newVSyncgroupMemberInfoFromJava(env, info)
+	var cSpec C.v23_syncbase_SyncgroupSpec
+	var cErr C.v23_syncbase_VError
+	v23_syncbase_DbJoinSyncgroup(cName, cRemoteSyncbaseName, cExpectedSyncbaseBlessings, cSgId, cMyInfo, &cSpec, &cErr)
+	maybeThrowException(env, &cErr)
+	return cSpec.extractToJava(env)
 }
+
+//export Java_io_v_syncbase_internal_Database_LeaveSyncgroup
 func Java_io_v_syncbase_internal_Database_LeaveSyncgroup(env *C.JNIEnv, cls C.jclass, name C.jstring, sgId C.jobject) {
+	cName := newVStringFromJava(env, name)
+	cSgId := newVIdFromJava(env, sgId)
+	var cErr C.v23_syncbase_VError
+	v23_syncbase_DbLeaveSyncgroup(cName, cSgId, &cErr)
+	maybeThrowException(env, &cErr)
 }
 
 //export Java_io_v_syncbase_internal_Database_DestroySyncgroup
@@ -205,16 +236,56 @@
 	maybeThrowException(env, &cErr)
 }
 
+//export Java_io_v_syncbase_internal_Database_EjectFromSyncgroup
 func Java_io_v_syncbase_internal_Database_EjectFromSyncgroup(env *C.JNIEnv, cls C.jclass, name C.jstring, sgId C.jobject, member C.jstring) {
+	cName := newVStringFromJava(env, name)
+	cSgId := newVIdFromJava(env, sgId)
+	cMember := newVStringFromJava(env, member)
+	var cErr C.v23_syncbase_VError
+	v23_syncbase_DbEjectFromSyncgroup(cName, cSgId, cMember, &cErr)
+	maybeThrowException(env, &cErr)
 }
+
+//export Java_io_v_syncbase_internal_Database_GetSyncgroupSpec
 func Java_io_v_syncbase_internal_Database_GetSyncgroupSpec(env *C.JNIEnv, cls C.jclass, name C.jstring, sgId C.jobject) C.jobject {
-	return nil
+	cName := newVStringFromJava(env, name)
+	cSgId := newVIdFromJava(env, sgId)
+	var cSpec C.v23_syncbase_SyncgroupSpec
+	var cVersion C.v23_syncbase_String
+	var cErr C.v23_syncbase_VError
+	v23_syncbase_DbGetSyncgroupSpec(cName, cSgId, &cSpec, &cVersion, &cErr)
+	if maybeThrowException(env, &cErr) {
+		return nil
+	}
+	obj := C.NewObjectA(env, versionedSyncgroupSpecClass.class, versionedSyncgroupSpecClass.init, nil)
+	C.SetObjectField(env, obj, versionedSyncgroupSpecClass.version, cVersion.extractToJava(env))
+	C.SetObjectField(env, obj, versionedSyncgroupSpecClass.syncgroupSpec, cSpec.extractToJava(env))
+	return obj
 }
-func Java_io_v_syncbase_internal_Database_SetSyncgroupSpec(env *C.JNIEnv, cls C.jclass, name C.jstring, sgId C.jobject, spec C.jobject) {
+
+//export Java_io_v_syncbase_internal_Database_SetSyncgroupSpec
+func Java_io_v_syncbase_internal_Database_SetSyncgroupSpec(env *C.JNIEnv, cls C.jclass, name C.jstring, sgId C.jobject, versionedSpec C.jobject) {
+	cName := newVStringFromJava(env, name)
+	cSgId := newVIdFromJava(env, sgId)
+	cSpec, cVersion := newVSyngroupSpecAndVersionFromJava(env, versionedSpec)
+	var cErr C.v23_syncbase_VError
+	v23_syncbase_DbSetSyncgroupSpec(cName, cSgId, cSpec, cVersion, &cErr)
+	maybeThrowException(env, &cErr)
 }
+
+//export Java_io_v_syncbase_internal_Database_GetSyncgroupMembers
 func Java_io_v_syncbase_internal_Database_GetSyncgroupMembers(env *C.JNIEnv, cls C.jclass, name C.jstring, sgId C.jobject) C.jobject {
-	return nil
+	cName := newVStringFromJava(env, name)
+	cSgId := newVIdFromJava(env, sgId)
+	var cMembers C.v23_syncbase_SyncgroupMemberInfoMap
+	var cErr C.v23_syncbase_VError
+	v23_syncbase_DbGetSyncgroupMembers(cName, cSgId, &cMembers, &cErr)
+	if maybeThrowException(env, &cErr) {
+		return nil
+	}
+	return cMembers.extractToJava(env)
 }
+
 func Java_io_v_syncbase_internal_Database_WatchPatterns(env *C.JNIEnv, cls C.jclass, name C.jstring, resumeMaker C.jbyteArray, patters C.jobject, callbacks C.jobject) {
 }
 
@@ -363,7 +434,9 @@
 // "inconsistent definitions" errors for various functions (C.NewObjectA and
 // C.SetObjectField for example).
 
-// extractToJava creates an Id object from a v23_syncbase_Id.
+// All the extractToJava methods return Java types and deallocate all the
+// pointers inside v23_syncbase_* variable.
+
 func (x *C.v23_syncbase_Id) extractToJava(env *C.JNIEnv) C.jobject {
 	obj := C.NewObjectA(env, idClass.class, idClass.init, nil)
 	C.SetObjectField(env, obj, idClass.blessing, x.blessing.extractToJava(env))
@@ -404,8 +477,6 @@
 	return r
 }
 
-// extractToJava constructs a jobject from a v23_syncbase_Ids. The pointers
-// inside v23_syncbase_Ids will be freed.
 func (x *C.v23_syncbase_Ids) extractToJava(env *C.JNIEnv) C.jobject {
 	obj := C.NewObjectA(env, arrayListClass.class, arrayListClass.init, nil)
 	for i := 0; i < int(x.n); i++ {
@@ -449,8 +520,40 @@
 	return r
 }
 
-// extractToJava constructs a jobject from a v23_syncbase_VError. The pointers
-// from inside v23_syncbase_VError will be freed.
+func (x *C.v23_syncbase_SyncgroupSpec) extractToJava(env *C.JNIEnv) C.jobject {
+	obj := C.NewObjectA(env, syncgroupSpecClass.class, syncgroupSpecClass.init, nil)
+	C.SetObjectField(env, obj, syncgroupSpecClass.description, x.description.extractToJava(env))
+	C.SetObjectField(env, obj, syncgroupSpecClass.publishSyncbaseName, x.publishSyncbaseName.extractToJava(env))
+	// TODO(razvanm): Also extract the permissions.
+	C.SetObjectField(env, obj, syncgroupSpecClass.collections, x.collections.extractToJava(env))
+	C.SetObjectField(env, obj, syncgroupSpecClass.mountTables, x.mountTables.extractToJava(env))
+	C.SetBooleanField(env, obj, syncgroupSpecClass.isPrivate, x.isPrivate.extractToJava())
+	return obj
+}
+
+func (x *C.v23_syncbase_SyncgroupMemberInfo) extractToJava(env *C.JNIEnv) C.jobject {
+	obj := C.NewObjectA(env, syncgroupMemberInfoClass.class, syncgroupMemberInfoClass.init, nil)
+	C.SetByteField(env, obj, syncgroupMemberInfoClass.syncPriority, C.jbyte(x.syncPriority))
+	C.SetByteField(env, obj, syncgroupMemberInfoClass.blobDevType, C.jbyte(x.blobDevType))
+	return obj
+}
+
+func (x *C.v23_syncbase_SyncgroupMemberInfoMap) extractToJava(env *C.JNIEnv) C.jobject {
+	obj := C.NewObjectA(env, hashMapClass.class, hashMapClass.init, nil)
+	for i := 0; i < int(x.n); i++ {
+		k, v := x.at(i)
+		key := k.extractToJava(env)
+		value := v.extractToJava(env)
+		args := C.allocJValueArray(2)
+		C.setJValueArrayElement(args, 0, *(*C.jvalue)(unsafe.Pointer(&key)))
+		C.setJValueArrayElement(args, 1, *(*C.jvalue)(unsafe.Pointer(&value)))
+		C.CallObjectMethodA(env, obj, hashMapClass.put, args)
+		C.free(unsafe.Pointer(args))
+	}
+	x.free()
+	return obj
+}
+
 func (x *C.v23_syncbase_VError) extractToJava(env *C.JNIEnv) C.jobject {
 	if x.id.p == nil {
 		return nil
@@ -463,3 +566,14 @@
 	x.free()
 	return obj
 }
+
+func (x *C.v23_syncbase_Strings) extractToJava(env *C.JNIEnv) C.jobject {
+	obj := C.NewObjectA(env, arrayListClass.class, arrayListClass.init, nil)
+	for i := 0; i < int(x.n); i++ {
+		s := x.at(i).extractToJava(env)
+		arg := *(*C.jvalue)(unsafe.Pointer(&s))
+		C.CallBooleanMethodA(env, obj, arrayListClass.add, &arg)
+	}
+	x.free()
+	return obj
+}
diff --git a/services/syncbase/bridge/cgo/jni_lib.go b/services/syncbase/bridge/cgo/jni_lib.go
index f06fc81..a83f4c8 100644
--- a/services/syncbase/bridge/cgo/jni_lib.go
+++ b/services/syncbase/bridge/cgo/jni_lib.go
@@ -27,6 +27,21 @@
 	}
 }
 
+type jHashMap struct {
+	class C.jclass
+	init  C.jmethodID
+	put   C.jmethodID
+}
+
+func newJHashMap(env *C.JNIEnv) jHashMap {
+	cls, init := initClass(env, "java/util/HashMap")
+	return jHashMap{
+		class: cls,
+		init:  init,
+		put:   jGetMethodID(env, cls, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"),
+	}
+}
+
 type jIteratorInterface struct {
 	hasNext C.jmethodID
 	next    C.jmethodID
@@ -133,6 +148,23 @@
 	}
 }
 
+type jVersionedSyncgroupSpec struct {
+	class         C.jclass
+	init          C.jmethodID
+	version       C.jfieldID
+	syncgroupSpec C.jfieldID
+}
+
+func newJVersionedSyncgroupSpec(env *C.JNIEnv) jVersionedSyncgroupSpec {
+	cls, init := initClass(env, "io/v/syncbase/internal/Database$VersionedSyncgroupSpec")
+	return jVersionedSyncgroupSpec{
+		class:         cls,
+		init:          init,
+		version:       jGetFieldID(env, cls, "version", "Ljava/lang/String;"),
+		syncgroupSpec: jGetFieldID(env, cls, "syncgroupSpec", "Lio/v/syncbase/internal/Database$SyncgroupSpec;"),
+	}
+}
+
 // initClass returns the jclass and the jmethodID of the default constructor for
 // a class.
 func initClass(env *C.JNIEnv, name string) (C.jclass, C.jmethodID) {
diff --git a/services/syncbase/bridge/cgo/jni_types.go b/services/syncbase/bridge/cgo/jni_types.go
index 173d79f..875833c 100644
--- a/services/syncbase/bridge/cgo/jni_types.go
+++ b/services/syncbase/bridge/cgo/jni_types.go
@@ -21,7 +21,17 @@
 // #include "lib.h"
 import "C"
 
-// newVBytesFromJava creates a v23_syncbase_Bytes from a jbyteArray.
+// All the extractToJava methods return Java types and deallocate all the
+// pointers inside v23_syncbase_* variable.
+
+// extractToJava constructs a jboolean from a bool.
+func (x *C.bool) extractToJava() C.jboolean {
+	if *x == false {
+		return 0
+	}
+	return 1
+}
+
 func newVBytesFromJava(env *C.JNIEnv, array C.jbyteArray) C.v23_syncbase_Bytes {
 	r := C.v23_syncbase_Bytes{}
 	n := C.GetArrayLength(env, array)
@@ -34,8 +44,6 @@
 	return r
 }
 
-// extractToJava constructs a jbyteArray from a v23_syncbase_Bytes. The pointer
-// inside v23_syncbase_Bytes will be freed.
 func (x *C.v23_syncbase_Bytes) extractToJava(env *C.JNIEnv) C.jbyteArray {
 	obj := C.NewByteArray(env, C.jsize(x.n))
 	if C.ExceptionOccurred(env) != nil {
@@ -49,7 +57,7 @@
 	return obj
 }
 
-// newVIdFromJava creates a v23_syncbase_Id from a jobject.
+// newVIdFromJava creates a v23_syncbase_Id from an Id object.
 func newVIdFromJava(env *C.JNIEnv, obj C.jobject) C.v23_syncbase_Id {
 	blessing := C.jstring(C.GetObjectField(env, obj, idClass.blessing))
 	if C.ExceptionOccurred(env) != nil {
@@ -73,6 +81,9 @@
 // UTF-8 strings (inner nulls are encoded as 0xC0, 0x80 and the string is
 // terminated with a null).
 func (x *C.v23_syncbase_String) extractToJava(env *C.JNIEnv) C.jstring {
+	if x.p == nil {
+		return nil
+	}
 	n := int(x.n)
 	srcPtr := uintptr(unsafe.Pointer(x.p))
 	numNulls := 0
@@ -103,7 +114,6 @@
 	return r
 }
 
-// newVStringFromJava creates a v23_syncbase_String from a jstring.
 func newVStringFromJava(env *C.JNIEnv, s C.jstring) C.v23_syncbase_String {
 	r := C.v23_syncbase_String{}
 	if s == nil {
@@ -149,7 +159,7 @@
 }
 
 // newVSyncgroupMemberInfoFromJava creates a v23_syncbase_SyncgroupMemberInfo
-// from a jobject.
+// from a SyncgroupMemberInfo object.
 func newVSyncgroupMemberInfoFromJava(env *C.JNIEnv, obj C.jobject) C.v23_syncbase_SyncgroupMemberInfo {
 	syncPriority := C.GetIntField(env, obj, syncgroupMemberInfoClass.syncPriority)
 	if C.ExceptionOccurred(env) != nil {
@@ -165,7 +175,8 @@
 	}
 }
 
-// newVSyngroupSpecFromJava creates a v23_syncbase_SyncgroupSpec from a jobject.
+// newVSyngroupSpecFromJava creates a v23_syncbase_SyncgroupSpec from a
+// SyncgroupSpec object.
 func newVSyngroupSpecFromJava(env *C.JNIEnv, obj C.jobject) C.v23_syncbase_SyncgroupSpec {
 	description := C.jstring(C.GetObjectField(env, obj, syncgroupSpecClass.description))
 	if C.ExceptionOccurred(env) != nil {
@@ -199,3 +210,17 @@
 		mountTables:         mountTablesStrings,
 	}
 }
+
+// newVSyngroupSpecAndVersionFromJava creates a v23_syncbase_SyncgroupSpec and
+// v23_syncbase_String version string from a VersionedSyncgroupSpec object.
+func newVSyngroupSpecAndVersionFromJava(env *C.JNIEnv, obj C.jobject) (C.v23_syncbase_SyncgroupSpec, C.v23_syncbase_String) {
+	version := C.jstring(C.GetObjectField(env, obj, versionedSyncgroupSpecClass.version))
+	if C.ExceptionOccurred(env) != nil {
+		panic("newVSyngroupSpecAndVersionFromJava exception while retrieving VersionedSyncgroupSpec.version")
+	}
+	spec := newVSyngroupSpecFromJava(env, C.GetObjectField(env, obj, versionedSyncgroupSpecClass.syncgroupSpec))
+	if C.ExceptionOccurred(env) != nil {
+		panic("newVSyngroupSpecAndVersionFromJava exception while retrieving VersionedSyncgroupSpec.syncgroupSpec")
+	}
+	return spec, newVStringFromJava(env, version)
+}
diff --git a/services/syncbase/bridge/cgo/jni_wrapper.c b/services/syncbase/bridge/cgo/jni_wrapper.c
index be8f139..bb00584 100644
--- a/services/syncbase/bridge/cgo/jni_wrapper.c
+++ b/services/syncbase/bridge/cgo/jni_wrapper.c
@@ -14,6 +14,10 @@
   return (*env)->CallObjectMethod(env,obj,methodID);
 }
 
+jobject CallObjectMethodA(JNIEnv *env, jobject obj, jmethodID methodID, jvalue *args) {
+  return (*env)->CallObjectMethodA(env, obj, methodID, args);
+}
+
 void ExceptionClear(JNIEnv *env) {
   (*env)->ExceptionClear(env);
 }
@@ -78,6 +82,10 @@
   return (*env)->NewObjectA(env, cls, methodID, args);
 }
 
+void SetBooleanField(JNIEnv *env, jobject obj, jfieldID fieldID, jboolean value) {
+  return (*env)->SetBooleanField(env, obj, fieldID, value);
+}
+
 void SetByteArrayRegion(JNIEnv *env, jbyteArray array, jsize start, jsize len, jbyte *buf) {
   return (*env)->SetByteArrayRegion(env, array, start, len, buf);
 }
@@ -86,6 +94,10 @@
   (*env)->SetLongField(env, obj, fieldID, value);
 }
 
+void SetByteField(JNIEnv *env, jobject obj, jfieldID fieldID, jbyte value) {
+  (*env)->SetByteField(env, obj, fieldID, value);
+}
+
 jbyteArray NewByteArray(JNIEnv *env, jsize length) {
   return (*env)->NewByteArray(env, length);
 }
diff --git a/services/syncbase/bridge/cgo/jni_wrapper.h b/services/syncbase/bridge/cgo/jni_wrapper.h
index a4ba834..36be6c8 100644
--- a/services/syncbase/bridge/cgo/jni_wrapper.h
+++ b/services/syncbase/bridge/cgo/jni_wrapper.h
@@ -13,6 +13,7 @@
 jboolean CallBooleanMethodA(JNIEnv *env, jobject obj, jmethodID methodID, jvalue *args);
 jobject CallIntMethod(JNIEnv *env, jobject obj, jmethodID methodID);
 jobject CallObjectMethod(JNIEnv *env, jobject obj, jmethodID methodID);
+jobject CallObjectMethodA(JNIEnv *env, jobject obj, jmethodID methodID, jvalue *args);
 void ExceptionClear(JNIEnv *env);
 jthrowable ExceptionOccurred(JNIEnv* env);
 jclass FindClass(JNIEnv* env, const char* name);
@@ -31,6 +32,8 @@
 jobject NewGlobalRef(JNIEnv* env, jobject obj);
 jobject NewObjectA(JNIEnv *env, jclass cls, jmethodID methodID, jvalue *args);
 jstring NewStringUTF(JNIEnv *env, const char *bytes);
+void SetBooleanField(JNIEnv *env, jobject obj, jfieldID fieldID, jboolean value);
+void SetByteField(JNIEnv *env, jobject obj, jfieldID fieldID, jbyte value);
 void SetByteArrayRegion(JNIEnv *env, jbyteArray array, jsize start, jsize len, jbyte *buf);
 void SetLongField(JNIEnv *env, jobject obj, jfieldID fieldID, jlong value);
 void SetObjectField(JNIEnv *env, jobject obj, jfieldID fieldID, jobject value);
diff --git a/services/syncbase/bridge/cgo/types.go b/services/syncbase/bridge/cgo/types.go
index 73a4ed9..a7b645c 100644
--- a/services/syncbase/bridge/cgo/types.go
+++ b/services/syncbase/bridge/cgo/types.go
@@ -293,6 +293,18 @@
 	return res
 }
 
+func (x *C.v23_syncbase_Strings) free() {
+	if x.p == nil {
+		return
+	}
+	for i := 0; i < int(x.n); i++ {
+		x.at(i).free()
+	}
+	C.free(unsafe.Pointer(x.p))
+	x.p = nil
+	x.n = 0
+}
+
 ////////////////////////////////////////////////////////////
 // C.v23_syncbase_SyncgroupSpec
 
@@ -353,6 +365,28 @@
 	}
 }
 
+func (x *C.v23_syncbase_SyncgroupMemberInfoMap) free() {
+	if x.n == 0 {
+		return
+	}
+	if x.keys == nil {
+		panic("v23_syncbase_SyncgroupMemberInfoMap keys corruption")
+	}
+	if x.values == nil {
+		panic("v23_syncbase_SyncgroupMemberInfoMap values corruption")
+	}
+	for i := 0; i < int(x.n); i++ {
+		// The values don't contain pointers.
+		k, _ := x.at(i)
+		k.free()
+	}
+	C.free(unsafe.Pointer(x.keys))
+	C.free(unsafe.Pointer(x.values))
+	x.keys = nil
+	x.values = nil
+	x.n = 0
+}
+
 ////////////////////////////////////////////////////////////
 // C.v23_syncbase_VError