OSDN Git Service

2005-10-22 Erik Edelmann <eedelman@gcc.gnu.org>
[pf3gnuchains/gcc-fork.git] / libjava / java / net / URLClassLoader.java
1 /* URLClassLoader.java --  ClassLoader that loads classes from one or more URLs
2    Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005
3    Free Software Foundation, Inc.
4
5 This file is part of GNU Classpath.
6
7 GNU Classpath is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2, or (at your option)
10 any later version.
11
12 GNU Classpath is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with GNU Classpath; see the file COPYING.  If not, write to the
19 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20 02110-1301 USA.
21
22 Linking this library statically or dynamically with other modules is
23 making a combined work based on this library.  Thus, the terms and
24 conditions of the GNU General Public License cover the whole
25 combination.
26
27 As a special exception, the copyright holders of this library give you
28 permission to link this library with independent modules to produce an
29 executable, regardless of the license terms of these independent
30 modules, and to copy and distribute the resulting executable under
31 terms of your choice, provided that you also meet, for each linked
32 independent module, the terms and conditions of the license of that
33 module.  An independent module is a module which is not derived from
34 or based on this library.  If you modify this library, you may extend
35 this exception to your version of the library, but you are not
36 obligated to do so.  If you do not wish to do so, delete this
37 exception statement from your version. */
38
39
40 package java.net;
41
42 import java.io.ByteArrayOutputStream;
43 import java.io.EOFException;
44 import java.io.File;
45 import java.io.FileInputStream;
46 import java.io.FilePermission;
47 import java.io.IOException;
48 import java.io.InputStream;
49 import java.security.AccessControlContext;
50 import java.security.AccessController;
51 import java.security.CodeSource;
52 import java.security.PermissionCollection;
53 import java.security.PrivilegedAction;
54 import java.security.SecureClassLoader;
55 import java.security.cert.Certificate;
56 import java.util.Enumeration;
57 import java.util.HashMap;
58 import java.util.Iterator;
59 import java.util.StringTokenizer;
60 import java.util.Vector;
61 import java.util.jar.Attributes;
62 import java.util.jar.JarEntry;
63 import java.util.jar.JarFile;
64 import java.util.jar.Manifest;
65 import gnu.gcj.runtime.SharedLibHelper;
66 import gnu.gcj.Core;
67 import gnu.java.net.protocol.core.CoreInputStream;
68
69 /**
70  * A secure class loader that can load classes and resources from
71  * multiple locations.  Given an array of <code>URL</code>s this class
72  * loader will retrieve classes and resources by fetching them from
73  * possible remote locations.  Each <code>URL</code> is searched in
74  * order in which it was added.  If the file portion of the
75  * <code>URL</code> ends with a '/' character then it is interpreted
76  * as a base directory, otherwise it is interpreted as a jar file from
77  * which the classes/resources are resolved.
78  *
79  * <p>New instances can be created by two static
80  * <code>newInstance()</code> methods or by three public
81  * contructors. Both ways give the option to supply an initial array
82  * of <code>URL</code>s and (optionally) a parent classloader (that is
83  * different from the standard system class loader).</p>
84  *
85  * <p>Normally creating a <code>URLClassLoader</code> throws a
86  * <code>SecurityException</code> if a <code>SecurityManager</code> is
87  * installed and the <code>checkCreateClassLoader()</code> method does
88  * not return true.  But the <code>newInstance()</code> methods may be
89  * used by any code as long as it has permission to acces the given
90  * <code>URL</code>s.  <code>URLClassLoaders</code> created by the
91  * <code>newInstance()</code> methods also explicitly call the
92  * <code>checkPackageAccess()</code> method of
93  * <code>SecurityManager</code> if one is installed before trying to
94  * load a class.  Note that only subclasses of
95  * <code>URLClassLoader</code> can add new URLs after the
96  * URLClassLoader had been created. But it is always possible to get
97  * an array of all URLs that the class loader uses to resolve classes
98  * and resources by way of the <code>getURLs()</code> method.</p>
99  *
100  * <p>Open issues:
101  * <ul>
102  *
103  * <li>Should the URLClassLoader actually add the locations found in
104  * the manifest or is this the responsibility of some other
105  * loader/(sub)class?  (see <a
106  * href="http://java.sun.com/products/jdk/1.4/docs/guide/extensions/spec.html">
107  * Extension Mechanism Architecture - Bundles Extensions</a>)</li>
108  *
109  * <li>How does <code>definePackage()</code> and sealing work
110  * precisely?</li>
111  *
112  * <li>We save and use the security context (when a created by
113  * <code>newInstance()</code> but do we have to use it in more
114  * places?</li>
115  *
116  * <li>The use of <code>URLStreamHandler</code>s has not been tested.</li>
117  *
118  * </ul>
119  * </p>
120  *
121  * @since 1.2
122  *
123  * @author Mark Wielaard (mark@klomp.org)
124  * @author Wu Gansha (gansha.wu@intel.com)
125  */
126 public class URLClassLoader extends SecureClassLoader
127 {
128   // Class Variables
129
130   /**
131    * A global cache to store mappings between URLLoader and URL,
132    * so we can avoid do all the homework each time the same URL
133    * comes.
134    * XXX - Keeps these loaders forever which prevents garbage collection.
135    */
136   private static HashMap urlloaders = new HashMap();
137
138   /**
139    * A cache to store mappings between handler factory and its
140    * private protocol handler cache (also a HashMap), so we can avoid
141    * create handlers each time the same protocol comes.
142    */
143   private static HashMap factoryCache = new HashMap(5);
144
145   // Instance variables
146
147   /** Locations to load classes from */
148   private final Vector urls = new Vector();
149
150   /**
151    * Store pre-parsed information for each url into this vector: each
152    * element is a URL loader.  A jar file has its own class-path
153    * attribute which adds to the URLs that will be searched, but this
154    * does not add to the list of urls.
155    */
156   private final Vector urlinfos = new Vector();
157
158   /** Factory used to get the protocol handlers of the URLs */
159   private final URLStreamHandlerFactory factory;
160
161   /**
162    * The security context when created from <code>newInstance()</code>
163    * or null when created through a normal constructor or when no
164    * <code>SecurityManager</code> was installed.
165    */
166   private final AccessControlContext securityContext;
167
168   // Helper classes
169
170   /**
171    * A <code>URLLoader</code> contains all logic to load resources from a
172    * given base <code>URL</code>.
173    */
174   abstract static class URLLoader
175   {
176     /**
177      * Our classloader to get info from if needed.
178      */
179     final URLClassLoader classloader;
180
181     /**
182      * The base URL from which all resources are loaded.
183      */
184     final URL baseURL;
185
186     /**
187      * A <code>CodeSource</code> without any associated certificates.
188      * It is common for classes to not have certificates associated
189      * with them.  If they come from the same <code>URLLoader</code>
190      * then it is safe to share the associated <code>CodeSource</code>
191      * between them since <code>CodeSource</code> is immutable.
192      */
193     final CodeSource noCertCodeSource;
194
195     URLLoader(URLClassLoader classloader, URL baseURL)
196     {
197       this(classloader, baseURL, baseURL);
198     }
199
200     URLLoader(URLClassLoader classloader, URL baseURL, URL overrideURL)
201     {
202       this.classloader = classloader;
203       this.baseURL = baseURL;
204       this.noCertCodeSource = new CodeSource(overrideURL, null);
205     }
206
207     /**
208      * Returns a <code>Class</code> loaded by this
209      * <code>URLLoader</code>, or <code>null</code> when this loader
210      * either can't load the class or doesn't know how to load classes
211      * at all.
212      */
213     Class getClass(String className)
214     {
215       return null;
216     }
217
218     /**
219      * Returns a <code>Resource</code> loaded by this
220      * <code>URLLoader</code>, or <code>null</code> when no
221      * <code>Resource</code> with the given name exists.
222      */
223     abstract Resource getResource(String s);
224
225     /**
226      * Returns the <code>Manifest</code> associated with the
227      * <code>Resource</code>s loaded by this <code>URLLoader</code> or
228      * <code>null</code> there is no such <code>Manifest</code>.
229      */
230     Manifest getManifest()
231     {
232       return null;
233     }
234
235     Vector getClassPath()
236     {
237       return null;
238     }
239   }
240
241   /**
242    * A <code>Resource</code> represents a resource in some
243    * <code>URLLoader</code>. It also contains all information (e.g.,
244    * <code>URL</code>, <code>CodeSource</code>, <code>Manifest</code> and
245    * <code>InputStream</code>) that is necessary for loading resources
246    * and creating classes from a <code>URL</code>.
247    */
248   abstract static class Resource
249   {
250     final URLLoader loader;
251     final String name;
252
253     Resource(URLLoader loader, String name)
254     {
255       this.loader = loader;
256       this.name = name;
257     }
258
259     /**
260      * Returns the non-null <code>CodeSource</code> associated with
261      * this resource.
262      */
263     CodeSource getCodeSource()
264     {
265       Certificate[] certs = getCertificates();
266       if (certs == null)
267         return loader.noCertCodeSource;
268       else
269         return new CodeSource(loader.baseURL, certs);
270     }
271
272     /**
273      * Returns <code>Certificates</code> associated with this
274      * resource, or null when there are none.
275      */
276     Certificate[] getCertificates()
277     {
278       return null;
279     }
280
281     /**
282      * Return a <code>URL</code> that can be used to access this resource.
283      */
284     abstract URL getURL();
285
286     /**
287      * Returns the size of this <code>Resource</code> in bytes or
288      * <code>-1</code> when unknown.
289      */
290     abstract int getLength();
291
292     /**
293      * Returns the non-null <code>InputStream</code> through which
294      * this resource can be loaded.
295      */
296     abstract InputStream getInputStream() throws IOException;
297   }
298
299   /**
300    * A <code>JarURLLoader</code> is a type of <code>URLLoader</code>
301    * only loading from jar url.
302    */
303   static final class JarURLLoader extends URLLoader
304   {
305     final JarFile jarfile; // The jar file for this url
306     final URL baseJarURL; // Base jar: url for all resources loaded from jar
307
308     Vector classPath;   // The "Class-Path" attribute of this Jar's manifest
309
310     public JarURLLoader(URLClassLoader classloader, URL baseURL)
311     {
312       super(classloader, baseURL);
313
314       // Cache url prefix for all resources in this jar url.
315       String external = baseURL.toExternalForm();
316       StringBuffer sb = new StringBuffer(external.length() + 6);
317       sb.append("jar:");
318       sb.append(external);
319       sb.append("!/");
320       String jarURL = sb.toString();
321
322       this.classPath = null;
323       URL baseJarURL = null;
324       JarFile jarfile = null;
325       try
326         {
327           baseJarURL =
328             new URL(null, jarURL, classloader.getURLStreamHandler("jar"));
329           
330           jarfile =
331             ((JarURLConnection) baseJarURL.openConnection()).getJarFile();
332           
333           Manifest manifest;
334           Attributes attributes;
335           String classPathString;
336
337           if ((manifest = jarfile.getManifest()) != null
338               && (attributes = manifest.getMainAttributes()) != null
339               && ((classPathString 
340                    = attributes.getValue(Attributes.Name.CLASS_PATH)) 
341                   != null))
342             {
343               this.classPath = new Vector();
344               
345               StringTokenizer st = new StringTokenizer(classPathString, " ");
346               while (st.hasMoreElements ()) 
347                 {  
348                   String e = st.nextToken ();
349                   try
350                     {
351                       URL url = new URL(baseURL, e);
352                       this.classPath.add(url);
353                     } 
354                   catch (java.net.MalformedURLException xx)
355                     {
356                       // Give up
357                     }
358                 }
359             }
360         }
361       catch (IOException ioe)
362         {
363           /* ignored */
364         }
365
366       this.baseJarURL = baseJarURL;
367       this.jarfile = jarfile;
368     }
369
370     /** get resource with the name "name" in the jar url */
371     Resource getResource(String name)
372     {
373       if (jarfile == null)
374         return null;
375
376       if (name.startsWith("/"))
377         name = name.substring(1);
378
379       JarEntry je = jarfile.getJarEntry(name);
380       if (je != null)
381         return new JarURLResource(this, name, je);
382       else
383         return null;
384     }
385
386     Manifest getManifest()
387     {
388       try
389         {
390           return (jarfile == null) ? null : jarfile.getManifest();
391         }
392       catch (IOException ioe)
393         {
394           return null;
395         }
396     }
397
398     Vector getClassPath()
399     {
400       return classPath;
401     }
402   }
403
404   static final class JarURLResource extends Resource
405   {
406     private final JarEntry entry;
407
408     JarURLResource(JarURLLoader loader, String name, JarEntry entry)
409     {
410       super(loader, name);
411       this.entry = entry;
412     }
413
414     InputStream getInputStream() throws IOException
415     {
416       return ((JarURLLoader) loader).jarfile.getInputStream(entry);
417     }
418
419     int getLength()
420     {
421       return (int) entry.getSize();
422     }
423
424     Certificate[] getCertificates()
425     {
426       // We have to get the entry from the jar file again, because the
427       // certificates will not be available until the entire entry has
428       // been read.
429       return ((JarEntry) ((JarURLLoader) loader).jarfile.getEntry(name))
430         .getCertificates();
431     }
432
433     URL getURL()
434     {
435       try
436         {
437           return new URL(((JarURLLoader) loader).baseJarURL, name,
438                          loader.classloader.getURLStreamHandler("jar"));
439         }
440       catch (MalformedURLException e)
441         {
442           InternalError ie = new InternalError();
443           ie.initCause(e);
444           throw ie;
445         }
446     }
447   }
448
449   /**
450    * Loader for remote directories.
451    */
452   static final class RemoteURLLoader extends URLLoader
453   {
454     private final String protocol;
455
456     RemoteURLLoader(URLClassLoader classloader, URL url)
457     {
458       super(classloader, url);
459       protocol = url.getProtocol();
460     }
461
462     /**
463      * Get a remote resource.
464      * Returns null if no such resource exists.
465      */
466     Resource getResource(String name)
467     {
468       try
469         {
470           URL url =
471             new URL(baseURL, name, classloader.getURLStreamHandler(protocol));
472           URLConnection connection = url.openConnection();
473
474           // Open the connection and check the stream
475           // just to be sure it exists.
476           int length = connection.getContentLength();
477           InputStream stream = connection.getInputStream();
478
479           // We can do some extra checking if it is a http request
480           if (connection instanceof HttpURLConnection)
481             {
482               int response =
483                 ((HttpURLConnection) connection).getResponseCode();
484               if (response / 100 != 2)
485                 return null;
486             }
487
488           if (stream != null)
489             return new RemoteResource(this, name, url, stream, length);
490           else
491             return null;
492         }
493       catch (IOException ioe)
494         {
495           return null;
496         }
497     }
498   }
499
500   /**
501    * A resource from some remote location.
502    */
503   static final class RemoteResource extends Resource
504   {
505     private final URL url;
506     private final InputStream stream;
507     private final int length;
508
509     RemoteResource(RemoteURLLoader loader, String name, URL url,
510                    InputStream stream, int length)
511     {
512       super(loader, name);
513       this.url = url;
514       this.stream = stream;
515       this.length = length;
516     }
517
518     InputStream getInputStream() throws IOException
519     {
520       return stream;
521     }
522
523     public int getLength()
524     {
525       return length;
526     }
527
528     public URL getURL()
529     {
530       return url;
531     }
532   }
533
534   /**
535    * A <code>SoURLLoader</code> is a type of <code>URLLoader</code>
536    * that loads classes and resources from a shared library.
537    */
538   final static class SoURLLoader extends URLLoader
539   {
540     SharedLibHelper helper;
541
542     SoURLLoader(URLClassLoader classloader, URL url)
543     {
544       this(classloader, url, url);
545     }
546
547     SoURLLoader(URLClassLoader classloader, URL url, URL overrideURL)
548     {
549       super(classloader, url, overrideURL);
550       helper = SharedLibHelper.findHelper(classloader, url.getFile(),
551                                           noCertCodeSource, true);
552     }
553
554     Class getClass(String className)
555     {
556       return helper.findClass(className);
557     }
558
559     Resource getResource(String name)
560     {
561       URL url = helper.findResource(name);
562       if (url == null)
563         return null;
564       return new SoResource(this, name, url);
565     }
566   }
567
568   final static class SoResource extends Resource
569   {
570     SoResource(SoURLLoader loader, String name, URL url)
571     {
572       super(loader, name);
573       this.url = url;
574     }
575
576     InputStream getInputStream() throws IOException
577     {
578       URLConnection conn = url.openConnection();
579       return conn.getInputStream();
580     }
581
582     public int getLength()
583     {
584       // FIXME we could find this by asking the core object.
585       return -1;
586     }
587
588     public URL getURL ()
589     {
590       return url;
591     }
592
593     final URL url;
594   }
595
596   /**
597    * A <code>FileURLLoader</code> is a type of <code>URLLoader</code>
598    * only loading from file url.
599    */
600   static final class FileURLLoader extends URLLoader
601   {
602     File dir; //the file for this file url
603
604     FileURLLoader(URLClassLoader classloader, URL url)
605     {
606       super(classloader, url);
607       dir = new File(baseURL.getFile());
608     }
609
610     /** get resource with the name "name" in the file url */
611     Resource getResource(String name)
612     {
613       try 
614         {
615           File file = new File(dir, name).getCanonicalFile();
616           if (file.exists() && !file.isDirectory())
617             return new FileResource(this, file.getPath(), file);
618         }
619       catch (IOException e)
620         {
621           // Fall through...
622         }
623       return null;
624     }
625   }
626
627   static final class FileResource extends Resource
628   {
629     final File file;
630
631     FileResource(FileURLLoader loader, String name, File file)
632     {
633       super(loader, name);
634       this.file = file;
635     }
636
637     InputStream getInputStream() throws IOException
638     {
639       // Delegate to the URL content handler mechanism to retrieve an
640       // HTML representation of the directory listing if a directory
641       if (file.isDirectory())
642         {
643           URL url = getURL();
644           return url.openStream();
645         }
646       // Otherwise simply return a FileInputStream
647       return new FileInputStream(file);
648     }
649
650     public int getLength()
651     {
652       // Delegate to the URL content handler mechanism to retrieve the
653       // length of the HTML representation of the directory listing if
654       // a directory, or -1 if an exception occurs opening the directory.
655       if (file.isDirectory())
656         {
657           URL url = getURL();
658           try
659             {
660               URLConnection connection = url.openConnection();
661               return connection.getContentLength();
662             }
663           catch (IOException e)
664             {
665               return -1;
666             }
667         }
668       // Otherwise simply return the file length
669       return (int) file.length();
670     }
671
672     public URL getURL()
673     {
674       try
675         {
676           return new URL(loader.baseURL, name,
677                          loader.classloader.getURLStreamHandler("file"));
678         }
679       catch (MalformedURLException e)
680         {
681           InternalError ie = new InternalError();
682           ie.initCause(e);
683           throw ie;
684         }
685     }
686   }
687
688   /**
689    * A <code>CoreURLLoader</code> is a type of <code>URLLoader</code>
690    * only loading from core url.
691    */
692   static final class CoreURLLoader extends URLLoader
693   {
694     private String dir;
695
696     CoreURLLoader(URLClassLoader classloader, URL url)
697     {
698       super(classloader, url);
699       dir = baseURL.getFile();
700     }
701
702     /** get resource with the name "name" in the core url */
703     Resource getResource(String name)
704     {
705       Core core = Core.find (dir + name);
706       if (core != null)
707         return new CoreResource(this, name, core);
708       return null;
709     }
710   }
711
712   static final class CoreResource extends Resource
713   {
714     final Core core;
715
716     CoreResource(CoreURLLoader loader, String name, Core core)
717     {
718       super(loader, name);
719       this.core = core;
720     }
721
722     InputStream getInputStream() throws IOException
723     {
724       return new CoreInputStream(core);
725     }
726
727     public int getLength()
728     {
729       return core.length;
730     }
731
732     public URL getURL()
733     {
734       try
735         {
736           return new URL(loader.baseURL, name,
737                          loader.classloader.getURLStreamHandler("core"));
738         }
739       catch (MalformedURLException e)
740         {
741           InternalError ie = new InternalError();
742           ie.initCause(e);
743           throw ie;
744         }
745     }
746   }
747
748   // Constructors
749
750   /**
751    * Creates a URLClassLoader that gets classes from the supplied URLs.
752    * To determine if this classloader may be created the constructor of
753    * the super class (<code>SecureClassLoader</code>) is called first, which
754    * can throw a SecurityException. Then the supplied URLs are added
755    * in the order given to the URLClassLoader which uses these URLs to
756    * load classes and resources (after using the default parent ClassLoader).
757    *
758    * @exception SecurityException if the SecurityManager disallows the
759    * creation of a ClassLoader.
760    * @param urls Locations that should be searched by this ClassLoader when
761    * resolving Classes or Resources.
762    * @see SecureClassLoader
763    */
764   public URLClassLoader(URL[] urls) throws SecurityException
765   {
766     super();
767     this.factory = null;
768     this.securityContext = null;
769     addURLs(urls);
770   }
771
772   /**
773    * Private constructor used by the static
774    * <code>newInstance(URL[])</code> method.  Creates an
775    * <code>URLClassLoader</code> without any <code>URL</code>s
776    * yet. This is used to bypass the normal security check for
777    * creating classloaders, but remembers the security context which
778    * will be used when defining classes.  The <code>URL</code>s to
779    * load from must be added by the <code>newInstance()</code> method
780    * in the security context of the caller.
781    *
782    * @param securityContext the security context of the unprivileged code.
783    */
784   private URLClassLoader(AccessControlContext securityContext)
785   {
786     super();
787     this.factory = null;
788     this.securityContext = securityContext;
789   }
790
791   /**
792    * Creates a <code>URLClassLoader</code> that gets classes from the supplied
793    * <code>URL</code>s.
794    * To determine if this classloader may be created the constructor of
795    * the super class (<code>SecureClassLoader</code>) is called first, which
796    * can throw a SecurityException. Then the supplied URLs are added
797    * in the order given to the URLClassLoader which uses these URLs to
798    * load classes and resources (after using the supplied parent ClassLoader).
799    * @exception SecurityException if the SecurityManager disallows the
800    * creation of a ClassLoader.
801    * @exception SecurityException
802    * @param urls Locations that should be searched by this ClassLoader when
803    * resolving Classes or Resources.
804    * @param parent The parent class loader used before trying this class
805    * loader.
806    * @see SecureClassLoader
807    */
808   public URLClassLoader(URL[] urls, ClassLoader parent)
809     throws SecurityException
810   {
811     super(parent);
812     this.factory = null;
813     this.securityContext = null;
814     addURLs(urls);
815   }
816
817   // Package-private to avoid a trampoline constructor.
818   /**
819    * Package-private constructor used by the static
820    * <code>newInstance(URL[])</code> method.  Creates an
821    * <code>URLClassLoader</code> with the given parent but without any
822    * <code>URL</code>s yet. This is used to bypass the normal security
823    * check for creating classloaders, but remembers the security
824    * context which will be used when defining classes.  The
825    * <code>URL</code>s to load from must be added by the
826    * <code>newInstance()</code> method in the security context of the
827    * caller.
828    *
829    * @param securityContext the security context of the unprivileged code.
830    */
831   URLClassLoader(ClassLoader parent, AccessControlContext securityContext)
832   {
833     super(parent);
834     this.factory = null;
835     this.securityContext = securityContext;
836   }
837
838   /**
839    * Creates a URLClassLoader that gets classes from the supplied URLs.
840    * To determine if this classloader may be created the constructor of
841    * the super class (<CODE>SecureClassLoader</CODE>) is called first, which
842    * can throw a SecurityException. Then the supplied URLs are added
843    * in the order given to the URLClassLoader which uses these URLs to
844    * load classes and resources (after using the supplied parent ClassLoader).
845    * It will use the supplied <CODE>URLStreamHandlerFactory</CODE> to get the
846    * protocol handlers of the supplied URLs.
847    * @exception SecurityException if the SecurityManager disallows the
848    * creation of a ClassLoader.
849    * @exception SecurityException
850    * @param urls Locations that should be searched by this ClassLoader when
851    * resolving Classes or Resources.
852    * @param parent The parent class loader used before trying this class
853    * loader.
854    * @param factory Used to get the protocol handler for the URLs.
855    * @see SecureClassLoader
856    */
857   public URLClassLoader(URL[] urls, ClassLoader parent,
858                         URLStreamHandlerFactory factory)
859     throws SecurityException
860   {
861     super(parent);
862     this.securityContext = null;
863     this.factory = factory;
864     addURLs(urls);
865
866     // If this factory is still not in factoryCache, add it,
867     //   since we only support three protocols so far, 5 is enough
868     //   for cache initial size
869     synchronized (factoryCache)
870       {
871         if (factory != null && factoryCache.get(factory) == null)
872           factoryCache.put(factory, new HashMap(5));
873       }
874   }
875
876   // Methods
877
878   /**
879    * Adds a new location to the end of the internal URL store.
880    * @param newUrl the location to add
881    */
882   protected void addURL(URL newUrl)
883   {
884     urls.add(newUrl);
885     addURLImpl(newUrl);
886   }
887
888   private void addURLImpl(URL newUrl)
889   {
890     synchronized (this)
891       {
892         if (newUrl == null)
893           return; // Silently ignore...
894
895         // Reset the toString() value.
896         thisString = null;
897
898         // Check global cache to see if there're already url loader
899         // for this url.
900         URLLoader loader = (URLLoader) urlloaders.get(newUrl);
901         if (loader == null)
902           {
903             String file = newUrl.getFile();
904             String protocol = newUrl.getProtocol();
905
906             // Check that it is not a directory
907             if ("gcjlib".equals(protocol))
908               loader = new SoURLLoader(this, newUrl);
909             else if (! (file.endsWith("/") || file.endsWith(File.separator)))
910               loader = new JarURLLoader(this, newUrl);
911             else if ("file".equals(protocol))
912               loader = new FileURLLoader(this, newUrl);
913             else if ("core".equals(protocol))
914               loader = new CoreURLLoader(this, newUrl);
915             else
916               loader = new RemoteURLLoader(this, newUrl);
917
918             // Cache it.
919             urlloaders.put(newUrl, loader);
920           }
921
922         urlinfos.add(loader);
923
924         Vector extraUrls = loader.getClassPath();
925         if (extraUrls != null)
926           {
927             Iterator it = extraUrls.iterator();
928             while (it.hasNext())
929               {
930                 URL url = (URL)it.next();
931                 URLLoader extraLoader = (URLLoader) urlloaders.get(url);
932                 if (! urlinfos.contains (extraLoader))
933                   addURLImpl(url);
934               }
935           }
936
937       }
938   }
939
940   /**
941    * Adds an array of new locations to the end of the internal URL store.
942    * @param newUrls the locations to add
943    */
944   private void addURLs(URL[] newUrls)
945   {
946     for (int i = 0; i < newUrls.length; i++)
947       addURL(newUrls[i]);
948   }
949
950   /**
951    * Look in both Attributes for a given value.  The first Attributes
952    * object, if not null, has precedence.
953    */
954   private String getAttributeValue(Attributes.Name name, Attributes first,
955                                    Attributes second)
956   {
957     String result = null;
958     if (first != null)
959       result = first.getValue(name);
960     if (result == null)
961       result = second.getValue(name);
962     return result;
963   }
964
965   /**
966    * Defines a Package based on the given name and the supplied manifest
967    * information. The manifest indicates the title, version and
968    * vendor information of the specification and implementation and whether the
969    * package is sealed. If the Manifest indicates that the package is sealed
970    * then the Package will be sealed with respect to the supplied URL.
971    *
972    * @exception IllegalArgumentException If this package name already exists
973    * in this class loader
974    * @param name The name of the package
975    * @param manifest The manifest describing the specification,
976    * implementation and sealing details of the package
977    * @param url the code source url to seal the package
978    * @return the defined Package
979    */
980   protected Package definePackage(String name, Manifest manifest, URL url)
981     throws IllegalArgumentException
982   {
983     // Compute the name of the package as it may appear in the
984     // Manifest.
985     StringBuffer xform = new StringBuffer(name);
986     for (int i = xform.length () - 1; i >= 0; --i)
987       if (xform.charAt(i) == '.')
988         xform.setCharAt(i, '/');
989     xform.append('/');
990     String xformName = xform.toString();
991
992     Attributes entryAttr = manifest.getAttributes(xformName);
993     Attributes attr = manifest.getMainAttributes();
994
995     String specTitle
996       = getAttributeValue(Attributes.Name.SPECIFICATION_TITLE,
997                           entryAttr, attr);
998     String specVersion
999       = getAttributeValue(Attributes.Name.SPECIFICATION_VERSION,
1000                           entryAttr, attr);
1001     String specVendor
1002       = getAttributeValue(Attributes.Name.SPECIFICATION_VENDOR,
1003                           entryAttr, attr);
1004     String implTitle
1005       = getAttributeValue(Attributes.Name.IMPLEMENTATION_TITLE,
1006                           entryAttr, attr);
1007     String implVersion
1008       = getAttributeValue(Attributes.Name.IMPLEMENTATION_VERSION,
1009                           entryAttr, attr);
1010     String implVendor
1011       = getAttributeValue(Attributes.Name.IMPLEMENTATION_VENDOR,
1012                           entryAttr, attr);
1013
1014     // Look if the Manifest indicates that this package is sealed
1015     // XXX - most likely not completely correct!
1016     // Shouldn't we also check the sealed attribute of the complete jar?
1017     // http://java.sun.com/products/jdk/1.4/docs/guide/extensions/spec.html#bundled
1018     // But how do we get that jar manifest here?
1019     String sealed = attr.getValue(Attributes.Name.SEALED);
1020     if ("false".equals(sealed))
1021       // make sure that the URL is null so the package is not sealed
1022       url = null;
1023
1024     return definePackage(name,
1025                          specTitle, specVendor, specVersion,
1026                          implTitle, implVendor, implVersion,
1027                          url);
1028   }
1029
1030   /**
1031    * Finds (the first) class by name from one of the locations. The locations
1032    * are searched in the order they were added to the URLClassLoader.
1033    *
1034    * @param className the classname to find
1035    * @exception ClassNotFoundException when the class could not be found or
1036    * loaded
1037    * @return a Class object representing the found class
1038    */
1039   protected Class findClass(final String className)
1040     throws ClassNotFoundException
1041   {
1042     // Just try to find the resource by the (almost) same name
1043     String resourceName = className.replace('.', '/') + ".class";
1044     int max = urlinfos.size();
1045     Resource resource = null;
1046     for (int i = 0; i < max && resource == null; i++)
1047       {
1048         URLLoader loader = (URLLoader)urlinfos.elementAt(i);
1049         if (loader == null)
1050           continue;
1051
1052         Class k = loader.getClass(className);
1053         if (k != null)
1054           return k;
1055
1056         resource = loader.getResource(resourceName);
1057       }
1058     if (resource == null)
1059       throw new ClassNotFoundException(className + " not found in " + this);
1060
1061     // Try to read the class data, create the CodeSource, Package and
1062     // construct the class (and watch out for those nasty IOExceptions)
1063     try
1064       {
1065         byte[] data;
1066         InputStream in = resource.getInputStream();
1067         int length = resource.getLength();
1068         if (length != -1)
1069           {
1070             // We know the length of the data.
1071             // Just try to read it in all at once
1072             data = new byte[length];
1073             int pos = 0;
1074             while (length - pos > 0)
1075               {
1076                 int len = in.read(data, pos, length - pos);
1077                 if (len == -1)
1078                   throw new EOFException("Not enough data reading from: "
1079                                          + in);
1080                 pos += len;
1081               }
1082           }
1083         else
1084           {
1085             // We don't know the data length.
1086             // Have to read it in chunks.
1087             ByteArrayOutputStream out = new ByteArrayOutputStream(4096);
1088             byte[] b = new byte[4096];
1089             int l = 0;
1090             while (l != -1)
1091               {
1092                 l = in.read(b);
1093                 if (l != -1)
1094                   out.write(b, 0, l);
1095               }
1096             data = out.toByteArray();
1097           }
1098         final byte[] classData = data;
1099
1100         // Now get the CodeSource
1101         final CodeSource source = resource.getCodeSource();
1102
1103         // Find out package name
1104         String packageName = null;
1105         int lastDot = className.lastIndexOf('.');
1106         if (lastDot != -1)
1107           packageName = className.substring(0, lastDot);
1108
1109         if (packageName != null && getPackage(packageName) == null)
1110           {
1111             // define the package
1112             Manifest manifest = resource.loader.getManifest();
1113             if (manifest == null)
1114               definePackage(packageName, null, null, null, null, null, null,
1115                             null);
1116             else
1117               definePackage(packageName, manifest, resource.loader.baseURL);
1118           }
1119
1120         // And finally construct the class!
1121         SecurityManager sm = System.getSecurityManager();
1122         Class result = null;
1123         if (sm != null && securityContext != null)
1124           {
1125             result = (Class)AccessController.doPrivileged
1126               (new PrivilegedAction()
1127                 {
1128                   public Object run()
1129                   {
1130                     return defineClass(className, classData,
1131                                        0, classData.length,
1132                                        source);
1133                   }
1134                 }, securityContext);
1135           }
1136         else
1137           result = defineClass(className, classData, 0, classData.length, source);
1138
1139         super.setSigners(result, resource.getCertificates());
1140         return result;
1141       }
1142     catch (IOException ioe)
1143       {
1144         ClassNotFoundException cnfe;
1145         cnfe = new ClassNotFoundException(className + " not found in " + this);
1146         cnfe.initCause(ioe);
1147         throw cnfe;
1148       }
1149   }
1150   
1151   // Cached String representation of this URLClassLoader
1152   private String thisString;
1153   
1154   /**
1155    * Returns a String representation of this URLClassLoader giving the
1156    * actual Class name, the URLs that are searched and the parent
1157    * ClassLoader.
1158    */
1159   public String toString()
1160   {
1161     synchronized (this)
1162       {
1163         if (thisString == null)
1164           {
1165             StringBuffer sb = new StringBuffer();
1166             sb.append(this.getClass().getName());
1167             sb.append("{urls=[" );
1168             URL[] thisURLs = getURLs();
1169             for (int i = 0; i < thisURLs.length; i++)
1170               {
1171                 sb.append(thisURLs[i]);
1172                 if (i < thisURLs.length - 1)
1173                   sb.append(',');
1174               }
1175             sb.append(']');
1176             sb.append(", parent=");
1177             sb.append(getParent());
1178             sb.append('}');
1179             thisString = sb.toString();
1180           }
1181         return thisString;
1182       }
1183   }
1184
1185   /**
1186    * Finds the first occurrence of a resource that can be found. The locations
1187    * are searched in the order they were added to the URLClassLoader.
1188    *
1189    * @param resourceName the resource name to look for
1190    * @return the URLResource for the resource if found, null otherwise
1191    */
1192   private Resource findURLResource(String resourceName)
1193   {
1194     int max = urlinfos.size();
1195     for (int i = 0; i < max; i++)
1196       {
1197         URLLoader loader = (URLLoader) urlinfos.elementAt(i);
1198         if (loader == null)
1199           continue;
1200
1201         Resource resource = loader.getResource(resourceName);
1202         if (resource != null)
1203           return resource;
1204       }
1205     return null;
1206   }
1207
1208   /**
1209    * Finds the first occurrence of a resource that can be found.
1210    *
1211    * @param resourceName the resource name to look for
1212    * @return the URL if found, null otherwise
1213    */
1214   public URL findResource(String resourceName)
1215   {
1216     Resource resource = findURLResource(resourceName);
1217     if (resource != null)
1218       return resource.getURL();
1219
1220     // Resource not found
1221     return null;
1222   }
1223
1224   /**
1225    * If the URLStreamHandlerFactory has been set this return the appropriate
1226    * URLStreamHandler for the given protocol, if not set returns null.
1227    *
1228    * @param protocol the protocol for which we need a URLStreamHandler
1229    * @return the appropriate URLStreamHandler or null
1230    */
1231   URLStreamHandler getURLStreamHandler(String protocol)
1232   {
1233     if (factory == null)
1234       return null;
1235
1236     URLStreamHandler handler;
1237     synchronized (factoryCache)
1238       {
1239         // Check if there're handler for the same protocol in cache.
1240         HashMap cache = (HashMap) factoryCache.get(factory);
1241         handler = (URLStreamHandler) cache.get(protocol);
1242         if (handler == null)
1243           {
1244             // Add it to cache.
1245             handler = factory.createURLStreamHandler(protocol);
1246             cache.put(protocol, handler);
1247           }
1248       }
1249     return handler;
1250   }
1251
1252   /**
1253    * Finds all the resources with a particular name from all the locations.
1254    *
1255    * @exception IOException when an error occurs accessing one of the
1256    * locations
1257    * @param resourceName the name of the resource to lookup
1258    * @return a (possible empty) enumeration of URLs where the resource can be
1259    * found
1260    */
1261   public Enumeration findResources(String resourceName)
1262     throws IOException
1263   {
1264     Vector resources = new Vector();
1265     int max = urlinfos.size();
1266     for (int i = 0; i < max; i++)
1267       {
1268         URLLoader loader = (URLLoader) urlinfos.elementAt(i);
1269         Resource resource = loader.getResource(resourceName);
1270         if (resource != null)
1271           resources.add(resource.getURL());
1272       }
1273     return resources.elements();
1274   }
1275
1276   /**
1277    * Returns the permissions needed to access a particular code
1278    * source.  These permissions includes those returned by
1279    * <code>SecureClassLoader.getPermissions()</code> and the actual
1280    * permissions to access the objects referenced by the URL of the
1281    * code source.  The extra permissions added depend on the protocol
1282    * and file portion of the URL in the code source. If the URL has
1283    * the "file" protocol ends with a '/' character then it must be a
1284    * directory and a file Permission to read everything in that
1285    * directory and all subdirectories is added. If the URL had the
1286    * "file" protocol and doesn't end with a '/' character then it must
1287    * be a normal file and a file permission to read that file is
1288    * added. If the <code>URL</code> has any other protocol then a
1289    * socket permission to connect and accept connections from the host
1290    * portion of the URL is added.
1291    *
1292    * @param source The codesource that needs the permissions to be accessed
1293    * @return the collection of permissions needed to access the code resource
1294    * @see java.security.SecureClassLoader#getPermissions()
1295    */
1296   protected PermissionCollection getPermissions(CodeSource source)
1297   {
1298     // XXX - This implementation does exactly as the Javadoc describes.
1299     // But maybe we should/could use URLConnection.getPermissions()?
1300     // First get the permissions that would normally be granted
1301     PermissionCollection permissions = super.getPermissions(source);
1302
1303     // Now add any extra permissions depending on the URL location.
1304     URL url = source.getLocation();
1305     String protocol = url.getProtocol();
1306     if (protocol.equals("file"))
1307       {
1308         String file = url.getFile();
1309
1310         // If the file end in / it must be an directory.
1311         if (file.endsWith("/") || file.endsWith(File.separator))
1312           {
1313             // Grant permission to read everything in that directory and
1314             // all subdirectories.
1315             permissions.add(new FilePermission(file + "-", "read"));
1316           }
1317         else
1318           {
1319             // It is a 'normal' file.
1320             // Grant permission to access that file.
1321             permissions.add(new FilePermission(file, "read"));
1322           }
1323       }
1324     else
1325       {
1326         // Grant permission to connect to and accept connections from host
1327         String host = url.getHost();
1328         if (host != null)
1329           permissions.add(new SocketPermission(host, "connect,accept"));
1330       }
1331
1332     return permissions;
1333   }
1334
1335   /**
1336    * Returns all the locations that this class loader currently uses the
1337    * resolve classes and resource. This includes both the initially supplied
1338    * URLs as any URLs added later by the loader.
1339    * @return All the currently used URLs
1340    */
1341   public URL[] getURLs()
1342   {
1343     return (URL[]) urls.toArray(new URL[urls.size()]);
1344   }
1345
1346   /**
1347    * Creates a new instance of a <code>URLClassLoader</code> that gets
1348    * classes from the supplied <code>URL</code>s. This class loader
1349    * will have as parent the standard system class loader.
1350    *
1351    * @param urls the initial URLs used to resolve classes and
1352    * resources
1353    *
1354    * @return the class loader
1355    *
1356    * @exception SecurityException when the calling code does not have
1357    * permission to access the given <code>URL</code>s
1358    */
1359   public static URLClassLoader newInstance(URL[] urls)
1360     throws SecurityException
1361   {
1362     return newInstance(urls, null);
1363   }
1364
1365   /**
1366    * Creates a new instance of a <code>URLClassLoader</code> that gets
1367    * classes from the supplied <code>URL</code>s and with the supplied
1368    * loader as parent class loader.
1369    *
1370    * @param urls the initial URLs used to resolve classes and
1371    * resources
1372    * @param parent the parent class loader
1373    *
1374    * @return the class loader
1375    *
1376    * @exception SecurityException when the calling code does not have
1377    * permission to access the given <code>URL</code>s
1378    */
1379   public static URLClassLoader newInstance(URL[] urls, final ClassLoader parent)
1380     throws SecurityException
1381   {
1382     SecurityManager sm = System.getSecurityManager();
1383     if (sm == null)
1384       return new URLClassLoader(urls, parent);
1385     else
1386       {
1387         final Object securityContext = sm.getSecurityContext();
1388
1389         // XXX - What to do with anything else then an AccessControlContext?
1390         if (! (securityContext instanceof AccessControlContext))
1391           throw new SecurityException("securityContext must be AccessControlContext: "
1392                                       + securityContext);
1393
1394         URLClassLoader loader =
1395           (URLClassLoader) AccessController.doPrivileged(new PrivilegedAction()
1396               {
1397                 public Object run()
1398                 {
1399                   return new URLClassLoader(parent,
1400                                             (AccessControlContext) securityContext);
1401                 }
1402               });
1403         loader.addURLs(urls);
1404         return loader;
1405       }
1406   }
1407 }