URLClassLoader.java revision 80a7fbab52b96c9fd47c72f8987d1babe2cd001d
1/* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package java.net; 19 20import java.io.BufferedReader; 21import java.io.ByteArrayOutputStream; 22import java.io.File; 23import java.io.FileInputStream; 24import java.io.FileNotFoundException; 25import java.io.FilePermission; 26import java.io.IOException; 27import java.io.InputStream; 28import java.io.InputStreamReader; 29import java.io.UnsupportedEncodingException; 30import java.security.AccessControlContext; 31import java.security.AccessController; 32import java.security.CodeSource; 33import java.security.PermissionCollection; 34import java.security.PrivilegedAction; 35import java.security.SecureClassLoader; 36import java.security.cert.Certificate; 37import java.util.ArrayList; 38import java.util.Collections; 39import java.util.Enumeration; 40import java.util.HashMap; 41import java.util.List; 42import java.util.Map; 43import java.util.StringTokenizer; 44import java.util.jar.Attributes; 45import java.util.jar.JarEntry; 46import java.util.jar.JarFile; 47import java.util.jar.Manifest; 48 49import org.apache.harmony.luni.util.Msg; 50 51/** 52 * This class loader is responsible for loading classes and resources from a 53 * list of URLs which can refer to either directories or JAR files. Classes 54 * loaded by this {@code URLClassLoader} are granted permission to access the 55 * URLs contained in the URL search list. 56 */ 57public class URLClassLoader extends SecureClassLoader { 58 59 ArrayList<URL> originalUrls; 60 61 List<URL> searchList; 62 ArrayList<URLHandler> handlerList; 63 Map<URL, URLHandler> handlerMap = new HashMap<URL, URLHandler>(); 64 65 private URLStreamHandlerFactory factory; 66 67 private AccessControlContext currentContext; 68 69 static class SubURLClassLoader extends URLClassLoader { 70 // The subclass that overwrites the loadClass() method 71 private boolean checkingPackageAccess = false; 72 73 SubURLClassLoader(URL[] urls) { 74 super(urls, ClassLoader.getSystemClassLoader()); 75 } 76 77 SubURLClassLoader(URL[] urls, ClassLoader parent) { 78 super(urls, parent); 79 } 80 81 /** 82 * Overrides the {@code loadClass()} of {@code ClassLoader}. It calls 83 * the security manager's {@code checkPackageAccess()} before 84 * attempting to load the class. 85 * 86 * @return the Class object. 87 * @param className 88 * String the name of the class to search for. 89 * @param resolveClass 90 * boolean indicates if class should be resolved after 91 * loading. 92 * @throws ClassNotFoundException 93 * If the class could not be found. 94 */ 95 @Override 96 protected synchronized Class<?> loadClass(String className, 97 boolean resolveClass) throws ClassNotFoundException { 98 SecurityManager sm = System.getSecurityManager(); 99 if (sm != null && !checkingPackageAccess) { 100 int index = className.lastIndexOf('.'); 101 if (index > 0) { // skip if class is from a default package 102 try { 103 checkingPackageAccess = true; 104 sm.checkPackageAccess(className.substring(0, index)); 105 } finally { 106 checkingPackageAccess = false; 107 } 108 } 109 } 110 return super.loadClass(className, resolveClass); 111 } 112 } 113 114 static class IndexFile { 115 116 private HashMap<String, ArrayList<URL>> map; 117 //private URLClassLoader host; 118 119 120 static IndexFile readIndexFile(JarFile jf, JarEntry indexEntry, URL url) { 121 BufferedReader in = null; 122 InputStream is = null; 123 try { 124 // Add mappings from resource to jar file 125 String parentURLString = getParentURL(url).toExternalForm(); 126 String prefix = "jar:" 127 + parentURLString + "/"; 128 is = jf.getInputStream(indexEntry); 129 in = new BufferedReader(new InputStreamReader(is, "UTF8")); 130 HashMap<String, ArrayList<URL>> pre_map = new HashMap<String, ArrayList<URL>>(); 131 // Ignore the 2 first lines (index version) 132 if (in.readLine() == null) return null; 133 if (in.readLine() == null) return null; 134 TOP_CYCLE: 135 while (true) { 136 String line = in.readLine(); 137 if (line == null) { 138 break; 139 } 140 URL jar = new URL(prefix + line + "!/"); 141 while (true) { 142 line = in.readLine(); 143 if (line == null) { 144 break TOP_CYCLE; 145 } 146 if (line.isEmpty()) { 147 break; 148 } 149 ArrayList<URL> list; 150 if (pre_map.containsKey(line)) { 151 list = pre_map.get(line); 152 } else { 153 list = new ArrayList<URL>(); 154 pre_map.put(line, list); 155 } 156 list.add(jar); 157 } 158 } 159 if (!pre_map.isEmpty()) { 160 return new IndexFile(pre_map); 161 } 162 } catch (MalformedURLException e) { 163 // Ignore this jar's index 164 } catch (IOException e) { 165 // Ignore this jar's index 166 } 167 finally { 168 if (in != null) { 169 try { 170 in.close(); 171 } catch (IOException e) { 172 } 173 } 174 if (is != null) { 175 try { 176 is.close(); 177 } catch (IOException e) { 178 } 179 } 180 } 181 return null; 182 } 183 184 private static URL getParentURL(URL url) throws IOException { 185 URL fileURL = ((JarURLConnection) url.openConnection()).getJarFileURL(); 186 String file = fileURL.getFile(); 187 String parentFile = new File(file).getParent(); 188 parentFile = parentFile.replace(File.separatorChar, '/'); 189 if (parentFile.charAt(0) != '/') { 190 parentFile = "/" + parentFile; 191 } 192 URL parentURL = new URL(fileURL.getProtocol(), fileURL 193 .getHost(), fileURL.getPort(), parentFile); 194 return parentURL; 195 } 196 197 public IndexFile(HashMap<String, ArrayList<URL>> map) { 198 this.map = map; 199 } 200 201 ArrayList<URL> get(String name) { 202 return map.get(name); 203 } 204 } 205 206 class URLHandler { 207 URL url; 208 URL codeSourceUrl; 209 210 public URLHandler(URL url) { 211 this.url = url; 212 this.codeSourceUrl = url; 213 } 214 215 void findResources(String name, ArrayList<URL> resources) { 216 URL res = findResource(name); 217 if (res != null && !resources.contains(res)) { 218 resources.add(res); 219 } 220 } 221 222 Class<?> findClass(String packageName, String name, String origName) { 223 URL resURL = targetURL(url, name); 224 if (resURL != null) { 225 try { 226 InputStream is = resURL.openStream(); 227 return createClass(is, packageName, origName); 228 } catch (IOException e) { 229 } 230 } 231 return null; 232 } 233 234 235 Class<?> createClass(InputStream is, String packageName, String origName) { 236 if (is == null) { 237 return null; 238 } 239 byte[] clBuf = null; 240 try { 241 clBuf = getBytes(is); 242 } catch (IOException e) { 243 return null; 244 } finally { 245 try { 246 is.close(); 247 } catch (IOException e) { 248 } 249 } 250 if (packageName != null) { 251 String packageDotName = packageName.replace('/', '.'); 252 Package packageObj = getPackage(packageDotName); 253 if (packageObj == null) { 254 definePackage(packageDotName, null, null, 255 null, null, null, null, null); 256 } else { 257 if (packageObj.isSealed()) { 258 throw new SecurityException(Msg 259 .getString("K004c")); 260 } 261 } 262 } 263 return defineClass(origName, clBuf, 0, clBuf.length, new CodeSource(codeSourceUrl, (Certificate[]) null)); 264 } 265 266 URL findResource(String name) { 267 URL resURL = targetURL(url, name); 268 if (resURL != null) { 269 try { 270 URLConnection uc = resURL.openConnection(); 271 uc.getInputStream().close(); 272 // HTTP can return a stream on a non-existent file 273 // So check for the return code; 274 if (!resURL.getProtocol().equals("http")) { 275 return resURL; 276 } 277 int code; 278 if ((code = ((HttpURLConnection) uc).getResponseCode()) >= 200 279 && code < 300) { 280 return resURL; 281 } 282 } catch (SecurityException e) { 283 return null; 284 } catch (IOException e) { 285 return null; 286 } 287 } 288 return null; 289 } 290 291 URL targetURL(URL base, String name) { 292 try { 293 String file = base.getFile() + URIEncoderDecoder.quoteIllegal(name, 294 "/@" + URI.SOME_LEGAL); 295 296 return new URL(base.getProtocol(), base.getHost(), base.getPort(), 297 file, null); 298 } catch (UnsupportedEncodingException e) { 299 return null; 300 } catch (MalformedURLException e) { 301 return null; 302 } 303 } 304 305 } 306 307 class URLJarHandler extends URLHandler { 308 final JarFile jf; 309 final String prefixName; 310 final IndexFile index; 311 final Map<URL, URLHandler> subHandlers = new HashMap<URL, URLHandler>(); 312 313 public URLJarHandler(URL url, URL jarURL, JarFile jf, String prefixName) { 314 super(url); 315 this.jf = jf; 316 this.prefixName = prefixName; 317 this.codeSourceUrl = jarURL; 318 final JarEntry je = jf.getJarEntry("META-INF/INDEX.LIST"); 319 this.index = (je == null ? null : IndexFile.readIndexFile(jf, je, url)); 320 } 321 322 public URLJarHandler(URL url, URL jarURL, JarFile jf, String prefixName, IndexFile index) { 323 super(url); 324 this.jf = jf; 325 this.prefixName = prefixName; 326 this.index = index; 327 this.codeSourceUrl = jarURL; 328 } 329 330 IndexFile getIndex() { 331 return index; 332 } 333 334 @Override 335 void findResources(String name, ArrayList<URL> resources) { 336 URL res = findResourceInOwn(name); 337 if (res != null && !resources.contains(res)) { 338 resources.add(res); 339 } 340 if (index != null) { 341 int pos = name.lastIndexOf("/"); 342 // only keep the directory part of the resource 343 // as index.list only keeps track of directories and root files 344 String indexedName = (pos > 0) ? name.substring(0, pos) : name; 345 ArrayList<URL> urls = index.get(indexedName); 346 if (urls != null) { 347 urls.remove(url); 348 for (URL url : urls) { 349 URLHandler h = getSubHandler(url); 350 if (h != null) { 351 h.findResources(name, resources); 352 } 353 } 354 } 355 } 356 357 } 358 359 @Override 360 Class<?> findClass(String packageName, String name, String origName) { 361 String entryName = prefixName + name; 362 JarEntry entry = jf.getJarEntry(entryName); 363 if (entry != null) { 364 /** 365 * Avoid recursive load class, especially the class 366 * is an implementation class of security provider 367 * and the jar is signed. 368 */ 369 try { 370 Manifest manifest = jf.getManifest(); 371 return createClass(entry, manifest, packageName, origName); 372 } catch (IOException e) { 373 } 374 } 375 if (index != null) { 376 ArrayList<URL> urls; 377 if (packageName == null) { 378 urls = index.get(name); 379 } else { 380 urls = index.get(packageName); 381 } 382 if (urls != null) { 383 urls.remove(url); 384 for (URL url : urls) { 385 URLHandler h = getSubHandler(url); 386 if (h != null) { 387 Class<?> res = h.findClass(packageName, name, origName); 388 if (res != null) { 389 return res; 390 } 391 } 392 } 393 } 394 } 395 return null; 396 } 397 398 private Class<?> createClass(JarEntry entry, Manifest manifest, String packageName, String origName) { 399 InputStream is = null; 400 byte[] clBuf = null; 401 try { 402 is = jf.getInputStream(entry); 403 clBuf = getBytes(is); 404 } catch (IOException e) { 405 return null; 406 } finally { 407 if (is != null) { 408 try { 409 is.close(); 410 } catch (IOException e) { 411 } 412 } 413 } 414 if (packageName != null) { 415 String packageDotName = packageName.replace('/', '.'); 416 Package packageObj = getPackage(packageDotName); 417 if (packageObj == null) { 418 if (manifest != null) { 419 definePackage(packageDotName, manifest, 420 codeSourceUrl); 421 } else { 422 definePackage(packageDotName, null, null, 423 null, null, null, null, null); 424 } 425 } else { 426 boolean exception = packageObj.isSealed(); 427 if (manifest != null) { 428 if (isSealed(manifest, packageName + "/")) { 429 exception = !packageObj 430 .isSealed(codeSourceUrl); 431 } 432 } 433 if (exception) { 434 throw new SecurityException(Msg 435 .getString("K0352", packageName)); 436 } 437 } 438 } 439 CodeSource codeS = new CodeSource(codeSourceUrl, entry.getCertificates()); 440 return defineClass(origName, clBuf, 0, clBuf.length, codeS); 441 } 442 443 URL findResourceInOwn(String name) { 444 String entryName = prefixName + name; 445 if (jf.getEntry(entryName) != null) { 446 return targetURL(url, name); 447 } 448 return null; 449 } 450 451 @Override 452 URL findResource(String name) { 453 URL res = findResourceInOwn(name); 454 if (res != null) { 455 return res; 456 } 457 if (index != null) { 458 int pos = name.lastIndexOf("/"); 459 // only keep the directory part of the resource 460 // as index.list only keeps track of directories and root files 461 String indexedName = (pos > 0) ? name.substring(0, pos) : name; 462 ArrayList<URL> urls = index.get(indexedName); 463 if (urls != null) { 464 urls.remove(url); 465 for (URL url : urls) { 466 URLHandler h = getSubHandler(url); 467 if (h != null) { 468 res = h.findResource(name); 469 if (res != null) { 470 return res; 471 } 472 } 473 } 474 } 475 } 476 return null; 477 } 478 479 private synchronized URLHandler getSubHandler(URL url) { 480 URLHandler sub = subHandlers.get(url); 481 if (sub != null) { 482 return sub; 483 } 484 String protocol = url.getProtocol(); 485 if (protocol.equals("jar")) { 486 sub = createURLJarHandler(url); 487 } else if (protocol.equals("file")) { 488 sub = createURLSubJarHandler(url); 489 } else { 490 sub = createURLHandler(url); 491 } 492 if (sub != null) { 493 subHandlers.put(url, sub); 494 } 495 return sub; 496 } 497 498 private URLHandler createURLSubJarHandler(URL url) { 499 String prefixName; 500 String file = url.getFile(); 501 if (url.getFile().endsWith("!/")) { 502 prefixName = ""; 503 } else { 504 int sepIdx = file.lastIndexOf("!/"); 505 if (sepIdx == -1) { 506 // Invalid URL, don't look here again 507 return null; 508 } 509 sepIdx += 2; 510 prefixName = file.substring(sepIdx); 511 } 512 try { 513 URL jarURL = ((JarURLConnection) url 514 .openConnection()).getJarFileURL(); 515 JarURLConnection juc = (JarURLConnection) new URL( 516 "jar", "", 517 jarURL.toExternalForm() + "!/").openConnection(); 518 JarFile jf = juc.getJarFile(); 519 URLJarHandler jarH = new URLJarHandler(url, jarURL, jf, prefixName, null); 520 // TODO : to think what we should do with indexes & manifest.class file here 521 return jarH; 522 } catch (IOException e) { 523 } 524 return null; 525 } 526 527 } 528 529 class URLFileHandler extends URLHandler { 530 private String prefix; 531 532 public URLFileHandler(URL url) { 533 super(url); 534 String baseFile = url.getFile(); 535 String host = url.getHost(); 536 int hostLength = 0; 537 if (host != null) { 538 hostLength = host.length(); 539 } 540 StringBuilder buf = new StringBuilder(2 + hostLength 541 + baseFile.length()); 542 if (hostLength > 0) { 543 buf.append("//").append(host); 544 } 545 // baseFile always ends with '/' 546 buf.append(baseFile); 547 prefix = buf.toString(); 548 } 549 550 @Override 551 Class<?> findClass(String packageName, String name, String origName) { 552 String filename = prefix + name; 553 try { 554 filename = URLDecoder.decode(filename, "UTF-8"); 555 } catch (IllegalArgumentException e) { 556 return null; 557 } catch (UnsupportedEncodingException e) { 558 return null; 559 } 560 561 File file = new File(filename); 562 if (file.exists()) { 563 try { 564 InputStream is = new FileInputStream(file); 565 return createClass(is, packageName, origName); 566 } catch (FileNotFoundException e) { 567 } 568 } 569 return null; 570 } 571 572 @Override 573 URL findResource(String name) { 574 int idx = 0; 575 String filename; 576 577 // Do not create a UNC path, i.e. \\host 578 while (idx < name.length() && 579 ((name.charAt(idx) == '/') || (name.charAt(idx) == '\\'))) { 580 idx++; 581 } 582 583 if (idx > 0) { 584 name = name.substring(idx); 585 } 586 587 try { 588 filename = URLDecoder.decode(prefix, "UTF-8") + name; 589 590 if (new File(filename).exists()) { 591 return targetURL(url, name); 592 } 593 return null; 594 } catch (IllegalArgumentException e) { 595 return null; 596 } catch (UnsupportedEncodingException e) { 597 // must not happen 598 throw new AssertionError(e); 599 } 600 } 601 602 } 603 604 605 /** 606 * Constructs a new {@code URLClassLoader} instance. The newly created 607 * instance will have the system ClassLoader as its parent. URLs that end 608 * with "/" are assumed to be directories, otherwise they are assumed to be 609 * JAR files. 610 * 611 * @param urls 612 * the list of URLs where a specific class or file could be 613 * found. 614 * @throws SecurityException 615 * if a security manager exists and its {@code 616 * checkCreateClassLoader()} method doesn't allow creation of 617 * new ClassLoaders. 618 */ 619 public URLClassLoader(URL[] urls) { 620 this(urls, ClassLoader.getSystemClassLoader(), null); 621 } 622 623 /** 624 * Constructs a new URLClassLoader instance. The newly created instance will 625 * have the system ClassLoader as its parent. URLs that end with "/" are 626 * assumed to be directories, otherwise they are assumed to be JAR files. 627 * 628 * @param urls 629 * the list of URLs where a specific class or file could be 630 * found. 631 * @param parent 632 * the class loader to assign as this loader's parent. 633 * @throws SecurityException 634 * if a security manager exists and its {@code 635 * checkCreateClassLoader()} method doesn't allow creation of 636 * new class loaders. 637 */ 638 public URLClassLoader(URL[] urls, ClassLoader parent) { 639 this(urls, parent, null); 640 } 641 642 /** 643 * Adds the specified URL to the search list. 644 * 645 * @param url 646 * the URL which is to add. 647 */ 648 protected void addURL(URL url) { 649 try { 650 originalUrls.add(url); 651 searchList.add(createSearchURL(url)); 652 } catch (MalformedURLException e) { 653 } 654 } 655 656 /** 657 * Returns all known URLs which point to the specified resource. 658 * 659 * @param name 660 * the name of the requested resource. 661 * @return the enumeration of URLs which point to the specified resource. 662 * @throws IOException 663 * if an I/O error occurs while attempting to connect. 664 */ 665 @Override 666 public Enumeration<URL> findResources(final String name) throws IOException { 667 if (name == null) { 668 return null; 669 } 670 ArrayList<URL> result = AccessController.doPrivileged( 671 new PrivilegedAction<ArrayList<URL>>() { 672 public ArrayList<URL> run() { 673 ArrayList<URL> results = new ArrayList<URL>(); 674 findResourcesImpl(name, results); 675 return results; 676 } 677 }, currentContext); 678 SecurityManager sm; 679 int length = result.size(); 680 if (length > 0 && (sm = System.getSecurityManager()) != null) { 681 ArrayList<URL> reduced = new ArrayList<URL>(length); 682 for (int i = 0; i < length; i++) { 683 URL url = result.get(i); 684 try { 685 sm.checkPermission(url.openConnection().getPermission()); 686 reduced.add(url); 687 } catch (IOException e) { 688 } catch (SecurityException e) { 689 } 690 } 691 result = reduced; 692 } 693 return Collections.enumeration(result); 694 } 695 696 void findResourcesImpl(String name, ArrayList<URL> result) { 697 int n = 0; 698 while (true) { 699 URLHandler handler = getHandler(n++); 700 if (handler == null) { 701 break; 702 } 703 handler.findResources(name, result); 704 } 705 } 706 707 708 /** 709 * Converts an input stream into a byte array. 710 * 711 * @param is 712 * the input stream 713 * @return byte[] the byte array 714 */ 715 private static byte[] getBytes(InputStream is) 716 throws IOException { 717 byte[] buf = new byte[4096]; 718 ByteArrayOutputStream bos = new ByteArrayOutputStream(4096); 719 int count; 720 while ((count = is.read(buf)) > 0) { 721 bos.write(buf, 0, count); 722 } 723 return bos.toByteArray(); 724 } 725 726 /** 727 * Gets all permissions for the specified {@code codesource}. First, this 728 * method retrieves the permissions from the system policy. If the protocol 729 * is "file:/" then a new permission, {@code FilePermission}, granting the 730 * read permission to the file is added to the permission collection. 731 * Otherwise, connecting to and accepting connections from the URL is 732 * granted. 733 * 734 * @param codesource 735 * the code source object whose permissions have to be known. 736 * @return the list of permissions according to the code source object. 737 */ 738 @Override 739 protected PermissionCollection getPermissions(final CodeSource codesource) { 740 PermissionCollection pc = super.getPermissions(codesource); 741 URL u = codesource.getLocation(); 742 if (u.getProtocol().equals("jar")) { 743 try { 744 // Create a URL for the resource the jar refers to 745 u = ((JarURLConnection) u.openConnection()).getJarFileURL(); 746 } catch (IOException e) { 747 // This should never occur. If it does continue using the jar 748 // URL 749 } 750 } 751 if (u.getProtocol().equals("file")) { 752 String path = u.getFile(); 753 String host = u.getHost(); 754 if (host != null && host.length() > 0) { 755 path = "//" + host + path; 756 } 757 758 if (File.separatorChar != '/') { 759 path = path.replace('/', File.separatorChar); 760 } 761 if (isDirectory(u)) { 762 pc.add(new FilePermission(path + "-", "read")); 763 } else { 764 pc.add(new FilePermission(path, "read")); 765 } 766 } else { 767 String host = u.getHost(); 768 if (host.length() == 0) { 769 host = "localhost"; 770 } 771 pc.add(new SocketPermission(host, "connect, accept")); 772 } 773 return pc; 774 } 775 776 /** 777 * Returns the search list of this {@code URLClassLoader}. 778 * 779 * @return the list of all known URLs of this instance. 780 */ 781 public URL[] getURLs() { 782 return originalUrls.toArray(new URL[originalUrls.size()]); 783 } 784 785 /** 786 * Determines if the URL is pointing to a directory. 787 */ 788 private static boolean isDirectory(URL url) { 789 String file = url.getFile(); 790 return (file.length() > 0 && file.charAt(file.length() - 1) == '/'); 791 } 792 793 /** 794 * Returns a new {@code URLClassLoader} instance for the given URLs and the 795 * system {@code ClassLoader} as its parent. The method {@code loadClass()} 796 * of the new instance will call {@code 797 * SecurityManager.checkPackageAccess()} before loading a class. 798 * 799 * @param urls 800 * the list of URLs that is passed to the new {@code 801 * URLClassloader}. 802 * @return the created {@code URLClassLoader} instance. 803 */ 804 public static URLClassLoader newInstance(final URL[] urls) { 805 URLClassLoader sub = AccessController 806 .doPrivileged(new PrivilegedAction<URLClassLoader>() { 807 public URLClassLoader run() { 808 return new SubURLClassLoader(urls); 809 } 810 }); 811 sub.currentContext = AccessController.getContext(); 812 return sub; 813 } 814 815 /** 816 * Returns a new {@code URLClassLoader} instance for the given URLs and the 817 * specified {@code ClassLoader} as its parent. The method {@code 818 * loadClass()} of the new instance will call the SecurityManager's {@code 819 * checkPackageAccess()} before loading a class. 820 * 821 * @param urls 822 * the list of URLs that is passed to the new URLClassloader. 823 * @param parentCl 824 * the parent class loader that is passed to the new 825 * URLClassloader. 826 * @return the created {@code URLClassLoader} instance. 827 */ 828 public static URLClassLoader newInstance(final URL[] urls, 829 final ClassLoader parentCl) { 830 URLClassLoader sub = AccessController 831 .doPrivileged(new PrivilegedAction<URLClassLoader>() { 832 public URLClassLoader run() { 833 return new SubURLClassLoader(urls, parentCl); 834 } 835 }); 836 sub.currentContext = AccessController.getContext(); 837 return sub; 838 } 839 840 /** 841 * Constructs a new {@code URLClassLoader} instance. The newly created 842 * instance will have the specified {@code ClassLoader} as its parent and 843 * use the specified factory to create stream handlers. URLs that end with 844 * "/" are assumed to be directories, otherwise they are assumed to be JAR 845 * files. 846 * 847 * @param searchUrls 848 * the list of URLs where a specific class or file could be 849 * found. 850 * @param parent 851 * the {@code ClassLoader} to assign as this loader's parent. 852 * @param factory 853 * the factory that will be used to create protocol-specific 854 * stream handlers. 855 * @throws SecurityException 856 * if a security manager exists and its {@code 857 * checkCreateClassLoader()} method doesn't allow creation of 858 * new {@code ClassLoader}s. 859 */ 860 public URLClassLoader(URL[] searchUrls, ClassLoader parent, 861 URLStreamHandlerFactory factory) { 862 super(parent); 863 // Required for pre-v1.2 security managers to work 864 SecurityManager security = System.getSecurityManager(); 865 if (security != null) { 866 security.checkCreateClassLoader(); 867 } 868 this.factory = factory; 869 // capture the context of the thread that creates this URLClassLoader 870 currentContext = AccessController.getContext(); 871 int nbUrls = searchUrls.length; 872 originalUrls = new ArrayList<URL>(nbUrls); 873 handlerList = new ArrayList<URLHandler>(nbUrls); 874 searchList = Collections.synchronizedList(new ArrayList<URL>(nbUrls)); 875 for (int i = 0; i < nbUrls; i++) { 876 originalUrls.add(searchUrls[i]); 877 try { 878 searchList.add(createSearchURL(searchUrls[i])); 879 } catch (MalformedURLException e) { 880 } 881 } 882 } 883 884 /** 885 * Tries to locate and load the specified class using the known URLs. If the 886 * class could be found, a class object representing the loaded class will 887 * be returned. 888 * 889 * @param clsName 890 * the name of the class which has to be found. 891 * @return the class that has been loaded. 892 * @throws ClassNotFoundException 893 * if the specified class cannot be loaded. 894 */ 895 @Override 896 protected Class<?> findClass(final String clsName) 897 throws ClassNotFoundException { 898 Class<?> cls = AccessController.doPrivileged( 899 new PrivilegedAction<Class<?>>() { 900 public Class<?> run() { 901 return findClassImpl(clsName); 902 } 903 }, currentContext); 904 if (cls != null) { 905 return cls; 906 } 907 throw new ClassNotFoundException(clsName); 908 } 909 910 /** 911 * Returns an URL that will be checked if it contains the class or resource. 912 * If the file component of the URL is not a directory, a Jar URL will be 913 * created. 914 * 915 * @return java.net.URL a test URL 916 */ 917 private URL createSearchURL(URL url) throws MalformedURLException { 918 if (url == null) { 919 return url; 920 } 921 922 String protocol = url.getProtocol(); 923 924 if (isDirectory(url) || protocol.equals("jar")) { 925 return url; 926 } 927 if (factory == null) { 928 return new URL("jar", "", 929 -1, url.toString() + "!/"); 930 } 931 // use jar protocol as the stream handler protocol 932 return new URL("jar", "", 933 -1, url.toString() + "!/", 934 factory.createURLStreamHandler("jar")); 935 } 936 937 /** 938 * Returns an URL referencing the specified resource or {@code null} if the 939 * resource could not be found. 940 * 941 * @param name 942 * the name of the requested resource. 943 * @return the URL which points to the given resource. 944 */ 945 @Override 946 public URL findResource(final String name) { 947 if (name == null) { 948 return null; 949 } 950 URL result = AccessController.doPrivileged(new PrivilegedAction<URL>() { 951 public URL run() { 952 return findResourceImpl(name); 953 } 954 }, currentContext); 955 SecurityManager sm; 956 if (result != null && (sm = System.getSecurityManager()) != null) { 957 try { 958 sm.checkPermission(result.openConnection().getPermission()); 959 } catch (IOException e) { 960 return null; 961 } catch (SecurityException e) { 962 return null; 963 } 964 } 965 return result; 966 } 967 968 /** 969 * Returns a URL among the given ones referencing the specified resource or 970 * null if no resource could be found. 971 * 972 * @param resName java.lang.String the name of the requested resource 973 * @return URL URL for the resource. 974 */ 975 URL findResourceImpl(String resName) { 976 int n = 0; 977 978 while (true) { 979 URLHandler handler = getHandler(n++); 980 if (handler == null) { 981 break; 982 } 983 URL res = handler.findResource(resName); 984 if (res != null) { 985 return res; 986 } 987 } 988 return null; 989 } 990 991 URLHandler getHandler(int num) { 992 if (num < handlerList.size()) { 993 return handlerList.get(num); 994 } 995 makeNewHandler(); 996 if (num < handlerList.size()) { 997 return handlerList.get(num); 998 } 999 return null; 1000 } 1001 1002 private synchronized void makeNewHandler() { 1003 while (!searchList.isEmpty()) { 1004 URL nextCandidate = searchList.remove(0); 1005 if (nextCandidate == null) { // KA024=One of urls is null 1006 throw new NullPointerException(Msg.getString("KA024")); 1007 } 1008 if (!handlerMap.containsKey(nextCandidate)) { 1009 URLHandler result; 1010 String protocol = nextCandidate.getProtocol(); 1011 if (protocol.equals("jar")) { 1012 result = createURLJarHandler(nextCandidate); 1013 } else if (protocol.equals("file")) { 1014 result = createURLFileHandler(nextCandidate); 1015 } else { 1016 result = createURLHandler(nextCandidate); 1017 } 1018 if (result != null) { 1019 handlerMap.put(nextCandidate, result); 1020 handlerList.add(result); 1021 return; 1022 } 1023 } 1024 } 1025 } 1026 1027 private URLHandler createURLHandler(URL url) { 1028 return new URLHandler(url); 1029 } 1030 1031 private URLHandler createURLFileHandler(URL url) { 1032 return new URLFileHandler(url); 1033 } 1034 1035 private URLHandler createURLJarHandler(URL url) { 1036 String prefixName; 1037 String file = url.getFile(); 1038 if (url.getFile().endsWith("!/")) { 1039 prefixName = ""; 1040 } else { 1041 int sepIdx = file.lastIndexOf("!/"); 1042 if (sepIdx == -1) { 1043 // Invalid URL, don't look here again 1044 return null; 1045 } 1046 sepIdx += 2; 1047 prefixName = file.substring(sepIdx); 1048 } 1049 try { 1050 URL jarURL = ((JarURLConnection) url 1051 .openConnection()).getJarFileURL(); 1052 JarURLConnection juc = (JarURLConnection) new URL( 1053 "jar", "", 1054 jarURL.toExternalForm() + "!/").openConnection(); 1055 JarFile jf = juc.getJarFile(); 1056 URLJarHandler jarH = new URLJarHandler(url, jarURL, jf, prefixName); 1057 1058 if (jarH.getIndex() == null) { 1059 try { 1060 Manifest manifest = jf.getManifest(); 1061 if (manifest != null) { 1062 String classpath = manifest.getMainAttributes().getValue( 1063 Attributes.Name.CLASS_PATH); 1064 if (classpath != null) { 1065 searchList.addAll(0, getInternalURLs(url, classpath)); 1066 } 1067 } 1068 } catch (IOException e) { 1069 } 1070 } 1071 return jarH; 1072 } catch (IOException e) { 1073 } 1074 return null; 1075 } 1076 1077 /** 1078 * Defines a new package using the information extracted from the specified 1079 * manifest. 1080 * 1081 * @param packageName 1082 * the name of the new package. 1083 * @param manifest 1084 * the manifest containing additional information for the new 1085 * package. 1086 * @param url 1087 * the URL to the code source for the new package. 1088 * @return the created package. 1089 * @throws IllegalArgumentException 1090 * if a package with the given name already exists. 1091 */ 1092 protected Package definePackage(String packageName, Manifest manifest, 1093 URL url) throws IllegalArgumentException { 1094 Attributes mainAttributes = manifest.getMainAttributes(); 1095 String dirName = packageName.replace('.', '/') + "/"; 1096 Attributes packageAttributes = manifest.getAttributes(dirName); 1097 boolean noEntry = false; 1098 if (packageAttributes == null) { 1099 noEntry = true; 1100 packageAttributes = mainAttributes; 1101 } 1102 String specificationTitle = packageAttributes 1103 .getValue(Attributes.Name.SPECIFICATION_TITLE); 1104 if (specificationTitle == null && !noEntry) { 1105 specificationTitle = mainAttributes 1106 .getValue(Attributes.Name.SPECIFICATION_TITLE); 1107 } 1108 String specificationVersion = packageAttributes 1109 .getValue(Attributes.Name.SPECIFICATION_VERSION); 1110 if (specificationVersion == null && !noEntry) { 1111 specificationVersion = mainAttributes 1112 .getValue(Attributes.Name.SPECIFICATION_VERSION); 1113 } 1114 String specificationVendor = packageAttributes 1115 .getValue(Attributes.Name.SPECIFICATION_VENDOR); 1116 if (specificationVendor == null && !noEntry) { 1117 specificationVendor = mainAttributes 1118 .getValue(Attributes.Name.SPECIFICATION_VENDOR); 1119 } 1120 String implementationTitle = packageAttributes 1121 .getValue(Attributes.Name.IMPLEMENTATION_TITLE); 1122 if (implementationTitle == null && !noEntry) { 1123 implementationTitle = mainAttributes 1124 .getValue(Attributes.Name.IMPLEMENTATION_TITLE); 1125 } 1126 String implementationVersion = packageAttributes 1127 .getValue(Attributes.Name.IMPLEMENTATION_VERSION); 1128 if (implementationVersion == null && !noEntry) { 1129 implementationVersion = mainAttributes 1130 .getValue(Attributes.Name.IMPLEMENTATION_VERSION); 1131 } 1132 String implementationVendor = packageAttributes 1133 .getValue(Attributes.Name.IMPLEMENTATION_VENDOR); 1134 if (implementationVendor == null && !noEntry) { 1135 implementationVendor = mainAttributes 1136 .getValue(Attributes.Name.IMPLEMENTATION_VENDOR); 1137 } 1138 1139 return definePackage(packageName, specificationTitle, 1140 specificationVersion, specificationVendor, implementationTitle, 1141 implementationVersion, implementationVendor, isSealed(manifest, 1142 dirName) ? url : null); 1143 } 1144 1145 private boolean isSealed(Manifest manifest, String dirName) { 1146 Attributes mainAttributes = manifest.getMainAttributes(); 1147 String value = mainAttributes.getValue(Attributes.Name.SEALED); 1148 boolean sealed = value != null && value.toLowerCase().equals("true"); 1149 Attributes attributes = manifest.getAttributes(dirName); 1150 if (attributes != null) { 1151 value = attributes.getValue(Attributes.Name.SEALED); 1152 if (value != null) { 1153 sealed = value.toLowerCase().equals("true"); 1154 } 1155 } 1156 return sealed; 1157 } 1158 1159 /** 1160 * returns URLs referenced in the string classpath. 1161 * 1162 * @param root 1163 * the jar URL that classpath is related to 1164 * @param classpath 1165 * the relative URLs separated by spaces 1166 * @return URL[] the URLs contained in the string classpath. 1167 */ 1168 private ArrayList<URL> getInternalURLs(URL root, String classpath) { 1169 // Class-path attribute is composed of space-separated values. 1170 StringTokenizer tokenizer = new StringTokenizer(classpath); 1171 ArrayList<URL> addedURLs = new ArrayList<URL>(); 1172 String file = root.getFile(); 1173 int jarIndex = file.lastIndexOf("!/") - 1; 1174 int index = file.lastIndexOf("/", jarIndex) + 1; 1175 if (index == 0) { 1176 index = file.lastIndexOf( 1177 System.getProperty("file.separator"), jarIndex) + 1; 1178 } 1179 file = file.substring(0, index); 1180 while (tokenizer.hasMoreElements()) { 1181 String element = tokenizer.nextToken(); 1182 if (!element.isEmpty()) { 1183 try { 1184 // Take absolute path case into consideration 1185 URL url = new URL(new URL(file), element); 1186 addedURLs.add(createSearchURL(url)); 1187 } catch (MalformedURLException e) { 1188 // Nothing is added 1189 } 1190 } 1191 } 1192 return addedURLs; 1193 } 1194 1195 Class<?> findClassImpl(String className) { 1196 String partialName = className.replace('.', '/'); 1197 final String classFileName = new StringBuilder(partialName).append(".class").toString(); 1198 String packageName = null; 1199 int position = partialName.lastIndexOf('/'); 1200 if ((position = partialName.lastIndexOf('/')) != -1) { 1201 packageName = partialName.substring(0, position); 1202 } 1203 int n = 0; 1204 while (true) { 1205 URLHandler handler = getHandler(n++); 1206 if (handler == null) { 1207 break; 1208 } 1209 Class<?> res = handler.findClass(packageName, classFileName, className); 1210 if (res != null) { 1211 return res; 1212 } 1213 } 1214 return null; 1215 1216 } 1217 1218} 1219