Add MethodHandles.dropArgumentsToMatch() from OpenJDK 11.0.13
Bug: 191446452
Test: atest CtsLibcoreOjTestCases:test.java.lang.invoke.DropArgumentsTest
Change-Id: I8be5fe72b4890be2985cf688c3b152bfa57c3e04
diff --git a/api/current.txt b/api/current.txt
index d0374f0..dc1b700 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -4349,6 +4349,7 @@
method public static java.lang.invoke.MethodHandle constant(Class<?>, Object);
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 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);
diff --git a/ojluni/src/main/java/java/lang/invoke/MethodHandles.java b/ojluni/src/main/java/java/lang/invoke/MethodHandles.java
index 8e71d68..e80c1cc 100644
--- a/ojluni/src/main/java/java/lang/invoke/MethodHandles.java
+++ b/ojluni/src/main/java/java/lang/invoke/MethodHandles.java
@@ -3048,16 +3048,17 @@
return oldType.ptypes();
}
+ // Android-changed: inclusive language preference for 'placeholder'.
/**
- * Produces a method handle which will discard some dummy arguments
+ * Produces a method handle which will discard some placeholder arguments
* before calling some other specified <i>target</i> method handle.
* The type of the new method handle will be the same as the target's type,
- * except it will also include the dummy argument types,
+ * except it will also include the placeholder argument types,
* at some given position.
* <p>
* The {@code pos} argument may range between zero and <i>N</i>,
* where <i>N</i> is the arity of the target.
- * If {@code pos} is zero, the dummy arguments will precede
+ * If {@code pos} is zero, the placeholder arguments will precede
* the target's real arguments; if {@code pos} is <i>N</i>
* they will come after.
* <p>
@@ -3092,21 +3093,29 @@
*/
public static
MethodHandle dropArguments(MethodHandle target, int pos, List<Class<?>> valueTypes) {
- valueTypes = copyTypes(valueTypes);
- MethodType oldType = target.type(); // get NPE
- int dropped = dropArgumentChecks(oldType, pos, valueTypes);
-
- MethodType newType = oldType.insertParameterTypes(pos, valueTypes);
- if (dropped == 0) {
- return target;
- }
-
- return new Transformers.DropArguments(newType, target, pos, valueTypes.size());
+ return dropArguments0(target, pos, copyTypes(valueTypes.toArray()));
}
- private static List<Class<?>> copyTypes(List<Class<?>> types) {
- Object[] a = types.toArray();
- return Arrays.asList(Arrays.copyOf(a, a.length, Class[].class));
+ private static List<Class<?>> copyTypes(Object[] array) {
+ return Arrays.asList(Arrays.copyOf(array, array.length, Class[].class));
+ }
+
+ private static
+ MethodHandle dropArguments0(MethodHandle target, int pos, List<Class<?>> valueTypes) {
+ MethodType oldType = target.type(); // get NPE
+ int dropped = dropArgumentChecks(oldType, pos, valueTypes);
+ MethodType newType = oldType.insertParameterTypes(pos, valueTypes);
+ if (dropped == 0) return target;
+ // Android-changed: transformer implementation.
+ // BoundMethodHandle result = target.rebind();
+ // LambdaForm lform = result.form;
+ // int insertFormArg = 1 + pos;
+ // for (Class<?> ptype : valueTypes) {
+ // lform = lform.editor().addArgumentForm(insertFormArg++, BasicType.basicType(ptype));
+ // }
+ // result = result.copyWith(newType, lform);
+ // return result;
+ return new Transformers.DropArguments(newType, target, pos, dropped);
}
private static int dropArgumentChecks(MethodType oldType, int pos, List<Class<?>> valueTypes) {
@@ -3121,20 +3130,20 @@
return dropped;
}
+ // Android-changed: inclusive language preference for 'placeholder'.
/**
- * Produces a method handle which will discard some dummy arguments
+ * Produces a method handle which will discard some placeholder arguments
* before calling some other specified <i>target</i> method handle.
* The type of the new method handle will be the same as the target's type,
- * except it will also include the dummy argument types,
+ * except it will also include the placeholder argument types,
* at some given position.
* <p>
* The {@code pos} argument may range between zero and <i>N</i>,
* where <i>N</i> is the arity of the target.
- * If {@code pos} is zero, the dummy arguments will precede
+ * If {@code pos} is zero, the placeholder arguments will precede
* the target's real arguments; if {@code pos} is <i>N</i>
* they will come after.
- * <p>
- * <b>Example:</b>
+ * @apiNote
* <blockquote><pre>{@code
import static java.lang.invoke.MethodHandles.*;
import static java.lang.invoke.MethodType.*;
@@ -3170,7 +3179,117 @@
*/
public static
MethodHandle dropArguments(MethodHandle target, int pos, Class<?>... valueTypes) {
- return dropArguments(target, pos, Arrays.asList(valueTypes));
+ return dropArguments0(target, pos, copyTypes(valueTypes));
+ }
+
+ // private version which allows caller some freedom with error handling
+ private static MethodHandle dropArgumentsToMatch(MethodHandle target, int skip, List<Class<?>> newTypes, int pos,
+ boolean nullOnFailure) {
+ newTypes = copyTypes(newTypes.toArray());
+ List<Class<?>> oldTypes = target.type().parameterList();
+ int match = oldTypes.size();
+ if (skip != 0) {
+ if (skip < 0 || skip > match) {
+ throw newIllegalArgumentException("illegal skip", skip, target);
+ }
+ oldTypes = oldTypes.subList(skip, match);
+ match -= skip;
+ }
+ List<Class<?>> addTypes = newTypes;
+ int add = addTypes.size();
+ if (pos != 0) {
+ if (pos < 0 || pos > add) {
+ throw newIllegalArgumentException("illegal pos", pos, newTypes);
+ }
+ addTypes = addTypes.subList(pos, add);
+ add -= pos;
+ assert(addTypes.size() == add);
+ }
+ // Do not add types which already match the existing arguments.
+ if (match > add || !oldTypes.equals(addTypes.subList(0, match))) {
+ if (nullOnFailure) {
+ return null;
+ }
+ throw newIllegalArgumentException("argument lists do not match", oldTypes, newTypes);
+ }
+ addTypes = addTypes.subList(match, add);
+ add -= match;
+ assert(addTypes.size() == add);
+ // newTypes: ( P*[pos], M*[match], A*[add] )
+ // target: ( S*[skip], M*[match] )
+ MethodHandle adapter = target;
+ if (add > 0) {
+ adapter = dropArguments0(adapter, skip+ match, addTypes);
+ }
+ // adapter: (S*[skip], M*[match], A*[add] )
+ if (pos > 0) {
+ adapter = dropArguments0(adapter, skip, newTypes.subList(0, pos));
+ }
+ // adapter: (S*[skip], P*[pos], M*[match], A*[add] )
+ return adapter;
+ }
+
+ // Android-changed: inclusive language preference for 'placeholder'.
+ /**
+ * Adapts a target method handle to match the given parameter type list. If necessary, adds placeholder arguments. Some
+ * leading parameters can be skipped before matching begins. The remaining types in the {@code target}'s parameter
+ * type list must be a sub-list of the {@code newTypes} type list at the starting position {@code pos}. The
+ * resulting handle will have the target handle's parameter type list, with any non-matching parameter types (before
+ * or after the matching sub-list) inserted in corresponding positions of the target's original parameters, as if by
+ * {@link #dropArguments(MethodHandle, int, Class[])}.
+ * <p>
+ * The resulting handle will have the same return type as the target handle.
+ * <p>
+ * In more formal terms, assume these two type lists:<ul>
+ * <li>The target handle has the parameter type list {@code S..., M...}, with as many types in {@code S} as
+ * indicated by {@code skip}. The {@code M} types are those that are supposed to match part of the given type list,
+ * {@code newTypes}.
+ * <li>The {@code newTypes} list contains types {@code P..., M..., A...}, with as many types in {@code P} as
+ * indicated by {@code pos}. The {@code M} types are precisely those that the {@code M} types in the target handle's
+ * parameter type list are supposed to match. The types in {@code A} are additional types found after the matching
+ * sub-list.
+ * </ul>
+ * Given these assumptions, the result of an invocation of {@code dropArgumentsToMatch} will have the parameter type
+ * list {@code S..., P..., M..., A...}, with the {@code P} and {@code A} types inserted as if by
+ * {@link #dropArguments(MethodHandle, int, Class[])}.
+ *
+ * @apiNote
+ * Two method handles whose argument lists are "effectively identical" (i.e., identical in a common prefix) may be
+ * mutually converted to a common type by two calls to {@code dropArgumentsToMatch}, as follows:
+ * <blockquote><pre>{@code
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+...
+MethodHandle h0 = constant(boolean.class, true);
+MethodHandle h1 = lookup().findVirtual(String.class, "concat", methodType(String.class, String.class));
+MethodType bigType = h1.type().insertParameterTypes(1, String.class, int.class);
+MethodHandle h2 = dropArguments(h1, 0, bigType.parameterList());
+if (h1.type().parameterCount() < h2.type().parameterCount())
+ h1 = dropArgumentsToMatch(h1, 0, h2.type().parameterList(), 0); // lengthen h1
+else
+ h2 = dropArgumentsToMatch(h2, 0, h1.type().parameterList(), 0); // lengthen h2
+MethodHandle h3 = guardWithTest(h0, h1, h2);
+assertEquals("xy", h3.invoke("x", "y", 1, "a", "b", "c"));
+ * }</pre></blockquote>
+ * @param target the method handle to adapt
+ * @param skip number of targets parameters to disregard (they will be unchanged)
+ * @param newTypes the list of types to match {@code target}'s parameter type list to
+ * @param pos place in {@code newTypes} where the non-skipped target parameters must occur
+ * @return a possibly adapted method handle
+ * @throws NullPointerException if either argument is null
+ * @throws IllegalArgumentException if any element of {@code newTypes} is {@code void.class},
+ * or if {@code skip} is negative or greater than the arity of the target,
+ * or if {@code pos} is negative or greater than the newTypes list size,
+ * or if {@code newTypes} does not contain the {@code target}'s non-skipped parameter types at position
+ * {@code pos}.
+ * @since 9
+ */
+ public static
+ MethodHandle dropArgumentsToMatch(MethodHandle target, int skip, List<Class<?>> newTypes, int pos) {
+ Objects.requireNonNull(target);
+ Objects.requireNonNull(newTypes);
+ return dropArgumentsToMatch(target, skip, newTypes, pos, false);
}
/**
diff --git a/ojluni/src/main/java/java/lang/invoke/Transformers.java b/ojluni/src/main/java/java/lang/invoke/Transformers.java
index 17a2594..5dda0e3 100644
--- a/ojluni/src/main/java/java/lang/invoke/Transformers.java
+++ b/ojluni/src/main/java/java/lang/invoke/Transformers.java
@@ -115,14 +115,8 @@
/** Implements {@code MethodHandles.dropArguments}. */
public static class DropArguments extends Transformer {
private final MethodHandle delegate;
-
private final EmulatedStackFrame.Range range1;
-
- /**
- * Note that {@code range2} will be null if the arguments that are being dropped are the
- * last {@code n}.
- */
- /* @Nullable */ private final EmulatedStackFrame.Range range2;
+ private final EmulatedStackFrame.Range range2;
public DropArguments(MethodType type, MethodHandle delegate, int startPos, int numDropped) {
super(type);
diff --git a/ojluni/src/test/java/lang/invoke/DropArgumentsTest.java b/ojluni/src/test/java/lang/invoke/DropArgumentsTest.java
new file mode 100644
index 0000000..be65b39
--- /dev/null
+++ b/ojluni/src/test/java/lang/invoke/DropArgumentsTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2016, 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. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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.
+ */
+
+/* @test
+ * @bug 8158169
+ * @summary unit tests for java.lang.invoke.MethodHandles
+ * @run testng test.java.lang.invoke.DropArgumentsTest
+ */
+package test.java.lang.invoke;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
+import java.util.Collections;
+import java.util.List;
+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+import static org.testng.AssertJUnit.*;
+import org.testng.annotations.*;
+
+public class DropArgumentsTest {
+
+ @Test
+ public void testDropArgumentsToMatch() throws Throwable {
+ MethodHandle cat = lookup().findVirtual(String.class, "concat", methodType(String.class, String.class));
+ MethodType bigType = cat.type().insertParameterTypes(0, String.class, String.class, int.class);
+ MethodHandle d0 = MethodHandles.dropArgumentsToMatch(cat, 0, bigType.parameterList(), 3);
+ assertEquals("xy",(String)d0.invokeExact("m", "n", 1, "x", "y"));
+ MethodHandle d1 = MethodHandles.dropArgumentsToMatch(cat, 0, bigType.parameterList(), 0);
+ assertEquals("mn",(String)d1.invokeExact("m", "n", 1, "x", "y"));
+ MethodHandle d2 = MethodHandles.dropArgumentsToMatch(cat, 1, bigType.parameterList(), 4);
+ assertEquals("xy",(String)d2.invokeExact("x", "b", "c", 1, "a", "y"));
+
+ }
+
+ @DataProvider(name = "dropArgumentsToMatchNPEData")
+ private Object[][] dropArgumentsToMatchNPEData()
+ throws NoSuchMethodException, IllegalAccessException {
+ MethodHandle cat = lookup().findVirtual(String.class, "concat", methodType(String.class, String.class));
+ return new Object[][] {
+ { (MethodHandle) null, 0, cat.type().parameterList(), 0 },
+ { cat, 0, null, 0 }
+ };
+ }
+
+ @Test(dataProvider = "dropArgumentsToMatchNPEData")
+ @ExpectedExceptions(NullPointerException.class)
+ public void dropArgumentsToMatchNPE(MethodHandle target, int pos, List<Class<?>> valueType, int skip) {
+ MethodHandles.dropArgumentsToMatch(target, pos, valueType , skip);
+ }
+
+ @DataProvider(name = "dropArgumentsToMatchIAEData")
+ private Object[][] dropArgumentsToMatchIAEData()
+ throws NoSuchMethodException, IllegalAccessException {
+ MethodHandle cat = lookup().findVirtual(String.class, "concat", methodType(String.class, String.class));
+ MethodType bigType = cat.type().insertParameterTypes(0, String.class, String.class, int.class);
+ return new Object[][] {
+ {cat, -1, bigType.parameterList(), 0},
+ {cat, 0, bigType.parameterList(), -1},
+ {cat, 3, bigType.parameterList(), 0},
+ {cat, 0, bigType.parameterList(), 6},
+ {cat, 0, bigType.parameterList(), 2}
+ };
+ }
+
+ @Test(dataProvider = "dropArgumentsToMatchIAEData")
+ @ExpectedExceptions(IllegalArgumentException.class)
+ public void dropArgumentsToMatchIAE(MethodHandle target, int pos, List<Class<?>> valueType, int skip) {
+ MethodHandles.dropArgumentsToMatch(target, pos, valueType , skip);
+ }
+
+ @Test
+ @ExpectedExceptions(IllegalArgumentException.class)
+ public void dropArgumentsToMatchTestWithVoid() throws Throwable {
+ MethodHandle cat = lookup().findVirtual(String.class, "concat",
+ MethodType.methodType(String.class, String.class));
+ MethodType bigTypewithVoid = cat.type().insertParameterTypes(0, void.class, String.class, int.class);
+ MethodHandle handle2 = MethodHandles.dropArgumentsToMatch(cat, 0, bigTypewithVoid.parameterList(), 1);
+ }
+
+ public static class MethodSet {
+
+ static void mVoid() {
+
+ }
+
+ static void mVoid(int t) {
+
+ }
+ }
+
+ @Test
+ public void dropArgumentsToMatchPosSkipRange() throws Throwable {
+ // newTypes.size() == 1, pos == 1 && target.paramSize() == 0, skip == 0
+ MethodHandle mh1 = MethodHandles.lookup().findStatic(MethodSet.class, "mVoid",
+ MethodType.methodType(void.class));
+ MethodHandle handle1 = dropArgumentsToMatch(mh1, 0, Collections.singletonList(int.class), 1);
+ assertEquals(1, handle1.type().parameterList().size());
+
+ // newTypes.size() == 1, pos == 0 && target.paramSize() == 1, skip == 1
+ MethodHandle mh2 = MethodHandles.lookup().findStatic(MethodSet.class, "mVoid",
+ MethodType.methodType(void.class, int.class));
+ MethodHandle handle2 = dropArgumentsToMatch(mh2, 1, Collections.singletonList(int.class), 0);
+ assertEquals(2, handle2.type().parameterList().size());
+ }
+}
diff --git a/ojluni/src/test/java/lang/invoke/JavaDocExamplesTest.java b/ojluni/src/test/java/lang/invoke/JavaDocExamplesTest.java
index c9873df..ae7ae63 100644
--- a/ojluni/src/test/java/lang/invoke/JavaDocExamplesTest.java
+++ b/ojluni/src/test/java/lang/invoke/JavaDocExamplesTest.java
@@ -281,8 +281,6 @@
}}
}
-// Android-removed: dropArguments to match not yet supported (b/191446452).
-/*
@Test public void testDropArgumentsToMatch() throws Throwable {
{{
{} /// JAVADOC
@@ -300,7 +298,7 @@
assertEquals("xy", h3.invoke("x", "y", 1, "a", "b", "c"));
}}
}
-*/
+
@Test public void testFilterArguments() throws Throwable {
{{
{} /// JAVADOC