1 /* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.dexmaker.stock; 18 19 import com.google.dexmaker.DexMakerTest; 20 import java.io.File; 21 import java.io.IOException; 22 import java.lang.reflect.InvocationHandler; 23 import java.lang.reflect.Method; 24 import java.lang.reflect.UndeclaredThrowableException; 25 import java.util.Arrays; 26 import java.util.Random; 27 import java.util.concurrent.Callable; 28 import java.util.concurrent.atomic.AtomicInteger; 29 import junit.framework.AssertionFailedError; 30 import junit.framework.TestCase; 31 32 public class ProxyBuilderTest extends TestCase { 33 private FakeInvocationHandler fakeHandler = new FakeInvocationHandler(); 34 35 public static class SimpleClass { 36 public String simpleMethod() { 37 throw new AssertionFailedError(); 38 } 39 } 40 41 public void testExampleOperation() throws Throwable { 42 fakeHandler.setFakeResult("expected"); 43 SimpleClass proxy = proxyFor(SimpleClass.class).build(); 44 assertEquals("expected", proxy.simpleMethod()); 45 } 46 47 public static class ConstructorTakesArguments { 48 private final String argument; 49 50 public ConstructorTakesArguments(String arg) { 51 argument = arg; 52 } 53 54 public String method() { 55 throw new AssertionFailedError(); 56 } 57 } 58 59 public void testConstruction_SucceedsIfCorrectArgumentsProvided() throws Throwable { 60 ConstructorTakesArguments proxy = proxyFor(ConstructorTakesArguments.class) 61 .constructorArgTypes(String.class) 62 .constructorArgValues("hello") 63 .build(); 64 assertEquals("hello", proxy.argument); 65 proxy.method(); 66 } 67 68 public void testConstruction_FailsWithWrongNumberOfArguments() throws Throwable { 69 try { 70 proxyFor(ConstructorTakesArguments.class).build(); 71 fail(); 72 } catch (IllegalArgumentException expected) {} 73 } 74 75 public void testClassIsNotAccessbile_FailsWithUnsupportedOperationException() throws Exception { 76 class MethodVisibilityClass { 77 } 78 try { 79 proxyFor(MethodVisibilityClass.class).build(); 80 fail(); 81 } catch (UnsupportedOperationException expected) {} 82 } 83 84 private static class PrivateVisibilityClass { 85 } 86 87 public void testPrivateClass_FailsWithUnsupportedOperationException() throws Exception { 88 try { 89 proxyFor(PrivateVisibilityClass.class).build(); 90 fail(); 91 } catch (UnsupportedOperationException expected) {} 92 } 93 94 protected static class ProtectedVisibilityClass { 95 public String foo() { 96 throw new AssertionFailedError(); 97 } 98 } 99 100 public void testProtectedVisibility_WorksFine() throws Exception { 101 assertEquals("fake result", proxyFor(ProtectedVisibilityClass.class).build().foo()); 102 } 103 104 public static class HasFinalMethod { 105 public String nonFinalMethod() { 106 return "non-final method"; 107 } 108 109 public final String finalMethod() { 110 return "final method"; 111 } 112 } 113 114 public void testCanProxyClassesWithFinalMethods_WillNotCallTheFinalMethod() throws Throwable { 115 HasFinalMethod proxy = proxyFor(HasFinalMethod.class).build(); 116 assertEquals("final method", proxy.finalMethod()); 117 assertEquals("fake result", proxy.nonFinalMethod()); 118 } 119 120 public static class HasPrivateMethod { 121 private String result() { 122 return "expected"; 123 } 124 } 125 126 public void testProxyingPrivateMethods_NotIntercepted() throws Throwable { 127 assertEquals("expected", proxyFor(HasPrivateMethod.class).build().result()); 128 } 129 130 public static class HasPackagePrivateMethod { 131 String result() { 132 throw new AssertionFailedError(); 133 } 134 } 135 136 public void testProxyingPackagePrivateMethods_AreIntercepted() throws Throwable { 137 assertEquals("fake result", proxyFor(HasPackagePrivateMethod.class).build().result()); 138 } 139 140 public static class HasProtectedMethod { 141 protected String result() { 142 throw new AssertionFailedError(); 143 } 144 } 145 146 public void testProxyingProtectedMethods_AreIntercepted() throws Throwable { 147 assertEquals("fake result", proxyFor(HasProtectedMethod.class).build().result()); 148 } 149 150 public static class HasVoidMethod { 151 public void dangerousMethod() { 152 fail(); 153 } 154 } 155 156 public void testVoidMethod_ShouldNotThrowRuntimeException() throws Throwable { 157 proxyFor(HasVoidMethod.class).build().dangerousMethod(); 158 } 159 160 public void testObjectMethodsAreAlsoProxied() throws Throwable { 161 Object proxy = proxyFor(Object.class).build(); 162 fakeHandler.setFakeResult("mystring"); 163 assertEquals("mystring", proxy.toString()); 164 fakeHandler.setFakeResult(-1); 165 assertEquals(-1, proxy.hashCode()); 166 fakeHandler.setFakeResult(false); 167 assertEquals(false, proxy.equals(proxy)); 168 } 169 170 public static class AllReturnTypes { 171 public boolean getBoolean() { return true; } 172 public int getInt() { return 1; } 173 public byte getByte() { return 2; } 174 public long getLong() { return 3L; } 175 public short getShort() { return 4; } 176 public float getFloat() { return 5f; } 177 public double getDouble() { return 6.0; } 178 public char getChar() { return 'c'; } 179 public int[] getIntArray() { return new int[] { 8, 9 }; } 180 public String[] getStringArray() { return new String[] { "d", "e" }; } 181 } 182 183 public void testAllReturnTypes() throws Throwable { 184 AllReturnTypes proxy = proxyFor(AllReturnTypes.class).build(); 185 fakeHandler.setFakeResult(false); 186 assertEquals(false, proxy.getBoolean()); 187 fakeHandler.setFakeResult(8); 188 assertEquals(8, proxy.getInt()); 189 fakeHandler.setFakeResult((byte) 9); 190 assertEquals(9, proxy.getByte()); 191 fakeHandler.setFakeResult(10L); 192 assertEquals(10, proxy.getLong()); 193 fakeHandler.setFakeResult((short) 11); 194 assertEquals(11, proxy.getShort()); 195 fakeHandler.setFakeResult(12f); 196 assertEquals(12f, proxy.getFloat()); 197 fakeHandler.setFakeResult(13.0); 198 assertEquals(13.0, proxy.getDouble()); 199 fakeHandler.setFakeResult('z'); 200 assertEquals('z', proxy.getChar()); 201 fakeHandler.setFakeResult(new int[] { -1, -2 }); 202 assertEquals("[-1, -2]", Arrays.toString(proxy.getIntArray())); 203 fakeHandler.setFakeResult(new String[] { "x", "y" }); 204 assertEquals("[x, y]", Arrays.toString(proxy.getStringArray())); 205 } 206 207 public static class PassThroughAllTypes { 208 public boolean getBoolean(boolean input) { return input; } 209 public int getInt(int input) { return input; } 210 public byte getByte(byte input) { return input; } 211 public long getLong(long input) { return input; } 212 public short getShort(short input) { return input; } 213 public float getFloat(float input) { return input; } 214 public double getDouble(double input) { return input; } 215 public char getChar(char input) { return input; } 216 public String getString(String input) { return input; } 217 public Object getObject(Object input) { return input; } 218 public void getNothing() {} 219 } 220 221 public static class InvokeSuperHandler implements InvocationHandler { 222 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 223 return ProxyBuilder.callSuper(proxy, method, args); 224 } 225 } 226 227 public void testPassThroughWorksForAllTypes() throws Exception { 228 PassThroughAllTypes proxy = proxyFor(PassThroughAllTypes.class) 229 .handler(new InvokeSuperHandler()) 230 .build(); 231 assertEquals(false, proxy.getBoolean(false)); 232 assertEquals(true, proxy.getBoolean(true)); 233 assertEquals(0, proxy.getInt(0)); 234 assertEquals(1, proxy.getInt(1)); 235 assertEquals((byte) 2, proxy.getByte((byte) 2)); 236 assertEquals((byte) 3, proxy.getByte((byte) 3)); 237 assertEquals(4L, proxy.getLong(4L)); 238 assertEquals(5L, proxy.getLong(5L)); 239 assertEquals((short) 6, proxy.getShort((short) 6)); 240 assertEquals((short) 7, proxy.getShort((short) 7)); 241 assertEquals(8f, proxy.getFloat(8f)); 242 assertEquals(9f, proxy.getFloat(9f)); 243 assertEquals(10.0, proxy.getDouble(10.0)); 244 assertEquals(11.0, proxy.getDouble(11.0)); 245 assertEquals('a', proxy.getChar('a')); 246 assertEquals('b', proxy.getChar('b')); 247 assertEquals("asdf", proxy.getString("asdf")); 248 assertEquals("qwer", proxy.getString("qwer")); 249 assertEquals(null, proxy.getString(null)); 250 Object a = new Object(); 251 assertEquals(a, proxy.getObject(a)); 252 assertEquals(null, proxy.getObject(null)); 253 proxy.getNothing(); 254 } 255 256 public static class ExtendsAllReturnTypes extends AllReturnTypes { 257 public int example() { return 0; } 258 } 259 260 public void testProxyWorksForSuperclassMethodsAlso() throws Throwable { 261 ExtendsAllReturnTypes proxy = proxyFor(ExtendsAllReturnTypes.class).build(); 262 fakeHandler.setFakeResult(99); 263 assertEquals(99, proxy.example()); 264 assertEquals(99, proxy.getInt()); 265 assertEquals(99, proxy.hashCode()); 266 } 267 268 public static class HasOddParams { 269 public long method(int first, Integer second) { 270 throw new AssertionFailedError(); 271 } 272 } 273 274 public void testMixingBoxedAndUnboxedParams() throws Throwable { 275 HasOddParams proxy = proxyFor(HasOddParams.class).build(); 276 fakeHandler.setFakeResult(99L); 277 assertEquals(99L, proxy.method(1, Integer.valueOf(2))); 278 } 279 280 public static class SingleInt { 281 public String getString(int value) { 282 throw new AssertionFailedError(); 283 } 284 } 285 286 public void testSinglePrimitiveParameter() throws Throwable { 287 InvocationHandler handler = new InvocationHandler() { 288 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 289 return "asdf" + ((Integer) args[0]).intValue(); 290 } 291 }; 292 assertEquals("asdf1", proxyFor(SingleInt.class).handler(handler).build().getString(1)); 293 } 294 295 public static class TwoConstructors { 296 private final String string; 297 298 public TwoConstructors() { 299 string = "no-arg"; 300 } 301 302 public TwoConstructors(boolean unused) { 303 string = "one-arg"; 304 } 305 } 306 307 public void testNoConstructorArguments_CallsNoArgConstructor() throws Throwable { 308 TwoConstructors twoConstructors = proxyFor(TwoConstructors.class).build(); 309 assertEquals("no-arg", twoConstructors.string); 310 } 311 312 public void testWithoutInvocationHandler_ThrowsIllegalArgumentException() throws Throwable { 313 try { 314 ProxyBuilder.forClass(TwoConstructors.class) 315 .dexCache(DexMakerTest.getDataDirectory()) 316 .build(); 317 fail(); 318 } catch (IllegalArgumentException expected) {} 319 } 320 321 public static class HardToConstructCorrectly { 322 public HardToConstructCorrectly() { fail(); } 323 public HardToConstructCorrectly(Runnable ignored) { fail(); } 324 public HardToConstructCorrectly(Exception ignored) { fail(); } 325 public HardToConstructCorrectly(Boolean ignored) { /* safe */ } 326 public HardToConstructCorrectly(Integer ignored) { fail(); } 327 } 328 329 public void testHardToConstruct_WorksIfYouSpecifyTheConstructorCorrectly() throws Throwable { 330 proxyFor(HardToConstructCorrectly.class) 331 .constructorArgTypes(Boolean.class) 332 .constructorArgValues(true) 333 .build(); 334 } 335 336 public void testHardToConstruct_EvenWorksWhenArgsAreAmbiguous() throws Throwable { 337 proxyFor(HardToConstructCorrectly.class) 338 .constructorArgTypes(Boolean.class) 339 .constructorArgValues(new Object[] { null }) 340 .build(); 341 } 342 343 public void testHardToConstruct_DoesNotInferTypesFromValues() throws Throwable { 344 try { 345 proxyFor(HardToConstructCorrectly.class) 346 .constructorArgValues(true) 347 .build(); 348 fail(); 349 } catch (IllegalArgumentException expected) {} 350 } 351 352 public void testDefaultProxyHasSuperMethodToAccessOriginal() throws Exception { 353 Object objectProxy = proxyFor(Object.class).build(); 354 assertNotNull(objectProxy.getClass().getMethod("super$hashCode$int")); 355 } 356 357 public static class PrintsOddAndValue { 358 public String method(int value) { 359 return "odd " + value; 360 } 361 } 362 363 public void testSometimesDelegateToSuper() throws Exception { 364 InvocationHandler delegatesOddValues = new InvocationHandler() { 365 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 366 if (method.getName().equals("method")) { 367 int intValue = ((Integer) args[0]).intValue(); 368 if (intValue % 2 == 0) { 369 return "even " + intValue; 370 } 371 } 372 return ProxyBuilder.callSuper(proxy, method, args); 373 } 374 }; 375 PrintsOddAndValue proxy = proxyFor(PrintsOddAndValue.class) 376 .handler(delegatesOddValues) 377 .build(); 378 assertEquals("even 0", proxy.method(0)); 379 assertEquals("odd 1", proxy.method(1)); 380 assertEquals("even 2", proxy.method(2)); 381 assertEquals("odd 3", proxy.method(3)); 382 } 383 384 public void testCallSuperThrows() throws Exception { 385 InvocationHandler handler = new InvocationHandler() { 386 public Object invoke(Object o, Method method, Object[] objects) throws Throwable { 387 return ProxyBuilder.callSuper(o, method, objects); 388 } 389 }; 390 391 FooThrows fooThrows = proxyFor(FooThrows.class) 392 .handler(handler) 393 .build(); 394 395 try { 396 fooThrows.foo(); 397 fail(); 398 } catch (IllegalStateException expected) { 399 assertEquals("boom!", expected.getMessage()); 400 } 401 } 402 403 public static class FooThrows { 404 public void foo() { 405 throw new IllegalStateException("boom!"); 406 } 407 } 408 409 public static class DoubleReturn { 410 public double getValue() { 411 return 2.0; 412 } 413 } 414 415 public void testUnboxedResult() throws Exception { 416 fakeHandler.fakeResult = 2.0; 417 assertEquals(2.0, proxyFor(DoubleReturn.class).build().getValue()); 418 } 419 420 public static void staticMethod() { 421 } 422 423 public void testDoesNotOverrideStaticMethods() throws Exception { 424 // Method should exist on this test class itself. 425 ProxyBuilderTest.class.getDeclaredMethod("staticMethod"); 426 // Method should not exist on the subclass. 427 try { 428 proxyFor(ProxyBuilderTest.class).build().getClass().getDeclaredMethod("staticMethod"); 429 fail(); 430 } catch (NoSuchMethodException expected) {} 431 } 432 433 public void testIllegalCacheDirectory() throws Exception { 434 try { 435 proxyFor(ProxyForIllegalCacheDirectory.class) 436 .dexCache(new File("/poop/")) 437 .build(); 438 fail(); 439 } catch (IOException expected) { 440 } 441 } 442 443 public static class ProxyForIllegalCacheDirectory { 444 } 445 446 public void testInvalidConstructorSpecification() throws Exception { 447 try { 448 proxyFor(Object.class) 449 .constructorArgTypes(String.class, Boolean.class) 450 .constructorArgValues("asdf", true) 451 .build(); 452 fail(); 453 } catch (IllegalArgumentException expected) {} 454 } 455 456 public static abstract class AbstractClass { 457 public abstract Object getValue(); 458 } 459 460 public void testAbstractClassBehaviour() throws Exception { 461 assertEquals("fake result", proxyFor(AbstractClass.class).build().getValue()); 462 } 463 464 public static class CtorHasDeclaredException { 465 public CtorHasDeclaredException() throws IOException { 466 throw new IOException(); 467 } 468 } 469 470 public static class CtorHasRuntimeException { 471 public CtorHasRuntimeException() { 472 throw new RuntimeException("my message"); 473 } 474 } 475 476 public static class CtorHasError { 477 public CtorHasError() { 478 throw new Error("my message again"); 479 } 480 } 481 482 public void testParentConstructorThrowsDeclaredException() throws Exception { 483 try { 484 proxyFor(CtorHasDeclaredException.class).build(); 485 fail(); 486 } catch (UndeclaredThrowableException expected) { 487 assertTrue(expected.getCause() instanceof IOException); 488 } 489 try { 490 proxyFor(CtorHasRuntimeException.class).build(); 491 fail(); 492 } catch (RuntimeException expected) { 493 assertEquals("my message", expected.getMessage()); 494 } 495 try { 496 proxyFor(CtorHasError.class).build(); 497 fail(); 498 } catch (Error expected) { 499 assertEquals("my message again", expected.getMessage()); 500 } 501 } 502 503 public void testGetInvocationHandler_NormalOperation() throws Exception { 504 Object proxy = proxyFor(Object.class).build(); 505 assertSame(fakeHandler, ProxyBuilder.getInvocationHandler(proxy)); 506 } 507 508 public void testGetInvocationHandler_NotAProxy() { 509 try { 510 ProxyBuilder.getInvocationHandler(new Object()); 511 fail(); 512 } catch (IllegalArgumentException expected) {} 513 } 514 515 public static class ReturnsObject { 516 public Object getValue() { 517 return new Object(); 518 } 519 } 520 521 public static class ReturnsString extends ReturnsObject { 522 @Override 523 public String getValue() { 524 return "a string"; 525 } 526 } 527 528 public void testCovariantReturnTypes_NormalBehaviour() throws Exception { 529 String expected = "some string"; 530 fakeHandler.setFakeResult(expected); 531 assertSame(expected, proxyFor(ReturnsObject.class).build().getValue()); 532 assertSame(expected, proxyFor(ReturnsString.class).build().getValue()); 533 } 534 535 public void testCovariantReturnTypes_WrongReturnType() throws Exception { 536 try { 537 fakeHandler.setFakeResult(new Object()); 538 proxyFor(ReturnsString.class).build().getValue(); 539 fail(); 540 } catch (ClassCastException expected) {} 541 } 542 543 public void testCaching() throws Exception { 544 SimpleClass a = proxyFor(SimpleClass.class).build(); 545 SimpleClass b = proxyFor(SimpleClass.class).build(); 546 assertSame(a.getClass(), b.getClass()); 547 } 548 549 public void testCachingWithMultipleConstructors() throws Exception { 550 HasMultipleConstructors a = ProxyBuilder.forClass(HasMultipleConstructors.class) 551 .constructorArgTypes() 552 .constructorArgValues() 553 .handler(fakeHandler) 554 .dexCache(DexMakerTest.getDataDirectory()).build(); 555 assertEquals("no args", a.calledConstructor); 556 HasMultipleConstructors b = ProxyBuilder.forClass(HasMultipleConstructors.class) 557 .constructorArgTypes(int.class) 558 .constructorArgValues(2) 559 .handler(fakeHandler) 560 .dexCache(DexMakerTest.getDataDirectory()).build(); 561 assertEquals("int 2", b.calledConstructor); 562 assertEquals(a.getClass(), b.getClass()); 563 564 HasMultipleConstructors c = ProxyBuilder.forClass(HasMultipleConstructors.class) 565 .constructorArgTypes(Integer.class) 566 .constructorArgValues(3) 567 .handler(fakeHandler) 568 .dexCache(DexMakerTest.getDataDirectory()).build(); 569 assertEquals("Integer 3", c.calledConstructor); 570 assertEquals(a.getClass(), c.getClass()); 571 } 572 573 public static class HasMultipleConstructors { 574 private final String calledConstructor; 575 public HasMultipleConstructors() { 576 calledConstructor = "no args"; 577 } 578 public HasMultipleConstructors(int b) { 579 calledConstructor = "int " + b; 580 } 581 public HasMultipleConstructors(Integer c) { 582 calledConstructor = "Integer " + c; 583 } 584 } 585 586 public void testClassNotCachedWithDifferentParentClassLoaders() throws Exception { 587 ClassLoader classLoaderA = newPathClassLoader(); 588 SimpleClass a = proxyFor(SimpleClass.class) 589 .parentClassLoader(classLoaderA) 590 .build(); 591 assertEquals(classLoaderA, a.getClass().getClassLoader().getParent()); 592 593 ClassLoader classLoaderB = newPathClassLoader(); 594 SimpleClass b = proxyFor(SimpleClass.class) 595 .parentClassLoader(classLoaderB) 596 .build(); 597 assertEquals(classLoaderB, b.getClass().getClassLoader().getParent()); 598 599 assertTrue(a.getClass() != b.getClass()); 600 } 601 602 public void testAbstractClassWithUndeclaredInterfaceMethod() throws Throwable { 603 DeclaresInterface declaresInterface = proxyFor(DeclaresInterface.class) 604 .build(); 605 assertEquals("fake result", declaresInterface.call()); 606 try { 607 ProxyBuilder.callSuper(declaresInterface, Callable.class.getMethod("call")); 608 fail(); 609 } catch (AbstractMethodError expected) { 610 } 611 } 612 613 public static abstract class DeclaresInterface implements Callable<String> { 614 } 615 616 public void testImplementingInterfaces() throws Throwable { 617 SimpleClass simpleClass = proxyFor(SimpleClass.class) 618 .implementing(Callable.class) 619 .implementing(Comparable.class) 620 .build(); 621 assertEquals("fake result", simpleClass.simpleMethod()); 622 623 Callable<?> asCallable = (Callable<?>) simpleClass; 624 assertEquals("fake result", asCallable.call()); 625 626 Comparable<?> asComparable = (Comparable<?>) simpleClass; 627 fakeHandler.fakeResult = 3; 628 assertEquals(3, asComparable.compareTo(null)); 629 } 630 631 public void testCallSuperWithInterfaceMethod() throws Throwable { 632 SimpleClass simpleClass = proxyFor(SimpleClass.class) 633 .implementing(Callable.class) 634 .build(); 635 try { 636 ProxyBuilder.callSuper(simpleClass, Callable.class.getMethod("call")); 637 fail(); 638 } catch (AbstractMethodError expected) { 639 } catch (NoSuchMethodError expected) { 640 } 641 } 642 643 public void testImplementInterfaceCallingThroughConcreteClass() throws Throwable { 644 InvocationHandler invocationHandler = new InvocationHandler() { 645 public Object invoke(Object o, Method method, Object[] objects) throws Throwable { 646 assertEquals("a", ProxyBuilder.callSuper(o, method, objects)); 647 return "b"; 648 } 649 }; 650 ImplementsCallable proxy = proxyFor(ImplementsCallable.class) 651 .implementing(Callable.class) 652 .handler(invocationHandler) 653 .build(); 654 assertEquals("b", proxy.call()); 655 assertEquals("a", ProxyBuilder.callSuper( 656 proxy, ImplementsCallable.class.getMethod("call"))); 657 } 658 659 /** 660 * This test is a bit unintuitive because it exercises the synthetic methods 661 * that support covariant return types. Calling 'Object call()' on the 662 * interface bridges to 'String call()', and so the super method appears to 663 * also be proxied. 664 */ 665 public void testImplementInterfaceCallingThroughInterface() throws Throwable { 666 final AtomicInteger count = new AtomicInteger(); 667 668 InvocationHandler invocationHandler = new InvocationHandler() { 669 public Object invoke(Object o, Method method, Object[] objects) throws Throwable { 670 count.incrementAndGet(); 671 return ProxyBuilder.callSuper(o, method, objects); 672 } 673 }; 674 675 Callable<?> proxy = proxyFor(ImplementsCallable.class) 676 .implementing(Callable.class) 677 .handler(invocationHandler) 678 .build(); 679 680 // the invocation handler is called twice! 681 assertEquals("a", proxy.call()); 682 assertEquals(2, count.get()); 683 684 // the invocation handler is called, even though this is a callSuper() call! 685 assertEquals("a", ProxyBuilder.callSuper(proxy, Callable.class.getMethod("call"))); 686 assertEquals(3, count.get()); 687 } 688 689 public static class ImplementsCallable implements Callable<String> { 690 public String call() throws Exception { 691 return "a"; 692 } 693 } 694 695 /** 696 * This test shows that our generated proxies follow the bytecode convention 697 * where methods can have the same name but unrelated return types. This is 698 * different from javac's convention where return types must be assignable 699 * in one direction or the other. 700 */ 701 public void testInterfacesSameNamesDifferentReturnTypes() throws Throwable { 702 InvocationHandler handler = new InvocationHandler() { 703 public Object invoke(Object o, Method method, Object[] objects) throws Throwable { 704 if (method.getReturnType() == void.class) { 705 return null; 706 } else if (method.getReturnType() == String.class) { 707 return "X"; 708 } else if (method.getReturnType() == int.class) { 709 return 3; 710 } else { 711 throw new AssertionFailedError(); 712 } 713 } 714 }; 715 716 Object o = proxyFor(Object.class) 717 .implementing(FooReturnsVoid.class, FooReturnsString.class, FooReturnsInt.class) 718 .handler(handler) 719 .build(); 720 721 FooReturnsVoid a = (FooReturnsVoid) o; 722 a.foo(); 723 724 FooReturnsString b = (FooReturnsString) o; 725 assertEquals("X", b.foo()); 726 727 FooReturnsInt c = (FooReturnsInt) o; 728 assertEquals(3, c.foo()); 729 } 730 731 public void testInterfacesSameNamesSameReturnType() throws Throwable { 732 Object o = proxyFor(Object.class) 733 .implementing(FooReturnsInt.class, FooReturnsInt2.class) 734 .build(); 735 736 fakeHandler.setFakeResult(3); 737 738 FooReturnsInt a = (FooReturnsInt) o; 739 assertEquals(3, a.foo()); 740 741 FooReturnsInt2 b = (FooReturnsInt2) o; 742 assertEquals(3, b.foo()); 743 } 744 745 public interface FooReturnsVoid { 746 void foo(); 747 } 748 749 public interface FooReturnsString { 750 String foo(); 751 } 752 753 public interface FooReturnsInt { 754 int foo(); 755 } 756 757 public interface FooReturnsInt2 { 758 int foo(); 759 } 760 761 private ClassLoader newPathClassLoader() throws Exception { 762 return (ClassLoader) Class.forName("dalvik.system.PathClassLoader") 763 .getConstructor(String.class, ClassLoader.class) 764 .newInstance("", getClass().getClassLoader()); 765 766 } 767 768 public void testSubclassOfRandom() throws Exception { 769 proxyFor(Random.class) 770 .handler(new InvokeSuperHandler()) 771 .build(); 772 } 773 774 public static class FinalToString { 775 @Override public final String toString() { 776 return "no proxy"; 777 } 778 } 779 780 // https://code.google.com/p/dexmaker/issues/detail?id=12 781 public void testFinalToString() throws Throwable { 782 assertEquals("no proxy", proxyFor(FinalToString.class).build().toString()); 783 } 784 785 /** Simple helper to add the most common args for this test to the proxy builder. */ 786 private <T> ProxyBuilder<T> proxyFor(Class<T> clazz) throws Exception { 787 return ProxyBuilder.forClass(clazz) 788 .handler(fakeHandler) 789 .dexCache(DexMakerTest.getDataDirectory()); 790 } 791 792 private static class FakeInvocationHandler implements InvocationHandler { 793 private Object fakeResult = "fake result"; 794 795 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 796 return fakeResult; 797 } 798 799 public void setFakeResult(Object result) { 800 fakeResult = result; 801 } 802 } 803 } 804