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