14f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet/* 24f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * Copyright (C) 2010 The Android Open Source Project 34f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * 44f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * Licensed under the Apache License, Version 2.0 (the "License"); 54f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * you may not use this file except in compliance with the License. 64f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * You may obtain a copy of the License at 74f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * 84f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * http://www.apache.org/licenses/LICENSE-2.0 94f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * 104f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * Unless required by applicable law or agreed to in writing, software 114f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * distributed under the License is distributed on an "AS IS" BASIS, 124f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 134f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * See the License for the specific language governing permissions and 144f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * limitations under the License. 154f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet */ 164f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet 174f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohetpackage com.android.layoutlib.bridge; 184f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet 194f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohetimport com.android.tools.layoutlib.annotations.LayoutlibDelegate; 204f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohetimport com.android.tools.layoutlib.create.CreateInfo; 214f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet 224f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohetimport java.lang.reflect.Method; 234f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohetimport java.lang.reflect.Modifier; 249a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohetimport java.util.ArrayList; 259a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohetimport java.util.List; 264f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet 274f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohetimport junit.framework.TestCase; 284f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet 294f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet/** 304f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * Tests that native delegate classes implement all the required methods. 314f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * 324f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * This looks at {@link CreateInfo#DELEGATE_CLASS_NATIVES} to get the list of classes that 334f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * have their native methods reimplemented through a delegate. 344f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * 354f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * Since the reimplemented methods are not native anymore, we look for the annotation 364f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * {@link LayoutlibDelegate}, and look for a matching method in the delegate (named the same 374f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * as the modified class with _Delegate added as a suffix). 384f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * If the original native method is not static, then we make sure the delegate method also 394f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * include the original class as first parameter (to access "this"). 404f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet * 414f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet */ 42071dee288ca726c7c15754c2559403b9cbf950bdXavier Ducrohetpublic class TestDelegates extends TestCase { 434f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet 44071dee288ca726c7c15754c2559403b9cbf950bdXavier Ducrohet public void testNativeDelegates() { 454f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet 464f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet final String[] classes = CreateInfo.DELEGATE_CLASS_NATIVES; 474f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet final int count = classes.length; 484f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet for (int i = 0 ; i < count ; i++) { 494f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet loadAndCompareClasses(classes[i], classes[i] + "_Delegate"); 504f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet } 514f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet } 524f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet 53071dee288ca726c7c15754c2559403b9cbf950bdXavier Ducrohet public void testMethodDelegates() { 54071dee288ca726c7c15754c2559403b9cbf950bdXavier Ducrohet final String[] methods = CreateInfo.DELEGATE_METHODS; 55071dee288ca726c7c15754c2559403b9cbf950bdXavier Ducrohet final int count = methods.length; 56071dee288ca726c7c15754c2559403b9cbf950bdXavier Ducrohet for (int i = 0 ; i < count ; i++) { 57071dee288ca726c7c15754c2559403b9cbf950bdXavier Ducrohet String methodName = methods[i]; 58071dee288ca726c7c15754c2559403b9cbf950bdXavier Ducrohet 59071dee288ca726c7c15754c2559403b9cbf950bdXavier Ducrohet // extract the class name 60071dee288ca726c7c15754c2559403b9cbf950bdXavier Ducrohet String className = methodName.substring(0, methodName.indexOf('#')); 61345f866bfd09476fd62aa10345a0670cc110b63cXavier Ducrohet String targetClassName = className.replace('$', '_') + "_Delegate"; 62071dee288ca726c7c15754c2559403b9cbf950bdXavier Ducrohet 63345f866bfd09476fd62aa10345a0670cc110b63cXavier Ducrohet loadAndCompareClasses(className, targetClassName); 64071dee288ca726c7c15754c2559403b9cbf950bdXavier Ducrohet } 65071dee288ca726c7c15754c2559403b9cbf950bdXavier Ducrohet } 66071dee288ca726c7c15754c2559403b9cbf950bdXavier Ducrohet 674f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet private void loadAndCompareClasses(String originalClassName, String delegateClassName) { 684f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet // load the classes 694f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet try { 70071dee288ca726c7c15754c2559403b9cbf950bdXavier Ducrohet ClassLoader classLoader = TestDelegates.class.getClassLoader(); 714f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet Class<?> originalClass = classLoader.loadClass(originalClassName); 724f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet Class<?> delegateClass = classLoader.loadClass(delegateClassName); 734f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet 744f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet compare(originalClass, delegateClass); 754f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet } catch (ClassNotFoundException e) { 764f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet fail("Failed to load class: " + e.getMessage()); 774f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet } catch (SecurityException e) { 784f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet fail("Failed to load class: " + e.getMessage()); 794f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet } 804f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet } 814f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet 824f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet private void compare(Class<?> originalClass, Class<?> delegateClass) throws SecurityException { 839a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet List<Method> checkedDelegateMethods = new ArrayList<Method>(); 844f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet 859a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet // loop on the methods of the original class, and for the ones that are annotated 869a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet // with @LayoutlibDelegate, look for a matching method in the delegate class. 879a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet // The annotation is automatically added by layoutlib_create when it replace a method 889a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet // by a call to a delegate 899a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet Method[] originalMethods = originalClass.getDeclaredMethods(); 904f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet for (Method originalMethod : originalMethods) { 919a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet // look for methods that are delegated: they have the LayoutlibDelegate annotation 924f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet if (originalMethod.getAnnotation(LayoutlibDelegate.class) == null) { 934f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet continue; 944f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet } 954f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet 964f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet // get the signature. 974f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet Class<?>[] parameters = originalMethod.getParameterTypes(); 984f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet 994f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet // if the method is not static, then the class is added as the first parameter 1004f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet // (for "this") 1014f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet if ((originalMethod.getModifiers() & Modifier.STATIC) == 0) { 1024f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet 1034f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet Class<?>[] newParameters = new Class<?>[parameters.length + 1]; 1044f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet newParameters[0] = originalClass; 1054f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet System.arraycopy(parameters, 0, newParameters, 1, parameters.length); 1064f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet parameters = newParameters; 1074f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet } 1084f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet 109345f866bfd09476fd62aa10345a0670cc110b63cXavier Ducrohet // if the original class is an inner class that's not static, then 110345f866bfd09476fd62aa10345a0670cc110b63cXavier Ducrohet // we add this on the enclosing class at the beginning 111345f866bfd09476fd62aa10345a0670cc110b63cXavier Ducrohet if (originalClass.getEnclosingClass() != null && 112345f866bfd09476fd62aa10345a0670cc110b63cXavier Ducrohet (originalClass.getModifiers() & Modifier.STATIC) == 0) { 113345f866bfd09476fd62aa10345a0670cc110b63cXavier Ducrohet Class<?>[] newParameters = new Class<?>[parameters.length + 1]; 114345f866bfd09476fd62aa10345a0670cc110b63cXavier Ducrohet newParameters[0] = originalClass.getEnclosingClass(); 115345f866bfd09476fd62aa10345a0670cc110b63cXavier Ducrohet System.arraycopy(parameters, 0, newParameters, 1, parameters.length); 116345f866bfd09476fd62aa10345a0670cc110b63cXavier Ducrohet parameters = newParameters; 117345f866bfd09476fd62aa10345a0670cc110b63cXavier Ducrohet } 118345f866bfd09476fd62aa10345a0670cc110b63cXavier Ducrohet 1194f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet try { 1204f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet // try to load the method with the given parameter types. 1219f63ff263b0a97f0fa63e97136c18f6abccbfc68Xavier Ducrohet Method delegateMethod = delegateClass.getDeclaredMethod(originalMethod.getName(), 1229f63ff263b0a97f0fa63e97136c18f6abccbfc68Xavier Ducrohet parameters); 1239f63ff263b0a97f0fa63e97136c18f6abccbfc68Xavier Ducrohet 1249a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet // check that the method has the annotation 1259a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet assertNotNull( 1269a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet String.format( 1279a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet "Delegate method %1$s for class %2$s does not have the @LayoutlibDelegate annotation", 1289a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet delegateMethod.getName(), 1299a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet originalClass.getName()), 1309a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet delegateMethod.getAnnotation(LayoutlibDelegate.class)); 1319a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet 1329f63ff263b0a97f0fa63e97136c18f6abccbfc68Xavier Ducrohet // check that the method is static 13382b9232565bfececdb643a94cecdd1bd1cb5c643Xavier Ducrohet assertTrue( 13482b9232565bfececdb643a94cecdd1bd1cb5c643Xavier Ducrohet String.format( 13582b9232565bfececdb643a94cecdd1bd1cb5c643Xavier Ducrohet "Delegate method %1$s for class %2$s is not static", 13682b9232565bfececdb643a94cecdd1bd1cb5c643Xavier Ducrohet delegateMethod.getName(), 13782b9232565bfececdb643a94cecdd1bd1cb5c643Xavier Ducrohet originalClass.getName()), 13882b9232565bfececdb643a94cecdd1bd1cb5c643Xavier Ducrohet (delegateMethod.getModifiers() & Modifier.STATIC) == Modifier.STATIC); 1399a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet 1409a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet // add the method as checked. 1419a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet checkedDelegateMethods.add(delegateMethod); 1424f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet } catch (NoSuchMethodException e) { 1439a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet String name = getMethodName(originalMethod, parameters); 1449a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet fail(String.format("Missing %1$s.%2$s", delegateClass.getName(), name)); 1459a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet } 1469a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet } 1479a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet 1489a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet // look for dead (delegate) code. 1499a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet // This looks for all methods in the delegate class, and if they have the 1509a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet // @LayoutlibDelegate annotation, make sure they have been previously found as a 1519a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet // match for a method in the original class. 1529a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet // If not, this means the method is a delegate for a method that either doesn't exist 1539a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet // anymore or is not delegated anymore. 1549a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet Method[] delegateMethods = delegateClass.getDeclaredMethods(); 1559a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet for (Method delegateMethod : delegateMethods) { 1569a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet // look for methods that are delegates: they have the LayoutlibDelegate annotation 1579a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet if (delegateMethod.getAnnotation(LayoutlibDelegate.class) == null) { 1589a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet continue; 1594f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet } 1609a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet 1619a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet assertTrue( 1629a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet String.format( 1639a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet "Delegate method %1$s.%2$s is not used anymore and must be removed", 1649a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet delegateClass.getName(), 1659a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet getMethodName(delegateMethod)), 1669a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet checkedDelegateMethods.contains(delegateMethod)); 1674f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet } 1689a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet 1699a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet } 1709a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet 1719a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet private String getMethodName(Method method) { 1729a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet return getMethodName(method, method.getParameterTypes()); 1739a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet } 1749a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet 1759a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet private String getMethodName(Method method, Class<?>[] parameters) { 1769a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet // compute a full class name that's long but not too long. 1779a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet StringBuilder sb = new StringBuilder(method.getName() + "("); 1789a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet for (int j = 0; j < parameters.length; j++) { 1799a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet Class<?> theClass = parameters[j]; 1809a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet sb.append(theClass.getName()); 1819a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet int dimensions = 0; 1829a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet while (theClass.isArray()) { 1839a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet dimensions++; 1849a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet theClass = theClass.getComponentType(); 1859a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet } 1869a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet for (int i = 0; i < dimensions; i++) { 1879a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet sb.append("[]"); 1889a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet } 1899a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet if (j < (parameters.length - 1)) { 1909a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet sb.append(","); 1919a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet } 1929a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet } 1939a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet sb.append(")"); 1949a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet 1959a4fe29c8d92014d2d9a848e9116b8cc9d0842f9Xavier Ducrohet return sb.toString(); 1964f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet } 1974f291d33e14e62b3301acc056a82fe206c74835fXavier Ducrohet} 198