java: Implement the Database.WatchPatterns and Collection.Scan

I wasn't able to see that the onChange is called (which matches what
zinman@ also observed).

This change also contains two fixes to the ScanCallbacks interface:

 - the onValue method is renamed to onKeyValue
 - the onDone is updated to take a VError argument.

Due to some new "inconsistent definitions" I also had to move the
jFindClass from jni_util to jni.go. The bottom of the jni.go and the
jni_lib.go are also sorted.

MultiPart: 1/2
Change-Id: Ie3a07a22cadb88fe3c888da5453fbc3ff0e293f1
diff --git a/services/syncbase/bridge/cgo/jni.go b/services/syncbase/bridge/cgo/jni.go
index a92db92..b68037a 100644
--- a/services/syncbase/bridge/cgo/jni.go
+++ b/services/syncbase/bridge/cgo/jni.go
@@ -8,7 +8,9 @@
 package main
 
 import (
+	"fmt"
 	"unsafe"
+	"v.io/x/ref/services/syncbase/bridge/cgo/refmap"
 )
 
 /*
@@ -24,22 +26,45 @@
 static void setJValueArrayElement(jvalue* arr, int index, jvalue val) {
   arr[index] = val;
 }
+
+void v23_syncbase_internal_onChange(v23_syncbase_Handle handle, v23_syncbase_WatchChange);
+void v23_syncbase_internal_onError(v23_syncbase_Handle handle, v23_syncbase_VError);
+
+static v23_syncbase_DbWatchPatternsCallbacks newVWatchPatternsCallbacks() {
+  v23_syncbase_DbWatchPatternsCallbacks cbs = {
+  	0, v23_syncbase_internal_onChange, v23_syncbase_internal_onError};
+  return cbs;
+}
+
+void v23_syncbase_internal_onKeyValue(v23_syncbase_Handle handle, v23_syncbase_KeyValue);
+void v23_syncbase_internal_onDone(v23_syncbase_Handle handle, v23_syncbase_VError);
+
+static v23_syncbase_CollectionScanCallbacks newVScanCallbacks() {
+  v23_syncbase_CollectionScanCallbacks cbs = {
+  	0, v23_syncbase_internal_onKeyValue, v23_syncbase_internal_onDone};
+  return cbs;
+}
 */
 import "C"
 
 var (
 	jVM                         *C.JavaVM
 	arrayListClass              jArrayListClass
+	collectionRowPatternClass   jCollectionRowPattern
 	hashMapClass                jHashMap
 	idClass                     jIdClass
+	keyValueClass               jKeyValue
 	permissionsClass            jPermissions
 	syncgroupMemberInfoClass    jSyncgroupMemberInfo
 	syncgroupSpecClass          jSyncgroupSpec
 	verrorClass                 jVErrorClass
 	versionedPermissionsClass   jVersionedPermissions
 	versionedSyncgroupSpecClass jVersionedSyncgroupSpec
+	watchChangeClass            jWatchChange
 )
 
+var globalRefMap = refmap.NewRefMap()
+
 // JNI_OnLoad is called when System.loadLibrary is called. We need to cache the
 // *JavaVM because that's the only way to get hold of a JNIEnv that is needed
 // for any JNI operation.
@@ -56,14 +81,17 @@
 
 	v23_syncbase_Init(C.v23_syncbase_Bool(1))
 	arrayListClass = newJArrayListClass(env)
+	collectionRowPatternClass = newJCollectionRowPattern(env)
 	hashMapClass = newJHashMap(env)
 	idClass = newJIdClass(env)
+	keyValueClass = newJKeyValue(env)
 	permissionsClass = newJPermissions(env)
 	syncgroupMemberInfoClass = newJSyncgroupMemberInfo(env)
 	syncgroupSpecClass = newJSyncgroupSpec(env)
 	verrorClass = newJVErrorClass(env)
 	versionedPermissionsClass = newJVersionedPermissions(env)
 	versionedSyncgroupSpecClass = newJVersionedSyncgroupSpec(env)
+	watchChangeClass = newJWatchChange(env)
 
 	return C.JNI_VERSION_1_6
 }
