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 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