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 17package com.google.dexmaker.stock; 18 19import com.google.dexmaker.DexMakerTest; 20import java.io.File; 21import java.io.IOException; 22import java.lang.reflect.Field; 23import java.lang.reflect.InvocationHandler; 24import java.lang.reflect.Method; 25import java.lang.reflect.UndeclaredThrowableException; 26import java.util.Arrays; 27import java.util.Map; 28import java.util.Random; 29import java.util.concurrent.Callable; 30import java.util.concurrent.atomic.AtomicInteger; 31import junit.framework.AssertionFailedError; 32import junit.framework.TestCase; 33 34public class ProxyBuilderTest extends TestCase { 35 private FakeInvocationHandler fakeHandler = new FakeInvocationHandler(); 36 private File versionedDxDir = new File(DexMakerTest.getDataDirectory(), "v1"); 37 38 public void setUp() throws Exception { 39 super.setUp(); 40 versionedDxDir.mkdirs(); 41 clearVersionedDxDir(); 42 getGeneratedProxyClasses().clear(); 43 } 44 45 public void tearDown() throws Exception { 46 getGeneratedProxyClasses().clear(); 47 clearVersionedDxDir(); 48 super.tearDown(); 49 } 50 51 private void clearVersionedDxDir() { 52 for (File f : versionedDxDir.listFiles()) { 53 f.delete(); 54 } 55 } 56 57 public static class SimpleClass { 58 public String simpleMethod() { 59 throw new AssertionFailedError(); 60 } 61 } 62 63 public void testExampleOperation() throws Throwable { 64 fakeHandler.setFakeResult("expected"); 65 SimpleClass proxy = proxyFor(SimpleClass.class).build(); 66 assertEquals("expected", proxy.simpleMethod()); 67 assertEquals(2, versionedDxDir.listFiles().length); 68 } 69 70 public void testExampleOperation_DexMakerCaching() throws Throwable { 71 fakeHandler.setFakeResult("expected"); 72 SimpleClass proxy = proxyFor(SimpleClass.class).build(); 73 assertEquals(2, versionedDxDir.listFiles().length); 74 assertEquals("expected", proxy.simpleMethod()); 75 76 // Force ProxyBuilder to create a DexMaker generator and call DexMaker.generateAndLoad(). 77 getGeneratedProxyClasses().clear(); 78 79 proxy = proxyFor(SimpleClass.class).build(); 80 assertEquals(2, versionedDxDir.listFiles().length); 81 assertEquals("expected", proxy.simpleMethod()); 82 } 83 84 public static class ConstructorTakesArguments { 85 private final String argument; 86 87 public ConstructorTakesArguments(String arg) { 88 argument = arg; 89 } 90 91 public String method() { 92 throw new AssertionFailedError(); 93 } 94 } 95 96 public void testConstruction_SucceedsIfCorrectArgumentsProvided() throws Throwable { 97 ConstructorTakesArguments proxy = proxyFor(ConstructorTakesArguments.class) 98 .constructorArgTypes(String.class) 99 .constructorArgValues("hello") 100 .build(); 101 assertEquals("hello", proxy.argument); 102 proxy.method(); 103 } 104 105 public void testConstruction_FailsWithWrongNumberOfArguments() throws Throwable { 106 try { 107 proxyFor(ConstructorTakesArguments.class).build(); 108 fail(); 109 } catch (IllegalArgumentException expected) {} 110 } 111 112 public void testClassIsNotAccessbile_FailsWithUnsupportedOperationException() throws Exception { 113 class MethodVisibilityClass { 114 } 115 try { 116 proxyFor(MethodVisibilityClass.class).build(); 117 fail(); 118 } catch (UnsupportedOperationException expected) {} 119 } 120 121 private static class PrivateVisibilityClass { 122 } 123 124 public void testPrivateClass_FailsWithUnsupportedOperationException() throws Exception { 125 try { 126 proxyFor(PrivateVisibilityClass.class).build(); 127 fail(); 128 } catch (UnsupportedOperationException expected) {} 129 } 130 131 protected static class ProtectedVisibilityClass { 132 public String foo() { 133 throw new AssertionFailedError(); 134 } 135 } 136 137 public void testProtectedVisibility_WorksFine() throws Exception { 138 assertEquals("fake result", proxyFor(ProtectedVisibilityClass.class).build().foo()); 139 } 140 141 public static class HasFinalMethod { 142 public String nonFinalMethod() { 143 return "non-final method"; 144 } 145 146 public final String finalMethod() { 147 return "final method"; 148 } 149 } 150 151 public void testCanProxyClassesWithFinalMethods_WillNotCallTheFinalMethod() throws Throwable { 152 HasFinalMethod proxy = proxyFor(HasFinalMethod.class).build(); 153 assertEquals("final method", proxy.finalMethod()); 154 assertEquals("fake result", proxy.nonFinalMethod()); 155 } 156 157 public static class HasPrivateMethod { 158 private String result() { 159 return "expected"; 160 } 161 } 162 163 public void testProxyingPrivateMethods_NotIntercepted() throws Throwable { 164 assertEquals("expected", proxyFor(HasPrivateMethod.class).build().result()); 165 } 166 167 public static class HasPackagePrivateMethod { 168 String result() { 169 throw new AssertionFailedError(); 170 } 171 } 172 173 public void testProxyingPackagePrivateMethods_AreIntercepted() throws Throwable { 174 assertEquals("fake result", proxyFor(HasPackagePrivateMethod.class).build().result()); 175 } 176 177 public static class HasProtectedMethod { 178 protected String result() { 179 throw new AssertionFailedError(); 180 } 181 } 182 183 public void testProxyingProtectedMethods_AreIntercepted() throws Throwable { 184 assertEquals("fake result", proxyFor(HasProtectedMethod.class).build().result()); 185 } 186 187 public static class HasVoidMethod { 188 public void dangerousMethod() { 189 fail(); 190 } 191 } 192 193 public void testVoidMethod_ShouldNotThrowRuntimeException() throws Throwable { 194 proxyFor(HasVoidMethod.class).build().dangerousMethod(); 195 } 196 197 public void testObjectMethodsAreAlsoProxied() throws Throwable { 198 Object proxy = proxyFor(Object.class).build(); 199 fakeHandler.setFakeResult("mystring"); 200 assertEquals("mystring", proxy.toString()); 201 fakeHandler.setFakeResult(-1); 202 assertEquals(-1, proxy.hashCode()); 203 fakeHandler.setFakeResult(false); 204 assertEquals(false, proxy.equals(proxy)); 205 } 206 207 public static class AllReturnTypes { 208 public boolean getBoolean() { return true; } 209 public int getInt() { return 1; } 210 public byte getByte() { return 2; } 211 public long getLong() { return 3L; } 212 public short getShort() { return 4; } 213 public float getFloat() { return 5f; } 214 public double getDouble() { return 6.0; } 215 public char getChar() { return 'c'; } 216 public int[] getIntArray() { return new int[] { 8, 9 }; } 217 public String[] getStringArray() { return new String[] { "d", "e" }; } 218 } 219 220 public void testAllReturnTypes() throws Throwable { 221 AllReturnTypes proxy = proxyFor(AllReturnTypes.class).build(); 222 fakeHandler.setFakeResult(false); 223 assertEquals(false, proxy.getBoolean()); 224 fakeHandler.setFakeResult(8); 225 assertEquals(8, proxy.getInt()); 226 fakeHandler.setFakeResult((byte) 9); 227 assertEquals(9, proxy.getByte()); 228 fakeHandler.setFakeResult(10L); 229 assertEquals(10, proxy.getLong()); 230 fakeHandler.setFakeResult((short) 11); 231 assertEquals(11, proxy.getShort()); 232 fakeHandler.setFakeResult(12f); 233 assertEquals(12f, proxy.getFloat()); 234 fakeHandler.setFakeResult(13.0); 235 assertEquals(13.0, proxy.getDouble()); 236 fakeHandler.setFakeResult('z'); 237 assertEquals('z', proxy.getChar()); 238 fakeHandler.setFakeResult(new int[] { -1, -2 }); 239 assertEquals("[-1, -2]", Arrays.toString(proxy.getIntArray())); 240 fakeHandler.setFakeResult(new String[] { "x", "y" }); 241 assertEquals("[x, y]", Arrays.toString(proxy.getStringArray())); 242 } 243 244 public static class PassThroughAllTypes { 245 public boolean getBoolean(boolean input) { return input; } 246 public int getInt(int input) { return input; } 247 public byte getByte(byte input) { return input; } 248 public long getLong(long input) { return input; } 249 public short getShort(short input) { return input; } 250 public float getFloat(float input) { return input; } 251 public double getDouble(double input) { return input; } 252 public char getChar(char input) { return input; } 253 public String getString(String input) { return input; } 254 public Object getObject(Object input) { return input; } 255 public void getNothing() {} 256 } 257 258 public static class InvokeSuperHandler implements InvocationHandler { 259 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 260 return ProxyBuilder.callSuper(proxy, method, args); 261 } 262 } 263 264 public void testPassThroughWorksForAllTypes() throws Exception { 265 PassThroughAllTypes proxy = proxyFor(PassThroughAllTypes.class) 266 .handler(new InvokeSuperHandler()) 267 .build(); 268 assertEquals(false, proxy.getBoolean(false)); 269 assertEquals(true, proxy.getBoolean(true)); 270 assertEquals(0, proxy.getInt(0)); 271 assertEquals(1, proxy.getInt(1)); 272 assertEquals((byte) 2, proxy.getByte((byte) 2)); 273 assertEquals((byte) 3, proxy.getByte((byte) 3)); 274 assertEquals(4L, proxy.getLong(4L)); 275 assertEquals(5L, proxy.getLong(5L)); 276 assertEquals((short) 6, proxy.getShort((short) 6)); 277 assertEquals((short) 7, proxy.getShort((short) 7)); 278 assertEquals(8f, proxy.getFloat(8f)); 279 assertEquals(9f, proxy.getFloat(9f)); 280 assertEquals(10.0, proxy.getDouble(10.0)); 281 assertEquals(11.0, proxy.getDouble(11.0)); 282 assertEquals('a', proxy.getChar('a')); 283 assertEquals('b', proxy.getChar('b')); 284 assertEquals("asdf", proxy.getString("asdf")); 285 assertEquals("qwer", proxy.getString("qwer")); 286 assertEquals(null, proxy.getString(null)); 287 Object a = new Object(); 288 assertEquals(a, proxy.getObject(a)); 289 assertEquals(null, proxy.getObject(null)); 290 proxy.getNothing(); 291 } 292 293 public static class ExtendsAllReturnTypes extends AllReturnTypes { 294 public int example() { return 0; } 295 } 296 297 public void testProxyWorksForSuperclassMethodsAlso() throws Throwable { 298 ExtendsAllReturnTypes proxy = proxyFor(ExtendsAllReturnTypes.class).build(); 299 fakeHandler.setFakeResult(99); 300 assertEquals(99, proxy.example()); 301 assertEquals(99, proxy.getInt()); 302 assertEquals(99, proxy.hashCode()); 303 } 304 305 public static class HasOddParams { 306 public long method(int first, Integer second) { 307 throw new AssertionFailedError(); 308 } 309 } 310 311 public void testMixingBoxedAndUnboxedParams() throws Throwable { 312 HasOddParams proxy = proxyFor(HasOddParams.class).build(); 313 fakeHandler.setFakeResult(99L); 314 assertEquals(99L, proxy.method(1, Integer.valueOf(2))); 315 } 316 317 public static class SingleInt { 318 public String getString(int value) { 319 throw new AssertionFailedError(); 320 } 321 } 322 323 public void testSinglePrimitiveParameter() throws Throwable { 324 InvocationHandler handler = new InvocationHandler() { 325 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 326 return "asdf" + ((Integer) args[0]).intValue(); 327 } 328 }; 329 assertEquals("asdf1", proxyFor(SingleInt.class).handler(handler).build().getString(1)); 330 } 331 332 public static class TwoConstructors { 333 private final String string; 334 335 public TwoConstructors() { 336 string = "no-arg"; 337 } 338 339 public TwoConstructors(boolean unused) { 340 string = "one-arg"; 341 } 342 } 343 344 public void testNoConstructorArguments_CallsNoArgConstructor() throws Throwable { 345 TwoConstructors twoConstructors = proxyFor(TwoConstructors.class).build(); 346 assertEquals("no-arg", twoConstructors.string); 347 } 348 349 public void testWithoutInvocationHandler_ThrowsIllegalArgumentException() throws Throwable { 350 try { 351 ProxyBuilder.forClass(TwoConstructors.class) 352 .dexCache(DexMakerTest.getDataDirectory()) 353 .build(); 354 fail(); 355 } catch (IllegalArgumentException expected) {} 356 } 357 358 public static class HardToConstructCorrectly { 359 public HardToConstructCorrectly() { fail(); } 360 public HardToConstructCorrectly(Runnable ignored) { fail(); } 361 public HardToConstructCorrectly(Exception ignored) { fail(); } 362 public HardToConstructCorrectly(Boolean ignored) { /* safe */ } 363 public HardToConstructCorrectly(Integer ignored) { fail(); } 364 } 365 366 public void testHardToConstruct_WorksIfYouSpecifyTheConstructorCorrectly() throws Throwable { 367 proxyFor(HardToConstructCorrectly.class) 368 .constructorArgTypes(Boolean.class) 369 .constructorArgValues(true) 370 .build(); 371 } 372 373 public void testHardToConstruct_EvenWorksWhenArgsAreAmbiguous() throws Throwable { 374 proxyFor(HardToConstructCorrectly.class) 375 .constructorArgTypes(Boolean.class) 376 .constructorArgValues(new Object[] { null }) 377 .build(); 378 } 379 380 public void testHardToConstruct_DoesNotInferTypesFromValues() throws Throwable { 381 try { 382 proxyFor(HardToConstructCorrectly.class) 383 .constructorArgValues(true) 384 .build(); 385 fail(); 386 } catch (IllegalArgumentException expected) {} 387 } 388 389 public void testDefaultProxyHasSuperMethodToAccessOriginal() throws Exception { 390 Object objectProxy = proxyFor(Object.class).build(); 391 assertNotNull(objectProxy.getClass().getMethod("super$hashCode$int")); 392 } 393 394 public static class PrintsOddAndValue { 395 public String method(int value) { 396 return "odd " + value; 397 } 398 } 399 400 public void testSometimesDelegateToSuper() throws Exception { 401 InvocationHandler delegatesOddValues = new InvocationHandler() { 402 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 403 if (method.getName().equals("method")) { 404 int intValue = ((Integer) args[0]).intValue(); 405 if (intValue % 2 == 0) { 406 return "even " + intValue; 407 } 408 } 409 return ProxyBuilder.callSuper(proxy, method, args); 410 } 411 }; 412 PrintsOddAndValue proxy = proxyFor(PrintsOddAndValue.class) 413 .handler(delegatesOddValues) 414 .build(); 415 assertEquals("even 0", proxy.method(0)); 416 assertEquals("odd 1", proxy.method(1)); 417 assertEquals("even 2", proxy.method(2)); 418 assertEquals("odd 3", proxy.method(3)); 419 } 420 421 public void testCallSuperThrows() throws Exception { 422 InvocationHandler handler = new InvocationHandler() { 423 public Object invoke(Object o, Method method, Object[] objects) throws Throwable { 424 return ProxyBuilder.callSuper(o, method, objects); 425 } 426 }; 427 428 FooThrows fooThrows = proxyFor(FooThrows.class) 429 .handler(handler) 430 .build(); 431 432 try { 433 fooThrows.foo(); 434 fail(); 435 } catch (IllegalStateException expected) { 436 assertEquals("boom!", expected.getMessage()); 437 } 438 } 439 440 public static class FooThrows { 441 public void foo() { 442 throw new IllegalStateException("boom!"); 443 } 444 } 445 446 public static class DoubleReturn { 447 public double getValue() { 448 return 2.0; 449 } 450 } 451 452 public void testUnboxedResult() throws Exception { 453 fakeHandler.fakeResult = 2.0; 454 assertEquals(2.0, proxyFor(DoubleReturn.class).build().getValue()); 455 } 456 457 public static void staticMethod() { 458 } 459 460 public void testDoesNotOverrideStaticMethods() throws Exception { 461 // Method should exist on this test class itself. 462 ProxyBuilderTest.class.getDeclaredMethod("staticMethod"); 463 // Method should not exist on the subclass. 464 try { 465 proxyFor(ProxyBuilderTest.class).build().getClass().getDeclaredMethod("staticMethod"); 466 fail(); 467 } catch (NoSuchMethodException expected) {} 468 } 469 470 public void testIllegalCacheDirectory() throws Exception { 471 try { 472 proxyFor(ProxyForIllegalCacheDirectory.class) 473 .dexCache(new File("/poop/")) 474 .build(); 475 fail(); 476 } catch (IOException expected) { 477 } 478 } 479 480 public static class ProxyForIllegalCacheDirectory { 481 } 482 483 public void testInvalidConstructorSpecification() throws Exception { 484 try { 485 proxyFor(Object.class) 486 .constructorArgTypes(String.class, Boolean.class) 487 .constructorArgValues("asdf", true) 488 .build(); 489 fail(); 490 } catch (IllegalArgumentException expected) {} 491 } 492 493 public static abstract class AbstractClass { 494 public abstract Object getValue(); 495 } 496 497 public void testAbstractClassBehaviour() throws Exception { 498 assertEquals("fake result", proxyFor(AbstractClass.class).build().getValue()); 499 } 500 501 public static class CtorHasDeclaredException { 502 public CtorHasDeclaredException() throws IOException { 503 throw new IOException(); 504 } 505 } 506 507 public static class CtorHasRuntimeException { 508 public CtorHasRuntimeException() { 509 throw new RuntimeException("my message"); 510 } 511 } 512 513 public static class CtorHasError { 514 public CtorHasError() { 515 throw new Error("my message again"); 516 } 517 } 518 519 public void testParentConstructorThrowsDeclaredException() throws Exception { 520 try { 521 proxyFor(CtorHasDeclaredException.class).build(); 522 fail(); 523 } catch (UndeclaredThrowableException expected) { 524 assertTrue(expected.getCause() instanceof IOException); 525 } 526 try { 527 proxyFor(CtorHasRuntimeException.class).build(); 528 fail(); 529 } catch (RuntimeException expected) { 530 assertEquals("my message", expected.getMessage()); 531 } 532 try { 533 proxyFor(CtorHasError.class).build(); 534 fail(); 535 } catch (Error expected) { 536 assertEquals("my message again", expected.getMessage()); 537 } 538 } 539 540 public void testGetInvocationHandler_NormalOperation() throws Exception { 541 Object proxy = proxyFor(Object.class).build(); 542 assertSame(fakeHandler, ProxyBuilder.getInvocationHandler(proxy)); 543 } 544 545 public void testGetInvocationHandler_NotAProxy() { 546 try { 547 ProxyBuilder.getInvocationHandler(new Object()); 548 fail(); 549 } catch (IllegalArgumentException expected) {} 550 } 551 552 public static class ReturnsObject { 553 public Object getValue() { 554 return new Object(); 555 } 556 } 557 558 public static class ReturnsString extends ReturnsObject { 559 @Override 560 public String getValue() { 561 return "a string"; 562 } 563 } 564 565 public void testCovariantReturnTypes_NormalBehaviour() throws Exception { 566 String expected = "some string"; 567 fakeHandler.setFakeResult(expected); 568 assertSame(expected, proxyFor(ReturnsObject.class).build().getValue()); 569 assertSame(expected, proxyFor(ReturnsString.class).build().getValue()); 570 } 571 572 public void testCovariantReturnTypes_WrongReturnType() throws Exception { 573 try { 574 fakeHandler.setFakeResult(new Object()); 575 proxyFor(ReturnsString.class).build().getValue(); 576 fail(); 577 } catch (ClassCastException expected) {} 578 } 579 580 public void testCaching() throws Exception { 581 SimpleClass a = proxyFor(SimpleClass.class).build(); 582 SimpleClass b = proxyFor(SimpleClass.class).build(); 583 assertSame(a.getClass(), b.getClass()); 584 } 585 586 public void testCachingWithMultipleConstructors() throws Exception { 587 HasMultipleConstructors a = ProxyBuilder.forClass(HasMultipleConstructors.class) 588 .constructorArgTypes() 589 .constructorArgValues() 590 .handler(fakeHandler) 591 .dexCache(DexMakerTest.getDataDirectory()).build(); 592 assertEquals("no args", a.calledConstructor); 593 HasMultipleConstructors b = ProxyBuilder.forClass(HasMultipleConstructors.class) 594 .constructorArgTypes(int.class) 595 .constructorArgValues(2) 596 .handler(fakeHandler) 597 .dexCache(DexMakerTest.getDataDirectory()).build(); 598 assertEquals("int 2", b.calledConstructor); 599 assertEquals(a.getClass(), b.getClass()); 600 601 HasMultipleConstructors c = ProxyBuilder.forClass(HasMultipleConstructors.class) 602 .constructorArgTypes(Integer.class) 603 .constructorArgValues(3) 604 .handler(fakeHandler) 605 .dexCache(DexMakerTest.getDataDirectory()).build(); 606 assertEquals("Integer 3", c.calledConstructor); 607 assertEquals(a.getClass(), c.getClass()); 608 } 609 610 public static class HasMultipleConstructors { 611 private final String calledConstructor; 612 public HasMultipleConstructors() { 613 calledConstructor = "no args"; 614 } 615 public HasMultipleConstructors(int b) { 616 calledConstructor = "int " + b; 617 } 618 public HasMultipleConstructors(Integer c) { 619 calledConstructor = "Integer " + c; 620 } 621 } 622 623 public void testClassNotCachedWithDifferentParentClassLoaders() throws Exception { 624 ClassLoader classLoaderA = newPathClassLoader(); 625 SimpleClass a = proxyFor(SimpleClass.class) 626 .parentClassLoader(classLoaderA) 627 .build(); 628 assertEquals(classLoaderA, a.getClass().getClassLoader().getParent()); 629 630 ClassLoader classLoaderB = newPathClassLoader(); 631 SimpleClass b = proxyFor(SimpleClass.class) 632 .parentClassLoader(classLoaderB) 633 .build(); 634 assertEquals(classLoaderB, b.getClass().getClassLoader().getParent()); 635 636 assertTrue(a.getClass() != b.getClass()); 637 } 638 639 public void testAbstractClassWithUndeclaredInterfaceMethod() throws Throwable { 640 DeclaresInterface declaresInterface = proxyFor(DeclaresInterface.class) 641 .build(); 642 assertEquals("fake result", declaresInterface.call()); 643 try { 644 ProxyBuilder.callSuper(declaresInterface, Callable.class.getMethod("call")); 645 fail(); 646 } catch (AbstractMethodError expected) { 647 } 648 } 649 650 public static abstract class DeclaresInterface implements Callable<String> { 651 } 652 653 public void testImplementingInterfaces() throws Throwable { 654 SimpleClass simpleClass = proxyFor(SimpleClass.class) 655 .implementing(Callable.class) 656 .implementing(Comparable.class) 657 .build(); 658 assertEquals("fake result", simpleClass.simpleMethod()); 659 660 Callable<?> asCallable = (Callable<?>) simpleClass; 661 assertEquals("fake result", asCallable.call()); 662 663 Comparable<?> asComparable = (Comparable<?>) simpleClass; 664 fakeHandler.fakeResult = 3; 665 assertEquals(3, asComparable.compareTo(null)); 666 } 667 668 public void testCallSuperWithInterfaceMethod() throws Throwable { 669 SimpleClass simpleClass = proxyFor(SimpleClass.class) 670 .implementing(Callable.class) 671 .build(); 672 try { 673 ProxyBuilder.callSuper(simpleClass, Callable.class.getMethod("call")); 674 fail(); 675 } catch (AbstractMethodError expected) { 676 } catch (NoSuchMethodError expected) { 677 } 678 } 679 680 public void testImplementInterfaceCallingThroughConcreteClass() throws Throwable { 681 InvocationHandler invocationHandler = new InvocationHandler() { 682 public Object invoke(Object o, Method method, Object[] objects) throws Throwable { 683 assertEquals("a", ProxyBuilder.callSuper(o, method, objects)); 684 return "b"; 685 } 686 }; 687 ImplementsCallable proxy = proxyFor(ImplementsCallable.class) 688 .implementing(Callable.class) 689 .handler(invocationHandler) 690 .build(); 691 assertEquals("b", proxy.call()); 692 assertEquals("a", ProxyBuilder.callSuper( 693 proxy, ImplementsCallable.class.getMethod("call"))); 694 } 695 696 /** 697 * This test is a bit unintuitive because it exercises the synthetic methods 698 * that support covariant return types. Calling 'Object call()' on the 699 * interface bridges to 'String call()', and so the super method appears to 700 * also be proxied. 701 */ 702 public void testImplementInterfaceCallingThroughInterface() throws Throwable { 703 final AtomicInteger count = new AtomicInteger(); 704 705 InvocationHandler invocationHandler = new InvocationHandler() { 706 public Object invoke(Object o, Method method, Object[] objects) throws Throwable { 707 count.incrementAndGet(); 708 return ProxyBuilder.callSuper(o, method, objects); 709 } 710 }; 711 712 Callable<?> proxy = proxyFor(ImplementsCallable.class) 713 .implementing(Callable.class) 714 .handler(invocationHandler) 715 .build(); 716 717 // the invocation handler is called twice! 718 assertEquals("a", proxy.call()); 719 assertEquals(2, count.get()); 720 721 // the invocation handler is called, even though this is a callSuper() call! 722 assertEquals("a", ProxyBuilder.callSuper(proxy, Callable.class.getMethod("call"))); 723 assertEquals(3, count.get()); 724 } 725 726 public static class ImplementsCallable implements Callable<String> { 727 public String call() throws Exception { 728 return "a"; 729 } 730 } 731 732 /** 733 * This test shows that our generated proxies follow the bytecode convention 734 * where methods can have the same name but unrelated return types. This is 735 * different from javac's convention where return types must be assignable 736 * in one direction or the other. 737 */ 738 public void testInterfacesSameNamesDifferentReturnTypes() throws Throwable { 739 InvocationHandler handler = new InvocationHandler() { 740 public Object invoke(Object o, Method method, Object[] objects) throws Throwable { 741 if (method.getReturnType() == void.class) { 742 return null; 743 } else if (method.getReturnType() == String.class) { 744 return "X"; 745 } else if (method.getReturnType() == int.class) { 746 return 3; 747 } else { 748 throw new AssertionFailedError(); 749 } 750 } 751 }; 752 753 Object o = proxyFor(Object.class) 754 .implementing(FooReturnsVoid.class, FooReturnsString.class, FooReturnsInt.class) 755 .handler(handler) 756 .build(); 757 758 FooReturnsVoid a = (FooReturnsVoid) o; 759 a.foo(); 760 761 FooReturnsString b = (FooReturnsString) o; 762 assertEquals("X", b.foo()); 763 764 FooReturnsInt c = (FooReturnsInt) o; 765 assertEquals(3, c.foo()); 766 } 767 768 public void testInterfacesSameNamesSameReturnType() throws Throwable { 769 Object o = proxyFor(Object.class) 770 .implementing(FooReturnsInt.class, FooReturnsInt2.class) 771 .build(); 772 773 fakeHandler.setFakeResult(3); 774 775 FooReturnsInt a = (FooReturnsInt) o; 776 assertEquals(3, a.foo()); 777 778 FooReturnsInt2 b = (FooReturnsInt2) o; 779 assertEquals(3, b.foo()); 780 } 781 782 public interface FooReturnsVoid { 783 void foo(); 784 } 785 786 public interface FooReturnsString { 787 String foo(); 788 } 789 790 public interface FooReturnsInt { 791 int foo(); 792 } 793 794 public interface FooReturnsInt2 { 795 int foo(); 796 } 797 798 private ClassLoader newPathClassLoader() throws Exception { 799 return (ClassLoader) Class.forName("dalvik.system.PathClassLoader") 800 .getConstructor(String.class, ClassLoader.class) 801 .newInstance("", getClass().getClassLoader()); 802 803 } 804 805 public void testSubclassOfRandom() throws Exception { 806 proxyFor(Random.class) 807 .handler(new InvokeSuperHandler()) 808 .build(); 809 } 810 811 public static class FinalToString { 812 @Override public final String toString() { 813 return "no proxy"; 814 } 815 } 816 817 // https://code.google.com/p/dexmaker/issues/detail?id=12 818 public void testFinalToString() throws Throwable { 819 assertEquals("no proxy", proxyFor(FinalToString.class).build().toString()); 820 } 821 822 /** Simple helper to add the most common args for this test to the proxy builder. */ 823 private <T> ProxyBuilder<T> proxyFor(Class<T> clazz) throws Exception { 824 return ProxyBuilder.forClass(clazz) 825 .handler(fakeHandler) 826 .dexCache(DexMakerTest.getDataDirectory()); 827 } 828 829 private static class FakeInvocationHandler implements InvocationHandler { 830 private Object fakeResult = "fake result"; 831 832 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 833 return fakeResult; 834 } 835 836 public void setFakeResult(Object result) { 837 fakeResult = result; 838 } 839 } 840 841 public static class TestOrderingClass { 842 public int returnsInt() { 843 return 0; 844 } 845 846 public int returnsInt(int param1, int param2) { 847 return 1; 848 } 849 850 public String returnsString() { 851 return "string"; 852 } 853 854 public boolean returnsBoolean() { 855 return false; 856 } 857 858 public double returnsDouble() { 859 return 1.0; 860 } 861 862 public Object returnsObject() { 863 return new Object(); 864 } 865 } 866 867 @SuppressWarnings("unchecked") 868 public void testMethodsGeneratedInDeterministicOrder() throws Exception { 869 // Grab the static methods array from the original class. 870 Method[] methods1 = getMethodsForProxyClass(TestOrderingClass.class); 871 assertNotNull(methods1); 872 873 // Clear ProxyBuilder's in-memory cache of classes. This will force 874 // it to rebuild the class and reset the static methods field. 875 Map<Class<?>, Class<?>> map = getGeneratedProxyClasses(); 876 assertNotNull(map); 877 map.clear(); 878 879 // Grab the static methods array from the rebuilt class. 880 Method[] methods2 = getMethodsForProxyClass(TestOrderingClass.class); 881 assertNotNull(methods2); 882 883 // Ensure that the two method arrays are equal. 884 assertTrue(Arrays.equals(methods1, methods2)); 885 } 886 887 public void testOrderingClassWithDexMakerCaching() throws Exception { 888 doTestOrderClassWithDexMakerCaching(); 889 890 // Force ProxyBuilder to call DexMaker.generateAndLoad() 891 getGeneratedProxyClasses().clear(); 892 893 doTestOrderClassWithDexMakerCaching(); 894 } 895 896 private void doTestOrderClassWithDexMakerCaching() throws Exception { 897 TestOrderingClass proxy = ProxyBuilder.forClass(TestOrderingClass.class) 898 .handler(new InvokeSuperHandler()) 899 .dexCache(DexMakerTest.getDataDirectory()) 900 .build(); 901 assertEquals(0, proxy.returnsInt()); 902 assertEquals(1, proxy.returnsInt(1, 1)); 903 assertEquals("string", proxy.returnsString()); 904 assertFalse(proxy.returnsBoolean()); 905 assertEquals(1.0, proxy.returnsDouble()); 906 assertNotNull(proxy.returnsObject()); 907 assertEquals(2, versionedDxDir.listFiles().length); 908 } 909 910 // Returns static methods array from a proxy class. 911 private Method[] getMethodsForProxyClass(Class<?> parentClass) throws Exception { 912 Class<?> proxyClass = proxyFor(parentClass).buildProxyClass(); 913 Method[] methods = null; 914 for (Field f : proxyClass.getDeclaredFields()) { 915 if (Method[].class.isAssignableFrom(f.getType())) { 916 f.setAccessible(true); 917 methods = (Method[]) f.get(null); 918 break; 919 } 920 } 921 922 return methods; 923 } 924 925 private Map<Class<?>, Class<?>> getGeneratedProxyClasses() throws Exception { 926 Field mapField = ProxyBuilder.class 927 .getDeclaredField("generatedProxyClasses"); 928 mapField.setAccessible(true); 929 return (Map<Class<?>, Class<?>>) mapField.get(null); 930 } 931 932 public static class ConcreteClassA implements FooReturnsInt { 933 // from FooReturnsInt 934 public int foo() { 935 return 1; 936 } 937 938 // not from FooReturnsInt 939 public String bar() { 940 return "bar"; 941 } 942 } 943 944 public static class ConcreteClassB implements FooReturnsInt { 945 // from FooReturnsInt 946 public int foo() { 947 return 0; 948 } 949 950 // not from FooReturnsInt 951 public String bar() { 952 return "bahhr"; 953 } 954 } 955 956 public void testTwoClassesWithIdenticalMethodSignatures_DexMakerCaching() throws Exception { 957 ConcreteClassA proxyA = ProxyBuilder.forClass(ConcreteClassA.class) 958 .handler(new InvokeSuperHandler()) 959 .dexCache(DexMakerTest.getDataDirectory()) 960 .build(); 961 assertEquals(1, proxyA.foo()); 962 assertEquals("bar", proxyA.bar()); 963 assertEquals(2, versionedDxDir.listFiles().length); 964 965 ConcreteClassB proxyB = ProxyBuilder.forClass(ConcreteClassB.class) 966 .handler(new InvokeSuperHandler()) 967 .dexCache(DexMakerTest.getDataDirectory()) 968 .build(); 969 assertEquals(0, proxyB.foo()); 970 assertEquals("bahhr", proxyB.bar()); 971 assertEquals(4, versionedDxDir.listFiles().length); 972 } 973} 974