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