Home | History | Annotate | Download | only in create
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.tools.layoutlib.create;
     18 
     19 import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
     20 
     21 import org.objectweb.asm.AnnotationVisitor;
     22 import org.objectweb.asm.Attribute;
     23 import org.objectweb.asm.ClassReader;
     24 import org.objectweb.asm.ClassVisitor;
     25 import org.objectweb.asm.Label;
     26 import org.objectweb.asm.MethodVisitor;
     27 import org.objectweb.asm.Opcodes;
     28 import org.objectweb.asm.Type;
     29 
     30 import java.util.ArrayList;
     31 
     32 /**
     33  * This method adapter rewrites a method by discarding the original code and generating
     34  * a call to a delegate. Original annotations are passed along unchanged.
     35  * <p/>
     36  * Calls are delegated to a class named <code>&lt;className&gt;_Delegate</code> with
     37  * static methods matching the methods to be overridden here. The methods have the
     38  * same return type. The argument type list is the same except the "this" reference is
     39  * passed first for non-static methods.
     40  * <p/>
     41  * A new annotation is added.
     42  * <p/>
     43  * Note that native methods have, by definition, no code so there's nothing a visitor
     44  * can visit. That means the caller must call {@link #generateCode()} directly for
     45  * a native and use the visitor pattern for non-natives.
     46  * <p/>
     47  * Instances of this class are not re-usable. You need a new instance for each method.
     48  */
     49 class DelegateMethodAdapter implements MethodVisitor {
     50 
     51     /**
     52      * Suffix added to delegate classes.
     53      */
     54     public static final String DELEGATE_SUFFIX = "_Delegate";
     55 
     56     private static String CONSTRUCTOR = "<init>";
     57     private static String CLASS_INIT = "<clinit>";
     58 
     59     /** The parent method writer */
     60     private MethodVisitor mParentVisitor;
     61     /** Flag to output the first line number. */
     62     private boolean mOutputFirstLineNumber = true;
     63     /** The original method descriptor (return type + argument types.) */
     64     private String mDesc;
     65     /** True if the original method is static. */
     66     private final boolean mIsStatic;
     67     /** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */
     68     private final String mClassName;
     69     /** The method name. */
     70     private final String mMethodName;
     71     /** Logger object. */
     72     private final Log mLog;
     73     /** True if {@link #visitCode()} has been invoked. */
     74     private boolean mVisitCodeCalled;
     75 
     76     /**
     77      * Creates a new {@link DelegateMethodAdapter} that will transform this method
     78      * into a delegate call.
     79      * <p/>
     80      * See {@link DelegateMethodAdapter} for more details.
     81      *
     82      * @param log The logger object. Must not be null.
     83      * @param mv the method visitor to which this adapter must delegate calls.
     84      * @param className The internal class name of the class to visit,
     85      *          e.g. <code>com/android/SomeClass$InnerClass</code>.
     86      * @param methodName The simple name of the method.
     87      * @param desc A method descriptor (c.f. {@link Type#getReturnType(String)} +
     88      *          {@link Type#getArgumentTypes(String)})
     89      * @param isStatic True if the method is declared static.
     90      */
     91     public DelegateMethodAdapter(Log log,
     92             MethodVisitor mv,
     93             String className,
     94             String methodName,
     95             String desc,
     96             boolean isStatic) {
     97         mLog = log;
     98         mParentVisitor = mv;
     99         mClassName = className;
    100         mMethodName = methodName;
    101         mDesc = desc;
    102         mIsStatic = isStatic;
    103 
    104         if (CONSTRUCTOR.equals(methodName) || CLASS_INIT.equals(methodName)) {
    105             // We're going to simplify by not supporting constructors.
    106             // The only trick with a constructor is to find the proper super constructor
    107             // and call it (and deciding if we should mirror the original method call to
    108             // a custom constructor or call a default one.)
    109             throw new UnsupportedOperationException(
    110                     String.format("Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)",
    111                             className, methodName, desc));
    112         }
    113     }
    114 
    115     /**
    116      * Generates the new code for the method.
    117      * <p/>
    118      * For native methods, this must be invoked directly by {@link DelegateClassAdapter}
    119      * (since they have no code to visit).
    120      * <p/>
    121      * Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to
    122      * return this instance of {@link DelegateMethodAdapter} and let the normal visitor pattern
    123      * invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then
    124      * this method will be invoked from {@link MethodVisitor#visitEnd()}.
    125      */
    126     public void generateCode() {
    127         /*
    128          * The goal is to generate a call to a static delegate method.
    129          * If this method is non-static, the first parameter will be 'this'.
    130          * All the parameters must be passed and then the eventual return type returned.
    131          *
    132          * Example, let's say we have a method such as
    133          *   public void method_1(int a, Object b, ArrayList<String> c) { ... }
    134          *
    135          * We'll want to create a body that calls a delegate method like this:
    136          *   TheClass_Delegate.method_1(this, a, b, c);
    137          *
    138          * If the method is non-static and the class name is an inner class (e.g. has $ in its
    139          * last segment), we want to push the 'this' of the outer class first:
    140          *   OuterClass_InnerClass_Delegate.method_1(
    141          *     OuterClass.this,
    142          *     OuterClass$InnerClass.this,
    143          *     a, b, c);
    144          *
    145          * Only one level of inner class is supported right now, for simplicity and because
    146          * we don't need more.
    147          *
    148          * The generated class name is the current class name with "_Delegate" appended to it.
    149          * One thing to realize is that we don't care about generics -- since generic types
    150          * are erased at runtime, they have no influence on the method name being called.
    151          */
    152 
    153         // Add our annotation
    154         AnnotationVisitor aw = mParentVisitor.visitAnnotation(
    155                 Type.getObjectType(Type.getInternalName(LayoutlibDelegate.class)).toString(),
    156                 true); // visible at runtime
    157         aw.visitEnd();
    158 
    159         if (!mVisitCodeCalled) {
    160             // If this is a direct call to generateCode() as done by DelegateClassAdapter
    161             // for natives, visitCode() hasn't been called yet.
    162             mParentVisitor.visitCode();
    163             mVisitCodeCalled = true;
    164         }
    165 
    166         ArrayList<Type> paramTypes = new ArrayList<Type>();
    167         String delegateClassName = mClassName + DELEGATE_SUFFIX;
    168         boolean pushedArg0 = false;
    169         int maxStack = 0;
    170 
    171         // For an instance method (e.g. non-static), push the 'this' preceded
    172         // by the 'this' of any outer class, if any.
    173         if (!mIsStatic) {
    174             // Check if the last segment of the class name has inner an class.
    175             // Right now we only support one level of inner classes.
    176             int slash = mClassName.lastIndexOf('/');
    177             int dol = mClassName.lastIndexOf('$');
    178             if (dol != -1 && dol > slash && dol == mClassName.indexOf('$')) {
    179                 String outerClass = mClassName.substring(0, dol);
    180                 Type outerType = Type.getObjectType(outerClass);
    181 
    182                 // Change a delegate class name to "com/foo/Outer_Inner_Delegate"
    183                 delegateClassName = delegateClassName.replace('$', '_');
    184 
    185                 // The first-level inner class has a package-protected member called 'this$0'
    186                 // that points to the outer class.
    187 
    188                 // Push this.getField("this$0") on the call stack.
    189                 mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0); // var 0 = this
    190                 mParentVisitor.visitFieldInsn(Opcodes.GETFIELD,
    191                         mClassName,                 // class where the field is defined
    192                         "this$0",                   // field name
    193                         outerType.getDescriptor()); // type of the field
    194                 maxStack++;
    195                 paramTypes.add(outerType);
    196             }
    197 
    198             // Push "this" for the instance method, which is always ALOAD 0
    199             mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0);
    200             maxStack++;
    201             pushedArg0 = true;
    202             paramTypes.add(Type.getObjectType(mClassName));
    203         }
    204 
    205         // Push all other arguments. Start at arg 1 if we already pushed 'this' above.
    206         Type[] argTypes = Type.getArgumentTypes(mDesc);
    207         int maxLocals = pushedArg0 ? 1 : 0;
    208         for (Type t : argTypes) {
    209             int size = t.getSize();
    210             mParentVisitor.visitVarInsn(t.getOpcode(Opcodes.ILOAD), maxLocals);
    211             maxLocals += size;
    212             maxStack += size;
    213             paramTypes.add(t);
    214         }
    215 
    216         // Construct the descriptor of the delegate based on the parameters
    217         // we pushed on the call stack. The return type remains unchanged.
    218         String desc = Type.getMethodDescriptor(
    219                 Type.getReturnType(mDesc),
    220                 paramTypes.toArray(new Type[paramTypes.size()]));
    221 
    222         // Invoke the static delegate
    223         mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
    224                 delegateClassName,
    225                 mMethodName,
    226                 desc);
    227 
    228         Type returnType = Type.getReturnType(mDesc);
    229         mParentVisitor.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
    230 
    231         mParentVisitor.visitMaxs(maxStack, maxLocals);
    232         mParentVisitor.visitEnd();
    233 
    234         // For debugging now. Maybe we should collect these and store them in
    235         // a text file for helping create the delegates. We could also compare
    236         // the text file to a golden and break the build on unsupported changes
    237         // or regressions. Even better we could fancy-print something that looks
    238         // like the expected Java method declaration.
    239         mLog.debug("Delegate: %1$s # %2$s %3$s", delegateClassName, mMethodName, desc);
    240     }
    241 
    242     /* Pass down to visitor writer. In this implementation, either do nothing. */
    243     public void visitCode() {
    244         mVisitCodeCalled = true;
    245         mParentVisitor.visitCode();
    246     }
    247 
    248     /*
    249      * visitMaxs is called just before visitEnd if there was any code to rewrite.
    250      * Skip the original.
    251      */
    252     public void visitMaxs(int maxStack, int maxLocals) {
    253     }
    254 
    255     /**
    256      * End of visiting. Generate the messaging code.
    257      */
    258     public void visitEnd() {
    259         generateCode();
    260     }
    261 
    262     /* Writes all annotation from the original method. */
    263     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
    264         return mParentVisitor.visitAnnotation(desc, visible);
    265     }
    266 
    267     /* Writes all annotation default values from the original method. */
    268     public AnnotationVisitor visitAnnotationDefault() {
    269         return mParentVisitor.visitAnnotationDefault();
    270     }
    271 
    272     public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
    273             boolean visible) {
    274         return mParentVisitor.visitParameterAnnotation(parameter, desc, visible);
    275     }
    276 
    277     /* Writes all attributes from the original method. */
    278     public void visitAttribute(Attribute attr) {
    279         mParentVisitor.visitAttribute(attr);
    280     }
    281 
    282     /*
    283      * Only writes the first line number present in the original code so that source
    284      * viewers can direct to the correct method, even if the content doesn't match.
    285      */
    286     public void visitLineNumber(int line, Label start) {
    287         if (mOutputFirstLineNumber) {
    288             mParentVisitor.visitLineNumber(line, start);
    289             mOutputFirstLineNumber = false;
    290         }
    291     }
    292 
    293     public void visitInsn(int opcode) {
    294         // Skip original code.
    295     }
    296 
    297     public void visitLabel(Label label) {
    298         // Skip original code.
    299     }
    300 
    301     public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
    302         // Skip original code.
    303     }
    304 
    305     public void visitMethodInsn(int opcode, String owner, String name, String desc) {
    306         // Skip original code.
    307     }
    308 
    309     public void visitFieldInsn(int opcode, String owner, String name, String desc) {
    310         // Skip original code.
    311     }
    312 
    313     public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
    314         // Skip original code.
    315     }
    316 
    317     public void visitIincInsn(int var, int increment) {
    318         // Skip original code.
    319     }
    320 
    321     public void visitIntInsn(int opcode, int operand) {
    322         // Skip original code.
    323     }
    324 
    325     public void visitJumpInsn(int opcode, Label label) {
    326         // Skip original code.
    327     }
    328 
    329     public void visitLdcInsn(Object cst) {
    330         // Skip original code.
    331     }
    332 
    333     public void visitLocalVariable(String name, String desc, String signature,
    334             Label start, Label end, int index) {
    335         // Skip original code.
    336     }
    337 
    338     public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
    339         // Skip original code.
    340     }
    341 
    342     public void visitMultiANewArrayInsn(String desc, int dims) {
    343         // Skip original code.
    344     }
    345 
    346     public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
    347         // Skip original code.
    348     }
    349 
    350     public void visitTypeInsn(int opcode, String type) {
    351         // Skip original code.
    352     }
    353 
    354     public void visitVarInsn(int opcode, int var) {
    355         // Skip original code.
    356     }
    357 
    358 }
    359