DelegateMethodAdapterpublic class DelegateMethodAdapter extends org.objectweb.asm.MethodVisitor This method adapter generates delegate methods.
Given a method {@code SomeClass.MethodName()}, this generates 1 or 2 methods:
- A copy of the original method named {@code SomeClass.MethodName_Original()}.
The content is the original method as-is from the reader.
This step is omitted if the method is native, since it has no Java implementation.
- A brand new implementation of {@code SomeClass.MethodName()} which calls to a
non-existing method named {@code SomeClass_Delegate.MethodName()}.
The implementation of this 'delegate' method is done in layoutlib_brigde.
A method visitor is generally constructed to generate a single method; however
here we might want to generate one or two depending on the context. To achieve
that, the visitor here generates the 'original' method and acts as a no-op if
no such method exists (e.g. when the original is a native method).
The delegate method is generated after the {@code visitEnd} of the original method
or by having the class adapter directly call {@link #generateDelegateCode()}
for native methods.
When generating the 'delegate', the implementation generates a call to a class
class named <className>_Delegate with static methods matching
the methods to be overridden here. The methods have the same return type.
The argument type list is the same except the "this" reference is passed first
for non-static methods.
A new annotation is added to these 'delegate' methods so that we can easily find them
for automated testing.
This class isn't intended to be generic or reusable.
It is called by {@link DelegateClassAdapter}, which takes care of properly initializing
the two method writers for the original and the delegate class, as needed, with their
expected names.
The class adapter also takes care of calling {@link #generateDelegateCode()} directly for
a native and use the visitor pattern for non-natives.
Note that native methods have, by definition, no code so there's nothing a visitor
can visit.
Instances of this class are not re-usable.
The class adapter creates a new instance for each method. |
Fields Summary |
---|
public static final String | DELEGATE_SUFFIXSuffix added to delegate classes. | private org.objectweb.asm.MethodVisitor | mOrgWriterThe parent method writer to copy of the original method.
Null when dealing with a native original method. | private org.objectweb.asm.MethodVisitor | mDelWriterThe parent method writer to generate the delegating method. Never null. | private String | mDescThe original method descriptor (return type + argument types.) | private final boolean | mIsStaticTrue if the original method is static. | private final String | mClassNameThe internal class name (e.g. com/android/SomeClass$InnerClass .) | private final String | mMethodNameThe method name. | private final Log | mLogLogger object. | private Object[] | mDelegateLineNumberArray used to capture the first line number information from the original method
and duplicate it in the delegate. |
Constructors Summary |
---|
public DelegateMethodAdapter(Log log, org.objectweb.asm.MethodVisitor mvOriginal, org.objectweb.asm.MethodVisitor mvDelegate, String className, String methodName, String desc, boolean isStatic)Creates a new {@link DelegateMethodAdapter} that will transform this method
into a delegate call.
See {@link DelegateMethodAdapter} for more details.
super(Opcodes.ASM4);
mLog = log;
mOrgWriter = mvOriginal;
mDelWriter = mvDelegate;
mClassName = className;
mMethodName = methodName;
mDesc = desc;
mIsStatic = isStatic;
|
Methods Summary |
---|
public void | generateDelegateCode()Generates the new code for the method.
For native methods, this must be invoked directly by {@link DelegateClassAdapter}
(since they have no code to visit).
Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to
return this instance of {@link DelegateMethodAdapter} and let the normal visitor pattern
invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then
this method will be invoked from {@link MethodVisitor#visitEnd()}.
/*
* The goal is to generate a call to a static delegate method.
* If this method is non-static, the first parameter will be 'this'.
* All the parameters must be passed and then the eventual return type returned.
*
* Example, let's say we have a method such as
* public void myMethod(int a, Object b, ArrayList<String> c) { ... }
*
* We'll want to create a body that calls a delegate method like this:
* TheClass_Delegate.myMethod(this, a, b, c);
*
* If the method is non-static and the class name is an inner class (e.g. has $ in its
* last segment), we want to push the 'this' of the outer class first:
* OuterClass_InnerClass_Delegate.myMethod(
* OuterClass.this,
* OuterClass$InnerClass.this,
* a, b, c);
*
* Only one level of inner class is supported right now, for simplicity and because
* we don't need more.
*
* The generated class name is the current class name with "_Delegate" appended to it.
* One thing to realize is that we don't care about generics -- since generic types
* are erased at build time, they have no influence on the method name being called.
*/
// Add our annotation
AnnotationVisitor aw = mDelWriter.visitAnnotation(
Type.getObjectType(Type.getInternalName(LayoutlibDelegate.class)).toString(),
true); // visible at runtime
if (aw != null) {
aw.visitEnd();
}
mDelWriter.visitCode();
if (mDelegateLineNumber != null) {
Object[] p = mDelegateLineNumber;
mDelWriter.visitLineNumber((Integer) p[0], (Label) p[1]);
}
ArrayList<Type> paramTypes = new ArrayList<Type>();
String delegateClassName = mClassName + DELEGATE_SUFFIX;
boolean pushedArg0 = false;
int maxStack = 0;
// Check if the last segment of the class name has inner an class.
// Right now we only support one level of inner classes.
Type outerType = null;
int slash = mClassName.lastIndexOf('/");
int dol = mClassName.lastIndexOf('$");
if (dol != -1 && dol > slash && dol == mClassName.indexOf('$")) {
String outerClass = mClassName.substring(0, dol);
outerType = Type.getObjectType(outerClass);
// Change a delegate class name to "com/foo/Outer_Inner_Delegate"
delegateClassName = delegateClassName.replace('$", '_");
}
// For an instance method (e.g. non-static), push the 'this' preceded
// by the 'this' of any outer class, if any.
if (!mIsStatic) {
if (outerType != null) {
// The first-level inner class has a package-protected member called 'this$0'
// that points to the outer class.
// Push this.getField("this$0") on the call stack.
mDelWriter.visitVarInsn(Opcodes.ALOAD, 0); // var 0 = this
mDelWriter.visitFieldInsn(Opcodes.GETFIELD,
mClassName, // class where the field is defined
"this$0", // field name
outerType.getDescriptor()); // type of the field
maxStack++;
paramTypes.add(outerType);
}
// Push "this" for the instance method, which is always ALOAD 0
mDelWriter.visitVarInsn(Opcodes.ALOAD, 0);
maxStack++;
pushedArg0 = true;
paramTypes.add(Type.getObjectType(mClassName));
}
// Push all other arguments. Start at arg 1 if we already pushed 'this' above.
Type[] argTypes = Type.getArgumentTypes(mDesc);
int maxLocals = pushedArg0 ? 1 : 0;
for (Type t : argTypes) {
int size = t.getSize();
mDelWriter.visitVarInsn(t.getOpcode(Opcodes.ILOAD), maxLocals);
maxLocals += size;
maxStack += size;
paramTypes.add(t);
}
// Construct the descriptor of the delegate based on the parameters
// we pushed on the call stack. The return type remains unchanged.
String desc = Type.getMethodDescriptor(
Type.getReturnType(mDesc),
paramTypes.toArray(new Type[paramTypes.size()]));
// Invoke the static delegate
mDelWriter.visitMethodInsn(Opcodes.INVOKESTATIC,
delegateClassName,
mMethodName,
desc);
Type returnType = Type.getReturnType(mDesc);
mDelWriter.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
mDelWriter.visitMaxs(maxStack, maxLocals);
mDelWriter.visitEnd();
// For debugging now. Maybe we should collect these and store them in
// a text file for helping create the delegates. We could also compare
// the text file to a golden and break the build on unsupported changes
// or regressions. Even better we could fancy-print something that looks
// like the expected Java method declaration.
mLog.debug("Delegate: %1$s # %2$s %3$s", delegateClassName, mMethodName, desc);
| public org.objectweb.asm.AnnotationVisitor | visitAnnotation(java.lang.String desc, boolean visible)
if (mOrgWriter != null) {
return mOrgWriter.visitAnnotation(desc, visible);
} else {
return null;
}
| public org.objectweb.asm.AnnotationVisitor | visitAnnotationDefault()
if (mOrgWriter != null) {
return mOrgWriter.visitAnnotationDefault();
} else {
return null;
}
| public void | visitAttribute(org.objectweb.asm.Attribute attr)
if (mOrgWriter != null) {
mOrgWriter.visitAttribute(attr);
}
| public void | visitCode()
if (mOrgWriter != null) {
mOrgWriter.visitCode();
}
| public void | visitEnd()End of visiting. Generate the delegating code.
if (mOrgWriter != null) {
mOrgWriter.visitEnd();
}
generateDelegateCode();
| public void | visitFieldInsn(int opcode, java.lang.String owner, java.lang.String name, java.lang.String desc)
if (mOrgWriter != null) {
mOrgWriter.visitFieldInsn(opcode, owner, name, desc);
}
| public void | visitFrame(int type, int nLocal, java.lang.Object[] local, int nStack, java.lang.Object[] stack)
if (mOrgWriter != null) {
mOrgWriter.visitFrame(type, nLocal, local, nStack, stack);
}
| public void | visitIincInsn(int var, int increment)
if (mOrgWriter != null) {
mOrgWriter.visitIincInsn(var, increment);
}
| public void | visitInsn(int opcode)
if (mOrgWriter != null) {
mOrgWriter.visitInsn(opcode);
}
| public void | visitIntInsn(int opcode, int operand)
if (mOrgWriter != null) {
mOrgWriter.visitIntInsn(opcode, operand);
}
| public void | visitJumpInsn(int opcode, org.objectweb.asm.Label label)
if (mOrgWriter != null) {
mOrgWriter.visitJumpInsn(opcode, label);
}
| public void | visitLabel(org.objectweb.asm.Label label)
if (mOrgWriter != null) {
mOrgWriter.visitLabel(label);
}
| public void | visitLdcInsn(java.lang.Object cst)
if (mOrgWriter != null) {
mOrgWriter.visitLdcInsn(cst);
}
| public void | visitLineNumber(int line, org.objectweb.asm.Label start)
// Capture the first line values for the new delegate method
if (mDelegateLineNumber == null) {
mDelegateLineNumber = new Object[] { line, start };
}
if (mOrgWriter != null) {
mOrgWriter.visitLineNumber(line, start);
}
| public void | visitLocalVariable(java.lang.String name, java.lang.String desc, java.lang.String signature, org.objectweb.asm.Label start, org.objectweb.asm.Label end, int index)
if (mOrgWriter != null) {
mOrgWriter.visitLocalVariable(name, desc, signature, start, end, index);
}
| public void | visitLookupSwitchInsn(org.objectweb.asm.Label dflt, int[] keys, org.objectweb.asm.Label[] labels)
if (mOrgWriter != null) {
mOrgWriter.visitLookupSwitchInsn(dflt, keys, labels);
}
| public void | visitMaxs(int maxStack, int maxLocals)
if (mOrgWriter != null) {
mOrgWriter.visitMaxs(maxStack, maxLocals);
}
| public void | visitMethodInsn(int opcode, java.lang.String owner, java.lang.String name, java.lang.String desc)
if (mOrgWriter != null) {
mOrgWriter.visitMethodInsn(opcode, owner, name, desc);
}
| public void | visitMultiANewArrayInsn(java.lang.String desc, int dims)
if (mOrgWriter != null) {
mOrgWriter.visitMultiANewArrayInsn(desc, dims);
}
| public org.objectweb.asm.AnnotationVisitor | visitParameterAnnotation(int parameter, java.lang.String desc, boolean visible)
if (mOrgWriter != null) {
return mOrgWriter.visitParameterAnnotation(parameter, desc, visible);
} else {
return null;
}
| public void | visitTableSwitchInsn(int min, int max, org.objectweb.asm.Label dflt, org.objectweb.asm.Label[] labels)
if (mOrgWriter != null) {
mOrgWriter.visitTableSwitchInsn(min, max, dflt, labels);
}
| public void | visitTryCatchBlock(org.objectweb.asm.Label start, org.objectweb.asm.Label end, org.objectweb.asm.Label handler, java.lang.String type)
if (mOrgWriter != null) {
mOrgWriter.visitTryCatchBlock(start, end, handler, type);
}
| public void | visitTypeInsn(int opcode, java.lang.String type)
if (mOrgWriter != null) {
mOrgWriter.visitTypeInsn(opcode, type);
}
| public void | visitVarInsn(int opcode, int var)
if (mOrgWriter != null) {
mOrgWriter.visitVarInsn(opcode, var);
}
|
|