Home | History | Annotate | Download | only in system
      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