Plumbing additional RPC options

MultiPart: 3/3
Change-Id: I835f5434b8f945bc3b4637bea3f7ad859afdf042
diff --git a/android-lib/build.gradle b/android-lib/build.gradle
index faeb2ed..a554289 100644
--- a/android-lib/build.gradle
+++ b/android-lib/build.gradle
@@ -11,7 +11,7 @@
                 'com.android.tools.build:gradle:2.1.0',
                 // http://stackoverflow.com/questions/33881984/errorcause-com-android-sdklib-repository-fullrevision
                 'com.github.JakeWharton:sdk-manager-plugin:220bf7a88a7072df3ed16dc8466fb144f2817070',
-                'io.v:gradle-plugin:1.7',
+                'io.v:gradle-plugin:1.9',
                 'me.tatarka:gradle-retrolambda:3.2.4'
         )
     }
@@ -30,7 +30,7 @@
 
 // You should change this after releasing a new version of the library. See the
 // list of published versions at https://repo1.maven.org/maven2/io/v/vanadium-android.
-def releaseVersion = '2.2.2'
+def releaseVersion = '2.2.3'
 
 android {
     buildToolsVersion '23.0.1'
diff --git a/gradle-plugin/build.gradle b/gradle-plugin/build.gradle
index 2c445a8..d28ff27 100644
--- a/gradle-plugin/build.gradle
+++ b/gradle-plugin/build.gradle
@@ -1,6 +1,6 @@
 // You should change this after releasing a new version of the gradle plugin. See the
 // list of published versions at https://repo1.maven.org/maven2/io/v/gradle-plugin.
-def releaseVersion = '1.8'
+def releaseVersion = '1.9'
 
 buildscript {
     repositories {
diff --git a/lib/build.gradle b/lib/build.gradle
index 2669139..43f5091 100644
--- a/lib/build.gradle
+++ b/lib/build.gradle
@@ -7,7 +7,7 @@
     }
     dependencies {
         classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4'
-        classpath 'io.v:gradle-plugin:1.6'
+        classpath 'io.v:gradle-plugin:1.9'
     }
 }
 
@@ -23,7 +23,7 @@
 
 // You should change this after releasing a new version of the library. See the
 // list of published versions at https://repo1.maven.org/maven2/io/v/vanadium.
-def releaseVersion = '2.2.2'
+def releaseVersion = '2.2.3'
 
 dependencies {
     compile group: 'joda-time', name: 'joda-time', version: '2.7'
diff --git a/lib/src/main/java/io/v/impl/google/rpc/ClientImpl.java b/lib/src/main/java/io/v/impl/google/rpc/ClientImpl.java
index 7ed3032..29568b0 100644
--- a/lib/src/main/java/io/v/impl/google/rpc/ClientImpl.java
+++ b/lib/src/main/java/io/v/impl/google/rpc/ClientImpl.java
@@ -7,14 +7,12 @@
 import com.google.common.util.concurrent.ListenableFuture;
 
 import io.v.impl.google.ListenableFutureCallback;
-import io.v.v23.OptionDefs;
 import io.v.v23.Options;
 import io.v.v23.context.VContext;
+import io.v.v23.options.RpcOptions;
 import io.v.v23.rpc.Callback;
 import io.v.v23.rpc.Client;
 import io.v.v23.rpc.ClientCall;
-import io.v.v23.security.Authorizer;
-import io.v.v23.security.VSecurity;
 import io.v.v23.verror.VException;
 import io.v.v23.vom.VomUtil;
 
@@ -29,8 +27,7 @@
 
     private native void nativeStartCall(long nativeRef, VContext context,
                                         String name, String method, byte[][] vomArgs,
-                                        Authorizer nameResolutionAuthorizer,
-                                        Authorizer serverAuthorizer,
+                                        RpcOptions opts,
                                         Callback<ClientCall> callback);
     private native void nativeClose(long nativeRef);
     private native void nativeFinalize(long nativeRef);
@@ -39,50 +36,36 @@
         this.nativeRef = nativeRef;
     }
 
-    private boolean shouldSkipServerAuth(Options opts) {
-        return !opts.has(OptionDefs.SKIP_SERVER_ENDPOINT_AUTHORIZATION)
-                    ? false
-                    : opts.get(OptionDefs.SKIP_SERVER_ENDPOINT_AUTHORIZATION, Boolean.class);
-    }
-
-    private Authorizer nameResolutionAuthorizer(Options opts) {
-        if (opts.has(OptionDefs.SKIP_SERVER_ENDPOINT_AUTHORIZATION) &&
-                opts.get(OptionDefs.SKIP_SERVER_ENDPOINT_AUTHORIZATION, Boolean.class))
-            return VSecurity.newAllowEveryoneAuthorizer();
-
-        return !opts.has(OptionDefs.NAME_RESOLUTION_AUTHORIZER)
-                ? null
-                : opts.get(OptionDefs.NAME_RESOLUTION_AUTHORIZER, Authorizer.class);
-    }
-
-    private Authorizer serverAuthorizer(Options opts) {
-        if (opts.has(OptionDefs.SKIP_SERVER_ENDPOINT_AUTHORIZATION) &&
-                opts.get(OptionDefs.SKIP_SERVER_ENDPOINT_AUTHORIZATION, Boolean.class))
-            return VSecurity.newAllowEveryoneAuthorizer();
-
-        return !opts.has(OptionDefs.SERVER_AUTHORIZER)
-                ? null
-                : opts.get(OptionDefs.SERVER_AUTHORIZER, Authorizer.class);
-    }
-
     // Implement io.v.v23.rpc.Client.
     @Override
     public ListenableFuture<ClientCall> startCall(
             VContext ctx, String name, String method, Object[] args, Type[] argTypes) {
-        return startCall(ctx, name, method, args, argTypes, null);
+        return startCall(ctx, name, method, args, argTypes, (RpcOptions)null);
     }
+
+    @Deprecated
     @Override
     public ListenableFuture<ClientCall> startCall(VContext ctx, String name, String method,
                                                   Object[] args, Type[] argTypes, Options opts) {
-        ListenableFutureCallback<ClientCall> callback = new ListenableFutureCallback<>();
         if (opts == null) {
             opts = new Options();
         }
+
+        return startCall(ctx, name, method, args, argTypes, RpcOptions.migrateOptions(opts));
+    }
+
+    @Override
+    public ListenableFuture<ClientCall> startCall(VContext ctx, String name, String method,
+                                                  Object[] args, Type[] argTypes,
+                                                  RpcOptions opts) {
+        ListenableFutureCallback<ClientCall> callback = new ListenableFutureCallback<>();
+        if (opts == null) {
+            opts = new RpcOptions();
+        }
         try {
             checkStartCallArgs(name, method, args, argTypes);
             nativeStartCall(nativeRef, ctx, name, getMethodName(method),
-                    getEncodedVomArgs(args, argTypes),
-                    nameResolutionAuthorizer(opts), serverAuthorizer(opts), callback);
+                    getEncodedVomArgs(args, argTypes), opts, callback);
         } catch (VException e) {
             callback.onFailure(e);
         }
diff --git a/lib/src/main/java/io/v/impl/google/rt/VRuntimeImpl.java b/lib/src/main/java/io/v/impl/google/rt/VRuntimeImpl.java
index eaf229f..8314905 100644
--- a/lib/src/main/java/io/v/impl/google/rt/VRuntimeImpl.java
+++ b/lib/src/main/java/io/v/impl/google/rt/VRuntimeImpl.java
@@ -8,18 +8,16 @@
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 
-import org.joda.time.Duration;
-
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 
 import io.v.impl.google.ListenableFutureCallback;
-import io.v.v23.OptionDefs;
 import io.v.v23.Options;
 import io.v.v23.VRuntime;
 import io.v.v23.context.VContext;
 import io.v.v23.discovery.Discovery;
 import io.v.v23.namespace.Namespace;
+import io.v.v23.options.RpcServerOptions;
 import io.v.v23.rpc.Callback;
 import io.v.v23.rpc.Client;
 import io.v.v23.rpc.Dispatcher;
@@ -45,7 +43,7 @@
     private static native Client nativeGetClient(VContext ctx) throws VException;
     private static native VContext nativeWithNewServer(VContext ctx, String name,
                                                        Dispatcher dispatcher,
-                                                       Duration lameDuckTimeout) throws VException;
+                                                       RpcServerOptions opts) throws VException;
     private static native VContext nativeWithPrincipal(VContext ctx, VPrincipal principal)
             throws VException;
     private static native VPrincipal nativeGetPrincipal(VContext ctx) throws VException;
