layoutlib_create: Generate delegate to implement native methods.

- Some new parameters are added to CreateInfo with the list of methods
  or classes to override with delegates.
- DelegateClassAdapter and DelegateMethodAdapter do the work... see javadoc.

Change-Id: I0657cd929837181d81c65db7051d8ccbdc59c741
diff --git a/tools/layoutlib/create/README.txt b/tools/layoutlib/create/README.txt
index c59e20d..65a64cd 100644
--- a/tools/layoutlib/create/README.txt
+++ b/tools/layoutlib/create/README.txt
@@ -195,5 +195,22 @@
 bridge will provide its own implementation.
 
 
+- References -
+--------------
+
+
+The JVM Specification 2nd edition:
+  http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html
+
+Understanding bytecode:
+  http://www.ibm.com/developerworks/ibm/library/it-haggar_bytecode/
+
+Bytecode opcode list:
+  http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings
+
+ASM user guide:
+  http://download.forge.objectweb.org/asm/asm-guide.pdf
+
+
 --
 end
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java
new file mode 100644
index 0000000..9a48ea6
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/annotations/LayoutlibDelegate.java
@@ -0,0 +1,27 @@
+/*
+ * 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.annotations;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Denotes a method that has been converted to a delegate by layoutlib_create.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+public @interface LayoutlibDelegate {
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
index 7b55ed3e..590923f 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
@@ -28,9 +28,9 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
+import java.util.Map.Entry;
 import java.util.Set;
 import java.util.TreeMap;
-import java.util.Map.Entry;
 import java.util.jar.JarEntry;
 import java.util.jar.JarOutputStream;
 
@@ -60,38 +60,55 @@
      *  old-FQCN to rename and they get erased as they get renamed. At the end, classes still
      *  left here are not in the code base anymore and thus were not renamed. */
     private HashSet<String> mClassesNotRenamed;
-    /** A map { FQCN => map { list of return types to delete from the FQCN } }. */
+    /** A map { FQCN => set { list of return types to delete from the FQCN } }. */
     private HashMap<String, Set<String>> mDeleteReturns;
+    /** A map { FQCN => set { method names } } of methods to rewrite as delegates.
+     *  The special name {@link DelegateClassAdapter#ALL_NATIVES} can be used as in internal set. */
+    private final HashMap<String, Set<String>> mDelegateMethods;
 
     /**
      * Creates a new generator that can generate the output JAR with the stubbed classes.
-     * 
+     *
      * @param log Output logger.
      * @param osDestJar The path of the destination JAR to create.
-     * @param injectClasses The list of class from layoutlib_create to inject in layoutlib.
-     * @param stubMethods The list of methods to stub out. Each entry must be in the form
-     *          "package.package.OuterClass$InnerClass#MethodName".
-     * @param renameClasses The list of classes to rename, must be an even list: the binary FQCN
-     *          of class to replace followed by the new FQCN.
-     * @param deleteReturns List of classes for which the methods returning them should be deleted.
-     * The array contains a list of null terminated section starting with the name of the class
-     * to rename in which the methods are deleted, followed by a list of return types identifying
-     * the methods to delete.
+     * @param createInfo Creation parameters. Must not be null.
      */
