FileDocCategorySizeDatePackage
AsmAnalyzer.javaAPI DocAndroid 5.1 API32871Thu Mar 12 22:22:44 GMT 2015com.android.tools.layoutlib.create

AsmAnalyzer

public 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
mLog
Output logger.
private final List
mOsSourceJar
The input source JAR to parse.
private final AsmGenerator
mGen
The generator to fill with the class list and dependency list.
private final String[]
mDeriveFrom
Keep all classes that derive from these one (these included).
private final String[]
mIncludeGlobs
Glob patterns of classes to keep, e.g. "com.foo.*"
private final Set
mExcludedClasses
The set of classes to exclude.
private final String[]
mIncludeFileGlobs
Glob patterns of files to keep as is.
private final Set
mReplaceMethodCallClasses
Internal names of classes that contain method calls that need to be rewritten.
Constructors Summary
public AsmAnalyzer(Log log, List osJarPath, AsmGenerator gen, String[] deriveFrom, String[] includeGlobs, Set excludeClasses, String[] includeFileGlobs)
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)
param
includeFileGlobs Glob patterns of files which are kept as is. This is only for files not ending in .class.


                                                                                                             
          
                 
              
        mLog = log;
        mGen = gen;
        mOsSourceJar = osJarPath != null ? osJarPath : new ArrayList<String>();
        mDeriveFrom = deriveFrom != null ? deriveFrom : new String[0];
        mIncludeGlobs = includeGlobs != null ? includeGlobs : new String[0];
        mExcludedClasses = excludeClasses;
        mIncludeFileGlobs = includeFileGlobs != null ? includeFileGlobs : new String[0];
    
Methods Summary
public voidanalyze()
Starts the analysis using parameters from the constructor. Fills the generator with classes & dependencies found.


        TreeMap<String, ClassReader> zipClasses = new TreeMap<String, ClassReader>();
        Map<String, InputStream> filesFound = new TreeMap<String, InputStream>();

        parseZip(mOsSourceJar, zipClasses, filesFound);
        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);
            mGen.setCopyFiles(filesFound);
            mGen.setRewriteMethodCallClasses(mReplaceMethodCallClasses);
        }
    
static java.lang.StringclassReaderToClassName(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.ClassReaderfindClass(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;
    
voidfindClassesDerivingFrom(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.

        if (mExcludedClasses.contains(getOuterClassName(super_name))) {
            return;
        }
        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.MapfindDeps(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()) {
            visitor.setClassName(cr.getClassName());
            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()) {
                visitor.setClassName(cr.getClassName());
                cr.accept(visitor, 0 /* flags */);
            }
        }

        mLog.info("Found %1$d classes to keep, %2$d class dependencies.",
                inOutKeepClasses.size(), deps.size());

        return deps;
    
voidfindGlobs(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.


        Pattern regexp = getPatternFromGlob(globPattern);

        for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
            String class_name = entry.getKey();
            if (regexp.matcher(class_name).matches() &&
                    !mExcludedClasses.contains(getOuterClassName(class_name))) {
                findClass(class_name, zipClasses, inOutFound);
            }
        }
    
java.util.MapfindIncludes(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;
    
private java.lang.StringgetOuterClassName(java.lang.String className)

        int pos = className.indexOf('$");
        if (pos > 0) {
            return className.substring(0, pos);
        }
        return className;
    
java.util.regex.PatterngetPatternFromGlob(java.lang.String globPattern)

     // 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 += "$";

        return Pattern.compile(globPattern);
    
com.android.tools.layoutlib.create.AsmAnalyzer$DependencyVisitorgetVisitor(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.StringinternalToBinaryClassName(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('/", '.");
        }
    
voidparseZip(java.util.List jarPathList, java.util.Map classes, java.util.Map filesFound)
Parses a JAR file and adds all the classes found to classes and all other files to filesFound.

param
classes The map of class name => ASM ClassReader. Class names are in the form "android.view.View".
param
filesFound The map of file name => InputStream. The file name is in the form "android/data/dataFile".

        if (classes == null || filesFound == null) {
            return;
        }

        Pattern[] includeFilePatterns = new Pattern[mIncludeFileGlobs.length];
        for (int i = 0; i < mIncludeFileGlobs.length; ++i) {
            includeFilePatterns[i] = getPatternFromGlob(mIncludeFileGlobs[i]);
        }

        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);
                } else {
                    for (Pattern includeFilePattern : includeFilePatterns) {
                        if (includeFilePattern.matcher(entry.getName()).matches()) {
                            filesFound.put(entry.getName(), zip.getInputStream(entry));
                            break;
                        }
                    }
                }
            }
        }