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 private List<String> mErrors = new ArrayList<String>(); 45 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 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 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 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 186 private String getMethodName(Method method) { 187 return getMethodName(method, method.getParameterTypes()); 188 } 189 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 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