OSDN Git Service

LayoutLib.Create: support Outer_Inner_Delegate renaming.
authorRaphael <raphael@google.com>
Fri, 28 Jan 2011 00:48:27 +0000 (16:48 -0800)
committerRaphael <raphael@google.com>
Mon, 31 Jan 2011 21:16:04 +0000 (13:16 -0800)
When generating delegates, LayoutLib.Create support renaming
inner classes. Only one level of inner class is supported.

The method Outer$Inner#get(...) generates a call to:
  static Outer_Inner_Delegate#get(Outer instance, Outer$Inner instance, ...)

Change-Id: Ie70f2b8e5e5f311ed9c7f26b7f64637ae6157a51

tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative.java [new file with mode: 0644]
tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative_Delegate.java [new file with mode: 0644]
tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java [new file with mode: 0644]
tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java [new file with mode: 0644]
tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_InnerClass_Delegate.java [new file with mode: 0644]

index c7968a4..8d7f016 100644 (file)
@@ -27,6 +27,8 @@ import org.objectweb.asm.MethodVisitor;
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 
+import java.util.ArrayList;
+
 /**
  * This method adapter rewrites a method by discarding the original code and generating
  * a call to a delegate. Original annotations are passed along unchanged.
@@ -124,7 +126,7 @@ class DelegateMethodAdapter implements MethodVisitor {
     public void generateCode() {
         /*
          * The goal is to generate a call to a static delegate method.
-         * If this method is not-static, the first parameter will be this.
+         * If this method is non-static, the first parameter will be 'this'.
          * All the parameters must be passed and then the eventual return type returned.
          *
          * Example, let's say we have a method such as
@@ -133,9 +135,19 @@ class DelegateMethodAdapter implements MethodVisitor {
          * We'll want to create a body that calls a delegate method like this:
          *   TheClass_Delegate.method_1(this, a, b, c);
          *
+         * If the method is non-static and the class name is an inner class (e.g. has $ in its
+         * last segment), we want to push the 'this' of the outer class first:
+         *   OuterClass_InnerClass_Delegate.method_1(
+         *     OuterClass.this,
+         *     OuterClass$InnerClass.this,
+         *     a, b, c);
+         *
+         * Only one level of inner class is supported right now, for simplicity and because
+         * we don't need more.
+         *
          * The generated class name is the current class name with "_Delegate" appended to it.
          * One thing to realize is that we don't care about generics -- since generic types
-         * are erased at runtime, they have no influence on the method being called.
+         * are erased at runtime, they have no influence on the method name being called.
          */
 
         // Add our annotation
@@ -151,34 +163,61 @@ class DelegateMethodAdapter implements MethodVisitor {
             mVisitCodeCalled = true;
         }
 
-        int numVars = 0;
+        ArrayList<Type> paramTypes = new ArrayList<Type>();
+        String delegateClassName = mClassName + DELEGATE_SUFFIX;
+        boolean pushedArg0 = false;
+        int maxStack = 0;
 
-        // Push "this" for an instance method, which is always ALOAD 0
+        // For an instance method (e.g. non-static), push the 'this' preceded
+        // by the 'this' of any outer class, if any.
         if (!mIsStatic) {
-            mParentVisitor.visitVarInsn(Opcodes.ALOAD, numVars++);
+            // Check if the last segment of the class name has inner an class.
+            // Right now we only support one level of inner classes.
+            int slash = mClassName.lastIndexOf('/');
+            int dol = mClassName.lastIndexOf('$');
+            if (dol != -1 && dol > slash && dol == mClassName.indexOf('$')) {
+                String outerClass = mClassName.substring(0, dol);
+                Type outerType = Type.getObjectType(outerClass);
+
+                // Change a delegate class name to "com/foo/Outer_Inner_Delegate"
+                delegateClassName = delegateClassName.replace('$', '_');
+
+                // The first-level inner class has a package-protected member called 'this$0'
+                // that points to the outer class.
+
+                // Push this.getField("this$0") on the call stack.
+                mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0); // var 0 = this
+                mParentVisitor.visitFieldInsn(Opcodes.GETFIELD,
+                        mClassName,                 // class where the field is defined
+                        "this$0",                   // field name
+                        outerType.getDescriptor()); // type of the field
+                maxStack++;
+                paramTypes.add(outerType);
+            }
+
+            // Push "this" for the instance method, which is always ALOAD 0
+            mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0);
+            maxStack++;
+            pushedArg0 = true;
+            paramTypes.add(Type.getObjectType(mClassName));
         }
 
