From 865c3bef54228a353fd449a093b0c8d155618296 Mon Sep 17 00:00:00 2001 From: Raphael Moll Date: Fri, 17 Jun 2011 17:12:52 -0700 Subject: Laoutlib_creator: keep original of delegate methods. For specific methods, Layoublib_create can rewrite the implementation of a method to invoke a delegate instead of the original code. This allows layoutlib to implement native code or override existing behavior. This patch also 'saves' the original implementation of a rewritten method so that the delegate can access the original implementation as needed. Obviously this is only done for non-native methods. Given a non-native SomeClass.MethodName, we 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. Change-Id: I5ca2cd3ac55991a8e8a51c417e75ee447bf9e9e6 --- tools/layoutlib/create/README.txt | 34 +- .../android/tools/layoutlib/create/CreateInfo.java | 3 + .../layoutlib/create/DelegateClassAdapter.java | 62 ++- .../layoutlib/create/DelegateMethodAdapter.java | 358 ----------------- .../layoutlib/create/DelegateMethodAdapter2.java | 431 +++++++++++++++++++++ .../tools/layoutlib/create/StubMethodAdapter.java | 13 +- .../layoutlib/create/DelegateClassAdapterTest.java | 58 ++- .../layoutlib/create/dataclass/OuterClass.java | 7 +- .../create/dataclass/OuterClass_Delegate.java | 4 + 9 files changed, 586 insertions(+), 384 deletions(-) delete mode 100644 tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java create mode 100644 tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java (limited to 'tools') diff --git a/tools/layoutlib/create/README.txt b/tools/layoutlib/create/README.txt index 65a64cd..894611b 100644 --- a/tools/layoutlib/create/README.txt +++ b/tools/layoutlib/create/README.txt @@ -30,9 +30,9 @@ The Android JAR can't be used directly in Eclipse: Consequently this tool: - parses the input JAR, - 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, -- 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. 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 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. 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 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 for which to delegate calls. - specific methods to remove based on their return type. - 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 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. @@ -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 can however specify the return value of the overridden method. -An extension being worked on is to actually replace these listeners by -direct calls to a delegate class, complete with parameters. +This strategy is now obsolete and replaced by the method delegates. - 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 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 @@ -195,6 +201,24 @@ example, the inner class Paint$Style in the Paint class should be discarded and 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 - -------------- diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index 4b62e43..85e6c3f 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -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 * "package.package.OuterClass$InnerClass#MethodName". * The list can be empty but must not be null. + *

