OSDN Git Service

2003-06-24 Michael Koch <konqueror@gmx.de>
[pf3gnuchains/gcc-fork.git] / libjava / java / net / URL.java
1 /* URL.java -- Uniform Resource Locator Class
2    Copyright (C) 1998, 1999, 2000, 2002, 2003  Free Software Foundation, Inc.
3
4 This file is part of GNU Classpath.
5
6 GNU Classpath is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2, or (at your option)
9 any later version.
10  
11 GNU Classpath is distributed in the hope that it will be useful, but
12 WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with GNU Classpath; see the file COPYING.  If not, write to the
18 Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
19 02111-1307 USA.
20
21 Linking this library statically or dynamically with other modules is
22 making a combined work based on this library.  Thus, the terms and
23 conditions of the GNU General Public License cover the whole
24 combination.
25
26 As a special exception, the copyright holders of this library give you
27 permission to link this library with independent modules to produce an
28 executable, regardless of the license terms of these independent
29 modules, and to copy and distribute the resulting executable under
30 terms of your choice, provided that you also meet, for each linked
31 independent module, the terms and conditions of the license of that
32 module.  An independent module is a module which is not derived from
33 or based on this library.  If you modify this library, you may extend
34 this exception to your version of the library, but you are not
35 obligated to do so.  If you do not wish to do so, delete this
36 exception statement from your version. */
37
38
39 package java.net;
40
41 import java.io.InputStream;
42 import java.io.IOException;
43 import java.io.Serializable;
44 import java.io.ObjectInputStream;
45 import java.io.ObjectOutputStream;
46 import java.util.Hashtable;
47 import java.util.StringTokenizer;
48
49
50 /*
51  * Written using on-line Java Platform 1.2 API Specification, as well
52  * as "The Java Class Libraries", 2nd edition (Addison-Wesley, 1998).
53  * Status:  Believed complete and correct.
54  */
55
56 /**
57   * This final class represents an Internet Uniform Resource Locator (URL).
58   * For details on the syntax of URL's and what they can be used for,
59   * refer to RFC 1738, available from <a 
60   * href="http://ds.internic.net/rfcs/rfc1738.txt">http://ds.internic.net/rfcs/rfc1738.txt</a>
61   * <p>
62   * There are a great many protocols supported by URL's such as "http",
63   * "ftp", and "file".  This object can handle any arbitrary URL for which
64   * a URLStreamHandler object can be written.  Default protocol handlers
65   * are provided for the "http" and "ftp" protocols.  Additional protocols
66   * handler implementations may be provided in the future.  In any case,
67   * an application or applet can install its own protocol handlers that
68   * can be "chained" with other protocol hanlders in the system to extend
69   * the base functionality provided with this class. (Note, however, that
70   * unsigned applets cannot access properties by default or install their
71   * own protocol handlers).
72   * <p>
73   * This chaining is done via the system property java.protocol.handler.pkgs
74   * If this property is set, it is assumed to be a "|" separated list of
75   * package names in which to attempt locating protocol handlers.  The
76   * protocol handler is searched for by appending the string 
77   * ".<protocol>.Handler" to each packed in the list until a hander is found.
78   * If a protocol handler is not found in this list of packages, or if the
79   * property does not exist, then the default protocol handler of
80   * "gnu.java.net.<protocol>.Handler" is tried.  If this is
81   * unsuccessful, a MalformedURLException is thrown.
82   * <p>
83   * All of the constructor methods of URL attempt to load a protocol
84   * handler and so any needed protocol handlers must be installed when
85   * the URL is constructed.
86   * <p>
87   * Here is an example of how URL searches for protocol handlers.  Assume
88   * the value of java.protocol.handler.pkgs is "com.foo|com.bar" and the
89   * URL is "news://comp.lang.java.programmer".  URL would looking the 
90   * following places for protocol handlers:
91   * <p><pre>
92   * com.foo.news.Handler
93   * com.bar.news.Handler
94   * gnu.java.net.news.Handler
95   * </pre><p>
96   * If the protocol handler is not found in any of those locations, a
97   * MalformedURLException would be thrown.
98   * <p>
99   * Please note that a protocol handler must be a subclass of
100   * URLStreamHandler.
101   *
102   * @author Aaron M. Renn <arenn@urbanophile.com>
103   * @author Warren Levy <warrenl@cygnus.com>
104   *
105   * @see URLStreamHandler
106   */
107 public final class URL implements Serializable
108 {
109   /**
110    * The name of the protocol for this URL.
111    * The protocol is always stored in lower case.
112    */
113   private String protocol;
114
115   /**
116    * The "authority" portion of the URL.
117    */
118   private String authority;
119
120   /**
121    * The hostname or IP address of this protocol.
122    * This includes a possible user. For example <code>joe@some.host.net</code>.
123    */
124   private String host;
125
126   /**
127    * The port number of this protocol or -1 if the port number used is
128    * the default for this protocol.
129    */
130   private int port = -1;        // Initialize for constructor using context.
131
132   /**
133    * The "file" portion of the URL. It is defined as <code>path[?query]</code>.
134    */
135   private String file;
136
137   /**
138    * The anchor portion of the URL.
139    */
140   private String ref;
141
142   /**
143    * This is the hashCode for this URL
144    */
145   private int hashCode = 0;
146
147   /**
148    * The protocol handler in use for this URL
149    */
150   transient URLStreamHandler ph;
151
152   /**
153    * This a table where we cache protocol handlers to avoid the overhead
154    * of looking them up each time.
155    */
156   private static Hashtable handlers = new Hashtable();
157
158   /**
159    * If an application installs its own protocol handler factory, this is
160    * where we keep track of it.
161    */
162   private static URLStreamHandlerFactory factory;
163
164   private static final long serialVersionUID = -7627629688361524110L;
165
166   /**
167    * Constructs a URL and loads a protocol handler for the values passed as
168    * arguments.
169    * 
170    * @param protocol The protocol for this URL ("http", "ftp", etc)
171    * @param host The hostname or IP address to connect to
172    * @param port The port number to use, or -1 to use the protocol's
173    * default port
174    * @param file The "file" portion of the URL.
175    *
176    * @exception MalformedURLException If a protocol handler cannot be loaded or
177    * a parse error occurs.
178    */
179   public URL(String protocol, String host, int port, String file)
180     throws MalformedURLException
181   {
182     this(protocol, host, port, file, null);
183   }
184
185   /**
186    * Constructs a URL and loads a protocol handler for the values passed in
187    * as arugments.  Uses the default port for the protocol.
188    *
189    * @param protocol The protocol for this URL ("http", "ftp", etc)
190    * @param host The hostname or IP address for this URL
191    * @param file The "file" portion of this URL.
192    *
193    * @exception MalformedURLException If a protocol handler cannot be loaded or
194    * a parse error occurs.
195    */
196   public URL(String protocol, String host, String file)
197     throws MalformedURLException
198   {
199     this(protocol, host, -1, file, null);
200   }
201
202
203   /**
204    * This method initializes a new instance of <code>URL</code> with the
205    * specified protocol, host, port, and file.  Additionally, this method
206    * allows the caller to specify a protocol handler to use instead of 
207    * the default.  If this handler is specified, the caller must have
208    * the "specifyStreamHandler" permission (see <code>NetPermission</code>)
209    * or a <code>SecurityException</code> will be thrown.
210    *
211    * @param protocol The protocol for this URL ("http", "ftp", etc)
212    * @param host The hostname or IP address to connect to
213    * @param port The port number to use, or -1 to use the protocol's default
214    * port
215    * @param file The "file" portion of the URL.
216    * @param handler The protocol handler to use with this URL.
217    *
218    * @exception MalformedURLException If no protocol handler can be loaded
219    * for the specified protocol.
220    * @exception SecurityException If the <code>SecurityManager</code> exists
221    * and does not allow the caller to specify its own protocol handler.
222    *
223    * @since 1.2
224    */
225   public URL(String protocol, String host, int port, String file,
226              URLStreamHandler ph)
227     throws MalformedURLException
228   {
229     if (protocol == null)
230       throw new MalformedURLException("null protocol");
231     this.protocol = protocol.toLowerCase();
232
233     if (ph != null)
234       {
235         SecurityManager s = System.getSecurityManager();
236         if (s != null)
237           s.checkPermission (new NetPermission ("specifyStreamHandler"));
238
239         this.ph = ph;
240       }
241     else
242       this.ph = getURLStreamHandler(protocol);
243
244     if (this.ph == null)
245       throw new MalformedURLException (
246                       "Protocol handler not found: " + protocol);
247
248     this.host = host;
249     this.port = port;
250     this.authority = null;
251
252     int hashAt = file.indexOf('#');
253     if (hashAt < 0)
254       {
255         this.file = file;
256         this.ref = null;
257       }
258     else
259       {
260         this.file = file.substring(0, hashAt);
261         this.ref = file.substring(hashAt + 1);
262       }
263     hashCode = hashCode();                      // Used for serialization.
264   }
265
266   /**
267    * Initializes a URL from a complete string specification such as
268    * "http://www.urbanophile.com/arenn/".  First the protocol name is parsed
269    * out of the string.  Then a handler is located for that protocol and
270    * the parseURL() method of that protocol handler is used to parse the
271    * remaining fields.
272    *
273    * @param spec The complete String representation of a URL
274    *
275    * @exception MalformedURLException If a protocol handler cannot be found
276    * or the URL cannot be parsed
277    */
278   public URL(String spec) throws MalformedURLException
279   {
280     this((URL) null, spec, (URLStreamHandler) null);
281   }
282
283   /*
284    * This method parses a String representation of a URL within the
285    * context of an existing URL.  Principally this means that any
286    * fields not present the URL are inheritied from the context URL.
287    * This allows relative URL's to be easily constructed.  If the
288    * context argument is null, then a complete URL must be specified
289    * in the URL string.  If the protocol parsed out of the URL is
290    * different from the context URL's protocol, then then URL String
291    * is also expected to be a complete URL.
292    *
293    * @param context The context on which to parse the specification
294    * @param spec The string to parse an URL
295    *
296    * @exception MalformedURLException If a protocol handler cannot be found 
297    * for the URL cannot be parsed
298    */
299   public URL(URL context, String spec) throws MalformedURLException
300   {
301     this(context, spec, (URLStreamHandler) null);
302   }
303
304   /**
305    * Creates an URL from given arguments
306    * This method parses a String representation of a URL within the
307    * context of an existing URL.  Principally this means that any fields
308    * not present the URL are inheritied from the context URL.  This allows
309    * relative URL's to be easily constructed.  If the context argument is
310    * null, then a complete URL must be specified in the URL string.
311    * If the protocol parsed out of the URL is different 
312    * from the context URL's protocol, then then URL String is also
313    * expected to be a complete URL.
314    * <p>
315    * Additionally, this method allows the caller to specify a protocol handler
316    * to use instead of  the default.  If this handler is specified, the caller
317    * must have the "specifyStreamHandler" permission
318    * (see <code>NetPermission</code>) or a <code>SecurityException</code>
319    * will be thrown.
320    *
321    * @param context The context in which to parse the specification
322    * @param spec The string to parse as an URL
323    * @param handler The stream handler for the URL
324    *
325    * @exception MalformedURLException If a protocol handler cannot be found
326    * or the URL cannot be parsed
327    * @exception SecurityException If the <code>SecurityManager</code> exists
328    * and does not allow the caller to specify its own protocol handler.
329    *
330    * @since 1.2
331    */
332   public URL(URL context, String spec, URLStreamHandler ph)
333     throws MalformedURLException
334   {
335     /* A protocol is defined by the doc as the substring before a ':'
336      * as long as the ':' occurs before any '/'.
337      *
338      * If context is null, then spec must be an absolute URL.
339      *
340      * The relative URL need not specify all the components of a URL.
341      * If the protocol, host name, or port number is missing, the value
342      * is inherited from the context.  A bare file component is appended
343      * to the context's file.  The optional anchor is not inherited. 
344      */
345
346     // If this is an absolute URL, then ignore context completely.
347     // An absolute URL must have chars prior to "://" but cannot have a colon
348     // right after the "://".  The second colon is for an optional port value
349     // and implies that the host from the context is used if available.
350     int colon;
351     if ((colon = spec.indexOf("://", 1)) > 0 &&
352         ! spec.regionMatches(colon, "://:", 0, 4))
353       context = null;
354
355     int slash;
356     if ((colon = spec.indexOf(':')) > 0 &&
357         (colon < (slash = spec.indexOf('/')) || slash < 0))
358       {
359         // Protocol specified in spec string.
360         protocol = spec.substring(0, colon).toLowerCase();
361         if (context != null && context.protocol.equals(protocol))
362           {
363             // The 1.2 doc specifically says these are copied to the new URL.
364             host = context.host;
365             port = context.port;
366             file = context.file;
367             authority = context.authority;
368           }
369       }
370     else if (context != null)
371       {
372         // Protocol NOT specified in spec string.
373         // Use context fields (except ref) as a foundation for relative URLs.
374         colon = -1;
375         protocol = context.protocol;
376         host = context.host;
377         port = context.port;
378         file = context.file;
379         authority = context.authority;
380       }
381     else        // Protocol NOT specified in spec. and no context available.
382       throw new
383           MalformedURLException("Absolute URL required with null context");
384
385     if (ph != null)
386       {
387         SecurityManager s = System.getSecurityManager ();
388         if (s != null)
389           s.checkPermission (new NetPermission ("specifyStreamHandler"));
390
391         this.ph = ph;
392       }
393     else
394       this.ph = getURLStreamHandler(protocol);
395
396     if (this.ph == null)
397       throw new MalformedURLException("Protocol handler not found: "
398                                       + protocol);
399
400     // JDK 1.2 doc for parseURL specifically states that any '#' ref
401     // is to be excluded by passing the 'limit' as the indexOf the '#'
402     // if one exists, otherwise pass the end of the string.
403     int hashAt = spec.indexOf('#', colon + 1);
404     this.ph.parseURL(this, spec, colon + 1,
405                      hashAt < 0 ? spec.length() : hashAt);
406     if (hashAt >= 0)
407       ref = spec.substring(hashAt + 1);
408
409     hashCode = hashCode();                      // Used for serialization.
410   }
411
412   /**
413    * Test another URL for equality with this one.  This will be true only if
414    * the argument is non-null and all of the fields in the URL's match 
415    * exactly (ie, protocol, host, port, file, and ref).  Overrides
416    * Object.equals(), implemented by calling the equals method of the handler.
417    *
418    * @param url The URL to compare with
419    *
420    * @return true if the URL is equal, false otherwise
421    */
422   public boolean equals (Object obj)
423   {
424     if (obj == null || ! (obj instanceof URL))
425       return false;
426
427     return ph.equals (this, (URL) obj);
428   }
429
430   /**
431    * Returns the contents of this URL as an object by first opening a
432    * connection, then calling the getContent() method against the connection
433    *
434    * @return A content object for this URL
435    * @exception IOException If opening the connection or getting the
436    * content fails.
437    *
438    * @since 1.3
439    */
440   public final Object getContent() throws IOException
441   {
442     return openConnection().getContent();
443   }
444
445   /**
446    * Gets the contents of this URL
447    *
448    * @exception IOException If an error occurs
449    */
450   public final Object getContent (Class[] classes) throws IOException
451   {
452     // FIXME: implement this
453     return getContent();
454   }
455
456   /**
457    * Returns the file portion of the URL.
458    * Defined as <code>path[?query]</code>.
459    * Returns the empty string if there is no file portion.
460    */
461   public String getFile()
462   {
463     return file == null ? "" : file;
464   }
465
466   /**
467    * Returns the path of the URL. This is the part of the file before any '?'
468    * character.
469    *
470    * @since 1.3
471    */
472   public String getPath()
473   {
474     int quest = (file == null) ? -1 : file.indexOf('?');
475     return quest < 0 ? getFile() : file.substring(0, quest);
476   }
477
478   /**
479    * Returns the authority of the URL
480    * 
481    * @since 1.3
482    */
483   public String getAuthority()
484   {
485     return authority;
486   }
487
488   /**
489    * Returns the host of the URL
490    */
491   public String getHost()
492   {
493     int at = (host == null) ? -1 : host.indexOf('@');
494     return at < 0 ? host : host.substring(at + 1, host.length());
495   }
496
497   /**
498    * Returns the port number of this URL or -1 if the default port number is
499    * being used.
500    *
501    * @return The port number
502    *
503    * @see #getDefaultPort()
504    */
505   public int getPort()
506   {
507     return port;
508   }
509
510   /**
511    * Returns the default port of the URL. If the StreamHandler for the URL
512    * protocol does not define a default port it returns -1.
513    */
514   public int getDefaultPort()
515   {
516     return ph.getDefaultPort();
517   }
518
519   /**
520    * Returns the protocol of the URL
521    */
522   public String getProtocol()
523   {
524     return protocol;
525   }
526
527   /**
528    * Returns the ref (sometimes called the "# reference" or "anchor") portion
529    * of the URL.
530    *
531    * @return The ref
532    */
533   public String getRef()
534   {
535     return ref;
536   }
537
538   /**
539    * Returns the user information of the URL. This is the part of the host
540    * name before the '@'.
541    *
542    * @return the user at a particular host or null when no user defined.
543    */
544   public String getUserInfo ()
545   {
546     int at = (host == null) ? -1 : host.indexOf('@');
547     return at < 0 ? null : host.substring(0, at);
548   }
549
550   /**
551    * Returns the query of the URL. This is the part of the file before the
552    * '?'.
553    *
554    * @return the query part of the file, or null when there is no query part.
555    */
556   public String getQuery ()
557   {
558     int quest = (file == null) ? -1 : file.indexOf('?');
559     return quest < 0 ? null : file.substring(quest + 1, file.length());
560   }
561
562   /**
563    * Returns a hashcode computed by the URLStreamHandler of this URL
564    */
565   public int hashCode()
566   {
567     if (hashCode != 0)
568       return hashCode;          // Use cached value if available.
569     else
570       return ph.hashCode (this);
571   }
572
573   /**
574    * Returns a URLConnection object that represents a connection to the remote
575    * object referred to by the URL. The URLConnection is created by calling the
576    * openConnection() method of the protocol handler
577    *
578    * @return A URLConnection for this URL
579    * @exception IOException If an error occurs
580    */
581   public URLConnection openConnection() throws IOException
582   {
583     return ph.openConnection(this);
584   }
585
586   /**
587    * Opens a connection to this URL and returns an InputStream for reading
588    * from that connection
589    *
590    * @exception IOException If an error occurs
591    */
592   public final InputStream openStream() throws IOException
593   {
594     return openConnection().getInputStream();
595   }
596
597   /**
598    * Tests whether or not another URL refers to the same "file" as this one.
599    * This will be true if and only if the passed object is not null, is a
600    * URL, and matches all fields but the ref (ie, protocol, host, port,
601    * and file);
602    *
603    * @param url The URL object to test with
604    *
605    * @return true if URL matches this URL's file, false otherwise
606    */
607   public boolean sameFile(URL other)
608   {
609     return ph.sameFile(this, other);
610   }
611
612   /**
613    * Sets the specified fields of the URL. This is not a public method so
614    * that only URLStreamHandlers can modify URL fields. This might be called
615    * by the <code>parseURL()</code> method in that class. URLs are otherwise
616    * constant.
617    *
618    * @param protocol The protocol name for this URL
619    * @param host The hostname or IP address for this URL
620    * @param port The port number of this URL
621    * @param file The "file" portion of this URL.
622    * @param ref The anchor portion of this URL.
623    */
624   protected void set(String protocol, String host, int port, String file,
625                      String ref)
626   {
627     // TBD: Theoretically, a poorly written StreamHandler could pass an
628     // invalid protocol.  It will cause the handler to be set to null
629     // thus overriding a valid handler.  Callers of this method should
630     // be aware of this.
631     this.ph = getURLStreamHandler(protocol);
632     this.protocol = protocol.toLowerCase();
633     this.authority = null;
634     this.port = port;
635     this.host = host;
636     this.file = file;
637     this.ref = ref;
638     hashCode = hashCode();                      // Used for serialization.
639   }
640
641   /**
642    * Sets the specified fields of the URL. This is not a public method so
643    * that only URLStreamHandlers can modify URL fields. URLs are otherwise
644    * constant.
645    *
646    * @since 1.3
647    */
648   protected void set(String protocol, String host, int port,
649                      String authority, String userInfo,
650                      String path, String query, String ref)
651   {
652     // TBD: Theoretically, a poorly written StreamHandler could pass an
653     // invalid protocol.  It will cause the handler to be set to null
654     // thus overriding a valid handler.  Callers of this method should
655     // be aware of this.
656     this.ph = getURLStreamHandler(protocol);
657     this.protocol = protocol.toLowerCase();
658     if (userInfo == null)
659       this.host = host;
660     else
661       this.host = userInfo + "@" + host;
662     this.port = port;
663     if (query == null)
664       this.file = path;
665     else
666       this.file = path + "?" + query;
667     this.ref = ref;
668     hashCode = hashCode();                      // Used for serialization.
669   }
670
671   /**
672    * Sets the URLStreamHandlerFactory for this class.  This factory is
673    * responsible for returning the appropriate protocol handler for
674    * a given URL.
675    *
676    * @param fac The URLStreamHandlerFactory class to use
677    *
678    * @exception Error If the factory is alread set.
679    * @exception SecurityException If a security manager exists and its
680    * checkSetFactory method doesn't allow the operation
681    */
682   public static synchronized void
683         setURLStreamHandlerFactory(URLStreamHandlerFactory fac)
684   {
685     if (factory != null)
686       throw new Error("URLStreamHandlerFactory already set");
687
688     // Throw an exception if an extant security mgr precludes
689     // setting the factory.
690     SecurityManager s = System.getSecurityManager();
691     if (s != null)
692       s.checkSetFactory();
693     factory = fac;
694   }
695
696   /**
697    * Returns a String representing this URL.  The String returned is
698    * created by calling the protocol handler's toExternalForm() method.
699    *
700    * @return A string for this URL
701    */
702   public String toExternalForm()
703   {
704     // Identical to toString().
705     return ph.toExternalForm(this);
706   }
707
708   /**
709    * Returns a String representing this URL.  Identical to toExternalForm().
710    * The value returned is created by the protocol handler's 
711    * toExternalForm method.  Overrides Object.toString()
712    *
713    * @return A string for this URL
714    */
715   public String toString()
716   {
717     // Identical to toExternalForm().
718     return ph.toExternalForm(this);
719   }
720
721   private static synchronized URLStreamHandler
722     getURLStreamHandler (String protocol)
723   {
724     URLStreamHandler ph;
725
726     // See if a handler has been cached for this protocol.
727     if ((ph = (URLStreamHandler) handlers.get(protocol)) != null)
728       return ph;
729
730     // If a non-default factory has been set, use it to find the protocol.
731     if (factory != null)
732       ph = factory.createURLStreamHandler(protocol);
733     else if (protocol.equals ("core"))
734       {
735         ph = new gnu.gcj.protocol.core.Handler ();
736       }
737     else if (protocol.equals ("file"))
738       {
739         // This is an interesting case.  It's tempting to think that we
740         // could call Class.forName ("gnu.gcj.protocol.file.Handler") to
741         // get the appropriate class.  Unfortunately, if we do that the
742         // program will never terminate, because getURLStreamHandler is
743         // eventually called by Class.forName.
744         //
745         // Treating "file" as a special case is the minimum that will
746         // fix this problem.  If other protocols are required in a
747         // statically linked application they will need to be handled in
748         // the same way as "file".
749         ph = new gnu.gcj.protocol.file.Handler ();
750       }
751
752     // Non-default factory may have returned null or a factory wasn't set.
753     // Use the default search algorithm to find a handler for this protocol.
754     if (ph == null)
755       {
756         // Get the list of packages to check and append our default handler
757         // to it, along with the JDK specified default as a last resort.
758         // Except in very unusual environments the JDK specified one shouldn't
759         // ever be needed (or available).
760         String propVal = System.getProperty("java.protocol.handler.pkgs");
761         propVal = (propVal == null) ? "" : (propVal + "|");
762         propVal = propVal + "gnu.gcj.protocol|sun.net.www.protocol";
763
764         StringTokenizer pkgPrefix = new StringTokenizer(propVal, "|");
765         do
766           {
767             String facName = pkgPrefix.nextToken() + "." + protocol +
768                                 ".Handler";
769             try
770               {
771                 ph = (URLStreamHandler) Class.forName(facName).newInstance();
772               }
773             catch (Exception e)
774               {
775                 // Can't instantiate; handler still null, go on to next element.
776               }
777           } while ((ph == null ||
778                     ! (ph instanceof URLStreamHandler)) &&
779                    pkgPrefix.hasMoreTokens());
780       }
781
782     // Update the hashtable with the new protocol handler.
783     if (ph != null)
784       if (ph instanceof URLStreamHandler)
785         handlers.put(protocol, ph);
786       else
787         ph = null;
788
789     return ph;
790   }
791
792   private void readObject(ObjectInputStream ois)
793     throws IOException, ClassNotFoundException
794   {
795     ois.defaultReadObject();
796     this.ph = getURLStreamHandler(protocol);
797     if (this.ph == null)
798       throw new IOException("Handler for protocol " + protocol + " not found");
799   }
800
801   private void writeObject(ObjectOutputStream oos) throws IOException
802   {
803     oos.defaultWriteObject();
804   }
805 }