-        // Push all other arguments
+        // Push all other arguments. Start at arg 1 if we already pushed 'this' above.
         Type[] argTypes = Type.getArgumentTypes(mDesc);
+        int maxLocals = pushedArg0 ? 1 : 0;
         for (Type t : argTypes) {
             int size = t.getSize();
-            mParentVisitor.visitVarInsn(t.getOpcode(Opcodes.ILOAD), numVars);
-            numVars += size;
-        }
-
-        // Construct the descriptor of the delegate. For a static method, it's the same
-        // however for an instance method we need to pass the 'this' reference first
-        String desc = mDesc;
-        if (!mIsStatic) {
-            Type[] argTypes2 = new Type[argTypes.length + 1];
-
-            argTypes2[0] = Type.getObjectType(mClassName);
-            System.arraycopy(argTypes, 0, argTypes2, 1, argTypes.length);
-
-            desc = Type.getMethodDescriptor(Type.getReturnType(mDesc), argTypes2);
+            mParentVisitor.visitVarInsn(t.getOpcode(Opcodes.ILOAD), maxLocals);
+            maxLocals += size;
+            maxStack += size;
+            paramTypes.add(t);
         }
 
-        String delegateClassName = mClassName + DELEGATE_SUFFIX;
+        // Construct the descriptor of the delegate based on the parameters
+        // we pushed on the call stack. The return type remains unchanged.
+        String desc = Type.getMethodDescriptor(
+                Type.getReturnType(mDesc),
+                paramTypes.toArray(new Type[paramTypes.size()]));
 
         // Invoke the static delegate
         mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
