v.io/x/jni: support for the Java invoker interface

Change-Id: Idc077cd9cb02776c58c6a9718480862e135c4e00
diff --git a/impl/google/rpc/invoker.go b/impl/google/rpc/invoker.go
index c9d4325..8b4fd10 100644
--- a/impl/google/rpc/invoker.go
+++ b/impl/google/rpc/invoker.go
@@ -9,13 +9,14 @@
 import (
 	"fmt"
 	"runtime"
+	"unsafe"
 
 	"v.io/v23/context"
 	"v.io/v23/naming"
 	"v.io/v23/rpc"
 	"v.io/v23/vdl"
 	"v.io/v23/vdlroot/signature"
-	"v.io/v23/vom"
+
 	jchannel "v.io/x/jni/impl/google/channel"
 	jutil "v.io/x/jni/util"
 	jcontext "v.io/x/jni/v23/context"
@@ -25,17 +26,25 @@
 import "C"
 
 func goInvoker(env *C.JNIEnv, jObj C.jobject) (rpc.Invoker, error) {
-	// Create a new Java VDLInvoker object.
-	jInvokerObj, err := jutil.NewObject(env, jVDLInvokerClass, []jutil.Sign{jutil.ObjectSign}, jObj)
-	if err != nil {
-		return nil, fmt.Errorf("error creating Java VDLInvoker object: %v", err)
+	// See if the Java object is an invoker.
+	var jInvoker C.jobject
+	if jutil.IsInstanceOf(env, jObj, jInvokerClass) {
+		jInvoker = jObj
+	} else {
+		// Create a new Java ReflectInvoker object.
+		jReflectInvoker, err := jutil.NewObject(env, jReflectInvokerClass, []jutil.Sign{jutil.ObjectSign}, jObj)
+		if err != nil {
+			return nil, fmt.Errorf("error creating Java ReflectInvoker object: %v", err)
+		}
+		jInvoker = C.jobject(jReflectInvoker)
 	}
+
 	// Reference Java invoker; it will be de-referenced when the go invoker
 	// created below is garbage-collected (through the finalizer callback we
 	// setup just below).
-	jInvoker := C.jobject(jutil.NewGlobalRef(env, jInvokerObj))
+	jInvokerRef := C.jobject(jutil.NewGlobalRef(env, jInvoker))
 	i := &invoker{
-		jInvoker: jInvoker,
+		jInvoker: jInvokerRef,
 	}
 	runtime.SetFinalizer(i, func(i *invoker) {
 		jEnv, freeFunc := jutil.GetEnv()
@@ -81,27 +90,130 @@
 	if err != nil {
 		return nil, err
 	}
-
 	jStreamServerCall, err := javaStreamServerCall(env, call)
 	if err != nil {
 		return nil, err
 	}
-
-	// VOM-encode the input arguments.
-	jVomArgs, err := encodeArgs(env, argptrs)
+	// Convert Go arguments to Java.
+	jArgs, err := i.prepareArgs(env, method, argptrs)
 	if err != nil {
 		return nil, err
 	}
 	// Invoke the method.
-	contextSign := jutil.ClassSign("io.v.v23.context.VContext")
-	callSign := jutil.ClassSign("io.v.v23.rpc.StreamServerCall")
-	replySign := jutil.ClassSign("io.v.impl.google.rpc.VDLInvoker$InvokeReply")
-	jReply, err := jutil.CallObjectMethod(env, i.jInvoker, "invoke", []jutil.Sign{contextSign, callSign, jutil.StringSign, jutil.ArraySign(jutil.ArraySign(jutil.ByteSign))}, replySign, jContext, jStreamServerCall, jutil.CamelCase(method), jVomArgs)
+	resultarr, err := jutil.CallObjectArrayMethod(env, i.jInvoker, "invoke", []jutil.Sign{contextSign, streamServerCallSign, jutil.StringSign, jutil.ArraySign(jutil.ObjectSign)}, jutil.ObjectSign, jContext, jStreamServerCall, jutil.CamelCase(method), jArgs)
 	if err != nil {
-		return nil, fmt.Errorf("error invoking Java method %q: %v", method, err)
+		return nil, err
 	}
-	// Decode and return results.
-	return decodeResults(env, C.jobject(jReply))
+	// Convert Java results into Go.
+	return i.prepareResults(env, method, resultarr)
+}
+
+func (i *invoker) Signature(ctx *context.T, call rpc.ServerCall) ([]signature.Interface, error) {
+	jEnv, freeFunc := jutil.GetEnv()
+	env := (*C.JNIEnv)(jEnv)
+	defer freeFunc()
+
+	jContext, err := jcontext.JavaContext(env, ctx, nil)
+	if err != nil {
+		return nil, err
+	}
+	jServerCall, err := JavaServerCall(env, call)
+	if err != nil {
+		return nil, err
+	}
+	ifaceSign := jutil.ClassSign("io.v.v23.vdlroot.signature.Interface")
+	interfacesArr, err := jutil.CallObjectArrayMethod(env, i.jInvoker, "getSignature", []jutil.Sign{contextSign, serverCallSign}, ifaceSign, jContext, jServerCall)
+	if err != nil {
+		return nil, err
+	}
+
+	result := make([]signature.Interface, len(interfacesArr))
+	for i, jInterface := range interfacesArr {
+		err = jutil.GoVomCopy(jEnv, jInterface, jInterfaceClass, &result[i])
+		if err != nil {
+			return nil, err
+		}
+	}
+	return result, nil
+}
+
+func (i *invoker) MethodSignature(ctx *context.T, call rpc.ServerCall, method string) (signature.Method, error) {
+	jEnv, freeFunc := jutil.GetEnv()
+	env := (*C.JNIEnv)(jEnv)
+	defer freeFunc()
+
+	jContext, err := jcontext.JavaContext(env, ctx, nil)
+	if err != nil {
+		return signature.Method{}, err
+	}
+	jServerCall, err := JavaServerCall(env, call)
+	if err != nil {
+		return signature.Method{}, err
+	}
+	methodSign := jutil.ClassSign("io.v.v23.vdlroot.signature.Method")
+	jMethod, err := jutil.CallObjectMethod(env, i.jInvoker, "getMethodSignature", []jutil.Sign{contextSign, serverCallSign, jutil.StringSign}, methodSign, jContext, jServerCall, method)
+	if err != nil {
+		return signature.Method{}, err
+	}
+
+	var result signature.Method
+	err = jutil.GoVomCopy(jEnv, jMethod, jMethodClass, &result)
+	if err != nil {
+		return signature.Method{}, err
+	}
+	return result, nil
+}
+
+func (i *invoker) Globber() *rpc.GlobState {
+	return &rpc.GlobState{AllGlobber: javaGlobber{i}}
+}
+
+// prepareArgs converts the provided arguments pointers into a Java Object array.
+func (i *invoker) prepareArgs(env *C.JNIEnv, method string, argptrs []interface{}) (unsafe.Pointer, error) {
+	args := make([]interface{}, len(argptrs))
+	for i, argptr := range argptrs {
+		args[i] = interface{}(jutil.DerefOrDie(argptr))
+	}
+	// Get Java argument types.
+	typesarr, err := jutil.CallObjectArrayMethod(env, i.jInvoker, "getArgumentTypes", []jutil.Sign{jutil.StringSign}, jutil.TypeSign, jutil.CamelCase(method))
+	if err != nil {
+		return nil, err
+	}
+	if len(typesarr) != len(args) {
+		return nil, fmt.Errorf("wrong number of arguments for method %s, want %d, have %d", method, len(typesarr), len(args))
+	}
+	arr := make([]interface{}, len(args))
+	for i, arg := range args {
+		var err error
+		if arr[i], err = jutil.JVomCopy(env, arg, typesarr[i]); err != nil {
+			return nil, err
+		}
+	}
+	return jutil.JObjectArray(env, arr, jObjectClass), nil
+}
+
+// prepareResults converts the provided Java result array into a Go slice of *vdl.Value.
+func (i *invoker) prepareResults(env *C.JNIEnv, method string, jResults []unsafe.Pointer) ([]interface{}, error) {
+	// Get Java result types.
+	typesarr, err := jutil.CallObjectArrayMethod(env, i.jInvoker, "getResultTypes", []jutil.Sign{jutil.StringSign}, jutil.TypeSign, jutil.CamelCase(method))
+	if err != nil {
+		return nil, err
+	}
+	if len(typesarr) != len(jResults) {
+		return nil, fmt.Errorf("wrong number of results for method %s, want %d, have %d", method, len(typesarr), len(jResults))
+	}
+	// VOM-encode Java results and decode into []*vdl.Value.
+	ret := make([]interface{}, len(jResults))
+	for i, jResult := range jResults {
+		data, err := jutil.JVomEncode(env, jResult, typesarr[i])
+		if err != nil {
+			return nil, err
+		}
+		if ret[i], err = jutil.VomDecodeToValue(data); err != nil {
+			return nil, err
+		}
+	}
+	return ret, nil
 }
 
 type javaGlobber struct {
@@ -145,7 +257,7 @@
 
 	callSign := jutil.ClassSign("io.v.v23.rpc.ServerCall")
 	channelSign := jutil.ClassSign("io.v.v23.OutputChannel")
-	// Invoke the VDLInvoker's glob method.
+	// Calls Java invoker's glob method.
 	go func(jServerCallRef C.jobject, jOutputChannelRef C.jobject) {
 		jEnv, freeFunc := jutil.GetEnv()
 		defer freeFunc()
@@ -157,94 +269,3 @@
 
 	return actualChannel, nil
 }
-
-func (i *invoker) Globber() *rpc.GlobState {
-	return &rpc.GlobState{AllGlobber: javaGlobber{i}}
-}
-
-func (i *invoker) Signature(ctx *context.T, call rpc.ServerCall) ([]signature.Interface, error) {
-	jEnv, freeFunc := jutil.GetEnv()
-	env := (*C.JNIEnv)(jEnv)
-	defer freeFunc()
-
-	replySign := jutil.ClassSign("io.v.v23.vdlroot.signature.Interface")
-
-	interfacesArr, err := jutil.CallObjectArrayMethod(env, i.jInvoker, "getSignature", nil, replySign)
-	if err != nil {
-		return nil, err
-	}
-
-	result := make([]signature.Interface, len(interfacesArr))
-	for i, jInterface := range interfacesArr {
-		err = jutil.GoVomCopy(jEnv, jInterface, jInterfaceClass, &result[i])
-		if err != nil {
-			return nil, err
-		}
-	}
-	return result, nil
-}
-
-func (i *invoker) MethodSignature(ctx *context.T, call rpc.ServerCall, method string) (signature.Method, error) {
-	jEnv, freeFunc := jutil.GetEnv()
-	env := (*C.JNIEnv)(jEnv)
-	defer freeFunc()
-
-	replySign := jutil.ClassSign("io.v.v23.vdlroot.signature.Method")
-
-	jMethod, err := jutil.CallObjectMethod(env, i.jInvoker, "getMethodSignature", []jutil.Sign{jutil.StringSign}, replySign, method)
-	if err != nil {
-		return signature.Method{}, err
-	}
-
-	var result signature.Method
-	err = jutil.GoVomCopy(jEnv, jMethod, jMethodClass, &result)
-	if err != nil {
-		return signature.Method{}, err
-	}
-	return result, nil
-}
-
-// encodeArgs VOM-encodes the provided arguments pointers and returns them as a
-// Java array of byte arrays.
-func encodeArgs(env *C.JNIEnv, argptrs []interface{}) (C.jobjectArray, error) {
-	vomArgs := make([][]byte, len(argptrs))
-	for i, argptr := range argptrs {
-		arg := interface{}(jutil.DerefOrDie(argptr))
-		var err error
-		if vomArgs[i], err = vom.Encode(arg); err != nil {
-			return nil, err
-		}
-	}
-	return C.jobjectArray(jutil.JByteArrayArray(env, vomArgs)), nil
-}
-
-// decodeResults VOM-decodes replies stored in the Java reply object into
-// an array *vdl.Value.
-func decodeResults(env *C.JNIEnv, jReply C.jobject) ([]interface{}, error) {
-	// Unpack the replies.
-	results, err := jutil.JByteArrayArrayField(env, jReply, "results")
-	if err != nil {
-		return nil, err
-	}
-	vomAppErr, err := jutil.JByteArrayField(env, jReply, "vomAppError")
-	if err != nil {
-		return nil, err
-	}
-	// Check for app error.
-	if vomAppErr != nil {
-		var appErr error
-		if err := vom.Decode(vomAppErr, &appErr); err != nil {
-			return nil, err
-		}
-		return nil, appErr
-	}
-	// VOM-decode results into *vdl.Value instances.
-	ret := make([]interface{}, len(results))
-	for i, result := range results {
-		var err error
-		if ret[i], err = jutil.VomDecodeToValue(result); err != nil {
-			return nil, err
-		}
-	}
-	return ret, nil
-}
diff --git a/impl/google/rpc/jni.go b/impl/google/rpc/jni.go
index 248e741..c4ec851 100644
--- a/impl/google/rpc/jni.go
+++ b/impl/google/rpc/jni.go
@@ -26,8 +26,12 @@
 import "C"
 
 var (
-	optionsSign        = jutil.ClassSign("io.v.v23.Options")
-	streamSign         = jutil.ClassSign("io.v.impl.google.rpc.Stream")
+	contextSign          = jutil.ClassSign("io.v.v23.context.VContext")
+	serverCallSign       = jutil.ClassSign("io.v.v23.rpc.ServerCall")
+	streamServerCallSign = jutil.ClassSign("io.v.v23.rpc.StreamServerCall")
+	streamSign           = jutil.ClassSign("io.v.impl.google.rpc.Stream")
+	optionsSign          = jutil.ClassSign("io.v.v23.Options")
+
 	listenAddrSign     = jutil.ClassSign("io.v.v23.rpc.ListenSpec$Address")
 	addressChooserSign = jutil.ClassSign("io.v.v23.rpc.AddressChooser")
 	serverStateSign    = jutil.ClassSign("io.v.v23.rpc.ServerState")
@@ -45,24 +49,26 @@
 	jServerCallClass C.jclass
 	// Global reference for io.v.impl.google.rpc.Stream class.
 	jStreamClass C.jclass
-	// Global reference for io.v.impl.google.rpc.VDLInvoker class.
-	jVDLInvokerClass C.jclass
-	// Global reference for io.v.v23.rpc.ServerStatus class.
-	jServerStatusClass C.jclass
-	// Global reference for io.v.v23.rpc.ServerState class.
-	jServerStateClass C.jclass
-	// Global reference for io.v.v23.rpc.MountStatus class.
-	jMountStatusClass C.jclass
-	// Global reference for io.v.v23.rpc.ProxyStatus class.
-	jProxyStatusClass C.jclass
+	// Global reference for io.v.v23.rpc.Invoker class.
+	jInvokerClass C.jclass
 	// Global reference for io.v.v23.rpc.ListenSpec class.
 	jListenSpecClass C.jclass
 	// Global reference for io.v.v23.rpc.ListenSpec$Address class.
 	jListenSpecAddressClass C.jclass
+	// Global reference for io.v.v23.rpc.MountStatus class.
+	jMountStatusClass C.jclass
 	// Global reference for io.v.v23.rpc.NetworkAddress class.
 	jNetworkAddressClass C.jclass
 	// Global reference for io.v.v23.rpc.NetworkChange class.
 	jNetworkChangeClass C.jclass
+	// Global reference for io.v.v23.rpc.ProxyStatus class.
+	jProxyStatusClass C.jclass
+	// Global reference for io.v.v23.rpc.ReflectInvoker class.
+	jReflectInvokerClass C.jclass
+	// Global reference for io.v.v23.rpc.ServerStatus class.
+	jServerStatusClass C.jclass
+	// Global reference for io.v.v23.rpc.ServerState class.
+	jServerStateClass C.jclass
 	// Global reference for io.v.v23.OptionDefs class.
 	jOptionDefsClass C.jclass
 	// Global reference for java.io.EOFException class.
@@ -75,6 +81,8 @@
 	jMethodClass C.jclass
 	// Global reference for io.v.v23.naming.GlobReply
 	jGlobReplyClass C.jclass
+	// Global reference for java.lang.Object class.
+	jObjectClass C.jclass
 )
 
 // Init initializes the JNI code with the given Java environment. This method
@@ -121,31 +129,11 @@
 		return err
 	}
 	jStreamClass = C.jclass(class)
-	class, err = jutil.JFindClass(jEnv, "io/v/impl/google/rpc/VDLInvoker")
+	class, err = jutil.JFindClass(jEnv, "io/v/v23/rpc/Invoker")
 	if err != nil {
 		return err
 	}
-	jVDLInvokerClass = C.jclass(class)
-	class, err = jutil.JFindClass(jEnv, "io/v/v23/rpc/ServerStatus")
-	if err != nil {
-		return err
-	}
-	jServerStatusClass = C.jclass(class)
-	class, err = jutil.JFindClass(jEnv, "io/v/v23/rpc/ServerState")
-	if err != nil {
-		return err
-	}
-	jServerStateClass = C.jclass(class)
-	class, err = jutil.JFindClass(jEnv, "io/v/v23/rpc/MountStatus")
-	if err != nil {
-		return err
-	}
-	jMountStatusClass = C.jclass(class)
-	class, err = jutil.JFindClass(jEnv, "io/v/v23/rpc/ProxyStatus")
-	if err != nil {
-		return err
-	}
-	jProxyStatusClass = C.jclass(class)
+	jInvokerClass = C.jclass(class)
 	class, err = jutil.JFindClass(jEnv, "io/v/v23/rpc/ListenSpec")
 	if err != nil {
 		return err
@@ -156,6 +144,11 @@
 		return err
 	}
 	jListenSpecAddressClass = C.jclass(class)
+	class, err = jutil.JFindClass(jEnv, "io/v/v23/rpc/MountStatus")
+	if err != nil {
+		return err
+	}
+	jMountStatusClass = C.jclass(class)
 	class, err = jutil.JFindClass(jEnv, "io/v/v23/rpc/NetworkAddress")
 	if err != nil {
 		return err
@@ -166,6 +159,26 @@
 		return err
 	}
 	jNetworkChangeClass = C.jclass(class)
+	class, err = jutil.JFindClass(jEnv, "io/v/v23/rpc/ProxyStatus")
+	if err != nil {
+		return err
+	}
+	jProxyStatusClass = C.jclass(class)
+	class, err = jutil.JFindClass(jEnv, "io/v/v23/rpc/ReflectInvoker")
+	if err != nil {
+		return err
+	}
+	jReflectInvokerClass = C.jclass(class)
+	class, err = jutil.JFindClass(jEnv, "io/v/v23/rpc/ServerStatus")
+	if err != nil {
+		return err
+	}
+	jServerStatusClass = C.jclass(class)
+	class, err = jutil.JFindClass(jEnv, "io/v/v23/rpc/ServerState")
+	if err != nil {
+		return err
+	}
+	jServerStateClass = C.jclass(class)
 	class, err = jutil.JFindClass(jEnv, "io/v/v23/OptionDefs")
 	if err != nil {
 		return err
@@ -196,6 +209,11 @@
 		return err
 	}
 	jGlobReplyClass = C.jclass(class)
+	class, err = jutil.JFindClass(jEnv, "java/lang/Object")
+	if err != nil {
+		return err
+	}
+	jObjectClass = C.jclass(class)
 	return nil
 }
 
diff --git a/test/fortune/fortune.vdl b/test/fortune/fortune.vdl
index 80b7bb9..4ce94c5 100644
--- a/test/fortune/fortune.vdl
+++ b/test/fortune/fortune.vdl
@@ -32,6 +32,9 @@
 	// StreamingGet returns a stream that can be used to obtain fortunes.
 	StreamingGet() stream<bool, string> (total int32 | error) {access.Read}
 
+	// MultipleGet returns the same fortune twice.
+	MultipleGet() (Fortune string, Another string | error) {access.Read}
+
 	// GetComplexError returns (always!) ErrComplex.
 	GetComplexError() error {access.Read}
 
diff --git a/test/fortune/fortune.vdl.go b/test/fortune/fortune.vdl.go
index dfe4a92..e56e09c 100644
--- a/test/fortune/fortune.vdl.go
+++ b/test/fortune/fortune.vdl.go
@@ -67,6 +67,8 @@
 	Get(*context.T, ...rpc.CallOpt) (Fortune string, err error)
 	// StreamingGet returns a stream that can be used to obtain fortunes.
 	StreamingGet(*context.T, ...rpc.CallOpt) (FortuneStreamingGetClientCall, error)
+	// MultipleGet returns the same fortune twice.
+	MultipleGet(*context.T, ...rpc.CallOpt) (Fortune string, Another string, err error)
 	// GetComplexError returns (always!) ErrComplex.
 	GetComplexError(*context.T, ...rpc.CallOpt) error
 	// NoTags is a method without tags.
@@ -110,6 +112,11 @@
 	return
 }
 
+func (c implFortuneClientStub) MultipleGet(ctx *context.T, opts ...rpc.CallOpt) (o0 string, o1 string, err error) {
+	err = v23.GetClient(ctx).Call(ctx, c.name, "MultipleGet", nil, []interface{}{&o0, &o1}, opts...)
+	return
+}
+
 func (c implFortuneClientStub) GetComplexError(ctx *context.T, opts ...rpc.CallOpt) (err error) {
 	err = v23.GetClient(ctx).Call(ctx, c.name, "GetComplexError", nil, nil, opts...)
 	return
@@ -238,6 +245,8 @@
 	Get(*context.T, rpc.ServerCall) (Fortune string, err error)
 	// StreamingGet returns a stream that can be used to obtain fortunes.
 	StreamingGet(*context.T, FortuneStreamingGetServerCall) (total int32, err error)
+	// MultipleGet returns the same fortune twice.
+	MultipleGet(*context.T, rpc.ServerCall) (Fortune string, Another string, err error)
 	// GetComplexError returns (always!) ErrComplex.
 	GetComplexError(*context.T, rpc.ServerCall) error
 	// NoTags is a method without tags.
@@ -258,6 +267,8 @@
 	Get(*context.T, rpc.ServerCall) (Fortune string, err error)
 	// StreamingGet returns a stream that can be used to obtain fortunes.
 	StreamingGet(*context.T, *FortuneStreamingGetServerCallStub) (total int32, err error)
+	// MultipleGet returns the same fortune twice.
+	MultipleGet(*context.T, rpc.ServerCall) (Fortune string, Another string, err error)
 	// GetComplexError returns (always!) ErrComplex.
 	GetComplexError(*context.T, rpc.ServerCall) error
 	// NoTags is a method without tags.
@@ -308,6 +319,10 @@
 	return s.impl.StreamingGet(ctx, call)
 }
 
+func (s implFortuneServerStub) MultipleGet(ctx *context.T, call rpc.ServerCall) (string, string, error) {
+	return s.impl.MultipleGet(ctx, call)
+}
+
 func (s implFortuneServerStub) GetComplexError(ctx *context.T, call rpc.ServerCall) error {
 	return s.impl.GetComplexError(ctx, call)
 }
@@ -362,6 +377,15 @@
 			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Read"))},
 		},
 		{
+			Name: "MultipleGet",
+			Doc:  "// MultipleGet returns the same fortune twice.",
+			OutArgs: []rpc.ArgDesc{
+				{"Fortune", ``}, // string
+				{"Another", ``}, // string
+			},
+			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Read"))},
+		},
+		{
 			Name: "GetComplexError",
 			Doc:  "// GetComplexError returns (always!) ErrComplex.",
 			Tags: []*vdl.Value{vdl.ValueOf(access.Tag("Read"))},
diff --git a/util/call.go b/util/call.go
index faa63f0..db66800 100644
--- a/util/call.go
+++ b/util/call.go
@@ -183,7 +183,7 @@
 
 // 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) {
+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)
@@ -200,7 +200,7 @@
 
 // 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) {
+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)
 
