summaryrefslogtreecommitdiffstats
path: root/libart/src/main/java/dalvik
diff options
context:
space:
mode:
authorBrian Carlstrom <bdc@google.com>2013-05-22 11:05:31 -0700
committerBrian Carlstrom <bdc@google.com>2013-05-22 11:12:51 -0700
commit2cf03dc15c40b92634ff606694af5a6e9aa4db09 (patch)
tree35e908dd1d72be7f2adf401360aea9646760623c /libart/src/main/java/dalvik
parent2efa673c6b6408b306b0372fdf37d232b29c2526 (diff)
downloadlibcore-2cf03dc15c40b92634ff606694af5a6e9aa4db09.zip
libcore-2cf03dc15c40b92634ff606694af5a6e9aa4db09.tar.gz
libcore-2cf03dc15c40b92634ff606694af5a6e9aa4db09.tar.bz2
Adding libart support to libcore
Change-Id: I86febf08eacf42bb4b2f06dbd51c5b2d5b25c9fb
Diffstat (limited to 'libart/src/main/java/dalvik')
-rw-r--r--libart/src/main/java/dalvik/system/BaseDexClassLoader.java139
-rw-r--r--libart/src/main/java/dalvik/system/DexFile.java318
-rw-r--r--libart/src/main/java/dalvik/system/DexPathList.java451
3 files changed, 908 insertions, 0 deletions
diff --git a/libart/src/main/java/dalvik/system/BaseDexClassLoader.java b/libart/src/main/java/dalvik/system/BaseDexClassLoader.java
new file mode 100644
index 0000000..6a1a493
--- /dev/null
+++ b/libart/src/main/java/dalvik/system/BaseDexClassLoader.java
@@ -0,0 +1,139 @@
+/*
+ * 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.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+/**
+ * Base class for common functionality between various dex-based
+ * {@link ClassLoader} implementations.
+ */
+public class BaseDexClassLoader extends ClassLoader {
+ 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.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
+ }
+
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
+ Class c = pathList.findClass(name, suppressedExceptions);
+ if (c == null) {
+ ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
+ for (Throwable t : suppressedExceptions) {
+ cnfe.addSuppressed(t);
+ }
+ throw cnfe;
+ }
+ return c;
+ }
+
+ @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;
+ }
+
+ /**
+ * @hide
+ */
+ public String getLdLibraryPath() {
+ StringBuilder result = new StringBuilder();
+ for (File directory : pathList.getNativeLibraryDirectories()) {
+ if (result.length() > 0) {
+ result.append(':');
+ }
+ result.append(directory);
+ }
+ return result.toString();
+ }
+
+ @Override public String toString() {
+ return getClass().getName() + "[" + pathList + "]";
+ }
+}
diff --git a/libart/src/main/java/dalvik/system/DexFile.java b/libart/src/main/java/dalvik/system/DexFile.java
new file mode 100644
index 0000000..06b834a
--- /dev/null
+++ b/libart/src/main/java/dalvik/system/DexFile.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2007 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.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import libcore.io.ErrnoException;
+import libcore.io.Libcore;
+import libcore.io.StructStat;
+
+/**
+ * Manipulates DEX files. The class is similar in principle to
+ * {@link java.util.zip.ZipFile}. It is used primarily by class loaders.
+ * <p>
+ * Note we don't directly open and read the DEX file here. They're memory-mapped
+ * read-only by the VM.
+ */
+public final class DexFile {
+ private int mCookie;
+ private final String mFileName;
+ private final CloseGuard guard = CloseGuard.get();
+
+ /**
+ * Opens a DEX file from a given File object. This will usually be a ZIP/JAR
+ * file with a "classes.dex" inside.
+ *
+ * The VM will generate the name of the corresponding file in
+ * /data/dalvik-cache and open it, possibly creating or updating
+ * it first if system permissions allow. Don't pass in the name of
+ * a file in /data/dalvik-cache, as the named file is expected to be
+ * in its original (pre-dexopt) state.
+ *
+ * @param file
+ * the File object referencing the actual DEX file
+ *
+ * @throws IOException
+ * if an I/O error occurs, such as the file not being found or
+ * access rights missing for opening it
+ */
+ public DexFile(File file) throws IOException {
+ this(file.getPath());
+ }
+
+ /**
+ * Opens a DEX file from a given filename. This will usually be a ZIP/JAR
+ * file with a "classes.dex" inside.
+ *
+ * The VM will generate the name of the corresponding file in
+ * /data/dalvik-cache and open it, possibly creating or updating
+ * it first if system permissions allow. Don't pass in the name of
+ * a file in /data/dalvik-cache, as the named file is expected to be
+ * in its original (pre-dexopt) state.
+ *
+ * @param fileName
+ * the filename of the DEX file
+ *
+ * @throws IOException
+ * if an I/O error occurs, such as the file not being found or
+ * access rights missing for opening it
+ */
+ public DexFile(String fileName) throws IOException {
+ mCookie = openDexFile(fileName, null, 0);
+ mFileName = fileName;
+ guard.open("close");
+ //System.out.println("DEX FILE cookie is " + mCookie);
+ }
+
+ /**
+ * Opens a DEX file from a given filename, using a specified file
+ * to hold the optimized data.
+ *
+ * @param sourceName
+ * Jar or APK file with "classes.dex".
+ * @param outputName
+ * File that will hold the optimized form of the DEX data.
+ * @param flags
+ * Enable optional features.
+ */
+ private DexFile(String sourceName, String outputName, int flags) throws IOException {
+ if (outputName != null) {
+ try {
+ String parent = new File(outputName).getParent();
+ if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) {
+ throw new IllegalArgumentException("Optimized data directory " + parent
+ + " is not owned by the current user. Shared storage cannot protect"
+ + " your application from code injection attacks.");
+ }
+ } catch (ErrnoException ignored) {
+ // assume we'll fail with a more contextual error later
+ }
+ }
+
+ mCookie = openDexFile(sourceName, outputName, flags);
+ mFileName = sourceName;
+ guard.open("close");
+ //System.out.println("DEX FILE cookie is " + mCookie);
+ }
+
+ /**
+ * Open a DEX file, specifying the file in which the optimized DEX
+ * data should be written. If the optimized form exists and appears
+ * to be current, it will be used; if not, the VM will attempt to
+ * regenerate it.
+ *
+ * This is intended for use by applications that wish to download
+ * and execute DEX files outside the usual application installation
+ * mechanism. This function should not be called directly by an
+ * application; instead, use a class loader such as
+ * dalvik.system.DexClassLoader.
+ *
+ * @param sourcePathName
+ * Jar or APK file with "classes.dex". (May expand this to include
+ * "raw DEX" in the future.)
+ * @param outputPathName
+ * File that will hold the optimized form of the DEX data.
+ * @param flags
+ * Enable optional features. (Currently none defined.)
+ * @return
+ * A new or previously-opened DexFile.
+ * @throws IOException
+ * If unable to open the source or output file.
+ */
+ static public DexFile loadDex(String sourcePathName, String outputPathName,
+ int flags) throws IOException {
+
+ /*
+ * TODO: we may want to cache previously-opened DexFile objects.
+ * The cache would be synchronized with close(). This would help
+ * us avoid mapping the same DEX more than once when an app
+ * decided to open it multiple times. In practice this may not
+ * be a real issue.
+ */
+ return new DexFile(sourcePathName, outputPathName, flags);
+ }
+
+ /**
+ * Gets the name of the (already opened) DEX file.
+ *
+ * @return the file name
+ */
+ public String getName() {
+ return mFileName;
+ }
+
+ /**
+ * Closes the DEX file.
+ * <p>
+ * This may not be able to release any resources. If classes from this
+ * DEX file are still resident, the DEX file can't be unmapped.
+ *
+ * @throws IOException
+ * if an I/O error occurs during closing the file, which
+ * normally should not happen
+ */
+ public void close() throws IOException {
+ if (mCookie != 0) {
+ guard.close();
+ closeDexFile(mCookie);
+ mCookie = 0;
+ }
+ }
+
+ /**
+ * Loads a class. Returns the class on success, or a {@code null} reference
+ * on failure.
+ * <p>
+ * If you are not calling this from a class loader, this is most likely not
+ * going to do what you want. Use {@link Class#forName(String)} instead.
+ * <p>
+ * The method does not throw {@link ClassNotFoundException} if the class
+ * isn't found because it isn't reasonable to throw exceptions wildly every
+ * time a class is not found in the first DEX file we look at.
+ *
+ * @param name
+ * the class name, which should look like "java/lang/String"
+ *
+ * @param loader
+ * the class loader that tries to load the class (in most cases
+ * the caller of the method
+ *
+ * @return the {@link Class} object representing the class, or {@code null}
+ * if the class cannot be loaded
+ */
+ public Class loadClass(String name, ClassLoader loader) {
+ String slashName = name.replace('.', '/');
+ return loadClassBinaryName(slashName, loader, null);
+ }
+
+ /**
+ * See {@link #loadClass(String, ClassLoader)}.
+ *
+ * This takes a "binary" class name to better match ClassLoader semantics.
+ *
+ * @hide
+ */
+ public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
+ return defineClass(name, loader, mCookie, suppressed);
+ }
+
+ private static Class defineClass(String name, ClassLoader loader, int cookie,
+ List<Throwable> suppressed) {
+ Class result = null;
+ try {
+ result = defineClassNative(name, loader, cookie);
+ } catch (NoClassDefFoundError e) {
+ if (suppressed != null) {
+ suppressed.add(e);
+ }
+ } catch (ClassNotFoundException e) {
+ if (suppressed != null) {
+ suppressed.add(e);
+ }
+ }
+ return result;
+ }
+
+ private static native Class defineClassNative(String name, ClassLoader loader, int cookie)
+ throws ClassNotFoundException, NoClassDefFoundError;
+
+ /**
+ * Enumerate the names of the classes in this DEX file.
+ *
+ * @return an enumeration of names of classes contained in the DEX file, in
+ * the usual internal form (like "java/lang/String").
+ */
+ public Enumeration<String> entries() {
+ return new DFEnum(this);
+ }
+
+ /*
+ * Helper class.
+ */
+ private class DFEnum implements Enumeration<String> {
+ private int mIndex;
+ private String[] mNameList;
+
+ DFEnum(DexFile df) {
+ mIndex = 0;
+ mNameList = getClassNameList(mCookie);
+ }
+
+ public boolean hasMoreElements() {
+ return (mIndex < mNameList.length);
+ }
+
+ public String nextElement() {
+ return mNameList[mIndex++];
+ }
+ }
+
+ /* return a String array with class names */
+ native private static String[] getClassNameList(int cookie);
+
+ /**
+ * Called when the class is finalized. Makes sure the DEX file is closed.
+ *
+ * @throws IOException
+ * if an I/O error occurs during closing the file, which
+ * normally should not happen
+ */
+ @Override protected void finalize() throws Throwable {
+ try {
+ if (guard != null) {
+ guard.warnIfOpen();
+ }
+ close();
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /*
+ * Open a DEX file. The value returned is a magic VM cookie. On
+ * failure, an IOException is thrown.
+ */
+ native private static int openDexFile(String sourceName, String outputName,
+ int flags) throws IOException;
+
+ /*
+ * Close DEX file.
+ */
+ native private static void closeDexFile(int cookie);
+
+ /**
+ * Returns true if the VM believes that the apk/jar file is out of date
+ * and should be passed through "dexopt" again.
+ *
+ * @param fileName the absolute path to the apk/jar file to examine.
+ * @return true if dexopt should be called on the file, false otherwise.
+ * @throws java.io.FileNotFoundException if fileName is not readable,
+ * not a file, or not present.
+ * @throws java.io.IOException if fileName is not a valid apk/jar file or
+ * if problems occur while parsing it.
+ * @throws java.lang.NullPointerException if fileName is null.
+ * @throws dalvik.system.StaleDexCacheError if the optimized dex file
+ * is stale but exists on a read-only partition.
+ */
+ native public static boolean isDexOptNeeded(String fileName)
+ throws FileNotFoundException, IOException;
+}
diff --git a/libart/src/main/java/dalvik/system/DexPathList.java b/libart/src/main/java/dalvik/system/DexPathList.java
new file mode 100644
index 0000000..5518c83
--- /dev/null
+++ b/libart/src/main/java/dalvik/system/DexPathList.java
@@ -0,0 +1,451 @@
+/*
+ * 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.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.regex.Pattern;
+import java.util.zip.ZipFile;
+import libcore.io.ErrnoException;
+import libcore.io.IoUtils;
+import libcore.io.Libcore;
+import libcore.io.StructStat;
+import static libcore.io.OsConstants.*;
+
+/**
+ * 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.
+ * Should be called pathElements, but the Facebook app uses reflection
+ * to modify 'dexElements' (http://b/7726934).
+ */
+ private final Element[] dexElements;
+
+ /** List of native library directories. */
+ 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);
+ }
+
+ @Override public String toString() {
+ return "DexPathList[" + Arrays.toString(dexElements) +
+ ",nativeLibraryDirectories=" + Arrays.toString(nativeLibraryDirectories) + "]";
+ }
+
+ /**
+ * For BaseDexClassLoader.getLdLibraryPath.
+ */
+ public File[] getNativeLibraryDirectories() {
+ return nativeLibraryDirectories;
+ }
+
+ /**
+ * 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 searchPath, boolean directoriesOnly,
+ ArrayList<File> resultList) {
+ if (searchPath == null) {
+ return;
+ }
+ for (String path : searchPath.split(":")) {
+ try {
+ StructStat sb = Libcore.os.stat(path);
+ if (!directoriesOnly || S_ISDIR(sb.st_mode)) {
+ resultList.add(new File(path));
+ }
+ } catch (ErrnoException ignored) {
+ }
+ }
+ }
+
+ /**
+ * 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) {
+ File 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) {
+ System.logE("Unable to load dex file: " + file, ex);
+ }
+ } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)
+ || name.endsWith(ZIP_SUFFIX)) {
+ zip = file;
+
+ 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 if (file.isDirectory()) {
+ // We support directories for looking up resources.
+ // This is only useful for running libcore tests.
+ elements.add(new Element(file, true, null, null));
+ } else {
+ System.logW("Unknown file type for: " + file);
+ }
+
+ if ((zip != null) || (dex != null)) {
+ elements.add(new Element(file, false, 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 oat file.
+ */
+ private static String optimizedPathFor(File path,
+ File optimizedDirectory) {
+ String fileName = path.getName() + ".oat";
+ 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.
+ *
+ * @param name of class to find
+ * @param suppressed exceptions encountered whilst finding the class
+ * @return the named class or {@code null} if the class is not
+ * found in any of the dex files
+ */
+ public Class findClass(String name, List<Throwable> suppressed) {
+ for (Element element : dexElements) {
+ DexFile dex = element.dexFile;
+
+ if (dex != null) {
+ Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
+ 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) {
+ String path = new File(directory, fileName).getPath();
+ if (IoUtils.canOpenReadOnly(path)) {
+ return path;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Element of the dex/resource file path
+ */
+ /*package*/ static class Element {
+ private final File file;
+ private final boolean isDirectory;
+ private final File zip;
+ private final DexFile dexFile;
+
+ private ZipFile zipFile;
+ private boolean initialized;
+
+ public Element(File file, boolean isDirectory, File zip, DexFile dexFile) {
+ this.file = file;
+ this.isDirectory = isDirectory;
+ this.zip = zip;
+ this.dexFile = dexFile;
+ }
+
+ @Override public String toString() {
+ if (isDirectory) {
+ return "directory \"" + file + "\"";
+ } else if (zip != null) {
+ return "zip file \"" + zip + "\"";
+ } else {
+ return "dex file \"" + dexFile + "\"";
+ }
+ }
+
+ public synchronized void maybeInit() {
+ if (initialized) {
+ return;
+ }
+
+ initialized = true;
+
+ if (isDirectory || zip == null) {
+ return;
+ }
+
+ try {
+ zipFile = new ZipFile(zip);
+ } catch (IOException ioe) {
+ /*
+ * 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).
+ */
+ System.logE("Unable to open zip file: " + file, ioe);
+ zipFile = null;
+ }
+ }
+
+ public URL findResource(String name) {
+ maybeInit();
+
+ // We support directories so we can run tests and/or legacy code
+ // that uses Class.getResource.
+ if (isDirectory) {
+ File resourceFile = new File(file, name);
+ if (resourceFile.exists()) {
+ try {
+ return resourceFile.toURI().toURL();
+ } catch (MalformedURLException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+ }
+
+ 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);
+ }
+ }
+ }
+}