@@ -189,7 +228,7 @@ class DelegateMethodAdapter implements MethodVisitor {
         Type returnType = Type.getReturnType(mDesc);
         mParentVisitor.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
 
-        mParentVisitor.visitMaxs(numVars, numVars);
+        mParentVisitor.visitMaxs(maxStack, maxLocals);
         mParentVisitor.visitEnd();
 
         // For debugging now. Maybe we should collect these and store them in
index 7d80796..e8b3ea8 100644 (file)
@@ -24,25 +24,38 @@ import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import com.android.tools.layoutlib.create.dataclass.ClassWithNative;
+import com.android.tools.layoutlib.create.dataclass.OuterClass;
+import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
 import org.objectweb.asm.ClassWriter;
 
 import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
 
 public class DelegateClassAdapterTest {
 
     private MockLog mLog;
 
-    private static final String CLASS_NAME =
-        DelegateClassAdapterTest.class.getCanonicalName() + "$" +
-        ClassWithNative.class.getSimpleName();
+    private static final String NATIVE_CLASS_NAME = ClassWithNative.class.getCanonicalName();
+    private static final String OUTER_CLASS_NAME = OuterClass.class.getCanonicalName();
+    private static final String INNER_CLASS_NAME = OuterClass.class.getCanonicalName() + "$" +
+                                                   InnerClass.class.getSimpleName();
 
     @Before
     public void setUp() throws Exception {
@@ -55,11 +68,11 @@ public class DelegateClassAdapterTest {
      */
     @SuppressWarnings("unchecked")
     @Test
-    public void testNoOp() throws Exception {
+    public void testNoOp() throws Throwable {
         // 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(CLASS_NAME);
+        Class<ClassWithNative> clazz1 = (Class<ClassWithNative>) cl1.loadClass(NATIVE_CLASS_NAME);
         ClassWithNative instance1 = clazz1.newInstance();
         assertEquals(42, instance1.add(20, 22));
         try {
@@ -73,42 +86,47 @@ public class DelegateClassAdapterTest {
         ClassWriter cw = new ClassWriter(0 /*flags*/);
 
         HashSet<String> delegateMethods = new HashSet<String>();
-        String internalClassName = CLASS_NAME.replace('.', '/');
+        String internalClassName = NATIVE_CLASS_NAME.replace('.', '/');
         DelegateClassAdapter cv = new DelegateClassAdapter(
                 mLog, cw, internalClassName, delegateMethods);
 
-        ClassReader cr = new ClassReader(CLASS_NAME);
+        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
-        final byte[] bytes = cw.toByteArray();
 
-        ClassLoader2 cl2 = new ClassLoader2(bytes) {
-            @Override
-            public void testModifiedInstance() throws Exception {
-                Class<?> clazz2 = loadClass(CLASS_NAME);
-                Object i2 = clazz2.newInstance();
-                assertNotNull(i2);
-                assertEquals(42, callAdd(i2, 20, 22));
+        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());
+                    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();
+                    assertEquals("native_instance", m[2].getName());
+                    assertTrue(Modifier.isNative(m[2].getModifiers()));
+                    Annotation[] a = m[2].getAnnotations();
+                    assertEquals(0, a.length);
                 }
-
-                // Check that the native method does NOT have the new annotation
-                Method[] m = clazz2.getDeclaredMethods();
-                assertEquals("native_instance", m[2].getName());
-                assertTrue(Modifier.isNative(m[2].getModifiers()));
-                Annotation[] a = m[2].getAnnotations();
-                assertEquals(0, a.length);
-            }
-        };
-        cl2.testModifiedInstance();
+            };
+            cl2.add(NATIVE_CLASS_NAME, cw);
+            cl2.testModifiedInstance();
+        } catch (Throwable t) {
+            throw dumpGeneratedClass(t, cl2);
+        }
     }
 
     /**
@@ -122,38 +140,37 @@ public class DelegateClassAdapterTest {
     public void testConstructorsNotSupported() throws IOException {
         ClassWriter cw = new ClassWriter(0 /*flags*/);
 
-        String internalClassName = CLASS_NAME.replace('.', '/');
+        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(CLASS_NAME);
+        ClassReader cr = new ClassReader(NATIVE_CLASS_NAME);
         cr.accept(cv, 0 /* flags */);
     }
 
     @Test
-    public void testDelegateNative() throws Exception {
+    public void testDelegateNative() throws Throwable {
         ClassWriter cw = new ClassWriter(0 /*flags*/);
-        String internalClassName = CLASS_NAME.replace('.', '/');
+        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(CLASS_NAME);
+        ClassReader cr = new ClassReader(NATIVE_CLASS_NAME);
         cr.accept(cv, 0 /* flags */);
 
         // Load the generated class in a different class loader and try it
-        final byte[] bytes = cw.toByteArray();
-
+        ClassLoader2 cl2 = null;
         try {
-            ClassLoader2 cl2 = new ClassLoader2(bytes) {
+            cl2 = new ClassLoader2() {
                 @Override
                 public void testModifiedInstance() throws Exception {
-                    Class<?> clazz2 = loadClass(CLASS_NAME);
+                    Class<?> clazz2 = loadClass(NATIVE_CLASS_NAME);
                     Object i2 = clazz2.newInstance();
                     assertNotNull(i2);
 
@@ -173,48 +190,105 @@ public class DelegateClassAdapterTest {
                      assertEquals("LayoutlibDelegate", a[0].annotationType().getSimpleName());
                 }
             };
+            cl2.add(NATIVE_CLASS_NAME, cw);
             cl2.testModifiedInstance();
+        } catch (Throwable t) {
+            throw dumpGeneratedClass(t, cl2);
+        }
+    }
+
+    @Test
+    public void testDelegateInner() throws Throwable {
+        // We'll delegate the "get" method of both the inner and outer class.
+        HashSet<String> delegateMethods = new HashSet<String>();
+        delegateMethods.add("get");
+
+        // 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));
+
+                    // 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(
+                                                                new Class<?>[] { outerClazz2 });
+                    Object i2 = innerCons.newInstance(new Object[] { o2 });
+                    assertNotNull(i2);
 
-        // This code block is useful for debugging. 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.
-        //
-        // } catch (Throwable t) {
-        //     For debugging, dump the bytecode of the class in case of unexpected error.
-        //     StringWriter sw = new StringWriter();
-        //     PrintWriter pw = new PrintWriter(sw);
-        //     TraceClassVisitor tcv = new TraceClassVisitor(pw);
-        //     ClassReader cr2 = new ClassReader(bytes);
-        //     cr2.accept(tcv, 0 /* flags */);
-        //     String msg = "\n" + t.getClass().getCanonicalName();
-        //     if (t.getMessage() != null) {
-        //       msg += ": " + t.getMessage();
-        //     }
-        //     msg = msg + "\nBytecode dump:\n" + sw.toString();
-        //  // Re-throw exception with new message
-        //     RuntimeException ex = new RuntimeException(msg, t);
-        //     throw ex;
-        } finally {
+                    // 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));
+                }
+            };
+            cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray());
+            cl2.add(INNER_CLASS_NAME, cwInner.toByteArray());
+            cl2.testModifiedInstance();
+        } catch (Throwable t) {
+            throw dumpGeneratedClass(t, cl2);
         }
     }
 
     //-------
 
     /**
-     * A class loader than can define and instantiate our dummy {@link ClassWithNative}.
+     * A class loader than can define and instantiate our modified classes.
+     * <p/>
+     * The trick here is that this class loader will test our <em>modified</em> version
+     * of the classes, the one with the delegate calls.
      * <p/>
-     * The trick here is that this class loader will test our modified version of ClassWithNative.
      * Trying to do so in the original class loader generates all sort of link issues because
      * there are 2 different definitions of the same class name. This class loader will
      * define and load the class when requested by name and provide helpers to access the
      * instance methods via reflection.
      */
     private abstract class ClassLoader2 extends ClassLoader {
-        private final byte[] mClassWithNative;
 
-        public ClassLoader2(byte[] classWithNative) {
+        private final Map<String, byte[]> mClassDefs = new HashMap<String, byte[]>();
+
+        public ClassLoader2() {
             super(null);
-            mClassWithNative = classWithNative;
+        }
+
+        public ClassLoader2 add(String className, byte[] definition) {
+            mClassDefs.put(className, definition);
+            return this;
+        }
+
+        public ClassLoader2 add(String className, ClassWriter rewrittenClass) {
+            mClassDefs.put(className, rewrittenClass.toByteArray());
+            return this;
+        }
+
+        private Set<Entry<String, byte[]>> getByteCode() {
+            return mClassDefs.entrySet();
         }
 
         @SuppressWarnings("unused")
@@ -224,9 +298,10 @@ public class DelegateClassAdapterTest {
                 return super.findClass(name);
             } catch (ClassNotFoundException e) {
 
-                if (CLASS_NAME.equals(name)) {
+                byte[] def = mClassDefs.get(name);
+                if (def != null) {
                     // Load the modified ClassWithNative from its bytes representation.
-                    return defineClass(CLASS_NAME, mClassWithNative, 0, mClassWithNative.length);
+                    return defineClass(name, def, 0, def.length);
                 }
 
                 try {
@@ -244,6 +319,17 @@ public class DelegateClassAdapterTest {
         }
 
         /**
+         * Accesses {@link OuterClass#get()} or {@link InnerClass#get() }via reflection.
+         */
+        public int callGet(Object instance, int a, long b) throws Exception {
+            Method m = instance.getClass().getMethod("get",
+                    new Class<?>[] { int.class, long.class } );
+
+            Object result = m.invoke(instance, new Object[] { a, b });
+            return ((Integer) result).intValue();
+        }
+
+        /**
          * Accesses {@link ClassWithNative#add(int, int)} via reflection.
          */
         public int callAdd(Object instance, int a, int b) throws Exception {
@@ -271,34 +357,53 @@ public class DelegateClassAdapterTest {
     }
 
     /**
-     * Dummy test class with a native method.
-     * The native method is not defined and any attempt to invoke it will
-     * throw an {@link UnsatisfiedLinkError}.
+     * 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}
      */
-    public static class ClassWithNative {
-        public ClassWithNative() {
-        }
-
-        public int add(int a, int b) {
-            return a + b;
-        }
+    private Throwable dumpGeneratedClass(Throwable t, ClassLoader2 cl2) {
+        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(new Class<?>[] { pw.getClass() });
+                Object tcv = cons.newInstance(new Object[] { pw });
+                ClassReader cr2 = new ClassReader(bytes);
+                cr2.accept((ClassVisitor) tcv, 0 /* flags */);
+
+                sb.append("\nBytecode dump: <").append(className).append(">:\n")
+                  .append(sw.toString());
+            }
 
-        public int callNativeInstance(int a, double d, Object[] o) {
-            return native_instance(a, d, o);
+            // Re-throw exception with new message
+            RuntimeException ex = new RuntimeException(sb.toString(), t);
+            return ex;
+        } catch (Throwable ignore) {
+            // In case of problem, just throw the original exception as-is.
+            return t;
         }
-
-        private native int native_instance(int a, double d, Object[] o);
     }
 
-    /**
-     * The delegate that receives the call to {@link ClassWithNative}'s overridden methods.
-     */
-    public static class ClassWithNative_Delegate {
-        public static int native_instance(ClassWithNative instance, int a, double d, Object[] o) {
-            if (o != null && o.length > 0) {
-                o[0] = instance;
-            }
-            return (int)(a + d);
-        }
-    }
 }
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative.java
new file mode 100644 (file)
index 0000000..c314853
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create.dataclass;
+
+import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
+
+/**
+ * Dummy test class with a native method.
+ * The native method is not defined and any attempt to invoke it will
+ * throw an {@link UnsatisfiedLinkError}.
+ *
+ * Used by {@link DelegateClassAdapterTest}.
+ */
+public class ClassWithNative {
+    public ClassWithNative() {
+    }
+
+    public int add(int a, int b) {
+        return a + b;
+    }
+
+    // Note: it's good to have a long or double for testing parameters since they take
+    // 2 slots in the stack/locals maps.
+
+    public int callNativeInstance(int a, double d, Object[] o) {
+        return native_instance(a, d, o);
+    }
+
+    private native int native_instance(int a, double d, Object[] o);
+}
+
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/ClassWithNative_Delegate.java
new file mode 100644 (file)
index 0000000..a3d4dc6
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create.dataclass;
+
+import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
+
+/**
+ * The delegate that receives the call to {@link ClassWithNative_Delegate}'s overridden methods.
+ *
+ * Used by {@link DelegateClassAdapterTest}.
+ */
+public class ClassWithNative_Delegate {
+    public static int native_instance(ClassWithNative instance, int a, double d, Object[] o) {
+        if (o != null && o.length > 0) {
+            o[0] = instance;
+        }
+        return (int)(a + d);
+    }
+}
+
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java
new file mode 100644 (file)
index 0000000..9dc2f69
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create.dataclass;
+
+import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
+
+/**
+ * Test class with an inner class.
+ *
+ * Used by {@link DelegateClassAdapterTest}.
+ */
+public class OuterClass {
+    private int mOuterValue = 1;
+    public OuterClass() {
+    }
+
+    // Outer.get returns 1 + a + b
+    // Note: it's good to have a long or double for testing parameters since they take
+    // 2 slots in the stack/locals maps.
+    public int get(int a, long b) {
+        return mOuterValue + a + (int) b;
+    }
+
+    public class InnerClass {
+        public InnerClass() {
+        }
+
+        // Inner.get returns 1+2=3 + a + b
+        public int get(int a, long b) {
+            return 2 + mOuterValue + a + (int) b;
+        }
+    }
+}
+
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java
new file mode 100644 (file)
index 0000000..3252d87
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create.dataclass;
+
+import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
+
+/**
+ * Used by {@link DelegateClassAdapterTest}.
+ */
+public class OuterClass_Delegate {
+    // The delegate override of Outer.get returns 4 + a + b
+    public static int get(OuterClass instance, int a, long b) {
+        return 4 + a + (int) b;
+    }
+}
+
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_InnerClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_InnerClass_Delegate.java
new file mode 100644 (file)
index 0000000..b472220
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tools.layoutlib.create.dataclass;
+
+import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
+import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass;
+
+/**
+ * Used by {@link DelegateClassAdapterTest}.
+ */
+public class OuterClass_InnerClass_Delegate {
+    // The delegate override of Inner.get return 6 + a + b
+    public static int get(OuterClass outer, InnerClass inner, int a, long b) {
+        return 6 + a + (int) b;
+    }
+}