java: Add an initial implementation for testWatch and testRunInBatch

To get the tests to work this change also finish properly exposing the
v23_syncbase_WatchChange.

MultiPart: 1/2
Change-Id: I3ce4f4b11791a993f3ba0af1ba1a1ce541d887e8
diff --git a/services/syncbase/bridge/cgo/jni.go b/services/syncbase/bridge/cgo/jni.go
index eda5486..3b9dec8 100644
--- a/services/syncbase/bridge/cgo/jni.go
+++ b/services/syncbase/bridge/cgo/jni.go
@@ -49,6 +49,7 @@
 var (
 	jVM                         *C.JavaVM
 	arrayListClass              jArrayListClass
+	changeTypeClass             jChangeType
 	collectionRowPatternClass   jCollectionRowPattern
 	hashMapClass                jHashMap
 	idClass                     jIdClass
@@ -77,6 +78,7 @@
 	jVM = vm
 
 	arrayListClass = newJArrayListClass(env)
+	changeTypeClass = newJChangeType(env)
 	collectionRowPatternClass = newJCollectionRowPattern(env)
 	hashMapClass = newJHashMap(env)
 	idClass = newJIdClass(env)
@@ -380,20 +382,17 @@
 
 //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()
+	id := uint64(uintptr(handle))
+	h := globalRefMap.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
@@ -646,6 +645,17 @@
 // All the extractToJava methods return Java types and deallocate all the
 // pointers inside v23_syncbase_* variable.
 
+func (x *C.v23_syncbase_ChangeType) extractToJava(env *C.JNIEnv) C.jobject {
+	var obj C.jobject
+	switch *x {
+	case C.v23_syncbase_ChangeTypePut:
+		obj = C.GetStaticObjectField(env, changeTypeClass.class, changeTypeClass.put)
+	case C.v23_syncbase_ChangeTypeDelete:
+		obj = C.GetStaticObjectField(env, changeTypeClass.class, changeTypeClass.delete)
+	}
+	return obj
+}
+
 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))
@@ -809,6 +819,18 @@
 	return obj
 }
 
+func (x *C.v23_syncbase_WatchChange) extractToJava(env *C.JNIEnv) C.jobject {
+	obj := C.NewObjectA(env, watchChangeClass.class, watchChangeClass.init, nil)
+	C.SetObjectField(env, obj, watchChangeClass.collection, x.collection.extractToJava(env))
+	C.SetObjectField(env, obj, watchChangeClass.row, x.row.extractToJava(env))
+	C.SetObjectField(env, obj, watchChangeClass.changeType, x.changeType.extractToJava(env))
+	C.SetObjectField(env, obj, watchChangeClass.value, x.value.extractToJava(env))
+	C.SetObjectField(env, obj, watchChangeClass.resumeMarker, x.resumeMarker.extractToJava(env))
+	C.SetBooleanField(env, obj, watchChangeClass.fromSync, x.fromSync.extractToJava())
+	C.SetBooleanField(env, obj, watchChangeClass.continued, x.continued.extractToJava())
+	return obj
+}
+
 // newVCollectionRowPatternsFromJava creates a
 // v23_syncbase_CollectionRowPatterns from a List<CollectionRowPattern>.
 func newVCollectionRowPatternsFromJava(env *C.JNIEnv, obj C.jobject) C.v23_syncbase_CollectionRowPatterns {
diff --git a/services/syncbase/bridge/cgo/jni_lib.go b/services/syncbase/bridge/cgo/jni_lib.go
index 7a720c7..1169ef6 100644
--- a/services/syncbase/bridge/cgo/jni_lib.go
+++ b/services/syncbase/bridge/cgo/jni_lib.go
@@ -287,15 +287,34 @@
 	}
 }
 
+type jChangeType struct {
+	class  C.jclass
+	put    C.jfieldID
+	delete C.jfieldID
+}
+
+func newJChangeType(env *C.JNIEnv) jChangeType {
+	cls := findClass(env, "io/v/syncbase/core/WatchChange$ChangeType")
+	return jChangeType{
+		class:  cls,
+		put:    jGetStaticFieldID(env, cls, "PUT", "Lio/v/syncbase/core/WatchChange$ChangeType;"),
+		delete: jGetStaticFieldID(env, cls, "DELETE", "Lio/v/syncbase/core/WatchChange$ChangeType;"),
+	}
+}
+
 // 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) {
+	cls := findClass(env, name)
+	return cls, jGetMethodID(env, cls, "<init>", "()V")
+}
+
+func findClass(env *C.JNIEnv, name string) C.jclass {
 	cls, err := jFindClass(env, name)
 	if err != nil {
 		// The invariant is that we only deal with classes that must be
 		// known to the JVM. A panic indicates a bug in our code.
 		panic(err)
 	}
-	init := jGetMethodID(env, cls, "<init>", "()V")
-	return cls, init
+	return cls
 }
diff --git a/services/syncbase/bridge/cgo/jni_util.go b/services/syncbase/bridge/cgo/jni_util.go
index 1baf81d..e2fdc34 100644
--- a/services/syncbase/bridge/cgo/jni_util.go
+++ b/services/syncbase/bridge/cgo/jni_util.go
@@ -52,6 +52,23 @@
 	return field
 }
 
+func jGetStaticFieldID(env *C.JNIEnv, cls C.jclass, name, sig string) C.jfieldID {
+	cName := C.CString(name)
+	defer C.free(unsafe.Pointer(cName))
+
+	cSig := C.CString(sig)
+	defer C.free(unsafe.Pointer(cSig))
+
+	field := C.GetStaticFieldID(env, cls, cName, cSig)
+	if field == nil {
+		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.
 
diff --git a/services/syncbase/bridge/cgo/jni_wrapper.c b/services/syncbase/bridge/cgo/jni_wrapper.c
index 4e36f47..9fafc41 100644
--- a/services/syncbase/bridge/cgo/jni_wrapper.c
+++ b/services/syncbase/bridge/cgo/jni_wrapper.c
@@ -74,6 +74,15 @@
   return (*env)->GetObjectField(env, obj, fieldID);
 }
 
+jfieldID GetStaticFieldID(JNIEnv *env, jclass cls, const char *name, const char *sig) {
+  return (*env)->GetStaticFieldID(env, cls, name, sig);
+}
+
+jobject GetStaticObjectField(JNIEnv *env, jclass cls, jfieldID fieldID)
+{
+  return (*env)->GetStaticObjectField(env, cls, fieldID);
+}
+
 jsize GetStringLength(JNIEnv *env, jstring string) {
   return (*env)->GetStringLength(env, string);
 }
diff --git a/services/syncbase/bridge/cgo/jni_wrapper.h b/services/syncbase/bridge/cgo/jni_wrapper.h
index a322045..8955790 100644
--- a/services/syncbase/bridge/cgo/jni_wrapper.h
+++ b/services/syncbase/bridge/cgo/jni_wrapper.h
@@ -30,6 +30,8 @@
 jmethodID GetMethodID(JNIEnv* env, jclass cls, const char* name, const char* sig);
 jclass GetObjectClass(JNIEnv *env, jobject obj);
 jobject GetObjectField(JNIEnv *env, jobject obj, jfieldID fieldID);
+jfieldID GetStaticFieldID(JNIEnv *env, jclass cls, const char *name, const char *sig);
+jobject GetStaticObjectField(JNIEnv *env, jclass cls, jfieldID fieldID);
 jsize GetStringLength(JNIEnv *env, jstring string);
 jsize GetStringUTFLength(JNIEnv *env, jstring string);
 void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize len, char *buf);