v.io/x/jni: native changes for listenspec

Change-Id: Ib1b6c928b9773e7b43e20d4464ae864bf1acef6c
diff --git a/impl/google/rpc/jni.go b/impl/google/rpc/jni.go
index 749dcee..cdac402 100644
--- a/impl/google/rpc/jni.go
+++ b/impl/google/rpc/jni.go
@@ -9,6 +9,7 @@
 import (
 	"io"
 	"log"
+	"net"
 	"unsafe"
 
 	"v.io/v23/rpc"
@@ -22,14 +23,16 @@
 )
 
 // #include "jni.h"
-// #include <stdlib.h>
 import "C"
 
 var (
-	optionsSign     = jutil.ClassSign("io.v.v23.Options")
-	streamSign      = jutil.ClassSign("io.v.impl.google.rpc.Stream")
-	listenAddrSign  = jutil.ClassSign("io.v.v23.rpc.ListenSpec$Address")
-	serverStateSign = jutil.ClassSign("io.v.v23.rpc.ServerState")
+	optionsSign        = jutil.ClassSign("io.v.v23.Options")
+	streamSign         = jutil.ClassSign("io.v.impl.google.rpc.Stream")
+	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")
+	// Global reference for io.v.impl.google.rpc.AddressChooser class.
+	jAddressChooserClass C.jclass
 	// Global reference for io.v.impl.google.rpc.Server class.
 	jServerClass C.jclass
 	// Global reference for io.v.impl.google.rpc.Client class.
@@ -83,7 +86,12 @@
 	// Cache global references to all Java classes used by the package.  This is
 	// necessary because JNI gets access to the class loader only in the system
 	// thread, so we aren't able to invoke FindClass in other threads.
-	class, err := jutil.JFindClass(jEnv, "io/v/impl/google/rpc/Server")
+	class, err := jutil.JFindClass(jEnv, "io/v/impl/google/rpc/AddressChooser")
+	if err != nil {
+		return err
+	}
+	jAddressChooserClass = C.jclass(class)
+	class, err = jutil.JFindClass(jEnv, "io/v/impl/google/rpc/Server")
 	if err != nil {
 		return err
 	}
@@ -428,8 +436,23 @@
 	jutil.GoUnref((*rpc.ClientCall)(jutil.Ptr(goPtr)))
 }
 
+//export Java_io_v_impl_google_rpc_ServerCall_nativeSuffix
+func Java_io_v_impl_google_rpc_ServerCall_nativeSuffix(env *C.JNIEnv, jServerCall C.jobject, goPtr C.jlong) C.jstring {
+	return C.jstring(jutil.JString(env, (*(*rpc.ServerCall)(jutil.Ptr(goPtr))).Suffix()))
+}
+
+//export Java_io_v_impl_google_rpc_ServerCall_nativeLocalEndpoint
+func Java_io_v_impl_google_rpc_ServerCall_nativeLocalEndpoint(env *C.JNIEnv, jServerCall C.jobject, goPtr C.jlong) C.jstring {
+	return C.jstring(jutil.JString(env, (*(*rpc.ServerCall)(jutil.Ptr(goPtr))).LocalEndpoint().String()))
+}
+
+//export Java_io_v_impl_google_rpc_ServerCall_nativeRemoteEndpoint
+func Java_io_v_impl_google_rpc_ServerCall_nativeRemoteEndpoint(env *C.JNIEnv, jServerCall C.jobject, goPtr C.jlong) C.jstring {
+	return C.jstring(jutil.JString(env, (*(*rpc.ServerCall)(jutil.Ptr(goPtr))).RemoteEndpoint().String()))
+}
+
 //export Java_io_v_impl_google_rpc_ServerCall_nativeGrantedBlessings
