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