@@ -329,7 +357,58 @@
 	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) {
+//export v23_syncbase_internal_onChange
+func v23_syncbase_internal_onChange(handle C.v23_syncbase_Handle, change C.v23_syncbase_WatchChange) {
+	// TODO(razvanm): Remove the panic and uncomment the code from below
+	// after the onChange starts working.
+	panic("v23_syncbase_internal_onChange not implemented")
+	//id := uint64(uintptr(handle))
+	//h := globalRrefMap.Get(id).(*watchPatternsCallbacksHandle)
+	//env, free := getEnv()
+	//obj := change.extractToJava(env)
+	//arg := *(*C.jvalue)(unsafe.Pointer(&obj))
+	//C.CallVoidMethodA(env, C.jobject(unsafe.Pointer(h.obj)), h.callbacks.onChange, &arg)
+	//if C.ExceptionOccurred(env) != nil {
+	//	C.ExceptionDescribe(env)
+	//	panic("java exception")
+	//}
+	//free()
+}
+
+//export v23_syncbase_internal_onError
+func v23_syncbase_internal_onError(handle C.v23_syncbase_Handle, error C.v23_syncbase_VError) {
+	id := uint64(uintptr(handle))
+	h := globalRefMap.Remove(id).(*watchPatternsCallbacksHandle)
+	env, free := getEnv()
+	obj := error.extractToJava(env)
+	arg := *(*C.jvalue)(unsafe.Pointer(&obj))
+	C.CallVoidMethodA(env, C.jobject(unsafe.Pointer(h.obj)), h.callbacks.onError, &arg)
+	if C.ExceptionOccurred(env) != nil {
+		C.ExceptionDescribe(env)
+		panic("java exception")
+	}
+	C.DeleteGlobalRef(env, unsafe.Pointer(h.obj))
+	free()
+}
+
+type watchPatternsCallbacksHandle struct {
+	obj       uintptr
+	callbacks jWatchPatternsCallbacks
+}
+
+//export Java_io_v_syncbase_internal_Database_WatchPatterns
+func Java_io_v_syncbase_internal_Database_WatchPatterns(env *C.JNIEnv, cls C.jclass, name C.jstring, resumeMaker C.jbyteArray, patterns C.jobject, callbacks C.jobject) {
+	cName := newVStringFromJava(env, name)
+	cResumeMarker := newVBytesFromJava(env, resumeMaker)
+	cPatterns := newVCollectionRowPatternsFromJava(env, patterns)
+	cbs := C.newVWatchPatternsCallbacks()
+	cbs.handle = C.v23_syncbase_Handle(uintptr(globalRefMap.Add(&watchPatternsCallbacksHandle{
+		obj:       uintptr(unsafe.Pointer(C.NewGlobalRef(env, callbacks))),
+		callbacks: newJWatchPatternsCallbacks(env, callbacks),
+	})))
+	var cErr C.v23_syncbase_VError
+	v23_syncbase_DbWatchPatterns(cName, cResumeMarker, cPatterns, cbs, &cErr)
+	maybeThrowException(env, &cErr)
 }
 
 //export Java_io_v_syncbase_internal_Collection_GetPermissions
@@ -396,7 +475,57 @@
 	maybeThrowException(env, &cErr)
 }
 
