ProxyBuilderTest.java revision 95689a700bfea5e2d78380a442fc2903cc40a3f2
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.InvocationHandler;
23import java.lang.reflect.Method;
24import java.lang.reflect.UndeclaredThrowableException;
25import java.util.Arrays;
26import java.util.Random;
27import java.util.concurrent.Callable;
28import java.util.concurrent.atomic.AtomicInteger;
29import junit.framework.AssertionFailedError;
30import junit.framework.TestCase;
31
32public 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