FileDocCategorySizeDatePackage
AndroidJarLoader.javaAPI DocAndroid 1.5 API16489Wed May 06 22:41:10 BST 2009com.android.ide.eclipse.adt.sdk

AndroidJarLoader

public class AndroidJarLoader extends ClassLoader implements IAndroidClassLoader
Custom class loader able to load a class from the SDK jar file.

Fields Summary
private String
mOsFrameworkLocation
private final HashMap
mEntryCache
A cache for binary data extracted from the zip
private final HashMap
mClassCache
A cache for already defined Classes
Constructors Summary
public AndroidJarLoader(String osFrameworkLocation)
Creates the class loader by providing the os path to the framework jar archive

param
osFrameworkLocation OS Path of the framework JAR file

    
                                 
       
        super();
        mOsFrameworkLocation = osFrameworkLocation;
    
Methods Summary
private java.lang.ClassdefineAndCacheClass(java.lang.String name, byte[] data)
Defines a class based on its binary data and caches the resulting class object.

param
name The binary name of the class (i.e. package.class1$class2)
param
data The binary data from the loader.
return
The class defined
throws
ClassFormatError if defineClass failed.

        Class<?> cached_class;
        cached_class = defineClass(null, data, 0, data.length);

        if (cached_class != null) {
            // Add new class to the cache class and remove it from the zip entry data cache
            mClassCache.put(name, cached_class);
            mEntryCache.remove(name);
        }
        return cached_class;
    
private java.lang.StringentryPathToClassName(java.lang.String entryPath)
Helper method that converts a Zip entry path into a corresponding Java full qualified binary class name.