@@ -58,8 +56,10 @@
 
     private static native Discovery nativeNewDiscovery(VContext ctx) throws VException;
 
-    // Attaches a server to the given context.  Used by this class and other classes
-    // that natively create a server.
+    /**
+     * Attaches a server to the given context. Used by this class and other classes that natively
+     * create a server. Invoked from JNI.
+     */
     private static VContext withServer(VContext ctx, Server server) {
         return ctx.withValue(new ServerKey(), server);
     }
@@ -106,14 +106,27 @@
             throw new RuntimeException("Couldn't get client", e);
         }
     }
+
+    @Deprecated
     @Override
     public VContext withNewServer(VContext ctx, String name, Dispatcher disp, Options opts)
             throws VException {
-        return nativeWithNewServer(ctx, name, disp, lameDuckTimeoutFromOptions(opts));
+        return withNewServer(ctx, name, disp, RpcServerOptions.migrateOptions(opts));
     }
     @Override
+    public VContext withNewServer(VContext ctx, String name, Dispatcher disp, RpcServerOptions opts)
+            throws VException {
+        return nativeWithNewServer(ctx, name, disp, opts);
+    }
+    // Deprecated in interface.
+    @Override
     public VContext withNewServer(VContext ctx, String name, Object object, Authorizer authorizer,
                                   Options opts) throws VException {
+        return withNewServer(ctx, name, object, authorizer, RpcServerOptions.migrateOptions(opts));
+    }
+    @Override
+    public VContext withNewServer(VContext ctx, String name, Object object, Authorizer authorizer,
+                                  RpcServerOptions opts) throws VException {
         if (object == null) {
             throw new VException("newServer called with a null object");
         }
@@ -195,16 +208,4 @@
             return 0;
         }
     }
