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 dalvik.system.DexFile;
     21 
     22 import java.io.File;
     23 import java.io.IOException;
     24 import java.util.Collections;
     25 import java.util.Enumeration;
     26 import java.util.HashSet;
     27 import java.util.Set;
     28 import java.util.TreeSet;
     29 import java.util.regex.Pattern;
     30 
     31 /**
     32  * Generate {@link ClassPathPackageInfo}s by scanning apk paths.
     33  *
     34  * {@hide} Not needed for 1.0 SDK.
     35  */
     36 @Deprecated
     37 public class ClassPathPackageInfoSource {
     38 
     39     private static final ClassLoader CLASS_LOADER
     40             = ClassPathPackageInfoSource.class.getClassLoader();
     41 
     42     private static String[] apkPaths;
     43 
     44     private static ClassPathPackageInfoSource classPathSource;
     45 
     46     private final SimpleCache<String, ClassPathPackageInfo> cache =
     47             new SimpleCache<String, ClassPathPackageInfo>() {
     48                 @Override
     49                 protected ClassPathPackageInfo load(String pkgName) {
     50                     return createPackageInfo(pkgName);
     51                 }
     52             };
     53 
     54     // The class path of the running application
     55     private final String[] classPath;
     56 
     57     private final ClassLoader classLoader;
     58 
     59     private ClassPathPackageInfoSource(ClassLoader classLoader) {
     60         this.classLoader = classLoader;
     61         classPath = getClassPath();
     62     }
     63 
     64     static void setApkPaths(String[] apkPaths) {
     65         ClassPathPackageInfoSource.apkPaths = apkPaths;
     66     }
     67 
     68     public static ClassPathPackageInfoSource forClassPath(ClassLoader classLoader) {
     69         if (classPathSource == null) {
     70             classPathSource = new ClassPathPackageInfoSource(classLoader);
     71         }
     72         return classPathSource;
     73     }
     74 
     75     public Set<Class<?>> getTopLevelClassesRecursive(String packageName) {
     76         ClassPathPackageInfo packageInfo = cache.get(packageName);
     77         return packageInfo.getTopLevelClassesRecursive();
     78     }
     79 
     80     private ClassPathPackageInfo createPackageInfo(String packageName) {
     81         Set<String> subpackageNames = new TreeSet<String>();
     82         Set<String> classNames = new TreeSet<String>();
     83         Set<Class<?>> topLevelClasses = new HashSet<>();
     84         findClasses(packageName, classNames, subpackageNames);
     85         for (String className : classNames) {
     86             if (className.endsWith(".R") || className.endsWith(".Manifest")) {
     87                 // Don't try to load classes that are generated. They usually aren't in test apks.
     88                 continue;
     89             }
     90 
     91             try {
     92                 // We get errors in the emulator if we don't use the caller's class loader.
     93                 topLevelClasses.add(Class.forName(className, false,
     94                         (classLoader != null) ? classLoader : CLASS_LOADER));
     95             } catch (ClassNotFoundException | NoClassDefFoundError e) {
     96                 // Should not happen unless there is a generated class that is not included in
     97                 // the .apk.
     98                 Log.w("ClassPathPackageInfoSource", "Cannot load class. "
     99                         + "Make sure it is in your apk. Class name: '" + className
    100                         + "'. Message: " + e.getMessage(), e);
    101             }
    102         }
    103         return new ClassPathPackageInfo(packageName, subpackageNames,
    104                 topLevelClasses);
    105     }
    106 
    107     /**
    108      * Finds all classes and sub packages that are below the packageName and
    109      * add them to the respective sets. Searches the package on the whole class
    110      * path.
    111      */
    112     private void findClasses(String packageName, Set<String> classNames,
    113             Set<String> subpackageNames) {
    114         for (String entryName : classPath) {
    115             File classPathEntry = new File(entryName);
    116 
    117             // Forge may not have brought over every item in the classpath. Be
    118             // polite and ignore missing entries.
    119             if (classPathEntry.exists()) {
    120                 try {
    121                     if (entryName.endsWith(".apk")) {
    122                         findClassesInApk(entryName, packageName, classNames, subpackageNames);
    123                     } else {
    124                         // scan the directories that contain apk files.
    125                         for (String apkPath : apkPaths) {
    126                             File file = new File(apkPath);
    127                             scanForApkFiles(file, packageName, classNames, subpackageNames);
    128                         }
    129                     }
    130                 } catch (IOException e) {
    131                     throw new AssertionError("Can't read classpath entry " +
    132                             entryName + ": " + e.getMessage());
    133                 }
    134             }
    135         }
    136     }
    137 
    138     private void scanForApkFiles(File source, String packageName,
    139             Set<String> classNames, Set<String> subpackageNames) throws IOException {
    140         if (source.getPath().endsWith(".apk")) {
    141             findClassesInApk(source.getPath(), packageName, classNames, subpackageNames);
    142         } else {
    143             File[] files = source.listFiles();
    144             if (files != null) {
    145                 for (File file : files) {
    146                     scanForApkFiles(file, packageName, classNames, subpackageNames);
    147                 }
    148             }
    149         }
    150     }
    151 
    152     /**
    153      * Finds all classes and sub packages that are below the packageName and
    154      * add them to the respective sets. Searches the package in a single apk file.
    155      */
    156     private void findClassesInApk(String apkPath, String packageName,
    157             Set<String> classNames, Set<String> subpackageNames)
    158             throws IOException {
    159 
    160         DexFile dexFile = null;
    161         try {
    162             dexFile = new DexFile(apkPath);
    163             Enumeration<String> apkClassNames = dexFile.entries();
    164             while (apkClassNames.hasMoreElements()) {
    165                 String className = apkClassNames.nextElement();
    166 
    167                 if (className.startsWith(packageName)) {
    168                     String subPackageName = packageName;
    169                     int lastPackageSeparator = className.lastIndexOf('.');
    170                     if (lastPackageSeparator > 0) {
    171                         subPackageName = className.substring(0, lastPackageSeparator);
    172                     }
    173                     if (subPackageName.length() > packageName.length()) {
    174                         subpackageNames.add(subPackageName);
    175                     } else if (isToplevelClass(className)) {
    176                         classNames.add(className);
    177                     }
    178                 }
    179             }
    180         } catch (IOException e) {
    181             if (false) {
    182                 Log.w("ClassPathPackageInfoSource",
    183                         "Error finding classes at apk path: " + apkPath, e);
    184             }
    185         } finally {
    186             if (dexFile != null) {
    187                 // Todo: figure out why closing causes a dalvik error resulting in vm shutdown.
    188 //                dexFile.close();
    189             }
    190         }
    191     }
    192 
    193     /**
    194      * Checks if a given file name represents a toplevel class.
    195      */
    196     private static boolean isToplevelClass(String fileName) {
    197         return fileName.indexOf('$') < 0;
    198     }
    199 
    200     /**
    201      * Gets the class path from the System Property "java.class.path" and splits
    202      * it up into the individual elements.
    203      */
    204     private static String[] getClassPath() {
    205         String classPath = System.getProperty("java.class.path");
    206         String separator = System.getProperty("path.separator", ":");
    207         return classPath.split(Pattern.quote(separator));
    208     }
    209 
    210     /**
    211      * The Package object doesn't allow you to iterate over the contained
    212      * classes and subpackages of that package.  This is a version that does.
    213      */
    214     private class ClassPathPackageInfo {
    215 
    216         private final String packageName;
    217         private final Set<String> subpackageNames;
    218         private final Set<Class<?>> topLevelClasses;
    219 
    220         private ClassPathPackageInfo(String packageName,
    221                 Set<String> subpackageNames, Set<Class<?>> topLevelClasses) {
    222             this.packageName = packageName;
    223             this.subpackageNames = Collections.unmodifiableSet(subpackageNames);
    224             this.topLevelClasses = Collections.unmodifiableSet(topLevelClasses);
    225         }
    226 
    227         private Set<ClassPathPackageInfo> getSubpackages() {
    228             Set<ClassPathPackageInfo> info = new HashSet<>();
    229             for (String name : subpackageNames) {
    230                 info.add(cache.get(name));
    231             }
    232             return info;
    233         }
    234 
    235         private Set<Class<?>> getTopLevelClassesRecursive() {
    236             Set<Class<?>> set = new HashSet<>();
    237             addTopLevelClassesTo(set);
    238             return set;
    239         }
    240 
    241         private void addTopLevelClassesTo(Set<Class<?>> set) {
    242             set.addAll(topLevelClasses);
    243             for (ClassPathPackageInfo info : getSubpackages()) {
    244                 info.addTopLevelClassesTo(set);
    245             }
    246         }
    247 
    248         @Override
    249         public boolean equals(Object obj) {
    250             if (obj instanceof ClassPathPackageInfo) {
    251                 ClassPathPackageInfo that = (ClassPathPackageInfo) obj;
    252                 return (this.packageName).equals(that.packageName);
    253             }
    254             return false;
    255         }
    256 
    257         @Override
    258         public int hashCode() {
    259             return packageName.hashCode();
    260         }
    261     }
    262 }
    263