blob: 9bcc25adf8d3b60a0a2dcb730334b5f6038405d8 [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
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
17package android.test;
18
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080019import android.util.Log;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080020import dalvik.system.DexFile;
21
22import java.io.File;
23import java.io.IOException;
24import java.util.Enumeration;
Paul Duffin8c5a24d2017-05-10 13:30:16 +010025import java.util.HashMap;
26import java.util.HashSet;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import java.util.Map;
28import java.util.Set;
29import java.util.TreeSet;
30import java.util.regex.Pattern;
31import java.util.zip.ZipEntry;
32import java.util.zip.ZipFile;
33
34/**
35 * Generate {@link ClassPathPackageInfo}s by scanning apk paths.
Stephan Linznerb51617f2016-01-27 18:09:50 -080036 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037 * {@hide} Not needed for 1.0 SDK.
38 */
Stephan Linznerb51617f2016-01-27 18:09:50 -080039@Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080040public 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
Paul Duffin8c5a24d2017-05-10 13:30:16 +010060 private final Map<File, Set<String>> jarFiles = new HashMap<>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061 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>();
Paul Duffin8c5a24d2017-05-10 13:30:16 +010079 Set<Class<?>> topLevelClasses = new HashSet<>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080080 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 }
Stephan Linznerb51617f2016-01-27 18:09:50 -080086
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080087 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));
Raluca Sauciuc0180dfe2014-10-03 10:58:29 -070091 } catch (ClassNotFoundException | NoClassDefFoundError e) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080092 // 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);
Brian Carlstrom08065b92011-04-01 15:49:41 -0700122 } else {
123 // scan the directories that contain apk files.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800124 for (String apkPath : apkPaths) {
125 File file = new File(apkPath);
126 scanForApkFiles(file, packageName, classNames, subpackageNames);
127 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800128 }
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)) {
Brett Chabot2c62f842009-03-31 17:07:19 -0700219 String subPackageName = packageName;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800220 int lastPackageSeparator = className.lastIndexOf('.');
Brett Chabot2c62f842009-03-31 17:07:19 -0700221 if (lastPackageSeparator > 0) {
222 subPackageName = className.substring(0, lastPackageSeparator);
223 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800224 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) {
Joe Onorato43a17652011-04-06 19:22:23 -0700232 if (false) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800233 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) {
Paul Duffin8c5a24d2017-05-10 13:30:16 +0100251 entryNames = new HashSet<>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800252 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}