| /* | |
| * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| /* @test | |
| * @summary unit tests for java.lang.invoke.MethodHandle.invoke | |
| * @compile InvokeGenericTest.java | |
| * @run testng/othervm test.java.lang.invoke.InvokeGenericTest | |
| */ | |
| package test.java.lang.invoke; | |
| import java.lang.invoke.*; | |
| import static java.lang.invoke.MethodHandles.*; | |
| import static java.lang.invoke.MethodType.*; | |
| import java.lang.reflect.*; | |
| import java.util.*; | |
| import org.testng.*; | |
| import static org.testng.AssertJUnit.*; | |
| import org.testng.annotations.*; | |
| /** | |
| * | |
| * @author jrose | |
| */ | |
| @SuppressWarnings("cast") // various casts help emphasize arguments to invokeExact | |
| public class InvokeGenericTest { | |
| // How much output? | |
| static int verbosity = 0; | |
| static { | |
| String vstr = System.getProperty("test.java.lang.invoke.InvokeGenericTest.verbosity"); | |
| if (vstr != null) verbosity = Integer.parseInt(vstr); | |
| } | |
| // public static void main(String... av) throws Throwable { | |
| // new InvokeGenericTest().testFirst(); | |
| // } | |
| @Test | |
| public void testFirst() throws Throwable { | |
| verbosity += 9; try { | |
| // left blank for debugging | |
| } finally { printCounts(); verbosity -= 9; } | |
| } | |
| public InvokeGenericTest() { | |
| } | |
| String testName; | |
| static int allPosTests, allNegTests; | |
| int posTests, negTests; | |
| @AfterMethod | |
| public void printCounts() { | |
| if (verbosity >= 2 && (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"); | |
| allPosTests += posTests; | |
| allNegTests += negTests; | |
| posTests = negTests = 0; | |
| } | |
| } | |
| void countTest(boolean positive) { | |
| if (positive) ++posTests; | |
| else ++negTests; | |
| } | |
| void countTest() { countTest(true); } | |
| void startTest(String name) { | |
| if (testName != null) printCounts(); | |
| if (verbosity >= 1) | |
| System.out.println("["+name+"]"); | |
| posTests = negTests = 0; | |
| testName = name; | |
| } | |
| @BeforeClass | |
| public static void setUpClass() throws Exception { | |
| calledLog.clear(); | |
| calledLog.add(null); | |
| nextArgVal = INITIAL_ARG_VAL; | |
| } | |
| @AfterClass | |
| public static void tearDownClass() throws Exception { | |
| int posTests = allPosTests, negTests = allNegTests; | |
| if (verbosity >= 2 && (posTests | negTests) != 0) { | |
| System.out.println(); | |
| if (posTests != 0) System.out.println("=== "+posTests+" total positive test cases"); | |
| if (negTests != 0) System.out.println("=== "+negTests+" total negative test cases"); | |
| } | |
| } | |
| static List<Object> 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) && verbosity < 9) 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", expected, actual); | |
| } | |
| static void printCalled(MethodHandle target, String name, Object... args) { | |
| if (verbosity >= 3) | |
| System.out.println("calling MH="+target+" to "+name+Arrays.toString(args)); | |
| } | |
| 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); | |
| if (dst == byte.class || dst == Byte.class) | |
| return (byte)(value); | |
| if (dst == boolean.class || dst == boolean.class) | |
| return ((value % 29) & 1) == 0; | |
| return null; | |
| } | |
| static final int ONE_MILLION = (1000*1000), // first int value | |
| TEN_BILLION = (10*1000*1000*1000), // scale factor to reach upper 32 bits | |
| INITIAL_ARG_VAL = ONE_MILLION << 1; // <<1 makes space for sign bit; | |
| static long nextArgVal; | |
| static long nextArg(boolean moreBits) { | |
| long val = nextArgVal++; | |
| long sign = -(val & 1); // alternate signs | |
| val >>= 1; | |
| if (moreBits) | |
| // Guarantee some bits in the high word. | |
| // In any case keep the decimal representation simple-looking, | |
| // with lots of zeroes, so as not to make the printed decimal | |
| // strings unnecessarily noisy. | |
| val += (val % ONE_MILLION) * TEN_BILLION; | |
| return val ^ sign; | |
| } | |
| static int nextArg() { | |
| // Produce a 32-bit result something like ONE_MILLION+(smallint). | |
| // Example: 1_000_042. | |
| return (int) nextArg(false); | |
| } | |
| static long nextArg(Class<?> kind) { | |
| if (kind == long.class || kind == Long.class || | |
| kind == double.class || kind == Double.class) | |
| // produce a 64-bit result something like | |
| // ((TEN_BILLION+1) * (ONE_MILLION+(smallint))) | |
| // Example: 10_000_420_001_000_042. | |
| return nextArg(true); | |
| return (long) nextArg(); | |
| } | |
| static Object randomArg(Class<?> param) { | |
| Object wrap = castToWrapperOrNull(nextArg(param), param); | |
| if (wrap != null) { | |
| return wrap; | |
| } | |
| // import sun.invoke.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()) { | |
| for (Class<?> c : param.getClasses()) { | |
| if (param.isAssignableFrom(c) && !c.isInterface()) | |
| { param = c; break; } | |
| } | |
| } | |
| if (param.isInterface() || param.isAssignableFrom(String.class)) | |
| return "#"+nextArg(); | |
| else | |
| try { | |
| return param.newInstance(); | |
| } catch (InstantiationException | 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 final Object ANON_OBJ = new Object(); | |
| static Object zeroArg(Class<?> param) { | |
| Object x = castToWrapperOrNull(0L, param); | |
| if (x != null) return x; | |
| if (param.isInterface() || param.isAssignableFrom(String.class)) return "\"\""; | |
| if (param == Object.class) return ANON_OBJ; | |
| if (param.getComponentType() != null) return Array.newInstance(param.getComponentType(), 0); | |
| return null; | |
| } | |
| static Object[] zeroArgs(Class<?>... params) { | |
| Object[] args = new Object[params.length]; | |
| for (int i = 0; i < args.length; i++) | |
| args[i] = zeroArg(params[i]); | |
| return args; | |
| } | |
| static Object[] zeroArgs(List<Class<?>> params) { | |
| return zeroArgs(params.toArray(new Class<?>[0])); | |
| } | |
| @SafeVarargs @SuppressWarnings("varargs") | |
| static <T, E extends T> T[] array(Class<T[]> atype, E... a) { | |
| return Arrays.copyOf(a, a.length, atype); | |
| } | |
| @SafeVarargs @SuppressWarnings("varargs") | |
| static <T> 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<Class<?>> argTypes = new ArrayList<>(targetType.parameterList()); | |
| Collections.fill(argTypes.subList(beg, end), argType); | |
| MethodType ttype2 = MethodType.methodType(targetType.returnType(), argTypes); | |
| return target.asType(ttype2); | |
| } | |
| // This lookup is good for all members in and under InvokeGenericTest. | |
| static final Lookup LOOKUP = MethodHandles.lookup(); | |
| Map<List<Class<?>>, MethodHandle> CALLABLES = new HashMap<>(); | |
| MethodHandle callable(List<Class<?>> params) { | |
| MethodHandle mh = CALLABLES.get(params); | |
| if (mh == null) { | |
| mh = collector_MH.asType(methodType(Object.class, params)); | |
| CALLABLES.put(params, mh); | |
| } | |
| return mh; | |
| } | |
| MethodHandle callable(Class<?>... params) { | |
| return callable(Arrays.asList(params)); | |
| } | |
| private static Object collector(Object... args) { | |
| return Arrays.asList(args); | |
| } | |
| private static final MethodHandle collector_MH; | |
| static { | |
| try { | |
| collector_MH | |
| = LOOKUP.findStatic(LOOKUP.lookupClass(), | |
| "collector", | |
| methodType(Object.class, Object[].class)); | |
| } catch (ReflectiveOperationException ex) { | |
| throw new RuntimeException(ex); | |
| } | |
| } | |
| @Test | |
| public void testSimple() throws Throwable { | |
| startTest("testSimple"); | |
| countTest(); | |
| String[] args = { "one", "two" }; | |
| MethodHandle mh = callable(Object.class, String.class); | |
| Object res; List<?> resl; | |
| res = resl = (List<?>) mh.invoke((String)args[0], (Object)args[1]); | |
| //System.out.println(res); | |
| assertEquals(Arrays.asList(args), res); | |
| } | |
| @Test | |
| public void testSimplePrims() throws Throwable { | |
| startTest("testSimplePrims"); | |
| countTest(); | |
| int[] args = { 1, 2 }; | |
| MethodHandle mh = callable(Object.class, Object.class); | |
| Object res; List<?> resl; | |
| res = resl = (List<?>) mh.invoke(args[0], args[1]); | |
| //System.out.println(res); | |
| assertEquals(Arrays.toString(args), res.toString()); | |
| } | |
| @Test | |
| public void testAlternateName() throws Throwable { | |
| startTest("testAlternateName"); | |
| countTest(); | |
| String[] args = { "one", "two" }; | |
| MethodHandle mh = callable(Object.class, String.class); | |
| Object res; List<?> resl; | |
| res = resl = (List<?>) mh.invoke((String)args[0], (Object)args[1]); | |
| //System.out.println(res); | |
| assertEquals(Arrays.asList(args), res); | |
| } | |
| @Test | |
| public void testWrongArgumentCount() throws Throwable { | |
| startTest("testWrongArgumentCount"); | |
| for (int i = 0; i <= 10; i++) { | |
| testWrongArgumentCount(Collections.<Class<?>>nCopies(i, Integer.class)); | |
| if (i <= 4) { | |
| testWrongArgumentCount(Collections.<Class<?>>nCopies(i, int.class)); | |
| testWrongArgumentCount(Collections.<Class<?>>nCopies(i, long.class)); | |
| } | |
| } | |
| } | |
| public void testWrongArgumentCount(List<Class<?>> params) throws Throwable { | |
| int max = params.size(); | |
| for (int i = 0; i < max; i++) { | |
| List<Class<?>> params2 = params.subList(0, i); | |
| for (int k = 0; k <= 2; k++) { | |
| if (k == 1) params = methodType(Object.class, params).generic().parameterList(); | |
| if (k == 2) params2 = methodType(Object.class, params2).generic().parameterList(); | |
| testWrongArgumentCount(params, params2); | |
| testWrongArgumentCount(params2, params); | |
| } | |
| } | |
| } | |
| public void testWrongArgumentCount(List<Class<?>> expect, List<Class<?>> observe) throws Throwable { | |
| countTest(false); | |
| if (expect.equals(observe)) | |
| assert(false); | |
| MethodHandle target = callable(expect); | |
| Object[] args = zeroArgs(observe); | |
| Object junk; | |
| try { | |
| switch (args.length) { | |
| case 0: | |
| junk = target.invoke(); break; | |
| case 1: | |
| junk = target.invoke(args[0]); break; | |
| case 2: | |
| junk = target.invoke(args[0], args[1]); break; | |
| case 3: | |
| junk = target.invoke(args[0], args[1], args[2]); break; | |
| case 4: | |
| junk = target.invoke(args[0], args[1], args[2], args[3]); break; | |
| default: | |
| junk = target.invokeWithArguments(args); break; | |
| } | |
| } catch (WrongMethodTypeException ex) { | |
| return; | |
| } catch (Exception ex) { | |
| throw new RuntimeException("wrong exception calling "+target+" on "+Arrays.asList(args), ex); | |
| } | |
| throw new RuntimeException("bad success calling "+target+" on "+Arrays.asList(args)); | |
| } | |
| /** Make a list of all combinations of the given types, with the given arities. | |
| * A void return type is possible iff the first type is void.class. | |
| */ | |
| static List<MethodType> allMethodTypes(int minargc, int maxargc, Class<?>... types) { | |
| ArrayList<MethodType> result = new ArrayList<>(); | |
| if (types.length > 0) { | |
| ArrayList<MethodType> argcTypes = new ArrayList<>(); | |
| // build arity-zero types first | |
| for (Class<?> rtype : types) { | |
| argcTypes.add(MethodType.methodType(rtype)); | |
| } | |
| if (types[0] == void.class) | |
| // void is not an argument type | |
| types = Arrays.copyOfRange(types, 1, types.length); | |
| for (int argc = 0; argc <= maxargc; argc++) { | |
| if (argc >= minargc) | |
| result.addAll(argcTypes); | |
| if (argc >= maxargc) | |
| break; | |
| ArrayList<MethodType> prevTypes = argcTypes; | |
| argcTypes = new ArrayList<>(); | |
| for (MethodType prevType : prevTypes) { | |
| for (Class<?> ptype : types) { | |
| argcTypes.add(prevType.insertParameterTypes(argc, ptype)); | |
| } | |
| } | |
| } | |
| } | |
| return Collections.unmodifiableList(result); | |
| } | |
| static List<MethodType> allMethodTypes(int argc, Class<?>... types) { | |
| return allMethodTypes(argc, argc, types); | |
| } | |
| MethodHandle toString_MH; | |
| @Test | |
| public void testReferenceConversions() throws Throwable { | |
| startTest("testReferenceConversions"); | |
| toString_MH = LOOKUP. | |
| findVirtual(Object.class, "toString", MethodType.methodType(String.class)); | |
| Object[] args = { "one", "two" }; | |
| for (MethodType type : allMethodTypes(2, Object.class, String.class, CharSequence.class)) { | |
| testReferenceConversions(type, args); | |
| } | |
| } | |
| public void testReferenceConversions(MethodType type, Object... args) throws Throwable { | |
| countTest(); | |
| int nargs = args.length; | |
| List<Object> argList = Arrays.asList(args); | |
| String expectString = argList.toString(); | |
| if (verbosity > 3) System.out.println("target type: "+type+expectString); | |
| MethodHandle mh = callable(type.parameterList()); | |
| mh = MethodHandles.filterReturnValue(mh, toString_MH); | |
| mh = mh.asType(type); | |
| Object res = null; | |
| if (nargs == 2) { | |
| res = mh.invoke((Object)args[0], (Object)args[1]); | |
| assertEquals(expectString, res); | |
| res = mh.invoke((String)args[0], (Object)args[1]); | |
| assertEquals(expectString, res); | |
| res = mh.invoke((Object)args[0], (String)args[1]); | |
| assertEquals(expectString, res); | |
| res = mh.invoke((String)args[0], (String)args[1]); | |
| assertEquals(expectString, res); | |
| res = mh.invoke((String)args[0], (CharSequence)args[1]); | |
| assertEquals(expectString, res); | |
| res = mh.invoke((CharSequence)args[0], (Object)args[1]); | |
| assertEquals(expectString, res); | |
| res = (String) mh.invoke((Object)args[0], (Object)args[1]); | |
| assertEquals(expectString, res); | |
| res = (String) mh.invoke((String)args[0], (Object)args[1]); | |
| assertEquals(expectString, res); | |
| res = (CharSequence) mh.invoke((String)args[0], (Object)args[1]); | |
| assertEquals(expectString, res); | |
| } else { | |
| assert(false); // write this code | |
| } | |
| //System.out.println(res); | |
| } | |
| @Test | |
| public void testBoxConversions() throws Throwable { | |
| startTest("testBoxConversions"); | |
| countTest(); | |
| Object[] args = { 1, 2 }; | |
| MethodHandle mh = callable(Object.class, int.class); | |
| Object res; List<?> resl; int resi; | |
| res = resl = (List<?>) mh.invoke((int)args[0], (Object)args[1]); | |
| //System.out.println(res); | |
| assertEquals(Arrays.asList(args), res); | |
| mh = MethodHandles.identity(int.class); | |
| mh = MethodHandles.dropArguments(mh, 1, int.class); | |
| res = resi = (int) mh.invoke((Object) args[0], (Object) args[1]); | |
| assertEquals(args[0], res); | |
| res = resi = (int) mh.invoke((int) args[0], (Object) args[1]); | |
| assertEquals(args[0], res); | |
| } | |
| } |