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