PathClassLoader.java revision af7e77337d97ca5961159a7298e3f47a1c507db3
1/* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 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 17package dalvik.system; 18 19import java.io.ByteArrayOutputStream; 20import java.io.File; 21import java.io.FileNotFoundException; 22import java.io.IOException; 23import java.io.InputStream; 24import java.io.RandomAccessFile; 25import java.net.MalformedURLException; 26import java.net.URL; 27import java.util.ArrayList; 28import java.util.Enumeration; 29import java.util.List; 30import java.util.NoSuchElementException; 31import java.util.zip.ZipEntry; 32import java.util.zip.ZipFile; 33 34/** 35 * Provides a simple {@link ClassLoader} implementation that operates on a list 36 * of files and directories in the local file system, but does not attempt to 37 * load classes from the network. Android uses this class for its system class 38 * loader and for its application class loader(s). 39 */ 40public class PathClassLoader extends ClassLoader { 41 42 private final String path; 43 private final String libPath; 44 45 private boolean initialized; 46 47 private String[] mPaths; 48 private File[] mFiles; 49 private ZipFile[] mZips; 50 private DexFile[] mDexs; 51 private List<String> libraryPathElements; 52 53 /** 54 * Creates a {@code PathClassLoader} that operates on a given list of files 55 * and directories. This method is equivalent to calling 56 * {@link #PathClassLoader(String, String, ClassLoader)} with a 57 * {@code null} value for the second argument (see description there). 58 * 59 * @param path 60 * the list of files and directories 61 * 62 * @param parent 63 * the parent class loader 64 */ 65 public PathClassLoader(String path, ClassLoader parent) { 66 this(path, null, parent); 67 } 68 69 /** 70 * Creates a {@code PathClassLoader} that operates on two given lists of 71 * files and directories. The entries of the first list should be one of the 72 * following: 73 * <ul> 74 * <li>Directories containing classes or resources. 75 * <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file. 76 * <li>"classes.dex" files. 77 * </ul> 78 * The entries of the second list should be directories containing native 79 * library files. Both lists are separated using the character specified by 80 * the "path.separator" system property, which, on Android, defaults to ":". 81 * 82 * @param path 83 * the list of files and directories containing classes and 84 * resources 85 * 86 * @param libPath 87 * the list of directories containing native libraries 88 * 89 * @param parent 90 * the parent class loader 91 */ 92 public PathClassLoader(String path, String libPath, ClassLoader parent) { 93 super(parent); 94 95 if (path == null) 96 throw new NullPointerException(); 97 98 this.path = path; 99 this.libPath = libPath; 100 } 101 102 private synchronized void ensureInit() { 103 if (initialized) { 104 return; 105 } 106 107 initialized = true; 108 109 mPaths = path.split(":"); 110 int length = mPaths.length; 111 112 //System.out.println("PathClassLoader: " + mPaths); 113 mFiles = new File[length]; 114 mZips = new ZipFile[length]; 115 mDexs = new DexFile[length]; 116 117 boolean wantDex = 118 System.getProperty("android.vm.dexfile", "").equals("true"); 119 120 /* open all Zip and DEX files up front */ 121 for (int i = 0; i < length; i++) { 122 //System.out.println("My path is: " + mPaths[i]); 123 File pathFile = new File(mPaths[i]); 124 mFiles[i] = pathFile; 125 126 if (pathFile.isFile()) { 127 try { 128 mZips[i] = new ZipFile(pathFile); 129 } 130 catch (IOException ioex) { 131 // expecting IOException and ZipException 132 //System.out.println("Failed opening '" + pathFile + "': " + ioex); 133 //ioex.printStackTrace(); 134 } 135 if (wantDex) { 136 /* we need both DEX and Zip, because dex has no resources */ 137 try { 138 mDexs[i] = new DexFile(pathFile); 139 } 140 catch (IOException ioex) {} 141 } 142 } 143 } 144 145 /* 146 * Native libraries may exist in both the system and application library 147 * paths, so we use this search order for these paths: 148 * 1. This class loader's library path for application libraries 149 * 2. The VM's library path from the system property for system libraries 150 * This order was reversed prior to Gingerbread; see http://b/2933456 151 */ 152 libraryPathElements = new ArrayList<String>(); 153 if (libPath != null) { 154 for (String pathElement : libPath.split(File.pathSeparator)) { 155 libraryPathElements.add(cleanupPathElement(pathElement)); 156 } 157 } 158 String systemLibraryPath = System.getProperty("java.library.path", "."); 159 if (!systemLibraryPath.isEmpty()) { 160 for (String pathElement : systemLibraryPath.split(File.pathSeparator)) { 161 libraryPathElements.add(cleanupPathElement(pathElement)); 162 } 163 } 164 } 165 166 /** 167 * Returns a path element that includes a trailing file separator. 168 */ 169 private String cleanupPathElement(String path) { 170 return path.endsWith(File.separator) ? path : (path + File.separator); 171 } 172 173 /** 174 * Finds a class. This method is called by {@code loadClass()} after the 175 * parent ClassLoader has failed to find a loaded class of the same name. 176 * 177 * @param name 178 * The "binary name" of the class to search for, in a 179 * human-readable form like "java.lang.String" or 180 * "java.net.URLClassLoader$3$1". 181 * @return the {@link Class} object representing the class 182 * @throws ClassNotFoundException 183 * if the class cannot be found 184 */ 185 @Override 186 protected Class<?> findClass(String name) throws ClassNotFoundException 187 { 188 ensureInit(); 189 190 //System.out.println("PathClassLoader " + this + ": findClass '" + name + "'"); 191 192 byte[] data = null; 193 int length = mPaths.length; 194 195 for (int i = 0; i < length; i++) { 196 //System.out.println("My path is: " + mPaths[i]); 197 198 if (mDexs[i] != null) { 199 Class clazz = mDexs[i].loadClassBinaryName(name, this); 200 if (clazz != null) 201 return clazz; 202 } else if (mZips[i] != null) { 203 String fileName = name.replace('.', '/') + ".class"; 204 data = loadFromArchive(mZips[i], fileName); 205 } else { 206 File pathFile = mFiles[i]; 207 if (pathFile.isDirectory()) { 208 String fileName = 209 mPaths[i] + "/" + name.replace('.', '/') + ".class"; 210 data = loadFromDirectory(fileName); 211 } else { 212 //System.out.println("PathClassLoader: can't find '" 213 // + mPaths[i] + "'"); 214 } 215 216 } 217 218 /* --this doesn't work in current version of Dalvik-- 219 if (data != null) { 220 System.out.println("--- Found class " + name 221 + " in zip[" + i + "] '" + mZips[i].getName() + "'"); 222 int dotIndex = name.lastIndexOf('.'); 223 if (dotIndex != -1) { 224 String packageName = name.substring(0, dotIndex); 225 synchronized (this) { 226 Package packageObj = getPackage(packageName); 227 if (packageObj == null) { 228 definePackage(packageName, null, null, 229 null, null, null, null, null); 230 } 231 } 232 } 233 234 return defineClass(name, data, 0, data.length); 235 } 236 */ 237 } 238 239 throw new ClassNotFoundException(name + " in loader " + this); 240 } 241 242 /** 243 * Finds a resource. This method is called by {@code getResource()} after 244 * the parent ClassLoader has failed to find a loaded resource of the same 245 * name. 246 * 247 * @param name 248 * The name of the resource to find 249 * @return the location of the resource as a URL, or {@code null} if the 250 * resource is not found. 251 */ 252 @Override 253 protected URL findResource(String name) { 254 ensureInit(); 255 256 //java.util.logging.Logger.global.severe("findResource: " + name); 257 258 int length = mPaths.length; 259 260 for (int i = 0; i < length; i++) { 261 URL result = findResource(name, i); 262 if(result != null) { 263 return result; 264 } 265 } 266 267 return null; 268 } 269 270 /** 271 * Finds an enumeration of URLs for the resource with the specified name. 272 * 273 * @param resName 274 * the name of the resource to find. 275 * @return an enumeration of {@code URL} objects for the requested resource. 276 */ 277 @Override 278 protected Enumeration<URL> findResources(String resName) { 279 ensureInit(); 280 281 int length = mPaths.length; 282 ArrayList<URL> results = new ArrayList<URL>(); 283 284 for (int i = 0; i < length; i++) { 285 URL result = findResource(resName, i); 286 if(result != null) { 287 results.add(result); 288 } 289 } 290 return new EnumerateListArray<URL>(results); 291 } 292 293 private URL findResource(String name, int i) { 294 File pathFile = mFiles[i]; 295 ZipFile zip = mZips[i]; 296 if (zip != null) { 297 if (isInArchive(zip, name)) { 298 //System.out.println(" found " + name + " in " + pathFile); 299 try { 300 // File.toURL() is compliant with RFC 1738 in always 301 // creating absolute path names. If we construct the 302 // URL by concatenating strings, we might end up with 303 // illegal URLs for relative names. 304 return new URL("jar:" + pathFile.toURL() + "!/" + name); 305 } 306 catch (MalformedURLException e) { 307 throw new RuntimeException(e); 308 } 309 } 310 } else if (pathFile.isDirectory()) { 311 File dataFile = new File(mPaths[i] + "/" + name); 312 if (dataFile.exists()) { 313 //System.out.println(" found resource " + name); 314 try { 315 // Same as archive case regarding URL construction. 316 return dataFile.toURL(); 317 } 318 catch (MalformedURLException e) { 319 throw new RuntimeException(e); 320 } 321 } 322 } else if (pathFile.isFile()) { 323 } else { 324 System.err.println("PathClassLoader: can't find '" 325 + mPaths[i] + "'"); 326 } 327 return null; 328 } 329 330 /* 331 * Load the contents of a file from a file in a directory. 332 * 333 * Returns null if the class wasn't found. 334 */ 335 private byte[] loadFromDirectory(String path) { 336 RandomAccessFile raf; 337 byte[] fileData; 338 339 //System.out.println("Trying to load from " + path); 340 try { 341 raf = new RandomAccessFile(path, "r"); 342 } 343 catch (FileNotFoundException fnfe) { 344 //System.out.println(" Not found: " + path); 345 return null; 346 } 347 348 try { 349 fileData = new byte[(int) raf.length()]; 350 raf.read(fileData); 351 raf.close(); 352 } 353 catch (IOException ioe) { 354 System.err.println("Error reading from " + path); 355 // swallow it, return null instead 356 fileData = null; 357 } 358 359 return fileData; 360 } 361 362 /* 363 * Load a class from a file in an archive. We currently assume that 364 * the file is a Zip archive. 365 * 366 * Returns null if the class wasn't found. 367 */ 368 private byte[] loadFromArchive(ZipFile zip, String name) { 369 ZipEntry entry; 370 371 entry = zip.getEntry(name); 372 if (entry == null) 373 return null; 374 375 ByteArrayOutputStream byteStream; 376 InputStream stream; 377 int count; 378 379 /* 380 * Copy the data out of the stream. Because we got the ZipEntry 381 * from a ZipFile, the uncompressed size is known, and we can set 382 * the initial size of the ByteArrayOutputStream appropriately. 383 */ 384 try { 385 stream = zip.getInputStream(entry); 386 byteStream = new ByteArrayOutputStream((int) entry.getSize()); 387 byte[] buf = new byte[4096]; 388 while ((count = stream.read(buf)) > 0) 389 byteStream.write(buf, 0, count); 390 391 stream.close(); 392 } 393 catch (IOException ioex) { 394 //System.out.println("Failed extracting '" + archive + "': " +ioex); 395 return null; 396 } 397 398 //System.out.println(" loaded from Zip"); 399 return byteStream.toByteArray(); 400 } 401 402 /* 403 * Figure out if "name" is a member of "archive". 404 */ 405 private boolean isInArchive(ZipFile zip, String name) { 406 return zip.getEntry(name) != null; 407 } 408 409 /** 410 * Finds a native library. This class loader first searches its own library 411 * path (as specified in the constructor) and then the system library path. 412 * In Android 2.2 and earlier, the search order was reversed. 413 * 414 * @param libname 415 * The name of the library to find 416 * @return the complete path of the library, or {@code null} if the library 417 * is not found. 418 */ 419 public String findLibrary(String libname) { 420 ensureInit(); 421 422 String fileName = System.mapLibraryName(libname); 423 for (String pathElement : libraryPathElements) { 424 String pathName = pathElement + fileName; 425 File test = new File(pathName); 426 427 if (test.exists()) { 428 return pathName; 429 } 430 } 431 432 return null; 433 } 434 435 /** 436 * Returns package information for the given package. Unfortunately, the 437 * PathClassLoader doesn't really have this information, and as a non-secure 438 * ClassLoader, it isn't even required to, according to the spec. Yet, we 439 * want to provide it, in order to make all those hopeful callers of 440 * <code>myClass.getPackage().getName()</code> happy. Thus we construct a 441 * Package object the first time it is being requested and fill most of the 442 * fields with dummy values. The Package object is then put into the 443 * ClassLoader's Package cache, so we see the same one next time. We don't 444 * create Package objects for null arguments or for the default package. 445 * <p> 446 * There a limited chance that we end up with multiple Package objects 447 * representing the same package: It can happen when when a package is 448 * scattered across different JAR files being loaded by different 449 * ClassLoaders. Rather unlikely, and given that this whole thing is more or 450 * less a workaround, probably not worth the effort. 451 * 452 * @param name 453 * the name of the class 454 * @return the package information for the class, or {@code null} if there 455 * is not package information available for it 456 */ 457 @Override 458 protected Package getPackage(String name) { 459 if (name != null && !name.isEmpty()) { 460 synchronized(this) { 461 Package pack = super.getPackage(name); 462 463 if (pack == null) { 464 pack = definePackage(name, "Unknown", "0.0", "Unknown", "Unknown", "0.0", "Unknown", null); 465 } 466 467 return pack; 468 } 469 } 470 471 return null; 472 } 473 474 /* 475 * Create an Enumeration for an ArrayList. 476 */ 477 private static class EnumerateListArray<T> implements Enumeration<T> { 478 private final ArrayList mList; 479 private int i = 0; 480 481 EnumerateListArray(ArrayList list) { 482 mList = list; 483 } 484 485 public boolean hasMoreElements() { 486 return i < mList.size(); 487 } 488 489 public T nextElement() { 490 if (i >= mList.size()) 491 throw new NoSuchElementException(); 492 return (T) mList.get(i++); 493 } 494 }; 495 496 public String toString () { 497 return getClass().getName() + "[" + path + "]"; 498 } 499} 500