FileDocCategorySizeDatePackage
TestDelegates.javaAPI DocAndroid 5.1 API9462Thu Mar 12 22:22:44 GMT 2015com.android.layoutlib.bridge

TestDelegates

public class TestDelegates extends TestCase
Tests that native delegate classes implement all the required methods. This looks at {@link CreateInfo#DELEGATE_CLASS_NATIVES} to get the list of classes that have their native methods reimplemented through a delegate. Since the reimplemented methods are not native anymore, we look for the annotation {@link LayoutlibDelegate}, and look for a matching method in the delegate (named the same as the modified class with _Delegate added as a suffix). If the original native method is not static, then we make sure the delegate method also include the original class as first parameter (to access "this").

Fields Summary
private List
mErrors
Constructors Summary
Methods Summary
private voidcompare(java.lang.Class originalClass, java.lang.Class delegateClass)

        List<Method> checkedDelegateMethods = new ArrayList<Method>();

        // loop on the methods of the original class, and for the ones that are annotated
        // with @LayoutlibDelegate, look for a matching method in the delegate class.
        // The annotation is automatically added by layoutlib_create when it replace a method
        // by a call to a delegate
        Method[] originalMethods = originalClass.getDeclaredMethods();
        for (Method originalMethod : originalMethods) {
            // look for methods that are delegated: they have the LayoutlibDelegate annotation
            if (originalMethod.getAnnotation(LayoutlibDelegate.class) == null) {
                continue;
            }

            // get the signature.
            Class<?>[] parameters = originalMethod.getParameterTypes();

            // if the method is not static, then the class is added as the first parameter
            // (for "this")
            if ((originalMethod.getModifiers() & Modifier.STATIC) == 0) {

                Class<?>[] newParameters = new Class<?>[parameters.length + 1];
                newParameters[0] = originalClass;
                System.arraycopy(parameters, 0, newParameters, 1, parameters.length);
                parameters = newParameters;
            }

            // if the original class is an inner class that's not static, then
            // we add this on the enclosing class at the beginning
            if (originalClass.getEnclosingClass() != null &&
                    (originalClass.getModifiers() & Modifier.STATIC) == 0) {
                Class<?>[] newParameters = new Class<?>[parameters.length + 1];
                newParameters[0] = originalClass.getEnclosingClass();
                System.arraycopy(parameters, 0, newParameters, 1, parameters.length);
                parameters = newParameters;
            }

            try {
                // try to load the method with the given parameter types.
                Method delegateMethod = delegateClass.getDeclaredMethod(originalMethod.getName(),
                        parameters);

                // check the return type of the methods match.
                if (delegateMethod.getReturnType() != originalMethod.getReturnType()) {
                    mErrors.add(
                            String.format("Delegate method %1$s.%2$s does not match the " +
                                    "corresponding framework method which returns %3$s",
                            delegateClass.getName(),
                            getMethodName(delegateMethod),
                            originalMethod.getReturnType().getName()));
                }

                // check that the method has the annotation
                if (delegateMethod.getAnnotation(LayoutlibDelegate.class) == null) {
                    mErrors.add(
                            String.format("Delegate method %1$s for class %2$s does not have the " +
                                            "@LayoutlibDelegate annotation",
                                    delegateMethod.getName(),
                                    originalClass.getName()));
                }

                // check that the method is static
                if ((delegateMethod.getModifiers() & Modifier.STATIC) != Modifier.STATIC) {
                    mErrors.add(
                            String.format(
                                    "Delegate method %1$s for class %2$s is not static",
                                    delegateMethod.getName(),
                                    originalClass.getName())
                    );
                }

                // add the method as checked.
                checkedDelegateMethods.add(delegateMethod);
            } catch (NoSuchMethodException e) {
                String name = getMethodName(originalMethod, parameters);
                mErrors.add(String.format("Missing %1$s.%2$s", delegateClass.getName(), name));
            }
        }

        // look for dead (delegate) code.
        // This looks for all methods in the delegate class, and if they have the
        // @LayoutlibDelegate annotation, make sure they have been previously found as a
        // match for a method in the original class.
        // If not, this means the method is a delegate for a method that either doesn't exist
        // anymore or is not delegated anymore.
        Method[] delegateMethods = delegateClass.getDeclaredMethods();
        for (Method delegateMethod : delegateMethods) {
            // look for methods that are delegates: they have the LayoutlibDelegate annotation
            if (delegateMethod.getAnnotation(LayoutlibDelegate.class) == null) {
                continue;
            }

            if (!checkedDelegateMethods.contains(delegateMethod)) {
                mErrors.add(String.format(
                        "Delegate method %1$s.%2$s is not used anymore and must be removed",
                        delegateClass.getName(),
                        getMethodName(delegateMethod)));
            }
        }

    
private java.lang.StringgetErrors()

        StringBuilder s = new StringBuilder();
        for (String error : mErrors) {
            s.append(error).append('\n");
        }
        return s.toString();
    
private java.lang.StringgetMethodName(java.lang.reflect.Method method)

        return getMethodName(method, method.getParameterTypes());
    
private java.lang.StringgetMethodName(java.lang.reflect.Method method, java.lang.Class[] parameters)

        // compute a full class name that's long but not too long.
        StringBuilder sb = new StringBuilder(method.getName() + "(");
        for (int j = 0; j < parameters.length; j++) {
            Class<?> theClass = parameters[j];
            sb.append(theClass.getName());
            int dimensions = 0;
            while (theClass.isArray()) {
                dimensions++;
                theClass = theClass.getComponentType();
            }
            for (int i = 0; i < dimensions; i++) {
                sb.append("[]");
            }
            if (j < (parameters.length - 1)) {
                sb.append(",");
            }
        }
        sb.append(")");

        return sb.toString();
    
private voidloadAndCompareClasses(java.lang.String originalClassName, java.lang.String delegateClassName)

        // load the classes
        try {
            ClassLoader classLoader = TestDelegates.class.getClassLoader();
            Class<?> originalClass = classLoader.loadClass(originalClassName);
            Class<?> delegateClass = classLoader.loadClass(delegateClassName);

            compare(originalClass, delegateClass);
        } catch (ClassNotFoundException e) {
            mErrors.add("Failed to load class: " + e.getMessage());
        } catch (SecurityException e) {
            mErrors.add("Failed to load class: " + e.getMessage());
        }
    
public voidtestMethodDelegates()

        final String[] methods = CreateInfo.DELEGATE_METHODS;
        mErrors.clear();
        for (String methodName : methods) {
            // extract the class name
            String className = methodName.substring(0, methodName.indexOf('#"));
            String targetClassName = className.replace('$", '_") + "_Delegate";

            loadAndCompareClasses(className, targetClassName);
        }
        assertTrue(getErrors(), mErrors.isEmpty());
    
public voidtestNativeDelegates()


       

        final String[] classes = CreateInfo.DELEGATE_CLASS_NATIVES;
        mErrors.clear();
        for (String clazz : classes) {
            loadAndCompareClasses(clazz, clazz + "_Delegate");
        }
        assertTrue(getErrors(), mErrors.isEmpty());