am 1cf5df38: Layoutlib Create: Remove references to non-std Java classes.

* commit '1cf5df38f4bdafa1beb2674ca548ad6d9650766b':
  Layoutlib Create: Remove references to non-std Java classes.
This commit is contained in:
Deepanshu Gupta
2013-10-17 09:03:27 -07:00
committed by Android Git Automerger
26 changed files with 1669 additions and 443 deletions

View File

@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<classpath> <classpath>
<classpathentry kind="src" path="src"/> <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.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/> <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"/> <classpathentry kind="output" path="bin"/>
</classpath> </classpath>

View File

@ -71,6 +71,9 @@ class names, for example "android.*.R**" ("*" does not matches dots whilst "**"
and "." and "$" are interpreted as-is). and "." and "$" are interpreted as-is).
In practice we almost but not quite request the inclusion of full packages. 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. 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 deriving from the requested bases classes are kept.
All classes which name matched the glob pattern are kept. All classes which name matched the glob pattern are kept.
@ -93,6 +96,7 @@ and lists:
- specific methods for which to delegate calls. - specific methods for which to delegate calls.
- specific methods to remove based on their return type. - specific methods to remove based on their return type.
- specific classes to rename. - specific classes to rename.
- specific classes to refactor.
Each of these are specific strategies we use to be able to modify the Android code 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. to fit within the Eclipse renderer. These strategies are explained beow.
@ -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 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. 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 The first step of the transformation is to implement the method delegates.
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 TransformClassAdapter is then used to process the potentially renamed class. The TransformClassAdapter is then used to process the potentially renamed class.
All protected or private classes are market as public. All protected or private classes are market as public.
@ -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 The code of the methods is then kept as-is, except for native methods which are
replaced by a stub. Methods that are to be overridden are also replaced by a stub. replaced by a stub. Methods that are to be overridden are also replaced by a stub.
The transformed class is then fed through the DelegateClassAdapter to implement
method delegates.
Finally fields are also visited and changed from protected/private to public. Finally fields are also visited and changed from protected/private to public.
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 - Method stubs
-------------- --------------
@ -141,19 +156,27 @@ This strategy is now obsolete and replaced by the method delegates.
- Strategies - 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 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. by the bridge (which runs in Eclipse) and the generator.
1- Class Injection 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 - OverrideMethod and its associated MethodListener and MethodAdapter are used
to intercept calls to some specific methods that are stubbed out and change to intercept calls to some specific methods that are stubbed out and change
their return value. their return value.
- CreateInfo class, which configured the generator. Not used yet, but could - CreateInfo class, which configured the generator. Not used yet, but could
in theory help us track what the generator changed. 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 2- Overriding methods
@ -189,7 +212,15 @@ we don't control object creation.
This won't rename/replace the inner static methods of a given class. 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 This is mostly an implementation detail of the bridge: in the Paint class
mentioned above, some inner static classes are used to pass around mentioned above, some inner static classes are used to pass around
@ -201,7 +232,7 @@ example, the inner class Paint$Style in the Paint class should be discarded and
bridge will provide its own implementation. bridge will provide its own implementation.
5- Method Delegates 6- Method Delegates
This strategy is used to override method implementations. This strategy is used to override method implementations.
Given a method SomeClass.MethodName(), 1 or 2 methods are generated: Given a method SomeClass.MethodName(), 1 or 2 methods are generated:
@ -233,7 +264,7 @@ Bytecode opcode list:
http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings
ASM user guide: ASM user guide:
http://download.forge.objectweb.org/asm/asm-guide.pdf http://download.forge.objectweb.org/asm/asm4-guide.pdf
-- --

View File

