M7350v1_en_gpl

This commit is contained in:
T
2024-09-09 08:52:07 +00:00
commit f9cc65cfda
65988 changed files with 26357421 additions and 0 deletions

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry excluding="mock_android/" kind="src" path="tests"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
<classpathentry kind="var" path="ANDROID_SRC/prebuilt/common/asm/asm-3.1.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>layoutlib_create</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,28 @@
#
# Copyright (C) 2008 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.
#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := $(call all-java-files-under,src)
LOCAL_JAR_MANIFEST := manifest.txt
LOCAL_STATIC_JAVA_LIBRARIES := \
asm-3.1
LOCAL_MODULE := layoutlib_create
include $(BUILD_HOST_JAVA_LIBRARY)

View File

@ -0,0 +1,216 @@
# Copyright (C) 2008 The Android Open Source Project
- Description -
---------------
Layoutlib_create generates a JAR library used by the Eclipse graphical layout editor
to perform layout.
- Usage -
---------
./layoutlib_create path/to/android.jar destination.jar
- Design Overview -
-------------------
Layoutlib_create uses the "android.jar" containing all the Java code used by Android
as generated by the Android build, right before the classes are converted to a DEX format.
The Android JAR can't be used directly in Eclipse:
- it contains references to native code (which we want to avoid in Eclipse),
- some classes need to be overridden, for example all the drawing code that is
replaced by Java 2D calls in Eclipse.
- some of the classes that need to be changed are final and/or we need access
to their private internal state.
Consequently this tool:
- parses the input JAR,
- modifies some of the classes directly using some bytecode manipulation,
- filters some packages and removes some that we don't want to end in the output JAR,
- injects some new classes,
- and generates a modified JAR file that is suitable for the Android plugin
for Eclipse to perform rendering.
The ASM library is used to do the bytecode modification using its visitor pattern API.
The layoutlib_create is *NOT* generic. There is no configuration file. Instead all the
configuration is done in the main() method and the CreateInfo structure is expected to
change with the Android platform as new classes are added, changed or removed.
The resulting JAR is used by layoutlib_bridge (a.k.a. "the bridge"), also part of the
platform, that provides all the necessary missing implementation for rendering graphics
in Eclipse.
- Implementation Notes -
------------------------
The tool works in two phases:
- first analyze the input jar (AsmAnalyzer class)
- then generate the output jar (AsmGenerator class),
- Analyzer
----------
The goal of the analyzer is to create a graph of all the classes from the input JAR
with their dependencies and then only keep the ones we want.
To do that, the analyzer is created with a list of base classes to keep -- everything
that derives from these is kept. Currently the one such class is android.view.View:
since we want to render layouts, anything that is sort of the view needs to be kept.
The analyzer is also given a list of class names to keep in the output.
This is done using shell-like glob patterns that filter on the fully-qualified
class names, for example "android.*.R**" ("*" does not matches dots whilst "**" does,
and "." and "$" are interpreted as-is).
In practice we almost but not quite request the inclusion of full packages.
With this information, the analyzer parses the input zip to find all the classes.
All classes deriving from the requested bases classes are kept.
All classes which name matched the glob pattern are kept.
The analysis then finds all the dependencies of the classes that are to be kept
using an ASM visitor on the class, the field types, the method types and annotations types.
Classes that belong to the current JRE are excluded.
The output of the analyzer is a set of ASM ClassReader instances which are then
fed to the generator.
- Generator
-----------
The generator is constructed from a CreateInfo struct that acts as a config file
and lists:
- the classes to inject in the output JAR -- these classes are directly implemented
in layoutlib_create and will be used to interface with the renderer in Eclipse.
- specific methods to override (see method stubs details below).
- specific methods to remove based on their return type.
- specific classes to rename.
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.
The core method of the generator is transform(): it takes an input ASM ClassReader
and modifies it to produce a byte array suitable for the final JAR file.
The first step of the transformation is changing the name of the class in case
we requested the class to be renamed. This uses the RenameClassAdapter to also rename
all inner classes and references in methods and types. Note that other classes are
not transformed and keep referencing the original name.
The TransformClassAdapter is then used to process the potentially renamed class.
All protected or private classes are market as public.
All classes are made non-final.
Interfaces are left as-is.
If a method has a return type that must be erased, the whole method is skipped.
Methods are also changed from protected/private to public.
The code of the methods is then kept as-is, except for native methods which are
replaced by a stub. Methods that are to be overridden are also replaced by a stub.
Finally fields are also visited and changed from protected/private to public.
- Method stubs
--------------
As indicated above, all native and overridden methods are replaced by a stub.
We don't have the code to replace with in layoutlib_create.
Instead the StubMethodAdapter replaces the code of the method by a call to
OverrideMethod.invokeX(). When using the final JAR, the bridge can register
listeners from these overridden method calls based on the method signatures.
The listeners are currently pretty basic: we only pass the signature of the
method being called, its caller object and a flag indicating whether the
method was native. We do not currently provide the parameters. The listener
can however specify the return value of the overridden method.
An extension being worked on is to actually replace these listeners by
direct calls to a delegate class, complete with parameters.
- Strategies
------------
We currently have 4 strategies to deal with overriding the rendering code
and make it run in Eclipse. Most of these strategies are implemented hand-in-hand
by the bridge (which runs in Eclipse) and the generator.
1- Class Injection
This is the easiest: we currently inject 4 classes, namely:
- OverrideMethod and its associated MethodListener and MethodAdapter are used
to intercept calls to some specific methods that are stubbed out and change
their return value.
- CreateInfo class, which configured the generator. Not used yet, but could
in theory help us track what the generator changed.
2- Overriding methods
As explained earlier, the creator doesn't have any replacement code for
methods to override. Instead it removes the original code and replaces it
by a call to a specific OveriddeMethod.invokeX(). The bridge then registers
a listener on the method signature and can provide an implementation.
3- Renaming classes
This simply changes the name of a class in its definition, as well as all its
references in internal inner classes and methods.
Calls from other classes are not modified -- they keep referencing the original
class name. This allows the bridge to literally replace an implementation.
An example will make this easier: android.graphics.Paint is the main drawing
class that we need to replace. To do so, the generator renames Paint to _original_Paint.
Later the bridge provides its own replacement version of Paint which will be used
by the rest of the Android stack. The replacement version of Paint can still use
(either by inheritance or delegation) all the original non-native code of _original_Paint
if it so desires.
Some of the Android classes are basically wrappers over native objects and since
we don't have the native code in Eclipse, we need to provide a full alternate
implementation. Sub-classing doesn't work as some native methods are static and
we don't control object creation.
This won't rename/replace the inner static methods of a given class.
4- Method erasure based on return type
This is mostly an implementation detail of the bridge: in the Paint class
mentioned above, some inner static classes are used to pass around
attributes (e.g. FontMetrics, or the Style enum) and all the original implementation
is native.
In this case we have a strategy that tells the generator that anything returning, for
example, the inner class Paint$Style in the Paint class should be discarded and the
bridge will provide its own implementation.
- References -
--------------
The JVM Specification 2nd edition:
http://java.sun.com/docs/books/jvms/second_edition/html/VMSpecTOC.doc.html
Understanding bytecode:
http://www.ibm.com/developerworks/ibm/library/it-haggar_bytecode/
Bytecode opcode list:
http://en.wikipedia.org/wiki/Java_bytecode_instruction_listings
ASM user guide:
http://download.forge.objectweb.org/asm/asm-guide.pdf
--
end

View File