+//export v23_syncbase_internal_onKeyValue
+func v23_syncbase_internal_onKeyValue(handle C.v23_syncbase_Handle, keyValue C.v23_syncbase_KeyValue) {
+	id := uint64(uintptr(handle))
+	h := globalRefMap.Get(id).(*scanCallbacksHandle)
+	env, free := getEnv()
+	obj := keyValue.extractToJava(env)
+	arg := *(*C.jvalue)(unsafe.Pointer(&obj))
+	C.CallVoidMethodA(env, C.jobject(unsafe.Pointer(h.obj)), h.callbacks.onKeyValue, &arg)
+	if C.ExceptionOccurred(env) != nil {
+		C.ExceptionDescribe(env)
+		panic("java exception")
+	}
+	free()
+}
+
+//export v23_syncbase_internal_onDone
+func v23_syncbase_internal_onDone(handle C.v23_syncbase_Handle, error C.v23_syncbase_VError) {
+	id := uint64(uintptr(handle))
+	h := globalRefMap.Get(id).(*scanCallbacksHandle)
+	env, free := getEnv()
+	obj := error.extractToJava(env)
+	arg := *(*C.jvalue)(unsafe.Pointer(&obj))
+	C.CallVoidMethodA(env, C.jobject(unsafe.Pointer(h.obj)), h.callbacks.onDone, &arg)
+	if C.ExceptionOccurred(env) != nil {
+		C.ExceptionDescribe(env)
+		panic("java exception")
+	}
+	C.DeleteGlobalRef(env, unsafe.Pointer(h.obj))
+	free()
+	globalRefMap.Remove(id)
+}
+
+type scanCallbacksHandle struct {
+	obj       uintptr
+	callbacks jScanCallbacks
+}
+
+//export Java_io_v_syncbase_internal_Collection_Scan
 func Java_io_v_syncbase_internal_Collection_Scan(env *C.JNIEnv, cls C.jclass, name C.jstring, handle C.jstring, start C.jbyteArray, limit C.jbyteArray, callbacks C.jobject) {
+	cName := newVStringFromJava(env, name)
+	cHandle := newVStringFromJava(env, handle)
+	cStart := newVBytesFromJava(env, start)
+	cLimit := newVBytesFromJava(env, limit)
+	cbs := C.newVScanCallbacks()
+	cbs.handle = C.v23_syncbase_Handle(uintptr(globalRefMap.Add(&scanCallbacksHandle{
+		obj:       uintptr(unsafe.Pointer(C.NewGlobalRef(env, callbacks))),
+		callbacks: newJScanCallbacks(env, callbacks),
+	})))
+	var cErr C.v23_syncbase_VError
+	v23_syncbase_CollectionScan(cName, cHandle, cStart, cLimit, cbs, &cErr)
+	maybeThrowException(env, &cErr)
 }
 
 //export Java_io_v_syncbase_internal_Row_Exists
@@ -547,6 +676,13 @@
 	return obj
 }
 
+func (x *C.v23_syncbase_KeyValue) extractToJava(env *C.JNIEnv) C.jobject {
+	obj := C.NewObjectA(env, keyValueClass.class, keyValueClass.init, nil)
+	C.SetObjectField(env, obj, keyValueClass.key, x.key.extractToJava(env))
+	C.SetObjectField(env, obj, keyValueClass.value, x.value.extractToJava(env))
+	return obj
+}
+
 func (x *C.v23_syncbase_Permissions) extractToJava(env *C.JNIEnv) C.jobject {
 	obj := C.NewObjectA(env, permissionsClass.class, permissionsClass.init, nil)
 	C.SetObjectField(env, obj, permissionsClass.json, x.json.extractToJava(env))
@@ -651,3 +787,52 @@
 	x.free()
 	return obj
 }