@ -0,0 +1,418 @@
/*
* 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);
}
}
}

View File

@ -34,6 +34,7 @@ import java.util.Enumeration;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
@ -57,6 +58,8 @@ public class AsmAnalyzer {
private final String[] mDeriveFrom; private final String[] mDeriveFrom;
/** Glob patterns of classes to keep, e.g. "com.foo.*" */ /** Glob patterns of classes to keep, e.g. "com.foo.*" */
private final String[] mIncludeGlobs; private final String[] mIncludeGlobs;
/** The set of classes to exclude.*/
private final Set<String> mExcludedClasses;
/** /**
* Creates a new analyzer. * Creates a new analyzer.
@ -69,12 +72,13 @@ public class AsmAnalyzer {
* ("*" does not matches dots whilst "**" does, "." and "$" are interpreted as-is) * ("*" does not matches dots whilst "**" does, "." and "$" are interpreted as-is)
*/ */
public AsmAnalyzer(Log log, List<String> osJarPath, AsmGenerator gen, public AsmAnalyzer(Log log, List<String> osJarPath, AsmGenerator gen,
String[] deriveFrom, String[] includeGlobs) { String[] deriveFrom, String[] includeGlobs, Set<String> excludeClasses) {
mLog = log; mLog = log;
mGen = gen; mGen = gen;
mOsSourceJar = osJarPath != null ? osJarPath : new ArrayList<String>(); mOsSourceJar = osJarPath != null ? osJarPath : new ArrayList<String>();
mDeriveFrom = deriveFrom != null ? deriveFrom : new String[0]; mDeriveFrom = deriveFrom != null ? deriveFrom : new String[0];
mIncludeGlobs = includeGlobs != null ? includeGlobs : new String[0]; mIncludeGlobs = includeGlobs != null ? includeGlobs : new String[0];
mExcludedClasses = excludeClasses;
} }
/** /**
@ -82,9 +86,6 @@ public class AsmAnalyzer {
* Fills the generator with classes & dependencies found. * Fills the generator with classes & dependencies found.
*/ */
public void analyze() throws IOException, LogAbortException { public void analyze() throws IOException, LogAbortException {
AsmAnalyzer visitor = this;
Map<String, ClassReader> zipClasses = parseZip(mOsSourceJar); Map<String, ClassReader> zipClasses = parseZip(mOsSourceJar);
mLog.info("Found %d classes in input JAR%s.", zipClasses.size(), mLog.info("Found %d classes in input JAR%s.", zipClasses.size(),
mOsSourceJar.size() > 1 ? "s" : ""); mOsSourceJar.size() > 1 ? "s" : "");
@ -232,7 +233,7 @@ public class AsmAnalyzer {
*/ */
void findClassesDerivingFrom(String super_name, Map<String, ClassReader> zipClasses, void findClassesDerivingFrom(String super_name, Map<String, ClassReader> zipClasses,
Map<String, ClassReader> inOutFound) throws LogAbortException { 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()) { for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
String className = entry.getKey(); String className = entry.getKey();
@ -363,11 +364,12 @@ public class AsmAnalyzer {
className = internalToBinaryClassName(className); 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) || if (mInKeep.containsKey(className) ||
mOutKeep.containsKey(className) || mOutKeep.containsKey(className) ||
mInDeps.containsKey(className) || mInDeps.containsKey(className) ||
mOutDeps.containsKey(className)) { mOutDeps.containsKey(className) ||
mExcludedClasses.contains(getBaseName(className))) {
return; return;
} }
@ -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 // --- ClassVisitor, FieldVisitor
@ -682,7 +691,7 @@ public class AsmAnalyzer {
} }
@Override @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 // pass -- table switch instruction
} }

View File

@ -65,6 +65,9 @@ public class AsmGenerator {
/** A map { FQCN => set { method names } } of methods to rewrite as delegates. /** 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. */ * The special name {@link DelegateClassAdapter#ALL_NATIVES} can be used as in internal set. */
private final HashMap<String, Set<String>> mDelegateMethods; 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. * Creates a new generator that can generate the output JAR with the stubbed classes.
@ -119,6 +122,17 @@ public class AsmGenerator {
mClassesNotRenamed.add(oldFqcn); 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. // create the map of renamed class -> return type of method to delete.
mDeleteReturns = new HashMap<String, Set<String>>(); mDeleteReturns = new HashMap<String, Set<String>>();
String[] deleteReturns = createInfo.getDeleteReturns(); String[] deleteReturns = createInfo.getDeleteReturns();
@ -308,14 +322,14 @@ public class AsmGenerator {
// original class reader. // original class reader.
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor rv = cw; ClassVisitor cv = new RefactorClassAdapter(cw, mRefactorClasses);
if (newName != className) { 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), mDeleteReturns.get(className),
newName, rv, newName, cv,
stubNativesOnly, stubNativesOnly || hasNativeMethods); stubNativesOnly, stubNativesOnly || hasNativeMethods);
Set<String> delegateMethods = mDelegateMethods.get(className); Set<String> delegateMethods = mDelegateMethods.get(className);

View File

@ -17,6 +17,10 @@
package com.android.tools.layoutlib.create; package com.android.tools.layoutlib.create;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate; import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import com.android.tools.layoutlib.java.AutoCloseable;
import com.android.tools.layoutlib.java.Charsets;
import com.android.tools.layoutlib.java.IntegralToString;
import com.android.tools.layoutlib.java.UnsafeByteSequence;
/** /**
* Describes the work to be done by {@link AsmGenerator}. * Describes the work to be done by {@link AsmGenerator}.
@ -84,6 +88,15 @@ public final class CreateInfo implements ICreateInfo {
return DELETE_RETURNS; return DELETE_RETURNS;
} }
/**
* Returns the list of classes to refactor, must be an even list: the binary FQCN of class to
* replace followed by the new FQCN. All references to the old class should be updated to the
* new class. The list can be empty but must not be null.
*/
@Override
public String[] getJavaPkgClasses() {
return JAVA_PKG_CLASSES;
}
//----- //-----
/** /**
@ -95,7 +108,12 @@ public final class CreateInfo implements ICreateInfo {
MethodAdapter.class, MethodAdapter.class,
ICreateInfo.class, ICreateInfo.class,
CreateInfo.class, CreateInfo.class,
LayoutlibDelegate.class LayoutlibDelegate.class,
/* Java package classes */
AutoCloseable.class,
IntegralToString.class,
UnsafeByteSequence.class,
Charsets.class,
}; };
/** /**
@ -194,6 +212,19 @@ public final class CreateInfo implements ICreateInfo {
"com.android.internal.policy.PolicyManager", "com.android.internal.policy._Original_PolicyManager", "com.android.internal.policy.PolicyManager", "com.android.internal.policy._Original_PolicyManager",
}; };
/**
* The list of class references to update, must be an even list: the binary
* FQCN of class to replace followed by the new FQCN. The classes to
* replace are to be excluded from the output.
*/
private final static String[] JAVA_PKG_CLASSES =
new String[] {
"java.lang.AutoCloseable", "com.android.tools.layoutlib.java.AutoCloseable",
"java.nio.charset.Charsets", "com.android.tools.layoutlib.java.Charsets",
"java.lang.IntegralToString", "com.android.tools.layoutlib.java.IntegralToString",
"java.lang.UnsafeByteSequence", "com.android.tools.layoutlib.java.UnsafeByteSequence",
};
/** /**
* List of classes for which the methods returning them should be deleted. * List of classes for which the methods returning them should be deleted.
* The array contains a list of null terminated section starting with the name of the class * The array contains a list of null terminated section starting with the name of the class

View File

@ -62,4 +62,11 @@ public interface ICreateInfo {
*/ */
public abstract String[] getDeleteReturns(); public abstract String[] getDeleteReturns();
/**
* Returns the list of classes to refactor, must be an even list: the
* binary FQCN of class to replace followed by the new FQCN. All references
* to the old class should be updated to the new class.
* The list can be empty but must not be null.
*/
public abstract String[] getJavaPkgClasses();
} }

View File

