| /* |
| * 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); |
| } |
| } |
| } |