@ -0,0 +1 @@
Main-Class: com.android.tools.layoutlib.create.Main

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2010 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.annotations;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Denotes a method that has been converted to a delegate by layoutlib_create.
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface LayoutlibDelegate {
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (C) 2010 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.annotations;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Denotes a parameter or field can be null.
* <p/>
* When decorating a method call parameter, this denotes the parameter can
* legitimately be null and the method will gracefully deal with it. Typically used
* on optional parameters.
* <p/>
* When decorating a method, this denotes the method might legitimately return null.
* <p/>
* This is a marker annotation and it has no specific attributes.
*/
@Retention(RetentionPolicy.SOURCE)
public @interface Nullable {
}

View File

@ -0,0 +1,50 @@
/*
* Copyright (C) 2010 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.annotations;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Denotes that the class, method or field has its visibility relaxed so
* that unit tests can access it.
* <p/>
* The <code>visibility</code> argument can be used to specific what the original
* visibility should have been if it had not been made public or package-private for testing.
* The default is to consider the element private.
*/
@Retention(RetentionPolicy.SOURCE)
public @interface VisibleForTesting {
/**
* Intended visibility if the element had not been made public or package-private for
* testing.
*/
enum Visibility {
/** The element should be considered protected. */
PROTECTED,
/** The element should be considered package-private. */
PACKAGE,
/** The element should be considered private. */
PRIVATE
}
/**
* Intended visibility if the element had not been made public or package-private for testing.
* If not specified, one should assume the element originally intended to be private.
*/
Visibility visibility() default Visibility.PRIVATE;
}

View File

@ -0,0 +1,751 @@
/*
* Copyright (C) 2008 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.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
/**
* Analyzes the input JAR using the ASM java bytecode manipulation library
* to list the desired classes and their dependencies.
*/
public class AsmAnalyzer {
// Note: a bunch of stuff has package-level access for unit tests. Consider it private.
/** Output logger. */
private final Log mLog;
/** The input source JAR to parse. */
private final List<String> mOsSourceJar;
/** The generator to fill with the class list and dependency list. */
private final AsmGenerator mGen;
/** Keep all classes that derive from these one (these included). */
private final String[] mDeriveFrom;
/** Glob patterns of classes to keep, e.g. "com.foo.*" */
private final String[] mIncludeGlobs;
/**
* Creates a new analyzer.
*
* @param log The log output.
* @param osJarPath The input source JARs to parse.
* @param gen The generator to fill with the class list and dependency list.
* @param deriveFrom Keep all classes that derive from these one (these included).
* @param includeGlobs Glob patterns of classes to keep, e.g. "com.foo.*"
* ("*" does not matches dots whilst "**" does, "." and "$" are interpreted as-is)
*/
public AsmAnalyzer(Log log, List<String> osJarPath, AsmGenerator gen,
String[] deriveFrom, String[] includeGlobs) {
mLog = log;
mGen = gen;
mOsSourceJar = osJarPath != null ? osJarPath : new ArrayList<String>();
mDeriveFrom = deriveFrom != null ? deriveFrom : new String[0];
mIncludeGlobs = includeGlobs != null ? includeGlobs : new String[0];
}
/**
* Starts the analysis using parameters from the constructor.
* Fills the generator with classes & dependencies found.
*/
public void analyze() throws IOException, LogAbortException {
AsmAnalyzer visitor = this;
Map<String, ClassReader> zipClasses = parseZip(mOsSourceJar);
mLog.info("Found %d classes in input JAR%s.", zipClasses.size(),
mOsSourceJar.size() > 1 ? "s" : "");
Map<String, ClassReader> found = findIncludes(zipClasses);
Map<String, ClassReader> deps = findDeps(zipClasses, found);
if (mGen != null) {
mGen.setKeep(found);
mGen.setDeps(deps);
}
}
/**
* Parses a JAR file and returns a list of all classes founds using a map
* class name => ASM ClassReader. Class names are in the form "android.view.View".
*/
Map<String,ClassReader> parseZip(List<String> jarPathList) throws IOException {
TreeMap<String, ClassReader> classes = new TreeMap<String, ClassReader>();
for (String jarPath : jarPathList) {
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;
}
/**
* Utility that returns the fully qualified binary class name for a ClassReader.
* E.g. it returns something like android.view.View.
*/
static String classReaderToClassName(ClassReader classReader) {
if (classReader == null) {
return null;
} else {
return classReader.getClassName().replace('/', '.');
}
}
/**
* Utility that returns the fully qualified binary class name from a path-like FQCN.
* E.g. it returns android.view.View from android/view/View.
*/
static String internalToBinaryClassName(String className) {
if (className == null) {
return null;
} else {
return className.replace('/', '.');
}
}
/**
* Process the "includes" arrays.
* <p/>
* This updates the in_out_found map.
*/
Map<String, ClassReader> findIncludes(Map<String, ClassReader> zipClasses)
throws LogAbortException {
TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
mLog.debug("Find classes to include.");
for (String s : mIncludeGlobs) {
findGlobs(s, zipClasses, found);
}
for (String s : mDeriveFrom) {
findClassesDerivingFrom(s, zipClasses, found);
}
return found;
}
/**
* Uses ASM to find the class reader for the given FQCN class name.
* If found, insert it in the in_out_found map.
* Returns the class reader object.
*/
ClassReader findClass(String className, Map<String, ClassReader> zipClasses,
Map<String, ClassReader> inOutFound) throws LogAbortException {
ClassReader classReader = zipClasses.get(className);
if (classReader == null) {
throw new LogAbortException("Class %s not found by ASM in %s",
className, mOsSourceJar);
}
inOutFound.put(className, classReader);
return classReader;
}
/**
* Insert in the inOutFound map all classes found in zipClasses that match the
* given glob pattern.
* <p/>
* The glob pattern is not a regexp. It only accepts the "*" keyword to mean
* "anything but a period". The "." and "$" characters match themselves.
* The "**" keyword means everything including ".".
* <p/>
* Examples:
* <ul>
* <li>com.foo.* matches all classes in the package com.foo but NOT sub-packages.
* <li>com.foo*.*$Event matches all internal Event classes in a com.foo*.* class.
* </ul>
*/
void findGlobs(String globPattern, Map<String, ClassReader> zipClasses,
Map<String, ClassReader> inOutFound) throws LogAbortException {
// transforms the glob pattern in a regexp:
// - escape "." with "\."
// - replace "*" by "[^.]*"
// - escape "$" with "\$"
// - add end-of-line match $
globPattern = globPattern.replaceAll("\\$", "\\\\\\$");
globPattern = globPattern.replaceAll("\\.", "\\\\.");
// prevent ** from being altered by the next rule, then process the * rule and finally
// the real ** rule (which is now @)
globPattern = globPattern.replaceAll("\\*\\*", "@");
globPattern = globPattern.replaceAll("\\*", "[^.]*");
globPattern = globPattern.replaceAll("@", ".*");
globPattern += "$";
Pattern regexp = Pattern.compile(globPattern);
for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
String class_name = entry.getKey();
if (regexp.matcher(class_name).matches()) {
findClass(class_name, zipClasses, inOutFound);
}
}
}
/**
* Checks all the classes defined in the JarClassName instance and uses BCEL to
* determine if they are derived from the given FQCN super class name.
* Inserts the super class and all the class objects found in the map.
*/
void findClassesDerivingFrom(String super_name, Map<String, ClassReader> zipClasses,
Map<String, ClassReader> inOutFound) throws LogAbortException {
ClassReader super_clazz = findClass(super_name, zipClasses, inOutFound);
for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
String className = entry.getKey();
if (super_name.equals(className)) {
continue;
}
ClassReader classReader = entry.getValue();
ClassReader parent_cr = classReader;
while (parent_cr != null) {
String parent_name = internalToBinaryClassName(parent_cr.getSuperName());
if (parent_name == null) {
// not found
break;
} else if (super_name.equals(parent_name)) {
inOutFound.put(className, classReader);
break;
}
parent_cr = zipClasses.get(parent_name);
}
}
}
/**
* Instantiates a new DependencyVisitor. Useful for unit tests.
*/
DependencyVisitor getVisitor(Map<String, ClassReader> zipClasses,
Map<String, ClassReader> inKeep,
Map<String, ClassReader> outKeep,
Map<String, ClassReader> inDeps,
Map<String, ClassReader> outDeps) {
return new DependencyVisitor(zipClasses, inKeep, outKeep, inDeps, outDeps);
}
/**
* Finds all dependencies for all classes in keepClasses which are also
* listed in zipClasses. Returns a map of all the dependencies found.
*/
Map<String, ClassReader> findDeps(Map<String, ClassReader> zipClasses,
Map<String, ClassReader> inOutKeepClasses) {
TreeMap<String, ClassReader> deps = new TreeMap<String, ClassReader>();
TreeMap<String, ClassReader> new_deps = new TreeMap<String, ClassReader>();
TreeMap<String, ClassReader> new_keep = new TreeMap<String, ClassReader>();
TreeMap<String, ClassReader> temp = new TreeMap<String, ClassReader>();
DependencyVisitor visitor = getVisitor(zipClasses,
inOutKeepClasses, new_keep,
deps, new_deps);
for (ClassReader cr : inOutKeepClasses.values()) {
cr.accept(visitor, 0 /* flags */);
}
while (new_deps.size() > 0 || new_keep.size() > 0) {
deps.putAll(new_deps);
inOutKeepClasses.putAll(new_keep);
temp.clear();
temp.putAll(new_deps);
temp.putAll(new_keep);
new_deps.clear();
new_keep.clear();
mLog.debug("Found %1$d to keep, %2$d dependencies.",
inOutKeepClasses.size(), deps.size());
for (ClassReader cr : temp.values()) {
cr.accept(visitor, 0 /* flags */);
}
}
mLog.info("Found %1$d classes to keep, %2$d class dependencies.",
inOutKeepClasses.size(), deps.size());
return deps;
}
// ----------------------------------
/**
* Visitor to collect all the type dependencies from a class.
*/
public class DependencyVisitor
implements ClassVisitor, FieldVisitor, MethodVisitor, SignatureVisitor, AnnotationVisitor {
/** All classes found in the source JAR. */
private final Map<String, ClassReader> mZipClasses;
/** Classes from which dependencies are to be found. */
private final Map<String, ClassReader> mInKeep;
/** Dependencies already known. */
private final Map<String, ClassReader> mInDeps;
/** New dependencies found by this visitor. */
private final Map<String, ClassReader> mOutDeps;
/** New classes to keep as-is found by this visitor. */
private final Map<String, ClassReader> mOutKeep;
/**
* Creates a new visitor that will find all the dependencies for the visited class.
* Types which are already in the zipClasses, keepClasses or inDeps are not marked.
* New dependencies are marked in outDeps.
*
* @param zipClasses All classes found in the source JAR.
* @param inKeep Classes from which dependencies are to be found.
* @param inDeps Dependencies already known.
* @param outDeps New dependencies found by this visitor.
*/
public DependencyVisitor(Map<String, ClassReader> zipClasses,
Map<String, ClassReader> inKeep,
Map<String, ClassReader> outKeep,
Map<String,ClassReader> inDeps,
Map<String,ClassReader> outDeps) {
mZipClasses = zipClasses;
mInKeep = inKeep;
mOutKeep = outKeep;
mInDeps = inDeps;
mOutDeps = outDeps;
}
/**
* Considers the given class name as a dependency.
* If it does, add to the mOutDeps map.
*/
public void considerName(String className) {
if (className == null) {
return;
}
className = internalToBinaryClassName(className);
// exclude classes that have already been found
if (mInKeep.containsKey(className) ||
mOutKeep.containsKey(className) ||
mInDeps.containsKey(className) ||
mOutDeps.containsKey(className)) {
return;
}
// exclude classes that are not part of the JAR file being examined
ClassReader cr = mZipClasses.get(className);
if (cr == null) {
return;
}
try {
// exclude classes that are part of the default JRE (the one executing this program)
if (getClass().getClassLoader().loadClass(className) != null) {
return;
}
} catch (ClassNotFoundException e) {
// ignore
}
// accept this class:
// - android classes are added to dependencies
// - non-android classes are added to the list of classes to keep as-is (they don't need
// to be stubbed).
if (className.indexOf("android") >= 0) { // TODO make configurable
mOutDeps.put(className, cr);
} else {
mOutKeep.put(className, cr);
}
}
/**
* Considers this array of names using considerName().
*/
public void considerNames(String[] classNames) {
if (classNames != null) {
for (String className : classNames) {
considerName(className);
}
}
}
/**
* Considers this signature or type signature by invoking the {@link SignatureVisitor}
* on it.
*/
public void considerSignature(String signature) {
if (signature != null) {
SignatureReader sr = new SignatureReader(signature);
// SignatureReader.accept will call accessType so we don't really have
// to differentiate where the signature comes from.
sr.accept(this);
}
}
/**
* Considers this {@link Type}. For arrays, the element type is considered.
* If the type is an object, it's internal name is considered.
*/
public void considerType(Type t) {
if (t != null) {
if (t.getSort() == Type.ARRAY) {
t = t.getElementType();
}
if (t.getSort() == Type.OBJECT) {
considerName(t.getInternalName());
}
}
}
/**
* Considers a descriptor string. The descriptor is converted to a {@link Type}
* and then considerType() is invoked.
*/
public void considerDesc(String desc) {
if (desc != null) {
try {
Type t = Type.getType(desc);
considerType(t);
} catch (ArrayIndexOutOfBoundsException e) {
// ignore, not a valid type.
}
}
}
// ---------------------------------------------------
// --- ClassVisitor, FieldVisitor
// ---------------------------------------------------
// Visits a class header
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
// signature is the signature of this class. May be null if the class is not a generic
// one, and does not extend or implement generic classes or interfaces.
if (signature != null) {
considerSignature(signature);
}
// superName is the internal of name of the super class (see getInternalName).
// For interfaces, the super class is Object. May be null but only for the Object class.
considerName(superName);
// interfaces is the internal names of the class's interfaces (see getInternalName).
// May be null.
considerNames(interfaces);
}
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
// desc is the class descriptor of the annotation class.
considerDesc(desc);
return this; // return this to visit annotion values
}
public void visitAttribute(Attribute attr) {
// pass
}
// Visits the end of a class
public void visitEnd() {
// pass
}
public FieldVisitor visitField(int access, String name, String desc,
String signature, Object value) {
// desc is the field's descriptor (see Type).
considerDesc(desc);
// signature is the field's signature. May be null if the field's type does not use
// generic types.
considerSignature(signature);
return this; // a visitor to visit field annotations and attributes
}
public void visitInnerClass(String name, String outerName, String innerName, int access) {
// name is the internal name of an inner class (see getInternalName).
considerName(name);
}
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
// desc is the method's descriptor (see Type).
considerDesc(desc);
// signature is the method's signature. May be null if the method parameters, return
// type and exceptions do not use generic types.
considerSignature(signature);
return this; // returns this to visit the method
}
public void visitOuterClass(String owner, String name, String desc) {
// pass
}
public void visitSource(String source, String debug) {
// pass
}
// ---------------------------------------------------
// --- MethodVisitor
// ---------------------------------------------------
public AnnotationVisitor visitAnnotationDefault() {
return this; // returns this to visit the default value
}
public void visitCode() {
// pass
}
// field instruction
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
// name is the field's name.
considerName(name);
// desc is the field's descriptor (see Type).
considerDesc(desc);
}
public void visitFrame(int type, int local, Object[] local2, int stack, Object[] stack2) {
// pass
}
public void visitIincInsn(int var, int increment) {
// pass -- an IINC instruction
}
public void visitInsn(int opcode) {
// pass -- a zero operand instruction
}
public void visitIntInsn(int opcode, int operand) {
// pass -- a single int operand instruction
}
public void visitJumpInsn(int opcode, Label label) {
// pass -- a jump instruction
}
public void visitLabel(Label label) {
// pass -- a label target
}
// instruction to load a constant from the stack
public void visitLdcInsn(Object cst) {
if (cst instanceof Type) {
considerType((Type) cst);
}
}
public void visitLineNumber(int line, Label start) {
// pass
}
public void visitLocalVariable(String name, String desc,
String signature, Label start, Label end, int index) {
// desc is the type descriptor of this local variable.
considerDesc(desc);
// signature is the type signature of this local variable. May be null if the local
// variable type does not use generic types.
considerSignature(signature);
}
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
// pass -- a lookup switch instruction
}
public void visitMaxs(int maxStack, int maxLocals) {
// pass
}
// instruction that invokes a method
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
// owner is the internal name of the method's owner class
considerName(owner);
// desc is the method's descriptor (see Type).
considerDesc(desc);
}
// instruction multianewarray, whatever that is
public void visitMultiANewArrayInsn(String desc, int dims) {
// desc an array type descriptor.
considerDesc(desc);
}
public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
boolean visible) {
// desc is the class descriptor of the annotation class.
considerDesc(desc);
return this; // return this to visit annotation values
}
public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
// pass -- table switch instruction
}
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
// type is the internal name of the type of exceptions handled by the handler,
// or null to catch any exceptions (for "finally" blocks).
considerName(type);
}
// type instruction
public void visitTypeInsn(int opcode, String type) {
// type is the operand of the instruction to be visited. This operand must be the
// internal name of an object or array class.
considerName(type);
}
public void visitVarInsn(int opcode, int var) {
// pass -- local variable instruction
}
// ---------------------------------------------------
// --- SignatureVisitor
// ---------------------------------------------------
private String mCurrentSignatureClass = null;
// Starts the visit of a signature corresponding to a class or interface type
public void visitClassType(String name) {
mCurrentSignatureClass = name;
considerName(name);
}
// Visits an inner class
public void visitInnerClassType(String name) {
if (mCurrentSignatureClass != null) {
mCurrentSignatureClass += "$" + name;
considerName(mCurrentSignatureClass);
}
}
public SignatureVisitor visitArrayType() {
return this; // returns this to visit the signature of the array element type
}
public void visitBaseType(char descriptor) {
// pass -- a primitive type, ignored
}
public SignatureVisitor visitClassBound() {
return this; // returns this to visit the signature of the class bound
}
public SignatureVisitor visitExceptionType() {
return this; // return this to visit the signature of the exception type.
}
public void visitFormalTypeParameter(String name) {
// pass
}
public SignatureVisitor visitInterface() {
return this; // returns this to visit the signature of the interface type
}
public SignatureVisitor visitInterfaceBound() {
return this; // returns this to visit the signature of the interface bound
}
public SignatureVisitor visitParameterType() {
return this; // returns this to visit the signature of the parameter type
}
public SignatureVisitor visitReturnType() {
return this; // returns this to visit the signature of the return type
}
public SignatureVisitor visitSuperclass() {
return this; // returns this to visit the signature of the super class type
}
public SignatureVisitor visitTypeArgument(char wildcard) {
return this; // returns this to visit the signature of the type argument
}
public void visitTypeVariable(String name) {
// pass
}
public void visitTypeArgument() {
// pass
}
// ---------------------------------------------------
// --- AnnotationVisitor
// ---------------------------------------------------
// Visits a primitive value of an annotation
public void visit(String name, Object value) {
// value is the actual value, whose type must be Byte, Boolean, Character, Short,
// Integer, Long, Float, Double, String or Type
if (value instanceof Type) {
considerType((Type) value);
}
}
public AnnotationVisitor visitAnnotation(String name, String desc) {
// desc is the class descriptor of the nested annotation class.
considerDesc(desc);
return this; // returns this to visit the actual nested annotation value
}
public AnnotationVisitor visitArray(String name) {
return this; // returns this to visit the actual array value elements
}
public void visitEnum(String name, String desc, String value) {
// desc is the class descriptor of the enumeration class.
considerDesc(desc);
}
}
}

View File

