/* * Copyright (c) 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 Test locating and invoking default/static method that defined * in interfaces and/or in inheritance * @bug 7184826 * @build helper.Mod helper.Declared DefaultStaticTestData * @run testng DefaultStaticInvokeTest * @author Yong Lu */ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertTrue; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; import static org.testng.Assert.fail; import org.testng.annotations.Test; import static helper.Mod.*; import static helper.Declared.*; import helper.Mod; public class DefaultStaticInvokeTest { // getMethods(): Make sure getMethods returns the expected methods. @Test(dataProvider = "testCasesAll", dataProviderClass = DefaultStaticTestData.class) public void testGetMethods(String testTarget, Object param) throws Exception { testMethods(ALL_METHODS, testTarget, param); } // getDeclaredMethods(): Make sure getDeclaredMethods returns the expected methods. @Test(dataProvider = "testCasesAll", dataProviderClass = DefaultStaticTestData.class) public void testGetDeclaredMethods(String testTarget, Object param) throws Exception { testMethods(DECLARED_ONLY, testTarget, param); } // getMethod(): Make sure that getMethod finds all methods it should find. @Test(dataProvider = "testCasesAll", dataProviderClass = DefaultStaticTestData.class) public void testGetMethod(String testTarget, Object param) throws Exception { Class<?> typeUnderTest = Class.forName(testTarget); MethodDesc[] descs = typeUnderTest.getAnnotationsByType(MethodDesc.class); for (MethodDesc desc : descs) { assertTrue(isFoundByGetMethod(typeUnderTest, desc.name(), argTypes(param))); } } // getMethod(): Make sure that getMethod does *not* find certain methods. @Test(dataProvider = "testCasesAll", dataProviderClass = DefaultStaticTestData.class) public void testGetMethodSuperInterfaces(String testTarget, Object param) throws Exception { // Make sure static methods in superinterfaces are not found (unless the type under // test declares a static method with the same signature). Class<?> typeUnderTest = Class.forName(testTarget); for (Class<?> interfaze : typeUnderTest.getInterfaces()) { for (MethodDesc desc : interfaze.getAnnotationsByType(MethodDesc.class)) { boolean isStatic = desc.mod() == STATIC; boolean declaredInThisType = isMethodDeclared(typeUnderTest, desc.name()); boolean expectedToBeFound = !isStatic || declaredInThisType; if (expectedToBeFound) continue; // already tested in testGetMethod() assertFalse(isFoundByGetMethod(typeUnderTest, desc.name(), argTypes(param))); } } } // Method.invoke(): Make sure Method.invoke returns the expected value. @Test(dataProvider = "testCasesAll", dataProviderClass = DefaultStaticTestData.class) public void testMethodInvoke(String testTarget, Object param) throws Exception { Class<?> typeUnderTest = Class.forName(testTarget); MethodDesc[] expectedMethods = typeUnderTest.getAnnotationsByType(MethodDesc.class); // test the method retrieved by Class.getMethod(String, Object[]) for (MethodDesc toTest : expectedMethods) { String name = toTest.name(); Method m = typeUnderTest.getMethod(name, argTypes(param)); testThisMethod(toTest, m, typeUnderTest, param); } } // MethodHandle.invoke(): Make sure MethodHandle.invoke returns the expected value. @Test(dataProvider = "testCasesAll", dataProviderClass = DefaultStaticTestData.class) public void testMethodHandleInvoke(String testTarget, Object param) throws Throwable { Class<?> typeUnderTest = Class.forName(testTarget); MethodDesc[] expectedMethods = typeUnderTest.getAnnotationsByType(MethodDesc.class); for (MethodDesc toTest : expectedMethods) { String mName = toTest.name(); Mod mod = toTest.mod(); if (mod != STATIC && typeUnderTest.isInterface()) { return; } String result = null; String expectedReturn = toTest.retval(); MethodHandle methodHandle = getTestMH(typeUnderTest, mName, param); if (mName.equals("staticMethod")) { result = (param == null) ? (String) methodHandle.invoke() : (String) methodHandle.invoke(param); } else { result = (param == null) ? (String) methodHandle.invoke(typeUnderTest.newInstance()) : (String) methodHandle.invoke(typeUnderTest.newInstance(), param); } assertEquals(result, expectedReturn); } } // Lookup.findStatic / .findVirtual: Make sure IllegalAccessException is thrown as expected. @Test(dataProvider = "testClasses", dataProviderClass = DefaultStaticTestData.class) public void testIAE(String testTarget, Object param) throws ClassNotFoundException { Class<?> typeUnderTest = Class.forName(testTarget); MethodDesc[] expectedMethods = typeUnderTest.getAnnotationsByType(MethodDesc.class); for (MethodDesc toTest : expectedMethods) { String mName = toTest.name(); Mod mod = toTest.mod(); if (mod != STATIC && typeUnderTest.isInterface()) { continue; } Exception caught = null; try { getTestMH(typeUnderTest, mName, param, true); } catch (Exception e) { caught = e; } assertNotNull(caught); assertEquals(caught.getClass(), IllegalAccessException.class); } } private static final String[] OBJECT_METHOD_NAMES = { "equals", "hashCode", "getClass", "notify", "notifyAll", "toString", "wait", "wait", "wait",}; private static final String LAMBDA_METHOD_NAMES = "lambda$"; private static final HashSet<String> OBJECT_NAMES = new HashSet<>(Arrays.asList(OBJECT_METHOD_NAMES)); private static final boolean DECLARED_ONLY = true; private static final boolean ALL_METHODS = false; private void testMethods(boolean declaredOnly, String testTarget, Object param) throws Exception { Class<?> typeUnderTest = Class.forName(testTarget); Method[] methods = declaredOnly ? typeUnderTest.getDeclaredMethods() : typeUnderTest.getMethods(); MethodDesc[] baseExpectedMethods = typeUnderTest.getAnnotationsByType(MethodDesc.class); MethodDesc[] expectedMethods; // If only declared filter out non-declared from expected result if (declaredOnly) { int nonDeclared = 0; for (MethodDesc desc : baseExpectedMethods) { if (desc.declared() == NO) { nonDeclared++; } } expectedMethods = new MethodDesc[baseExpectedMethods.length - nonDeclared]; int i = 0; for (MethodDesc desc : baseExpectedMethods) { if (desc.declared() == YES) { expectedMethods[i++] = desc; } } } else { expectedMethods = baseExpectedMethods; } HashMap<String, Method> myMethods = new HashMap<>(methods.length); for (Method m : methods) { String mName = m.getName(); // don't add Object methods and method created from lambda expression if ((!OBJECT_NAMES.contains(mName)) && (!mName.contains(LAMBDA_METHOD_NAMES))) { myMethods.put(mName, m); } } assertEquals(myMethods.size(), expectedMethods.length); for (MethodDesc toTest : expectedMethods) { String name = toTest.name(); Method candidate = myMethods.remove(name); assertNotNull(candidate); testThisMethod(toTest, candidate, typeUnderTest, param); } // Should be no methods left since we remove all we expect to see assertTrue(myMethods.isEmpty()); } private void testThisMethod(MethodDesc toTest, Method method, Class<?> typeUnderTest, Object param) throws Exception { // Test modifiers, and invoke Mod mod = toTest.mod(); String expectedReturn = toTest.retval(); switch (mod) { case STATIC: //assert candidate is static assertTrue(Modifier.isStatic(method.getModifiers())); assertFalse(method.isDefault()); // Test invoke it assertEquals(tryInvoke(method, null, param), expectedReturn); break; case DEFAULT: // if typeUnderTest is a class then instantiate and invoke if (!typeUnderTest.isInterface()) { assertEquals(tryInvoke( method, typeUnderTest, param), expectedReturn); } //assert candidate is default assertFalse(Modifier.isStatic(method.getModifiers())); assertTrue(method.isDefault()); break; case REGULAR: // if typeUnderTest must be a class assertEquals(tryInvoke( method, typeUnderTest, param), expectedReturn); //assert candidate is neither default nor static assertFalse(Modifier.isStatic(method.getModifiers())); assertFalse(method.isDefault()); break; case ABSTRACT: //assert candidate is neither default nor static assertFalse(Modifier.isStatic(method.getModifiers())); assertFalse(method.isDefault()); break; default: fail(); //this should never happen break; } } private boolean isMethodDeclared(Class<?> type, String name) { MethodDesc[] methDescs = type.getAnnotationsByType(MethodDesc.class); for (MethodDesc desc : methDescs) { if (desc.declared() == YES && desc.name().equals(name)) return true; } return false; } private boolean isFoundByGetMethod(Class<?> c, String method, Class<?>... argTypes) { try { c.getMethod(method, argTypes); return true; } catch (NoSuchMethodException notFound) { return false; } } private Class<?>[] argTypes(Object param) { return param == null ? new Class[0] : new Class[] { Object.class }; } private Object tryInvoke(Method m, Class<?> receiverType, Object param) throws Exception { Object receiver = receiverType == null ? null : receiverType.newInstance(); Object[] args = param == null ? new Object[0] : new Object[] { param }; return m.invoke(receiver, args); } private MethodHandle getTestMH(Class clazz, String methodName, Object param) throws Exception { return getTestMH(clazz, methodName, param, false); } private MethodHandle getTestMH(Class clazz, String methodName, Object param, boolean isNegativeTest) throws Exception { MethodType mType = (param != null) ? MethodType.genericMethodType(1) : MethodType.methodType(String.class); MethodHandles.Lookup lookup = MethodHandles.lookup(); if (!isNegativeTest) { return methodName.equals("staticMethod") ? lookup.findStatic(clazz, methodName, mType) : lookup.findVirtual(clazz, methodName, mType); } else { return methodName.equals("staticMethod") ? lookup.findVirtual(clazz, methodName, mType) : lookup.findStatic(clazz, methodName, mType); } } }