-
-    private static Duration lameDuckTimeoutFromOptions(Options opts) {
-        if (!opts.has(OptionDefs.SERVER_LAME_DUCK_TIMEOUT)) {
-            return Duration.standardSeconds(0);
-        }
-        Object timeout = opts.get(OptionDefs.SERVER_LAME_DUCK_TIMEOUT);
-        if (!(timeout instanceof Duration)) {
-            throw new RuntimeException("SERVER_LAME_DUCK_TIMEOUT option if specified must " +
-                        "contain an object of type org.joda.time.Duration");
-        }
-        return (Duration) timeout;
-    }
 }
diff --git a/lib/src/main/java/io/v/v23/V.java b/lib/src/main/java/io/v/v23/V.java
index 2f5d40a..6dd73ac 100644
--- a/lib/src/main/java/io/v/v23/V.java
+++ b/lib/src/main/java/io/v/v23/V.java
@@ -20,6 +20,7 @@
 import io.v.v23.context.VContext;
 import io.v.v23.discovery.Discovery;
 import io.v.v23.namespace.Namespace;
+import io.v.v23.options.RpcServerOptions;
 import io.v.v23.rpc.Client;
 import io.v.v23.rpc.Dispatcher;
 import io.v.v23.rpc.ListenSpec;
@@ -279,7 +280,7 @@
      */
     public static VContext withNewServer(VContext ctx, String name, Object object,
                                          Authorizer authorizer) throws VException {
-        return withNewServer(ctx, name, object, authorizer, null);
+        return withNewServer(ctx, name, object, authorizer, (RpcServerOptions) null);
     }
 
     /**
@@ -304,15 +305,25 @@
      * @return                 a child context to which the new server is attached
      * @throws VException      if a new server cannot be created
      */
