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