blob: 215ee3dd66a79b35ad0329c84cc8fc7295a8ec8b [file] [log] [blame]
/*
* 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);
}
}
}