2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.eclipse.org/org/documents/epl-v10.php
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.
17 package com.android.ide.eclipse.adt.internal.sdk;
19 import com.android.ide.eclipse.adt.AndroidConstants;
21 import org.eclipse.core.runtime.IProgressMonitor;
22 import org.eclipse.core.runtime.SubMonitor;
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;
32 import javax.management.InvalidAttributeValueException;
35 * Custom class loader able to load a class from the SDK jar file.
37 public class AndroidJarLoader extends ClassLoader implements IAndroidClassLoader {
40 * Wrapper around a {@link Class} to provide the methods of
41 * {@link IAndroidClassLoader.IClassDescriptor}.
43 public final static class ClassWrapper implements IClassDescriptor {
44 private Class<?> mClass;
46 public ClassWrapper(Class<?> clazz) {
50 public String getFullClassName() {
51 return mClass.getCanonicalName();
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]);
64 public IClassDescriptor getEnclosingClass() {
65 return new ClassWrapper(mClass.getEnclosingClass());
68 public String getSimpleName() {
69 return mClass.getSimpleName();
72 public IClassDescriptor getSuperclass() {
73 return new ClassWrapper(mClass.getSuperclass());
77 public boolean equals(Object clazz) {
78 if (clazz instanceof ClassWrapper) {
79 return mClass.equals(((ClassWrapper)clazz).mClass);
81 return super.equals(clazz);
85 public int hashCode() {
86 return mClass.hashCode();
90 public boolean isInstantiable() {
91 int modifiers = mClass.getModifiers();
92 return Modifier.isAbstract(modifiers) == false && Modifier.isPublic(modifiers) == true;
95 public Class<?> wrappedClass() {
101 private String mOsFrameworkLocation;
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<?> >();
109 * Creates the class loader by providing the os path to the framework jar archive
111 * @param osFrameworkLocation OS Path of the framework JAR file
113 public AndroidJarLoader(String osFrameworkLocation) {
115 mOsFrameworkLocation = osFrameworkLocation;
118 public String getSource() {
119 return mOsFrameworkLocation;
123 * Pre-loads all class binary data that belong to the given package by reading the archive
124 * once and caching them internally.
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.
129 * All classes which package name starts with "packageFilter" will be included and can be
132 * May throw some exceptions if the framework JAR cannot be read.
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
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$
148 SubMonitor progress = SubMonitor.convert(monitor, taskLabel == null ? "" : taskLabel, 100);
150 // create streams to read the intermediary archive
151 FileInputStream fis = new FileInputStream(mOsFrameworkLocation);
152 ZipInputStream zis = new ZipInputStream(fis);
154 while ((entry = zis.getNextEntry()) != null) {
155 // get the name of the entry.
156 String entryPath = entry.getName();
158 if (!entryPath.endsWith(AndroidConstants.DOT_CLASS)) {
159 // only accept class files
163 // check if it is part of the package to preload
164 if (pathFilter.length() > 0 && !entryPath.startsWith(pathFilter)) {
167 String className = entryPathToClassName(entryPath);
169 if (!mEntryCache.containsKey(className)) {
170 long entrySize = entry.getSize();
171 if (entrySize > Integer.MAX_VALUE) {
172 throw new InvalidAttributeValueException();
174 byte[] data = readZipData(zis, (int)entrySize);
175 mEntryCache.put(className, data);
178 // advance 5% of whatever is allocated on the progress bar
179 progress.setWorkRemaining(100);
181 progress.subTask(String.format("Preload %1$s", className));
186 * Finds and loads all classes that derive from a given set of super classes.
188 * As a side-effect this will load and cache most, if not all, classes in the input JAR file.
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
197 * @throws IOException
198 * @throws InvalidAttributeValueException
199 * @throws ClassFormatError
201 public HashMap<String, ArrayList<IClassDescriptor>> findClassesDerivingFrom(
202 String packageFilter,
203 String[] superClasses)
204 throws IOException, InvalidAttributeValueException, ClassFormatError {
206 packageFilter = packageFilter.replaceAll("\\.", "/"); //$NON-NLS-1$ //$NON-NLS-2$
208 HashMap<String, ArrayList<IClassDescriptor>> mClassesFound =
209 new HashMap<String, ArrayList<IClassDescriptor>>();
211 for (String className : superClasses) {
212 mClassesFound.put(className, new ArrayList<IClassDescriptor>());
215 // create streams to read the intermediary archive
216 FileInputStream fis = new FileInputStream(mOsFrameworkLocation);
217 ZipInputStream zis = new ZipInputStream(fis);
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
226 if (packageFilter.length() > 0 && !entryPath.startsWith(packageFilter)) {
227 // only accept stuff from the requested root package.
230 String className = entryPathToClassName(entryPath);
232 Class<?> loaded_class = mClassCache.get(className);
233 if (loaded_class == null) {
234 byte[] data = mEntryCache.get(className);
236 // Get the class and cache it
237 long entrySize = entry.getSize();
238 if (entrySize > Integer.MAX_VALUE) {
239 throw new InvalidAttributeValueException();
241 data = readZipData(zis, (int)entrySize);
243 loaded_class = defineAndCacheClass(className, data);
246 for (Class<?> superClass = loaded_class.getSuperclass();
248 superClass = superClass.getSuperclass()) {
249 String superName = superClass.getCanonicalName();
250 if (mClassesFound.containsKey(superName)) {
251 mClassesFound.get(superName).add(new ClassWrapper(loaded_class));
257 return mClassesFound;
260 /** Helper method that converts a Zip entry path into a corresponding
261 * Java full qualified binary class name.
263 * F.ex, this converts "com/my/package/Foo.class" into "com.my.package.Foo".
265 private String entryPathToClassName(String entryPath) {
266 return entryPath.replaceFirst("\\.class$", "").replaceAll("[/\\\\]", "."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
270 * Finds the class with the specified binary name.
275 protected Class<?> findClass(String name) throws ClassNotFoundException {
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) {
286 // if not found, look it up and cache it
287 byte[] data = loadClassData(name);
289 return defineAndCacheClass(name, data);
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);
296 } catch (ClassNotFoundException e) {
298 } catch (Exception e) {
299 throw new ClassNotFoundException(e.getMessage());
304 * Defines a class based on its binary data and caches the resulting class object.
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.
311 private Class<?> defineAndCacheClass(String name, byte[] data) throws ClassFormatError {
312 Class<?> cached_class;
313 cached_class = defineClass(null, data, 0, data.length);
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);
324 * Loads a class data from its binary name.
326 * This uses the class binary data that has been preloaded earlier by the preLoadClasses()
327 * method if possible.
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
334 private synchronized byte[] loadClassData(String className)
335 throws InvalidAttributeValueException, IOException {
337 byte[] data = mEntryCache.get(className);
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$
346 // create streams to read the intermediary archive
347 FileInputStream fis = new FileInputStream(mOsFrameworkLocation);
348 ZipInputStream zis = new ZipInputStream(fis);
350 // loop on the entries of the intermediary package and put them in the final package.
353 while ((entry = zis.getNextEntry()) != null) {
354 // get the name of the entry.
355 String currEntryName = entry.getName();
357 if (currEntryName.equals(entryName)) {
358 long entrySize = entry.getSize();
359 if (entrySize > Integer.MAX_VALUE) {
360 throw new InvalidAttributeValueException();
363 data = readZipData(zis, (int)entrySize);
372 * Reads data for the <em>current</em> entry from the zip input stream.
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.
379 private byte[] readZipData(ZipInputStream zis, int entrySize) throws IOException {
380 int block_size = 1024;
381 int data_size = entrySize < 1 ? block_size : entrySize;
383 byte[] data = new byte[data_size];
385 while(zis.available() != 0) {
386 int count = zis.read(data, offset, data_size - offset);
387 if (count < 0) { // read data is done
392 if (entrySize >= 1 && offset >= entrySize) { // we know the size and we're done
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;
407 if (offset < data_size) {
408 // buffer was allocated too large, trim it
409 byte[] temp = new byte[offset];
411 System.arraycopy(data, 0, temp, 0, offset);
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
424 public IClassDescriptor getClass(String className) throws ClassNotFoundException {
426 return new ClassWrapper(loadClass(className));
427 } catch (ClassNotFoundException e) {
428 throw e; // useful for debugging