+
+// newVCollectionRowPatternsFromJava creates a
+// v23_syncbase_CollectionRowPatterns from a List<CollectionRowPattern>.
+func newVCollectionRowPatternsFromJava(env *C.JNIEnv, obj C.jobject) C.v23_syncbase_CollectionRowPatterns {
+	if obj == nil {
+		return C.v23_syncbase_CollectionRowPatterns{}
+	}
+	listInterface := newJListInterface(env, obj)
+	iterObj := C.CallObjectMethod(env, obj, listInterface.iterator)
+	if C.ExceptionOccurred(env) != nil {
+		panic("newVCollectionRowPatternsFromJava exception while trying to call List.iterator()")
+	}
+
+	iteratorInterface := newJIteratorInterface(env, iterObj)
+	tmp := []C.v23_syncbase_CollectionRowPattern{}
+	for C.CallBooleanMethodA(env, iterObj, iteratorInterface.hasNext, nil) == C.JNI_TRUE {
+		idObj := C.CallObjectMethod(env, iterObj, iteratorInterface.next)
+		if C.ExceptionOccurred(env) != nil {
+			panic("newVCollectionRowPatternsFromJava exception while trying to call Iterator.next()")
+		}
+		tmp = append(tmp, newVCollectionRowPatternFromJava(env, idObj))
+	}
+
+	size := C.size_t(len(tmp)) * C.size_t(C.sizeof_v23_syncbase_CollectionRowPattern)
+	r := C.v23_syncbase_CollectionRowPatterns{
+		p: (*C.v23_syncbase_CollectionRowPattern)(unsafe.Pointer(C.malloc(size))),
+		n: C.int(len(tmp)),
+	}
+	for i := range tmp {
+		*r.at(i) = tmp[i]
+	}
+	return r
+}
+
+func jFindClass(env *C.JNIEnv, name string) (C.jclass, error) {
+	cName := C.CString(name)
+	defer C.free(unsafe.Pointer(cName))
+
+	class := C.FindClass(env, cName)
+	if C.ExceptionOccurred(env) != nil {
+		return nil, fmt.Errorf("couldn't find class %s", name)
+	}
+
+	globalRef := C.jclass(C.NewGlobalRef(env, class))
+	if globalRef == nil {
+		return nil, fmt.Errorf("couldn't allocate a global reference for class %s", name)
+	}
+	return globalRef, nil
+}
diff --git a/services/syncbase/bridge/cgo/jni_lib.go b/services/syncbase/bridge/cgo/jni_lib.go
index 8eef51a..a43e6b6 100644
--- a/services/syncbase/bridge/cgo/jni_lib.go
+++ b/services/syncbase/bridge/cgo/jni_lib.go
@@ -27,6 +27,25 @@
 	}
 }
 
+type jCollectionRowPattern struct {
+	class              C.jclass
+	init               C.jmethodID
+	collectionBlessing C.jfieldID
+	collectionName     C.jfieldID
+	rowKey             C.jfieldID
+}
+
+func newJCollectionRowPattern(env *C.JNIEnv) jCollectionRowPattern {
+	cls, init := initClass(env, "io/v/syncbase/internal/Database$CollectionRowPattern")
+	return jCollectionRowPattern{
+		class:              cls,
+		init:               init,
+		collectionBlessing: jGetFieldID(env, cls, "collectionBlessing", "Ljava/lang/String;"),
+		collectionName:     jGetFieldID(env, cls, "collectionName", "Ljava/lang/String;"),
+		rowKey:             jGetFieldID(env, cls, "rowKey", "Ljava/lang/String;"),
+	}
+}
+
 type jHashMap struct {
 	class C.jclass
 	init  C.jmethodID
@@ -42,19 +61,6 @@
 	}
 }
 
-type jIteratorInterface struct {
-	hasNext C.jmethodID
-	next    C.jmethodID
-}
-
-func newJIteratorInterface(env *C.JNIEnv, obj C.jobject) jIteratorInterface {
-	cls := C.GetObjectClass(env, obj)
-	return jIteratorInterface{
-		hasNext: jGetMethodID(env, cls, "hasNext", "()Z"),
-		next:    jGetMethodID(env, cls, "next", "()Ljava/lang/Object;"),
-	}
-}
-
 type jIdClass struct {
 	class    C.jclass
 	init     C.jmethodID
@@ -72,6 +78,37 @@
 	}
 }
 
+type jIteratorInterface struct {
+	hasNext C.jmethodID
+	next    C.jmethodID
+}
+
+func newJIteratorInterface(env *C.JNIEnv, obj C.jobject) jIteratorInterface {
+	cls := C.GetObjectClass(env, obj)
+	return jIteratorInterface{
+		hasNext: jGetMethodID(env, cls, "hasNext", "()Z"),
+		next:    jGetMethodID(env, cls, "next", "()Ljava/lang/Object;"),
+	}
+}
+
+type jKeyValue struct {
+	class C.jclass
+	init  C.jmethodID
+	key   C.jfieldID
+	value C.jfieldID
+}
+
+func newJKeyValue(env *C.JNIEnv) jKeyValue {
+	cls, init := initClass(env, "io/v/syncbase/internal/Collection$KeyValue")
+	return jKeyValue{
+		class: cls,
+		init:  init,
+		key:   jGetFieldID(env, cls, "key", "Ljava/lang/String;"),
+		value: jGetFieldID(env, cls, "value", "[B"),
+	}
+
+}
+
 type jListInterface struct {
 	iterator C.jmethodID
 	size     C.jmethodID
@@ -85,6 +122,34 @@
 	}
 }
 