@ -18,6 +18,7 @@ package com.android.tools.layoutlib.create;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -86,7 +87,9 @@ public class Main {
} }
try { try {
AsmGenerator agen = new AsmGenerator(log, osDestJar, new CreateInfo()); CreateInfo info = new CreateInfo();
Set<String> excludeClasses = getExcludedClasses(info);
AsmGenerator agen = new AsmGenerator(log, osDestJar, info);
AsmAnalyzer aa = new AsmAnalyzer(log, osJarPath, agen, AsmAnalyzer aa = new AsmAnalyzer(log, osJarPath, agen,
new String[] { // derived from new String[] { // derived from
@ -110,7 +113,8 @@ public class Main {
"android.pim.*", // for datepicker "android.pim.*", // for datepicker
"android.os.*", // for android.os.Handler "android.os.*", // for android.os.Handler
"android.database.ContentObserver", // for Digital clock "android.database.ContentObserver", // for Digital clock
}); },
excludeClasses);
aa.analyze(); aa.analyze();
agen.generate(); agen.generate();
@ -146,6 +150,16 @@ public class Main {
return 1; return 1;
} }
private static Set<String> getExcludedClasses(CreateInfo info) {
String[] refactoredClasses = info.getJavaPkgClasses();
Set<String> excludedClasses = new HashSet<String>(refactoredClasses.length);
for (int i = 0; i < refactoredClasses.length; i+=2) {
excludedClasses.add(refactoredClasses[i]);
}
return excludedClasses;
}
private static int listDeps(ArrayList<String> osJarPath, Log log) { private static int listDeps(ArrayList<String> osJarPath, Log log) {
DependencyFinder df = new DependencyFinder(log); DependencyFinder df = new DependencyFinder(log);
try { try {

View File

@ -0,0 +1,49 @@
/*
* 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 java.util.HashMap;
import org.objectweb.asm.ClassVisitor;
public class RefactorClassAdapter extends AbstractClassAdapter {
private final HashMap<String, String> mRefactorClasses;
RefactorClassAdapter(ClassVisitor cv, HashMap<String, String> refactorClasses) {
super(cv);
mRefactorClasses = refactorClasses;
}
@Override
protected String renameInternalType(String oldClassName) {
if (oldClassName != null) {
String newName = mRefactorClasses.get(oldClassName);
if (newName != null) {
return newName;
}
int pos = oldClassName.indexOf('$');
if (pos > 0) {
newName = mRefactorClasses.get(oldClassName.substring(0, pos));
if (newName != null) {
return newName + oldClassName.substring(pos);
}
}
}
return oldClassName;
}
}

View File

@ -16,17 +16,7 @@
package com.android.tools.layoutlib.create; package com.android.tools.layoutlib.create;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
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;
/** /**
* This class visitor renames a class from a given old name to a given new name. * This class visitor renames a class from a given old name to a given new name.
@ -36,7 +26,7 @@ import org.objectweb.asm.signature.SignatureWriter;
* For inner classes, this handles only the case where the outer class name changes. * For inner classes, this handles only the case where the outer class name changes.
* The inner class name should remain the same. * The inner class name should remain the same.
*/ */
public class RenameClassAdapter extends ClassVisitor { public class RenameClassAdapter extends AbstractClassAdapter {
private final String mOldName; private final String mOldName;
@ -49,8 +39,8 @@ public class RenameClassAdapter extends ClassVisitor {
* The class visitor will also rename all inner classes and references in the methods. * The class visitor will also rename all inner classes and references in the methods.
* The names must be full qualified internal ASM names (e.g. com/blah/MyClass$InnerClass). * The names must be full qualified internal ASM names (e.g. com/blah/MyClass$InnerClass).
*/ */
public RenameClassAdapter(ClassWriter cv, String oldName, String newName) { public RenameClassAdapter(ClassVisitor cv, String oldName, String newName) {
super(Opcodes.ASM4, cv); super(cv);
mOldBase = mOldName = oldName; mOldBase = mOldName = oldName;
mNewBase = mNewName = newName; mNewBase = mNewName = newName;
@ -66,71 +56,6 @@ public class RenameClassAdapter extends ClassVisitor {
assert (mOldBase == null && mNewBase == null) || (mOldBase != null && mNewBase != null); assert (mOldBase == null && mNewBase == null) || (mOldBase != null && mNewBase != null);
} }
/**
* 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 an internal type name, e.g. "com.package.MyClass". * Renames an internal type name, e.g. "com.package.MyClass".
* If the type doesn't need to be renamed, returns the input string as-is. * If the type doesn't need to be renamed, returns the input string as-is.
@ -138,7 +63,8 @@ public class RenameClassAdapter extends ClassVisitor {
* The internal type of some of the MethodVisitor turns out to be a type * The internal type of some of the MethodVisitor turns out to be a type
descriptor sometimes so descriptors are renamed too. descriptor sometimes so descriptors are renamed too.
*/ */
String renameInternalType(String type) { @Override
protected String renameInternalType(String type) {
if (type == null) { if (type == null) {
return null; return null;
} }
@ -155,309 +81,7 @@ public class RenameClassAdapter extends ClassVisitor {
if (pos == mOldBase.length() && type.startsWith(mOldBase)) { if (pos == mOldBase.length() && type.startsWith(mOldBase)) {
return mNewBase + type.substring(pos); return mNewBase + type.substring(pos);
} }
// The internal type of some of the MethodVisitor turns out to be a type
// descriptor sometimes. This is the case with visitTypeInsn(type) and
// visitMethodInsn(owner). We try to detect it and adjust it here.
if (type.indexOf(';') > 0) {
type = renameTypeDesc(type);
}
return type; 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) {
if (sig == null) {
return null;
}
SignatureReader reader = new SignatureReader(sig);
SignatureWriter writer = new SignatureWriter();
reader.acceptType(new RenameSignatureAdapter(writer));
sig = writer.toString();
return 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);
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
assert outerName.equals(mOldName);
outerName = renameInternalType(outerName);
name = outerName + "$" + innerName;
super.visitInnerClass(name, outerName, innerName, access);
}
@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);
signature = renameFieldSignature(signature);
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) {
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) {
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);
}
}
} }

View File

@ -0,0 +1,31 @@
/*
* 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.java;
/**
* Defines the same interface as the java.lang.AutoCloseable which was added in
* Java 7. This hack makes it possible to run the Android code which uses Java 7
* features (API 18 and beyond) to run on Java 6.
* <p/>
* Extracted from API level 18, file:
* platform/libcore/luni/src/main/java/java/lang/AutoCloseable.java
*/
public interface AutoCloseable {
/**
* Closes the object and release any system resources it holds.
*/
void close() throws Exception; }

View File

@ -0,0 +1,133 @@
/*
* 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.java;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
/**
* Defines the same class as the java.nio.charset.Charsets which was added in
* Dalvik VM. This hack, provides a replacement for that class which can't be
* loaded in the standard JVM since it's in the java package and standard JVM
* doesn't have it. An implementation of the native methods in the original
* class has been added.
* <p/>
* Extracted from API level 18, file:
* platform/libcore/luni/src/main/java/java/nio/charset/Charsets
*/
public final class Charsets {
/**
* A cheap and type-safe constant for the ISO-8859-1 Charset.
*/
public static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
/**
* A cheap and type-safe constant for the US-ASCII Charset.
*/
public static final Charset US_ASCII = Charset.forName("US-ASCII");
/**
* A cheap and type-safe constant for the UTF-8 Charset.
*/
public static final Charset UTF_8 = Charset.forName("UTF-8");
/**
* Returns a new byte array containing the bytes corresponding to the given characters,
* encoded in US-ASCII. Unrepresentable characters are replaced by (byte) '?'.
*/
public static byte[] toAsciiBytes(char[] chars, int offset, int length) {
CharBuffer cb = CharBuffer.allocate(length);
cb.put(chars, offset, length);
return US_ASCII.encode(cb).array();
}
/**
* Returns a new byte array containing the bytes corresponding to the given characters,
* encoded in ISO-8859-1. Unrepresentable characters are replaced by (byte) '?'.
*/
public static byte[] toIsoLatin1Bytes(char[] chars, int offset, int length) {
CharBuffer cb = CharBuffer.allocate(length);
cb.put(chars, offset, length);
return ISO_8859_1.encode(cb).array();
}
/**
* Returns a new byte array containing the bytes corresponding to the given characters,
* encoded in UTF-8. All characters are representable in UTF-8.
*/
public static byte[] toUtf8Bytes(char[] chars, int offset, int length) {
CharBuffer cb = CharBuffer.allocate(length);
cb.put(chars, offset, length);
return UTF_8.encode(cb).array();
}
/**
* Returns a new byte array containing the bytes corresponding to the given characters,
* encoded in UTF-16BE. All characters are representable in UTF-16BE.
*/
public static byte[] toBigEndianUtf16Bytes(char[] chars, int offset, int length) {
byte[] result = new byte[length * 2];
int end = offset + length;
int resultIndex = 0;
for (int i = offset; i < end; ++i) {
char ch = chars[i];
result[resultIndex++] = (byte) (ch >> 8);
result[resultIndex++] = (byte) ch;
}
return result;
}
/**
* Decodes the given US-ASCII bytes into the given char[]. Equivalent to but faster than:
*
* for (int i = 0; i < count; ++i) {
* char ch = (char) (data[start++] & 0xff);
* value[i] = (ch <= 0x7f) ? ch : REPLACEMENT_CHAR;
* }
*/
public static void asciiBytesToChars(byte[] bytes, int offset, int length, char[] chars) {
if (bytes == null || chars == null) {
return;
}
final char REPLACEMENT_CHAR = (char)0xffd;
int start = offset;
for (int i = 0; i < length; ++i) {
char ch = (char) (bytes[start++] & 0xff);
chars[i] = (ch <= 0x7f) ? ch : REPLACEMENT_CHAR;
}
}
/**
* Decodes the given ISO-8859-1 bytes into the given char[]. Equivalent to but faster than:
*
* for (int i = 0; i < count; ++i) {
* value[i] = (char) (data[start++] & 0xff);
* }
*/
public static void isoLatin1BytesToChars(byte[] bytes, int offset, int length, char[] chars) {
if (bytes == null || chars == null) {
return;
}
int start = offset;
for (int i = 0; i < length; ++i) {
chars[i] = (char) (bytes[start++] & 0xff);
}
}
private Charsets() {
}
}

View File

@ -0,0 +1,537 @@
/*
* 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.java;
/**
* Defines the same class as the java.lang.IntegralToString which was added in
* Dalvik VM. This hack, provides a replacement for that class which can't be
* loaded in the standard JVM since it's in the java package and standard JVM
* doesn't have it. Since it's no longer in java.lang, access to package
* private methods and classes has been replaced by the closes matching public
* implementation.
* <p/>
* Extracted from API level 18, file:
* platform/libcore/luni/src/main/java/java/lang/IntegralToString.java
*/
public final class IntegralToString {
/**
* When appending to an AbstractStringBuilder, this thread-local char[] lets us avoid
* allocation of a temporary array. (We can't write straight into the AbstractStringBuilder
* because it's almost as expensive to work out the exact length of the result as it is to
* do the formatting. We could try being conservative and "delete"-ing the unused space
* afterwards, but then we'd need to duplicate convertInt and convertLong rather than share
* the code.)
*/
private static final ThreadLocal<char[]> BUFFER = new ThreadLocal<char[]>() {
@Override protected char[] initialValue() {
return new char[20]; // Maximum length of a base-10 long.
}
};
/**
* These tables are used to special-case toString computation for
* small values. This serves three purposes: it reduces memory usage;
* it increases performance for small values; and it decreases the
* number of comparisons required to do the length computation.
* Elements of this table are lazily initialized on first use.
* No locking is necessary, i.e., we use the non-volatile, racy
* single-check idiom.
*/
private static final String[] SMALL_NONNEGATIVE_VALUES = new String[100];
private static final String[] SMALL_NEGATIVE_VALUES = new String[100];
/** TENS[i] contains the tens digit of the number i, 0 <= i <= 99. */
private static final char[] TENS = {
'0', '0', '0', '0', '0', '0', '0', '0', '0', '0',
'1', '1', '1', '1', '1', '1', '1', '1', '1', '1',
'2', '2', '2', '2', '2', '2', '2', '2', '2', '2',
'3', '3', '3', '3', '3', '3', '3', '3', '3', '3',
'4', '4', '4', '4', '4', '4', '4', '4', '4', '4',
'5', '5', '5', '5', '5', '5', '5', '5', '5', '5',
'6', '6', '6', '6', '6', '6', '6', '6', '6', '6',
'7', '7', '7', '7', '7', '7', '7', '7', '7', '7',
'8', '8', '8', '8', '8', '8', '8', '8', '8', '8',
'9', '9', '9', '9', '9', '9', '9', '9', '9', '9'
};
/** Ones [i] contains the tens digit of the number i, 0 <= i <= 99. */
private static final char[] ONES = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
};
/**
* Table for MOD / DIV 10 computation described in Section 10-21
* of Hank Warren's "Hacker's Delight" online addendum.
* http://www.hackersdelight.org/divcMore.pdf
*/
private static final char[] MOD_10_TABLE = {
0, 1, 2, 2, 3, 3, 4, 5, 5, 6, 7, 7, 8, 8, 9, 0
};
/**
* The digits for every supported radix.
*/
private static final char[] DIGITS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
'u', 'v', 'w', 'x', 'y', 'z'
};
private static final char[] UPPER_CASE_DIGITS = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
'U', 'V', 'W', 'X', 'Y', 'Z'
};
private IntegralToString() {
}
/**
* Equivalent to Integer.toString(i, radix).
*/
public static String intToString(int i, int radix) {
if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
radix = 10;
}
if (radix == 10) {
return intToString(i);
}
/*
* If i is positive, negate it. This is the opposite of what one might
* expect. It is necessary because the range of the negative values is
* strictly larger than that of the positive values: there is no
* positive value corresponding to Integer.MIN_VALUE.
*/
boolean negative = false;
if (i < 0) {
negative = true;
} else {
i = -i;
}
int bufLen = radix < 8 ? 33 : 12; // Max chars in result (conservative)
char[] buf = new char[bufLen];
int cursor = bufLen;
do {
int q = i / radix;
buf[--cursor] = DIGITS[radix * q - i];
i = q;
} while (i != 0);
if (negative) {
buf[--cursor] = '-';
}
return new String(buf, cursor, bufLen - cursor);
}
/**
* Equivalent to Integer.toString(i).
*/
public static String intToString(int i) {
return convertInt(null, i);
}
/**
* Equivalent to sb.append(Integer.toString(i)).
*/
public static void appendInt(StringBuilder sb, int i) {
convertInt(sb, i);
}
/**
* Returns the string representation of i and leaves sb alone if sb is null.
* Returns null and appends the string representation of i to sb if sb is non-null.
*/
private static String convertInt(StringBuilder sb, int i) {
boolean negative = false;
String quickResult = null;
if (i < 0) {
negative = true;
i = -i;
if (i < 100) {
if (i < 0) {
// If -n is still negative, n is Integer.MIN_VALUE
quickResult = "-2147483648";
} else {
quickResult = SMALL_NEGATIVE_VALUES[i];
if (quickResult == null) {
SMALL_NEGATIVE_VALUES[i] = quickResult =
i < 10 ? stringOf('-', ONES[i]) : stringOf('-', TENS[i], ONES[i]);
}
}
}
} else {
if (i < 100) {
quickResult = SMALL_NONNEGATIVE_VALUES[i];
if (quickResult == null) {
SMALL_NONNEGATIVE_VALUES[i] = quickResult =
i < 10 ? stringOf(ONES[i]) : stringOf(TENS[i], ONES[i]);
}
}
}
if (quickResult != null) {
if (sb != null) {
sb.append(quickResult);
return null;
}
return quickResult;
}
int bufLen = 11; // Max number of chars in result
char[] buf = (sb != null) ? BUFFER.get() : new char[bufLen];
int cursor = bufLen;
// Calculate digits two-at-a-time till remaining digits fit in 16 bits
while (i >= (1 << 16)) {
// Compute q = n/100 and r = n % 100 as per "Hacker's Delight" 10-8
int q = (int) ((0x51EB851FL * i) >>> 37);
int r = i - 100*q;
buf[--cursor] = ONES[r];
buf[--cursor] = TENS[r];
i = q;
}
// Calculate remaining digits one-at-a-time for performance
while (i != 0) {
// Compute q = n/10 and r = n % 10 as per "Hacker's Delight" 10-8
int q = (0xCCCD * i) >>> 19;
int r = i - 10*q;
buf[--cursor] = DIGITS[r];
i = q;
}
if (negative) {
buf[--cursor] = '-';
}
if (sb != null) {
sb.append(buf, cursor, bufLen - cursor);
return null;
} else {
return new String(buf, cursor, bufLen - cursor);
}
}
/**
* Equivalent to Long.toString(v, radix).
*/
public static String longToString(long v, int radix) {
int i = (int) v;
if (i == v) {
return intToString(i, radix);
}
if (radix < Character.MIN_RADIX || radix > Character.MAX_RADIX) {
radix = 10;
}
if (radix == 10) {
return longToString(v);
}
/*
* If v is positive, negate it. This is the opposite of what one might
* expect. It is necessary because the range of the negative values is
* strictly larger than that of the positive values: there is no
* positive value corresponding to Integer.MIN_VALUE.
*/
boolean negative = false;
if (v < 0) {
negative = true;
} else {
v = -v;
}
int bufLen = radix < 8 ? 65 : 23; // Max chars in result (conservative)
char[] buf = new char[bufLen];
int cursor = bufLen;
do {
long q = v / radix;
buf[--cursor] = DIGITS[(int) (radix * q - v)];
v = q;
} while (v != 0);
if (negative) {
buf[--cursor] = '-';
}
return new String(buf, cursor, bufLen - cursor);
}
/**
* Equivalent to Long.toString(l).
*/
public static String longToString(long l) {
return convertLong(null, l);
}
/**
* Equivalent to sb.append(Long.toString(l)).
*/
public static void appendLong(StringBuilder sb, long l) {
convertLong(sb, l);
}
/**
* Returns the string representation of n and leaves sb alone if sb is null.
* Returns null and appends the string representation of n to sb if sb is non-null.
*/
private static String convertLong(StringBuilder sb, long n) {
int i = (int) n;
if (i == n) {
return convertInt(sb, i);
}
boolean negative = (n < 0);
if (negative) {
n = -n;
if (n < 0) {
// If -n is still negative, n is Long.MIN_VALUE
String quickResult = "-9223372036854775808";
if (sb != null) {
sb.append(quickResult);
return null;
}
return quickResult;
}
}
int bufLen = 20; // Maximum number of chars in result
char[] buf = (sb != null) ? BUFFER.get() : new char[bufLen];
int low = (int) (n % 1000000000); // Extract low-order 9 digits
int cursor = intIntoCharArray(buf, bufLen, low);
// Zero-pad Low order part to 9 digits
while (cursor != (bufLen - 9)) {
buf[--cursor] = '0';
}
/*
* The remaining digits are (n - low) / 1,000,000,000. This
* "exact division" is done as per the online addendum to Hank Warren's
* "Hacker's Delight" 10-20, http://www.hackersdelight.org/divcMore.pdf
*/
n = ((n - low) >>> 9) * 0x8E47CE423A2E9C6DL;
/*
* If the remaining digits fit in an int, emit them using a
* single call to intIntoCharArray. Otherwise, strip off the
* low-order digit, put it in buf, and then call intIntoCharArray
* on the remaining digits (which now fit in an int).
*/
if ((n & (-1L << 32)) == 0) {
cursor = intIntoCharArray(buf, cursor, (int) n);
} else {
/*
* Set midDigit to n % 10
*/
int lo32 = (int) n;
int hi32 = (int) (n >>> 32);
// midDigit = ((unsigned) low32) % 10, per "Hacker's Delight" 10-21
int midDigit = MOD_10_TABLE[(0x19999999 * lo32 + (lo32 >>> 1) + (lo32 >>> 3)) >>> 28];
// Adjust midDigit for hi32. (assert hi32 == 1 || hi32 == 2)
midDigit -= hi32 << 2; // 1L << 32 == -4 MOD 10
if (midDigit < 0) {
midDigit += 10;
}
buf[--cursor] = DIGITS[midDigit];
// Exact division as per Warren 10-20
int rest = ((int) ((n - midDigit) >>> 1)) * 0xCCCCCCCD;
cursor = intIntoCharArray(buf, cursor, rest);
}
if (negative) {
buf[--cursor] = '-';
}
if (sb != null) {
sb.append(buf, cursor, bufLen - cursor);
return null;
} else {
return new String(buf, cursor, bufLen - cursor);
}
}
/**
* Inserts the unsigned decimal integer represented by n into the specified
* character array starting at position cursor. Returns the index after
* the last character inserted (i.e., the value to pass in as cursor the
* next time this method is called). Note that n is interpreted as a large
* positive integer (not a negative integer) if its sign bit is set.
*/
private static int intIntoCharArray(char[] buf, int cursor, int n) {
// Calculate digits two-at-a-time till remaining digits fit in 16 bits
while ((n & 0xffff0000) != 0) {
/*
* Compute q = n/100 and r = n % 100 as per "Hacker's Delight" 10-8.
* This computation is slightly different from the corresponding
* computation in intToString: the shifts before and after
* multiply can't be combined, as that would yield the wrong result
* if n's sign bit were set.
*/
int q = (int) ((0x51EB851FL * (n >>> 2)) >>> 35);
int r = n - 100*q;
buf[--cursor] = ONES[r];
buf[--cursor] = TENS[r];
n = q;
}
// Calculate remaining digits one-at-a-time for performance
while (n != 0) {
// Compute q = n / 10 and r = n % 10 as per "Hacker's Delight" 10-8
int q = (0xCCCD * n) >>> 19;
int r = n - 10*q;
buf[--cursor] = DIGITS[r];
n = q;
}
return cursor;
}
public static String intToBinaryString(int i) {
int bufLen = 32; // Max number of binary digits in an int
char[] buf = new char[bufLen];
int cursor = bufLen;
do {
buf[--cursor] = DIGITS[i & 1];
} while ((i >>>= 1) != 0);
return new String(buf, cursor, bufLen - cursor);
}
public static String longToBinaryString(long v) {
int i = (int) v;
if (v >= 0 && i == v) {
return intToBinaryString(i);
}
int bufLen = 64; // Max number of binary digits in a long
char[] buf = new char[bufLen];
int cursor = bufLen;
do {
buf[--cursor] = DIGITS[((int) v) & 1];
} while ((v >>>= 1) != 0);
return new String(buf, cursor, bufLen - cursor);
}
public static StringBuilder appendByteAsHex(StringBuilder sb, byte b, boolean upperCase) {
char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS;
sb.append(digits[(b >> 4) & 0xf]);
sb.append(digits[b & 0xf]);
return sb;
}
public static String byteToHexString(byte b, boolean upperCase) {
char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS;
char[] buf = new char[2]; // We always want two digits.
buf[0] = digits[(b >> 4) & 0xf];
buf[1] = digits[b & 0xf];
return new String(buf, 0, 2);
}
public static String bytesToHexString(byte[] bytes, boolean upperCase) {
char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS;
char[] buf = new char[bytes.length * 2];
int c = 0;
for (byte b : bytes) {
buf[c++] = digits[(b >> 4) & 0xf];
buf[c++] = digits[b & 0xf];
}
return new String(buf);
}
public static String intToHexString(int i, boolean upperCase, int minWidth) {
int bufLen = 8; // Max number of hex digits in an int
char[] buf = new char[bufLen];
int cursor = bufLen;
char[] digits = upperCase ? UPPER_CASE_DIGITS : DIGITS;
do {
buf[--cursor] = digits[i & 0xf];
} while ((i >>>= 4) != 0 || (bufLen - cursor < minWidth));
return new String(buf, cursor, bufLen - cursor);
}
public static String longToHexString(long v) {
int i = (int) v;
if (v >= 0 && i == v) {
return intToHexString(i, false, 0);
}
int bufLen = 16; // Max number of hex digits in a long
char[] buf = new char[bufLen];
int cursor = bufLen;
do {
buf[--cursor] = DIGITS[((int) v) & 0xF];
} while ((v >>>= 4) != 0);
return new String(buf, cursor, bufLen - cursor);
}
public static String intToOctalString(int i) {
int bufLen = 11; // Max number of octal digits in an int
char[] buf = new char[bufLen];
int cursor = bufLen;
do {
buf[--cursor] = DIGITS[i & 7];
} while ((i >>>= 3) != 0);
return new String(buf, cursor, bufLen - cursor);
}
public static String longToOctalString(long v) {
int i = (int) v;
if (v >= 0 && i == v) {
return intToOctalString(i);
}
int bufLen = 22; // Max number of octal digits in a long
char[] buf = new char[bufLen];
int cursor = bufLen;
do {
buf[--cursor] = DIGITS[((int) v) & 7];
} while ((v >>>= 3) != 0);
return new String(buf, cursor, bufLen - cursor);
}
private static String stringOf(char... args) {
return new String(args, 0, args.length);
}
}

