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 org.objectweb.asm.AnnotationVisitor;
20import org.objectweb.asm.Attribute;
21import org.objectweb.asm.Label;
22import org.objectweb.asm.MethodVisitor;
23import org.objectweb.asm.Opcodes;
24import org.objectweb.asm.Type;
25
26/**
27 * This method adapter rewrites a method by discarding the original code and generating
28 * a stub depending on the return type. Original annotations are passed along unchanged.
29 */
30class StubMethodAdapter extends MethodVisitor {
31
32    private static final String CONSTRUCTOR = "<init>";
33    private static final String CLASS_INIT = "<clinit>";
34
35    /** The parent method writer */
36    private MethodVisitor mParentVisitor;
37    /** The method return type. Can be null. */
38    private Type mReturnType;
39    /** Message to be printed by stub methods. */
40    private String mInvokeSignature;
41    /** Flag to output the first line number. */
42    private boolean mOutputFirstLineNumber = true;
43    /** Flag that is true when implementing a constructor, to accept all original
44     *  code calling the original super constructor. */
45    private boolean mIsInitMethod = false;
46
47    private boolean mMessageGenerated;
48    private final boolean mIsStatic;
49    private final boolean mIsNative;
50
51    public StubMethodAdapter(MethodVisitor mv, String methodName, Type returnType,
52            String invokeSignature, boolean isStatic, boolean isNative) {
53        super(Main.ASM_VERSION);
54        mParentVisitor = mv;
55        mReturnType = returnType;
56        mInvokeSignature = invokeSignature;
57        mIsStatic = isStatic;
58        mIsNative = isNative;
59
60        if (CONSTRUCTOR.equals(methodName) || CLASS_INIT.equals(methodName)) {
61            mIsInitMethod = true;
62        }
63    }
64
65    private void generateInvoke() {
66        /* Generates the code:
67         *  OverrideMethod.invoke("signature", mIsNative ? true : false, null or this);
68         */
69        mParentVisitor.visitLdcInsn(mInvokeSignature);
70        // push true or false
71        mParentVisitor.visitInsn(mIsNative ? Opcodes.ICONST_1 : Opcodes.ICONST_0);
72        // push null or this
73        if (mIsStatic) {
74            mParentVisitor.visitInsn(Opcodes.ACONST_NULL);
75        } else {
76            mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0);
77        }
78
79        int sort = mReturnType != null ? mReturnType.getSort() : Type.VOID;
80        switch(sort) {
81        case Type.VOID:
82            mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
83                    "com/android/tools/layoutlib/create/OverrideMethod",
84                    "invokeV",
85                    "(Ljava/lang/String;ZLjava/lang/Object;)V",
86                    false);
87            mParentVisitor.visitInsn(Opcodes.RETURN);
88            break;
89        case Type.BOOLEAN:
90        case Type.CHAR:
91        case Type.BYTE:
92        case Type.SHORT:
93        case Type.INT:
94            mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
95                    "com/android/tools/layoutlib/create/OverrideMethod",
96                    "invokeI",
97                    "(Ljava/lang/String;ZLjava/lang/Object;)I",
98                    false);
99            switch(sort) {
100            case Type.BOOLEAN:
101                Label l1 = new Label();
102                mParentVisitor.visitJumpInsn(Opcodes.IFEQ, l1);
103                mParentVisitor.visitInsn(Opcodes.ICONST_1);
104                mParentVisitor.visitInsn(Opcodes.IRETURN);
105                mParentVisitor.visitLabel(l1);
106                mParentVisitor.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
107                mParentVisitor.visitInsn(Opcodes.ICONST_0);
108                break;
109            case Type.CHAR:
110                mParentVisitor.visitInsn(Opcodes.I2C);
111                break;
112            case Type.BYTE:
113                mParentVisitor.visitInsn(Opcodes.I2B);
114                break;
115            case Type.SHORT:
116                mParentVisitor.visitInsn(Opcodes.I2S);
117                break;
118            }
119            mParentVisitor.visitInsn(Opcodes.IRETURN);
120            break;
121        case Type.LONG:
122            mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
123                    "com/android/tools/layoutlib/create/OverrideMethod",
124                    "invokeL",
125                    "(Ljava/lang/String;ZLjava/lang/Object;)J",
126                    false);
127            mParentVisitor.visitInsn(Opcodes.LRETURN);
128            break;
129        case Type.FLOAT:
130            mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
131                    "com/android/tools/layoutlib/create/OverrideMethod",
132                    "invokeF",
133                    "(Ljava/lang/String;ZLjava/lang/Object;)F",
134                    false);
135            mParentVisitor.visitInsn(Opcodes.FRETURN);
136            break;
137        case Type.DOUBLE:
138            mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
139                    "com/android/tools/layoutlib/create/OverrideMethod",
140                    "invokeD",
141                    "(Ljava/lang/String;ZLjava/lang/Object;)D",
142                    false);
143            mParentVisitor.visitInsn(Opcodes.DRETURN);
144            break;
145        case Type.ARRAY:
146        case Type.OBJECT:
147            mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
148                    "com/android/tools/layoutlib/create/OverrideMethod",
149                    "invokeA",
150                    "(Ljava/lang/String;ZLjava/lang/Object;)Ljava/lang/Object;",
151                    false);
152            mParentVisitor.visitTypeInsn(Opcodes.CHECKCAST, mReturnType.getInternalName());
153            mParentVisitor.visitInsn(Opcodes.ARETURN);
154            break;
155        }
156
157    }
158
159    private void generatePop() {
160        /* Pops the stack, depending on the return type.
161         */
162        switch(mReturnType != null ? mReturnType.getSort() : Type.VOID) {
163        case Type.VOID:
164            break;
165        case Type.BOOLEAN:
166        case Type.CHAR:
167        case Type.BYTE:
168        case Type.SHORT:
169        case Type.INT:
170        case Type.FLOAT:
171        case Type.ARRAY:
172        case Type.OBJECT:
173            mParentVisitor.visitInsn(Opcodes.POP);
174            break;
175        case Type.LONG:
176        case Type.DOUBLE:
177            mParentVisitor.visitInsn(Opcodes.POP2);
178            break;
179        }
180    }
181
182    /* Pass down to visitor writer. In this implementation, either do nothing. */
183    @Override
184    public void visitCode() {
185        mParentVisitor.visitCode();
186    }
187
188    /*
189     * visitMaxs is called just before visitEnd if there was any code to rewrite.
190     * For non-constructor, generate the messaging code and the return statement
191     * if it hasn't been done before.
192     */
193    @Override
194    public void visitMaxs(int maxStack, int maxLocals) {
195        if (!mIsInitMethod && !mMessageGenerated) {
196            generateInvoke();
197            mMessageGenerated = true;
198        }
199        mParentVisitor.visitMaxs(maxStack, maxLocals);
200    }
201
202    /**
203     * End of visiting.
204     * For non-constructor, generate the messaging code and the return statement
205     * if it hasn't been done before.
206     */
207    @Override
208    public void visitEnd() {
209        if (!mIsInitMethod && !mMessageGenerated) {
210            generateInvoke();
211            mMessageGenerated = true;
212            mParentVisitor.visitMaxs(1, 1);
213        }
214        mParentVisitor.visitEnd();
215    }
216
217    /* Writes all annotation from the original method. */
218    @Override
219    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
220        return mParentVisitor.visitAnnotation(desc, visible);
221    }
222
223    /* Writes all annotation default values from the original method. */
224    @Override
225    public AnnotationVisitor visitAnnotationDefault() {
226        return mParentVisitor.visitAnnotationDefault();
227    }
228
229    @Override
230    public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
231            boolean visible) {
232        return mParentVisitor.visitParameterAnnotation(parameter, desc, visible);
233    }
234
235    /* Writes all attributes from the original method. */
236    @Override
237    public void visitAttribute(Attribute attr) {
238        mParentVisitor.visitAttribute(attr);
239    }
240
241    /*
242     * Only writes the first line number present in the original code so that source
243     * viewers can direct to the correct method, even if the content doesn't match.
244     */
245    @Override
246    public void visitLineNumber(int line, Label start) {
247        if (mIsInitMethod || mOutputFirstLineNumber) {
248            mParentVisitor.visitLineNumber(line, start);
249            mOutputFirstLineNumber = false;
250        }
251    }
252
253    /**
254     * For non-constructor, rewrite existing "return" instructions to write the message.
255     */
256    @Override
257    public void visitInsn(int opcode) {
258        if (mIsInitMethod) {
259            switch (opcode) {
260            case Opcodes.RETURN:
261            case Opcodes.ARETURN:
262            case Opcodes.DRETURN:
263            case Opcodes.FRETURN:
264            case Opcodes.IRETURN:
265            case Opcodes.LRETURN:
266                // Pop the last word from the stack since invoke will generate its own return.
267                generatePop();
268                generateInvoke();
269                mMessageGenerated = true;
270                //$FALL-THROUGH$
271            default:
272                mParentVisitor.visitInsn(opcode);
273            }
274        }
275    }
276
277    @Override
278    public void visitLabel(Label label) {
279        if (mIsInitMethod) {
280            mParentVisitor.visitLabel(label);
281        }
282    }
283
284    @Override
285    public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
286        if (mIsInitMethod) {
287            mParentVisitor.visitTryCatchBlock(start, end, handler, type);
288        }
289    }
290
291    @Override
292    public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
293        if (mIsInitMethod) {
294            mParentVisitor.visitMethodInsn(opcode, owner, name, desc, itf);
295        }
296    }
297
298    @Override
299    public void visitFieldInsn(int opcode, String owner, String name, String desc) {
300        if (mIsInitMethod) {
301            mParentVisitor.visitFieldInsn(opcode, owner, name, desc);
302        }
303    }
304
305    @Override
306    public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
307        if (mIsInitMethod) {
308            mParentVisitor.visitFrame(type, nLocal, local, nStack, stack);
309        }
310    }
311
312    @Override
313    public void visitIincInsn(int var, int increment) {
314        if (mIsInitMethod) {
315            mParentVisitor.visitIincInsn(var, increment);
316        }
317    }
318
319    @Override
320    public void visitIntInsn(int opcode, int operand) {
321        if (mIsInitMethod) {
322            mParentVisitor.visitIntInsn(opcode, operand);
323        }
324    }
325
326    @Override
327    public void visitJumpInsn(int opcode, Label label) {
328        if (mIsInitMethod) {
329            mParentVisitor.visitJumpInsn(opcode, label);
330        }
331    }
332
333    @Override
334    public void visitLdcInsn(Object cst) {
335        if (mIsInitMethod) {
336            mParentVisitor.visitLdcInsn(cst);
337        }
338    }
339
340    @Override
341    public void visitLocalVariable(String name, String desc, String signature,
342            Label start, Label end, int index) {
343        if (mIsInitMethod) {
344            mParentVisitor.visitLocalVariable(name, desc, signature, start, end, index);
345        }
346    }
347
348    @Override
349    public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
350        if (mIsInitMethod) {
351            mParentVisitor.visitLookupSwitchInsn(dflt, keys, labels);
352        }
353    }
354
355    @Override
356    public void visitMultiANewArrayInsn(String desc, int dims) {
357        if (mIsInitMethod) {
358            mParentVisitor.visitMultiANewArrayInsn(desc, dims);
359        }
360    }
361
362    @Override
363    public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
364        if (mIsInitMethod) {
365            mParentVisitor.visitTableSwitchInsn(min, max, dflt, labels);
366        }
367    }
368
369    @Override
370    public void visitTypeInsn(int opcode, String type) {
371        if (mIsInitMethod) {
372            mParentVisitor.visitTypeInsn(opcode, type);
373        }
374    }
375
376    @Override
377    public void visitVarInsn(int opcode, int var) {
378        if (mIsInitMethod) {
379            mParentVisitor.visitVarInsn(opcode, var);
380        }
381    }
382
383}
384