View Javadoc
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 org.apache.river.api.net;
19  
20  import org.apache.river.action.GetPropertyAction;
21  import java.io.BufferedReader;
22  import java.io.ByteArrayOutputStream;
23  import java.io.File;
24  import java.io.FileInputStream;
25  import java.io.FileNotFoundException;
26  import java.io.IOException;
27  import java.io.InputStream;
28  import java.io.InputStreamReader;
29  import java.io.ObjectStreamException;
30  import java.io.UnsupportedEncodingException;
31  import java.net.HttpURLConnection;
32  import java.net.JarURLConnection;
33  import java.net.MalformedURLException;
34  import java.net.URISyntaxException;
35  import java.net.URL;
36  import java.net.URLClassLoader;
37  import java.net.URLConnection;
38  import java.net.URLDecoder;
39  import java.net.URLStreamHandlerFactory;
40  import java.security.AccessControlContext;
41  import java.security.AccessController;
42  import java.security.CodeSource;
43  import java.security.Permission;
44  import java.security.PrivilegedAction;
45  import java.security.cert.Certificate;
46  import java.util.ArrayList;
47  import java.util.Arrays;
48  import java.util.Collection;
49  import java.util.Collections;
50  import java.util.Enumeration;
51  import java.util.HashMap;
52  import java.util.Iterator;
53  import java.util.LinkedList;
54  import java.util.List;
55  import java.util.Locale;
56  import java.util.Map;
57  import java.util.StringTokenizer;
58  import java.util.concurrent.CopyOnWriteArrayList;
59  import java.util.jar.Attributes;
60  import java.util.jar.JarEntry;
61  import java.util.jar.JarFile;
62  import java.util.jar.Manifest;
63  import java.util.logging.Level;
64  import java.util.logging.Logger;
65  
66  import org.apache.river.impl.Messages;
67  
68  /**
69   * This class loader is responsible for loading classes and resources from a
70   * list of URLs which can refer to either directories or JAR files. Classes
71   * loaded by this {@code URLClassLoader} are granted permission to access the
72   * URLs contained in the URL search list.
73   * <p>
74   * Unlike java.net.URLClassLoader, {@link CodeSource#equals(java.lang.Object) } 
75   * and {@link CodeSource#hashCode() } is based on Certificate
76   * and RFC3986 {@link Uri#equals(java.lang.Object) } and {@link Uri#hashCode() }, 
77   * not {@link URL#equals(java.lang.Object)}. SecureClassLoader
78   * uses the overridden CodeSource equality as a key to cache ProtectionDomain's.
79   * <p>
80   * The following property 
81   * <code>-Dnet.jini.loader.codebaseAnnotation=URL</code> 
82   * may be set from the command line to revert to {@link URL#equals(java.lang.Object) }
83   * and {@link URL#hashCode() }.
84   * <p>
85   * This allows implementors of {@link java.rmi.Remote} to do two things:
86   * <ol>
87   * <li>Utilise replication of codebase servers or mirrors.</li>
88   * <li>Use different domain names to ensure separation of proxy classes that 
89   * otherwise utilise identical jar files</li>
90   * </ol>
91   * <p>
92   * The locking strategy of this ClassLoader is by default, the standard 
93   * ClassLoader strategy.  This ClassLoader is also thread safe, so can use
94   * a Parallel loading / synchronization strategy if the platform supports it.
95   * <p>
96   * @since 3.0.0
97   */
98  public class RFC3986URLClassLoader extends java.net.URLClassLoader {
99      
100     /**
101      * value of "net.jini.loader.codebaseAnnotation" property, as cached at class
102      * initialization time.  It may contain malformed URLs.
103      */
104     private final static boolean uri;
105     
106     private final static Logger logger = Logger.getLogger(RFC3986URLClassLoader.class.getName());
107     
108     static {
109         try {
110             registerAsParallelCapable();//Since 1.7
111         } catch (NoSuchMethodError e){
112 	    // Ignore, earlier version of Java.
113             logger.log(Level.FINEST, "Platform doesn't support parallel class loading", e);
114 	}        
115         String codebaseAnnotationProperty = null;
116 	String prop = AccessController.doPrivileged(
117            new GetPropertyAction("net.jini.loader.codebaseAnnotation"));
118 	if (prop != null && prop.trim().length() > 0) codebaseAnnotationProperty = prop;
119         uri = codebaseAnnotationProperty == null || 
120             !Uri.asciiStringsUpperCaseEqual(codebaseAnnotationProperty, "URL");
121     }
122     
123     private final List<URL> originalUrls; // Copy on Write
124 
125     private final List<URL> searchList; // Synchronized
126     
127     /* synchronize on handlerList for all access to handlerList and handlerMap */
128     private final List<URLHandler> handlerList;
129     private final Map<Uri, URLHandler> handlerMap = new HashMap<Uri, URLHandler>();
130 
131     private final URLStreamHandlerFactory factory;
132 
133     private final AccessControlContext creationContext;
134 
135     private static class SubURLClassLoader extends RFC3986URLClassLoader {
136         // The subclass that overwrites the loadClass() method
137 
138         SubURLClassLoader(URL[] urls, AccessControlContext context) {
139             super(urls, ClassLoader.getSystemClassLoader(), null, context);
140         }
141 
142         SubURLClassLoader(URL[] urls, ClassLoader parent, AccessControlContext context) {
143             super(urls, parent, null, context);
144         }
145 
146         /**
147          * Overrides the {@code loadClass()} of {@code ClassLoader}. It calls
148          * the security manager's {@code checkPackageAccess()} before
149          * attempting to load the class.
150          *
151          * @return the Class object.
152          * @param className
153          *            String the name of the class to search for.
154          * @param resolveClass
155          *            boolean indicates if class should be resolved after
156          *            loading.
157          * @throws ClassNotFoundException
158          *             If the class could not be found.
159          */
160         @Override
161         protected Class<?> loadClass(String className, boolean resolveClass) 
162                 throws ClassNotFoundException 
163         {
164             /* Synchronization or locking isn't necessary here, ClassLoader
165              * has it's own locking scheme, which is likely to change depending
166              * on concurrency or multi thread strategies.
167              */ 
168             SecurityManager sm = System.getSecurityManager();
169             if (sm != null) {
170                 int index = className.lastIndexOf('.');
171                 if (index != -1) { // skip if class is from a default package
172                         sm.checkPackageAccess(className.substring(0, index));
173                 }
174             }
175             return super.loadClass(className, resolveClass);
176         }
177     }
178 
179     private static class IndexFile {
180 
181         private final HashMap<String, List<URL>> map;
182         //private URLClassLoader host;
183 
184 
185         static IndexFile readIndexFile(JarFile jf, JarEntry indexEntry, URL url) {
186             BufferedReader in = null;
187             InputStream is = null;
188             try {
189                 // Add mappings from resource to jar file
190                 String parentURLString = getParentURL(url).toExternalForm();
191                 String prefix = "jar:" //$NON-NLS-1$
192                         + parentURLString + "/"; //$NON-NLS-1$
193                 is = jf.getInputStream(indexEntry);
194                 in = new BufferedReader(new InputStreamReader(is, "UTF8"));
195                 HashMap<String, List<URL>> pre_map = new HashMap<String, List<URL>>();
196                 // Ignore the 2 first lines (index version)
197                 if (in.readLine() == null) return null;
198                 if (in.readLine() == null) return null;
199                 TOP_CYCLE:
200                 while (true) {
201                     String line = in.readLine();
202                     if (line == null) {
203                         break;
204                     }
205                     URL jar = new URL(prefix + line + "!/"); //$NON-NLS-1$
206                     while (true) {
207                         line = in.readLine();
208                         if (line == null) {
209                             break TOP_CYCLE;
210                         }
211                         if ("".equals(line)) {
212                             break;
213                         }
214                         List<URL> list;
215                         if (pre_map.containsKey(line)) {
216                             list = pre_map.get(line);
217                         } else {
218                             list = new LinkedList<URL>();
219                             pre_map.put(line, list);
220                         }
221                         list.add(jar);
222                     }
223                 }
224                 if (!pre_map.isEmpty()) {
225                     return new IndexFile(pre_map);
226                 }
227             } catch (MalformedURLException e) {
228                 // Ignore this jar's index
229             } catch (IOException e) {
230                 // Ignore this jar's index
231             }
232             finally {
233                 if (in != null) {
234                     try {
235                         in.close();
236                     } catch (IOException e) {
237                     }
238                 }
239                 if (is != null) {
240                     try {
241                         is.close();
242                     } catch (IOException e) {
243                     }
244                 }
245             }
246             return null;
247         }
248 
249         private static URL getParentURL(URL url) throws IOException {
250             URL fileURL = ((JarURLConnection) url.openConnection()).getJarFileURL();
251             String file = fileURL.getFile();
252             String parentFile = new File(file).getParent();
253             parentFile = parentFile.replace(File.separatorChar, '/');
254             if (parentFile.charAt(0) != '/') {
255                 parentFile = "/" + parentFile; //$NON-NLS-1$
256             }
257             URL parentURL = new URL(fileURL.getProtocol(), fileURL
258                     .getHost(), fileURL.getPort(), parentFile);
259             return parentURL;
260         }
261 
262         public IndexFile(HashMap<String,List<URL>> map) {
263             // Don't need to defensively copy map, it's created for and only
264             // used here.
265             this.map = map;
266         }
267 
268         List<URL> get(String name) {
269             synchronized (map){
270                 return map.get(name);
271             }
272         }
273     }
274 
275     private static class URLHandler {
276         final URL url;
277         final URL codeSourceUrl;
278         final RFC3986URLClassLoader loader;
279 
280         public URLHandler(URL url, RFC3986URLClassLoader loader) {
281             this.url = url;
282             this.codeSourceUrl = url;
283             this.loader = loader;
284         }
285         
286         public URLHandler(URL url, URL codeSourceUrl, RFC3986URLClassLoader loader){
287             this.url = url;
288             this.codeSourceUrl = codeSourceUrl;
289             this.loader = loader;
290         }
291 
292         void findResources(String name, List<URL> resources) {
293             URL res = findResource(name);
294             if (res != null && !resources.contains(res)) {
295                 resources.add(res);
296             }
297         }
298 
299         Class<?> findClass(String packageName, String name, String origName) {
300             URL resURL = targetURL(url, name);
301             if (resURL != null) {
302                 try {
303                     InputStream is = resURL.openStream();
304                     return createClass(is, packageName, origName);
305                 } catch (IOException e) {
306                 }
307             }
308             return null;
309         }
310 
311 
312         Class<?> createClass(InputStream is, String packageName, String origName) {
313             if (is == null) {
314                 return null;
315             }
316             byte[] clBuf = null;
317             try {
318                 clBuf = getBytes(is);
319             } catch (IOException e) {
320                 return null;
321             } finally {
322                 try {
323                     is.close();
324                 } catch (IOException e) {
325                 }
326             }
327             if (packageName != null) {
328                 String packageDotName = packageName.replace('/', '.');
329                 Package packageObj = loader.getPackage(packageDotName);
330                 if (packageObj == null) {
331                     try {
332                         loader.definePackage(packageDotName, null, null,
333                                             null, null, null, null, null);
334                     } catch (IllegalArgumentException e){
335                         // Already exists, this is in case of concurrent access
336                         // which is very unlikely.
337                         packageObj = loader.getPackage(packageDotName);
338                         if (packageObj.isSealed()) {
339                             throw new SecurityException(Messages
340                                     .getString("luni.A1")); //$NON-NLS-1$
341                         }
342                     }
343                 } else {
344                     if (packageObj.isSealed()) {
345                         throw new SecurityException(Messages
346                                 .getString("luni.A1")); //$NON-NLS-1$
347                     }
348                 }
349             }
350             // The package is defined and isn't sealed, safe to define class.
351             if (uri) return loader.defineClass(
352                     origName,
353                     clBuf,
354                     0, 
355                     clBuf != null ? clBuf.length: 0,
356                     new UriCodeSource(codeSourceUrl, (Certificate[]) null, null)
357             );
358             return loader.defineClass(
359                     origName, 
360                     clBuf,
361                     0, 
362                     clBuf != null ? clBuf.length: 0,
363                     new CodeSource(codeSourceUrl, (Certificate[]) null)
364             );
365         }
366 
367         URL findResource(String name) {
368             URL resURL = targetURL(url, name);
369             if (resURL != null) {
370                 try {
371                     URLConnection uc = resURL.openConnection();
372                     uc.getInputStream().close();
373                     // HTTP can return a stream on a non-existent file
374                     // So check for the return code;
375                     if (!resURL.getProtocol().equals("http")) { //$NON-NLS-1$
376                         return resURL;
377                     }
378                     int code;
379                     if ((code = ((HttpURLConnection) uc).getResponseCode()) >= 200
380                             && code < 300) {
381                         return resURL;
382                     }
383                 } catch (SecurityException e) {
384                     return null;
385                 } catch (IOException e) {
386                     return null;
387                 }
388             }
389             return null;
390         }
391 
392         URL targetURL(URL base, String name) {
393             try {
394                 String file = base.getFile() + URIEncoderDecoder.quoteIllegal(name,
395                         "/@" + Uri.someLegal);
396 
397                 return new URL(base.getProtocol(), base.getHost(), base.getPort(),
398                         file, null);
399             } catch (UnsupportedEncodingException e) {
400                 return null;
401             } catch (MalformedURLException e) {
402                 return null;
403             }
404         }
405         
406         public void close() throws IOException {
407             // do nothing.
408         }
409 
410     }
411 
412     private static class URLJarHandler extends URLHandler {
413         private final JarFile jf;
414         private final String prefixName;
415         private final IndexFile index;
416         private final Map<Uri, URLHandler> subHandlers = new HashMap<Uri, URLHandler>();
417 
418         public URLJarHandler(URL url, URL jarURL, JarFile jf, String prefixName, RFC3986URLClassLoader loader) {
419             super(url, jarURL, loader);
420             this.jf = jf;
421             this.prefixName = prefixName;
422             final JarEntry je = jf.getJarEntry("META-INF/INDEX.LIST"); //$NON-NLS-1$
423             this.index = (je == null ? null : IndexFile.readIndexFile(jf, je, url));
424         }
425 
426         public URLJarHandler(URL url, URL jarURL, JarFile jf, String prefixName, IndexFile index, RFC3986URLClassLoader loader) {
427             super(url, jarURL, loader);
428             this.jf = jf;
429             this.prefixName = prefixName;
430             this.index = index;
431         }
432 
433         IndexFile getIndex() {
434             return index;
435         }
436 
437         @Override
438         void findResources(String name, List<URL> resources) {
439             URL res = findResourceInOwn(name);
440             if (res != null && !resources.contains(res)) {
441                 resources.add(res);
442             }
443             if (index != null) {
444                 int pos = name.lastIndexOf("/"); //$NON-NLS-1$
445                 // only keep the directory part of the resource
446                 // as index.list only keeps track of directories and root files
447                 String indexedName = (pos > 0) ? name.substring(0, pos) : name;
448                 List<URL> urls = index.get(indexedName);
449                 if (urls != null) {
450                     synchronized (urls){
451                         urls.remove(url);
452                         urls = new ArrayList<URL>(urls); // Defensive copy to avoid sync
453                     }
454                     for (URL u : urls) {
455                         URLHandler h = getSubHandler(u);
456                         if (h != null) {
457                             h.findResources(name, resources);
458                         }
459                     }
460                 }
461             }
462 
463         }
464 
465         @Override
466         Class<?> findClass(String packageName, String name, String origName) {
467             String entryName = prefixName + name;
468             JarEntry entry = jf.getJarEntry(entryName);
469             if (entry != null) {
470                 /**
471                  * Avoid recursive load class, especially the class
472                  * is an implementation class of security provider
473                  * and the jar is signed.
474                  */
475                 try {
476                     Manifest manifest = jf.getManifest();
477                     return createClass(entry, manifest, packageName, origName);
478                 } catch (IOException e) {
479                 }
480             }
481             if (index != null) {
482                 List<URL> urls;
483                 if (packageName == null) {
484                     urls = index.get(name);
485                 } else {
486                     urls = index.get(packageName);
487                 }
488                 if (urls != null) {
489                     synchronized (urls){
490                         urls.remove(url);
491                         urls = new ArrayList<URL>(urls); // Defensive copy.
492                     }
493                     for (URL u : urls) {
494                         URLHandler h = getSubHandler(u);
495                         if (h != null) {
496                             Class<?> res = h.findClass(packageName, name, origName);
497                             if (res != null) {
498                                 return res;
499                             }
500                         }
501                     }
502                 }
503             }
504             return null;
505         }
506 
507         private Class<?> createClass(JarEntry entry, Manifest manifest, String packageName, String origName) {
508             InputStream is = null;
509             byte[] clBuf;
510             try {
511                 is = jf.getInputStream(entry);
512                 clBuf = getBytes(is);
513             } catch (IOException e) {
514                 return null;
515             } finally {
516                 if (is != null) {
517                     try {
518                         is.close();
519                     } catch (IOException e) {
520                     }
521                 }
522             }
523             if (packageName != null) {
524                 String packageDotName = packageName.replace('/', '.');
525                 Package packageObj = loader.getPackage(packageDotName);
526                 if (packageObj == null) {
527                     if (manifest != null) {
528                         loader.definePackage(packageDotName, manifest,
529                                 codeSourceUrl);
530                     } else {
531                         loader.definePackage(packageDotName, null, null,
532                                 null, null, null, null, null);
533                     }
534                 } else {
535                     boolean exception = packageObj.isSealed();
536                     if (manifest != null) {
537                         if (loader.isSealed(manifest, packageName + "/")) {
538                             exception = !packageObj
539                                     .isSealed(codeSourceUrl);
540                         }
541                     }
542                     if (exception) {
543                         throw new SecurityException(Messages
544                                 .getString("luni.A1", packageName)); //$NON-NLS-1$
545                     }
546                 }
547             }
548             CodeSource codeS = uri ? 
549                 new UriCodeSource(codeSourceUrl, entry.getCertificates(),null) 
550                 : new CodeSource(codeSourceUrl, entry.getCertificates());
551             return loader.defineClass(
552                     origName,
553                     clBuf,
554                     0,
555 		    clBuf.length,
556                     codeS
557             );
558         }
559 
560         URL findResourceInOwn(String name) {
561             String entryName = prefixName + name;
562             if (jf.getEntry(entryName) != null) {
563                 return targetURL(url, name);
564             }
565             return null;
566         }
567 
568         @Override
569         URL findResource(String name) {
570             URL res = findResourceInOwn(name);
571             if (res != null) {
572                 return res;
573             }
574             if (index != null) {
575                 int pos = name.lastIndexOf("/"); //$NON-NLS-1$
576                 // only keep the directory part of the resource
577                 // as index.list only keeps track of directories and root files
578                 String indexedName = (pos > 0) ? name.substring(0, pos) : name;
579                 List<URL> urls = index.get(indexedName);
580                 if (urls != null) {
581                     synchronized (urls){
582                         urls.remove(url);
583                         urls = new ArrayList<URL>(urls); // Defensive copy.
584                     }
585                     for (URL u : urls) {
586                         URLHandler h = getSubHandler(u);
587                         if (h != null) {
588                             res = h.findResource(name);
589                             if (res != null) {
590                                 return res;
591                             }
592                         }
593                     }
594                 }
595             }
596             return null;
597         }
598 
599         private URLHandler getSubHandler(URL url) {
600             Uri key = null;
601             try {
602                 key = Uri.urlToUri(url);
603             } catch (URISyntaxException ex) {
604                 logger.log(Level.WARNING, "Unable to create Uri from URL" + url.toString(), ex);
605             }
606             synchronized (subHandlers){
607                 URLHandler sub = subHandlers.get(key);
608                 if (sub != null) {
609                     return sub;
610                 }
611                 String protocol = url.getProtocol();
612                 if (protocol.equals("jar")) { //$NON-NLS-1$
613                     sub = loader.createURLJarHandler(url);
614                 } else if (protocol.equals("file")) { //$NON-NLS-1$
615                     sub = createURLSubJarHandler(url);
616                 } else {
617                     sub = loader.createURLHandler(url);
618                 }
619                 if (sub != null && key != null) {
620                     subHandlers.put(key, sub);
621                 }
622                 return sub;
623             }
624         }
625 
626         private URLHandler createURLSubJarHandler(URL url) {
627             String prfixName;
628             String file = url.getFile();
629             if (url.getFile().endsWith("!/")) { //$NON-NLS-1$
630                 prfixName = "";
631             } else {
632                 int sepIdx = file.lastIndexOf("!/"); //$NON-NLS-1$
633                 if (sepIdx == -1) {
634                     // Invalid URL, don't look here again
635                     return null;
636                 }
637                 sepIdx += 2;
638                 prfixName = file.substring(sepIdx);
639             }
640             try {
641                 URL jarURL = ((JarURLConnection) url
642                         .openConnection()).getJarFileURL();
643                 JarURLConnection juc = (JarURLConnection) new URL(
644                         "jar", "", //$NON-NLS-1$ //$NON-NLS-2$
645                         jarURL.toExternalForm() + "!/").openConnection(); //$NON-NLS-1$
646                 JarFile jfile = juc.getJarFile();
647                 URLJarHandler jarH = new URLJarHandler(url, jarURL, jfile, prfixName, loader);
648                 // TODO : to think what we should do with indexes & manifest.class file here
649                 return jarH;
650             } catch (IOException e) {
651             }
652             return null;
653         }
654         
655         public void close() throws IOException {
656             IOException first = null;
657             try {
658                 jf.close();
659             } catch (IOException e){
660                 first = e;
661             }
662             synchronized (subHandlers){
663                 Iterator<URLHandler> it = subHandlers.values().iterator();
664                 while (it.hasNext()){
665                     try {
666                         it.next().close();
667                     } catch (IOException e){
668                         if (first == null) first = e;
669                         else {
670                             logger.log(Level.WARNING, "Unable to close URLHandler during URLClassLoader close()", e);
671                         }
672                     }
673                 }
674                 subHandlers.clear();
675             }
676             if (first != null) throw first;
677         }
678 
679     }
680 
681     private static class URLFileHandler extends URLHandler {
682         private final String prefix;
683 
684         public URLFileHandler(URL url, RFC3986URLClassLoader loader) {
685             super(url, loader);
686             String baseFile = url.getFile();
687             String host = url.getHost();
688             int hostLength = 0;
689             if (host != null) {
690                 hostLength = host.length();
691             }
692             StringBuilder buf = new StringBuilder(2 + hostLength
693                     + baseFile.length());
694             if (hostLength > 0) {
695                 buf.append("//").append(host); //$NON-NLS-1$
696             }
697             // baseFile always ends with '/'
698             buf.append(baseFile);
699             prefix = buf.toString();
700         }
701 
702         @Override
703         Class<?> findClass(String packageName, String name, String origName) {
704             String filename = prefix + name;
705             try {
706                 filename = URLDecoder.decode(filename, "UTF-8"); //$NON-NLS-1$
707             } catch (IllegalArgumentException e) {
708                 return null;
709             } catch (UnsupportedEncodingException e) {
710                 return null;
711             }
712 
713             File file = new File(filename);
714             if (file.exists()) {
715 		InputStream is = null;
716                 try {
717                     is = new FileInputStream(file);
718                     return createClass(is, packageName, origName);
719                 } catch (FileNotFoundException e) {
720                 } finally {
721 		    try {
722 			if (is != null) is.close();
723 		    } catch (IOException ex){}//Ignore
724 		}
725             }
726             return null;
727         }
728 
729         @Override
730         URL findResource(String name) {
731             int idx = 0;
732             String filename;
733 
734             // Do not create a UNC path, i.e. \\host
735             while (idx < name.length() && 
736                    ((name.charAt(idx) == '/') || (name.charAt(idx) == '\\'))) {
737                 idx++;
738             }
739 
740             if (idx > 0) {
741                 name = name.substring(idx);
742             }
743 
744             try {
745                 filename = URLDecoder.decode(prefix, "UTF-8") + name; //$NON-NLS-1$
746 
747                 if (new File(filename).exists()) {
748                     return targetURL(url, name);
749                 }
750                 return null;
751             } catch (IllegalArgumentException e) {
752                 return null;
753             } catch (UnsupportedEncodingException e) {
754                 // must not happen
755                 throw new AssertionError(e);
756             }
757         }
758 
759     }
760     
761     /**
762      * To avoid CodeSource equals and hashCode methods in SecureClassLoader keys.
763      * 
764      * CodeSource uses DNS lookup calls to check location IP addresses are 
765      * equal.
766      * 
767      * This class must not be serialized.
768      */
769     private static class UriCodeSource extends CodeSource {
770         private static final long serialVersionUID = 1L;
771         private final Uri uri;
772         private final int hashCode;
773         
774         UriCodeSource(URL url, Certificate [] certs, Collection<Permission> perms){
775             super(url, certs);
776             Uri uRi = null;
777             try {
778                 uRi = Uri.urlToUri(url);
779             } catch (URISyntaxException ex) { }//Ignore
780             this.uri = uRi;
781             int hash = 7;
782             hash = 23 * hash + (this.uri != null ? this.uri.hashCode() : 0);
783             hash = 23 * hash + (certs != null ? Arrays.hashCode(certs) : 0);
784             hashCode = hash;
785         }
786 
787         @Override
788         public int hashCode() {
789             return hashCode;
790         }
791         
792         @Override
793         public boolean equals(Object o){
794             if (!(o instanceof UriCodeSource)) return false;
795             if (uri == null) return super.equals(o);
796             UriCodeSource that = (UriCodeSource) o;
797             if ( !uri.equals(that.uri)) return false;
798             Certificate [] mine = getCertificates();
799             Certificate [] theirs = that.getCertificates();
800             if ( mine == null && theirs == null) return true;
801             if ( mine == null && theirs != null) return false;
802             if ( mine != null && theirs == null) return false;
803             return (Arrays.asList(getCertificates()).equals(
804                     Arrays.asList(that.getCertificates())));
805         }
806         
807         Object writeReplace() throws ObjectStreamException {
808             return new CodeSource(getLocation(), getCertificates());
809         }
810        
811     }
812 
813 
814     /**
815      * Constructs a new {@code URLClassLoader} instance. The newly created
816      * instance will have the system ClassLoader as its parent. URLs that end
817      * with "/" are assumed to be directories, otherwise they are assumed to be
818      * JAR files.
819      *
820      * @param urls
821      *            the list of URLs where a specific class or file could be
822      *            found.
823      * @throws SecurityException
824      *             if a security manager exists and its {@code
825      *             checkCreateClassLoader()} method doesn't allow creation of
826      *             new ClassLoaders.
827      */
828     public RFC3986URLClassLoader(URL[] urls) {
829         this(urls, ClassLoader.getSystemClassLoader(), null);
830     }
831 
832     /**
833      * Constructs a new URLClassLoader instance. The newly created instance will
834      * have the system ClassLoader as its parent. URLs that end with "/" are
835      * assumed to be directories, otherwise they are assumed to be JAR files.
836      * 
837      * @param urls
838      *            the list of URLs where a specific class or file could be
839      *            found.
840      * @param parent
841      *            the class loader to assign as this loader's parent.
842      * @throws SecurityException
843      *             if a security manager exists and its {@code
844      *             checkCreateClassLoader()} method doesn't allow creation of
845      *             new class loaders.
846      */
847     public RFC3986URLClassLoader(URL[] urls, ClassLoader parent) {
848         this(urls, parent, null);
849     }
850 
851     /**
852      * Adds the specified URL to the search list.
853      *
854      * @param url
855      *            the URL which is to add.
856      */
857     @Override
858     protected void addURL(URL url) {
859         try {
860             originalUrls.add(url);
861             searchList.add(createSearchURL(url));
862         } catch (MalformedURLException e) {
863         }
864     }
865 
866     /**
867      * Returns all known URLs which point to the specified resource.
868      *
869      * @param name
870      *            the name of the requested resource.
871      * @return the enumeration of URLs which point to the specified resource.
872      * @throws IOException
873      *             if an I/O error occurs while attempting to connect.
874      */
875     @Override
876     public Enumeration<URL> findResources(final String name) throws IOException {
877         List<URL> result = AccessController.doPrivileged(
878                 new PrivilegedAction<List<URL>>() {
879                     @Override
880                     public List<URL> run() {
881                         List<URL> results = new LinkedList<URL>();
882                         findResourcesImpl(name, results);
883                         return results;
884                     }
885                 }, creationContext);
886         SecurityManager sm;
887         int length = result.size();
888         if (length > 0 && (sm = System.getSecurityManager()) != null) {
889             ArrayList<URL> reduced = new ArrayList<URL>(length);
890             for (int i = 0; i < length; i++) {
891                 URL url = result.get(i);
892                 try {
893                     sm.checkPermission(url.openConnection().getPermission());
894                     reduced.add(url);
895                 } catch (IOException e) {
896                 } catch (SecurityException e) {
897                 }
898             }
899             result = reduced;
900         }
901         return Collections.enumeration(result);
902     }
903 
904     void findResourcesImpl(String name, List<URL> result) {
905         if (name == null) {
906             return;
907         }
908         int n = 0;
909         while (true) {
910             URLHandler handler = getHandler(n++);
911             if (handler == null) {
912                 break;
913             }
914             handler.findResources(name, result);
915         }
916     }
917 
918 
919     /**
920      * Converts an input stream into a byte array.
921      *
922      * @param is
923      *            the input stream
924      * @return byte[] the byte array
925      */
926     private static byte[] getBytes(InputStream is)
927             throws IOException {
928         byte[] buf = new byte[4096];
929         ByteArrayOutputStream bos = new ByteArrayOutputStream(4096);
930         int count;
931         while ((count = is.read(buf)) > 0) {
932             bos.write(buf, 0, count);
933         }
934         return bos.toByteArray();
935     }
936 
937     /**
938      * Gets all permissions for the specified {@code codesource}. First, this
939      * method retrieves the permissions from the system policy. If the protocol
940      * is "file:/" then a new permission, {@code FilePermission}, granting the
941      * read permission to the file is added to the permission collection.
942      * Otherwise, connecting to and accepting connections from the URL is
943      * granted.
944      *
945      * @param codesource
946      *            the code source object whose permissions have to be known.
947      * @return the list of permissions according to the code source object.
948      */
949 //    @Override
950 //    protected PermissionCollection getPermissions(final CodeSource codesource) {
951 //        PermissionCollection pc = super.getPermissions(codesource);
952 //        URL u = codesource.getLocation();
953 //        if (u.getProtocol().equals("jar")) { //$NON-NLS-1$
954 //            try {
955 //                // Create a URL for the resource the jar refers to
956 //                u = ((JarURLConnection) u.openConnection()).getJarFileURL();
957 //            } catch (IOException e) {
958 //                // This should never occur. If it does continue using the jar
959 //                // URL
960 //            }
961 //        }
962 //        if (u.getProtocol().equals("file")) { //$NON-NLS-1$
963 //            String path = u.getFile();
964 //            String host = u.getHost();
965 //            if (host != null && host.length() > 0) {
966 //                path = "//" + host + path; //$NON-NLS-1$
967 //            }
968 //
969 //            if (File.separatorChar != '/') {
970 //                path = path.replace('/', File.separatorChar);
971 //            }
972 //            if (isDirectory(u)) {
973 //                pc.add(new FilePermission(path + "-", "read")); //$NON-NLS-1$ //$NON-NLS-2$
974 //            } else {
975 //                pc.add(new FilePermission(path, "read")); //$NON-NLS-1$
976 //            }
977 //        } else {
978 //            String host = u.getHost();
979 //            if (host.length() == 0) {
980 //                host = "localhost"; //$NON-NLS-1$
981 //            }
982 //            pc.add(new SocketPermission(host, "connect, accept")); //$NON-NLS-1$
983 //        }
984 //        return pc;
985 //    }
986 
987     /**
988      * Returns the search list of this {@code URLClassLoader}.
989      *
990      * @return the list of all known URLs of this instance.
991      */
992     @Override
993     public URL[] getURLs() {
994         return originalUrls.toArray(new URL[originalUrls.size()]);
995     }
996 
997     /**
998      * Determines if the URL is pointing to a directory.
999      */
1000     private static boolean isDirectory(URL url) {
1001         String file = url.getFile();
1002         return (file.length() > 0 && file.charAt(file.length() - 1) == File.separatorChar);
1003     }
1004 
1005     /**
1006      * Returns a new {@code URLClassLoader} instance for the given URLs and the
1007      * system {@code ClassLoader} as its parent. The method {@code loadClass()}
1008      * of the new instance will call {@code
1009      * SecurityManager.checkPackageAccess()} before loading a class.
1010      *
1011      * @param urls
1012      *            the list of URLs that is passed to the new {@code
1013      *            URLClassloader}.
1014      * @return the created {@code URLClassLoader} instance.
1015      */
1016     public static URLClassLoader newInstance(final URL[] urls) {
1017         final AccessControlContext context = AccessController.getContext();
1018         RFC3986URLClassLoader sub = AccessController
1019                 .doPrivileged(new PrivilegedAction<RFC3986URLClassLoader>() {
1020                     @Override
1021                     public RFC3986URLClassLoader run() {
1022                         return new SubURLClassLoader(urls, context);
1023                     }
1024                 });
1025         return sub;
1026     }
1027 
1028     /**
1029      * Returns a new {@code URLClassLoader} instance for the given URLs and the
1030      * specified {@code ClassLoader} as its parent. The method {@code
1031      * loadClass()} of the new instance will call the SecurityManager's {@code
1032      * checkPackageAccess()} before loading a class.
1033      *
1034      * @param urls
1035      *            the list of URLs that is passed to the new URLClassloader.
1036      * @param parentCl
1037      *            the parent class loader that is passed to the new
1038      *            URLClassloader.
1039      * @return the created {@code URLClassLoader} instance.
1040      */
1041     public static URLClassLoader newInstance(final URL[] urls,
1042                                              final ClassLoader parentCl) {
1043         final AccessControlContext context = AccessController.getContext();
1044         RFC3986URLClassLoader sub = AccessController
1045                 .doPrivileged(new PrivilegedAction<RFC3986URLClassLoader>() {
1046                     @Override
1047                     public RFC3986URLClassLoader run() {
1048                         return new SubURLClassLoader(urls, parentCl, context);
1049                     }
1050                 });
1051         return sub;
1052     }
1053 
1054     /**
1055      * Constructs a new {@code URLClassLoader} instance. The newly created
1056      * instance will have the specified {@code ClassLoader} as its parent and
1057      * use the specified factory to create stream handlers. URLs that end with
1058      * "/" are assumed to be directories, otherwise they are assumed to be JAR
1059      * files.
1060      * 
1061      * @param searchUrls
1062      *            the list of URLs where a specific class or file could be
1063      *            found.
1064      * @param parent
1065      *            the {@code ClassLoader} to assign as this loader's parent.
1066      * @param factory
1067      *            the factory that will be used to create protocol-specific
1068      *            stream handlers.
1069      * @throws SecurityException
1070      *             if a security manager exists and its {@code
1071      *             checkCreateClassLoader()} method doesn't allow creation of
1072      *             new {@code ClassLoader}s.
1073      */
1074     public RFC3986URLClassLoader(URL[] searchUrls, ClassLoader parent,
1075                           URLStreamHandlerFactory factory) {
1076         this(searchUrls, parent, factory, AccessController.getContext());
1077     }
1078     
1079     RFC3986URLClassLoader( URL[] searchUrls, 
1080                             ClassLoader parent, 
1081                             URLStreamHandlerFactory factory, 
1082                             AccessControlContext context)
1083     {
1084         super(searchUrls, parent, factory);  // ClassLoader protectes against finalizer attack.
1085         this.factory = factory;
1086         // capture the context of the thread that creates this URLClassLoader
1087         creationContext = context;
1088         int nbUrls = searchUrls.length;
1089         List<URL> origUrls = new ArrayList<URL>(nbUrls);
1090         handlerList = new ArrayList<URLHandler>(nbUrls);
1091         searchList = Collections.synchronizedList(new LinkedList<URL>());
1092         for (int i = 0; i < nbUrls; i++) {
1093             origUrls.add(searchUrls[i]);
1094             try {
1095                 searchList.add(createSearchURL(searchUrls[i]));
1096             } catch (MalformedURLException e) {
1097             }
1098         }
1099         this.originalUrls = new CopyOnWriteArrayList<URL>(origUrls);
1100     }
1101 
1102     /**
1103      * Tries to locate and load the specified class using the known URLs. If the
1104      * class could be found, a class object representing the loaded class will
1105      * be returned.
1106      * 
1107      * The locking and synchronization strategy of this method is the 
1108      * responsibility of the caller.
1109      *
1110      * @param clsName
1111      *            the name of the class which has to be found.
1112      * @return the class that has been loaded.
1113      * @throws ClassNotFoundException
1114      *             if the specified class cannot be loaded.
1115      */
1116     @Override
1117     protected Class<?> findClass(final String clsName)
1118             throws ClassNotFoundException {
1119         Class<?> cls = AccessController.doPrivileged(
1120                 new PrivilegedAction<Class<?>>() {
1121                     @Override
1122                     public Class<?> run() {
1123                         return findClassImpl(clsName);
1124                     }
1125                 }, creationContext);
1126         if (cls != null) {
1127             return cls;
1128         }
1129         throw new ClassNotFoundException(clsName);
1130     }
1131 
1132     /**
1133      * Returns an URL that will be checked if it contains the class or resource.
1134      * If the file component of the URL is not a directory, a Jar URL will be
1135      * created.
1136      * 
1137      * We need to modify this implementation to allow URLStreamHandlerFactory's
1138      * to use custom caching, such as the per ClassLoader caching used by
1139      * Apache Geronimo.  This would be very useful as it allows services
1140      * to upgrade themselves by reloading and replacing their proxy's.
1141      *
1142      * @return java.net.URL a test URL
1143      */
1144     private URL createSearchURL(URL url) throws MalformedURLException {
1145         if (url == null) {
1146             return url;
1147         }
1148 
1149         String protocol = url.getProtocol();
1150 
1151         if (isDirectory(url) || protocol.equals("jar")) { //$NON-NLS-1$
1152             return url;
1153         }
1154         if (factory == null) {
1155             return new URL("jar", "", //$NON-NLS-1$ //$NON-NLS-2$
1156                     -1, url.toString() + "!/"); //$NON-NLS-1$
1157         }
1158         // use jar protocol as the stream handler protocol
1159         return new URL("jar", "", //$NON-NLS-1$ //$NON-NLS-2$
1160                 -1, url.toString() + "!/", //$NON-NLS-1$
1161                 factory.createURLStreamHandler("jar"));//$NON-NLS-1$
1162     }
1163 
1164     /**
1165      * Returns an URL referencing the specified resource or {@code null} if the
1166      * resource could not be found.
1167      *
1168      * @param name
1169      *            the name of the requested resource.
1170      * @return the URL which points to the given resource.
1171      */
1172     @Override
1173     public URL findResource(final String name) {
1174         if (name == null) {
1175             return null;
1176         }
1177         URL result = AccessController.doPrivileged(new PrivilegedAction<URL>() {
1178             @Override
1179             public URL run() {
1180                 return findResourceImpl(name);
1181             }
1182         }, creationContext);
1183         SecurityManager sm;
1184         if (result != null && (sm = System.getSecurityManager()) != null) {
1185             try {
1186                 sm.checkPermission(result.openConnection().getPermission());
1187             } catch (IOException e) {
1188                 return null;
1189             } catch (SecurityException e) {
1190                 return null;
1191             }
1192         }
1193         return result;
1194     }
1195 
1196     /**
1197      * Returns a URL among the given ones referencing the specified resource or
1198      * null if no resource could be found.
1199      *
1200      * @param resName java.lang.String the name of the requested resource
1201      * @return URL URL for the resource.
1202      */
1203     URL findResourceImpl(String resName) {
1204         int n = 0;
1205 
1206         while (true) {
1207             URLHandler handler = getHandler(n++);
1208             if (handler == null) {
1209                 break;
1210             }
1211             URL res = handler.findResource(resName);
1212             if (res != null) {
1213                 return res;
1214             }
1215         }
1216         return null;
1217     }
1218 
1219     URLHandler getHandler(int num) {
1220         synchronized (handlerList){
1221             if (num < handlerList.size()) {
1222                 return handlerList.get(num);
1223             }
1224 
1225             makeNewHandler();
1226             if (num < handlerList.size()) {
1227                 return handlerList.get(num);
1228             }
1229             return null;
1230         }
1231     }
1232 
1233     // synchronize on handlerList.
1234     private void makeNewHandler() {
1235         while (!searchList.isEmpty()) {
1236             URL nextCandidate = searchList.remove(0);
1237             if (nextCandidate == null) {  // luni.94=One of urls is null
1238                 throw new NullPointerException(Messages.getString("luni.94")); //$NON-NLS-1$
1239             }
1240             Uri candidateKey = null;
1241             try {
1242                 candidateKey = Uri.urlToUri(nextCandidate);
1243             } catch (URISyntaxException ex) {
1244                 logger.log(Level.WARNING, "Unable to parse URL" + nextCandidate.toString(), ex);
1245             }
1246             if (!handlerMap.containsKey(candidateKey)) {
1247                 URLHandler result;
1248                 String protocol = nextCandidate.getProtocol();
1249                 if (protocol.equals("jar")) { //$NON-NLS-1$
1250                     result = createURLJarHandler(nextCandidate);
1251                 } else if (protocol.equals("file")) { //$NON-NLS-1$
1252                     result = createURLFileHandler(nextCandidate);
1253                 } else {
1254                     result = createURLHandler(nextCandidate);
1255                 }
1256                 if (result != null) {
1257                     handlerMap.put(candidateKey, result);
1258                     handlerList.add(result);
1259                     return;
1260                 }
1261             }
1262         }
1263     }
1264 
1265     private URLHandler createURLHandler(URL url) {
1266         return new URLHandler(url, this);
1267     }
1268 
1269     private URLHandler createURLFileHandler(URL url) {
1270         return new URLFileHandler(url, this);
1271     }
1272 
1273     private URLHandler createURLJarHandler(URL url) {
1274         String prefixName;
1275         String file = url.getFile();
1276         if (url.getFile().endsWith("!/")) { //$NON-NLS-1$
1277             prefixName = "";
1278         } else {
1279             int sepIdx = file.lastIndexOf("!/"); //$NON-NLS-1$
1280             if (sepIdx == -1) {
1281                 // Invalid URL, don't look here again
1282                 return null;
1283             }
1284             sepIdx += 2;
1285             prefixName = file.substring(sepIdx);
1286         }
1287         try {
1288             URL jarURL = ((JarURLConnection) url
1289                     .openConnection()).getJarFileURL();
1290             JarURLConnection juc = (JarURLConnection) new URL(
1291                     "jar", "", //$NON-NLS-1$ //$NON-NLS-2$
1292                     jarURL.toExternalForm() + "!/").openConnection(); //$NON-NLS-1$
1293             JarFile jf = juc.getJarFile();
1294             URLJarHandler jarH = new URLJarHandler(url, jarURL, jf, prefixName, this);
1295 
1296             if (jarH.getIndex() == null) {
1297                 try {
1298                     Manifest manifest = jf.getManifest();
1299                     if (manifest != null) {
1300                         String classpath = manifest.getMainAttributes().getValue(
1301                                 Attributes.Name.CLASS_PATH);
1302                         if (classpath != null) {
1303                             searchList.addAll(0, getInternalURLs(url, classpath));
1304                         }
1305                     }
1306                 } catch (IOException e) {
1307                 }
1308             }
1309             return jarH;
1310         } catch (IOException e) {
1311         }
1312         return null;
1313     }
1314 
1315     /**
1316      * Defines a new package using the information extracted from the specified
1317      * manifest.
1318      *
1319      * @param packageName
1320      *            the name of the new package.
1321      * @param manifest
1322      *            the manifest containing additional information for the new
1323      *            package.
1324      * @param url
1325      *            the URL to the code source for the new package.
1326      * @return the created package.
1327      * @throws IllegalArgumentException
1328      *             if a package with the given name already exists.
1329      */
1330     @Override
1331     protected Package definePackage(String packageName, Manifest manifest,
1332                                     URL url) throws IllegalArgumentException {
1333         Attributes mainAttributes = manifest.getMainAttributes();
1334         String dirName = packageName.replace('.', '/') + "/"; //$NON-NLS-1$
1335         Attributes packageAttributes = manifest.getAttributes(dirName);
1336         boolean noEntry = false;
1337         if (packageAttributes == null) {
1338             noEntry = true;
1339             packageAttributes = mainAttributes;
1340         }
1341         String specificationTitle = packageAttributes
1342                 .getValue(Attributes.Name.SPECIFICATION_TITLE);
1343         if (specificationTitle == null && !noEntry) {
1344             specificationTitle = mainAttributes
1345                     .getValue(Attributes.Name.SPECIFICATION_TITLE);
1346         }
1347         String specificationVersion = packageAttributes
1348                 .getValue(Attributes.Name.SPECIFICATION_VERSION);
1349         if (specificationVersion == null && !noEntry) {
1350             specificationVersion = mainAttributes
1351                     .getValue(Attributes.Name.SPECIFICATION_VERSION);
1352         }
1353         String specificationVendor = packageAttributes
1354                 .getValue(Attributes.Name.SPECIFICATION_VENDOR);
1355         if (specificationVendor == null && !noEntry) {
1356             specificationVendor = mainAttributes
1357                     .getValue(Attributes.Name.SPECIFICATION_VENDOR);
1358         }
1359         String implementationTitle = packageAttributes
1360                 .getValue(Attributes.Name.IMPLEMENTATION_TITLE);
1361         if (implementationTitle == null && !noEntry) {
1362             implementationTitle = mainAttributes
1363                     .getValue(Attributes.Name.IMPLEMENTATION_TITLE);
1364         }
1365         String implementationVersion = packageAttributes
1366                 .getValue(Attributes.Name.IMPLEMENTATION_VERSION);
1367         if (implementationVersion == null && !noEntry) {
1368             implementationVersion = mainAttributes
1369                     .getValue(Attributes.Name.IMPLEMENTATION_VERSION);
1370         }
1371         String implementationVendor = packageAttributes
1372                 .getValue(Attributes.Name.IMPLEMENTATION_VENDOR);
1373         if (implementationVendor == null && !noEntry) {
1374             implementationVendor = mainAttributes
1375                     .getValue(Attributes.Name.IMPLEMENTATION_VENDOR);
1376         }
1377 
1378         return definePackage(packageName, specificationTitle,
1379                 specificationVersion, specificationVendor, implementationTitle,
1380                 implementationVersion, implementationVendor, isSealed(manifest,
1381                 dirName) ? url : null);
1382     }
1383 
1384     private boolean isSealed(Manifest manifest, String dirName) {
1385         Attributes mainAttributes = manifest.getMainAttributes();
1386         String value = mainAttributes.getValue(Attributes.Name.SEALED);
1387         boolean sealed = value != null && value.toLowerCase(Locale.getDefault()).equals("true"); //$NON-NLS-1$
1388         Attributes attributes = manifest.getAttributes(dirName);
1389         if (attributes != null) {
1390             value = attributes.getValue(Attributes.Name.SEALED);
1391             if (value != null) {
1392                 sealed = value.toLowerCase(Locale.getDefault()).equals("true"); //$NON-NLS-1$
1393             }
1394         }
1395         return sealed;
1396     }
1397 
1398     /**
1399      * returns URLs referenced in the string classpath.
1400      *
1401      * @param root
1402      *            the jar URL that classpath is related to
1403      * @param classpath
1404      *            the relative URLs separated by spaces
1405      * @return URL[] the URLs contained in the string classpath.
1406      */
1407     private ArrayList<URL> getInternalURLs(URL root, String classpath) {
1408         // Class-path attribute is composed of space-separated values.
1409         StringTokenizer tokenizer = new StringTokenizer(classpath);
1410         ArrayList<URL> addedURLs = new ArrayList<URL>();
1411         String file = root.getFile();
1412         int jarIndex = file.lastIndexOf("!/") - 1; //$NON-NLS-1$
1413         int index = file.lastIndexOf("/", jarIndex) + 1; //$NON-NLS-1$
1414         if (index == 0) {
1415             index = file.lastIndexOf(
1416                     System.getProperty("file.separator"), jarIndex) + 1; //$NON-NLS-1$
1417         }
1418         file = file.substring(0, index);
1419         while (tokenizer.hasMoreElements()) {
1420             String element = tokenizer.nextToken();
1421             if (!element.equals("")) { //$NON-NLS-1$
1422                 try {
1423                     // Take absolute path case into consideration
1424                     URL url = new URL(new URL(file), element);
1425                     addedURLs.add(createSearchURL(url));
1426                 } catch (MalformedURLException e) {
1427                     // Nothing is added
1428                 }
1429             }
1430         }
1431         return addedURLs;
1432     }
1433 
1434     Class<?> findClassImpl(String className) {
1435         char dot = '.';
1436         char slash = '/';
1437         int len = className.length();
1438         char[] name = new char [len + 6];
1439         // Poplulate name
1440         className.getChars(0,len, name, 0);
1441         ".class".getChars(0, 6, name, len);
1442         // Replace dots with slashes up to len and remember the index of the last slash.
1443         int lastSlash = -1;
1444         for (int i = 0; i < len; i++){ // len excludes ".class"
1445             if (name[i] == dot) {
1446                 name[i] = slash;
1447                 lastSlash = i;
1448             }
1449         }
1450         // Create our new classFileName
1451         String classFileName = new String(name);
1452         // Share the underlying char[] of classFileName with packageName
1453         String packageName = null;
1454         if (lastSlash != -1) {
1455             packageName = classFileName.substring(0, lastSlash);
1456         }
1457         // Query our URLHandlers for the class.
1458         int n = 0;
1459         while (true) {
1460             URLHandler handler = getHandler(n++);
1461             if (handler == null) {
1462                 break;
1463             }
1464             Class<?> res = handler.findClass(packageName, classFileName, className);
1465             if (res != null) {
1466                 return res;
1467             }
1468         }
1469         return null;
1470 
1471     }
1472     
1473     /**
1474      * Java 6 compatible implementation that overrides Java 7 URLClassLoader.close()
1475      * 
1476      * URLClassLoader implements Closeable in Java 7 to allow resources such
1477      * as open jar files to be released.
1478      * 
1479      * Closes any open resources and prevents this ClassLoader loading any
1480      * additional classes or resources.
1481      * 
1482      * TODO: Add support for nested Exceptions when support for Java 6 is dropped. 
1483      * 
1484      * @throws IOException if closing any file opened by this ClassLoader 
1485      * causes an IOException, only the first instance is thrown.
1486      */
1487     public void close() throws IOException {
1488         synchronized (searchList){
1489             searchList.clear(); // Prevent any new searches.
1490         }
1491         IOException first = null;
1492         synchronized (handlerList){
1493             Iterator<URLHandler> it = handlerList.iterator();
1494             while (it.hasNext()){
1495                 try {
1496                     it.next().close();
1497                 } catch (IOException e){
1498                     if (first == null) first = e;
1499                     else {
1500                         // Log it because it can't be included in returned exceptions.
1501                         logger.log(Level.WARNING, "Unable to close URLHandler during URLClassLoader close()", e);
1502                     }
1503                 }
1504             }
1505             handlerList.clear();
1506             handlerMap.clear();
1507             if (first != null) throw first;
1508         }
1509         // This ClassLoader is no longer able to load any new resources.
1510     }
1511 
1512 }