DexPathList.java revision 6133a233130f3014b65b96ca92a20c2083c713f4
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 17package dalvik.system; 18 19import java.io.File; 20import java.io.IOException; 21import java.net.MalformedURLException; 22import java.net.URL; 23import java.util.ArrayList; 24import java.util.Collections; 25import java.util.Enumeration; 26import java.util.regex.Pattern; 27import java.util.zip.ZipFile; 28import libcore.io.ErrnoException; 29import libcore.io.IoUtils; 30import libcore.io.Libcore; 31import libcore.io.StructStat; 32import static libcore.io.OsConstants.*; 33 34/** 35 * A pair of lists of entries, associated with a {@code ClassLoader}. 36 * One of the lists is a dex/resource path — typically referred 37 * to as a "class path" — list, and the other names directories 38 * containing native code libraries. Class path entries may be any of: 39 * a {@code .jar} or {@code .zip} file containing an optional 40 * top-level {@code classes.dex} file as well as arbitrary resources, 41 * or a plain {@code .dex} file (with no possibility of associated 42 * resources). 43 * 44 * <p>This class also contains methods to use these lists to look up 45 * classes and resources.</p> 46 */ 47/*package*/ final class DexPathList { 48 private static final String DEX_SUFFIX = ".dex"; 49 private static final String JAR_SUFFIX = ".jar"; 50 private static final String ZIP_SUFFIX = ".zip"; 51 private static final String APK_SUFFIX = ".apk"; 52 53 /** class definition context */ 54 private final ClassLoader definingContext; 55 56 /** list of dex/resource (class path) elements */ 57 private final Element[] dexElements; 58 59 /** list of native library directory elements */ 60 private final File[] nativeLibraryDirectories; 61 62 /** 63 * Constructs an instance. 64 * 65 * @param definingContext the context in which any as-yet unresolved 66 * classes should be defined 67 * @param dexPath list of dex/resource path elements, separated by 68 * {@code File.pathSeparator} 69 * @param libraryPath list of native library directory path elements, 70 * separated by {@code File.pathSeparator} 71 * @param optimizedDirectory directory where optimized {@code .dex} files 72 * should be found and written to, or {@code null} to use the default 73 * system directory for same 74 */ 75 public DexPathList(ClassLoader definingContext, String dexPath, 76 String libraryPath, File optimizedDirectory) { 77 if (definingContext == null) { 78 throw new NullPointerException("definingContext == null"); 79 } 80 81 if (dexPath == null) { 82 throw new NullPointerException("dexPath == null"); 83 } 84 85 if (optimizedDirectory != null) { 86 if (!optimizedDirectory.exists()) { 87 throw new IllegalArgumentException( 88 "optimizedDirectory doesn't exist: " 89 + optimizedDirectory); 90 } 91 92 if (!(optimizedDirectory.canRead() 93 && optimizedDirectory.canWrite())) { 94 throw new IllegalArgumentException( 95 "optimizedDirectory not readable/writable: " 96 + optimizedDirectory); 97 } 98 } 99 100 this.definingContext = definingContext; 101 this.dexElements = 102 makeDexElements(splitDexPath(dexPath), optimizedDirectory); 103 this.nativeLibraryDirectories = splitLibraryPath(libraryPath); 104 } 105 106 /** 107 * Splits the given dex path string into elements using the path 108 * separator, pruning out any elements that do not refer to existing 109 * and readable files. (That is, directories are not included in the 110 * result.) 111 */ 112 private static ArrayList<File> splitDexPath(String path) { 113 return splitPaths(path, null, false); 114 } 115 116 /** 117 * Splits the given library directory path string into elements 118 * using the path separator ({@code File.pathSeparator}, which 119 * defaults to {@code ":"} on Android, appending on the elements 120 * from the system library path, and pruning out any elements that 121 * do not refer to existing and readable directories. 122 */ 123 private static File[] splitLibraryPath(String path) { 124 /* 125 * Native libraries may exist in both the system and 126 * application library paths, and we use this search order: 127 * 128 * 1. this class loader's library path for application 129 * libraries 130 * 2. the VM's library path from the system 131 * property for system libraries 132 * 133 * This order was reversed prior to Gingerbread; see http://b/2933456. 134 */ 135 ArrayList<File> result = splitPaths( 136 path, System.getProperty("java.library.path", "."), true); 137 return result.toArray(new File[result.size()]); 138 } 139 140 /** 141 * Splits the given path strings into file elements using the path 142 * separator, combining the results and filtering out elements 143 * that don't exist, aren't readable, or aren't either a regular 144 * file or a directory (as specified). Either string may be empty 145 * or {@code null}, in which case it is ignored. If both strings 146 * are empty or {@code null}, or all elements get pruned out, then 147 * this returns a zero-element list. 148 */ 149 private static ArrayList<File> splitPaths(String path1, String path2, 150 boolean wantDirectories) { 151 ArrayList<File> result = new ArrayList<File>(); 152 153 splitAndAdd(path1, wantDirectories, result); 154 splitAndAdd(path2, wantDirectories, result); 155 return result; 156 } 157 158 /** 159 * Helper for {@link #splitPaths}, which does the actual splitting 160 * and filtering and adding to a result. 161 */ 162 private static void splitAndAdd(String searchPath, boolean wantDirectories, 163 ArrayList<File> resultList) { 164 if (searchPath == null) { 165 return; 166 } 167 for (String path : searchPath.split(":")) { 168 try { 169 StructStat sb = Libcore.os.stat(path); 170 if ((wantDirectories && S_ISDIR(sb.st_mode)) || 171 (!wantDirectories && S_ISREG(sb.st_mode))) { 172 resultList.add(new File(path)); 173 } 174 } catch (ErrnoException ignored) { 175 } 176 } 177 } 178 179 /** 180 * Makes an array of dex/resource path elements, one per element of 181 * the given array. 182 */ 183 private static Element[] makeDexElements(ArrayList<File> files, 184 File optimizedDirectory) { 185 ArrayList<Element> elements = new ArrayList<Element>(); 186 187 /* 188 * Open all files and load the (direct or contained) dex files 189 * up front. 190 */ 191 for (File file : files) { 192 File zip = null; 193 DexFile dex = null; 194 String name = file.getName(); 195 196 if (name.endsWith(DEX_SUFFIX)) { 197 // Raw dex file (not inside a zip/jar). 198 try { 199 dex = loadDexFile(file, optimizedDirectory); 200 } catch (IOException ex) { 201 System.logE("Unable to load dex file: " + file, ex); 202 } 203 } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX) 204 || name.endsWith(ZIP_SUFFIX)) { 205 zip = file; 206 207 try { 208 dex = loadDexFile(file, optimizedDirectory); 209 } catch (IOException ignored) { 210 /* 211 * IOException might get thrown "legitimately" by 212 * the DexFile constructor if the zip file turns 213 * out to be resource-only (that is, no 214 * classes.dex file in it). Safe to just ignore 215 * the exception here, and let dex == null. 216 */ 217 } 218 } else { 219 System.logW("Unknown file type for: " + file); 220 } 221 222 if ((zip != null) || (dex != null)) { 223 elements.add(new Element(file, zip, dex)); 224 } 225 } 226 227 return elements.toArray(new Element[elements.size()]); 228 } 229 230 /** 231 * Constructs a {@code DexFile} instance, as appropriate depending 232 * on whether {@code optimizedDirectory} is {@code null}. 233 */ 234 private static DexFile loadDexFile(File file, File optimizedDirectory) 235 throws IOException { 236 if (optimizedDirectory == null) { 237 return new DexFile(file); 238 } else { 239 String optimizedPath = optimizedPathFor(file, optimizedDirectory); 240 return DexFile.loadDex(file.getPath(), optimizedPath, 0); 241 } 242 } 243 244 /** 245 * Converts a dex/jar file path and an output directory to an 246 * output file path for an associated optimized dex file. 247 */ 248 private static String optimizedPathFor(File path, 249 File optimizedDirectory) { 250 /* 251 * Get the filename component of the path, and replace the 252 * suffix with ".dex" if that's not already the suffix. 253 * 254 * We don't want to use ".odex", because the build system uses 255 * that for files that are paired with resource-only jar 256 * files. If the VM can assume that there's no classes.dex in 257 * the matching jar, it doesn't need to open the jar to check 258 * for updated dependencies, providing a slight performance 259 * boost at startup. The use of ".dex" here matches the use on 260 * files in /data/dalvik-cache. 261 */ 262 String fileName = path.getName(); 263 if (!fileName.endsWith(DEX_SUFFIX)) { 264 int lastDot = fileName.lastIndexOf("."); 265 if (lastDot < 0) { 266 fileName += DEX_SUFFIX; 267 } else { 268 StringBuilder sb = new StringBuilder(lastDot + 4); 269 sb.append(fileName, 0, lastDot); 270 sb.append(DEX_SUFFIX); 271 fileName = sb.toString(); 272 } 273 } 274 275 File result = new File(optimizedDirectory, fileName); 276 return result.getPath(); 277 } 278 279 /** 280 * Finds the named class in one of the dex files pointed at by 281 * this instance. This will find the one in the earliest listed 282 * path element. If the class is found but has not yet been 283 * defined, then this method will define it in the defining 284 * context that this instance was constructed with. 285 * 286 * @return the named class or {@code null} if the class is not 287 * found in any of the dex files 288 */ 289 public Class findClass(String name) { 290 for (Element element : dexElements) { 291 DexFile dex = element.dexFile; 292 293 if (dex != null) { 294 Class clazz = dex.loadClassBinaryName(name, definingContext); 295 if (clazz != null) { 296 return clazz; 297 } 298 } 299 } 300 301 return null; 302 } 303 304 /** 305 * Finds the named resource in one of the zip/jar files pointed at 306 * by this instance. This will find the one in the earliest listed 307 * path element. 308 * 309 * @return a URL to the named resource or {@code null} if the 310 * resource is not found in any of the zip/jar files 311 */ 312 public URL findResource(String name) { 313 for (Element element : dexElements) { 314 URL url = element.findResource(name); 315 if (url != null) { 316 return url; 317 } 318 } 319 320 return null; 321 } 322 323 /** 324 * Finds all the resources with the given name, returning an 325 * enumeration of them. If there are no resources with the given 326 * name, then this method returns an empty enumeration. 327 */ 328 public Enumeration<URL> findResources(String name) { 329 ArrayList<URL> result = new ArrayList<URL>(); 330 331 for (Element element : dexElements) { 332 URL url = element.findResource(name); 333 if (url != null) { 334 result.add(url); 335 } 336 } 337 338 return Collections.enumeration(result); 339 } 340 341 /** 342 * Finds the named native code library on any of the library 343 * directories pointed at by this instance. This will find the 344 * one in the earliest listed directory, ignoring any that are not 345 * readable regular files. 346 * 347 * @return the complete path to the library or {@code null} if no 348 * library was found 349 */ 350 public String findLibrary(String libraryName) { 351 String fileName = System.mapLibraryName(libraryName); 352 for (File directory : nativeLibraryDirectories) { 353 String path = new File(directory, fileName).getPath(); 354 if (IoUtils.canOpenReadOnly(path)) { 355 return path; 356 } 357 } 358 return null; 359 } 360 361 /** 362 * Element of the dex/resource file path 363 */ 364 /*package*/ static class Element { 365 private final File file; 366 private final File zip; 367 private final DexFile dexFile; 368 369 private ZipFile zipFile; 370 private boolean init; 371 372 public Element(File file, File zip, DexFile dexFile) { 373 this.file = file; 374 this.zip = zip; 375 this.dexFile = dexFile; 376 } 377 378 public synchronized void maybeInit() { 379 if (init) { 380 return; 381 } 382 383 init = true; 384 385 if (zip == null) { 386 /* 387 * Either this element has no zip/jar file (first 388 * clause), or the zip/jar file doesn't have an entry 389 * for the given name (second clause). 390 */ 391 return; 392 } 393 394 try { 395 zipFile = new ZipFile(zip); 396 } catch (IOException ioe) { 397 /* 398 * Note: ZipException (a subclass of IOException) 399 * might get thrown by the ZipFile constructor 400 * (e.g. if the file isn't actually a zip/jar 401 * file). 402 */ 403 System.logE("Unable to open zip file: " + file, ioe); 404 zipFile = null; 405 } 406 } 407 408 public URL findResource(String name) { 409 maybeInit(); 410 411 if (zipFile == null || zipFile.getEntry(name) == null) { 412 return null; 413 } 414 415 try { 416 /* 417 * File.toURL() is compliant with RFC 1738 in 418 * always creating absolute path names. If we 419 * construct the URL by concatenating strings, we 420 * might end up with illegal URLs for relative 421 * names. 422 */ 423 return new URL("jar:" + file.toURL() + "!/" + name); 424 } catch (MalformedURLException ex) { 425 throw new RuntimeException(ex); 426 } 427 } 428 } 429} 430