Home | History | Annotate | Download | only in stock
      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