@ -0,0 +1,370 @@
/*
* Copyright (C) 2008 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.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream;
/**
* Class that generates a new JAR from a list of classes, some of which are to be kept as-is
* and some of which are to be stubbed partially or totally.
*/
public class AsmGenerator {
/** Output logger. */
private final Log mLog;
/** The path of the destination JAR to create. */
private final String mOsDestJar;
/** List of classes to inject in the final JAR from _this_ archive. */
private final Class<?>[] mInjectClasses;
/** The set of methods to stub out. */
private final Set<String> mStubMethods;
/** All classes to output as-is, except if they have native methods. */
private Map<String, ClassReader> mKeep;
/** All dependencies that must be completely stubbed. */
private Map<String, ClassReader> mDeps;
/** Counter of number of classes renamed during transform. */
private int mRenameCount;
/** FQCN Names of the classes to rename: map old-FQCN => new-FQCN */
private final HashMap<String, String> mRenameClasses;
/** FQCN Names of "old" classes that were NOT renamed. This starts with the full list of
* old-FQCN to rename and they get erased as they get renamed. At the end, classes still
* left here are not in the code base anymore and thus were not renamed. */
private HashSet<String> mClassesNotRenamed;
/** A map { FQCN => set { list of return types to delete from the FQCN } }. */
private HashMap<String, Set<String>> mDeleteReturns;
/** A map { FQCN => set { method names } } of methods to rewrite as delegates.
* The special name {@link DelegateClassAdapter#ALL_NATIVES} can be used as in internal set. */
private final HashMap<String, Set<String>> mDelegateMethods;
/**
* Creates a new generator that can generate the output JAR with the stubbed classes.
*
* @param log Output logger.
* @param osDestJar The path of the destination JAR to create.
* @param createInfo Creation parameters. Must not be null.
*/
public AsmGenerator(Log log, String osDestJar, ICreateInfo createInfo) {
mLog = log;
mOsDestJar = osDestJar;
mInjectClasses = createInfo.getInjectedClasses();
mStubMethods = new HashSet<String>(Arrays.asList(createInfo.getOverriddenMethods()));
// Create the map/set of methods to change to delegates
mDelegateMethods = new HashMap<String, Set<String>>();
for (String signature : createInfo.getDelegateMethods()) {
int pos = signature.indexOf('#');
if (pos <= 0 || pos >= signature.length() - 1) {
continue;
}
String className = binaryToInternalClassName(signature.substring(0, pos));
String methodName = signature.substring(pos + 1);
Set<String> methods = mDelegateMethods.get(className);
if (methods == null) {
methods = new HashSet<String>();
mDelegateMethods.put(className, methods);
}
methods.add(methodName);
}
for (String className : createInfo.getDelegateClassNatives()) {
className = binaryToInternalClassName(className);
Set<String> methods = mDelegateMethods.get(className);
if (methods == null) {
methods = new HashSet<String>();
mDelegateMethods.put(className, methods);
}
methods.add(DelegateClassAdapter.ALL_NATIVES);
}
// Create the map of classes to rename.
mRenameClasses = new HashMap<String, String>();
mClassesNotRenamed = new HashSet<String>();
String[] renameClasses = createInfo.getRenamedClasses();
int n = renameClasses.length;
for (int i = 0; i < n; i += 2) {
assert i + 1 < n;
// The ASM class names uses "/" separators, whereas regular FQCN use "."
String oldFqcn = binaryToInternalClassName(renameClasses[i]);
String newFqcn = binaryToInternalClassName(renameClasses[i + 1]);
mRenameClasses.put(oldFqcn, newFqcn);
mClassesNotRenamed.add(oldFqcn);
}
// create the map of renamed class -> return type of method to delete.
mDeleteReturns = new HashMap<String, Set<String>>();
String[] deleteReturns = createInfo.getDeleteReturns();
Set<String> returnTypes = null;
String renamedClass = null;
for (String className : deleteReturns) {
// if we reach the end of a section, add it to the main map
if (className == null) {
if (returnTypes != null) {
mDeleteReturns.put(renamedClass, returnTypes);
}
renamedClass = null;
continue;
}
// if the renamed class is null, this is the beginning of a section
if (renamedClass == null) {
renamedClass = binaryToInternalClassName(className);
continue;
}
// just a standard return type, we add it to the list.
if (returnTypes == null) {
returnTypes = new HashSet<String>();
}
returnTypes.add(binaryToInternalClassName(className));
}
}
/**
* Returns the list of classes that have not been renamed yet.
* <p/>
* The names are "internal class names" rather than FQCN, i.e. they use "/" instead "."
* as package separators.
*/
public Set<String> getClassesNotRenamed() {
return mClassesNotRenamed;
}
/**
* Utility that returns the internal ASM class name from a fully qualified binary class
* name. E.g. it returns android/view/View from android.view.View.
*/
String binaryToInternalClassName(String className) {
if (className == null) {
return null;
} else {
return className.replace('.', '/');
}
}
/** Sets the map of classes to output as-is, except if they have native methods */
public void setKeep(Map<String, ClassReader> keep) {
mKeep = keep;
}
/** Sets the map of dependencies that must be completely stubbed */
public void setDeps(Map<String, ClassReader> deps) {
mDeps = deps;
}
/** Gets the map of classes to output as-is, except if they have native methods */
public Map<String, ClassReader> getKeep() {
return mKeep;
}
/** Gets the map of dependencies that must be completely stubbed */
public Map<String, ClassReader> getDeps() {
return mDeps;
}
/** Generates the final JAR */
public void generate() throws FileNotFoundException, IOException {
TreeMap<String, byte[]> all = new TreeMap<String, byte[]>();
for (Class<?> clazz : mInjectClasses) {
String name = classToEntryPath(clazz);
InputStream is = ClassLoader.getSystemResourceAsStream(name);
ClassReader cr = new ClassReader(is);
byte[] b = transform(cr, true /* stubNativesOnly */);
name = classNameToEntryPath(transformName(cr.getClassName()));
all.put(name, b);
}
for (Entry<String, ClassReader> entry : mDeps.entrySet()) {
ClassReader cr = entry.getValue();
byte[] b = transform(cr, true /* stubNativesOnly */);
String name = classNameToEntryPath(transformName(cr.getClassName()));
all.put(name, b);
}
for (Entry<String, ClassReader> entry : mKeep.entrySet()) {
ClassReader cr = entry.getValue();
byte[] b = transform(cr, true /* stubNativesOnly */);
String name = classNameToEntryPath(transformName(cr.getClassName()));
all.put(name, b);
}
mLog.info("# deps classes: %d", mDeps.size());
mLog.info("# keep classes: %d", mKeep.size());
mLog.info("# renamed : %d", mRenameCount);
createJar(new FileOutputStream(mOsDestJar), all);
mLog.info("Created JAR file %s", mOsDestJar);
}
/**
* Writes the JAR file.
*
* @param outStream The file output stream were to write the JAR.
* @param all The map of all classes to output.
* @throws IOException if an I/O error has occurred
*/
void createJar(FileOutputStream outStream, Map<String,byte[]> all) throws IOException {
JarOutputStream jar = new JarOutputStream(outStream);
for (Entry<String, byte[]> entry : all.entrySet()) {
String name = entry.getKey();
JarEntry jar_entry = new JarEntry(name);
jar.putNextEntry(jar_entry);
jar.write(entry.getValue());
jar.closeEntry();
}
jar.flush();
jar.close();
}
/**
* Utility method that converts a fully qualified java name into a JAR entry path
* e.g. for the input "android.view.View" it returns "android/view/View.class"
*/
String classNameToEntryPath(String className) {
return className.replaceAll("\\.", "/").concat(".class");
}
/**
* Utility method to get the JAR entry path from a Class name.
* e.g. it returns someting like "com/foo/OuterClass$InnerClass1$InnerClass2.class"
*/
private String classToEntryPath(Class<?> clazz) {
String name = "";
Class<?> parent;
while ((parent = clazz.getEnclosingClass()) != null) {
name = "$" + clazz.getSimpleName() + name;
clazz = parent;
}
return classNameToEntryPath(clazz.getCanonicalName() + name);
}
/**
* Transforms a class.
* <p/>
* There are 3 kind of transformations:
*
* 1- For "mock" dependencies classes, we want to remove all code from methods and replace
* by a stub. Native methods must be implemented with this stub too. Abstract methods are
* left intact. Modified classes must be overridable (non-private, non-final).
* Native methods must be made non-final, non-private.
*
* 2- For "keep" classes, we want to rewrite all native methods as indicated above.
* If a class has native methods, it must also be made non-private, non-final.
*
* Note that unfortunately static methods cannot be changed to non-static (since static and
* non-static are invoked differently.)
*/
byte[] transform(ClassReader cr, boolean stubNativesOnly) {
boolean hasNativeMethods = hasNativeMethods(cr);
// Get the class name, as an internal name (e.g. com/android/SomeClass$InnerClass)
String className = cr.getClassName();
String newName = transformName(className);
// transformName returns its input argument if there's no need to rename the class
if (newName != className) {
mRenameCount++;
// This class is being renamed, so remove it from the list of classes not renamed.
mClassesNotRenamed.remove(className);
}
mLog.debug("Transform %s%s%s%s", className,
newName == className ? "" : " (renamed to " + newName + ")",
hasNativeMethods ? " -- has natives" : "",
stubNativesOnly ? " -- stub natives only" : "");
// Rewrite the new class from scratch, without reusing the constant pool from the
// original class reader.
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor rv = cw;
if (newName != className) {
rv = new RenameClassAdapter(cw, className, newName);
}
ClassVisitor cv = new TransformClassAdapter(mLog, mStubMethods,
mDeleteReturns.get(className),
newName, rv,
stubNativesOnly, stubNativesOnly || hasNativeMethods);
Set<String> delegateMethods = mDelegateMethods.get(className);
if (delegateMethods != null && !delegateMethods.isEmpty()) {
// If delegateMethods only contains one entry ALL_NATIVES and the class is
// known to have no native methods, just skip this step.
if (hasNativeMethods ||
!(delegateMethods.size() == 1 &&
delegateMethods.contains(DelegateClassAdapter.ALL_NATIVES))) {
cv = new DelegateClassAdapter(mLog, cv, className, delegateMethods);
}
}
cr.accept(cv, 0 /* flags */);
return cw.toByteArray();
}
/**
* Should this class be renamed, this returns the new name. Otherwise it returns the
* original name.
*
* @param className The internal ASM name of the class that may have to be renamed
* @return A new transformed name or the original input argument.
*/
String transformName(String className) {
String newName = mRenameClasses.get(className);
if (newName != null) {
return newName;
}
int pos = className.indexOf('$');
if (pos > 0) {
// Is this an inner class of a renamed class?
String base = className.substring(0, pos);
newName = mRenameClasses.get(base);
if (newName != null) {
return newName + className.substring(pos);
}
}
return className;
}
/**
* Returns true if a class has any native methods.
*/
boolean hasNativeMethods(ClassReader cr) {
ClassHasNativeVisitor cv = new ClassHasNativeVisitor();
cr.accept(cv, 0 /* flags */);
return cv.hasNativeMethods();
}
}

View File

@ -0,0 +1,90 @@
/*
* Copyright (C) 2008 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 com.android.tools.layoutlib.annotations.VisibleForTesting;
import com.android.tools.layoutlib.annotations.VisibleForTesting.Visibility;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
/**
* Indicates if a class contains any native methods.
*/
public class ClassHasNativeVisitor implements ClassVisitor {
private boolean mHasNativeMethods = false;
public boolean hasNativeMethods() {
return mHasNativeMethods;
}
@VisibleForTesting(visibility=Visibility.PRIVATE)
protected void setHasNativeMethods(boolean hasNativeMethods, String methodName) {
mHasNativeMethods = hasNativeMethods;
}
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
// pass
}
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
// pass
return null;
}
public void visitAttribute(Attribute attr) {
// pass
}
public void visitEnd() {
// pass
}
public FieldVisitor visitField(int access, String name, String desc,
String signature, Object value) {
// pass
return null;
}
public void visitInnerClass(String name, String outerName,
String innerName, int access) {
// pass
}
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
if ((access & Opcodes.ACC_NATIVE) != 0) {
setHasNativeMethods(true, name);
}
return null;
}
public void visitOuterClass(String owner, String name, String desc) {
// pass
}
public void visitSource(String source, String debug) {
// pass
}
}

View File

@ -0,0 +1,182 @@
/*
* Copyright (C) 2008 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 com.android.tools.layoutlib.annotations.LayoutlibDelegate;
/**
* Describes the work to be done by {@link AsmGenerator}.
*/
public final class CreateInfo implements ICreateInfo {
/**
* Returns the list of class from layoutlib_create to inject in layoutlib.
* The list can be empty but must not be null.
*/
public Class<?>[] getInjectedClasses() {
return INJECTED_CLASSES;
}
/**
* Returns the list of methods to rewrite as delegates.
* The list can be empty but must not be null.
*/
public String[] getDelegateMethods() {
return DELEGATE_METHODS;
}
/**
* Returns the list of classes on which to delegate all native methods.
* The list can be empty but must not be null.
*/
public String[] getDelegateClassNatives() {
return DELEGATE_CLASS_NATIVES;
}
/**
* Returns The list of methods to stub out. Each entry must be in the form
* "package.package.OuterClass$InnerClass#MethodName".
* The list can be empty but must not be null.
*/
public String[] getOverriddenMethods() {
return OVERRIDDEN_METHODS;
}
/**
* Returns the list of classes to rename, must be an even list: the binary FQCN
* of class to replace followed by the new FQCN.
* The list can be empty but must not be null.
*/
public String[] getRenamedClasses() {
return RENAMED_CLASSES;
}
/**
* Returns the 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
* to rename in which the methods are deleted, followed by a list of return types identifying
* the methods to delete.
* The list can be empty but must not be null.
*/
public String[] getDeleteReturns() {
return DELETE_RETURNS;
}
//-----
/**
* The list of class from layoutlib_create to inject in layoutlib.
*/
private final static Class<?>[] INJECTED_CLASSES = new Class<?>[] {
OverrideMethod.class,
MethodListener.class,
MethodAdapter.class,
ICreateInfo.class,
CreateInfo.class,
LayoutlibDelegate.class
};
/**
* The list of methods to rewrite as delegates.
*/
private final static String[] DELEGATE_METHODS = new String[] {
"android.content.res.Resources$Theme#obtainStyledAttributes",
"android.content.res.Resources$Theme#resolveAttribute",
"android.graphics.BitmapFactory#finishDecode",
"android.os.Handler#sendMessageAtTime",
"android.os.HandlerThread#run",
"android.os.Build#getString",
"android.view.LayoutInflater#parseInclude",
"android.view.View#isInEditMode",
"com.android.internal.util.XmlUtils#convertValueToInt",
};
/**
* The list of classes on which to delegate all native methods.
*/
private final static String[] DELEGATE_CLASS_NATIVES = new String[] {
"android.graphics.AvoidXfermode",
"android.graphics.Bitmap",
"android.graphics.BitmapFactory",
"android.graphics.BitmapShader",
"android.graphics.BlurMaskFilter",
"android.graphics.Canvas",
"android.graphics.ColorFilter",
"android.graphics.ColorMatrixColorFilter",
"android.graphics.ComposePathEffect",
"android.graphics.ComposeShader",
"android.graphics.CornerPathEffect",
"android.graphics.DashPathEffect",
"android.graphics.DiscretePathEffect",
"android.graphics.DrawFilter",
"android.graphics.EmbossMaskFilter",
"android.graphics.LayerRasterizer",
"android.graphics.LightingColorFilter",
"android.graphics.LinearGradient",
"android.graphics.MaskFilter",
"android.graphics.Matrix",
"android.graphics.NinePatch",
"android.graphics.Paint",
"android.graphics.PaintFlagsDrawFilter",
"android.graphics.Path",
"android.graphics.PathDashPathEffect",
"android.graphics.PathEffect",
"android.graphics.PixelXorXfermode",
"android.graphics.PorterDuffColorFilter",
"android.graphics.PorterDuffXfermode",
"android.graphics.RadialGradient",
"android.graphics.Rasterizer",
"android.graphics.Region",
"android.graphics.Shader",
"android.graphics.SumPathEffect",
"android.graphics.SweepGradient",
"android.graphics.Typeface",
"android.graphics.Xfermode",
"android.os.SystemClock",
"android.util.FloatMath",
};
/**
* The list of methods to stub out. Each entry must be in the form
* "package.package.OuterClass$InnerClass#MethodName".
*/
private final static String[] OVERRIDDEN_METHODS = new String[] {
};
/**
* The list of classes to rename, must be an even list: the binary FQCN
* of class to replace followed by the new FQCN.
*/
private final static String[] RENAMED_CLASSES =
new String[] {
"android.os.ServiceManager", "android.os._Original_ServiceManager",
"android.view.SurfaceView", "android.view._Original_SurfaceView",
"android.view.accessibility.AccessibilityManager", "android.view.accessibility._Original_AccessibilityManager",
"android.webkit.WebView", "android.webkit._Original_WebView",
};
/**
* 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
* to rename in which the methods are deleted, followed by a list of return types identifying
* the methods to delete.
*/
private final static String[] DELETE_RETURNS =
new String[] {
null }; // separator, for next class/methods list.
}

View File

@ -0,0 +1,94 @@
/*
* Copyright (C) 2010 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.ClassAdapter;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.util.Set;
/**
* A {@link DelegateClassAdapter} can transform some methods from a class into
* delegates that defer the call to an associated delegate class.
* <p/>
* This is used to override specific methods and or all native methods in classes.
*/
public class DelegateClassAdapter extends ClassAdapter {
public final static String ALL_NATIVES = "<<all_natives>>";
private final String mClassName;
private final Set<String> mDelegateMethods;
private final Log mLog;
/**
* Creates a new {@link DelegateClassAdapter} that can transform some methods
* from a class into delegates that defer the call to an associated delegate class.
* <p/>
* This is used to override specific methods and or all native methods in classes.
*
* @param log The logger object. Must not be null.
* @param cv the class visitor to which this adapter must delegate calls.
* @param className The internal class name of the class to visit,
* e.g. <code>com/android/SomeClass$InnerClass</code>.
* @param delegateMethods The set of method names to modify and/or the
* special constant {@link #ALL_NATIVES} to convert all native methods.
*/
public DelegateClassAdapter(Log log,
ClassVisitor cv,
String className,
Set<String> delegateMethods) {
super(cv);
mLog = log;
mClassName = className;
mDelegateMethods = delegateMethods;
}
//----------------------------------
// Methods from the ClassAdapter
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
boolean useDelegate = (isNative && mDelegateMethods.contains(ALL_NATIVES)) ||
mDelegateMethods.contains(name);
if (useDelegate) {
// remove native
access = access & ~Opcodes.ACC_NATIVE;
}
MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
if (useDelegate) {
DelegateMethodAdapter a = new DelegateMethodAdapter(mLog, mw, mClassName,
name, desc, isStatic);
if (isNative) {
// A native has no code to visit, so we need to generate it directly.
a.generateCode();
} else {
return a;
}
}
return mw;
}
}

View File

