• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ClassWrapper(Class<?> clazz)47         public ClassWrapper(Class<?> clazz) {
48             mClass = clazz;
49         }
50 
51         @Override
getFullClassName()52         public String getFullClassName() {
53             return mClass.getCanonicalName();
54         }
55 
56         @Override
getDeclaredClasses()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
getEnclosingClass()68         public IClassDescriptor getEnclosingClass() {
69             return new ClassWrapper(mClass.getEnclosingClass());
70         }
71 
72         @Override
getSimpleName()73         public String getSimpleName() {
74             return mClass.getSimpleName();
75         }
76 
77         @Override
getSuperclass()78         public IClassDescriptor getSuperclass() {
79             return new ClassWrapper(mClass.getSuperclass());
80         }
81 
82         @Override
equals(Object clazz)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
hashCode()91         public int hashCode() {
92             return mClass.hashCode();
93         }
94 
95 
96         @Override
isInstantiable()97         public boolean isInstantiable() {
98             int modifiers = mClass.getModifiers();
99             return Modifier.isAbstract(modifiers) == false && Modifier.isPublic(modifiers) == true;
100         }
101 
wrappedClass()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      */
AndroidJarLoader(String osFrameworkLocation)120     public AndroidJarLoader(String osFrameworkLocation) {
121         super();
122         mOsFrameworkLocation = osFrameworkLocation;
123     }
124 
125     @Override
getSource()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      */
preLoadClasses(String packageFilter, String taskLabel, IProgressMonitor monitor)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
findClassesDerivingFrom( String packageFilter, String[] superClasses)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                     try {
255                         loaded_class = defineAndCacheClass(className, data);
256                     } catch (NoClassDefFoundError error) {
257                         if (error.getMessage().startsWith("java/")) {
258                             // Can't define these; we just need to stop
259                             // iteration here
260                             continue;
261                         }
262                         throw error;
263                     }
264                 }
265 
266                 for (Class<?> superClass = loaded_class.getSuperclass();
267                         superClass != null;
268                         superClass = superClass.getSuperclass()) {
269                     String superName = superClass.getCanonicalName();
270                     if (mClassesFound.containsKey(superName)) {
271                         mClassesFound.get(superName).add(new ClassWrapper(loaded_class));
272                         break;
273                     }
274                 }
275             }
276         } finally {
277             Closeables.closeQuietly(zis);
278         }
279 
280         return mClassesFound;
281     }
282 
283     /** Helper method that converts a Zip entry path into a corresponding
284      *  Java full qualified binary class name.
285      *  <p/>
286      *  F.ex, this converts "com/my/package/Foo.class" into "com.my.package.Foo".
287      */
entryPathToClassName(String entryPath)288     private String entryPathToClassName(String entryPath) {
289         return entryPath.replaceFirst("\\.class$", "").replaceAll("[/\\\\]", "."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
290     }
291 
292     /**
293      * Finds the class with the specified binary name.
294      *
295      * {@inheritDoc}
296      */
297     @Override
findClass(String name)298     protected Class<?> findClass(String name) throws ClassNotFoundException {
299         try {
300             // try to find the class in the cache
301             Class<?> cached_class = mClassCache.get(name);
302             if (cached_class == ClassNotFoundException.class) {
303                 // we already know we can't find this class, don't try again
304                 throw new ClassNotFoundException(name);
305             } else if (cached_class != null) {
306                 return cached_class;
307             }
308 
309             // if not found, look it up and cache it
310             byte[] data = loadClassData(name);
311             if (data != null) {
312                 return defineAndCacheClass(name, data);
313             } else {
314                 // if the class can't be found, record a CNFE class in the map so
315                 // that we don't try to reload it next time
316                 mClassCache.put(name, ClassNotFoundException.class);
317                 throw new ClassNotFoundException(name);
318             }
319         } catch (ClassNotFoundException e) {
320             throw e;
321         } catch (Exception e) {
322             throw new ClassNotFoundException(e.getMessage());
323         }
324     }
325 
326     /**
327      * Defines a class based on its binary data and caches the resulting class object.
328      *
329      * @param name The binary name of the class (i.e. package.class1$class2)
330      * @param data The binary data from the loader.
331      * @return The class defined
332      * @throws ClassFormatError if defineClass failed.
333      */
defineAndCacheClass(String name, byte[] data)334     private Class<?> defineAndCacheClass(String name, byte[] data) throws ClassFormatError {
335         Class<?> cached_class;
336         cached_class = defineClass(null, data, 0, data.length);
337 
338         if (cached_class != null) {
339             // Add new class to the cache class and remove it from the zip entry data cache
340             mClassCache.put(name, cached_class);
341             mEntryCache.remove(name);
342         }
343         return cached_class;
344     }
345 
346     /**
347      * Loads a class data from its binary name.
348      * <p/>
349      * This uses the class binary data that has been preloaded earlier by the preLoadClasses()
350      * method if possible.
351      *
352      * @param className the binary name
353      * @return an array of bytes representing the class data or null if not found
354      * @throws InvalidAttributeValueException
355      * @throws IOException
356      */
loadClassData(String className)357     private synchronized byte[] loadClassData(String className)
358             throws InvalidAttributeValueException, IOException {
359 
360         byte[] data = mEntryCache.get(className);
361         if (data != null) {
362             return data;
363         }
364 
365         // The name is a binary name. Something like "android.R", or "android.R$id".
366         // Make a path out of it.
367         String entryName = className.replaceAll("\\.", "/") + SdkConstants.DOT_CLASS; //$NON-NLS-1$ //$NON-NLS-2$
368 
369        // create streams to read the intermediary archive
370         FileInputStream fis = new FileInputStream(mOsFrameworkLocation);
371         ZipInputStream zis = new ZipInputStream(fis);
372         try {
373             // loop on the entries of the intermediary package and put them in the final package.
374             ZipEntry entry;
375 
376             while ((entry = zis.getNextEntry()) != null) {
377                 // get the name of the entry.
378                 String currEntryName = entry.getName();
379 
380                 if (currEntryName.equals(entryName)) {
381                     long entrySize = entry.getSize();
382                     if (entrySize > Integer.MAX_VALUE) {
383                         throw new InvalidAttributeValueException();
384                     }
385 
386                     data = readZipData(zis, (int)entrySize);
387                     return data;
388                 }
389             }
390 
391             return null;
392         } finally {
393             zis.close();
394         }
395     }
396 
397     /**
398      * Reads data for the <em>current</em> entry from the zip input stream.
399      *
400      * @param zis The Zip input stream
401      * @param entrySize The entry size. -1 if unknown.
402      * @return The new data for the <em>current</em> entry.
403      * @throws IOException If ZipInputStream.read() fails.
404      */
readZipData(ZipInputStream zis, int entrySize)405     private byte[] readZipData(ZipInputStream zis, int entrySize) throws IOException {
406         int block_size = 1024;
407         int data_size = entrySize < 1 ? block_size : entrySize;
408         int offset = 0;
409         byte[] data = new byte[data_size];
410 
411         while(zis.available() != 0) {
412             int count = zis.read(data, offset, data_size - offset);
413             if (count < 0) {  // read data is done
414                 break;
415             }
416             offset += count;
417 
418             if (entrySize >= 1 && offset >= entrySize) {  // we know the size and we're done
419                 break;
420             }
421 
422             // if we don't know the entry size and we're not done reading,
423             // expand the data buffer some more.
424             if (offset >= data_size) {
425                 byte[] temp = new byte[data_size + block_size];
426                 System.arraycopy(data, 0, temp, 0, data_size);
427                 data_size += block_size;
428                 data = temp;
429                 block_size *= 2;
430             }
431         }
432 
433         if (offset < data_size) {
434             // buffer was allocated too large, trim it
435             byte[] temp = new byte[offset];
436             if (offset > 0) {
437                 System.arraycopy(data, 0, temp, 0, offset);
438             }
439             data = temp;
440         }
441 
442         return data;
443     }
444 
445     /**
446      * Returns a {@link IAndroidClassLoader.IClassDescriptor} by its fully-qualified name.
447      * @param className the fully-qualified name of the class to return.
448      * @throws ClassNotFoundException
449      */
450     @Override
getClass(String className)451     public IClassDescriptor getClass(String className) throws ClassNotFoundException {
452         try {
453             return new ClassWrapper(loadClass(className));
454         } catch (ClassNotFoundException e) {
455             throw e;  // useful for debugging
456         }
457     }
458 }
459