Add MethodHandles.{dropReturn,tableSwitch} from OpenJDK 17.0.2-ga

Bug: N/A
Test: atest CtsLibcoreOjTestCases:test.java.lang.invoke
Change-Id: I52ed2d781448faf53a101a5827f523a3e5c0d23c
(cherry picked from commit a2d3555f8feb3ba77c6e357a68b0f5dcc96001e9)
Merged-In: I52ed2d781448faf53a101a5827f523a3e5c0d23c
diff --git a/api/current.txt b/api/current.txt
index fb751ea..db95a21 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -4357,6 +4357,7 @@
     method public static java.lang.invoke.MethodHandle dropArguments(java.lang.invoke.MethodHandle, int, java.util.List<java.lang.Class<?>>);
     method public static java.lang.invoke.MethodHandle dropArguments(java.lang.invoke.MethodHandle, int, Class<?>...);
     method public static java.lang.invoke.MethodHandle dropArgumentsToMatch(java.lang.invoke.MethodHandle, int, java.util.List<java.lang.Class<?>>, int);
+    method public static java.lang.invoke.MethodHandle dropReturn(java.lang.invoke.MethodHandle);
     method public static java.lang.invoke.MethodHandle empty(java.lang.invoke.MethodType);
     method public static java.lang.invoke.MethodHandle exactInvoker(java.lang.invoke.MethodType);
     method public static java.lang.invoke.MethodHandle explicitCastArguments(java.lang.invoke.MethodHandle, java.lang.invoke.MethodType);
@@ -4376,6 +4377,7 @@
     method public static java.lang.invoke.MethodHandles.Lookup publicLookup();
     method public static <T extends java.lang.reflect.Member> T reflectAs(Class<T>, java.lang.invoke.MethodHandle);
     method public static java.lang.invoke.MethodHandle spreadInvoker(java.lang.invoke.MethodType, int);
+    method public static java.lang.invoke.MethodHandle tableSwitch(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle...);
     method public static java.lang.invoke.MethodHandle throwException(Class<?>, Class<? extends java.lang.Throwable>);
     method public static java.lang.invoke.MethodHandle tryFinally(java.lang.invoke.MethodHandle, java.lang.invoke.MethodHandle);
     method public static java.lang.invoke.MethodHandle varHandleExactInvoker(java.lang.invoke.VarHandle.AccessMode, java.lang.invoke.MethodType);
