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