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.AdtConstants; 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(AdtConstants.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(AdtConstants.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("\\.", "/") + AdtConstants.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 } 432