@ -0,0 +1,358 @@
/*
* Copyright (C) 2008 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 com.android.tools.layoutlib.annotations.LayoutlibDelegate;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import java.util.ArrayList;
/**
* This method adapter rewrites a method by discarding the original code and generating
* a call to a delegate. Original annotations are passed along unchanged.
* <p/>
* Calls are delegated to a class named <code>&lt;className&gt;_Delegate</code> with
* static methods matching the methods to be overridden here. The methods have the
* same return type. The argument type list is the same except the "this" reference is
* passed first for non-static methods.
* <p/>
* A new annotation is added.
* <p/>
* Note that native methods have, by definition, no code so there's nothing a visitor
* can visit. That means the caller must call {@link #generateCode()} directly for
* a native and use the visitor pattern for non-natives.
* <p/>
* Instances of this class are not re-usable. You need a new instance for each method.
*/
class DelegateMethodAdapter implements MethodVisitor {
/**
* Suffix added to delegate classes.
*/
public static final String DELEGATE_SUFFIX = "_Delegate";
private static String CONSTRUCTOR = "<init>";
private static String CLASS_INIT = "<clinit>";
/** The parent method writer */
private MethodVisitor mParentVisitor;
/** Flag to output the first line number. */
private boolean mOutputFirstLineNumber = true;
/** The original method descriptor (return type + argument types.) */
private String mDesc;
/** True if the original method is static. */
private final boolean mIsStatic;
/** The internal class name (e.g. <code>com/android/SomeClass$InnerClass</code>.) */
private final String mClassName;
/** The method name. */
private final String mMethodName;
/** Logger object. */
private final Log mLog;
/** True if {@link #visitCode()} has been invoked. */
private boolean mVisitCodeCalled;
/**
* Creates a new {@link DelegateMethodAdapter} that will transform this method
* into a delegate call.
* <p/>
* See {@link DelegateMethodAdapter} for more details.
*
* @param log The logger object. Must not be null.
* @param mv the method visitor to which this adapter must delegate calls.
* @param className The internal class name of the class to visit,
* e.g. <code>com/android/SomeClass$InnerClass</code>.
* @param methodName The simple name of the method.
* @param desc A method descriptor (c.f. {@link Type#getReturnType(String)} +
* {@link Type#getArgumentTypes(String)})
* @param isStatic True if the method is declared static.
*/
public DelegateMethodAdapter(Log log,
MethodVisitor mv,
String className,
String methodName,
String desc,
boolean isStatic) {
mLog = log;
mParentVisitor = mv;
mClassName = className;
mMethodName = methodName;
mDesc = desc;
mIsStatic = isStatic;
if (CONSTRUCTOR.equals(methodName) || CLASS_INIT.equals(methodName)) {
// We're going to simplify by not supporting constructors.
// The only trick with a constructor is to find the proper super constructor
// and call it (and deciding if we should mirror the original method call to
// a custom constructor or call a default one.)
throw new UnsupportedOperationException(
String.format("Delegate doesn't support overriding constructor %1$s:%2$s(%3$s)",
className, methodName, desc));
}
}
/**
* Generates the new code for the method.
* <p/>
* For native methods, this must be invoked directly by {@link DelegateClassAdapter}
* (since they have no code to visit).
* <p/>
* Otherwise for non-native methods the {@link DelegateClassAdapter} simply needs to
* return this instance of {@link DelegateMethodAdapter} and let the normal visitor pattern
* invoke it as part of the {@link ClassReader#accept(ClassVisitor, int)} workflow and then
* this method will be invoked from {@link MethodVisitor#visitEnd()}.
*/
public void generateCode() {
/*
* The goal is to generate a call to a static delegate method.
* If this method is non-static, the first parameter will be 'this'.
* All the parameters must be passed and then the eventual return type returned.
*
* Example, let's say we have a method such as
* public void method_1(int a, Object b, ArrayList<String> c) { ... }
*
* We'll want to create a body that calls a delegate method like this:
* TheClass_Delegate.method_1(this, a, b, c);
*
* If the method is non-static and the class name is an inner class (e.g. has $ in its
* last segment), we want to push the 'this' of the outer class first:
* OuterClass_InnerClass_Delegate.method_1(
* OuterClass.this,
* OuterClass$InnerClass.this,
* a, b, c);
*
* Only one level of inner class is supported right now, for simplicity and because
* we don't need more.
*
* The generated class name is the current class name with "_Delegate" appended to it.
* One thing to realize is that we don't care about generics -- since generic types
* are erased at runtime, they have no influence on the method name being called.
*/
// Add our annotation
AnnotationVisitor aw = mParentVisitor.visitAnnotation(
Type.getObjectType(Type.getInternalName(LayoutlibDelegate.class)).toString(),
true); // visible at runtime
aw.visitEnd();
if (!mVisitCodeCalled) {
// If this is a direct call to generateCode() as done by DelegateClassAdapter
// for natives, visitCode() hasn't been called yet.
mParentVisitor.visitCode();
mVisitCodeCalled = true;
}
ArrayList<Type> paramTypes = new ArrayList<Type>();
String delegateClassName = mClassName + DELEGATE_SUFFIX;
boolean pushedArg0 = false;
int maxStack = 0;
// For an instance method (e.g. non-static), push the 'this' preceded
// by the 'this' of any outer class, if any.
if (!mIsStatic) {
// Check if the last segment of the class name has inner an class.
// Right now we only support one level of inner classes.
int slash = mClassName.lastIndexOf('/');
int dol = mClassName.lastIndexOf('$');
if (dol != -1 && dol > slash && dol == mClassName.indexOf('$')) {
String outerClass = mClassName.substring(0, dol);
Type outerType = Type.getObjectType(outerClass);
// Change a delegate class name to "com/foo/Outer_Inner_Delegate"
delegateClassName = delegateClassName.replace('$', '_');
// The first-level inner class has a package-protected member called 'this$0'
// that points to the outer class.
// Push this.getField("this$0") on the call stack.
mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0); // var 0 = this
mParentVisitor.visitFieldInsn(Opcodes.GETFIELD,
mClassName, // class where the field is defined
"this$0", // field name
outerType.getDescriptor()); // type of the field
maxStack++;
paramTypes.add(outerType);
}
// Push "this" for the instance method, which is always ALOAD 0
mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0);
maxStack++;
pushedArg0 = true;
paramTypes.add(Type.getObjectType(mClassName));
}
// Push all other arguments. Start at arg 1 if we already pushed 'this' above.
Type[] argTypes = Type.getArgumentTypes(mDesc);
int maxLocals = pushedArg0 ? 1 : 0;
for (Type t : argTypes) {
int size = t.getSize();
mParentVisitor.visitVarInsn(t.getOpcode(Opcodes.ILOAD), maxLocals);
maxLocals += size;
maxStack += size;
paramTypes.add(t);
}
// Construct the descriptor of the delegate based on the parameters
// we pushed on the call stack. The return type remains unchanged.
String desc = Type.getMethodDescriptor(
Type.getReturnType(mDesc),
paramTypes.toArray(new Type[paramTypes.size()]));
// Invoke the static delegate
mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
delegateClassName,
mMethodName,
desc);
Type returnType = Type.getReturnType(mDesc);
mParentVisitor.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
mParentVisitor.visitMaxs(maxStack, maxLocals);
mParentVisitor.visitEnd();
// For debugging now. Maybe we should collect these and store them in
// a text file for helping create the delegates. We could also compare
// the text file to a golden and break the build on unsupported changes
// or regressions. Even better we could fancy-print something that looks
// like the expected Java method declaration.
mLog.debug("Delegate: %1$s # %2$s %3$s", delegateClassName, mMethodName, desc);
}
/* Pass down to visitor writer. In this implementation, either do nothing. */
public void visitCode() {
mVisitCodeCalled = true;
mParentVisitor.visitCode();
}
/*
* visitMaxs is called just before visitEnd if there was any code to rewrite.
* Skip the original.
*/
public void visitMaxs(int maxStack, int maxLocals) {
}
/**
* End of visiting. Generate the messaging code.
*/
public void visitEnd() {
generateCode();
}
/* Writes all annotation from the original method. */
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return mParentVisitor.visitAnnotation(desc, visible);
}
/* Writes all annotation default values from the original method. */
public AnnotationVisitor visitAnnotationDefault() {
return mParentVisitor.visitAnnotationDefault();
}
public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
boolean visible) {
return mParentVisitor.visitParameterAnnotation(parameter, desc, visible);
}
/* Writes all attributes from the original method. */
public void visitAttribute(Attribute attr) {
mParentVisitor.visitAttribute(attr);
}
/*
* Only writes the first line number present in the original code so that source
* viewers can direct to the correct method, even if the content doesn't match.
*/
public void visitLineNumber(int line, Label start) {
if (mOutputFirstLineNumber) {
mParentVisitor.visitLineNumber(line, start);
mOutputFirstLineNumber = false;
}
}
public void visitInsn(int opcode) {
// Skip original code.
}
public void visitLabel(Label label) {
// Skip original code.
}
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
// Skip original code.
}
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
// Skip original code.
}
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
// Skip original code.
}
public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
// Skip original code.
}
public void visitIincInsn(int var, int increment) {
// Skip original code.
}
public void visitIntInsn(int opcode, int operand) {
// Skip original code.
}
public void visitJumpInsn(int opcode, Label label) {
// Skip original code.
}
public void visitLdcInsn(Object cst) {
// Skip original code.
}
public void visitLocalVariable(String name, String desc, String signature,
Label start, Label end, int index) {
// Skip original code.
}
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
// Skip original code.
}
public void visitMultiANewArrayInsn(String desc, int dims) {
// Skip original code.
}
public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
// Skip original code.
}
public void visitTypeInsn(int opcode, String type) {
// Skip original code.
}
public void visitVarInsn(int opcode, int var) {
// Skip original code.
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (C) 2010 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;
/**
* Interface describing the work to be done by {@link AsmGenerator}.
*/
public interface ICreateInfo {
/**
* Returns the list of class from layoutlib_create to inject in layoutlib.
* The list can be empty but must not be null.
*/
public abstract Class<?>[] getInjectedClasses();
/**
* Returns the list of methods to rewrite as delegates.
* The list can be empty but must not be null.
*/
public abstract String[] getDelegateMethods();
/**
* Returns the list of classes on which to delegate all native methods.
* The list can be empty but must not be null.
*/
public abstract String[] getDelegateClassNatives();
/**
* Returns The list of methods to stub out. Each entry must be in the form
* "package.package.OuterClass$InnerClass#MethodName".
* The list can be empty but must not be null.
*/
public abstract String[] getOverriddenMethods();
/**
* Returns the list of classes to rename, must be an even list: the binary FQCN
* of class to replace followed by the new FQCN.
* The list can be empty but must not be null.
*/
public abstract String[] getRenamedClasses();
/**
* Returns the 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
* to rename in which the methods are deleted, followed by a list of return types identifying
* the methods to delete.
* The list can be empty but must not be null.
*/
public abstract String[] getDeleteReturns();
}

View File

@ -0,0 +1,64 @@
/*
* Copyright (C) 2008 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.io.PrintWriter;
import java.io.StringWriter;
public class Log {
private boolean mVerbose = false;
public void setVerbose(boolean verbose) {
mVerbose = verbose;
}
public void debug(String format, Object... args) {
if (mVerbose) {
info(format, args);
}
}
public void info(String format, Object... args) {
String s = String.format(format, args);
outPrintln(s);
}
public void error(String format, Object... args) {
String s = String.format(format, args);
errPrintln(s);
}
public void exception(Throwable t, String format, Object... args) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
t.printStackTrace(pw);
pw.flush();
error(format + "\n" + sw.toString(), args);
}
/** for unit testing */
protected void errPrintln(String msg) {
System.err.println(msg);
}
/** for unit testing */
protected void outPrintln(String msg) {
System.out.println(msg);
}
}

View File

@ -0,0 +1,32 @@
/*
* Copyright (C) 2008 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;
public class LogAbortException extends Exception {
private final String mFormat;
private final Object[] mArgs;
public LogAbortException(String format, Object... args) {
mFormat = format;
mArgs = args;
}
public void error(Log log) {
log.error(mFormat, mArgs);
}
}

View File

@ -0,0 +1,162 @@
/*
* Copyright (C) 2008 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.io.IOException;
import java.util.ArrayList;
import java.util.Set;
/**
* Entry point for the layoutlib_create tool.
* <p/>
* The tool does not currently rely on any external configuration file.
* Instead the configuration is mostly done via the {@link CreateInfo} class.
* <p/>
* For a complete description of the tool and its implementation, please refer to
* the "README.txt" file at the root of this project.
* <p/>
* For a quick test, invoke this as follows:
* <pre>
* $ make layoutlib
* </pre>
* which does:
* <pre>
* $ make layoutlib_create &lt;bunch of framework jars&gt;
* $ out/host/linux-x86/framework/bin/layoutlib_create \
* out/host/common/obj/JAVA_LIBRARIES/temp_layoutlib_intermediates/javalib.jar \
* out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar \
* out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar
* </pre>
*/
public class Main {
public static void main(String[] args) {
Log log = new Log();
ArrayList<String> osJarPath = new ArrayList<String>();
String[] osDestJar = { null };
if (!processArgs(log, args, osJarPath, osDestJar)) {
log.error("Usage: layoutlib_create [-v] output.jar input.jar ...");
System.exit(1);
}
log.info("Output: %1$s", osDestJar[0]);
for (String path : osJarPath) {
log.info("Input : %1$s", path);
}
try {
AsmGenerator agen = new AsmGenerator(log, osDestJar[0], new CreateInfo());
AsmAnalyzer aa = new AsmAnalyzer(log, osJarPath, agen,
new String[] { // derived from
"android.view.View",
},
new String[] { // include classes
"android.*", // for android.R
"android.util.*",
"com.android.internal.util.*",
"android.view.*",
"android.widget.*",
"com.android.internal.widget.*",
"android.text.**",
"android.graphics.*",
"android.graphics.drawable.*",
"android.content.*",
"android.content.res.*",
"org.apache.harmony.xml.*",
"com.android.internal.R**",
"android.pim.*", // for datepicker
"android.os.*", // for android.os.Handler
"android.database.ContentObserver", // for Digital clock
});
aa.analyze();
agen.generate();
// Throw an error if any class failed to get renamed by the generator
//
// IMPORTANT: if you're building the platform and you get this error message,
// it means the renameClasses[] array in AsmGenerator needs to be updated: some
// class should have been renamed but it was not found in the input JAR files.
Set<String> notRenamed = agen.getClassesNotRenamed();
if (notRenamed.size() > 0) {
// (80-column guide below for error formatting)
// 01234567890123456789012345678901234567890123456789012345678901234567890123456789
log.error(
"ERROR when running layoutlib_create: the following classes are referenced\n" +
"by tools/layoutlib/create but were not actually found in the input JAR files.\n" +
"This may be due to some platform classes having been renamed.");
for (String fqcn : notRenamed) {
log.error("- Class not found: %s", fqcn.replace('/', '.'));
}
for (String path : osJarPath) {
log.info("- Input JAR : %1$s", path);
}
System.exit(1);
}
System.exit(0);
} catch (IOException e) {
log.exception(e, "Failed to load jar");
} catch (LogAbortException e) {
e.error(log);
}
System.exit(1);
}
/**
* Returns true if args where properly parsed.
* Returns false if program should exit with command-line usage.
* <p/>
* Note: the String[0] is an output parameter wrapped in an array, since there is no
* "out" parameter support.
*/
private static boolean processArgs(Log log, String[] args,
ArrayList<String> osJarPath, String[] osDestJar) {
for (int i = 0; i < args.length; i++) {
String s = args[i];
if (s.equals("-v")) {
log.setVerbose(true);
} else if (!s.startsWith("-")) {
if (osDestJar[0] == null) {
osDestJar[0] = s;
} else {
osJarPath.add(s);
}
} else {
log.error("Unknow argument: %s", s);
return false;
}
}
if (osJarPath.isEmpty()) {
log.error("Missing parameter: path to input jar");
return false;
}
if (osDestJar[0] == null) {
log.error("Missing parameter: path to output jar");
return false;
}
return true;
}
}

View File

