OSDN Git Service

original
[gb-231r1-is01/Gingerbread_2.3.3_r1_IS01.git] / sdk / eclipse / plugins / com.android.ide.eclipse.adt / src / com / android / ide / eclipse / adt / internal / sdk / AndroidJarLoader.java
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.AndroidConstants;
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(AndroidConstants.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(AndroidConstants.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("\\.", "/") + AndroidConstants.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 }