summaryrefslogtreecommitdiffstats
path: root/tools/layoutlib/create
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
committerThe Android Open Source Project <initial-contribution@android.com>2008-10-21 07:00:00 -0700
commit54b6cfa9a9e5b861a9930af873580d6dc20f773c (patch)
tree35051494d2af230dce54d6b31c6af8fc24091316 /tools/layoutlib/create
downloadframeworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.zip
frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.gz
frameworks_base-54b6cfa9a9e5b861a9930af873580d6dc20f773c.tar.bz2
Initial Contribution
Diffstat (limited to 'tools/layoutlib/create')
-rw-r--r--tools/layoutlib/create/.classpath9
-rw-r--r--tools/layoutlib/create/.project17
-rw-r--r--tools/layoutlib/create/Android.mk28
-rw-r--r--tools/layoutlib/create/README.txt71
-rw-r--r--tools/layoutlib/create/manifest.txt1
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java751
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java337
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java80
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/Log.java64
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/LogAbortException.java32
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java172
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java91
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java446
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java294
-rw-r--r--tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java175
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java228
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java90
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java113
-rw-r--r--tools/layoutlib/create/tests/com/android/tools/layoutlib/create/RenameClassAdapterTest.java120
-rw-r--r--tools/layoutlib/create/tests/data/mock_android.jarbin0 -> 9075 bytes
-rw-r--r--tools/layoutlib/create/tests/data/mock_android.jardesc18
-rw-r--r--tools/layoutlib/create/tests/mock_android/dummy/InnerTest.java90
-rw-r--r--tools/layoutlib/create/tests/mock_android/view/View.java21
-rw-r--r--tools/layoutlib/create/tests/mock_android/view/ViewGroup.java29
-rw-r--r--tools/layoutlib/create/tests/mock_android/widget/LinearLayout.java27
-rw-r--r--tools/layoutlib/create/tests/mock_android/widget/TableLayout.java27
26 files changed, 3331 insertions, 0 deletions
diff --git a/tools/layoutlib/create/.classpath b/tools/layoutlib/create/.classpath
new file mode 100644
index 0000000..fecb8d0
--- /dev/null
+++ b/tools/layoutlib/create/.classpath
@@ -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="DEVICE_SRC/prebuilt/common/asm/asm-3.1.jar" sourcepath="/DEVICE_SRC/extlibs/asm-3.1/src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/tools/layoutlib/create/.project b/tools/layoutlib/create/.project
new file mode 100644
index 0000000..e100d17
--- /dev/null
+++ b/tools/layoutlib/create/.project
@@ -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>
diff --git a/tools/layoutlib/create/Android.mk b/tools/layoutlib/create/Android.mk
new file mode 100644
index 0000000..310fae5
--- /dev/null
+++ b/tools/layoutlib/create/Android.mk
@@ -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)
+
diff --git a/tools/layoutlib/create/README.txt b/tools/layoutlib/create/README.txt
new file mode 100644
index 0000000..09b392b
--- /dev/null
+++ b/tools/layoutlib/create/README.txt
@@ -0,0 +1,71 @@
+# Copyright (C) 2008 The Android Open Source Project
+
+
+- Description -
+---------------
+
+makeLayoutLib generates a library used by the Eclipse graphical layout editor
+to perform layout.
+
+
+
+- Usage -
+---------
+
+ ./makeLayoutLib path/to/android.jar destination.jar
+
+
+
+- Implementation Notes -
+------------------------
+
+The goal of makeLayoutLib is to list all the classes from the input jar and create a new
+jar that only keeps certain classes and create stubs for all their dependencies.
+
+First the input jar is parsed to find all the classes defined.
+
+In the Main(), the following list of classes are hardcoded (TODO config file later):
+- keep all classes that derive from android.view.View.
+- keep all classes in the android.view and android.widget packages (sub-packages excluded).
+- keep specific classes such as android.policy.PhoneLayoutInflater.
+
+For each class to keep, their dependencies are examined using BCEL.
+A dependency is defined as a class needed to instantiate the given class that should be kept,
+directly or indirectly. So a dependency is a class that is used by the input class, that is
+defined in the input jar and that is not part of the current JRE.
+
+Dependencies are computed recursively.
+
+Once all dependencies are found, the final jar can be created.
+There are three kind of classes to write:
+- classes that are to be kept as-is. They are just dumped in the new jar unchanged.
+- classes that are to be kept yet contain native methods or fields.
+- classes that are just dependencies. We don't want to expose their implementation in the final
+ jar.
+
+The implementation of native methods and all methods of mock classes is replaced by a stub
+that throws UnsupportedOperationException.
+
+Incidentally, the access level of native and mock classes needs to be changed in order for
+native methods to be later overridden. Methods that are "final private native" must become
+non-final, non-native and at most protected. Package-default access is changed to public.
+Classes that are final are made non-final. Abstract methods are left untouched.
+
+
+
+----
+20080617 Replace Class
+
+Some classes are basically wrappers over native objects.
+Subclassing doesn't work as most methods are either static or we don't
+control object creation. In this scenario the idea is to be able to
+replace classes in the final jar.
+
+Example: android.graphics.Paint would get renamed to OriginalPaint
+in the generated jar. Then in the bridge we'll introduce a replacement
+Paint class that derives from OriginalPaint.
+
+This won't rename/replace the inner static methods of a given class.
+
+
+
diff --git a/tools/layoutlib/create/manifest.txt b/tools/layoutlib/create/manifest.txt
new file mode 100644
index 0000000..238e7f9
--- /dev/null
+++ b/tools/layoutlib/create/manifest.txt
@@ -0,0 +1 @@
+Main-Class: com.android.tools.layoutlib.create.Main
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
new file mode 100644
index 0000000..b197ea7
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmAnalyzer.java
@@ -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);
+ }
+
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
new file mode 100644
index 0000000..f1da42a
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/AsmGenerator.java
@@ -0,0 +1,337 @@
+/*
+ * 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.Set;
+import java.util.TreeMap;
+import java.util.Map.Entry;
+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 => map { list of return types to delete from the FQCN } }. */
+ private HashMap<String, Set<String>> mDeleteReturns;
+
+ /**
+ * 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 stubMethods The list of methods to stub out
+ * @param renameClasses The list of classes to rename, must be an even list: the binary FQCN
+ * of class to replace followed by the new FQCN.
+ * @param deleteReturns 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.
+ */
+ public AsmGenerator(Log log, String osDestJar,
+ Class<?>[] injectClasses,
+ String[] stubMethods,
+ String[] renameClasses, String[] deleteReturns) {
+ mLog = log;
+ mOsDestJar = osDestJar;
+ mInjectClasses = injectClasses != null ? injectClasses : new Class<?>[0];
+ mStubMethods = stubMethods != null ? new HashSet<String>(Arrays.asList(stubMethods)) :
+ new HashSet<String>();
+
+ // Create the map of classes to rename.
+ mRenameClasses = new HashMap<String, String>();
+ mClassesNotRenamed = new HashSet<String>();
+ int n = renameClasses == null ? 0 : 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>>();
+ if (deleteReturns != null) {
+ 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, false /* 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);
+ 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);
+ }
+
+ TransformClassAdapter cv = new TransformClassAdapter(mLog, mStubMethods,
+ mDeleteReturns.get(className),
+ newName, rv,
+ stubNativesOnly, stubNativesOnly || hasNativeMethods);
+ 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();
+ }
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java
new file mode 100644
index 0000000..5424efa
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/ClassHasNativeVisitor.java
@@ -0,0 +1,80 @@
+/*
+ * 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.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;
+ }
+
+ 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) {
+ mHasNativeMethods |= ((access & Opcodes.ACC_NATIVE) != 0);
+ return null;
+ }
+
+ public void visitOuterClass(String owner, String name, String desc) {
+ // pass
+ }
+
+ public void visitSource(String source, String debug) {
+ // pass
+ }
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Log.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Log.java
new file mode 100644
index 0000000..8efd871
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Log.java
@@ -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);
+ }
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/LogAbortException.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/LogAbortException.java
new file mode 100644
index 0000000..dc4b4a7
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/LogAbortException.java
@@ -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);
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
new file mode 100644
index 0000000..9202e44
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/Main.java
@@ -0,0 +1,172 @@
+/*
+ * 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;
+
+
+
+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 Class<?>[] { // classes to inject in the final JAR
+ OverrideMethod.class,
+ OverrideMethod.MethodListener.class
+ },
+ new String[] { // methods to force override
+ "android.content.res.Resources$Theme#obtainStyledAttributes",
+ },
+ new String[] { // classes to rename (so that we can replace them in layoutlib)
+ // original-platform-class-name ======> renamed-class-name
+ "android.graphics.Matrix", "android.graphics._Original_Matrix",
+ "android.graphics.Paint", "android.graphics._Original_Paint",
+ "android.graphics.Typeface", "android.graphics._Original_Typeface",
+ "android.graphics.Bitmap", "android.graphics._Original_Bitmap",
+ "android.graphics.Path", "android.graphics._Original_Path",
+ "android.graphics.PorterDuffXfermode", "android.graphics._Original_PorterDuffXfermode",
+ "android.graphics.Shader", "android.graphics._Original_Shader",
+ "android.graphics.LinearGradient", "android.graphics._Original_LinearGradient",
+ "android.graphics.BitmapShader", "android.graphics._Original_BitmapShader",
+ "android.graphics.ComposeShader", "android.graphics._Original_ComposeShader",
+ "android.graphics.RadialGradient", "android.graphics._Original_RadialGradient",
+ "android.graphics.SweepGradient", "android.graphics._Original_SweepGradient",
+ "android.util.FloatMath", "android.util._Original_FloatMath",
+ "android.view.SurfaceView", "android.view._Original_SurfaceView",
+ },
+ new String[] { // methods deleted from their return type.
+ "android.graphics.Paint", // class to delete method from
+ "android.graphics.Paint$Align", // list of type identifying methods to delete
+ "android.graphics.Paint$Style",
+ "android.graphics.Paint$Join",
+ "android.graphics.Paint$Cap",
+ "android.graphics.Paint$FontMetrics",
+ "android.graphics.Paint$FontMetricsInt",
+ null }
+ );
+
+ AsmAnalyzer aa = new AsmAnalyzer(log, osJarPath, agen,
+ new String[] { "android.view.View" }, // derived from
+ 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
+ });
+ 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;
+ }
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java
new file mode 100644
index 0000000..61dd151
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/OverrideMethod.java
@@ -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;
+
+import java.util.HashMap;
+
+/**
+ * Allows stub methods from LayoutLib to be overriden at runtime.
+ */
+public final class OverrideMethod {
+
+ /**
+ * Interface to allow a method invocation to be listend upon.
+ */
+ public interface MethodListener {
+ /**
+ * A stub method is being invoked.
+ * <p/>
+ * Known limitation: caller arguments are not available. Return value cannot be set.
+ *
+ * @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 caller The calling object. Null for static methods, "this" for instance methods.
+ */
+ public void onInvoke(String signature, Object caller);
+ }
+
+ /** Map of method overriden. */
+ 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);
+ }
+ }
+
+ /**
+ * Invoke the specific listener for the given signature or the default one if defined.
+ * <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 caller The calling object. Null for static methods, "this" for instance methods.
+ */
+ public static void invoke(String signature, Object caller) {
+ MethodListener i = sMethods.get(signature);
+ if (i != null) {
+ i.onInvoke(signature, caller);
+ } else if (sDefaultListener != null) {
+ sDefaultListener.onInvoke(signature, caller);
+ }
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java
new file mode 100644
index 0000000..0956b92
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/RenameClassAdapter.java
@@ -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);
+ }
+
+ }
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
new file mode 100644
index 0000000..2450d4b
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/StubMethodAdapter.java
@@ -0,0 +1,294 @@
+/*
+ * 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;
+
+ public StubMethodAdapter(MethodVisitor mv, String methodName, Type returnType,
+ String invokeSignature, boolean isStatic) {
+ mParentVisitor = mv;
+ mReturnType = returnType;
+ mInvokeSignature = invokeSignature;
+ mIsStatic = isStatic;
+
+ if (CONSTRUCTOR.equals(methodName) || CLASS_INIT.equals(methodName)) {
+ mIsInitMethod = true;
+ }
+ }
+
+ private void generateInvoke() {
+ /* Generates the code:
+ * OverrideMethod.invoke("signature", this);
+ */
+ mParentVisitor.visitLdcInsn(mInvokeSignature);
+ if (mIsStatic) {
+ mParentVisitor.visitInsn(Opcodes.ACONST_NULL); // push null
+ } else {
+ mParentVisitor.visitVarInsn(Opcodes.ALOAD, 0); // push this
+ }
+ mParentVisitor.visitMethodInsn(Opcodes.INVOKESTATIC,
+ "com/android/tools/layoutlib/create/OverrideMethod",
+ "invoke",
+ "(Ljava/lang/String;Ljava/lang/Object;)V");
+ }
+
+ private void generateReturn() {
+ /* Generates one of, depending on the return type:
+ * return;
+ * return 0;
+ * return 0L;
+ * return 0.0f;
+ * return 0.0;
+ * return null;
+ */
+ switch(mReturnType != null ? mReturnType.getSort() : Type.VOID) {
+ case Type.VOID:
+ mParentVisitor.visitInsn(Opcodes.RETURN);
+ break;
+ case Type.BOOLEAN:
+ case Type.CHAR:
+ case Type.BYTE:
+ case Type.SHORT:
+ case Type.INT:
+ mParentVisitor.visitInsn(Opcodes.ICONST_0);
+ mParentVisitor.visitInsn(Opcodes.IRETURN);
+ break;
+ case Type.LONG:
+ mParentVisitor.visitInsn(Opcodes.LCONST_0);
+ mParentVisitor.visitInsn(Opcodes.LRETURN);
+ break;
+ case Type.FLOAT:
+ mParentVisitor.visitInsn(Opcodes.FCONST_0);
+ mParentVisitor.visitInsn(Opcodes.FRETURN);
+ break;
+ case Type.DOUBLE:
+ mParentVisitor.visitInsn(Opcodes.DCONST_0);
+ mParentVisitor.visitInsn(Opcodes.DRETURN);
+ break;
+ case Type.ARRAY:
+ case Type.OBJECT:
+ mParentVisitor.visitInsn(Opcodes.ACONST_NULL);
+ mParentVisitor.visitInsn(Opcodes.ARETURN);
+ 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();
+ generateReturn();
+ 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();
+ generateReturn();
+ 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:
+ generateInvoke();
+ mMessageGenerated = true;
+ }
+ 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);
+ }
+ }
+
+}
diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
new file mode 100644
index 0000000..a84353c
--- /dev/null
+++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/TransformClassAdapter.java
@@ -0,0 +1,175 @@
+/*
+ * 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
+ * @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)) {
+
+ // remove abstract, final and native
+ access = access & ~(Opcodes.ACC_ABSTRACT | Opcodes.ACC_FINAL | Opcodes.ACC_NATIVE);
+
+ boolean isStatic = (access & Opcodes.ACC_STATIC) != 0;
+
+ String invokeSignature = methodSignature + desc;
+ mLog.debug(" Stub: %s", invokeSignature);
+
+ MethodVisitor mw = super.visitMethod(access, name, desc, signature, exceptions);
+ return new StubMethodAdapter(mw, name, returnType(desc), invokeSignature, isStatic);
+
+ } 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;
+ }
+}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
new file mode 100644
index 0000000..603284e
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmAnalyzerTest.java
@@ -0,0 +1,228 @@
+/*
+ * 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 com.android.tools.layoutlib.create.LogTest.MockLog;
+
+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 LogTest.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());
+ }
+}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
new file mode 100644
index 0000000..7cdf79a
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/AsmGeneratorTest.java
@@ -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 static org.junit.Assert.assertArrayEquals;
+
+import com.android.tools.layoutlib.create.LogTest.MockLog;
+
+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 LogTest.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 {
+
+ AsmGenerator agen = new AsmGenerator(mLog, mOsDestJar,
+ null, // classes to inject in the final JAR
+ null, // methods to force override
+ new String[] { // classes to rename (so that we can replace them)
+ "mock_android.view.View", "mock_android.view._Original_View",
+ "not.an.actual.ClassName", "anoter.fake.NewClassName",
+ },
+ null // methods deleted from their return type.
+ );
+
+ 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());
+ }
+}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
new file mode 100644
index 0000000..3f13158
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/LogTest.java
@@ -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.*;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class LogTest {
+
+ public static 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');
+ }
+ }
+
+ 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"));
+ }
+}
diff --git a/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/RenameClassAdapterTest.java b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/RenameClassAdapterTest.java
new file mode 100644
index 0000000..90c6a9c
--- /dev/null
+++ b/tools/layoutlib/create/tests/com/android/tools/layoutlib/create/RenameClassAdapterTest.java
@@ -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;"));
+ }
+
+
+
+}
diff --git a/tools/layoutlib/create/tests/data/mock_android.jar b/tools/layoutlib/create/tests/data/mock_android.jar
new file mode 100644
index 0000000..a7ea74f
--- /dev/null
+++ b/tools/layoutlib/create/tests/data/mock_android.jar
Binary files differ
diff --git a/tools/layoutlib/create/tests/data/mock_android.jardesc b/tools/layoutlib/create/tests/data/mock_android.jardesc
new file mode 100644
index 0000000..95f7591
--- /dev/null
+++ b/tools/layoutlib/create/tests/data/mock_android.jardesc
@@ -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>
diff --git a/tools/layoutlib/create/tests/mock_android/dummy/InnerTest.java b/tools/layoutlib/create/tests/mock_android/dummy/InnerTest.java
new file mode 100644
index 0000000..e355ead
--- /dev/null
+++ b/tools/layoutlib/create/tests/mock_android/dummy/InnerTest.java
@@ -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>());
+ }
+}
diff --git a/tools/layoutlib/create/tests/mock_android/view/View.java b/tools/layoutlib/create/tests/mock_android/view/View.java
new file mode 100644
index 0000000..a80a98d
--- /dev/null
+++ b/tools/layoutlib/create/tests/mock_android/view/View.java
@@ -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 {
+
+}
diff --git a/tools/layoutlib/create/tests/mock_android/view/ViewGroup.java b/tools/layoutlib/create/tests/mock_android/view/ViewGroup.java
new file mode 100644
index 0000000..466470f
--- /dev/null
+++ b/tools/layoutlib/create/tests/mock_android/view/ViewGroup.java
@@ -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 {
+
+ }
+
+}
diff --git a/tools/layoutlib/create/tests/mock_android/widget/LinearLayout.java b/tools/layoutlib/create/tests/mock_android/widget/LinearLayout.java
new file mode 100644
index 0000000..3870a63
--- /dev/null
+++ b/tools/layoutlib/create/tests/mock_android/widget/LinearLayout.java
@@ -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 {
+
+ }
+
+}
diff --git a/tools/layoutlib/create/tests/mock_android/widget/TableLayout.java b/tools/layoutlib/create/tests/mock_android/widget/TableLayout.java
new file mode 100644
index 0000000..e455e7d
--- /dev/null
+++ b/tools/layoutlib/create/tests/mock_android/widget/TableLayout.java
@@ -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 {
+
+ }
+
+}