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