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