diff options
author | Dan Bornstein <danfuzz@android.com> | 2011-02-10 10:35:52 -0800 |
---|---|---|
committer | Dan Bornstein <danfuzz@android.com> | 2011-02-16 11:55:47 -0800 |
commit | ea52753a0f80fcd70acfe9150ecb854511ff38db (patch) | |
tree | 5f004f82db4ce1bd5b6c7c3314e6c12b5a2fb786 /dalvik/src/main | |
parent | 8efab4cc6c0dd46712e4ca80d9cb851dd5bbf6c2 (diff) | |
download | libcore-ea52753a0f80fcd70acfe9150ecb854511ff38db.zip libcore-ea52753a0f80fcd70acfe9150ecb854511ff38db.tar.gz libcore-ea52753a0f80fcd70acfe9150ecb854511ff38db.tar.bz2 |
Refactor DexClassLoader and PathClassLoader.
This resulted in the creation of two new classes: DexPathList contains
most of the common functionality, namely managing the two path lists
(dex/resource files and native library directories) plus all the
salient initialization and lookup code. BaseDexClassLoader provides
the ClassLoader API, mostly by making calls to a contained DexPathList
instance. The two original classes just become trivial subclasses
which take different constructor args and make correspondingly
different super() calls in their respective constructors.
I took the opportunity to remove the ability for PathClassLoader to
take directories on the dex/resource list. This functionality hadn't
ever been used, at least not since well before 1.0.
Change-Id: I92ed300417431d0e0ac4c5ecf2f10d6a9b0691c7
Diffstat (limited to 'dalvik/src/main')
-rw-r--r-- | dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java | 128 | ||||
-rw-r--r-- | dalvik/src/main/java/dalvik/system/DexClassLoader.java | 334 | ||||
-rw-r--r-- | dalvik/src/main/java/dalvik/system/DexPathList.java | 428 | ||||
-rw-r--r-- | dalvik/src/main/java/dalvik/system/PathClassLoader.java | 439 |
4 files changed, 595 insertions, 734 deletions
diff --git a/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java b/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java new file mode 100644 index 0000000..ab24f0b --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dalvik.system; + +import java.io.File; +import java.net.URL; +import java.util.Enumeration; + +/** + * Base class for common functionality between various dex-based + * {@link ClassLoader} implementations. + */ +public class BaseDexClassLoader extends ClassLoader { + /** originally specified path (just used for {@code toString()}) */ + private final String originalPath; + + /** structured lists of path elements */ + private final DexPathList pathList; + + /** + * Constructs an instance. + * + * @param dexPath the list of jar/apk files containing classes and + * resources, delimited by {@code File.pathSeparator}, which + * defaults to {@code ":"} on Android + * @param optimizedDirectory directory where optimized dex files + * should be written; may be {@code null} + * @param libraryPath the list of directories containing native + * libraries, delimited by {@code File.pathSeparator}; may be + * {@code null} + * @param parent the parent class loader + */ + public BaseDexClassLoader(String dexPath, File optimizedDirectory, + String libraryPath, ClassLoader parent) { + super(parent); + + this.originalPath = dexPath; + this.pathList = + new DexPathList(this, dexPath, libraryPath, optimizedDirectory); + } + + @Override + protected Class<?> findClass(String name) throws ClassNotFoundException { + Class clazz = pathList.findClass(name); + + if (clazz == null) { + throw new ClassNotFoundException(name); + } + + return clazz; + } + + @Override + protected URL findResource(String name) { + return pathList.findResource(name); + } + + @Override + protected Enumeration<URL> findResources(String name) { + return pathList.findResources(name); + } + + @Override + public String findLibrary(String name) { + return pathList.findLibrary(name); + } + + /** + * Returns package information for the given package. + * Unfortunately, instances of this class don't really have this + * information, and as a non-secure {@code ClassLoader}, it isn't + * even required to, according to the spec. Yet, we want to + * provide it, in order to make all those hopeful callers of + * {@code myClass.getPackage().getName()} happy. Thus we construct + * a {@code Package} object the first time it is being requested + * and fill most of the fields with dummy values. The {@code + * Package} object is then put into the {@code ClassLoader}'s + * package cache, so we see the same one next time. We don't + * create {@code Package} objects for {@code null} arguments or + * for the default package. + * + * <p>There is a limited chance that we end up with multiple + * {@code Package} objects representing the same package: It can + * happen when when a package is scattered across different JAR + * files which were loaded by different {@code ClassLoader} + * instances. This is rather unlikely, and given that this whole + * thing is more or less a workaround, probably not worth the + * effort to address. + * + * @param name the name of the class + * @return the package information for the class, or {@code null} + * if there is no package information available for it + */ + @Override + protected synchronized Package getPackage(String name) { + if (name != null && !name.isEmpty()) { + Package pack = super.getPackage(name); + + if (pack == null) { + pack = definePackage(name, "Unknown", "0.0", "Unknown", + "Unknown", "0.0", "Unknown", null); + } + + return pack; + } + + return null; + } + + @Override + public String toString() { + return getClass().getName() + "[" + originalPath + "]"; + } +} diff --git a/dalvik/src/main/java/dalvik/system/DexClassLoader.java b/dalvik/src/main/java/dalvik/system/DexClassLoader.java index fea65dd..e059b7d 100644 --- a/dalvik/src/main/java/dalvik/system/DexClassLoader.java +++ b/dalvik/src/main/java/dalvik/system/DexClassLoader.java @@ -17,10 +17,6 @@ package dalvik.system; import java.io.File; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.zip.ZipFile; /** * Provides a simple {@link ClassLoader} implementation that operates on a @@ -28,323 +24,29 @@ import java.util.zip.ZipFile; * holds the optimized form of the files is specified explicitly. This * can be used to execute code not installed as part of an application. * - * The best place to put the optimized DEX files is in app-specific + * The best place to put the optimized dex files is in app-specific * storage, so that removal of the app will automatically remove the - * optimized DEX files. If other storage is used (e.g. /sdcard), the + * optimized dex files. If other storage is used (e.g. /sdcard), the * app may not have an opportunity to remove them. */ -public class DexClassLoader extends ClassLoader { - // TODO: Factor out commonality between this class and PathClassLoader. - - private static final boolean VERBOSE_DEBUG = false; - - /* constructor args, held for init */ - private final String mRawDexPath; - private final String mRawLibPath; - private final String mDexOutputPath; - - /* - * Parallel arrays for jar/apk files. - * - * (could stuff these into an object and have a single array; - * improves clarity but adds overhead) - */ - private final File[] mFiles; // source file Files, for rsrc URLs - private final ZipFile[] mZips; // source zip files, with resources - private final DexFile[] mDexs; // opened, prepped DEX files - - /** - * Native library path. - */ - private final String[] mLibPaths; - +public class DexClassLoader extends BaseDexClassLoader { /** * Creates a {@code DexClassLoader} that finds interpreted and native - * code. Interpreted classes are found in a set of DEX files contained - * in Jar or APK files. - * - * The path lists are separated using the character specified by - * the "path.separator" system property, which defaults to ":". + * code. Interpreted classes are found in a set of dex files contained + * in jar or apk files. * - * @param dexPath - * the list of jar/apk files containing classes and resources - * @param dexOutputDir - * directory where optimized DEX files should be written - * @param libPath - * the list of directories containing native libraries; may be null - * @param parent - * the parent class loader - */ - public DexClassLoader(String dexPath, String dexOutputDir, String libPath, - ClassLoader parent) { - super(parent); - - if (dexPath == null || dexOutputDir == null) { - throw new NullPointerException(); - } - - mRawDexPath = dexPath; - mDexOutputPath = dexOutputDir; - mRawLibPath = libPath; - - String[] dexPathList = mRawDexPath.split(":"); - int length = dexPathList.length; - - mFiles = new File[length]; - mZips = new ZipFile[length]; - mDexs = new DexFile[length]; - - /* open all Zip and DEX files up front */ - for (int i = 0; i < length; i++) { - File pathFile = new File(dexPathList[i]); - mFiles[i] = pathFile; - - if (pathFile.isFile()) { - if (!dexPathList[i].endsWith(".dex")) { - /* - * If the name doesn't end with ".dex" assume it's a zip - * file of some sort. - */ - try { - mZips[i] = new ZipFile(pathFile); - } - catch (IOException ioex) { - // expecting IOException and ZipException - //System.out.println("Failed opening '" + pathFile - // + "': " + ioex); - //ioex.printStackTrace(); - } - } - - /* - * If we got a zip file, we still need to extract out - * the dex file from it. - */ - try { - String outputName = - generateOutputName(dexPathList[i], mDexOutputPath); - mDexs[i] = DexFile.loadDex(dexPathList[i], outputName, 0); - } catch (IOException ioex) { - // It might be a resource-only zip. - //System.out.println("Failed to construct DexFile '" - // + pathFile + "': " + ioex); - } - } - } - - /* - * Prep for native library loading. - */ - String pathList = System.getProperty("java.library.path", "."); - String pathSep = System.getProperty("path.separator", ":"); - String fileSep = System.getProperty("file.separator", "/"); - - if (mRawLibPath != null) { - if (pathList.length() > 0) { - pathList += pathSep + mRawLibPath; - } - else { - pathList = mRawLibPath; - } - } - - mLibPaths = pathList.split(pathSep); - length = mLibPaths.length; - - // Add a '/' to the end so we don't have to do the property lookup - // and concatenation later. - for (int i = 0; i < length; i++) { - if (!mLibPaths[i].endsWith(fileSep)) - mLibPaths[i] += fileSep; - if (VERBOSE_DEBUG) - System.out.println("Native lib path " +i+ ": " - + mLibPaths[i]); - } - } - - /** - * Convert a source path name and an output directory to an output - * file name. - */ - private static String generateOutputName(String sourcePathName, - String outputDir) { - StringBuilder newStr = new StringBuilder(80); - - /* start with the output directory */ - newStr.append(outputDir); - if (!outputDir.endsWith("/")) - newStr.append("/"); - - /* get the filename component of the path */ - String sourceFileName; - int lastSlash = sourcePathName.lastIndexOf("/"); - if (lastSlash < 0) - sourceFileName = sourcePathName; - else - sourceFileName = sourcePathName.substring(lastSlash+1); - - /* - * Replace ".jar", ".zip", whatever with ".dex". We don't want to - * use ".odex", because the build system uses that for files that - * are paired with resource-only jar files. If the VM can assume - * that there's no classes.dex in the matching jar, it doesn't need - * to open the jar to check for updated dependencies, providing a - * slight performance boost at startup. The use of ".dex" here - * matches the use on files in /data/dalvik-cache. - */ - int lastDot = sourceFileName.lastIndexOf("."); - if (lastDot < 0) - newStr.append(sourceFileName); - else - newStr.append(sourceFileName, 0, lastDot); - newStr.append(".dex"); - - if (VERBOSE_DEBUG) - System.out.println("Output file will be " + newStr.toString()); - return newStr.toString(); - } - - /** - * Finds a class. This method is called by {@code loadClass()} after the - * parent ClassLoader has failed to find a loaded class of the same name. - * - * @param name - * The name of the class to search for, in a human-readable form - * like "java.lang.String" or "java.net.URLClassLoader$3$1". - * @return the {@link Class} object representing the class - * @throws ClassNotFoundException - * if the class cannot be found - */ - @Override - protected Class<?> findClass(String name) throws ClassNotFoundException { - if (VERBOSE_DEBUG) - System.out.println("DexClassLoader " + this - + ": findClass '" + name + "'"); - - int length = mFiles.length; - - for (int i = 0; i < length; i++) { - if (VERBOSE_DEBUG) - System.out.println(" Now searching: " + mFiles[i].getPath()); - - if (mDexs[i] != null) { - String slashName = name.replace('.', '/'); - Class clazz = mDexs[i].loadClass(slashName, this); - if (clazz != null) { - if (VERBOSE_DEBUG) - System.out.println(" found"); - return clazz; - } - } - } - - throw new ClassNotFoundException(name + " in loader " + this); - } - - /** - * Finds a resource. This method is called by {@code getResource()} after - * the parent ClassLoader has failed to find a loaded resource of the same - * name. - * - * @param name - * The name of the resource to find - * @return the location of the resource as a URL, or {@code null} if the - * resource is not found. - */ - @Override - protected URL findResource(String name) { - int length = mFiles.length; - - for (int i = 0; i < length; i++) { - File pathFile = mFiles[i]; - ZipFile zip = mZips[i]; - - if (zip.getEntry(name) != null) { - if (VERBOSE_DEBUG) - System.out.println(" found " + name + " in " + pathFile); - try { - // File.toURL() is compliant with RFC 1738 in always - // creating absolute path names. If we construct the - // URL by concatenating strings, we might end up with - // illegal URLs for relative names. - return new URL("jar:" + pathFile.toURL() + "!/" + name); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } - } - } - - if (VERBOSE_DEBUG) - System.out.println(" resource " + name + " not found"); - - return null; - } - - /** - * Finds a native library. This method is called after the parent - * ClassLoader has failed to find a native library of the same name. - * - * @param libname - * The name of the library to find - * @return the complete path of the library, or {@code null} if the library - * is not found. - */ - @Override - protected String findLibrary(String libname) { - String fileName = System.mapLibraryName(libname); - for (int i = 0; i < mLibPaths.length; i++) { - String pathName = mLibPaths[i] + fileName; - File test = new File(pathName); - - if (test.exists()) { - if (VERBOSE_DEBUG) - System.out.println(" found " + libname); - return pathName; - } - } - - if (VERBOSE_DEBUG) - System.out.println(" library " + libname + " not found"); - return null; - } - - /** - * Returns package information for the given package. Unfortunately, the - * DexClassLoader doesn't really have this information, and as a non-secure - * ClassLoader, it isn't even required to, according to the spec. Yet, we - * want to provide it, in order to make all those hopeful callers of - * <code>myClass.getPackage().getName()</code> happy. Thus we construct a - * Package object the first time it is being requested and fill most of the - * fields with dummy values. The Package object is then put into the - * ClassLoader's Package cache, so we see the same one next time. We don't - * create Package objects for null arguments or for the default package. - * <p> - * There a limited chance that we end up with multiple Package objects - * representing the same package: It can happen when when a package is - * scattered across different JAR files being loaded by different - * ClassLoaders. Rather unlikely, and given that this whole thing is more or - * less a workaround, probably not worth the effort. - * - * @param name - * the name of the class - * @return the package information for the class, or {@code null} if there - * is not package information available for it - */ - @Override - protected Package getPackage(String name) { - if (name != null && !name.isEmpty()) { - synchronized(this) { - Package pack = super.getPackage(name); - - if (pack == null) { - pack = definePackage(name, "Unknown", "0.0", "Unknown", - "Unknown", "0.0", "Unknown", null); - } - - return pack; - } - } - - return null; + * @param dexPath the list of jar/apk files containing classes and + * resources, delimited by {@code File.pathSeparator}, which + * defaults to {@code ":"} on Android + * @param optimizedDirectory directory where optimized dex files + * should be written; must not be {@code null} + * @param libraryPath the list of directories containing native + * libraries, delimited by {@code File.pathSeparator}; may be + * {@code null} + * @param parent the parent class loader + */ + public DexClassLoader(String dexPath, String optimizedDirectory, + String libraryPath, ClassLoader parent) { + super(dexPath, new File(optimizedDirectory), libraryPath, parent); } } diff --git a/dalvik/src/main/java/dalvik/system/DexPathList.java b/dalvik/src/main/java/dalvik/system/DexPathList.java new file mode 100644 index 0000000..3446fea --- /dev/null +++ b/dalvik/src/main/java/dalvik/system/DexPathList.java @@ -0,0 +1,428 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package dalvik.system; + +import java.io.File; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; +import java.util.zip.ZipFile; + +/** + * A pair of lists of entries, associated with a {@code ClassLoader}. + * One of the lists is a dex/resource path — typically referred + * to as a "class path" — list, and the other names directories + * containing native code libraries. Class path entries may be any of: + * a {@code .jar} or {@code .zip} file containing an optional + * top-level {@code classes.dex} file as well as arbitrary resources, + * or a plain {@code .dex} file (with no possibility of associated + * resources). + * + * <p>This class also contains methods to use these lists to look up + * classes and resources.</p> + */ +/*package*/ final class DexPathList { + private static final String DEX_SUFFIX = ".dex"; + private static final String JAR_SUFFIX = ".jar"; + private static final String ZIP_SUFFIX = ".zip"; + private static final String APK_SUFFIX = ".apk"; + + /** class definition context */ + private final ClassLoader definingContext; + + /** list of dex/resource (class path) elements */ + private final Element[] dexElements; + + /** list of native library directory elements */ + private final File[] nativeLibraryDirectories; + + /** + * Constructs an instance. + * + * @param definingContext the context in which any as-yet unresolved + * classes should be defined + * @param dexPath list of dex/resource path elements, separated by + * {@code File.pathSeparator} + * @param libraryPath list of native library directory path elements, + * separated by {@code File.pathSeparator} + * @param optimizedDirectory directory where optimized {@code .dex} files + * should be found and written to, or {@code null} to use the default + * system directory for same + */ + public DexPathList(ClassLoader definingContext, String dexPath, + String libraryPath, File optimizedDirectory) { + if (definingContext == null) { + throw new NullPointerException("definingContext == null"); + } + + if (dexPath == null) { + throw new NullPointerException("dexPath == null"); + } + + if (optimizedDirectory != null) { + if (!optimizedDirectory.exists()) { + throw new IllegalArgumentException( + "optimizedDirectory doesn't exist: " + + optimizedDirectory); + } + + if (!(optimizedDirectory.canRead() + && optimizedDirectory.canWrite())) { + throw new IllegalArgumentException( + "optimizedDirectory not readable/writable: " + + optimizedDirectory); + } + } + + this.definingContext = definingContext; + this.dexElements = + makeDexElements(splitDexPath(dexPath), optimizedDirectory); + this.nativeLibraryDirectories = splitLibraryPath(libraryPath); + } + + /** + * Splits the given dex path string into elements using the path + * separator, pruning out any elements that do not refer to existing + * and readable files. (That is, directories are not included in the + * result.) + */ + private static ArrayList<File> splitDexPath(String path) { + return splitPaths(path, null, false); + } + + /** + * Splits the given library directory path string into elements + * using the path separator ({@code File.pathSeparator}, which + * defaults to {@code ":"} on Android, appending on the elements + * from the system library path, and pruning out any elements that + * do not refer to existing and readable directories. + */ + private static File[] splitLibraryPath(String path) { + /* + * Native libraries may exist in both the system and + * application library paths, and we use this search order: + * + * 1. this class loader's library path for application + * libraries + * 2. the VM's library path from the system + * property for system libraries + * + * This order was reversed prior to Gingerbread; see http://b/2933456. + */ + ArrayList<File> result = splitPaths( + path, System.getProperty("java.library.path", "."), true); + return result.toArray(new File[result.size()]); + } + + /** + * Splits the given path strings into file elements using the path + * separator, combining the results and filtering out elements + * that don't exist, aren't readable, or aren't either a regular + * file or a directory (as specified). Either string may be empty + * or {@code null}, in which case it is ignored. If both strings + * are empty or {@code null}, or all elements get pruned out, then + * this returns a zero-element list. + */ + private static ArrayList<File> splitPaths(String path1, String path2, + boolean wantDirectories) { + ArrayList<File> result = new ArrayList<File>(); + + splitAndAdd(path1, wantDirectories, result); + splitAndAdd(path2, wantDirectories, result); + return result; + } + + /** + * Helper for {@link #splitPaths}, which does the actual splitting + * and filtering and adding to a result. + */ + private static void splitAndAdd(String path, boolean wantDirectories, + ArrayList<File> resultList) { + if (path == null) { + return; + } + + String[] strings = path.split(Pattern.quote(File.pathSeparator)); + + for (String s : strings) { + File file = new File(s); + + if (! (file.exists() && file.canRead())) { + continue; + } + + /* + * Note: There are other entities in filesystems than + * regular files and directories. + */ + if (wantDirectories) { + if (!file.isDirectory()) { + continue; + } + } else { + if (!file.isFile()) { + continue; + } + } + + resultList.add(file); + } + } + + /** + * Makes an array of dex/resource path elements, one per element of + * the given array. + */ + private static Element[] makeDexElements(ArrayList<File> files, + File optimizedDirectory) { + ArrayList<Element> elements = new ArrayList<Element>(); + + /* + * Open all files and load the (direct or contained) dex files + * up front. + */ + for (File file : files) { + ZipFile zip = null; + DexFile dex = null; + String name = file.getName(); + + if (name.endsWith(DEX_SUFFIX)) { + // Raw dex file (not inside a zip/jar). + try { + dex = loadDexFile(file, optimizedDirectory); + } catch (IOException ex) { + Logger.global.log(Level.SEVERE, + "Unable to load dex file: " + file, + ex); + } + } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX) + || name.endsWith(ZIP_SUFFIX)) { + try { + zip = new ZipFile(file); + } catch (IOException ex) { + /* + * Note: ZipException (a subclass of IOException) + * might get thrown by the ZipFile constructor + * (e.g. if the file isn't actually a zip/jar + * file). + */ + Logger.global.log(Level.SEVERE, + "Unable to open zip file: " + file, + ex); + } + + try { + dex = loadDexFile(file, optimizedDirectory); + } catch (IOException ignored) { + /* + * IOException might get thrown "legitimately" by + * the DexFile constructor if the zip file turns + * out to be resource-only (that is, no + * classes.dex file in it). Safe to just ignore + * the exception here, and let dex == null. + */ + } + } else { + Logger.global.warning("Unknown file type for: " + file); + } + + if ((zip != null) || (dex != null)) { + elements.add(new Element(file, zip, dex)); + } + } + + return elements.toArray(new Element[elements.size()]); + } + + /** + * Constructs a {@code DexFile} instance, as appropriate depending + * on whether {@code optimizedDirectory} is {@code null}. + */ + private static DexFile loadDexFile(File file, File optimizedDirectory) + throws IOException { + if (optimizedDirectory == null) { + return new DexFile(file); + } else { + String optimizedPath = optimizedPathFor(file, optimizedDirectory); + return DexFile.loadDex(file.getPath(), optimizedPath, 0); + } + } + + /** + * Converts a dex/jar file path and an output directory to an + * output file path for an associated optimized dex file. + */ + private static String optimizedPathFor(File path, + File optimizedDirectory) { + /* + * Get the filename component of the path, and replace the + * suffix with ".dex" if that's not already the suffix. + * + * We don't want to use ".odex", because the build system uses + * that for files that are paired with resource-only jar + * files. If the VM can assume that there's no classes.dex in + * the matching jar, it doesn't need to open the jar to check + * for updated dependencies, providing a slight performance + * boost at startup. The use of ".dex" here matches the use on + * files in /data/dalvik-cache. + */ + String fileName = path.getName(); + if (!fileName.endsWith(DEX_SUFFIX)) { + int lastDot = fileName.lastIndexOf("."); + if (lastDot < 0) { + fileName += DEX_SUFFIX; + } else { + StringBuilder sb = new StringBuilder(lastDot + 4); + sb.append(fileName, 0, lastDot); + sb.append(DEX_SUFFIX); + fileName = sb.toString(); + } + } + + File result = new File(optimizedDirectory, fileName); + return result.getPath(); + } + + /** + * Finds the named class in one of the dex files pointed at by + * this instance. This will find the one in the earliest listed + * path element. If the class is found but has not yet been + * defined, then this method will define it in the defining + * context that this instance was constructed with. + * + * @return the named class or {@code null} if the class is not + * found in any of the dex files + */ + public Class findClass(String name) { + for (Element element : dexElements) { + DexFile dex = element.dexFile; + + if (dex != null) { + Class clazz = dex.loadClassBinaryName(name, definingContext); + if (clazz != null) { + return clazz; + } + } + } + + return null; + } + + /** + * Finds the named resource in one of the zip/jar files pointed at + * by this instance. This will find the one in the earliest listed + * path element. + * + * @return a URL to the named resource or {@code null} if the + * resource is not found in any of the zip/jar files + */ + public URL findResource(String name) { + for (Element element : dexElements) { + URL url = element.findResource(name); + if (url != null) { + return url; + } + } + + return null; + } + + /** + * Finds all the resources with the given name, returning an + * enumeration of them. If there are no resources with the given + * name, then this method returns an empty enumeration. + */ + public Enumeration<URL> findResources(String name) { + ArrayList<URL> result = new ArrayList<URL>(); + + for (Element element : dexElements) { + URL url = element.findResource(name); + if (url != null) { + result.add(url); + } + } + + return Collections.enumeration(result); + } + + /** + * Finds the named native code library on any of the library + * directories pointed at by this instance. This will find the + * one in the earliest listed directory, ignoring any that are not + * readable regular files. + * + * @return the complete path to the library or {@code null} if no + * library was found + */ + public String findLibrary(String libraryName) { + String fileName = System.mapLibraryName(libraryName); + + for (File directory : nativeLibraryDirectories) { + File file = new File(directory, fileName); + if (file.exists() && file.isFile() && file.canRead()) { + return file.getPath(); + } + } + + return null; + } + + /** + * Element of the dex/resource file path + */ + /*package*/ static class Element { + public final File file; + public final ZipFile zipFile; + public final DexFile dexFile; + + public Element(File file, ZipFile zipFile, DexFile dexFile) { + this.file = file; + this.zipFile = zipFile; + this.dexFile = dexFile; + } + + public URL findResource(String name) { + if ((zipFile == null) || (zipFile.getEntry(name) == null)) { + /* + * Either this element has no zip/jar file (first + * clause), or the zip/jar file doesn't have an entry + * for the given name (second clause). + */ + return null; + } + + try { + /* + * File.toURL() is compliant with RFC 1738 in + * always creating absolute path names. If we + * construct the URL by concatenating strings, we + * might end up with illegal URLs for relative + * names. + */ + return new URL("jar:" + file.toURL() + "!/" + name); + } catch (MalformedURLException ex) { + throw new RuntimeException(ex); + } + } + } +} diff --git a/dalvik/src/main/java/dalvik/system/PathClassLoader.java b/dalvik/src/main/java/dalvik/system/PathClassLoader.java index 08178a5..32c5586 100644 --- a/dalvik/src/main/java/dalvik/system/PathClassLoader.java +++ b/dalvik/src/main/java/dalvik/system/PathClassLoader.java @@ -16,65 +16,26 @@ package dalvik.system; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.RandomAccessFile; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Enumeration; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; -import libcore.io.IoUtils; - /** * Provides a simple {@link ClassLoader} implementation that operates on a list * of files and directories in the local file system, but does not attempt to * load classes from the network. Android uses this class for its system class * loader and for its application class loader(s). */ -public class PathClassLoader extends ClassLoader { - // TODO: Factor out commonality between this class and DexClassLoader. - - private final String path; - private final String libPath; - - /* - * Parallel arrays for jar/apk files. - * - * (could stuff these into an object and have a single array; - * improves clarity but adds overhead) - */ - private final String[] mPaths; - private final File[] mFiles; - private final ZipFile[] mZips; - private final DexFile[] mDexs; - - /** - * Native library path. - */ - private final List<String> libraryPathElements; - +public class PathClassLoader extends BaseDexClassLoader { /** * Creates a {@code PathClassLoader} that operates on a given list of files * and directories. This method is equivalent to calling * {@link #PathClassLoader(String, String, ClassLoader)} with a * {@code null} value for the second argument (see description there). * - * @param path - * the list of files and directories - * - * @param parent - * the parent class loader + * @param dexPath the list of jar/apk files containing classes and + * resources, delimited by {@code File.pathSeparator}, which + * defaults to {@code ":"} on Android + * @param parent the parent class loader */ - public PathClassLoader(String path, ClassLoader parent) { - this(path, null, parent); + public PathClassLoader(String dexPath, ClassLoader parent) { + super(dexPath, null, null, parent); } /** @@ -83,382 +44,24 @@ public class PathClassLoader extends ClassLoader { * should be one of the following: * * <ul> - * <li>Directories containing classes or resources. - * <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file. - * <li>"classes.dex" files. + * <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as + * well as arbitrary resources. + * <li>Raw ".dex" files (not inside a zip file). * </ul> * * The entries of the second list should be directories containing - * native library files. Both lists are separated using the - * character specified by the "path.separator" system property, - * which, on Android, defaults to ":". - * - * @param path - * the list of files and directories containing classes and - * resources - * - * @param libPath - * the list of directories containing native libraries - * - * @param parent - * the parent class loader - */ - public PathClassLoader(String path, String libPath, ClassLoader parent) { - super(parent); - - if (path == null) { - throw new NullPointerException(); - } - - this.path = path; - this.libPath = libPath; - - mPaths = path.split(":"); - int length = mPaths.length; - - mFiles = new File[length]; - mZips = new ZipFile[length]; - mDexs = new DexFile[length]; - - /* open all Zip and DEX files up front */ - for (int i = 0; i < length; i++) { - File pathFile = new File(mPaths[i]); - mFiles[i] = pathFile; - - if (pathFile.isFile()) { - if (!mPaths[i].endsWith(".dex")) { - /* - * If the name doesn't end with ".dex" assume it's a zip - * file of some sort. - */ - try { - mZips[i] = new ZipFile(pathFile); - } - catch (IOException ioex) { - // expecting IOException and ZipException - //System.out.println("Failed opening '" + pathFile - // + "': " + ioex); - //ioex.printStackTrace(); - } - } - - /* - * If we got a zip file, we still need to extract out - * the dex file from it. - */ - try { - mDexs[i] = new DexFile(pathFile); - } - catch (IOException ioex) { - // It might be a resource-only zip. - //System.out.println("Failed to construct DexFile '" - // + pathFile + "': " + ioex); - } - } - } - - /* - * Native libraries may exist in both the system and application library - * paths, so we use this search order for these paths: - * 1. This class loader's library path for application libraries - * 2. The VM's library path from the system property for system libraries - * This order was reversed prior to Gingerbread; see http://b/2933456 - */ - libraryPathElements = new ArrayList<String>(); - if (libPath != null) { - for (String pathElement : libPath.split(File.pathSeparator)) { - libraryPathElements.add(cleanupPathElement(pathElement)); - } - } - String systemLibraryPath = System.getProperty("java.library.path", "."); - if (!systemLibraryPath.isEmpty()) { - for (String pathElement : systemLibraryPath.split(File.pathSeparator)) { - libraryPathElements.add(cleanupPathElement(pathElement)); - } - } - } - - /** - * Returns a path element that includes a trailing file separator. - */ - private String cleanupPathElement(String path) { - return path.endsWith(File.separator) ? path : (path + File.separator); - } - - /** - * Finds a class. This method is called by {@code loadClass()} after the - * parent ClassLoader has failed to find a loaded class of the same name. + * native library files. * - * @param name - * The "binary name" of the class to search for, in a - * human-readable form like "java.lang.String" or - * "java.net.URLClassLoader$3$1". - * @return the {@link Class} object representing the class - * @throws ClassNotFoundException - * if the class cannot be found + * @param dexPath the list of jar/apk files containing classes and + * resources, delimited by {@code File.pathSeparator}, which + * defaults to {@code ":"} on Android + * @param libraryPath the list of directories containing native + * libraries, delimited by {@code File.pathSeparator}; may be + * {@code null} + * @param parent the parent class loader */ - @Override - protected Class<?> findClass(String name) throws ClassNotFoundException { - //System.out.println("PathClassLoader " + this + ": findClass '" + name + "'"); - - byte[] data = null; - int length = mPaths.length; - - for (int i = 0; i < length; i++) { - //System.out.println("My path is: " + mPaths[i]); - - if (mDexs[i] != null) { - Class clazz = mDexs[i].loadClassBinaryName(name, this); - if (clazz != null) - return clazz; - } else if (mZips[i] != null) { - String fileName = name.replace('.', '/') + ".class"; - data = loadFromArchive(mZips[i], fileName); - } else { - File pathFile = mFiles[i]; - if (pathFile.isDirectory()) { - String fileName = - mPaths[i] + "/" + name.replace('.', '/') + ".class"; - data = loadFromDirectory(fileName); - } else { - //System.out.println("PathClassLoader: can't find '" - // + mPaths[i] + "'"); - } - - } - - /* --this doesn't work in current version of Dalvik-- - if (data != null) { - System.out.println("--- Found class " + name - + " in zip[" + i + "] '" + mZips[i].getName() + "'"); - int dotIndex = name.lastIndexOf('.'); - if (dotIndex != -1) { - String packageName = name.substring(0, dotIndex); - synchronized (this) { - Package packageObj = getPackage(packageName); - if (packageObj == null) { - definePackage(packageName, null, null, - null, null, null, null, null); - } - } - } - - return defineClass(name, data, 0, data.length); - } - */ - } - - throw new ClassNotFoundException(name + " in loader " + this); - } - - /** - * Finds a resource. This method is called by {@code getResource()} after - * the parent ClassLoader has failed to find a loaded resource of the same - * name. - * - * @param name - * The name of the resource to find - * @return the location of the resource as a URL, or {@code null} if the - * resource is not found. - */ - @Override - protected URL findResource(String name) { - //java.util.logging.Logger.global.severe("findResource: " + name); - - int length = mPaths.length; - - for (int i = 0; i < length; i++) { - URL result = findResource(name, i); - if (result != null) { - return result; - } - } - - return null; - } - - /** - * Finds an enumeration of URLs for the resource with the specified name. - * - * @param resName - * the name of the resource to find. - * @return an enumeration of {@code URL} objects for the requested resource. - */ - @Override - protected Enumeration<URL> findResources(String resName) { - int length = mPaths.length; - ArrayList<URL> results = new ArrayList<URL>(); - for (int i = 0; i < length; i++) { - URL result = findResource(resName, i); - if (result != null) { - results.add(result); - } - } - return Collections.enumeration(results); - } - - private URL findResource(String name, int i) { - File pathFile = mFiles[i]; - ZipFile zip = mZips[i]; - if (zip != null) { - if (isInArchive(zip, name)) { - //System.out.println(" found " + name + " in " + pathFile); - try { - // File.toURL() is compliant with RFC 1738 in always - // creating absolute path names. If we construct the - // URL by concatenating strings, we might end up with - // illegal URLs for relative names. - return new URL("jar:" + pathFile.toURL() + "!/" + name); - } - catch (MalformedURLException e) { - throw new RuntimeException(e); - } - } - } else if (pathFile.isDirectory()) { - File dataFile = new File(mPaths[i] + "/" + name); - if (dataFile.exists()) { - //System.out.println(" found resource " + name); - try { - // Same as archive case regarding URL construction. - return dataFile.toURL(); - } - catch (MalformedURLException e) { - throw new RuntimeException(e); - } - } - } else if (pathFile.isFile()) { - } else { - System.err.println("PathClassLoader: can't find '" - + mPaths[i] + "'"); - } - return null; - } - - /* - * Load the contents of a file from a file in a directory. - * - * Returns null if the class wasn't found. - */ - private byte[] loadFromDirectory(String path) { - try { - return IoUtils.readFileAsByteArray(path); - } catch (IOException ex) { - System.err.println("Error reading from " + path); - // swallow it, return null instead - return null; - } - } - - /* - * Load a class from a file in an archive. We currently assume that - * the file is a Zip archive. - * - * Returns null if the class wasn't found. - */ - private byte[] loadFromArchive(ZipFile zip, String name) { - ZipEntry entry; - - entry = zip.getEntry(name); - if (entry == null) - return null; - - ByteArrayOutputStream byteStream; - InputStream stream; - int count; - - /* - * Copy the data out of the stream. Because we got the ZipEntry - * from a ZipFile, the uncompressed size is known, and we can set - * the initial size of the ByteArrayOutputStream appropriately. - */ - try { - stream = zip.getInputStream(entry); - byteStream = new ByteArrayOutputStream((int) entry.getSize()); - byte[] buf = new byte[4096]; - while ((count = stream.read(buf)) > 0) - byteStream.write(buf, 0, count); - - stream.close(); - } - catch (IOException ioex) { - //System.out.println("Failed extracting '" + archive + "': " +ioex); - return null; - } - - //System.out.println(" loaded from Zip"); - return byteStream.toByteArray(); - } - - /* - * Figure out if "name" is a member of "archive". - */ - private boolean isInArchive(ZipFile zip, String name) { - return zip.getEntry(name) != null; - } - - /** - * Finds a native library. This class loader first searches its own library - * path (as specified in the constructor) and then the system library path. - * In Android 2.2 and earlier, the search order was reversed. - * - * @param libname - * The name of the library to find - * @return the complete path of the library, or {@code null} if the library - * is not found. - */ - public String findLibrary(String libname) { - String fileName = System.mapLibraryName(libname); - for (String pathElement : libraryPathElements) { - String pathName = pathElement + fileName; - File test = new File(pathName); - - if (test.exists()) { - return pathName; - } - } - - return null; - } - - /** - * Returns package information for the given package. Unfortunately, the - * PathClassLoader doesn't really have this information, and as a non-secure - * ClassLoader, it isn't even required to, according to the spec. Yet, we - * want to provide it, in order to make all those hopeful callers of - * <code>myClass.getPackage().getName()</code> happy. Thus we construct a - * Package object the first time it is being requested and fill most of the - * fields with dummy values. The Package object is then put into the - * ClassLoader's Package cache, so we see the same one next time. We don't - * create Package objects for null arguments or for the default package. - * <p> - * There a limited chance that we end up with multiple Package objects - * representing the same package: It can happen when when a package is - * scattered across different JAR files being loaded by different - * ClassLoaders. Rather unlikely, and given that this whole thing is more or - * less a workaround, probably not worth the effort. - * - * @param name - * the name of the class - * @return the package information for the class, or {@code null} if there - * is not package information available for it - */ - @Override - protected Package getPackage(String name) { - if (name != null && !name.isEmpty()) { - synchronized(this) { - Package pack = super.getPackage(name); - - if (pack == null) { - pack = definePackage(name, "Unknown", "0.0", "Unknown", "Unknown", "0.0", "Unknown", null); - } - - return pack; - } - } - return null; - } - - public String toString () { - return getClass().getName() + "[" + path + "]"; + public PathClassLoader(String dexPath, String libraryPath, + ClassLoader parent) { + super(dexPath, null, libraryPath, parent); } } |