1 /* 2 * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import java.io.IOException; 25 import java.lang.reflect.InvocationHandler; 26 import java.lang.reflect.InvocationTargetException; 27 import java.lang.reflect.Method; 28 import java.lang.reflect.Proxy; 29 import java.util.Arrays; 30 31 import org.testng.annotations.DataProvider; 32 import org.testng.annotations.Test; 33 import static org.testng.Assert.*; 34 35 /** 36 * @test 37 * @bug 8159746 38 * @run testng DefaultMethods 39 * @summary Basic tests for Proxy::invokeSuper default method 40 */ 41 42 public class DefaultMethods { 43 public interface I1 { m()44 default int m() { 45 return 10; 46 } 47 } 48 49 public interface I2 { m()50 default int m() { 51 return 20; 52 } 53 privateMethod()54 private void privateMethod() { 55 throw new Error("should not reach here"); 56 } 57 } 58 59 // I3::m inherits from I2:m 60 public interface I3 extends I2 { m3(String... s)61 default int m3(String... s) { 62 return Arrays.stream(s).mapToInt(String::length).sum(); 63 } 64 } 65 66 public interface I4 extends I1, I2 { m()67 default int m() { 68 return 40; 69 } 70 mix(int a, String b)71 default int mix(int a, String b) { 72 return 0; 73 } 74 } 75 76 public interface I12 extends I1, I2 { 77 @Override m()78 int m(); 79 sum(int a, int b)80 default int sum(int a, int b) { 81 return a + b; 82 } 83 concat(Object first, Object... rest)84 default Object[] concat(Object first, Object... rest) { 85 Object[] result = new Object[1 + rest.length]; 86 result[0] = first; 87 System.arraycopy(rest, 0, result, 1, rest.length); 88 return result; 89 } 90 } 91 92 public interface IX { doThrow(Throwable exception)93 default void doThrow(Throwable exception) throws Throwable { 94 throw exception; 95 } 96 } 97 findDefaultMethod(Class<?> refc, Method m)98 private static Method findDefaultMethod(Class<?> refc, Method m) { 99 try { 100 assertTrue(refc.isInterface()); 101 102 Method method = refc.getMethod(m.getName(), m.getParameterTypes()); 103 assertTrue(method.isDefault()); 104 return method; 105 } catch (NoSuchMethodException e) { 106 throw new RuntimeException(e); 107 } 108 } 109 110 @Test test()111 public void test() { 112 ClassLoader loader = DefaultMethods.class.getClassLoader(); 113 Object proxy = Proxy.newProxyInstance(loader, new Class<?>[] { I1.class, I2.class}, 114 (o, method, params) -> { 115 return InvocationHandler.invokeDefault(o, findDefaultMethod(I2.class, method), params); 116 }); 117 I1 i1 = (I1) proxy; 118 assertEquals(i1.m(), 20); 119 } 120 121 // a default method is declared in one of the proxy interfaces 122 @DataProvider(name = "defaultMethods") defaultMethods()123 private Object[][] defaultMethods() { 124 return new Object[][]{ 125 new Object[]{new Class<?>[]{I1.class, I2.class}, true, 10}, 126 new Object[]{new Class<?>[]{I1.class, I3.class}, true, 10}, 127 new Object[]{new Class<?>[]{I1.class, I12.class}, true, 10}, 128 new Object[]{new Class<?>[]{I2.class, I12.class}, true, 20}, 129 new Object[]{new Class<?>[]{I4.class}, true, 40}, 130 new Object[]{new Class<?>[]{I4.class, I3.class}, true, 40}, 131 new Object[]{new Class<?>[]{I12.class}, false, -1}, 132 new Object[]{new Class<?>[]{I12.class, I1.class, I2.class}, false, -1} 133 }; 134 } 135 136 @Test(dataProvider = "defaultMethods") testDefaultMethod(Class<?>[] intfs, boolean isDefault, int expected)137 public void testDefaultMethod(Class<?>[] intfs, boolean isDefault, int expected) throws Throwable { 138 InvocationHandler ih = (proxy, method, params) -> { 139 System.out.format("invoking %s with parameters: %s%n", method, Arrays.toString(params)); 140 switch (method.getName()) { 141 case "m": 142 assertTrue(method.isDefault() == isDefault); 143 assertTrue(Arrays.stream(proxy.getClass().getInterfaces()) 144 .anyMatch(intf -> method.getDeclaringClass() == intf), 145 Arrays.toString(proxy.getClass().getInterfaces())); 146 if (method.isDefault()) { 147 return InvocationHandler.invokeDefault(proxy, method, params); 148 } else { 149 return -1; 150 } 151 default: 152 throw new UnsupportedOperationException(method.toString()); 153 } 154 }; 155 156 Object proxy = Proxy.newProxyInstance(DefaultMethods.class.getClassLoader(), intfs, ih); 157 Method m = proxy.getClass().getMethod("m"); 158 int result = (int)m.invoke(proxy); 159 assertEquals(result, expected); 160 } 161 162 // a default method may be declared in a proxy interface or 163 // inherited from a superinterface of a proxy interface 164 @DataProvider(name = "supers") supers()165 private Object[][] supers() { 166 return new Object[][]{ 167 // invoke "m" implemented in the first proxy interface 168 // same as the method passed to InvocationHandler::invoke 169 new Object[]{new Class<?>[]{I1.class}, I1.class, 10}, 170 new Object[]{new Class<?>[]{I2.class}, I2.class, 20}, 171 new Object[]{new Class<?>[]{I1.class, I2.class}, I1.class, 10}, 172 // "m" is implemented in I2, an indirect superinterface of I3 173 new Object[]{new Class<?>[]{I3.class}, I3.class, 20}, 174 // "m" is implemented in I1, I2 and overridden in I4 175 new Object[]{new Class<?>[]{I4.class}, I4.class, 40}, 176 // invoke "m" implemented in the second proxy interface 177 // different from the method passed to InvocationHandler::invoke 178 new Object[]{new Class<?>[]{I1.class, I2.class}, I2.class, 20}, 179 new Object[]{new Class<?>[]{I1.class, I3.class}, I3.class, 20}, 180 // I2::m is implemented in more than one proxy interface directly or indirectly 181 // I3::m resolves to I2::m (indirect superinterface) 182 // I2 is the superinterface of I4 and I4 overrides m 183 // the proxy class can invoke I4::m and I2::m 184 new Object[]{new Class<?>[]{I3.class, I4.class}, I3.class, 20}, 185 new Object[]{new Class<?>[]{I3.class, I4.class}, I4.class, 40}, 186 new Object[]{new Class<?>[]{I4.class, I3.class}, I3.class, 20}, 187 new Object[]{new Class<?>[]{I4.class, I3.class}, I4.class, 40} 188 }; 189 } 190 191 @Test(dataProvider = "supers") testSuper(Class<?>[] intfs, Class<?> proxyInterface, int expected)192 public void testSuper(Class<?>[] intfs, Class<?> proxyInterface, int expected) throws Throwable { 193 final InvocationHandler ih = (proxy, method, params) -> { 194 switch (method.getName()) { 195 case "m": 196 assertTrue(method.isDefault()); 197 return InvocationHandler.invokeDefault(proxy, findDefaultMethod(proxyInterface, method), params); 198 default: 199 throw new UnsupportedOperationException(method.toString()); 200 } 201 }; 202 ClassLoader loader = proxyInterface.getClassLoader(); 203 Object proxy = Proxy.newProxyInstance(loader, intfs, ih); 204 if (proxyInterface == I1.class) { 205 I1 i1 = (I1) proxy; 206 assertEquals(i1.m(), expected); 207 } else if (proxyInterface == I2.class) { 208 I2 i2 = (I2) proxy; 209 assertEquals(i2.m(), expected); 210 } else if (proxyInterface == I3.class) { 211 I3 i3 = (I3) proxy; 212 assertEquals(i3.m(), expected); 213 } else if (proxyInterface == I4.class) { 214 I4 i4 = (I4) proxy; 215 assertEquals(i4.m(), expected); 216 } else { 217 throw new UnsupportedOperationException(proxyInterface.toString()); 218 } 219 // invoke via InvocationHandler.invokeDefaultMethod directly 220 assertEquals(InvocationHandler.invokeDefault(proxy, proxyInterface.getMethod("m")), expected); 221 } 222 223 // invoke I12 default methods with parameters and var args 224 @Test testI12()225 public void testI12() throws Throwable { 226 final InvocationHandler ih = (proxy, method, params) -> { 227 System.out.format("invoking %s with parameters: %s%n", method, Arrays.toString(params)); 228 switch (method.getName()) { 229 case "sum": 230 case "concat": 231 assertTrue(method.isDefault()); 232 return InvocationHandler.invokeDefault(proxy, method, params); 233 default: 234 throw new UnsupportedOperationException(method.toString()); 235 } 236 }; 237 ClassLoader loader = DefaultMethods.class.getClassLoader(); 238 I12 i12 = (I12) Proxy.newProxyInstance(loader, new Class<?>[] { I12.class }, ih); 239 assertEquals(i12.sum(1, 2), 3); 240 assertEquals(i12.concat(1, 2, 3, 4), new Object[]{1, 2, 3, 4}); 241 Method m = I12.class.getMethod("concat", Object.class, Object[].class); 242 assertTrue(m.isDefault()); 243 assertEquals(InvocationHandler.invokeDefault(i12, m, 100, new Object[] {"foo", true, "bar"}), 244 new Object[] {100, "foo", true, "bar"}); 245 } 246 247 // test a no-arg default method with and without arguments passed in the invocation 248 @Test testEmptyArgument()249 public void testEmptyArgument() throws Throwable { 250 ClassLoader loader = DefaultMethods.class.getClassLoader(); 251 Object proxy = Proxy.newProxyInstance(loader, new Class<?>[]{I4.class}, HANDLER); 252 Method m1 = I4.class.getMethod("m"); 253 assertTrue(m1.getDeclaringClass() == I4.class); 254 assertTrue(m1.isDefault()); 255 InvocationHandler.invokeDefault(proxy, m1); 256 InvocationHandler.invokeDefault(proxy, m1, new Object[0]); 257 258 Method m2 = I4.class.getMethod("mix", int.class, String.class); 259 assertTrue(m1.getDeclaringClass() == I4.class); 260 assertTrue(m1.isDefault()); 261 InvocationHandler.invokeDefault(proxy, m2, Integer.valueOf(100), "foo"); 262 } 263 264 @Test testVarArgs()265 public void testVarArgs() throws Throwable { 266 ClassLoader loader = DefaultMethods.class.getClassLoader(); 267 I3 proxy = (I3)Proxy.newProxyInstance(loader, new Class<?>[]{I3.class}, HANDLER); 268 Method m = I3.class.getMethod("m3", String[].class); 269 assertTrue(m.isVarArgs() && m.isDefault()); 270 assertEquals(proxy.m3("a", "b", "cde"), 5); 271 assertEquals(InvocationHandler.invokeDefault(proxy, m, (Object)new String[] { "a", "bc" }), 3); 272 } 273 274 /* 275 * Invoke I12::m which is an abstract method 276 */ 277 @Test(expectedExceptions = {IllegalArgumentException.class}) invokeAbstractMethod()278 public void invokeAbstractMethod() throws Exception { 279 ClassLoader loader = DefaultMethods.class.getClassLoader(); 280 I12 proxy = (I12) Proxy.newProxyInstance(loader, new Class<?>[]{I12.class}, HANDLER); 281 Method method = I12.class.getMethod("m"); 282 assertTrue(method.getDeclaringClass() == I12.class); 283 assertFalse(method.isDefault()); 284 proxy.m(); 285 } 286 287 /* 288 * Invoke a non proxy (default) method with parameters 289 */ 290 @Test(expectedExceptions = {IllegalArgumentException.class}) invokeNonProxyMethod()291 public void invokeNonProxyMethod() throws Throwable { 292 ClassLoader loader = DefaultMethods.class.getClassLoader(); 293 I3 proxy = (I3) Proxy.newProxyInstance(loader, new Class<?>[]{I3.class}, HANDLER); 294 Method m = I4.class.getMethod("mix", int.class, String.class); 295 assertTrue(m.isDefault()); 296 InvocationHandler.invokeDefault(proxy, m); 297 } 298 299 // negative cases 300 @DataProvider(name = "negativeCases") negativeCases()301 private Object[][] negativeCases() { 302 return new Object[][]{ 303 // I4::m overrides I1::m and I2::m 304 new Object[] { new Class<?>[]{I4.class}, I1.class, "m" }, 305 new Object[] { new Class<?>[]{I4.class}, I2.class, "m" }, 306 // I12::m is not a default method 307 new Object[] { new Class<?>[]{I12.class}, I12.class, "m" }, 308 // non-proxy default method 309 new Object[] { new Class<?>[]{I3.class}, I1.class, "m" }, 310 // not a default method and not a proxy interface 311 new Object[] { new Class<?>[]{I12.class}, DefaultMethods.class, "test" }, 312 new Object[] { new Class<?>[]{I12.class}, Runnable.class, "run" }, 313 // I2::privateMethod is a private method 314 new Object[] { new Class<?>[]{I3.class}, I2.class, "privateMethod" } 315 }; 316 } 317 318 @Test(dataProvider = "negativeCases", expectedExceptions = {IllegalArgumentException.class}) testNegativeCase(Class<?>[] interfaces, Class<?> defc, String name)319 public void testNegativeCase(Class<?>[] interfaces, Class<?> defc, String name) 320 throws Throwable { 321 ClassLoader loader = DefaultMethods.class.getClassLoader(); 322 Object proxy = Proxy.newProxyInstance(loader, interfaces, HANDLER); 323 try { 324 Method method = defc.getDeclaredMethod(name); 325 InvocationHandler.invokeDefault(proxy, method); 326 } catch (Throwable e) { 327 System.out.format("%s method %s::%s exception thrown: %s%n", 328 Arrays.toString(interfaces), defc.getName(), name, e.getMessage()); 329 throw e; 330 } 331 } 332 333 @DataProvider(name = "illegalArguments") illegalArguments()334 private Object[][] illegalArguments() { 335 return new Object[][] { 336 new Object[] {}, 337 new Object[] { 100 }, 338 new Object[] { 100, "foo", 100 }, 339 new Object[] { 100L, "foo" }, 340 new Object[] { "foo", 100}, 341 new Object[] { null, "foo" } 342 }; 343 } 344 345 @Test(dataProvider = "illegalArguments", expectedExceptions = {IllegalArgumentException.class}) testIllegalArgument(Object... args)346 public void testIllegalArgument(Object... args) throws Throwable { 347 ClassLoader loader = DefaultMethods.class.getClassLoader(); 348 I4 proxy = (I4)Proxy.newProxyInstance(loader, new Class<?>[]{I4.class}, HANDLER); 349 Method m = I4.class.getMethod("mix", int.class, String.class); 350 assertTrue(m.isDefault()); 351 if (args.length == 0) { 352 // substitute empty args with null since @DataProvider doesn't allow null array 353 args = null; 354 } 355 InvocationHandler.invokeDefault(proxy, m, args); 356 } 357 358 @DataProvider(name = "throwables") throwables()359 private Object[][] throwables() { 360 return new Object[][] { 361 new Object[] { new IOException() }, 362 new Object[] { new IllegalArgumentException() }, 363 new Object[] { new ClassCastException() }, 364 new Object[] { new NullPointerException() }, 365 new Object[] { new AssertionError() }, 366 new Object[] { new Throwable() } 367 }; 368 } 369 370 @Test(dataProvider = "throwables") testInvocationException(Throwable exception)371 public void testInvocationException(Throwable exception) throws Throwable { 372 ClassLoader loader = DefaultMethods.class.getClassLoader(); 373 IX proxy = (IX)Proxy.newProxyInstance(loader, new Class<?>[]{IX.class}, HANDLER); 374 Method m = IX.class.getMethod("doThrow", Throwable.class); 375 try { 376 InvocationHandler.invokeDefault(proxy, m, exception); 377 } catch (Throwable e) { 378 assertEquals(e, exception); 379 } 380 } 381 382 private static final InvocationHandler HANDLER = (proxy, method, params) -> { 383 System.out.format("invoking %s with parameters: %s%n", method, Arrays.toString(params)); 384 return InvocationHandler.invokeDefault(proxy, method, params); 385 }; 386 } 387