diff --git a/ojluni/src/main/java/java/lang/invoke/MethodHandles.java b/ojluni/src/main/java/java/lang/invoke/MethodHandles.java
index d5d4448..9f68d6c 100644
--- a/ojluni/src/main/java/java/lang/invoke/MethodHandles.java
+++ b/ojluni/src/main/java/java/lang/invoke/MethodHandles.java
@@ -3296,6 +3296,31 @@
     }
 
     /**
+     * Drop the return value of the target handle (if any).
+     * The returned method handle will have a {@code void} return type.
+     *
+     * @param target the method handle to adapt
+     * @return a possibly adapted method handle
+     * @throws NullPointerException if {@code target} is null
+     * @since 16
+     */
+    public static MethodHandle dropReturn(MethodHandle target) {
+        Objects.requireNonNull(target);
+        MethodType oldType = target.type();
+        Class<?> oldReturnType = oldType.returnType();
+        if (oldReturnType == void.class)
+            return target;
+
+        MethodType newType = oldType.changeReturnType(void.class);
+        // Android-changed: no support for BoundMethodHandle or LambdaForm.
+        // BoundMethodHandle result = target.rebind();
+        // LambdaForm lform = result.editor().filterReturnForm(V_TYPE, true);
+        // result = result.copyWith(newType, lform);
+        // return result;
+        return target.asType(newType);
+    }
+
+    /**
      * Adapts a target method handle by pre-processing
      * one or more of its arguments, each with its own unary filter function,
      * and then calling the target with each pre-processed argument
@@ -5450,6 +5475,96 @@
         }
     }
 
+    /**
+     * Creates a table switch method handle, which can be used to switch over a set of target
+     * method handles, based on a given target index, called selector.
+     * <p>
+     * For a selector value of {@code n}, where {@code n} falls in the range {@code [0, N)},
+     * and where {@code N} is the number of target method handles, the table switch method
+     * handle will invoke the n-th target method handle from the list of target method handles.
+     * <p>
+     * For a selector value that does not fall in the range {@code [0, N)}, the table switch
+     * method handle will invoke the given fallback method handle.
+     * <p>
+     * All method handles passed to this method must have the same type, with the additional
+     * requirement that the leading parameter be of type {@code int}. The leading parameter
+     * represents the selector.
+     * <p>
+     * Any trailing parameters present in the type will appear on the returned table switch
+     * method handle as well. Any arguments assigned to these parameters will be forwarded,
+     * together with the selector value, to the selected method handle when invoking it.
+     *
+     * @apiNote Example:
+     * The cases each drop the {@code selector} value they are given, and take an additional
+     * {@code String} argument, which is concatenated (using {@link String#concat(String)})
+     * to a specific constant label string for each case:
+     * <blockquote><pre>{@code
+     * MethodHandles.Lookup lookup = MethodHandles.lookup();
+     * MethodHandle caseMh = lookup.findVirtual(String.class, "concat",
+     *         MethodType.methodType(String.class, String.class));
+     * caseMh = MethodHandles.dropArguments(caseMh, 0, int.class);
+     *
+     * MethodHandle caseDefault = MethodHandles.insertArguments(caseMh, 1, "default: ");
+     * MethodHandle case0 = MethodHandles.insertArguments(caseMh, 1, "case 0: ");
+     * MethodHandle case1 = MethodHandles.insertArguments(caseMh, 1, "case 1: ");
+     *
+     * MethodHandle mhSwitch = MethodHandles.tableSwitch(
+     *     caseDefault,
+     *     case0,
+     *     case1
+     * );
+     *
+     * assertEquals("default: data", (String) mhSwitch.invokeExact(-1, "data"));
+     * assertEquals("case 0: data", (String) mhSwitch.invokeExact(0, "data"));
+     * assertEquals("case 1: data", (String) mhSwitch.invokeExact(1, "data"));
+     * assertEquals("default: data", (String) mhSwitch.invokeExact(2, "data"));
+     * }</pre></blockquote>
+     *
+     * @param fallback the fallback method handle that is called when the selector is not
+     *                 within the range {@code [0, N)}.
+     * @param targets array of target method handles.
+     * @return the table switch method handle.
+     * @throws NullPointerException if {@code fallback}, the {@code targets} array, or any
+     *                              any of the elements of the {@code targets} array are
+     *                              {@code null}.
+     * @throws IllegalArgumentException if the {@code targets} array is empty, if the leading
+     *                                  parameter of the fallback handle or any of the target
+     *                                  handles is not {@code int}, or if the types of
+     *                                  the fallback handle and all of target handles are
+     *                                  not the same.
+     */
+    public static MethodHandle tableSwitch(MethodHandle fallback, MethodHandle... targets) {
+        Objects.requireNonNull(fallback);
+        Objects.requireNonNull(targets);
+        targets = targets.clone();
+        MethodType type = tableSwitchChecks(fallback, targets);
+        // Android-changed: use a Transformer for the implementation.
+        // return MethodHandleImpl.makeTableSwitch(type, fallback, targets);
+        return new Transformers.TableSwitch(type, fallback, targets);
+    }
+
+    private static MethodType tableSwitchChecks(MethodHandle defaultCase, MethodHandle[] caseActions) {
+        if (caseActions.length == 0)
+            throw new IllegalArgumentException("Not enough cases: " + Arrays.toString(caseActions));
+
+        MethodType expectedType = defaultCase.type();
+
+        if (!(expectedType.parameterCount() >= 1) || expectedType.parameterType(0) != int.class)
+            throw new IllegalArgumentException(
+                "Case actions must have int as leading parameter: " + Arrays.toString(caseActions));
+
+        for (MethodHandle mh : caseActions) {
+            Objects.requireNonNull(mh);
+            // Android-changed: MethodType's not interned.
+            // if (mh.type() != expectedType)
+            if (!mh.type().equals(expectedType))
+                throw new IllegalArgumentException(
+                    "Case actions must have the same type: " + Arrays.toString(caseActions));
+        }
+
+        return expectedType;
+    }
+
     // BEGIN Android-added: Code from OpenJDK's MethodHandleImpl.
 
     /**
diff --git a/ojluni/src/main/java/java/lang/invoke/Transformers.java b/ojluni/src/main/java/java/lang/invoke/Transformers.java
index 590b9a7..5183cf1 100644
--- a/ojluni/src/main/java/java/lang/invoke/Transformers.java
+++ b/ojluni/src/main/java/java/lang/invoke/Transformers.java
@@ -3110,4 +3110,34 @@
             finiFrame.copyReturnValueTo(callerFrame);
         }
     }
+
+    /** Implements {@code MethodHandles.tableSwitch}. */
+    static class TableSwitch extends Transformer {
+        private final MethodHandle fallback;
+        private final MethodHandle[] targets;
+
+        TableSwitch(MethodType type, MethodHandle fallback, MethodHandle[] targets) {
+            super(type);
+            this.fallback = fallback;
+            this.targets = targets;
+        }
+
+        @Override
+        public void transform(EmulatedStackFrame callerFrame) throws Throwable {
+            final MethodHandle selected = selectMethodHandle(callerFrame);
+            invokeExactFromTransform(selected, callerFrame);
+        }
+
+        private MethodHandle selectMethodHandle(EmulatedStackFrame callerFrame) {
+            StackFrameReader reader = new StackFrameReader();
+            reader.attach(callerFrame);
+            final int index = reader.nextInt();
+
+            if (index >= 0 && index < targets.length) {
+                return targets[index];
+            } else {
+                return fallback;
+            }
+        }
+    }
 }
