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 loaded_class = defineAndCacheClass(className, data); 255 } 256 257 for (Class<?> superClass = loaded_class.getSuperclass(); 258 superClass != null; 259 superClass = superClass.getSuperclass()) { 260 String superName = superClass.getCanonicalName(); 261 if (mClassesFound.containsKey(superName)) { 262 mClassesFound.get(superName).add(new ClassWrapper(loaded_class)); 263 break; 264 } 265 } 266 } 267 } finally { 268 Closeables.closeQuietly(zis); 269 } 270 271 return mClassesFound; 272 } 273 274 /** Helper method that converts a Zip entry path into a corresponding 275 * Java full qualified binary class name. 276 * <p/> 277 * F.ex, this converts "com/my/package/Foo.class" into "com.my.package.Foo". 278 */ 279 private String entryPathToClassName(String entryPath) { 280 return entryPath.replaceFirst("\\.class$", "").replaceAll("[/\\\\]", "."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ 281 } 282 283 /** 284 * Finds the class with the specified binary name. 285 * 286 * {@inheritDoc} 287 */ 288 @Override 289 protected Class<?> findClass(String name) throws ClassNotFoundException { 290 try { 291 // try to find the class in the cache 292 Class<?> cached_class = mClassCache.get(name); 293 if (cached_class == ClassNotFoundException.class) { 294 // we already know we can't find this class, don't try again 295 throw new ClassNotFoundException(name); 296 } else if (cached_class != null) { 297 return cached_class; 298 } 299 300 // if not found, look it up and cache it 301 byte[] data = loadClassData(name); 302 if (data != null) { 303 return defineAndCacheClass(name, data); 304 } else { 305 // if the class can't be found, record a CNFE class in the map so 306 // that we don't try to reload it next time 307 mClassCache.put(name, ClassNotFoundException.class); 308 throw new ClassNotFoundException(name); 309 } 310 } catch (ClassNotFoundException e) { 311 throw e; 312 } catch (Exception e) { 313 throw new ClassNotFoundException(e.getMessage()); 314 } 315 } 316 317 /** 318 * Defines a class based on its binary data and caches the resulting class object. 319 * 320 * @param name The binary name of the class (i.e. package.class1$class2) 321 * @param data The binary data from the loader. 322 * @return The class defined 323 * @throws ClassFormatError if defineClass failed. 324 */ 325 private Class<?> defineAndCacheClass(String name, byte[] data) throws ClassFormatError { 326 Class<?> cached_class; 327 cached_class = defineClass(null, data, 0, data.length); 328 329 if (cached_class != null) { 330 // Add new class to the cache class and remove it from the zip entry data cache 331 mClassCache.put(name, cached_class); 332 mEntryCache.remove(name); 333 } 334 return cached_class; 335 } 336 337 /** 338 * Loads a class data from its binary name. 339 * <p/> 340 * This uses the class binary data that has been preloaded earlier by the preLoadClasses() 341 * method if possible. 342 * 343 * @param className the binary name 344 * @return an array of bytes representing the class data or null if not found 345 * @throws InvalidAttributeValueException 346 * @throws IOException 347 */ 348 private synchronized byte[] loadClassData(String className) 349 throws InvalidAttributeValueException, IOException { 350 351 byte[] data = mEntryCache.get(className); 352 if (data != null) { 353 return data; 354 } 355 356 // The name is a binary name. Something like "android.R", or "android.R$id". 357 // Make a path out of it. 358 String entryName = className.replaceAll("\\.", "/") + SdkConstants.DOT_CLASS; //$NON-NLS-1$ //$NON-NLS-2$ 359 360 // create streams to read the intermediary archive 361 FileInputStream fis = new FileInputStream(mOsFrameworkLocation); 362 ZipInputStream zis = new ZipInputStream(fis); 363 try { 364 // loop on the entries of the intermediary package and put them in the final package. 365 ZipEntry entry; 366 367 while ((entry = zis.getNextEntry()) != null) { 368 // get the name of the entry. 369 String currEntryName = entry.getName(); 370 371 if (currEntryName.equals(entryName)) { 372 long entrySize = entry.getSize(); 373 if (entrySize > Integer.MAX_VALUE) { 374 throw new InvalidAttributeValueException(); 375 } 376 377 data = readZipData(zis, (int)entrySize); 378 return data; 379 } 380 } 381 382 return null; 383 } finally { 384 zis.close(); 385 } 386 } 387 388 /** 389 * Reads data for the <em>current</em> entry from the zip input stream. 390 * 391 * @param zis The Zip input stream 392 * @param entrySize The entry size. -1 if unknown. 393 * @return The new data for the <em>current</em> entry. 394 * @throws IOException If ZipInputStream.read() fails. 395 */ 396 private byte[] readZipData(ZipInputStream zis, int entrySize) throws IOException { 397 int block_size = 1024; 398 int data_size = entrySize < 1 ? block_size : entrySize; 399 int offset = 0; 400 byte[] data = new byte[data_size]; 401 402 while(zis.available() != 0) { 403 int count = zis.read(data, offset, data_size - offset); 404 if (count < 0) { // read data is done 405 break; 406 } 407 offset += count; 408 409 if (entrySize >= 1 && offset >= entrySize) { // we know the size and we're done 410 break; 411 } 412 413 // if we don't know the entry size and we're not done reading, 414 // expand the data buffer some more. 415 if (offset >= data_size) { 416 byte[] temp = new byte[data_size + block_size]; 417 System.arraycopy(data, 0, temp, 0, data_size); 418 data_size += block_size; 419 data = temp; 420 block_size *= 2; 421 } 422 } 423 424 if (offset < data_size) { 425 // buffer was allocated too large, trim it 426 byte[] temp = new byte[offset]; 427 if (offset > 0) { 428 System.arraycopy(data, 0, temp, 0, offset); 429 } 430 data = temp; 431 } 432 433 return data; 434 } 435 436 /** 437 * Returns a {@link IAndroidClassLoader.IClassDescriptor} by its fully-qualified name. 438 * @param className the fully-qualified name of the class to return. 439 * @throws ClassNotFoundException 440 */ 441 @Override 442 public IClassDescriptor getClass(String className) throws ClassNotFoundException { 443 try { 444 return new ClassWrapper(loadClass(className)); 445 } catch (ClassNotFoundException e) { 446 throw e; // useful for debugging 447 } 448 } 449 } 450