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