Home | History | Annotate | Download | only in runner
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.test.runner;
     18 
     19 import dalvik.system.DexFile;
     20 
     21 import java.io.IOException;
     22 import java.util.Enumeration;
     23 import java.util.HashSet;
     24 import java.util.LinkedHashSet;
     25 import java.util.Set;
     26 
     27 /**
     28  * Finds class entries in apks.
     29  * <p/>
     30  * Adapted from tools/tradefederation/..ClassPathScanner
     31  */
     32 class ClassPathScanner {
     33 
     34     /**
     35      * A filter for classpath entry paths
     36      * <p/>
     37      * Patterned after {@link java.io.FileFilter}
     38      */
     39     public static interface ClassNameFilter {
     40         /**
     41          * Tests whether or not the specified abstract pathname should be included in a class path
     42          * entry list.
     43          *
     44          * @param pathName the relative path of the class path entry
     45          */
     46         boolean accept(String className);
     47     }
     48 
     49     /**
     50      * A {@link ClassNameFilter} that accepts all class names.
     51      */
     52     public static class AcceptAllFilter implements ClassNameFilter {
     53 
     54         /**
     55          * {@inheritDoc}
     56          */
     57         @Override
     58         public boolean accept(String className) {
     59             return true;
     60         }
     61 
     62     }
     63 
     64     /**
     65      * A {@link ClassNameFilter} that chains one or more filters together
     66      */
     67     public static class ChainedClassNameFilter implements ClassNameFilter {
     68         private final ClassNameFilter[] mFilters;
     69 
     70         public ChainedClassNameFilter(ClassNameFilter... filters) {
     71             mFilters = filters;
     72         }
     73 
     74         /**
     75          * {@inheritDoc}
     76          */
     77         @Override
     78         public boolean accept(String className) {
     79             for (ClassNameFilter filter : mFilters) {
     80                 if (!filter.accept(className)) {
     81                     return false;
     82                 }
     83             }
     84             return true;
     85         }
     86     }
     87 
     88     /**
     89      * A {@link ClassNameFilter} that rejects inner classes.
     90      */
     91     public static class ExternalClassNameFilter implements ClassNameFilter {
     92         /**
     93          * {@inheritDoc}
     94          */
     95         @Override
     96         public boolean accept(String pathName) {
     97             return !pathName.contains("$");
     98         }
     99     }
    100 
    101     /**
    102      * A {@link ClassNameFilter} that only accepts package names within the given namespace.
    103      */
    104     public static class InclusivePackageNameFilter implements ClassNameFilter {
    105 
    106         private final String mPkgName;
    107 
    108         InclusivePackageNameFilter(String pkgName) {
    109             if (!pkgName.endsWith(".")) {
    110                 mPkgName = String.format("%s.", pkgName);
    111             } else {
    112                 mPkgName = pkgName;
    113             }
    114         }
    115 
    116         /**
    117          * {@inheritDoc}
    118          */
    119         @Override
    120         public boolean accept(String pathName) {
    121             return pathName.startsWith(mPkgName);
    122         }
    123     }
    124 
    125     /**
    126      * A {@link ClassNameFilter} that only rejects a given package names within the given namespace.
    127      */
    128     public static class ExcludePackageNameFilter implements ClassNameFilter {
    129 
    130         private final String mPkgName;
    131 
    132         ExcludePackageNameFilter(String pkgName) {
    133             if (!pkgName.endsWith(".")) {
    134                 mPkgName = String.format("%s.", pkgName);
    135             } else {
    136                 mPkgName = pkgName;
    137             }
    138         }
    139 
    140         /**
    141          * {@inheritDoc}
    142          */
    143         @Override
    144         public boolean accept(String pathName) {
    145             return !pathName.startsWith(mPkgName);
    146         }
    147     }
    148 
    149     private Set<String> mApkPaths = new HashSet<String>();
    150 
    151     public ClassPathScanner(String... apkPaths) {
    152         for (String apkPath : apkPaths) {
    153             mApkPaths.add(apkPath);
    154         }
    155     }
    156 
    157     /**
    158      * Gets the names of all entries contained in given apk file, that match given filter.
    159      * @throws IOException
    160      */
    161     private void addEntriesFromApk(Set<String> entryNames, String apkPath, ClassNameFilter filter)
    162             throws IOException {
    163         DexFile dexFile = null;
    164         try {
    165             dexFile = new DexFile(apkPath);
    166             Enumeration<String> apkClassNames = getDexEntries(dexFile);
    167             while (apkClassNames.hasMoreElements()) {
    168                 String apkClassName = apkClassNames.nextElement();
    169                 if (filter.accept(apkClassName)) {
    170                     entryNames.add(apkClassName);
    171                 }
    172             }
    173         } finally {
    174             if (dexFile != null) {
    175                 dexFile.close();
    176             }
    177         }
    178     }
    179 
    180     /**
    181      * Retrieves the entry names from given {@link DexFile}.
    182      * <p/>
    183      * Exposed for unit testing.
    184      *
    185      * @param dexFile
    186      * @return {@link Enumeration} of {@link String}s
    187      */
    188     Enumeration<String> getDexEntries(DexFile dexFile) {
    189         return dexFile.entries();
    190     }
    191 
    192     /**
    193      * Retrieves set of classpath entries that match given {@link ClassNameFilter}.
    194      * @throws IOException
    195      */
    196     public Set<String> getClassPathEntries(ClassNameFilter filter) throws IOException {
    197         // use LinkedHashSet for predictable order
    198         Set<String> entryNames = new LinkedHashSet<String>();
    199         for (String apkPath : mApkPaths) {
    200             addEntriesFromApk(entryNames, apkPath, filter);
    201         }
    202         return entryNames;
    203     }
    204 }
    205