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