• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ClassPathPackageInfoSource()63     ClassPathPackageInfoSource() {
64         classPath = getClassPath();
65     }
66 
67 
setApkPaths(String[] apkPaths)68     public static void setApkPaths(String[] apkPaths) {
69         ClassPathPackageInfoSource.apkPaths = apkPaths;
70     }
71 
getPackageInfo(String pkgName)72     public ClassPathPackageInfo getPackageInfo(String pkgName) {
73         return cache.get(pkgName);
74     }
75 
createPackageInfo(String packageName)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      */
findClasses(String packageName, Set<String> classNames, Set<String> subpackageNames)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 
scanForApkFiles(File source, String packageName, Set<String> classNames, Set<String> subpackageNames)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      */
findClassesInDirectory(File classDir, String packagePrefix, String pathPrefix, Set<String> classNames, Set<String> subpackageNames)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      */
findClassesInJar(File jarFile, String pathPrefix, Set<String> classNames, Set<String> subpackageNames)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      */
findClassesInApk(String apkPath, String packageName, Set<String> classNames, Set<String> subpackageNames)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      */
getJarEntries(File jarFile)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      */
isToplevelClass(String fileName)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      */
getClassName(String className)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      */
getClassPath()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 
setClassLoader(ClassLoader classLoader)310     public void setClassLoader(ClassLoader classLoader) {
311         this.classLoader = classLoader;
312     }
313 }
314