+type jPermissions struct {
+	class C.jclass
+	init  C.jmethodID
+	json  C.jfieldID
+}
+
+func newJPermissions(env *C.JNIEnv) jPermissions {
+	cls, init := initClass(env, "io/v/syncbase/internal/Permissions")
+	return jPermissions{
+		class: cls,
+		init:  init,
+		json:  jGetFieldID(env, cls, "json", "[B"),
+	}
+}
+
+type jScanCallbacks struct {
+	onKeyValue C.jmethodID
+	onDone     C.jmethodID
+}
+
+func newJScanCallbacks(env *C.JNIEnv, obj C.jobject) jScanCallbacks {
+	cls := C.GetObjectClass(env, obj)
+	return jScanCallbacks{
+		onKeyValue: jGetMethodID(env, cls, "onKeyValue", "(Lio/v/syncbase/internal/Collection$KeyValue;)V"),
+		onDone:     jGetMethodID(env, cls, "onDone", "(Lio/v/syncbase/internal/VError;)V"),
+	}
+}
+
 type jSyncgroupMemberInfo struct {
 	class        C.jclass
 	init         C.jmethodID
@@ -148,6 +213,23 @@
 	}
 }
 
+type jVersionedPermissions struct {
+	class       C.jclass
+	init        C.jmethodID
+	version     C.jfieldID
+	permissions C.jfieldID
+}
+
+func newJVersionedPermissions(env *C.JNIEnv) jVersionedPermissions {
+	cls, init := initClass(env, "io/v/syncbase/internal/VersionedPermissions")
+	return jVersionedPermissions{
+		class:       cls,
+		init:        init,
+		version:     jGetFieldID(env, cls, "version", "Ljava/lang/String;"),
+		permissions: jGetFieldID(env, cls, "permissions", "Lio/v/syncbase/internal/Permissions;"),
+	}
+}
+
 type jVersionedSyncgroupSpec struct {
 	class         C.jclass
 	init          C.jmethodID
@@ -165,35 +247,43 @@
 	}
 }
 