F.ex, this converts "com/my/package/Foo.class" into "com.my.package.Foo".

        return entryPath.replaceFirst("\\.class$", "").replaceAll("[/\\\\]", "."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    
protected java.lang.ClassfindClass(java.lang.String name)
Finds the class with the specified binary name. {@inheritDoc}

        try {
            // try to find the class in the cache
            Class<?> cached_class = mClassCache.get(name);
            if (cached_class == ClassNotFoundException.class) {
                // we already know we can't find this class, don't try again
                throw new ClassNotFoundException(name);
            } else if (cached_class != null) {
                return cached_class;
            }
            
            // if not found, look it up and cache it
            byte[] data = loadClassData(name);
            if (data != null) {
                return defineAndCacheClass(name, data);
            } else {
                // if the class can't be found, record a CNFE class in the map so
                // that we don't try to reload it next time
                mClassCache.put(name, ClassNotFoundException.class);
                throw new ClassNotFoundException(name);
            }
        } catch (ClassNotFoundException e) {
            throw e;
        } catch (Exception e) {
            throw new ClassNotFoundException(e.getMessage()); 
        }
    
public java.util.HashMapfindClassesDerivingFrom(java.lang.String packageFilter, java.lang.String[] superClasses)
Finds and loads all classes that derive from a given set of super classes.

As a side-effect this will load and cache most, if not all, classes in the input JAR file.

param
packageFilter Base name of package of classes to find. Use an empty string to find everyting.
param
superClasses The super classes of all the classes to find.
return
An hash map which keys are the super classes looked for and which values are ArrayList of the classes found. The array lists are always created for all the valid keys, they are simply empty if no deriving class is found for a given super class.
throws
IOException
throws
InvalidAttributeValueException
throws
ClassFormatError


        packageFilter = packageFilter.replaceAll("\\.", "/"); //$NON-NLS-1$ //$NON-NLS-2$

        HashMap<String, ArrayList<IClassDescriptor>> mClassesFound =
                new HashMap<String, ArrayList<IClassDescriptor>>();

        for (String className : superClasses) {
            mClassesFound.put(className, new ArrayList<IClassDescriptor>());
        }

        // create streams to read the intermediary archive
        FileInputStream fis = new FileInputStream(mOsFrameworkLocation);
        ZipInputStream zis = new ZipInputStream(fis);
        ZipEntry entry;
        while ((entry = zis.getNextEntry()) != null) {
            // get the name of the entry and convert to a class binary name
            String entryPath = entry.getName();
            if (!entryPath.endsWith(AndroidConstants.DOT_CLASS)) {
                // only accept class files
                continue;
            }
            if (packageFilter.length() > 0 && !entryPath.startsWith(packageFilter)) {
                // only accept stuff from the requested root package.
                continue;
            }
            String className = entryPathToClassName(entryPath);
      
            Class<?> loaded_class = mClassCache.get(className);
            if (loaded_class == null) {
                byte[] data = mEntryCache.get(className);
                if (data == null) {    
                    // Get the class and cache it
                    long entrySize = entry.getSize();
                    if (entrySize > Integer.MAX_VALUE) {
                        throw new InvalidAttributeValueException();
                    }
                    data = readZipData(zis, (int)entrySize);
                }
                loaded_class = defineAndCacheClass(className, data);
            }

            for (Class<?> superClass = loaded_class.getSuperclass();
                    superClass != null;
                    superClass = superClass.getSuperclass()) {
                String superName = superClass.getCanonicalName();
                if (mClassesFound.containsKey(superName)) {
                    mClassesFound.get(superName).add(new ClassWrapper(loaded_class));
                    break;
                }
            }
        }

        return mClassesFound;
    
public IClassDescriptorgetClass(java.lang.String className)
Returns a {@link IAndroidClassLoader.IClassDescriptor} by its fully-qualified name.

param
className the fully-qualified name of the class to return.
throws
ClassNotFoundException

        try {
            return new ClassWrapper(loadClass(className));
        } catch (ClassNotFoundException e) {
            throw e;  // useful for debugging
        }
    
public java.lang.StringgetSource()

        return mOsFrameworkLocation;
    
private synchronized byte[]loadClassData(java.lang.String className)
Loads a class data from its binary name.

This uses the class binary data that has been preloaded earlier by the preLoadClasses() method if possible.

param
className the binary name
return
an array of bytes representing the class data or null if not found
throws
InvalidAttributeValueException
throws
IOException


        byte[] data = mEntryCache.get(className);
        if (data != null) {
            return data;
        }
        
        // The name is a binary name. Something like "android.R", or "android.R$id".
        // Make a path out of it.
        String entryName = className.replaceAll("\\.", "/") + AndroidConstants.DOT_CLASS; //$NON-NLS-1$ //$NON-NLS-2$

       // create streams to read the intermediary archive
        FileInputStream fis = new FileInputStream(mOsFrameworkLocation);
        ZipInputStream zis = new ZipInputStream(fis);
        
        // loop on the entries of the intermediary package and put them in the final package.
        ZipEntry entry;

        while ((entry = zis.getNextEntry()) != null) {
            // get the name of the entry.
            String currEntryName = entry.getName();
            
            if (currEntryName.equals(entryName)) {
                long entrySize = entry.getSize();
                if (entrySize > Integer.MAX_VALUE) {
                    throw new InvalidAttributeValueException();
                }

                data = readZipData(zis, (int)entrySize);
                return data;
            }
        }

        return null;
    
public voidpreLoadClasses(java.lang.String packageFilter, java.lang.String taskLabel, org.eclipse.core.runtime.IProgressMonitor monitor)
Pre-loads all class binary data that belong to the given package by reading the archive once and caching them internally.

This does not actually preload "classes", it just reads the unzipped bytes for a given class. To obtain a class, one must call {@link #findClass(String)} later.

All classes which package name starts with "packageFilter" will be included and can be found later.

May throw some exceptions if the framework JAR cannot be read.

param
packageFilter The package that contains all the class data to preload, using a fully qualified binary name (.e.g "com.my.package."). The matching algorithm is simple "startsWith". Use an empty string to include everything.
param
taskLabel An optional task name for the sub monitor. Can be null.
param
monitor A progress monitor. Can be null. Caller is responsible for calling done.
throws
IOException
throws
InvalidAttributeValueException
throws
ClassFormatError

        // Transform the package name into a zip entry path
        String pathFilter = packageFilter.replaceAll("\\.", "/"); //$NON-NLS-1$ //$NON-NLS-2$
        
        SubMonitor progress = SubMonitor.convert(monitor, taskLabel == null ? "" : taskLabel, 100);
        
        // create streams to read the intermediary archive
        FileInputStream fis = new FileInputStream(mOsFrameworkLocation);
        ZipInputStream zis = new ZipInputStream(fis);
        ZipEntry entry;       
        while ((entry = zis.getNextEntry()) != null) {
            // get the name of the entry.
            String entryPath = entry.getName();
            
            if (!entryPath.endsWith(AndroidConstants.DOT_CLASS)) {
                // only accept class files
                continue;
            }

            // check if it is part of the package to preload
            if (pathFilter.length() > 0 && !entryPath.startsWith(pathFilter)) {
                continue;
            }
            String className = entryPathToClassName(entryPath);

            if (!mEntryCache.containsKey(className)) {
                long entrySize = entry.getSize();
                if (entrySize > Integer.MAX_VALUE) {
                    throw new InvalidAttributeValueException();
                }
                byte[] data = readZipData(zis, (int)entrySize);
                mEntryCache.put(className, data);
            }

            // advance 5% of whatever is allocated on the progress bar
            progress.setWorkRemaining(100);
            progress.worked(5);
            progress.subTask(String.format("Preload %1$s", className));
        }
    
private byte[]readZipData(java.util.zip.ZipInputStream zis, int entrySize)
Reads data for the current entry from the zip input stream.

param
zis The Zip input stream
param
entrySize The entry size. -1 if unknown.
return
The new data for the current entry.
throws
IOException If ZipInputStream.read() fails.

        int block_size = 1024;
        int data_size = entrySize < 1 ? block_size : entrySize; 
        int offset = 0;
        byte[] data = new byte[data_size];
        
        while(zis.available() != 0) {
            int count = zis.read(data, offset, data_size - offset);
            if (count < 0) {  // read data is done
                break;
            }
            offset += count;
            
            if (entrySize >= 1 && offset >= entrySize) {  // we know the size and we're done
                break;
            }

            // if we don't know the entry size and we're not done reading,
            // expand the data buffer some more.
            if (offset >= data_size) {
                byte[] temp = new byte[data_size + block_size];
                System.arraycopy(data, 0, temp, 0, data_size);
                data_size += block_size;
                data = temp;
                block_size *= 2;
            }
        }
        
        if (offset < data_size) {
            // buffer was allocated too large, trim it
            byte[] temp = new byte[offset];
            if (offset > 0) {
                System.arraycopy(data, 0, temp, 0, offset);
            }
            data = temp;
        }
        
        return data;