FileDocCategorySizeDatePackage
PathClassLoader.javaAPI DocAndroid 1.5 API16885Wed May 06 22:41:02 BST 2009dalvik.system

PathClassLoader

public class PathClassLoader extends ClassLoader
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

Fields Summary
private final String
path
private final String
libPath
private boolean
initialized
private String[]
mPaths
private File[]
mFiles
private ZipFile[]
mZips
private dalvik.system.DexFile[]
mDexs
private String[]
mLibPaths
Constructors Summary
public PathClassLoader(String path, ClassLoader parent)
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

        this(path, null, parent);
    
public PathClassLoader(String path, String libPath, ClassLoader 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:
  • Directories containing classes or resources.
  • JAR/ZIP/APK files, possibly containing a "classes.dex" file.
  • "classes.dex" files.
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

        super(parent);

        if (path == null)
            throw new NullPointerException();

        this.path = path;
        this.libPath = libPath;
    
Methods Summary
private synchronized voidensureInit()

        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 '" + archive + "': " + 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]);
        }
    
protected java.lang.ClassfindClass(java.lang.String name)
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

        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) {
                String slashName = name.replace('.", '/");
                Class clazz = mDexs[i].loadClass(slashName, 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);
    
protected java.lang.StringfindLibrary(java.lang.String libname)
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.

        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;
    
protected java.net.URLfindResource(java.lang.String name)
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.

        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;
    
private java.net.URLfindResource(java.lang.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;
    
protected java.util.EnumerationfindResources(java.lang.String resName)
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

        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);
    
protected java.lang.PackagegetPackage(java.lang.String name)
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 myClass.getPackage().getName() 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.

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

        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;
    
private booleanisInArchive(java.util.zip.ZipFile zip, java.lang.String name)

        return zip.getEntry(name) != null;
    
private byte[]loadFromArchive(java.util.zip.ZipFile zip, java.lang.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();
    
private byte[]loadFromDirectory(java.lang.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;