diff options
author | Bob Badour <bbadour@google.com> | 2020-05-06 14:54:04 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-05-06 14:54:04 +0000 |
commit | d58f8ba3b1869530926bd5f167103dfa161787a1 (patch) | |
tree | fd845444b59dfc72656b7781596e0b1a0662c4c7 /eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/sdk/AndroidJarLoader.java | |
parent | e36f051d414b814cf80b289770c1f7e58f6655ea (diff) | |
parent | d3c69fa48e25645a343d97ac392300583e38b30a (diff) | |
download | sdk-d58f8ba3b1869530926bd5f167103dfa161787a1.tar.gz |
Merge "Revert "Remove unused project."" am: fc7cda06f5 am: d3c69fa48e
Change-Id: I2920d1fca1593e86805ec8a728121cf2513e2bc5
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.java | 458 |
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 + } + } +} |