@ -0,0 +1,91 @@
/*
* Copyright (C) 2008 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;
/**
* An adapter to make it easier to use {@link MethodListener}.
* <p/>
* The adapter calls the void {@link #onInvokeV(String, boolean, Object)} listener
* for all types (I, L, F, D and A), returning 0 or null as appropriate.
*/
public class MethodAdapter implements MethodListener {
/**
* A stub method is being invoked.
* <p/>
* Known limitation: caller arguments are not available.
*
* @param signature The signature of the method being invoked, composed of the
* binary class name followed by the method descriptor (aka argument
* types). Example: "com/foo/MyClass/InnerClass/printInt(I)V".
* @param isNative True if the method was a native method.
* @param caller The calling object. Null for static methods, "this" for instance methods.
*/
public void onInvokeV(String signature, boolean isNative, Object caller) {
}
/**
* Same as {@link #onInvokeV(String, boolean, Object)} but returns an integer or similar.
* @see #onInvokeV(String, boolean, Object)
* @return an integer, or a boolean, or a short or a byte.
*/
public int onInvokeI(String signature, boolean isNative, Object caller) {
onInvokeV(signature, isNative, caller);
return 0;
}
/**
* Same as {@link #onInvokeV(String, boolean, Object)} but returns a long.
* @see #onInvokeV(String, boolean, Object)
* @return a long.
*/
public long onInvokeL(String signature, boolean isNative, Object caller) {
onInvokeV(signature, isNative, caller);
return 0;
}
/**
* Same as {@link #onInvokeV(String, boolean, Object)} but returns a float.
* @see #onInvokeV(String, boolean, Object)
* @return a float.
*/
public float onInvokeF(String signature, boolean isNative, Object caller) {
onInvokeV(signature, isNative, caller);
return 0;
}
/**
* Same as {@link #onInvokeV(String, boolean, Object)} but returns a double.
* @see #onInvokeV(String, boolean, Object)
* @return a double.
*/
public double onInvokeD(String signature, boolean isNative, Object caller) {
onInvokeV(signature, isNative, caller);
return 0;
}
/**
* Same as {@link #onInvokeV(String, boolean, Object)} but returns an object.
* @see #onInvokeV(String, boolean, Object)
* @return an object.
*/
public Object onInvokeA(String signature, boolean isNative, Object caller) {
onInvokeV(signature, isNative, caller);
return null;
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright (C) 2008 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;
/**
* Interface to allow a method invocation to be listened upon.
* <p/>
* This is used by {@link OverrideMethod} to register a listener for methods that
* have been stubbed by the {@link AsmGenerator}. At runtime the stub will call either a
* default global listener or a specific listener based on the method signature.
*/
public interface MethodListener {
/**
* A stub method is being invoked.
* <p/>
* Known limitation: caller arguments are not available.
*
* @param signature The signature of the method being invoked, composed of the
* binary class name followed by the method descriptor (aka argument
* types). Example: "com/foo/MyClass/InnerClass/printInt(I)V".
* @param isNative True if the method was a native method.
* @param caller The calling object. Null for static methods, "this" for instance methods.
*/
public void onInvokeV(String signature, boolean isNative, Object caller);
/**
* Same as {@link #onInvokeV(String, boolean, Object)} but returns an integer or similar.
* @see #onInvokeV(String, boolean, Object)
* @return an integer, or a boolean, or a short or a byte.
*/
public int onInvokeI(String signature, boolean isNative, Object caller);
/**
* Same as {@link #onInvokeV(String, boolean, Object)} but returns a long.
* @see #onInvokeV(String, boolean, Object)
* @return a long.
*/
public long onInvokeL(String signature, boolean isNative, Object caller);
/**
* Same as {@link #onInvokeV(String, boolean, Object)} but returns a float.
* @see #onInvokeV(String, boolean, Object)
* @return a float.
*/
public float onInvokeF(String signature, boolean isNative, Object caller);
/**
* Same as {@link #onInvokeV(String, boolean, Object)} but returns a double.
* @see #onInvokeV(String, boolean, Object)
* @return a double.
*/
public double onInvokeD(String signature, boolean isNative, Object caller);
/**
* Same as {@link #onInvokeV(String, boolean, Object)} but returns an object.
* @see #onInvokeV(String, boolean, Object)
* @return an object.
*/
public Object onInvokeA(String signature, boolean isNative, Object caller);
}

View File

@ -0,0 +1,151 @@
/*
* Copyright (C) 2008 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;
/**
* Allows stub methods from LayoutLib to be overriden at runtime.
* <p/>
* Implementation note: all types required by this class(inner/outer classes & interfaces)
* must be referenced by the injectClass argument to {@link AsmGenerator} in Main.java;
* Otherwise they won't be accessible in layoutlib.jar at runtime.
*/
public final class OverrideMethod {
/** Map of method overridden. */
private static HashMap<String, MethodListener> sMethods = new HashMap<String, MethodListener>();
/** Default listener for all method not listed in sMethods. Nothing if null. */
private static MethodListener sDefaultListener = null;
/**
* Sets the default listener for all methods not specifically handled.
* Null means to do nothing.
*/
public static void setDefaultListener(MethodListener listener) {
sDefaultListener = listener;
}
/**
* Defines or reset a listener for the given method signature.
*
* @param signature The signature of the method being invoked, composed of the
* binary class name followed by the method descriptor (aka argument
* types). Example: "com/foo/MyClass/InnerClass/printInt(I)V"
* @param listener The new listener. Removes it if null.
*/
public static void setMethodListener(String signature, MethodListener listener) {
if (listener == null) {
sMethods.remove(signature);
} else {
sMethods.put(signature, listener);
}
}
/**
* Invokes the specific listener for the given signature or the default one if defined.
* <p/>
* This version invokes the method listener for the void return type.
* <p/>
* Note: this is not intended to be used by the LayoutLib Bridge. It is intended to be called
* by the stubbed methods generated by the LayoutLib_create tool.
*
* @param signature The signature of the method being invoked, composed of the
* binary class name followed by the method descriptor (aka argument
* types). Example: "com/foo/MyClass/InnerClass/printInt(I)V".
* @param isNative True if the method was a native method.
* @param caller The calling object. Null for static methods, "this" for instance methods.
*/
public static void invokeV(String signature, boolean isNative, Object caller) {
MethodListener i = sMethods.get(signature);
if (i != null) {
i.onInvokeV(signature, isNative, caller);
} else if (sDefaultListener != null) {
sDefaultListener.onInvokeV(signature, isNative, caller);
}
}
/**
* Invokes the specific listener for the int return type.
* @see #invokeV(String, boolean, Object)
*/
public static int invokeI(String signature, boolean isNative, Object caller) {
MethodListener i = sMethods.get(signature);
if (i != null) {
return i.onInvokeI(signature, isNative, caller);
} else if (sDefaultListener != null) {
return sDefaultListener.onInvokeI(signature, isNative, caller);
}
return 0;
}
/**
* Invokes the specific listener for the long return type.
* @see #invokeV(String, boolean, Object)
*/
public static long invokeL(String signature, boolean isNative, Object caller) {
MethodListener i = sMethods.get(signature);
if (i != null) {
return i.onInvokeL(signature, isNative, caller);
} else if (sDefaultListener != null) {
return sDefaultListener.onInvokeL(signature, isNative, caller);
}
return 0;
}
/**
* Invokes the specific listener for the float return type.
* @see #invokeV(String, boolean, Object)
*/
public static float invokeF(String signature, boolean isNative, Object caller) {
MethodListener i = sMethods.get(signature);
if (i != null) {
return i.onInvokeF(signature, isNative, caller);
} else if (sDefaultListener != null) {
return sDefaultListener.onInvokeF(signature, isNative, caller);
}
return 0;
}
/**
* Invokes the specific listener for the double return type.
* @see #invokeV(String, boolean, Object)
*/
public static double invokeD(String signature, boolean isNative, Object caller) {
MethodListener i = sMethods.get(signature);
if (i != null) {
return i.onInvokeD(signature, isNative, caller);
} else if (sDefaultListener != null) {
return sDefaultListener.onInvokeD(signature, isNative, caller);
}
return 0;
}
/**
* Invokes the specific listener for the object return type.
* @see #invokeV(String, boolean, Object)
*/
public static Object invokeA(String signature, boolean isNative, Object caller) {
MethodListener i = sMethods.get(signature);
if (i != null) {
return i.onInvokeA(signature, isNative, caller);
} else if (sDefaultListener != null) {
return sDefaultListener.onInvokeA(signature, isNative, caller);
}
return null;
}
}

View File

@ -0,0 +1,446 @@
/*
* Copyright (C) 2008 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.ClassAdapter;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodAdapter;
import org.objectweb.asm.MethodVisitor;
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.
* The class visitor will also rename all inner classes and references in the methods.
* <p/>
*
* For inner classes, this handles only the case where the outer class name changes.
* The inner class name should remain the same.
*/
public class RenameClassAdapter extends ClassAdapter {
private final String mOldName;
private final String mNewName;
private String mOldBase;
private String mNewBase;
/**
* Creates a class visitor that renames a class from a given old name to a given new name.
* The class visitor will also rename all inner classes and references in the methods.
* The names must be full qualified internal ASM names (e.g. com/blah/MyClass$InnerClass).
*/
public RenameClassAdapter(ClassWriter cv, String oldName, String newName) {
super(cv);
mOldBase = mOldName = oldName;
mNewBase = mNewName = newName;
int pos = mOldName.indexOf('$');
if (pos > 0) {
mOldBase = mOldName.substring(0, pos);
}
pos = mNewName.indexOf('$');
if (pos > 0) {
mNewBase = mNewName.substring(0, pos);
}
assert (mOldBase == null && mNewBase == null) || (mOldBase != null && mNewBase != null);
}
/**
* Renames a type descriptor, e.g. "Lcom.package.MyClass;"
* If the type doesn't need to be renamed, returns the input string as-is.
*/
String renameTypeDesc(String desc) {
if (desc == null) {
return null;
}
return renameType(Type.getType(desc));
}
/**
* Renames an object type, e.g. "Lcom.package.MyClass;" or an array type that has an
* object element, e.g. "[Lcom.package.MyClass;"
* If the type doesn't need to be renamed, returns the internal name of the input type.
*/
String renameType(Type type) {
if (type == null) {
return null;
}
if (type.getSort() == Type.OBJECT) {
String in = type.getInternalName();
return "L" + renameInternalType(in) + ";";
} else if (type.getSort() == Type.ARRAY) {
StringBuilder sb = new StringBuilder();
for (int n = type.getDimensions(); n > 0; n--) {
sb.append('[');
}
sb.append(renameType(type.getElementType()));
return sb.toString();
}
return type.getDescriptor();
}
/**
* Renames an object type, e.g. "Lcom.package.MyClass;" or an array type that has an
* object element, e.g. "[Lcom.package.MyClass;".
* This is like renameType() except that it returns a Type object.
* If the type doesn't need to be renamed, returns the input type object.
*/
Type renameTypeAsType(Type type) {
if (type == null) {
return null;
}
if (type.getSort() == Type.OBJECT) {
String in = type.getInternalName();
String newIn = renameInternalType(in);
if (newIn != in) {
return Type.getType("L" + newIn + ";");
}
} else if (type.getSort() == Type.ARRAY) {
StringBuilder sb = new StringBuilder();
for (int n = type.getDimensions(); n > 0; n--) {
sb.append('[');
}
sb.append(renameType(type.getElementType()));
return Type.getType(sb.toString());
}
return type;
}
/**
* Renames an internal type name, e.g. "com.package.MyClass".
* If the type doesn't need to be renamed, returns the input string as-is.
* <p/>
* The internal type of some of the MethodVisitor turns out to be a type
descriptor sometimes so descriptors are renamed too.
*/
String renameInternalType(String type) {
if (type == null) {
return null;
}
if (type.equals(mOldName)) {
return mNewName;
}
if (mOldBase != mOldName && type.equals(mOldBase)) {
return mNewBase;
}
int pos = type.indexOf('$');
if (pos == mOldBase.length() && type.startsWith(mOldBase)) {
return mNewBase + type.substring(pos);
}
// The internal type of some of the MethodVisitor turns out to be a type
// descriptor sometimes. This is the case with visitTypeInsn(type) and
// visitMethodInsn(owner). We try to detect it and adjust it here.
if (type.indexOf(';') > 0) {
type = renameTypeDesc(type);
}
return type;
}
/**
* Renames a method descriptor, i.e. applies renameType to all arguments and to the
* return value.
*/
String renameMethodDesc(String desc) {
if (desc == null) {
return null;
}
Type[] args = Type.getArgumentTypes(desc);
StringBuilder sb = new StringBuilder("(");
for (Type arg : args) {
String name = renameType(arg);
sb.append(name);
}
sb.append(')');
Type ret = Type.getReturnType(desc);
String name = renameType(ret);
sb.append(name);
return sb.toString();
}
/**
* Renames the ClassSignature handled by ClassVisitor.visit
* or the MethodTypeSignature handled by ClassVisitor.visitMethod.
*/
String renameTypeSignature(String sig) {
if (sig == null) {
return null;
}
SignatureReader reader = new SignatureReader(sig);
SignatureWriter writer = new SignatureWriter();
reader.accept(new RenameSignatureAdapter(writer));
sig = writer.toString();
return sig;
}
/**
* Renames the FieldTypeSignature handled by ClassVisitor.visitField
* or MethodVisitor.visitLocalVariable.
*/
String renameFieldSignature(String sig) {
if (sig == null) {
return null;
}
SignatureReader reader = new SignatureReader(sig);
SignatureWriter writer = new SignatureWriter();
reader.acceptType(new RenameSignatureAdapter(writer));
sig = writer.toString();
return sig;
}
//----------------------------------
// Methods from the ClassAdapter
@Override
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
name = renameInternalType(name);
superName = renameInternalType(superName);
signature = renameTypeSignature(signature);
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
assert outerName.equals(mOldName);
outerName = renameInternalType(outerName);
name = outerName + "$" + innerName;
super.visitInnerClass(name, outerName, innerName, access);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
desc = renameMethodDesc(desc);
signature = renameTypeSignature(signature);
MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
return new RenameMethodAdapter(mw);
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
desc = renameTypeDesc(desc);
return super.visitAnnotation(desc, visible);
}
@Override
public FieldVisitor visitField(int access, String name, String desc,
String signature, Object value) {
desc = renameTypeDesc(desc);
signature = renameFieldSignature(signature);
return super.visitField(access, name, desc, signature, value);
}
//----------------------------------
/**
* A method visitor that renames all references from an old class name to a new class name.
*/
public class RenameMethodAdapter extends MethodAdapter {
/**
* 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(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 implements SignatureVisitor {
private final SignatureVisitor mSv;
public RenameSignatureAdapter(SignatureVisitor sv) {
mSv = sv;
}
public void visitClassType(String name) {
name = renameInternalType(name);
mSv.visitClassType(name);
}
public void visitInnerClassType(String name) {
name = renameInternalType(name);
mSv.visitInnerClassType(name);
}
public SignatureVisitor visitArrayType() {
SignatureVisitor sv = mSv.visitArrayType();
return new RenameSignatureAdapter(sv);
}
public void visitBaseType(char descriptor) {
mSv.visitBaseType(descriptor);
}
public SignatureVisitor visitClassBound() {
SignatureVisitor sv = mSv.visitClassBound();
return new RenameSignatureAdapter(sv);
}
public void visitEnd() {
mSv.visitEnd();
}
public SignatureVisitor visitExceptionType() {
SignatureVisitor sv = mSv.visitExceptionType();
return new RenameSignatureAdapter(sv);
}
public void visitFormalTypeParameter(String name) {
mSv.visitFormalTypeParameter(name);
}
public SignatureVisitor visitInterface() {
SignatureVisitor sv = mSv.visitInterface();
return new RenameSignatureAdapter(sv);
}
public SignatureVisitor visitInterfaceBound() {
SignatureVisitor sv = mSv.visitInterfaceBound();
return new RenameSignatureAdapter(sv);
}
public SignatureVisitor visitParameterType() {
SignatureVisitor sv = mSv.visitParameterType();
return new RenameSignatureAdapter(sv);
}
public SignatureVisitor visitReturnType() {
SignatureVisitor sv = mSv.visitReturnType();
return new RenameSignatureAdapter(sv);
}
public SignatureVisitor visitSuperclass() {
SignatureVisitor sv = mSv.visitSuperclass();
return new RenameSignatureAdapter(sv);
}
public void visitTypeArgument() {
mSv.visitTypeArgument();
}
public SignatureVisitor visitTypeArgument(char wildcard) {
SignatureVisitor sv = mSv.visitTypeArgument(wildcard);
return new RenameSignatureAdapter(sv);
}
public void visitTypeVariable(String name) {
mSv.visitTypeVariable(name);
}
}
}

View File

@ -0,0 +1,350 @@
/*
* Copyright (C) 2008 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.Attribute;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
/**
* This method adapter rewrites a method by discarding the original code and generating
* a stub depending on the return type. Original annotations are passed along unchanged.
*/
class StubMethodAdapter implements MethodVisitor {
private static String CONSTRUCTOR = "<init>";
private static String CLASS_INIT = "<clinit>";
/** The parent method writer */
private MethodVisitor mParentVisitor;
/** The method return type. Can be null. */
private Type mReturnType;
/** Message to be printed by stub methods. */
private String mInvokeSignature;
/** Flag to output the first line number. */
private boolean mOutputFirstLineNumber = true;
/** Flag that is true when implementing a constructor, to accept all original
* code calling the original super constructor. */
private boolean mIsInitMethod = false;
private boolean mMessageGenerated;
private final boolean mIsStatic;
private final boolean mIsNative;
public StubMethodAdapter(MethodVisitor mv, String methodName, Type returnType,
String invokeSignature, boolean isStatic, boolean isNative) {
mParentVisitor = mv;
mReturnType = returnType;
mInvokeSignature = invokeSignature;
mIsStatic = isStatic;
mIsNative = isNative;
if (CONSTRUCTOR.equals(methodName) || CLASS_INIT.equals(methodName)) {
mIsInitMethod = true;
}
}
private void generateInvoke() {
/* Generates the code:
* OverrideMethod.invoke("signature", mIsNative ? true : false, null or this);
*/
mParentVisitor.visitLdcInsn(mInvokeSignature);
// push true or false
mParentVisitor.visitInsn(mIsNative ? Opcodes.ICONST_1 : Opcodes.ICONST_0);
// push null or this
if (mIsStatic) {
mParentVisitor.visitInsn(Opcodes.ACONST_NULL);
} else {
mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0);
}
int sort = mReturnType != null ? mReturnType.getSort() : Type.VOID;
switch(sort) {
case Type.VOID:
mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
"com/android/tools/layoutlib/create/OverrideMethod",
"invokeV",
"(Ljava/lang/String;ZLjava/lang/Object;)V");
mParentVisitor.visitInsn(Opcodes.RETURN);
break;
case Type.BOOLEAN:
case Type.CHAR:
case Type.BYTE:
case Type.SHORT:
case Type.INT:
mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
"com/android/tools/layoutlib/create/OverrideMethod",
"invokeI",
"(Ljava/lang/String;ZLjava/lang/Object;)I");
switch(sort) {
case Type.BOOLEAN:
Label l1 = new Label();
mParentVisitor.visitJumpInsn(Opcodes.IFEQ, l1);
mParentVisitor.visitInsn(Opcodes.ICONST_1);
mParentVisitor.visitInsn(Opcodes.IRETURN);
mParentVisitor.visitLabel(l1);
mParentVisitor.visitInsn(Opcodes.ICONST_0);
break;
case Type.CHAR:
mParentVisitor.visitInsn(Opcodes.I2C);
break;
case Type.BYTE:
mParentVisitor.visitInsn(Opcodes.I2B);
break;
case Type.SHORT:
mParentVisitor.visitInsn(Opcodes.I2S);
break;
}
mParentVisitor.visitInsn(Opcodes.IRETURN);
break;
case Type.LONG:
mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
"com/android/tools/layoutlib/create/OverrideMethod",
"invokeL",
"(Ljava/lang/String;ZLjava/lang/Object;)J");
mParentVisitor.visitInsn(Opcodes.LRETURN);
break;
case Type.FLOAT:
mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
"com/android/tools/layoutlib/create/OverrideMethod",
"invokeF",
"(Ljava/lang/String;ZLjava/lang/Object;)F");
mParentVisitor.visitInsn(Opcodes.FRETURN);
break;
case Type.DOUBLE:
mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
"com/android/tools/layoutlib/create/OverrideMethod",
"invokeD",
"(Ljava/lang/String;ZLjava/lang/Object;)D");
mParentVisitor.visitInsn(Opcodes.DRETURN);
break;
case Type.ARRAY:
case Type.OBJECT:
mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
"com/android/tools/layoutlib/create/OverrideMethod",
"invokeA",
"(Ljava/lang/String;ZLjava/lang/Object;)Ljava/lang/Object;");
mParentVisitor.visitTypeInsn(Opcodes.CHECKCAST, mReturnType.getInternalName());
mParentVisitor.visitInsn(Opcodes.ARETURN);
break;
}
}
private void generatePop() {
/* Pops the stack, depending on the return type.
*/
switch(mReturnType != null ? mReturnType.getSort() : Type.VOID) {
case Type.VOID:
break;
case Type.BOOLEAN:
case Type.CHAR:
case Type.BYTE:
case Type.SHORT:
case Type.INT:
case Type.FLOAT:
case Type.ARRAY:
case Type.OBJECT:
mParentVisitor.visitInsn(Opcodes.POP);
break;
case Type.LONG:
case Type.DOUBLE:
mParentVisitor.visitInsn(Opcodes.POP2);
break;
}
}
/* Pass down to visitor writer. In this implementation, either do nothing. */
public void visitCode() {
mParentVisitor.visitCode();
}
/*
* visitMaxs is called just before visitEnd if there was any code to rewrite.
* For non-constructor, generate the messaging code and the return statement
* if it hasn't been done before.
*/
public void visitMaxs(int maxStack, int maxLocals) {
if (!mIsInitMethod && !mMessageGenerated) {
generateInvoke();
mMessageGenerated = true;
}
mParentVisitor.visitMaxs(maxStack, maxLocals);
}
/**
* End of visiting.
* For non-constructor, generate the messaging code and the return statement
* if it hasn't been done before.
*/
public void visitEnd() {
if (!mIsInitMethod && !mMessageGenerated) {
generateInvoke();
mMessageGenerated = true;
mParentVisitor.visitMaxs(1, 1);
}
mParentVisitor.visitEnd();
}
/* Writes all annotation from the original method. */
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
return mParentVisitor.visitAnnotation(desc, visible);
}
/* Writes all annotation default values from the original method. */
public AnnotationVisitor visitAnnotationDefault() {
return mParentVisitor.visitAnnotationDefault();
}
public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
boolean visible) {
return mParentVisitor.visitParameterAnnotation(parameter, desc, visible);
}
/* Writes all attributes from the original method. */
public void visitAttribute(Attribute attr) {
mParentVisitor.visitAttribute(attr);
}
/*
* Only writes the first line number present in the original code so that source
* viewers can direct to the correct method, even if the content doesn't match.
*/
public void visitLineNumber(int line, Label start) {
if (mIsInitMethod || mOutputFirstLineNumber) {
mParentVisitor.visitLineNumber(line, start);
mOutputFirstLineNumber = false;
}
}
/**
* For non-constructor, rewrite existing "return" instructions to write the message.
*/
public void visitInsn(int opcode) {
if (mIsInitMethod) {
switch (opcode) {
case Opcodes.RETURN:
case Opcodes.ARETURN:
case Opcodes.DRETURN:
case Opcodes.FRETURN:
case Opcodes.IRETURN:
case Opcodes.LRETURN:
// Pop the last word from the stack since invoke will generate its own return.
generatePop();
generateInvoke();
mMessageGenerated = true;
default:
mParentVisitor.visitInsn(opcode);
}
}
}
public void visitLabel(Label label) {
if (mIsInitMethod) {
mParentVisitor.visitLabel(label);
}
}
public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
if (mIsInitMethod) {
mParentVisitor.visitTryCatchBlock(start, end, handler, type);
}
}
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
if (mIsInitMethod) {
mParentVisitor.visitMethodInsn(opcode, owner, name, desc);
}
}
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
if (mIsInitMethod) {
mParentVisitor.visitFieldInsn(opcode, owner, name, desc);
}
}
public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
if (mIsInitMethod) {
mParentVisitor.visitFrame(type, nLocal, local, nStack, stack);
}
}
public void visitIincInsn(int var, int increment) {
if (mIsInitMethod) {
mParentVisitor.visitIincInsn(var, increment);
}
}
public void visitIntInsn(int opcode, int operand) {
if (mIsInitMethod) {
mParentVisitor.visitIntInsn(opcode, operand);
}
}
public void visitJumpInsn(int opcode, Label label) {
if (mIsInitMethod) {
mParentVisitor.visitJumpInsn(opcode, label);
}
}
public void visitLdcInsn(Object cst) {
if (mIsInitMethod) {
mParentVisitor.visitLdcInsn(cst);
}
}
public void visitLocalVariable(String name, String desc, String signature,
Label start, Label end, int index) {
if (mIsInitMethod) {
mParentVisitor.visitLocalVariable(name, desc, signature, start, end, index);
}
}
public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
if (mIsInitMethod) {
mParentVisitor.visitLookupSwitchInsn(dflt, keys, labels);
}
}
public void visitMultiANewArrayInsn(String desc, int dims) {
if (mIsInitMethod) {
mParentVisitor.visitMultiANewArrayInsn(desc, dims);
}
}
public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
if (mIsInitMethod) {
mParentVisitor.visitTableSwitchInsn(min, max, dflt, labels);
}
}
public void visitTypeInsn(int opcode, String type) {
if (mIsInitMethod) {
mParentVisitor.visitTypeInsn(opcode, type);
}
}
public void visitVarInsn(int opcode, int var) {
if (mIsInitMethod) {
mParentVisitor.visitVarInsn(opcode, var);
}
}
}