-    public AsmGenerator(Log log, String osDestJar,
-            Class<?>[] injectClasses,
-            String[] stubMethods,
-            String[] renameClasses, String[] deleteReturns) {
+    public AsmGenerator(Log log, String osDestJar, ICreateInfo createInfo) {
         mLog = log;
         mOsDestJar = osDestJar;
-        mInjectClasses = injectClasses != null ? injectClasses : new Class<?>[0];
-        mStubMethods = stubMethods != null ? new HashSet<String>(Arrays.asList(stubMethods)) :
-                                             new HashSet<String>();
+        mInjectClasses = createInfo.getInjectedClasses();
+        mStubMethods = new HashSet<String>(Arrays.asList(createInfo.getOverriddenMethods()));
+
+        // Create the map/set of methods to change to delegates
+        mDelegateMethods = new HashMap<String, Set<String>>();
+        for (String signature : createInfo.getDelegateMethods()) {
+            int pos = signature.indexOf('#');
+            if (pos <= 0 || pos >= signature.length() - 1) {
+                continue;
+            }
+            String className = binaryToInternalClassName(signature.substring(0, pos));
+            String methodName = signature.substring(pos + 1);
+            Set<String> methods = mDelegateMethods.get(className);
+            if (methods == null) {
+                methods = new HashSet<String>();
+                mDelegateMethods.put(className, methods);
+            }
+            methods.add(methodName);
+        }
+        for (String className : createInfo.getDelegateClassNatives()) {
+            Set<String> methods = mDelegateMethods.get(className);
+            if (methods == null) {
+                methods = new HashSet<String>();
+                mDelegateMethods.put(className, methods);
+            }
+            methods.add(DelegateClassAdapter.ALL_NATIVES);
+        }
 
         // Create the map of classes to rename.
         mRenameClasses = new HashMap<String, String>();
         mClassesNotRenamed = new HashSet<String>();
-        int n = renameClasses == null ? 0 : renameClasses.length;
+        String[] renameClasses = createInfo.getRenamedClasses();
+        int n = renameClasses.length;
         for (int i = 0; i < n; i += 2) {
             assert i + 1 < n;
             // The ASM class names uses "/" separators, whereas regular FQCN use "."
@@ -100,38 +117,37 @@
             mRenameClasses.put(oldFqcn, newFqcn);
             mClassesNotRenamed.add(oldFqcn);
         }
-        
+
         // create the map of renamed class -> return type of method to delete.
         mDeleteReturns = new HashMap<String, Set<String>>();
-        if (deleteReturns != null) {
-            Set<String> returnTypes = null;
-            String renamedClass = null;
-            for (String className : deleteReturns) {
-                // if we reach the end of a section, add it to the main map
-                if (className == null) {
-                    if (returnTypes != null) {
-                        mDeleteReturns.put(renamedClass, returnTypes);
-                    }
-                    
-                    renamedClass = null;
-                    continue;
+        String[] deleteReturns = createInfo.getDeleteReturns();
+        Set<String> returnTypes = null;
+        String renamedClass = null;
+        for (String className : deleteReturns) {
+            // if we reach the end of a section, add it to the main map
+            if (className == null) {
+                if (returnTypes != null) {
+                    mDeleteReturns.put(renamedClass, returnTypes);
                 }
-    
-                // if the renamed class is null, this is the beginning of a section
-                if (renamedClass == null) {
-                    renamedClass = binaryToInternalClassName(className);
-                    continue;
-                }
-    
-                // just a standard return type, we add it to the list.
-                if (returnTypes == null) {
-                    returnTypes = new HashSet<String>();
-                }
-                returnTypes.add(binaryToInternalClassName(className));
+
+                renamedClass = null;
+                continue;
             }
+
+            // if the renamed class is null, this is the beginning of a section
+            if (renamedClass == null) {
+                renamedClass = binaryToInternalClassName(className);
+                continue;
+            }
+
+            // just a standard return type, we add it to the list.
+            if (returnTypes == null) {
+                returnTypes = new HashSet<String>();
+            }
+            returnTypes.add(binaryToInternalClassName(className));
         }
     }
-    
+
     /**
      * Returns the list of classes that have not been renamed yet.
      * <p/>
@@ -163,12 +179,12 @@
     public void setDeps(Map<String, ClassReader> deps) {
         mDeps = deps;
     }
-    
+
     /** Gets the map of classes to output as-is, except if they have native methods */
     public Map<String, ClassReader> getKeep() {
         return mKeep;
     }
-    
+
     /** Gets the map of dependencies that must be completely stubbed */
     public Map<String, ClassReader> getDeps() {
         return mDeps;
@@ -177,7 +193,7 @@
     /** Generates the final JAR */
     public void generate() throws FileNotFoundException, IOException {
         TreeMap<String, byte[]> all = new TreeMap<String, byte[]>();
-        
+
         for (Class<?> clazz : mInjectClasses) {
             String name = classToEntryPath(clazz);
             InputStream is = ClassLoader.getSystemResourceAsStream(name);
@@ -186,7 +202,7 @@
             name = classNameToEntryPath(transformName(cr.getClassName()));
             all.put(name, b);
         }
-        
+
         for (Entry<String, ClassReader> entry : mDeps.entrySet()) {
             ClassReader cr = entry.getValue();
             byte[] b = transform(cr, true /* stubNativesOnly */);
@@ -211,8 +227,8 @@
 
     /**
      * Writes the JAR file.
-     * 
-     * @param outStream The file output stream were to write the JAR. 
+     *
+     * @param outStream The file output stream were to write the JAR.
      * @param all The map of all classes to output.
      * @throws IOException if an I/O error has occurred
      */
@@ -236,7 +252,7 @@
     String classNameToEntryPath(String className) {
         return className.replaceAll("\\.", "/").concat(".class");
     }
-    
+
     /**
      * Utility method to get the JAR entry path from a Class name.
      * e.g. it returns someting like "com/foo/OuterClass$InnerClass1$InnerClass2.class"
@@ -248,30 +264,32 @@
             name = "$" + clazz.getSimpleName() + name;
             clazz = parent;
         }
-        return classNameToEntryPath(clazz.getCanonicalName() + name);        
+        return classNameToEntryPath(clazz.getCanonicalName() + name);
     }
 
     /**
      * Transforms a class.
      * <p/>
      * There are 3 kind of transformations:
-     * 
+     *
      * 1- For "mock" dependencies classes, we want to remove all code from methods and replace
      * by a stub. Native methods must be implemented with this stub too. Abstract methods are
      * left intact. Modified classes must be overridable (non-private, non-final).
      * Native methods must be made non-final, non-private.
-     * 
+     *
      * 2- For "keep" classes, we want to rewrite all native methods as indicated above.
      * If a class has native methods, it must also be made non-private, non-final.
-     * 
+     *
      * Note that unfortunately static methods cannot be changed to non-static (since static and
      * non-static are invoked differently.)
      */
     byte[] transform(ClassReader cr, boolean stubNativesOnly) {
 
         boolean hasNativeMethods = hasNativeMethods(cr);
+
+        // Get the class name, as an internal name (e.g. com/android/SomeClass$InnerClass)
         String className = cr.getClassName();
-        
+
         String newName = transformName(className);
         // transformName returns its input argument if there's no need to rename the class
         if (newName != className) {
@@ -288,13 +306,24 @@
         // Rewrite the new class from scratch, without reusing the constant pool from the
         // original class reader.
         ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
-        
+
         ClassVisitor rv = cw;
         if (newName != className) {
             rv = new RenameClassAdapter(cw, className, newName);
         }
-        
-        TransformClassAdapter cv = new TransformClassAdapter(mLog, mStubMethods, 
+
+        Set<String> delegateMethods = mDelegateMethods.get(className);
+        if (delegateMethods != null && !delegateMethods.isEmpty()) {
+            // If delegateMethods only contains one entry ALL_NATIVES and the class is
+            // known to have no native methods, just skip this step.
+            if (hasNativeMethods ||
+                    !(delegateMethods.size() == 1 &&
+                            delegateMethods.contains(DelegateClassAdapter.ALL_NATIVES))) {
+                rv = new DelegateClassAdapter(mLog, rv, className, delegateMethods);
+            }
+        }
+
+        TransformClassAdapter cv = new TransformClassAdapter(mLog, mStubMethods,
                 mDeleteReturns.get(className),
                 newName, rv,
                 stubNativesOnly, stubNativesOnly || hasNativeMethods);
@@ -323,7 +352,7 @@
                 return newName + className.substring(pos);
             }
         }
-        
+
         return className;
     }
 
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
index 2ed8641..92892784 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java
@@ -16,11 +16,70 @@
 
 package com.android.tools.layoutlib.create;
 
-public class CreateInfo {
+/**
+ * Describes the work to be done by {@link AsmGenerator}.
+ */
+public final class CreateInfo implements ICreateInfo {
+
+    /**
+     * Returns the list of class from layoutlib_create to inject in layoutlib.
+     * The list can be empty but must not be null.
+     */
+    public Class<?>[] getInjectedClasses() {
+        return INJECTED_CLASSES;
+    }
+
+    /**
+     * Returns the list of methods to rewrite as delegates.
+     * The list can be empty but must not be null.
+     */
+    public String[] getDelegateMethods() {
+        return DELEGATE_METHODS;
+    }
+
+    /**
+     * Returns the list of classes on which to delegate all native methods.
+     * The list can be empty but must not be null.
+     */
+    public String[] getDelegateClassNatives() {
+        return DELEGATE_CLASS_NATIVES;
+    }
+
+    /**
+     * Returns The list of methods to stub out. Each entry must be in the form
+     * "package.package.OuterClass$InnerClass#MethodName".
+     * The list can be empty but must not be null.
+     */
+    public String[] getOverriddenMethods() {
+        return OVERRIDDEN_METHODS;
+    }
+
+    /**
+     * Returns the list of classes to rename, must be an even list: the binary FQCN
+     * of class to replace followed by the new FQCN.
+     * The list can be empty but must not be null.
+     */
+    public String[] getRenamedClasses() {
+        return RENAMED_CLASSES;
+    }
+
+    /**
+     * Returns the list of classes for which the methods returning them should be deleted.
+     * The array contains a list of null terminated section starting with the name of the class
+     * to rename in which the methods are deleted, followed by a list of return types identifying
+     * the methods to delete.
+     * The list can be empty but must not be null.
+     */
+    public String[] getDeleteReturns() {
+        return DELETE_RETURNS;
+    }
+
+    //-----
+
     /**
      * The list of class from layoutlib_create to inject in layoutlib.
      */
-    public final static Class<?>[] INJECTED_CLASSES = new Class<?>[] {
+    private final static Class<?>[] INJECTED_CLASSES = new Class<?>[] {
             OverrideMethod.class,
             MethodListener.class,
             MethodAdapter.class,
@@ -28,19 +87,37 @@
         };
 
     /**
+     * The list of methods to rewrite as delegates.
+     */
+    private final static String[] DELEGATE_METHODS = new String[] {
+        // TODO: comment out once DelegateClass is working
+        // "android.view.View#isInEditMode",
+        // "android.content.res.Resources$Theme#obtainStyledAttributes",
+    };
+
+    /**
+     * The list of classes on which to delegate all native methods.
+     */
+    private final static String[] DELEGATE_CLASS_NATIVES = new String[] {
+        // TODO: comment out once DelegateClass is working
+        // "android.graphics.Paint"
+    };
+
+    /**
      * The list of methods to stub out. Each entry must be in the form
      *  "package.package.OuterClass$InnerClass#MethodName".
      */
-    public final static String[] OVERRIDDEN_METHODS = new String[] {
-            "android.view.View#isInEditMode",
-            "android.content.res.Resources$Theme#obtainStyledAttributes",
-        };
+    private final static String[] OVERRIDDEN_METHODS = new String[] {
+        // TODO: remove once DelegateClass is working
+        "android.view.View#isInEditMode",
+        "android.content.res.Resources$Theme#obtainStyledAttributes",
+    };
 
     /**
      *  The list of classes to rename, must be an even list: the binary FQCN
      *  of class to replace followed by the new FQCN.
      */
-    public final static String[] RENAMED_CLASSES =
+    private final static String[] RENAMED_CLASSES =
         new String[] {
             "android.graphics.Bitmap",              "android.graphics._Original_Bitmap",
             "android.graphics.BitmapFactory",       "android.graphics._Original_BitmapFactory",
@@ -69,7 +146,7 @@
      * to rename in which the methods are deleted, followed by a list of return types identifying
      * the methods to delete.
      */
-    public final static String[] REMOVED_METHODS =
+    private final static String[] DELETE_RETURNS =
         new String[] {
             "android.graphics.Paint",       // class to delete methods from
             "android.graphics.Paint$Align", // list of type identifying methods to delete
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
new file mode 100644
index 0000000..9cba8a0
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java
@@ -0,0 +1,94 @@
+/*
+ * 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;
+
+import org.objectweb.asm.ClassAdapter;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+import java.util.Set;
+
+/**
+ * A {@link DelegateClassAdapter} can transform some methods from a class into
+ * delegates that defer the call to an associated delegate class.
+ * <p/>
+ * This is used to override specific methods and or all native methods in classes.
+ */
+public class DelegateClassAdapter extends ClassAdapter {
+
+    public final static String ALL_NATIVES = "<<all_natives>>";
+
+    private final String mClassName;
+    private final Set<String> mDelegateMethods;
+    private final Log mLog;
+
+    /**
+     * Creates a new {@link DelegateClassAdapter} that can transform some methods
+     * from a class into delegates that defer the call to an associated delegate class.
+     * <p/>
+     * This is used to override specific methods and or all native methods in classes.
+     *
+     * @param log The logger object. Must not be null.
+     * @param cv the class visitor to which this adapter must delegate calls.
+     * @param className The internal class name of the class to visit,
+     *          e.g. <code>com/android/SomeClass$InnerClass</code>.
+     * @param delegateMethods The set of method names to modify and/or the
+     *          special constant {@link #ALL_NATIVES} to convert all native methods.
+     */
+    public DelegateClassAdapter(Log log,
+            ClassVisitor cv,
+            String className,
+            Set<String> delegateMethods) {
+        super(cv);
+        mLog = log;
+        mClassName = className;
+        mDelegateMethods = delegateMethods;
+    }
+
+    //----------------------------------
+    // Methods from the ClassAdapter
+
+    @Override
+    public MethodVisitor visitMethod(int access, String name, String desc,
+            String signature, String[] exceptions) {
+
+        boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
+        boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
+
+        boolean useDelegate = (isNative && mDelegateMethods.contains(ALL_NATIVES)) ||
+                              mDelegateMethods.contains(name);
+
+        if (useDelegate) {
+            // remove native
+            access = access & ~Opcodes.ACC_NATIVE;
+        }
+
+        MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
+        if (useDelegate) {
+            DelegateMethodAdapter a = new DelegateMethodAdapter(mLog, mw, mClassName,
+                                                                name, desc, isStatic);
+            if (isNative) {
+                // A native has no code to visit, so we need to generate it directly.
+                a.generateCode();
+            } else {
+                return a;
+            }
+        }
+        return mw;
+    }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
new file mode 100644
index 0000000..21d6682
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2008 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;
+
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+/**
+ * This method adapter rewrites a method by discarding the original code and generating
+ * a call to a delegate. Original annotations are passed along unchanged.
+ * <p/>
+ * Calls are delegated to a class named <code>&lt;className&gt;_Delegate</code> with
+ * static methods matching the methods to be overridden here. The methods have the
+ * same return type. The argument type list is the same except the "this" reference is
+ * passed first for non-static methods.
+ * <p/>
+ * A new annotation is added.
+ * <p/>
+ * Note that native methods have, by definition, no code so there's nothing a visitor
+ * can visit. That means the caller must call {@link #generateCode()} directly for
+ * a native and use the visitor pattern for non-natives.
+ * <p/>
+ * Instances of this class are not re-usable. You need a new instance for each method.
+ */
+class DelegateMethodAdapter implements MethodVisitor {
+
+    /**
+     * Suffix added to delegate classes.
+     */
+    public static final String DELEGATE_SUFFIX = "_Delegate";
+
+    private static String CONSTRUCTOR = "<init>";
+    private static String CLASS_INIT = "<clinit>";
+
+    /** The parent method writer */
+    private MethodVisitor mParentVisitor;
+    /** Flag to output the first line number. */
+    private boolean mOutputFirstLineNumber = true;
+    /** The original method descriptor (return type + argument types.) */
+    private String mDesc;
+    /** True if the original method is static. */
+    private final boolean mIsStatic;
+    /** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */
+    private final String mClassName;
+    /** The method name. */
+    private final String mMethodName;
+    /** Logger object. */
+    private final Log mLog;
+    /** True if {@link #visitCode()} has been invoked. */
+    private boolean mVisitCodeCalled;
+
+    /**
+     * Creates a new {@link DelegateMethodAdapter} that will transform this method
+     * into a delegate call.
+     * <p/>
+     * See {@link DelegateMethodAdapter} for more details.
+     *
+     * @param log The logger object. Must not be null.
+     * @param mv the method visitor to which this adapter must delegate calls.
+     * @param className The internal class name of the class to visit,
+     *          e.g. <code>com/android/SomeClass$InnerClass</code>.
+     * @param methodName The simple name of the method.
+     * @param desc A method descriptor (c.f. {@link Type#getReturnType(String)} +
+     *          {@link Type#getArgumentTypes(String)})
+     * @param isStatic True if the method is declared static.
+     */
+    public DelegateMethodAdapter(Log log,
+            MethodVisitor mv,
+            String className,
+            String methodName,
+            String desc,
+            boolean isStatic) {
+        mLog = log;
+        mParentVisitor = mv;
+        mClassName = className;
+        mMethodName = methodName;
+        mDesc = desc;
+        mIsStatic = isStatic;
+
+        if (CONSTRUCTOR.equals(methodName) || CLASS_INIT.equals(methodName)) {
+            // We're going to simplify by not supporting constructors.
+            // The only trick with a constructor is to find the proper super constructor
+            // and call it (and deciding if we should mirror the original method call to
+            // a custom constructor or call a default one.)
+            throw new UnsupportedOperationException(
+                    String.format("Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)",
+                            className, methodName, desc));
+        }
+    }
+
+    /**
+     * Generates the new code for the method.
+     * <p/>
+     * For native methods, this must be invoked directly by {@link DelegateClassAdapter}
+     * (since they have no code to visit).
+     * <p/>
+     * Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to
+     * return this instance of {@link DelegateMethodAdapter} and let the normal visitor pattern
+     * invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then
+     * this method will be invoked from {@link MethodVisitor#visitEnd()}.
+     */
+    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.
+         * All the parameters must be passed and then the eventual return type returned.
+         *
+         * Example, let's say we have a method such as
+         *   public void method_1(int a, Object b, ArrayList<String> c) { ... }
+         *
+         * We'll want to create a body that calls a delegate method like this:
+         *   TheClass_Delegate.method_1(this, a, b, c);
+         *
+         * 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.
+         */
+
+        // Add our annotation
+        AnnotationVisitor aw = mParentVisitor.visitAnnotation(
+                Type.getObjectType(Type.getInternalName(LayoutlibDelegate.class)).toString(),
+                true); // visible at runtime
+        aw.visitEnd();
+
+        if (!mVisitCodeCalled) {
+            // If this is a direct call to generateCode() as done by DelegateClassAdapter
+            // for natives, visitCode() hasn't been called yet.
+            mParentVisitor.visitCode();
+            mVisitCodeCalled = true;
+        }
+
+        int numVars = 0;
+
+        // Push "this" for an instance method, which is always ALOAD 0
+        if (!mIsStatic) {
+            mParentVisitor.visitVarInsn(Opcodes.ALOAD, numVars++);
+        }
+
+        // Push all other arguments
+        Type[] argTypes = Type.getArgumentTypes(mDesc);
+        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 && argTypes.length > 0) {
+            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);
+        }
+
+        String delegateClassName = mClassName + DELEGATE_SUFFIX;
+
+        // Invoke the static delegate
+        mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
+                delegateClassName,
+                mMethodName,
+                desc);
+
+        Type returnType = Type.getReturnType(mDesc);
+        mParentVisitor.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
+
+        mParentVisitor.visitMaxs(numVars, numVars);
+        mParentVisitor.visitEnd();
+
+        // For debugging now. Maybe we should collect these and store them in
+        // a text file for helping create the delegates. We could also compare
+        // the text file to a golden and break the build on unsupported changes
+        // or regressions. Even better we could fancy-print something that looks
+        // like the expected Java method declaration.
+        mLog.debug("Delegate: %1$s # %2$s %3$s", delegateClassName, mMethodName, desc);
+    }
+
+    /* Pass down to visitor writer. In this implementation, either do nothing. */
+    public void visitCode() {
+        mVisitCodeCalled = true;
+        mParentVisitor.visitCode();
+    }
+
+    /*
+     * visitMaxs is called just before visitEnd if there was any code to rewrite.
+     * Skip the original.
+     */
+    public void visitMaxs(int maxStack, int maxLocals) {
+    }
+
+    /**
+     * End of visiting. Generate the messaging code.
+     */
+    public void visitEnd() {
+        generateCode();
+    }
+
+    /* Writes all annotation from the original method. */
+    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
+        return mParentVisitor.visitAnnotation(desc, visible);
+    }
+
+    /* Writes all annotation default values from the original method. */
+    public AnnotationVisitor visitAnnotationDefault() {
+        return mParentVisitor.visitAnnotationDefault();
+    }
+
+    public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
+            boolean visible) {
+        return mParentVisitor.visitParameterAnnotation(parameter, desc, visible);
+    }
+
+    /* Writes all attributes from the original method. */
+    public void visitAttribute(Attribute attr) {
+        mParentVisitor.visitAttribute(attr);
+    }
+
+    /*
+     * Only writes the first line number present in the original code so that source
+     * viewers can direct to the correct method, even if the content doesn't match.
+     */
+    public void visitLineNumber(int line, Label start) {
+        if (mOutputFirstLineNumber) {
+            mParentVisitor.visitLineNumber(line, start);
+            mOutputFirstLineNumber = false;
+        }
+    }
+
+    public void visitInsn(int opcode) {
+        // Skip original code.
+    }
+
+    public void visitLabel(Label label) {
+        // Skip original code.
+    }
+
+    public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
+        // Skip original code.
+    }
+
+    public void visitMethodInsn(int opcode, String owner, String name, String desc) {
+        // Skip original code.
+    }
+
+    public void visitFieldInsn(int opcode, String owner, String name, String desc) {
+        // Skip original code.
+    }
+
+    public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
+        // Skip original code.
+    }
+
+    public void visitIincInsn(int var, int increment) {
+        // Skip original code.
+    }
+
+    public void visitIntInsn(int opcode, int operand) {
+        // Skip original code.
+    }
+
+    public void visitJumpInsn(int opcode, Label label) {
+        // Skip original code.
+    }
+
+    public void visitLdcInsn(Object cst) {
+        // Skip original code.
+    }
+
+    public void visitLocalVariable(String name, String desc, String signature,
+            Label start, Label end, int index) {
+        // Skip original code.
+    }
+
+    public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
+        // Skip original code.
+    }
+
+    public void visitMultiANewArrayInsn(String desc, int dims) {
+        // Skip original code.
+    }
+
+    public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
+        // Skip original code.
+    }
+
+    public void visitTypeInsn(int opcode, String type) {
+        // Skip original code.
+    }
+
+    public void visitVarInsn(int opcode, int var) {
+        // Skip original code.
+    }
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
new file mode 100644
index 0000000..40c1706
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ICreateInfo.java
@@ -0,0 +1,65 @@
+/*
+ * 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;
+
+/**
+ * Interface describing the work to be done by {@link AsmGenerator}.
+ */
+public interface ICreateInfo {
+
+    /**
+     * Returns the list of class from layoutlib_create to inject in layoutlib.
+     * The list can be empty but must not be null.
+     */
+    public abstract Class<?>[] getInjectedClasses();
+
+    /**
+     * Returns the list of methods to rewrite as delegates.
+     * The list can be empty but must not be null.
+     */
+    public abstract String[] getDelegateMethods();
+
+    /**
+     * Returns the list of classes on which to delegate all native methods.
+     * The list can be empty but must not be null.
+     */
+    public abstract String[] getDelegateClassNatives();
+
+    /**
+     * Returns The list of methods to stub out. Each entry must be in the form
+     * "package.package.OuterClass$InnerClass#MethodName".
+     * The list can be empty but must not be null.
+     */
+    public abstract String[] getOverriddenMethods();
+
+    /**
+     * Returns the list of classes to rename, must be an even list: the binary FQCN
+     * of class to replace followed by the new FQCN.
+     * The list can be empty but must not be null.
+     */
+    public abstract String[] getRenamedClasses();
+
+    /**
+     * Returns the list of classes for which the methods returning them should be deleted.
+     * The array contains a list of null terminated section starting with the name of the class
+     * to rename in which the methods are deleted, followed by a list of return types identifying
+     * the methods to delete.
+     * The list can be empty but must not be null.
+     */
+    public abstract String[] getDeleteReturns();
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
index 303f097..4adaff9 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
@@ -21,7 +21,28 @@
 import java.util.Set;
 
 
-
+/**
+ * Entry point for the layoutlib_create tool.
+ * <p/>
+ * The tool does not currently rely on any external configuration file.
+ * Instead the configuration is mostly done via the {@link CreateInfo} class.
+ * <p/>
+ * For a complete description of the tool and its implementation, please refer to
+ * the "README.txt" file at the root of this project.
+ * <p/>
+ * For a quick test, invoke this as follows:
+ * <pre>
+ * $ make layoutlib
+ * </pre>
+ * which does:
+ * <pre>
+ * $ make layoutlib_create &lt;bunch of framework jars&gt;
+ * $ out/host/linux-x86/framework/bin/layoutlib_create \
+ *        out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar \
+ *        out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar \
+ *        out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar
+ * </pre>
+ */
 public class Main {
 
     public static void main(String[] args) {
@@ -42,12 +63,7 @@
         }
 
         try {
-            AsmGenerator agen = new AsmGenerator(log, osDestJar[0],
-                    CreateInfo.INJECTED_CLASSES,
-                    CreateInfo.OVERRIDDEN_METHODS,
-                    CreateInfo.RENAMED_CLASSES,
-                    CreateInfo.REMOVED_METHODS
-            );
+            AsmGenerator agen = new AsmGenerator(log, osDestJar[0], new CreateInfo());
 
             AsmAnalyzer aa = new AsmAnalyzer(log, osJarPath, agen,
                     new String[] { "android.view.View" },   // derived from
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
index e294d56..f2d9755 100644
--- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
@@ -26,7 +26,7 @@
 import java.util.Set;
 
 /**
- * Class adapter that can stub some or all of the methods of the class. 
+ * Class adapter that can stub some or all of the methods of the class.
  */
 class TransformClassAdapter extends ClassAdapter {
 
@@ -41,12 +41,12 @@
 
     /**
      * Creates a new class adapter that will stub some or all methods.
-     * @param logger 
-     * @param stubMethods 
+     * @param logger
+     * @param stubMethods  list of method signatures to always stub out
      * @param deleteReturns list of types that trigger the deletion of methods returning them.
      * @param className The name of the class being modified
      * @param cv The parent class writer visitor
-     * @param stubNativesOnly True if only native methods should be stubbed. False if all 
+     * @param stubNativesOnly True if only native methods should be stubbed. False if all
      *                        methods should be stubbed.
      * @param hasNative True if the method has natives, in which case its access should be
      *                  changed.
@@ -67,10 +67,10 @@
     @Override
     public void visit(int version, int access, String name,
             String signature, String superName, String[] interfaces) {
-        
+
         // This class might be being renamed.
         name = mClassName;
-        
+
         // remove protected or private and set as public
         access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
         access |= Opcodes.ACC_PUBLIC;
@@ -82,7 +82,7 @@
         mIsInterface = ((access & Opcodes.ACC_INTERFACE) != 0);
         super.visit(version, access, name, signature, superName, interfaces);
     }
-    
+
     /* Visits the header of an inner class. */
     @Override
     public void visitInnerClass(String name, String outerName, String innerName, int access) {
@@ -101,7 +101,7 @@
     @Override
     public MethodVisitor visitMethod(int access, String name, String desc,
             String signature, String[] exceptions) {
-        
+
         if (mDeleteReturns != null) {
             Type t = Type.getReturnType(desc);
             if (t.getSort() == Type.OBJECT) {
@@ -130,16 +130,16 @@
             (mStubAll ||
              (access & Opcodes.ACC_NATIVE) != 0) ||
              mStubMethods.contains(methodSignature)) {
-            
+
             boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
             boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
 
             // remove abstract, final and native
             access = access & ~(Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL | Opcodes.ACC_NATIVE);
-            
+
             String invokeSignature = methodSignature + desc;
             mLog.debug("  Stub: %s (%s)", invokeSignature, isNative ? "native" : "");
-            
+
             MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
             return new StubMethodAdapter(mw, name, returnType(desc), invokeSignature,
                     isStatic, isNative);
@@ -149,7 +149,7 @@
             return super.visitMethod(access, name, desc, signature, exceptions);
         }
     }
-    
+
     /* Visits a field. Makes it public. */
     @Override
     public FieldVisitor visitField(int access, String name, String desc, String signature,
@@ -157,7 +157,7 @@
         // change access to public
         access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
         access |= Opcodes.ACC_PUBLIC;
-        
+
         return super.visitField(access, name, desc, signature, value);
     }
 
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
index 603284e..d6dba6a 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
@@ -22,7 +22,6 @@
 import static org.junit.Assert.assertNotNull;
 
 import com.android.tools.layoutlib.create.AsmAnalyzer.DependencyVisitor;
-import com.android.tools.layoutlib.create.LogTest.MockLog;
 
 import org.junit.After;
 import org.junit.Before;
@@ -46,9 +45,9 @@
 
     @Before
     public void setUp() throws Exception {
-        mLog = new LogTest.MockLog();
+        mLog = new MockLog();
         URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
-        
+
         mOsJarPath = new ArrayList<String>();
         mOsJarPath.add(url.getFile());
 
@@ -69,9 +68,9 @@
                 "mock_android.dummy.InnerTest$DerivingClass",
                 "mock_android.dummy.InnerTest$MyGenerics1",
                 "mock_android.dummy.InnerTest$MyIntEnum",
-                "mock_android.dummy.InnerTest$MyStaticInnerClass",   
-                "mock_android.dummy.InnerTest$NotStaticInner1", 
-                "mock_android.dummy.InnerTest$NotStaticInner2",  
+                "mock_android.dummy.InnerTest$MyStaticInnerClass",
+                "mock_android.dummy.InnerTest$NotStaticInner1",
+                "mock_android.dummy.InnerTest$NotStaticInner2",
                 "mock_android.view.View",
                 "mock_android.view.ViewGroup",
                 "mock_android.view.ViewGroup$LayoutParams",
@@ -83,7 +82,7 @@
             },
             map.keySet().toArray());
     }
-    
+
     @Test
     public void testFindClass() throws IOException, LogAbortException {
         Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
@@ -91,7 +90,7 @@
 
         ClassReader cr = mAa.findClass("mock_android.view.ViewGroup$LayoutParams",
                 zipClasses, found);
-        
+
         assertNotNull(cr);
         assertEquals("mock_android/view/ViewGroup$LayoutParams", cr.getClassName());
         assertArrayEquals(new String[] { "mock_android.view.ViewGroup$LayoutParams" },
@@ -172,14 +171,14 @@
                 "mock_android.widget.TableLayout",
             },
             found.keySet().toArray());
-        
+
         for (String key : found.keySet()) {
             ClassReader value = found.get(key);
             assertNotNull("No value for " + key, value);
             assertEquals(key, AsmAnalyzer.classReaderToClassName(value));
         }
     }
-    
+
     @Test
     public void testDependencyVisitor() throws IOException, LogAbortException {
         Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
@@ -190,7 +189,7 @@
 
         ClassReader cr = mAa.findClass("mock_android.widget.TableLayout", zipClasses, keep);
         DependencyVisitor visitor = mAa.getVisitor(zipClasses, keep, new_keep, in_deps, out_deps);
-        
+
         // get first level dependencies
         cr.accept(visitor, 0 /* flags */);
 
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
index 7cdf79a..f4ff389 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
@@ -20,8 +20,6 @@
 
 import static org.junit.Assert.assertArrayEquals;
 
-import com.android.tools.layoutlib.create.LogTest.MockLog;
-
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -44,9 +42,9 @@
 
     @Before
     public void setUp() throws Exception {
-        mLog = new LogTest.MockLog();
+        mLog = new MockLog();
         URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
-        
+
         mOsJarPath = new ArrayList<String>();
         mOsJarPath.add(url.getFile());
 
@@ -65,16 +63,41 @@
 
     @Test
     public void testClassRenaming() throws IOException, LogAbortException {
-        
-        AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar,
-            null, // classes to inject in the final JAR
-            null,  // methods to force override
-            new String[] {  // classes to rename (so that we can replace them)
-                "mock_android.view.View", "mock_android.view._Original_View",
-                "not.an.actual.ClassName", "anoter.fake.NewClassName",
-            },
-            null // methods deleted from their return type.
-            );
+
+        ICreateInfo ci = new ICreateInfo() {
+            public Class<?>[] getInjectedClasses() {
+                // classes to inject in the final JAR
+                return new Class<?>[0];
+            }
+
+            public String[] getDelegateMethods() {
+                return new String[0];
+            }
+
+            public String[] getDelegateClassNatives() {
+                return new String[0];
+            }
+
+            public String[] getOverriddenMethods() {
+                // methods to force override
+                return new String[0];
+            }
+
+            public String[] getRenamedClasses() {
+                // classes to rename (so that we can replace them)
+                return new String[] {
+                        "mock_android.view.View", "mock_android.view._Original_View",
+                        "not.an.actual.ClassName", "anoter.fake.NewClassName",
+                };
+            }
+
+            public String[] getDeleteReturns() {
+                 // methods deleted from their return type.
+                return new String[0];
+            }
+        };
+
+        AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
 
         AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
                 null,                 // derived from
@@ -83,7 +106,7 @@
                 });
         aa.analyze();
         agen.generate();
-        
+
         Set<String> notRenamed = agen.getClassesNotRenamed();
         assertArrayEquals(new String[] { "not/an/actual/ClassName" }, notRenamed.toArray());
     }
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
index d6916ae..0135c40 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/ClassHasNativeVisitorTest.java
@@ -33,8 +33,9 @@
     @Test
     public void testHasNative() throws IOException {
         MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor();
-        ClassReader cr = new ClassReader(
-                "com.android.tools.layoutlib.create.ClassHasNativeVisitorTest$ClassWithNative");
+        String className =
+                this.getClass().getCanonicalName() + "$" + ClassWithNative.class.getSimpleName();
+        ClassReader cr = new ClassReader(className);
 
         cr.accept(cv, 0 /* flags */);
         assertArrayEquals(new String[] { "native_method" }, cv.getMethodsFound());
@@ -44,14 +45,17 @@
     @Test
     public void testHasNoNative() throws IOException {
         MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor();
-        ClassReader cr = new ClassReader(
-                "com.android.tools.layoutlib.create.ClassHasNativeVisitorTest$ClassWithoutNative");
+        String className =
+            this.getClass().getCanonicalName() + "$" + ClassWithoutNative.class.getSimpleName();
+        ClassReader cr = new ClassReader(className);
 
         cr.accept(cv, 0 /* flags */);
         assertArrayEquals(new String[0], cv.getMethodsFound());
         assertFalse(cv.hasNativeMethods());
     }
 
+    //-------
+
     /**
      * Overrides {@link ClassHasNativeVisitor} to collec the name of the native methods found.
      */
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
new file mode 100644
index 0000000..9ad2e6e
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java
@@ -0,0 +1,305 @@
+/*
+ * 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;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.util.TraceClassVisitor;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.HashSet;
+
+public class DelegateClassAdapterTest {
+
+    private MockLog mLog;
+
+    private static final String CLASS_NAME =
+        DelegateClassAdapterTest.class.getCanonicalName() + "$" +
+        ClassWithNative.class.getSimpleName();
+
+    @Before
+    public void setUp() throws Exception {
+        mLog = new MockLog();
+        mLog.setVerbose(true); // capture debug error too
+    }
+
+    /**
+     * Tests that a class not being modified still works.
+     */
+    @SuppressWarnings("unchecked")
+    @Test
+    public void testNoOp() throws Exception {
+        // 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);
+        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 = CLASS_NAME.replace('.', '/');
+        DelegateClassAdapter cv = new DelegateClassAdapter(
+                mLog, cw, internalClassName, delegateMethods);
+
+        ClassReader cr = new ClassReader(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));
+
+                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);
+            }
+        };
+        cl2.testModifiedInstance();
+    }
+
+    /**
+     * {@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.
+     */
+    @Test(expected=UnsupportedOperationException.class)
+    public void testConstructorsNotSupported() throws IOException {
+        ClassWriter cw = new ClassWriter(0 /*flags*/);
+
+        String internalClassName = 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);
+        cr.accept(cv, 0 /* flags */);
+    }
+
+    @Test
+    public void testDelegateNative() throws Exception {
+        ClassWriter cw = new ClassWriter(0 /*flags*/);
+        String internalClassName = 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);
+        cr.accept(cv, 0 /* flags */);
+
+        // Load the generated class in a different class loader and try it
+        final byte[] bytes = cw.toByteArray();
+
+        try {
+            ClassLoader2 cl2 = new ClassLoader2(bytes) {
+                @Override
+                public void testModifiedInstance() throws Exception {
+                    Class<?> clazz2 = loadClass(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();
+                     assertEquals("native_instance", m[2].getName());
+                     assertFalse(Modifier.isNative(m[2].getModifiers()));
+                     Annotation[] a = m[2].getAnnotations();
+                     assertEquals("LayoutlibDelegate", a[0].annotationType().getSimpleName());
+                }
+            };
+            cl2.testModifiedInstance();
+
+        } 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;
+        }
+    }
+
+    //-------
+
+    /**
+     * A class loader than can define and instantiate our dummy {@link ClassWithNative}.
+     * <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) {
+            super(null);
+            mClassWithNative = classWithNative;
+        }
+
+        @SuppressWarnings("unused")
+        @Override
+        protected Class<?> findClass(String name) throws ClassNotFoundException {
+            try {
+                return super.findClass(name);
+            } catch (ClassNotFoundException e) {
+
+                if (CLASS_NAME.equals(name)) {
+                    // Load the modified ClassWithNative from its bytes representation.
+                    return defineClass(CLASS_NAME, mClassWithNative, 0, mClassWithNative.length);
+                }
+
+                try {
+                    // Load everything else from the original definition into the new class loader.
+                    ClassReader cr = new ClassReader(name);
+                    ClassWriter cw = new ClassWriter(0);
+                    cr.accept(cw, 0);
+                    byte[] bytes = cw.toByteArray();
+                    return defineClass(name, bytes, 0, bytes.length);
+
+                } catch (IOException ioe) {
+                    throw new RuntimeException(ioe);
+                }
+            }
+        }
+
+        /**
+         * Accesses {@link ClassWithNative#add(int, int)} via reflection.
+         */
+        public int callAdd(Object instance, int a, int b) throws Exception {
+            Method m = instance.getClass().getMethod("add",
+                    new Class<?>[] { int.class, int.class });
+
+            Object result = m.invoke(instance, new Object[] { a, b });
+            return ((Integer) result).intValue();
+        }
+
+        /**
+         * Accesses {@link ClassWithNative#callNativeInstance(int, double, Object[])}
+         * via reflection.
+         */
+        public int callCallNativeInstance(Object instance, int a, double d, Object[] o)
+                throws Exception {
+            Method m = instance.getClass().getMethod("callNativeInstance",
+                    new Class<?>[] { int.class, double.class, Object[].class });
+
+            Object result = m.invoke(instance, new Object[] { a, d, o });
+            return ((Integer) result).intValue();
+        }
+
+        public abstract void testModifiedInstance() throws Exception;
+    }
+
+    /**
+     * Dummy test class with a native method.
+     * The native method is not defined and any attempt to invoke it will
+     * throw an {@link UnsatisfiedLinkError}.
+     */
+    public static class ClassWithNative {
+        public ClassWithNative() {
+        }
+
+        public int add(int a, int b) {
+            return a + b;
+        }
+
+        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);
+    }
+
+    /**
+     * 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/LogTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
index 3f13158..1a5f653 100644
--- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
@@ -24,33 +24,8 @@
 
 public class LogTest {
 
-    public static class MockLog extends Log {
-        StringBuilder mOut = new StringBuilder();
-        StringBuilder mErr = new StringBuilder();
-        
-        public String getOut() {
-            return mOut.toString();
-        }
-        
-        public String getErr() {
-            return mErr.toString();
-        }
-        
-        @Override
-        protected void outPrintln(String msg) {
-            mOut.append(msg);
-            mOut.append('\n');
-        }
-        
-        @Override
-        protected void errPrintln(String msg) {
-            mErr.append(msg);
-            mErr.append('\n');
-        }
-    }
-
     private MockLog mLog;
-    
+
     @Before
     public void setUp() throws Exception {
         mLog = new MockLog();
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java
new file mode 100644
index 0000000..de750a3
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/MockLog.java
@@ -0,0 +1,43 @@
+/*
+ * 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;
+
+
+public class MockLog extends Log {
+    StringBuilder mOut = new StringBuilder();
+    StringBuilder mErr = new StringBuilder();
+
+    public String getOut() {
+        return mOut.toString();
+    }
+
+    public String getErr() {
+        return mErr.toString();
+    }
+
+    @Override
+    protected void outPrintln(String msg) {
+        mOut.append(msg);
+        mOut.append('\n');
+    }
+
+    @Override
+    protected void errPrintln(String msg) {
+        mErr.append(msg);
+        mErr.append('\n');
+    }
+}