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:
@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<classpath>
|
||||
<classpathentry kind="src" path="src"/>
|
||||
<classpathentry excluding="mock_android/" kind="src" path="tests"/>
|
||||
<classpathentry excluding="mock_data/" kind="src" path="tests"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
|
||||
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar"/>
|
||||
<classpathentry kind="var" path="ANDROID_SRC/prebuilts/tools/common/asm-tools/asm-4.0.jar" sourcepath="/ANDROID_PLAT/prebuilts/tools/common/asm-tools/src-4.0.zip"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
</classpath>
|
||||
|
@ -71,6 +71,9 @@ class names, for example "android.*.R**" ("*" does not matches dots whilst "**"
|
||||
and "." and "$" are interpreted as-is).
|
||||
In practice we almost but not quite request the inclusion of full packages.
|
||||
|
||||
The analyzer is also given a list of classes to exclude. A fake implementation of these
|
||||
classes is injected by the Generator.
|
||||
|
||||
With this information, the analyzer parses the input zip to find all the classes.
|
||||
All classes deriving from the requested bases classes are kept.
|
||||
All classes which name matched the glob pattern are kept.
|
||||
@ -93,6 +96,7 @@ and lists:
|
||||
- specific methods for which to delegate calls.
|
||||
- specific methods to remove based on their return type.
|
||||
- specific classes to rename.
|
||||
- specific classes to refactor.
|
||||
|
||||
Each of these are specific strategies we use to be able to modify the Android code
|
||||
to fit within the Eclipse renderer. These strategies are explained beow.
|
||||
@ -100,10 +104,7 @@ to fit within the Eclipse renderer. These strategies are explained beow.
|
||||
The core method of the generator is transform(): it takes an input ASM ClassReader
|
||||
and modifies it to produce a byte array suitable for the final JAR file.
|
||||
|
||||
The first step of the transformation is changing the name of the class in case
|
||||
we requested the class to be renamed. This uses the RenameClassAdapter to also rename
|
||||
all inner classes and references in methods and types. Note that other classes are
|
||||
not transformed and keep referencing the original name.
|
||||
The first step of the transformation is to implement the method delegates.
|
||||
|
||||
The TransformClassAdapter is then used to process the potentially renamed class.
|
||||
All protected or private classes are market as public.
|
||||
@ -115,11 +116,25 @@ Methods are also changed from protected/private to public.
|
||||
The code of the methods is then kept as-is, except for native methods which are
|
||||
replaced by a stub. Methods that are to be overridden are also replaced by a stub.
|
||||
|
||||
The transformed class is then fed through the DelegateClassAdapter to implement
|
||||
method delegates.
|
||||
|
||||
Finally fields are also visited and changed from protected/private to public.
|
||||
|
||||
The next step of the transformation is changing the name of the class in case
|
||||
we requested the class to be renamed. This uses the RenameClassAdapter to also rename
|
||||
all inner classes and references in methods and types. Note that other classes are
|
||||
not transformed and keep referencing the original name.
|
||||
|
||||
The class is then fed to RefactorClassAdapter which is like RenameClassAdapter but
|
||||
updates the references in all classes. This is used to update the references of classes
|
||||
in the java package that were added in the Dalvik VM but are not a part of the standard
|
||||
JVM. The existing classes are modified to update all references to these non-standard
|
||||
classes. An alternate implementation of these (com.android.tools.layoutlib.java.*) is
|
||||
injected.
|
||||
|
||||
The ClassAdapters are chained together to achieve the desired output. (Look at section
|
||||
2.2.7 Transformation chains in the asm user guide, link in the References.) The order of
|
||||
execution of these is:
|
||||
ClassReader -> [DelegateClassAdapter] -> TransformClassAdapter -> [RenameClassAdapter] ->
|
||||
RefactorClassAdapter -> ClassWriter
|
||||
|
||||
- Method stubs
|
||||
--------------
|
||||
@ -141,19 +156,27 @@ This strategy is now obsolete and replaced by the method delegates.
|
||||
- Strategies
|
||||
------------
|
||||
|
||||
We currently have 4 strategies to deal with overriding the rendering code
|
||||
We currently have 6 strategies to deal with overriding the rendering code
|
||||
and make it run in Eclipse. Most of these strategies are implemented hand-in-hand
|
||||
by the bridge (which runs in Eclipse) and the generator.
|
||||
|
||||
|
||||
1- Class Injection
|
||||
|
||||
This is the easiest: we currently inject 4 classes, namely:
|
||||
This is the easiest: we currently inject the following classes:
|
||||
- OverrideMethod and its associated MethodListener and MethodAdapter are used
|
||||
to intercept calls to some specific methods that are stubbed out and change
|
||||
their return value.
|
||||
- CreateInfo class, which configured the generator. Not used yet, but could
|
||||
in theory help us track what the generator changed.
|
||||
- AutoCloseable is part of Java 7. To enable us to still run on Java 6, a new class is
|
||||
injected. The implementation for the class has been taken from Android's libcore
|
||||
(platform/libcore/luni/src/main/java/java/lang/AutoCloseable.java).
|
||||
- Charsets, IntegralToString and UnsafeByteSequence are not part of the standard JAVA VM.
|
||||
They are added to the Dalvik VM for performance reasons. An implementation that is very
|
||||
close to the original (which is at platform/libcore/luni/src/main/java/...) is injected.
|
||||
Since these classees were in part of the java package, where we can't inject classes,
|
||||
all references to these have been updated (See strategy 4- Refactoring Classes).
|
||||
|
||||
|
||||
2- Overriding methods
|
||||
@ -189,7 +212,15 @@ we don't control object creation.
|
||||
This won't rename/replace the inner static methods of a given class.
|
||||
|
||||
|
||||
4- Method erasure based on return type
|
||||
4- Refactoring classes
|
||||
|
||||
This is very similar to the Renaming classes except that it also updates the reference in
|
||||
all classes. This is done for classes which are added to the Dalvik VM for performance
|
||||
reasons but are not present in the Standard Java VM. An implementation for these classes
|
||||
is also injected.
|
||||
|
||||
|
||||
5- Method erasure based on return type
|
||||
|
||||
This is mostly an implementation detail of the bridge: in the Paint class
|
||||
mentioned above, some inner static classes are used to pass around
|
||||
@ -201,7 +232,7 @@ example, the inner class Paint$Style in the Paint class should be discarded and
|
||||
bridge will provide its own implementation.
|
||||
|
||||
|
||||
5- Method Delegates
|
||||
6- Method Delegates
|
||||
|
||||
This strategy is used to override method implementations.
|
||||
Given a method SomeClass.MethodName(), 1 or 2 methods are generated:
|
||||
@ -233,7 +264,7 @@ Bytecode opcode list:
|
||||
http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings
|
||||
|
||||
ASM user guide:
|
||||
http://download.forge.objectweb.org/asm/asm-guide.pdf
|
||||
http://download.forge.objectweb.org/asm/asm4-guide.pdf
|
||||
|
||||
|
||||
--
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -34,6 +34,7 @@ import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.ZipEntry;
|
||||
@ -57,6 +58,8 @@ public class AsmAnalyzer {
|
||||
private final String[] mDeriveFrom;
|
||||
/** Glob patterns of classes to keep, e.g. "com.foo.*" */
|
||||
private final String[] mIncludeGlobs;
|
||||
/** The set of classes to exclude.*/
|
||||
private final Set<String> mExcludedClasses;
|
||||
|
||||
/**
|
||||
* Creates a new analyzer.
|
||||
@ -69,12 +72,13 @@ public class AsmAnalyzer {
|
||||
* ("*" does not matches dots whilst "**" does, "." and "$" are interpreted as-is)
|
||||
*/
|
||||
public AsmAnalyzer(Log log, List<String> osJarPath, AsmGenerator gen,
|
||||
String[] deriveFrom, String[] includeGlobs) {
|
||||
String[] deriveFrom, String[] includeGlobs, Set<String> excludeClasses) {
|
||||
mLog = log;
|
||||
mGen = gen;
|
||||
mOsSourceJar = osJarPath != null ? osJarPath : new ArrayList<String>();
|
||||
mDeriveFrom = deriveFrom != null ? deriveFrom : new String[0];
|
||||
mIncludeGlobs = includeGlobs != null ? includeGlobs : new String[0];
|
||||
mExcludedClasses = excludeClasses;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,9 +86,6 @@ public class AsmAnalyzer {
|
||||
* Fills the generator with classes & dependencies found.
|
||||
*/
|
||||
public void analyze() throws IOException, LogAbortException {
|
||||
|
||||
AsmAnalyzer visitor = this;
|
||||
|
||||
Map<String, ClassReader> zipClasses = parseZip(mOsSourceJar);
|
||||
mLog.info("Found %d classes in input JAR%s.", zipClasses.size(),
|
||||
mOsSourceJar.size() > 1 ? "s" : "");
|
||||
@ -232,7 +233,7 @@ public class AsmAnalyzer {
|
||||
*/
|
||||
void findClassesDerivingFrom(String super_name, Map<String, ClassReader> zipClasses,
|
||||
Map<String, ClassReader> inOutFound) throws LogAbortException {
|
||||
ClassReader super_clazz = findClass(super_name, zipClasses, inOutFound);
|
||||
findClass(super_name, zipClasses, inOutFound);
|
||||
|
||||
for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
|
||||
String className = entry.getKey();
|
||||
@ -363,11 +364,12 @@ public class AsmAnalyzer {
|
||||
|
||||
className = internalToBinaryClassName(className);
|
||||
|
||||
// exclude classes that have already been found
|
||||
// exclude classes that have already been found or are marked to be excluded
|
||||
if (mInKeep.containsKey(className) ||
|
||||
mOutKeep.containsKey(className) ||
|
||||
mInDeps.containsKey(className) ||
|
||||
mOutDeps.containsKey(className)) {
|
||||
mOutDeps.containsKey(className) ||
|
||||
mExcludedClasses.contains(getBaseName(className))) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -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
|
||||
@ -682,7 +691,7 @@ public class AsmAnalyzer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
|
||||
public void visitTableSwitchInsn(int min, int max, Label dflt, Label... labels) {
|
||||
// pass -- table switch instruction
|
||||
|
||||
}
|
||||
|
@ -65,6 +65,9 @@ public class AsmGenerator {
|
||||
/** A map { FQCN => set { method names } } of methods to rewrite as delegates.
|
||||
* The special name {@link DelegateClassAdapter#ALL_NATIVES} can be used as in internal set. */
|
||||
private final HashMap<String, Set<String>> mDelegateMethods;
|
||||
/** FQCN Names of classes to refactor. All reference to old-FQCN will be updated to new-FQCN.
|
||||
* map old-FQCN => new-FQCN */
|
||||
private final HashMap<String, String> mRefactorClasses;
|
||||
|
||||
/**
|
||||
* Creates a new generator that can generate the output JAR with the stubbed classes.
|
||||
@ -119,6 +122,17 @@ public class AsmGenerator {
|
||||
mClassesNotRenamed.add(oldFqcn);
|
||||
}
|
||||
|
||||
// Create a map of classes to be refactored.
|
||||
mRefactorClasses = new HashMap<String, String>();
|
||||
String[] refactorClasses = createInfo.getJavaPkgClasses();
|
||||
n = refactorClasses.length;
|
||||
for (int i = 0; i < n; i += 2) {
|
||||
assert i + 1 < n;
|
||||
String oldFqcn = binaryToInternalClassName(refactorClasses[i]);
|
||||
String newFqcn = binaryToInternalClassName(refactorClasses[i + 1]);
|
||||
mRefactorClasses.put(oldFqcn, newFqcn);;
|
||||
}
|
||||
|
||||
// create the map of renamed class -> return type of method to delete.
|
||||
mDeleteReturns = new HashMap<String, Set<String>>();
|
||||
String[] deleteReturns = createInfo.getDeleteReturns();
|
||||
@ -308,14 +322,14 @@ public class AsmGenerator {
|
||||
// original class reader.
|
||||
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
|
||||
|
||||
ClassVisitor rv = cw;
|
||||
ClassVisitor cv = new RefactorClassAdapter(cw, mRefactorClasses);
|
||||
if (newName != className) {
|
||||
rv = new RenameClassAdapter(cw, className, newName);
|
||||
cv = new RenameClassAdapter(cv, className, newName);
|
||||
}
|
||||
|
||||
ClassVisitor cv = new TransformClassAdapter(mLog, mStubMethods,
|
||||
cv = new TransformClassAdapter(mLog, mStubMethods,
|
||||
mDeleteReturns.get(className),
|
||||
newName, rv,
|
||||
newName, cv,
|
||||
stubNativesOnly, stubNativesOnly || hasNativeMethods);
|
||||
|
||||
Set<String> delegateMethods = mDelegateMethods.get(className);
|
||||
|
@ -17,6 +17,10 @@
|
||||
package com.android.tools.layoutlib.create;
|
||||
|
||||
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}.
|
||||
@ -84,6 +88,15 @@ public final class CreateInfo implements ICreateInfo {
|
||||
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,
|
||||
ICreateInfo.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",
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* The array contains a list of null terminated section starting with the name of the class
|
||||
|
@ -62,4 +62,11 @@ public interface ICreateInfo {
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ package com.android.tools.layoutlib.create;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@ -86,7 +87,9 @@ public class Main {
|
||||
}
|
||||
|
||||
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,
|
||||
new String[] { // derived from
|
||||
@ -110,7 +113,8 @@ public class Main {
|
||||
"android.pim.*", // for datepicker
|
||||
"android.os.*", // for android.os.Handler
|
||||
"android.database.ContentObserver", // for Digital clock
|
||||
});
|
||||
},
|
||||
excludeClasses);
|
||||
aa.analyze();
|
||||
agen.generate();
|
||||
|
||||
@ -146,6 +150,16 @@ public class Main {
|
||||
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) {
|
||||
DependencyFinder df = new DependencyFinder(log);
|
||||
try {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -16,17 +16,7 @@
|
||||
|
||||
package com.android.tools.layoutlib.create;
|
||||
|
||||
import org.objectweb.asm.AnnotationVisitor;
|
||||
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.
|
||||
@ -36,7 +26,7 @@ import org.objectweb.asm.signature.SignatureWriter;
|
||||
* For inner classes, this handles only the case where the outer class name changes.
|
||||
* The inner class name should remain the same.
|
||||
*/
|
||||
public class RenameClassAdapter extends ClassVisitor {
|
||||
public class RenameClassAdapter extends AbstractClassAdapter {
|
||||
|
||||
|
||||
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 names must be full qualified internal ASM names (e.g. com/blah/MyClass$InnerClass).
|
||||
*/
|
||||
public RenameClassAdapter(ClassWriter cv, String oldName, String newName) {
|
||||
super(Opcodes.ASM4, cv);
|
||||
public RenameClassAdapter(ClassVisitor cv, String oldName, String newName) {
|
||||
super(cv);
|
||||
mOldBase = mOldName = oldName;
|
||||
mNewBase = mNewName = newName;
|
||||
|
||||
@ -66,71 +56,6 @@ public class RenameClassAdapter extends ClassVisitor {
|
||||
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".
|
||||
* 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
|
||||
descriptor sometimes so descriptors are renamed too.
|
||||
*/
|
||||
String renameInternalType(String type) {
|
||||
@Override
|
||||
protected String renameInternalType(String type) {
|
||||
if (type == null) {
|
||||
return null;
|
||||
}
|
||||
@ -155,309 +81,7 @@ public class RenameClassAdapter extends ClassVisitor {
|
||||
if (pos == mOldBase.length() && type.startsWith(mOldBase)) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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; }
|
@ -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() {
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -31,7 +31,9 @@ import org.objectweb.asm.ClassReader;
|
||||
import java.io.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
@ -51,8 +53,10 @@ public class AsmAnalyzerTest {
|
||||
mOsJarPath = new ArrayList<String>();
|
||||
mOsJarPath.add(url.getFile());
|
||||
|
||||
Set<String> excludeClasses = new HashSet<String>(1);
|
||||
excludeClasses.add("java.lang.JavaClass");
|
||||
mAa = new AsmAnalyzer(mLog, mOsJarPath, null /* gen */,
|
||||
null /* deriveFrom */, null /* includeGlobs */ );
|
||||
null /* deriveFrom */, null /* includeGlobs */, excludeClasses);
|
||||
}
|
||||
|
||||
@After
|
||||
@ -64,6 +68,7 @@ public class AsmAnalyzerTest {
|
||||
Map<String, ClassReader> map = mAa.parseZip(mOsJarPath);
|
||||
|
||||
assertArrayEquals(new String[] {
|
||||
"java.lang.JavaClass",
|
||||
"mock_android.dummy.InnerTest",
|
||||
"mock_android.dummy.InnerTest$DerivingClass",
|
||||
"mock_android.dummy.InnerTest$MyGenerics1",
|
||||
@ -221,7 +226,11 @@ public class AsmAnalyzerTest {
|
||||
for (ClassReader cr2 : in_deps.values()) {
|
||||
cr2.accept(visitor, 0 /* flags */);
|
||||
}
|
||||
keep.putAll(new_keep);
|
||||
|
||||
assertArrayEquals(new String[] { }, out_deps.keySet().toArray());
|
||||
assertArrayEquals(new String[] {
|
||||
"mock_android.widget.TableLayout",
|
||||
}, keep.keySet().toArray());
|
||||
}
|
||||
}
|
||||
|
@ -19,16 +19,29 @@ package com.android.tools.layoutlib.create;
|
||||
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
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.IOException;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
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}.
|
||||
@ -40,6 +53,9 @@ public class AsmGeneratorTest {
|
||||
private String mOsDestJar;
|
||||
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
|
||||
public void setUp() throws Exception {
|
||||
mLog = new MockLog();
|
||||
@ -48,7 +64,7 @@ public class AsmGeneratorTest {
|
||||
mOsJarPath = new ArrayList<String>();
|
||||
mOsJarPath.add(url.getFile());
|
||||
|
||||
mTempFile = File.createTempFile("mock", "jar");
|
||||
mTempFile = File.createTempFile("mock", ".jar");
|
||||
mOsDestJar = mTempFile.getAbsolutePath();
|
||||
mTempFile.deleteOnExit();
|
||||
}
|
||||
@ -96,6 +112,11 @@ public class AsmGeneratorTest {
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getJavaPkgClasses() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getDeleteReturns() {
|
||||
// methods deleted from their return type.
|
||||
@ -109,11 +130,201 @@ public class AsmGeneratorTest {
|
||||
null, // derived from
|
||||
new String[] { // include classes
|
||||
"**"
|
||||
});
|
||||
},
|
||||
new HashSet<String>(0) /* excluded classes */);
|
||||
aa.analyze();
|
||||
agen.generate();
|
||||
|
||||
Set<String> notRenamed = agen.getClassesNotRenamed();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,7 @@ import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
public class RenameClassAdapterTest {
|
||||
|
||||
@ -36,10 +36,10 @@ public class RenameClassAdapterTest {
|
||||
mOuter = new RenameClassAdapter(null, // cv
|
||||
"com.pack.Old",
|
||||
"org.blah.New");
|
||||
|
||||
|
||||
mInner = new RenameClassAdapter(null, // cv
|
||||
"com.pack.Old$Inner",
|
||||
"org.blah.New$Inner");
|
||||
"org.blah.New$Inner");
|
||||
}
|
||||
|
||||
@After
|
||||
@ -72,7 +72,7 @@ public class RenameClassAdapterTest {
|
||||
// arrays
|
||||
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;"));
|
||||
}
|
||||
@ -93,10 +93,6 @@ public class RenameClassAdapterTest {
|
||||
*/
|
||||
@Test
|
||||
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
|
||||
assertEquals("org.blah.New", mOuter.renameInternalType("com.pack.Old"));
|
||||
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;"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
Binary file not shown.
@ -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<mock_android.widget"/>
|
||||
<javaElement handleIdentifier="=layoutlib_create/tests<mock_android.view"/>
|
||||
<javaElement handleIdentifier="=layoutlib_create/tests<mock_android.dummy"/>
|
||||
</selectedElements>
|
||||
</jardesc>
|
@ -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";
|
||||
}
|
@ -19,6 +19,7 @@ package mock_android.dummy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
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 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) {
|
||||
@ -85,6 +86,6 @@ public class InnerTest {
|
||||
mInnerInstance = m;
|
||||
mTheIntEnum = null;
|
||||
mGeneric1 = new MyGenerics1();
|
||||
genericMethod(new DerivingClass[0], new ArrayList<DerivingClass>(), new ArrayList<InnerTest>());
|
||||
genericMethod4(new DerivingClass[0], new ArrayList<DerivingClass>(), new ArrayList<InnerTest>());
|
||||
}
|
||||
}
|
@ -16,6 +16,10 @@
|
||||
|
||||
package mock_android.view;
|
||||
|
||||
import java.lang.JavaClass;
|
||||
|
||||
public class View {
|
||||
|
||||
String x = JavaClass.test;
|
||||
|
||||
}
|
Reference in New Issue
Block a user