@@ -220,7 +220,7 @@
 	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)
+	jenv, jobject, jmid, valArray, freeFunc, err := setupMethodCall(env, object, name, argSigns, retSign, args...)
 	if err != nil {
 		return nil, err
 	}
@@ -358,7 +358,7 @@
 
 // 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)
+	jenv, jobject, jmid, valArray, freeFunc, err := setupMethodCall(env, object, name, argSigns, BoolSign, args...)
 	if err != nil {
 		return false, err
 	}
@@ -369,7 +369,7 @@
 
 // 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)
+	jenv, jobject, jmid, valArray, freeFunc, err := setupMethodCall(env, object, name, argSigns, IntSign, args...)
 	if err != nil {
 		return 0, err
 	}
@@ -380,7 +380,7 @@
 
 // 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)
+	jenv, jobject, jmid, valArray, freeFunc, err := setupMethodCall(env, object, name, argSigns, LongSign, args...)
 	if err != nil {
 		return 0, err
 	}
@@ -391,7 +391,7 @@
 
 // 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)
+	jenv, jobject, jmid, valArray, freeFunc, err := setupMethodCall(env, object, name, argSigns, VoidSign, args...)
 	if err != nil {
 		return err
 	}
@@ -406,7 +406,7 @@
 	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)
+	jenv, jclass, jmid, jvalArray, freeFunc, err := setupStaticMethodCall(env, class, name, argSigns, retSign, args...)
 	if err != nil {
 		return nil, err
 	}
