Loading tools/layoutlib/create/.classpath +2 −2 Original line number Diff line number Diff line <?xml version="1.0" encoding="UTF-8"?> <classpath> <classpathentry kind="src" path="src"/> <classpathentry excluding="mock_android/" kind="src" path="tests"/> <classpathentry excluding="mock_data/" kind="src" path="tests"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/> <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar"/> <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_PLAT/prebuilts/tools/common/asm-tools/src-4.0.zip"/> <classpathentry kind="output" path="bin"/> </classpath> tools/layoutlib/create/README.txt +43 −12 Original line number Diff line number Diff line Loading @@ -71,6 +71,9 @@ class names, for example "android.*.R**" ("*" does not matches dots whilst "**" and "." and "$" are interpreted as-is). In practice we almost but not quite request the inclusion of full packages. The analyzer is also given a list of classes to exclude. A fake implementation of these classes is injected by the Generator. With this information, the analyzer parses the input zip to find all the classes. All classes deriving from the requested bases classes are kept. All classes which name matched the glob pattern are kept. Loading @@ -93,6 +96,7 @@ and lists: - specific methods for which to delegate calls. - specific methods to remove based on their return type. - specific classes to rename. - specific classes to refactor. Each of these are specific strategies we use to be able to modify the Android code to fit within the Eclipse renderer. These strategies are explained beow. Loading @@ -100,10 +104,7 @@ to fit within the Eclipse renderer. These strategies are explained beow. The core method of the generator is transform(): it takes an input ASM ClassReader and modifies it to produce a byte array suitable for the final JAR file. The first step of the transformation is changing the name of the class in case we requested the class to be renamed. This uses the RenameClassAdapter to also rename all inner classes and references in methods and types. Note that other classes are not transformed and keep referencing the original name. The first step of the transformation is to implement the method delegates. The TransformClassAdapter is then used to process the potentially renamed class. All protected or private classes are market as public. Loading @@ -115,11 +116,25 @@ 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. The next step of the transformation is changing the name of the class in case we requested the class to be renamed. This uses the RenameClassAdapter to also rename all inner classes and references in methods and types. Note that other classes are not transformed and keep referencing the original name. The class is then fed to RefactorClassAdapter which is like RenameClassAdapter but updates the references in all classes. This is used to update the references of classes in the java package that were added in the Dalvik VM but are not a part of the standard JVM. The existing classes are modified to update all references to these non-standard classes. An alternate implementation of these (com.android.tools.layoutlib.java.*) is injected. The ClassAdapters are chained together to achieve the desired output. (Look at section 2.2.7 Transformation chains in the asm user guide, link in the References.) The order of execution of these is: ClassReader -> [DelegateClassAdapter] -> TransformClassAdapter -> [RenameClassAdapter] -> RefactorClassAdapter -> ClassWriter - Method stubs -------------- Loading @@ -141,19 +156,27 @@ This strategy is now obsolete and replaced by the method delegates. - Strategies ------------ We currently have 4 strategies to deal with overriding the rendering code We currently have 6 strategies to deal with overriding the rendering code and make it run in Eclipse. Most of these strategies are implemented hand-in-hand by the bridge (which runs in Eclipse) and the generator. 1- Class Injection This is the easiest: we currently inject 4 classes, namely: This is the easiest: we currently inject the following classes: - OverrideMethod and its associated MethodListener and MethodAdapter are used to intercept calls to some specific methods that are stubbed out and change their return value. - CreateInfo class, which configured the generator. Not used yet, but could in theory help us track what the generator changed. - AutoCloseable is part of Java 7. To enable us to still run on Java 6, a new class is injected. The implementation for the class has been taken from Android's libcore (platform/libcore/luni/src/main/java/java/lang/AutoCloseable.java). - Charsets, IntegralToString and UnsafeByteSequence are not part of the standard JAVA VM. They are added to the Dalvik VM for performance reasons. An implementation that is very close to the original (which is at platform/libcore/luni/src/main/java/...) is injected. Since these classees were in part of the java package, where we can't inject classes, all references to these have been updated (See strategy 4- Refactoring Classes). 2- Overriding methods Loading Loading @@ -189,7 +212,15 @@ we don't control object creation. This won't rename/replace the inner static methods of a given class. 4- Method erasure based on return type 4- Refactoring classes This is very similar to the Renaming classes except that it also updates the reference in all classes. This is done for classes which are added to the Dalvik VM for performance reasons but are not present in the Standard Java VM. An implementation for these classes is also injected. 5- Method erasure based on return type This is mostly an implementation detail of the bridge: in the Paint class mentioned above, some inner static classes are used to pass around Loading @@ -201,7 +232,7 @@ example, the inner class Paint$Style in the Paint class should be discarded and bridge will provide its own implementation. 5- Method Delegates 6- Method Delegates This strategy is used to override method implementations. Given a method SomeClass.MethodName(), 1 or 2 methods are generated: Loading Loading @@ -233,7 +264,7 @@ Bytecode opcode list: http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings ASM user guide: http://download.forge.objectweb.org/asm/asm-guide.pdf http://download.forge.objectweb.org/asm/asm4-guide.pdf -- Loading tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java 0 → 100644 +418 −0 Original line number Diff line number Diff line /* * Copyright (C) 2013 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 org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.signature.SignatureReader; import org.objectweb.asm.signature.SignatureVisitor; import org.objectweb.asm.signature.SignatureWriter; /** * Provides the common code for RenameClassAdapter and RefactorClassAdapter. It * goes through the complete class and finds references to other classes. It * then calls {@link #renameInternalType(String)} to convert the className to * the new value, if need be. */ public abstract class AbstractClassAdapter extends ClassVisitor { /** * Returns the new FQCN for the class, if the reference to this class needs * to be updated. Else, it returns the same string. * @param name Old FQCN * @return New FQCN if it needs to be renamed, else the old FQCN */ abstract String renameInternalType(String name); public AbstractClassAdapter(ClassVisitor cv) { super(Opcodes.ASM4, cv); } /** * Renames a type descriptor, e.g. "Lcom.package.MyClass;" * If the type doesn't need to be renamed, returns the input string as-is. */ String renameTypeDesc(String desc) { if (desc == null) { return null; } return renameType(Type.getType(desc)); } /** * Renames an object type, e.g. "Lcom.package.MyClass;" or an array type that has an * object element, e.g. "[Lcom.package.MyClass;" * If the type doesn't need to be renamed, returns the internal name of the input type. */ String renameType(Type type) { if (type == null) { return null; } if (type.getSort() == Type.OBJECT) { String in = type.getInternalName(); return "L" + renameInternalType(in) + ";"; } else if (type.getSort() == Type.ARRAY) { StringBuilder sb = new StringBuilder(); for (int n = type.getDimensions(); n > 0; n--) { sb.append('['); } sb.append(renameType(type.getElementType())); return sb.toString(); } return type.getDescriptor(); } /** * Renames an object type, e.g. "Lcom.package.MyClass;" or an array type that has an * object element, e.g. "[Lcom.package.MyClass;". * This is like renameType() except that it returns a Type object. * If the type doesn't need to be renamed, returns the input type object. */ Type renameTypeAsType(Type type) { if (type == null) { return null; } if (type.getSort() == Type.OBJECT) { String in = type.getInternalName(); String newIn = renameInternalType(in); if (newIn != in) { return Type.getType("L" + newIn + ";"); } } else if (type.getSort() == Type.ARRAY) { StringBuilder sb = new StringBuilder(); for (int n = type.getDimensions(); n > 0; n--) { sb.append('['); } sb.append(renameType(type.getElementType())); return Type.getType(sb.toString()); } return type; } /** * Renames a method descriptor, i.e. applies renameType to all arguments and to the * return value. */ String renameMethodDesc(String desc) { if (desc == null) { return null; } Type[] args = Type.getArgumentTypes(desc); StringBuilder sb = new StringBuilder("("); for (Type arg : args) { String name = renameType(arg); sb.append(name); } sb.append(')'); Type ret = Type.getReturnType(desc); String name = renameType(ret); sb.append(name); return sb.toString(); } /** * Renames the ClassSignature handled by ClassVisitor.visit * or the MethodTypeSignature handled by ClassVisitor.visitMethod. */ String renameTypeSignature(String sig) { if (sig == null) { return null; } SignatureReader reader = new SignatureReader(sig); SignatureWriter writer = new SignatureWriter(); reader.accept(new RenameSignatureAdapter(writer)); sig = writer.toString(); return sig; } /** * Renames the FieldTypeSignature handled by ClassVisitor.visitField * or MethodVisitor.visitLocalVariable. */ String renameFieldSignature(String sig) { return renameTypeSignature(sig); } //---------------------------------- // Methods from the ClassAdapter @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { name = renameInternalType(name); superName = renameInternalType(superName); signature = renameTypeSignature(signature); if (interfaces != null) { for (int i = 0; i < interfaces.length; ++i) { interfaces[i] = renameInternalType(interfaces[i]); } } super.visit(version, access, name, signature, superName, interfaces); } @Override public void visitInnerClass(String name, String outerName, String innerName, int access) { name = renameInternalType(name); outerName = renameInternalType(outerName); super.visitInnerClass(name, outerName, innerName, access); } @Override public void visitOuterClass(String owner, String name, String desc) { super.visitOuterClass(renameInternalType(owner), name, renameTypeDesc(desc)); } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { desc = renameMethodDesc(desc); signature = renameTypeSignature(signature); MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions); return new RenameMethodAdapter(mw); } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { desc = renameTypeDesc(desc); return super.visitAnnotation(desc, visible); } @Override public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { desc = renameTypeDesc(desc); return super.visitField(access, name, desc, signature, value); } //---------------------------------- /** * A method visitor that renames all references from an old class name to a new class name. */ public class RenameMethodAdapter extends MethodVisitor { /** * Creates a method visitor that renames all references from a given old name to a given new * name. The method visitor will also rename all inner classes. * The names must be full qualified internal ASM names (e.g. com/blah/MyClass$InnerClass). */ public RenameMethodAdapter(MethodVisitor mv) { super(Opcodes.ASM4, mv); } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { desc = renameTypeDesc(desc); return super.visitAnnotation(desc, visible); } @Override public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { desc = renameTypeDesc(desc); return super.visitParameterAnnotation(parameter, desc, visible); } @Override public void visitTypeInsn(int opcode, String type) { // The type sometimes turns out to be a type descriptor. We try to detect it and fix. if (type.indexOf(';') > 0) { type = renameTypeDesc(type); } else { type = renameInternalType(type); } super.visitTypeInsn(opcode, type); } @Override public void visitFieldInsn(int opcode, String owner, String name, String desc) { owner = renameInternalType(owner); desc = renameTypeDesc(desc); super.visitFieldInsn(opcode, owner, name, desc); } @Override public void visitMethodInsn(int opcode, String owner, String name, String desc) { // The owner sometimes turns out to be a type descriptor. We try to detect it and fix. if (owner.indexOf(';') > 0) { owner = renameTypeDesc(owner); } else { owner = renameInternalType(owner); } desc = renameMethodDesc(desc); super.visitMethodInsn(opcode, owner, name, desc); } @Override public void visitLdcInsn(Object cst) { // If cst is a Type, this means the code is trying to pull the .class constant // for this class, so it needs to be renamed too. if (cst instanceof Type) { cst = renameTypeAsType((Type) cst); } super.visitLdcInsn(cst); } @Override public void visitMultiANewArrayInsn(String desc, int dims) { desc = renameTypeDesc(desc); super.visitMultiANewArrayInsn(desc, dims); } @Override public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { type = renameInternalType(type); super.visitTryCatchBlock(start, end, handler, type); } @Override public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { desc = renameTypeDesc(desc); signature = renameFieldSignature(signature); super.visitLocalVariable(name, desc, signature, start, end, index); } } //---------------------------------- public class RenameSignatureAdapter extends SignatureVisitor { private final SignatureVisitor mSv; public RenameSignatureAdapter(SignatureVisitor sv) { super(Opcodes.ASM4); mSv = sv; } @Override public void visitClassType(String name) { name = renameInternalType(name); mSv.visitClassType(name); } @Override public void visitInnerClassType(String name) { name = renameInternalType(name); mSv.visitInnerClassType(name); } @Override public SignatureVisitor visitArrayType() { SignatureVisitor sv = mSv.visitArrayType(); return new RenameSignatureAdapter(sv); } @Override public void visitBaseType(char descriptor) { mSv.visitBaseType(descriptor); } @Override public SignatureVisitor visitClassBound() { SignatureVisitor sv = mSv.visitClassBound(); return new RenameSignatureAdapter(sv); } @Override public void visitEnd() { mSv.visitEnd(); } @Override public SignatureVisitor visitExceptionType() { SignatureVisitor sv = mSv.visitExceptionType(); return new RenameSignatureAdapter(sv); } @Override public void visitFormalTypeParameter(String name) { mSv.visitFormalTypeParameter(name); } @Override public SignatureVisitor visitInterface() { SignatureVisitor sv = mSv.visitInterface(); return new RenameSignatureAdapter(sv); } @Override public SignatureVisitor visitInterfaceBound() { SignatureVisitor sv = mSv.visitInterfaceBound(); return new RenameSignatureAdapter(sv); } @Override public SignatureVisitor visitParameterType() { SignatureVisitor sv = mSv.visitParameterType(); return new RenameSignatureAdapter(sv); } @Override public SignatureVisitor visitReturnType() { SignatureVisitor sv = mSv.visitReturnType(); return new RenameSignatureAdapter(sv); } @Override public SignatureVisitor visitSuperclass() { SignatureVisitor sv = mSv.visitSuperclass(); return new RenameSignatureAdapter(sv); } @Override public void visitTypeArgument() { mSv.visitTypeArgument(); } @Override public SignatureVisitor visitTypeArgument(char wildcard) { SignatureVisitor sv = mSv.visitTypeArgument(wildcard); return new RenameSignatureAdapter(sv); } @Override public void visitTypeVariable(String name) { mSv.visitTypeVariable(name); } } } tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java +17 −8 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import java.util.Enumeration; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import java.util.regex.Pattern; import java.util.zip.ZipEntry; Loading @@ -57,6 +58,8 @@ public class AsmAnalyzer { private final String[] mDeriveFrom; /** Glob patterns of classes to keep, e.g. "com.foo.*" */ private final String[] mIncludeGlobs; /** The set of classes to exclude.*/ private final Set<String> mExcludedClasses; /** * Creates a new analyzer. Loading @@ -69,12 +72,13 @@ public class AsmAnalyzer { * ("*" does not matches dots whilst "**" does, "." and "$" are interpreted as-is) */ public AsmAnalyzer(Log log, List<String> osJarPath, AsmGenerator gen, String[] deriveFrom, String[] includeGlobs) { String[] deriveFrom, String[] includeGlobs, Set<String> excludeClasses) { mLog = log; mGen = gen; mOsSourceJar = osJarPath != null ? osJarPath : new ArrayList<String>(); mDeriveFrom = deriveFrom != null ? deriveFrom : new String[0]; mIncludeGlobs = includeGlobs != null ? includeGlobs : new String[0]; mExcludedClasses = excludeClasses; } /** Loading @@ -82,9 +86,6 @@ public class AsmAnalyzer { * Fills the generator with classes & dependencies found. */ public void analyze() throws IOException, LogAbortException { AsmAnalyzer visitor = this; Map<String, ClassReader> zipClasses = parseZip(mOsSourceJar); mLog.info("Found %d classes in input JAR%s.", zipClasses.size(), mOsSourceJar.size() > 1 ? "s" : ""); Loading Loading @@ -232,7 +233,7 @@ public class AsmAnalyzer { */ void findClassesDerivingFrom(String super_name, Map<String, ClassReader> zipClasses, Map<String, ClassReader> inOutFound) throws LogAbortException { ClassReader super_clazz = findClass(super_name, zipClasses, inOutFound); findClass(super_name, zipClasses, inOutFound); for (Entry<String, ClassReader> entry : zipClasses.entrySet()) { String className = entry.getKey(); Loading Loading @@ -363,11 +364,12 @@ public class AsmAnalyzer { className = internalToBinaryClassName(className); // exclude classes that have already been found // exclude classes that have already been found or are marked to be excluded if (mInKeep.containsKey(className) || mOutKeep.containsKey(className) || mInDeps.containsKey(className) || mOutDeps.containsKey(className)) { mOutDeps.containsKey(className) || mExcludedClasses.contains(getBaseName(className))) { return; } Loading Loading @@ -451,6 +453,13 @@ public class AsmAnalyzer { } } private String getBaseName(String className) { int pos = className.indexOf('$'); if (pos > 0) { return className.substring(0, pos); } return className; } // --------------------------------------------------- // --- ClassVisitor, FieldVisitor Loading Loading @@ -682,7 +691,7 @@ public class AsmAnalyzer { } @Override public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) { public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { // pass -- table switch instruction } Loading tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java +18 −4 Original line number Diff line number Diff line Loading @@ -65,6 +65,9 @@ public class AsmGenerator { /** A map { FQCN => set { method names } } of methods to rewrite as delegates. * The special name {@link DelegateClassAdapter#ALL_NATIVES} can be used as in internal set. */ private final HashMap<String, Set<String>> mDelegateMethods; /** FQCN Names of classes to refactor. All reference to old-FQCN will be updated to new-FQCN. * map old-FQCN => new-FQCN */ private final HashMap<String, String> mRefactorClasses; /** * Creates a new generator that can generate the output JAR with the stubbed classes. Loading Loading @@ -119,6 +122,17 @@ public class AsmGenerator { mClassesNotRenamed.add(oldFqcn); } // Create a map of classes to be refactored. mRefactorClasses = new HashMap<String, String>(); String[] refactorClasses = createInfo.getJavaPkgClasses(); n = refactorClasses.length; for (int i = 0; i < n; i += 2) { assert i + 1 < n; String oldFqcn = binaryToInternalClassName(refactorClasses[i]); String newFqcn = binaryToInternalClassName(refactorClasses[i + 1]); mRefactorClasses.put(oldFqcn, newFqcn);; } // create the map of renamed class -> return type of method to delete. mDeleteReturns = new HashMap<String, Set<String>>(); String[] deleteReturns = createInfo.getDeleteReturns(); Loading Loading @@ -308,14 +322,14 @@ public class AsmGenerator { // original class reader. ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassVisitor rv = cw; ClassVisitor cv = new RefactorClassAdapter(cw, mRefactorClasses); if (newName != className) { rv = new RenameClassAdapter(cw, className, newName); cv = new RenameClassAdapter(cv, className, newName); } ClassVisitor cv = new TransformClassAdapter(mLog, mStubMethods, cv = new TransformClassAdapter(mLog, mStubMethods, mDeleteReturns.get(className), newName, rv, newName, cv, stubNativesOnly, stubNativesOnly || hasNativeMethods); Set<String> delegateMethods = mDelegateMethods.get(className); Loading Loading
tools/layoutlib/create/.classpath +2 −2 Original line number Diff line number Diff line <?xml version="1.0" encoding="UTF-8"?> <classpath> <classpathentry kind="src" path="src"/> <classpathentry excluding="mock_android/" kind="src" path="tests"/> <classpathentry excluding="mock_data/" kind="src" path="tests"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/> <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/> <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar"/> <classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_PLAT/prebuilts/tools/common/asm-tools/src-4.0.zip"/> <classpathentry kind="output" path="bin"/> </classpath>
tools/layoutlib/create/README.txt +43 −12 Original line number Diff line number Diff line Loading @@ -71,6 +71,9 @@ class names, for example "android.*.R**" ("*" does not matches dots whilst "**" and "." and "$" are interpreted as-is). In practice we almost but not quite request the inclusion of full packages. The analyzer is also given a list of classes to exclude. A fake implementation of these classes is injected by the Generator. With this information, the analyzer parses the input zip to find all the classes. All classes deriving from the requested bases classes are kept. All classes which name matched the glob pattern are kept. Loading @@ -93,6 +96,7 @@ and lists: - specific methods for which to delegate calls. - specific methods to remove based on their return type. - specific classes to rename. - specific classes to refactor. Each of these are specific strategies we use to be able to modify the Android code to fit within the Eclipse renderer. These strategies are explained beow. Loading @@ -100,10 +104,7 @@ to fit within the Eclipse renderer. These strategies are explained beow. The core method of the generator is transform(): it takes an input ASM ClassReader and modifies it to produce a byte array suitable for the final JAR file. The first step of the transformation is changing the name of the class in case we requested the class to be renamed. This uses the RenameClassAdapter to also rename all inner classes and references in methods and types. Note that other classes are not transformed and keep referencing the original name. The first step of the transformation is to implement the method delegates. The TransformClassAdapter is then used to process the potentially renamed class. All protected or private classes are market as public. Loading @@ -115,11 +116,25 @@ 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. The next step of the transformation is changing the name of the class in case we requested the class to be renamed. This uses the RenameClassAdapter to also rename all inner classes and references in methods and types. Note that other classes are not transformed and keep referencing the original name. The class is then fed to RefactorClassAdapter which is like RenameClassAdapter but updates the references in all classes. This is used to update the references of classes in the java package that were added in the Dalvik VM but are not a part of the standard JVM. The existing classes are modified to update all references to these non-standard classes. An alternate implementation of these (com.android.tools.layoutlib.java.*) is injected. The ClassAdapters are chained together to achieve the desired output. (Look at section 2.2.7 Transformation chains in the asm user guide, link in the References.) The order of execution of these is: ClassReader -> [DelegateClassAdapter] -> TransformClassAdapter -> [RenameClassAdapter] -> RefactorClassAdapter -> ClassWriter - Method stubs -------------- Loading @@ -141,19 +156,27 @@ This strategy is now obsolete and replaced by the method delegates. - Strategies ------------ We currently have 4 strategies to deal with overriding the rendering code We currently have 6 strategies to deal with overriding the rendering code and make it run in Eclipse. Most of these strategies are implemented hand-in-hand by the bridge (which runs in Eclipse) and the generator. 1- Class Injection This is the easiest: we currently inject 4 classes, namely: This is the easiest: we currently inject the following classes: - OverrideMethod and its associated MethodListener and MethodAdapter are used to intercept calls to some specific methods that are stubbed out and change their return value. - CreateInfo class, which configured the generator. Not used yet, but could in theory help us track what the generator changed. - AutoCloseable is part of Java 7. To enable us to still run on Java 6, a new class is injected. The implementation for the class has been taken from Android's libcore (platform/libcore/luni/src/main/java/java/lang/AutoCloseable.java). - Charsets, IntegralToString and UnsafeByteSequence are not part of the standard JAVA VM. They are added to the Dalvik VM for performance reasons. An implementation that is very close to the original (which is at platform/libcore/luni/src/main/java/...) is injected. Since these classees were in part of the java package, where we can't inject classes, all references to these have been updated (See strategy 4- Refactoring Classes). 2- Overriding methods Loading Loading @@ -189,7 +212,15 @@ we don't control object creation. This won't rename/replace the inner static methods of a given class. 4- Method erasure based on return type 4- Refactoring classes This is very similar to the Renaming classes except that it also updates the reference in all classes. This is done for classes which are added to the Dalvik VM for performance reasons but are not present in the Standard Java VM. An implementation for these classes is also injected. 5- Method erasure based on return type This is mostly an implementation detail of the bridge: in the Paint class mentioned above, some inner static classes are used to pass around Loading @@ -201,7 +232,7 @@ example, the inner class Paint$Style in the Paint class should be discarded and bridge will provide its own implementation. 5- Method Delegates 6- Method Delegates This strategy is used to override method implementations. Given a method SomeClass.MethodName(), 1 or 2 methods are generated: Loading Loading @@ -233,7 +264,7 @@ Bytecode opcode list: http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings ASM user guide: http://download.forge.objectweb.org/asm/asm-guide.pdf http://download.forge.objectweb.org/asm/asm4-guide.pdf -- Loading
tools/layoutlib/create/src/com/android/tools/layoutlib/create/AbstractClassAdapter.java 0 → 100644 +418 −0 Original line number Diff line number Diff line /* * Copyright (C) 2013 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 org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.signature.SignatureReader; import org.objectweb.asm.signature.SignatureVisitor; import org.objectweb.asm.signature.SignatureWriter; /** * Provides the common code for RenameClassAdapter and RefactorClassAdapter. It * goes through the complete class and finds references to other classes. It * then calls {@link #renameInternalType(String)} to convert the className to * the new value, if need be. */ public abstract class AbstractClassAdapter extends ClassVisitor { /** * Returns the new FQCN for the class, if the reference to this class needs * to be updated. Else, it returns the same string. * @param name Old FQCN * @return New FQCN if it needs to be renamed, else the old FQCN */ abstract String renameInternalType(String name); public AbstractClassAdapter(ClassVisitor cv) { super(Opcodes.ASM4, cv); } /** * Renames a type descriptor, e.g. "Lcom.package.MyClass;" * If the type doesn't need to be renamed, returns the input string as-is. */ String renameTypeDesc(String desc) { if (desc == null) { return null; } return renameType(Type.getType(desc)); } /** * Renames an object type, e.g. "Lcom.package.MyClass;" or an array type that has an * object element, e.g. "[Lcom.package.MyClass;" * If the type doesn't need to be renamed, returns the internal name of the input type. */ String renameType(Type type) { if (type == null) { return null; } if (type.getSort() == Type.OBJECT) { String in = type.getInternalName(); return "L" + renameInternalType(in) + ";"; } else if (type.getSort() == Type.ARRAY) { StringBuilder sb = new StringBuilder(); for (int n = type.getDimensions(); n > 0; n--) { sb.append('['); } sb.append(renameType(type.getElementType())); return sb.toString(); } return type.getDescriptor(); } /** * Renames an object type, e.g. "Lcom.package.MyClass;" or an array type that has an * object element, e.g. "[Lcom.package.MyClass;". * This is like renameType() except that it returns a Type object. * If the type doesn't need to be renamed, returns the input type object. */ Type renameTypeAsType(Type type) { if (type == null) { return null; } if (type.getSort() == Type.OBJECT) { String in = type.getInternalName(); String newIn = renameInternalType(in); if (newIn != in) { return Type.getType("L" + newIn + ";"); } } else if (type.getSort() == Type.ARRAY) { StringBuilder sb = new StringBuilder(); for (int n = type.getDimensions(); n > 0; n--) { sb.append('['); } sb.append(renameType(type.getElementType())); return Type.getType(sb.toString()); } return type; } /** * Renames a method descriptor, i.e. applies renameType to all arguments and to the * return value. */ String renameMethodDesc(String desc) { if (desc == null) { return null; } Type[] args = Type.getArgumentTypes(desc); StringBuilder sb = new StringBuilder("("); for (Type arg : args) { String name = renameType(arg); sb.append(name); } sb.append(')'); Type ret = Type.getReturnType(desc); String name = renameType(ret); sb.append(name); return sb.toString(); } /** * Renames the ClassSignature handled by ClassVisitor.visit * or the MethodTypeSignature handled by ClassVisitor.visitMethod. */ String renameTypeSignature(String sig) { if (sig == null) { return null; } SignatureReader reader = new SignatureReader(sig); SignatureWriter writer = new SignatureWriter(); reader.accept(new RenameSignatureAdapter(writer)); sig = writer.toString(); return sig; } /** * Renames the FieldTypeSignature handled by ClassVisitor.visitField * or MethodVisitor.visitLocalVariable. */ String renameFieldSignature(String sig) { return renameTypeSignature(sig); } //---------------------------------- // Methods from the ClassAdapter @Override public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { name = renameInternalType(name); superName = renameInternalType(superName); signature = renameTypeSignature(signature); if (interfaces != null) { for (int i = 0; i < interfaces.length; ++i) { interfaces[i] = renameInternalType(interfaces[i]); } } super.visit(version, access, name, signature, superName, interfaces); } @Override public void visitInnerClass(String name, String outerName, String innerName, int access) { name = renameInternalType(name); outerName = renameInternalType(outerName); super.visitInnerClass(name, outerName, innerName, access); } @Override public void visitOuterClass(String owner, String name, String desc) { super.visitOuterClass(renameInternalType(owner), name, renameTypeDesc(desc)); } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { desc = renameMethodDesc(desc); signature = renameTypeSignature(signature); MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions); return new RenameMethodAdapter(mw); } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { desc = renameTypeDesc(desc); return super.visitAnnotation(desc, visible); } @Override public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { desc = renameTypeDesc(desc); return super.visitField(access, name, desc, signature, value); } //---------------------------------- /** * A method visitor that renames all references from an old class name to a new class name. */ public class RenameMethodAdapter extends MethodVisitor { /** * Creates a method visitor that renames all references from a given old name to a given new * name. The method visitor will also rename all inner classes. * The names must be full qualified internal ASM names (e.g. com/blah/MyClass$InnerClass). */ public RenameMethodAdapter(MethodVisitor mv) { super(Opcodes.ASM4, mv); } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { desc = renameTypeDesc(desc); return super.visitAnnotation(desc, visible); } @Override public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) { desc = renameTypeDesc(desc); return super.visitParameterAnnotation(parameter, desc, visible); } @Override public void visitTypeInsn(int opcode, String type) { // The type sometimes turns out to be a type descriptor. We try to detect it and fix. if (type.indexOf(';') > 0) { type = renameTypeDesc(type); } else { type = renameInternalType(type); } super.visitTypeInsn(opcode, type); } @Override public void visitFieldInsn(int opcode, String owner, String name, String desc) { owner = renameInternalType(owner); desc = renameTypeDesc(desc); super.visitFieldInsn(opcode, owner, name, desc); } @Override public void visitMethodInsn(int opcode, String owner, String name, String desc) { // The owner sometimes turns out to be a type descriptor. We try to detect it and fix. if (owner.indexOf(';') > 0) { owner = renameTypeDesc(owner); } else { owner = renameInternalType(owner); } desc = renameMethodDesc(desc); super.visitMethodInsn(opcode, owner, name, desc); } @Override public void visitLdcInsn(Object cst) { // If cst is a Type, this means the code is trying to pull the .class constant // for this class, so it needs to be renamed too. if (cst instanceof Type) { cst = renameTypeAsType((Type) cst); } super.visitLdcInsn(cst); } @Override public void visitMultiANewArrayInsn(String desc, int dims) { desc = renameTypeDesc(desc); super.visitMultiANewArrayInsn(desc, dims); } @Override public void visitTryCatchBlock(Label start, Label end, Label handler, String type) { type = renameInternalType(type); super.visitTryCatchBlock(start, end, handler, type); } @Override public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { desc = renameTypeDesc(desc); signature = renameFieldSignature(signature); super.visitLocalVariable(name, desc, signature, start, end, index); } } //---------------------------------- public class RenameSignatureAdapter extends SignatureVisitor { private final SignatureVisitor mSv; public RenameSignatureAdapter(SignatureVisitor sv) { super(Opcodes.ASM4); mSv = sv; } @Override public void visitClassType(String name) { name = renameInternalType(name); mSv.visitClassType(name); } @Override public void visitInnerClassType(String name) { name = renameInternalType(name); mSv.visitInnerClassType(name); } @Override public SignatureVisitor visitArrayType() { SignatureVisitor sv = mSv.visitArrayType(); return new RenameSignatureAdapter(sv); } @Override public void visitBaseType(char descriptor) { mSv.visitBaseType(descriptor); } @Override public SignatureVisitor visitClassBound() { SignatureVisitor sv = mSv.visitClassBound(); return new RenameSignatureAdapter(sv); } @Override public void visitEnd() { mSv.visitEnd(); } @Override public SignatureVisitor visitExceptionType() { SignatureVisitor sv = mSv.visitExceptionType(); return new RenameSignatureAdapter(sv); } @Override public void visitFormalTypeParameter(String name) { mSv.visitFormalTypeParameter(name); } @Override public SignatureVisitor visitInterface() { SignatureVisitor sv = mSv.visitInterface(); return new RenameSignatureAdapter(sv); } @Override public SignatureVisitor visitInterfaceBound() { SignatureVisitor sv = mSv.visitInterfaceBound(); return new RenameSignatureAdapter(sv); } @Override public SignatureVisitor visitParameterType() { SignatureVisitor sv = mSv.visitParameterType(); return new RenameSignatureAdapter(sv); } @Override public SignatureVisitor visitReturnType() { SignatureVisitor sv = mSv.visitReturnType(); return new RenameSignatureAdapter(sv); } @Override public SignatureVisitor visitSuperclass() { SignatureVisitor sv = mSv.visitSuperclass(); return new RenameSignatureAdapter(sv); } @Override public void visitTypeArgument() { mSv.visitTypeArgument(); } @Override public SignatureVisitor visitTypeArgument(char wildcard) { SignatureVisitor sv = mSv.visitTypeArgument(wildcard); return new RenameSignatureAdapter(sv); } @Override public void visitTypeVariable(String name) { mSv.visitTypeVariable(name); } } }
tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java +17 −8 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import java.util.Enumeration; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.TreeMap; import java.util.regex.Pattern; import java.util.zip.ZipEntry; Loading @@ -57,6 +58,8 @@ public class AsmAnalyzer { private final String[] mDeriveFrom; /** Glob patterns of classes to keep, e.g. "com.foo.*" */ private final String[] mIncludeGlobs; /** The set of classes to exclude.*/ private final Set<String> mExcludedClasses; /** * Creates a new analyzer. Loading @@ -69,12 +72,13 @@ public class AsmAnalyzer { * ("*" does not matches dots whilst "**" does, "." and "$" are interpreted as-is) */ public AsmAnalyzer(Log log, List<String> osJarPath, AsmGenerator gen, String[] deriveFrom, String[] includeGlobs) { String[] deriveFrom, String[] includeGlobs, Set<String> excludeClasses) { mLog = log; mGen = gen; mOsSourceJar = osJarPath != null ? osJarPath : new ArrayList<String>(); mDeriveFrom = deriveFrom != null ? deriveFrom : new String[0]; mIncludeGlobs = includeGlobs != null ? includeGlobs : new String[0]; mExcludedClasses = excludeClasses; } /** Loading @@ -82,9 +86,6 @@ public class AsmAnalyzer { * Fills the generator with classes & dependencies found. */ public void analyze() throws IOException, LogAbortException { AsmAnalyzer visitor = this; Map<String, ClassReader> zipClasses = parseZip(mOsSourceJar); mLog.info("Found %d classes in input JAR%s.", zipClasses.size(), mOsSourceJar.size() > 1 ? "s" : ""); Loading Loading @@ -232,7 +233,7 @@ public class AsmAnalyzer { */ void findClassesDerivingFrom(String super_name, Map<String, ClassReader> zipClasses, Map<String, ClassReader> inOutFound) throws LogAbortException { ClassReader super_clazz = findClass(super_name, zipClasses, inOutFound); findClass(super_name, zipClasses, inOutFound); for (Entry<String, ClassReader> entry : zipClasses.entrySet()) { String className = entry.getKey(); Loading Loading @@ -363,11 +364,12 @@ public class AsmAnalyzer { className = internalToBinaryClassName(className); // exclude classes that have already been found // exclude classes that have already been found or are marked to be excluded if (mInKeep.containsKey(className) || mOutKeep.containsKey(className) || mInDeps.containsKey(className) || mOutDeps.containsKey(className)) { mOutDeps.containsKey(className) || mExcludedClasses.contains(getBaseName(className))) { return; } Loading Loading @@ -451,6 +453,13 @@ public class AsmAnalyzer { } } private String getBaseName(String className) { int pos = className.indexOf('$'); if (pos > 0) { return className.substring(0, pos); } return className; } // --------------------------------------------------- // --- ClassVisitor, FieldVisitor Loading Loading @@ -682,7 +691,7 @@ public class AsmAnalyzer { } @Override public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) { public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) { // pass -- table switch instruction } Loading
tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java +18 −4 Original line number Diff line number Diff line Loading @@ -65,6 +65,9 @@ public class AsmGenerator { /** A map { FQCN => set { method names } } of methods to rewrite as delegates. * The special name {@link DelegateClassAdapter#ALL_NATIVES} can be used as in internal set. */ private final HashMap<String, Set<String>> mDelegateMethods; /** FQCN Names of classes to refactor. All reference to old-FQCN will be updated to new-FQCN. * map old-FQCN => new-FQCN */ private final HashMap<String, String> mRefactorClasses; /** * Creates a new generator that can generate the output JAR with the stubbed classes. Loading Loading @@ -119,6 +122,17 @@ public class AsmGenerator { mClassesNotRenamed.add(oldFqcn); } // Create a map of classes to be refactored. mRefactorClasses = new HashMap<String, String>(); String[] refactorClasses = createInfo.getJavaPkgClasses(); n = refactorClasses.length; for (int i = 0; i < n; i += 2) { assert i + 1 < n; String oldFqcn = binaryToInternalClassName(refactorClasses[i]); String newFqcn = binaryToInternalClassName(refactorClasses[i + 1]); mRefactorClasses.put(oldFqcn, newFqcn);; } // create the map of renamed class -> return type of method to delete. mDeleteReturns = new HashMap<String, Set<String>>(); String[] deleteReturns = createInfo.getDeleteReturns(); Loading Loading @@ -308,14 +322,14 @@ public class AsmGenerator { // original class reader. ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassVisitor rv = cw; ClassVisitor cv = new RefactorClassAdapter(cw, mRefactorClasses); if (newName != className) { rv = new RenameClassAdapter(cw, className, newName); cv = new RenameClassAdapter(cv, className, newName); } ClassVisitor cv = new TransformClassAdapter(mLog, mStubMethods, cv = new TransformClassAdapter(mLog, mStubMethods, mDeleteReturns.get(className), newName, rv, newName, cv, stubNativesOnly, stubNativesOnly || hasNativeMethods); Set<String> delegateMethods = mDelegateMethods.get(className); Loading