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 dalvik.system; 18 19 import android.system.ErrnoException; 20 import android.system.StructStat; 21 import java.io.File; 22 import java.io.IOException; 23 import java.net.MalformedURLException; 24 import java.net.URL; 25 import java.nio.ByteBuffer; 26 import java.util.ArrayList; 27 import java.util.Arrays; 28 import java.util.Collections; 29 import java.util.Enumeration; 30 import java.util.List; 31 import libcore.io.ClassPathURLStreamHandler; 32 import libcore.io.IoUtils; 33 import libcore.io.Libcore; 34 35 import static android.system.OsConstants.S_ISDIR; 36 37 /** 38 * A pair of lists of entries, associated with a {@code ClassLoader}. 39 * One of the lists is a dex/resource path — typically referred 40 * to as a "class path" — list, and the other names directories 41 * containing native code libraries. Class path entries may be any of: 42 * a {@code .jar} or {@code .zip} file containing an optional 43 * top-level {@code classes.dex} file as well as arbitrary resources, 44 * or a plain {@code .dex} file (with no possibility of associated 45 * resources). 46 * 47 * <p>This class also contains methods to use these lists to look up 48 * classes and resources.</p> 49 */ 50 /*package*/ final class DexPathList { 51 private static final String DEX_SUFFIX = ".dex"; 52 private static final String zipSeparator = "!/"; 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 Element[] dexElements; 63 64 /** List of native library path elements. */ 65 private final NativeLibraryElement[] nativeLibraryPathElements; 66 67 /** List of application native library directories. */ 68 private final List<File> nativeLibraryDirectories; 69 70 /** List of system native library directories. */ 71 private final List<File> systemNativeLibraryDirectories; 72 73 /** 74 * Exceptions thrown during creation of the dexElements list. 75 */ 76 private IOException[] dexElementsSuppressedExceptions; 77 78 /** 79 * Construct an instance. 80 * 81 * @param definingContext the context in which any as-yet unresolved 82 * classes should be defined 83 * 84 * @param dexFiles the bytebuffers containing the dex files that we should load classes from. 85 */ 86 public DexPathList(ClassLoader definingContext, ByteBuffer[] dexFiles) { 87 if (definingContext == null) { 88 throw new NullPointerException("definingContext == null"); 89 } 90 if (dexFiles == null) { 91 throw new NullPointerException("dexFiles == null"); 92 } 93 if (Arrays.stream(dexFiles).anyMatch(v -> v == null)) { 94 throw new NullPointerException("dexFiles contains a null Buffer!"); 95 } 96 97 this.definingContext = definingContext; 98 // TODO It might be useful to let in-memory dex-paths have native libraries. 99 this.nativeLibraryDirectories = Collections.emptyList(); 100 this.systemNativeLibraryDirectories = 101 splitPaths(System.getProperty("java.library.path"), true); 102 this.nativeLibraryPathElements = makePathElements(this.systemNativeLibraryDirectories); 103 104 ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); 105 this.dexElements = makeInMemoryDexElements(dexFiles, suppressedExceptions); 106 if (suppressedExceptions.size() > 0) { 107 this.dexElementsSuppressedExceptions = 108 suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]); 109 } else { 110 dexElementsSuppressedExceptions = null; 111 } 112 } 113 114 /** 115 * Constructs an instance. 116 * 117 * @param definingContext the context in which any as-yet unresolved 118 * classes should be defined 119 * @param dexPath list of dex/resource path elements, separated by 120 * {@code File.pathSeparator} 121 * @param librarySearchPath list of native library directory path elements, 122 * separated by {@code File.pathSeparator} 123 * @param optimizedDirectory directory where optimized {@code .dex} files 124 * should be found and written to, or {@code null} to use the default 125 * system directory for same 126 */ 127 public DexPathList(ClassLoader definingContext, String dexPath, 128 String librarySearchPath, File optimizedDirectory) { 129 130 if (definingContext == null) { 131 throw new NullPointerException("definingContext == null"); 132 } 133 134 if (dexPath == null) { 135 throw new NullPointerException("dexPath == null"); 136 } 137 138 if (optimizedDirectory != null) { 139 if (!optimizedDirectory.exists()) { 140 throw new IllegalArgumentException( 141 "optimizedDirectory doesn't exist: " 142 + optimizedDirectory); 143 } 144 145 if (!(optimizedDirectory.canRead() 146 && optimizedDirectory.canWrite())) { 147 throw new IllegalArgumentException( 148 "optimizedDirectory not readable/writable: " 149 + optimizedDirectory); 150 } 151 } 152 153 this.definingContext = definingContext; 154 155 ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); 156 // save dexPath for BaseDexClassLoader 157 this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, 158 suppressedExceptions, definingContext); 159 160 // Native libraries may exist in both the system and 161 // application library paths, and we use this search order: 162 // 163 // 1. This class loader's library path for application libraries (librarySearchPath): 164 // 1.1. Native library directories 165 // 1.2. Path to libraries in apk-files 166 // 2. The VM's library path from the system property for system libraries 167 // also known as java.library.path 168 // 169 // This order was reversed prior to Gingerbread; see http://b/2933456. 170 this.nativeLibraryDirectories = splitPaths(librarySearchPath, false); 171 this.systemNativeLibraryDirectories = 172 splitPaths(System.getProperty("java.library.path"), true); 173 List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories); 174 allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories); 175 176 this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories); 177 178 if (suppressedExceptions.size() > 0) { 179 this.dexElementsSuppressedExceptions = 180 suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]); 181 } else { 182 dexElementsSuppressedExceptions = null; 183 } 184 } 185 186 @Override public String toString() { 187 List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories); 188 allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories); 189 190 File[] nativeLibraryDirectoriesArray = 191 allNativeLibraryDirectories.toArray( 192 new File[allNativeLibraryDirectories.size()]); 193 194 return "DexPathList[" + Arrays.toString(dexElements) + 195 ",nativeLibraryDirectories=" + Arrays.toString(nativeLibraryDirectoriesArray) + "]"; 196 } 197 198 /** 199 * For BaseDexClassLoader.getLdLibraryPath. 200 */ 201 public List<File> getNativeLibraryDirectories() { 202 return nativeLibraryDirectories; 203 } 204 205 /** 206 * Adds a new path to this instance 207 * @param dexPath list of dex/resource path element, separated by 208 * {@code File.pathSeparator} 209 * @param optimizedDirectory directory where optimized {@code .dex} files 210 * should be found and written to, or {@code null} to use the default 211 * system directory for same 212 */ 213 public void addDexPath(String dexPath, File optimizedDirectory) { 214 final List<IOException> suppressedExceptionList = new ArrayList<IOException>(); 215 final Element[] newElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, 216 suppressedExceptionList, definingContext); 217 218 if (newElements != null && newElements.length > 0) { 219 final Element[] oldElements = dexElements; 220 dexElements = new Element[oldElements.length + newElements.length]; 221 System.arraycopy( 222 oldElements, 0, dexElements, 0, oldElements.length); 223 System.arraycopy( 224 newElements, 0, dexElements, oldElements.length, newElements.length); 225 } 226 227 if (suppressedExceptionList.size() > 0) { 228 final IOException[] newSuppressedExceptions = suppressedExceptionList.toArray( 229 new IOException[suppressedExceptionList.size()]); 230 if (dexElementsSuppressedExceptions != null) { 231 final IOException[] oldSuppressedExceptions = dexElementsSuppressedExceptions; 232 final int suppressedExceptionsLength = oldSuppressedExceptions.length + 233 newSuppressedExceptions.length; 234 dexElementsSuppressedExceptions = new IOException[suppressedExceptionsLength]; 235 System.arraycopy(oldSuppressedExceptions, 0, dexElementsSuppressedExceptions, 236 0, oldSuppressedExceptions.length); 237 System.arraycopy(newSuppressedExceptions, 0, dexElementsSuppressedExceptions, 238 oldSuppressedExceptions.length, newSuppressedExceptions.length); 239 } else { 240 dexElementsSuppressedExceptions = newSuppressedExceptions; 241 } 242 } 243 } 244 245 /** 246 * Splits the given dex path string into elements using the path 247 * separator, pruning out any elements that do not refer to existing 248 * and readable files. 249 */ 250 private static List<File> splitDexPath(String path) { 251 return splitPaths(path, false); 252 } 253 254 /** 255 * Splits the given path strings into file elements using the path 256 * separator, combining the results and filtering out elements 257 * that don't exist, aren't readable, or aren't either a regular 258 * file or a directory (as specified). Either string may be empty 259 * or {@code null}, in which case it is ignored. If both strings 260 * are empty or {@code null}, or all elements get pruned out, then 261 * this returns a zero-element list. 262 */ 263 private static List<File> splitPaths(String searchPath, boolean directoriesOnly) { 264 List<File> result = new ArrayList<>(); 265 266 if (searchPath != null) { 267 for (String path : searchPath.split(File.pathSeparator)) { 268 if (directoriesOnly) { 269 try { 270 StructStat sb = Libcore.os.stat(path); 271 if (!S_ISDIR(sb.st_mode)) { 272 continue; 273 } 274 } catch (ErrnoException ignored) { 275 continue; 276 } 277 } 278 result.add(new File(path)); 279 } 280 } 281 282 return result; 283 } 284 285 private static Element[] makeInMemoryDexElements(ByteBuffer[] dexFiles, 286 List<IOException> suppressedExceptions) { 287 Element[] elements = new Element[dexFiles.length]; 288 int elementPos = 0; 289 for (ByteBuffer buf : dexFiles) { 290 try { 291 DexFile dex = new DexFile(buf); 292 elements[elementPos++] = new Element(dex); 293 } catch (IOException suppressed) { 294 System.logE("Unable to load dex file: " + buf, suppressed); 295 suppressedExceptions.add(suppressed); 296 } 297 } 298 if (elementPos != elements.length) { 299 elements = Arrays.copyOf(elements, elementPos); 300 } 301 return elements; 302 } 303 304 /** 305 * Makes an array of dex/resource path elements, one per element of 306 * the given array. 307 */ 308 private static Element[] makeDexElements(List<File> files, File optimizedDirectory, 309 List<IOException> suppressedExceptions, ClassLoader loader) { 310 Element[] elements = new Element[files.size()]; 311 int elementsPos = 0; 312 /* 313 * Open all files and load the (direct or contained) dex files up front. 314 */ 315 for (File file : files) { 316 if (file.isDirectory()) { 317 // We support directories for looking up resources. Looking up resources in 318 // directories is useful for running libcore tests. 319 elements[elementsPos++] = new Element(file); 320 } else if (file.isFile()) { 321 String name = file.getName(); 322 323 if (name.endsWith(DEX_SUFFIX)) { 324 // Raw dex file (not inside a zip/jar). 325 try { 326 DexFile dex = loadDexFile(file, optimizedDirectory, loader, elements); 327 if (dex != null) { 328 elements[elementsPos++] = new Element(dex, null); 329 } 330 } catch (IOException suppressed) { 331 System.logE("Unable to load dex file: " + file, suppressed); 332 suppressedExceptions.add(suppressed); 333 } 334 } else { 335 DexFile dex = null; 336 try { 337 dex = loadDexFile(file, optimizedDirectory, loader, elements); 338 } catch (IOException suppressed) { 339 /* 340 * IOException might get thrown "legitimately" by the DexFile constructor if 341 * the zip file turns out to be resource-only (that is, no classes.dex file 342 * in it). 343 * Let dex == null and hang on to the exception to add to the tea-leaves for 344 * when findClass returns null. 345 */ 346 suppressedExceptions.add(suppressed); 347 } 348 349 if (dex == null) { 350 elements[elementsPos++] = new Element(file); 351 } else { 352 elements[elementsPos++] = new Element(dex, file); 353 } 354 } 355 } else { 356 System.logW("ClassLoader referenced unknown path: " + file); 357 } 358 } 359 if (elementsPos != elements.length) { 360 elements = Arrays.copyOf(elements, elementsPos); 361 } 362 return elements; 363 } 364 365 /** 366 * Constructs a {@code DexFile} instance, as appropriate depending on whether 367 * {@code optimizedDirectory} is {@code null}. An application image file may be associated with 368 * the {@code loader} if it is not null. 369 */ 370 private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader, 371 Element[] elements) 372 throws IOException { 373 if (optimizedDirectory == null) { 374 return new DexFile(file, loader, elements); 375 } else { 376 String optimizedPath = optimizedPathFor(file, optimizedDirectory); 377 return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements); 378 } 379 } 380 381 /** 382 * Converts a dex/jar file path and an output directory to an 383 * output file path for an associated optimized dex file. 384 */ 385 private static String optimizedPathFor(File path, 386 File optimizedDirectory) { 387 /* 388 * Get the filename component of the path, and replace the 389 * suffix with ".dex" if that's not already the suffix. 390 * 391 * We don't want to use ".odex", because the build system uses 392 * that for files that are paired with resource-only jar 393 * files. If the VM can assume that there's no classes.dex in 394 * the matching jar, it doesn't need to open the jar to check 395 * for updated dependencies, providing a slight performance 396 * boost at startup. The use of ".dex" here matches the use on 397 * files in /data/dalvik-cache. 398 */ 399 String fileName = path.getName(); 400 if (!fileName.endsWith(DEX_SUFFIX)) { 401 int lastDot = fileName.lastIndexOf("."); 402 if (lastDot < 0) { 403 fileName += DEX_SUFFIX; 404 } else { 405 StringBuilder sb = new StringBuilder(lastDot + 4); 406 sb.append(fileName, 0, lastDot); 407 sb.append(DEX_SUFFIX); 408 fileName = sb.toString(); 409 } 410 } 411 412 File result = new File(optimizedDirectory, fileName); 413 return result.getPath(); 414 } 415 416 /* 417 * TODO (dimitry): Revert after apps stops relying on the existence of this 418 * method (see http://b/21957414 and http://b/26317852 for details) 419 */ 420 @SuppressWarnings("unused") 421 private static Element[] makePathElements(List<File> files, File optimizedDirectory, 422 List<IOException> suppressedExceptions) { 423 return makeDexElements(files, optimizedDirectory, suppressedExceptions, null); 424 } 425 426 /** 427 * Makes an array of directory/zip path elements for the native library search path, one per 428 * element of the given array. 429 */ 430 private static NativeLibraryElement[] makePathElements(List<File> files) { 431 NativeLibraryElement[] elements = new NativeLibraryElement[files.size()]; 432 int elementsPos = 0; 433 for (File file : files) { 434 String path = file.getPath(); 435 436 if (path.contains(zipSeparator)) { 437 String split[] = path.split(zipSeparator, 2); 438 File zip = new File(split[0]); 439 String dir = split[1]; 440 elements[elementsPos++] = new NativeLibraryElement(zip, dir); 441 } else if (file.isDirectory()) { 442 // We support directories for looking up native libraries. 443 elements[elementsPos++] = new NativeLibraryElement(file); 444 } 445 } 446 if (elementsPos != elements.length) { 447 elements = Arrays.copyOf(elements, elementsPos); 448 } 449 return elements; 450 } 451 452 /** 453 * Finds the named class in one of the dex files pointed at by 454 * this instance. This will find the one in the earliest listed 455 * path element. If the class is found but has not yet been 456 * defined, then this method will define it in the defining 457 * context that this instance was constructed with. 458 * 459 * @param name of class to find 460 * @param suppressed exceptions encountered whilst finding the class 461 * @return the named class or {@code null} if the class is not 462 * found in any of the dex files 463 */ 464 public Class<?> findClass(String name, List<Throwable> suppressed) { 465 for (Element element : dexElements) { 466 Class<?> clazz = element.findClass(name, definingContext, suppressed); 467 if (clazz != null) { 468 return clazz; 469 } 470 } 471 472 if (dexElementsSuppressedExceptions != null) { 473 suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); 474 } 475 return null; 476 } 477 478 /** 479 * Finds the named resource in one of the zip/jar files pointed at 480 * by this instance. This will find the one in the earliest listed 481 * path element. 482 * 483 * @return a URL to the named resource or {@code null} if the 484 * resource is not found in any of the zip/jar files 485 */ 486 public URL findResource(String name) { 487 for (Element element : dexElements) { 488 URL url = element.findResource(name); 489 if (url != null) { 490 return url; 491 } 492 } 493 494 return null; 495 } 496 497 /** 498 * Finds all the resources with the given name, returning an 499 * enumeration of them. If there are no resources with the given 500 * name, then this method returns an empty enumeration. 501 */ 502 public Enumeration<URL> findResources(String name) { 503 ArrayList<URL> result = new ArrayList<URL>(); 504 505 for (Element element : dexElements) { 506 URL url = element.findResource(name); 507 if (url != null) { 508 result.add(url); 509 } 510 } 511 512 return Collections.enumeration(result); 513 } 514 515 /** 516 * Finds the named native code library on any of the library 517 * directories pointed at by this instance. This will find the 518 * one in the earliest listed directory, ignoring any that are not 519 * readable regular files. 520 * 521 * @return the complete path to the library or {@code null} if no 522 * library was found 523 */ 524 public String findLibrary(String libraryName) { 525 String fileName = System.mapLibraryName(libraryName); 526 527 for (NativeLibraryElement element : nativeLibraryPathElements) { 528 String path = element.findNativeLibrary(fileName); 529 530 if (path != null) { 531 return path; 532 } 533 } 534 535 return null; 536 } 537 538 /** 539 * Returns the list of all individual dex files paths from the current list. 540 * The list will contain only file paths (i.e. no directories). 541 */ 542 /*package*/ List<String> getDexPaths() { 543 List<String> dexPaths = new ArrayList<String>(); 544 for (Element e : dexElements) { 545 String dexPath = e.getDexPath(); 546 if (dexPath != null) { 547 // Add the element to the list only if it is a file. A null dex path signals the 548 // element is a resource directory or an in-memory dex file. 549 dexPaths.add(dexPath); 550 } 551 } 552 return dexPaths; 553 } 554 555 /** 556 * Element of the dex/resource path. Note: should be called DexElement, but apps reflect on 557 * this. 558 */ 559 /*package*/ static class Element { 560 /** 561 * A file denoting a zip file (in case of a resource jar or a dex jar), or a directory 562 * (only when dexFile is null). 563 */ 564 private final File path; 565 566 private final DexFile dexFile; 567 568 private ClassPathURLStreamHandler urlHandler; 569 private boolean initialized; 570 571 /** 572 * Element encapsulates a dex file. This may be a plain dex file (in which case dexZipPath 573 * should be null), or a jar (in which case dexZipPath should denote the zip file). 574 */ 575 public Element(DexFile dexFile, File dexZipPath) { 576 this.dexFile = dexFile; 577 this.path = dexZipPath; 578 } 579 580 public Element(DexFile dexFile) { 581 this.dexFile = dexFile; 582 this.path = null; 583 } 584 585 public Element(File path) { 586 this.path = path; 587 this.dexFile = null; 588 } 589 590 /** 591 * Constructor for a bit of backwards compatibility. Some apps use reflection into 592 * internal APIs. Warn, and emulate old behavior if we can. See b/33399341. 593 * 594 * @deprecated The Element class has been split. Use new Element constructors for 595 * classes and resources, and NativeLibraryElement for the library 596 * search path. 597 */ 598 @Deprecated 599 public Element(File dir, boolean isDirectory, File zip, DexFile dexFile) { 600 System.err.println("Warning: Using deprecated Element constructor. Do not use internal" 601 + " APIs, this constructor will be removed in the future."); 602 if (dir != null && (zip != null || dexFile != null)) { 603 throw new IllegalArgumentException("Using dir and zip|dexFile no longer" 604 + " supported."); 605 } 606 if (isDirectory && (zip != null || dexFile != null)) { 607 throw new IllegalArgumentException("Unsupported argument combination."); 608 } 609 if (dir != null) { 610 this.path = dir; 611 this.dexFile = null; 612 } else { 613 this.path = zip; 614 this.dexFile = dexFile; 615 } 616 } 617 618 /* 619 * Returns the dex path of this element or null if the element refers to a directory. 620 */ 621 private String getDexPath() { 622 if (path != null) { 623 return path.isDirectory() ? null : path.getAbsolutePath(); 624 } else if (dexFile != null) { 625 // DexFile.getName() returns the path of the dex file. 626 return dexFile.getName(); 627 } 628 return null; 629 } 630 631 @Override 632 public String toString() { 633 if (dexFile == null) { 634 return (path.isDirectory() ? "directory \"" : "zip file \"") + path + "\""; 635 } else { 636 if (path == null) { 637 return "dex file \"" + dexFile + "\""; 638 } else { 639 return "zip file \"" + path + "\""; 640 } 641 } 642 } 643 644 public synchronized void maybeInit() { 645 if (initialized) { 646 return; 647 } 648 649 if (path == null || path.isDirectory()) { 650 initialized = true; 651 return; 652 } 653 654 try { 655 urlHandler = new ClassPathURLStreamHandler(path.getPath()); 656 } catch (IOException ioe) { 657 /* 658 * Note: ZipException (a subclass of IOException) 659 * might get thrown by the ZipFile constructor 660 * (e.g. if the file isn't actually a zip/jar 661 * file). 662 */ 663 System.logE("Unable to open zip file: " + path, ioe); 664 urlHandler = null; 665 } 666 667 // Mark this element as initialized only after we've successfully created 668 // the associated ClassPathURLStreamHandler. That way, we won't leave this 669 // element in an inconsistent state if an exception is thrown during initialization. 670 // 671 // See b/35633614. 672 initialized = true; 673 } 674 675 public Class<?> findClass(String name, ClassLoader definingContext, 676 List<Throwable> suppressed) { 677 return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed) 678 : null; 679 } 680 681 public URL findResource(String name) { 682 maybeInit(); 683 684 if (urlHandler != null) { 685 return urlHandler.getEntryUrlOrNull(name); 686 } 687 688 // We support directories so we can run tests and/or legacy code 689 // that uses Class.getResource. 690 if (path != null && path.isDirectory()) { 691 File resourceFile = new File(path, name); 692 if (resourceFile.exists()) { 693 try { 694 return resourceFile.toURI().toURL(); 695 } catch (MalformedURLException ex) { 696 throw new RuntimeException(ex); 697 } 698 } 699 } 700 701 return null; 702 } 703 } 704 705 /** 706 * Element of the native library path 707 */ 708 /*package*/ static class NativeLibraryElement { 709 /** 710 * A file denoting a directory or zip file. 711 */ 712 private final File path; 713 714 /** 715 * If path denotes a zip file, this denotes a base path inside the zip. 716 */ 717 private final String zipDir; 718 719 private ClassPathURLStreamHandler urlHandler; 720 private boolean initialized; 721 722 public NativeLibraryElement(File dir) { 723 this.path = dir; 724 this.zipDir = null; 725 726 // We should check whether path is a directory, but that is non-eliminatable overhead. 727 } 728 729 public NativeLibraryElement(File zip, String zipDir) { 730 this.path = zip; 731 this.zipDir = zipDir; 732 733 // Simple check that should be able to be eliminated by inlining. We should also 734 // check whether path is a file, but that is non-eliminatable overhead. 735 if (zipDir == null) { 736 throw new IllegalArgumentException(); 737 } 738 } 739 740 @Override 741 public String toString() { 742 if (zipDir == null) { 743 return "directory \"" + path + "\""; 744 } else { 745 return "zip file \"" + path + "\"" + 746 (!zipDir.isEmpty() ? ", dir \"" + zipDir + "\"" : ""); 747 } 748 } 749 750 public synchronized void maybeInit() { 751 if (initialized) { 752 return; 753 } 754 755 if (zipDir == null) { 756 initialized = true; 757 return; 758 } 759 760 try { 761 urlHandler = new ClassPathURLStreamHandler(path.getPath()); 762 } catch (IOException ioe) { 763 /* 764 * Note: ZipException (a subclass of IOException) 765 * might get thrown by the ZipFile constructor 766 * (e.g. if the file isn't actually a zip/jar 767 * file). 768 */ 769 System.logE("Unable to open zip file: " + path, ioe); 770 urlHandler = null; 771 } 772 773 // Mark this element as initialized only after we've successfully created 774 // the associated ClassPathURLStreamHandler. That way, we won't leave this 775 // element in an inconsistent state if an exception is thrown during initialization. 776 // 777 // See b/35633614. 778 initialized = true; 779 } 780 781 public String findNativeLibrary(String name) { 782 maybeInit(); 783 784 if (zipDir == null) { 785 String entryPath = new File(path, name).getPath(); 786 if (IoUtils.canOpenReadOnly(entryPath)) { 787 return entryPath; 788 } 789 } else if (urlHandler != null) { 790 // Having a urlHandler means the element has a zip file. 791 // In this case Android supports loading the library iff 792 // it is stored in the zip uncompressed. 793 String entryName = zipDir + '/' + name; 794 if (urlHandler.isEntryStored(entryName)) { 795 return path.getPath() + zipSeparator + entryName; 796 } 797 } 798 799 return null; 800 } 801 } 802 } 803