• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 DelegateMethodAdapter 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 DelegateMethodAdapter} that will transform this method
101      * into a delegate call.
102      * <p/>
103      * See {@link DelegateMethodAdapter} 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      */
DelegateMethodAdapter(Log log, MethodVisitor mvOriginal, MethodVisitor mvDelegate, String className, String methodName, String desc, boolean isStatic)117     public DelegateMethodAdapter(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 DelegateMethodAdapter} 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      */
generateDelegateCode()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
visitCode()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
visitMaxs(int maxStack, int maxLocals)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
visitEnd()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
visitAnnotation(String desc, boolean visible)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
visitAnnotationDefault()307     public AnnotationVisitor visitAnnotationDefault() {
308         if (mOrgWriter != null) {
309             return mOrgWriter.visitAnnotationDefault();
310         } else {
311             return null;
312         }
313     }
314 
315     @Override
visitParameterAnnotation(int parameter, String desc, boolean visible)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
visitAttribute(Attribute attr)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
visitLineNumber(int line, Label start)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
visitInsn(int opcode)349     public void visitInsn(int opcode) {
350         if (mOrgWriter != null) {
351             mOrgWriter.visitInsn(opcode);
352         }
353     }
354 
355     @Override
visitLabel(Label label)356     public void visitLabel(Label label) {
357         if (mOrgWriter != null) {
358             mOrgWriter.visitLabel(label);
359         }
360     }
361 
362     @Override
visitTryCatchBlock(Label start, Label end, Label handler, String type)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
visitMethodInsn(int opcode, String owner, String name, String desc)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
visitFieldInsn(int opcode, String owner, String name, String desc)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
visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack)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
visitIincInsn(int var, int increment)391     public void visitIincInsn(int var, int increment) {
392         if (mOrgWriter != null) {
393             mOrgWriter.visitIincInsn(var, increment);
394         }
395     }
396 
397     @Override
visitIntInsn(int opcode, int operand)398     public void visitIntInsn(int opcode, int operand) {
399         if (mOrgWriter != null) {
400             mOrgWriter.visitIntInsn(opcode, operand);
401         }
402     }
403 
404     @Override
visitJumpInsn(int opcode, Label label)405     public void visitJumpInsn(int opcode, Label label) {
406         if (mOrgWriter != null) {
407             mOrgWriter.visitJumpInsn(opcode, label);
408         }
409     }
410 
411     @Override
visitLdcInsn(Object cst)412     public void visitLdcInsn(Object cst) {
413         if (mOrgWriter != null) {
414             mOrgWriter.visitLdcInsn(cst);
415         }
416     }
417 
418     @Override
visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index)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
visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels)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
visitMultiANewArrayInsn(String desc, int dims)434     public void visitMultiANewArrayInsn(String desc, int dims) {
435         if (mOrgWriter != null) {
436             mOrgWriter.visitMultiANewArrayInsn(desc, dims);
437         }
438     }
439 
440     @Override
visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels)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
visitTypeInsn(int opcode, String type)448     public void visitTypeInsn(int opcode, String type) {
449         if (mOrgWriter != null) {
450             mOrgWriter.visitTypeInsn(opcode, type);
451         }
452     }
453 
454     @Override
visitVarInsn(int opcode, int var)455     public void visitVarInsn(int opcode, int var) {
456         if (mOrgWriter != null) {
457             mOrgWriter.visitVarInsn(opcode, var);
458         }
459     }
460 
461 }
462