/* * Copyright 2009 Sun Microsystems, Inc. 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. Sun designates this * particular file as subject to the "Classpath" exception as provided * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ /* @test * @summary unit tests for java.dyn.MethodHandles * @compile -XDinvokedynamic MethodHandlesTest.java * @run junit/othervm -XX:+UnlockExperimentalVMOptions -XX:+EnableInvokeDynamic jdk.java.dyn.MethodHandlesTest */ package jdk.java.dyn; import java.dyn.*; import java.dyn.MethodHandles.Lookup; import java.lang.reflect.*; import java.util.*; import org.junit.*; import static org.junit.Assert.*; import static org.junit.Assume.assumeTrue; /** * * @author jrose */ public class MethodHandlesTest { // How much output? static int verbosity = 1; // Set this true during development if you want to fast-forward to // a particular new, non-working test. Tests which are known to // work (or have recently worked) test this flag and return on true. static boolean CAN_SKIP_WORKING = false; //static { CAN_SKIP_WORKING = true; } // Set true to test more calls. If false, some tests are just // lookups, without exercising the actual method handle. static boolean DO_MORE_CALLS = false; @Test public void testFirst() throws Throwable { verbosity += 9; try { // left blank for debugging } finally { verbosity -= 9; } } static final int MAX_ARG_INCREASE = 3; public MethodHandlesTest() { } @Before public void checkImplementedPlatform() { boolean platformOK = false; Properties properties = System.getProperties(); String vers = properties.getProperty("java.vm.version"); String name = properties.getProperty("java.vm.name"); String arch = properties.getProperty("os.arch"); if (arch.equals("i386") && (name.contains("Client") || name.contains("Server")) ) { platformOK = true; } else { System.err.println("Skipping tests for unsupported platform: "+Arrays.asList(vers, name, arch)); } assumeTrue(platformOK); } String testName; int posTests, negTests; @After public void printCounts() { if (verbosity >= 1 && (posTests | negTests) != 0) { System.out.println(); if (posTests != 0) System.out.println("=== "+testName+": "+posTests+" positive test cases run"); if (negTests != 0) System.out.println("=== "+testName+": "+negTests+" negative test cases run"); } } void countTest(boolean positive) { if (positive) ++posTests; else ++negTests; } void countTest() { countTest(true); } void startTest(String name) { if (testName != null) printCounts(); if (verbosity >= 0) System.out.println(name); posTests = negTests = 0; testName = name; } @BeforeClass public static void setUpClass() throws Exception { calledLog.clear(); calledLog.add(null); nextArg = 1000000; } @AfterClass public static void tearDownClass() throws Exception { } static List calledLog = new ArrayList(); static Object logEntry(String name, Object... args) { return Arrays.asList(name, Arrays.asList(args)); } static Object called(String name, Object... args) { Object entry = logEntry(name, args); calledLog.add(entry); return entry; } static void assertCalled(String name, Object... args) { Object expected = logEntry(name, args); Object actual = calledLog.get(calledLog.size() - 1); if (expected.equals(actual)) return; System.out.println("assertCalled "+name+":"); System.out.println("expected: "+expected); System.out.println("actual: "+actual); System.out.println("ex. types: "+getClasses(expected)); System.out.println("act. types: "+getClasses(actual)); assertEquals("previous method call types", expected, actual); assertEquals("previous method call", expected, actual); } static void printCalled(MethodHandle target, String name, Object... args) { if (verbosity >= 2) System.out.println("calling "+logEntry(name, args)+" on "+target); } static Object castToWrapper(Object value, Class dst) { Object wrap = null; if (value instanceof Number) wrap = castToWrapperOrNull(((Number)value).longValue(), dst); if (value instanceof Character) wrap = castToWrapperOrNull((char)(Character)value, dst); if (wrap != null) return wrap; return dst.cast(value); } static Object castToWrapperOrNull(long value, Class dst) { if (dst == int.class || dst == Integer.class) return (int)(value); if (dst == long.class || dst == Long.class) return (long)(value); if (dst == char.class || dst == Character.class) return (char)(value); if (dst == short.class || dst == Short.class) return (short)(value); if (dst == float.class || dst == Float.class) return (float)(value); if (dst == double.class || dst == Double.class) return (double)(value); return null; } static int nextArg; static Object randomArg(Class param) { Object wrap = castToWrapperOrNull(nextArg, param); if (wrap != null) { nextArg++; return wrap; } // import sun.dyn.util.Wrapper; // Wrapper wrap = Wrapper.forBasicType(dst); // if (wrap == Wrapper.OBJECT && Wrapper.isWrapperType(dst)) // wrap = Wrapper.forWrapperType(dst); // if (wrap != Wrapper.OBJECT) // return wrap.wrap(nextArg++); if (param.isInterface() || param.isAssignableFrom(String.class)) return "#"+(nextArg++); else try { return param.newInstance(); } catch (InstantiationException ex) { } catch (IllegalAccessException ex) { } return null; // random class not Object, String, Integer, etc. } static Object[] randomArgs(Class... params) { Object[] args = new Object[params.length]; for (int i = 0; i < args.length; i++) args[i] = randomArg(params[i]); return args; } static Object[] randomArgs(int nargs, Class param) { Object[] args = new Object[nargs]; for (int i = 0; i < args.length; i++) args[i] = randomArg(param); return args; } static T[] array(Class atype, E... a) { return Arrays.copyOf(a, a.length, atype); } static T[] cat(T[] a, T... b) { int alen = a.length, blen = b.length; if (blen == 0) return a; T[] c = Arrays.copyOf(a, alen + blen); System.arraycopy(b, 0, c, alen, blen); return c; } static Integer[] boxAll(int... vx) { Integer[] res = new Integer[vx.length]; for (int i = 0; i < res.length; i++) { res[i] = vx[i]; } return res; } static Object getClasses(Object x) { if (x == null) return x; if (x instanceof String) return x; // keep the name if (x instanceof List) { // recursively report classes of the list elements Object[] xa = ((List)x).toArray(); for (int i = 0; i < xa.length; i++) xa[i] = getClasses(xa[i]); return Arrays.asList(xa); } return x.getClass().getSimpleName(); } static MethodHandle changeArgTypes(MethodHandle target, Class argType) { return changeArgTypes(target, 0, 999, argType); } static MethodHandle changeArgTypes(MethodHandle target, int beg, int end, Class argType) { MethodType targetType = target.type(); end = Math.min(end, targetType.parameterCount()); ArrayList> argTypes = new ArrayList>(targetType.parameterList()); Collections.fill(argTypes.subList(beg, end), argType); MethodType ttype2 = MethodType.make(targetType.returnType(), argTypes); return MethodHandles.convertArguments(target, ttype2); } // This lookup is good for all members in and under MethodHandlesTest. static final Lookup PRIVATE = MethodHandles.lookup(); // This lookup is good for package-private members but not private ones. static final Lookup PACKAGE = PackageSibling.lookup(); // This lookup is good only for public members. static final Lookup PUBLIC = MethodHandles.Lookup.PUBLIC_LOOKUP; // Subject methods... static class Example implements IntExample { final String name; public Example() { name = "Example#"+(nextArg++); } protected Example(String name) { this.name = name; } protected Example(int x) { this(); called("protected ", this, x); } @Override public String toString() { return name; } public void v0() { called("v0", this); } void pkg_v0() { called("pkg_v0", this); } private void pri_v0() { called("pri_v0", this); } public static void s0() { called("s0"); } static void pkg_s0() { called("pkg_s0"); } private static void pri_s0() { called("pri_s0"); } public Object v1(Object x) { return called("v1", this, x); } public Object v2(Object x, Object y) { return called("v2", this, x, y); } public Object v2(Object x, int y) { return called("v2", this, x, y); } public Object v2(int x, Object y) { return called("v2", this, x, y); } public Object v2(int x, int y) { return called("v2", this, x, y); } public static Object s1(Object x) { return called("s1", x); } public static Object s2(int x) { return called("s2", x); } public static Object s3(long x) { return called("s3", x); } public static Object s4(int x, int y) { return called("s4", x, y); } public static Object s5(long x, int y) { return called("s5", x, y); } public static Object s6(int x, long y) { return called("s6", x, y); } public static Object s7(float x, double y) { return called("s7", x, y); } } public static class PubExample extends Example { } static class SubExample extends Example { @Override public void v0() { called("Sub/v0", this); } @Override void pkg_v0() { called("Sub/pkg_v0", this); } private SubExample(int x) { called("", this, x); } public SubExample() { super("SubExample#"+(nextArg++)); } } public static interface IntExample { public void v0(); static class Impl implements IntExample { public void v0() { called("Int/v0", this); } final String name; public Impl() { name = "Example#"+(nextArg++); } } } static final Object[][][] ACCESS_CASES = { { { true, PRIVATE } } // only one test case at present }; static Object[][] accessCases(Class defc, String name) { return ACCESS_CASES[0]; } @Test public void testFindStatic() throws Throwable { if (CAN_SKIP_WORKING) return; startTest("findStatic"); testFindStatic(PubExample.class, void.class, "s0"); testFindStatic(Example.class, void.class, "s0"); testFindStatic(Example.class, void.class, "pkg_s0"); testFindStatic(Example.class, void.class, "pri_s0"); testFindStatic(Example.class, Object.class, "s1", Object.class); testFindStatic(Example.class, Object.class, "s2", int.class); testFindStatic(Example.class, Object.class, "s3", long.class); testFindStatic(Example.class, Object.class, "s4", int.class, int.class); testFindStatic(Example.class, Object.class, "s5", long.class, int.class); testFindStatic(Example.class, Object.class, "s6", int.class, long.class); testFindStatic(Example.class, Object.class, "s7", float.class, double.class); testFindStatic(false, PRIVATE, Example.class, void.class, "bogus"); } void testFindStatic(Class defc, Class ret, String name, Class... params) throws Throwable { for (Object[] ac : accessCases(defc, name)) { testFindStatic((Boolean)ac[0], (Lookup)ac[1], defc, ret, name, params); } } void testFindStatic(Lookup lookup, Class defc, Class ret, String name, Class... params) throws Throwable { testFindStatic(true, lookup, defc, ret, name, params); } void testFindStatic(boolean positive, Lookup lookup, Class defc, Class ret, String name, Class... params) throws Throwable { countTest(positive); MethodType type = MethodType.make(ret, params); MethodHandle target = null; RuntimeException noAccess = null; try { target = lookup.findStatic(defc, name, type); } catch (NoAccessException ex) { noAccess = ex; } if (verbosity >= 2) System.out.println("findStatic "+lookup+": "+defc+"."+name+"/"+type+" => "+target +(noAccess == null ? "" : " !! "+noAccess)); if (positive && noAccess != null) throw noAccess; assertEquals(positive ? "positive test" : "negative test erroneously passed", positive, target != null); if (!positive) return; // negative test failed as expected assertEquals(type, target.type()); assertTrue(target.toString().contains(name)); // rough check if (!DO_MORE_CALLS && lookup != PRIVATE) return; Object[] args = randomArgs(params); printCalled(target, name, args); MethodHandles.invoke(target, args); assertCalled(name, args); System.out.print(':'); } @Test public void testFindVirtual() throws Throwable { if (CAN_SKIP_WORKING) return; startTest("findVirtual"); testFindVirtual(Example.class, void.class, "v0"); testFindVirtual(Example.class, void.class, "pkg_v0"); testFindVirtual(Example.class, void.class, "pri_v0"); testFindVirtual(Example.class, Object.class, "v1", Object.class); testFindVirtual(Example.class, Object.class, "v2", Object.class, Object.class); testFindVirtual(Example.class, Object.class, "v2", Object.class, int.class); testFindVirtual(Example.class, Object.class, "v2", int.class, Object.class); testFindVirtual(Example.class, Object.class, "v2", int.class, int.class); testFindVirtual(false, PRIVATE, Example.class, Example.class, void.class, "bogus"); // test dispatch testFindVirtual(SubExample.class, SubExample.class, void.class, "Sub/v0"); testFindVirtual(SubExample.class, Example.class, void.class, "Sub/v0"); testFindVirtual(SubExample.class, IntExample.class, void.class, "Sub/v0"); testFindVirtual(SubExample.class, SubExample.class, void.class, "Sub/pkg_v0"); testFindVirtual(SubExample.class, Example.class, void.class, "Sub/pkg_v0"); testFindVirtual(Example.class, IntExample.class, void.class, "v0"); testFindVirtual(IntExample.Impl.class, IntExample.class, void.class, "Int/v0"); } void testFindVirtual(Class defc, Class ret, String name, Class... params) throws Throwable { Class rcvc = defc; testFindVirtual(rcvc, defc, ret, name, params); } void testFindVirtual(Class rcvc, Class defc, Class ret, String name, Class... params) throws Throwable { for (Object[] ac : accessCases(defc, name)) { testFindVirtual((Boolean)ac[0], (Lookup)ac[1], rcvc, defc, ret, name, params); } } void testFindVirtual(Lookup lookup, Class rcvc, Class defc, Class ret, String name, Class... params) throws Throwable { testFindVirtual(true, lookup, rcvc, defc, ret, name, params); } void testFindVirtual(boolean positive, Lookup lookup, Class rcvc, Class defc, Class ret, String name, Class... params) throws Throwable { countTest(positive); String methodName = name.substring(1 + name.indexOf('/')); // foo/bar => foo MethodType type = MethodType.make(ret, params); MethodHandle target = null; RuntimeException noAccess = null; try { target = lookup.findVirtual(defc, methodName, type); } catch (NoAccessException ex) { noAccess = ex; } if (verbosity >= 2) System.out.println("findVirtual "+lookup+": "+defc+"."+name+"/"+type+" => "+target +(noAccess == null ? "" : " !! "+noAccess)); if (positive && noAccess != null) throw noAccess; assertEquals(positive ? "positive test" : "negative test erroneously passed", positive, target != null); if (!positive) return; // negative test failed as expected Class[] paramsWithSelf = cat(array(Class[].class, (Class)defc), params); MethodType typeWithSelf = MethodType.make(ret, paramsWithSelf); MethodType ttype = target.type(); ttype = ttype.changeParameterType(0, defc); // FIXME: test this assertEquals(typeWithSelf, ttype); assertTrue(target.toString().contains(methodName)); // rough check if (!DO_MORE_CALLS && lookup != PRIVATE) return; Object[] argsWithSelf = randomArgs(paramsWithSelf); if (rcvc != defc) argsWithSelf[0] = randomArg(rcvc); printCalled(target, name, argsWithSelf); MethodHandles.invoke(target, argsWithSelf); assertCalled(name, argsWithSelf); System.out.print(':'); } @Test public void testFindSpecial() throws Throwable { if (CAN_SKIP_WORKING) return; startTest("findSpecial"); testFindSpecial(Example.class, void.class, "v0"); testFindSpecial(Example.class, void.class, "pkg_v0"); testFindSpecial(false, PRIVATE, Example.class, void.class, "", int.class); testFindSpecial(false, PRIVATE, Example.class, void.class, "bogus"); } void testFindSpecial(Class defc, Class ret, String name, Class... params) throws Throwable { testFindSpecial(true, PRIVATE, defc, ret, name, params); testFindSpecial(false, PACKAGE, defc, ret, name, params); testFindSpecial(false, PUBLIC, defc, ret, name, params); } void testFindSpecial(boolean positive, Lookup lookup, Class defc, Class ret, String name, Class... params) throws Throwable { countTest(positive); MethodType type = MethodType.make(ret, params); MethodHandle target = null; RuntimeException noAccess = null; try { target = lookup.findSpecial(defc, name, type, defc); } catch (NoAccessException ex) { noAccess = ex; } if (verbosity >= 2) System.out.println("findSpecial "+defc+"."+name+"/"+type+" => "+target +(noAccess == null ? "" : " !! "+noAccess)); if (positive && noAccess != null) throw noAccess; assertEquals(positive ? "positive test" : "negative test erroneously passed", positive, target != null); if (!positive) return; // negative test failed as expected Class[] paramsWithSelf = cat(array(Class[].class, (Class)defc), params); MethodType typeWithSelf = MethodType.make(ret, paramsWithSelf); MethodType ttype = target.type(); ttype = ttype.changeParameterType(0, defc); // FIXME: test this assertEquals(typeWithSelf, ttype); assertTrue(target.toString().contains(name)); // rough check if (!DO_MORE_CALLS && lookup != PRIVATE) return; Object[] args = randomArgs(paramsWithSelf); printCalled(target, name, args); MethodHandles.invoke(target, args); assertCalled(name, args); System.out.print(':'); } @Test public void testBind() throws Throwable { if (CAN_SKIP_WORKING) return; startTest("bind"); testBind(Example.class, void.class, "v0"); testBind(Example.class, void.class, "pkg_v0"); testBind(Example.class, void.class, "pri_v0"); testBind(Example.class, Object.class, "v1", Object.class); testBind(Example.class, Object.class, "v2", Object.class, Object.class); testBind(Example.class, Object.class, "v2", Object.class, int.class); testBind(Example.class, Object.class, "v2", int.class, Object.class); testBind(Example.class, Object.class, "v2", int.class, int.class); testBind(false, PRIVATE, Example.class, void.class, "bogus"); testBind(SubExample.class, void.class, "Sub/v0"); testBind(SubExample.class, void.class, "Sub/pkg_v0"); testBind(IntExample.Impl.class, void.class, "Int/v0"); } void testBind(Class defc, Class ret, String name, Class... params) throws Throwable { for (Object[] ac : accessCases(defc, name)) { testBind((Boolean)ac[0], (Lookup)ac[1], defc, ret, name, params); } } void testBind(boolean positive, Lookup lookup, Class defc, Class ret, String name, Class... params) throws Throwable { countTest(positive); String methodName = name.substring(1 + name.indexOf('/')); // foo/bar => foo MethodType type = MethodType.make(ret, params); Object receiver = randomArg(defc); MethodHandle target = null; RuntimeException noAccess = null; try { target = lookup.bind(receiver, methodName, type); } catch (NoAccessException ex) { noAccess = ex; } if (verbosity >= 2) System.out.println("bind "+receiver+"."+name+"/"+type+" => "+target +(noAccess == null ? "" : " !! "+noAccess)); if (positive && noAccess != null) throw noAccess; assertEquals(positive ? "positive test" : "negative test erroneously passed", positive, target != null); if (!positive) return; // negative test failed as expected assertEquals(type, target.type()); Object[] args = randomArgs(params); printCalled(target, name, args); MethodHandles.invoke(target, args); Object[] argsWithReceiver = cat(array(Object[].class, receiver), args); assertCalled(name, argsWithReceiver); System.out.print(':'); } @Test public void testUnreflect() throws Throwable { if (CAN_SKIP_WORKING) return; startTest("unreflect"); testUnreflect(Example.class, true, void.class, "s0"); testUnreflect(Example.class, true, void.class, "pkg_s0"); testUnreflect(Example.class, true, void.class, "pri_s0"); testUnreflect(Example.class, true, Object.class, "s1", Object.class); testUnreflect(Example.class, true, Object.class, "s2", int.class); //testUnreflect(Example.class, true, Object.class, "s3", long.class); //testUnreflect(Example.class, true, Object.class, "s4", int.class, int.class); //testUnreflect(Example.class, true, Object.class, "s5", long.class, int.class); //testUnreflect(Example.class, true, Object.class, "s6", int.class, long.class); testUnreflect(Example.class, false, void.class, "v0"); testUnreflect(Example.class, false, void.class, "pkg_v0"); testUnreflect(Example.class, false, void.class, "pri_v0"); testUnreflect(Example.class, false, Object.class, "v1", Object.class); testUnreflect(Example.class, false, Object.class, "v2", Object.class, Object.class); testUnreflect(Example.class, false, Object.class, "v2", Object.class, int.class); testUnreflect(Example.class, false, Object.class, "v2", int.class, Object.class); testUnreflect(Example.class, false, Object.class, "v2", int.class, int.class); } void testUnreflect(Class defc, boolean isStatic, Class ret, String name, Class... params) throws Throwable { for (Object[] ac : accessCases(defc, name)) { testUnreflect((Boolean)ac[0], (Lookup)ac[1], defc, isStatic, ret, name, params); } } void testUnreflect(boolean positive, Lookup lookup, Class defc, boolean isStatic, Class ret, String name, Class... params) throws Throwable { countTest(positive); MethodType type = MethodType.make(ret, params); Method rmethod = null; MethodHandle target = null; RuntimeException noAccess = null; try { rmethod = defc.getDeclaredMethod(name, params); } catch (NoSuchMethodException ex) { throw new NoAccessException(ex); } assertEquals(isStatic, Modifier.isStatic(rmethod.getModifiers())); try { target = lookup.unreflect(rmethod); } catch (NoAccessException ex) { noAccess = ex; } if (verbosity >= 2) System.out.println("unreflect "+defc+"."+name+"/"+type+" => "+target +(noAccess == null ? "" : " !! "+noAccess)); if (positive && noAccess != null) throw noAccess; assertEquals(positive ? "positive test" : "negative test erroneously passed", positive, target != null); if (!positive) return; // negative test failed as expected Class[] paramsMaybeWithSelf = params; if (!isStatic) { paramsMaybeWithSelf = cat(array(Class[].class, (Class)defc), params); } MethodType typeMaybeWithSelf = MethodType.make(ret, paramsMaybeWithSelf); MethodType ttype = target.type(); if (!isStatic) ttype = ttype.changeParameterType(0, defc); // FIXME: test this assertEquals(typeMaybeWithSelf, ttype); Object[] argsMaybeWithSelf = randomArgs(paramsMaybeWithSelf); printCalled(target, name, argsMaybeWithSelf); MethodHandles.invoke(target, argsMaybeWithSelf); assertCalled(name, argsMaybeWithSelf); System.out.print(':'); } @Test @Ignore("unimplemented") public void testUnreflectSpecial() throws Throwable { Lookup lookup = PRIVATE; // FIXME: test more lookups than this one startTest("unreflectSpecial"); Method m = null; MethodHandle expResult = null; MethodHandle result = lookup.unreflectSpecial(m, Example.class); assertEquals(expResult, result); fail("The test case is a prototype."); } @Test @Ignore("unimplemented") public void testUnreflectGetter() throws Throwable { Lookup lookup = PRIVATE; // FIXME: test more lookups than this one startTest("unreflectGetter"); Field f = null; MethodHandle expResult = null; MethodHandle result = lookup.unreflectGetter(f); assertEquals(expResult, result); fail("The test case is a prototype."); } @Test @Ignore("unimplemented") public void testUnreflectSetter() throws Throwable { Lookup lookup = PRIVATE; // FIXME: test more lookups than this one startTest("unreflectSetter"); Field f = null; MethodHandle expResult = null; MethodHandle result = lookup.unreflectSetter(f); assertEquals(expResult, result); fail("The test case is a prototype."); } @Test @Ignore("unimplemented") public void testArrayElementGetter() throws Throwable { startTest("arrayElementGetter"); Class arrayClass = null; MethodHandle expResult = null; MethodHandle result = MethodHandles.arrayElementGetter(arrayClass); assertEquals(expResult, result); fail("The test case is a prototype."); } @Test @Ignore("unimplemented") public void testArrayElementSetter() throws Throwable { startTest("arrayElementSetter"); Class arrayClass = null; MethodHandle expResult = null; MethodHandle result = MethodHandles.arrayElementSetter(arrayClass); assertEquals(expResult, result); fail("The test case is a prototype."); } static class Callee { static Object id() { return called("id"); } static Object id(Object x) { return called("id", x); } static Object id(Object x, Object y) { return called("id", x, y); } static Object id(Object x, Object y, Object z) { return called("id", x, y, z); } static Object id(Object... vx) { return called("id", vx); } static MethodHandle ofType(int n) { return ofType(Object.class, n); } static MethodHandle ofType(Class rtype, int n) { if (n == -1) return ofType(MethodType.make(rtype, Object[].class)); return ofType(MethodType.makeGeneric(n).changeReturnType(rtype)); } static MethodHandle ofType(Class rtype, Class... ptypes) { return ofType(MethodType.make(rtype, ptypes)); } static MethodHandle ofType(MethodType type) { Class rtype = type.returnType(); String pfx = ""; if (rtype != Object.class) pfx = rtype.getSimpleName().substring(0, 1).toLowerCase(); String name = pfx+"id"; return PRIVATE.findStatic(Callee.class, name, type); } } @Test public void testConvertArguments() throws Throwable { if (CAN_SKIP_WORKING) return; startTest("convertArguments"); testConvert(Callee.ofType(1), null, "id", int.class); testConvert(Callee.ofType(1), null, "id", String.class); testConvert(Callee.ofType(1), null, "id", Integer.class); testConvert(Callee.ofType(1), null, "id", short.class); } void testConvert(MethodHandle id, Class rtype, String name, Class... params) throws Throwable { testConvert(true, id, rtype, name, params); } void testConvert(boolean positive, MethodHandle id, Class rtype, String name, Class... params) throws Throwable { countTest(positive); MethodType idType = id.type(); if (rtype == null) rtype = idType.returnType(); for (int i = 0; i < params.length; i++) { if (params[i] == null) params[i] = idType.parameterType(i); } // simulate the pairwise conversion MethodType newType = MethodType.make(rtype, params); Object[] args = randomArgs(newType.parameterArray()); Object[] convArgs = args.clone(); for (int i = 0; i < args.length; i++) { Class src = newType.parameterType(i); Class dst = idType.parameterType(i); if (src != dst) convArgs[i] = castToWrapper(convArgs[i], dst); } Object convResult = MethodHandles.invoke(id, convArgs); { Class dst = newType.returnType(); Class src = idType.returnType(); if (src != dst) convResult = castToWrapper(convResult, dst); } MethodHandle target = null; RuntimeException error = null; try { target = MethodHandles.convertArguments(id, newType); } catch (RuntimeException ex) { error = ex; } if (verbosity >= 2) System.out.println("convert "+id+ " to "+newType+" => "+target +(error == null ? "" : " !! "+error)); if (positive && error != null) throw error; assertEquals(positive ? "positive test" : "negative test erroneously passed", positive, target != null); if (!positive) return; // negative test failed as expected assertEquals(newType, target.type()); printCalled(target, id.toString(), args); Object result = MethodHandles.invoke(target, args); assertCalled(name, convArgs); assertEquals(convResult, result); System.out.print(':'); } @Test public void testInsertArguments() throws Throwable { if (CAN_SKIP_WORKING) return; startTest("insertArguments"); for (int nargs = 0; nargs <= 4; nargs++) { for (int ins = 0; ins <= 4; ins++) { if (ins > MAX_ARG_INCREASE) continue; // FIXME Fail_6 for (int pos = 0; pos <= nargs; pos++) { testInsertArguments(nargs, pos, ins); } } } } void testInsertArguments(int nargs, int pos, int ins) throws Throwable { if (pos != 0 || ins != 1) return; // temp. restriction until MHs.insertArguments countTest(); MethodHandle target = ValueConversions.varargsArray(nargs + ins); Object[] args = randomArgs(target.type().parameterArray()); List resList = Arrays.asList(args); List argsToPass = new ArrayList(resList); List argsToInsert = argsToPass.subList(pos, pos + ins); if (verbosity >= 2) System.out.println("insert: "+argsToInsert+" into "+target); MethodHandle target2 = MethodHandles.insertArgument(target, pos, argsToInsert.get(0)); argsToInsert.clear(); // remove from argsToInsert Object res2 = MethodHandles.invoke(target2, argsToPass.toArray()); Object res2List = Arrays.asList((Object[])res2); if (verbosity >= 2) System.out.println("result: "+res2List); //if (!resList.equals(res2List)) // System.out.println("*** fail at n/p/i = "+nargs+"/"+pos+"/"+ins+": "+resList+" => "+res2List); assertEquals(resList, res2List); } private static final String MISSING_ARG = "missingArg"; static Object targetIfEquals() { return called("targetIfEquals"); } static Object fallbackIfNotEquals() { return called("fallbackIfNotEquals"); } static Object targetIfEquals(Object x) { assertEquals(x, MISSING_ARG); return called("targetIfEquals", x); } static Object fallbackIfNotEquals(Object x) { assertFalse(x.toString(), x.equals(MISSING_ARG)); return called("fallbackIfNotEquals", x); } static Object targetIfEquals(Object x, Object y) { assertEquals(x, y); return called("targetIfEquals", x, y); } static Object fallbackIfNotEquals(Object x, Object y) { assertFalse(x.toString(), x.equals(y)); return called("fallbackIfNotEquals", x, y); } static Object targetIfEquals(Object x, Object y, Object z) { assertEquals(x, y); return called("targetIfEquals", x, y, z); } static Object fallbackIfNotEquals(Object x, Object y, Object z) { assertFalse(x.toString(), x.equals(y)); return called("fallbackIfNotEquals", x, y, z); } } // Local abbreviated copy of sun.dyn.util.ValueConversions class ValueConversions { private static final Lookup IMPL_LOOKUP = MethodHandles.lookup(); private static final Object[] NO_ARGS_ARRAY = {}; private static Object[] makeArray(Object... args) { return args; } private static Object[] array() { return NO_ARGS_ARRAY; } private static Object[] array(Object a0) { return makeArray(a0); } private static Object[] array(Object a0, Object a1) { return makeArray(a0, a1); } private static Object[] array(Object a0, Object a1, Object a2) { return makeArray(a0, a1, a2); } private static Object[] array(Object a0, Object a1, Object a2, Object a3) { return makeArray(a0, a1, a2, a3); } private static Object[] array(Object a0, Object a1, Object a2, Object a3, Object a4) { return makeArray(a0, a1, a2, a3, a4); } private static Object[] array(Object a0, Object a1, Object a2, Object a3, Object a4, Object a5) { return makeArray(a0, a1, a2, a3, a4, a5); } private static Object[] array(Object a0, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6) { return makeArray(a0, a1, a2, a3, a4, a5, a6); } private static Object[] array(Object a0, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7) { return makeArray(a0, a1, a2, a3, a4, a5, a6, a7); } private static Object[] array(Object a0, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7, Object a8) { return makeArray(a0, a1, a2, a3, a4, a5, a6, a7, a8); } private static Object[] array(Object a0, Object a1, Object a2, Object a3, Object a4, Object a5, Object a6, Object a7, Object a8, Object a9) { return makeArray(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9); } static MethodHandle[] makeArrays() { ArrayList arrays = new ArrayList(); MethodHandles.Lookup lookup = IMPL_LOOKUP; for (;;) { int nargs = arrays.size(); MethodType type = MethodType.makeGeneric(nargs).changeReturnType(Object[].class); String name = "array"; MethodHandle array = null; try { array = lookup.findStatic(ValueConversions.class, name, type); } catch (NoAccessException ex) { } if (array == null) break; arrays.add(array); } assert(arrays.size() == 11); // current number of methods return arrays.toArray(new MethodHandle[0]); } static final MethodHandle[] ARRAYS = makeArrays(); /** Return a method handle that takes the indicated number of Object * arguments and returns an Object array of them, as if for varargs. */ public static MethodHandle varargsArray(int nargs) { if (nargs < ARRAYS.length) return ARRAYS[nargs]; // else need to spin bytecode or do something else fancy throw new UnsupportedOperationException("NYI"); } } // This guy tests access from outside the same package member, but inside // the package itself. class PackageSibling { static Lookup lookup() { return MethodHandles.lookup(); } }