• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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 com.android.test.runner;
18 
19 import dalvik.system.DexFile;
20 
21 import java.io.IOException;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Enumeration;
25 import java.util.HashSet;
26 import java.util.LinkedHashSet;
27 import java.util.List;
28 import java.util.Set;
29 
30 /**
31  * Finds class entries in apks.
32  * <p/>
33  * Adapted from tools/tradefederation/..ClassPathScanner
34  */
35 class ClassPathScanner {
36 
37     /**
38      * A filter for classpath entry paths
39      * <p/>
40      * Patterned after {@link java.io.FileFilter}
41      */
42     public static interface ClassNameFilter {
43         /**
44          * Tests whether or not the specified abstract pathname should be included in a class path
45          * entry list.
46          *
47          * @param pathName the relative path of the class path entry
48          */
accept(String className)49         boolean accept(String className);
50     }
51 
52     /**
53      * A {@link ClassNameFilter} that accepts all class names.
54      */
55     public static class AcceptAllFilter implements ClassNameFilter {
56 
57         /**
58          * {@inheritDoc}
59          */
60         @Override
accept(String className)61         public boolean accept(String className) {
62             return true;
63         }
64 
65     }
66 
67     /**
68      * A {@link ClassNameFilter} that chains one or more filters together
69      */
70     public static class ChainedClassNameFilter implements ClassNameFilter {
71         private final List<ClassNameFilter> mFilters = new ArrayList<ClassNameFilter>();
72 
add(ClassNameFilter filter)73         public void add(ClassNameFilter filter) {
74             mFilters.add(filter);
75         }
76 
addAll(ClassNameFilter... filters)77         public void addAll(ClassNameFilter... filters) {
78             mFilters.addAll(Arrays.asList(filters));
79         }
80 
81         /**
82          * {@inheritDoc}
83          */
84         @Override
accept(String className)85         public boolean accept(String className) {
86             for (ClassNameFilter filter : mFilters) {
87                 if (!filter.accept(className)) {
88                     return false;
89                 }
90             }
91             return true;
92         }
93     }
94 
95     /**
96      * A {@link ClassNameFilter} that rejects inner classes.
97      */
98     public static class ExternalClassNameFilter implements ClassNameFilter {
99         /**
100          * {@inheritDoc}
101          */
102         @Override
accept(String pathName)103         public boolean accept(String pathName) {
104             return !pathName.contains("$");
105         }
106     }
107 
108     /**
109      * A {@link ClassNameFilter} that only accepts package names within the given namespace.
110      */
111     public static class InclusivePackageNameFilter implements ClassNameFilter {
112 
113         private final String mPkgName;
114 
InclusivePackageNameFilter(String pkgName)115         InclusivePackageNameFilter(String pkgName) {
116             if (!pkgName.endsWith(".")) {
117                 mPkgName = String.format("%s.", pkgName);
118             } else {
119                 mPkgName = pkgName;
120             }
121         }
122 
123         /**
124          * {@inheritDoc}
125          */
126         @Override
accept(String pathName)127         public boolean accept(String pathName) {
128             return pathName.startsWith(mPkgName);
129         }
130     }
131 
132     /**
133      * A {@link ClassNameFilter} that only rejects a given package names within the given namespace.
134      */
135     public static class ExcludePackageNameFilter implements ClassNameFilter {
136 
137         private final String mPkgName;
138 
ExcludePackageNameFilter(String pkgName)139         ExcludePackageNameFilter(String pkgName) {
140             if (!pkgName.endsWith(".")) {
141                 mPkgName = String.format("%s.", pkgName);
142             } else {
143                 mPkgName = pkgName;
144             }
145         }
146 
147         /**
148          * {@inheritDoc}
149          */
150         @Override
accept(String pathName)151         public boolean accept(String pathName) {
152             return !pathName.startsWith(mPkgName);
153         }
154     }
155 
156     private Set<String> mApkPaths = new HashSet<String>();
157 
ClassPathScanner(String... apkPaths)158     public ClassPathScanner(String... apkPaths) {
159         for (String apkPath : apkPaths) {
160             mApkPaths.add(apkPath);
161         }
162     }
163 
164     /**
165      * Gets the names of all entries contained in given apk file, that match given filter.
166      * @throws IOException
167      */
addEntriesFromApk(Set<String> entryNames, String apkPath, ClassNameFilter filter)168     private void addEntriesFromApk(Set<String> entryNames, String apkPath, ClassNameFilter filter)
169             throws IOException {
170         DexFile dexFile = null;
171         try {
172             dexFile = new DexFile(apkPath);
173             Enumeration<String> apkClassNames = getDexEntries(dexFile);
174             while (apkClassNames.hasMoreElements()) {
175                 String apkClassName = apkClassNames.nextElement();
176                 if (filter.accept(apkClassName)) {
177                     entryNames.add(apkClassName);
178                 }
179             }
180         } finally {
181             if (dexFile != null) {
182                 dexFile.close();
183             }
184         }
185     }
186 
187     /**
188      * Retrieves the entry names from given {@link DexFile}.
189      * <p/>
190      * Exposed for unit testing.
191      *
192      * @param dexFile
193      * @return {@link Enumeration} of {@link String}s
194      */
getDexEntries(DexFile dexFile)195     Enumeration<String> getDexEntries(DexFile dexFile) {
196         return dexFile.entries();
197     }
198 
199     /**
200      * Retrieves set of classpath entries that match given {@link ClassNameFilter}.
201      * @throws IOException
202      */
getClassPathEntries(ClassNameFilter filter)203     public Set<String> getClassPathEntries(ClassNameFilter filter) throws IOException {
204         // use LinkedHashSet for predictable order
205         Set<String> entryNames = new LinkedHashSet<String>();
206         for (String apkPath : mApkPaths) {
207             addEntriesFromApk(entryNames, apkPath, filter);
208         }
209         return entryNames;
210     }
211 }
212