@@ -432,7 +432,7 @@
 
 // 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)
+	jenv, jclass, jmid, jvalArray, freeFunc, err := setupStaticMethodCall(env, class, name, argSigns, IntSign, args...)
 	if err != nil {
 		return 0, err
 	}
@@ -443,7 +443,7 @@
 
 // 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)
+	jenv, jclass, jmid, jvalArray, freeFunc, err := setupStaticMethodCall(env, class, name, argSigns, VoidSign, args...)
 	if err != nil {
 		return err
 	}
diff --git a/util/coding.go b/util/coding.go
index 81b6bcc..89276ff 100644
--- a/util/coding.go
+++ b/util/coding.go
@@ -41,12 +41,12 @@
 	return vom.Decode(data, dstptr)
 }
 
-// JVomEncode VOM-encodes the provided Java object of the given class.
+// JVomEncode VOM-encodes the provided Java object of the given type.
 // NOTE: Because CGO creates package-local types and because this method may be
 // invoked from a different package, Java types are passed in an empty interface
 // and then cast into their package local types.
-func JVomEncode(jEnv, jObj, jClass interface{}) ([]byte, error) {
-	return CallStaticByteArrayMethod(jEnv, jVomUtilClass, "encode", []Sign{ObjectSign, TypeSign}, jObj, jClass)
+func JVomEncode(jEnv, jObj, jType interface{}) ([]byte, error) {
+	return CallStaticByteArrayMethod(jEnv, jVomUtilClass, "encode", []Sign{ObjectSign, TypeSign}, jObj, jType)
 }
 
 // JVomEncode VOM-encodes the provided Java VdlValue object.
@@ -61,12 +61,12 @@
 // NOTE: Because CGO creates package-local types and because this method may be
 // invoked from a different package, Java types are passed in an empty interface
 // and then cast into their package local types.
-func JVomDecode(jEnv interface{}, data []byte, jClass interface{}) (unsafe.Pointer, error) {
-	class := getClass(jClass)
-	if class == nil {
-		class = jObjectClass
+func JVomDecode(jEnv interface{}, data []byte, jTypeObj interface{}) (unsafe.Pointer, error) {
+	jType := getObject(jTypeObj)
+	if jType == nil {
+		jType = C.jobject(jObjectClass)
 	}
-	return CallStaticObjectMethod(jEnv, jVomUtilClass, "decode", []Sign{ByteArraySign, TypeSign}, ObjectSign, data, class)
+	return CallStaticObjectMethod(jEnv, jVomUtilClass, "decode", []Sign{ByteArraySign, TypeSign}, ObjectSign, data, jType)
 }
 
 // JVomCopy copies the provided Go value into a corresponding Java object by
@@ -74,12 +74,12 @@
 // NOTE: Because CGO creates package-local types and because this method may be
 // invoked from a different package, Java types are passed in an empty interface
 // and then cast into their package local types.
-func JVomCopy(jEnv interface{}, src interface{}, jClass interface{}) (unsafe.Pointer, error) {
-	data, err := vom.Encode(src)
+func JVomCopy(jEnv interface{}, val interface{}, jType interface{}) (unsafe.Pointer, error) {
+	data, err := vom.Encode(val)
 	if err != nil {
 		return nil, err
 	}
-	return JVomDecode(jEnv, data, jClass)
+	return JVomDecode(jEnv, data, jType)
 }
 
 // GoVomCopy copies the provided Java object into a provided Go value pointer by
diff --git a/util/util.go b/util/util.go
index 0c313d7..9f470e5 100644
--- a/util/util.go
+++ b/util/util.go
@@ -280,15 +280,11 @@
 	if err == nil {
 		return nil, nil
 	}
-	data, err := vom.Encode(err)
-	if err != nil {
-		return nil, err
-	}
-	return JVomDecode(jEnv, data, jVExceptionClass)
+	return JVomCopy(jEnv, err, jVExceptionClass)
 }
 
-// JExceptionMsg returns the exception message if an exception occurred, or
-// nil otherwise.
+// JExceptionMsg returns the exception message as a Go error, if an exception
+// occurred, or nil otherwise.
 // NOTE: Because CGO creates package-local types and because this method may be
 // invoked from a different package, Java types are passed in an empty interface
 // and then cast into their package local types.
@@ -299,11 +295,40 @@
 		return nil
 	}
 	C.ExceptionClear(env)
-	id, err := JMethodID(env, jThrowableClass, "getMessage", FuncSign(nil, StringSign))
-	if err != nil {
-		panic(err.Error())
+	if IsInstanceOf(jEnv, C.jobject(e), jVExceptionClass) {
+		// VException: convert it into a verror.
+		// Note that we can't use CallStaticObjectMethod below as it may lead to
+		// an infinite loop.
+		jenv, jclass, jmid, jValArray, freeFunc, err := setupStaticMethodCall(env, jVomUtilClass, "encode", []Sign{ObjectSign, TypeSign}, ByteArraySign, C.jobject(e), jVExceptionClass)
+		if err != nil {
+			return fmt.Errorf("error converting VException: " + err.Error())
+		}
+		defer freeFunc()
+		jData := C.CallStaticObjectMethodA(jenv, jclass, jmid, jValArray)
+		if e := C.ExceptionOccurred(env); e != nil {
+			C.ExceptionClear(env)
+			return fmt.Errorf("error converting VException: exception during VomUtil.encode()")
+		}
+		data := GoByteArray(env, jData)
+		var verr error
+		if err := vom.Decode(data, &verr); err != nil {
+			return fmt.Errorf("error converting VException: " + err.Error())
+		}
+		return verr
 	}
-	jMsg := C.CallGetExceptionMessage(env, C.jobject(e), C.jmethodID(id))
+	// Not a VException: convert it into a Go error.
+	// Note that we can't use CallObjectMethod below, as it may lead to an
+	// infinite loop.
+	jenv, jobject, jmid, jValArray, freeFunc, err := setupMethodCall(env, C.jobject(e), "getMessage", nil, StringSign)
+	if err != nil {
+		return fmt.Errorf("error converting exception: " + err.Error())
+	}
+	defer freeFunc()
+	jMsg := C.CallObjectMethodA(jenv, jobject, jmid, jValArray)
+	if e := C.ExceptionOccurred(env); e != nil {
+		C.ExceptionClear(env)
+		return fmt.Errorf("error converting exception: exception during Throwable.getMessage()")
+	}
 	return errors.New(GoString(env, jMsg))
 }