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