diff --git a/src/share/classes/java/lang/invoke/MethodHandles.java b/src/share/classes/java/lang/invoke/MethodHandles.java index 77a65385121a7740bc786ff5049b96a85bf661be..0b55bfb48305e1a8ffeaa14fa24f71bc83bd4105 100644 --- a/src/share/classes/java/lang/invoke/MethodHandles.java +++ b/src/share/classes/java/lang/invoke/MethodHandles.java @@ -2216,15 +2216,120 @@ assertEquals("XY", (String) f2.invokeExact("x", "y")); // XY return MethodHandleImpl.makeCollectArguments(target, filter, pos, false); } - // FIXME: Make this public in M1. - /*non-public*/ static - MethodHandle collectArguments(MethodHandle target, int pos, MethodHandle collector) { + /** + * Adapts a target method handle by pre-processing + * a sub-sequence of its arguments with a filter (another method handle). + * The pre-processed arguments are replaced by the result (if any) of the + * filter function. + * The target is then called on the modified (usually shortened) argument list. + *

+ * If the filter returns a value, the target must accept that value as + * its argument in position {@code pos}, preceded and/or followed by + * any arguments not passed to the filter. + * If the filter returns void, the target must accept all arguments + * not passed to the filter. + * No arguments are reordered, and a result returned from the filter + * replaces (in order) the whole subsequence of arguments originally + * passed to the adapter. + *

+ * The argument types (if any) of the filter + * replace zero or one argument types of the target, at position {@code pos}, + * in the resulting adapted method handle. + * The return type of the filter (if any) must be identical to the + * argument type of the target at position {@code pos}, and that target argument + * is supplied by the return value of the filter. + *

+ * In all cases, {@code pos} must be greater than or equal to zero, and + * {@code pos} must also be less than or equal to the target's arity. + *

Example: + *

+import static java.lang.invoke.MethodHandles.*;
+import static java.lang.invoke.MethodType.*;
+...
+MethodHandle deepToString = publicLookup()
+  .findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class));
+
+MethodHandle ts1 = deepToString.asCollector(String[].class, 1);
+assertEquals("[strange]", (String) ts1.invokeExact("strange"));
+
+MethodHandle ts2 = deepToString.asCollector(String[].class, 2);
+assertEquals("[up, down]", (String) ts2.invokeExact("up", "down"));
+
+MethodHandle ts3 = deepToString.asCollector(String[].class, 3);
+MethodHandle ts3_ts2 = collectArguments(ts3, 1, ts2);
+assertEquals("[top, [up, down], strange]",
+             (String) ts3_ts2.invokeExact("top", "up", "down", "strange"));
+
+MethodHandle ts3_ts2_ts1 = collectArguments(ts3_ts2, 3, ts1);
+assertEquals("[top, [up, down], [strange]]",
+             (String) ts3_ts2_ts1.invokeExact("top", "up", "down", "strange"));
+
+MethodHandle ts3_ts2_ts3 = collectArguments(ts3_ts2, 1, ts3);
+assertEquals("[top, [[up, down, strange], charm], bottom]",
+             (String) ts3_ts2_ts3.invokeExact("top", "up", "down", "strange", "charm", "bottom"));
+     * 
+ *

Here is pseudocode for the resulting adapter: + *

+     * T target(A...,V,C...);
+     * V filter(B...);
+     * T adapter(A... a,B... b,C... c) {
+     *   V v = filter(b...);
+     *   return target(a...,v,c...);
+     * }
+     * // and if the filter has no arguments:
+     * T target2(A...,V,C...);
+     * V filter2();
+     * T adapter2(A... a,C... c) {
+     *   V v = filter2();
+     *   return target2(a...,v,c...);
+     * }
+     * // and if the filter has a void return:
+     * T target3(A...,C...);
+     * void filter3(B...);
+     * void adapter3(A... a,B... b,C... c) {
+     *   filter3(b...);
+     *   return target3(a...,c...);
+     * }
+     * 
+ *

+ * A collection adapter {@code collectArguments(mh, 0, coll)} is equivalent to + * one which first "folds" the affected arguments, and then drops them, in separate + * steps as follows: + *

