FileDocCategorySizeDatePackage
DelegateClassAdapterTest.javaAPI DocAndroid 5.1 API19987Thu Mar 12 22:22:44 GMT 2015com.android.tools.layoutlib.create

DelegateClassAdapterTest

public class DelegateClassAdapterTest extends Object

Fields Summary
private MockLog
mLog
private static final String
NATIVE_CLASS_NAME
private static final String
OUTER_CLASS_NAME
private static final String
INNER_CLASS_NAME
Constructors Summary
Methods Summary
private java.lang.ThrowabledumpGeneratedClass(java.lang.Throwable t, com.android.tools.layoutlib.create.DelegateClassAdapterTest$ClassLoader2 cl2)
For debugging, it's useful to dump the content of the generated classes along with the exception that was generated. However to make it work you need to pull in the org.objectweb.asm.util.TraceClassVisitor class and associated utilities which are found in the ASM source jar. Since we don't want that dependency in the source code, we only put it manually for development and access the TraceClassVisitor via reflection if present.

param
t The exception thrown by {@link ClassLoader2#testModifiedInstance()}
param
cl2 The {@link ClassLoader2} instance with the generated bytecode.
return
Either original {@code t} or a new wrapper {@link Throwable}

        try {
            // For debugging, dump the bytecode of the class in case of unexpected error
            // if we can find the TraceClassVisitor class.
            Class<?> tcvClass = Class.forName("org.objectweb.asm.util.TraceClassVisitor");

            StringBuilder sb = new StringBuilder();
            sb.append('\n").append(t.getClass().getCanonicalName());
            if (t.getMessage() != null) {
                sb.append(": ").append(t.getMessage());
              }

            for (Entry<String, byte[]> entry : cl2.getByteCode()) {
                String className = entry.getKey();
                byte[] bytes = entry.getValue();

                StringWriter sw = new StringWriter();
                PrintWriter pw = new PrintWriter(sw);
                // next 2 lines do: TraceClassVisitor tcv = new TraceClassVisitor(pw);
                Constructor<?> cons = tcvClass.getConstructor(pw.getClass());
                Object tcv = cons.newInstance(pw);
                ClassReader cr2 = new ClassReader(bytes);
                cr2.accept((ClassVisitor) tcv, 0 /* flags */);

                sb.append("\nBytecode dump: <").append(className).append(">:\n")
                  .append(sw.toString());
            }

            // Re-throw exception with new message
            return new RuntimeException(sb.toString(), t);
        } catch (Throwable ignore) {
            // In case of problem, just throw the original exception as-is.
            return t;
        }
    
public voidsetUp()


    
         
        mLog = new MockLog();
        mLog.setVerbose(true); // capture debug error too
    
public voidtestConstructorsNotSupported()
{@link DelegateMethodAdapter} does not support overriding constructors yet, so this should fail with an {@link UnsupportedOperationException}. Although not tested here, the message of the exception should contain the constructor signature.

        ClassWriter cw = new ClassWriter(0 /*flags*/);

        String internalClassName = NATIVE_CLASS_NAME.replace('.", '/");

        HashSet<String> delegateMethods = new HashSet<String>();
        delegateMethods.add("<init>");
        DelegateClassAdapter cv = new DelegateClassAdapter(
                mLog, cw, internalClassName, delegateMethods);

        ClassReader cr = new ClassReader(NATIVE_CLASS_NAME);
        cr.accept(cv, 0 /* flags */);
    
public voidtestDelegateInner()

        // We'll delegate the "get" method of both the inner and outer class.
        HashSet<String> delegateMethods = new HashSet<String>();
        delegateMethods.add("get");
        delegateMethods.add("privateMethod");

        // Generate the delegate for the outer class.
        ClassWriter cwOuter = new ClassWriter(0 /*flags*/);
        String outerClassName = OUTER_CLASS_NAME.replace('.", '/");
        DelegateClassAdapter cvOuter = new DelegateClassAdapter(
                mLog, cwOuter, outerClassName, delegateMethods);
        ClassReader cr = new ClassReader(OUTER_CLASS_NAME);
        cr.accept(cvOuter, 0 /* flags */);

        // Generate the delegate for the inner class.
        ClassWriter cwInner = new ClassWriter(0 /*flags*/);
        String innerClassName = INNER_CLASS_NAME.replace('.", '/");
        DelegateClassAdapter cvInner = new DelegateClassAdapter(
                mLog, cwInner, innerClassName, delegateMethods);
        cr = new ClassReader(INNER_CLASS_NAME);
        cr.accept(cvInner, 0 /* flags */);

        // Load the generated classes in a different class loader and try them
        ClassLoader2 cl2 = null;
        try {
            cl2 = new ClassLoader2() {
                @Override
                public void testModifiedInstance() throws Exception {

                    // Check the outer class
                    Class<?> outerClazz2 = loadClass(OUTER_CLASS_NAME);
                    Object o2 = outerClazz2.newInstance();
                    assertNotNull(o2);

                    // The original Outer.get returns 1+10+20,
                    // but the delegate makes it return 4+10+20
                    assertEquals(4+10+20, callGet(o2, 10, 20));
                    assertEquals(1+10+20, callGet_Original(o2, 10, 20));

                    // The original Outer has a private method,
                    // so by default we can't access it.
                    boolean gotIllegalAccessException = false;
                    try {
                         callMethod(o2, "privateMethod", false /*makePublic*/);
                    } catch(IllegalAccessException e) {
                        gotIllegalAccessException = true;
                    }
                    assertTrue(gotIllegalAccessException);

                    // The private method from original Outer has been
                    // delegated. The delegate generated should have the
                    // same access.
                    gotIllegalAccessException = false;
                    try {
                        assertEquals("outerPrivateMethod",
                                callMethod(o2, "privateMethod_Original", false /*makePublic*/));
                    } catch (IllegalAccessException e) {
                        gotIllegalAccessException = true;
                    }
                    assertTrue(gotIllegalAccessException);

                    // Check the inner class. Since it's not a static inner class, we need
                    // to use the hidden constructor that takes the outer class as first parameter.
                    Class<?> innerClazz2 = loadClass(INNER_CLASS_NAME);
                    Constructor<?> innerCons = innerClazz2.getConstructor(outerClazz2);
                    Object i2 = innerCons.newInstance(o2);
                    assertNotNull(i2);

                    // The original Inner.get returns 3+10+20,
                    // but the delegate makes it return 6+10+20
                    assertEquals(6+10+20, callGet(i2, 10, 20));
                    assertEquals(3+10+20, callGet_Original(i2, 10, 20));
                }
            };
            cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray());
            cl2.add(INNER_CLASS_NAME, cwInner.toByteArray());
            cl2.testModifiedInstance();
        } catch (Throwable t) {
            throw dumpGeneratedClass(t, cl2);
        }
    
public voidtestDelegateNative()

        ClassWriter cw = new ClassWriter(0 /*flags*/);
        String internalClassName = NATIVE_CLASS_NAME.replace('.", '/");

        HashSet<String> delegateMethods = new HashSet<String>();
        delegateMethods.add(DelegateClassAdapter.ALL_NATIVES);
        DelegateClassAdapter cv = new DelegateClassAdapter(
                mLog, cw, internalClassName, delegateMethods);

        ClassReader cr = new ClassReader(NATIVE_CLASS_NAME);
        cr.accept(cv, 0 /* flags */);

        // Load the generated class in a different class loader and try it
        ClassLoader2 cl2 = null;
        try {
            cl2 = new ClassLoader2() {
                @Override
                public void testModifiedInstance() throws Exception {
                    Class<?> clazz2 = loadClass(NATIVE_CLASS_NAME);
                    Object i2 = clazz2.newInstance();
                    assertNotNull(i2);

                    // Use reflection to access inner methods
                    assertEquals(42, callAdd(i2, 20, 22));

                     Object[] objResult = new Object[] { null };
                     int result = callCallNativeInstance(i2, 10, 3.1415, objResult);
                     assertEquals((int)(10 + 3.1415), result);
                     assertSame(i2, objResult[0]);

                     // Check that the native method now has the new annotation and is not native
                     Method[] m = clazz2.getDeclaredMethods();
                     Method nativeInstanceMethod = null;
                     for (Method method : m) {
                         if ("native_instance".equals(method.getName())) {
                             nativeInstanceMethod = method;
                             break;
                         }
                     }
                     assertNotNull(nativeInstanceMethod);
                     assertFalse(Modifier.isNative(nativeInstanceMethod.getModifiers()));
                     Annotation[] a = nativeInstanceMethod.getAnnotations();
                     assertEquals("LayoutlibDelegate", a[0].annotationType().getSimpleName());
                }
            };
            cl2.add(NATIVE_CLASS_NAME, cw);
            cl2.testModifiedInstance();
        } catch (Throwable t) {
            throw dumpGeneratedClass(t, cl2);
        }
    
public voidtestNoOp()
Tests that a class not being modified still works.

        // create an instance of the class that will be modified
        // (load the class in a distinct class loader so that we can trash its definition later)
        ClassLoader cl1 = new ClassLoader(this.getClass().getClassLoader()) { };
        Class<ClassWithNative> clazz1 = (Class<ClassWithNative>) cl1.loadClass(NATIVE_CLASS_NAME);
        ClassWithNative instance1 = clazz1.newInstance();
        assertEquals(42, instance1.add(20, 22));
        try {
            instance1.callNativeInstance(10, 3.1415, new Object[0] );
            fail("Test should have failed to invoke callTheNativeMethod [1]");
        } catch (UnsatisfiedLinkError e) {
            // This is expected to fail since the native method is not implemented.
        }

        // Now process it but tell the delegate to not modify any method
        ClassWriter cw = new ClassWriter(0 /*flags*/);

        HashSet<String> delegateMethods = new HashSet<String>();
        String internalClassName = NATIVE_CLASS_NAME.replace('.", '/");
        DelegateClassAdapter cv = new DelegateClassAdapter(
                mLog, cw, internalClassName, delegateMethods);

        ClassReader cr = new ClassReader(NATIVE_CLASS_NAME);
        cr.accept(cv, 0 /* flags */);

        // Load the generated class in a different class loader and try it again

        ClassLoader2 cl2 = null;
        try {
            cl2 = new ClassLoader2() {
                @Override
                public void testModifiedInstance() throws Exception {
                    Class<?> clazz2 = loadClass(NATIVE_CLASS_NAME);
                    Object i2 = clazz2.newInstance();
                    assertNotNull(i2);
                    assertEquals(42, callAdd(i2, 20, 22));

                    try {
                        callCallNativeInstance(i2, 10, 3.1415, new Object[0]);
                        fail("Test should have failed to invoke callTheNativeMethod [2]");
                    } catch (InvocationTargetException e) {
                        // This is expected to fail since the native method has NOT been
                        // overridden here.
                        assertEquals(UnsatisfiedLinkError.class, e.getCause().getClass());
                    }

                    // Check that the native method does NOT have the new annotation
                    Method[] m = clazz2.getDeclaredMethods();
                    Method nativeInstanceMethod = null;
                    for (Method method : m) {
                        if ("native_instance".equals(method.getName())) {
                            nativeInstanceMethod = method;
                            break;
                        }
                    }
                    assertNotNull(nativeInstanceMethod);
                    assertTrue(Modifier.isNative(nativeInstanceMethod.getModifiers()));
                    Annotation[] a = nativeInstanceMethod.getAnnotations();
                    assertEquals(0, a.length);
                }
            };
            cl2.add(NATIVE_CLASS_NAME, cw);
            cl2.testModifiedInstance();
        } catch (Throwable t) {
            throw dumpGeneratedClass(t, cl2);
        }