Home | History | Annotate | Download | only in test
      1 /*
      2  * Copyright (C) 2008 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 android.test;
     18 
     19 import android.util.Log;
     20 import com.google.android.collect.Maps;
     21 import com.google.android.collect.Sets;
     22 import dalvik.system.DexFile;
     23 
     24 import java.io.File;
     25 import java.io.IOException;
     26 import java.util.Enumeration;
     27 import java.util.Map;
     28 import java.util.Set;
     29 import java.util.TreeSet;
     30 import java.util.regex.Pattern;
     31 import java.util.zip.ZipEntry;
     32 import java.util.zip.ZipFile;
     33 
     34 /**
     35  * Generate {@link ClassPathPackageInfo}s by scanning apk paths.
     36  *
     37  * {@hide} Not needed for 1.0 SDK.
     38  */
     39 @Deprecated
     40 public class ClassPathPackageInfoSource {
     41 
     42     private static final String CLASS_EXTENSION = ".class";
     43 
     44     private static final ClassLoader CLASS_LOADER
     45             = ClassPathPackageInfoSource.class.getClassLoader();
     46 
     47     private final SimpleCache<String, ClassPathPackageInfo> cache =
     48             new SimpleCache<String, ClassPathPackageInfo>() {
     49                 @Override
     50                 protected ClassPathPackageInfo load(String pkgName) {
     51                     return createPackageInfo(pkgName);
     52                 }
     53             };
     54 
     55     // The class path of the running application
     56     private final String[] classPath;
     57     private static String[] apkPaths;
     58 
     59     // A cache of jar file contents
     60     private final Map<File, Set<String>> jarFiles = Maps.newHashMap();
     61     private ClassLoader classLoader;
     62 
     63     ClassPathPackageInfoSource() {
     64         classPath = getClassPath();
     65     }
     66 
     67 
     68     public static void setApkPaths(String[] apkPaths) {
     69         ClassPathPackageInfoSource.apkPaths = apkPaths;
     70     }
     71 
     72     public ClassPathPackageInfo getPackageInfo(String pkgName) {
     73         return cache.get(pkgName);
     74     }
     75 
     76     private ClassPathPackageInfo createPackageInfo(String packageName) {
     77         Set<String> subpackageNames = new TreeSet<String>();
     78         Set<String> classNames = new TreeSet<String>();
     79         Set<Class<?>> topLevelClasses = Sets.newHashSet();
     80         findClasses(packageName, classNames, subpackageNames);
     81         for (String className : classNames) {
     82             if (className.endsWith(".R") || className.endsWith(".Manifest")) {
     83                 // Don't try to load classes that are generated. They usually aren't in test apks.
     84                 continue;
     85             }
     86 
     87             try {
     88                 // We get errors in the emulator if we don't use the caller's class loader.
     89                 topLevelClasses.add(Class.forName(className, false,
     90                         (classLoader != null) ? classLoader : CLASS_LOADER));
     91             } catch (ClassNotFoundException | NoClassDefFoundError e) {
     92                 // Should not happen unless there is a generated class that is not included in
     93                 // the .apk.
     94                 Log.w("ClassPathPackageInfoSource", "Cannot load class. "
     95                         + "Make sure it is in your apk. Class name: '" + className
     96                         + "'. Message: " + e.getMessage(), e);
     97             }
     98         }
     99         return new ClassPathPackageInfo(this, packageName, subpackageNames,
    100                 topLevelClasses);
    101     }
    102 
    103     /**
    104      * Finds all classes and sub packages that are below the packageName and
    105      * add them to the respective sets. Searches the package on the whole class
    106      * path.
    107      */
    108     private void findClasses(String packageName, Set<String> classNames,
    109             Set<String> subpackageNames) {
    110         String packagePrefix = packageName + '.';
    111         String pathPrefix = packagePrefix.replace('.', '/');
    112 
    113         for (String entryName : classPath) {
    114             File classPathEntry = new File(entryName);
    115 
    116             // Forge may not have brought over every item in the classpath. Be
    117             // polite and ignore missing entries.
    118             if (classPathEntry.exists()) {
    119                 try {
    120                     if (entryName.endsWith(".apk")) {
    121                         findClassesInApk(entryName, packageName, classNames, subpackageNames);
    122                     } else {
    123                         // scan the directories that contain apk files.
    124                         for (String apkPath : apkPaths) {
    125                             File file = new File(apkPath);
    126                             scanForApkFiles(file, packageName, classNames, subpackageNames);
    127                         }
    128                     }
    129                 } catch (IOException e) {
    130                     throw new AssertionError("Can't read classpath entry " +
    131                             entryName + ": " + e.getMessage());
    132                 }
    133             }
    134         }
    135     }
    136 
    137     private void scanForApkFiles(File source, String packageName,
    138             Set<String> classNames, Set<String> subpackageNames) throws IOException {
    139         if (source.getPath().endsWith(".apk")) {
    140             findClassesInApk(source.getPath(), packageName, classNames, subpackageNames);
    141         } else {
    142             File[] files = source.listFiles();
    143             if (files != null) {
    144                 for (File file : files) {
    145                     scanForApkFiles(file, packageName, classNames, subpackageNames);
    146                 }
    147             }
    148         }
    149     }
    150 
    151     /**
    152      * Finds all classes and sub packages that are below the packageName and
    153      * add them to the respective sets. Searches the package in a class directory.
    154      */
    155     private void findClassesInDirectory(File classDir,
    156             String packagePrefix, String pathPrefix, Set<String> classNames,
    157             Set<String> subpackageNames)
    158             throws IOException {
    159         File directory = new File(classDir, pathPrefix);
    160 
    161         if (directory.exists()) {
    162             for (File f : directory.listFiles()) {
    163                 String name = f.getName();
    164                 if (name.endsWith(CLASS_EXTENSION) && isToplevelClass(name)) {
    165                     classNames.add(packagePrefix + getClassName(name));
    166                 } else if (f.isDirectory()) {
    167                     subpackageNames.add(packagePrefix + name);
    168                 }
    169             }
    170         }
    171     }
    172 
    173     /**
    174      * Finds all classes and sub packages that are below the packageName and
    175      * add them to the respective sets. Searches the package in a single jar file.
    176      */
    177     private void findClassesInJar(File jarFile, String pathPrefix,
    178             Set<String> classNames, Set<String> subpackageNames)
    179             throws IOException {
    180         Set<String> entryNames = getJarEntries(jarFile);
    181         // check if the Jar contains the package.
    182         if (!entryNames.contains(pathPrefix)) {
    183             return;
    184         }
    185         int prefixLength = pathPrefix.length();
    186         for (String entryName : entryNames) {
    187             if (entryName.startsWith(pathPrefix)) {
    188                 if (entryName.endsWith(CLASS_EXTENSION)) {
    189                     // check if the class is in the package itself or in one of its
    190                     // subpackages.
    191                     int index = entryName.indexOf('/', prefixLength);
    192                     if (index >= 0) {
    193                         String p = entryName.substring(0, index).replace('/', '.');
    194                         subpackageNames.add(p);
    195                     } else if (isToplevelClass(entryName)) {
    196                         classNames.add(getClassName(entryName).replace('/', '.'));
    197                     }
    198                 }
    199             }
    200         }
    201     }
    202 
    203     /**
    204      * Finds all classes and sub packages that are below the packageName and
    205      * add them to the respective sets. Searches the package in a single apk file.
    206      */
    207     private void findClassesInApk(String apkPath, String packageName,
    208             Set<String> classNames, Set<String> subpackageNames)
    209             throws IOException {
    210 
    211         DexFile dexFile = null;
    212         try {
    213             dexFile = new DexFile(apkPath);
    214             Enumeration<String> apkClassNames = dexFile.entries();
    215             while (apkClassNames.hasMoreElements()) {
    216                 String className = apkClassNames.nextElement();
    217 
    218                 if (className.startsWith(packageName)) {
    219                     String subPackageName = packageName;
    220                     int lastPackageSeparator = className.lastIndexOf('.');
    221                     if (lastPackageSeparator > 0) {
    222                         subPackageName = className.substring(0, lastPackageSeparator);
    223                     }
    224                     if (subPackageName.length() > packageName.length()) {
    225                         subpackageNames.add(subPackageName);
    226                     } else if (isToplevelClass(className)) {
    227                         classNames.add(className);
    228                     }
    229                 }
    230             }
    231         } catch (IOException e) {
    232             if (false) {
    233                 Log.w("ClassPathPackageInfoSource",
    234                         "Error finding classes at apk path: " + apkPath, e);
    235             }
    236         } finally {
    237             if (dexFile != null) {
    238                 // Todo: figure out why closing causes a dalvik error resulting in vm shutdown.
    239 //                dexFile.close();
    240             }
    241         }
    242     }
    243 
    244     /**
    245      * Gets the class and package entries from a Jar.
    246      */
    247     private Set<String> getJarEntries(File jarFile)
    248             throws IOException {
    249         Set<String> entryNames = jarFiles.get(jarFile);
    250         if (entryNames == null) {
    251             entryNames = Sets.newHashSet();
    252             ZipFile zipFile = new ZipFile(jarFile);
    253             Enumeration<? extends ZipEntry> entries = zipFile.entries();
    254             while (entries.hasMoreElements()) {
    255                 String entryName = entries.nextElement().getName();
    256                 if (entryName.endsWith(CLASS_EXTENSION)) {
    257                     // add the entry name of the class
    258                     entryNames.add(entryName);
    259 
    260                     // add the entry name of the classes package, i.e. the entry name of
    261                     // the directory that the class is in. Used to quickly skip jar files
    262                     // if they do not contain a certain package.
    263                     //
    264                     // Also add parent packages so that a JAR that contains
    265                     // pkg1/pkg2/Foo.class will be marked as containing pkg1/ in addition
    266                     // to pkg1/pkg2/ and pkg1/pkg2/Foo.class.  We're still interested in
    267                     // JAR files that contains subpackages of a given package, even if
    268                     // an intermediate package contains no direct classes.
    269                     //
    270                     // Classes in the default package will cause a single package named
    271                     // "" to be added instead.
    272                     int lastIndex = entryName.lastIndexOf('/');
    273                     do {
    274                         String packageName = entryName.substring(0, lastIndex + 1);
    275                         entryNames.add(packageName);
    276                         lastIndex = entryName.lastIndexOf('/', lastIndex - 1);
    277                     } while (lastIndex > 0);
    278                 }
    279             }
    280             jarFiles.put(jarFile, entryNames);
    281         }
    282         return entryNames;
    283     }
    284 
    285     /**
    286      * Checks if a given file name represents a toplevel class.
    287      */
    288     private static boolean isToplevelClass(String fileName) {
    289         return fileName.indexOf('$') < 0;
    290     }
    291 
    292     /**
    293      * Given the absolute path of a class file, return the class name.
    294      */
    295     private static String getClassName(String className) {
    296         int classNameEnd = className.length() - CLASS_EXTENSION.length();
    297         return className.substring(0, classNameEnd);
    298     }
    299 
    300     /**
    301      * Gets the class path from the System Property "java.class.path" and splits
    302      * it up into the individual elements.
    303      */
    304     private static String[] getClassPath() {
    305         String classPath = System.getProperty("java.class.path");
    306         String separator = System.getProperty("path.separator", ":");
    307         return classPath.split(Pattern.quote(separator));
    308     }
    309 
    310     public void setClassLoader(ClassLoader classLoader) {
    311         this.classLoader = classLoader;
    312     }
    313 }
    314