1 /*
2  * Copyright (C) 2011 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.tradefed.util;
18 
19 import com.android.tradefed.log.LogUtil.CLog;
20 
21 import java.io.File;
22 import java.io.IOException;
23 import java.util.Enumeration;
24 import java.util.LinkedHashMap;
25 import java.util.LinkedHashSet;
26 import java.util.LinkedList;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.jar.JarFile;
31 import java.util.regex.Pattern;
32 import java.util.zip.ZipEntry;
33 
34 /**
35  * Finds entries on classpath.
36  *
37  * <p>Adapted from vogar.target.ClassPathScanner</p>
38  */
39 public class ClassPathScanner {
40 
41     private String[] mClassPath;
42 
43     /**
44      * A filter for classpath entry paths
45      * <p/>
46      * Patterned after {@link java.io.FileFilter}
47      */
48     public static interface IClassPathFilter {
49         /**
50          * Tests whether or not the specified abstract pathname should be included in a class path
51          * entry list.
52          *
53          * @param pathName the relative path of the class path entry
54          */
accept(String pathName)55         boolean accept(String pathName);
56 
57         /**
58          * An optional converter for a class path entry path names.
59          *
60          * @param pathName the relative path of the class path entry, in format "foo/path/file.ext".
61          * @return the pathName converted into context specific format
62          */
63 
transform(String pathName)64         String transform(String pathName);
65     }
66 
67     /**
68      * A {@link IClassPathFilter} that filters and transforms java class names.
69      */
70     public static class ClassNameFilter implements IClassPathFilter {
71         private static final String DOT_CLASS = ".class";
72 
73         /**
74          * {@inheritDoc}
75          */
76         @Override
accept(String pathName)77         public boolean accept(String pathName) {
78             return pathName.endsWith(DOT_CLASS);
79         }
80 
81         /**
82          * {@inheritDoc}
83          */
84         @Override
transform(String pathName)85         public String transform(String pathName) {
86             String className = pathName.substring(0, pathName.length() - DOT_CLASS.length());
87             className = className.replace('/', '.');
88             return className;
89         }
90 
91     }
92 
93     /**
94      * A {@link ClassNameFilter} that rejects inner classes
95      */
96     public static class ExternalClassNameFilter extends  ClassNameFilter {
97         /**
98          * {@inheritDoc}
99          */
100         @Override
accept(String pathName)101         public boolean accept(String pathName) {
102             return super.accept(pathName) && !pathName.contains("$");
103         }
104     }
105 
ClassPathScanner()106     public ClassPathScanner() {
107         mClassPath = getClassPath();
108     }
109 
110     /**
111      * Gets the names of all entries contained in given jar file, that match given filter
112      *
113      * @throws IOException
114      */
getEntriesFromJar(File plainFile, IClassPathFilter filter)115     public Map<String, String> getEntriesFromJar(File plainFile, IClassPathFilter filter)
116             throws IOException {
117         Map<String, String> entryNames = new LinkedHashMap<>();
118         JarFile jarFile = new JarFile(plainFile);
119         for (Enumeration<? extends ZipEntry> e = jarFile.entries(); e.hasMoreElements(); ) {
120             String entryName = e.nextElement().getName();
121             if (filter.accept(entryName)) {
122                 entryNames.put(filter.transform(entryName), plainFile.getName());
123             }
124             entryName = null;
125         }
126         jarFile.close();
127         return entryNames;
128     }
129 
130     /**
131      * Gets the names of all entries contained in given class path directory, that match given
132      * filter
133      * @throws IOException
134      */
getEntriesFromDir(File classPathDir, IClassPathFilter filter)135     public Set<String> getEntriesFromDir(File classPathDir, IClassPathFilter filter)
136             throws IOException {
137         Set<String> entryNames = new LinkedHashSet<String>();
138         getEntriesFromDir(classPathDir, entryNames, new LinkedList<String>(), filter);
139         return entryNames;
140     }
141 
142     /**
143      * Recursively adds the names of all entries contained in given class path directory,
144      * that match given filter.
145      *
146      * @param dir the directory to scan
147      * @param entries the {@link Set} of class path entry names to add to
148      * @param rootPath the relative path of <var>dir</var> from class path element root
149      * @param filter the {@link IClassPathFilter} to use
150      * @throws IOException
151      */
getEntriesFromDir(File dir, Set<String> entries, List<String> rootPath, IClassPathFilter filter)152     private void getEntriesFromDir(File dir, Set<String> entries, List<String> rootPath,
153             IClassPathFilter filter) throws IOException {
154         File[] childFiles = dir.listFiles();
155         if (childFiles == null) {
156             CLog.w("Directory %s in classPath is not readable, skipping", dir.getAbsolutePath());
157             return;
158         }
159         for (File childFile : childFiles) {
160             if (childFile.isDirectory()) {
161                 rootPath.add(childFile.getName() + "/");
162                 getEntriesFromDir(childFile, entries, rootPath, filter);
163                 // pop off the path element for this directory
164                 rootPath.remove(rootPath.size() - 1);
165             } else if (childFile.isFile()) {
166                 // construct relative path of this file
167                 String classPathEntryName = constructPath(rootPath, childFile.getName());
168                 if (filter.accept(classPathEntryName)) {
169                     entries.add(filter.transform(classPathEntryName));
170                 }
171             } else {
172                 CLog.d("file %s in classPath is not recognized, skipping", dir.getAbsolutePath());
173             }
174         }
175     }
176 
177     /**
178      * Construct a relative class path path for the given class path file
179      *
180      * @param rootPath the root path in {@link List} form
181      * @param fileName the file name
182      * @return the relative classpath path
183      */
constructPath(List<String> rootPath, String fileName)184     private String constructPath(List<String> rootPath, String fileName) {
185         StringBuilder pathBuilder = new StringBuilder();
186         for (String element : rootPath) {
187             pathBuilder.append(element);
188         }
189         pathBuilder.append(fileName);
190         return pathBuilder.toString();
191     }
192 
193     /**
194      * Retrieves set of classpath entries that match given {@link IClassPathFilter}
195      */
getClassPathEntries(IClassPathFilter filter)196     public Set<String> getClassPathEntries(IClassPathFilter filter) {
197         Set<String> entryNames = new LinkedHashSet<String>();
198         for (String classPathElement : mClassPath) {
199             File classPathFile = new File(classPathElement);
200             try {
201                 if (classPathFile.isFile() && classPathElement.endsWith(".jar")) {
202                     entryNames.addAll(getEntriesFromJar(classPathFile, filter).keySet());
203                 } else if (classPathFile.isDirectory()) {
204                     entryNames.addAll(getEntriesFromDir(classPathFile, filter));
205                 } else {
206                     CLog.w(
207                             "class path entry %s does not exist or is not recognized, skipping",
208                             classPathElement);
209                 }
210             } catch (IOException e) {
211                 CLog.w(
212                         "Failed to read class path entry %s. Reason: %s",
213                         classPathElement, e.toString());
214             }
215         }
216         return entryNames;
217     }
218 
219     /**
220      * Retrieves set of classpath entries that match given {@link IClassPathFilter} and returns them
221      * with which JAR they come from. Used to validate origin of files.
222      */
getClassPathEntriesFromJar(IClassPathFilter filter)223     public Map<String, String> getClassPathEntriesFromJar(IClassPathFilter filter) {
224         Map<String, String> entryNames = new LinkedHashMap<>();
225         for (String classPathElement : mClassPath) {
226             File classPathFile = new File(classPathElement);
227             try {
228                 if (classPathFile.isFile() && classPathElement.endsWith(".jar")) {
229                     entryNames.putAll(getEntriesFromJar(classPathFile, filter));
230                 } else {
231                     CLog.w(
232                             "class path entry %s does not exist or is not recognized, skipping",
233                             classPathElement);
234                 }
235             } catch (IOException e) {
236                 CLog.w(
237                         "Failed to read class path entry %s. Reason: %s",
238                         classPathElement, e.toString());
239             }
240         }
241         return entryNames;
242     }
243 
244     /**
245      * Gets the class path from the System Property "java.class.path" and splits
246      * it up into the individual elements.
247      */
getClassPath()248     public static String[] getClassPath() {
249         String classPath = System.getProperty("java.class.path");
250         return classPath.split(Pattern.quote(File.pathSeparator));
251     }
252 }
253