-    public static VContext withNewServer(VContext ctx, String name, Object object,
-                                         Authorizer authorizer, Options opts) throws VException {
+    public static VContext withNewServer(
+            VContext ctx, String name, Object object, Authorizer authorizer, RpcServerOptions opts)
+            throws VException {
         if (opts == null) {
-            opts = new Options();
+            opts = new RpcServerOptions();
         }
         return getRuntime(ctx).withNewServer(ctx, name, object, authorizer, opts);
     }
 
     /**
+     * @deprecated Use
+     *  {@link #withNewServer(VContext, String, Object, Authorizer, RpcServerOptions)} instead.
+     */
+    public static VContext withNewServer(VContext ctx, String name, Object object,
+                                         Authorizer authorizer, Options opts) throws VException {
+        return withNewServer(ctx, name, object, authorizer, RpcServerOptions.migrateOptions(opts));
+    }
+
+    /**
      * Creates a new {@link Server} instance to serve a dispatcher and attaches
      * it to a new context (which is derived from the provided context).
      *
@@ -340,7 +351,7 @@
      */
     public static VContext withNewServer(VContext ctx, String name,
                                          Dispatcher dispatcher) throws VException {
-        return withNewServer(ctx, name, dispatcher, (Options) null);
+        return withNewServer(ctx, name, dispatcher, (RpcServerOptions) null);
     }
 
     /**
@@ -365,14 +376,22 @@
      * @throws VException      if a new server cannot be created
      */
     public static VContext withNewServer(VContext ctx, String name, Dispatcher dispatcher,
-                                         Options opts) throws VException {
+                                         RpcServerOptions opts) throws VException {
         if (opts == null) {
-            opts = new Options();
+            opts = new RpcServerOptions();
         }
         return getRuntime(ctx).withNewServer(ctx, name, dispatcher, opts);
     }
 
     /**
+     * @deprecated Use {@link #withNewServer(VContext, String, Dispatcher, RpcServerOptions)}
+     */
+    public static VContext withNewServer(VContext ctx, String name, Dispatcher dispatcher,
+                                         Options opts) throws VException {
+        return withNewServer(ctx, name, dispatcher, RpcServerOptions.migrateOptions(opts));
+    }
+
+    /**
      * Returns the server attached to the given context, or {@code null} if no server is attached.
      */
     public static Server getServer(VContext ctx) {
diff --git a/lib/src/main/java/io/v/v23/VRuntime.java b/lib/src/main/java/io/v/v23/VRuntime.java
index 76a4494..f58a0b8 100644
--- a/lib/src/main/java/io/v/v23/VRuntime.java
+++ b/lib/src/main/java/io/v/v23/VRuntime.java
@@ -7,6 +7,7 @@
 import io.v.v23.context.VContext;
 import io.v.v23.discovery.Discovery;
 import io.v.v23.namespace.Namespace;
+import io.v.v23.options.RpcServerOptions;
 import io.v.v23.rpc.Client;
 import io.v.v23.rpc.Dispatcher;
 import io.v.v23.rpc.ListenSpec;
@@ -88,7 +89,14 @@
      * @throws VException      if a new server cannot be created
      */
     VContext withNewServer(VContext ctx, String name, Object object, Authorizer authorizer,
-                     Options opts) throws VException;
+                           RpcServerOptions opts) throws VException;
+
+    /**
+     * @deprecated Use
+     *  {@link #withNewServer(VContext, String, Object, Authorizer, RpcServerOptions)}.
+     */
+    VContext withNewServer(VContext ctx, String name, Object object, Authorizer authorizer,
+                           Options opts) throws VException;
 
     /**
      * Creates a new {@link Server} instance to serve a dispatcher and attaches
@@ -124,6 +132,12 @@
      * @throws VException      if a new server cannot be created
      */
     VContext withNewServer(VContext ctx, String name, Dispatcher dispatcher,
+                           RpcServerOptions opts) throws VException;
+
+    /**
+     * @deprecated Use {@link #withNewServer(VContext, String, Dispatcher, RpcServerOptions)}
+     */
+    VContext withNewServer(VContext ctx, String name, Dispatcher dispatcher,
                            Options opts) throws VException;
 
     /**
diff --git a/lib/src/main/java/io/v/v23/options/RpcOptions.java b/lib/src/main/java/io/v/v23/options/RpcOptions.java
new file mode 100644
index 0000000..5a0a81f
--- /dev/null
+++ b/lib/src/main/java/io/v/v23/options/RpcOptions.java
@@ -0,0 +1,115 @@
+// 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.
+
+package io.v.v23.options;
+
+import org.joda.time.Duration;
+
+import javax.annotation.Nullable;
+
+import io.v.v23.OptionDefs;
+import io.v.v23.Options;
+import io.v.v23.naming.MountEntry;
+import io.v.v23.security.Authorizer;
+import io.v.v23.security.VSecurity;
+
+/**
+ * Strongly-typed alternative to {@link io.v.v23.Options} for RPCs, supporting more options. See
+ * also
+ * <a href="https://github.com/vanadium/go.v23/blob/master/options/options.go">v.io/v23/options</a>.
+ */
+public final class RpcOptions {
+    private static Authorizer migrateNameResolutionAuthorizer(final Options opts) {
+        if (opts.has(OptionDefs.SKIP_SERVER_ENDPOINT_AUTHORIZATION) &&
+                opts.get(OptionDefs.SKIP_SERVER_ENDPOINT_AUTHORIZATION, Boolean.class))
+            return VSecurity.newAllowEveryoneAuthorizer();
+
+        return !opts.has(OptionDefs.NAME_RESOLUTION_AUTHORIZER)
+                ? null
+                : opts.get(OptionDefs.NAME_RESOLUTION_AUTHORIZER, Authorizer.class);
+    }
+
+    private static Authorizer migrateServerAuthorizer(final Options opts) {
+        if (opts.has(OptionDefs.SKIP_SERVER_ENDPOINT_AUTHORIZATION) &&
+                opts.get(OptionDefs.SKIP_SERVER_ENDPOINT_AUTHORIZATION, Boolean.class))
+            return VSecurity.newAllowEveryoneAuthorizer();
+
+        return !opts.has(OptionDefs.SERVER_AUTHORIZER)
+                ? null
+                : opts.get(OptionDefs.SERVER_AUTHORIZER, Authorizer.class);
+    }
+
+    /**
+     * @deprecated For migration purposes only; call overloads taking {@code RpcOptions} directly.
+     */
+    @Nullable public static RpcOptions migrateOptions(@Nullable final Options legacy) {
+        return legacy == null ? null : new RpcOptions()
+                .nameResolutionAuthorizer(migrateNameResolutionAuthorizer(legacy))
+                .serverAuthorizer(migrateServerAuthorizer(legacy));
+    }
+
+    private Authorizer nameResolutionAuthorizer, serverAuthorizer;
+    private MountEntry preresolved;
+    private boolean noRetry;
+    private Duration connectionTimeout, channelTimeout;
+
+    public RpcOptions nameResolutionAuthorizer(final Authorizer nameResolutionAuthorizer) {
+        this.nameResolutionAuthorizer = nameResolutionAuthorizer;
+        return this;
+    }
+
+    public RpcOptions serverAuthorizer(final Authorizer serverAuthorizer) {
+        this.serverAuthorizer = serverAuthorizer;
+        return this;
+    }
+
+    public RpcOptions preresolved(final MountEntry preresolved) {
+        this.preresolved = preresolved;
+        return this;
+    }
+
+    public RpcOptions noRetry(final boolean noRetry) {
+        this.noRetry = noRetry;
+        return this;
+    }
+
+    public RpcOptions connectionTimeout(final Duration connectionTimeout) {
+        this.connectionTimeout = connectionTimeout;
+        return this;
+    }
+
+    public RpcOptions channelTimeout(final Duration channelTimeout) {
+        this.channelTimeout = channelTimeout;
+        return this;
+    }
+
+    public Authorizer nameResolutionAuthorizer() {
+        return this.nameResolutionAuthorizer;
+    }
+
+    public Authorizer serverAuthorizer() {
+        return this.serverAuthorizer;
+    }
+
+    public MountEntry preresolved() {
+        return this.preresolved;
+    }
+
+    public boolean noRetry() {
+        return this.noRetry;
+    }
+
+    public Duration connectionTimeout() {
+        return this.connectionTimeout;
+    }
+
+    public Duration channelTimeout() {
+        return this.channelTimeout;
+    }
+
+    public RpcOptions skipServerEndpointAuthorization() {
+        nameResolutionAuthorizer = serverAuthorizer = VSecurity.newAllowEveryoneAuthorizer();
+        return this;
+    }
+}
diff --git a/lib/src/main/java/io/v/v23/options/RpcServerOptions.java b/lib/src/main/java/io/v/v23/options/RpcServerOptions.java
new file mode 100644
index 0000000..ca8ad8f
--- /dev/null
+++ b/lib/src/main/java/io/v/v23/options/RpcServerOptions.java
@@ -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.
+
+package io.v.v23.options;
+
+import org.joda.time.Duration;
+
+import javax.annotation.Nullable;
+
+import io.v.v23.OptionDefs;
+import io.v.v23.Options;
+
+/**
+ * Strongly-typed alternative to {@link io.v.v23.Options} for RPC servers, supporting more options.
+ * See also
+ * <a href="https://github.com/vanadium/go.v23/blob/master/options/options.go">v.io/v23/options</a>.
+ */
+public final class RpcServerOptions {
+    private static final Duration DEFAULT_LAME_DUCK_TIMEOUT = Duration.standardSeconds(0);
+
+    private static Duration migrateLameDuckTimeout(final Options opts) {
+        if (!opts.has(OptionDefs.SERVER_LAME_DUCK_TIMEOUT)) {
+            return DEFAULT_LAME_DUCK_TIMEOUT;
+        }
+        Object timeout = opts.get(OptionDefs.SERVER_LAME_DUCK_TIMEOUT);
+        if (!(timeout instanceof Duration)) {
+            throw new RuntimeException("SERVER_LAME_DUCK_TIMEOUT option if specified must " +
+                    "contain an object of type org.joda.time.Duration");
+        }
+        return (Duration) timeout;
+    }
+
+    /**
+     * @deprecated For migration purposes only; call overloads taking {@code RpcServerOptions}
+     *  directly.
+     */
+    @Nullable
+    public static RpcServerOptions migrateOptions(@Nullable final Options legacy) {
+        return legacy == null ? null : new RpcServerOptions()
+                .lameDuckTimeout(migrateLameDuckTimeout(legacy));
+    }
+
+    private boolean servesMountTable;
+    // TODO(rosswang): This is a nullable type... it's unclear why it should default to a different
+    // value than the default behavior in Go when omitted. (isLeaf defaults to true in Go, and could
+    // as well be a nullable Boolean instead.)
+    private Duration lameDuckTimeout = DEFAULT_LAME_DUCK_TIMEOUT;
+    // Defaults to true:
+    // https://github.com/vanadium/go.ref/blob/60698e6/runtime/internal/rpc/server.go#L97
+    private boolean isLeaf = true;
+    private Duration channelTimeout;
+
+    public RpcServerOptions servesMountTable(final boolean servesMountTable) {
+        this.servesMountTable = servesMountTable;
+        return this;
+    }
+
+    public RpcServerOptions lameDuckTimeout(final Duration lameDuckTimeout) {
+        this.lameDuckTimeout = lameDuckTimeout;
+        return this;
+    }
+
+    public RpcServerOptions isLeaf(final boolean isLeaf) {
+        this.isLeaf = isLeaf;
+        return this;
+    }
+
+    public RpcServerOptions channelTimeout(final Duration channelTimeout) {
+        this.channelTimeout = channelTimeout;
+        return this;
+    }
+
+    public boolean servesMountTable() {
+        return this.servesMountTable;
+    }
+
+    public Duration lameDuckTimeout() {
+        return this.lameDuckTimeout;
+    }
+
+    public boolean isLeaf() {
+        return this.isLeaf;
+    }
+
+    public Duration channelTimeout() {
+        return this.channelTimeout;
+    }
+}
diff --git a/lib/src/main/java/io/v/v23/rpc/Client.java b/lib/src/main/java/io/v/v23/rpc/Client.java
index 1bcd62d..c0c9424 100644
--- a/lib/src/main/java/io/v/v23/rpc/Client.java
+++ b/lib/src/main/java/io/v/v23/rpc/Client.java
@@ -5,19 +5,29 @@
 package io.v.v23.rpc;
 
 import com.google.common.util.concurrent.ListenableFuture;
-import io.v.v23.Options;
-import io.v.v23.context.VContext;
 
 import java.lang.reflect.Type;
 
 import javax.annotation.CheckReturnValue;
 
+import io.v.v23.Options;
+import io.v.v23.context.VContext;
+import io.v.v23.options.RpcOptions;
+
 /**
  * The interface for making RPC calls.  There may be multiple outstanding calls associated with a
  * single client, and a client may be used by multiple threads concurrently.
  */
 public interface Client {
     /**
+     * @deprecated Use {@link #startCall(VContext, String, String, Object[], Type[], RpcOptions)}
+     *  instead, which uses a strongly-typed {@link RpcOptions} object that supports more features.
+     */
+    @CheckReturnValue
+    ListenableFuture<ClientCall> startCall(VContext context, String name, String method,
+                                           Object[] args, Type[] argTypes, Options opts);
+
+    /**
      * Starts an asynchronous call of the method on the server instance identified by name with the
      * given input args (of any arity) and provided options.
      * <p>
@@ -44,7 +54,7 @@
      */
     @CheckReturnValue
     ListenableFuture<ClientCall> startCall(VContext context, String name, String method,
-                                           Object[] args, Type[] argTypes, Options opts);
+                                           Object[] args, Type[] argTypes, RpcOptions opts);
 
     /**
      * A shortcut for {@link #startCall(VContext, String, String, Object[], Type[], Options)} with