diff --git a/ojluni/src/test/java/lang/invoke/MethodHandles/TestDropReturn.java b/ojluni/src/test/java/lang/invoke/MethodHandles/TestDropReturn.java
new file mode 100644
index 0000000..f0b83e9
--- /dev/null
+++ b/ojluni/src/test/java/lang/invoke/MethodHandles/TestDropReturn.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+// Android-added: package declaration.
+package test.java.lang.invoke.MethodHandles;
+
+/*
+ * @test
+ * @bug 8255398
+ * @run testng TestDropReturn
+ */
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+
+import static java.lang.invoke.MethodType.methodType;
+import static org.testng.Assert.assertEquals;
+
+public class TestDropReturn {
+
+    @Test(dataProvider = "dropReturnCases")
+    public void testDropReturn(Class<?> cls, Object testValue) throws Throwable {
+        MethodHandle mh = MethodHandles.identity(cls);
+        assertEquals(mh.type(), methodType(cls, cls));
+        Object x = mh.invoke(testValue);
+        assertEquals(x, testValue);
+
+        mh = MethodHandles.dropReturn(mh);
+        assertEquals(mh.type(), methodType(void.class, cls));
+        mh.invoke(testValue); // should at least work
+    }
+
+    @DataProvider
+    public static Object[][] dropReturnCases() {
+        return new Object[][]{
+            { boolean.class, true         },
+            { byte.class,    (byte) 10    },
+            { char.class,    'x'          },
+            { short.class,   (short) 10   },
+            { int.class,     10           },
+            { long.class,    10L          },
+            { float.class,   10F          },
+            { double.class,  10D          },
+            { Object.class,  new Object() },
+            { String.class,  "ABCD"       },
+        };
+    }
+}
diff --git a/ojluni/src/test/java/lang/invoke/MethodHandles/TestTableSwitch.java b/ojluni/src/test/java/lang/invoke/MethodHandles/TestTableSwitch.java
new file mode 100644
index 0000000..ad13c86
--- /dev/null
+++ b/ojluni/src/test/java/lang/invoke/MethodHandles/TestTableSwitch.java
@@ -0,0 +1,236 @@
+/*
+ * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+// Android-added: package declaration.
+package test.java.lang.invoke.MethodHandles;
+
+/*
+ * @test
+ * @run testng/othervm -Xverify:all TestTableSwitch
+ */
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.IntConsumer;
+import java.util.function.IntFunction;
+
+import static org.testng.Assert.assertEquals;
+
+public class TestTableSwitch {
+
+    static final MethodHandle MH_IntConsumer_accept;
+    static final MethodHandle MH_check;
+
+    static {
+        try {
+            MethodHandles.Lookup lookup = MethodHandles.lookup();
+            MH_IntConsumer_accept = lookup.findVirtual(IntConsumer.class, "accept",
+                    MethodType.methodType(void.class, int.class));
+            MH_check = lookup.findStatic(TestTableSwitch.class, "check",
+                    MethodType.methodType(void.class, List.class, Object[].class));
+        } catch (ReflectiveOperationException e) {
+            throw new ExceptionInInitializerError(e);
+        }
+    }
+
+    public static MethodHandle simpleTestCase(String value) {
+        return simpleTestCase(String.class, value);
+    }
+
+    public static MethodHandle simpleTestCase(Class<?> type, Object value) {
+        return MethodHandles.dropArguments(MethodHandles.constant(type, value), 0, int.class);
+    }
+
+    public static Object testValue(Class<?> type) {
+        if (type == String.class) {
+            return "X";
+        } else if (type == byte.class) {
+            return (byte) 42;
+        } else if (type == short.class) {
+            return (short) 84;
+        } else if (type == char.class) {
+            return 'Y';
+        } else if (type == int.class) {
+            return 168;
+        } else if (type == long.class) {
+            return 336L;
+        } else if (type == float.class) {
+            return 42F;
+        } else if (type == double.class) {
+            return 84D;
+        } else if (type == boolean.class) {
+            return true;
+        }
+        return null;
+    }
+
+    static final Class<?>[] TEST_TYPES = {
+        Object.class,
+        String.class,
+        byte.class,
+        short.class,
+        char.class,
+        int.class,
+        long.class,
+        float.class,
+        double.class,
+        boolean.class
+    };
+
+    public static Object[] testArguments(int caseNum, List<Object> testValues) {
+        Object[] args = new Object[testValues.size() + 1];
+        args[0] = caseNum;
+        int insertPos = 1;
+        for (Object testValue : testValues) {
+            args[insertPos++] = testValue;
+        }
+        return args;
+    }
+
+    @DataProvider
+    public static Object[][] nonVoidCases() {
+        List<Object[]> tests = new ArrayList<>();
+
+        for (Class<?> returnType : TEST_TYPES) {
+            for (int numCases = 1; numCases < 5; numCases++) {
+                tests.add(new Object[] { returnType, numCases, List.of() });
+                tests.add(new Object[] { returnType, numCases, List.of(TEST_TYPES) });
+            }
+        }
+
+        return tests.toArray(Object[][]::new);
+    }
+
+    private static void check(List<Object> testValues, Object[] collectedValues) {
+        assertEquals(collectedValues, testValues.toArray());
+    }
+
+    @Test(dataProvider = "nonVoidCases")
+    public void testNonVoidHandles(Class<?> type, int numCases, List<Class<?>> additionalTypes) throws Throwable {
+        MethodHandle collector = MH_check;
+        List<Object> testArguments = new ArrayList<>();
+        collector = MethodHandles.insertArguments(collector, 0, testArguments);
+        collector = collector.asCollector(Object[].class, additionalTypes.size());
+
+        Object defaultReturnValue = testValue(type);
+        MethodHandle defaultCase = simpleTestCase(type, defaultReturnValue);
+        defaultCase = MethodHandles.collectArguments(defaultCase, 1, collector);
+        Object[] returnValues = new Object[numCases];
+        MethodHandle[] cases = new MethodHandle[numCases];
+        for (int i = 0; i < cases.length; i++) {
+            Object returnValue = testValue(type);
+            returnValues[i] = returnValue;
+            MethodHandle theCase = simpleTestCase(type, returnValue);
+            theCase = MethodHandles.collectArguments(theCase, 1, collector);
+            cases[i] = theCase;
+        }
+
+        MethodHandle mhSwitch = MethodHandles.tableSwitch(
+            defaultCase,
+            cases
+        );
+
+        for (Class<?> additionalType : additionalTypes) {
+            testArguments.add(testValue(additionalType));
+        }
+
+        assertEquals(mhSwitch.invokeWithArguments(testArguments(-1, testArguments)), defaultReturnValue);
+
+        for (int i = 0; i < numCases; i++) {
+            assertEquals(mhSwitch.invokeWithArguments(testArguments(i, testArguments)), returnValues[i]);
+        }
+
+        assertEquals(mhSwitch.invokeWithArguments(testArguments(numCases, testArguments)), defaultReturnValue);
+    }
+
+    @Test
+    public void testVoidHandles() throws Throwable {
+        IntFunction<MethodHandle> makeTestCase = expectedIndex -> {
+            IntConsumer test = actualIndex -> assertEquals(actualIndex, expectedIndex);
+            return MH_IntConsumer_accept.bindTo(test);
+        };
+
+        MethodHandle mhSwitch = MethodHandles.tableSwitch(
+            /* default: */ makeTestCase.apply(-1),
+            /* case 0: */  makeTestCase.apply(0),
+            /* case 1: */  makeTestCase.apply(1),
+            /* case 2: */  makeTestCase.apply(2)
+        );
+
+        mhSwitch.invokeExact((int) -1);
+        mhSwitch.invokeExact((int) 0);
+        mhSwitch.invokeExact((int) 1);
+        mhSwitch.invokeExact((int) 2);
+    }
+
+    @Test(expectedExceptions = NullPointerException.class)
+    public void testNullDefaultHandle() {
+        MethodHandles.tableSwitch(null, simpleTestCase("test"));
+    }
+
+    @Test(expectedExceptions = NullPointerException.class)
+    public void testNullCases() {
+        MethodHandle[] cases = null;
+        MethodHandles.tableSwitch(simpleTestCase("default"), cases);
+    }
+
+    @Test(expectedExceptions = NullPointerException.class)
+    public void testNullCase() {
+        MethodHandles.tableSwitch(simpleTestCase("default"), simpleTestCase("case"), null);
+    }
+
+    @Test(expectedExceptions = IllegalArgumentException.class,
+          expectedExceptionsMessageRegExp = ".*Not enough cases.*")
+    public void testNotEnoughCases() {
+        MethodHandles.tableSwitch(simpleTestCase("default"));
+    }
+
+    @Test(expectedExceptions = IllegalArgumentException.class,
+          expectedExceptionsMessageRegExp = ".*Case actions must have int as leading parameter.*")
+    public void testNotEnoughParameters() {
+        MethodHandle empty = MethodHandles.empty(MethodType.methodType(void.class));
+        MethodHandles.tableSwitch(empty, empty, empty);
+    }
+
+    @Test(expectedExceptions = IllegalArgumentException.class,
+          expectedExceptionsMessageRegExp = ".*Case actions must have int as leading parameter.*")
+    public void testNoLeadingIntParameter() {
+        MethodHandle empty = MethodHandles.empty(MethodType.methodType(void.class, double.class));
+        MethodHandles.tableSwitch(empty, empty, empty);
+    }
+
+    @Test(expectedExceptions = IllegalArgumentException.class,
+          expectedExceptionsMessageRegExp = ".*Case actions must have the same type.*")
+    public void testWrongCaseType() {
+        // doesn't return a String
+        MethodHandle wrongType = MethodHandles.empty(MethodType.methodType(void.class, int.class));
+        MethodHandles.tableSwitch(simpleTestCase("default"), simpleTestCase("case"), wrongType);
+    }
+
+}