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.File; 22import java.io.FileInputStream; 23import java.io.FileNotFoundException; 24import java.io.FilePermission; 25import java.io.IOException; 26import java.io.InputStream; 27import java.io.InputStreamReader; 28import java.io.UnsupportedEncodingException; 29import java.nio.charset.Charsets; 30import java.security.CodeSource; 31import java.security.PermissionCollection; 32import java.security.SecureClassLoader; 33import java.security.cert.Certificate; 34import java.util.ArrayList; 35import java.util.Collections; 36import java.util.Enumeration; 37import java.util.HashMap; 38import java.util.List; 39import java.util.Map; 40import java.util.StringTokenizer; 41import java.util.jar.Attributes; 42import java.util.jar.JarEntry; 43import java.util.jar.JarFile; 44import java.util.jar.Manifest; 45import libcore.io.IoUtils; 46import libcore.io.Streams; 47 48/** 49 * This class loader is responsible for loading classes and resources from a 50 * list of URLs which can refer to either directories or JAR files. Classes 51 * loaded by this {@code URLClassLoader} are granted permission to access the 52 * URLs contained in the URL search list. 53 */ 54@FindBugsSuppressWarnings({ "DMI_COLLECTION_OF_URLS", "DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED" }) 55public class URLClassLoader extends SecureClassLoader { 56 57 ArrayList<URL> originalUrls; 58 59 List<URL> searchList; 60 ArrayList<URLHandler> handlerList; 61 Map<URL, URLHandler> handlerMap = new HashMap<URL, URLHandler>(); 62 63 private URLStreamHandlerFactory factory; 64 65 static class IndexFile { 66 67 private HashMap<String, ArrayList<URL>> map; 68 //private URLClassLoader host; 69 70 71 static IndexFile readIndexFile(JarFile jf, JarEntry indexEntry, URL url) { 72 BufferedReader in = null; 73 InputStream is = null; 74 try { 75 // Add mappings from resource to jar file 76 String parentURLString = getParentURL(url).toExternalForm(); 77 String prefix = "jar:" + parentURLString + "/"; 78 is = jf.getInputStream(indexEntry); 79 in = new BufferedReader(new InputStreamReader(is, Charsets.UTF_8)); 80 HashMap<String, ArrayList<URL>> pre_map = new HashMap<String, ArrayList<URL>>(); 81 // Ignore the 2 first lines (index version) 82 if (in.readLine() == null) return null; 83 if (in.readLine() == null) return null; 84 TOP_CYCLE: 85 while (true) { 86 String line = in.readLine(); 87 if (line == null) { 88 break; 89 } 90 URL jar = new URL(prefix + line + "!/"); 91 while (true) { 92 line = in.readLine(); 93 if (line == null) { 94 break TOP_CYCLE; 95 } 96 if (line.isEmpty()) { 97 break; 98 } 99 ArrayList<URL> list; 100 if (pre_map.containsKey(line)) { 101 list = pre_map.get(line); 102 } else { 103 list = new ArrayList<URL>(); 104 pre_map.put(line, list); 105 } 106 list.add(jar); 107 } 108 } 109 if (!pre_map.isEmpty()) { 110 return new IndexFile(pre_map); 111 } 112 } catch (MalformedURLException e) { 113 // Ignore this jar's index 114 } catch (IOException e) { 115 // Ignore this jar's index 116 } finally { 117 IoUtils.closeQuietly(in); 118 IoUtils.closeQuietly(is); 119 } 120 return null; 121 } 122 123 private static URL getParentURL(URL url) throws IOException { 124 URL fileURL = ((JarURLConnection) url.openConnection()).getJarFileURL(); 125 String file = fileURL.getFile(); 126 String parentFile = new File(file).getParent(); 127 parentFile = parentFile.replace(File.separatorChar, '/'); 128 if (parentFile.charAt(0) != '/') { 129 parentFile = "/" + parentFile; 130 } 131 URL parentURL = new URL(fileURL.getProtocol(), fileURL 132 .getHost(), fileURL.getPort(), parentFile); 133 return parentURL; 134 } 135 136 public IndexFile(HashMap<String, ArrayList<URL>> map) { 137 this.map = map; 138 } 139 140 ArrayList<URL> get(String name) { 141 return map.get(name); 142 } 143 } 144 145 class URLHandler { 146 URL url; 147 URL codeSourceUrl; 148 149 public URLHandler(URL url) { 150 this.url = url; 151 this.codeSourceUrl = url; 152 } 153 154 void findResources(String name, ArrayList<URL> resources) { 155 URL res = findResource(name); 156 if (res != null && !resources.contains(res)) { 157 resources.add(res); 158 } 159 } 160 161 Class<?> findClass(String packageName, String name, String origName) { 162 URL resURL = targetURL(url, name); 163 if (resURL != null) { 164 try { 165 InputStream is = resURL.openStream(); 166 return createClass(is, packageName, origName); 167 } catch (IOException e) { 168 } 169 } 170 return null; 171 } 172 173 174 Class<?> createClass(InputStream is, String packageName, String origName) { 175 if (is == null) { 176 return null; 177 } 178 byte[] clBuf; 179 try { 180 clBuf = Streams.readFully(is); 181 } catch (IOException e) { 182 return null; 183 } 184 if (packageName != null) { 185 String packageDotName = packageName.replace('/', '.'); 186 Package packageObj = getPackage(packageDotName); 187 if (packageObj == null) { 188 definePackage(packageDotName, null, null, 189 null, null, null, null, null); 190 } else { 191 if (packageObj.isSealed()) { 192 throw new SecurityException("Package is sealed"); 193 } 194 } 195 } 196 return defineClass(origName, clBuf, 0, clBuf.length, new CodeSource(codeSourceUrl, (Certificate[]) null)); 197 } 198 199 URL findResource(String name) { 200 URL resURL = targetURL(url, name); 201 if (resURL != null) { 202 try { 203 URLConnection uc = resURL.openConnection(); 204 uc.getInputStream().close(); 205 // HTTP can return a stream on a non-existent file 206 // So check for the return code; 207 if (!resURL.getProtocol().equals("http")) { 208 return resURL; 209 } 210 int code; 211 if ((code = ((HttpURLConnection) uc).getResponseCode()) >= 200 212 && code < 300) { 213 return resURL; 214 } 215 } catch (SecurityException e) { 216 return null; 217 } catch (IOException e) { 218 return null; 219 } 220 } 221 return null; 222 } 223 224 URL targetURL(URL base, String name) { 225 try { 226 StringBuilder fileBuilder = new StringBuilder(); 227 fileBuilder.append(base.getFile()); 228 URI.PATH_ENCODER.appendEncoded(fileBuilder, name); 229 String file = fileBuilder.toString(); 230 231 return new URL(base.getProtocol(), base.getHost(), base.getPort(), file, null); 232 } catch (MalformedURLException e) { 233 return null; 234 } 235 } 236 237 } 238 239 class URLJarHandler extends URLHandler { 240 final JarFile jf; 241 final String prefixName; 242 final IndexFile index; 243 final Map<URL, URLHandler> subHandlers = new HashMap<URL, URLHandler>(); 244 245 public URLJarHandler(URL url, URL jarURL, JarFile jf, String prefixName) { 246 super(url); 247 this.jf = jf; 248 this.prefixName = prefixName; 249 this.codeSourceUrl = jarURL; 250 final JarEntry je = jf.getJarEntry("META-INF/INDEX.LIST"); 251 this.index = (je == null ? null : IndexFile.readIndexFile(jf, je, url)); 252 } 253 254 public URLJarHandler(URL url, URL jarURL, JarFile jf, String prefixName, IndexFile index) { 255 super(url); 256 this.jf = jf; 257 this.prefixName = prefixName; 258 this.index = index; 259 this.codeSourceUrl = jarURL; 260 } 261 262 IndexFile getIndex() { 263 return index; 264 } 265 266 @Override 267 void findResources(String name, ArrayList<URL> resources) { 268 URL res = findResourceInOwn(name); 269 if (res != null && !resources.contains(res)) { 270 resources.add(res); 271 } 272 if (index != null) { 273 int pos = name.lastIndexOf("/"); 274 // only keep the directory part of the resource 275 // as index.list only keeps track of directories and root files 276 String indexedName = (pos > 0) ? name.substring(0, pos) : name; 277 ArrayList<URL> urls = index.get(indexedName); 278 if (urls != null) { 279 urls.remove(url); 280 for (URL url : urls) { 281 URLHandler h = getSubHandler(url); 282 if (h != null) { 283 h.findResources(name, resources); 284 } 285 } 286 } 287 } 288 289 } 290 291 @Override 292 Class<?> findClass(String packageName, String name, String origName) { 293 String entryName = prefixName + name; 294 JarEntry entry = jf.getJarEntry(entryName); 295 if (entry != null) { 296 /** 297 * Avoid recursive load class, especially the class 298 * is an implementation class of security provider 299 * and the jar is signed. 300 */ 301 try { 302 Manifest manifest = jf.getManifest(); 303 return createClass(entry, manifest, packageName, origName); 304 } catch (IOException e) { 305 } 306 } 307 if (index != null) { 308 ArrayList<URL> urls; 309 if (packageName == null) { 310 urls = index.get(name); 311 } else { 312 urls = index.get(packageName); 313 } 314 if (urls != null) { 315 urls.remove(url); 316 for (URL url : urls) { 317 URLHandler h = getSubHandler(url); 318 if (h != null) { 319 Class<?> res = h.findClass(packageName, name, origName); 320 if (res != null) { 321 return res; 322 } 323 } 324 } 325 } 326 } 327 return null; 328 } 329 330 private Class<?> createClass(JarEntry entry, Manifest manifest, String packageName, String origName) { 331 byte[] clBuf; 332 try { 333 InputStream is = jf.getInputStream(entry); 334 clBuf = Streams.readFully(is); 335 } catch (IOException e) { 336 return null; 337 } 338 if (packageName != null) { 339 String packageDotName = packageName.replace('/', '.'); 340 Package packageObj = getPackage(packageDotName); 341 if (packageObj == null) { 342 if (manifest != null) { 343 definePackage(packageDotName, manifest, 344 codeSourceUrl); 345 } else { 346 definePackage(packageDotName, null, null, 347 null, null, null, null, null); 348 } 349 } else { 350 boolean exception = packageObj.isSealed(); 351 if (manifest != null) { 352 if (isSealed(manifest, packageName + "/")) { 353 exception = !packageObj 354 .isSealed(codeSourceUrl); 355 } 356 } 357 if (exception) { 358 throw new SecurityException(String.format("Package %s is sealed", 359 packageName)); 360 } 361 } 362 } 363 CodeSource codeS = new CodeSource(codeSourceUrl, entry.getCertificates()); 364 return defineClass(origName, clBuf, 0, clBuf.length, codeS); 365 } 366 367 URL findResourceInOwn(String name) { 368 String entryName = prefixName + name; 369 if (jf.getEntry(entryName) != null) { 370 return targetURL(url, name); 371 } 372 return null; 373 } 374 375 @Override 376 URL findResource(String name) { 377 URL res = findResourceInOwn(name); 378 if (res != null) { 379 return res; 380 } 381 if (index != null) { 382 int pos = name.lastIndexOf("/"); 383 // only keep the directory part of the resource 384 // as index.list only keeps track of directories and root files 385 String indexedName = (pos > 0) ? name.substring(0, pos) : name; 386 ArrayList<URL> urls = index.get(indexedName); 387 if (urls != null) { 388 urls.remove(url); 389 for (URL url : urls) { 390 URLHandler h = getSubHandler(url); 391 if (h != null) { 392 res = h.findResource(name); 393 if (res != null) { 394 return res; 395 } 396 } 397 } 398 } 399 } 400 return null; 401 } 402 403 private synchronized URLHandler getSubHandler(URL url) { 404 URLHandler sub = subHandlers.get(url); 405 if (sub != null) { 406 return sub; 407 } 408 String protocol = url.getProtocol(); 409 if (protocol.equals("jar")) { 410 sub = createURLJarHandler(url); 411 } else if (protocol.equals("file")) { 412 sub = createURLSubJarHandler(url); 413 } else { 414 sub = createURLHandler(url); 415 } 416 if (sub != null) { 417 subHandlers.put(url, sub); 418 } 419 return sub; 420 } 421 422 private URLHandler createURLSubJarHandler(URL url) { 423 String prefixName; 424 String file = url.getFile(); 425 if (url.getFile().endsWith("!/")) { 426 prefixName = ""; 427 } else { 428 int sepIdx = file.lastIndexOf("!/"); 429 if (sepIdx == -1) { 430 // Invalid URL, don't look here again 431 return null; 432 } 433 sepIdx += 2; 434 prefixName = file.substring(sepIdx); 435 } 436 try { 437 URL jarURL = ((JarURLConnection) url 438 .openConnection()).getJarFileURL(); 439 JarURLConnection juc = (JarURLConnection) new URL( 440 "jar", "", 441 jarURL.toExternalForm() + "!/").openConnection(); 442 JarFile jf = juc.getJarFile(); 443 URLJarHandler jarH = new URLJarHandler(url, jarURL, jf, prefixName, null); 444 // TODO : to think what we should do with indexes & manifest.class file here 445 return jarH; 446 } catch (IOException e) { 447 } 448 return null; 449 } 450 451 } 452 453 class URLFileHandler extends URLHandler { 454 private String prefix; 455 456 public URLFileHandler(URL url) { 457 super(url); 458 String baseFile = url.getFile(); 459 String host = url.getHost(); 460 int hostLength = 0; 461 if (host != null) { 462 hostLength = host.length(); 463 } 464 StringBuilder buf = new StringBuilder(2 + hostLength 465 + baseFile.length()); 466 if (hostLength > 0) { 467 buf.append("//").append(host); 468 } 469 // baseFile always ends with '/' 470 buf.append(baseFile); 471 prefix = buf.toString(); 472 } 473 474 @Override 475 Class<?> findClass(String packageName, String name, String origName) { 476 String filename = prefix + name; 477 try { 478 filename = URLDecoder.decode(filename, "UTF-8"); 479 } catch (IllegalArgumentException e) { 480 return null; 481 } catch (UnsupportedEncodingException e) { 482 return null; 483 } 484 485 File file = new File(filename); 486 if (file.exists()) { 487 try { 488 InputStream is = new FileInputStream(file); 489 return createClass(is, packageName, origName); 490 } catch (FileNotFoundException e) { 491 } 492 } 493 return null; 494 } 495 496 @Override 497 URL findResource(String name) { 498 int idx = 0; 499 String filename; 500 501 // Do not create a UNC path, i.e. \\host 502 while (idx < name.length() && 503 ((name.charAt(idx) == '/') || (name.charAt(idx) == '\\'))) { 504 idx++; 505 } 506 507 if (idx > 0) { 508 name = name.substring(idx); 509 } 510 511 try { 512 filename = URLDecoder.decode(prefix, "UTF-8") + name; 513 514 if (new File(filename).exists()) { 515 return targetURL(url, name); 516 } 517 return null; 518 } catch (IllegalArgumentException e) { 519 return null; 520 } catch (UnsupportedEncodingException e) { 521 // must not happen 522 throw new AssertionError(e); 523 } 524 } 525 526 } 527 528 529 /** 530 * Constructs a new {@code URLClassLoader} instance. The newly created 531 * instance will have the system ClassLoader as its parent. URLs that end 532 * with "/" are assumed to be directories, otherwise they are assumed to be 533 * JAR files. 534 * 535 * @param urls 536 * the list of URLs where a specific class or file could be 537 * found. 538 */ 539 public URLClassLoader(URL[] urls) { 540 this(urls, ClassLoader.getSystemClassLoader(), null); 541 } 542 543 /** 544 * Constructs a new URLClassLoader instance. The newly created instance will 545 * have the system ClassLoader as its parent. URLs that end with "/" are 546 * assumed to be directories, otherwise they are assumed to be JAR files. 547 * 548 * @param urls 549 * the list of URLs where a specific class or file could be 550 * found. 551 * @param parent 552 * the class loader to assign as this loader's parent. 553 */ 554 public URLClassLoader(URL[] urls, ClassLoader parent) { 555 this(urls, parent, null); 556 } 557 558 /** 559 * Adds the specified URL to the search list. 560 * 561 * @param url 562 * the URL which is to add. 563 */ 564 protected void addURL(URL url) { 565 try { 566 originalUrls.add(url); 567 searchList.add(createSearchURL(url)); 568 } catch (MalformedURLException e) { 569 } 570 } 571 572 /** 573 * Returns all known URLs which point to the specified resource. 574 * 575 * @param name 576 * the name of the requested resource. 577 * @return the enumeration of URLs which point to the specified resource. 578 * @throws IOException 579 * if an I/O error occurs while attempting to connect. 580 */ 581 @Override 582 public Enumeration<URL> findResources(final String name) throws IOException { 583 if (name == null) { 584 return null; 585 } 586 ArrayList<URL> result = new ArrayList<URL>(); 587 int n = 0; 588 while (true) { 589 URLHandler handler = getHandler(n++); 590 if (handler == null) { 591 break; 592 } 593 handler.findResources(name, result); 594 } 595 return Collections.enumeration(result); 596 } 597 598 /** 599 * Gets all permissions for the specified {@code codesource}. First, this 600 * method retrieves the permissions from the system policy. If the protocol 601 * is "file:/" then a new permission, {@code FilePermission}, granting the 602 * read permission to the file is added to the permission collection. 603 * Otherwise, connecting to and accepting connections from the URL is 604 * granted. 605 * 606 * @param codesource 607 * the code source object whose permissions have to be known. 608 * @return the list of permissions according to the code source object. 609 */ 610 @Override 611 protected PermissionCollection getPermissions(final CodeSource codesource) { 612 PermissionCollection pc = super.getPermissions(codesource); 613 URL u = codesource.getLocation(); 614 if (u.getProtocol().equals("jar")) { 615 try { 616 // Create a URL for the resource the jar refers to 617 u = ((JarURLConnection) u.openConnection()).getJarFileURL(); 618 } catch (IOException e) { 619 // This should never occur. If it does continue using the jar 620 // URL 621 } 622 } 623 if (u.getProtocol().equals("file")) { 624 String path = u.getFile(); 625 String host = u.getHost(); 626 if (host != null && host.length() > 0) { 627 path = "//" + host + path; 628 } 629 630 if (File.separatorChar != '/') { 631 path = path.replace('/', File.separatorChar); 632 } 633 if (isDirectory(u)) { 634 pc.add(new FilePermission(path + "-", "read")); 635 } else { 636 pc.add(new FilePermission(path, "read")); 637 } 638 } else { 639 String host = u.getHost(); 640 if (host.length() == 0) { 641 host = "localhost"; 642 } 643 pc.add(new SocketPermission(host, "connect, accept")); 644 } 645 return pc; 646 } 647 648 /** 649 * Returns the search list of this {@code URLClassLoader}. 650 * 651 * @return the list of all known URLs of this instance. 652 */ 653 public URL[] getURLs() { 654 return originalUrls.toArray(new URL[originalUrls.size()]); 655 } 656 657 /** 658 * Determines if the URL is pointing to a directory. 659 */ 660 private static boolean isDirectory(URL url) { 661 String file = url.getFile(); 662 return (file.length() > 0 && file.charAt(file.length() - 1) == '/'); 663 } 664 665 /** 666 * Returns a new {@code URLClassLoader} instance for the given URLs and the 667 * system {@code ClassLoader} as its parent. 668 * 669 * @param urls 670 * the list of URLs that is passed to the new {@code 671 * URLClassLoader}. 672 * @return the created {@code URLClassLoader} instance. 673 */ 674 public static URLClassLoader newInstance(final URL[] urls) { 675 return new URLClassLoader(urls, ClassLoader.getSystemClassLoader()); 676 } 677 678 /** 679 * Returns a new {@code URLClassLoader} instance for the given URLs and the 680 * specified {@code ClassLoader} as its parent. 681 * 682 * @param urls 683 * the list of URLs that is passed to the new URLClassLoader. 684 * @param parentCl 685 * the parent class loader that is passed to the new 686 * URLClassLoader. 687 * @return the created {@code URLClassLoader} instance. 688 */ 689 public static URLClassLoader newInstance(final URL[] urls, final ClassLoader parentCl) { 690 return new URLClassLoader(urls, parentCl); 691 } 692 693 /** 694 * Constructs a new {@code URLClassLoader} instance. The newly created 695 * instance will have the specified {@code ClassLoader} as its parent and 696 * use the specified factory to create stream handlers. URLs that end with 697 * "/" are assumed to be directories, otherwise they are assumed to be JAR 698 * files. 699 * 700 * @param searchUrls 701 * the list of URLs where a specific class or file could be 702 * found. 703 * @param parent 704 * the {@code ClassLoader} to assign as this loader's parent. 705 * @param factory 706 * the factory that will be used to create protocol-specific 707 * stream handlers. 708 */ 709 public URLClassLoader(URL[] searchUrls, ClassLoader parent, URLStreamHandlerFactory factory) { 710 super(parent); 711 this.factory = factory; 712 int nbUrls = searchUrls.length; 713 originalUrls = new ArrayList<URL>(nbUrls); 714 handlerList = new ArrayList<URLHandler>(nbUrls); 715 searchList = Collections.synchronizedList(new ArrayList<URL>(nbUrls)); 716 for (int i = 0; i < nbUrls; i++) { 717 originalUrls.add(searchUrls[i]); 718 try { 719 searchList.add(createSearchURL(searchUrls[i])); 720 } catch (MalformedURLException e) { 721 } 722 } 723 } 724 725 /** 726 * Tries to locate and load the specified class using the known URLs. If the 727 * class could be found, a class object representing the loaded class will 728 * be returned. 729 * 730 * @throws ClassNotFoundException 731 * if the specified class cannot be loaded. 732 */ 733 @Override 734 protected Class<?> findClass(final String className) throws ClassNotFoundException { 735 String partialName = className.replace('.', '/'); 736 final String classFileName = new StringBuilder(partialName).append(".class").toString(); 737 String packageName = null; 738 int position = partialName.lastIndexOf('/'); 739 if ((position = partialName.lastIndexOf('/')) != -1) { 740 packageName = partialName.substring(0, position); 741 } 742 int n = 0; 743 while (true) { 744 URLHandler handler = getHandler(n++); 745 if (handler == null) { 746 break; 747 } 748 Class<?> res = handler.findClass(packageName, classFileName, className); 749 if (res != null) { 750 return res; 751 } 752 } 753 throw new ClassNotFoundException(className); 754 } 755 756 /** 757 * Returns an URL that will be checked if it contains the class or resource. 758 * If the file component of the URL is not a directory, a Jar URL will be 759 * created. 760 * 761 * @return java.net.URL a test URL 762 */ 763 private URL createSearchURL(URL url) throws MalformedURLException { 764 if (url == null) { 765 return url; 766 } 767 768 String protocol = url.getProtocol(); 769 770 if (isDirectory(url) || protocol.equals("jar")) { 771 return url; 772 } 773 if (factory == null) { 774 return new URL("jar", "", 775 -1, url.toString() + "!/"); 776 } 777 // use jar protocol as the stream handler protocol 778 return new URL("jar", "", 779 -1, url.toString() + "!/", 780 factory.createURLStreamHandler("jar")); 781 } 782 783 /** 784 * Returns an URL referencing the specified resource or {@code null} if the 785 * resource could not be found. 786 * 787 * @param name 788 * the name of the requested resource. 789 * @return the URL which points to the given resource. 790 */ 791 @Override 792 public URL findResource(final String name) { 793 if (name == null) { 794 return null; 795 } 796 int n = 0; 797 while (true) { 798 URLHandler handler = getHandler(n++); 799 if (handler == null) { 800 break; 801 } 802 URL res = handler.findResource(name); 803 if (res != null) { 804 return res; 805 } 806 } 807 return null; 808 } 809 810 private URLHandler getHandler(int num) { 811 if (num < handlerList.size()) { 812 return handlerList.get(num); 813 } 814 makeNewHandler(); 815 if (num < handlerList.size()) { 816 return handlerList.get(num); 817 } 818 return null; 819 } 820 821 private synchronized void makeNewHandler() { 822 while (!searchList.isEmpty()) { 823 URL nextCandidate = searchList.remove(0); 824 if (nextCandidate == null) { 825 throw new NullPointerException("A URL is null"); 826 } 827 if (!handlerMap.containsKey(nextCandidate)) { 828 URLHandler result; 829 String protocol = nextCandidate.getProtocol(); 830 if (protocol.equals("jar")) { 831 result = createURLJarHandler(nextCandidate); 832 } else if (protocol.equals("file")) { 833 result = createURLFileHandler(nextCandidate); 834 } else { 835 result = createURLHandler(nextCandidate); 836 } 837 if (result != null) { 838 handlerMap.put(nextCandidate, result); 839 handlerList.add(result); 840 return; 841 } 842 } 843 } 844 } 845 846 private URLHandler createURLHandler(URL url) { 847 return new URLHandler(url); 848 } 849 850 private URLHandler createURLFileHandler(URL url) { 851 return new URLFileHandler(url); 852 } 853 854 private URLHandler createURLJarHandler(URL url) { 855 String prefixName; 856 String file = url.getFile(); 857 if (url.getFile().endsWith("!/")) { 858 prefixName = ""; 859 } else { 860 int sepIdx = file.lastIndexOf("!/"); 861 if (sepIdx == -1) { 862 // Invalid URL, don't look here again 863 return null; 864 } 865 sepIdx += 2; 866 prefixName = file.substring(sepIdx); 867 } 868 try { 869 URL jarURL = ((JarURLConnection) url 870 .openConnection()).getJarFileURL(); 871 JarURLConnection juc = (JarURLConnection) new URL( 872 "jar", "", 873 jarURL.toExternalForm() + "!/").openConnection(); 874 JarFile jf = juc.getJarFile(); 875 URLJarHandler jarH = new URLJarHandler(url, jarURL, jf, prefixName); 876 877 if (jarH.getIndex() == null) { 878 try { 879 Manifest manifest = jf.getManifest(); 880 if (manifest != null) { 881 String classpath = manifest.getMainAttributes().getValue( 882 Attributes.Name.CLASS_PATH); 883 if (classpath != null) { 884 searchList.addAll(0, getInternalURLs(url, classpath)); 885 } 886 } 887 } catch (IOException e) { 888 } 889 } 890 return jarH; 891 } catch (IOException e) { 892 } 893 return null; 894 } 895 896 /** 897 * Defines a new package using the information extracted from the specified 898 * manifest. 899 * 900 * @param packageName 901 * the name of the new package. 902 * @param manifest 903 * the manifest containing additional information for the new 904 * package. 905 * @param url 906 * the URL to the code source for the new package. 907 * @return the created package. 908 * @throws IllegalArgumentException 909 * if a package with the given name already exists. 910 */ 911 protected Package definePackage(String packageName, Manifest manifest, 912 URL url) throws IllegalArgumentException { 913 Attributes mainAttributes = manifest.getMainAttributes(); 914 String dirName = packageName.replace('.', '/') + "/"; 915 Attributes packageAttributes = manifest.getAttributes(dirName); 916 boolean noEntry = false; 917 if (packageAttributes == null) { 918 noEntry = true; 919 packageAttributes = mainAttributes; 920 } 921 String specificationTitle = packageAttributes 922 .getValue(Attributes.Name.SPECIFICATION_TITLE); 923 if (specificationTitle == null && !noEntry) { 924 specificationTitle = mainAttributes 925 .getValue(Attributes.Name.SPECIFICATION_TITLE); 926 } 927 String specificationVersion = packageAttributes 928 .getValue(Attributes.Name.SPECIFICATION_VERSION); 929 if (specificationVersion == null && !noEntry) { 930 specificationVersion = mainAttributes 931 .getValue(Attributes.Name.SPECIFICATION_VERSION); 932 } 933 String specificationVendor = packageAttributes 934 .getValue(Attributes.Name.SPECIFICATION_VENDOR); 935 if (specificationVendor == null && !noEntry) { 936 specificationVendor = mainAttributes 937 .getValue(Attributes.Name.SPECIFICATION_VENDOR); 938 } 939 String implementationTitle = packageAttributes 940 .getValue(Attributes.Name.IMPLEMENTATION_TITLE); 941 if (implementationTitle == null && !noEntry) { 942 implementationTitle = mainAttributes 943 .getValue(Attributes.Name.IMPLEMENTATION_TITLE); 944 } 945 String implementationVersion = packageAttributes 946 .getValue(Attributes.Name.IMPLEMENTATION_VERSION); 947 if (implementationVersion == null && !noEntry) { 948 implementationVersion = mainAttributes 949 .getValue(Attributes.Name.IMPLEMENTATION_VERSION); 950 } 951 String implementationVendor = packageAttributes 952 .getValue(Attributes.Name.IMPLEMENTATION_VENDOR); 953 if (implementationVendor == null && !noEntry) { 954 implementationVendor = mainAttributes 955 .getValue(Attributes.Name.IMPLEMENTATION_VENDOR); 956 } 957 958 return definePackage(packageName, specificationTitle, 959 specificationVersion, specificationVendor, implementationTitle, 960 implementationVersion, implementationVendor, isSealed(manifest, 961 dirName) ? url : null); 962 } 963 964 private boolean isSealed(Manifest manifest, String dirName) { 965 Attributes attributes = manifest.getAttributes(dirName); 966 if (attributes != null) { 967 String value = attributes.getValue(Attributes.Name.SEALED); 968 if (value != null) { 969 return value.equalsIgnoreCase("true"); 970 } 971 } 972 Attributes mainAttributes = manifest.getMainAttributes(); 973 String value = mainAttributes.getValue(Attributes.Name.SEALED); 974 return (value != null && value.equalsIgnoreCase("true")); 975 } 976 977 /** 978 * returns URLs referenced in the string classpath. 979 * 980 * @param root 981 * the jar URL that classpath is related to 982 * @param classpath 983 * the relative URLs separated by spaces 984 * @return URL[] the URLs contained in the string classpath. 985 */ 986 private ArrayList<URL> getInternalURLs(URL root, String classpath) { 987 // Class-path attribute is composed of space-separated values. 988 StringTokenizer tokenizer = new StringTokenizer(classpath); 989 ArrayList<URL> addedURLs = new ArrayList<URL>(); 990 String file = root.getFile(); 991 int jarIndex = file.lastIndexOf("!/") - 1; 992 int index = file.lastIndexOf("/", jarIndex) + 1; 993 if (index == 0) { 994 index = file.lastIndexOf( 995 System.getProperty("file.separator"), jarIndex) + 1; 996 } 997 file = file.substring(0, index); 998 while (tokenizer.hasMoreElements()) { 999 String element = tokenizer.nextToken(); 1000 if (!element.isEmpty()) { 1001 try { 1002 // Take absolute path case into consideration 1003 URL url = new URL(new URL(file), element); 1004 addedURLs.add(createSearchURL(url)); 1005 } catch (MalformedURLException e) { 1006 // Nothing is added 1007 } 1008 } 1009 } 1010 return addedURLs; 1011 } 1012} 1013