8001110: method handles should have a collectArguments transform, generalizing asCollector
Summary: promote an existing private method; make unit tests on all argument positions to arity 10 with mixed types
Reviewed-by: twisti, vlivanov
diff --git a/test/java/lang/invoke/MethodHandlesTest.java b/test/java/lang/invoke/MethodHandlesTest.java
index b77f74e..53eb09c 100644
--- a/test/java/lang/invoke/MethodHandlesTest.java
+++ b/test/java/lang/invoke/MethodHandlesTest.java
@@ -277,6 +277,9 @@
             args[i] = randomArg(param);
         return args;
     }
+    static Object[] randomArgs(List<Class<?>> params) {
+        return randomArgs(params.toArray(new Class<?>[params.size()]));
+    }
 
     @SafeVarargs @SuppressWarnings("varargs")
     static <T, E extends T> T[] array(Class<T[]> atype, E... a) {
@@ -347,6 +350,11 @@
         }
         return list.asType(listType);
     }
+    /** Variation of varargsList, but with the given ptypes and rtype. */
+    static MethodHandle varargsList(List<Class<?>> 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 @@
     }
 
     @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);
@@ -1997,37 +2005,108 @@
     }
 
     @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<Class<?>> argTypes = new ArrayList<>(Collections.nCopies(nargs, Object.class));
+                    int maxMix = 20;
+                    if (collectType != Object.class)  maxMix = 0;
+                    Map<Object,Integer> 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<Class<?>> argTypes, int mix, Map<Object,Integer> 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<Class<?>> 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<Object> argsToPass = Arrays.asList(randomArgs(nargs, Object.class));
+        List<Class<?>> combineArgTypes = argTypes.subList(pos, pos + fold);
+        List<Class<?>> 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<Object> 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<Object> expected = new ArrayList<>(argsToPass);
-        List<Object> argsToFold = expected.subList(pos, pos + fold);
+        List<Object> expectedList = new ArrayList<>(argsToPass);
+        List<Object> 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);