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.SdkConstants; 20 import com.google.common.io.Closeables; 21 22 import org.eclipse.core.runtime.IProgressMonitor; 23 import org.eclipse.core.runtime.SubMonitor; 24 25 import java.io.FileInputStream; 26 import java.io.IOException; 27 import java.lang.reflect.Modifier; 28 import java.util.ArrayList; 29 import java.util.HashMap; 30 import java.util.zip.ZipEntry; 31 import java.util.zip.ZipInputStream; 32 33 import javax.management.InvalidAttributeValueException; 34 35 /** 36 * Custom class loader able to load a class from the SDK jar file. 37 */ 38 public class AndroidJarLoader extends ClassLoader implements IAndroidClassLoader { 39 40 /** 41 * Wrapper around a {@link Class} to provide the methods of 42 * {@link IAndroidClassLoader.IClassDescriptor}. 43 */ 44 public final static class ClassWrapper implements IClassDescriptor { 45 private Class<?> mClass; 46 47 public ClassWrapper(Class<?> clazz) { 48 mClass = clazz; 49 } 50 51 @Override 52 public String getFullClassName() { 53 return mClass.getCanonicalName(); 54 } 55 56 @Override 57 public IClassDescriptor[] getDeclaredClasses() { 58 Class<?>[] classes = mClass.getDeclaredClasses(); 59 IClassDescriptor[] iclasses = new IClassDescriptor[classes.length]; 60 for (int i = 0 ; i < classes.length ; i++) { 61 iclasses[i] = new ClassWrapper(classes[i]); 62 } 63 64 return iclasses; 65 } 66 67 @Override 68 public IClassDescriptor getEnclosingClass() { 69 return new ClassWrapper(mClass.getEnclosingClass()); 70 } 71 72 @Override 73 public String getSimpleName() { 74 return mClass.getSimpleName(); 75 } 76 77 @Override 78 public IClassDescriptor getSuperclass() { 79 return new ClassWrapper(mClass.getSuperclass()); 80 } 81 82 @Override 83 public boolean equals(Object clazz) { 84 if (clazz instanceof ClassWrapper) { 85 return mClass.equals(((ClassWrapper)clazz).mClass); 86 } 87 return super.equals(clazz); 88 } 89 90 @Override 91 public int hashCode() { 92 return mClass.hashCode(); 93 } 94 95 96 @Override 97 public boolean isInstantiable() { 98 int modifiers = mClass.getModifiers(); 99 return Modifier.isAbstract(modifiers) == false && Modifier.isPublic(modifiers) == true; 100 } 101 102 public Class<?> wrappedClass() { 103 return mClass; 104 } 105 106 } 107 108 private String mOsFrameworkLocation; 109 110 /** A cache for binary data extracted from the zip */ 111 private final HashMap<String, byte[]> mEntryCache = new HashMap<String, byte[]>(); 112 /** A cache for already defined Classes */ 113 private final HashMap<String, Class<?> > mClassCache = new HashMap<String, Class<?> >(); 114 115 /** 116 * Creates the class loader by providing the os path to the framework jar archive 117 * 118 * @param osFrameworkLocation OS Path of the framework JAR file 119 */ 120 public AndroidJarLoader(String osFrameworkLocation) { 121 super(); 122 mOsFrameworkLocation = osFrameworkLocation; 123 } 124 125 @Override 126 public String getSource() { 127 return mOsFrameworkLocation; 128 } 129 130 /** 131 * Pre-loads all class binary data that belong to the given package by reading the archive 132 * once and caching them internally. 133 * <p/> 134 * This does not actually preload "classes", it just reads the unzipped bytes for a given 135 * class. To obtain a class, one must call {@link #findClass(String)} later. 136 * <p/> 137 * All classes which package name starts with "packageFilter" will be included and can be 138 * found later. 139 * <p/> 140 * May throw some exceptions if the framework JAR cannot be read. 141 * 142 * @param packageFilter The package that contains all the class data to preload, using a fully 143 * qualified binary name (.e.g "com.my.package."). The matching algorithm 144 * is simple "startsWith". Use an empty string to include everything. 145 * @param taskLabel An optional task name for the sub monitor. Can be null. 146 * @param monitor A progress monitor. Can be null. Caller is responsible for calling done. 147 * @throws IOException 148 * @throws InvalidAttributeValueException 149 * @throws ClassFormatError 150 */ 151 public void preLoadClasses(String packageFilter, String taskLabel, IProgressMonitor monitor) 152 throws IOException, InvalidAttributeValueException, ClassFormatError { 153 // Transform the package name into a zip entry path 154 String pathFilter = packageFilter.replaceAll("\\.", "/"); //$NON-NLS-1$ //$NON-NLS-2$ 155 156 SubMonitor progress = SubMonitor.convert(monitor, taskLabel == null ? "" : taskLabel, 100); 157 158 // create streams to read the intermediary archive 159 FileInputStream fis = new FileInputStream(mOsFrameworkLocation); 160 ZipInputStream zis = new ZipInputStream(fis); 161 ZipEntry entry; 162 while ((entry = zis.getNextEntry()) != null) { 163 // get the name of the entry. 164 String entryPath = entry.getName(); 165 166 if (!entryPath.endsWith(SdkConstants.DOT_CLASS)) { 167 // only accept class files 168 continue; 169 } 170 171 // check if it is part of the package to preload 172 if (pathFilter.length() > 0 && !entryPath.startsWith(pathFilter)) { 173 continue; 174 } 175 String className = entryPathToClassName(entryPath); 176 177 if (!mEntryCache.containsKey(className)) { 178 long entrySize = entry.getSize(); 179 if (entrySize > Integer.MAX_VALUE) { 180 throw new InvalidAttributeValueException(); 181 } 182 byte[] data = readZipData(zis, (int)entrySize); 183 mEntryCache.put(className, data); 184 } 185 186 // advance 5% of whatever is allocated on the progress bar 187 progress.setWorkRemaining(100); 188 progress.worked(5); 189 progress.subTask(String.format("Preload %1$s", className)); 190 } 191 } 192 193 /** 194 * Finds and loads all classes that derive from a given set of super classes. 195 * <p/> 196 * As a side-effect this will load and cache most, if not all, classes in the input JAR file. 197 * 198 * @param packageFilter Base name of package of classes to find. 199 * Use an empty string to find everyting. 200 * @param superClasses The super classes of all the classes to find. 201 * @return An hash map which keys are the super classes looked for and which values are 202 * ArrayList of the classes found. The array lists are always created for all the 203 * valid keys, they are simply empty if no deriving class is found for a given 204 * super class. 205 * @throws IOException 206 * @throws InvalidAttributeValueException 207 * @throws ClassFormatError 208 */ 209 @SuppressWarnings("resource") // Eclipse doesn't understand Closeables.closeQuietly 210 @Override 211 public HashMap<String, ArrayList<IClassDescriptor>> findClassesDerivingFrom( 212 String packageFilter, 213 String[] superClasses) 214 throws IOException, InvalidAttributeValueException, ClassFormatError { 215 216 packageFilter = packageFilter.replaceAll("\\.", "/"); //$NON-NLS-1$ //$NON-NLS-2$ 217 218 HashMap<String, ArrayList<IClassDescriptor>> mClassesFound = 219 new HashMap<String, ArrayList<IClassDescriptor>>(); 220 221 for (String className : superClasses) { 222 mClassesFound.put(className, new ArrayList<IClassDescriptor>()); 223 } 224 225 // create streams to read the intermediary archive 226 FileInputStream fis = new FileInputStream(mOsFrameworkLocation); 227 ZipInputStream zis = new ZipInputStream(fis); 228 try { 229 ZipEntry entry; 230 while ((entry = zis.getNextEntry()) != null) { 231 // get the name of the entry and convert to a class binary name 232 String entryPath = entry.getName(); 233 if (!entryPath.endsWith(SdkConstants.DOT_CLASS)) { 234 // only accept class files 235 continue; 236 } 237 if (packageFilter.length() > 0 && !entryPath.startsWith(packageFilter)) { 238 // only accept stuff from the requested root package. 239 continue; 240 } 241 String className = entryPathToClassName(entryPath); 242 243 Class<?> loaded_class = mClassCache.get(className); 244 if (loaded_class == null) { 245 byte[] data = mEntryCache.get(className); 246 if (data == null) { 247 // Get the class and cache it 248 long entrySize = entry.getSize(); 249 if (entrySize > Integer.MAX_VALUE) { 250 throw new InvalidAttributeValueException(); 251 } 252 data = readZipData(zis, (int)entrySize); 253 } 254 try { 255 loaded_class = defineAndCacheClass(className, data); 256 } catch (NoClassDefFoundError error) { 257 if (error.getMessage().startsWith("java/")) { 258 // Can't define these; we just need to stop 259 // iteration here 260 continue; 261 } 262 throw error; 263 } 264 } 265 266 for (Class<?> superClass = loaded_class.getSuperclass(); 267 superClass != null; 268 superClass = superClass.getSuperclass()) { 269 String superName = superClass.getCanonicalName(); 270 if (mClassesFound.containsKey(superName)) { 271 mClassesFound.get(superName).add(new ClassWrapper(loaded_class)); 272 break; 273 } 274 } 275 } 276 } finally { 277 Closeables.closeQuietly(zis); 278 } 279 280 return mClassesFound; 281 } 282 283 /** Helper method that converts a Zip entry path into a corresponding 284 * Java full qualified binary class name. 285 * <p/> 286 * F.ex, this converts "com/my/package/Foo.class" into "com.my.package.Foo". 287 */ 288 private String entryPathToClassName(String entryPath) { 289 return entryPath.replaceFirst("\\.class$", "").replaceAll("[/\\\\]", "."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ 290 } 291 292 /** 293 * Finds the class with the specified binary name. 294 * 295 * {@inheritDoc} 296 */ 297 @Override 298 protected Class<?> findClass(String name) throws ClassNotFoundException { 299 try { 300 // try to find the class in the cache 301 Class<?> cached_class = mClassCache.get(name); 302 if (cached_class == ClassNotFoundException.class) { 303 // we already know we can't find this class, don't try again 304 throw new ClassNotFoundException(name); 305 } else if (cached_class != null) { 306 return cached_class; 307 } 308 309 // if not found, look it up and cache it 310 byte[] data = loadClassData(name); 311 if (data != null) { 312 return defineAndCacheClass(name, data); 313 } else { 314 // if the class can't be found, record a CNFE class in the map so 315 // that we don't try to reload it next time 316 mClassCache.put(name, ClassNotFoundException.class); 317 throw new ClassNotFoundException(name); 318 } 319 } catch (ClassNotFoundException e) { 320 throw e; 321 } catch (Exception e) { 322 throw new ClassNotFoundException(e.getMessage()); 323 } 324 } 325 326 /** 327 * Defines a class based on its binary data and caches the resulting class object. 328 * 329 * @param name The binary name of the class (i.e. package.class1$class2) 330 * @param data The binary data from the loader. 331 * @return The class defined 332 * @throws ClassFormatError if defineClass failed. 333 */ 334 private Class<?> defineAndCacheClass(String name, byte[] data) throws ClassFormatError { 335 Class<?> cached_class; 336 cached_class = defineClass(null, data, 0, data.length); 337 338 if (cached_class != null) { 339 // Add new class to the cache class and remove it from the zip entry data cache 340 mClassCache.put(name, cached_class); 341 mEntryCache.remove(name); 342 } 343 return cached_class; 344 } 345 346 /** 347 * Loads a class data from its binary name. 348 * <p/> 349 * This uses the class binary data that has been preloaded earlier by the preLoadClasses() 350 * method if possible. 351 * 352 * @param className the binary name 353 * @return an array of bytes representing the class data or null if not found 354 * @throws InvalidAttributeValueException 355 * @throws IOException 356 */ 357 private synchronized byte[] loadClassData(String className) 358 throws InvalidAttributeValueException, IOException { 359 360 byte[] data = mEntryCache.get(className); 361 if (data != null) { 362 return data; 363 } 364 365 // The name is a binary name. Something like "android.R", or "android.R$id". 366 // Make a path out of it. 367 String entryName = className.replaceAll("\\.", "/") + SdkConstants.DOT_CLASS; //$NON-NLS-1$ //$NON-NLS-2$ 368 369 // create streams to read the intermediary archive 370 FileInputStream fis = new FileInputStream(mOsFrameworkLocation); 371 ZipInputStream zis = new ZipInputStream(fis); 372 try { 373 // loop on the entries of the intermediary package and put them in the final package. 374 ZipEntry entry; 375 376 while ((entry = zis.getNextEntry()) != null) { 377 // get the name of the entry. 378 String currEntryName = entry.getName(); 379 380 if (currEntryName.equals(entryName)) { 381 long entrySize = entry.getSize(); 382 if (entrySize > Integer.MAX_VALUE) { 383 throw new InvalidAttributeValueException(); 384 } 385 386 data = readZipData(zis, (int)entrySize); 387 return data; 388 } 389 } 390 391 return null; 392 } finally { 393 zis.close(); 394 } 395 } 396 397 /** 398 * Reads data for the <em>current</em> entry from the zip input stream. 399 * 400 * @param zis The Zip input stream 401 * @param entrySize The entry size. -1 if unknown. 402 * @return The new data for the <em>current</em> entry. 403 * @throws IOException If ZipInputStream.read() fails. 404 */ 405 private byte[] readZipData(ZipInputStream zis, int entrySize) throws IOException { 406 int block_size = 1024; 407 int data_size = entrySize < 1 ? block_size : entrySize; 408 int offset = 0; 409 byte[] data = new byte[data_size]; 410 411 while(zis.available() != 0) { 412 int count = zis.read(data, offset, data_size - offset); 413 if (count < 0) { // read data is done 414 break; 415 } 416 offset += count; 417 418 if (entrySize >= 1 && offset >= entrySize) { // we know the size and we're done 419 break; 420 } 421 422 // if we don't know the entry size and we're not done reading, 423 // expand the data buffer some more. 424 if (offset >= data_size) { 425 byte[] temp = new byte[data_size + block_size]; 426 System.arraycopy(data, 0, temp, 0, data_size); 427 data_size += block_size; 428 data = temp; 429 block_size *= 2; 430 } 431 } 432 433 if (offset < data_size) { 434 // buffer was allocated too large, trim it 435 byte[] temp = new byte[offset]; 436 if (offset > 0) { 437 System.arraycopy(data, 0, temp, 0, offset); 438 } 439 data = temp; 440 } 441 442 return data; 443 } 444 445 /** 446 * Returns a {@link IAndroidClassLoader.IClassDescriptor} by its fully-qualified name. 447 * @param className the fully-qualified name of the class to return. 448 * @throws ClassNotFoundException 449 */ 450 @Override 451 public IClassDescriptor getClass(String className) throws ClassNotFoundException { 452 try { 453 return new ClassWrapper(loadClass(className)); 454 } catch (ClassNotFoundException e) { 455 throw e; // useful for debugging 456 } 457 } 458 } 459