View File

@ -0,0 +1,81 @@
/*
* 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.java;
import java.nio.charset.Charset;
/**
* Defines the same class as the java.lang.UnsafeByteSequence which was added in
* Dalvik VM. This hack, provides a replacement for that class which can't be
* loaded in the standard JVM since it's in the java package and standard JVM
* doesn't have it.
* <p/>
* Extracted from API level 18, file:
* platform/libcore/luni/src/main/java/java/lang/UnsafeByteSequence.java
*/
public class UnsafeByteSequence {
private byte[] bytes;
private int count;
public UnsafeByteSequence(int initialCapacity) {
this.bytes = new byte[initialCapacity];
}
public int size() {
return count;
}
/**
* Moves the write pointer back to the beginning of the sequence,
* but without resizing or reallocating the buffer.
*/
public void rewind() {
count = 0;
}
public void write(byte[] buffer, int offset, int length) {
if (count + length >= bytes.length) {
byte[] newBytes = new byte[(count + length) * 2];
System.arraycopy(bytes, 0, newBytes, 0, count);
bytes = newBytes;
}
System.arraycopy(buffer, offset, bytes, count, length);
count += length;
}
public void write(int b) {
if (count == bytes.length) {
byte[] newBytes = new byte[count * 2];
System.arraycopy(bytes, 0, newBytes, 0, count);
bytes = newBytes;
}
bytes[count++] = (byte) b;
}
public byte[] toByteArray() {
if (count == bytes.length) {
return bytes;
}
byte[] result = new byte[count];
System.arraycopy(bytes, 0, result, 0, count);
return result;
}
public String toString(Charset cs) {
return new String(bytes, 0, count, cs);
}
}

