blob: 49b3ad159f5eda5569fe1e92eb6c26f12e83fd23 [file] [log] [blame]
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package libcore.java.lang.reflect;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import junit.framework.TestCase;
public final class MethodTest extends TestCase {
public void test_getExceptionTypes() throws Exception {
Method method = MethodTestHelper.class.getMethod("m1", new Class[0]);
Class[] exceptions = method.getExceptionTypes();
assertEquals(1, exceptions.length);
assertEquals(IndexOutOfBoundsException.class, exceptions[0]);
// Check that corrupting our array doesn't affect other callers.
exceptions[0] = NullPointerException.class;
exceptions = method.getExceptionTypes();
assertEquals(1, exceptions.length);
assertEquals(IndexOutOfBoundsException.class, exceptions[0]);
}
public void test_getParameterTypes() throws Exception {
Class[] expectedParameters = new Class[] { Object.class };
Method method = MethodTestHelper.class.getMethod("m2", expectedParameters);
Class[] parameters = method.getParameterTypes();
assertEquals(1, parameters.length);
assertEquals(expectedParameters[0], parameters[0]);
// Check that corrupting our array doesn't affect other callers.
parameters[0] = String.class;
parameters = method.getParameterTypes();
assertEquals(1, parameters.length);
assertEquals(expectedParameters[0], parameters[0]);
}
public void testGetMethodWithPrivateMethodAndInterfaceMethod() throws Exception {
assertEquals(InterfaceA.class, Sub.class.getMethod("a").getDeclaringClass());
}
public void testGetMethodReturnsIndirectlyImplementedInterface() throws Exception {
assertEquals(InterfaceA.class, ImplementsC.class.getMethod("a").getDeclaringClass());
assertEquals(InterfaceA.class, ExtendsImplementsC.class.getMethod("a").getDeclaringClass());
}
public void testGetDeclaredMethodReturnsIndirectlyImplementedInterface() throws Exception {
try {
ImplementsC.class.getDeclaredMethod("a").getDeclaringClass();
fail();
} catch (NoSuchMethodException expected) {
}
try {
ExtendsImplementsC.class.getDeclaredMethod("a").getDeclaringClass();
fail();
} catch (NoSuchMethodException expected) {
}
}
public void testGetMethodWithConstructorName() throws Exception {
try {
MethodTestHelper.class.getMethod("<init>");
fail();
} catch (NoSuchMethodException expected) {
}
}
public void testGetMethodWithNullName() throws Exception {
try {
MethodTestHelper.class.getMethod(null);
fail();
} catch (NullPointerException expected) {
}
}
public void testGetMethodWithNullArgumentsArray() throws Exception {
Method m1 = MethodTestHelper.class.getMethod("m1", (Class[]) null);
assertEquals(0, m1.getParameterTypes().length);
}
public void testGetMethodWithNullArgument() throws Exception {
try {
MethodTestHelper.class.getMethod("m2", new Class[] { null });
fail();
} catch (NoSuchMethodException expected) {
}
}
public void testGetMethodReturnsInheritedStaticMethod() throws Exception {
Method b = Sub.class.getMethod("b");
assertEquals(void.class, b.getReturnType());
}
public void testGetDeclaredMethodReturnsPrivateMethods() throws Exception {
Method method = Super.class.getDeclaredMethod("a");
assertEquals(void.class, method.getReturnType());
}
public void testGetDeclaredMethodDoesNotReturnSuperclassMethods() throws Exception {
try {
Sub.class.getDeclaredMethod("a");
fail();
} catch (NoSuchMethodException expected) {
}
}
public void testGetDeclaredMethodDoesNotReturnImplementedInterfaceMethods() throws Exception {
try {
InterfaceB.class.getDeclaredMethod("a");
fail();
} catch (NoSuchMethodException expected) {
}
}
public void testImplementedInterfaceMethodOfAnonymousClass() throws Exception {
Object anonymous = new InterfaceA() {
@Override public void a() {
}
};
Method method = anonymous.getClass().getMethod("a");
assertEquals(anonymous.getClass(), method.getDeclaringClass());
}
public void testPublicMethodOfAnonymousClass() throws Exception {
Object anonymous = new Object() {
public void a() {
}
};
Method method = anonymous.getClass().getMethod("a");
assertEquals(anonymous.getClass(), method.getDeclaringClass());
}
public void testGetMethodDoesNotReturnPrivateMethodOfAnonymousClass() throws Exception {
Object anonymous = new Object() {
private void a() {
}
};
try {
anonymous.getClass().getMethod("a");
fail();
} catch (NoSuchMethodException expected) {
}
}
public void testGetDeclaredMethodReturnsPrivateMethodOfAnonymousClass() throws Exception {
Object anonymous = new Object() {
private void a() {
}
};
Method method = anonymous.getClass().getDeclaredMethod("a");
assertEquals(anonymous.getClass(), method.getDeclaringClass());
}
public void testEqualMethodEqualsAndHashCode() throws Exception {
Method m1 = MethodTestHelper.class.getMethod("m1");
Method m2 = MethodTestHelper.class.getMethod("m1");
assertEquals(m1, m2);
assertEquals(m1.hashCode(), m2.hashCode());
assertEquals(MethodTestHelper.class.getName().hashCode() ^ "m1".hashCode(), m1.hashCode());
}
public void testHashCodeSpec() throws Exception {
Method m1 = MethodTestHelper.class.getMethod("m1");
assertEquals(MethodTestHelper.class.getName().hashCode() ^ "m1".hashCode(), m1.hashCode());
}
public void testDifferentMethodEqualsAndHashCode() throws Exception {
Method m1 = MethodTestHelper.class.getMethod("m1");
Method m2 = MethodTestHelper.class.getMethod("m2", Object.class);
assertFalse(m1.equals(m2));
assertFalse(m1.hashCode() == m2.hashCode());
}
// http://b/1045939
public void testMethodToString() throws Exception {
assertEquals("public final native void java.lang.Object.notify()",
Object.class.getMethod("notify", new Class[] { }).toString());
assertEquals("public java.lang.String java.lang.Object.toString()",
Object.class.getMethod("toString", new Class[] { }).toString());
assertEquals("public final native void java.lang.Object.wait(long,int)"
+ " throws java.lang.InterruptedException",
Object.class.getMethod("wait", new Class[] { long.class, int.class }).toString());
assertEquals("public boolean java.lang.Object.equals(java.lang.Object)",
Object.class.getMethod("equals", new Class[] { Object.class }).toString());
assertEquals("public static java.lang.String java.lang.String.valueOf(char[])",
String.class.getMethod("valueOf", new Class[] { char[].class }).toString());
assertEquals( "public java.lang.Process java.lang.Runtime.exec(java.lang.String[])"
+ " throws java.io.IOException",
Runtime.class.getMethod("exec", new Class[] { String[].class }).toString());
// http://b/18488857
assertEquals(
"public int java.lang.String.compareTo(java.lang.Object)",
String.class.getMethod("compareTo", Object.class).toString());
}
// Tests that the "varargs" modifier is handled correctly.
// The underlying constant value for it is the same as for the "transient" field modifier.
// http://b/18488857
public void testVarargsModifier() throws NoSuchMethodException {
Method stringFormatMethod = String.class.getMethod(
"format", new Class[] { String.class, Object[].class });
assertTrue(stringFormatMethod.isVarArgs());
assertEquals(
"public static java.lang.String java.lang.String.format("
+ "java.lang.String,java.lang.Object[])",
stringFormatMethod.toString());
}
public static class MethodTestHelper {
public void m1() throws IndexOutOfBoundsException { }
public void m2(Object o) { }
}
public static class Super {
private void a() {}
public static void b() {}
}
public interface InterfaceA {
void a();
}
public static abstract class Sub extends Super implements InterfaceA {
}
public interface InterfaceB extends InterfaceA {}
public interface InterfaceC extends InterfaceB {}
public static abstract class ImplementsC implements InterfaceC {}
public static abstract class ExtendsImplementsC extends ImplementsC {}
// Static interface method reflection.
public interface InterfaceWithStatic {
static String staticMethod() {
return identifyCaller();
}
}
public void testStaticInterfaceMethod_getMethod() throws Exception {
Method method = InterfaceWithStatic.class.getMethod("staticMethod");
assertFalse(method.isDefault());
assertEquals(Modifier.PUBLIC | Modifier.STATIC, method.getModifiers());
assertEquals(InterfaceWithStatic.class, method.getDeclaringClass());
}
public void testStaticInterfaceMethod_getDeclaredMethod() throws Exception {
Method declaredMethod = InterfaceWithStatic.class.getDeclaredMethod("staticMethod");
assertFalse(declaredMethod.isDefault());
assertEquals(Modifier.PUBLIC | Modifier.STATIC, declaredMethod.getModifiers());
assertEquals(InterfaceWithStatic.class, declaredMethod.getDeclaringClass());
}
public void testStaticInterfaceMethod_invoke() throws Exception {
String interfaceWithStaticClassName = InterfaceWithStatic.class.getName();
assertEquals(interfaceWithStaticClassName, InterfaceWithStatic.staticMethod());
Method method = InterfaceWithStatic.class.getMethod("staticMethod");
assertEquals(interfaceWithStaticClassName, method.invoke(null));
assertEquals(interfaceWithStaticClassName, method.invoke(new InterfaceWithStatic() {}));
}
public void testStaticInterfaceMethod_setAccessible() throws Exception {
String interfaceWithStaticClassName = InterfaceWithStatic.class.getName();
Method method = InterfaceWithStatic.class.getMethod("staticMethod");
method.setAccessible(false);
// No effect expected.
assertEquals(interfaceWithStaticClassName, method.invoke(null));
}
// Default method reflection.
public interface InterfaceWithDefault {
default String defaultMethod() {
return identifyCaller();
}
}
public static class ImplementationWithDefault implements InterfaceWithDefault {
}
public void testDefaultMethod_getDeclaredMethod_interface() throws Exception {
Class<InterfaceWithDefault> interfaceWithDefaultClass = InterfaceWithDefault.class;
Method defaultMethod = interfaceWithDefaultClass.getDeclaredMethod("defaultMethod");
assertEquals(InterfaceWithDefault.class, defaultMethod.getDeclaringClass());
assertTrue(defaultMethod.isDefault());
}
public void testDefaultMethod_inheritance() throws Exception {
Class<InterfaceWithDefault> interfaceWithDefaultClass = InterfaceWithDefault.class;
String interfaceWithDefaultClassName = interfaceWithDefaultClass.getName();
Method defaultMethod = interfaceWithDefaultClass.getDeclaredMethod("defaultMethod");
InterfaceWithDefault anon = new InterfaceWithDefault() {};
Class<?> anonClass = anon.getClass();
Method inheritedDefaultMethod = anonClass.getMethod("defaultMethod");
assertEquals(inheritedDefaultMethod, defaultMethod);
// Check invocation behavior.
assertEquals(interfaceWithDefaultClassName, defaultMethod.invoke(anon));
assertEquals(interfaceWithDefaultClassName, inheritedDefaultMethod.invoke(anon));
assertEquals(interfaceWithDefaultClassName, anon.defaultMethod());
// Check other method properties.
assertEquals(InterfaceWithDefault.class, inheritedDefaultMethod.getDeclaringClass());
assertTrue(inheritedDefaultMethod.isDefault());
// Confirm the method is not considered declared on the anonymous class.
assertNull(getDeclaredMethodOrNull(anonClass, "defaultMethod"));
}
public void testDefaultMethod_override() throws Exception {
Class<InterfaceWithDefault> interfaceWithDefaultClass = InterfaceWithDefault.class;
Method defaultMethod = interfaceWithDefaultClass.getDeclaredMethod("defaultMethod");
InterfaceWithDefault anon = new InterfaceWithDefault() {
@Override public String defaultMethod() {
return identifyCaller();
}
};
Class<? extends InterfaceWithDefault> anonClass = anon.getClass();
String anonymousClassName = anonClass.getName();
Method overriddenDefaultMethod = getDeclaredMethodOrNull(anonClass, "defaultMethod");
assertNotNull(overriddenDefaultMethod);
assertFalse(overriddenDefaultMethod.equals(defaultMethod));
// Check invocation behavior.
assertEquals(anonymousClassName, defaultMethod.invoke(anon));
assertEquals(anonymousClassName, overriddenDefaultMethod.invoke(anon));
assertEquals(anonymousClassName, anon.defaultMethod());
// Check other method properties.
assertEquals(anonClass, overriddenDefaultMethod.getDeclaringClass());
assertFalse(overriddenDefaultMethod.isDefault());
}
public void testDefaultMethod_setAccessible() throws Exception {
InterfaceWithDefault anon = new InterfaceWithDefault() {};
Method defaultMethod = anon.getClass().getMethod("defaultMethod");
defaultMethod.setAccessible(false);
// setAccessible(false) should have no effect.
assertEquals(InterfaceWithDefault.class.getName(), defaultMethod.invoke(anon));
InterfaceWithDefault anon2 = new InterfaceWithDefault() {
@Override public String defaultMethod() {
return identifyCaller();
}
};
Class<? extends InterfaceWithDefault> anon2Class = anon2.getClass();
Method overriddenDefaultMethod = anon2Class.getDeclaredMethod("defaultMethod");
overriddenDefaultMethod.setAccessible(false);
// setAccessible(false) should have no effect.
assertEquals(anon2Class.getName(), overriddenDefaultMethod.invoke(anon2));
}
interface InterfaceWithReAbstractedMethod extends InterfaceWithDefault {
// Re-abstract a default method.
@Override String defaultMethod();
}
public void testDefaultMethod_reabstracted() throws Exception {
Class<InterfaceWithReAbstractedMethod> subclass = InterfaceWithReAbstractedMethod.class;
Method reabstractedDefaultMethod = subclass.getMethod("defaultMethod");
assertFalse(reabstractedDefaultMethod.isDefault());
assertEquals(reabstractedDefaultMethod, subclass.getDeclaredMethod("defaultMethod"));
assertEquals(subclass, reabstractedDefaultMethod.getDeclaringClass());
}
public void testDefaultMethod_reimplementedInClass() throws Exception {
InterfaceWithDefault impl = new InterfaceWithReAbstractedMethod() {
// Implement a reabstracted default method.
@Override public String defaultMethod() {
return identifyCaller();
}
};
Class<?> implClass = impl.getClass();
String implClassName = implClass.getName();
Method implClassDefaultMethod = getDeclaredMethodOrNull(implClass, "defaultMethod");
assertEquals(implClassDefaultMethod, implClass.getMethod("defaultMethod"));
// Check invocation behavior.
assertEquals(implClassName, impl.defaultMethod());
assertEquals(implClassName, implClassDefaultMethod.invoke(impl));
// Check other method properties.
assertEquals(implClass, implClassDefaultMethod.getDeclaringClass());
assertFalse(implClassDefaultMethod.isDefault());
}
interface InterfaceWithRedefinedMethods extends InterfaceWithReAbstractedMethod {
// Reimplement an abstracted default method.
@Override default String defaultMethod() {
return identifyCaller();
}
}
public void testDefaultMethod_reimplementInInterface() throws Exception {
Class<?> interfaceClass = InterfaceWithRedefinedMethods.class;
String interfaceClassName = interfaceClass.getName();
// NOTE: The line below defines an anonymous class that implements
// InterfaceWithReDefinedMethods (and does not need to provide any declarations).
// See the {}.
InterfaceWithDefault impl = new InterfaceWithRedefinedMethods() {};
Class<?> implClass = impl.getClass();
Method implClassDefaultMethod = implClass.getMethod("defaultMethod");
assertNull(getDeclaredMethodOrNull(implClass, "defaultMethod"));
// Check invocation behavior.
assertEquals(interfaceClassName, impl.defaultMethod());
assertEquals(interfaceClassName, implClassDefaultMethod.invoke(impl));
// Check other method properties.
assertEquals(interfaceClass, implClassDefaultMethod.getDeclaringClass());
assertTrue(implClassDefaultMethod.isDefault());
}
public void testDefaultMethod_invoke() throws Exception {
InterfaceWithDefault impl1 = new InterfaceWithRedefinedMethods() {};
InterfaceWithDefault impl2 = new InterfaceWithReAbstractedMethod() {
@Override public String defaultMethod() {
return identifyCaller();
}
};
InterfaceWithDefault impl3 = new InterfaceWithDefault() {};
Class[] classes = {
InterfaceWithRedefinedMethods.class,
impl1.getClass(),
InterfaceWithReAbstractedMethod.class,
impl2.getClass(),
InterfaceWithDefault.class,
impl3.getClass(),
};
Object[] instances = { impl1, impl2, impl3 };
// Attempt to invoke all declarations of defaultMethod() on a selection of instances.
for (Class<?> clazz : classes) {
Method method = clazz.getMethod("defaultMethod");
for (Object instance : instances) {
if (method.getDeclaringClass().isAssignableFrom(instance.getClass())) {
Method trueMethod = instance.getClass().getMethod("defaultMethod");
// All implementations of defaultMethod return the class where the method is
// declared, enabling us to tell if the correct implementation has been called.
Class<?> declaringClass = trueMethod.getDeclaringClass();
assertEquals(declaringClass.getName(), method.invoke(instance));
} else {
try {
method.invoke(instance);
fail();
} catch (IllegalArgumentException expected) {
}
}
}
}
}
interface OtherInterfaceWithDefault {
default String defaultMethod() {
return identifyCaller();
}
}
public void testDefaultMethod_superSyntax() throws Exception {
class ImplementationSuperUser implements InterfaceWithDefault, OtherInterfaceWithDefault {
@Override public String defaultMethod() {
return identifyCaller() + ":" +
InterfaceWithDefault.super.defaultMethod() + ":" +
OtherInterfaceWithDefault.super.defaultMethod();
}
}
String implementationSuperUserClassName = ImplementationSuperUser.class.getName();
String interfaceWithDefaultClassName = InterfaceWithDefault.class.getName();
String otherInterfaceWithDefaultClassName = OtherInterfaceWithDefault.class.getName();
String expectedReturnValue = implementationSuperUserClassName + ":" +
interfaceWithDefaultClassName + ":" + otherInterfaceWithDefaultClassName;
ImplementationSuperUser obj = new ImplementationSuperUser();
assertEquals(expectedReturnValue, obj.defaultMethod());
Method defaultMethod = ImplementationSuperUser.class.getMethod("defaultMethod");
assertEquals(expectedReturnValue, defaultMethod.invoke(obj));
}
public void testProxyWithDefaultMethods() throws Exception {
InvocationHandler invocationHandler = new InvocationHandler() {
@Override public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
assertSame(InterfaceWithDefault.class, method.getDeclaringClass());
return identifyCaller();
}
};
InterfaceWithDefault proxyWithDefaultMethod = (InterfaceWithDefault) Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
new Class[] { InterfaceWithDefault.class },
invocationHandler);
String invocationHandlerClassName = invocationHandler.getClass().getName();
// Check the proxy implements the default method.
Class<? extends InterfaceWithDefault> proxyClass = proxyWithDefaultMethod.getClass();
Method defaultMethod = proxyClass.getMethod("defaultMethod");
assertEquals(proxyClass, defaultMethod.getDeclaringClass());
assertFalse(defaultMethod.isDefault());
// The default method is intercepted like anything else.
assertEquals(invocationHandlerClassName, proxyWithDefaultMethod.defaultMethod());
}
private static Method getDeclaredMethodOrNull(Class<?> clazz, String methodName) {
try {
Method m = clazz.getDeclaredMethod(methodName);
assertNotNull(m);
return m;
} catch (NoSuchMethodException e) {
return null;
}
}
/**
* Keep this package-protected or public to avoid the introduction of synthetic methods that
* throw off the offset.
*/
static String identifyCaller() {
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
int i = 0;
while (!stack[i++].getMethodName().equals("identifyCaller")) {}
return stack[i].getClassName();
}
}