content: /*
   * Copyright (C) 2016 The Android Open Source Project
   *
   * 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.
   */
  
  import java.lang.invoke.MethodHandle;
  import java.lang.invoke.MethodHandles;
  import java.lang.invoke.MethodHandles.Lookup;
  import java.lang.invoke.MethodType;
  import java.lang.invoke.WrongMethodTypeException;
  
  public class Main {
    public static void main(String[] args) throws Throwable {
      testThrowException();
      testDropArguments();
      testCatchException();
      testGuardWithTest();
      testArrayElementGetter();
      testArrayElementSetter();
      testIdentity();
      testConstant();
      testBindTo();
      testFilterReturnValue();
      testPermuteArguments();
      testInvokers();
      testSpreaders_reference();
      testSpreaders_primitive();
      testInvokeWithArguments();
      testAsCollector();
      testFilterArguments();
      testCollectArguments();
      testInsertArguments();
      testFoldArguments();
    }
  
    public static void testThrowException() throws Throwable {
      MethodHandle handle = MethodHandles.throwException(String.class,
          IllegalArgumentException.class);
  
      if (handle.type().returnType() != String.class) {
        fail("Unexpected return type for handle: " + handle +
            " [ " + handle.type() + "]");
      }
  
      final IllegalArgumentException iae = new IllegalArgumentException("boo!");
      try {
        handle.invoke(iae);
        fail("Expected an exception of type: java.lang.IllegalArgumentException");
      } catch (IllegalArgumentException expected) {
        if (expected != iae) {
          fail("Wrong exception: expected " + iae + " but was " + expected);
        }
      }
    }
  
    public static void dropArguments_delegate(String message, long message2) {
      System.out.println("Message: " + message + ", Message2: " + message2);
    }
  
    public static void testDropArguments() throws Throwable {
      MethodHandle delegate = MethodHandles.lookup().findStatic(Main.class,
          "dropArguments_delegate",
          MethodType.methodType(void.class, new Class<?>[] { String.class, long.class }));
  
      MethodHandle transform = MethodHandles.dropArguments(delegate, 0, int.class, Object.class);
  
      // The transformer will accept two additional arguments at position zero.
      try {
        transform.invokeExact("foo", 42l);
        fail();
      } catch (WrongMethodTypeException expected) {
      }
  
      transform.invokeExact(45, new Object(), "foo", 42l);
      transform.invoke(45, new Object(), "foo", 42l);
  
      // Additional arguments at position 1.
      transform = MethodHandles.dropArguments(delegate, 1, int.class, Object.class);
      transform.invokeExact("foo", 45, new Object(), 42l);
      transform.invoke("foo", 45, new Object(), 42l);
  
      // Additional arguments at position 2.
      transform = MethodHandles.dropArguments(delegate, 2, int.class, Object.class);
      transform.invokeExact("foo", 42l, 45, new Object());
      transform.invoke("foo", 42l, 45, new Object());
  
      // Note that we still perform argument conversions even for the arguments that
      // are subsequently dropped.
      try {
        transform.invoke("foo", 42l, 45l, new Object());
        fail();
      } catch (WrongMethodTypeException expected) {
      } catch (IllegalArgumentException expected) {
        // TODO(narayan): We currently throw the wrong type of exception here,
        // it's IAE and should be WMTE instead.
      }
  
      // Check that asType works as expected.
      transform = MethodHandles.dropArguments(delegate, 0, int.class, Object.class);
      transform = transform.asType(MethodType.methodType(void.class,
            new Class<?>[] { short.class, Object.class, String.class, long.class }));
      transform.invokeExact((short) 45, new Object(), "foo", 42l);
  
      // Invalid argument location, should not be allowed.
      try {
        MethodHandles.dropArguments(delegate, -1, int.class, Object.class);
        fail();
      } catch (IllegalArgumentException expected) {
      }
  
      // Invalid argument location, should not be allowed.
      try {
        MethodHandles.dropArguments(delegate, 3, int.class, Object.class);
        fail();
      } catch (IllegalArgumentException expected) {
      }
  
      try {
        MethodHandles.dropArguments(delegate, 1, void.class);
        fail();
      } catch (IllegalArgumentException expected) {
      }
    }
  
    public static String testCatchException_target(String arg1, long arg2, String exceptionMessage)
        throws Throwable {
      if (exceptionMessage != null) {
        throw new IllegalArgumentException(exceptionMessage);
      }
  
      System.out.println("Target: Arg1: " + arg1 + ", Arg2: " + arg2);
      return "target";
    }
  
    public static String testCatchException_handler(IllegalArgumentException iae, String arg1, long arg2,
        String exMsg) {
      System.out.println("Handler: " + iae + ", Arg1: " + arg1 + ", Arg2: " + arg2 + ", ExMsg: " + exMsg);
      return "handler1";
    }
  
    public static String testCatchException_handler2(IllegalArgumentException iae, String arg1) {
      System.out.println("Handler: " + iae + ", Arg1: " + arg1);
      return "handler2";
    }
  
    public static void testCatchException() throws Throwable {
      MethodHandle target = MethodHandles.lookup().findStatic(Main.class,
          "testCatchException_target",
          MethodType.methodType(String.class, new Class<?>[] { String.class, long.class, String.class }));
  
      MethodHandle handler = MethodHandles.lookup().findStatic(Main.class,
          "testCatchException_handler",
          MethodType.methodType(String.class, new Class<?>[] { IllegalArgumentException.class,
              String.class, long.class, String.class }));
  
      MethodHandle adapter = MethodHandles.catchException(target, IllegalArgumentException.class,
          handler);
  
      String returnVal = null;
  
      // These two should end up calling the target always. We're passing a null exception
      // message here, which means the target will not throw.
      returnVal = (String) adapter.invoke("foo", 42, null);
      assertEquals("target", returnVal);
      returnVal = (String) adapter.invokeExact("foo", 42l, (String) null);
      assertEquals("target", returnVal);
  
      // We're passing a non-null exception message here, which means the target will throw,
      // which in turn means that the handler must be called for the next two invokes.
      returnVal = (String) adapter.invoke("foo", 42, "exceptionMessage");
      assertEquals("handler1", returnVal);
      returnVal = (String) adapter.invokeExact("foo", 42l, "exceptionMessage");
      assertEquals("handler1", returnVal);
  
      handler = MethodHandles.lookup().findStatic(Main.class,
          "testCatchException_handler2",
          MethodType.methodType(String.class, new Class<?>[] { IllegalArgumentException.class,
              String.class }));
      adapter = MethodHandles.catchException(target, IllegalArgumentException.class, handler);
  
      returnVal = (String) adapter.invoke("foo", 42, "exceptionMessage");
      assertEquals("handler2", returnVal);
      returnVal = (String) adapter.invokeExact("foo", 42l, "exceptionMessage");
      assertEquals("handler2", returnVal);
  
      // Test that the type of the invoke doesn't matter. Here we call
      // IllegalArgumentException.toString() on the exception that was thrown by
      // the target.
      handler = MethodHandles.lookup().findVirtual(IllegalArgumentException.class,
          "toString", MethodType.methodType(String.class));
      adapter = MethodHandles.catchException(target, IllegalArgumentException.class, handler);
  
      returnVal = (String) adapter.invoke("foo", 42, "exceptionMessage");
      assertEquals("java.lang.IllegalArgumentException: exceptionMessage", returnVal);
      returnVal = (String) adapter.invokeExact("foo", 42l, "exceptionMessage2");
      assertEquals("java.lang.IllegalArgumentException: exceptionMessage2", returnVal);
  
      // Check that asType works as expected.
      adapter = MethodHandles.catchException(target, IllegalArgumentException.class,
          handler);
      adapter = adapter.asType(MethodType.methodType(String.class,
            new Class<?>[] { String.class, int.class, String.class }));
      returnVal = (String) adapter.invokeExact("foo", 42, "exceptionMessage");
      assertEquals("java.lang.IllegalArgumentException: exceptionMessage", returnVal);
    }
  
    public static boolean testGuardWithTest_test(String arg1, long arg2) {
      return "target".equals(arg1) && 42 == arg2;
    }
  
    public static String testGuardWithTest_target(String arg1, long arg2, int arg3) {
      System.out.println("target: " + arg1 + ", " + arg2  + ", " + arg3);
      return "target";
    }
  
    public static String testGuardWithTest_fallback(String arg1, long arg2, int arg3) {
      System.out.println("fallback: " + arg1 + ", " + arg2  + ", " + arg3);
      return "fallback";
    }
  
    public static void testGuardWithTest() throws Throwable {
      MethodHandle test = MethodHandles.lookup().findStatic(Main.class,
          "testGuardWithTest_test",
          MethodType.methodType(boolean.class, new Class<?>[] { String.class, long.class }));
  
      final MethodType type = MethodType.methodType(String.class,
          new Class<?>[] { String.class, long.class, int.class });
  
      final MethodHandle target = MethodHandles.lookup().findStatic(Main.class,
          "testGuardWithTest_target", type);
      final MethodHandle fallback = MethodHandles.lookup().findStatic(Main.class,
          "testGuardWithTest_fallback", type);
  
      MethodHandle adapter = MethodHandles.guardWithTest(test, target, fallback);
  
      String returnVal = null;
  
      returnVal = (String) adapter.invoke("target", 42, 56);
      assertEquals("target", returnVal);
      returnVal = (String) adapter.invokeExact("target", 42l, 56);
      assertEquals("target", returnVal);
  
      returnVal = (String) adapter.invoke("fallback", 42l, 56);
      assertEquals("fallback", returnVal);
      returnVal = (String) adapter.invokeExact("target", 42l, 56);
      assertEquals("target", returnVal);
  
      // Check that asType works as expected.
      adapter = adapter.asType(MethodType.methodType(String.class,
            new Class<?>[] { String.class, int.class, int.class }));
      returnVal = (String) adapter.invokeExact("target", 42, 56);
      assertEquals("target", returnVal);
    }
  
    public static void testArrayElementGetter() throws Throwable {
      MethodHandle getter = MethodHandles.arrayElementGetter(int[].class);
  
      {
        int[] array = new int[1];
        array[0] = 42;
        int value = (int) getter.invoke(array, 0);
        if (value != 42) {
          fail("Unexpected value: " + value);
        }
  
        try {
          value = (int) getter.invoke(array, -1);
          fail();
        } catch (ArrayIndexOutOfBoundsException expected) {
        }
  
        try {
          value = (int) getter.invoke(null, -1);
          fail();
        } catch (NullPointerException expected) {
        }
      }
  
      {
        getter = MethodHandles.arrayElementGetter(long[].class);
        long[] array = new long[1];
        array[0] = 42;
        long value = (long) getter.invoke(array, 0);
        if (value != 42l) {
          fail("Unexpected value: " + value);
        }
      }
  
      {
        getter = MethodHandles.arrayElementGetter(short[].class);
        short[] array = new short[1];
        array[0] = 42;
        short value = (short) getter.invoke(array, 0);
        if (value != 42l) {
          fail("Unexpected value: " + value);
        }
      }
  
      {
        getter = MethodHandles.arrayElementGetter(char[].class);
        char[] array = new char[1];
        array[0] = 42;
        char value = (char) getter.invoke(array, 0);
        if (value != 42l) {
          fail("Unexpected value: " + value);
        }
      }
  
      {
        getter = MethodHandles.arrayElementGetter(byte[].class);
        byte[] array = new byte[1];
        array[0] = (byte) 0x8;
        byte value = (byte) getter.invoke(array, 0);
        if (value != (byte) 0x8) {
          fail("Unexpected value: " + value);
        }
      }
  
      {
        getter = MethodHandles.arrayElementGetter(boolean[].class);
        boolean[] array = new boolean[1];
        array[0] = true;
        boolean value = (boolean) getter.invoke(array, 0);
        if (!value) {
          fail("Unexpected value: " + value);
        }
      }
  
      {
        getter = MethodHandles.arrayElementGetter(float[].class);
        float[] array = new float[1];
        array[0] = 42.0f;
        float value = (float) getter.invoke(array, 0);
        if (value != 42.0f) {
          fail("Unexpected value: " + value);
        }
      }
  
      {
        getter = MethodHandles.arrayElementGetter(double[].class);
        double[] array = new double[1];
        array[0] = 42.0;
        double value = (double) getter.invoke(array, 0);
        if (value != 42.0) {
          fail("Unexpected value: " + value);
        }
      }
  
      {
        getter = MethodHandles.arrayElementGetter(String[].class);
        String[] array = new String[3];
        array[0] = "42";
        array[1] = "48";
        array[2] = "54";
        String value = (String) getter.invoke(array, 0);
        assertEquals("42", value);
        value = (String) getter.invoke(array, 1);
        assertEquals("48", value);
        value = (String) getter.invoke(array, 2);
        assertEquals("54", value);
      }
    }
  
    public static void testArrayElementSetter() throws Throwable {
      MethodHandle setter = MethodHandles.arrayElementSetter(int[].class);
  
      {
        int[] array = new int[2];
        setter.invoke(array, 0, 42);
        setter.invoke(array, 1, 43);
  
        if (array[0] != 42) {
          fail("Unexpected value: " + array[0]);
        }
        if (array[1] != 43) {
          fail("Unexpected value: " + array[1]);
        }
  
        try {
          setter.invoke(array, -1, 42);
          fail();
        } catch (ArrayIndexOutOfBoundsException expected) {
        }
  
        try {
          setter.invoke(null, 0, 42);
          fail();
        } catch (NullPointerException expected) {
        }
      }
  
      {
        setter = MethodHandles.arrayElementSetter(long[].class);
        long[] array = new long[1];
        setter.invoke(array, 0, 42l);
        if (array[0] != 42l) {
          fail("Unexpected value: " + array[0]);
        }
      }
  
      {
        setter = MethodHandles.arrayElementSetter(short[].class);
        short[] array = new short[1];
        setter.invoke(array, 0, (short) 42);
        if (array[0] != 42l) {
          fail("Unexpected value: " + array[0]);
        }
      }
  
      {
        setter = MethodHandles.arrayElementSetter(char[].class);
        char[] array = new char[1];
        setter.invoke(array, 0, (char) 42);
        if (array[0] != 42) {
          fail("Unexpected value: " + array[0]);
        }
      }
  
      {
        setter = MethodHandles.arrayElementSetter(byte[].class);
        byte[] array = new byte[1];
        setter.invoke(array, 0, (byte) 0x8);
        if (array[0] != (byte) 0x8) {
          fail("Unexpected value: " + array[0]);
        }
      }
  
      {
        setter = MethodHandles.arrayElementSetter(boolean[].class);
        boolean[] array = new boolean[1];
        setter.invoke(array, 0, true);
        if (!array[0]) {
          fail("Unexpected value: " + array[0]);
        }
      }
  
      {
        setter = MethodHandles.arrayElementSetter(float[].class);
        float[] array = new float[1];
        setter.invoke(array, 0, 42.0f);
        if (array[0] != 42.0f) {
          fail("Unexpected value: " + array[0]);
        }
      }
  
      {
        setter = MethodHandles.arrayElementSetter(double[].class);
        double[] array = new double[1];
        setter.invoke(array, 0, 42.0);
        if (array[0] != 42.0) {
          fail("Unexpected value: " + array[0]);
        }
      }
  
      {
        setter = MethodHandles.arrayElementSetter(String[].class);
        String[] array = new String[3];
        setter.invoke(array, 0, "42");
        setter.invoke(array, 1, "48");
        setter.invoke(array, 2, "54");
        assertEquals("42", array[0]);
        assertEquals("48", array[1]);
        assertEquals("54", array[2]);
      }
    }
  
    public static void testIdentity() throws Throwable {
      {
        MethodHandle identity = MethodHandles.identity(boolean.class);
        boolean value = (boolean) identity.invoke(false);
        if (value) {
          fail("Unexpected value: " + value);
        }
      }
  
      {
        MethodHandle identity = MethodHandles.identity(byte.class);
        byte value = (byte) identity.invoke((byte) 0x8);
        if (value != (byte) 0x8) {
          fail("Unexpected value: " + value);
        }
      }
  
      {
        MethodHandle identity = MethodHandles.identity(char.class);
        char value = (char) identity.invoke((char) -56);
        if (value != (char) -56) {
          fail("Unexpected value: " + value);
        }
      }
  
      {
        MethodHandle identity = MethodHandles.identity(short.class);
        short value = (short) identity.invoke((short) -59);
        if (value != (short) -59) {
          fail("Unexpected value: " + Short.toString(value));
        }
      }
  
      {
        MethodHandle identity = MethodHandles.identity(int.class);
        int value = (int) identity.invoke(52);
        if (value != 52) {
          fail("Unexpected value: " + value);
        }
      }
  
      {
        MethodHandle identity = MethodHandles.identity(long.class);
        long value = (long) identity.invoke(-76l);
        if (value != (long) -76) {
          fail("Unexpected value: " + value);
        }
      }
  
      {
        MethodHandle identity = MethodHandles.identity(float.class);
        float value = (float) identity.invoke(56.0f);
        if (value != (float) 56.0f) {
          fail("Unexpected value: " + value);
        }
      }
  
      {
        MethodHandle identity = MethodHandles.identity(double.class);
        double value = (double) identity.invoke((double) 72.0);
        if (value != (double) 72.0) {
          fail("Unexpected value: " + value);
        }
      }
  
      {
        MethodHandle identity = MethodHandles.identity(String.class);
        String value = (String) identity.invoke("bazman");
        assertEquals("bazman", value);
      }
    }
  
    public static void testConstant() throws Throwable {
      // int constants.
      {
        MethodHandle constant = MethodHandles.constant(int.class, 56);
        int value = (int) constant.invoke();
        if (value != 56) {
          fail("Unexpected value: " + value);
        }
  
        // short constant values are converted to int.
        constant = MethodHandles.constant(int.class, (short) 52);
        value = (int) constant.invoke();
        if (value != 52) {
          fail("Unexpected value: " + value);
        }
  
        // char constant values are converted to int.
        constant = MethodHandles.constant(int.class, (char) 'b');
        value = (int) constant.invoke();
        if (value != (int) 'b') {
          fail("Unexpected value: " + value);
        }
  
        // int constant values are converted to int.
        constant = MethodHandles.constant(int.class, (byte) 0x1);
        value = (int) constant.invoke();
        if (value != 1) {
          fail("Unexpected value: " + value);
        }
  
        // boolean, float, double and long primitive constants are not convertible
        // to int, so the handle creation must fail with a CCE.
        try {
          MethodHandles.constant(int.class, false);
          fail();
        } catch (ClassCastException expected) {
        }
  
        try {
          MethodHandles.constant(int.class, 0.1f);
          fail();
        } catch (ClassCastException expected) {
        }
  
        try {
          MethodHandles.constant(int.class, 0.2);
          fail();
        } catch (ClassCastException expected) {
        }
  
        try {
          MethodHandles.constant(int.class, 73l);
          fail();
        } catch (ClassCastException expected) {
        }
      }
  
      // long constants.
      {
        MethodHandle constant = MethodHandles.constant(long.class, 56l);
        long value = (long) constant.invoke();
        if (value != 56l) {
          fail("Unexpected value: " + value);
        }
  
        constant = MethodHandles.constant(long.class, (int) 56);
        value = (long) constant.invoke();
        if (value != 56l) {
          fail("Unexpected value: " + value);
        }
      }
  
      // byte constants.
      {
        MethodHandle constant = MethodHandles.constant(byte.class, (byte) 0x12);
        byte value = (byte) constant.invoke();
        if (value != (byte) 0x12) {
          fail("Unexpected value: " + value);
        }
      }
  
      // boolean constants.
      {
        MethodHandle constant = MethodHandles.constant(boolean.class, true);
        boolean value = (boolean) constant.invoke();
        if (!value) {
          fail("Unexpected value: " + value);
        }
      }
  
      // char constants.
      {
        MethodHandle constant = MethodHandles.constant(char.class, 'f');
        char value = (char) constant.invoke();
        if (value != 'f') {
          fail("Unexpected value: " + value);
        }
      }
  
      // short constants.
      {
        MethodHandle constant = MethodHandles.constant(short.class, (short) 123);
        short value = (short) constant.invoke();
        if (value != (short) 123) {
          fail("Unexpected value: " + value);
        }
      }
  
      // float constants.
      {
        MethodHandle constant = MethodHandles.constant(float.class, 56.0f);
        float value = (float) constant.invoke();
        if (value != 56.0f) {
          fail("Unexpected value: " + value);
        }
      }
  
      // double constants.
      {
        MethodHandle constant = MethodHandles.constant(double.class, 256.0);
        double value = (double) constant.invoke();
        if (value != 256.0) {
          fail("Unexpected value: " + value);
        }
      }
  
      // reference constants.
      {
        MethodHandle constant = MethodHandles.constant(String.class, "256.0");
        String value = (String) constant.invoke();
        assertEquals("256.0", value);
      }
    }
  
    public static void testBindTo() throws Throwable {
      MethodHandle stringCharAt = MethodHandles.lookup().findVirtual(
          String.class, "charAt", MethodType.methodType(char.class, int.class));
  
      char value = (char) stringCharAt.invoke("foo", 0);
      if (value != 'f') {
        fail("Unexpected value: " + value);
      }
  
      MethodHandle bound = stringCharAt.bindTo("foo");
      value = (char) bound.invoke(0);
      if (value != 'f') {
        fail("Unexpected value: " + value);
      }
  
      try {
        stringCharAt.bindTo(new Object());
        fail();
      } catch (ClassCastException expected) {
      }
  
      bound = stringCharAt.bindTo(null);
      try {
        bound.invoke(0);
        fail();
      } catch (NullPointerException expected) {
      }
  
      MethodHandle integerParseInt = MethodHandles.lookup().findStatic(
          Integer.class, "parseInt", MethodType.methodType(int.class, String.class));
  
      bound = integerParseInt.bindTo("78452");
      int intValue = (int) bound.invoke();
      if (intValue != 78452) {
        fail("Unexpected value: " + intValue);
      }
    }
  
    public static String filterReturnValue_target(int a) {
      return "ReturnValue" + a;
    }
  
    public static boolean filterReturnValue_filter(String value) {
      return value.indexOf("42") != -1;
    }
  
    public static int filterReturnValue_intTarget(String a) {
      return Integer.parseInt(a);
    }
  
    public static int filterReturnValue_intFilter(int b) {
      return b + 1;
    }
  
    public static void filterReturnValue_voidTarget() {
    }
  
    public static int filterReturnValue_voidFilter() {
      return 42;
    }
  
    public static void testFilterReturnValue() throws Throwable {
      // A target that returns a reference.
      {
        final MethodHandle target = MethodHandles.lookup().findStatic(Main.class,
            "filterReturnValue_target", MethodType.methodType(String.class, int.class));
        final MethodHandle filter = MethodHandles.lookup().findStatic(Main.class,
            "filterReturnValue_filter", MethodType.methodType(boolean.class, String.class));
  
        MethodHandle adapter = MethodHandles.filterReturnValue(target, filter);
  
        boolean value = (boolean) adapter.invoke((int) 42);
        if (!value) {
          fail("Unexpected value: " + value);
        }
        value = (boolean) adapter.invoke((int) 43);
        if (value) {
          fail("Unexpected value: " + value);
        }
      }
  
      // A target that returns a primitive.
      {
        final MethodHandle target = MethodHandles.lookup().findStatic(Main.class,
            "filterReturnValue_intTarget", MethodType.methodType(int.class, String.class));
        final MethodHandle filter = MethodHandles.lookup().findStatic(Main.class,
            "filterReturnValue_intFilter", MethodType.methodType(int.class, int.class));
  
        MethodHandle adapter = MethodHandles.filterReturnValue(target, filter);
  
        int value = (int) adapter.invoke("56");
        if (value != 57) {
          fail("Unexpected value: " + value);
        }
      }
  
      // A target that returns void.
      {
        final MethodHandle target = MethodHandles.lookup().findStatic(Main.class,
            "filterReturnValue_voidTarget", MethodType.methodType(void.class));
        final MethodHandle filter = MethodHandles.lookup().findStatic(Main.class,
            "filterReturnValue_voidFilter", MethodType.methodType(int.class));
  
        MethodHandle adapter = MethodHandles.filterReturnValue(target, filter);
  
        int value = (int) adapter.invoke();
        if (value != 42) {
          fail("Unexpected value: " + value);
        }
      }
    }
  
    public static void permuteArguments_callee(boolean a, byte b, char c,
        short d, int e, long f, float g, double h) {
      if (a == true && b == (byte) 'b' && c == 'c' && d == (short) 56 &&
          e == 78 && f == (long) 97 && g == 98.0f && f == 97.0) {
        return;
      }
  
      fail("Unexpected arguments: " + a + ", " + b + ", " + c
          + ", " + d + ", " + e + ", " + f + ", " + g + ", " + h);
    }
  
    public static void permuteArguments_boxingCallee(boolean a, Integer b) {
      if (a && b.intValue() == 42) {
        return;
      }
  
      fail("Unexpected arguments: " + a + ", " + b);
    }
  
    public static void testPermuteArguments() throws Throwable {
      {
        final MethodHandle target = MethodHandles.lookup().findStatic(
            Main.class, "permuteArguments_callee",
            MethodType.methodType(void.class, new Class<?>[] {
              boolean.class, byte.class, char.class, short.class, int.class,
              long.class, float.class, double.class }));
  
        final MethodType newType = MethodType.methodType(void.class, new Class<?>[] {
          double.class, float.class, long.class, int.class, short.class, char.class,
          byte.class, boolean.class });
  
        final MethodHandle permutation = MethodHandles.permuteArguments(
            target, newType, new int[] { 7, 6, 5, 4, 3, 2, 1, 0 });
  
        permutation.invoke((double) 97.0, (float) 98.0f, (long) 97, 78,
            (short) 56, 'c', (byte) 'b', (boolean) true);
  
        // The permutation array was not of the right length.
        try {
          MethodHandles.permuteArguments(target, newType,
              new int[] { 7 });
          fail();
        } catch (IllegalArgumentException expected) {
        }
  
        // The permutation array has an element that's out of bounds
        // (there's no argument with idx == 8).
        try {
          MethodHandles.permuteArguments(target, newType,
              new int[] { 8, 6, 5, 4, 3, 2, 1, 0 });
          fail();
        } catch (IllegalArgumentException expected) {
        }
  
        // The permutation array maps to an incorrect type.
        try {
          MethodHandles.permuteArguments(target, newType,
              new int[] { 7, 7, 5, 4, 3, 2, 1, 0 });
          fail();
        } catch (IllegalArgumentException expected) {
        }
      }
  
      // Tests for reference arguments as well as permutations that
      // repeat arguments.
      {
        final MethodHandle target = MethodHandles.lookup().findVirtual(
            String.class, "concat", MethodType.methodType(String.class, String.class));
  
        final MethodType newType = MethodType.methodType(String.class, String.class,
            String.class);
  
        assertEquals("foobar", (String) target.invoke("foo", "bar"));
  
        MethodHandle permutation = MethodHandles.permuteArguments(target,
            newType, new int[] { 1, 0 });
        assertEquals("barfoo", (String) permutation.invoke("foo", "bar"));
  
        permutation = MethodHandles.permuteArguments(target, newType, new int[] { 0, 0 });
        assertEquals("foofoo", (String) permutation.invoke("foo", "bar"));
  
        permutation = MethodHandles.permuteArguments(target, newType, new int[] { 1, 1 });
        assertEquals("barbar", (String) permutation.invoke("foo", "bar"));
      }
  
      // Tests for boxing and unboxing.
      {
        final MethodHandle target = MethodHandles.lookup().findStatic(
            Main.class, "permuteArguments_boxingCallee",
            MethodType.methodType(void.class, new Class<?>[] { boolean.class, Integer.class }));
  
        final MethodType newType = MethodType.methodType(void.class,
            new Class<?>[] { Integer.class, boolean.class });
  
        MethodHandle permutation = MethodHandles.permuteArguments(target,
            newType, new int[] { 1, 0 });
  
        permutation.invoke(42, true);
        permutation.invoke(42, Boolean.TRUE);
        permutation.invoke(Integer.valueOf(42), true);
        permutation.invoke(Integer.valueOf(42), Boolean.TRUE);
      }
    }
  
    private static Object returnBar() {
      return "bar";
    }
  
    public static void testInvokers() throws Throwable {
      final MethodType targetType = MethodType.methodType(String.class, String.class);
      final MethodHandle target = MethodHandles.lookup().findVirtual(
          String.class, "concat", targetType);
  
      MethodHandle invoker = MethodHandles.invoker(target.type());
      assertEquals("barbar", (String) invoker.invoke(target, "bar", "bar"));
      assertEquals("barbar", (String) invoker.invoke(target, (Object) returnBar(), "bar"));
      try {
        String foo = (String) invoker.invoke(target, "bar", "bar", 24);
        fail();
      } catch (WrongMethodTypeException expected) {
      }
  
      MethodHandle exactInvoker = MethodHandles.exactInvoker(target.type());
      assertEquals("barbar", (String) exactInvoker.invoke(target, "bar", "bar"));
      try {
        String foo = (String) exactInvoker.invoke(target, (Object) returnBar(), "bar");
        fail();
      } catch (WrongMethodTypeException expected) {
      }
      try {
        String foo = (String) exactInvoker.invoke(target, "bar", "bar", 24);
        fail();
      } catch (WrongMethodTypeException expected) {
      }
    }
  
    public static int spreadReferences(String a, String b, String c) {
      System.out.println("a: " + a + ", b:" + b + ", c: " + c);
      return 42;
    }
  
    public static int spreadReferences_Unbox(String a, int b) {
      System.out.println("a: " + a + ", b:" + b);
      return 43;
    }
  
    public static void testSpreaders_reference() throws Throwable {
      MethodType methodType = MethodType.methodType(int.class,
          new Class<?>[] { String.class, String.class, String.class });
      MethodHandle delegate = MethodHandles.lookup().findStatic(
          Main.class, "spreadReferences", methodType);
  
      // Basic checks on array lengths.
      //
      // Array size = 0
      MethodHandle mhAsSpreader = delegate.asSpreader(String[].class, 0);
      int ret = (int) mhAsSpreader.invoke("a", "b", "c", new String[] {});
      assertEquals(42, ret);
      // Array size = 1
      mhAsSpreader = delegate.asSpreader(String[].class, 1);
      ret = (int) mhAsSpreader.invoke("a", "b", new String[] { "c" });
      assertEquals(42, ret);
      // Array size = 2
      mhAsSpreader = delegate.asSpreader(String[].class, 2);
      ret = (int) mhAsSpreader.invoke("a", new String[] { "b", "c" });
      assertEquals(42, ret);
      // Array size = 3
      mhAsSpreader = delegate.asSpreader(String[].class, 3);
      ret = (int) mhAsSpreader.invoke(new String[] { "a", "b", "c"});
      assertEquals(42, ret);
  
      // Exception case, array size = 4 is illegal.
      try {
        delegate.asSpreader(String[].class, 4);
        fail();
      } catch (IllegalArgumentException expected) {
      }
  
      // Exception case, calling with an arg of the wrong size.
      // Array size = 3
      mhAsSpreader = delegate.asSpreader(String[].class, 3);
      try {
        ret = (int) mhAsSpreader.invoke(new String[] { "a", "b"});
      } catch (IllegalArgumentException expected) {
      }
  
      // Various other hijinks, pass as Object[] arrays, Object etc.
      mhAsSpreader = delegate.asSpreader(Object[].class, 2);
      ret = (int) mhAsSpreader.invoke("a", new String[] { "b", "c" });
      assertEquals(42, ret);
  
      mhAsSpreader = delegate.asSpreader(Object[].class, 2);
      ret = (int) mhAsSpreader.invoke("a", new Object[] { "b", "c" });
      assertEquals(42, ret);
  
      mhAsSpreader = delegate.asSpreader(Object[].class, 2);
      ret = (int) mhAsSpreader.invoke("a", (Object) new Object[] { "b", "c" });
      assertEquals(42, ret);
  
      // Test implicit unboxing.
      MethodType methodType2 = MethodType.methodType(int.class,
          new Class<?>[] { String.class, int.class });
      MethodHandle delegate2 = MethodHandles.lookup().findStatic(
          Main.class, "spreadReferences_Unbox", methodType2);
  
      // .. with an Integer[] array.
      mhAsSpreader = delegate2.asSpreader(Integer[].class, 1);
      ret = (int) mhAsSpreader.invoke("a", new Integer[] { 43 });
      assertEquals(43, ret);
  
      // .. with an Integer[] array declared as an Object[] argument type.
      mhAsSpreader = delegate2.asSpreader(Object[].class, 1);
      ret = (int) mhAsSpreader.invoke("a", new Integer[] { 43 });
      assertEquals(43, ret);
  
      // .. with an Object[] array.
      mhAsSpreader = delegate2.asSpreader(Object[].class, 1);
      ret = (int) mhAsSpreader.invoke("a", new Object[] { Integer.valueOf(43)});
      assertEquals(43, ret);
  
      // -- Part 2--
      // Run a subset of these tests on MethodHandles.spreadInvoker, which only accepts
      // a trailing argument type of Object[].
      MethodHandle spreadInvoker = MethodHandles.spreadInvoker(methodType2, 1);
      ret = (int) spreadInvoker.invoke(delegate2, "a", new Object[] { Integer.valueOf(43)});
      assertEquals(43, ret);
  
      ret = (int) spreadInvoker.invoke(delegate2, "a", new Integer[] { 43 });
      assertEquals(43, ret);
  
      // NOTE: Annoyingly, the second argument here is leadingArgCount and not
      // arrayLength.
      spreadInvoker = MethodHandles.spreadInvoker(methodType, 3);
      ret = (int) spreadInvoker.invoke(delegate, "a", "b", "c", new String[] {});
      assertEquals(42, ret);
  
      spreadInvoker = MethodHandles.spreadInvoker(methodType, 0);
      ret = (int) spreadInvoker.invoke(delegate, new String[] { "a", "b", "c" });
      assertEquals(42, ret);
  
      // Exact invokes: Double check that the expected parameter type is
      // Object[] and not T[].
      try {
        spreadInvoker.invokeExact(delegate, new String[] { "a", "b", "c" });
        fail();
      } catch (WrongMethodTypeException expected) {
      }
  
      ret = (int) spreadInvoker.invoke(delegate, new Object[] { "a", "b", "c" });
      assertEquals(42, ret);
    }
  
    public static int spreadBoolean(String a, Boolean b, boolean c) {
      System.out.println("a: " + a + ", b:" + b + ", c: " + c);
      return 44;
    }
  
    public static int spreadByte(String a, Byte b, byte c,
        short d, int e, long f, float g, double h) {
      System.out.println("a: " + a + ", b:" + b + ", c: " + c +
          ", d: " + d + ", e: " + e + ", f:" + f + ", g: " + g +
          ", h: " + h);
      return 45;
    }
  
    public static int spreadChar(String a, Character b, char c,
        int d, long e, float f, double g) {
      System.out.println("a: " + a + ", b:" + b + ", c: " + c +
          ", d: " + d + ", e: " + e + ", f:" + f + ", g: " + g);
      return 46;
    }
  
    public static int spreadShort(String a, Short b, short c,
        int d, long e, float f, double g) {
      System.out.println("a: " + a + ", b:" + b + ", c: " + c +
          ", d: " + d + ", e: " + e + ", f:" + f + ", g:" + g);
      return 47;
    }
  
    public static int spreadInt(String a, Integer b, int c,
        long d, float e, double f) {
      System.out.println("a: " + a + ", b:" + b + ", c: " + c +
          ", d: " + d + ", e: " + e + ", f:" + f);
      return 48;
    }
  
    public static int spreadLong(String a, Long b, long c, float d, double e) {
      System.out.println("a: " + a + ", b:" + b + ", c: " + c +
          ", d: " + d + ", e: " + e);
      return 49;
    }
  
    public static int spreadFloat(String a, Float b, float c, double d) {
      System.out.println("a: " + a + ", b:" + b + ", c: " + c + ", d: " + d);
      return 50;
    }
  
    public static int spreadDouble(String a, Double b, double c) {
      System.out.println("a: " + a + ", b:" + b + ", c: " + c);
      return 51;
    }
  
    public static void testSpreaders_primitive() throws Throwable {
      // boolean[]
      // ---------------------
      MethodType type = MethodType.methodType(int.class,
          new Class<?>[] { String.class, Boolean.class, boolean.class });
      MethodHandle delegate = MethodHandles.lookup().findStatic(
          Main.class, "spreadBoolean", type);
  
      MethodHandle spreader = delegate.asSpreader(boolean[].class, 2);
      int ret = (int) spreader.invokeExact("a", new boolean[] { true, false });
      assertEquals(44, ret);
      ret = (int) spreader.invoke("a", new boolean[] { true, false });
      assertEquals(44, ret);
  
      // boolean can't be cast to String (the first argument to the method).
      try {
        delegate.asSpreader(boolean[].class, 3);
        fail();
      } catch (WrongMethodTypeException expected) {
      }
  
      // int can't be cast to boolean to supply the last argument to the method.
      try {
        delegate.asSpreader(int[].class, 1);
        fail();
      } catch (WrongMethodTypeException expected) {
      }
  
      // byte[]
      // ---------------------
      type = MethodType.methodType(int.class,
          new Class<?>[] {
            String.class, Byte.class, byte.class,
            short.class, int.class, long.class,
            float.class, double.class });
      delegate = MethodHandles.lookup().findStatic(Main.class, "spreadByte", type);
  
      spreader = delegate.asSpreader(byte[].class, 7);
      ret = (int) spreader.invokeExact("a",
          new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 });
      assertEquals(45, ret);
      ret = (int) spreader.invoke("a",
          new byte[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7 });
      assertEquals(45, ret);
  
      // char[]
      // ---------------------
      type = MethodType.methodType(int.class,
          new Class<?>[] {
            String.class, Character.class,char.class,
            int.class, long.class, float.class, double.class });
      delegate = MethodHandles.lookup().findStatic(Main.class, "spreadChar", type);
  
      spreader = delegate.asSpreader(char[].class, 6);
      ret = (int) spreader.invokeExact("a",
          new char[] { '1', '2', '3', '4', '5', '6' });
      assertEquals(46, ret);
      ret = (int) spreader.invokeExact("a",
          new char[] { '1', '2', '3', '4', '5', '6' });
      assertEquals(46, ret);
  
      // short[]
      // ---------------------
      type = MethodType.methodType(int.class,
          new Class<?>[] {
            String.class, Short.class, short.class,
            int.class, long.class, float.class, double.class });
      delegate = MethodHandles.lookup().findStatic(Main.class, "spreadShort", type);
  
      spreader = delegate.asSpreader(short[].class, 6);
      ret = (int) spreader.invokeExact("a",
          new short[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6 });
      assertEquals(47, ret);
      ret = (int) spreader.invoke("a",
          new short[] { 0x1, 0x2, 0x3, 0x4, 0x5, 0x6 });
      assertEquals(47, ret);
  
      // int[]
      // ---------------------
      type = MethodType.methodType(int.class,
          new Class<?>[] {
            String.class, Integer.class, int.class,
            long.class, float.class, double.class });
      delegate = MethodHandles.lookup().findStatic(Main.class, "spreadInt", type);
  
      spreader = delegate.asSpreader(int[].class, 5);
      ret = (int) spreader.invokeExact("a", new int[] { 1, 2, 3, 4, 5 });
      assertEquals(48, ret);
      ret = (int) spreader.invokeExact("a", new int[] { 1, 2, 3, 4, 5 });
      assertEquals(48, ret);
  
      // long[]
      // ---------------------
      type = MethodType.methodType(int.class,
          new Class<?>[] {
            String.class, Long.class, long.class, float.class, double.class });
      delegate = MethodHandles.lookup().findStatic(Main.class, "spreadLong", type);
  
      spreader = delegate.asSpreader(long[].class, 4);
      ret = (int) spreader.invokeExact("a",
          new long[] { 0x1, 0x2, 0x3, 0x4 });
      assertEquals(49, ret);
      ret = (int) spreader.invoke("a",
          new long[] { 0x1, 0x2, 0x3, 0x4 });
      assertEquals(49, ret);
  
      // float[]
      // ---------------------
      type = MethodType.methodType(int.class,
          new Class<?>[] {
            String.class, Float.class, float.class, double.class });
      delegate = MethodHandles.lookup().findStatic(Main.class, "spreadFloat", type);
  
      spreader = delegate.asSpreader(float[].class, 3);
      ret = (int) spreader.invokeExact("a",
          new float[] { 1.0f, 2.0f, 3.0f });
      assertEquals(50, ret);
      ret = (int) spreader.invokeExact("a",
          new float[] { 1.0f, 2.0f, 3.0f });
      assertEquals(50, ret);
  
      // double[]
      // ---------------------
      type = MethodType.methodType(int.class,
          new Class<?>[] { String.class, Double.class, double.class });
      delegate = MethodHandles.lookup().findStatic(Main.class, "spreadDouble", type);
  
      spreader = delegate.asSpreader(double[].class, 2);
      ret = (int) spreader.invokeExact("a", new double[] { 1.0, 2.0 });
      assertEquals(51, ret);
      ret = (int) spreader.invokeExact("a", new double[] { 1.0, 2.0 });
      assertEquals(51, ret);
    }
  
    public static void testInvokeWithArguments() throws Throwable {
      MethodType methodType = MethodType.methodType(int.class,
          new Class<?>[] { String.class, String.class, String.class });
      MethodHandle handle = MethodHandles.lookup().findStatic(
          Main.class, "spreadReferences", methodType);
  
      Object ret = handle.invokeWithArguments(new Object[] { "a", "b", "c"});
      assertEquals(42, (int) ret);
      handle.invokeWithArguments(new String[] { "a", "b", "c" });
      assertEquals(42, (int) ret);
  
      // Pass in an array that's too small. Should throw an IAE.
      try {
        handle.invokeWithArguments(new Object[] { "a", "b" });
        fail();
      } catch (IllegalArgumentException expected) {
      } catch (WrongMethodTypeException expected) {
      }
  
      // Test implicit unboxing.
      MethodType methodType2 = MethodType.methodType(int.class,
          new Class<?>[] { String.class, int.class });
      MethodHandle handle2 = MethodHandles.lookup().findStatic(
          Main.class, "spreadReferences_Unbox", methodType2);
  
      ret = (int) handle2.invokeWithArguments(new Object[] { "a", 43 });
      assertEquals(43, (int) ret);
    }
  
    public static int collectBoolean(String a, boolean[] b) {
      System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]);
      return 44;
    }
  
    public static int collectByte(String a, byte[] b) {
      System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]);
      return 45;
    }
  
    public static int collectChar(String a, char[] b) {
      System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]);
      return 46;
    }
  
    public static int collectShort(String a, short[] b) {
      System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]);
      return 47;
    }
  
    public static int collectInt(String a, int[] b) {
      System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]);
      return 48;
    }
  
    public static int collectLong(String a, long[] b) {
      System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]);
      return 49;
    }
  
    public static int collectFloat(String a, float[] b) {
      System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]);
      return 50;
    }
  
    public static int collectDouble(String a, double[] b) {
      System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]);
      return 51;
    }
  
    public static int collectCharSequence(String a, CharSequence[] b) {
      System.out.println("a: " + a + ", b:" + b[0] + ", c: " + b[1]);
      return 99;
    }
  
    public static void testAsCollector() throws Throwable {
      // Reference arrays.
      // -------------------
      MethodHandle trailingRef = MethodHandles.lookup().findStatic(
          Main.class, "collectCharSequence",
          MethodType.methodType(int.class, String.class, CharSequence[].class));
  
      // int[] is not convertible to CharSequence[].class.
      try {
        trailingRef.asCollector(int[].class, 1);
        fail();
      } catch (IllegalArgumentException expected) {
      }
  
      // Object[] is not convertible to CharSequence[].class.
      try {
        trailingRef.asCollector(Object[].class, 1);
        fail();
      } catch (IllegalArgumentException expected) {
      }
  
      // String[].class is convertible to CharSequence.class
      MethodHandle collector = trailingRef.asCollector(String[].class, 2);
      assertEquals(99, (int) collector.invoke("a", "b", "c"));
  
      // Too few arguments should fail with a WMTE.
      try {
        collector.invoke("a", "b");
        fail();
      } catch (WrongMethodTypeException expected) {
      }
  
      // Too many arguments should fail with a WMTE.
      try {
        collector.invoke("a", "b", "c", "d");
        fail();
      } catch (WrongMethodTypeException expected) {
      }
  
      // Sanity checks on other array types.
  
      MethodHandle target = MethodHandles.lookup().findStatic(
          Main.class, "collectBoolean",
          MethodType.methodType(int.class, String.class, boolean[].class));
      assertEquals(44, (int) target.asCollector(boolean[].class, 2).invoke("a", true, false));
  
      target = MethodHandles.lookup().findStatic(Main.class, "collectByte",
          MethodType.methodType(int.class, String.class, byte[].class));
      assertEquals(45, (int) target.asCollector(byte[].class, 2).invoke("a", (byte) 1, (byte) 2));
  
      target = MethodHandles.lookup().findStatic(Main.class, "collectChar",
          MethodType.methodType(int.class, String.class, char[].class));
      assertEquals(46, (int) target.asCollector(char[].class, 2).invoke("a", 'a', 'b'));
  
      target = MethodHandles.lookup().findStatic(Main.class, "collectShort",
          MethodType.methodType(int.class, String.class, short[].class));
      assertEquals(47, (int) target.asCollector(short[].class, 2).invoke("a", (short) 3, (short) 4));
  
      target = MethodHandles.lookup().findStatic(Main.class, "collectInt",
          MethodType.methodType(int.class, String.class, int[].class));
      assertEquals(48, (int) target.asCollector(int[].class, 2).invoke("a", 42, 43));
  
      target = MethodHandles.lookup().findStatic(Main.class, "collectLong",
          MethodType.methodType(int.class, String.class, long[].class));
      assertEquals(49, (int) target.asCollector(long[].class, 2).invoke("a", 100, 99));
  
      target = MethodHandles.lookup().findStatic(Main.class, "collectFloat",
          MethodType.methodType(int.class, String.class, float[].class));
      assertEquals(50, (int) target.asCollector(float[].class, 2).invoke("a", 8.9f, 9.1f));
  
      target = MethodHandles.lookup().findStatic(Main.class, "collectDouble",
          MethodType.methodType(int.class, String.class, double[].class));
      assertEquals(51, (int) target.asCollector(double[].class, 2).invoke("a", 6.7, 7.8));
    }
  
    public static String filter1(char a) {
      return String.valueOf(a);
    }
  
    public static char filter2(String b) {
      return b.charAt(0);
    }
  
    public static String badFilter1(char a, char b) {
      return "bad";
    }
  
    public static int filterTarget(String a, char b, String c, char d) {
      System.out.println("a: " + a + ", b: " + b + ", c:" + c + ", d:" + d);
      return 56;
    }
  
    public static void testFilterArguments() throws Throwable {
      MethodHandle filter1 = MethodHandles.lookup().findStatic(
          Main.class, "filter1", MethodType.methodType(String.class, char.class));
      MethodHandle filter2 = MethodHandles.lookup().findStatic(
          Main.class, "filter2", MethodType.methodType(char.class, String.class));
  
      MethodHandle target = MethodHandles.lookup().findStatic(
          Main.class, "filterTarget", MethodType.methodType(int.class,
            String.class, char.class, String.class, char.class));
  
      // In all the cases below, the values printed will be 'a', 'b', 'c', 'd'.
  
      // Filter arguments [0, 1] - all other arguments are passed through
      // as is.
      MethodHandle adapter = MethodHandles.filterArguments(
          target, 0, filter1, filter2);
      assertEquals(56, (int) adapter.invokeExact('a', "bXXXX", "c", 'd'));
  
      // Filter arguments [1, 2].
      adapter = MethodHandles.filterArguments(target, 1, filter2, filter1);
      assertEquals(56, (int) adapter.invokeExact("a", "bXXXX", 'c', 'd'));
  
      // Filter arguments [2, 3].
      adapter = MethodHandles.filterArguments(target, 2, filter1, filter2);
      assertEquals(56, (int) adapter.invokeExact("a", 'b', 'c', "dXXXXX"));
  
      // Try out a few error cases :
  
      // The return types of the filter doesn't align with the expected argument
      // type of the target.
      try {
        adapter = MethodHandles.filterArguments(target, 2, filter2, filter1);
        fail();
      } catch (IllegalArgumentException expected) {
      }
  
      // There are more filters than arguments.
      try {
        adapter = MethodHandles.filterArguments(target, 3, filter2, filter1);
        fail();
      } catch (IllegalArgumentException expected) {
      }
  
      // We pass in an obviously bogus position.
      try {
        adapter = MethodHandles.filterArguments(target, -1, filter2, filter1);
        fail();
      } catch (ArrayIndexOutOfBoundsException expected) {
      }
  
      // We pass in a function that has more than one argument.
      MethodHandle badFilter1 = MethodHandles.lookup().findStatic(
          Main.class, "badFilter1",
          MethodType.methodType(String.class, char.class, char.class));
  
      try {
        adapter = MethodHandles.filterArguments(target, 0, badFilter1, filter2);
        fail();
      } catch (IllegalArgumentException expected) {
      }
    }
  
    static void voidFilter(char a, char b) {
      System.out.println("voidFilter");
    }
  
    static String filter(char a, char b) {
      return String.valueOf(a) + "+" + b;
    }
  
    static char badFilter(char a, char b) {
      return 0;
    }
  
    static int target(String a, String b, String c) {
      System.out.println("a: " + a + ", b: " + b + ", c: " + c);
      return 57;
    }
  
    public static void testCollectArguments() throws Throwable {
      // Test non-void filters.
      MethodHandle filter = MethodHandles.lookup().findStatic(
          Main.class, "filter",
          MethodType.methodType(String.class, char.class, char.class));
  
      MethodHandle target = MethodHandles.lookup().findStatic(
          Main.class, "target",
          MethodType.methodType(int.class, String.class, String.class, String.class));
  
      // Filter at position 0.
      MethodHandle adapter = MethodHandles.collectArguments(target, 0, filter);
      assertEquals(57, (int) adapter.invokeExact('a', 'b', "c", "d"));
  
      // Filter at position 1.
      adapter = MethodHandles.collectArguments(target, 1, filter);
      assertEquals(57, (int) adapter.invokeExact("a", 'b', 'c', "d"));
  
      // Filter at position 2.
      adapter = MethodHandles.collectArguments(target, 2, filter);
      assertEquals(57, (int) adapter.invokeExact("a", "b", 'c', 'd'));
  
      // Test void filters. Note that we're passing in one more argument
      // than usual because the filter returns nothing - we have to invoke with
      // the full set of filter args and the full set of target args.
      filter = MethodHandles.lookup().findStatic(Main.class, "voidFilter",
          MethodType.methodType(void.class, char.class, char.class));
      adapter = MethodHandles.collectArguments(target, 0, filter);
      assertEquals(57, (int) adapter.invokeExact('a', 'b', "a", "b", "c"));
  
      adapter = MethodHandles.collectArguments(target, 1, filter);
      assertEquals(57, (int) adapter.invokeExact("a", 'a', 'b', "b", "c"));
  
      // Test out a few failure cases.
      filter = MethodHandles.lookup().findStatic(
          Main.class, "filter",
          MethodType.methodType(String.class, char.class, char.class));
  
      // Bogus filter position.
      try {
        adapter = MethodHandles.collectArguments(target, 3, filter);
        fail();
      } catch (IndexOutOfBoundsException expected) {
      }
  
      // Mismatch in filter return type.
      filter = MethodHandles.lookup().findStatic(
          Main.class, "badFilter",
          MethodType.methodType(char.class, char.class, char.class));
      try {
        adapter = MethodHandles.collectArguments(target, 0, filter);
        fail();
      } catch (IllegalArgumentException expected) {
      }
    }
  
    static int insertReceiver(String a, int b, Integer c, String d) {
      System.out.println("a: " + a + ", b:" + b + ", c:" + c + ", d:" + d);
      return 73;
    }
  
    public static void testInsertArguments() throws Throwable {
      MethodHandle target = MethodHandles.lookup().findStatic(
          Main.class, "insertReceiver",
          MethodType.methodType(int.class,
            String.class, int.class, Integer.class, String.class));
  
      // Basic single element array inserted at position 0.
      MethodHandle adapter = MethodHandles.insertArguments(
          target, 0, new Object[] { "foo" });
      assertEquals(73, (int) adapter.invokeExact(45, Integer.valueOf(56), "bar"));
  
      // Exercise unboxing.
      adapter = MethodHandles.insertArguments(
          target, 1, new Object[] { Integer.valueOf(56), 57 });
      assertEquals(73, (int) adapter.invokeExact("foo", "bar"));
  
      // Exercise a widening conversion.
      adapter = MethodHandles.insertArguments(
          target, 1, new Object[] { (short) 56, Integer.valueOf(57) });
      assertEquals(73, (int) adapter.invokeExact("foo", "bar"));
  
      // Insert an argument at the last position.
      adapter = MethodHandles.insertArguments(
          target, 3, new Object[] { "bar" });
      assertEquals(73, (int) adapter.invokeExact("foo", 45, Integer.valueOf(46)));
  
      // Exercise a few error cases.
  
      // A reference type that can't be cast to another reference type.
      try {
        MethodHandles.insertArguments(target, 3, new Object[] { new Object() });
        fail();
      } catch (ClassCastException expected) {
      }
  
      // A boxed type that can't be unboxed correctly.
      try {
        MethodHandles.insertArguments(target, 1, new Object[] { Long.valueOf(56) });
        fail();
      } catch (ClassCastException expected) {
      }
    }
  
    public static String foldFilter(char a, char b) {
      return String.valueOf(a) + "+" + b;
    }
  
    public static void voidFoldFilter(String e, char a, char b) {
      System.out.println(String.valueOf(a) + "+" + b);
    }
  
    public static int foldTarget(String a, char b, char c, String d) {
      System.out.println("a: " + a + " ,b:" + b + " ,c:" + c + " ,d:" + d);
      return 89;
    }
  
    public static void mismatchedVoidFilter(Integer a) {
    }
  
    public static Integer mismatchedNonVoidFilter(char a, char b) {
      return null;
    }
  
    public static void testFoldArguments() throws Throwable {
      // Test non-void filters.
      MethodHandle filter = MethodHandles.lookup().findStatic(
          Main.class, "foldFilter",
          MethodType.methodType(String.class, char.class, char.class));
  
      MethodHandle target = MethodHandles.lookup().findStatic(
          Main.class, "foldTarget",
          MethodType.methodType(int.class, String.class,
            char.class, char.class, String.class));
  
      // Folder with a non-void type.
      MethodHandle adapter = MethodHandles.foldArguments(target, filter);
      assertEquals(89, (int) adapter.invokeExact('c', 'd', "e"));
  
      // Folder with a void type.
      filter = MethodHandles.lookup().findStatic(
          Main.class, "voidFoldFilter",
          MethodType.methodType(void.class, String.class, char.class, char.class));
      adapter = MethodHandles.foldArguments(target, filter);
      assertEquals(89, (int) adapter.invokeExact("a", 'c', 'd', "e"));
  
      // Test a few erroneous cases.
  
      filter = MethodHandles.lookup().findStatic(
          Main.class, "mismatchedVoidFilter",
          MethodType.methodType(void.class, Integer.class));
      try {
        adapter = MethodHandles.foldArguments(target, filter);
        fail();
      } catch (IllegalArgumentException expected) {
      }
  
      filter = MethodHandles.lookup().findStatic(
          Main.class, "mismatchedNonVoidFilter",
          MethodType.methodType(Integer.class, char.class, char.class));
      try {
        adapter = MethodHandles.foldArguments(target, filter);
        fail();
      } catch (IllegalArgumentException expected) {
      }
    }
  
    public static void fail() {
      System.out.println("FAIL");
      Thread.dumpStack();
    }
  
    public static void fail(String message) {
      System.out.println("fail: " + message);
      Thread.dumpStack();
    }
  
    public static void assertEquals(int i1, int i2) {
      if (i1 != i2) throw new AssertionError("Expected: " + i1 + " was " + i2);
    }
  
    public static void assertEquals(String s1, String s2) {
      if (s1 == s2) {
        return;
      }
  
      if (s1 != null && s2 != null && s1.equals(s2)) {
        return;
      }
  
      throw new AssertionError("assertEquals s1: " + s1 + ", s2: " + s2);
    }
  }
  