Home | History | Annotate | Download | only in bridge
      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