Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3cff14ca authored by Raphael's avatar Raphael Committed by Android (Google) Code Review
Browse files

Merge "layoutlib_create: Generate delegate to implement native methods."

parents b4152eda bc101806
Loading
Loading
Loading
Loading
+17 −0
Original line number Original line Diff line number Diff line
@@ -195,5 +195,22 @@ example, the inner class Paint$Style in the Paint class should be discarded and
bridge will provide its own implementation.
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
end
+27 −0
Original line number Original line Diff line number Diff line
/*
 * 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 {
}
+91 −62
Original line number Original line Diff line number Diff line
@@ -28,9 +28,9 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.HashSet;
import java.util.Map;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeMap;
import java.util.Map.Entry;
import java.util.jar.JarEntry;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
import java.util.jar.JarOutputStream;


@@ -60,38 +60,55 @@ public class AsmGenerator {
     *  old-FQCN to rename and they get erased as they get renamed. At the end, classes still
     *  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. */
     *  left here are not in the code base anymore and thus were not renamed. */
    private HashSet<String> mClassesNotRenamed;
    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;
    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.
     * Creates a new generator that can generate the output JAR with the stubbed classes.
     *
     *
     * @param log Output logger.
     * @param log Output logger.
     * @param osDestJar The path of the destination JAR to create.
     * @param osDestJar The path of the destination JAR to create.
     * @param injectClasses The list of class from layoutlib_create to inject in layoutlib.
     * @param createInfo Creation parameters. Must not be null.
     * @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.
     */
     */
    public AsmGenerator(Log log, String osDestJar,
    public AsmGenerator(Log log, String osDestJar, ICreateInfo createInfo) {
            Class<?>[] injectClasses,
            String[] stubMethods,
            String[] renameClasses, String[] deleteReturns) {
        mLog = log;
        mLog = log;
        mOsDestJar = osDestJar;
        mOsDestJar = osDestJar;
        mInjectClasses = injectClasses != null ? injectClasses : new Class<?>[0];
        mInjectClasses = createInfo.getInjectedClasses();
        mStubMethods = stubMethods != null ? new HashSet<String>(Arrays.asList(stubMethods)) :
        mStubMethods = new HashSet<String>(Arrays.asList(createInfo.getOverriddenMethods()));
                                             new HashSet<String>();

        // 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.
        // Create the map of classes to rename.
        mRenameClasses = new HashMap<String, String>();
        mRenameClasses = new HashMap<String, String>();
        mClassesNotRenamed = new HashSet<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) {
        for (int i = 0; i < n; i += 2) {
            assert i + 1 < n;
            assert i + 1 < n;
            // The ASM class names uses "/" separators, whereas regular FQCN use "."
            // The ASM class names uses "/" separators, whereas regular FQCN use "."
@@ -103,7 +120,7 @@ public class AsmGenerator {


        // create the map of renamed class -> return type of method to delete.
        // create the map of renamed class -> return type of method to delete.
        mDeleteReturns = new HashMap<String, Set<String>>();
        mDeleteReturns = new HashMap<String, Set<String>>();
        if (deleteReturns != null) {
        String[] deleteReturns = createInfo.getDeleteReturns();
        Set<String> returnTypes = null;
        Set<String> returnTypes = null;
        String renamedClass = null;
        String renamedClass = null;
        for (String className : deleteReturns) {
        for (String className : deleteReturns) {
@@ -130,7 +147,6 @@ public class AsmGenerator {
            returnTypes.add(binaryToInternalClassName(className));
            returnTypes.add(binaryToInternalClassName(className));
        }
        }
    }
    }
    }


    /**
    /**
     * Returns the list of classes that have not been renamed yet.
     * Returns the list of classes that have not been renamed yet.
@@ -270,6 +286,8 @@ public class AsmGenerator {
    byte[] transform(ClassReader cr, boolean stubNativesOnly) {
    byte[] transform(ClassReader cr, boolean stubNativesOnly) {


        boolean hasNativeMethods = hasNativeMethods(cr);
        boolean hasNativeMethods = hasNativeMethods(cr);

        // Get the class name, as an internal name (e.g. com/android/SomeClass$InnerClass)
        String className = cr.getClassName();
        String className = cr.getClassName();


        String newName = transformName(className);
        String newName = transformName(className);
@@ -294,6 +312,17 @@ public class AsmGenerator {
            rv = new RenameClassAdapter(cw, className, newName);
            rv = new RenameClassAdapter(cw, className, newName);
        }
        }


        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,
        TransformClassAdapter cv = new TransformClassAdapter(mLog, mStubMethods,
                mDeleteReturns.get(className),
                mDeleteReturns.get(className),
                newName, rv,
                newName, rv,
+85 −8
Original line number Original line Diff line number Diff line
@@ -16,22 +16,99 @@


package com.android.tools.layoutlib.create;
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.
     * 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,
            OverrideMethod.class,
            MethodListener.class,
            MethodListener.class,
            MethodAdapter.class,
            MethodAdapter.class,
            CreateInfo.class
            CreateInfo.class
        };
        };


    /**
     * 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
     * The list of methods to stub out. Each entry must be in the form
     *  "package.package.OuterClass$InnerClass#MethodName".
     *  "package.package.OuterClass$InnerClass#MethodName".
     */
     */
    public final static String[] OVERRIDDEN_METHODS = new String[] {
    private final static String[] OVERRIDDEN_METHODS = new String[] {
        // TODO: remove once DelegateClass is working
        "android.view.View#isInEditMode",
        "android.view.View#isInEditMode",
        "android.content.res.Resources$Theme#obtainStyledAttributes",
        "android.content.res.Resources$Theme#obtainStyledAttributes",
    };
    };
@@ -40,7 +117,7 @@ public class CreateInfo {
     *  The list of classes to rename, must be an even list: the binary FQCN
     *  The list of classes to rename, must be an even list: the binary FQCN
     *  of class to replace followed by the new FQCN.
     *  of class to replace followed by the new FQCN.
     */
     */
    public final static String[] RENAMED_CLASSES =
    private final static String[] RENAMED_CLASSES =
        new String[] {
        new String[] {
            "android.graphics.Bitmap",              "android.graphics._Original_Bitmap",
            "android.graphics.Bitmap",              "android.graphics._Original_Bitmap",
            "android.graphics.BitmapFactory",       "android.graphics._Original_BitmapFactory",
            "android.graphics.BitmapFactory",       "android.graphics._Original_BitmapFactory",
@@ -69,7 +146,7 @@ public class CreateInfo {
     * to rename in which the methods are deleted, followed by a list of return types identifying
     * to rename in which the methods are deleted, followed by a list of return types identifying
     * the methods to delete.
     * the methods to delete.
     */
     */
    public final static String[] REMOVED_METHODS =
    private final static String[] DELETE_RETURNS =
        new String[] {
        new String[] {
            "android.graphics.Paint",       // class to delete methods from
            "android.graphics.Paint",       // class to delete methods from
            "android.graphics.Paint$Align", // list of type identifying methods to delete
            "android.graphics.Paint$Align", // list of type identifying methods to delete
+94 −0
Original line number Original line Diff line number Diff line
/*
 * 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;
    }
}
Loading