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 ClassPathPackageInfoSource(ClassLoader classLoader)59 private ClassPathPackageInfoSource(ClassLoader classLoader) { 60 this.classLoader = classLoader; 61 classPath = getClassPath(); 62 } 63 setApkPaths(String[] apkPaths)64 static void setApkPaths(String[] apkPaths) { 65 ClassPathPackageInfoSource.apkPaths = apkPaths; 66 } 67 forClassPath(ClassLoader classLoader)68 public static ClassPathPackageInfoSource forClassPath(ClassLoader classLoader) { 69 if (classPathSource == null) { 70 classPathSource = new ClassPathPackageInfoSource(classLoader); 71 } 72 return classPathSource; 73 } 74 getTopLevelClassesRecursive(String packageName)75 public Set<Class<?>> getTopLevelClassesRecursive(String packageName) { 76 ClassPathPackageInfo packageInfo = cache.get(packageName); 77 return packageInfo.getTopLevelClassesRecursive(); 78 } 79 createPackageInfo(String packageName)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 */ findClasses(String packageName, Set<String> classNames, Set<String> subpackageNames)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 scanForApkFiles(File source, String packageName, Set<String> classNames, Set<String> subpackageNames)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 */ findClassesInApk(String apkPath, String packageName, Set<String> classNames, Set<String> subpackageNames)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 */ isToplevelClass(String fileName)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 */ getClassPath()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 ClassPathPackageInfo(String packageName, Set<String> subpackageNames, Set<Class<?>> topLevelClasses)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 getSubpackages()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 getTopLevelClassesRecursive()235 private Set<Class<?>> getTopLevelClassesRecursive() { 236 Set<Class<?>> set = new HashSet<>(); 237 addTopLevelClassesTo(set); 238 return set; 239 } 240 addTopLevelClassesTo(Set<Class<?>> set)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 equals(Object obj)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 hashCode()258 public int hashCode() { 259 return packageName.hashCode(); 260 } 261 } 262 } 263