1package com.xtremelabs.robolectric.bytecode;
2
3import com.xtremelabs.robolectric.Robolectric;
4import com.xtremelabs.robolectric.internal.Implementation;
5import com.xtremelabs.robolectric.internal.Implements;
6import com.xtremelabs.robolectric.util.Join;
7import org.junit.Assert;
8import org.junit.Before;
9import org.junit.Test;
10
11import java.lang.reflect.Member;
12import java.lang.reflect.Method;
13import java.lang.reflect.Modifier;
14import java.util.ArrayList;
15import java.util.List;
16
17public class RobolectricWiringTest {
18    private List<String> mismatches;
19
20    @Before public void setUp() throws Exception {
21        mismatches = new ArrayList<String>();
22    }
23
24    @Test
25    public void testAllImplementationMethodsHaveCorrectSignature() throws Exception {
26        for (Class<?> shadowClass : Robolectric.getDefaultShadowClasses()) {
27            verifyClass(shadowClass);
28        }
29
30        Assert.assertEquals("@Implementation method mismatch: " + Join.join("\n", mismatches), 0, mismatches.size());
31    }
32
33    private void verifyClass(final Class<?> shadowClass) {
34        Implements annotation = shadowClass.getAnnotation(Implements.class);
35        Class implementedClass = annotation.value();
36
37        try {
38            shadowClass.getConstructor(implementedClass);
39        } catch (NoSuchMethodException e) {
40            try {
41                shadowClass.getConstructor();
42            } catch (NoSuchMethodException e1) {
43                mismatches.add("Missing constructor for " + shadowClass.getSimpleName());
44            }
45        }
46
47        for (Method shadowMethod : shadowClass.getDeclaredMethods()) {
48            verifyMethod(implementedClass, shadowMethod);
49        }
50    }
51
52    private void verifyMethod(Class implementedClass, Method shadowMethod) {
53        Member implementedMember;
54
55        boolean isConstructor = shadowMethod.getName().equals("__constructor__");
56        if (isAnnotatedImplementation(shadowMethod) || isConstructor) {
57            if (isConstructor) {
58                implementedMember = findConstructor(implementedClass, shadowMethod);
59            } else {
60                implementedMember = findMethod(implementedClass, shadowMethod);
61            }
62            if (implementedMember == null) {
63                mismatches.add(shadowMethod.toGenericString() + " doesn't match a real method");
64            } else if (staticMismatch(shadowMethod, implementedMember)) {
65                mismatches.add(shadowMethod.toGenericString() + " doesn't match the staticness of the real method");
66            }
67            if (!Modifier.isPublic(shadowMethod.getModifiers())) {
68                mismatches.add(shadowMethod.toGenericString() + " should be public");
69            }
70        } else {
71            implementedMember = findMethod(implementedClass, shadowMethod);
72            if (implementedMember != null) {
73                mismatches.add(shadowMethod.toGenericString() + " should be annotated @Implementation");
74            }
75        }
76    }
77
78    private boolean isAnnotatedImplementation(Method shadowMethod) {
79        // works around a weird bug causing overridden methods to show no annotations
80        try {
81            return shadowMethod.getDeclaringClass().getDeclaredMethod(shadowMethod.getName(), shadowMethod.getParameterTypes()).isAnnotationPresent(Implementation.class);
82        } catch (NoSuchMethodException e) {
83            throw new RuntimeException(e);
84        }
85    }
86
87    private Member findConstructor(Class implementedClass, Method shadowMethod) {
88        Class<?>[] parameterTypes = shadowMethod.getParameterTypes();
89        try {
90            return implementedClass.getConstructor(parameterTypes);
91        } catch (NoSuchMethodException e1) {
92            try {
93                return implementedClass.getDeclaredConstructor(parameterTypes);
94            } catch (NoSuchMethodException e2) {
95                return null;
96            }
97        }
98    }
99
100    private Member findMethod(Class implementedClass, Method shadowMethod) {
101        Class<?>[] parameterTypes = shadowMethod.getParameterTypes();
102        String methodName = shadowMethod.getName();
103        try {
104            return implementedClass.getMethod(methodName, parameterTypes);
105        } catch (NoSuchMethodException e1) {
106            try {
107                return implementedClass.getDeclaredMethod(methodName, parameterTypes);
108            } catch (NoSuchMethodException e2) {
109                return null;
110            }
111        }
112    }
113
114    private boolean staticMismatch(Member shadowMethod, Member implementedMethod) {
115        return Modifier.isStatic(implementedMethod.getModifiers()) != Modifier.isStatic(shadowMethod.getModifiers());
116    }
117}
118