am 2b5f2d01: am e2b41b0d: Merge "Laoutlib_creator: keep original of delegate methods."

* commit '2b5f2d01558ba338042f486c754f63873c4061fe':
  Laoutlib_creator: keep original of delegate methods.
This commit is contained in:
Raphael Moll
2011-06-22 18:05:22 -07:00
committed by Android Git Automerger
8 changed files with 319 additions and 117 deletions

View File

@ -30,9 +30,9 @@ The Android JAR can't be used directly in Eclipse:
Consequently this tool: Consequently this tool:
- parses the input JAR, - parses the input JAR,
- modifies some of the classes directly using some bytecode manipulation, - modifies some of the classes directly using some bytecode manipulation,
- filters some packages and removes some that we don't want to end in the output JAR, - filters some packages and removes those we don't want in the output JAR,
- injects some new classes, - injects some new classes,
- and generates a modified JAR file that is suitable for the Android plugin - generates a modified JAR file that is suitable for the Android plugin
for Eclipse to perform rendering. for Eclipse to perform rendering.
The ASM library is used to do the bytecode modification using its visitor pattern API. The ASM library is used to do the bytecode modification using its visitor pattern API.
@ -63,7 +63,7 @@ with their dependencies and then only keep the ones we want.
To do that, the analyzer is created with a list of base classes to keep -- everything To do that, the analyzer is created with a list of base classes to keep -- everything
that derives from these is kept. Currently the one such class is android.view.View: that derives from these is kept. Currently the one such class is android.view.View:
since we want to render layouts, anything that is sort of the view needs to be kept. since we want to render layouts, anything that is sort of a view needs to be kept.
The analyzer is also given a list of class names to keep in the output. The analyzer is also given a list of class names to keep in the output.
This is done using shell-like glob patterns that filter on the fully-qualified This is done using shell-like glob patterns that filter on the fully-qualified
@ -90,6 +90,7 @@ and lists:
- the classes to inject in the output JAR -- these classes are directly implemented - the classes to inject in the output JAR -- these classes are directly implemented
in layoutlib_create and will be used to interface with the renderer in Eclipse. in layoutlib_create and will be used to interface with the renderer in Eclipse.
- specific methods to override (see method stubs details below). - specific methods to override (see method stubs details below).
- specific methods for which to delegate calls.
- specific methods to remove based on their return type. - specific methods to remove based on their return type.
- specific classes to rename. - specific classes to rename.
@ -114,6 +115,9 @@ Methods are also changed from protected/private to public.
The code of the methods is then kept as-is, except for native methods which are The code of the methods is then kept as-is, except for native methods which are
replaced by a stub. Methods that are to be overridden are also replaced by a stub. replaced by a stub. Methods that are to be overridden are also replaced by a stub.
The transformed class is then fed through the DelegateClassAdapter to implement
method delegates.
Finally fields are also visited and changed from protected/private to public. Finally fields are also visited and changed from protected/private to public.
@ -131,8 +135,7 @@ method being called, its caller object and a flag indicating whether the
method was native. We do not currently provide the parameters. The listener method was native. We do not currently provide the parameters. The listener
can however specify the return value of the overridden method. can however specify the return value of the overridden method.
An extension being worked on is to actually replace these listeners by This strategy is now obsolete and replaced by the method delegates.
direct calls to a delegate class, complete with parameters.
- Strategies - Strategies
@ -160,6 +163,9 @@ methods to override. Instead it removes the original code and replaces it
by a call to a specific OveriddeMethod.invokeX(). The bridge then registers by a call to a specific OveriddeMethod.invokeX(). The bridge then registers
a listener on the method signature and can provide an implementation. a listener on the method signature and can provide an implementation.
This strategy is now obsolete and replaced by the method delegates.
See strategy 5 below.
3- Renaming classes 3- Renaming classes
@ -195,6 +201,24 @@ example, the inner class Paint$Style in the Paint class should be discarded and
bridge will provide its own implementation. bridge will provide its own implementation.
5- Method Delegates
This strategy is used to override method implementations.
Given a method SomeClass.MethodName(), 1 or 2 methods are generated:
a- A copy of the original method named 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.
b- A brand new implementation of SomeClass.MethodName() which calls to a
non-existing static method named SomeClass_Delegate.MethodName().
The implementation of this 'delegate' method is done in layoutlib_brigde.
The delegate method is a static method.
If the original method is non-static, the delegate method receives the original 'this'
as its first argument. If the original method is an inner non-static method, it also
receives the inner 'this' as the second argument.
- References - - References -
-------------- --------------

