| /* |
| * Copyright (c) 2014, 2015, 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. |
| */ |
| |
| import com.oracle.testlibrary.jsr292.Helper; |
| import java.io.File; |
| import java.io.Serializable; |
| import java.lang.invoke.MethodHandle; |
| import java.lang.invoke.MethodHandles; |
| import java.lang.invoke.MethodType; |
| import java.lang.invoke.WrongMethodTypeException; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Random; |
| import sun.invoke.util.Wrapper; |
| |
| /* |
| * @test |
| * @bug 8060483 8066746 |
| * @library /lib/testlibrary /lib/testlibrary/jsr292 |
| * @summary unit tests for MethodHandles.explicitCastArguments() |
| * @run main ExplicitCastArgumentsTest |
| */ |
| |
| /** |
| * Tests for MethodHandles.explicitCastArguments(). |
| */ |
| public class ExplicitCastArgumentsTest { |
| |
| private static final boolean VERBOSE = Helper.IS_VERBOSE; |
| private static final Class<?> THIS_CLASS = ExplicitCastArgumentsTest.class; |
| private static final Random RNG = Helper.RNG; |
| private static final Map<Wrapper, Object> RANDOM_VALUES = new HashMap<>(9); |
| |
| static { |
| RANDOM_VALUES.put(Wrapper.BOOLEAN, RNG.nextBoolean()); |
| RANDOM_VALUES.put(Wrapper.BYTE, (byte) RNG.nextInt()); |
| RANDOM_VALUES.put(Wrapper.SHORT, (short) RNG.nextInt()); |
| RANDOM_VALUES.put(Wrapper.CHAR, (char) RNG.nextInt()); |
| RANDOM_VALUES.put(Wrapper.INT, RNG.nextInt()); |
| RANDOM_VALUES.put(Wrapper.LONG, RNG.nextLong()); |
| RANDOM_VALUES.put(Wrapper.FLOAT, RNG.nextFloat()); |
| RANDOM_VALUES.put(Wrapper.DOUBLE, RNG.nextDouble()); |
| RANDOM_VALUES.put(Wrapper.OBJECT, new Object()); |
| } |
| |
| public static void main(String[] args) throws Throwable { |
| testVarargsCollector(); |
| testNullRef2Prim(); |
| testRef2Prim(); |
| testPrim2Ref(); |
| testPrim2Prim(); |
| testNonBCPRef2NonBCPRef(); |
| testBCPRef2BCPRef(); |
| testNonBCPRef2BCPRef(); |
| testReturnAny2Void(); |
| testReturnVoid2Any(); |
| testMultipleArgs(); |
| System.out.println("TEST PASSED"); |
| } |
| |
| /** |
| * Dummy method used in {@link #testVarargsCollector} test to form a method |
| * handle. |
| * |
| * @param args - any args |
| * @return - returns args |
| */ |
| public static String[] f(String... args) { |
| return args; |
| } |
| |
| /** |
| * Tests that MHs.explicitCastArguments does incorrect type checks for |
| * VarargsCollector. Bug 8066746. |
| * |
| * @throws java.lang.Throwable |
| */ |
| public static void testVarargsCollector() throws Throwable { |
| MethodType mt = MethodType.methodType(String[].class, String[].class); |
| MethodHandle mh = MethodHandles.publicLookup() |
| .findStatic(THIS_CLASS, "f", mt); |
| mh = MethodHandles.explicitCastArguments(mh, |
| MethodType.methodType(Object.class, Object.class)); |
| mh.invokeWithArguments((Object) (new String[]{"str1", "str2"})); |
| } |
| |
| /** |
| * Tests that null wrapper reference is successfully converted to primitive |
| * types. Converted result should be zero for a primitive. Bug 8060483. |
| */ |
| public static void testNullRef2Prim() { |
| for (Wrapper from : Wrapper.values()) { |
| for (Wrapper to : Wrapper.values()) { |
| if (from == Wrapper.VOID || to == Wrapper.VOID) { |
| continue; |
| } |
| // MHs.eCA javadoc: |
| // If T0 is a reference and T1 a primitive, and if the reference |
| // is null at runtime, a zero value is introduced. |
| for (TestConversionMode mode : TestConversionMode.values()) { |
| testConversion(mode, from.wrapperType(), |
| to.primitiveType(), null, to.zero(), false, null); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Tests that non-null wrapper reference is successfully converted to |
| * primitive types. |
| */ |
| public static void testRef2Prim() { |
| for (Wrapper from : Wrapper.values()) { |
| for (Wrapper to : Wrapper.values()) { |
| if (from == Wrapper.VOID || to == Wrapper.VOID |
| || to == Wrapper.OBJECT) { |
| continue; |
| } |
| Object value = RANDOM_VALUES.get(from); |
| for (TestConversionMode mode : TestConversionMode.values()) { |
| if (from != Wrapper.OBJECT) { |
| Object convValue = to.wrap(value); |
| testConversion(mode, from.wrapperType(), |
| to.primitiveType(), value, convValue, false, null); |
| } else { |
| testConversion(mode, from.wrapperType(), |
| to.primitiveType(), value, null, |
| true, ClassCastException.class); |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Tests that primitive is successfully converted to wrapper reference |
| * types, to the Number type (if possible) and to the Object type. |
| */ |
| public static void testPrim2Ref() { |
| for (Wrapper from : Wrapper.values()) { |
| for (Wrapper to : Wrapper.values()) { |
| if (from == Wrapper.VOID || from == Wrapper.OBJECT |
| || to == Wrapper.VOID || to == Wrapper.OBJECT) { |
| continue; |
| } |
| Object value = RANDOM_VALUES.get(from); |
| for (TestConversionMode mode : TestConversionMode.values()) { |
| if (from == to) { |
| testConversion(mode, from.primitiveType(), |
| to.wrapperType(), value, value, false, null); |
| } else { |
| testConversion(mode, from.primitiveType(), |
| to.wrapperType(), value, null, true, ClassCastException.class); |
| } |
| if (from != Wrapper.BOOLEAN && from != Wrapper.CHAR) { |
| testConversion(mode, from.primitiveType(), |
| Number.class, value, value, false, null); |
| } else { |
| testConversion(mode, from.primitiveType(), |
| Number.class, value, null, |
| true, ClassCastException.class); |
| } |
| testConversion(mode, from.primitiveType(), |
| Object.class, value, value, false, null); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Tests that primitive is successfully converted to other primitive type. |
| */ |
| public static void testPrim2Prim() { |
| for (Wrapper from : Wrapper.values()) { |
| for (Wrapper to : Wrapper.values()) { |
| if (from == Wrapper.VOID || to == Wrapper.VOID |
| || from == Wrapper.OBJECT || to == Wrapper.OBJECT) { |
| continue; |
| } |
| Object value = RANDOM_VALUES.get(from); |
| Object convValue = to.wrap(value); |
| for (TestConversionMode mode : TestConversionMode.values()) { |
| testConversion(mode, from.primitiveType(), |
| to.primitiveType(), value, convValue, false, null); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Dummy interface for {@link #testNonBCPRef2Ref} test. |
| */ |
| public static interface TestInterface {} |
| |
| /** |
| * Dummy class for {@link #testNonBCPRef2Ref} test. |
| */ |
| public static class TestSuperClass implements TestInterface {} |
| |
| /** |
| * Dummy class for {@link #testNonBCPRef2Ref} test. |
| */ |
| public static class TestSubClass1 extends TestSuperClass {} |
| |
| /** |
| * Dummy class for {@link #testNonBCPRef2Ref} test. |
| */ |
| public static class TestSubClass2 extends TestSuperClass {} |
| |
| /** |
| * Tests non-bootclasspath reference to reference conversions. |
| * |
| * @throws java.lang.Throwable |
| */ |
| public static void testNonBCPRef2NonBCPRef() throws Throwable { |
| Class testInterface = TestInterface.class; |
| Class testSuperClass = TestSuperClass.class; |
| Class testSubClass1 = TestSubClass1.class; |
| Class testSubClass2 = TestSubClass2.class; |
| Object testSuperObj = new TestSuperClass(); |
| Object testObj01 = new TestSubClass1(); |
| Object testObj02 = new TestSubClass2(); |
| Class[] parents = {testInterface, testSuperClass}; |
| Class[] children = {testSubClass1, testSubClass2}; |
| Object[] childInst = {testObj01, testObj02}; |
| for (TestConversionMode mode : TestConversionMode.values()) { |
| for (Class parent : parents) { |
| for (int j = 0; j < children.length; j++) { |
| // Child type to parent type non-null conversion, shoud succeed |
| testConversion(mode, children[j], parent, childInst[j], childInst[j], false, null); |
| // Child type to parent type null conversion, shoud succeed |
| testConversion(mode, children[j], parent, null, null, false, null); |
| // Parent type to child type non-null conversion with parent |
| // type instance, should fail |
| testConversion(mode, parent, children[j], testSuperObj, null, true, ClassCastException.class); |
| // Parent type to child type non-null conversion with child |
| // type instance, should succeed |
| testConversion(mode, parent, children[j], childInst[j], childInst[j], false, null); |
| // Parent type to child type null conversion, should succeed |
| testConversion(mode, parent, children[j], null, null, false, null); |
| } |
| // Parent type to child type non-null conversion with sibling |
| // type instance, should fail |
| testConversion(mode, parent, testSubClass1, testObj02, null, true, ClassCastException.class); |
| } |
| // Sibling type non-null conversion, should fail |
| testConversion(mode, testSubClass1, |
| testSubClass2, testObj01, null, true, |
| ClassCastException.class); |
| // Sibling type null conversion, should succeed |
| testConversion(mode, testSubClass1, |
| testSubClass2, null, null, false, null); |
| } |
| } |
| |
| /** |
| * Dummy interface for {@link #testNonBCPRef2BCPRef} test. |
| */ |
| public static interface TestSerializableInterface extends Serializable {} |
| |
| /** |
| * Dummy class for {@link #testNonBCPRef2BCPRef} test. |
| */ |
| public static class TestSerializableClass |
| implements TestSerializableInterface {} |
| |
| /** |
| * Dummy class for {@link #testNonBCPRef2BCPRef} test. |
| */ |
| public static class TestFileChildClass extends File |
| implements TestSerializableInterface { |
| public TestFileChildClass(String pathname) { |
| super(pathname); |
| } |
| } |
| |
| /** |
| * Tests non-bootclasspath reference to bootclasspath reference conversions |
| * and vice-versa. |
| * |
| * @throws java.lang.Throwable |
| */ |
| public static void testNonBCPRef2BCPRef() throws Throwable { |
| Class bcpInterface = Serializable.class; |
| Class bcpSuperClass = File.class; |
| Class nonBcpInterface = TestSerializableInterface.class; |
| Class nonBcpSuperSiblingClass = TestSerializableClass.class; |
| Class nonBcpSubClass = TestFileChildClass.class; |
| Object bcpSuperObj = new File("."); |
| Object testSuperSiblingObj = new TestSerializableClass(); |
| Object testSubObj = new TestFileChildClass("."); |
| Class[] parents = {bcpInterface, bcpSuperClass}; |
| for (TestConversionMode mode : TestConversionMode.values()) { |
| for (Class parent : parents) { |
| // Child type to parent type non-null conversion, shoud succeed |
| testConversion(mode, nonBcpSubClass, parent, testSubObj, |
| testSubObj, false, null); |
| // Child type to parent type null conversion, shoud succeed |
| testConversion(mode, nonBcpSubClass, parent, null, null, |
| false, null); |
| // Parent type to child type non-null conversion with parent |
| // type instance, should fail |
| testConversion(mode, parent, nonBcpSubClass, bcpSuperObj, null, |
| true, ClassCastException.class); |
| // Parent type to child type non-null conversion with child |
| // type instance, should succeed |
| testConversion(mode, parent, nonBcpSubClass, testSubObj, |
| testSubObj, false, null); |
| // Parent type to child type null conversion, should succeed |
| testConversion(mode, parent, nonBcpSubClass, null, null, |
| false, null); |
| } |
| // Parent type to child type non-null conversion with |
| // super sibling type instance, should fail |
| testConversion(mode, bcpInterface, nonBcpSubClass, |
| testSuperSiblingObj, null, true, ClassCastException.class); |
| Class[] siblings = {nonBcpSubClass, bcpSuperClass}; |
| for (Class sibling : siblings) { |
| // Non-bcp class to bcp/non-bcp sibling class non-null |
| // conversion with nonBcpSuperSiblingClass instance, should fail |
| testConversion(mode, nonBcpSuperSiblingClass, sibling, |
| testSuperSiblingObj, null, true, ClassCastException.class); |
| // Non-bcp class to bcp/non-bcp sibling class null conversion, |
| // should succeed |
| testConversion(mode, nonBcpSuperSiblingClass, sibling, |
| null, null, false, null); |
| // Non-bcp interface to bcp/non-bcp sibling class non-null |
| // conversion with nonBcpSubClass instance, should succeed |
| testConversion(mode, nonBcpInterface, sibling, testSubObj, |
| testSubObj, false, null); |
| // Non-bcp interface to bcp/non-bcp sibling class |
| // null conversion, should succeed |
| testConversion(mode, nonBcpInterface, sibling, null, null, |
| false, null); |
| // Non-bcp interface to bcp/non-bcp sibling class non-null |
| // conversion with nonBcpSuperSiblingClass instance, should fail |
| testConversion(mode, nonBcpInterface, sibling, |
| testSuperSiblingObj, testSubObj, |
| true, ClassCastException.class); |
| } |
| } |
| } |
| |
| /** |
| * Tests bootclasspath reference to reference conversions. |
| */ |
| public static void testBCPRef2BCPRef() { |
| Class bcpInterface = CharSequence.class; |
| Class bcpSubClass1 = String.class; |
| Class bcpSubClass2 = StringBuffer.class; |
| Object testObj01 = new String("test"); |
| Object testObj02 = new StringBuffer("test"); |
| Class[] children = {bcpSubClass1, bcpSubClass2}; |
| Object[] childInst = {testObj01, testObj02}; |
| for (TestConversionMode mode : TestConversionMode.values()) { |
| for (int i = 0; i < children.length; i++) { |
| // Child type to parent type non-null conversion, shoud succeed |
| testConversion(mode, children[i], bcpInterface, childInst[i], |
| childInst[i], false, null); |
| // Child type to parent type null conversion, shoud succeed |
| testConversion(mode, children[i], bcpInterface, null, |
| null, false, null); |
| // Parent type to child type non-null conversion with child |
| // type instance, should succeed |
| testConversion(mode, bcpInterface, |
| children[i], childInst[i], childInst[i], false, null); |
| // Parent type to child type null conversion, should succeed |
| testConversion(mode, bcpInterface, |
| children[i], null, null, false, null); |
| } |
| // Sibling type non-null conversion, should fail |
| testConversion(mode, bcpSubClass1, |
| bcpSubClass2, testObj01, null, true, |
| ClassCastException.class); |
| // Sibling type null conversion, should succeed |
| testConversion(mode, bcpSubClass1, |
| bcpSubClass2, null, null, false, null); |
| // Parent type to child type non-null conversion with sibling |
| // type instance, should fail |
| testConversion(mode, bcpInterface, bcpSubClass1, testObj02, |
| null, true, ClassCastException.class); |
| } |
| } |
| |
| /** |
| * Dummy method used in {@link #testReturnAny2Void} and |
| * {@link #testReturnVoid2Any} tests to form a method handle. |
| */ |
| public static void retVoid() {} |
| |
| /** |
| * Tests that non-null any return is successfully converted to non-type |
| * void. |
| */ |
| public static void testReturnAny2Void() { |
| for (Wrapper from : Wrapper.values()) { |
| testConversion(TestConversionMode.RETURN_VALUE, from.wrapperType(), |
| void.class, RANDOM_VALUES.get(from), |
| null, false, null); |
| testConversion(TestConversionMode.RETURN_VALUE, from.primitiveType(), |
| void.class, RANDOM_VALUES.get(from), |
| null, false, null); |
| } |
| } |
| |
| /** |
| * Tests that void return is successfully converted to primitive and |
| * reference. Result should be zero for primitives and null for references. |
| */ |
| public static void testReturnVoid2Any() { |
| for (Wrapper to : Wrapper.values()) { |
| testConversion(TestConversionMode.RETURN_VALUE, void.class, |
| to.primitiveType(), null, |
| to.zero(), false, null); |
| testConversion(TestConversionMode.RETURN_VALUE, void.class, |
| to.wrapperType(), null, |
| null, false, null); |
| } |
| } |
| |
| private static void checkForWrongMethodTypeException(MethodHandle mh, MethodType mt) { |
| try { |
| MethodHandles.explicitCastArguments(mh, mt); |
| throw new AssertionError("Expected WrongMethodTypeException is not thrown"); |
| } catch (WrongMethodTypeException wmte) { |
| if (VERBOSE) { |
| System.out.printf("Expected exception %s: %s\n", |
| wmte.getClass(), wmte.getMessage()); |
| } |
| } |
| } |
| |
| /** |
| * Tests that MHs.eCA method works correctly with MHs with multiple arguments. |
| * @throws Throwable |
| */ |
| public static void testMultipleArgs() throws Throwable { |
| int arity = 1 + RNG.nextInt(Helper.MAX_ARITY / 2 - 2); |
| int arityMinus = RNG.nextInt(arity); |
| int arityPlus = arity + RNG.nextInt(Helper.MAX_ARITY / 2 - arity) + 1; |
| MethodType mType = Helper.randomMethodTypeGenerator(arity); |
| MethodType mTypeNew = Helper.randomMethodTypeGenerator(arity); |
| MethodType mTypeNewMinus = Helper.randomMethodTypeGenerator(arityMinus); |
| MethodType mTypeNewPlus = Helper.randomMethodTypeGenerator(arityPlus); |
| Class<?> rType = mType.returnType(); |
| MethodHandle original; |
| if (rType.equals(void.class)) { |
| MethodType mt = MethodType.methodType(void.class); |
| original = MethodHandles.publicLookup() |
| .findStatic(THIS_CLASS, "retVoid", mt); |
| } else { |
| Object rValue = Helper.castToWrapper(1, rType); |
| original = MethodHandles.constant(rType, rValue); |
| } |
| original = Helper.addTrailingArgs(original, arity, mType.parameterList()); |
| MethodHandle target = MethodHandles |
| .explicitCastArguments(original, mTypeNew); |
| Object[] parList = Helper.randomArgs(mTypeNew.parameterList()); |
| for (int i = 0; i < parList.length; i++) { |
| if (parList[i] instanceof String) { |
| parList[i] = null; //getting rid of Stings produced by randomArgs |
| } |
| } |
| target.invokeWithArguments(parList); |
| checkForWrongMethodTypeException(original, mTypeNewMinus); |
| checkForWrongMethodTypeException(original, mTypeNewPlus); |
| } |
| |
| /** |
| * Enumeration of test conversion modes. |
| */ |
| public enum TestConversionMode { |
| RETURN_VALUE, |
| ARGUMENT; |
| } |
| |
| /** |
| * Tests type and value conversion. Comparing with the given expected result. |
| * |
| * @param mode - test conversion mode. See {@link #TestConversionMode}. |
| * @param from - source type. |
| * @param to - destination type. |
| * @param param - value to be converted. |
| * @param expectedResult - expected value after conversion. |
| * @param failureExpected - true if conversion failure expected. |
| * @param expectedException - expected exception class if |
| * {@code failureExpected} is true. |
| */ |
| public static void testConversion(TestConversionMode mode, |
| Class<?> from, Class<?> to, Object param, |
| Object expectedResult, boolean failureExpected, |
| Class<? extends Throwable> expectedException) { |
| if (VERBOSE) { |
| System.out.printf("Testing return value conversion: " |
| + "%-10s => %-10s: %5s: ", from.getSimpleName(), |
| to.getSimpleName(), param); |
| } |
| MethodHandle original = null; |
| MethodType newType = null; |
| switch (mode) { |
| case RETURN_VALUE: |
| if (from.equals(void.class)) { |
| MethodType mt = MethodType.methodType(void.class); |
| try { |
| original = MethodHandles.publicLookup() |
| .findStatic(THIS_CLASS, "retVoid", mt); |
| } catch (NoSuchMethodException | IllegalAccessException ex) { |
| throw new Error("Unexpected issue", ex); |
| } |
| } else { |
| original = MethodHandles.constant(from, param); |
| } |
| newType = original.type().changeReturnType(to); |
| break; |
| case ARGUMENT: |
| if (from.equals(void.class) || to.equals(void.class)) { |
| throw new Error("Test issue: argument conversion does not" |
| + " work with non-type void"); |
| } |
| original = MethodHandles.identity(to); |
| newType = original.type().changeParameterType(0, from); |
| break; |
| default: |
| String msg = String.format("Test issue: unknown test" |
| + " convertion mode %s.", mode.name()); |
| throw new Error(msg); |
| } |
| try { |
| MethodHandle target = MethodHandles |
| .explicitCastArguments(original, newType); |
| Object result; |
| switch (mode) { |
| case RETURN_VALUE: |
| result = target.invokeWithArguments(); |
| break; |
| case ARGUMENT: |
| result = target.invokeWithArguments(param); |
| break; |
| default: |
| String msg = String.format("Test issue: unknown test" |
| + " convertion mode %s.", mode.name()); |
| throw new Error(msg); |
| } |
| if (!failureExpected |
| && (expectedResult != null && !expectedResult.equals(result) |
| || expectedResult == null && result != null)) { |
| String msg = String.format("Conversion result %s is not equal" |
| + " to the expected result %10s", |
| result, expectedResult); |
| throw new AssertionError(msg); |
| } |
| if (VERBOSE) { |
| String resultStr; |
| if (result != null) { |
| resultStr = String.format("Converted value and type are" |
| + " %10s (%10s)", "'" + result + "'", |
| result.getClass().getSimpleName()); |
| } else { |
| resultStr = String.format("Converted value is %10s", result); |
| } |
| System.out.println(resultStr); |
| } |
| if (failureExpected) { |
| String msg = String.format("No exception thrown while testing" |
| + " return value conversion: %10s => %10s;" |
| + " parameter: %10s", |
| from, to, param); |
| throw new AssertionError(msg); |
| } |
| } catch (AssertionError e) { |
| throw e; // report test failure |
| } catch (Throwable e) { |
| if (VERBOSE) { |
| System.out.printf("%s: %s\n", e.getClass(), e.getMessage()); |
| } |
| if (!failureExpected || !e.getClass().equals(expectedException)) { |
| String msg = String.format("Unexpected exception was thrown" |
| + " while testing return value conversion:" |
| + " %s => %s; parameter: %s", from, to, param); |
| throw new AssertionError(msg, e); |
| } |
| } |
| } |
| } |