View File

@ -0,0 +1,177 @@
/*
* Copyright (C) 2008 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.ClassAdapter;
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.util.Set;
/**
* Class adapter that can stub some or all of the methods of the class.
*/
class TransformClassAdapter extends ClassAdapter {
/** True if all methods should be stubbed, false if only native ones must be stubbed. */
private final boolean mStubAll;
/** True if the class is an interface. */
private boolean mIsInterface;
private final String mClassName;
private final Log mLog;
private final Set<String> mStubMethods;
private Set<String> mDeleteReturns;
/**
* Creates a new class adapter that will stub some or all methods.
* @param logger
* @param stubMethods list of method signatures to always stub out
* @param deleteReturns list of types that trigger the deletion of methods returning them.
* @param className The name of the class being modified
* @param cv The parent class writer visitor
* @param stubNativesOnly True if only native methods should be stubbed. False if all
* methods should be stubbed.
* @param hasNative True if the method has natives, in which case its access should be
* changed.
*/
public TransformClassAdapter(Log logger, Set<String> stubMethods,
Set<String> deleteReturns, String className, ClassVisitor cv,
boolean stubNativesOnly, boolean hasNative) {
super(cv);
mLog = logger;
mStubMethods = stubMethods;
mClassName = className;
mStubAll = !stubNativesOnly;
mIsInterface = false;
mDeleteReturns = deleteReturns;
}
/* Visits the class header. */
@Override
public void visit(int version, int access, String name,
String signature, String superName, String[] interfaces) {
// This class might be being renamed.
name = mClassName;
// remove protected or private and set as public
access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
access |= Opcodes.ACC_PUBLIC;
// remove final
access = access & ~Opcodes.ACC_FINAL;
// note: leave abstract classes as such
// don't try to implement stub for interfaces
mIsInterface = ((access & Opcodes.ACC_INTERFACE) != 0);
super.visit(version, access, name, signature, superName, interfaces);
}
/* Visits the header of an inner class. */
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
// remove protected or private and set as public
access = access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
access |= Opcodes.ACC_PUBLIC;
// remove final
access = access & ~Opcodes.ACC_FINAL;
// note: leave abstract classes as such
// don't try to implement stub for interfaces
super.visitInnerClass(name, outerName, innerName, access);
}
/* Visits a method. */
@Override
public MethodVisitor visitMethod(int access, String name, String desc,
String signature, String[] exceptions) {
if (mDeleteReturns != null) {
Type t = Type.getReturnType(desc);
if (t.getSort() == Type.OBJECT) {
String returnType = t.getInternalName();
if (returnType != null) {
if (mDeleteReturns.contains(returnType)) {
return null;
}
}
}
}
String methodSignature = mClassName.replace('/', '.') + "#" + name;
// change access to public
access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
access |= Opcodes.ACC_PUBLIC;
// remove final
access = access & ~Opcodes.ACC_FINAL;
// stub this method if they are all to be stubbed or if it is a native method
// and don't try to stub interfaces nor abstract non-native methods.
if (!mIsInterface &&
((access & (Opcodes.ACC_ABSTRACT | Opcodes.ACC_NATIVE)) != Opcodes.ACC_ABSTRACT) &&
(mStubAll ||
(access & Opcodes.ACC_NATIVE) != 0) ||
mStubMethods.contains(methodSignature)) {
boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
boolean isNative = (access & Opcodes.ACC_NATIVE) != 0;
// remove abstract, final and native
access = access & ~(Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL | Opcodes.ACC_NATIVE);
String invokeSignature = methodSignature + desc;
mLog.debug(" Stub: %s (%s)", invokeSignature, isNative ? "native" : "");
MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
return new StubMethodAdapter(mw, name, returnType(desc), invokeSignature,
isStatic, isNative);
} else {
mLog.debug(" Keep: %s %s", name, desc);
return super.visitMethod(access, name, desc, signature, exceptions);
}
}
/* Visits a field. Makes it public. */
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature,
Object value) {
// change access to public
access &= ~(Opcodes.ACC_PROTECTED | Opcodes.ACC_PRIVATE);
access |= Opcodes.ACC_PUBLIC;
return super.visitField(access, name, desc, signature, value);
}
/**
* Extracts the return {@link Type} of this descriptor.
*/
Type returnType(String desc) {
if (desc != null) {
try {
return Type.getReturnType(desc);
} catch (ArrayIndexOutOfBoundsException e) {
// ignore, not a valid type.
}
}
return null;
}
}

View File

