diff options
author | Deepanshu Gupta <deepanshu@google.com> | 2014-06-09 18:57:18 -0700 |
---|---|---|
committer | Deepanshu Gupta <deepanshu@google.com> | 2014-06-10 17:44:14 -0700 |
commit | 1160e6d2f7018117b0c29a7e2adba9ece36faec1 (patch) | |
tree | a18723ef2987cd7335539f994e53f0068ba681b2 | |
parent | 6d7a25f317be60ae8a4d8806e517052be2398753 (diff) | |
download | frameworks_base-1160e6d2f7018117b0c29a7e2adba9ece36faec1.zip frameworks_base-1160e6d2f7018117b0c29a7e2adba9ece36faec1.tar.gz frameworks_base-1160e6d2f7018117b0c29a7e2adba9ece36faec1.tar.bz2 |
LayoutLib: Fix EditText rendering
Framework has overloaded java.lang.System.arraycopy() for char[]. The
method is not present on the Desktop VMs. This change replaces the calls
to this method by its more general alternative - the one using Objects.
TODO: Make it more configurable and flexible to allow rewrite of any
such methods in the future.
Change-Id: I6823b13e52b1c555eb257d02b79707d84e73236f
5 files changed, 107 insertions, 35 deletions
diff --git a/tools/layoutlib/create/README.txt b/tools/layoutlib/create/README.txt index 6e0a300..32625ae 100644 --- a/tools/layoutlib/create/README.txt +++ b/tools/layoutlib/create/README.txt @@ -119,8 +119,8 @@ 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 +were added in the Dalvik VM but are not a part of the Desktop VM. The existing classes are +modified to update all references to these non-desktop classes. An alternate implementation of these (com.android.tools.layoutlib.java.*) is injected. RenameClassAdapter and RefactorClassAdapter both inherit from AbstractClassAdapter which changes the @@ -130,11 +130,15 @@ the StackMapTable correctly and Java 7 VM enforces that classes with version gre valid StackMapTable. As a side benefit of this, we can continue to support Java 6 because Java 7 on Mac has horrible font rendering support. +ReplaceMethodCallsAdapter replaces calls to certain methods. Currently, it only rewrites calls to +java.lang.System.arraycopy([CI[CII)V, which is not part of the Desktop VM to call the more general +method java.lang.System.arraycopy(Ljava/lang/Object;ILjava/lang/Object;II)V. + 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 +RefactorClassAdapter -> [ReplaceMethodCallsAdapter] -> ClassWriter - Method stubs -------------- @@ -169,7 +173,7 @@ This is the easiest: we currently inject the following classes: - AutoCloseable and Objects are part of Java 7. To enable us to still run on Java 6, new classes are injected. The implementation for these classes has been taken from Android's libcore (platform/libcore/luni/src/main/java/java/...). -- Charsets, IntegralToString and UnsafeByteSequence are not part of the standard JAVA VM. They are +- Charsets, IntegralToString and UnsafeByteSequence are not part of the Desktop 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 @@ -209,7 +213,7 @@ This won't rename/replace the inner static methods of a given class. 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. +not present in the Desktop VM. An implementation for these classes is also injected. 5- Method erasure based on return type diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java index 3e75c9e..8373e30 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java @@ -32,6 +32,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Enumeration; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -63,7 +64,8 @@ public class AsmAnalyzer { private final Set<String> mExcludedClasses; /** Glob patterns of files to keep as is. */ private final String[] mIncludeFileGlobs; - /** Copy these files into the output as is. */ + /** Internal names of classes that contain method calls that need to be rewritten. */ + private final Set<String> mReplaceMethodCallClasses = new HashSet<String>(); /** * Creates a new analyzer. @@ -109,6 +111,7 @@ public class AsmAnalyzer { mGen.setKeep(found); mGen.setDeps(deps); mGen.setCopyFiles(filesFound); + mGen.setRewriteMethodCallClasses(mReplaceMethodCallClasses); } } @@ -118,7 +121,7 @@ public class AsmAnalyzer { * * @param classes The map of class name => ASM ClassReader. Class names are * in the form "android.view.View". - * @param fileFound The map of file name => InputStream. The file name is + * @param filesFound The map of file name => InputStream. The file name is * in the form "android/data/dataFile". */ void parseZip(List<String> jarPathList, Map<String, ClassReader> classes, @@ -143,8 +146,8 @@ public class AsmAnalyzer { String className = classReaderToClassName(cr); classes.put(className, cr); } else { - for (int i = 0; i < includeFilePatterns.length; ++i) { - if (includeFilePatterns[i].matcher(entry.getName()).matches()) { + for (Pattern includeFilePattern : includeFilePatterns) { + if (includeFilePattern.matcher(entry.getName()).matches()) { filesFound.put(entry.getName(), zip.getInputStream(entry)); break; } @@ -321,6 +324,7 @@ public class AsmAnalyzer { deps, new_deps); for (ClassReader cr : inOutKeepClasses.values()) { + visitor.setClassName(cr.getClassName()); cr.accept(visitor, 0 /* flags */); } @@ -367,6 +371,8 @@ public class AsmAnalyzer { /** New classes to keep as-is found by this visitor. */ private final Map<String, ClassReader> mOutKeep; + private String mClassName; + /** * Creates a new visitor that will find all the dependencies for the visited class. * Types which are already in the zipClasses, keepClasses or inDeps are not marked. @@ -390,6 +396,10 @@ public class AsmAnalyzer { mOutDeps = outDeps; } + private void setClassName(String className) { + mClassName = className; + } + /** * Considers the given class name as a dependency. * If it does, add to the mOutDeps map. @@ -429,7 +439,7 @@ public class AsmAnalyzer { // - android classes are added to dependencies // - non-android classes are added to the list of classes to keep as-is (they don't need // to be stubbed). - if (className.indexOf("android") >= 0) { // TODO make configurable + if (className.contains("android")) { // TODO make configurable mOutDeps.put(className, cr); } else { mOutKeep.put(className, cr); @@ -594,7 +604,7 @@ public class AsmAnalyzer { // type and exceptions do not use generic types. considerSignature(signature); - return new MyMethodVisitor(); + return new MyMethodVisitor(mClassName); } @Override @@ -614,8 +624,11 @@ public class AsmAnalyzer { private class MyMethodVisitor extends MethodVisitor { - public MyMethodVisitor() { + private String mOwnerClass; + + public MyMethodVisitor(String ownerClass) { super(Opcodes.ASM4); + mOwnerClass = ownerClass; } @@ -709,6 +722,13 @@ public class AsmAnalyzer { considerName(owner); // desc is the method's descriptor (see Type). considerDesc(desc); + + + // Check if method is java.lang.System.arrayCopy([CI[CII)V + if (owner.equals("java/lang/System") && name.equals("arraycopy") + && desc.equals("([CI[CII)V")) { + mReplaceMethodCallClasses.add(mOwnerClass); + } } // instruction multianewarray, whatever that is diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java index 207d8ae..c96a143 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java @@ -21,7 +21,6 @@ import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import java.io.ByteArrayOutputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -55,6 +54,8 @@ public class AsmGenerator { private Map<String, ClassReader> mDeps; /** All files that are to be copied as-is. */ private Map<String, InputStream> mCopyFiles; + /** All classes where certain method calls need to be rewritten. */ + private Set<String> mReplaceMethodCallsClasses; /** Counter of number of classes renamed during transform. */ private int mRenameCount; /** FQCN Names of the classes to rename: map old-FQCN => new-FQCN */ @@ -133,7 +134,7 @@ public class AsmGenerator { assert i + 1 < n; String oldFqcn = binaryToInternalClassName(refactorClasses[i]); String newFqcn = binaryToInternalClassName(refactorClasses[i + 1]); - mRefactorClasses.put(oldFqcn, newFqcn);; + mRefactorClasses.put(oldFqcn, newFqcn); } // create the map of renamed class -> return type of method to delete. @@ -203,23 +204,12 @@ public class AsmGenerator { mCopyFiles = copyFiles; } - /** Gets the map of classes to output as-is, except if they have native methods */ - public Map<String, ClassReader> getKeep() { - return mKeep; - } - - /** Gets the map of dependencies that must be completely stubbed */ - public Map<String, ClassReader> getDeps() { - return mDeps; - } - - /** Gets the map of files to output as-is. */ - public Map<String, InputStream> getCopyFiles() { - return mCopyFiles; + public void setRewriteMethodCallClasses(Set<String> rewriteMethodCallClasses) { + mReplaceMethodCallsClasses = rewriteMethodCallClasses; } /** Generates the final JAR */ - public void generate() throws FileNotFoundException, IOException { + public void generate() throws IOException { TreeMap<String, byte[]> all = new TreeMap<String, byte[]>(); for (Class<?> clazz : mInjectClasses) { @@ -329,14 +319,14 @@ public class AsmGenerator { String newName = transformName(className); // transformName returns its input argument if there's no need to rename the class - if (newName != className) { + if (!newName.equals(className)) { mRenameCount++; // This class is being renamed, so remove it from the list of classes not renamed. mClassesNotRenamed.remove(className); } mLog.debug("Transform %s%s%s%s", className, - newName == className ? "" : " (renamed to " + newName + ")", + newName.equals(className) ? "" : " (renamed to " + newName + ")", hasNativeMethods ? " -- has natives" : "", stubNativesOnly ? " -- stub natives only" : ""); @@ -344,8 +334,14 @@ public class AsmGenerator { // original class reader. ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); - ClassVisitor cv = new RefactorClassAdapter(cw, mRefactorClasses); - if (newName != className) { + ClassVisitor cv = cw; + + if (mReplaceMethodCallsClasses.contains(className)) { + cv = new ReplaceMethodCallsAdapter(cv); + } + + cv = new RefactorClassAdapter(cv, mRefactorClasses); + if (!newName.equals(className)) { cv = new RenameClassAdapter(cv, className, newName); } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java index 2e952fc..ad10656 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java @@ -193,8 +193,7 @@ public class Main { private static boolean processArgs(Log log, String[] args, ArrayList<String> osJarPath, String[] osDestJar) { boolean needs_dest = true; - for (int i = 0; i < args.length; i++) { - String s = args[i]; + for (String s : args) { if (s.equals("-v")) { log.setVerbose(true); } else if (s.equals("-p")) { @@ -212,7 +211,7 @@ public class Main { osJarPath.add(s); } } else { - log.error("Unknow argument: %s", s); + log.error("Unknown argument: %s", s); return false; } } diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java new file mode 100644 index 0000000..e57eba1 --- /dev/null +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ReplaceMethodCallsAdapter.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2014 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.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** + * Replaces calls to certain methods that do not exist in the Desktop VM. + */ +public class ReplaceMethodCallsAdapter extends ClassVisitor { + public ReplaceMethodCallsAdapter(ClassVisitor cv) { + super(Opcodes.ASM4, cv); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, + String[] exceptions) { + return new MyMethodVisitor(super.visitMethod(access, name, desc, signature, exceptions)); + } + + private class MyMethodVisitor extends MethodVisitor { + + public MyMethodVisitor(MethodVisitor mv) { + super(Opcodes.ASM4, mv); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc) { + // Check if method is java.lang.System.arrayCopy([CI[CII)V + if (owner.equals("java/lang/System") && name.equals("arraycopy") + && desc.equals("([CI[CII)V")) { + desc = "(Ljava/lang/Object;ILjava/lang/Object;II)V"; + } + super.visitMethodInsn(opcode, owner, name, desc); + } + } +} |