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