Add Support of NIO ByteBuffer overloading methods with Covariant Return Types.

- In JDK 9, [JDK-4774077](https://bugs.openjdk.java.net/browse/JDK-4774077) added a bunch of covariant overrides to `ByteBuffer`

PiperOrigin-RevId: 294712886
diff --git a/src/test/java/com/google/devtools/build/android/desugar/BUILD b/src/test/java/com/google/devtools/build/android/desugar/BUILD
index 4caf05b..5e835d1 100644
--- a/src/test/java/com/google/devtools/build/android/desugar/BUILD
+++ b/src/test/java/com/google/devtools/build/android/desugar/BUILD
@@ -18,6 +18,7 @@
         "//src/test/java/com/google/devtools/build/android/desugar/io:srcs",
         "//src/test/java/com/google/devtools/build/android/desugar/langmodel:srcs",
         "//src/test/java/com/google/devtools/build/android/desugar/nest:srcs",
+        "//src/test/java/com/google/devtools/build/android/desugar/covariantreturn:srcs",
         "//src/test/java/com/google/devtools/build/android/desugar/runtime:srcs",
         "//src/test/java/com/google/devtools/build/android/desugar/scan:srcs",
         "//src/test/java/com/google/devtools/build/android/desugar/stringconcat:srcs",
diff --git a/src/test/java/com/google/devtools/build/android/desugar/covariantreturn/BUILD b/src/test/java/com/google/devtools/build/android/desugar/covariantreturn/BUILD
new file mode 100644
index 0000000..1d7b0bf
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/desugar/covariantreturn/BUILD
@@ -0,0 +1,50 @@
+load("@rules_java//java:defs.bzl", "java_test")
+
+# Description:
+#   Tests for the Java 8 desugaring tool for Android.
+package(
+    default_testonly = 1,
+    default_visibility = ["//src/test/java/com/google/devtools/build/android/desugar:__subpackages__"],
+)
+
+licenses(["notice"])  # Apache 2.0
+
+java_test(
+    name = "NioBufferRefConverterTest",
+    size = "medium",
+    srcs = ["NioBufferRefConverterTest.java"],
+    data = [
+        ":nio_buffer_invocations_src",
+        "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:android_jar_for_testing",
+        "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:jacoco_agent_jar",
+    ],
+    jvm_flags = [
+        "-Dinput_srcs='$(locations :nio_buffer_invocations_src)'",
+        "-Djdk.internal.lambda.dumpProxyClasses=$$(mktemp -d)",
+        "-Dandroid_runtime_jar=$(location //src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:android_jar_for_testing)",
+        "-Djacoco_agent_jar=$(location //src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:jacoco_agent_jar)",
+    ],
+    test_class = "com.google.devtools.build.android.desugar.covariantreturn.NioBufferRefConverterTest",
+    deps = [
+        "//src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit:desugar_rule",
+        "//third_party:asm",
+        "//third_party:asm-tree",
+        "//third_party:guava",
+        "//third_party:guava-testlib",
+        "//third_party:jsr330_inject",
+        "//third_party:junit4",
+        "//third_party:truth",
+        "//third_party:truth8",
+    ],
+)
+
+filegroup(
+    name = "nio_buffer_invocations_src",
+    srcs = ["NioBufferInvocations.java"],
+)
+
+filegroup(
+    name = "srcs",
+    testonly = 0,
+    srcs = glob(["**"]),
+)
diff --git a/src/test/java/com/google/devtools/build/android/desugar/covariantreturn/NioBufferInvocations.java b/src/test/java/com/google/devtools/build/android/desugar/covariantreturn/NioBufferInvocations.java
new file mode 100644
index 0000000..029b854
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/desugar/covariantreturn/NioBufferInvocations.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2020 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.devtools.build.android.desugar.covariantreturn;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.DoubleBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.LongBuffer;
+import java.nio.ShortBuffer;
+
+/** Test source for {@link NioBufferRefConverterTest}. */
+public final class NioBufferInvocations {
+
+  public static IntBuffer getIntBufferPosition(IntBuffer buffer, int position) {
+    return buffer.position(position);
+  }
+
+  public static CharBuffer getCharBufferPosition(CharBuffer buffer, int position) {
+    return buffer.position(position);
+  }
+
+  public static FloatBuffer getFloatBufferPosition(FloatBuffer buffer, int position) {
+    return buffer.position(position);
+  }
+
+  public static DoubleBuffer getDoubleBufferPosition(DoubleBuffer buffer, int position) {
+    return buffer.position(position);
+  }
+
+  public static ShortBuffer getShortBufferPosition(ShortBuffer buffer, int position) {
+    return buffer.position(position);
+  }
+
+  public static LongBuffer getLongBufferPosition(LongBuffer buffer, int position) {
+    return buffer.position(position);
+  }
+
+  public static ByteBuffer getByteBufferPosition(ByteBuffer buffer, int position) {
+    return buffer.position(position);
+  }
+
+  private NioBufferInvocations() {}
+}
diff --git a/src/test/java/com/google/devtools/build/android/desugar/covariantreturn/NioBufferRefConverterTest.java b/src/test/java/com/google/devtools/build/android/desugar/covariantreturn/NioBufferRefConverterTest.java
new file mode 100644
index 0000000..ecb64b1
--- /dev/null
+++ b/src/test/java/com/google/devtools/build/android/desugar/covariantreturn/NioBufferRefConverterTest.java
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2020 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.devtools.build.android.desugar.covariantreturn;
+
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.truth.Truth.assertThat;
+import static org.objectweb.asm.tree.AbstractInsnNode.METHOD_INSN;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.devtools.build.android.desugar.testing.junit.AsmNode;
+import com.google.devtools.build.android.desugar.testing.junit.DesugarRule;
+import com.google.devtools.build.android.desugar.testing.junit.DesugarRunner;
+import com.google.devtools.build.android.desugar.testing.junit.JdkSuppress;
+import com.google.devtools.build.android.desugar.testing.junit.JdkVersion;
+import com.google.devtools.build.android.desugar.testing.junit.RuntimeMethodHandle;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.DoubleBuffer;
+import java.nio.FloatBuffer;
+import java.nio.IntBuffer;
+import java.nio.LongBuffer;
+import java.nio.ShortBuffer;
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.tree.AbstractInsnNode;
+import org.objectweb.asm.tree.MethodInsnNode;
+import org.objectweb.asm.tree.MethodNode;
+import org.objectweb.asm.tree.TypeInsnNode;
+
+/** Functional Tests for {@link NioBufferRefConverter}. */
+@RunWith(DesugarRunner.class)
+@JdkSuppress(minJdkVersion = JdkVersion.V11)
+public class NioBufferRefConverterTest {
+
+  @Rule
+  public final DesugarRule desugarRule =
+      DesugarRule.builder(this, MethodHandles.lookup())
+          .addSourceInputsFromJvmFlag("input_srcs")
+          .addJavacOptions("-source 11", "-target 11")
+          .addCommandOptions("desugar_nest_based_private_access", "true")
+          .addCommandOptions("allow_empty_bootclasspath", "true")
+          .addCommandOptions("core_library", "true")
+          .setWorkingJavaPackage("com.google.devtools.build.android.desugar.covariantreturn")
+          .build();
+
+  @Test
+  public void methodOfNioBufferWithCovariantTypes_beforeDesugar(
+      @AsmNode(className = "NioBufferInvocations", memberName = "getByteBufferPosition", round = 0)
+          MethodNode before) {
+    ImmutableList<AbstractInsnNode> methodInvocations =
+        Arrays.stream(before.instructions.toArray())
+            .filter(insnNode -> insnNode.getType() == METHOD_INSN)
+            .collect(toImmutableList());
+
+    assertThat(methodInvocations).hasSize(1);
+    MethodInsnNode methodInsnNode = (MethodInsnNode) Iterables.getOnlyElement(methodInvocations);
+
+    assertThat(methodInsnNode.owner).isEqualTo("java/nio/ByteBuffer");
+    assertThat(methodInsnNode.name).isEqualTo("position");
+    assertThat(methodInsnNode.desc).isEqualTo("(I)Ljava/nio/ByteBuffer;");
+
+    assertThat(methodInsnNode.getNext().getOpcode()).isEqualTo(Opcodes.ARETURN);
+  }
+
+  @Test
+  public void methodOfNioBufferWithCovariantTypes_afterDesugar(
+      @AsmNode(className = "NioBufferInvocations", memberName = "getByteBufferPosition", round = 1)
+          MethodNode after) {
+    ImmutableList<AbstractInsnNode> methodInvocations =
+        Arrays.stream(after.instructions.toArray())
+            .filter(insnNode -> insnNode.getType() == METHOD_INSN)
+            .collect(toImmutableList());
+
+    assertThat(methodInvocations).hasSize(1);
+    MethodInsnNode methodInsnNode = (MethodInsnNode) Iterables.getOnlyElement(methodInvocations);
+
+    assertThat(methodInsnNode.owner).isEqualTo("java/nio/ByteBuffer");
+    assertThat(methodInsnNode.name).isEqualTo("position");
+    assertThat(methodInsnNode.desc).isEqualTo("(I)Ljava/nio/Buffer;");
+
+    TypeInsnNode typeInsnNode = (TypeInsnNode) methodInsnNode.getNext();
+    assertThat(typeInsnNode.getOpcode()).isEqualTo(Opcodes.CHECKCAST);
+    assertThat(typeInsnNode.desc).isEqualTo("java/nio/ByteBuffer");
+
+    assertThat(typeInsnNode.getNext().getOpcode()).isEqualTo(Opcodes.ARETURN);
+  }
+
+  @Test
+  public void methodOfNioBufferWithCovariantTypes_beforeDesugarInvocation(
+      @RuntimeMethodHandle(
+              className = "NioBufferInvocations",
+              memberName = "getByteBufferPosition",
+              round = 0)
+          MethodHandle before)
+      throws Throwable {
+    ByteBuffer buffer = ByteBuffer.wrap("random text".getBytes(Charset.defaultCharset()));
+    int expectedPos = 2;
+
+    ByteBuffer result = (ByteBuffer) before.invoke(buffer, expectedPos);
+    assertThat(result.position()).isEqualTo(expectedPos);
+  }
+
+  @Test
+  public void methodOfNioBufferWithCovariantTypes_afterDesugarInvocationOfByteBufferMethod(
+      @RuntimeMethodHandle(className = "NioBufferInvocations", memberName = "getByteBufferPosition")
+          MethodHandle after)
+      throws Throwable {
+    ByteBuffer buffer = ByteBuffer.wrap("random text".getBytes(Charset.defaultCharset()));
+    int expectedPos = 2;
+
+    ByteBuffer result = (ByteBuffer) after.invoke(buffer, expectedPos);
+    assertThat(result.position()).isEqualTo(expectedPos);
+  }
+
+  @Test
+  public void methodOfNioBufferWithCovariantTypes_afterDesugarInvocationOfCharBufferMethod(
+      @RuntimeMethodHandle(className = "NioBufferInvocations", memberName = "getCharBufferPosition")
+          MethodHandle after)
+      throws Throwable {
+    CharBuffer buffer = CharBuffer.wrap("random text".toCharArray());
+    int expectedPos = 2;
+
+    CharBuffer result = (CharBuffer) after.invoke(buffer, expectedPos);
+    assertThat(result.position()).isEqualTo(expectedPos);
+  }
+
+  @Test
+  public void methodOfNioBufferWithCovariantTypes_afterDesugarInvocationOfIntBufferMethod(
+      @RuntimeMethodHandle(className = "NioBufferInvocations", memberName = "getIntBufferPosition")
+          MethodHandle after)
+      throws Throwable {
+    IntBuffer buffer = IntBuffer.wrap(new int[] {10, 20, 30});
+    int expectedPos = 2;
+
+    IntBuffer result = (IntBuffer) after.invoke(buffer, expectedPos);
+    assertThat(result.position()).isEqualTo(expectedPos);
+  }
+
+  @Test
+  public void methodOfNioBufferWithCovariantTypes_afterDesugarInvocationOfFloatBufferMethod(
+      @RuntimeMethodHandle(
+              className = "NioBufferInvocations",
+              memberName = "getFloatBufferPosition")
+          MethodHandle after)
+      throws Throwable {
+    FloatBuffer buffer = FloatBuffer.wrap(new float[] {10f, 20f, 30f});
+    int expectedPos = 2;
+
+    FloatBuffer result = (FloatBuffer) after.invoke(buffer, expectedPos);
+    assertThat(result.position()).isEqualTo(expectedPos);
+  }
+
+  @Test
+  public void methodOfNioBufferWithCovariantTypes_afterDesugarInvocationOfDoubleBufferMethod(
+      @RuntimeMethodHandle(
+              className = "NioBufferInvocations",
+              memberName = "getDoubleBufferPosition")
+          MethodHandle after)
+      throws Throwable {
+    DoubleBuffer buffer = DoubleBuffer.wrap(new double[] {10.0, 20.0, 30.0});
+    int expectedPos = 2;
+
+    DoubleBuffer result = (DoubleBuffer) after.invoke(buffer, expectedPos);
+    assertThat(result.position()).isEqualTo(expectedPos);
+  }
+
+  @Test
+  public void methodOfNioBufferWithCovariantTypes_afterDesugarInvocationOfShortBufferMethod(
+      @RuntimeMethodHandle(
+              className = "NioBufferInvocations",
+              memberName = "getShortBufferPosition")
+          MethodHandle after)
+      throws Throwable {
+    ShortBuffer buffer = ShortBuffer.wrap(new short[] {10, 20, 30});
+    int expectedPos = 2;
+
+    ShortBuffer result = (ShortBuffer) after.invoke(buffer, expectedPos);
+    assertThat(result.position()).isEqualTo(expectedPos);
+  }
+
+  @Test
+  public void methodOfNioBufferWithCovariantTypes_afterDesugarInvocationOfLongBufferMethod(
+      @RuntimeMethodHandle(className = "NioBufferInvocations", memberName = "getLongBufferPosition")
+          MethodHandle after)
+      throws Throwable {
+    LongBuffer buffer = LongBuffer.wrap(new long[] {10L, 20L, 30L});
+    int expectedPos = 2;
+
+    LongBuffer result = (LongBuffer) after.invoke(buffer, expectedPos);
+    assertThat(result.position()).isEqualTo(expectedPos);
+  }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD
index 6e7673f..959a814 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/BUILD
@@ -41,6 +41,7 @@
         "//src/main/java/com/google/devtools/common/options",
         "//src/main/protobuf:worker_protocol_java_proto",
         "//src/tools/android/java/com/google/devtools/build/android:android_builder_lib",
+        "//src/tools/android/java/com/google/devtools/build/android/desugar/covariantreturn",
         "//src/tools/android/java/com/google/devtools/build/android/desugar/io",
         "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel",
         "//src/tools/android/java/com/google/devtools/build/android/desugar/nest",
@@ -70,6 +71,7 @@
         "//src/tools/android/java/com/google/devtools/build/android/desugar/io:srcs",
         "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel:srcs",
         "//src/tools/android/java/com/google/devtools/build/android/desugar/nest:srcs",
+        "//src/tools/android/java/com/google/devtools/build/android/desugar/covariantreturn:srcs",
         "//src/tools/android/java/com/google/devtools/build/android/desugar/runtime:srcs",
         "//src/tools/android/java/com/google/devtools/build/android/desugar/strconcat:srcs",
         "//src/tools/android/java/com/google/devtools/build/android/desugar/scan:srcs",
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java b/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java
index fe28295..625eb1c 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/Desugar.java
@@ -31,6 +31,7 @@
 import com.google.common.io.Resources;
 import com.google.devtools.build.android.Converters.ExistingPathConverter;
 import com.google.devtools.build.android.Converters.PathConverter;
+import com.google.devtools.build.android.desugar.covariantreturn.NioBufferRefConverter;
 import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter;
 import com.google.devtools.build.android.desugar.io.CoreLibraryRewriter.UnprefixingClassWriter;
 import com.google.devtools.build.android.desugar.io.FileContentProvider;
@@ -1068,6 +1069,8 @@
       visitor = new IndyStringConcatDesugaring(classMemberUseCounter, visitor);
     }
 
+    visitor = NioBufferRefConverter.create(visitor, rewriter.getPrefixer());
+
     return visitor;
   }
 
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/covariantreturn/BUILD b/src/tools/android/java/com/google/devtools/build/android/desugar/covariantreturn/BUILD
new file mode 100644
index 0000000..addc876
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/covariantreturn/BUILD
@@ -0,0 +1,28 @@
+load("@rules_java//java:defs.bzl", "java_library")
+
+package(
+    default_visibility = [
+        "//src/test/java/com/google/devtools/build/android/desugar:__subpackages__",
+        "//src/tools/android/java/com/google/devtools/build/android/desugar:__subpackages__",
+    ],
+)
+
+java_library(
+    name = "covariantreturn",
+    srcs = glob(["*.java"]),
+    deps = [
+        "//src/tools/android/java/com/google/devtools/build/android/desugar/langmodel",
+        "//third_party:asm",
+        "//third_party:asm-commons",
+        "//third_party:asm-tree",
+        "//third_party:auto_value",
+        "//third_party:guava",
+        "//third_party:jsr305",
+    ],
+)
+
+filegroup(
+    name = "srcs",
+    srcs = glob(["**"]),
+    visibility = ["//src/tools/android/java/com/google/devtools/build/android/desugar:__pkg__"],
+)
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/covariantreturn/NioBufferRefConverter.java b/src/tools/android/java/com/google/devtools/build/android/desugar/covariantreturn/NioBufferRefConverter.java
new file mode 100644
index 0000000..2696d06
--- /dev/null
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/covariantreturn/NioBufferRefConverter.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2020 The Bazel Authors. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.devtools.build.android.desugar.covariantreturn;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.devtools.build.android.desugar.langmodel.ClassName;
+import com.google.devtools.build.android.desugar.langmodel.MethodKey;
+import com.google.devtools.build.android.desugar.langmodel.TypeMapper;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+/**
+ * A bytecode converter that supports to use covariant return types in the NIO buffer hierarchy.
+ *
+ * @see https://bugs.openjdk.java.net/browse/JDK-4774077.
+ */
+public final class NioBufferRefConverter extends ClassVisitor {
+
+  /** The inheritance hierarchy root of Java NIO buffer family. */
+  private static final ClassName NIO_BUFFER_BASE = ClassName.create("java/nio/Buffer");
+
+  /** All overloading methods in {@link java.nio.Buffer} with covariant return type. */
+  private static final ImmutableList<MethodKey> BASE_METHODS_WITH_COVARIANT_RETURN_TYPES =
+      ImmutableList.of(
+          MethodKey.create(NIO_BUFFER_BASE, "position", "(I)Ljava/nio/Buffer;"),
+          MethodKey.create(NIO_BUFFER_BASE, "limit", "(I)Ljava/nio/Buffer;"),
+          MethodKey.create(NIO_BUFFER_BASE, "mark", "()Ljava/nio/Buffer;"),
+          MethodKey.create(NIO_BUFFER_BASE, "reset", "()Ljava/nio/Buffer;"),
+          MethodKey.create(NIO_BUFFER_BASE, "clear", "()Ljava/nio/Buffer;"),
+          MethodKey.create(NIO_BUFFER_BASE, "flip", "()Ljava/nio/Buffer;"),
+          MethodKey.create(NIO_BUFFER_BASE, "rewind", "()Ljava/nio/Buffer;"));
+
+  /** All public type-specific NIO buffer classes derived from {@link java.nio.Buffer}. */
+  private static final ImmutableSet<ClassName> TYPE_SPECIFIC_NIO_BUFFERS =
+      ImmutableSet.of(
+          ClassName.create("java/nio/IntBuffer"),
+          ClassName.create("java/nio/CharBuffer"),
+          ClassName.create("java/nio/FloatBuffer"),
+          ClassName.create("java/nio/DoubleBuffer"),
+          ClassName.create("java/nio/ShortBuffer"),
+          ClassName.create("java/nio/LongBuffer"),
+          ClassName.create("java/nio/ByteBuffer"));
+
+  /** Used to find the replacement method from the original method invocation specification. */
+  private final ImmutableMap<MethodKey, MethodKey> methodInvocationMappings;
+
+  /** The public factory API for this class. */
+  public static NioBufferRefConverter create(
+      ClassVisitor classVisitor, TypeMapper corePackagePrefixer) {
+    return new NioBufferRefConverter(
+        classVisitor, corePackagePrefixer.map(getMethodInvocationMappings()));
+  }
+
+  private NioBufferRefConverter(
+      ClassVisitor classVisitor, ImmutableMap<MethodKey, MethodKey> methodInvocationMappings) {
+    super(Opcodes.ASM7, classVisitor);
+    this.methodInvocationMappings = methodInvocationMappings;
+  }
+
+  @Override
+  public MethodVisitor visitMethod(
+      int access, String name, String descriptor, String signature, String[] exceptions) {
+    MethodVisitor mv = super.visitMethod(access, name, descriptor, signature, exceptions);
+    return mv == null ? null : new NioBufferMethodVisitor(api, mv, methodInvocationMappings);
+  }
+
+  /** Computes methods in Java NIO buffer family that are subject to invocation conversion. */
+  private static ImmutableMap<MethodKey, MethodKey> getMethodInvocationMappings() {
+    ImmutableMap.Builder<MethodKey, MethodKey> methodMappings = ImmutableMap.builder();
+    for (ClassName typeSpecificNioBuffer : TYPE_SPECIFIC_NIO_BUFFERS) {
+      for (MethodKey baseMethod : BASE_METHODS_WITH_COVARIANT_RETURN_TYPES) {
+        methodMappings.put(
+            MethodKey.create(
+                typeSpecificNioBuffer,
+                baseMethod.name(),
+                Type.getMethodDescriptor(
+                    typeSpecificNioBuffer.toAsmObjectType(), baseMethod.getArgumentTypeArray())),
+            MethodKey.create(typeSpecificNioBuffer, baseMethod.name(), baseMethod.descriptor()));
+      }
+    }
+    return methodMappings.build();
+  }
+
+  private static class NioBufferMethodVisitor extends MethodVisitor {
+
+    private final ImmutableMap<MethodKey, MethodKey> methodInvocationMappings;
+
+    NioBufferMethodVisitor(
+        int api,
+        MethodVisitor methodVisitor,
+        ImmutableMap<MethodKey, MethodKey> methodInvocationMappings) {
+      super(api, methodVisitor);
+      this.methodInvocationMappings = methodInvocationMappings;
+    }
+
+    @Override
+    public void visitMethodInsn(
+        int opcode, String owner, String name, String descriptor, boolean isInterface) {
+      MethodKey methodKey = MethodKey.create(ClassName.create(owner), name, descriptor);
+      if (methodInvocationMappings.containsKey(methodKey)) {
+        MethodKey mappedMethodKey = methodInvocationMappings.get(methodKey);
+        super.visitMethodInsn(
+            opcode,
+            mappedMethodKey.ownerName(),
+            mappedMethodKey.name(),
+            mappedMethodKey.descriptor(),
+            isInterface);
+        super.visitTypeInsn(Opcodes.CHECKCAST, owner);
+        return;
+      }
+      super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+    }
+  }
+}
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberKey.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberKey.java
index b80ca57..b10e6e7 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberKey.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberKey.java
@@ -16,6 +16,8 @@
 
 package com.google.devtools.build.android.desugar.langmodel;
 
