java: Hook and test the Service.ListDatabases and Database.Create

The listing of databases doesn't work properly so the test doesn't
attempt to list the databases after creating a database.

This change also adds the caching of appropriate class/method/field
IDs for a few classes (ArrayListClass, Id and VError). The code doing
that is isolated in the jni_lib.go file.

This change also adds a bunch of utility functions to talk to JNI and
to convert from v23_syncbase_* to Java equivalents. The code is
isolated in the jni_util.go file.

Note on V23SStringToJString: I initially wanted to write a Go unit
test but manufacturing a C.JNIEnv or bringing up an entire JVM are
both untractive options.

MultiPart: 2/2
Change-Id: I2c36eddb8015211dd67211a7dbbd700b2294aa49
diff --git a/services/syncbase/bridge/cgo/jni.go b/services/syncbase/bridge/cgo/jni.go
index 8e6dd82..c55c593 100644
--- a/services/syncbase/bridge/cgo/jni.go
+++ b/services/syncbase/bridge/cgo/jni.go
@@ -8,14 +8,21 @@
 package main
 
 import (
+	"fmt"
+	"os"
 	"unsafe"
 )
 
+// #include <stdlib.h>
 // #include "jni_wrapper.h"
+// #include "lib.h"
 import "C"
 
 var (
-	jVM *C.JavaVM
+	jVM            *C.JavaVM
+	arrayListClass jArrayListClass
+	idClass        jIdClass
+	verrorClass    jVErrorClass
 )
 
 // JNI_OnLoad is called when System.loadLibrary is called. We need to cache the
@@ -31,7 +38,26 @@
 		return C.JNI_ERR
 	}
 	jVM = vm
+
 	v23_syncbase_Init()
+
+	// We don't bother throwing errors in here because attempting to create
+	// an exception can also fail.
+	if arrayListClass.Init(env) != nil {
+		fmt.Fprintln(os.Stderr, "Error caching the ArrayList class")
+		return C.JNI_ERR
+	}
+
+	if idClass.Init(env) != nil {
+		fmt.Fprintln(os.Stderr, "Error caching the ID class")
+		return C.JNI_ERR
+	}
+
+	if verrorClass.Init(env) != nil {
+		fmt.Fprintln(os.Stderr, "Error caching the VError class")
+		return C.JNI_ERR
+	}
+
 	return C.JNI_VERSION_1_6
 }
 
@@ -39,8 +65,15 @@
 	return nil
 }
 func Java_io_v_syncbase_internal_Service_SetPermissions(env *C.JNIEnv, cls C.jclass, obj C.jobject) {}
+
+//export Java_io_v_syncbase_internal_Service_ListDatabases
 func Java_io_v_syncbase_internal_Service_ListDatabases(env *C.JNIEnv, cls C.jclass) C.jobject {
-	return nil
+	var cIds C.v23_syncbase_Ids
+	var cErr C.v23_syncbase_VError
+	v23_syncbase_ServiceListDatabases(&cIds, &cErr)
+	obj := C.NewObjectA(env, arrayListClass.class, arrayListClass.init, nil)
+	// TODO(razvanm): populate the obj based on the data from cIds.
+	return obj
 }
 
 func Java_io_v_syncbase_internal_Database_GetPermissions(env *C.JNIEnv, cls C.jclass, name C.jstring) C.jobject {
@@ -48,7 +81,35 @@
 }
 func Java_io_v_syncbase_internal_Database_SetPermissions(env *C.JNIEnv, cls C.jclass, name C.jstring, perms C.jobject) {
 }