View File

@ -31,7 +31,9 @@ import org.objectweb.asm.ClassReader;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.TreeMap; import java.util.TreeMap;
/** /**
@ -51,8 +53,10 @@ public class AsmAnalyzerTest {
mOsJarPath = new ArrayList<String>(); mOsJarPath = new ArrayList<String>();
mOsJarPath.add(url.getFile()); mOsJarPath.add(url.getFile());
Set<String> excludeClasses = new HashSet<String>(1);
excludeClasses.add("java.lang.JavaClass");
mAa = new AsmAnalyzer(mLog, mOsJarPath, null /* gen */, mAa = new AsmAnalyzer(mLog, mOsJarPath, null /* gen */,
null /* deriveFrom */, null /* includeGlobs */ ); null /* deriveFrom */, null /* includeGlobs */, excludeClasses);
} }
@After @After
@ -64,6 +68,7 @@ public class AsmAnalyzerTest {
Map<String, ClassReader> map = mAa.parseZip(mOsJarPath); Map<String, ClassReader> map = mAa.parseZip(mOsJarPath);
assertArrayEquals(new String[] { assertArrayEquals(new String[] {
"java.lang.JavaClass",
"mock_android.dummy.InnerTest", "mock_android.dummy.InnerTest",
"mock_android.dummy.InnerTest$DerivingClass", "mock_android.dummy.InnerTest$DerivingClass",
"mock_android.dummy.InnerTest$MyGenerics1", "mock_android.dummy.InnerTest$MyGenerics1",
@ -221,7 +226,11 @@ public class AsmAnalyzerTest {
for (ClassReader cr2 : in_deps.values()) { for (ClassReader cr2 : in_deps.values()) {
cr2.accept(visitor, 0 /* flags */); cr2.accept(visitor, 0 /* flags */);
} }
keep.putAll(new_keep);
assertArrayEquals(new String[] { }, out_deps.keySet().toArray()); assertArrayEquals(new String[] { }, out_deps.keySet().toArray());
assertArrayEquals(new String[] {
"mock_android.widget.TableLayout",
}, keep.keySet().toArray());
} }
} }

View File

@ -19,16 +19,29 @@ package com.android.tools.layoutlib.create;
import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertTrue;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.TreeMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/** /**
* Unit tests for some methods of {@link AsmGenerator}. * Unit tests for some methods of {@link AsmGenerator}.
@ -40,6 +53,9 @@ public class AsmGeneratorTest {
private String mOsDestJar; private String mOsDestJar;
private File mTempFile; private File mTempFile;
// ASM internal name for the the class in java package that should be refactored.
private static final String JAVA_CLASS_NAME = "java/lang/JavaClass";
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
mLog = new MockLog(); mLog = new MockLog();
@ -48,7 +64,7 @@ public class AsmGeneratorTest {
mOsJarPath = new ArrayList<String>(); mOsJarPath = new ArrayList<String>();
mOsJarPath.add(url.getFile()); mOsJarPath.add(url.getFile());
mTempFile = File.createTempFile("mock", "jar"); mTempFile = File.createTempFile("mock", ".jar");
mOsDestJar = mTempFile.getAbsolutePath(); mOsDestJar = mTempFile.getAbsolutePath();
mTempFile.deleteOnExit(); mTempFile.deleteOnExit();
} }
@ -96,6 +112,11 @@ public class AsmGeneratorTest {
}; };
} }
@Override
public String[] getJavaPkgClasses() {
return new String[0];
}
@Override @Override
public String[] getDeleteReturns() { public String[] getDeleteReturns() {
// methods deleted from their return type. // methods deleted from their return type.
@ -109,11 +130,201 @@ public class AsmGeneratorTest {
null, // derived from null, // derived from
new String[] { // include classes new String[] { // include classes
"**" "**"
}); },
new HashSet<String>(0) /* excluded classes */);
aa.analyze(); aa.analyze();
agen.generate(); agen.generate();
Set<String> notRenamed = agen.getClassesNotRenamed(); Set<String> notRenamed = agen.getClassesNotRenamed();
assertArrayEquals(new String[] { "not/an/actual/ClassName" }, notRenamed.toArray()); assertArrayEquals(new String[] { "not/an/actual/ClassName" }, notRenamed.toArray());
}
@Test
public void testClassRefactoring() throws IOException, LogAbortException {
ICreateInfo ci = new ICreateInfo() {
@Override
public Class<?>[] getInjectedClasses() {
// classes to inject in the final JAR
return new Class<?>[] {
com.android.tools.layoutlib.create.dataclass.JavaClass.class
};
}
@Override
public String[] getDelegateMethods() {
return new String[0];
}
@Override
public String[] getDelegateClassNatives() {
return new String[0];
}
@Override
public String[] getOverriddenMethods() {
// methods to force override
return new String[0];
}
@Override
public String[] getRenamedClasses() {
// classes to rename (so that we can replace them)
return new String[0];
}
@Override
public String[] getJavaPkgClasses() {
// classes to refactor (so that we can replace them)
return new String[] {
"java.lang.JavaClass", "com.android.tools.layoutlib.create.dataclass.JavaClass",
};
}
@Override
public String[] getDeleteReturns() {
// methods deleted from their return type.
return new String[0];
}
};
AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar, ci);
AsmAnalyzer aa = new AsmAnalyzer(mLog, mOsJarPath, agen,
null, // derived from
new String[] { // include classes
"**"
},
new HashSet<String>(1));
aa.analyze();
agen.generate();
Map<String, ClassReader> output = parseZip(mOsDestJar);
boolean injectedClassFound = false;
for (ClassReader cr: output.values()) {
TestClassVisitor cv = new TestClassVisitor();
cr.accept(cv, 0);
injectedClassFound |= cv.mInjectedClassFound;
}
assertTrue(injectedClassFound);
}
private Map<String,ClassReader> parseZip(String jarPath) throws IOException {
TreeMap<String, ClassReader> classes = new TreeMap<String, ClassReader>();
ZipFile zip = new ZipFile(jarPath);
Enumeration<? extends ZipEntry> entries = zip.entries();
ZipEntry entry;
while (entries.hasMoreElements()) {
entry = entries.nextElement();
if (entry.getName().endsWith(".class")) {
ClassReader cr = new ClassReader(zip.getInputStream(entry));
String className = classReaderToClassName(cr);
classes.put(className, cr);
}
}
return classes;
}
private String classReaderToClassName(ClassReader classReader) {
if (classReader == null) {
return null;
} else {
return classReader.getClassName().replace('/', '.');
}
}
private class TestClassVisitor extends ClassVisitor {
boolean mInjectedClassFound = false;
TestClassVisitor() {
super(Opcodes.ASM4);
}
@Override
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
assertTrue(!getBase(name).equals(JAVA_CLASS_NAME));
if (name.equals("com/android/tools/layoutlib/create/dataclass/JavaClass")) {
mInjectedClassFound = true;
}
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public FieldVisitor visitField(int access, String name, String desc,
String signature, Object value) {
assertTrue(testType(Type.getType(desc)));
return super.visitField(access, name, desc, signature, value);
}
@SuppressWarnings("hiding")
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions);
return new MethodVisitor(Opcodes.ASM4, mv) {
@Override
public void visitFieldInsn(int opcode, String owner, String name,
String desc) {
assertTrue(!getBase(owner).equals(JAVA_CLASS_NAME));
assertTrue(testType(Type.getType(desc)));
super.visitFieldInsn(opcode, owner, name, desc);
}
@Override
public void visitLdcInsn(Object cst) {
if (cst instanceof Type) {
assertTrue(testType((Type)cst));
}
super.visitLdcInsn(cst);
}
@Override
public void visitTypeInsn(int opcode, String type) {
assertTrue(!getBase(type).equals(JAVA_CLASS_NAME));
super.visitTypeInsn(opcode, type);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name,
String desc) {
assertTrue(!getBase(owner).equals(JAVA_CLASS_NAME));
assertTrue(testType(Type.getType(desc)));
super.visitMethodInsn(opcode, owner, name, desc);
}
};
}
private boolean testType(Type type) {
int sort = type.getSort();
if (sort == Type.OBJECT) {
assertTrue(!getBase(type.getInternalName()).equals(JAVA_CLASS_NAME));
} else if (sort == Type.ARRAY) {
assertTrue(!getBase(type.getElementType().getInternalName())
.equals(JAVA_CLASS_NAME));
} else if (sort == Type.METHOD) {
boolean r = true;
for (Type t : type.getArgumentTypes()) {
r &= testType(t);
}
return r & testType(type.getReturnType());
}
return true;
}
private String getBase(String className) {
if (className == null) {
return null;
}
int pos = className.indexOf('$');
if (pos > 0) {
return className.substring(0, pos);
}
return className;
}
} }
} }

