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