blob: abd5851d371d5a02283b32c19eec511aad6f3e21 [file] [log] [blame]
/*
* Copyright (C) 2010 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.io.EOFException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import java.net.SocketException;
import junit.framework.TestCase;
import tests.util.ClassLoaderBuilder;
public final class ProxyTest extends TestCase {
private final ClassLoader loader = getClass().getClassLoader();
private final InvocationHandler returnHandler = new TestInvocationHandler();
private final InvocationHandler throwHandler = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
throw (Throwable) args[0];
}
};
/**
* Make sure the proxy's class loader fails if it cannot see the class
* loaders of its implemented interfaces. http://b/1608481
*/
public void testClassLoaderMustSeeImplementedInterfaces() throws Exception {
String prefix = ProxyTest.class.getName();
ClassLoader loaderA = new ClassLoaderBuilder().withPrivateCopy(prefix).build();
ClassLoader loaderB = new ClassLoaderBuilder().withPrivateCopy(prefix).build();
Class[] interfacesA = { loaderA.loadClass(prefix + "$Echo") };
try {
Proxy.newProxyInstance(loaderB, interfacesA, returnHandler);
fail();
} catch (IllegalArgumentException expected) {
}
}
public void testClassLoaderDoesNotNeedToSeeInvocationHandlerLoader() throws Exception {
String prefix = ProxyTest.class.getName();
ClassLoader loaderA = new ClassLoaderBuilder().withPrivateCopy(prefix).build();
ClassLoader loaderB = new ClassLoaderBuilder().withPrivateCopy(prefix).build();
InvocationHandler invocationHandlerB = (InvocationHandler) loaderB.loadClass(
prefix + "$TestInvocationHandler").newInstance();
Class[] interfacesA = { loaderA.loadClass(prefix + "$Echo") };
Object proxy = Proxy.newProxyInstance(loaderA, interfacesA, invocationHandlerB);
assertEquals(loaderA, proxy.getClass().getClassLoader());
assertEquals("foo", proxy.getClass().getMethod("echo", String.class).invoke(proxy, "foo"));
}
public void testIncompatibleReturnTypesPrimitiveAndPrimitive() {
try {
Proxy.newProxyInstance(loader, new Class[] {ReturnsInt.class, ReturnsFloat.class},
returnHandler);
fail();
} catch (IllegalArgumentException expected) {
}
}
public void testIncompatibleReturnTypesPrimitiveAndWrapper() {
try {
Proxy.newProxyInstance(loader, new Class[] {ReturnsInt.class, ReturnsInteger.class},
returnHandler);
fail();
} catch (IllegalArgumentException expected) {
}
}
public void testIncompatibleReturnTypesPrimitiveAndVoid() {
try {
Proxy.newProxyInstance(loader, new Class[] {ReturnsInt.class, ReturnsVoid.class},
returnHandler);
fail();
} catch (IllegalArgumentException expected) {
}
}
public void testIncompatibleReturnTypesIncompatibleObjects() {
try {
Proxy.newProxyInstance(loader, new Class[] {ReturnsInteger.class, ReturnsString.class },
returnHandler);
fail();
} catch (IllegalArgumentException expected) {
}
}
public void testCompatibleReturnTypesImplementedInterface() {
Proxy.newProxyInstance(loader, new Class[] {ReturnsString.class, ReturnsCharSequence.class},
returnHandler);
Proxy.newProxyInstance(loader, new Class[]{ReturnsObject.class, ReturnsCharSequence.class,
ReturnsString.class}, returnHandler);
Proxy.newProxyInstance(loader, new Class[]{ReturnsObject.class, ReturnsCharSequence.class,
ReturnsString.class, ReturnsSerializable.class, ReturnsComparable.class},
returnHandler);
}
public void testCompatibleReturnTypesSuperclass() {
Proxy.newProxyInstance(loader, new Class[] {ReturnsString.class, ReturnsObject.class},
returnHandler);
}
public void testDeclaredExceptionIntersectionIsSubtype() throws Exception {
ThrowsIOException instance = (ThrowsIOException) Proxy.newProxyInstance(loader,
new Class[] {ThrowsIOException.class, ThrowsEOFException.class},
throwHandler);
try {
instance.run(new EOFException());
fail();
} catch (EOFException expected) {
}
try {
instance.run(new IOException());
fail();
} catch (UndeclaredThrowableException expected) {
}
try {
instance.run(new Exception());
fail();
} catch (UndeclaredThrowableException expected) {
}
}
public void testDeclaredExceptionIntersectionIsEmpty() throws Exception {
ThrowsEOFException instance = (ThrowsEOFException) Proxy.newProxyInstance(loader,
new Class[] {ThrowsSocketException.class, ThrowsEOFException.class},
throwHandler);
try {
instance.run(new EOFException());
fail();
} catch (UndeclaredThrowableException expected) {
}
try {
instance.run(new SocketException());
fail();
} catch (UndeclaredThrowableException expected) {
}
}
public void testDeclaredExceptionIntersectionIsSubset() throws Exception {
ThrowsEOFException instance = (ThrowsEOFException) Proxy.newProxyInstance(loader,
new Class[] {ThrowsEOFException.class, ThrowsSocketExceptionAndEOFException.class},
throwHandler);
try {
instance.run(new EOFException());
fail();
} catch (EOFException expected) {
}
try {
instance.run(new SocketException());
fail();
} catch (UndeclaredThrowableException expected) {
}
try {
instance.run(new IOException());
fail();
} catch (UndeclaredThrowableException expected) {
}
}
public void testDeclaredExceptionIntersectedByExactReturnTypes() throws Exception {
ThrowsIOException instance = (ThrowsIOException) Proxy.newProxyInstance(loader,
new Class[] {ThrowsIOException.class, ThrowsEOFExceptionReturnsString.class},
throwHandler);
try {
instance.run(new EOFException());
fail();
} catch (EOFException expected) {
}
try {
instance.run(new IOException());
fail();
} catch (IOException expected) {
}
try {
((ThrowsEOFExceptionReturnsString) instance).run(new EOFException());
fail();
} catch (EOFException expected) {
}
try {
((ThrowsEOFExceptionReturnsString) instance).run(new IOException());
fail();
} catch (UndeclaredThrowableException expected) {
}
}
public void test_getProxyClass_nullInterfaces() {
try {
Proxy.getProxyClass(loader, new Class<?>[] { null });
fail();
} catch (NullPointerException expected) {
}
try {
Proxy.getProxyClass(loader, Echo.class, null);
fail();
} catch (NullPointerException expected) {
}
}
public void test_getProxyClass_duplicateInterfaces() {
try {
Proxy.getProxyClass(loader, Echo.class, Echo.class);
fail();
} catch (IllegalArgumentException expected) {
}
}
public void test_getProxyClass_caching() throws Exception {
Class<?> proxy1 = Proxy.getProxyClass(loader, Echo.class, ReturnsInt.class);
Class<?> proxy2 = Proxy.getProxyClass(loader, Echo.class, ReturnsInt.class);
Class<?> proxy3 = Proxy.getProxyClass(loader, ReturnsInt.class, Echo.class);
assertSame(proxy1, proxy2);
assertTrue(!proxy2.equals(proxy3));
}
public void testMethodsImplementedByFarIndirectInterface() {
ExtendsExtendsDeclaresFiveMethods instance = (ExtendsExtendsDeclaresFiveMethods)
Proxy.newProxyInstance(loader, new Class[]{ExtendsExtendsDeclaresFiveMethods.class},
returnHandler);
assertEquals("foo", instance.a("foo"));
assertEquals(0x12345678, instance.b(0x12345678));
assertEquals(Double.MIN_VALUE, instance.c(Double.MIN_VALUE));
assertEquals(null, instance.d(null));
assertEquals(0x1234567890abcdefL, instance.e(0x1234567890abcdefL));
}
public void testEquals() {
InvocationHandler handler = new InvocationHandler() {
@Override public Object invoke(Object proxy, Method method, Object[] args) {
return args[0] == ProxyTest.class; // bogus as equals(), but good for testing
}
};
Echo instance = (Echo) Proxy.newProxyInstance(loader, new Class[]{Echo.class}, handler);
assertTrue(instance.equals(ProxyTest.class));
assertFalse(instance.equals(new Object()));
assertFalse(instance.equals(instance));
assertFalse(instance.equals(null));
}
public void testHashCode() {
InvocationHandler handler = new InvocationHandler() {
@Override public Object invoke(Object proxy, Method method, Object[] args) {
return 0x12345678;
}
};
Echo instance = (Echo) Proxy.newProxyInstance(loader, new Class[]{Echo.class}, handler);
assertEquals(0x12345678, instance.hashCode());
}
public void testToString() {
InvocationHandler handler = new InvocationHandler() {
@Override public Object invoke(Object proxy, Method method, Object[] args) {
return "foo";
}
};
Echo instance = (Echo) Proxy.newProxyInstance(loader, new Class[]{Echo.class}, handler);
assertEquals("foo", instance.toString());
}
public void testReturnTypeDoesNotSatisfyAllConstraintsWithLenientCaller() {
InvocationHandler handler = new InvocationHandler() {
@Override public Object invoke(Object proxy, Method method, Object[] args) {
assertEquals(Object.class, method.getReturnType());
return Boolean.TRUE; // not the right type for 'ReturnsString' callers
}
};
ReturnsObject returnsObject = (ReturnsObject) Proxy.newProxyInstance(loader,
new Class[] {ReturnsString.class, ReturnsObject.class}, handler);
assertEquals(true, returnsObject.foo());
}
public void testReturnTypeDoesNotSatisfyAllConstraintsWithStrictCaller() {
InvocationHandler handler = new InvocationHandler() {
@Override public Object invoke(Object proxy, Method method, Object[] args) {
assertEquals(String.class, method.getReturnType());
return Boolean.TRUE; // not the right type for 'ReturnsString' callers
}
};
ReturnsString returnsString = (ReturnsString) Proxy.newProxyInstance(loader,
new Class[] {ReturnsString.class, ReturnsObject.class}, handler);
try {
returnsString.foo();
fail();
} catch (ClassCastException expected) {
}
}
public void testReturnsTypeAndInterfaceNotImplementedByThatType() {
try {
Proxy.newProxyInstance(loader, new Class[] {ReturnsString.class, ReturnsEcho.class},
returnHandler);
fail();
} catch (IllegalArgumentException expected) {
}
}
public interface Echo {
String echo(String s);
}
public interface ReturnsInt {
int foo();
}
public interface ReturnsFloat {
float foo();
}
public interface ReturnsInteger {
Integer foo();
}
public interface ReturnsString {
String foo();
}
public interface ReturnsCharSequence {
CharSequence foo();
}
public interface ReturnsSerializable {
CharSequence foo();
}
public interface ReturnsComparable {
CharSequence foo();
}
public interface ReturnsObject {
Object foo();
}
public interface ReturnsVoid {
void foo();
}
public interface ReturnsEcho {
Echo foo();
}
public interface ThrowsIOException {
Object run(Throwable toThrow) throws IOException;
}
public interface ThrowsEOFException {
Object run(Throwable toThrow) throws EOFException;
}
public interface ThrowsEOFExceptionReturnsString {
String run(Throwable toThrow) throws EOFException;
}
public interface ThrowsSocketException {
Object run(Throwable toThrow) throws SocketException;
}
public interface ThrowsSocketExceptionAndEOFException {
Object run(Throwable toThrow) throws SocketException, EOFException;
}
public interface DeclaresFiveMethods {
String a(String a);
int b(int b);
double c(double c);
Object d(Object d);
long e(long e);
}
public interface ExtendsDeclaresFiveMethods extends DeclaresFiveMethods {
}
public interface ExtendsExtendsDeclaresFiveMethods extends ExtendsDeclaresFiveMethods {
}
public static class TestInvocationHandler implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return args[0];
}
}
// https://code.google.com/p/android/issues/detail?id=24846
public void test24846() throws Exception {
ClassLoader cl = getClass().getClassLoader();
Class[] interfaces = { java.beans.PropertyChangeListener.class };
Object proxy = Proxy.newProxyInstance(cl, interfaces, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
});
for (Field field : proxy.getClass().getDeclaredFields()) {
field.setAccessible(true);
assertFalse(field.isAnnotationPresent(Deprecated.class));
}
}
}