@ -0,0 +1,227 @@
/*
* Copyright (C) 2008 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 static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import com.android.tools.layoutlib.create.AsmAnalyzer.DependencyVisitor;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.objectweb.asm.ClassReader;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;
/**
* Unit tests for some methods of {@link AsmAnalyzer}.
*/
public class AsmAnalyzerTest {
private MockLog mLog;
private ArrayList<String> mOsJarPath;
private AsmAnalyzer mAa;
@Before
public void setUp() throws Exception {
mLog = new MockLog();
URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
mOsJarPath = new ArrayList<String>();
mOsJarPath.add(url.getFile());
mAa = new AsmAnalyzer(mLog, mOsJarPath, null /* gen */,
null /* deriveFrom */, null /* includeGlobs */ );
}
@After
public void tearDown() throws Exception {
}
@Test
public void testParseZip() throws IOException {
Map<String, ClassReader> map = mAa.parseZip(mOsJarPath);
assertArrayEquals(new String[] {
"mock_android.dummy.InnerTest",
"mock_android.dummy.InnerTest$DerivingClass",
"mock_android.dummy.InnerTest$MyGenerics1",
"mock_android.dummy.InnerTest$MyIntEnum",
"mock_android.dummy.InnerTest$MyStaticInnerClass",
"mock_android.dummy.InnerTest$NotStaticInner1",
"mock_android.dummy.InnerTest$NotStaticInner2",
"mock_android.view.View",
"mock_android.view.ViewGroup",
"mock_android.view.ViewGroup$LayoutParams",
"mock_android.view.ViewGroup$MarginLayoutParams",
"mock_android.widget.LinearLayout",
"mock_android.widget.LinearLayout$LayoutParams",
"mock_android.widget.TableLayout",
"mock_android.widget.TableLayout$LayoutParams"
},
map.keySet().toArray());
}
@Test
public void testFindClass() throws IOException, LogAbortException {
Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
ClassReader cr = mAa.findClass("mock_android.view.ViewGroup$LayoutParams",
zipClasses, found);
assertNotNull(cr);
assertEquals("mock_android/view/ViewGroup$LayoutParams", cr.getClassName());
assertArrayEquals(new String[] { "mock_android.view.ViewGroup$LayoutParams" },
found.keySet().toArray());
assertArrayEquals(new ClassReader[] { cr }, found.values().toArray());
}
@Test
public void testFindGlobs() throws IOException, LogAbortException {
Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
// this matches classes, a package match returns nothing
found.clear();
mAa.findGlobs("mock_android.view", zipClasses, found);
assertArrayEquals(new String[] { },
found.keySet().toArray());
// a complex glob search. * is a search pattern that matches names, not dots
mAa.findGlobs("mock_android.*.*Group$*Layout*", zipClasses, found);
assertArrayEquals(new String[] {
"mock_android.view.ViewGroup$LayoutParams",
"mock_android.view.ViewGroup$MarginLayoutParams"
},
found.keySet().toArray());
// a complex glob search. ** is a search pattern that matches names including dots
mAa.findGlobs("mock_android.**Group*", zipClasses, found);
assertArrayEquals(new String[] {
"mock_android.view.ViewGroup",
"mock_android.view.ViewGroup$LayoutParams",
"mock_android.view.ViewGroup$MarginLayoutParams"
},
found.keySet().toArray());
// matches a single class
found.clear();
mAa.findGlobs("mock_android.view.View", zipClasses, found);
assertArrayEquals(new String[] {
"mock_android.view.View"
},
found.keySet().toArray());
// matches everyting inside the given package but not sub-packages
found.clear();
mAa.findGlobs("mock_android.view.*", zipClasses, found);
assertArrayEquals(new String[] {
"mock_android.view.View",
"mock_android.view.ViewGroup",
"mock_android.view.ViewGroup$LayoutParams",
"mock_android.view.ViewGroup$MarginLayoutParams"
},
found.keySet().toArray());
for (String key : found.keySet()) {
ClassReader value = found.get(key);
assertNotNull("No value for " + key, value);
assertEquals(key, AsmAnalyzer.classReaderToClassName(value));
}
}
@Test
public void testFindClassesDerivingFrom() throws LogAbortException, IOException {
Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
mAa.findClassesDerivingFrom("mock_android.view.View", zipClasses, found);
assertArrayEquals(new String[] {
"mock_android.view.View",
"mock_android.view.ViewGroup",
"mock_android.widget.LinearLayout",
"mock_android.widget.TableLayout",
},
found.keySet().toArray());
for (String key : found.keySet()) {
ClassReader value = found.get(key);
assertNotNull("No value for " + key, value);
assertEquals(key, AsmAnalyzer.classReaderToClassName(value));
}
}
@Test
public void testDependencyVisitor() throws IOException, LogAbortException {
Map<String, ClassReader> zipClasses = mAa.parseZip(mOsJarPath);
TreeMap<String, ClassReader> keep = new TreeMap<String, ClassReader>();
TreeMap<String, ClassReader> new_keep = new TreeMap<String, ClassReader>();
TreeMap<String, ClassReader> in_deps = new TreeMap<String, ClassReader>();
TreeMap<String, ClassReader> out_deps = new TreeMap<String, ClassReader>();
ClassReader cr = mAa.findClass("mock_android.widget.TableLayout", zipClasses, keep);
DependencyVisitor visitor = mAa.getVisitor(zipClasses, keep, new_keep, in_deps, out_deps);
// get first level dependencies
cr.accept(visitor, 0 /* flags */);
assertArrayEquals(new String[] {
"mock_android.view.ViewGroup",
"mock_android.widget.TableLayout$LayoutParams",
},
out_deps.keySet().toArray());
in_deps.putAll(out_deps);
out_deps.clear();
// get second level dependencies
for (ClassReader cr2 : in_deps.values()) {
cr2.accept(visitor, 0 /* flags */);
}
assertArrayEquals(new String[] {
"mock_android.view.View",
"mock_android.view.ViewGroup$LayoutParams",
"mock_android.view.ViewGroup$MarginLayoutParams",
},
out_deps.keySet().toArray());
in_deps.putAll(out_deps);
out_deps.clear();
// get third level dependencies (there are none)
for (ClassReader cr2 : in_deps.values()) {
cr2.accept(visitor, 0 /* flags */);
}
assertArrayEquals(new String[] { }, out_deps.keySet().toArray());
}
}

View File

@ -0,0 +1,113 @@
/*
* Copyright (C) 2008 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 static org.junit.Assert.assertArrayEquals;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Set;
/**
* Unit tests for some methods of {@link AsmGenerator}.
*/
public class AsmGeneratorTest {
private MockLog mLog;
private ArrayList<String> mOsJarPath;
private String mOsDestJar;
private File mTempFile;
@Before
public void setUp() throws Exception {
mLog = new MockLog();
URL url = this.getClass().getClassLoader().getResource("data/mock_android.jar");
mOsJarPath = new ArrayList<String>();
mOsJarPath.add(url.getFile());
mTempFile = File.createTempFile("mock", "jar");
mOsDestJar = mTempFile.getAbsolutePath();
mTempFile.deleteOnExit();
}
@After
public void tearDown() throws Exception {
if (mTempFile != null) {
mTempFile.delete();
mTempFile = null;
}
}
@Test
public void testClassRenaming() throws IOException, LogAbortException {
ICreateInfo ci = new ICreateInfo() {
public Class<?>[] getInjectedClasses() {
// classes to inject in the final JAR
return new Class<?>[0];
}
public String[] getDelegateMethods() {
return new String[0];
}
public String[] getDelegateClassNatives() {
return new String[0];
}
public String[] getOverriddenMethods() {
// methods to force override
return new String[0];
}
public String[] getRenamedClasses() {
// classes to rename (so that we can replace them)
return new String[] {
"mock_android.view.View", "mock_android.view._Original_View",
"not.an.actual.ClassName", "anoter.fake.NewClassName",
};
}
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
"**"
});
aa.analyze();
agen.generate();
Set<String> notRenamed = agen.getClassesNotRenamed();
assertArrayEquals(new String[] { "not/an/actual/ClassName" }, notRenamed.toArray());
}
}

View File

@ -0,0 +1,102 @@
/*
* Copyright (C) 2010 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 static org.junit.Assert.*;
import org.junit.Test;
import org.objectweb.asm.ClassReader;
import java.io.IOException;
import java.util.ArrayList;
/**
* Tests {@link ClassHasNativeVisitor}.
*/
public class ClassHasNativeVisitorTest {
@Test
public void testHasNative() throws IOException {
MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor();
String className =
this.getClass().getCanonicalName() + "$" + ClassWithNative.class.getSimpleName();
ClassReader cr = new ClassReader(className);
cr.accept(cv, 0 /* flags */);
assertArrayEquals(new String[] { "native_method" }, cv.getMethodsFound());
assertTrue(cv.hasNativeMethods());
}
@Test
public void testHasNoNative() throws IOException {
MockClassHasNativeVisitor cv = new MockClassHasNativeVisitor();
String className =
this.getClass().getCanonicalName() + "$" + ClassWithoutNative.class.getSimpleName();
ClassReader cr = new ClassReader(className);
cr.accept(cv, 0 /* flags */);
assertArrayEquals(new String[0], cv.getMethodsFound());
assertFalse(cv.hasNativeMethods());
}
//-------
/**
* Overrides {@link ClassHasNativeVisitor} to collec the name of the native methods found.
*/
private static class MockClassHasNativeVisitor extends ClassHasNativeVisitor {
private ArrayList<String> mMethodsFound = new ArrayList<String>();
public String[] getMethodsFound() {
return mMethodsFound.toArray(new String[mMethodsFound.size()]);
}
@Override
protected void setHasNativeMethods(boolean hasNativeMethods, String methodName) {
if (hasNativeMethods) {
mMethodsFound.add(methodName);
}
super.setHasNativeMethods(hasNativeMethods, methodName);
}
}
/**
* Dummy test class with a native method.
*/
public static class ClassWithNative {
public ClassWithNative() {
}
public void callTheNativeMethod() {
native_method();
}
private native void native_method();
}
/**
* Dummy test class with no native method.
*/
public static class ClassWithoutNative {
public ClassWithoutNative() {
}
public void someMethod() {
}
}
}

View File

@ -0,0 +1,409 @@
/*
* Copyright (C) 2010 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 static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tools.layoutlib.create.dataclass.ClassWithNative;
import com.android.tools.layoutlib.create.dataclass.OuterClass;
import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass;
import org.junit.Before;
import org.junit.Test;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
public class DelegateClassAdapterTest {
private MockLog mLog;
private static final String NATIVE_CLASS_NAME = ClassWithNative.class.getCanonicalName();
private static final String OUTER_CLASS_NAME = OuterClass.class.getCanonicalName();
private static final String INNER_CLASS_NAME = OuterClass.class.getCanonicalName() + "$" +
InnerClass.class.getSimpleName();
@Before
public void setUp() throws Exception {
mLog = new MockLog();
mLog.setVerbose(true); // capture debug error too
}
/**
* Tests that a class not being modified still works.
*/
@SuppressWarnings("unchecked")
@Test
public void testNoOp() throws Throwable {
// create an instance of the class that will be modified
// (load the class in a distinct class loader so that we can trash its definition later)
ClassLoader cl1 = new ClassLoader(this.getClass().getClassLoader()) { };
Class<ClassWithNative> clazz1 = (Class<ClassWithNative>) cl1.loadClass(NATIVE_CLASS_NAME);
ClassWithNative instance1 = clazz1.newInstance();
assertEquals(42, instance1.add(20, 22));
try {
instance1.callNativeInstance(10, 3.1415, new Object[0] );
fail("Test should have failed to invoke callTheNativeMethod [1]");
} catch (UnsatisfiedLinkError e) {
// This is expected to fail since the native method is not implemented.
}
// Now process it but tell the delegate to not modify any method
ClassWriter cw = new ClassWriter(0 /*flags*/);
HashSet<String> delegateMethods = new HashSet<String>();
String internalClassName = NATIVE_CLASS_NAME.replace('.', '/');
DelegateClassAdapter cv = new DelegateClassAdapter(
mLog, cw, internalClassName, delegateMethods);
ClassReader cr = new ClassReader(NATIVE_CLASS_NAME);
cr.accept(cv, 0 /* flags */);
// Load the generated class in a different class loader and try it again
ClassLoader2 cl2 = null;
try {
cl2 = new ClassLoader2() {
@Override
public void testModifiedInstance() throws Exception {
Class<?> clazz2 = loadClass(NATIVE_CLASS_NAME);
Object i2 = clazz2.newInstance();
assertNotNull(i2);
assertEquals(42, callAdd(i2, 20, 22));
try {
callCallNativeInstance(i2, 10, 3.1415, new Object[0]);
fail("Test should have failed to invoke callTheNativeMethod [2]");
} catch (InvocationTargetException e) {
// This is expected to fail since the native method has NOT been
// overridden here.
assertEquals(UnsatisfiedLinkError.class, e.getCause().getClass());
}
// Check that the native method does NOT have the new annotation
Method[] m = clazz2.getDeclaredMethods();
assertEquals("native_instance", m[2].getName());
assertTrue(Modifier.isNative(m[2].getModifiers()));
Annotation[] a = m[2].getAnnotations();
assertEquals(0, a.length);
}
};
cl2.add(NATIVE_CLASS_NAME, cw);
cl2.testModifiedInstance();
} catch (Throwable t) {
throw dumpGeneratedClass(t, cl2);
}
}
/**
* {@link DelegateMethodAdapter} does not support overriding constructors yet,
* so this should fail with an {@link UnsupportedOperationException}.
*
* Although not tested here, the message of the exception should contain the
* constructor signature.
*/
@Test(expected=UnsupportedOperationException.class)
public void testConstructorsNotSupported() throws IOException {
ClassWriter cw = new ClassWriter(0 /*flags*/);
String internalClassName = NATIVE_CLASS_NAME.replace('.', '/');
HashSet<String> delegateMethods = new HashSet<String>();
delegateMethods.add("<init>");
DelegateClassAdapter cv = new DelegateClassAdapter(
mLog, cw, internalClassName, delegateMethods);
ClassReader cr = new ClassReader(NATIVE_CLASS_NAME);
cr.accept(cv, 0 /* flags */);
}
@Test
public void testDelegateNative() throws Throwable {
ClassWriter cw = new ClassWriter(0 /*flags*/);
String internalClassName = NATIVE_CLASS_NAME.replace('.', '/');
HashSet<String> delegateMethods = new HashSet<String>();
delegateMethods.add(DelegateClassAdapter.ALL_NATIVES);
DelegateClassAdapter cv = new DelegateClassAdapter(
mLog, cw, internalClassName, delegateMethods);
ClassReader cr = new ClassReader(NATIVE_CLASS_NAME);
cr.accept(cv, 0 /* flags */);
// Load the generated class in a different class loader and try it
ClassLoader2 cl2 = null;
try {
cl2 = new ClassLoader2() {
@Override
public void testModifiedInstance() throws Exception {
Class<?> clazz2 = loadClass(NATIVE_CLASS_NAME);
Object i2 = clazz2.newInstance();
assertNotNull(i2);
// Use reflection to access inner methods
assertEquals(42, callAdd(i2, 20, 22));
Object[] objResult = new Object[] { null };
int result = callCallNativeInstance(i2, 10, 3.1415, objResult);
assertEquals((int)(10 + 3.1415), result);
assertSame(i2, objResult[0]);
// Check that the native method now has the new annotation and is not native
Method[] m = clazz2.getDeclaredMethods();
assertEquals("native_instance", m[2].getName());
assertFalse(Modifier.isNative(m[2].getModifiers()));
Annotation[] a = m[2].getAnnotations();
assertEquals("LayoutlibDelegate", a[0].annotationType().getSimpleName());
}
};
cl2.add(NATIVE_CLASS_NAME, cw);
cl2.testModifiedInstance();
} catch (Throwable t) {
throw dumpGeneratedClass(t, cl2);
}
}
@Test
public void testDelegateInner() throws Throwable {
// We'll delegate the "get" method of both the inner and outer class.
HashSet<String> delegateMethods = new HashSet<String>();
delegateMethods.add("get");
// Generate the delegate for the outer class.
ClassWriter cwOuter = new ClassWriter(0 /*flags*/);
String outerClassName = OUTER_CLASS_NAME.replace('.', '/');
DelegateClassAdapter cvOuter = new DelegateClassAdapter(
mLog, cwOuter, outerClassName, delegateMethods);
ClassReader cr = new ClassReader(OUTER_CLASS_NAME);
cr.accept(cvOuter, 0 /* flags */);
// Generate the delegate for the inner class.
ClassWriter cwInner = new ClassWriter(0 /*flags*/);
String innerClassName = INNER_CLASS_NAME.replace('.', '/');
DelegateClassAdapter cvInner = new DelegateClassAdapter(
mLog, cwInner, innerClassName, delegateMethods);
cr = new ClassReader(INNER_CLASS_NAME);
cr.accept(cvInner, 0 /* flags */);
// Load the generated classes in a different class loader and try them
ClassLoader2 cl2 = null;
try {
cl2 = new ClassLoader2() {
@Override
public void testModifiedInstance() throws Exception {
// Check the outer class
Class<?> outerClazz2 = loadClass(OUTER_CLASS_NAME);
Object o2 = outerClazz2.newInstance();
assertNotNull(o2);
// The original Outer.get returns 1+10+20,
// but the delegate makes it return 4+10+20
assertEquals(4+10+20, callGet(o2, 10, 20));
// Check the inner class. Since it's not a static inner class, we need
// to use the hidden constructor that takes the outer class as first parameter.
Class<?> innerClazz2 = loadClass(INNER_CLASS_NAME);
Constructor<?> innerCons = innerClazz2.getConstructor(
new Class<?>[] { outerClazz2 });
Object i2 = innerCons.newInstance(new Object[] { o2 });
assertNotNull(i2);
// The original Inner.get returns 3+10+20,
// but the delegate makes it return 6+10+20
assertEquals(6+10+20, callGet(i2, 10, 20));
}
};
cl2.add(OUTER_CLASS_NAME, cwOuter.toByteArray());
cl2.add(INNER_CLASS_NAME, cwInner.toByteArray());
cl2.testModifiedInstance();
} catch (Throwable t) {
throw dumpGeneratedClass(t, cl2);
}
}
//-------
/**
* A class loader than can define and instantiate our modified classes.
* <p/>
* The trick here is that this class loader will test our <em>modified</em> version
* of the classes, the one with the delegate calls.
* <p/>
* Trying to do so in the original class loader generates all sort of link issues because
* there are 2 different definitions of the same class name. This class loader will
* define and load the class when requested by name and provide helpers to access the
* instance methods via reflection.
*/
private abstract class ClassLoader2 extends ClassLoader {
private final Map<String, byte[]> mClassDefs = new HashMap<String, byte[]>();
public ClassLoader2() {
super(null);
}
public ClassLoader2 add(String className, byte[] definition) {
mClassDefs.put(className, definition);
return this;
}
public ClassLoader2 add(String className, ClassWriter rewrittenClass) {
mClassDefs.put(className, rewrittenClass.toByteArray());
return this;
}
private Set<Entry<String, byte[]>> getByteCode() {
return mClassDefs.entrySet();
}
@SuppressWarnings("unused")
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
return super.findClass(name);
} catch (ClassNotFoundException e) {
byte[] def = mClassDefs.get(name);
if (def != null) {
// Load the modified ClassWithNative from its bytes representation.
return defineClass(name, def, 0, def.length);
}
try {
// Load everything else from the original definition into the new class loader.
ClassReader cr = new ClassReader(name);
ClassWriter cw = new ClassWriter(0);
cr.accept(cw, 0);
byte[] bytes = cw.toByteArray();
return defineClass(name, bytes, 0, bytes.length);
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}
}
/**
* Accesses {@link OuterClass#get()} or {@link InnerClass#get() }via reflection.
*/
public int callGet(Object instance, int a, long b) throws Exception {
Method m = instance.getClass().getMethod("get",
new Class<?>[] { int.class, long.class } );
Object result = m.invoke(instance, new Object[] { a, b });
return ((Integer) result).intValue();
}
/**
* Accesses {@link ClassWithNative#add(int, int)} via reflection.
*/
public int callAdd(Object instance, int a, int b) throws Exception {
Method m = instance.getClass().getMethod("add",
new Class<?>[] { int.class, int.class });
Object result = m.invoke(instance, new Object[] { a, b });
return ((Integer) result).intValue();
}
/**
* Accesses {@link ClassWithNative#callNativeInstance(int, double, Object[])}
* via reflection.
*/
public int callCallNativeInstance(Object instance, int a, double d, Object[] o)
throws Exception {
Method m = instance.getClass().getMethod("callNativeInstance",
new Class<?>[] { int.class, double.class, Object[].class });
Object result = m.invoke(instance, new Object[] { a, d, o });
return ((Integer) result).intValue();
}
public abstract void testModifiedInstance() throws Exception;
}
/**
* For debugging, it's useful to dump the content of the generated classes
* along with the exception that was generated.
*
* However to make it work you need to pull in the org.objectweb.asm.util.TraceClassVisitor
* class and associated utilities which are found in the ASM source jar. Since we don't
* want that dependency in the source code, we only put it manually for development and
* access the TraceClassVisitor via reflection if present.
*
* @param t The exception thrown by {@link ClassLoader2#testModifiedInstance()}
* @param cl2 The {@link ClassLoader2} instance with the generated bytecode.
* @return Either original {@code t} or a new wrapper {@link Throwable}
*/
private Throwable dumpGeneratedClass(Throwable t, ClassLoader2 cl2) {
try {
// For debugging, dump the bytecode of the class in case of unexpected error
// if we can find the TraceClassVisitor class.
Class<?> tcvClass = Class.forName("org.objectweb.asm.util.TraceClassVisitor");
StringBuilder sb = new StringBuilder();
sb.append('\n').append(t.getClass().getCanonicalName());
if (t.getMessage() != null) {
sb.append(": ").append(t.getMessage());
}
for (Entry<String, byte[]> entry : cl2.getByteCode()) {
String className = entry.getKey();
byte[] bytes = entry.getValue();
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
// next 2 lines do: TraceClassVisitor tcv = new TraceClassVisitor(pw);
Constructor<?> cons = tcvClass.getConstructor(new Class<?>[] { pw.getClass() });
Object tcv = cons.newInstance(new Object[] { pw });
ClassReader cr2 = new ClassReader(bytes);
cr2.accept((ClassVisitor) tcv, 0 /* flags */);
sb.append("\nBytecode dump: <").append(className).append(">:\n")
.append(sw.toString());
}
// Re-throw exception with new message
RuntimeException ex = new RuntimeException(sb.toString(), t);
return ex;
} catch (Throwable ignore) {
// In case of problem, just throw the original exception as-is.
return t;
}
}
}

