1/*
2 * Copyright (C) 2010 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.android.layoutlib.bridge;
18
19import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
20import com.android.tools.layoutlib.create.CreateInfo;
21
22import java.lang.reflect.Method;
23import java.lang.reflect.Modifier;
24import java.util.ArrayList;
25import java.util.List;
26
27import junit.framework.TestCase;
28
29/**
30 * Tests that native delegate classes implement all the required methods.
31 *
32 * This looks at {@link CreateInfo#DELEGATE_CLASS_NATIVES} to get the list of classes that
33 * have their native methods reimplemented through a delegate.
34 *
35 * Since the reimplemented methods are not native anymore, we look for the annotation
36 * {@link LayoutlibDelegate}, and look for a matching method in the delegate (named the same
37 * as the modified class with _Delegate added as a suffix).
38 * If the original native method is not static, then we make sure the delegate method also
39 * include the original class as first parameter (to access "this").
40 *
41 */
42public class TestDelegates extends TestCase {
43
44    public void testNativeDelegates() {
45
46        final String[] classes = CreateInfo.DELEGATE_CLASS_NATIVES;
47        final int count = classes.length;
48        for (int i = 0 ; i < count ; i++) {
49            loadAndCompareClasses(classes[i], classes[i] + "_Delegate");
50        }
51    }
52
53    public void testMethodDelegates() {
54        final String[] methods = CreateInfo.DELEGATE_METHODS;
55        final int count = methods.length;
56        for (int i = 0 ; i < count ; i++) {
57            String methodName = methods[i];
58
59            // extract the class name
60            String className = methodName.substring(0, methodName.indexOf('#'));
61            String targetClassName = className.replace('$', '_') + "_Delegate";
62
63            loadAndCompareClasses(className, targetClassName);
64        }
65    }
66
67    private void loadAndCompareClasses(String originalClassName, String delegateClassName) {
68        // load the classes
69        try {
70            ClassLoader classLoader = TestDelegates.class.getClassLoader();
71            Class<?> originalClass = classLoader.loadClass(originalClassName);
72            Class<?> delegateClass = classLoader.loadClass(delegateClassName);
73
74            compare(originalClass, delegateClass);
75        } catch (ClassNotFoundException e) {
76           fail("Failed to load class: " + e.getMessage());
77        } catch (SecurityException e) {
78            fail("Failed to load class: " + e.getMessage());
79        }
80    }
81
82    private void compare(Class<?> originalClass, Class<?> delegateClass) throws SecurityException {
83        List<Method> checkedDelegateMethods = new ArrayList<Method>();
84
85        // loop on the methods of the original class, and for the ones that are annotated
86        // with @LayoutlibDelegate, look for a matching method in the delegate class.
87        // The annotation is automatically added by layoutlib_create when it replace a method
88        // by a call to a delegate
89        Method[] originalMethods = originalClass.getDeclaredMethods();
90        for (Method originalMethod : originalMethods) {
91            // look for methods that are delegated: they have the LayoutlibDelegate annotation
92            if (originalMethod.getAnnotation(LayoutlibDelegate.class) == null) {
93                continue;
94            }
95
96            // get the signature.
97            Class<?>[] parameters = originalMethod.getParameterTypes();
98
99            // if the method is not static, then the class is added as the first parameter
100            // (for "this")
101            if ((originalMethod.getModifiers() & Modifier.STATIC) == 0) {
102
103                Class<?>[] newParameters = new Class<?>[parameters.length + 1];
104                newParameters[0] = originalClass;
105                System.arraycopy(parameters, 0, newParameters, 1, parameters.length);
106                parameters = newParameters;
107            }
108
109            // if the original class is an inner class that's not static, then
110            // we add this on the enclosing class at the beginning
111            if (originalClass.getEnclosingClass() != null &&
112                    (originalClass.getModifiers() & Modifier.STATIC) == 0) {
113                Class<?>[] newParameters = new Class<?>[parameters.length + 1];
114                newParameters[0] = originalClass.getEnclosingClass();
115                System.arraycopy(parameters, 0, newParameters, 1, parameters.length);
116                parameters = newParameters;
117            }
118
119            try {
120                // try to load the method with the given parameter types.
121                Method delegateMethod = delegateClass.getDeclaredMethod(originalMethod.getName(),
122                        parameters);
123
124                // check that the method has the annotation
125                assertNotNull(
126                        String.format(
127                                "Delegate method %1$s for class %2$s does not have the @LayoutlibDelegate annotation",
128                                delegateMethod.getName(),
129                                originalClass.getName()),
130                        delegateMethod.getAnnotation(LayoutlibDelegate.class));
131
132                // check that the method is static
133                assertTrue(
134                        String.format(
135                                "Delegate method %1$s for class %2$s is not static",
136                                delegateMethod.getName(),
137                                originalClass.getName()),
138                        (delegateMethod.getModifiers() & Modifier.STATIC) == Modifier.STATIC);
139
140                // add the method as checked.
141                checkedDelegateMethods.add(delegateMethod);
142            } catch (NoSuchMethodException e) {
143                String name = getMethodName(originalMethod, parameters);
144                fail(String.format("Missing %1$s.%2$s", delegateClass.getName(), name));
145            }
146        }
147
148        // look for dead (delegate) code.
149        // This looks for all methods in the delegate class, and if they have the
150        // @LayoutlibDelegate annotation, make sure they have been previously found as a
151        // match for a method in the original class.
152        // If not, this means the method is a delegate for a method that either doesn't exist
153        // anymore or is not delegated anymore.
154        Method[] delegateMethods = delegateClass.getDeclaredMethods();
155        for (Method delegateMethod : delegateMethods) {
156            // look for methods that are delegates: they have the LayoutlibDelegate annotation
157            if (delegateMethod.getAnnotation(LayoutlibDelegate.class) == null) {
158                continue;
159            }
160
161            assertTrue(
162                    String.format(
163                            "Delegate method %1$s.%2$s is not used anymore and must be removed",
164                            delegateClass.getName(),
165                            getMethodName(delegateMethod)),
166                    checkedDelegateMethods.contains(delegateMethod));
167        }
168
169    }
170
171    private String getMethodName(Method method) {
172        return getMethodName(method, method.getParameterTypes());
173    }
174
175    private String getMethodName(Method method, Class<?>[] parameters) {
176        // compute a full class name that's long but not too long.
177        StringBuilder sb = new StringBuilder(method.getName() + "(");
178        for (int j = 0; j < parameters.length; j++) {
179            Class<?> theClass = parameters[j];
180            sb.append(theClass.getName());
181            int dimensions = 0;
182            while (theClass.isArray()) {
183                dimensions++;
184                theClass = theClass.getComponentType();
185            }
186            for (int i = 0; i < dimensions; i++) {
187                sb.append("[]");
188            }
189            if (j < (parameters.length - 1)) {
190                sb.append(",");
191            }
192        }
193        sb.append(")");
194
195        return sb.toString();
196    }
197}
198