+import org.objectweb.asm.Type;
+
 /** The key that indexes a class member, including fields, constructors and methods. */
 public abstract class ClassMemberKey<T extends ClassMemberKey<T>> implements TypeMappable<T> {
 
@@ -36,6 +38,11 @@
     return owner().binaryName();
   }
 
+  /** The asm type name of {@link #owner()} */
+  public final Type ownerAsmObjectType() {
+    return owner().toAsmObjectType();
+  }
+
   /** Whether member key represents a constructor. */
   public final boolean isConstructor() {
     return "<init>".equals(name());
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberRecord.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberRecord.java
index d6bd18c..94dfd4b 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberRecord.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassMemberRecord.java
@@ -55,12 +55,13 @@
   }
 
   /** Find all member keys that represent a constructor. */
-  public ImmutableList<ClassMemberKey> findAllConstructorMemberKeys() {
+  public ImmutableList<ClassMemberKey<?>> findAllConstructorMemberKeys() {
     return findAllMatchedMemberKeys(ClassMemberKey::isConstructor);
   }
 
   /** Find all member keys based on the given member key predicate. */
-  ImmutableList<ClassMemberKey> findAllMatchedMemberKeys(Predicate<ClassMemberKey> predicate) {
+  ImmutableList<ClassMemberKey<?>> findAllMatchedMemberKeys(
+      Predicate<ClassMemberKey<?>> predicate) {
     return reasons.keySet().stream().filter(predicate).collect(toImmutableList());
   }
 
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassName.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassName.java
index fb47b5f..7cb47c7 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassName.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/ClassName.java
@@ -44,7 +44,11 @@
   }
 
   public static ClassName create(Class<?> clazz) {
-    return create(Type.getInternalName(clazz));
+    return create(Type.getType(clazz));
+  }
+
+  public static ClassName create(Type asmType) {
+    return create(asmType.getInternalName());
   }
 
   public final Type toAsmObjectType() {
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodKey.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodKey.java
index 3e153f2..ca868c5 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodKey.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/MethodKey.java
@@ -19,7 +19,7 @@
 import static com.google.common.base.Preconditions.checkState;
 
 import com.google.auto.value.AutoValue;
-import java.util.Arrays;
+import com.google.common.collect.ImmutableList;
 import org.objectweb.asm.Type;
 
 /** The key to index a class or interface method or constructor. */
@@ -43,20 +43,32 @@
     return Type.getReturnType(descriptor());
   }
 
+  /** The return type of a method. */
+  public ClassName getReturnTypeName() {
+    return ClassName.create(Type.getReturnType(descriptor()));
+  }
+
   /** The formal parameter types of a method. */
-  public Type[] getArgumentTypes() {
+  public Type[] getArgumentTypeArray() {
     return Type.getArgumentTypes(descriptor());
   }
 
+  /** The formal parameter types of a method. */
+  public ImmutableList<Type> getArgumentTypes() {
+    return ImmutableList.copyOf(getArgumentTypeArray());
+  }
+
   /** The synthetic constructor for a private constructor. */
   public final MethodKey bridgeOfConstructor(ClassName nestCompanion) {
     checkState(isConstructor(), "Expect to use for a constructor but is %s", this);
     Type companionClassType = nestCompanion.toAsmObjectType();
-    Type[] argumentTypes = getArgumentTypes();
-    Type[] bridgeConstructorArgTypes = Arrays.copyOf(argumentTypes, argumentTypes.length + 1);
-    bridgeConstructorArgTypes[argumentTypes.length] = companionClassType;
+    ImmutableList<Type> argumentTypes = getArgumentTypes();
+    ImmutableList<Type> bridgeConstructorArgTypes =
+        ImmutableList.<Type>builder().addAll(argumentTypes).add(companionClassType).build();
     return create(
-        owner(), name(), Type.getMethodDescriptor(getReturnType(), bridgeConstructorArgTypes));
+        owner(),
+        name(),
+        Type.getMethodDescriptor(getReturnType(), bridgeConstructorArgTypes.toArray(new Type[0])));
   }
 
   /** The synthetic bridge method for a private static method in a class. */
@@ -84,11 +96,14 @@
   /** The descriptor of the static version of a given instance method. */
   private static String instanceMethodToStaticDescriptor(MethodKey methodKey) {
     checkState(!methodKey.isConstructor(), "Expect a Non-constructor method: %s", methodKey);
-    Type[] argumentTypes = methodKey.getArgumentTypes();
-    Type[] bridgeMethodArgTypes = new Type[argumentTypes.length + 1];
-    bridgeMethodArgTypes[0] = Type.getObjectType(methodKey.ownerName());
-    System.arraycopy(argumentTypes, 0, bridgeMethodArgTypes, 1, argumentTypes.length);
-    return Type.getMethodDescriptor(methodKey.getReturnType(), bridgeMethodArgTypes);
+    ImmutableList<Type> argumentTypes = methodKey.getArgumentTypes();
+    ImmutableList<Type> bridgeMethodArgTypes =
+        ImmutableList.<Type>builder()
+            .add(methodKey.ownerAsmObjectType())
+            .addAll(argumentTypes)
+            .build();
+    return Type.getMethodDescriptor(
+        methodKey.getReturnType(), bridgeMethodArgTypes.toArray(new Type[0]));
   }
 
   @Override
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/TypeMapper.java b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/TypeMapper.java
index 4e0d1e9..188c7f3 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/TypeMapper.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/langmodel/TypeMapper.java
@@ -16,6 +16,13 @@
 
 package com.google.devtools.build.android.desugar.langmodel;
 
+import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableMap.toImmutableMap;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
 import java.util.function.Function;
 import org.objectweb.asm.commons.Remapper;
 
@@ -36,4 +43,21 @@
   public ClassName map(ClassName internalName) {
     return classNameMapper.apply(internalName);
   }
+
+  public <E extends TypeMappable<E>> ImmutableList<? extends E> map(
+      ImmutableList<E> mappableTypes) {
+    return mappableTypes.stream().map(e -> e.acceptTypeMapper(this)).collect(toImmutableList());
+  }
+
+  public <E extends TypeMappable<E>> ImmutableSet<? extends E> map(ImmutableSet<E> mappableTypes) {
+    return mappableTypes.stream().map(e -> e.acceptTypeMapper(this)).collect(toImmutableSet());
+  }
+
+  public <K extends TypeMappable<K>, V extends TypeMappable<V>> ImmutableMap<K, V> map(
+      ImmutableMap<K, V> mappableTypes) {
+    return mappableTypes.entrySet().stream()
+        .collect(
+            toImmutableMap(
+                e -> e.getKey().acceptTypeMapper(this), e -> e.getValue().acceptTypeMapper(this)));
+  }
 }
diff --git a/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/RuntimeEntityResolver.java b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/RuntimeEntityResolver.java
index 6dce8d6..06b7888 100644
--- a/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/RuntimeEntityResolver.java
+++ b/src/tools/android/java/com/google/devtools/build/android/desugar/testing/junit/RuntimeEntityResolver.java
@@ -381,8 +381,7 @@
     if (restoredClassMemberKey == null || restoredClassMemberKey.isEmpty()) {
       throw new IllegalStateException(
           String.format(
-              "Unable to find class member (%s). Please check its presence.",
-              restoredClassMemberKey));
+              "Unable to find class member (%s). Please check its presence.", classMemberKey));
     } else if (restoredClassMemberKey.size() > 1) {
       throw new IllegalStateException(
           String.format(