View File

@ -24,7 +24,7 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
/** /**
* *
*/ */
public class RenameClassAdapterTest { public class RenameClassAdapterTest {
@ -36,10 +36,10 @@ public class RenameClassAdapterTest {
mOuter = new RenameClassAdapter(null, // cv mOuter = new RenameClassAdapter(null, // cv
"com.pack.Old", "com.pack.Old",
"org.blah.New"); "org.blah.New");
mInner = new RenameClassAdapter(null, // cv mInner = new RenameClassAdapter(null, // cv
"com.pack.Old$Inner", "com.pack.Old$Inner",
"org.blah.New$Inner"); "org.blah.New$Inner");
} }
@After @After
@ -72,7 +72,7 @@ public class RenameClassAdapterTest {
// arrays // arrays
assertEquals("[Lorg.blah.New;", mOuter.renameTypeDesc("[Lcom.pack.Old;")); assertEquals("[Lorg.blah.New;", mOuter.renameTypeDesc("[Lcom.pack.Old;"));
assertEquals("[[Lorg.blah.New;", mOuter.renameTypeDesc("[[Lcom.pack.Old;")); assertEquals("[[Lorg.blah.New;", mOuter.renameTypeDesc("[[Lcom.pack.Old;"));
assertEquals("[Lorg.blah.New;", mInner.renameTypeDesc("[Lcom.pack.Old;")); assertEquals("[Lorg.blah.New;", mInner.renameTypeDesc("[Lcom.pack.Old;"));
assertEquals("[[Lorg.blah.New;", mInner.renameTypeDesc("[[Lcom.pack.Old;")); assertEquals("[[Lorg.blah.New;", mInner.renameTypeDesc("[[Lcom.pack.Old;"));
} }
@ -93,10 +93,6 @@ public class RenameClassAdapterTest {
*/ */
@Test @Test
public void testRenameInternalType() { public void testRenameInternalType() {
// a descriptor is not left untouched
assertEquals("Lorg.blah.New;", mOuter.renameInternalType("Lcom.pack.Old;"));
assertEquals("Lorg.blah.New$Inner;", mOuter.renameInternalType("Lcom.pack.Old$Inner;"));
// an actual FQCN // an actual FQCN
assertEquals("org.blah.New", mOuter.renameInternalType("com.pack.Old")); assertEquals("org.blah.New", mOuter.renameInternalType("com.pack.Old"));
assertEquals("org.blah.New$Inner", mOuter.renameInternalType("com.pack.Old$Inner")); assertEquals("org.blah.New$Inner", mOuter.renameInternalType("com.pack.Old$Inner"));
@ -115,6 +111,6 @@ public class RenameClassAdapterTest {
mOuter.renameMethodDesc("(IDLcom.pack.Old;[Lcom.pack.Old$Inner;)Lcom.pack.Old$Other;")); mOuter.renameMethodDesc("(IDLcom.pack.Old;[Lcom.pack.Old$Inner;)Lcom.pack.Old$Other;"));
} }
} }

