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><className>_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 DelegateMethodAdapter2 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 DelegateMethodAdapter2} that will transform this method 101 * into a delegate call. 102 * <p/> 103 * See {@link DelegateMethodAdapter2} 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 */ 117 public DelegateMethodAdapter2(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 DelegateMethodAdapter2} 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 */ 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 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 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 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 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 307 public AnnotationVisitor visitAnnotationDefault() { 308 if (mOrgWriter != null) { 309 return mOrgWriter.visitAnnotationDefault(); 310 } else { 311 return null; 312 } 313 } 314 315 @Override 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 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 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 349 public void visitInsn(int opcode) { 350 if (mOrgWriter != null) { 351 mOrgWriter.visitInsn(opcode); 352 } 353 } 354 355 @Override 356 public void visitLabel(Label label) { 357 if (mOrgWriter != null) { 358 mOrgWriter.visitLabel(label); 359 } 360 } 361 362 @Override 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 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 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 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 391 public void visitIincInsn(int var, int increment) { 392 if (mOrgWriter != null) { 393 mOrgWriter.visitIincInsn(var, increment); 394 } 395 } 396 397 @Override 398 public void visitIntInsn(int opcode, int operand) { 399 if (mOrgWriter != null) { 400 mOrgWriter.visitIntInsn(opcode, operand); 401 } 402 } 403 404 @Override 405 public void visitJumpInsn(int opcode, Label label) { 406 if (mOrgWriter != null) { 407 mOrgWriter.visitJumpInsn(opcode, label); 408 } 409 } 410 411 @Override 412 public void visitLdcInsn(Object cst) { 413 if (mOrgWriter != null) { 414 mOrgWriter.visitLdcInsn(cst); 415 } 416 } 417 418 @Override 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 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 434 public void visitMultiANewArrayInsn(String desc, int dims) { 435 if (mOrgWriter != null) { 436 mOrgWriter.visitMultiANewArrayInsn(desc, dims); 437 } 438 } 439 440 @Override 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 448 public void visitTypeInsn(int opcode, String type) { 449 if (mOrgWriter != null) { 450 mOrgWriter.visitTypeInsn(opcode, type); 451 } 452 } 453 454 @Override 455 public void visitVarInsn(int opcode, int var) { 456 if (mOrgWriter != null) { 457 mOrgWriter.visitVarInsn(opcode, var); 458 } 459 } 460 461 } 462