-type jPermissions struct {
-	class C.jclass
-	init  C.jmethodID
-	json  C.jfieldID
+type jWatchChange struct {
+	class        C.jclass
+	init         C.jmethodID
+	collection   C.jfieldID
+	row          C.jfieldID
+	changeType   C.jfieldID
+	value        C.jfieldID
+	resumeMarker C.jfieldID
+	fromSync     C.jfieldID
+	continued    C.jfieldID
 }
 
-func newJPermissions(env *C.JNIEnv) jPermissions {
-	cls, init := initClass(env, "io/v/syncbase/internal/Permissions")
-	return jPermissions{
-		class: cls,
-		init:  init,
-		json:  jGetFieldID(env, cls, "json", "[B"),
+func newJWatchChange(env *C.JNIEnv) jWatchChange {
+	cls, init := initClass(env, "io/v/syncbase/internal/Database$WatchChange")
+	return jWatchChange{
+		class:        cls,
+		init:         init,
+		collection:   jGetFieldID(env, cls, "collection", "Lio/v/syncbase/internal/Id;"),
+		row:          jGetFieldID(env, cls, "row", "Ljava/lang/String;"),
+		changeType:   jGetFieldID(env, cls, "changeType", "Lio/v/syncbase/internal/Database$ChangeType;"),
+		value:        jGetFieldID(env, cls, "value", "[B"),
+		resumeMarker: jGetFieldID(env, cls, "resumeMarker", "Ljava/lang/String;"),
+		fromSync:     jGetFieldID(env, cls, "fromSync", "Z"),
+		continued:    jGetFieldID(env, cls, "continued", "Z"),
 	}
 }
 
-type jVersionedPermissions struct {
-	class       C.jclass
-	init        C.jmethodID
-	version     C.jfieldID
-	permissions C.jfieldID
+type jWatchPatternsCallbacks struct {
+	onChange C.jmethodID
+	onError  C.jmethodID
 }
 
-func newJVersionedPermissions(env *C.JNIEnv) jVersionedPermissions {
-	cls, init := initClass(env, "io/v/syncbase/internal/VersionedPermissions")
-	return jVersionedPermissions{
-		class:       cls,
-		init:        init,
-		version:     jGetFieldID(env, cls, "version", "Ljava/lang/String;"),
-		permissions: jGetFieldID(env, cls, "permissions", "Lio/v/syncbase/internal/Permissions;"),
+func newJWatchPatternsCallbacks(env *C.JNIEnv, obj C.jobject) jWatchPatternsCallbacks {
+	cls := C.GetObjectClass(env, obj)
+	return jWatchPatternsCallbacks{
+		onChange: jGetMethodID(env, cls, "onChange", "(Lio/v/syncbase/internal/Database$WatchChange;)V"),
+		onError:  jGetMethodID(env, cls, "onError", "(Lio/v/syncbase/internal/VError;)V"),
 	}
 }
 
diff --git a/services/syncbase/bridge/cgo/jni_types.go b/services/syncbase/bridge/cgo/jni_types.go
index 9c48df6..f605b98 100644
--- a/services/syncbase/bridge/cgo/jni_types.go
+++ b/services/syncbase/bridge/cgo/jni_types.go
@@ -262,3 +262,28 @@
 	}
 	return cPerms, newVStringFromJava(env, version)
 }
+
+// newVCollectionRowPatternFromJava creates a v23_syncbase_CollectionRowPattern
+// from a CollectionRowPattern object.
+func newVCollectionRowPatternFromJava(env *C.JNIEnv, obj C.jobject) C.v23_syncbase_CollectionRowPattern {
+	collectionBlessing := C.jstring(C.GetObjectField(env, obj, collectionRowPatternClass.collectionBlessing))
+	if C.ExceptionOccurred(env) != nil {
+		panic("newVCollectionRowPatternFromJava exception while retrieving CollectionRowPattern.collectionBlessing")
+	}
+
+	collectionName := C.jstring(C.GetObjectField(env, obj, collectionRowPatternClass.collectionName))
+	if C.ExceptionOccurred(env) != nil {
+		panic("newVCollectionRowPatternFromJava exception while retrieving CollectionRowPattern.collectionName")
+	}
+
+	rowKey := C.jstring(C.GetObjectField(env, obj, collectionRowPatternClass.rowKey))
+	if C.ExceptionOccurred(env) != nil {
+		panic("newVCollectionRowPatternFromJava exception while retrieving CollectionRowPattern.rowKey")
+	}
+
+	return C.v23_syncbase_CollectionRowPattern{
+		collectionBlessing: newVStringFromJava(env, collectionBlessing),
+		collectionName:     newVStringFromJava(env, collectionName),
+		rowKey:             newVStringFromJava(env, rowKey),
+	}
+}
diff --git a/services/syncbase/bridge/cgo/jni_util.go b/services/syncbase/bridge/cgo/jni_util.go
index 7385467..1baf81d 100644
--- a/services/syncbase/bridge/cgo/jni_util.go
+++ b/services/syncbase/bridge/cgo/jni_util.go
@@ -9,6 +9,7 @@
 
 import (
 	"fmt"
+	"runtime"
 	"unsafe"
 )
 
@@ -17,22 +18,6 @@
 // #include "lib.h"
 import "C"
 
-func jFindClass(env *C.JNIEnv, name string) (C.jclass, error) {
-	cName := C.CString(name)
-	defer C.free(unsafe.Pointer(cName))
-
-	class := C.FindClass(env, cName)
-	if C.ExceptionOccurred(env) != nil {
-		return nil, fmt.Errorf("couldn't find class %s", name)
-	}
-
-	globalRef := C.jclass(C.NewGlobalRef(env, class))
-	if globalRef == nil {
-		return nil, fmt.Errorf("couldn't allocate a global reference for class %s", name)
-	}
-	return globalRef, nil
-}
-
 func jGetMethodID(env *C.JNIEnv, cls C.jclass, name, sig string) C.jmethodID {
 	cName := C.CString(name)
 	defer C.free(unsafe.Pointer(cName))
@@ -59,10 +44,56 @@
 
 	field := C.GetFieldID(env, cls, cName, cSig)
 	if field == nil {
-		panic(fmt.Sprintf("couldn't get field %q with signature ", name, sig))
+		panic(fmt.Sprintf("couldn't get field %q with signature %s", name, sig))
 	}
 
 	// Note: the validity of the field is bounded by the lifetime of the
 	// ClassLoader that did the loading of the class.
 	return field
 }
+
+// The function from below was hoisted from jni/util/util.go and adapted to not
+// use custom types.
+
+// GetEnv returns the Java environment for the running thread, creating a new
+// one if it doesn't already exist.  This method also returns a function which
+// must be invoked when the returned environment is no longer needed. The
+// returned environment can only be used by the thread that invoked this method,
+// and the function must be invoked by the same thread as well.
+func getEnv() (*C.JNIEnv, func()) {
+	// Lock the goroutine to the current OS thread.  This is necessary as
+	// *C.JNIEnv must not be shared across threads.  The scenario that can
+	// break this requirement is:
+	//   - goroutine A executing on thread X, obtains a *C.JNIEnv pointer P.
+	//   - goroutine A gets re-scheduled on thread Y, maintaining the P.
+	//   - goroutine B starts executing on thread X, obtaining pointer P.
+	//
+	// By locking the goroutines to their OS thread while they hold the
+	// pointer to *C.JNIEnv, the above scenario can never occur.
+	runtime.LockOSThread()
+	var env *C.JNIEnv
+	if C.GetEnv(jVM, &env, C.JNI_VERSION_1_6) != C.JNI_OK {
+		// Couldn't get env; attach the thread.  Note that we never
+		// detach the thread, so the next call to GetEnv on this thread
+		// will succeed. We also don't have to worry about calling
+		// DetachCurrentThread before the thread exits.
+		C.AttachCurrentThreadAsDaemon(jVM, &env, nil)
+	}
+	// GetEnv is called by Go code that wishes to call Java methods. In
+	// this case, JNI cannot automatically free unused local references.
+	// We must do it manually by pushing a new local reference frame. The
+	// frame will be popped in the env's cleanup function below, at which
+	// point JNI will free the unused references.
+	// http://developer.android.com/training/articles/perf-jni.html states
+	// that the JNI implementation is only required to provide a local
+	// reference table with a capacity of 16, so here we provide a table of
+	// that size.
+	localRefCapacity := 16
+	if newCapacity := C.PushLocalFrame(env, C.jint(localRefCapacity)); newCapacity < 0 {
+		panic("PushLocalFrame(" + string(localRefCapacity) + ") returned < 0 (was " + string(newCapacity) + ")")
+	}
+	return env, func() {
+		C.PopLocalFrame(env, nil)
+		runtime.UnlockOSThread()
+	}
+}
diff --git a/services/syncbase/bridge/cgo/jni_wrapper.c b/services/syncbase/bridge/cgo/jni_wrapper.c
index bb00584..c411dcc 100644
--- a/services/syncbase/bridge/cgo/jni_wrapper.c
+++ b/services/syncbase/bridge/cgo/jni_wrapper.c
@@ -6,6 +6,14 @@
 
 #include "jni_wrapper.h"
 
+jint AttachCurrentThread(JavaVM *jvm, JNIEnv **env, void *args) {
+  return (*jvm)->AttachCurrentThread(jvm, (void **)env, args);
+}
+
+jint AttachCurrentThreadAsDaemon(JavaVM *jvm, JNIEnv **env, void *args) {
+  return (*jvm)->AttachCurrentThreadAsDaemon(jvm, (void **)env, args);
+}
+
 jboolean CallBooleanMethodA(JNIEnv *env, jobject obj, jmethodID methodID, jvalue *args) {
   return (*env)->CallBooleanMethodA(env, obj, methodID, args);
 }
@@ -18,6 +26,18 @@
   return (*env)->CallObjectMethodA(env, obj, methodID, args);
 }
 
+void CallVoidMethod(JNIEnv *env, jobject obj, jmethodID methodID) {
+  (*env)->CallVoidMethod(env, obj, methodID);
+}
+
+void CallVoidMethodA(JNIEnv *env, jobject obj, jmethodID methodID, jvalue *args) {
+  (*env)->CallVoidMethodA(env, obj, methodID, args);
+}
+
+void DeleteGlobalRef(JNIEnv *env, jobject globalRef) {
+  (*env)->DeleteGlobalRef(env, globalRef);
+}
+
 void ExceptionClear(JNIEnv *env) {
   (*env)->ExceptionClear(env);
 }
@@ -116,4 +136,16 @@
 
 jint ThrowNew(JNIEnv *env, jclass cls, const char *message) {
   return (*env)->ThrowNew(env, cls, message);
+}
+
+jint PushLocalFrame(JNIEnv *env, jint capacity) {
+  return (*env)->PushLocalFrame(env, capacity);
+}
+
+jobject PopLocalFrame(JNIEnv *env, jobject result) {
+  return (*env)->PopLocalFrame(env, result);
+}
+
+void ExceptionDescribe(JNIEnv *env) {
+  (*env)->ExceptionDescribe(env);
 }
\ No newline at end of file
diff --git a/services/syncbase/bridge/cgo/jni_wrapper.h b/services/syncbase/bridge/cgo/jni_wrapper.h
index 36be6c8..a322045 100644
--- a/services/syncbase/bridge/cgo/jni_wrapper.h
+++ b/services/syncbase/bridge/cgo/jni_wrapper.h
@@ -10,10 +10,15 @@
 
 #include "jni.h"
 
+jint AttachCurrentThread(JavaVM *jvm, JNIEnv **env, void *args);
+jint AttachCurrentThreadAsDaemon(JavaVM* jvm, JNIEnv** env, void* args);
 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 CallVoidMethod(JNIEnv *env, jobject obj, jmethodID methodID);
+void CallVoidMethodA(JNIEnv *env, jobject obj, jmethodID methodID, jvalue *args);
+void DeleteGlobalRef(JNIEnv *env, jobject globalRef);
 void ExceptionClear(JNIEnv *env);
 jthrowable ExceptionOccurred(JNIEnv* env);
 jclass FindClass(JNIEnv* env, const char* name);
@@ -38,4 +43,8 @@
 void SetLongField(JNIEnv *env, jobject obj, jfieldID fieldID, jlong value);
 void SetObjectField(JNIEnv *env, jobject obj, jfieldID fieldID, jobject value);
 jint Throw(JNIEnv *env, jthrowable obj);
-jint ThrowNew(JNIEnv *env, jclass cls, const char *message);
\ No newline at end of file
+jint ThrowNew(JNIEnv *env, jclass cls, const char *message);
+
+jint PushLocalFrame(JNIEnv *env, jint capacity);
+jobject PopLocalFrame(JNIEnv *env, jobject result);
+void ExceptionDescribe(JNIEnv *env);
\ No newline at end of file
diff --git a/services/syncbase/bridge/cgo/refmap/refmap.go b/services/syncbase/bridge/cgo/refmap/refmap.go
new file mode 100644
index 0000000..a3ab087
--- /dev/null
+++ b/services/syncbase/bridge/cgo/refmap/refmap.go
@@ -0,0 +1,49 @@
+// 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 refmap
+
+import (
+	"sync"
+)
+
+func NewRefMap() *refMap {
+	return &refMap{
+		refs: make(map[uint64]interface{}),
+	}
+}
+
+type refMap struct {
+	refs   map[uint64]interface{}
+	lastId uint64
+	lock   sync.Mutex
+}
+
+func (r *refMap) Add(val interface{}) uint64 {
+	r.lock.Lock()
+	defer r.lock.Unlock()
+	id := r.lastId
+	r.lastId++
+	r.refs[id] = val
+	return id
+}
+
+func (r *refMap) Get(id uint64) interface{} {
+	r.lock.Lock()
+	defer r.lock.Unlock()
+	if val, ok := r.refs[id]; ok {
+		return val
+	}
+	return nil
+}
+
+func (r *refMap) Remove(id uint64) interface{} {
+	r.lock.Lock()
+	defer r.lock.Unlock()
+	if val, ok := r.refs[id]; ok {
+		delete(r.refs, id)
+		return val
+	}
+	return nil
+}