| /* |
| * 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.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.InputStream; |
| import java.io.IOException; |
| import java.io.RandomAccessFile; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Enumeration; |
| import java.util.NoSuchElementException; |
| import java.util.zip.ZipEntry; |
| import java.util.zip.ZipFile; |
| import java.net.MalformedURLException; |
| |
| import dalvik.system.DexFile; |
| |
| /** |
| * 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). |
| * |
| * @since Android 1.0 |
| */ |
| public class PathClassLoader extends ClassLoader { |
| |
| private final String path; |
| private final String libPath; |
| |
| private boolean initialized; |
| |
| private String[] mPaths; |
| private File[] mFiles; |
| private ZipFile[] mZips; |
| private DexFile[] mDexs; |
| private String[] mLibPaths; |
| |
| /** |
| * 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 |
| */ |
| public PathClassLoader(String path, ClassLoader parent) { |
| this(path, null, parent); |
| } |
| |
| /** |
| * Creates a {@code PathClassLoader} that operates on two given lists of |
| * files and directories. The entries of the first list 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. |
| * </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; |
| } |
| |
| private synchronized void ensureInit() { |
| if (initialized) { |
| return; |
| } |
| |
| initialized = true; |
| |
| mPaths = path.split(":"); |
| int length = mPaths.length; |
| |
| //System.out.println("PathClassLoader: " + mPaths); |
| mFiles = new File[length]; |
| mZips = new ZipFile[length]; |
| mDexs = new DexFile[length]; |
| |
| boolean wantDex = |
| System.getProperty("android.vm.dexfile", "").equals("true"); |
| |
| /* open all Zip and DEX files up front */ |
| for (int i = 0; i < length; i++) { |
| //System.out.println("My path is: " + mPaths[i]); |
| File pathFile = new File(mPaths[i]); |
| mFiles[i] = pathFile; |
| |
| if (pathFile.isFile()) { |
| try { |
| mZips[i] = new ZipFile(pathFile); |
| } |
| catch (IOException ioex) { |
| // expecting IOException and ZipException |
| //System.out.println("Failed opening '" + pathFile + "': " + ioex); |
| //ioex.printStackTrace(); |
| } |
| if (wantDex) { |
| /* we need both DEX and Zip, because dex has no resources */ |
| try { |
| mDexs[i] = new DexFile(pathFile); |
| } |
| catch (IOException 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 (libPath != null) { |
| if (pathList.length() > 0) { |
| pathList += pathSep + libPath; |
| } |
| else { |
| pathList = libPath; |
| } |
| } |
| |
| 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 (false) |
| System.out.println("Native lib path: " + mLibPaths[i]); |
| } |
| } |
| |
| /** |
| * 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 "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 |
| */ |
| @Override |
| protected Class<?> findClass(String name) throws ClassNotFoundException |
| { |
| ensureInit(); |
| |
| //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) { |
| ensureInit(); |
| |
| //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. |
| * @since Android 1.0 |
| */ |
| @Override |
| protected Enumeration<URL> findResources(String resName) { |
| ensureInit(); |
| |
| 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 new EnumerateListArray<URL>(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) { |
| RandomAccessFile raf; |
| byte[] fileData; |
| |
| //System.out.println("Trying to load from " + path); |
| try { |
| raf = new RandomAccessFile(path, "r"); |
| } |
| catch (FileNotFoundException fnfe) { |
| //System.out.println(" Not found: " + path); |
| return null; |
| } |
| |
| try { |
| fileData = new byte[(int) raf.length()]; |
| raf.read(fileData); |
| raf.close(); |
| } |
| catch (IOException ioe) { |
| System.err.println("Error reading from " + path); |
| // swallow it, return null instead |
| fileData = null; |
| } |
| |
| return fileData; |
| } |
| |
| /* |
| * 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 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. |
| */ |
| protected String findLibrary(String libname) { |
| ensureInit(); |
| |
| 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()) |
| 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 && !"".equals(name)) { |
| 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; |
| } |
| |
| /* |
| * Create an Enumeration for an ArrayList. |
| */ |
| private static class EnumerateListArray<T> implements Enumeration<T> { |
| private final ArrayList mList; |
| private int i = 0; |
| |
| EnumerateListArray(ArrayList list) { |
| mList = list; |
| } |
| |
| public boolean hasMoreElements() { |
| return i < mList.size(); |
| } |
| |
| public T nextElement() { |
| if (i >= mList.size()) |
| throw new NoSuchElementException(); |
| return (T) mList.get(i++); |
| } |
| }; |
| |
| public String toString () { |
| return getClass().getName() + "[" + path + "]"; |
| } |
| } |