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