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