summaryrefslogtreecommitdiffstats
path: root/dalvik/src/main
diff options
context:
space:
mode:
authorDan Bornstein <danfuzz@android.com>2011-02-10 10:35:52 -0800
committerDan Bornstein <danfuzz@android.com>2011-02-16 11:55:47 -0800
commitea52753a0f80fcd70acfe9150ecb854511ff38db (patch)
tree5f004f82db4ce1bd5b6c7c3314e6c12b5a2fb786 /dalvik/src/main
parent8efab4cc6c0dd46712e4ca80d9cb851dd5bbf6c2 (diff)
downloadlibcore-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.java128
-rw-r--r--dalvik/src/main/java/dalvik/system/DexClassLoader.java334
-rw-r--r--dalvik/src/main/java/dalvik/system/DexPathList.java428
-rw-r--r--dalvik/src/main/java/dalvik/system/PathClassLoader.java439
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 &mdash; typically referred
+ * to as a "class path" &mdash; 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);
}
}