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.ArrayList;
     23 import java.util.Arrays;
     24 import java.util.Enumeration;
     25 import java.util.HashSet;
     26 import java.util.LinkedHashSet;
     27 import java.util.List;
     28 import java.util.Set;
     29 
     30 /**
     31  * Finds class entries in apks.
     32  * <p/>
     33  * Adapted from tools/tradefederation/..ClassPathScanner
     34  */
     35 class ClassPathScanner {
     36 
     37     /**
     38      * A filter for classpath entry paths
     39      * <p/>
     40      * Patterned after {@link java.io.FileFilter}
     41      */
     42     public static interface ClassNameFilter {
     43         /**
     44          * Tests whether or not the specified abstract pathname should be included in a class path
     45          * entry list.
     46          *
     47          * @param pathName the relative path of the class path entry
     48          */
     49         boolean accept(String className);
     50     }
     51 
     52     /**
     53      * A {@link ClassNameFilter} that accepts all class names.
     54      */
     55     public static class AcceptAllFilter implements ClassNameFilter {
     56 
     57         /**
     58          * {@inheritDoc}
     59          */
     60         @Override
     61         public boolean accept(String className) {
     62             return true;
     63         }
     64 
     65     }
     66 
     67     /**
     68      * A {@link ClassNameFilter} that chains one or more filters together
     69      */
     70     public static class ChainedClassNameFilter implements ClassNameFilter {
     71         private final List<ClassNameFilter> mFilters = new ArrayList<ClassNameFilter>();
     72 
     73         public void add(ClassNameFilter filter) {
     74             mFilters.add(filter);
     75         }
     76 
     77         public void addAll(ClassNameFilter... filters) {
     78             mFilters.addAll(Arrays.asList(filters));
     79         }
     80 
     81         /**
     82          * {@inheritDoc}
     83          */
     84         @Override
     85         public boolean accept(String className) {
     86             for (ClassNameFilter filter : mFilters) {
     87                 if (!filter.accept(className)) {
     88                     return false;
     89                 }
     90             }
     91             return true;
     92         }
     93     }
     94 
     95     /**
     96      * A {@link ClassNameFilter} that rejects inner classes.
     97      */
     98     public static class ExternalClassNameFilter implements ClassNameFilter {
     99         /**
    100          * {@inheritDoc}
    101          */
    102         @Override
    103         public boolean accept(String pathName) {
    104             return !pathName.contains("$");
    105         }
    106     }
    107 
    108     /**
    109      * A {@link ClassNameFilter} that only accepts package names within the given namespace.
    110      */
    111     public static class InclusivePackageNameFilter implements ClassNameFilter {
    112 
    113         private final String mPkgName;
    114 
    115         InclusivePackageNameFilter(String pkgName) {
    116             if (!pkgName.endsWith(".")) {
    117                 mPkgName = String.format("%s.", pkgName);
    118             } else {
    119                 mPkgName = pkgName;
    120             }
    121         }
    122 
    123         /**
    124          * {@inheritDoc}
    125          */
    126         @Override
    127         public boolean accept(String pathName) {
    128             return pathName.startsWith(mPkgName);
    129         }
    130     }
    131 
    132     /**
    133      * A {@link ClassNameFilter} that only rejects a given package names within the given namespace.
    134      */
    135     public static class ExcludePackageNameFilter implements ClassNameFilter {
    136 
    137         private final String mPkgName;
    138 
    139         ExcludePackageNameFilter(String pkgName) {
    140             if (!pkgName.endsWith(".")) {
    141                 mPkgName = String.format("%s.", pkgName);
    142             } else {
    143                 mPkgName = pkgName;
    144             }
    145         }
    146 
    147         /**
    148          * {@inheritDoc}
    149          */
    150         @Override
    151         public boolean accept(String pathName) {
    152             return !pathName.startsWith(mPkgName);
    153         }
    154     }
    155 
    156     private Set<String> mApkPaths = new HashSet<String>();
    157 
    158     public ClassPathScanner(String... apkPaths) {
    159         for (String apkPath : apkPaths) {
    160             mApkPaths.add(apkPath);
    161         }
    162     }
    163 
    164     /**
    165      * Gets the names of all entries contained in given apk file, that match given filter.
    166      * @throws IOException
    167      */
    168     private void addEntriesFromApk(Set<String> entryNames, String apkPath, ClassNameFilter filter)
    169             throws IOException {
    170         DexFile dexFile = null;
    171         try {
    172             dexFile = new DexFile(apkPath);
    173             Enumeration<String> apkClassNames = getDexEntries(dexFile);
    174             while (apkClassNames.hasMoreElements()) {
    175                 String apkClassName = apkClassNames.nextElement();
    176                 if (filter.accept(apkClassName)) {
    177                     entryNames.add(apkClassName);
    178                 }
    179             }
    180         } finally {
    181             if (dexFile != null) {
    182                 dexFile.close();
    183             }
    184         }
    185     }
    186 
    187     /**
    188      * Retrieves the entry names from given {@link DexFile}.
    189      * <p/>
    190      * Exposed for unit testing.
    191      *
    192      * @param dexFile
    193      * @return {@link Enumeration} of {@link String}s
    194      */
    195     Enumeration<String> getDexEntries(DexFile dexFile) {
    196         return dexFile.entries();
    197     }
    198 
    199     /**
    200      * Retrieves set of classpath entries that match given {@link ClassNameFilter}.
    201      * @throws IOException
    202      */
    203     public Set<String> getClassPathEntries(ClassNameFilter filter) throws IOException {
    204         // use LinkedHashSet for predictable order
    205         Set<String> entryNames = new LinkedHashSet<String>();
    206         for (String apkPath : mApkPaths) {
    207             addEntriesFromApk(entryNames, apkPath, filter);
    208         }
    209         return entryNames;
    210     }
    211 }
    212