View File

@ -0,0 +1,88 @@
/*
* Copyright (C) 2008 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 static org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class LogTest {
private MockLog mLog;
@Before
public void setUp() throws Exception {
mLog = new MockLog();
}
@After
public void tearDown() throws Exception {
// pass
}
@Test
public void testDebug() {
assertEquals("", mLog.getOut());
assertEquals("", mLog.getErr());
mLog.setVerbose(false);
mLog.debug("Test %d", 42);
assertEquals("", mLog.getOut());
mLog.setVerbose(true);
mLog.debug("Test %d", 42);
assertEquals("Test 42\n", mLog.getOut());
assertEquals("", mLog.getErr());
}
@Test
public void testInfo() {
assertEquals("", mLog.getOut());
assertEquals("", mLog.getErr());
mLog.info("Test %d", 43);
assertEquals("Test 43\n", mLog.getOut());
assertEquals("", mLog.getErr());
}
@Test
public void testError() {
assertEquals("", mLog.getOut());
assertEquals("", mLog.getErr());
mLog.error("Test %d", 44);
assertEquals("", mLog.getOut());
assertEquals("Test 44\n", mLog.getErr());
}
@Test
public void testException() {
assertEquals("", mLog.getOut());
assertEquals("", mLog.getErr());
Exception e = new Exception("My Exception");
mLog.exception(e, "Test %d", 44);
assertEquals("", mLog.getOut());
assertTrue(mLog.getErr().startsWith("Test 44\njava.lang.Exception: My Exception"));
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (C) 2010 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;
public class MockLog extends Log {
StringBuilder mOut = new StringBuilder();
StringBuilder mErr = new StringBuilder();
public String getOut() {
return mOut.toString();
}
public String getErr() {
return mErr.toString();
}
@Override
protected void outPrintln(String msg) {
mOut.append(msg);
mOut.append('\n');
}
@Override
protected void errPrintln(String msg) {
mErr.append(msg);
mErr.append('\n');
}
}

View File

@ -0,0 +1,120 @@
/*
* Copyright (C) 2008 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 static org.junit.Assert.*;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
/**
*
*/
public class RenameClassAdapterTest {
private RenameClassAdapter mOuter;
private RenameClassAdapter mInner;
@Before
public void setUp() throws Exception {
mOuter = new RenameClassAdapter(null, // cv
"com.pack.Old",
"org.blah.New");
mInner = new RenameClassAdapter(null, // cv
"com.pack.Old$Inner",
"org.blah.New$Inner");
}
@After
public void tearDown() throws Exception {
}
/**
* Renames a type, e.g. "Lcom.package.My;"
* If the type doesn't need to be renamed, returns the input string as-is.
*/
@Test
public void testRenameTypeDesc() {
// primitive types are left untouched
assertEquals("I", mOuter.renameTypeDesc("I"));
assertEquals("D", mOuter.renameTypeDesc("D"));
assertEquals("V", mOuter.renameTypeDesc("V"));
// object types that need no renaming are left untouched
assertEquals("Lcom.package.MyClass;", mOuter.renameTypeDesc("Lcom.package.MyClass;"));
assertEquals("Lcom.package.MyClass;", mInner.renameTypeDesc("Lcom.package.MyClass;"));
// object types that match the requirements
assertEquals("Lorg.blah.New;", mOuter.renameTypeDesc("Lcom.pack.Old;"));
assertEquals("Lorg.blah.New$Inner;", mInner.renameTypeDesc("Lcom.pack.Old$Inner;"));
// inner classes match the base type which is being renamed
assertEquals("Lorg.blah.New$Other;", mOuter.renameTypeDesc("Lcom.pack.Old$Other;"));
assertEquals("Lorg.blah.New$Other;", mInner.renameTypeDesc("Lcom.pack.Old$Other;"));
// arrays
assertEquals("[Lorg.blah.New;", mOuter.renameTypeDesc("[Lcom.pack.Old;"));
assertEquals("[[Lorg.blah.New;", mOuter.renameTypeDesc("[[Lcom.pack.Old;"));
assertEquals("[Lorg.blah.New;", mInner.renameTypeDesc("[Lcom.pack.Old;"));
assertEquals("[[Lorg.blah.New;", mInner.renameTypeDesc("[[Lcom.pack.Old;"));
}
/**
* 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.
*/
@Test
public void testRenameType() {
// Skip. This is actually tested by testRenameTypeDesc above.
}
/**
* 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.
*/
@Test
public void testRenameInternalType() {
// a descriptor is not left untouched
assertEquals("Lorg.blah.New;", mOuter.renameInternalType("Lcom.pack.Old;"));
assertEquals("Lorg.blah.New$Inner;", mOuter.renameInternalType("Lcom.pack.Old$Inner;"));
// an actual FQCN
assertEquals("org.blah.New", mOuter.renameInternalType("com.pack.Old"));
assertEquals("org.blah.New$Inner", mOuter.renameInternalType("com.pack.Old$Inner"));
assertEquals("org.blah.New$Other", mInner.renameInternalType("com.pack.Old$Other"));
assertEquals("org.blah.New$Other", mInner.renameInternalType("com.pack.Old$Other"));
}
/**
* Renames a method descriptor, i.e. applies renameType to all arguments and to the
* return value.
*/
@Test
public void testRenameMethodDesc() {
assertEquals("(IDLorg.blah.New;[Lorg.blah.New$Inner;)Lorg.blah.New$Other;",
mOuter.renameMethodDesc("(IDLcom.pack.Old;[Lcom.pack.Old$Inner;)Lcom.pack.Old$Other;"));
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright (C) 2010 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.dataclass;
import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
/**
* Dummy test class with a native method.
* The native method is not defined and any attempt to invoke it will
* throw an {@link UnsatisfiedLinkError}.
*
* Used by {@link DelegateClassAdapterTest}.
*/
public class ClassWithNative {
public ClassWithNative() {
}
public int add(int a, int b) {
return a + b;
}
// Note: it's good to have a long or double for testing parameters since they take
// 2 slots in the stack/locals maps.
public int callNativeInstance(int a, double d, Object[] o) {
return native_instance(a, d, o);
}
private native int native_instance(int a, double d, Object[] o);
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (C) 2010 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.dataclass;
import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
/**
* The delegate that receives the call to {@link ClassWithNative_Delegate}'s overridden methods.
*
* Used by {@link DelegateClassAdapterTest}.
*/
public class ClassWithNative_Delegate {
public static int native_instance(ClassWithNative instance, int a, double d, Object[] o) {
if (o != null && o.length > 0) {
o[0] = instance;
}
return (int)(a + d);
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) 2011 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.dataclass;
import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
/**
* Test class with an inner class.
*
* Used by {@link DelegateClassAdapterTest}.
*/
public class OuterClass {
private int mOuterValue = 1;
public OuterClass() {
}
// Outer.get returns 1 + a + b
// Note: it's good to have a long or double for testing parameters since they take
// 2 slots in the stack/locals maps.
public int get(int a, long b) {
return mOuterValue + a + (int) b;
}
public class InnerClass {
public InnerClass() {
}
// Inner.get returns 1+2=3 + a + b
public int get(int a, long b) {
return 2 + mOuterValue + a + (int) b;
}
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) 2011 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.dataclass;
import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
/**
* Used by {@link DelegateClassAdapterTest}.
*/
public class OuterClass_Delegate {
// The delegate override of Outer.get returns 4 + a + b
public static int get(OuterClass instance, int a, long b) {
return 4 + a + (int) b;
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) 2011 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.dataclass;
import com.android.tools.layoutlib.create.DelegateClassAdapterTest;
import com.android.tools.layoutlib.create.dataclass.OuterClass.InnerClass;
/**
* Used by {@link DelegateClassAdapterTest}.
*/
public class OuterClass_InnerClass_Delegate {
// The delegate override of Inner.get return 6 + a + b
public static int get(OuterClass outer, InnerClass inner, int a, long b) {
return 6 + a + (int) b;
}
}

View File

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

View File

@ -0,0 +1,90 @@
/*
* Copyright (C) 2008 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 mock_android.dummy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class InnerTest {
private int mSomeField;
private MyStaticInnerClass mInnerInstance;
private MyIntEnum mTheIntEnum;
private MyGenerics1<int[][], InnerTest, MyIntEnum, float[]> mGeneric1;
public class NotStaticInner2 extends NotStaticInner1 {
}
public class NotStaticInner1 {
public void someThing() {
mSomeField = 2;
mInnerInstance = null;
}
}
private static class MyStaticInnerClass {
}
private static class DerivingClass extends InnerTest {
}
// enums are a kind of inner static class
public enum MyIntEnum {
VALUE0(0),
VALUE1(1),
VALUE2(2);
MyIntEnum(int myInt) {
this.myInt = myInt;
}
final int myInt;
}
public static class MyGenerics1<T, U, V, W> {
public MyGenerics1() {
int a = 1;
}
}
public <X> void genericMethod1(X a, X[] a) {
}
public <X, Y> void genericMethod2(X a, List<Y> b) {
}
public <X, Y> void genericMethod3(X a, List<Y extends InnerTest> b) {
}
public <T extends InnerTest> void genericMethod4(T[] a, Collection<T> b, Collection<?> c) {
Iterator<T> i = b.iterator();
}
public void someMethod(InnerTest self) {
mSomeField = self.mSomeField;
MyStaticInnerClass m = new MyStaticInnerClass();
mInnerInstance = m;
mTheIntEnum = null;
mGeneric1 = new MyGenerics1();
genericMethod(new DerivingClass[0], new ArrayList<DerivingClass>(), new ArrayList<InnerTest>());
}
}

View File

@ -0,0 +1,21 @@
/*
* Copyright (C) 2008 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 mock_android.view;
public class View {
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (C) 2008 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 mock_android.view;
public class ViewGroup extends View {
public class MarginLayoutParams extends LayoutParams {
}
public class LayoutParams {
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2008 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 mock_android.widget;
import mock_android.view.ViewGroup;
public class LinearLayout extends ViewGroup {
public class LayoutParams extends mock_android.view.ViewGroup.LayoutParams {
}
}

View File

@ -0,0 +1,27 @@
/*
* Copyright (C) 2008 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 mock_android.widget;
import mock_android.view.ViewGroup;
public class TableLayout extends ViewGroup {
public class LayoutParams extends MarginLayoutParams {
}
}