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"?>
|
<?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>
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
|
@ -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.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
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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.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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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.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>());
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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;
|
||||||
|
|
||||||
}
|
}
|
Reference in New Issue
Block a user