View File

@ -51,6 +51,8 @@ public final class CreateInfo implements ICreateInfo {
* Returns The list of methods to stub out. Each entry must be in the form * Returns The list of methods to stub out. Each entry must be in the form
* "package.package.OuterClass$InnerClass#MethodName". * "package.package.OuterClass$InnerClass#MethodName".
* The list can be empty but must not be null. * The list can be empty but must not be null.
* <p/>
* This usage is deprecated. Please use method 'delegates' instead.
*/ */
public String[] getOverriddenMethods() { public String[] getOverriddenMethods() {
return OVERRIDDEN_METHODS; return OVERRIDDEN_METHODS;
@ -158,6 +160,7 @@ public final class CreateInfo implements ICreateInfo {
/** /**
* The list of methods to stub out. Each entry must be in the form * The list of methods to stub out. Each entry must be in the form
* "package.package.OuterClass$InnerClass#MethodName". * "package.package.OuterClass$InnerClass#MethodName".
* This usage is deprecated. Please use method 'delegates' instead.
*/ */
private final static String[] OVERRIDDEN_METHODS = new String[] { private final static String[] OVERRIDDEN_METHODS = new String[] {
}; };

View File

@ -31,6 +31,11 @@ import java.util.Set;
*/ */
public class DelegateClassAdapter extends ClassAdapter { public class DelegateClassAdapter extends ClassAdapter {
/** Suffix added to original methods. */
private static final String ORIGINAL_SUFFIX = "_Original";
private static String CONSTRUCTOR = "<init>";
private static String CLASS_INIT = "<clinit>";
public final static String ALL_NATIVES = "<<all_natives>>"; public final static String ALL_NATIVES = "<<all_natives>>";
private final String mClassName; private final String mClassName;
@ -73,22 +78,55 @@ public class DelegateClassAdapter extends ClassAdapter {
boolean useDelegate = (isNative && mDelegateMethods.contains(ALL_NATIVES)) || boolean useDelegate = (isNative && mDelegateMethods.contains(ALL_NATIVES)) ||
mDelegateMethods.contains(name); mDelegateMethods.contains(name);
if (useDelegate) { if (!useDelegate) {
// remove native // Not creating a delegate for this method, pass it as-is from the reader
access = access & ~Opcodes.ACC_NATIVE; // to the writer.
return super.visitMethod(access, name, desc, signature, exceptions);
} }
MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
if (useDelegate) { if (useDelegate) {
DelegateMethodAdapter a = new DelegateMethodAdapter(mLog, mw, mClassName, if (CONSTRUCTOR.equals(name) || CLASS_INIT.equals(name)) {
name, desc, isStatic); // We don't currently support generating delegates for constructors.
if (isNative) { throw new UnsupportedOperationException(
// A native has no code to visit, so we need to generate it directly. String.format(
a.generateCode(); "Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)", //$NON-NLS-1$
} else { mClassName, name, desc));
return a;
} }
} }
return mw;
if (isNative) {
// Remove native flag
access = access & ~Opcodes.ACC_NATIVE;
MethodVisitor mwDelegate = super.visitMethod(access, name, desc, signature, exceptions);
DelegateMethodAdapter2 a = new DelegateMethodAdapter2(
mLog, null /*mwOriginal*/, mwDelegate, mClassName, name, desc, isStatic);
// A native has no code to visit, so we need to generate it directly.
a.generateDelegateCode();
return mwDelegate;
}
// Given a non-native SomeClass.MethodName(), we want to generate 2 methods:
// - A copy of the original method named SomeClass.MethodName_Original().
// The content is the original method as-is from the reader.
// - A brand new implementation of SomeClass.MethodName() which calls to a
// non-existing method named SomeClass_Delegate.MethodName().
// The implementation of this 'delegate' method is done in layoutlib_brigde.
int accessDelegate = access;
// change access to public for the original one
access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
access |= Opcodes.ACC_PUBLIC;
MethodVisitor mwOriginal = super.visitMethod(access, name + ORIGINAL_SUFFIX,
desc, signature, exceptions);
MethodVisitor mwDelegate = super.visitMethod(accessDelegate, name,
desc, signature, exceptions);
DelegateMethodAdapter2 a = new DelegateMethodAdapter2(
mLog, mwOriginal, mwDelegate, mClassName, name, desc, isStatic);
return a;
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2008 The Android Open Source Project * Copyright (C) 2010 The Android Open Source Project
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -30,36 +30,57 @@ import org.objectweb.asm.Type;
import java.util.ArrayList; import java.util.ArrayList;
/** /**
* This method adapter rewrites a method by discarding the original code and generating * This method adapter generates delegate methods.
* a call to a delegate. Original annotations are passed along unchanged.
* <p/> * <p/>
* Calls are delegated to a class named <code>&lt;className&gt;_Delegate</code> with * Given a method {@code SomeClass.MethodName()}, this generates 1 or 2 methods:
* static methods matching the methods to be overridden here. The methods have the * <ul>
* same return type. The argument type list is the same except the "this" reference is * <li> A copy of the original method named {@code SomeClass.MethodName_Original()}.
* passed first for non-static methods. * 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.
* <li> 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.
* </ul>
* 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 <em>directly</em> call {@link #generateDelegateCode()}
* for native methods.
* <p/> * <p/>
* A new annotation is added. * When generating the 'delegate', the implementation generates a call to a class
* class named <code>&lt;className&gt;_Delegate</code> 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.
* <p/> * <p/>
* Note that native methods have, by definition, no code so there's nothing a visitor * A new annotation is added to these 'delegate' methods so that we can easily find them
* can visit. That means the caller must call {@link #generateCode()} directly for * for automated testing.
* <p/>
* 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.
* <p/>
* The class adapter also takes care of calling {@link #generateDelegateCode()} directly for
* a native and use the visitor pattern for non-natives. * 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.
* <p/> * <p/>
* Instances of this class are not re-usable. You need a new instance for each method. * Instances of this class are not re-usable.
* The class adapter creates a new instance for each method.
*/ */
class DelegateMethodAdapter implements MethodVisitor { class DelegateMethodAdapter2 implements MethodVisitor {
/** /** Suffix added to delegate classes. */
* Suffix added to delegate classes.
*/
public static final String DELEGATE_SUFFIX = "_Delegate"; public static final String DELEGATE_SUFFIX = "_Delegate";
private static String CONSTRUCTOR = "<init>"; /** The parent method writer to copy of the original method.
private static String CLASS_INIT = "<clinit>"; * Null when dealing with a native original method. */
private MethodVisitor mOrgWriter;
/** The parent method writer */ /** The parent method writer to generate the delegating method. Never null. */
private MethodVisitor mParentVisitor; private MethodVisitor mDelWriter;
/** Flag to output the first line number. */
private boolean mOutputFirstLineNumber = true;
/** The original method descriptor (return type + argument types.) */ /** The original method descriptor (return type + argument types.) */
private String mDesc; private String mDesc;
/** True if the original method is static. */ /** True if the original method is static. */
@ -70,17 +91,22 @@ class DelegateMethodAdapter implements MethodVisitor {
private final String mMethodName; private final String mMethodName;
/** Logger object. */ /** Logger object. */
private final Log mLog; private final Log mLog;
/** True if {@link #visitCode()} has been invoked. */
private boolean mVisitCodeCalled; /** Array used to capture the first line number information from the original method
* and duplicate it in the delegate. */
private Object[] mDelegateLineNumber;
/** /**
* Creates a new {@link DelegateMethodAdapter} that will transform this method * Creates a new {@link DelegateMethodAdapter2} that will transform this method
* into a delegate call. * into a delegate call.
* <p/> * <p/>
* See {@link DelegateMethodAdapter} for more details. * See {@link DelegateMethodAdapter2} for more details.
* *
* @param log The logger object. Must not be null. * @param log The logger object. Must not be null.
* @param mv the method visitor to which this adapter must delegate calls. * @param mvOriginal The parent method writer to copy of the original method.
* Must be {@code null} when dealing with a native original method.
* @param mvDelegate The parent method writer to generate the delegating method.
* Must never be null.
* @param className The internal class name of the class to visit, * @param className The internal class name of the class to visit,
* e.g. <code>com/android/SomeClass$InnerClass</code>. * e.g. <code>com/android/SomeClass$InnerClass</code>.
* @param methodName The simple name of the method. * @param methodName The simple name of the method.
@ -88,28 +114,20 @@ class DelegateMethodAdapter implements MethodVisitor {
* {@link Type#getArgumentTypes(String)}) * {@link Type#getArgumentTypes(String)})
* @param isStatic True if the method is declared static. * @param isStatic True if the method is declared static.
*/ */
public DelegateMethodAdapter(Log log, public DelegateMethodAdapter2(Log log,
MethodVisitor mv, MethodVisitor mvOriginal,
MethodVisitor mvDelegate,
String className, String className,
String methodName, String methodName,
String desc, String desc,
boolean isStatic) { boolean isStatic) {
mLog = log; mLog = log;
mParentVisitor = mv; mOrgWriter = mvOriginal;
mDelWriter = mvDelegate;
mClassName = className; mClassName = className;
mMethodName = methodName; mMethodName = methodName;
mDesc = desc; mDesc = desc;
mIsStatic = isStatic; mIsStatic = isStatic;
if (CONSTRUCTOR.equals(methodName) || CLASS_INIT.equals(methodName)) {
// We're going to simplify by not supporting constructors.
// The only trick with a constructor is to find the proper super constructor
// and call it (and deciding if we should mirror the original method call to
// a custom constructor or call a default one.)
throw new UnsupportedOperationException(
String.format("Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)",
className, methodName, desc));
}
} }
/** /**
@ -119,25 +137,25 @@ class DelegateMethodAdapter implements MethodVisitor {
* (since they have no code to visit). * (since they have no code to visit).
* <p/> * <p/>
* Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to * Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to
* return this instance of {@link DelegateMethodAdapter} and let the normal visitor pattern * return this instance of {@link DelegateMethodAdapter2} and let the normal visitor pattern
* invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then * invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then
* this method will be invoked from {@link MethodVisitor#visitEnd()}. * this method will be invoked from {@link MethodVisitor#visitEnd()}.
*/ */
public void generateCode() { public void generateDelegateCode() {
/* /*
* The goal is to generate a call to a static delegate method. * The goal is to generate a call to a static delegate method.
* If this method is non-static, the first parameter will be 'this'. * 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. * All the parameters must be passed and then the eventual return type returned.
* *
* Example, let's say we have a method such as * Example, let's say we have a method such as
* public void method_1(int a, Object b, ArrayList<String> c) { ... } * public void myMethod(int a, Object b, ArrayList<String> c) { ... }
* *
* We'll want to create a body that calls a delegate method like this: * We'll want to create a body that calls a delegate method like this:
* TheClass_Delegate.method_1(this, a, b, c); * 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 * 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: * last segment), we want to push the 'this' of the outer class first:
* OuterClass_InnerClass_Delegate.method_1( * OuterClass_InnerClass_Delegate.myMethod(
* OuterClass.this, * OuterClass.this,
* OuterClass$InnerClass.this, * OuterClass$InnerClass.this,
* a, b, c); * a, b, c);
@ -147,20 +165,22 @@ class DelegateMethodAdapter implements MethodVisitor {
* *
* The generated class name is the current class name with "_Delegate" appended to it. * 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 * One thing to realize is that we don't care about generics -- since generic types
* are erased at runtime, they have no influence on the method name being called. * are erased at build time, they have no influence on the method name being called.
*/ */
// Add our annotation // Add our annotation
AnnotationVisitor aw = mParentVisitor.visitAnnotation( AnnotationVisitor aw = mDelWriter.visitAnnotation(
Type.getObjectType(Type.getInternalName(LayoutlibDelegate.class)).toString(), Type.getObjectType(Type.getInternalName(LayoutlibDelegate.class)).toString(),
true); // visible at runtime true); // visible at runtime
aw.visitEnd(); if (aw != null) {
aw.visitEnd();
}
if (!mVisitCodeCalled) { mDelWriter.visitCode();
// If this is a direct call to generateCode() as done by DelegateClassAdapter
// for natives, visitCode() hasn't been called yet. if (mDelegateLineNumber != null) {
mParentVisitor.visitCode(); Object[] p = mDelegateLineNumber;
mVisitCodeCalled = true; mDelWriter.visitLineNumber((Integer) p[0], (Label) p[1]);
} }
ArrayList<Type> paramTypes = new ArrayList<Type>(); ArrayList<Type> paramTypes = new ArrayList<Type>();
@ -186,8 +206,8 @@ class DelegateMethodAdapter implements MethodVisitor {
// that points to the outer class. // that points to the outer class.
// Push this.getField("this$0") on the call stack. // Push this.getField("this$0") on the call stack.
mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0); // var 0 = this mDelWriter.visitVarInsn(Opcodes.ALOAD, 0); // var 0 = this
mParentVisitor.visitFieldInsn(Opcodes.GETFIELD, mDelWriter.visitFieldInsn(Opcodes.GETFIELD,
mClassName, // class where the field is defined mClassName, // class where the field is defined
"this$0", // field name "this$0", // field name
outerType.getDescriptor()); // type of the field outerType.getDescriptor()); // type of the field
@ -196,7 +216,7 @@ class DelegateMethodAdapter implements MethodVisitor {
} }
// Push "this" for the instance method, which is always ALOAD 0 // Push "this" for the instance method, which is always ALOAD 0
mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0); mDelWriter.visitVarInsn(Opcodes.ALOAD, 0);
maxStack++; maxStack++;
pushedArg0 = true; pushedArg0 = true;
paramTypes.add(Type.getObjectType(mClassName)); paramTypes.add(Type.getObjectType(mClassName));
@ -207,7 +227,7 @@ class DelegateMethodAdapter implements MethodVisitor {
int maxLocals = pushedArg0 ? 1 : 0; int maxLocals = pushedArg0 ? 1 : 0;
for (Type t : argTypes) { for (Type t : argTypes) {
int size = t.getSize(); int size = t.getSize();
mParentVisitor.visitVarInsn(t.getOpcode(Opcodes.ILOAD), maxLocals); mDelWriter.visitVarInsn(t.getOpcode(Opcodes.ILOAD), maxLocals);
maxLocals += size; maxLocals += size;
maxStack += size; maxStack += size;
paramTypes.add(t); paramTypes.add(t);
@ -220,16 +240,16 @@ class DelegateMethodAdapter implements MethodVisitor {
paramTypes.toArray(new Type[paramTypes.size()])); paramTypes.toArray(new Type[paramTypes.size()]));
// Invoke the static delegate // Invoke the static delegate
mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, mDelWriter.visitMethodInsn(Opcodes.INVOKESTATIC,
delegateClassName, delegateClassName,
mMethodName, mMethodName,
desc); desc);
Type returnType = Type.getReturnType(mDesc); Type returnType = Type.getReturnType(mDesc);
mParentVisitor.visitInsn(returnType.getOpcode(Opcodes.IRETURN)); mDelWriter.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
mParentVisitor.visitMaxs(maxStack, maxLocals); mDelWriter.visitMaxs(maxStack, maxLocals);
mParentVisitor.visitEnd(); mDelWriter.visitEnd();
// For debugging now. Maybe we should collect these and store them in // For debugging now. Maybe we should collect these and store them in
// a text file for helping create the delegates. We could also compare // a text file for helping create the delegates. We could also compare
@ -241,42 +261,60 @@ class DelegateMethodAdapter implements MethodVisitor {
/* Pass down to visitor writer. In this implementation, either do nothing. */ /* Pass down to visitor writer. In this implementation, either do nothing. */
public void visitCode() { public void visitCode() {
mVisitCodeCalled = true; if (mOrgWriter != null) {
mParentVisitor.visitCode(); mOrgWriter.visitCode();
}
} }
/* /*
* visitMaxs is called just before visitEnd if there was any code to rewrite. * visitMaxs is called just before visitEnd if there was any code to rewrite.
* Skip the original.
*/ */
public void visitMaxs(int maxStack, int maxLocals) { public void visitMaxs(int maxStack, int maxLocals) {
if (mOrgWriter != null) {
mOrgWriter.visitMaxs(maxStack, maxLocals);
}
} }
/** /** End of visiting. Generate the delegating code. */
* End of visiting. Generate the messaging code.
*/
public void visitEnd() { public void visitEnd() {
generateCode(); if (mOrgWriter != null) {
mOrgWriter.visitEnd();
}
generateDelegateCode();
} }
/* Writes all annotation from the original method. */ /* Writes all annotation from the original method. */
public AnnotationVisitor visitAnnotation(String desc, boolean visible) { public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return mParentVisitor.visitAnnotation(desc, visible); if (mOrgWriter != null) {
return mOrgWriter.visitAnnotation(desc, visible);
} else {
return null;
}
} }
/* Writes all annotation default values from the original method. */ /* Writes all annotation default values from the original method. */
public AnnotationVisitor visitAnnotationDefault() { public AnnotationVisitor visitAnnotationDefault() {
return mParentVisitor.visitAnnotationDefault(); if (mOrgWriter != null) {
return mOrgWriter.visitAnnotationDefault();
} else {
return null;
}
} }
public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
boolean visible) { boolean visible) {
return mParentVisitor.visitParameterAnnotation(parameter, desc, visible); if (mOrgWriter != null) {
return mOrgWriter.visitParameterAnnotation(parameter, desc, visible);
} else {
return null;
}
} }
/* Writes all attributes from the original method. */ /* Writes all attributes from the original method. */
public void visitAttribute(Attribute attr) { public void visitAttribute(Attribute attr) {
mParentVisitor.visitAttribute(attr); if (mOrgWriter != null) {
mOrgWriter.visitAttribute(attr);
}
} }
/* /*
@ -284,75 +322,110 @@ class DelegateMethodAdapter implements MethodVisitor {
* viewers can direct to the correct method, even if the content doesn't match. * viewers can direct to the correct method, even if the content doesn't match.
*/ */
public void visitLineNumber(int line, Label start) { public void visitLineNumber(int line, Label start) {
if (mOutputFirstLineNumber) { // Capture the first line values for the new delegate method
mParentVisitor.visitLineNumber(line, start); if (mDelegateLineNumber == null) {
mOutputFirstLineNumber = false; mDelegateLineNumber = new Object[] { line, start };
}
if (mOrgWriter != null) {
mOrgWriter.visitLineNumber(line, start);
} }
} }
public void visitInsn(int opcode) { public void visitInsn(int opcode) {
// Skip original code. if (mOrgWriter != null) {
mOrgWriter.visitInsn(opcode);
}
} }
public void visitLabel(Label label) { public void visitLabel(Label label) {
// Skip original code. if (mOrgWriter != null) {
mOrgWriter.visitLabel(label);
}
} }
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
// Skip original code. if (mOrgWriter != null) {
mOrgWriter.visitTryCatchBlock(start, end, handler, type);
}
} }
public void visitMethodInsn(int opcode, String owner, String name, String desc) { public void visitMethodInsn(int opcode, String owner, String name, String desc) {
// Skip original code. if (mOrgWriter != null) {
mOrgWriter.visitMethodInsn(opcode, owner, name, desc);
}
} }
public void visitFieldInsn(int opcode, String owner, String name, String desc) { public void visitFieldInsn(int opcode, String owner, String name, String desc) {
// Skip original code. if (mOrgWriter != null) {
mOrgWriter.visitFieldInsn(opcode, owner, name, desc);
}
} }
public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
// Skip original code. if (mOrgWriter != null) {
mOrgWriter.visitFrame(type, nLocal, local, nStack, stack);
}
} }
public void visitIincInsn(int var, int increment) { public void visitIincInsn(int var, int increment) {
// Skip original code. if (mOrgWriter != null) {
mOrgWriter.visitIincInsn(var, increment);
}
} }
public void visitIntInsn(int opcode, int operand) { public void visitIntInsn(int opcode, int operand) {
// Skip original code. if (mOrgWriter != null) {
mOrgWriter.visitIntInsn(opcode, operand);
}
} }
public void visitJumpInsn(int opcode, Label label) { public void visitJumpInsn(int opcode, Label label) {
// Skip original code. if (mOrgWriter != null) {
mOrgWriter.visitJumpInsn(opcode, label);
}
} }
public void visitLdcInsn(Object cst) { public void visitLdcInsn(Object cst) {
// Skip original code. if (mOrgWriter != null) {
mOrgWriter.visitLdcInsn(cst);
}
} }
public void visitLocalVariable(String name, String desc, String signature, public void visitLocalVariable(String name, String desc, String signature,
Label start, Label end, int index) { Label start, Label end, int index) {
// Skip original code. if (mOrgWriter != null) {
mOrgWriter.visitLocalVariable(name, desc, signature, start, end, index);
}
} }
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
// Skip original code. if (mOrgWriter != null) {
mOrgWriter.visitLookupSwitchInsn(dflt, keys, labels);
}
} }
public void visitMultiANewArrayInsn(String desc, int dims) { public void visitMultiANewArrayInsn(String desc, int dims) {
// Skip original code. if (mOrgWriter != null) {
mOrgWriter.visitMultiANewArrayInsn(desc, dims);
}
} }
public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) { public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
// Skip original code. if (mOrgWriter != null) {
mOrgWriter.visitTableSwitchInsn(min, max, dflt, labels);
}
} }
public void visitTypeInsn(int opcode, String type) { public void visitTypeInsn(int opcode, String type) {
// Skip original code. if (mOrgWriter != null) {
mOrgWriter.visitTypeInsn(opcode, type);
}
} }
public void visitVarInsn(int opcode, int var) { public void visitVarInsn(int opcode, int var) {
// Skip original code. if (mOrgWriter != null) {
mOrgWriter.visitVarInsn(opcode, var);
}
} }
} }