View File

@ -0,0 +1,22 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
*
* 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.dataclass;
public final class JavaClass {
public static final String test = "test";
}

View File

@ -1,18 +0,0 @@
<?xml version="1.0" encoding="WINDOWS-1252" standalone="no"?>
<jardesc>
<jar path="C:/ralf/google/src/raphael-lapdroid/device/tools/layoutlib/create/tests/data/mock_android.jar"/>
<options buildIfNeeded="true" compress="true" descriptionLocation="/layoutlib_create/tests/data/mock_android.jardesc" exportErrors="true" exportWarnings="true" includeDirectoryEntries="false" overwrite="false" saveDescription="true" storeRefactorings="false" useSourceFolders="false"/>
<storedRefactorings deprecationInfo="true" structuralOnly="false"/>
<selectedProjects/>
<manifest generateManifest="true" manifestLocation="" manifestVersion="1.0" reuseManifest="false" saveManifest="false" usesManifest="true">
<sealing sealJar="false">
<packagesToSeal/>
<packagesToUnSeal/>
</sealing>
</manifest>
<selectedElements exportClassFiles="true" exportJavaFiles="false" exportOutputFolder="false">
<javaElement handleIdentifier="=layoutlib_create/tests&lt;mock_android.widget"/>
<javaElement handleIdentifier="=layoutlib_create/tests&lt;mock_android.view"/>
<javaElement handleIdentifier="=layoutlib_create/tests&lt;mock_android.dummy"/>
</selectedElements>
</jardesc>

