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
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 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 */
49class 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