View File

@ -31,7 +31,7 @@ class StubMethodAdapter implements MethodVisitor {
private static String CONSTRUCTOR = "<init>"; private static String CONSTRUCTOR = "<init>";
private static String CLASS_INIT = "<clinit>"; private static String CLASS_INIT = "<clinit>";
/** The parent method writer */ /** The parent method writer */
private MethodVisitor mParentVisitor; private MethodVisitor mParentVisitor;
/** The method return type. Can be null. */ /** The method return type. Can be null. */
@ -40,7 +40,7 @@ class StubMethodAdapter implements MethodVisitor {
private String mInvokeSignature; private String mInvokeSignature;
/** Flag to output the first line number. */ /** Flag to output the first line number. */
private boolean mOutputFirstLineNumber = true; private boolean mOutputFirstLineNumber = true;
/** Flag that is true when implementing a constructor, to accept all original /** Flag that is true when implementing a constructor, to accept all original
* code calling the original super constructor. */ * code calling the original super constructor. */
private boolean mIsInitMethod = false; private boolean mIsInitMethod = false;
@ -55,12 +55,12 @@ class StubMethodAdapter implements MethodVisitor {
mInvokeSignature = invokeSignature; mInvokeSignature = invokeSignature;
mIsStatic = isStatic; mIsStatic = isStatic;
mIsNative = isNative; mIsNative = isNative;
if (CONSTRUCTOR.equals(methodName) || CLASS_INIT.equals(methodName)) { if (CONSTRUCTOR.equals(methodName) || CLASS_INIT.equals(methodName)) {
mIsInitMethod = true; mIsInitMethod = true;
} }
} }
private void generateInvoke() { private void generateInvoke() {
/* Generates the code: /* Generates the code:
* OverrideMethod.invoke("signature", mIsNative ? true : false, null or this); * OverrideMethod.invoke("signature", mIsNative ? true : false, null or this);
@ -188,7 +188,7 @@ class StubMethodAdapter implements MethodVisitor {
} }
mParentVisitor.visitMaxs(maxStack, maxLocals); mParentVisitor.visitMaxs(maxStack, maxLocals);
} }
/** /**
* End of visiting. * End of visiting.
* For non-constructor, generate the messaging code and the return statement * For non-constructor, generate the messaging code and the return statement
@ -250,6 +250,7 @@ class StubMethodAdapter implements MethodVisitor {
generatePop(); generatePop();
generateInvoke(); generateInvoke();
mMessageGenerated = true; mMessageGenerated = true;
//$FALL-THROUGH$
default: default:
mParentVisitor.visitInsn(opcode); mParentVisitor.visitInsn(opcode);
} }
@ -346,5 +347,5 @@ class StubMethodAdapter implements MethodVisitor {
mParentVisitor.visitVarInsn(opcode, var); mParentVisitor.visitVarInsn(opcode, var);
} }
} }
} }

View File

@ -130,7 +130,7 @@ public class DelegateClassAdapterTest {
} }
/** /**
* {@link DelegateMethodAdapter} does not support overriding constructors yet, * {@link DelegateMethodAdapter2} does not support overriding constructors yet,
* so this should fail with an {@link UnsupportedOperationException}. * so this should fail with an {@link UnsupportedOperationException}.
* *
* Although not tested here, the message of the exception should contain the * Although not tested here, the message of the exception should contain the
@ -202,6 +202,7 @@ public class DelegateClassAdapterTest {
// We'll delegate the "get" method of both the inner and outer class. // We'll delegate the "get" method of both the inner and outer class.
HashSet<String> delegateMethods = new HashSet<String>(); HashSet<String> delegateMethods = new HashSet<String>();
delegateMethods.add("get"); delegateMethods.add("get");
delegateMethods.add("privateMethod");
// Generate the delegate for the outer class. // Generate the delegate for the outer class.
ClassWriter cwOuter = new ClassWriter(0 /*flags*/); ClassWriter cwOuter = new ClassWriter(0 /*flags*/);
@ -234,6 +235,25 @@ public class DelegateClassAdapterTest {
// The original Outer.get returns 1+10+20, // The original Outer.get returns 1+10+20,
// but the delegate makes it return 4+10+20 // but the delegate makes it return 4+10+20
assertEquals(4+10+20, callGet(o2, 10, 20)); assertEquals(4+10+20, callGet(o2, 10, 20));
assertEquals(1+10+20, callGet_Original(o2, 10, 20));
// The original Outer has a private method that is
// delegated. We should be able to call both the delegate
// and the original (which is now public).
assertEquals("outerPrivateMethod",
callMethod(o2, "privateMethod_Original", false /*makePublic*/));
// The original method is private, so by default we can't access it
boolean gotIllegalAccessException = false;
try {
callMethod(o2, "privateMethod", false /*makePublic*/);
} catch(IllegalAccessException e) {
gotIllegalAccessException = true;
}
assertTrue(gotIllegalAccessException);
// Try again, but now making it accessible
assertEquals("outerPrivate_Delegate",
callMethod(o2, "privateMethod", true /*makePublic*/));
// Check the inner class. Since it's not a static inner class, we need // Check the inner class. Since it's not a static inner class, we need
// to use the hidden constructor that takes the outer class as first parameter. // to use the hidden constructor that takes the outer class as first parameter.
@ -246,6 +266,7 @@ public class DelegateClassAdapterTest {
// The original Inner.get returns 3+10+20, // The original Inner.get returns 3+10+20,
// but the delegate makes it return 6+10+20 // but the delegate makes it return 6+10+20
assertEquals(6+10+20, callGet(i2, 10, 20)); assertEquals(6+10+20, callGet(i2, 10, 20));
assertEquals(3+10+20, callGet_Original(i2, 10, 20));
} }
}; };
cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray()); cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray());
@ -319,7 +340,7 @@ public class DelegateClassAdapterTest {
} }
/** /**
* Accesses {@link OuterClass#get()} or {@link InnerClass#get() }via reflection. * Accesses {@link OuterClass#get} or {@link InnerClass#get}via reflection.
*/ */
public int callGet(Object instance, int a, long b) throws Exception { public int callGet(Object instance, int a, long b) throws Exception {
Method m = instance.getClass().getMethod("get", Method m = instance.getClass().getMethod("get",
@ -329,6 +350,39 @@ public class DelegateClassAdapterTest {
return ((Integer) result).intValue(); return ((Integer) result).intValue();
} }
/**
* Accesses the "_Original" methods for {@link OuterClass#get}
* or {@link InnerClass#get}via reflection.
*/
public int callGet_Original(Object instance, int a, long b) throws Exception {
Method m = instance.getClass().getMethod("get_Original",
new Class<?>[] { int.class, long.class } );
Object result = m.invoke(instance, new Object[] { a, b });
return ((Integer) result).intValue();
}
/**
* Accesses the any declared method that takes no parameter via reflection.
*/
@SuppressWarnings("unchecked")
public <T> T callMethod(Object instance, String methodName, boolean makePublic) throws Exception {
Method m = instance.getClass().getDeclaredMethod(methodName, (Class<?>[])null);
boolean wasAccessible = m.isAccessible();
if (makePublic && !wasAccessible) {
m.setAccessible(true);
}
Object result = m.invoke(instance, (Object[])null);
if (makePublic && !wasAccessible) {
m.setAccessible(false);
}
return (T) result;
}
/** /**
* Accesses {@link ClassWithNative#add(int, int)} via reflection. * Accesses {@link ClassWithNative#add(int, int)} via reflection.
*/ */

View File

@ -39,10 +39,15 @@ public class OuterClass {
public InnerClass() { public InnerClass() {
} }
// Inner.get returns 1+2=3 + a + b // Inner.get returns 2 + 1 + a + b
public int get(int a, long b) { public int get(int a, long b) {
return 2 + mOuterValue + a + (int) b; return 2 + mOuterValue + a + (int) b;
} }
} }
@SuppressWarnings("unused")
private String privateMethod() {
return "outerPrivateMethod";
}
} }

View File

@ -26,5 +26,9 @@ public class OuterClass_Delegate {
public static int get(OuterClass instance, int a, long b) { public static int get(OuterClass instance, int a, long b) {
return 4 + a + (int) b; return 4 + a + (int) b;
} }
public static String privateMethod(OuterClass instance) {
return "outerPrivate_Delegate";
}
} }