{@code
+     * mh = MethodHandles.dropArguments(mh, 1, coll.type().parameterList()); //step 2
+     * mh = MethodHandles.foldArguments(mh, coll); //step 1
+     * }
+ * If the target method handle consumes no arguments besides than the result + * (if any) of the filter {@code coll}, then {@code collectArguments(mh, 0, coll)} + * is equivalent to {@code filterReturnValue(coll, mh)}. + * If the filter method handle {@code coll} consumes one argument and produces + * a non-void result, then {@code collectArguments(mh, N, coll)} + * is equivalent to {@code filterArguments(mh, N, coll)}. + * Other equivalences are possible but would require argument permutation. + * + * @param target the method handle to invoke after filtering the subsequence of arguments + * @param pos the position of the first adapter argument to pass to the filter, + * and/or the target argument which receives the result of the filter + * @param filter method handle to call on the subsequence of arguments + * @return method handle which incorporates the specified argument subsequence filtering logic + * @throws NullPointerException if either argument is null + * @throws IllegalArgumentException if the return type of {@code filter} + * is non-void and is not the same as the {@code pos} argument of the target, + * or if {@code pos} is not between 0 and the target's arity, inclusive, + * or if the resulting method handle's type would have + * too many parameters + * @see MethodHandles#foldArguments + * @see MethodHandles#filterArguments + * @see MethodHandles#filterReturnValue + */ + public static + MethodHandle collectArguments(MethodHandle target, int pos, MethodHandle filter) { MethodType targetType = target.type(); - MethodType filterType = collector.type(); + MethodType filterType = filter.type(); if (filterType.returnType() != void.class && filterType.returnType() != targetType.parameterType(pos)) throw newIllegalArgumentException("target and filter types do not match", targetType, filterType); - return MethodHandleImpl.makeCollectArguments(target, collector, pos, false); + return MethodHandleImpl.makeCollectArguments(target, filter, pos, false); } /** diff --git a/src/share/classes/sun/invoke/util/ValueConversions.java b/src/share/classes/sun/invoke/util/ValueConversions.java index 912fab4d99e98463863745f433e889cfd053e679..1158ccb890a2098c4e09cb05f693dabfafbd6ec9 100644 --- a/src/share/classes/sun/invoke/util/ValueConversions.java +++ b/src/share/classes/sun/invoke/util/ValueConversions.java @@ -502,51 +502,6 @@ public class ValueConversions { } } - static MethodHandle collectArguments(MethodHandle mh, int pos, MethodHandle collector) { - // FIXME: API needs public MHs.collectArguments. - // Should be: - // return MethodHandles.collectArguments(mh, 0, collector); - // The rest of this code is a workaround for not having that API. - if (COLLECT_ARGUMENTS != null) { - try { - return (MethodHandle) - COLLECT_ARGUMENTS.invokeExact(mh, pos, collector); - } catch (Throwable ex) { - if (ex instanceof RuntimeException) - throw (RuntimeException) ex; - if (ex instanceof Error) - throw (Error) ex; - throw new Error(ex.getMessage(), ex); - } - } - // Emulate MHs.collectArguments using fold + drop. - // This is slightly inefficient. - // More seriously, it can put a MH over the 255-argument limit. - mh = MethodHandles.dropArguments(mh, 1, collector.type().parameterList()); - mh = MethodHandles.foldArguments(mh, collector); - return mh; - } - private static final MethodHandle COLLECT_ARGUMENTS; - static { - MethodHandle mh = null; - try { - final java.lang.reflect.Method m = MethodHandles.class - .getDeclaredMethod("collectArguments", - MethodHandle.class, int.class, MethodHandle.class); - AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Void run() { - m.setAccessible(true); - return null; - } - }); - mh = IMPL_LOOKUP.unreflect(m); - } catch (ReflectiveOperationException ex) { - throw newInternalError(ex); - } - COLLECT_ARGUMENTS = mh; - } - private static final EnumMap[] WRAPPER_CASTS = newWrapperCaches(1); @@ -1050,12 +1005,12 @@ public class ValueConversions { if (mh == ARRAY_IDENTITY) mh = rightFiller; else - mh = collectArguments(mh, 0, rightFiller); + mh = MethodHandles.collectArguments(mh, 0, rightFiller); } if (mh == ARRAY_IDENTITY) mh = leftCollector; else - mh = collectArguments(mh, 0, leftCollector); + mh = MethodHandles.collectArguments(mh, 0, leftCollector); return mh; } @@ -1101,7 +1056,7 @@ public class ValueConversions { if (midLen == LEFT_ARGS) return rightFill; else - return collectArguments(rightFill, 0, midFill); + return MethodHandles.collectArguments(rightFill, 0, midFill); } // Type-polymorphic version of varargs maker. diff --git a/test/java/lang/invoke/JavaDocExamplesTest.java b/test/java/lang/invoke/JavaDocExamplesTest.java index 982bfe90054f375053810e469c51ac36eebcef1c..194564f2f913dde049c25f25f98e64699a51326f 100644 --- a/test/java/lang/invoke/JavaDocExamplesTest.java +++ b/test/java/lang/invoke/JavaDocExamplesTest.java @@ -281,6 +281,28 @@ assertEquals("XY", (String) f2.invokeExact("x", "y")); // XY }} } + @Test public void testCollectArguments() throws Throwable { + {{ +{} /// JAVADOC +MethodHandle deepToString = publicLookup() + .findStatic(Arrays.class, "deepToString", methodType(String.class, Object[].class)); +MethodHandle ts1 = deepToString.asCollector(String[].class, 1); +assertEquals("[strange]", (String) ts1.invokeExact("strange")); +MethodHandle ts2 = deepToString.asCollector(String[].class, 2); +assertEquals("[up, down]", (String) ts2.invokeExact("up", "down")); +MethodHandle ts3 = deepToString.asCollector(String[].class, 3); +MethodHandle ts3_ts2 = collectArguments(ts3, 1, ts2); +assertEquals("[top, [up, down], strange]", + (String) ts3_ts2.invokeExact("top", "up", "down", "strange")); +MethodHandle ts3_ts2_ts1 = collectArguments(ts3_ts2, 3, ts1); +assertEquals("[top, [up, down], [strange]]", + (String) ts3_ts2_ts1.invokeExact("top", "up", "down", "strange")); +MethodHandle ts3_ts2_ts3 = collectArguments(ts3_ts2, 1, ts3); +assertEquals("[top, [[up, down, strange], charm], bottom]", + (String) ts3_ts2_ts3.invokeExact("top", "up", "down", "strange", "charm", "bottom")); + }} + } + @Test public void testFoldArguments() throws Throwable { {{ {} /// JAVADOC diff --git a/test/java/lang/invoke/MethodHandlesTest.java b/test/java/lang/invoke/MethodHandlesTest.java index b77f74e3d261923634648d4aea8f253af03bbb78..53eb09cbfcd8020a059fe463c00068782bf302ca 100644 --- a/test/java/lang/invoke/MethodHandlesTest.java +++ b/test/java/lang/invoke/MethodHandlesTest.java @@ -277,6 +277,9 @@ public class MethodHandlesTest { args[i] = randomArg(param); return args; } + static Object[] randomArgs(List> params) { + return randomArgs(params.toArray(new Class[params.size()])); + } @SafeVarargs @SuppressWarnings("varargs") static T[] array(Class atype, E... a) { @@ -347,6 +350,11 @@ public class MethodHandlesTest { } return list.asType(listType); } + /** Variation of varargsList, but with the given ptypes and rtype. */ + static MethodHandle varargsList(List> ptypes, Class rtype) { + MethodHandle list = varargsList(ptypes.size(), rtype); + return list.asType(MethodType.methodType(rtype, ptypes)); + } private static MethodHandle LIST_TO_STRING, LIST_TO_INT; private static String listToString(List x) { return x.toString(); } private static int listToInt(List x) { return x.toString().hashCode(); } @@ -1833,24 +1841,24 @@ public class MethodHandlesTest { } @Test // SLOW - public void testCollectArguments() throws Throwable { + public void testAsCollector() throws Throwable { if (CAN_SKIP_WORKING) return; - startTest("collectArguments"); + startTest("asCollector"); for (Class argType : new Class[]{Object.class, Integer.class, int.class}) { if (verbosity >= 3) - System.out.println("collectArguments "+argType); + System.out.println("asCollector "+argType); for (int nargs = 0; nargs < 50; nargs++) { if (CAN_TEST_LIGHTLY && nargs > 11) break; for (int pos = 0; pos <= nargs; pos++) { if (CAN_TEST_LIGHTLY && pos > 2 && pos < nargs-2) continue; if (nargs > 10 && pos > 4 && pos < nargs-4 && pos % 10 != 3) continue; - testCollectArguments(argType, pos, nargs); + testAsCollector(argType, pos, nargs); } } } } - public void testCollectArguments(Class argType, int pos, int nargs) throws Throwable { + public void testAsCollector(Class argType, int pos, int nargs) throws Throwable { countTest(); // fake up a MH with the same type as the desired adapter: MethodHandle fake = varargsArray(nargs); @@ -1996,38 +2004,109 @@ public class MethodHandlesTest { assertEquals(expected, result); } + @Test + public void testCollectArguments() throws Throwable { + if (CAN_SKIP_WORKING) return; + startTest("collectArguments"); + testFoldOrCollectArguments(true); + } + @Test public void testFoldArguments() throws Throwable { if (CAN_SKIP_WORKING) return; startTest("foldArguments"); - for (int nargs = 0; nargs <= 4; nargs++) { - for (int fold = 0; fold <= nargs; fold++) { - for (int pos = 0; pos <= nargs; pos++) { - testFoldArguments(nargs, pos, fold); + testFoldOrCollectArguments(false); + } + + void testFoldOrCollectArguments(boolean isCollect) throws Throwable { + for (Class lastType : new Class[]{ Object.class, String.class, int.class }) { + for (Class collectType : new Class[]{ Object.class, String.class, int.class, void.class }) { + int maxArity = 10; + if (collectType != String.class) maxArity = 5; + if (lastType != Object.class) maxArity = 4; + for (int nargs = 0; nargs <= maxArity; nargs++) { + ArrayList> argTypes = new ArrayList<>(Collections.nCopies(nargs, Object.class)); + int maxMix = 20; + if (collectType != Object.class) maxMix = 0; + Map argTypesSeen = new HashMap<>(); + for (int mix = 0; mix <= maxMix; mix++) { + if (!mixArgs(argTypes, mix, argTypesSeen)) continue; + for (int collect = 0; collect <= nargs; collect++) { + for (int pos = 0; pos <= nargs - collect; pos++) { + testFoldOrCollectArguments(argTypes, pos, collect, collectType, lastType, isCollect); + } + } + } } } } } - void testFoldArguments(int nargs, int pos, int fold) throws Throwable { - if (pos != 0) return; // can fold only at pos=0 for now + boolean mixArgs(List> argTypes, int mix, Map argTypesSeen) { + assert(mix >= 0); + if (mix == 0) return true; // no change + if ((mix >>> argTypes.size()) != 0) return false; + for (int i = 0; i < argTypes.size(); i++) { + if (i >= 31) break; + boolean bit = (mix & (1 << i)) != 0; + if (bit) { + Class type = argTypes.get(i); + if (type == Object.class) + type = String.class; + else if (type == String.class) + type = int.class; + else + type = Object.class; + argTypes.set(i, type); + } + } + Integer prev = argTypesSeen.put(new ArrayList<>(argTypes), mix); + if (prev != null) { + if (verbosity >= 4) System.out.println("mix "+prev+" repeated "+mix+": "+argTypes); + return false; + } + if (verbosity >= 3) System.out.println("mix "+mix+" = "+argTypes); + return true; + } + + void testFoldOrCollectArguments(List> argTypes, // argument types minus the inserted combineType + int pos, int fold, // position and length of the folded arguments + Class combineType, // type returned from the combiner + Class lastType, // type returned from the target + boolean isCollect) throws Throwable { + int nargs = argTypes.size(); + if (pos != 0 && !isCollect) return; // can fold only at pos=0 for now countTest(); - MethodHandle target = varargsList(1 + nargs); - MethodHandle combine = varargsList(fold).asType(MethodType.genericMethodType(fold)); - List argsToPass = Arrays.asList(randomArgs(nargs, Object.class)); + List> combineArgTypes = argTypes.subList(pos, pos + fold); + List> targetArgTypes = new ArrayList<>(argTypes); + if (isCollect) // does targret see arg[pos..pos+cc-1]? + targetArgTypes.subList(pos, pos + fold).clear(); + if (combineType != void.class) + targetArgTypes.add(pos, combineType); + MethodHandle target = varargsList(targetArgTypes, lastType); + MethodHandle combine = varargsList(combineArgTypes, combineType); + List argsToPass = Arrays.asList(randomArgs(argTypes)); if (verbosity >= 3) - System.out.println("fold "+target+" with "+combine); - MethodHandle target2 = MethodHandles.foldArguments(target, combine); + System.out.println((isCollect ? "collect" : "fold")+" "+target+" with "+combine); + MethodHandle target2; + if (isCollect) + target2 = MethodHandles.collectArguments(target, pos, combine); + else + target2 = MethodHandles.foldArguments(target, combine); // Simulate expected effect of combiner on arglist: - List expected = new ArrayList<>(argsToPass); - List argsToFold = expected.subList(pos, pos + fold); + List expectedList = new ArrayList<>(argsToPass); + List argsToFold = expectedList.subList(pos, pos + fold); if (verbosity >= 3) - System.out.println("fold: "+argsToFold+" into "+target2); + System.out.println((isCollect ? "collect" : "fold")+": "+argsToFold+" into "+target2); Object foldedArgs = combine.invokeWithArguments(argsToFold); - argsToFold.add(0, foldedArgs); + if (isCollect) + argsToFold.clear(); + if (combineType != void.class) + argsToFold.add(0, foldedArgs); Object result = target2.invokeWithArguments(argsToPass); if (verbosity >= 3) System.out.println("result: "+result); + Object expected = target.invokeWithArguments(expectedList); if (!expected.equals(result)) System.out.println("*** fail at n/p/f = "+nargs+"/"+pos+"/"+fold+": "+argsToPass+" => "+result+" != "+expected); assertEquals(expected, result);