dfe8833f5d
Reviewed-by: iris
387 lines
16 KiB
Java
387 lines
16 KiB
Java
/*
|
|
* Copyright (c) 2020, 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.
|
|
*/
|
|
|
|
import java.io.IOException;
|
|
import java.lang.reflect.InvocationHandler;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.lang.reflect.Method;
|
|
import java.lang.reflect.Proxy;
|
|
import java.util.Arrays;
|
|
|
|
import org.testng.annotations.DataProvider;
|
|
import org.testng.annotations.Test;
|
|
import static org.testng.Assert.*;
|
|
|
|
/**
|
|
* @test
|
|
* @bug 8159746
|
|
* @run testng DefaultMethods
|
|
* @summary Basic tests for Proxy::invokeSuper default method
|
|
*/
|
|
|
|
public class DefaultMethods {
|
|
public interface I1 {
|
|
default int m() {
|
|
return 10;
|
|
}
|
|
}
|
|
|
|
public interface I2 {
|
|
default int m() {
|
|
return 20;
|
|
}
|
|
|
|
private void privateMethod() {
|
|
throw new Error("should not reach here");
|
|
}
|
|
}
|
|
|
|
// I3::m inherits from I2:m
|
|
public interface I3 extends I2 {
|
|
default int m3(String... s) {
|
|
return Arrays.stream(s).mapToInt(String::length).sum();
|
|
}
|
|
}
|
|
|
|
public interface I4 extends I1, I2 {
|
|
default int m() {
|
|
return 40;
|
|
}
|
|
|
|
default int mix(int a, String b) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
public interface I12 extends I1, I2 {
|
|
@Override
|
|
int m();
|
|
|
|
default int sum(int a, int b) {
|
|
return a + b;
|
|
}
|
|
|
|
default Object[] concat(Object first, Object... rest) {
|
|
Object[] result = new Object[1 + rest.length];
|
|
result[0] = first;
|
|
System.arraycopy(rest, 0, result, 1, rest.length);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
public interface IX {
|
|
default void doThrow(Throwable exception) throws Throwable {
|
|
throw exception;
|
|
}
|
|
}
|
|
|
|
private static Method findDefaultMethod(Class<?> refc, Method m) {
|
|
try {
|
|
assertTrue(refc.isInterface());
|
|
|
|
Method method = refc.getMethod(m.getName(), m.getParameterTypes());
|
|
assertTrue(method.isDefault());
|
|
return method;
|
|
} catch (NoSuchMethodException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void test() {
|
|
ClassLoader loader = DefaultMethods.class.getClassLoader();
|
|
Object proxy = Proxy.newProxyInstance(loader, new Class<?>[] { I1.class, I2.class},
|
|
(o, method, params) -> {
|
|
return InvocationHandler.invokeDefault(o, findDefaultMethod(I2.class, method), params);
|
|
});
|
|
I1 i1 = (I1) proxy;
|
|
assertEquals(i1.m(), 20);
|
|
}
|
|
|
|
// a default method is declared in one of the proxy interfaces
|
|
@DataProvider(name = "defaultMethods")
|
|
private Object[][] defaultMethods() {
|
|
return new Object[][]{
|
|
new Object[]{new Class<?>[]{I1.class, I2.class}, true, 10},
|
|
new Object[]{new Class<?>[]{I1.class, I3.class}, true, 10},
|
|
new Object[]{new Class<?>[]{I1.class, I12.class}, true, 10},
|
|
new Object[]{new Class<?>[]{I2.class, I12.class}, true, 20},
|
|
new Object[]{new Class<?>[]{I4.class}, true, 40},
|
|
new Object[]{new Class<?>[]{I4.class, I3.class}, true, 40},
|
|
new Object[]{new Class<?>[]{I12.class}, false, -1},
|
|
new Object[]{new Class<?>[]{I12.class, I1.class, I2.class}, false, -1}
|
|
};
|
|
}
|
|
|
|
@Test(dataProvider = "defaultMethods")
|
|
public void testDefaultMethod(Class<?>[] intfs, boolean isDefault, int expected) throws Throwable {
|
|
InvocationHandler ih = (proxy, method, params) -> {
|
|
System.out.format("invoking %s with parameters: %s%n", method, Arrays.toString(params));
|
|
switch (method.getName()) {
|
|
case "m":
|
|
assertTrue(method.isDefault() == isDefault);
|
|
assertTrue(Arrays.stream(proxy.getClass().getInterfaces())
|
|
.anyMatch(intf -> method.getDeclaringClass() == intf),
|
|
Arrays.toString(proxy.getClass().getInterfaces()));
|
|
if (method.isDefault()) {
|
|
return InvocationHandler.invokeDefault(proxy, method, params);
|
|
} else {
|
|
return -1;
|
|
}
|
|
default:
|
|
throw new UnsupportedOperationException(method.toString());
|
|
}
|
|
};
|
|
|
|
Object proxy = Proxy.newProxyInstance(DefaultMethods.class.getClassLoader(), intfs, ih);
|
|
Method m = proxy.getClass().getMethod("m");
|
|
int result = (int)m.invoke(proxy);
|
|
assertEquals(result, expected);
|
|
}
|
|
|
|
// a default method may be declared in a proxy interface or
|
|
// inherited from a superinterface of a proxy interface
|
|
@DataProvider(name = "supers")
|
|
private Object[][] supers() {
|
|
return new Object[][]{
|
|
// invoke "m" implemented in the first proxy interface
|
|
// same as the method passed to InvocationHandler::invoke
|
|
new Object[]{new Class<?>[]{I1.class}, I1.class, 10},
|
|
new Object[]{new Class<?>[]{I2.class}, I2.class, 20},
|
|
new Object[]{new Class<?>[]{I1.class, I2.class}, I1.class, 10},
|
|
// "m" is implemented in I2, an indirect superinterface of I3
|
|
new Object[]{new Class<?>[]{I3.class}, I3.class, 20},
|
|
// "m" is implemented in I1, I2 and overridden in I4
|
|
new Object[]{new Class<?>[]{I4.class}, I4.class, 40},
|
|
// invoke "m" implemented in the second proxy interface
|
|
// different from the method passed to InvocationHandler::invoke
|
|
new Object[]{new Class<?>[]{I1.class, I2.class}, I2.class, 20},
|
|
new Object[]{new Class<?>[]{I1.class, I3.class}, I3.class, 20},
|
|
// I2::m is implemented in more than one proxy interface directly or indirectly
|
|
// I3::m resolves to I2::m (indirect superinterface)
|
|
// I2 is the superinterface of I4 and I4 overrides m
|
|
// the proxy class can invoke I4::m and I2::m
|
|
new Object[]{new Class<?>[]{I3.class, I4.class}, I3.class, 20},
|
|
new Object[]{new Class<?>[]{I3.class, I4.class}, I4.class, 40},
|
|
new Object[]{new Class<?>[]{I4.class, I3.class}, I3.class, 20},
|
|
new Object[]{new Class<?>[]{I4.class, I3.class}, I4.class, 40}
|
|
};
|
|
}
|
|
|
|
@Test(dataProvider = "supers")
|
|
public void testSuper(Class<?>[] intfs, Class<?> proxyInterface, int expected) throws Throwable {
|
|
final InvocationHandler ih = (proxy, method, params) -> {
|
|
switch (method.getName()) {
|
|
case "m":
|
|
assertTrue(method.isDefault());
|
|
return InvocationHandler.invokeDefault(proxy, findDefaultMethod(proxyInterface, method), params);
|
|
default:
|
|
throw new UnsupportedOperationException(method.toString());
|
|
}
|
|
};
|
|
ClassLoader loader = proxyInterface.getClassLoader();
|
|
Object proxy = Proxy.newProxyInstance(loader, intfs, ih);
|
|
if (proxyInterface == I1.class) {
|
|
I1 i1 = (I1) proxy;
|
|
assertEquals(i1.m(), expected);
|
|
} else if (proxyInterface == I2.class) {
|
|
I2 i2 = (I2) proxy;
|
|
assertEquals(i2.m(), expected);
|
|
} else if (proxyInterface == I3.class) {
|
|
I3 i3 = (I3) proxy;
|
|
assertEquals(i3.m(), expected);
|
|
} else if (proxyInterface == I4.class) {
|
|
I4 i4 = (I4) proxy;
|
|
assertEquals(i4.m(), expected);
|
|
} else {
|
|
throw new UnsupportedOperationException(proxyInterface.toString());
|
|
}
|
|
// invoke via InvocationHandler.invokeDefaultMethod directly
|
|
assertEquals(InvocationHandler.invokeDefault(proxy, proxyInterface.getMethod("m")), expected);
|
|
}
|
|
|
|
// invoke I12 default methods with parameters and var args
|
|
@Test
|
|
public void testI12() throws Throwable {
|
|
final InvocationHandler ih = (proxy, method, params) -> {
|
|
System.out.format("invoking %s with parameters: %s%n", method, Arrays.toString(params));
|
|
switch (method.getName()) {
|
|
case "sum":
|
|
case "concat":
|
|
assertTrue(method.isDefault());
|
|
return InvocationHandler.invokeDefault(proxy, method, params);
|
|
default:
|
|
throw new UnsupportedOperationException(method.toString());
|
|
}
|
|
};
|
|
ClassLoader loader = DefaultMethods.class.getClassLoader();
|
|
I12 i12 = (I12) Proxy.newProxyInstance(loader, new Class<?>[] { I12.class }, ih);
|
|
assertEquals(i12.sum(1, 2), 3);
|
|
assertEquals(i12.concat(1, 2, 3, 4), new Object[]{1, 2, 3, 4});
|
|
Method m = I12.class.getMethod("concat", Object.class, Object[].class);
|
|
assertTrue(m.isDefault());
|
|
assertEquals(InvocationHandler.invokeDefault(i12, m, 100, new Object[] {"foo", true, "bar"}),
|
|
new Object[] {100, "foo", true, "bar"});
|
|
}
|
|
|
|
// test a no-arg default method with and without arguments passed in the invocation
|
|
@Test
|
|
public void testEmptyArgument() throws Throwable {
|
|
ClassLoader loader = DefaultMethods.class.getClassLoader();
|
|
Object proxy = Proxy.newProxyInstance(loader, new Class<?>[]{I4.class}, HANDLER);
|
|
Method m1 = I4.class.getMethod("m");
|
|
assertTrue(m1.getDeclaringClass() == I4.class);
|
|
assertTrue(m1.isDefault());
|
|
InvocationHandler.invokeDefault(proxy, m1);
|
|
InvocationHandler.invokeDefault(proxy, m1, new Object[0]);
|
|
|
|
Method m2 = I4.class.getMethod("mix", int.class, String.class);
|
|
assertTrue(m1.getDeclaringClass() == I4.class);
|
|
assertTrue(m1.isDefault());
|
|
InvocationHandler.invokeDefault(proxy, m2, Integer.valueOf(100), "foo");
|
|
}
|
|
|
|
@Test
|
|
public void testVarArgs() throws Throwable {
|
|
ClassLoader loader = DefaultMethods.class.getClassLoader();
|
|
I3 proxy = (I3)Proxy.newProxyInstance(loader, new Class<?>[]{I3.class}, HANDLER);
|
|
Method m = I3.class.getMethod("m3", String[].class);
|
|
assertTrue(m.isVarArgs() && m.isDefault());
|
|
assertEquals(proxy.m3("a", "b", "cde"), 5);
|
|
assertEquals(InvocationHandler.invokeDefault(proxy, m, (Object)new String[] { "a", "bc" }), 3);
|
|
}
|
|
|
|
/*
|
|
* Invoke I12::m which is an abstract method
|
|
*/
|
|
@Test(expectedExceptions = {IllegalArgumentException.class})
|
|
public void invokeAbstractMethod() throws Exception {
|
|
ClassLoader loader = DefaultMethods.class.getClassLoader();
|
|
I12 proxy = (I12) Proxy.newProxyInstance(loader, new Class<?>[]{I12.class}, HANDLER);
|
|
Method method = I12.class.getMethod("m");
|
|
assertTrue(method.getDeclaringClass() == I12.class);
|
|
assertFalse(method.isDefault());
|
|
proxy.m();
|
|
}
|
|
|
|
/*
|
|
* Invoke a non proxy (default) method with parameters
|
|
*/
|
|
@Test(expectedExceptions = {IllegalArgumentException.class})
|
|
public void invokeNonProxyMethod() throws Throwable {
|
|
ClassLoader loader = DefaultMethods.class.getClassLoader();
|
|
I3 proxy = (I3) Proxy.newProxyInstance(loader, new Class<?>[]{I3.class}, HANDLER);
|
|
Method m = I4.class.getMethod("mix", int.class, String.class);
|
|
assertTrue(m.isDefault());
|
|
InvocationHandler.invokeDefault(proxy, m);
|
|
}
|
|
|
|
// negative cases
|
|
@DataProvider(name = "negativeCases")
|
|
private Object[][] negativeCases() {
|
|
return new Object[][]{
|
|
// I4::m overrides I1::m and I2::m
|
|
new Object[] { new Class<?>[]{I4.class}, I1.class, "m" },
|
|
new Object[] { new Class<?>[]{I4.class}, I2.class, "m" },
|
|
// I12::m is not a default method
|
|
new Object[] { new Class<?>[]{I12.class}, I12.class, "m" },
|
|
// non-proxy default method
|
|
new Object[] { new Class<?>[]{I3.class}, I1.class, "m" },
|
|
// not a default method and not a proxy interface
|
|
new Object[] { new Class<?>[]{I12.class}, DefaultMethods.class, "test" },
|
|
new Object[] { new Class<?>[]{I12.class}, Runnable.class, "run" },
|
|
// I2::privateMethod is a private method
|
|
new Object[] { new Class<?>[]{I3.class}, I2.class, "privateMethod" }
|
|
};
|
|
}
|
|
|
|
@Test(dataProvider = "negativeCases", expectedExceptions = {IllegalArgumentException.class})
|
|
public void testNegativeCase(Class<?>[] interfaces, Class<?> defc, String name)
|
|
throws Throwable {
|
|
ClassLoader loader = DefaultMethods.class.getClassLoader();
|
|
Object proxy = Proxy.newProxyInstance(loader, interfaces, HANDLER);
|
|
try {
|
|
Method method = defc.getDeclaredMethod(name);
|
|
InvocationHandler.invokeDefault(proxy, method);
|
|
} catch (Throwable e) {
|
|
System.out.format("%s method %s::%s exception thrown: %s%n",
|
|
Arrays.toString(interfaces), defc.getName(), name, e.getMessage());
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
@DataProvider(name = "illegalArguments")
|
|
private Object[][] illegalArguments() {
|
|
return new Object[][] {
|
|
new Object[] { new Object[0]},
|
|
new Object[] { new Object[] { 100 }},
|
|
new Object[] { new Object[] { 100, "foo", 100 }},
|
|
new Object[] { new Object[] { 100L, "foo" }},
|
|
new Object[] { new Object[] { "foo", 100 }},
|
|
new Object[] { new Object[] { null, "foo" }}
|
|
};
|
|
}
|
|
|
|
@Test(dataProvider = "illegalArguments", expectedExceptions = {IllegalArgumentException.class})
|
|
public void testIllegalArgument(Object[] args) throws Throwable {
|
|
ClassLoader loader = DefaultMethods.class.getClassLoader();
|
|
I4 proxy = (I4)Proxy.newProxyInstance(loader, new Class<?>[]{I4.class}, HANDLER);
|
|
Method m = I4.class.getMethod("mix", int.class, String.class);
|
|
assertTrue(m.isDefault());
|
|
if (args.length == 0) {
|
|
// substitute empty args with null since @DataProvider doesn't allow null array
|
|
args = null;
|
|
}
|
|
InvocationHandler.invokeDefault(proxy, m, args);
|
|
}
|
|
|
|
@DataProvider(name = "throwables")
|
|
private Object[][] throwables() {
|
|
return new Object[][] {
|
|
new Object[] { new IOException() },
|
|
new Object[] { new IllegalArgumentException() },
|
|
new Object[] { new ClassCastException() },
|
|
new Object[] { new NullPointerException() },
|
|
new Object[] { new AssertionError() },
|
|
new Object[] { new Throwable() }
|
|
};
|
|
}
|
|
|
|
@Test(dataProvider = "throwables")
|
|
public void testInvocationException(Throwable exception) throws Throwable {
|
|
ClassLoader loader = DefaultMethods.class.getClassLoader();
|
|
IX proxy = (IX)Proxy.newProxyInstance(loader, new Class<?>[]{IX.class}, HANDLER);
|
|
Method m = IX.class.getMethod("doThrow", Throwable.class);
|
|
try {
|
|
InvocationHandler.invokeDefault(proxy, m, exception);
|
|
} catch (Throwable e) {
|
|
assertEquals(e, exception);
|
|
}
|
|
}
|
|
|
|
private static final InvocationHandler HANDLER = (proxy, method, params) -> {
|
|
System.out.format("invoking %s with parameters: %s%n", method, Arrays.toString(params));
|
|
return InvocationHandler.invokeDefault(proxy, method, params);
|
|
};
|
|
}
|