Home | History | Annotate | Download | only in create
      1 /*
      2  * Copyright (C) 2010 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 generates delegate methods.
     34  * <p/>
     35  * Given a method {@code SomeClass.MethodName()}, this generates 1 or 2 methods:
     36  * <ul>
     37  * <li> A copy of the original method named {@code SomeClass.MethodName_Original()}.
     38  *   The content is the original method as-is from the reader.
     39  *   This step is omitted if the method is native, since it has no Java implementation.
     40  * <li> A brand new implementation of {@code SomeClass.MethodName()} which calls to a
     41  *   non-existing method named {@code SomeClass_Delegate.MethodName()}.
     42  *   The implementation of this 'delegate' method is done in layoutlib_brigde.
     43  * </ul>
     44  * A method visitor is generally constructed to generate a single method; however
     45  * here we might want to generate one or two depending on the context. To achieve
     46  * that, the visitor here generates the 'original' method and acts as a no-op if
     47  * no such method exists (e.g. when the original is a native method).
     48  * The delegate method is generated after the {@code visitEnd} of the original method
     49  * or by having the class adapter <em>directly</em> call {@link #generateDelegateCode()}
     50  * for native methods.
     51  * <p/>
     52  * When generating the 'delegate', the implementation generates a call to a class
     53  * class named <code>&lt;className&gt;_Delegate</code> with static methods matching
     54  * the methods to be overridden here. The methods have the same return type.
     55  * The argument type list is the same except the "this" reference is passed first
     56  * for non-static methods.
     57  * <p/>
     58  * A new annotation is added to these 'delegate' methods so that we can easily find them
     59  * for automated testing.
     60  * <p/>
     61  * This class isn't intended to be generic or reusable.
     62  * It is called by {@link DelegateClassAdapter}, which takes care of properly initializing
     63  * the two method writers for the original and the delegate class, as needed, with their
     64  * expected names.
     65  * <p/>
     66  * The class adapter also takes care of calling {@link #generateDelegateCode()} directly for
     67  * a native and use the visitor pattern for non-natives.
     68  * Note that native methods have, by definition, no code so there's nothing a visitor
     69  * can visit.
     70  * <p/>
     71  * Instances of this class are not re-usable.
     72  * The class adapter creates a new instance for each method.
     73  */
     74 class DelegateMethodAdapter2 extends MethodVisitor {
     75 
     76     /** Suffix added to delegate classes. */
     77     public static final String DELEGATE_SUFFIX = "_Delegate";
     78 
     79     /** The parent method writer to copy of the original method.
     80      *  Null when dealing with a native original method. */
     81     private MethodVisitor mOrgWriter;
     82     /** The parent method writer to generate the delegating method. Never null. */
     83     private MethodVisitor mDelWriter;
     84     /** The original method descriptor (return type + argument types.) */
     85     private String mDesc;
     86     /** True if the original method is static. */
     87     private final boolean mIsStatic;
     88     /** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */
     89     private final String mClassName;
     90     /** The method name. */
     91     private final String mMethodName;
     92     /** Logger object. */
     93     private final Log mLog;
     94 
     95     /** Array used to capture the first line number information from the original method
     96      *  and duplicate it in the delegate. */
     97     private Object[] mDelegateLineNumber;
     98 
     99     /**
    100      * Creates a new {@link DelegateMethodAdapter2} that will transform this method
    101      * into a delegate call.
    102      * <p/>
    103      * See {@link DelegateMethodAdapter2} for more details.
    104      *
    105      * @param log The logger object. Must not be null.
    106      * @param mvOriginal The parent method writer to copy of the original method.
    107      *          Must be {@code null} when dealing with a native original method.
    108      * @param mvDelegate The parent method writer to generate the delegating method.
    109      *          Must never be null.
    110      * @param className The internal class name of the class to visit,
    111      *          e.g. <code>com/android/SomeClass$InnerClass</code>.
    112      * @param methodName The simple name of the method.
    113      * @param desc A method descriptor (c.f. {@link Type#getReturnType(String)} +
    114      *          {@link Type#getArgumentTypes(String)})
    115      * @param isStatic True if the method is declared static.
    116      */
    117     public DelegateMethodAdapter2(Log log,
    118             MethodVisitor mvOriginal,
    119             MethodVisitor mvDelegate,
    120             String className,
    121             String methodName,
    122             String desc,
    123             boolean isStatic) {
    124         super(Opcodes.ASM4);
    125         mLog = log;
    126         mOrgWriter = mvOriginal;
    127         mDelWriter = mvDelegate;
    128         mClassName = className;
    129         mMethodName = methodName;
    130         mDesc = desc;
    131         mIsStatic = isStatic;
    132     }
    133 
    134     /**
    135      * Generates the new code for the method.
    136      * <p/>
    137      * For native methods, this must be invoked directly by {@link DelegateClassAdapter}
    138      * (since they have no code to visit).
    139      * <p/>
    140      * Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to
    141      * return this instance of {@link DelegateMethodAdapter2} and let the normal visitor pattern
    142      * invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then
    143      * this method will be invoked from {@link MethodVisitor#visitEnd()}.
    144      */
    145     public void generateDelegateCode() {
    146         /*
    147          * The goal is to generate a call to a static delegate method.
    148          * If this method is non-static, the first parameter will be 'this'.
    149          * All the parameters must be passed and then the eventual return type returned.
    150          *
    151          * Example, let's say we have a method such as
    152          *   public void myMethod(int a, Object b, ArrayList<String> c) { ... }
    153          *
    154          * We'll want to create a body that calls a delegate method like this:
    155          *   TheClass_Delegate.myMethod(this, a, b, c);
    156          *
    157          * If the method is non-static and the class name is an inner class (e.g. has $ in its
    158          * last segment), we want to push the 'this' of the outer class first:
    159          *   OuterClass_InnerClass_Delegate.myMethod(
    160          *     OuterClass.this,
    161          *     OuterClass$InnerClass.this,
    162          *     a, b, c);
    163          *
    164          * Only one level of inner class is supported right now, for simplicity and because
    165          * we don't need more.
    166          *
    167          * The generated class name is the current class name with "_Delegate" appended to it.
    168          * One thing to realize is that we don't care about generics -- since generic types
    169          * are erased at build time, they have no influence on the method name being called.
    170          */
    171 
    172         // Add our annotation
    173         AnnotationVisitor aw = mDelWriter.visitAnnotation(
    174                 Type.getObjectType(Type.getInternalName(LayoutlibDelegate.class)).toString(),
    175                 true); // visible at runtime
    176         if (aw != null) {
    177             aw.visitEnd();
    178         }
    179 
    180         mDelWriter.visitCode();
    181 
    182         if (mDelegateLineNumber != null) {
    183             Object[] p = mDelegateLineNumber;
    184             mDelWriter.visitLineNumber((Integer) p[0], (Label) p[1]);
    185         }
    186 
    187         ArrayList<Type> paramTypes = new ArrayList<Type>();
    188         String delegateClassName = mClassName + DELEGATE_SUFFIX;
    189         boolean pushedArg0 = false;
    190         int maxStack = 0;
    191 
    192         // Check if the last segment of the class name has inner an class.
    193         // Right now we only support one level of inner classes.
    194         Type outerType = null;
    195         int slash = mClassName.lastIndexOf('/');
    196         int dol = mClassName.lastIndexOf('$');
    197         if (dol != -1 && dol > slash && dol == mClassName.indexOf('$')) {
    198             String outerClass = mClassName.substring(0, dol);
    199             outerType = Type.getObjectType(outerClass);
    200 
    201             // Change a delegate class name to "com/foo/Outer_Inner_Delegate"
    202             delegateClassName = delegateClassName.replace('$', '_');
    203         }
    204 
    205         // For an instance method (e.g. non-static), push the 'this' preceded
    206         // by the 'this' of any outer class, if any.
    207         if (!mIsStatic) {
    208 
    209             if (outerType != null) {
    210                 // The first-level inner class has a package-protected member called 'this$0'
    211                 // that points to the outer class.
    212 
    213                 // Push this.getField("this$0") on the call stack.
    214                 mDelWriter.visitVarInsn(Opcodes.ALOAD, 0); // var 0 = this
    215                 mDelWriter.visitFieldInsn(Opcodes.GETFIELD,
    216                         mClassName,                 // class where the field is defined
    217                         "this$0",                   // field name
    218                         outerType.getDescriptor()); // type of the field
    219                 maxStack++;
    220                 paramTypes.add(outerType);
    221 
    222             }
    223 
    224             // Push "this" for the instance method, which is always ALOAD 0
    225             mDelWriter.visitVarInsn(Opcodes.ALOAD, 0);
    226             maxStack++;
    227             pushedArg0 = true;
    228             paramTypes.add(Type.getObjectType(mClassName));
    229         }
    230 
    231         // Push all other arguments. Start at arg 1 if we already pushed 'this' above.
    232         Type[] argTypes = Type.getArgumentTypes(mDesc);
    233         int maxLocals = pushedArg0 ? 1 : 0;
    234         for (Type t : argTypes) {
    235             int size = t.getSize();
    236             mDelWriter.visitVarInsn(t.getOpcode(Opcodes.ILOAD), maxLocals);
    237             maxLocals += size;
    238             maxStack += size;
    239             paramTypes.add(t);
    240         }
    241 
    242         // Construct the descriptor of the delegate based on the parameters
    243         // we pushed on the call stack. The return type remains unchanged.
    244         String desc = Type.getMethodDescriptor(
    245                 Type.getReturnType(mDesc),
    246                 paramTypes.toArray(new Type[paramTypes.size()]));
    247 
    248         // Invoke the static delegate
    249         mDelWriter.visitMethodInsn(Opcodes.INVOKESTATIC,
    250                 delegateClassName,
    251                 mMethodName,
    252                 desc);
    253 
    254         Type returnType = Type.getReturnType(mDesc);
    255         mDelWriter.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
    256 
    257         mDelWriter.visitMaxs(maxStack, maxLocals);
    258         mDelWriter.visitEnd();
    259 
    260         // For debugging now. Maybe we should collect these and store them in
    261         // a text file for helping create the delegates. We could also compare
    262         // the text file to a golden and break the build on unsupported changes
    263         // or regressions. Even better we could fancy-print something that looks
    264         // like the expected Java method declaration.
    265         mLog.debug("Delegate: %1$s # %2$s %3$s", delegateClassName, mMethodName, desc);
    266     }
    267 
    268     /* Pass down to visitor writer. In this implementation, either do nothing. */
    269     @Override
    270     public void visitCode() {
    271         if (mOrgWriter != null) {
    272             mOrgWriter.visitCode();
    273         }
    274     }
    275 
    276     /*
    277      * visitMaxs is called just before visitEnd if there was any code to rewrite.
    278      */
    279     @Override
    280     public void visitMaxs(int maxStack, int maxLocals) {
    281         if (mOrgWriter != null) {
    282             mOrgWriter.visitMaxs(maxStack, maxLocals);
    283         }
    284     }
    285 
    286     /** End of visiting. Generate the delegating code. */
    287     @Override
    288     public void visitEnd() {
    289         if (mOrgWriter != null) {
    290             mOrgWriter.visitEnd();
    291         }
    292         generateDelegateCode();
    293     }
    294 
    295     /* Writes all annotation from the original method. */
    296     @Override
    297     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
    298         if (mOrgWriter != null) {
    299             return mOrgWriter.visitAnnotation(desc, visible);
    300         } else {
    301             return null;
    302         }
    303     }
    304 
    305     /* Writes all annotation default values from the original method. */
    306     @Override
    307     public AnnotationVisitor visitAnnotationDefault() {
    308         if (mOrgWriter != null) {
    309             return mOrgWriter.visitAnnotationDefault();
    310         } else {
    311             return null;
    312         }
    313     }
    314 
    315     @Override
    316     public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
    317             boolean visible) {
    318         if (mOrgWriter != null) {
    319             return mOrgWriter.visitParameterAnnotation(parameter, desc, visible);
    320         } else {
    321             return null;
    322         }
    323     }
    324 
    325     /* Writes all attributes from the original method. */
    326     @Override
    327     public void visitAttribute(Attribute attr) {
    328         if (mOrgWriter != null) {
    329             mOrgWriter.visitAttribute(attr);
    330         }
    331     }
    332 
    333     /*
    334      * Only writes the first line number present in the original code so that source
    335      * viewers can direct to the correct method, even if the content doesn't match.
    336      */
    337     @Override
    338     public void visitLineNumber(int line, Label start) {
    339         // Capture the first line values for the new delegate method
    340         if (mDelegateLineNumber == null) {
    341             mDelegateLineNumber = new Object[] { line, start };
    342         }
    343         if (mOrgWriter != null) {
    344             mOrgWriter.visitLineNumber(line, start);
    345         }
    346     }
    347 
    348     @Override
    349     public void visitInsn(int opcode) {
    350         if (mOrgWriter != null) {
    351             mOrgWriter.visitInsn(opcode);
    352         }
    353     }
    354 
    355     @Override
    356     public void visitLabel(Label label) {
    357         if (mOrgWriter != null) {
    358             mOrgWriter.visitLabel(label);
    359         }
    360     }
    361 
    362     @Override
    363     public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
    364         if (mOrgWriter != null) {
    365             mOrgWriter.visitTryCatchBlock(start, end, handler, type);
    366         }
    367     }
    368 
    369     @Override
    370     public void visitMethodInsn(int opcode, String owner, String name, String desc) {
    371         if (mOrgWriter != null) {
    372             mOrgWriter.visitMethodInsn(opcode, owner, name, desc);
    373         }
    374     }
    375 
    376     @Override
    377     public void visitFieldInsn(int opcode, String owner, String name, String desc) {
    378         if (mOrgWriter != null) {
    379             mOrgWriter.visitFieldInsn(opcode, owner, name, desc);
    380         }
    381     }
    382 
    383     @Override
    384     public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
    385         if (mOrgWriter != null) {
    386             mOrgWriter.visitFrame(type, nLocal, local, nStack, stack);
    387         }
    388     }
    389 
    390     @Override
    391     public void visitIincInsn(int var, int increment) {
    392         if (mOrgWriter != null) {
    393             mOrgWriter.visitIincInsn(var, increment);
    394         }
    395     }
    396 
    397     @Override
    398     public void visitIntInsn(int opcode, int operand) {
    399         if (mOrgWriter != null) {
    400             mOrgWriter.visitIntInsn(opcode, operand);
    401         }
    402     }
    403 
    404     @Override
    405     public void visitJumpInsn(int opcode, Label label) {
    406         if (mOrgWriter != null) {
    407             mOrgWriter.visitJumpInsn(opcode, label);
    408         }
    409     }
    410 
    411     @Override
    412     public void visitLdcInsn(Object cst) {
    413         if (mOrgWriter != null) {
    414             mOrgWriter.visitLdcInsn(cst);
    415         }
    416     }
    417 
    418     @Override
    419     public void visitLocalVariable(String name, String desc, String signature,
    420             Label start, Label end, int index) {
    421         if (mOrgWriter != null) {
    422             mOrgWriter.visitLocalVariable(name, desc, signature, start, end, index);
    423         }
    424     }
    425 
    426     @Override
    427     public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
    428         if (mOrgWriter != null) {
    429             mOrgWriter.visitLookupSwitchInsn(dflt, keys, labels);
    430         }
    431     }
    432 
    433     @Override
    434     public void visitMultiANewArrayInsn(String desc, int dims) {
    435         if (mOrgWriter != null) {
    436             mOrgWriter.visitMultiANewArrayInsn(desc, dims);
    437         }
    438     }
    439 
    440     @Override
    441     public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
    442         if (mOrgWriter != null) {
    443             mOrgWriter.visitTableSwitchInsn(min, max, dflt, labels);
    444         }
    445     }
    446 
    447     @Override
    448     public void visitTypeInsn(int opcode, String type) {
    449         if (mOrgWriter != null) {
    450             mOrgWriter.visitTypeInsn(opcode, type);
    451         }
    452     }
    453 
    454     @Override
    455     public void visitVarInsn(int opcode, int var) {
    456         if (mOrgWriter != null) {
    457             mOrgWriter.visitVarInsn(opcode, var);
    458         }
    459     }
    460 
    461 }
    462