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.LinkedHashSet; 25 import java.util.LinkedList; 26 import java.util.List; 27 import java.util.Set; 28 import java.util.jar.JarFile; 29 import java.util.regex.Pattern; 30 import java.util.zip.ZipEntry; 31 32 /** 33 * Finds entries on classpath. 34 * 35 * <p>Adapted from vogar.target.ClassPathScanner</p> 36 */ 37 public class ClassPathScanner { 38 39 private static final String LOG_TAG = "ClassPathScanner"; 40 private String[] mClassPath; 41 42 /** 43 * A filter for classpath entry paths 44 * <p/> 45 * Patterned after {@link java.io.FileFilter} 46 */ 47 public static interface IClassPathFilter { 48 /** 49 * Tests whether or not the specified abstract pathname should be included in a class path 50 * entry list. 51 * 52 * @param pathName the relative path of the class path entry 53 */ accept(String pathName)54 boolean accept(String pathName); 55 56 /** 57 * An optional converter for a class path entry path names. 58 * 59 * @param pathName the relative path of the class path entry, in format "foo/path/file.ext". 60 * @return the pathName converted into context specific format 61 */ 62 transform(String pathName)63 String transform(String pathName); 64 } 65 66 /** 67 * A {@link IClassPathFilter} that filters and transforms java class names. 68 */ 69 public static class ClassNameFilter implements IClassPathFilter { 70 private static final String DOT_CLASS = ".class"; 71 72 /** 73 * {@inheritDoc} 74 */ 75 @Override accept(String pathName)76 public boolean accept(String pathName) { 77 return pathName.endsWith(DOT_CLASS); 78 } 79 80 /** 81 * {@inheritDoc} 82 */ 83 @Override transform(String pathName)84 public String transform(String pathName) { 85 String className = pathName.substring(0, pathName.length() - DOT_CLASS.length()); 86 className = className.replace('/', '.'); 87 return className; 88 } 89 90 } 91 92 /** 93 * A {@link ClassNameFilter} that rejects inner classes 94 */ 95 public static class ExternalClassNameFilter extends ClassNameFilter { 96 /** 97 * {@inheritDoc} 98 */ 99 @Override accept(String pathName)100 public boolean accept(String pathName) { 101 return super.accept(pathName) && !pathName.contains("$"); 102 } 103 } 104 ClassPathScanner()105 public ClassPathScanner() { 106 mClassPath = getClassPath(); 107 } 108 109 /** 110 * Gets the names of all entries contained in given jar file, that match given filter 111 * @throws IOException 112 */ getEntriesFromJar(File plainFile, IClassPathFilter filter)113 public Set<String> getEntriesFromJar(File plainFile, IClassPathFilter filter) 114 throws IOException { 115 Set<String> entryNames = new LinkedHashSet<String>(); 116 JarFile jarFile = new JarFile(plainFile); 117 for (Enumeration<? extends ZipEntry> e = jarFile.entries(); e.hasMoreElements(); ) { 118 String entryName = e.nextElement().getName(); 119 if (filter.accept(entryName)) { 120 entryNames.add(filter.transform(entryName)); 121 } 122 entryName = null; 123 } 124 jarFile.close(); 125 return entryNames; 126 } 127 128 /** 129 * Gets the names of all entries contained in given class path directory, that match given 130 * filter 131 * @throws IOException 132 */ getEntriesFromDir(File classPathDir, IClassPathFilter filter)133 public Set<String> getEntriesFromDir(File classPathDir, IClassPathFilter filter) 134 throws IOException { 135 Set<String> entryNames = new LinkedHashSet<String>(); 136 getEntriesFromDir(classPathDir, entryNames, new LinkedList<String>(), filter); 137 return entryNames; 138 } 139 140 /** 141 * Recursively adds the names of all entries contained in given class path directory, 142 * that match given filter. 143 * 144 * @param dir the directory to scan 145 * @param entries the {@link Set} of class path entry names to add to 146 * @param rootPath the relative path of <var>dir</var> from class path element root 147 * @param filter the {@link IClassPathFilter} to use 148 * @throws IOException 149 */ getEntriesFromDir(File dir, Set<String> entries, List<String> rootPath, IClassPathFilter filter)150 private void getEntriesFromDir(File dir, Set<String> entries, List<String> rootPath, 151 IClassPathFilter filter) throws IOException { 152 File[] childFiles = dir.listFiles(); 153 if (childFiles == null) { 154 Log.w(LOG_TAG, String.format("Directory %s in classPath is not readable, skipping", 155 dir.getAbsolutePath())); 156 return; 157 } 158 for (File childFile : childFiles) { 159 if (childFile.isDirectory()) { 160 rootPath.add(childFile.getName() + "/"); 161 getEntriesFromDir(childFile, entries, rootPath, filter); 162 // pop off the path element for this directory 163 rootPath.remove(rootPath.size() - 1); 164 } else if (childFile.isFile()) { 165 // construct relative path of this file 166 String classPathEntryName = constructPath(rootPath, childFile.getName()); 167 if (filter.accept(classPathEntryName)) { 168 entries.add(filter.transform(classPathEntryName)); 169 } 170 } else { 171 Log.d(LOG_TAG, String.format("file %s in classPath is not recognized, skipping", 172 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)); 203 } else if (classPathFile.isDirectory()) { 204 entryNames.addAll(getEntriesFromDir(classPathFile, filter)); 205 } else { 206 Log.w(LOG_TAG, String.format( 207 "class path entry %s does not exist or is not recognized, skipping", 208 classPathElement)); 209 } 210 } catch (IOException e) { 211 Log.w(LOG_TAG, String.format("Failed to read class path entry %s. Reason: %s", 212 classPathElement, e.toString())); 213 } 214 } 215 return entryNames; 216 } 217 218 /** 219 * Gets the class path from the System Property "java.class.path" and splits 220 * it up into the individual elements. 221 */ getClassPath()222 public static String[] getClassPath() { 223 String classPath = System.getProperty("java.class.path"); 224 return classPath.split(Pattern.quote(File.pathSeparator)); 225 } 226 } 227