AsmAnalyzerpublic class AsmAnalyzer extends Object Analyzes the input JAR using the ASM java bytecode manipulation library
to list the desired classes and their dependencies. |
Fields Summary |
---|
private final Log | mLogOutput logger. | private final List | mOsSourceJarThe input source JAR to parse. | private final AsmGenerator | mGenThe generator to fill with the class list and dependency list. | private final String[] | mDeriveFromKeep all classes that derive from these one (these included). | private final String[] | mIncludeGlobsGlob patterns of classes to keep, e.g. "com.foo.*" |
Constructors Summary |
---|
public AsmAnalyzer(Log log, List osJarPath, AsmGenerator gen, String[] deriveFrom, String[] includeGlobs)Creates a new analyzer.
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];
|
Methods Summary |
---|
public void | analyze()Starts the analysis using parameters from the constructor.
Fills the generator with classes & dependencies found.
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);
}
| static java.lang.String | classReaderToClassName(org.objectweb.asm.ClassReader classReader)Utility that returns the fully qualified binary class name for a ClassReader.
E.g. it returns something like android.view.View.
if (classReader == null) {
return null;
} else {
return classReader.getClassName().replace('/", '.");
}
| org.objectweb.asm.ClassReader | findClass(java.lang.String className, java.util.Map zipClasses, java.util.Map inOutFound)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 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;
| void | findClassesDerivingFrom(java.lang.String super_name, java.util.Map zipClasses, java.util.Map 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.
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);
}
}
| java.util.Map | findDeps(java.util.Map zipClasses, java.util.Map inOutKeepClasses)Finds all dependencies for all classes in keepClasses which are also
listed in zipClasses. Returns a map of all the dependencies found.
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;
| void | findGlobs(java.lang.String globPattern, java.util.Map zipClasses, java.util.Map inOutFound)Insert in the inOutFound map all classes found in zipClasses that match the
given glob pattern.
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 ".".
Examples:
- com.foo.* matches all classes in the package com.foo but NOT sub-packages.
- com.foo*.*$Event matches all internal Event classes in a com.foo*.* class.
// 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);
}
}
| java.util.Map | findIncludes(java.util.Map zipClasses)Process the "includes" arrays.
This updates the in_out_found map.
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;
| com.android.tools.layoutlib.create.AsmAnalyzer$DependencyVisitor | getVisitor(java.util.Map zipClasses, java.util.Map inKeep, java.util.Map outKeep, java.util.Map inDeps, java.util.Map outDeps)Instantiates a new DependencyVisitor. Useful for unit tests.
return new DependencyVisitor(zipClasses, inKeep, outKeep, inDeps, outDeps);
| static java.lang.String | internalToBinaryClassName(java.lang.String className)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.
if (className == null) {
return null;
} else {
return className.replace('/", '.");
}
| java.util.Map | parseZip(java.util.List jarPathList)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".
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;
|
|