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