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