Home | History | Annotate | Download | only in net
      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 
     18 package java.net;
     19 
     20 import java.io.BufferedReader;
     21 import java.io.File;
     22 import java.io.FileInputStream;
     23 import java.io.FileNotFoundException;
     24 import java.io.FilePermission;
     25 import java.io.IOException;
     26 import java.io.InputStream;
     27 import java.io.InputStreamReader;
     28 import java.io.UnsupportedEncodingException;
     29 import java.nio.charset.Charsets;
     30 import java.security.CodeSource;
     31 import java.security.PermissionCollection;
     32 import java.security.SecureClassLoader;
     33 import java.security.cert.Certificate;
     34 import java.util.ArrayList;
     35 import java.util.Collections;
     36 import java.util.Enumeration;
     37 import java.util.HashMap;
     38 import java.util.List;
     39 import java.util.Map;
     40 import java.util.StringTokenizer;
     41 import java.util.jar.Attributes;
     42 import java.util.jar.JarEntry;
     43 import java.util.jar.JarFile;
     44 import java.util.jar.Manifest;
     45 import libcore.io.IoUtils;
     46 import 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" })
     55 public 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("nextCandidate == 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