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 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 /** 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 DelegateMethodAdapter} that will transform this method 101 * into a delegate call. 102 * <p/> 103 * See {@link DelegateMethodAdapter} 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 */ DelegateMethodAdapter(Log log, MethodVisitor mvOriginal, MethodVisitor mvDelegate, String className, String methodName, String desc, boolean isStatic)117 public DelegateMethodAdapter(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 DelegateMethodAdapter} 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 */ generateDelegateCode()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 visitCode()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 visitMaxs(int maxStack, int maxLocals)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 visitEnd()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 visitAnnotation(String desc, boolean visible)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 visitAnnotationDefault()307 public AnnotationVisitor visitAnnotationDefault() { 308 if (mOrgWriter != null) { 309 return mOrgWriter.visitAnnotationDefault(); 310 } else { 311 return null; 312 } 313 } 314 315 @Override visitParameterAnnotation(int parameter, String desc, boolean visible)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 visitAttribute(Attribute attr)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 visitLineNumber(int line, Label start)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 visitInsn(int opcode)349 public void visitInsn(int opcode) { 350 if (mOrgWriter != null) { 351 mOrgWriter.visitInsn(opcode); 352 } 353 } 354 355 @Override visitLabel(Label label)356 public void visitLabel(Label label) { 357 if (mOrgWriter != null) { 358 mOrgWriter.visitLabel(label); 359 } 360 } 361 362 @Override visitTryCatchBlock(Label start, Label end, Label handler, String type)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 visitMethodInsn(int opcode, String owner, String name, String desc)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 visitFieldInsn(int opcode, String owner, String name, String desc)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 visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack)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 visitIincInsn(int var, int increment)391 public void visitIincInsn(int var, int increment) { 392 if (mOrgWriter != null) { 393 mOrgWriter.visitIincInsn(var, increment); 394 } 395 } 396 397 @Override visitIntInsn(int opcode, int operand)398 public void visitIntInsn(int opcode, int operand) { 399 if (mOrgWriter != null) { 400 mOrgWriter.visitIntInsn(opcode, operand); 401 } 402 } 403 404 @Override visitJumpInsn(int opcode, Label label)405 public void visitJumpInsn(int opcode, Label label) { 406 if (mOrgWriter != null) { 407 mOrgWriter.visitJumpInsn(opcode, label); 408 } 409 } 410 411 @Override visitLdcInsn(Object cst)412 public void visitLdcInsn(Object cst) { 413 if (mOrgWriter != null) { 414 mOrgWriter.visitLdcInsn(cst); 415 } 416 } 417 418 @Override visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index)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 visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels)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 visitMultiANewArrayInsn(String desc, int dims)434 public void visitMultiANewArrayInsn(String desc, int dims) { 435 if (mOrgWriter != null) { 436 mOrgWriter.visitMultiANewArrayInsn(desc, dims); 437 } 438 } 439 440 @Override visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels)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 visitTypeInsn(int opcode, String type)448 public void visitTypeInsn(int opcode, String type) { 449 if (mOrgWriter != null) { 450 mOrgWriter.visitTypeInsn(opcode, type); 451 } 452 } 453 454 @Override visitVarInsn(int opcode, int var)455 public void visitVarInsn(int opcode, int var) { 456 if (mOrgWriter != null) { 457 mOrgWriter.visitVarInsn(opcode, var); 458 } 459 } 460 461 } 462