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