+
+func throwException(env *C.JNIEnv, cErr *C.v23_syncbase_VError) {
+	obj := C.NewObjectA(env, verrorClass.class, verrorClass.init, nil)
+	if s, err := V23SStringToJString(env, cErr.id); err == nil {
+		C.SetObjectField(env, obj, verrorClass.id, s)
+	}
+	C.SetLongField(env, obj, verrorClass.actionCode, C.jlong(cErr.actionCode))
+	if s, err := V23SStringToJString(env, cErr.msg); err == nil {
+		C.SetObjectField(env, obj, verrorClass.message, s)
+	}
+	if s, err := V23SStringToJString(env, cErr.stack); err == nil {
+		C.SetObjectField(env, obj, verrorClass.stack, s)
+	}
+	C.Throw(env, obj)
+}
+
+//export Java_io_v_syncbase_internal_Database_Create
 func Java_io_v_syncbase_internal_Database_Create(env *C.JNIEnv, cls C.jclass, name C.jstring, perms C.jobject) {
+	cName, err := JStringToV23SString(env, name)
+	if err != nil {
+		return
+	}
+	var cErr C.v23_syncbase_VError
+	// TODO(razvanm): construct a proper C.v23_syncbase_Permissions based on
+	// the perms object.
+	v23_syncbase_DbCreate(cName, C.v23_syncbase_Permissions{}, &cErr)
+	if cErr.id.p != nil {
+		throwException(env, &cErr)
+	}
 }
 func Java_io_v_syncbase_internal_Database_Destroy(env *C.JNIEnv, cls C.jclass, name C.jstring) {}
 func Java_io_v_syncbase_internal_Database_Exists(env *C.JNIEnv, cls C.jclass, name C.jstring) C.jboolean {
diff --git a/services/syncbase/bridge/cgo/jni_lib.go b/services/syncbase/bridge/cgo/jni_lib.go
new file mode 100644
index 0000000..1c4e4c2
--- /dev/null
+++ b/services/syncbase/bridge/cgo/jni_lib.go
@@ -0,0 +1,89 @@
+// 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.
+
+// +build java android
+// +build cgo
+
+package main
+
+// #include <stdlib.h>
+// #include "jni_wrapper.h"
+// #include "lib.h"
+import "C"
+
+type jArrayListClass struct {
+	class C.jclass
+	init  C.jmethodID
+}
+
+func (c *jArrayListClass) Init(env *C.JNIEnv) error {
+	var err error
+	c.class, c.init, err = initClass(env, "java/util/ArrayList")
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+type jIdClass struct {
+	class C.jclass
+	init  C.jmethodID
+}
+
+func (c *jIdClass) Init(env *C.JNIEnv) error {
+	var err error
+	c.class, c.init, err = initClass(env, "io/v/syncbase/internal/Id")
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+type jVErrorClass struct {
+	class      C.jclass
+	init       C.jmethodID
+	id         C.jfieldID
+	actionCode C.jfieldID
+	message    C.jfieldID
+	stack      C.jfieldID
+}
+
+func (c *jVErrorClass) Init(env *C.JNIEnv) error {
+	var err error
+	c.class, c.init, err = initClass(env, "io/v/syncbase/internal/VError")
+	if err != nil {
+		return err
+	}
+	c.id, err = JGetFieldID(env, c.class, "id", "Ljava/lang/String;")
+	if err != nil {
+		return err
+	}
+	c.actionCode, err = JGetFieldID(env, c.class, "actionCode", "J")
+	if err != nil {
+		return err
+	}
+	c.message, err = JGetFieldID(env, c.class, "message", "Ljava/lang/String;")
+	if err != nil {
+		return err
+	}
+	c.stack, err = JGetFieldID(env, c.class, "stack", "Ljava/lang/String;")
+	if err != nil {
+		return err
+	}
+	return nil
+}
+
+// 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, error) {
+	cls, err := JFindClass(env, name)
+	if err != nil {
+		return nil, nil, err
+	}
+	init, err := JGetMethodID(env, cls, "<init>", "()V")
+	if err != nil {
+		return nil, nil, err
+	}
+	return cls, init, nil
+}
diff --git a/services/syncbase/bridge/cgo/jni_util.go b/services/syncbase/bridge/cgo/jni_util.go
new file mode 100644
index 0000000..8d9c68a
--- /dev/null
+++ b/services/syncbase/bridge/cgo/jni_util.go
@@ -0,0 +1,133 @@
+// 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.
+
+// +build java android
+// +build cgo
+
+package main
+
+import (
+	"fmt"
+	"unsafe"
+)
+
+// #include <stdlib.h>
+// #include "jni_wrapper.h"
+// #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, error) {
+	cName := C.CString(name)
+	defer C.free(unsafe.Pointer(cName))
+
+	cSig := C.CString(sig)
+	defer C.free(unsafe.Pointer(cSig))
+
+	method := C.GetMethodID(env, cls, cName, cSig)
+	if method == nil {
+		return nil, fmt.Errorf("couldn't get method %q with signature %s", name, sig)
+	}
+
+	// Note: the validity of the method is bounded by the lifetime of the
+	// ClassLoader that did the loading of the class.
+	return method, nil
+}
+
+func JGetFieldID(env *C.JNIEnv, cls C.jclass, name, sig string) (C.jfieldID, error) {
+	cName := C.CString(name)
+	defer C.free(unsafe.Pointer(cName))
+
+	cSig := C.CString(sig)
+	defer C.free(unsafe.Pointer(cSig))
+
+	field := C.GetFieldID(env, cls, cName, cSig)
+	if field == nil {
+		return nil, fmt.Errorf("couldn't get field %q with signature ", 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, nil
+}
+
+// V23SStringToJString constructs a C.jstring from a C.v23_syncbase_String. The
+// code is somewhat complicated and inefficient because the NewStringUTF from
+// JNI only works with modified UTF-8 strings (inner nulls are encoded as 0xC0,
+// 0x80 and the string is terminated with a null).
+func V23SStringToJString(env *C.JNIEnv, src C.v23_syncbase_String) (C.jstring, error) {
+	n := int(src.n)
+	srcPtr := uintptr(unsafe.Pointer(src.p))
+	numNulls := 0
+	for i := 0; i < n; i++ {
+		if *(*byte)(unsafe.Pointer(srcPtr + uintptr(i))) == 0 {
+			numNulls++
+		}
+	}
+	tmp := C.malloc(C.size_t(n + numNulls + 1))
+	defer C.free(tmp)
+	tmpPtr := uintptr(tmp)
+	j := 0
+	for i := 0; i < n; i, j = i+1, j+1 {
+		if *(*byte)(unsafe.Pointer(srcPtr + uintptr(i))) != 0 {
+			*(*byte)(unsafe.Pointer(tmpPtr + uintptr(j))) = *(*byte)(unsafe.Pointer(srcPtr + uintptr(i)))
+			continue
+		}
+		*(*byte)(unsafe.Pointer(tmpPtr + uintptr(j))) = 0xC0
+		j++
+		*(*byte)(unsafe.Pointer(tmpPtr + uintptr(j))) = 0x80
+	}
+	*(*byte)(unsafe.Pointer(tmpPtr + uintptr(j))) = 0
+	r := C.NewStringUTF(env, (*C.char)(tmp))
+	if C.ExceptionOccurred(env) != nil {
+		panic("NewStringUTF OutOfMemoryError exception")
+	}
+	return r, nil
+}
+
+// JStringToV23SString creates a v23_syncbase_String from a jstring. This is the
+// revert of the V23SStringToJString.
+func JStringToV23SString(env *C.JNIEnv, s C.jstring) (C.v23_syncbase_String, error) {
+	r := C.v23_syncbase_String{}
+	r.n = C.int(C.GetStringUTFLength(env, s))
+	r.p = (*C.char)(C.malloc(C.size_t(r.n)))
+	p := uintptr(unsafe.Pointer(r.p))
+	// Note that GetStringUTFLength does not include a trailing zero.
+	n := int(C.GetStringUTFLength(env, s))
+	C.GetStringUTFRegion(env, s, 0, C.GetStringLength(env, s), r.p)
+	// We don't have to check for exceptions because GetStringUTFRegion can
+	// only throw StringIndexOutOfBoundsException and we know the requested
+	// amount of characters is valid.
+	j := 0
+	for i := 0; i < n; i, j = i+1, j+1 {
+		if i+1 < n && *(*byte)(unsafe.Pointer(p + uintptr(i))) == 0xC0 && *(*byte)(unsafe.Pointer(p + uintptr(i+1))) == 0x80 {
+			*(*byte)(unsafe.Pointer(p + uintptr(j))) = 0
+			i++
+			continue
+		}
+
+		if j == i {
+			continue
+		}
+
+		*(*byte)(unsafe.Pointer(p + uintptr(j))) = *(*byte)(unsafe.Pointer(p + uintptr(i)))
+	}
+	r.p = (*C.char)(C.realloc(unsafe.Pointer(r.p), (C.size_t)(j)))
+	return r, nil
+}
diff --git a/services/syncbase/bridge/cgo/jni_wrapper.c b/services/syncbase/bridge/cgo/jni_wrapper.c
index 65d0bfd..d3cb147 100644
--- a/services/syncbase/bridge/cgo/jni_wrapper.c
+++ b/services/syncbase/bridge/cgo/jni_wrapper.c
@@ -6,6 +6,66 @@
 
 #include "jni_wrapper.h"
 
+void ExceptionClear(JNIEnv *env) {
+  (*env)->ExceptionClear(env);
+}
+
+jthrowable ExceptionOccurred(JNIEnv* env) {
+  return (*env)->ExceptionOccurred(env);
+}
+
+jclass FindClass(JNIEnv* env, const char* name) {
+  return (*env)->FindClass(env, name);
+}
+
 jint GetEnv(JavaVM* jvm, JNIEnv** env, jint version) {
-    return (*jvm)->GetEnv(jvm, (void**)env, version);
+  return (*jvm)->GetEnv(jvm, (void**)env, version);
+}
+
+jfieldID GetFieldID(JNIEnv *env, jclass cls, const char *name, const char *sig) {
+  return (*env)->GetFieldID(env, cls, name, sig);
+}
+
+jmethodID GetMethodID(JNIEnv* env, jclass cls, const char* name, const char* args) {
+  return (*env)->GetMethodID(env, cls, name, args);
+}
+
+jsize GetStringLength(JNIEnv *env, jstring string) {
+  return (*env)->GetStringLength(env, string);
+}
+
+jsize GetStringUTFLength(JNIEnv *env, jstring string) {
+  return (*env)->GetStringLength(env, string);
+}
+
+void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize len, char *buf) {
+  (*env)->GetStringUTFRegion(env, str, start, len, buf);
+}
+
+jobject NewGlobalRef(JNIEnv* env, jobject obj) {
+  return (*env)->NewGlobalRef(env, obj);
+}
+
+jobject NewObjectA(JNIEnv *env, jclass cls, jmethodID methodID, jvalue *args) {
+  return (*env)->NewObjectA(env, cls, methodID, args);
+}
+
+void SetLongField(JNIEnv *env, jobject obj, jfieldID fieldID, jlong value) {
+  (*env)->SetLongField(env, obj, fieldID, value);
+}
+
+jstring NewStringUTF(JNIEnv *env, const char *bytes) {
+  return (*env)->NewStringUTF(env, bytes);
+}
+
+void SetObjectField(JNIEnv *env, jobject obj, jfieldID fieldID, jobject value) {
+  (*env)->SetObjectField(env, obj, fieldID, value);
+}
+
+jint Throw(JNIEnv *env, jthrowable obj) {
+  return (*env)->Throw(env, obj);
+}
+
+jint ThrowNew(JNIEnv *env, jclass cls, const char *message) {
+  return (*env)->ThrowNew(env, cls, message);
 }
\ 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 9300020..ab15784 100644
--- a/services/syncbase/bridge/cgo/jni_wrapper.h
+++ b/services/syncbase/bridge/cgo/jni_wrapper.h
@@ -4,6 +4,25 @@
 
 // +build java android cgo
 
+// All JNI functions are function pointers of a JNIEnv variable. Go language
+// cannot call function pointers so we need use some wrapper functions to do
+// that.
+
 #include "jni.h"
 
-jint GetEnv(JavaVM* jvm, JNIEnv** env, jint version);
\ No newline at end of file
+void ExceptionClear(JNIEnv *env);
+jthrowable ExceptionOccurred(JNIEnv* env);
+jclass FindClass(JNIEnv* env, const char* name);
+jint GetEnv(JavaVM* jvm, JNIEnv** env, jint version);
+jfieldID GetFieldID(JNIEnv *env, jclass cls, const char *name, const char *sig);
+jmethodID GetMethodID(JNIEnv* env, jclass cls, const char* name, const char* sig);
+jsize GetStringLength(JNIEnv *env, jstring string);
+jsize GetStringUTFLength(JNIEnv *env, jstring string);
+void GetStringUTFRegion(JNIEnv *env, jstring str, jsize start, jsize len, char *buf);
+jobject NewGlobalRef(JNIEnv* env, jobject obj);
+jobject NewObjectA(JNIEnv *env, jclass cls, jmethodID methodID, jvalue *args);
+jstring NewStringUTF(JNIEnv *env, const char *bytes);
+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