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