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 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 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 */ 74class 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 /** True if the method is contained in a static inner class */ 89 private final boolean mIsStaticInnerClass; 90 /** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */ 91 private final String mClassName; 92 /** The method name. */ 93 private final String mMethodName; 94 /** Logger object. */ 95 private final Log mLog; 96 97 /** Array used to capture the first line number information from the original method 98 * and duplicate it in the delegate. */ 99 private Object[] mDelegateLineNumber; 100 101 /** 102 * Creates a new {@link DelegateMethodAdapter} that will transform this method 103 * into a delegate call. 104 * <p/> 105 * See {@link DelegateMethodAdapter} for more details. 106 * 107 * @param log The logger object. Must not be null. 108 * @param mvOriginal The parent method writer to copy of the original method. 109 * Must be {@code null} when dealing with a native original method. 110 * @param mvDelegate The parent method writer to generate the delegating method. 111 * Must never be null. 112 * @param className The internal class name of the class to visit, 113 * e.g. <code>com/android/SomeClass$InnerClass</code>. 114 * @param methodName The simple name of the method. 115 * @param desc A method descriptor (c.f. {@link Type#getReturnType(String)} + 116 * {@link Type#getArgumentTypes(String)}) 117 * @param isStatic True if the method is declared static. 118 */ 119 public DelegateMethodAdapter(Log log, 120 MethodVisitor mvOriginal, 121 MethodVisitor mvDelegate, 122 String className, 123 String methodName, 124 String desc, 125 boolean isStatic, 126 boolean isStaticClass) { 127 super(Main.ASM_VERSION); 128 mLog = log; 129 mOrgWriter = mvOriginal; 130 mDelWriter = mvDelegate; 131 mClassName = className; 132 mMethodName = methodName; 133 mDesc = desc; 134 mIsStatic = isStatic; 135 mIsStaticInnerClass = isStaticClass; 136 } 137 138 /** 139 * Generates the new code for the method. 140 * <p/> 141 * For native methods, this must be invoked directly by {@link DelegateClassAdapter} 142 * (since they have no code to visit). 143 * <p/> 144 * Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to 145 * return this instance of {@link DelegateMethodAdapter} and let the normal visitor pattern 146 * invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then 147 * this method will be invoked from {@link MethodVisitor#visitEnd()}. 148 */ 149 public void generateDelegateCode() { 150 /* 151 * The goal is to generate a call to a static delegate method. 152 * If this method is non-static, the first parameter will be 'this'. 153 * All the parameters must be passed and then the eventual return type returned. 154 * 155 * Example, let's say we have a method such as 156 * public void myMethod(int a, Object b, ArrayList<String> c) { ... } 157 * 158 * We'll want to create a body that calls a delegate method like this: 159 * TheClass_Delegate.myMethod(this, a, b, c); 160 * 161 * If the method is non-static and the class name is an inner class (e.g. has $ in its 162 * last segment), we want to push the 'this' of the outer class first: 163 * OuterClass_InnerClass_Delegate.myMethod( 164 * OuterClass.this, 165 * OuterClass$InnerClass.this, 166 * a, b, c); 167 * 168 * Only one level of inner class is supported right now, for simplicity and because 169 * we don't need more. 170 * 171 * The generated class name is the current class name with "_Delegate" appended to it. 172 * One thing to realize is that we don't care about generics -- since generic types 173 * are erased at build time, they have no influence on the method name being called. 174 */ 175 176 // Add our annotation 177 AnnotationVisitor aw = mDelWriter.visitAnnotation( 178 Type.getObjectType(Type.getInternalName(LayoutlibDelegate.class)).toString(), 179 true); // visible at runtime 180 if (aw != null) { 181 aw.visitEnd(); 182 } 183 184 mDelWriter.visitCode(); 185 186 if (mDelegateLineNumber != null) { 187 Object[] p = mDelegateLineNumber; 188 mDelWriter.visitLineNumber((Integer) p[0], (Label) p[1]); 189 } 190 191 ArrayList<Type> paramTypes = new ArrayList<>(); 192 String delegateClassName = mClassName + DELEGATE_SUFFIX; 193 boolean pushedArg0 = false; 194 int maxStack = 0; 195 196 // Check if the last segment of the class name has inner an class. 197 // Right now we only support one level of inner classes. 198 Type outerType = null; 199 int slash = mClassName.lastIndexOf('/'); 200 int dol = mClassName.lastIndexOf('$'); 201 if (dol != -1 && dol > slash && dol == mClassName.indexOf('$')) { 202 String outerClass = mClassName.substring(0, dol); 203 outerType = Type.getObjectType(outerClass); 204 205 // Change a delegate class name to "com/foo/Outer_Inner_Delegate" 206 delegateClassName = delegateClassName.replace('$', '_'); 207 } 208 209 // For an instance method (e.g. non-static), push the 'this' preceded 210 // by the 'this' of any outer class, if any. 211 if (!mIsStatic) { 212 213 if (outerType != null && !mIsStaticInnerClass) { 214 // The first-level inner class has a package-protected member called 'this$0' 215 // that points to the outer class. 216 217 // Push this.getField("this$0") on the call stack. 218 mDelWriter.visitVarInsn(Opcodes.ALOAD, 0); // var 0 = this 219 mDelWriter.visitFieldInsn(Opcodes.GETFIELD, 220 mClassName, // class where the field is defined 221 "this$0", // field name 222 outerType.getDescriptor()); // type of the field 223 maxStack++; 224 paramTypes.add(outerType); 225 226 } 227 228 // Push "this" for the instance method, which is always ALOAD 0 229 mDelWriter.visitVarInsn(Opcodes.ALOAD, 0); 230 maxStack++; 231 pushedArg0 = true; 232 paramTypes.add(Type.getObjectType(mClassName)); 233 } 234 235 // Push all other arguments. Start at arg 1 if we already pushed 'this' above. 236 Type[] argTypes = Type.getArgumentTypes(mDesc); 237 int maxLocals = pushedArg0 ? 1 : 0; 238 for (Type t : argTypes) { 239 int size = t.getSize(); 240 mDelWriter.visitVarInsn(t.getOpcode(Opcodes.ILOAD), maxLocals); 241 maxLocals += size; 242 maxStack += size; 243 paramTypes.add(t); 244 } 245 246 // Construct the descriptor of the delegate based on the parameters 247 // we pushed on the call stack. The return type remains unchanged. 248 String desc = Type.getMethodDescriptor( 249 Type.getReturnType(mDesc), 250 paramTypes.toArray(new Type[paramTypes.size()])); 251 252 // Invoke the static delegate 253 mDelWriter.visitMethodInsn(Opcodes.INVOKESTATIC, 254 delegateClassName, 255 mMethodName, 256 desc, 257 false); 258 259 Type returnType = Type.getReturnType(mDesc); 260 mDelWriter.visitInsn(returnType.getOpcode(Opcodes.IRETURN)); 261 262 mDelWriter.visitMaxs(maxStack, maxLocals); 263 mDelWriter.visitEnd(); 264 265 // For debugging now. Maybe we should collect these and store them in 266 // a text file for helping create the delegates. We could also compare 267 // the text file to a golden and break the build on unsupported changes 268 // or regressions. Even better we could fancy-print something that looks 269 // like the expected Java method declaration. 270 mLog.debug("Delegate: %1$s # %2$s %3$s", delegateClassName, mMethodName, desc); 271 } 272 273 /* Pass down to visitor writer. In this implementation, either do nothing. */ 274 @Override 275 public void visitCode() { 276 if (mOrgWriter != null) { 277 mOrgWriter.visitCode(); 278 } 279 } 280 281 /* 282 * visitMaxs is called just before visitEnd if there was any code to rewrite. 283 */ 284 @Override 285 public void visitMaxs(int maxStack, int maxLocals) { 286 if (mOrgWriter != null) { 287 mOrgWriter.visitMaxs(maxStack, maxLocals); 288 } 289 } 290 291 /** End of visiting. Generate the delegating code. */ 292 @Override 293 public void visitEnd() { 294 if (mOrgWriter != null) { 295 mOrgWriter.visitEnd(); 296 } 297 generateDelegateCode(); 298 } 299 300 /* Writes all annotation from the original method. */ 301 @Override 302 public AnnotationVisitor visitAnnotation(String desc, boolean visible) { 303 if (mOrgWriter != null) { 304 return mOrgWriter.visitAnnotation(desc, visible); 305 } else { 306 return null; 307 } 308 } 309 310 /* Writes all annotation default values from the original method. */ 311 @Override 312 public AnnotationVisitor visitAnnotationDefault() { 313 if (mOrgWriter != null) { 314 return mOrgWriter.visitAnnotationDefault(); 315 } else { 316 return null; 317 } 318 } 319 320 @Override 321 public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, 322 boolean visible) { 323 if (mOrgWriter != null) { 324 return mOrgWriter.visitParameterAnnotation(parameter, desc, visible); 325 } else { 326 return null; 327 } 328 } 329 330 /* Writes all attributes from the original method. */ 331 @Override 332 public void visitAttribute(Attribute attr) { 333 if (mOrgWriter != null) { 334 mOrgWriter.visitAttribute(attr); 335 } 336 } 337 338 /* 339 * Only writes the first line number present in the original code so that source 340 * viewers can direct to the correct method, even if the content doesn't match. 341 */ 342 @Override 343 public void visitLineNumber(int line, Label start) { 344 // Capture the first line values for the new delegate method 345 if (mDelegateLineNumber == null) { 346 mDelegateLineNumber = new Object[] { line, start }; 347 } 348 if (mOrgWriter != null) { 349 mOrgWriter.visitLineNumber(line, start); 350 } 351 } 352 353 @Override 354 public void visitInsn(int opcode) { 355 if (mOrgWriter != null) { 356 mOrgWriter.visitInsn(opcode); 357 } 358 } 359 360 @Override 361 public void visitLabel(Label label) { 362 if (mOrgWriter != null) { 363 mOrgWriter.visitLabel(label); 364 } 365 } 366 367 @Override 368 public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { 369 if (mOrgWriter != null) { 370 mOrgWriter.visitTryCatchBlock(start, end, handler, type); 371 } 372 } 373 374 @Override 375 public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { 376 if (mOrgWriter != null) { 377 mOrgWriter.visitMethodInsn(opcode, owner, name, desc, itf); 378 } 379 } 380 381 @Override 382 public void visitFieldInsn(int opcode, String owner, String name, String desc) { 383 if (mOrgWriter != null) { 384 mOrgWriter.visitFieldInsn(opcode, owner, name, desc); 385 } 386 } 387 388 @Override 389 public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { 390 if (mOrgWriter != null) { 391 mOrgWriter.visitFrame(type, nLocal, local, nStack, stack); 392 } 393 } 394 395 @Override 396 public void visitIincInsn(int var, int increment) { 397 if (mOrgWriter != null) { 398 mOrgWriter.visitIincInsn(var, increment); 399 } 400 } 401 402 @Override 403 public void visitIntInsn(int opcode, int operand) { 404 if (mOrgWriter != null) { 405 mOrgWriter.visitIntInsn(opcode, operand); 406 } 407 } 408 409 @Override 410 public void visitJumpInsn(int opcode, Label label) { 411 if (mOrgWriter != null) { 412 mOrgWriter.visitJumpInsn(opcode, label); 413 } 414 } 415 416 @Override 417 public void visitLdcInsn(Object cst) { 418 if (mOrgWriter != null) { 419 mOrgWriter.visitLdcInsn(cst); 420 } 421 } 422 423 @Override 424 public void visitLocalVariable(String name, String desc, String signature, 425 Label start, Label end, int index) { 426 if (mOrgWriter != null) { 427 mOrgWriter.visitLocalVariable(name, desc, signature, start, end, index); 428 } 429 } 430 431 @Override 432 public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { 433 if (mOrgWriter != null) { 434 mOrgWriter.visitLookupSwitchInsn(dflt, keys, labels); 435 } 436 } 437 438 @Override 439 public void visitMultiANewArrayInsn(String desc, int dims) { 440 if (mOrgWriter != null) { 441 mOrgWriter.visitMultiANewArrayInsn(desc, dims); 442 } 443 } 444 445 @Override 446 public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) { 447 if (mOrgWriter != null) { 448 mOrgWriter.visitTableSwitchInsn(min, max, dflt, labels); 449 } 450 } 451 452 @Override 453 public void visitTypeInsn(int opcode, String type) { 454 if (mOrgWriter != null) { 455 mOrgWriter.visitTypeInsn(opcode, type); 456 } 457 } 458 459 @Override 460 public void visitVarInsn(int opcode, int var) { 461 if (mOrgWriter != null) { 462 mOrgWriter.visitVarInsn(opcode, var); 463 } 464 } 465 466} 467