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