blob: 755b540cbbb8fada2ff05a11873ff66414e77fbf [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;
Paul Duffinaaaba762017-06-20 16:18:07 +010024import java.util.Collections;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080025import java.util.Enumeration;
Paul Duffin8c5a24d2017-05-10 13:30:16 +010026import java.util.HashSet;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080027import java.util.Set;
28import java.util.TreeSet;
29import java.util.regex.Pattern;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030
31/**
32 * Generate {@link ClassPathPackageInfo}s by scanning apk paths.
Stephan Linznerb51617f2016-01-27 18:09:50 -080033 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080034 * {@hide} Not needed for 1.0 SDK.
35 */
Stephan Linznerb51617f2016-01-27 18:09:50 -080036@Deprecated
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080037public class ClassPathPackageInfoSource {
38
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039 private static final ClassLoader CLASS_LOADER
40 = ClassPathPackageInfoSource.class.getClassLoader();
41
Paul Duffinaaaba762017-06-20 16:18:07 +010042 private static String[] apkPaths;
43
44 private static ClassPathPackageInfoSource classPathSource;
45
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080046 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;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080056
Paul Duffinaaaba762017-06-20 16:18:07 +010057 private final ClassLoader classLoader;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080058
Paul Duffinaaaba762017-06-20 16:18:07 +010059 private ClassPathPackageInfoSource(ClassLoader classLoader) {
60 this.classLoader = classLoader;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061 classPath = getClassPath();
62 }
63
Paul Duffinaaaba762017-06-20 16:18:07 +010064 static void setApkPaths(String[] apkPaths) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065 ClassPathPackageInfoSource.apkPaths = apkPaths;
66 }
67
Paul Duffinaaaba762017-06-20 16:18:07 +010068 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();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080078 }
79
80 private ClassPathPackageInfo createPackageInfo(String packageName) {
81 Set<String> subpackageNames = new TreeSet<String>();
82 Set<String> classNames = new TreeSet<String>();
Paul Duffin8c5a24d2017-05-10 13:30:16 +010083 Set<Class<?>> topLevelClasses = new HashSet<>();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080084 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 }
Stephan Linznerb51617f2016-01-27 18:09:50 -080090
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080091 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));
Raluca Sauciuc0180dfe2014-10-03 10:58:29 -070095 } catch (ClassNotFoundException | NoClassDefFoundError e) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080096 // 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 }
Paul Duffinaaaba762017-06-20 16:18:07 +0100103 return new ClassPathPackageInfo(packageName, subpackageNames,
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800104 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) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 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);
Brian Carlstrom08065b92011-04-01 15:49:41 -0700123 } else {
124 // scan the directories that contain apk files.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800125 for (String apkPath : apkPaths) {
126 File file = new File(apkPath);
127 scanForApkFiles(file, packageName, classNames, subpackageNames);
128 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800129 }
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
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800154 * 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)) {
Brett Chabot2c62f842009-03-31 17:07:19 -0700168 String subPackageName = packageName;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800169 int lastPackageSeparator = className.lastIndexOf('.');
Brett Chabot2c62f842009-03-31 17:07:19 -0700170 if (lastPackageSeparator > 0) {
171 subPackageName = className.substring(0, lastPackageSeparator);
172 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800173 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) {
Joe Onorato43a17652011-04-06 19:22:23 -0700181 if (false) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800182 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 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800194 * 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 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800201 * 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
Paul Duffinaaaba762017-06-20 16:18:07 +0100210 /**
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 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800261 }
262}