-func Java_io_v_impl_google_rpc_ServerCall_nativeGrantedBlessings(env *C.JNIEnv, jStreamServerClass C.jclass, goPtr C.jlong) C.jobject {
+func Java_io_v_impl_google_rpc_ServerCall_nativeGrantedBlessings(env *C.JNIEnv, jServerCall C.jobject, goPtr C.jlong) C.jobject {
 	blessings := (*(*rpc.ServerCall)(jutil.Ptr(goPtr))).GrantedBlessings()
 	jBlessings, err := jsecurity.JavaBlessings(env, blessings)
 	if err != nil {
@@ -439,23 +462,19 @@
 	return C.jobject(jBlessings)
 }
 
-//export Java_io_v_impl_google_rpc_ServerCall_nativeLocalEndpoint
-func Java_io_v_impl_google_rpc_ServerCall_nativeLocalEndpoint(env *C.JNIEnv, jStreamServerClass C.jclass, goPtr C.jlong) C.jstring {
-	return C.jstring(jutil.JString(env, (*(*rpc.ServerCall)(jutil.Ptr(goPtr))).LocalEndpoint().String()))
-}
-
-//export Java_io_v_impl_google_rpc_ServerCall_nativeRemoteEndpoint
-func Java_io_v_impl_google_rpc_ServerCall_nativeRemoteEndpoint(env *C.JNIEnv, jStreamServerClass C.jclass, goPtr C.jlong) C.jstring {
-	return C.jstring(jutil.JString(env, (*(*rpc.ServerCall)(jutil.Ptr(goPtr))).RemoteEndpoint().String()))
-}
-
-//export Java_io_v_impl_google_rpc_ServerCall_nativeSuffix
-func Java_io_v_impl_google_rpc_ServerCall_nativeSuffix(env *C.JNIEnv, jStreamServerClass C.jclass, goPtr C.jlong) C.jstring {
-	return C.jstring(jutil.JString(env, (*(*rpc.ServerCall)(jutil.Ptr(goPtr))).Suffix()))
+//export Java_io_v_impl_google_rpc_ServerCall_nativeServer
+func Java_io_v_impl_google_rpc_ServerCall_nativeServer(env *C.JNIEnv, jServerCall C.jobject, goPtr C.jlong) C.jobject {
+	server := (*(*rpc.ServerCall)(jutil.Ptr(goPtr))).Server()
+	jServer, err := JavaServer(env, server)
+	if err != nil {
+		jutil.JThrowV(env, err)
+		return nil
+	}
+	return C.jobject(jServer)
 }
 
 //export Java_io_v_impl_google_rpc_ServerCall_nativeFinalize
-func Java_io_v_impl_google_rpc_ServerCall_nativeFinalize(env *C.JNIEnv, jStreamServerCall C.jobject, goPtr C.jlong) {
+func Java_io_v_impl_google_rpc_ServerCall_nativeFinalize(env *C.JNIEnv, jServerCall C.jobject, goPtr C.jlong) {
 	jutil.GoUnref((*rpc.ServerCall)(jutil.Ptr(goPtr)))
 }
 
@@ -463,3 +482,29 @@
 func Java_io_v_impl_google_rpc_StreamServerCall_nativeFinalize(env *C.JNIEnv, jStreamServerCall C.jobject, goPtr C.jlong) {
 	jutil.GoUnref((*rpc.StreamServerCall)(jutil.Ptr(goPtr)))
 }
+
+//export Java_io_v_impl_google_rpc_AddressChooser_nativeChoose
+func Java_io_v_impl_google_rpc_AddressChooser_nativeChoose(env *C.JNIEnv, jAddressChooser C.jobject, goPtr C.jlong, jProtocol C.jstring, jCandidates C.jobjectArray) C.jobjectArray {
+	protocol := jutil.GoString(env, jProtocol)
+	candidates, err := GoNetworkAddressArray(env, jCandidates)
+	if err != nil {
+		jutil.JThrowV(env, err)
+		return nil
+	}
+	addrs, err := (*((*func(protocol string, candidates []net.Addr) ([]net.Addr, error))(jutil.Ptr(goPtr))))(protocol, candidates)
+	if err != nil {
+		jutil.JThrowV(env, err)
+		return nil
+	}
+	jAddrs, err := JavaNetworkAddressArray(env, addrs)
+	if err != nil {
+		jutil.JThrowV(env, err)
+		return nil
+	}
+	return C.jobjectArray(jAddrs)
+}
+
+//export Java_io_v_impl_google_rpc_AddressChooser_nativeFinalize
+func Java_io_v_impl_google_rpc_AddressChooser_nativeFinalize(env *C.JNIEnv, jAddressChooser C.jobject, goPtr C.jlong) {
+	jutil.GoUnref((*func(protocol string, candidates []net.Addr) ([]net.Addr, error))(jutil.Ptr(goPtr)))
+}
diff --git a/impl/google/rpc/util.go b/impl/google/rpc/util.go
index b584d42..0b1fd5e 100644
--- a/impl/google/rpc/util.go
+++ b/impl/google/rpc/util.go
@@ -9,6 +9,7 @@
 import (
 	"fmt"
 	"net"
+	"runtime"
 	"unsafe"
 
 	"v.io/v23/rpc"
@@ -22,12 +23,11 @@
 // 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 JavaServer(jEnv interface{}, server rpc.Server, jListenSpec interface{}) (unsafe.Pointer, error) {
+func JavaServer(jEnv interface{}, server rpc.Server) (unsafe.Pointer, error) {
 	if server == nil {
 		return nil, fmt.Errorf("Go Server value cannot be nil")
 	}
-	listenSpecSign := jutil.ClassSign("io.v.v23.rpc.ListenSpec")
-	jServer, err := jutil.NewObject(jEnv, jServerClass, []jutil.Sign{jutil.LongSign, listenSpecSign}, int64(jutil.PtrValue(&server)), jListenSpec)
+	jServer, err := jutil.NewObject(jEnv, jServerClass, []jutil.Sign{jutil.LongSign}, int64(jutil.PtrValue(&server)))
 	if err != nil {
 		return nil, err
 	}
@@ -202,53 +202,33 @@
 	return jStatus, nil
 }
 
-var (
-	roamingSpec rpc.ListenSpec
-)
-
-// SetRoamingSpec saves the provided roaming spec for later use.
-func SetRoamingSpec(spec rpc.ListenSpec) {
-	roamingSpec = spec
-}
-
 // GoListenSpec converts the provided Java ListenSpec into a Go ListenSpec.
 // 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 GoListenSpec(jEnv, jSpec interface{}) (rpc.ListenSpec, error) {
-	addrArr, err := jutil.CallObjectArrayMethod(jEnv, jSpec, "getAddresses", nil, listenAddrSign)
+	jAddrs, err := jutil.CallObjectMethod(jEnv, jSpec, "getAddresses", nil, jutil.ArraySign(listenAddrSign))
 	if err != nil {
 		return rpc.ListenSpec{}, err
 	}
-	addrs := make(rpc.ListenAddrs, len(addrArr))
-	for i, jAddr := range addrArr {
-		var err error
-		addrs[i].Protocol, err = jutil.CallStringMethod(jEnv, jAddr, "getProtocol", nil)
-		if err != nil {
-			return rpc.ListenSpec{}, err
-		}
-		addrs[i].Address, err = jutil.CallStringMethod(jEnv, jAddr, "getAddress", nil)
-		if err != nil {
-			return rpc.ListenSpec{}, err
-		}
+	addrs, err := GoListenAddrs(jEnv, jAddrs)
+	if err != nil {
+		return rpc.ListenSpec{}, err
 	}
 	proxy, err := jutil.CallStringMethod(jEnv, jSpec, "getProxy", nil)
 	if err != nil {
 		return rpc.ListenSpec{}, err
 	}
-	isRoaming, err := jutil.CallBooleanMethod(jEnv, jSpec, "isRoaming", nil)
+	jChooser, err := jutil.CallObjectMethod(jEnv, jSpec, "getChooser", nil, addressChooserSign)
 	if err != nil {
 		return rpc.ListenSpec{}, err
 	}
-	// TODO(spetrovic): fix this roaming hack, probably by implementing
-	// Publisher and AddressChooser in Java as well (ugh!).
-	var spec rpc.ListenSpec
-	if isRoaming {
-		spec = roamingSpec
-	}
-	spec.Addrs = addrs
-	spec.Proxy = proxy
-	return spec, nil
+	chooser := GoAddressChooser(jEnv, jChooser)
+	return rpc.ListenSpec{
+		Addrs:          addrs,
+		Proxy:          proxy,
+		AddressChooser: chooser,
+	}, nil
 }
 
 // GoListenSpec converts the provided Go ListenSpec into a Java ListenSpec.
@@ -256,25 +236,59 @@
 // invoked from a different package, Java types are passed in an empty interface
 // and then cast into their package local types.
 func JavaListenSpec(jEnv interface{}, spec rpc.ListenSpec) (unsafe.Pointer, error) {
-	addrarr := make([]interface{}, len(spec.Addrs))
-	for i, addr := range spec.Addrs {
+	jAddrs, err := JavaListenAddrArray(jEnv, spec.Addrs)
+	if err != nil {
+		return nil, err
+	}
+	jProxy := jutil.JString(jEnv, spec.Proxy)
+	jChooser, err := JavaAddressChooser(jEnv, spec.AddressChooser)
+	if err != nil {
+		return nil, err
+	}
+	addressSign := jutil.ClassSign("io.v.v23.rpc.ListenSpec$Address")
+	jSpec, err := jutil.NewObject(jEnv, jListenSpecClass, []jutil.Sign{jutil.ArraySign(addressSign), jutil.StringSign, addressChooserSign}, jAddrs, jProxy, jChooser)
+	if err != nil {
+		return nil, err
+	}
+	return jSpec, nil
+}
+
+// JavaListenAddrArray converts Go rpc.ListenAddrs into a Java
+// ListenSpec$Address array.
+// 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 JavaListenAddrArray(jEnv interface{}, addrs rpc.ListenAddrs) (unsafe.Pointer, error) {
+	addrarr := make([]interface{}, len(addrs))
+	for i, addr := range addrs {
 		var err error
 		if addrarr[i], err = jutil.NewObject(jEnv, jListenSpecAddressClass, []jutil.Sign{jutil.StringSign, jutil.StringSign}, addr.Protocol, addr.Address); err != nil {
 			return nil, err
 		}
 	}
-	jAddrs := jutil.JObjectArray(jEnv, addrarr, jListenSpecAddressClass)
-	isRoaming := false
-	if spec.AddressChooser != nil {
-		// Our best guess that this is a roaming spec.
-		isRoaming = true
+	return jutil.JObjectArray(jEnv, addrarr, jListenSpecAddressClass), nil
+}
+
+// GoListenAddrs converts Java ListenSpec$Address array into a Go
+// rpc.ListenAddrs value.
+// 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 GoListenAddrs(jEnv, jAddrs interface{}) (rpc.ListenAddrs, error) {
+	addrarr := jutil.GoObjectArray(jEnv, jAddrs)
+	addrs := make(rpc.ListenAddrs, len(addrarr))
+	for i, jAddr := range addrarr {
+		var err error
+		addrs[i].Protocol, err = jutil.CallStringMethod(jEnv, jAddr, "getProtocol", nil)
+		if err != nil {
+			return nil, err
+		}
+		addrs[i].Address, err = jutil.CallStringMethod(jEnv, jAddr, "getAddress", nil)
+		if err != nil {
+			return nil, err
+		}
 	}
-	addressSign := jutil.ClassSign("io.v.v23.rpc.ListenSpec$Address")
-	jSpec, err := jutil.NewObject(jEnv, jListenSpecClass, []jutil.Sign{jutil.ArraySign(addressSign), jutil.StringSign, jutil.BoolSign}, jAddrs, spec.Proxy, isRoaming)
-	if err != nil {
-		return nil, err
-	}
-	return jSpec, nil
+	return addrs, nil
 }
 
 // JavaNetworkChange converts the Go NetworkChange value into a Java NetworkChange object.
@@ -314,13 +328,13 @@
 // 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 JavaServerCall(jEnv interface{}, serverCall rpc.ServerCall) (C.jobject, error) {
+func JavaServerCall(jEnv interface{}, serverCall rpc.ServerCall) (unsafe.Pointer, error) {
 	jServerCall, err := jutil.NewObject(jEnv, jServerCallClass, []jutil.Sign{jutil.LongSign}, int64(jutil.PtrValue(&serverCall)))
 	if err != nil {
 		return nil, err
 	}
 	jutil.GoRef(&serverCall) // Un-refed when the Java ServerCall object is finalized.
-	return C.jobject(jServerCall), nil
+	return jServerCall, nil
 }
 
 // JavaNetworkAddress converts a Go net.Addr into a Java NetworkAddress object.
@@ -331,8 +345,36 @@
 	return jutil.NewObject(jEnv, jNetworkAddressClass, []jutil.Sign{jutil.StringSign, jutil.StringSign}, addr.Network(), addr.String())
 }
 
-// JavaNetworkAddressArray converts a slice of net.Addr values into a Java array
-// of NetworkAddress objects.
+type jniAddr struct {
+	network, addr string
+}
+
+func (a *jniAddr) Network() string {
+	return a.network
+}
+
+func (a *jniAddr) String() string {
+	return a.addr
+}
+
+// GoNetworkAddress converts a Java NetworkAddress object into Go net.Addr.
+// 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 GoNetworkAddress(jEnv, jAddr interface{}) (net.Addr, error) {
+	network, err := jutil.CallStringMethod(jEnv, jAddr, "network", nil)
+	if err != nil {
+		return nil, err
+	}
+	addr, err := jutil.CallStringMethod(jEnv, jAddr, "address", nil)
+	if err != nil {
+		return nil, err
+	}
+	return &jniAddr{network, addr}, nil
+}
+
+// JavaNetworkAddressArray converts a Go slice of net.Addr values into a Java
+// array of NetworkAddress objects.
 // 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.
@@ -346,3 +388,66 @@
 	}
 	return jutil.JObjectArray(jEnv, arr, jNetworkAddressClass), nil
 }
+
+// GoNetworkAddressArray converts a Java array of NetworkAddress objects into a
+// Go slice of net.Addr values.
+// 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 GoNetworkAddressArray(jEnv, jAddrs interface{}) ([]net.Addr, error) {
+	arr := jutil.GoObjectArray(jEnv, jAddrs)
+	ret := make([]net.Addr, len(arr))
+	for i, jAddr := range arr {
+		var err error
+		if ret[i], err = GoNetworkAddress(jEnv, jAddr); err != nil {
+			return nil, err
+		}
+	}
+	return ret, nil
+}
+
+// JavaAddressChooser converts a Go address chooser function into a Java
+// AddressChooser object.
+// 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 JavaAddressChooser(jEnv interface{}, chooser func(protocol string, candidates []net.Addr) ([]net.Addr, error)) (unsafe.Pointer, error) {
+	jAddressChooser, err := jutil.NewObject(jEnv, jAddressChooserClass, []jutil.Sign{jutil.LongSign}, int64(jutil.PtrValue(&chooser)))
+	if err != nil {
+		return nil, err
+	}
+	jutil.GoRef(&chooser) // Un-refed when the Java AddressChooser object is finalized.
+	return jAddressChooser, nil
+}
+
+// GoAddressChooser converts a Java AddressChooser object into a Go address
+// chooser function.
+// 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 GoAddressChooser(jEnv, jChooserObj interface{}) func(protocol string, candidates []net.Addr) ([]net.Addr, error) {
+	// Reference Java chooser; it will be de-referenced when the go function
+	// created below is garbage-collected (through the finalizer callback we
+	// setup just below).
+	jChooser := jutil.NewGlobalRef(jEnv, jChooserObj)
+	f := func(protocol string, candidates []net.Addr) ([]net.Addr, error) {
+		javaEnv, freeFunc := jutil.GetEnv()
+		defer freeFunc()
+		jCandidates, err := JavaNetworkAddressArray(javaEnv, candidates)
+		if err != nil {
+			return nil, err
+		}
+		addrsSign := jutil.ArraySign(jutil.ClassSign("io.v.v23.rpc.NetworkAddress"))
+		jAddrs, err := jutil.CallObjectMethod(javaEnv, jChooser, "choose", []jutil.Sign{jutil.StringSign, addrsSign}, addrsSign, protocol, jCandidates)
+		if err != nil {
+			return nil, err
+		}
+		return GoNetworkAddressArray(javaEnv, jAddrs)
+	}
+	runtime.SetFinalizer(&f, func(f *func(protocol string, candidates []net.Addr) ([]net.Addr, error)) {
+		javaEnv, freeFunc := jutil.GetEnv()
+		defer freeFunc()
+		jutil.DeleteGlobalRef(javaEnv, jChooser)
+	})
+	return f
+}
diff --git a/impl/google/rt/jni.go b/impl/google/rt/jni.go
index b184e9b..adb37a4 100644
--- a/impl/google/rt/jni.go
+++ b/impl/google/rt/jni.go
@@ -33,10 +33,6 @@
 //export Java_io_v_impl_google_rt_VRuntime_nativeInit
 func Java_io_v_impl_google_rt_VRuntime_nativeInit(env *C.JNIEnv, jRuntime C.jclass) C.jobject {
 	ctx, _ := v23.Init()
-	// Get the original spec, which is guaranteed to be a roaming spec (as we
-	// import the roaming profile).
-	roamingSpec := v23.GetListenSpec(ctx)
-	jrpc.SetRoamingSpec(roamingSpec)
 	jCtx, err := jcontext.JavaContext(env, ctx, nil)
 	if err != nil {
 		jutil.JThrowV(env, err)
@@ -85,7 +81,7 @@
 }
 
 //export Java_io_v_impl_google_rt_VRuntime_nativeNewServer
-func Java_io_v_impl_google_rt_VRuntime_nativeNewServer(env *C.JNIEnv, jRuntime C.jclass, jContext C.jobject, jListenSpec C.jobject) C.jobject {
+func Java_io_v_impl_google_rt_VRuntime_nativeNewServer(env *C.JNIEnv, jRuntime C.jclass, jContext C.jobject) C.jobject {
 	// TODO(spetrovic): Have Java context support nativePtr()?
 	ctx, err := jcontext.GoContext(env, jContext)
 	if err != nil {
@@ -97,7 +93,7 @@
 		jutil.JThrowV(env, err)
 		return nil
 	}
-	jServer, err := jrpc.JavaServer(env, server, jListenSpec)
+	jServer, err := jrpc.JavaServer(env, server)
 	if err != nil {
 		jutil.JThrowV(env, err)
 		return nil