aboutsummaryrefslogtreecommitdiff
path: root/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java
diff options
context:
space:
mode:
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java')
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java458
1 files changed, 458 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java
new file mode 100644
index 000000000..754cedf79
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.sdk;
+
+import com.android.SdkConstants;
+import com.google.common.io.Closeables;
+
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.SubMonitor;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import javax.management.InvalidAttributeValueException;
+
+/**
+ * Custom class loader able to load a class from the SDK jar file.
+ */
+public class AndroidJarLoader extends ClassLoader implements IAndroidClassLoader {
+
+ /**
+ * Wrapper around a {@link Class} to provide the methods of
+ * {@link IAndroidClassLoader.IClassDescriptor}.
+ */
+ public final static class ClassWrapper implements IClassDescriptor {
+ private Class<?> mClass;
+
+ public ClassWrapper(Class<?> clazz) {
+ mClass = clazz;
+ }
+
+ @Override
+ public String getFullClassName() {
+ return mClass.getCanonicalName();
+ }
+
+ @Override
+ public IClassDescriptor[] getDeclaredClasses() {
+ Class<?>[] classes = mClass.getDeclaredClasses();
+ IClassDescriptor[] iclasses = new IClassDescriptor[classes.length];
+ for (int i = 0 ; i < classes.length ; i++) {
+ iclasses[i] = new ClassWrapper(classes[i]);
+ }
+
+ return iclasses;
+ }
+
+ @Override
+ public IClassDescriptor getEnclosingClass() {
+ return new ClassWrapper(mClass.getEnclosingClass());
+ }
+
+ @Override
+ public String getSimpleName() {
+ return mClass.getSimpleName();
+ }
+
+ @Override
+ public IClassDescriptor getSuperclass() {
+ return new ClassWrapper(mClass.getSuperclass());
+ }
+
+ @Override
+ public boolean equals(Object clazz) {
+ if (clazz instanceof ClassWrapper) {
+ return mClass.equals(((ClassWrapper)clazz).mClass);
+ }
+ return super.equals(clazz);
+ }
+
+ @Override
+ public int hashCode() {
+ return mClass.hashCode();
+ }
+
+
+ @Override
+ public boolean isInstantiable() {
+ int modifiers = mClass.getModifiers();
+ return Modifier.isAbstract(modifiers) == false && Modifier.isPublic(modifiers) == true;
+ }
+
+ public Class<?> wrappedClass() {
+ return mClass;
+ }
+
+ }
+
+ private String mOsFrameworkLocation;
+
+ /** A cache for binary data extracted from the zip */
+ private final HashMap<String, byte[]> mEntryCache = new HashMap<String, byte[]>();
+ /** A cache for already defined Classes */
+ private final HashMap<String, Class<?> > mClassCache = new HashMap<String, Class<?> >();
+
+ /**
+ * Creates the class loader by providing the os path to the framework jar archive
+ *
+ * @param osFrameworkLocation OS Path of the framework JAR file
+ */
+ public AndroidJarLoader(String osFrameworkLocation) {
+ super();
+ mOsFrameworkLocation = osFrameworkLocation;
+ }
+
+ @Override
+ public String getSource() {
+ return mOsFrameworkLocation;
+ }
+
+ /**
+ * Pre-loads all class binary data that belong to the given package by reading the archive
+ * once and caching them internally.
+ * <p/>
+ * This does not actually preload "classes", it just reads the unzipped bytes for a given
+ * class. To obtain a class, one must call {@link #findClass(String)} later.
+ * <p/>
+ * All classes which package name starts with "packageFilter" will be included and can be
+ * found later.
+ * <p/>
+ * May throw some exceptions if the framework JAR cannot be read.
+ *
+ * @param packageFilter The package that contains all the class data to preload, using a fully
+ * qualified binary name (.e.g "com.my.package."). The matching algorithm
+ * is simple "startsWith". Use an empty string to include everything.
+ * @param taskLabel An optional task name for the sub monitor. Can be null.
+ * @param monitor A progress monitor. Can be null. Caller is responsible for calling done.
+ * @throws IOException
+ * @throws InvalidAttributeValueException
+ * @throws ClassFormatError
+ */
+ public void preLoadClasses(String packageFilter, String taskLabel, IProgressMonitor monitor)
+ throws IOException, InvalidAttributeValueException, ClassFormatError {
+ // Transform the package name into a zip entry path
+ String pathFilter = packageFilter.replaceAll("\\.", "/"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ SubMonitor progress = SubMonitor.convert(monitor, taskLabel == null ? "" : taskLabel, 100);
+
+ // create streams to read the intermediary archive
+ FileInputStream fis = new FileInputStream(mOsFrameworkLocation);
+ ZipInputStream zis = new ZipInputStream(fis);
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null) {
+ // get the name of the entry.
+ String entryPath = entry.getName();
+
+ if (!entryPath.endsWith(SdkConstants.DOT_CLASS)) {
+ // only accept class files
+ continue;
+ }
+
+ // check if it is part of the package to preload
+ if (pathFilter.length() > 0 && !entryPath.startsWith(pathFilter)) {
+ continue;
+ }
+ String className = entryPathToClassName(entryPath);
+
+ if (!mEntryCache.containsKey(className)) {
+ long entrySize = entry.getSize();
+ if (entrySize > Integer.MAX_VALUE) {
+ throw new InvalidAttributeValueException();
+ }
+ byte[] data = readZipData(zis, (int)entrySize);
+ mEntryCache.put(className, data);
+ }
+
+ // advance 5% of whatever is allocated on the progress bar
+ progress.setWorkRemaining(100);
+ progress.worked(5);
+ progress.subTask(String.format("Preload %1$s", className));
+ }
+ }
+
+ /**
+ * Finds and loads all classes that derive from a given set of super classes.
+ * <p/>
+ * As a side-effect this will load and cache most, if not all, classes in the input JAR file.
+ *
+ * @param packageFilter Base name of package of classes to find.
+ * Use an empty string to find everyting.
+ * @param superClasses The super classes of all the classes to find.
+ * @return An hash map which keys are the super classes looked for and which values are
+ * ArrayList of the classes found. The array lists are always created for all the
+ * valid keys, they are simply empty if no deriving class is found for a given
+ * super class.
+ * @throws IOException
+ * @throws InvalidAttributeValueException
+ * @throws ClassFormatError
+ */
+ @SuppressWarnings("resource") // Eclipse doesn't understand Closeables.closeQuietly
+ @Override
+ public HashMap<String, ArrayList<IClassDescriptor>> findClassesDerivingFrom(
+ String packageFilter,
+ String[] superClasses)
+ throws IOException, InvalidAttributeValueException, ClassFormatError {
+
+ packageFilter = packageFilter.replaceAll("\\.", "/"); //$NON-NLS-1$ //$NON-NLS-2$
+
+ HashMap<String, ArrayList<IClassDescriptor>> mClassesFound =
+ new HashMap<String, ArrayList<IClassDescriptor>>();
+
+ for (String className : superClasses) {
+ mClassesFound.put(className, new ArrayList<IClassDescriptor>());
+ }
+
+ // create streams to read the intermediary archive
+ FileInputStream fis = new FileInputStream(mOsFrameworkLocation);
+ ZipInputStream zis = new ZipInputStream(fis);
+ try {
+ ZipEntry entry;
+ while ((entry = zis.getNextEntry()) != null) {
+ // get the name of the entry and convert to a class binary name
+ String entryPath = entry.getName();
+ if (!entryPath.endsWith(SdkConstants.DOT_CLASS)) {
+ // only accept class files
+ continue;
+ }
+ if (packageFilter.length() > 0 && !entryPath.startsWith(packageFilter)) {
+ // only accept stuff from the requested root package.
+ continue;
+ }
+ String className = entryPathToClassName(entryPath);
+
+ Class<?> loaded_class = mClassCache.get(className);
+ if (loaded_class == null) {
+ byte[] data = mEntryCache.get(className);
+ if (data == null) {
+ // Get the class and cache it
+ long entrySize = entry.getSize();
+ if (entrySize > Integer.MAX_VALUE) {
+ throw new InvalidAttributeValueException();
+ }
+ data = readZipData(zis, (int)entrySize);
+ }
+ try {
+ loaded_class = defineAndCacheClass(className, data);
+ } catch (NoClassDefFoundError error) {
+ if (error.getMessage().startsWith("java/")) {
+ // Can't define these; we just need to stop
+ // iteration here
+ continue;
+ }
+ throw error;
+ }
+ }
+
+ for (Class<?> superClass = loaded_class.getSuperclass();
+ superClass != null;
+ superClass = superClass.getSuperclass()) {
+ String superName = superClass.getCanonicalName();
+ if (mClassesFound.containsKey(superName)) {
+ mClassesFound.get(superName).add(new ClassWrapper(loaded_class));
+ break;
+ }
+ }
+ }
+ } finally {
+ Closeables.closeQuietly(zis);
+ }
+
+ return mClassesFound;
+ }
+
+ /** Helper method that converts a Zip entry path into a corresponding
+ * Java full qualified binary class name.
+ * <p/>
+ * F.ex, this converts "com/my/package/Foo.class" into "com.my.package.Foo".
+ */
+ private String entryPathToClassName(String entryPath) {
+ return entryPath.replaceFirst("\\.class$", "").replaceAll("[/\\\\]", "."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ /**
+ * Finds the class with the specified binary name.
+ *
+ * {@inheritDoc}
+ */
+ @Override
+ protected Class<?> findClass(String name) throws ClassNotFoundException {
+ try {
+ // try to find the class in the cache
+ Class<?> cached_class = mClassCache.get(name);
+ if (cached_class == ClassNotFoundException.class) {
+ // we already know we can't find this class, don't try again
+ throw new ClassNotFoundException(name);
+ } else if (cached_class != null) {
+ return cached_class;
+ }
+
+ // if not found, look it up and cache it
+ byte[] data = loadClassData(name);
+ if (data != null) {
+ return defineAndCacheClass(name, data);
+ } else {
+ // if the class can't be found, record a CNFE class in the map so
+ // that we don't try to reload it next time
+ mClassCache.put(name, ClassNotFoundException.class);
+ throw new ClassNotFoundException(name);
+ }
+ } catch (ClassNotFoundException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new ClassNotFoundException(e.getMessage());
+ }
+ }
+
+ /**
+ * Defines a class based on its binary data and caches the resulting class object.
+ *
+ * @param name The binary name of the class (i.e. package.class1$class2)
+ * @param data The binary data from the loader.
+ * @return The class defined
+ * @throws ClassFormatError if defineClass failed.
+ */
+ private Class<?> defineAndCacheClass(String name, byte[] data) throws ClassFormatError {
+ Class<?> cached_class;
+ cached_class = defineClass(null, data, 0, data.length);
+
+ if (cached_class != null) {
+ // Add new class to the cache class and remove it from the zip entry data cache
+ mClassCache.put(name, cached_class);
+ mEntryCache.remove(name);
+ }
+ return cached_class;
+ }
+
+ /**
+ * Loads a class data from its binary name.
+ * <p/>
+ * This uses the class binary data that has been preloaded earlier by the preLoadClasses()
+ * method if possible.
+ *
+ * @param className the binary name
+ * @return an array of bytes representing the class data or null if not found
+ * @throws InvalidAttributeValueException
+ * @throws IOException
+ */
+ private synchronized byte[] loadClassData(String className)
+ throws InvalidAttributeValueException, IOException {
+
+ byte[] data = mEntryCache.get(className);
+ if (data != null) {
+ return data;
+ }
+
+ // The name is a binary name. Something like "android.R", or "android.R$id".
+ // Make a path out of it.
+ String entryName = className.replaceAll("\\.", "/") + SdkConstants.DOT_CLASS; //$NON-NLS-1$ //$NON-NLS-2$
+
+ // create streams to read the intermediary archive
+ FileInputStream fis = new FileInputStream(mOsFrameworkLocation);
+ ZipInputStream zis = new ZipInputStream(fis);
+ try {
+ // loop on the entries of the intermediary package and put them in the final package.
+ ZipEntry entry;
+
+ while ((entry = zis.getNextEntry()) != null) {
+ // get the name of the entry.
+ String currEntryName = entry.getName();
+
+ if (currEntryName.equals(entryName)) {
+ long entrySize = entry.getSize();
+ if (entrySize > Integer.MAX_VALUE) {
+ throw new InvalidAttributeValueException();
+ }
+
+ data = readZipData(zis, (int)entrySize);
+ return data;
+ }
+ }
+
+ return null;
+ } finally {
+ zis.close();
+ }
+ }
+
+ /**
+ * Reads data for the <em>current</em> entry from the zip input stream.
+ *
+ * @param zis The Zip input stream
+ * @param entrySize The entry size. -1 if unknown.
+ * @return The new data for the <em>current</em> entry.
+ * @throws IOException If ZipInputStream.read() fails.
+ */
+ private byte[] readZipData(ZipInputStream zis, int entrySize) throws IOException {
+ int block_size = 1024;
+ int data_size = entrySize < 1 ? block_size : entrySize;
+ int offset = 0;
+ byte[] data = new byte[data_size];
+
+ while(zis.available() != 0) {
+ int count = zis.read(data, offset, data_size - offset);
+ if (count < 0) { // read data is done
+ break;
+ }
+ offset += count;
+
+ if (entrySize >= 1 && offset >= entrySize) { // we know the size and we're done
+ break;
+ }
+
+ // if we don't know the entry size and we're not done reading,
+ // expand the data buffer some more.
+ if (offset >= data_size) {
+ byte[] temp = new byte[data_size + block_size];
+ System.arraycopy(data, 0, temp, 0, data_size);
+ data_size += block_size;
+ data = temp;
+ block_size *= 2;
+ }
+ }
+
+ if (offset < data_size) {
+ // buffer was allocated too large, trim it
+ byte[] temp = new byte[offset];
+ if (offset > 0) {
+ System.arraycopy(data, 0, temp, 0, offset);
+ }
+ data = temp;
+ }
+
+ return data;
+ }
+
+ /**
+ * Returns a {@link IAndroidClassLoader.IClassDescriptor} by its fully-qualified name.
+ * @param className the fully-qualified name of the class to return.
+ * @throws ClassNotFoundException
+ */
+ @Override
+ public IClassDescriptor getClass(String className) throws ClassNotFoundException {
+ try {
+ return new ClassWrapper(loadClass(className));
+ } catch (ClassNotFoundException e) {
+ throw e; // useful for debugging
+ }
+ }
+}