Home | History | Annotate | Download | only in sdk
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Eclipse Public License, Version 1.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.eclipse.org/org/documents/epl-v10.php
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.ide.eclipse.adt.internal.sdk;
     18 
     19 import com.android.ide.eclipse.adt.AdtConstants;
     20 
     21 import org.eclipse.core.runtime.IProgressMonitor;
     22 import org.eclipse.core.runtime.SubMonitor;
     23 
     24 import java.io.FileInputStream;
     25 import java.io.IOException;
     26 import java.lang.reflect.Modifier;
     27 import java.util.ArrayList;
     28 import java.util.HashMap;
     29 import java.util.zip.ZipEntry;
     30 import java.util.zip.ZipInputStream;
     31 
     32 import javax.management.InvalidAttributeValueException;
     33 
     34 /**
     35  * Custom class loader able to load a class from the SDK jar file.
     36  */
     37 public class AndroidJarLoader extends ClassLoader implements IAndroidClassLoader {
     38 
     39     /**
     40      * Wrapper around a {@link Class} to provide the methods of
     41      * {@link IAndroidClassLoader.IClassDescriptor}.
     42      */
     43     public final static class ClassWrapper implements IClassDescriptor {
     44         private Class<?> mClass;
     45 
     46         public ClassWrapper(Class<?> clazz) {
     47             mClass = clazz;
     48         }
     49 
     50         public String getFullClassName() {
     51             return mClass.getCanonicalName();
     52         }
     53 
     54         public IClassDescriptor[] getDeclaredClasses() {
     55             Class<?>[] classes = mClass.getDeclaredClasses();
     56             IClassDescriptor[] iclasses = new IClassDescriptor[classes.length];
     57             for (int i = 0 ; i < classes.length ; i++) {
     58                 iclasses[i] = new ClassWrapper(classes[i]);
     59             }
     60 
     61             return iclasses;
     62         }
     63 
     64         public IClassDescriptor getEnclosingClass() {
     65             return new ClassWrapper(mClass.getEnclosingClass());
     66         }
     67 
     68         public String getSimpleName() {
     69             return mClass.getSimpleName();
     70         }
     71 
     72         public IClassDescriptor getSuperclass() {
     73             return new ClassWrapper(mClass.getSuperclass());
     74         }
     75 
     76         @Override
     77         public boolean equals(Object clazz) {
     78             if (clazz instanceof ClassWrapper) {
     79                 return mClass.equals(((ClassWrapper)clazz).mClass);
     80             }
     81             return super.equals(clazz);
     82         }
     83 
     84         @Override
     85         public int hashCode() {
     86             return mClass.hashCode();
     87         }
     88 
     89 
     90         public boolean isInstantiable() {
     91             int modifiers = mClass.getModifiers();
     92             return Modifier.isAbstract(modifiers) == false && Modifier.isPublic(modifiers) == true;
     93         }
     94 
     95         public Class<?> wrappedClass() {
     96             return mClass;
     97         }
     98 
     99     }
    100 
    101     private String mOsFrameworkLocation;
    102 
    103     /** A cache for binary data extracted from the zip */
    104     private final HashMap<String, byte[]> mEntryCache = new HashMap<String, byte[]>();
    105     /** A cache for already defined Classes */
    106     private final HashMap<String, Class<?> > mClassCache = new HashMap<String, Class<?> >();
    107 
    108     /**
    109      * Creates the class loader by providing the os path to the framework jar archive
    110      *
    111      * @param osFrameworkLocation OS Path of the framework JAR file
    112      */
    113     public AndroidJarLoader(String osFrameworkLocation) {
    114         super();
    115         mOsFrameworkLocation = osFrameworkLocation;
    116     }
    117 
    118     public String getSource() {
    119         return mOsFrameworkLocation;
    120     }
    121 
    122     /**
    123      * Pre-loads all class binary data that belong to the given package by reading the archive
    124      * once and caching them internally.
    125      * <p/>
    126      * This does not actually preload "classes", it just reads the unzipped bytes for a given
    127      * class. To obtain a class, one must call {@link #findClass(String)} later.
    128      * <p/>
    129      * All classes which package name starts with "packageFilter" will be included and can be
    130      * found later.
    131      * <p/>
    132      * May throw some exceptions if the framework JAR cannot be read.
    133      *
    134      * @param packageFilter The package that contains all the class data to preload, using a fully
    135      *                    qualified binary name (.e.g "com.my.package."). The matching algorithm
    136      *                    is simple "startsWith". Use an empty string to include everything.
    137      * @param taskLabel An optional task name for the sub monitor. Can be null.
    138      * @param monitor A progress monitor. Can be null. Caller is responsible for calling done.
    139      * @throws IOException
    140      * @throws InvalidAttributeValueException
    141      * @throws ClassFormatError
    142      */
    143     public void preLoadClasses(String packageFilter, String taskLabel, IProgressMonitor monitor)
    144         throws IOException, InvalidAttributeValueException, ClassFormatError {
    145         // Transform the package name into a zip entry path
    146         String pathFilter = packageFilter.replaceAll("\\.", "/"); //$NON-NLS-1$ //$NON-NLS-2$
    147 
    148         SubMonitor progress = SubMonitor.convert(monitor, taskLabel == null ? "" : taskLabel, 100);
    149 
    150         // create streams to read the intermediary archive
    151         FileInputStream fis = new FileInputStream(mOsFrameworkLocation);
    152         ZipInputStream zis = new ZipInputStream(fis);
    153         ZipEntry entry;
    154         while ((entry = zis.getNextEntry()) != null) {
    155             // get the name of the entry.
    156             String entryPath = entry.getName();
    157 
    158             if (!entryPath.endsWith(AdtConstants.DOT_CLASS)) {
    159                 // only accept class files
    160                 continue;
    161             }
    162 
    163             // check if it is part of the package to preload
    164             if (pathFilter.length() > 0 && !entryPath.startsWith(pathFilter)) {
    165                 continue;
    166             }
    167             String className = entryPathToClassName(entryPath);
    168 
    169             if (!mEntryCache.containsKey(className)) {
    170                 long entrySize = entry.getSize();
    171                 if (entrySize > Integer.MAX_VALUE) {
    172                     throw new InvalidAttributeValueException();
    173                 }
    174                 byte[] data = readZipData(zis, (int)entrySize);
    175                 mEntryCache.put(className, data);
    176             }
    177 
    178             // advance 5% of whatever is allocated on the progress bar
    179             progress.setWorkRemaining(100);
    180             progress.worked(5);
    181             progress.subTask(String.format("Preload %1$s", className));
    182         }
    183     }
    184 
    185     /**
    186      * Finds and loads all classes that derive from a given set of super classes.
    187      * <p/>
    188      * As a side-effect this will load and cache most, if not all, classes in the input JAR file.
    189      *
    190      * @param packageFilter Base name of package of classes to find.
    191      *                      Use an empty string to find everyting.
    192      * @param superClasses The super classes of all the classes to find.
    193      * @return An hash map which keys are the super classes looked for and which values are
    194      *         ArrayList of the classes found. The array lists are always created for all the
    195      *         valid keys, they are simply empty if no deriving class is found for a given
    196      *         super class.
    197      * @throws IOException
    198      * @throws InvalidAttributeValueException
    199      * @throws ClassFormatError
    200      */
    201     public HashMap<String, ArrayList<IClassDescriptor>> findClassesDerivingFrom(
    202             String packageFilter,
    203             String[] superClasses)
    204             throws IOException, InvalidAttributeValueException, ClassFormatError {
    205 
    206         packageFilter = packageFilter.replaceAll("\\.", "/"); //$NON-NLS-1$ //$NON-NLS-2$
    207 
    208         HashMap<String, ArrayList<IClassDescriptor>> mClassesFound =
    209                 new HashMap<String, ArrayList<IClassDescriptor>>();
    210 
    211         for (String className : superClasses) {
    212             mClassesFound.put(className, new ArrayList<IClassDescriptor>());
    213         }
    214 
    215         // create streams to read the intermediary archive
    216         FileInputStream fis = new FileInputStream(mOsFrameworkLocation);
    217         ZipInputStream zis = new ZipInputStream(fis);
    218         ZipEntry entry;
    219         while ((entry = zis.getNextEntry()) != null) {
    220             // get the name of the entry and convert to a class binary name
    221             String entryPath = entry.getName();
    222             if (!entryPath.endsWith(AdtConstants.DOT_CLASS)) {
    223                 // only accept class files
    224                 continue;
    225             }
    226             if (packageFilter.length() > 0 && !entryPath.startsWith(packageFilter)) {
    227                 // only accept stuff from the requested root package.
    228                 continue;
    229             }
    230             String className = entryPathToClassName(entryPath);
    231 
    232             Class<?> loaded_class = mClassCache.get(className);
    233             if (loaded_class == null) {
    234                 byte[] data = mEntryCache.get(className);
    235                 if (data == null) {
    236                     // Get the class and cache it
    237                     long entrySize = entry.getSize();
    238                     if (entrySize > Integer.MAX_VALUE) {
    239                         throw new InvalidAttributeValueException();
    240                     }
    241                     data = readZipData(zis, (int)entrySize);
    242                 }
    243                 loaded_class = defineAndCacheClass(className, data);
    244             }
    245 
    246             for (Class<?> superClass = loaded_class.getSuperclass();
    247                     superClass != null;
    248                     superClass = superClass.getSuperclass()) {
    249                 String superName = superClass.getCanonicalName();
    250                 if (mClassesFound.containsKey(superName)) {
    251                     mClassesFound.get(superName).add(new ClassWrapper(loaded_class));
    252                     break;
    253                 }
    254             }
    255         }
    256 
    257         return mClassesFound;
    258     }
    259 
    260     /** Helper method that converts a Zip entry path into a corresponding
    261      *  Java full qualified binary class name.
    262      *  <p/>
    263      *  F.ex, this converts "com/my/package/Foo.class" into "com.my.package.Foo".
    264      */
    265     private String entryPathToClassName(String entryPath) {
    266         return entryPath.replaceFirst("\\.class$", "").replaceAll("[/\\\\]", "."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
    267     }
    268 
    269     /**
    270      * Finds the class with the specified binary name.
    271      *
    272      * {@inheritDoc}
    273      */
    274     @Override
    275     protected Class<?> findClass(String name) throws ClassNotFoundException {
    276         try {
    277             // try to find the class in the cache
    278             Class<?> cached_class = mClassCache.get(name);
    279             if (cached_class == ClassNotFoundException.class) {
    280                 // we already know we can't find this class, don't try again
    281                 throw new ClassNotFoundException(name);
    282             } else if (cached_class != null) {
    283                 return cached_class;
    284             }
    285 
    286             // if not found, look it up and cache it
    287             byte[] data = loadClassData(name);
    288             if (data != null) {
    289                 return defineAndCacheClass(name, data);
    290             } else {
    291                 // if the class can't be found, record a CNFE class in the map so
    292                 // that we don't try to reload it next time
    293                 mClassCache.put(name, ClassNotFoundException.class);
    294                 throw new ClassNotFoundException(name);
    295             }
    296         } catch (ClassNotFoundException e) {
    297             throw e;
    298         } catch (Exception e) {
    299             throw new ClassNotFoundException(e.getMessage());
    300         }
    301     }
    302 
    303     /**
    304      * Defines a class based on its binary data and caches the resulting class object.
    305      *
    306      * @param name The binary name of the class (i.e. package.class1$class2)
    307      * @param data The binary data from the loader.
    308      * @return The class defined
    309      * @throws ClassFormatError if defineClass failed.
    310      */
    311     private Class<?> defineAndCacheClass(String name, byte[] data) throws ClassFormatError {
    312         Class<?> cached_class;
    313         cached_class = defineClass(null, data, 0, data.length);
    314 
    315         if (cached_class != null) {
    316             // Add new class to the cache class and remove it from the zip entry data cache
    317             mClassCache.put(name, cached_class);
    318             mEntryCache.remove(name);
    319         }
    320         return cached_class;
    321     }
    322 
    323     /**
    324      * Loads a class data from its binary name.
    325      * <p/>
    326      * This uses the class binary data that has been preloaded earlier by the preLoadClasses()
    327      * method if possible.
    328      *
    329      * @param className the binary name
    330      * @return an array of bytes representing the class data or null if not found
    331      * @throws InvalidAttributeValueException
    332      * @throws IOException
    333      */
    334     private synchronized byte[] loadClassData(String className)
    335             throws InvalidAttributeValueException, IOException {
    336 
    337         byte[] data = mEntryCache.get(className);
    338         if (data != null) {
    339             return data;
    340         }
    341 
    342         // The name is a binary name. Something like "android.R", or "android.R$id".
    343         // Make a path out of it.
    344         String entryName = className.replaceAll("\\.", "/") + AdtConstants.DOT_CLASS; //$NON-NLS-1$ //$NON-NLS-2$
    345 
    346        // create streams to read the intermediary archive
    347         FileInputStream fis = new FileInputStream(mOsFrameworkLocation);
    348         ZipInputStream zis = new ZipInputStream(fis);
    349 
    350         // loop on the entries of the intermediary package and put them in the final package.
    351         ZipEntry entry;
    352 
    353         while ((entry = zis.getNextEntry()) != null) {
    354             // get the name of the entry.
    355             String currEntryName = entry.getName();
    356 
    357             if (currEntryName.equals(entryName)) {
    358                 long entrySize = entry.getSize();
    359                 if (entrySize > Integer.MAX_VALUE) {
    360                     throw new InvalidAttributeValueException();
    361                 }
    362 
    363                 data = readZipData(zis, (int)entrySize);
    364                 return data;
    365             }
    366         }
    367 
    368         return null;
    369     }
    370 
    371     /**
    372      * Reads data for the <em>current</em> entry from the zip input stream.
    373      *
    374      * @param zis The Zip input stream
    375      * @param entrySize The entry size. -1 if unknown.
    376      * @return The new data for the <em>current</em> entry.
    377      * @throws IOException If ZipInputStream.read() fails.
    378      */
    379     private byte[] readZipData(ZipInputStream zis, int entrySize) throws IOException {
    380         int block_size = 1024;
    381         int data_size = entrySize < 1 ? block_size : entrySize;
    382         int offset = 0;
    383         byte[] data = new byte[data_size];
    384 
    385         while(zis.available() != 0) {
    386             int count = zis.read(data, offset, data_size - offset);
    387             if (count < 0) {  // read data is done
    388                 break;
    389             }
    390             offset += count;
    391 
    392             if (entrySize >= 1 && offset >= entrySize) {  // we know the size and we're done
    393                 break;
    394             }
    395 
    396             // if we don't know the entry size and we're not done reading,
    397             // expand the data buffer some more.
    398             if (offset >= data_size) {
    399                 byte[] temp = new byte[data_size + block_size];
    400                 System.arraycopy(data, 0, temp, 0, data_size);
    401                 data_size += block_size;
    402                 data = temp;
    403                 block_size *= 2;
    404             }
    405         }
    406 
    407         if (offset < data_size) {
    408             // buffer was allocated too large, trim it
    409             byte[] temp = new byte[offset];
    410             if (offset > 0) {
    411                 System.arraycopy(data, 0, temp, 0, offset);
    412             }
    413             data = temp;
    414         }
    415 
    416         return data;
    417     }
    418 
    419     /**
    420      * Returns a {@link IAndroidClassLoader.IClassDescriptor} by its fully-qualified name.
    421      * @param className the fully-qualified name of the class to return.
    422      * @throws ClassNotFoundException
    423      */
    424     public IClassDescriptor getClass(String className) throws ClassNotFoundException {
    425         try {
    426             return new ClassWrapper(loadClass(className));
    427         } catch (ClassNotFoundException e) {
    428             throw e;  // useful for debugging
    429         }
    430     }
    431 }
    432