+ * This usage is deprecated. Please use method 'delegates' instead. */ public String[] getOverriddenMethods() { return OVERRIDDEN_METHODS; @@ -153,6 +155,7 @@ public final class CreateInfo implements ICreateInfo { /** * The list of methods to stub out. Each entry must be in the form * "package.package.OuterClass$InnerClass#MethodName". + * This usage is deprecated. Please use method 'delegates' instead. */ private final static String[] OVERRIDDEN_METHODS = new String[] { }; diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java index 9cba8a0..49ddf1d 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateClassAdapter.java @@ -31,6 +31,11 @@ import java.util.Set; */ public class DelegateClassAdapter extends ClassAdapter { + /** Suffix added to original methods. */ + private static final String ORIGINAL_SUFFIX = "_Original"; + private static String CONSTRUCTOR = ""; + private static String CLASS_INIT = ""; + public final static String ALL_NATIVES = "<>"; private final String mClassName; @@ -73,22 +78,55 @@ public class DelegateClassAdapter extends ClassAdapter { boolean useDelegate = (isNative && mDelegateMethods.contains(ALL_NATIVES)) || mDelegateMethods.contains(name); - if (useDelegate) { - // remove native - access = access & ~Opcodes.ACC_NATIVE; + if (!useDelegate) { + // Not creating a delegate for this method, pass it as-is from the reader + // to the writer. + return super.visitMethod(access, name, desc, signature, exceptions); } - MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions); if (useDelegate) { - DelegateMethodAdapter a = new DelegateMethodAdapter(mLog, mw, mClassName, - name, desc, isStatic); - if (isNative) { - // A native has no code to visit, so we need to generate it directly. - a.generateCode(); - } else { - return a; + if (CONSTRUCTOR.equals(name) || CLASS_INIT.equals(name)) { + // We don't currently support generating delegates for constructors. + throw new UnsupportedOperationException( + String.format( + "Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)", //$NON-NLS-1$ + mClassName, name, desc)); } } - 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; } } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java deleted file mode 100644 index 8d7f016..0000000 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter.java +++ /dev/null @@ -1,358 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.tools.layoutlib.create; - -import com.android.tools.layoutlib.annotations.LayoutlibDelegate; - -import org.objectweb.asm.AnnotationVisitor; -import org.objectweb.asm.Attribute; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.Label; -import org.objectweb.asm.MethodVisitor; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; - -import java.util.ArrayList; - -/** - * This method adapter rewrites a method by discarding the original code and generating - * a call to a delegate. Original annotations are passed along unchanged. - *

- * Calls are delegated to a 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. - *

- * Note that native methods have, by definition, no code so there's nothing a visitor - * can visit. That means the caller must call {@link #generateCode()} directly for - * a native and use the visitor pattern for non-natives. - *

- * Instances of this class are not re-usable. You need a new instance for each method. - */ -class DelegateMethodAdapter implements MethodVisitor { - - /** - * Suffix added to delegate classes. - */ - public static final String DELEGATE_SUFFIX = "_Delegate"; - - private static String CONSTRUCTOR = ""; - private static String CLASS_INIT = ""; - - /** The parent method writer */ - private MethodVisitor mParentVisitor; - /** Flag to output the first line number. */ - private boolean mOutputFirstLineNumber = true; - /** The original method descriptor (return type + argument types.) */ - private String mDesc; - /** True if the original method is static. */ - private final boolean mIsStatic; - /** The internal class name (e.g. com/android/SomeClass$InnerClass.) */ - private final String mClassName; - /** The method name. */ - private final String mMethodName; - /** Logger object. */ - private final Log mLog; - /** True if {@link #visitCode()} has been invoked. */ - private boolean mVisitCodeCalled; - - /** - * Creates a new {@link DelegateMethodAdapter} that will transform this method - * into a delegate call. - *

- * See {@link DelegateMethodAdapter} for more details. - * - * @param log The logger object. Must not be null. - * @param mv the method visitor to which this adapter must delegate calls. - * @param className The internal class name of the class to visit, - * e.g. com/android/SomeClass$InnerClass. - * @param methodName The simple name of the method. - * @param desc A method descriptor (c.f. {@link Type#getReturnType(String)} + - * {@link Type#getArgumentTypes(String)}) - * @param isStatic True if the method is declared static. - */ - public DelegateMethodAdapter(Log log, - MethodVisitor mv, - String className, - String methodName, - String desc, - boolean isStatic) { - mLog = log; - mParentVisitor = mv; - mClassName = className; - mMethodName = methodName; - mDesc = desc; - 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)); - } - } - - /** - * 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()}. - */ - public void generateCode() { - /* - * 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 method_1(int a, Object b, ArrayList c) { ... } - * - * We'll want to create a body that calls a delegate method like this: - * TheClass_Delegate.method_1(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.method_1( - * 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 runtime, they have no influence on the method name being called. - */ - - // Add our annotation - AnnotationVisitor aw = mParentVisitor.visitAnnotation( - Type.getObjectType(Type.getInternalName(LayoutlibDelegate.class)).toString(), - true); // visible at runtime - aw.visitEnd(); - - if (!mVisitCodeCalled) { - // If this is a direct call to generateCode() as done by DelegateClassAdapter - // for natives, visitCode() hasn't been called yet. - mParentVisitor.visitCode(); - mVisitCodeCalled = true; - } - - ArrayList paramTypes = new ArrayList(); - String delegateClassName = mClassName + DELEGATE_SUFFIX; - boolean pushedArg0 = false; - int maxStack = 0; - - // For an instance method (e.g. non-static), push the 'this' preceded - // by the 'this' of any outer class, if any. - if (!mIsStatic) { - // Check if the last segment of the class name has inner an class. - // Right now we only support one level of inner classes. - int slash = mClassName.lastIndexOf('/'); - int dol = mClassName.lastIndexOf('$'); - if (dol != -1 && dol > slash && dol == mClassName.indexOf('$')) { - String outerClass = mClassName.substring(0, dol); - Type outerType = Type.getObjectType(outerClass); - - // Change a delegate class name to "com/foo/Outer_Inner_Delegate" - delegateClassName = delegateClassName.replace('$', '_'); - - // 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. - mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0); // var 0 = this - mParentVisitor.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 - mParentVisitor.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(); - mParentVisitor.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 - mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, - delegateClassName, - mMethodName, - desc); - - Type returnType = Type.getReturnType(mDesc); - mParentVisitor.visitInsn(returnType.getOpcode(Opcodes.IRETURN)); - - mParentVisitor.visitMaxs(maxStack, maxLocals); - mParentVisitor.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); - } - - /* Pass down to visitor writer. In this implementation, either do nothing. */ - public void visitCode() { - mVisitCodeCalled = true; - mParentVisitor.visitCode(); - } - - /* - * visitMaxs is called just before visitEnd if there was any code to rewrite. - * Skip the original. - */ - public void visitMaxs(int maxStack, int maxLocals) { - } - - /** - * End of visiting. Generate the messaging code. - */ - public void visitEnd() { - generateCode(); - } - - /* Writes all annotation from the original method. */ - public AnnotationVisitor visitAnnotation(String desc, boolean visible) { - return mParentVisitor.visitAnnotation(desc, visible); - } - - /* Writes all annotation default values from the original method. */ - public AnnotationVisitor visitAnnotationDefault() { - return mParentVisitor.visitAnnotationDefault(); - } - - public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, - boolean visible) { - return mParentVisitor.visitParameterAnnotation(parameter, desc, visible); - } - - /* Writes all attributes from the original method. */ - public void visitAttribute(Attribute attr) { - mParentVisitor.visitAttribute(attr); - } - - /* - * Only writes the first line number present in the original code so that source - * viewers can direct to the correct method, even if the content doesn't match. - */ - public void visitLineNumber(int line, Label start) { - if (mOutputFirstLineNumber) { - mParentVisitor.visitLineNumber(line, start); - mOutputFirstLineNumber = false; - } - } - - public void visitInsn(int opcode) { - // Skip original code. - } - - public void visitLabel(Label label) { - // Skip original code. - } - - public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { - // Skip original code. - } - - public void visitMethodInsn(int opcode, String owner, String name, String desc) { - // Skip original code. - } - - public void visitFieldInsn(int opcode, String owner, String name, String desc) { - // Skip original code. - } - - public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) { - // Skip original code. - } - - public void visitIincInsn(int var, int increment) { - // Skip original code. - } - - public void visitIntInsn(int opcode, int operand) { - // Skip original code. - } - - public void visitJumpInsn(int opcode, Label label) { - // Skip original code. - } - - public void visitLdcInsn(Object cst) { - // Skip original code. - } - - public void visitLocalVariable(String name, String desc, String signature, - Label start, Label end, int index) { - // Skip original code. - } - - public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { - // Skip original code. - } - - public void visitMultiANewArrayInsn(String desc, int dims) { - // Skip original code. - } - - public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) { - // Skip original code. - } - - public void visitTypeInsn(int opcode, String type) { - // Skip original code. - } - - public void visitVarInsn(int opcode, int var) { - // Skip original code. - } - -} diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java new file mode 100644 index 0000000..ac4ae6d --- /dev/null +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/DelegateMethodAdapter2.java @@ -0,0 +1,431 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.tools.layoutlib.create; + +import com.android.tools.layoutlib.annotations.LayoutlibDelegate; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Attribute; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +import java.util.ArrayList; + +/** + * This method adapter generates delegate methods. + *

+ * Given a method {@code SomeClass.MethodName()}, this generates 1 or 2 methods: + *

+ * 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. + */ +class DelegateMethodAdapter2 implements MethodVisitor { + + /** Suffix added to delegate classes. */ + public static final String DELEGATE_SUFFIX = "_Delegate"; + + /** The parent method writer to copy of the original method. + * Null when dealing with a native original method. */ + private MethodVisitor mOrgWriter; + /** The parent method writer to generate the delegating method. Never null. */ + private MethodVisitor mDelWriter; + /** The original method descriptor (return type + argument types.) */ + private String mDesc; + /** True if the original method is static. */ + private final boolean mIsStatic; + /** The internal class name (e.g. com/android/SomeClass$InnerClass.) */ + private final String mClassName; + /** The method name. */ + private final String mMethodName; + /** Logger object. */ + private final Log mLog; + + /** 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 DelegateMethodAdapter2} that will transform this method + * into a delegate call. + *

+ * See {@link DelegateMethodAdapter2} for more details. + * + * @param log The logger object. Must not be null. + * @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, + * e.g. com/android/SomeClass$InnerClass. + * @param methodName The simple name of the method. + * @param desc A method descriptor (c.f. {@link Type#getReturnType(String)} + + * {@link Type#getArgumentTypes(String)}) + * @param isStatic True if the method is declared static. + */ + public DelegateMethodAdapter2(Log log, + MethodVisitor mvOriginal, + MethodVisitor mvDelegate, + String className, + String methodName, + String desc, + boolean isStatic) { + mLog = log; + mOrgWriter = mvOriginal; + mDelWriter = mvDelegate; + mClassName = className; + mMethodName = methodName; + mDesc = desc; + mIsStatic = isStatic; + } + + /** + * 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 DelegateMethodAdapter2} 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()}. + */ + public void generateDelegateCode() { + /* + * 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 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 paramTypes = new ArrayList(); + String delegateClassName = mClassName + DELEGATE_SUFFIX; + boolean pushedArg0 = false; + int maxStack = 0; + + // For an instance method (e.g. non-static), push the 'this' preceded + // by the 'this' of any outer class, if any. + if (!mIsStatic) { + // Check if the last segment of the class name has inner an class. + // Right now we only support one level of inner classes. + int slash = mClassName.lastIndexOf('/'); + int dol = mClassName.lastIndexOf('$'); + if (dol != -1 && dol > slash && dol == mClassName.indexOf('$')) { + String outerClass = mClassName.substring(0, dol); + Type outerType = Type.getObjectType(outerClass); + + // Change a delegate class name to "com/foo/Outer_Inner_Delegate" + delegateClassName = delegateClassName.replace('$', '_'); + + // 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); + } + + /* Pass down to visitor writer. In this implementation, either do nothing. */ + public void visitCode() { + if (mOrgWriter != null) { + mOrgWriter.visitCode(); + } + } + + /* + * visitMaxs is called just before visitEnd if there was any code to rewrite. + */ + public void visitMaxs(int maxStack, int maxLocals) { + if (mOrgWriter != null) { + mOrgWriter.visitMaxs(maxStack, maxLocals); + } + } + + /** End of visiting. Generate the delegating code. */ + public void visitEnd() { + if (mOrgWriter != null) { + mOrgWriter.visitEnd(); + } + generateDelegateCode(); + } + + /* Writes all annotation from the original method. */ + public AnnotationVisitor visitAnnotation(String desc, boolean visible) { + if (mOrgWriter != null) { + return mOrgWriter.visitAnnotation(desc, visible); + } else { + return null; + } + } + + /* Writes all annotation default values from the original method. */ + public AnnotationVisitor visitAnnotationDefault() { + if (mOrgWriter != null) { + return mOrgWriter.visitAnnotationDefault(); + } else { + return null; + } + } + + public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, + boolean visible) { + if (mOrgWriter != null) { + return mOrgWriter.visitParameterAnnotation(parameter, desc, visible); + } else { + return null; + } + } + + /* Writes all attributes from the original method. */ + public void visitAttribute(Attribute attr) { + if (mOrgWriter != null) { + mOrgWriter.visitAttribute(attr); + } + } + + /* + * Only writes the first line number present in the original code so that source + * viewers can direct to the correct method, even if the content doesn't match. + */ + public void visitLineNumber(int line, 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 visitInsn(int opcode) { + if (mOrgWriter != null) { + mOrgWriter.visitInsn(opcode); + } + } + + public void visitLabel(Label label) { + if (mOrgWriter != null) { + mOrgWriter.visitLabel(label); + } + } + + public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { + if (mOrgWriter != null) { + mOrgWriter.visitTryCatchBlock(start, end, handler, type); + } + } + + public void visitMethodInsn(int opcode, String owner, String name, String desc) { + if (mOrgWriter != null) { + mOrgWriter.visitMethodInsn(opcode, owner, name, desc); + } + } + + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + if (mOrgWriter != null) { + mOrgWriter.visitFieldInsn(opcode, owner, name, desc); + } + } + + public void visitFrame(int type, int nLocal, Object[] local, int nStack, 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 visitIntInsn(int opcode, int operand) { + if (mOrgWriter != null) { + mOrgWriter.visitIntInsn(opcode, operand); + } + } + + public void visitJumpInsn(int opcode, Label label) { + if (mOrgWriter != null) { + mOrgWriter.visitJumpInsn(opcode, label); + } + } + + public void visitLdcInsn(Object cst) { + if (mOrgWriter != null) { + mOrgWriter.visitLdcInsn(cst); + } + } + + public void visitLocalVariable(String name, String desc, String signature, + Label start, Label end, int index) { + if (mOrgWriter != null) { + mOrgWriter.visitLocalVariable(name, desc, signature, start, end, index); + } + } + + public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) { + if (mOrgWriter != null) { + mOrgWriter.visitLookupSwitchInsn(dflt, keys, labels); + } + } + + public void visitMultiANewArrayInsn(String desc, int dims) { + if (mOrgWriter != null) { + mOrgWriter.visitMultiANewArrayInsn(desc, dims); + } + } + + public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) { + if (mOrgWriter != null) { + mOrgWriter.visitTableSwitchInsn(min, max, dflt, labels); + } + } + + public void visitTypeInsn(int opcode, String type) { + if (mOrgWriter != null) { + mOrgWriter.visitTypeInsn(opcode, type); + } + } + + public void visitVarInsn(int opcode, int var) { + if (mOrgWriter != null) { + mOrgWriter.visitVarInsn(opcode, var); + } + } + +} diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java index 9a57a4a..d70d028 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java @@ -31,7 +31,7 @@ class StubMethodAdapter implements MethodVisitor { private static String CONSTRUCTOR = ""; private static String CLASS_INIT = ""; - + /** The parent method writer */ private MethodVisitor mParentVisitor; /** The method return type. Can be null. */ @@ -40,7 +40,7 @@ class StubMethodAdapter implements MethodVisitor { private String mInvokeSignature; /** Flag to output the first line number. */ 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. */ private boolean mIsInitMethod = false; @@ -55,12 +55,12 @@ class StubMethodAdapter implements MethodVisitor { mInvokeSignature = invokeSignature; mIsStatic = isStatic; mIsNative = isNative; - + if (CONSTRUCTOR.equals(methodName) || CLASS_INIT.equals(methodName)) { mIsInitMethod = true; } } - + private void generateInvoke() { /* Generates the code: * OverrideMethod.invoke("signature", mIsNative ? true : false, null or this); @@ -188,7 +188,7 @@ class StubMethodAdapter implements MethodVisitor { } mParentVisitor.visitMaxs(maxStack, maxLocals); } - + /** * End of visiting. * For non-constructor, generate the messaging code and the return statement @@ -250,6 +250,7 @@ class StubMethodAdapter implements MethodVisitor { generatePop(); generateInvoke(); mMessageGenerated = true; + //$FALL-THROUGH$ default: mParentVisitor.visitInsn(opcode); } @@ -346,5 +347,5 @@ class StubMethodAdapter implements MethodVisitor { mParentVisitor.visitVarInsn(opcode, var); } } - + } diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java index e8b3ea8..6e120ce 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/DelegateClassAdapterTest.java @@ -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}. * * 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. HashSet delegateMethods = new HashSet(); delegateMethods.add("get"); + delegateMethods.add("privateMethod"); // Generate the delegate for the outer class. ClassWriter cwOuter = new ClassWriter(0 /*flags*/); @@ -234,6 +235,25 @@ public class DelegateClassAdapterTest { // The original Outer.get returns 1+10+20, // but the delegate makes it return 4+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 // 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, // but the delegate makes it return 6+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()); @@ -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 { Method m = instance.getClass().getMethod("get", @@ -330,6 +351,39 @@ public class DelegateClassAdapterTest { } /** + * 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 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. */ public int callAdd(Object instance, int a, int b) throws Exception { diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java index 9dc2f69..f083e76 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass.java @@ -39,10 +39,15 @@ public class OuterClass { public InnerClass() { } - // Inner.get returns 1+2=3 + a + b + // Inner.get returns 2 + 1 + a + b public int get(int a, long b) { return 2 + mOuterValue + a + (int) b; } } + + @SuppressWarnings("unused") + private String privateMethod() { + return "outerPrivateMethod"; + } } diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java index 3252d87..774be8e 100644 --- a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java +++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/dataclass/OuterClass_Delegate.java @@ -26,5 +26,9 @@ public class OuterClass_Delegate { public static int get(OuterClass instance, int a, long b) { return 4 + a + (int) b; } + + public static String privateMethod(OuterClass instance) { + return "outerPrivate_Delegate"; + } } -- cgit v1.1