View File

@ -0,0 +1,22 @@
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php
*
* 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 java.lang;
public class JavaClass {
public static String test = "test";
}

View File

@ -19,6 +19,7 @@ package mock_android.dummy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import java.util.List;
public class InnerTest { public class InnerTest {
@ -66,13 +67,13 @@ public class InnerTest {
} }
} }
public <X> void genericMethod1(X a, X[] a) { public <X> void genericMethod1(X a, X[] b) {
} }
public <X, Y> void genericMethod2(X a, List<Y> b) { public <X, Y> void genericMethod2(X a, List<Y> b) {
} }
public <X, Y> void genericMethod3(X a, List<Y extends InnerTest> b) { public <X, Y extends InnerTest> void genericMethod3(X a, List<Y> b) {
} }
public <T extends InnerTest> void genericMethod4(T[] a, Collection<T> b, Collection<?> c) { public <T extends InnerTest> void genericMethod4(T[] a, Collection<T> b, Collection<?> c) {
@ -85,6 +86,6 @@ public class InnerTest {
mInnerInstance = m; mInnerInstance = m;
mTheIntEnum = null; mTheIntEnum = null;
mGeneric1 = new MyGenerics1(); mGeneric1 = new MyGenerics1();
genericMethod(new DerivingClass[0], new ArrayList<DerivingClass>(), new ArrayList<InnerTest>()); genericMethod4(new DerivingClass[0], new ArrayList<DerivingClass>(), new ArrayList<InnerTest>());
} }
} }

View File

@ -16,6 +16,10 @@
package mock_android.view; package mock_android.view;
import java.lang.JavaClass;
public class View { public class View {
String x = JavaClass.test;
} }