| // Copyright 2015 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 |
| |
| package util |
| |
| import ( |
| "fmt" |
| "reflect" |
| "time" |
| "unsafe" |
| ) |
| |
| // #include <stdlib.h> |
| // #include "jni_wrapper.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" |
| |
| // jArg converts a Go argument to a Java argument. It uses the provided sign to |
| // validate that the argument is of a compatible type. |
| func jArg(env *C.JNIEnv, v interface{}, sign Sign) (unsafe.Pointer, bool) { |
| rv := reflect.ValueOf(v) |
| if !rv.IsValid() { // nil value |
| jv := C.jobject(nil) |
| return unsafe.Pointer(&jv), true |
| } |
| if rv.Type() == reflect.TypeOf(time.Time{}) { |
| if sign != DateTimeSign { |
| return unsafe.Pointer(nil), false |
| } |
| jv, err := JTime(env, rv.Interface().(time.Time)) |
| if err != nil { |
| return unsafe.Pointer(nil), false |
| } |
| return unsafe.Pointer(&jv), true |
| } |
| if rv.Type() == reflect.TypeOf(time.Duration(0)) { |
| if sign != DurationSign { |
| return unsafe.Pointer(nil), false |
| } |
| jv, err := JDuration(env, rv.Interface().(time.Duration)) |
| if err != nil { |
| return unsafe.Pointer(nil), false |
| } |
| return unsafe.Pointer(&jv), true |
| } |
| if rv.Type().Implements(reflect.TypeOf((*error)(nil)).Elem()) { |
| if sign != VExceptionSign { |
| return unsafe.Pointer(nil), false |
| } |
| jv, err := JVException(env, rv.Interface().(error)) |
| if err != nil { |
| return unsafe.Pointer(nil), false |
| } |
| return unsafe.Pointer(&jv), true |
| } |
| if rv.Kind() == reflect.Ptr || rv.Kind() == reflect.UnsafePointer { |
| rv = reflect.ValueOf(rv.Pointer()) // Convert the pointer's address to a uintptr |
| } |
| switch rv.Kind() { |
| case reflect.Bool: |
| if sign != BoolSign { |
| return unsafe.Pointer(nil), false |
| } |
| jv := C.jboolean(C.JNI_FALSE) |
| if rv.Bool() { |
| jv = C.JNI_TRUE |
| } |
| return unsafe.Pointer(&jv), true |
| case reflect.Int, reflect.Int32, reflect.Int16, reflect.Int8, reflect.Uint, reflect.Uint32, reflect.Uint16, reflect.Uint8: |
| if !isSignOneOf(sign, []Sign{ByteSign, ShortSign, IntSign, LongSign}) { |
| return unsafe.Pointer(nil), false |
| } |
| jv := C.jint(rv.Int()) |
| return unsafe.Pointer(&jv), true |
| case reflect.Int64: |
| if sign != LongSign { |
| return unsafe.Pointer(nil), false |
| } |
| jv := C.jlong(rv.Int()) |
| return unsafe.Pointer(&jv), true |
| case reflect.Uint64: |
| if sign != LongSign { |
| return unsafe.Pointer(nil), false |
| } |
| jv := C.jlong(rv.Uint()) |
| return unsafe.Pointer(&jv), true |
| case reflect.Uintptr: |
| if isSignOneOf(sign, []Sign{ByteSign, BoolSign, CharSign, ShortSign, IntSign, FloatSign, DoubleSign}) { |
| return unsafe.Pointer(nil), false |
| } |
| jv := C.jlong(rv.Uint()) |
| return unsafe.Pointer(&jv), true |
| case reflect.String: |
| if sign != StringSign { |
| return unsafe.Pointer(nil), false |
| } |
| // JString allocates the strings locally, so they are freed automatically when we return to Java. |
| jv := JString(env, rv.String()) |
| if jv == nil { |
| return unsafe.Pointer(nil), false |
| } |
| return unsafe.Pointer(&jv), true |
| case reflect.Slice, reflect.Array: |
| switch rv.Type().Elem().Kind() { |
| case reflect.Uint8: |
| if sign != ArraySign(ByteSign) { |
| return unsafe.Pointer(nil), false |
| } |
| bs := rv.Interface().([]byte) |
| jv := JByteArray(env, bs) |
| return unsafe.Pointer(&jv), true |
| case reflect.String: |
| if sign != ArraySign(StringSign) { |
| return unsafe.Pointer(nil), false |
| } |
| // TODO(bprosnitz) We should handle objects by calling jArg recursively. We need a way to get the sign of the target type or treat it as an Object for non-string types. |
| strs := rv.Interface().([]string) |
| jv := JStringArray(env, strs) |
| return unsafe.Pointer(&jv), true |
| default: |
| return unsafe.Pointer(nil), false |
| } |
| default: |
| return unsafe.Pointer(nil), false |
| } |
| } |
| |
| // jArgArray converts a slice of Go args to an array of Java args. It uses the provided slice of |
| // Signs to validate that the arguments are of compatible types. |
| func jArgArray(env *C.JNIEnv, args []interface{}, argSigns []Sign) (jArr *C.jvalue, free func(), err error) { |
| if len(argSigns) != len(args) { |
| return (*C.jvalue)(nil), nil, fmt.Errorf("mismatch in number of arguments, want %d, got %d", len(argSigns), len(args)) |
| } |
| jvalueArr := C.allocJValueArray(C.int(len(args))) |
| for i, arg := range args { |
| sign := argSigns[i] |
| jValPtr, ok := jArg(env, arg, sign) |
| if !ok { |
| return (*C.jvalue)(nil), nil, fmt.Errorf("couldn't get Java value for argument #%d [%v] of expected type %v", i, arg, sign) |
| } |
| C.setJValueArrayElement(jvalueArr, C.int(i), *(*C.jvalue)(jValPtr)) |
| } |
| freeFunc := func() { |
| C.free(unsafe.Pointer(jvalueArr)) |
| } |
| return jvalueArr, freeFunc, nil |
| } |
| |
| // NewObject calls a java constructor through JNI, passing the specified args. |
| func NewObject(env interface{}, class interface{}, argSigns []Sign, args ...interface{}) (unsafe.Pointer, error) { |
| if class == nil { |
| panic("cannot call constructor of nil class") |
| } |
| jenv := getEnv(env) |
| jclass := getClass(class) |
| |
| jcid, err := JMethodID(jenv, jclass, "<init>", FuncSign(argSigns, VoidSign)) |
| if err != nil { |
| return nil, err |
| } |
| |
| valArray, freeFunc, err := jArgArray(jenv, args, argSigns) |
| if err != nil { |
| return nil, err |
| } |
| defer freeFunc() |
| |
| ret := C.NewObjectA(jenv, jclass, C.jmethodID(jcid), valArray) |
| err = JExceptionMsg(env) |
| return unsafe.Pointer(ret), err |
| } |
| |
| // setupMethodCall performs the shared preparation operations between various |
| // Java method invocation functions. |
| func setupMethodCall(env interface{}, object interface{}, name string, argSigns []Sign, retSign Sign, args ...interface{}) (jenv *C.JNIEnv, jobject C.jobject, jmid C.jmethodID, jvalArray *C.jvalue, freeFunc func(), err error) { |
| jenv = getEnv(env) |
| jobject = getObject(object) |
| jclass := C.GetObjectClass(jenv, jobject) |
| |
| var id unsafe.Pointer |
| id, err = JMethodID(jenv, jclass, name, FuncSign(argSigns, retSign)) |
| if err != nil { |
| return |
| } |
| jmid = C.jmethodID(id) |
| jvalArray, freeFunc, err = jArgArray(jenv, args, argSigns) |
| return |
| } |
| |
| // setupStaticMethodCall performs the shared preparation operations between |
| // various Java static method invocation functions. |
| func setupStaticMethodCall(env interface{}, class interface{}, name string, argSigns []Sign, retSign Sign, args ...interface{}) (jenv *C.JNIEnv, jclass C.jclass, jmid C.jmethodID, jvalArray *C.jvalue, freeFunc func(), err error) { |
| jenv = getEnv(env) |
| jclass = getClass(class) |
| |
| var id unsafe.Pointer |
| id, err = JStaticMethodID(jenv, jclass, name, FuncSign(argSigns, retSign)) |
| if err != nil { |
| return |
| } |
| jmid = C.jmethodID(id) |
| jvalArray, freeFunc, err = jArgArray(jenv, args, argSigns) |
| return |
| } |
| |
| // CallObjectMethod calls a Java method that returns a java object. |
| func CallObjectMethod(env interface{}, object interface{}, name string, argSigns []Sign, retSign Sign, args ...interface{}) (unsafe.Pointer, error) { |
| switch retSign { |
| case ByteSign, CharSign, ShortSign, LongSign, FloatSign, DoubleSign, BoolSign, IntSign, VoidSign: |
| panic(fmt.Sprintf("Illegal call to CallObjectMethod on method with return sign %s", retSign)) |
| } |
| jenv, jobject, jmid, valArray, freeFunc, err := setupMethodCall(env, object, name, argSigns, retSign, args...) |
| if err != nil { |
| return nil, err |
| } |
| defer freeFunc() |
| ret := C.CallObjectMethodA(jenv, jobject, jmid, valArray) |
| return unsafe.Pointer(ret), JExceptionMsg(env) |
| } |
| |
| // CallStringMethod calls a Java method that returns a string. |
| func CallStringMethod(env interface{}, object interface{}, name string, argSigns []Sign, args ...interface{}) (string, error) { |
| jstr, err := CallObjectMethod(env, object, name, argSigns, StringSign, args...) |
| return GoString(env, jstr), err |
| } |
| |
| // CallByteArrayMethod calls a Java method that returns a byte array. |
| func CallByteArrayMethod(env interface{}, object interface{}, name string, argSigns []Sign, args ...interface{}) ([]byte, error) { |
| jArr, err := CallObjectMethod(env, object, name, argSigns, ArraySign(ByteSign), args...) |
| if err != nil { |
| return nil, err |
| } |
| return GoByteArray(env, jArr), nil |
| } |
| |
| // CallObjectArrayMethod calls a Java method that returns an object array. |
| func CallObjectArrayMethod(env interface{}, object interface{}, name string, argSigns []Sign, retElemSign Sign, args ...interface{}) ([]unsafe.Pointer, error) { |
| jenv := getEnv(env) |
| jArr, err := CallObjectMethod(env, object, name, argSigns, ArraySign(retElemSign), args...) |
| if err != nil { |
| return nil, err |
| } |
| if jArr == nil { |
| return nil, nil |
| } |
| ret := make([]unsafe.Pointer, int(C.GetArrayLength(jenv, C.jarray(jArr)))) |
| for i, _ := range ret { |
| ret[i] = unsafe.Pointer(C.GetObjectArrayElement(jenv, C.jobjectArray(jArr), C.jsize(i))) |
| } |
| return ret, nil |
| } |
| |
| // CallStringArrayMethod calls a Java method that returns an string array. |
| func CallStringArrayMethod(env interface{}, object interface{}, name string, argSigns []Sign, args ...interface{}) ([]string, error) { |
| objarr, err := CallObjectArrayMethod(env, object, name, argSigns, StringSign, args...) |
| if err != nil { |
| return nil, err |
| } |
| strs := make([]string, len(objarr)) |
| for i, obj := range objarr { |
| strs[i] = GoString(env, obj) |
| } |
| return strs, nil |
| } |
| |
| // CallMapMethod calls a Java method that returns a Map. |
| func CallMapMethod(env interface{}, object interface{}, name string, argSigns []Sign, args ...interface{}) (map[unsafe.Pointer]unsafe.Pointer, error) { |
| jMap, err := CallObjectMethod(env, object, name, argSigns, MapSign, args...) |
| if err != nil { |
| return nil, err |
| } |
| if jMap == nil { |
| return nil, nil |
| } |
| jEntrySet, err := CallObjectMethod(env, jMap, "entrySet", nil, SetSign) |
| if err != nil { |
| return nil, err |
| } |
| jIter, err := CallObjectMethod(env, jEntrySet, "iterator", nil, IteratorSign) |
| if err != nil { |
| return nil, err |
| } |
| ret := make(map[unsafe.Pointer]unsafe.Pointer) |
| for { |
| if hasNext, err := CallBooleanMethod(env, jIter, "hasNext", nil); err != nil { |
| return nil, err |
| } else if !hasNext { |
| break |
| } |
| jEntry, err := CallObjectMethod(env, jIter, "next", nil, ObjectSign) |
| if err != nil { |
| return nil, err |
| } |
| jKey, err := CallObjectMethod(env, jEntry, "getKey", nil, ObjectSign) |
| if err != nil { |
| return nil, err |
| } |
| jValue, err := CallObjectMethod(env, jEntry, "getValue", nil, ObjectSign) |
| if err != nil { |
| return nil, err |
| } |
| ret[jKey] = jValue |
| } |
| return ret, nil |
| } |
| |
| // CallMultimapMethod calls a Java method that returns a Multimap. |
| func CallMultimapMethod(env interface{}, object interface{}, name string, argSigns []Sign, args ...interface{}) (map[unsafe.Pointer][]unsafe.Pointer, error) { |
| jMultimap, err := CallObjectMethod(env, object, name, argSigns, MultimapSign, args...) |
| if err != nil { |
| return nil, err |
| } |
| if jMultimap == nil { |
| return nil, nil |
| } |
| jEntrySet, err := CallObjectMethod(env, jMultimap, "entrySet", nil, SetSign) |
| if err != nil { |
| return nil, err |
| } |
| jIter, err := CallObjectMethod(env, jEntrySet, "iterator", nil, IteratorSign) |
| if err != nil { |
| return nil, err |
| } |
| ret := make(map[unsafe.Pointer][]unsafe.Pointer) |
| for { |
| if hasNext, err := CallBooleanMethod(env, jIter, "hasNext", nil); err != nil { |
| return nil, err |
| } else if !hasNext { |
| break |
| } |
| jEntry, err := CallObjectMethod(env, jIter, "next", nil, ObjectSign) |
| if err != nil { |
| return nil, err |
| } |
| jKey, err := CallObjectMethod(env, jEntry, "getKey", nil, ObjectSign) |
| if err != nil { |
| return nil, err |
| } |
| jValue, err := CallObjectMethod(env, jEntry, "getValue", nil, ObjectSign) |
| if err != nil { |
| return nil, err |
| } |
| ret[jKey] = append(ret[jKey], jValue) |
| } |
| return ret, nil |
| } |
| |
| // CallBooleanMethod calls a Java method that returns a boolean. |
| func CallBooleanMethod(env interface{}, object interface{}, name string, argSigns []Sign, args ...interface{}) (bool, error) { |
| jenv, jobject, jmid, valArray, freeFunc, err := setupMethodCall(env, object, name, argSigns, BoolSign, args...) |
| if err != nil { |
| return false, err |
| } |
| defer freeFunc() |
| ret := C.CallBooleanMethodA(jenv, jobject, jmid, valArray) != C.JNI_OK |
| return ret, JExceptionMsg(env) |
| } |
| |
| // CallIntMethod calls a Java method that returns an int. |
| func CallIntMethod(env interface{}, object interface{}, name string, argSigns []Sign, args ...interface{}) (int, error) { |
| jenv, jobject, jmid, valArray, freeFunc, err := setupMethodCall(env, object, name, argSigns, IntSign, args...) |
| if err != nil { |
| return 0, err |
| } |
| defer freeFunc() |
| ret := int(C.CallIntMethodA(jenv, jobject, jmid, valArray)) |
| return ret, JExceptionMsg(env) |
| } |
| |
| // CallLongMethod calls a Java method that returns an int64. |
| func CallLongMethod(env interface{}, object interface{}, name string, argSigns []Sign, args ...interface{}) (int64, error) { |
| jenv, jobject, jmid, valArray, freeFunc, err := setupMethodCall(env, object, name, argSigns, LongSign, args...) |
| if err != nil { |
| return 0, err |
| } |
| defer freeFunc() |
| ret := int64(C.CallLongMethodA(jenv, jobject, jmid, valArray)) |
| return ret, JExceptionMsg(env) |
| } |
| |
| // CallVoidMethod calls a Java method that doesn't return anything. |
| func CallVoidMethod(env interface{}, object interface{}, name string, argSigns []Sign, args ...interface{}) error { |
| jenv, jobject, jmid, valArray, freeFunc, err := setupMethodCall(env, object, name, argSigns, VoidSign, args...) |
| if err != nil { |
| return err |
| } |
| defer freeFunc() |
| C.CallVoidMethodA(jenv, jobject, jmid, valArray) |
| return JExceptionMsg(env) |
| } |
| |
| // CallStaticObjectMethod calls a static Java method that returns a Java object. |
| func CallStaticObjectMethod(env interface{}, class interface{}, name string, argSigns []Sign, retSign Sign, args ...interface{}) (unsafe.Pointer, error) { |
| switch retSign { |
| case ByteSign, CharSign, ShortSign, LongSign, FloatSign, DoubleSign, BoolSign, IntSign, VoidSign: |
| panic(fmt.Sprintf("Illegal call to CallStaticObjectMethod on method with return sign %s", retSign)) |
| } |
| jenv, jclass, jmid, jvalArray, freeFunc, err := setupStaticMethodCall(env, class, name, argSigns, retSign, args...) |
| if err != nil { |
| return nil, err |
| } |
| defer freeFunc() |
| ret := C.CallStaticObjectMethodA(jenv, jclass, jmid, jvalArray) |
| return unsafe.Pointer(ret), JExceptionMsg(env) |
| } |
| |
| // CallStaticStringMethod calls a static Java method that returns a string. |
| func CallStaticStringMethod(env interface{}, class interface{}, name string, argSigns []Sign, args ...interface{}) (string, error) { |
| jString, err := CallStaticObjectMethod(env, class, name, argSigns, StringSign, args...) |
| return GoString(env, jString), err |
| } |
| |
| // CallStaticByteArrayMethod calls a static Java method that returns a byte array. |
| func CallStaticByteArrayMethod(env interface{}, class interface{}, name string, argSigns []Sign, args ...interface{}) ([]byte, error) { |
| jArr, err := CallStaticObjectMethod(env, class, name, argSigns, ArraySign(ByteSign), args...) |
| if err != nil { |
| return nil, err |
| } |
| return GoByteArray(env, jArr), nil |
| } |
| |
| // CallStaticLongArrayMethod calls a static Java method that returns a array of long. |
| func CallStaticLongArrayMethod(env interface{}, class interface{}, name string, argSigns []Sign, args ...interface{}) ([]int64, error) { |
| jArr, err := CallStaticObjectMethod(env, class, name, argSigns, ArraySign(LongSign), args...) |
| if err != nil { |
| return nil, err |
| } |
| return GoLongArray(env, jArr), nil |
| } |
| |
| // CallStaticIntMethod calls a static Java method that returns an int. |
| func CallStaticIntMethod(env interface{}, class interface{}, name string, argSigns []Sign, args ...interface{}) (int, error) { |
| jenv, jclass, jmid, jvalArray, freeFunc, err := setupStaticMethodCall(env, class, name, argSigns, IntSign, args...) |
| if err != nil { |
| return 0, err |
| } |
| defer freeFunc() |
| ret := int(C.CallStaticIntMethodA(jenv, jclass, jmid, jvalArray)) |
| return ret, JExceptionMsg(env) |
| } |
| |
| // CallStaticVoidMethod calls a static Java method doesn't return anything. |
| func CallStaticVoidMethod(env interface{}, class interface{}, name string, argSigns []Sign, args ...interface{}) error { |
| jenv, jclass, jmid, jvalArray, freeFunc, err := setupStaticMethodCall(env, class, name, argSigns, VoidSign, args...) |
| if err != nil { |
| return err |
| } |
| defer freeFunc() |
| C.CallStaticVoidMethodA(jenv, jclass, jmid, jvalArray) |
| return JExceptionMsg(env) |
| } |
| |
| func handleError(name string, err error) { |
| if err != nil { |
| panic(fmt.Sprintf("error while calling jni method %q: %v", name, err)) |
| } |
| } |
| |
| func isSignOneOf(sign Sign, set []Sign) bool { |
| for _, s := range set { |
| if sign == s { |
| return true |
| } |
| } |
| return false |
| } |