OSDN Git Service

2003-10-08 Michael Koch <konqueror@gmx.de>
[pf3gnuchains/gcc-fork.git] / libjava / java / net / URLConnection.java
1 /* URLConnection.java -- Abstract superclass for reading from URL's
2    Copyright (C) 1998, 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.OutputStream;
44 import java.security.Permission;
45 import java.security.AllPermission;
46 import java.text.ParsePosition;
47 import java.text.SimpleDateFormat;
48 import java.util.Collections;
49 import java.util.Date;
50 import java.util.Hashtable;
51 import java.util.Locale;
52 import java.util.Map;
53 import java.util.StringTokenizer;
54 import gnu.gcj.io.MimeTypes;
55
56 /**
57  * Written using on-line Java Platform 1.2 API Specification, as well
58  * as "The Java Class Libraries", 2nd edition (Addison-Wesley, 1998).
59  * Status:  One guessContentTypeFrom... methods not implemented.
60  *    getContent method assumes content type from response; see comment there.
61  */
62
63 /**
64  * This class models a connection that retrieves the information pointed
65  * to by a URL object.  This is typically a connection to a remote node
66  * on the network, but could be a simple disk read.
67  * <p>
68  * A URLConnection object is normally created by calling the openConnection()
69  * method of a URL object.  This method is somewhat misnamed because it does
70  * not actually open the connection.  Instead, it return an unconnected
71  * instance of this object.  The caller then has the opportunity to set
72  * various connection options prior to calling the actual connect() method.
73  * <p>
74  * After the connection has been opened, there are a number of methods in
75  * this class that access various attributes of the data, typically
76  * represented by headers sent in advance of the actual data itself.
77  * <p>
78  * Also of note are the getInputStream and getContent() methods which allow
79  * the caller to retrieve the actual data from the connection.  Note that
80  * for some types of connections, writing is also allowed.  The setDoOutput()
81  * method must be called prior to connecing in order to enable this, then
82  * the getOutputStream method called after the connection in order to
83  * obtain a stream to write the output to.
84  * <p>
85  * The getContent() method is of particular note.  This method returns an
86  * Object that encapsulates the data returned.  There is no way do determine
87  * the type of object that will be returned in advance.  This is determined
88  * by the actual content handlers as described in the description of that
89  * method.
90  *
91  * @author Aaron M. Renn <arenn@urbanophile.com>
92  * @author Warren Levy <warrenl@cygnus.com>
93  */
94 public abstract class URLConnection
95 {
96   /**
97    * This is an object that maps filenames to MIME types.  The interface
98    * to do this is implemented by this class, so just create an empty
99    * instance and store it here.
100    */
101   private static FileNameMap fileNameMap;
102  
103   /**
104    * This is the ContentHandlerFactory set by the caller, if any
105    */
106   private static ContentHandlerFactory factory;
107   
108   /**
109    * This is the default value that will be used to determine whether or
110    * not user interaction should be allowed.
111    */
112   private static boolean defaultAllowUserInteraction = false;
113   
114   /**
115    * This is the default flag indicating whether or not to use caches to
116    * store the data returned from a server
117    */
118   private static boolean defaultUseCaches = true;
119
120   /**
121    * This variable determines whether or not interaction is allowed with
122    * the user.  For example, to prompt for a username and password.
123    */
124   protected boolean allowUserInteraction;
125
126   /**
127    * Indicates whether or not a connection has been established to the
128    * destination specified in the URL
129    */
130   protected boolean connected = false;
131   
132   /**
133    * Indicates whether or not input can be read from this URL
134    */
135   protected boolean doInput = true;
136   
137   /**
138    * Indicates whether or not output can be sent to this URL
139    */
140   protected boolean doOutput = false;
141   
142   /**
143    * If this flag is set, the protocol is allowed to cache data whenever
144    * it can (caching is not guaranteed). If it is not set, the protocol
145    * must a get a fresh copy of the data.
146    * <p>
147    * This field is set by the setUseCaches method and returned by the
148    * getUseCaches method.
149    *
150    * Its default value is that determined by the last invocation of
151    * setDefaultUseCaches
152    */
153   protected boolean useCaches;
154
155   /**
156    * If this value is non-zero, then the connection will only attempt to
157    * fetch the document pointed to by the URL if the document has been
158    * modified more recently than the date set in this variable.  That date
159    * should be specified as the number of seconds since 1/1/1970 GMT.
160    */
161   protected long ifModifiedSince = 0L;
162
163   /**
164    * This is the URL associated with this connection
165    */
166   protected URL url;
167
168   private static ContentHandler contentHandler;
169   private static Hashtable handlers = new Hashtable();
170   private static Locale locale; 
171   private static SimpleDateFormat dateFormat1, dateFormat2, dateFormat3;
172   private static boolean dateformats_initialized = false;
173
174   /**
175    * Creates a URL connection to a given URL. A real connection is not made.
176    * Use #connect to do this.
177    *
178    * @param url The Object to create the URL connection to
179    *
180    * @see URLConnection#connect()
181    */
182   protected URLConnection(URL url)
183   {
184     // Set up all our instance variables
185     this.url = url;
186     allowUserInteraction = defaultAllowUserInteraction;
187     useCaches = defaultUseCaches;
188   }
189
190   /**
191    * Establishes the actual connection to the URL associated with this
192    * connection object
193    */
194   public abstract void connect() throws IOException;
195
196   /**
197    * Returns the URL object associated with this connection
198    *
199    * @return The URL for this connection.
200    */
201   public URL getURL()
202   {
203     return url;
204   }
205
206   /**
207    * Returns the value of the content-length header field or -1 if the value
208    * is not known or not present.
209    *
210    * @return The content-length field
211    */
212   public int getContentLength()
213   {
214     return getHeaderFieldInt("content-length", -1);
215   }
216
217   /**
218    * Returns the the content-type of the data pointed to by the URL.  This
219    * method first tries looking for a content-type header.  If that is not
220    * present, it attempts to use the file name to determine the content's
221    * MIME type.  If that is unsuccessful, the method returns null.  The caller
222    * may then still attempt to determine the MIME type by a call to
223    * guessContentTypeFromStream()
224    *
225    * @return The content MIME type
226    */
227   public String getContentType()
228   {
229     return getHeaderField("content-type");
230   }
231
232   /**
233    * Returns the value of the content-encoding field or null if it is not
234    * known or not present.
235    * 
236    * @return The content-encoding field
237    */
238   public String getContentEncoding()
239   {
240     return getHeaderField("content-encoding");
241   }
242
243   /**
244    * Returns the value of the expires header or 0 if not known or present.
245    * If populated, the return value is number of seconds since midnight
246    * on 1/1/1970 GMT.
247    *
248    * @return The expiration time.
249    */
250   public long getExpiration()
251   {
252     return getHeaderFieldDate("expires", 0L);
253   }
254
255   /**
256    * Returns the date of the document pointed to by the URL as reported in
257    * the date field of the header or 0 if the value is not present or not
258    * known. If populated, the return value is number of seconds since
259    * midnight on 1/1/1970 GMT.
260    *
261    * @return The document date
262    */
263   public long getDate()
264   {
265     return getHeaderFieldDate("date", 0L);
266   }
267
268   /**
269    * Returns the value of the last-modified header field or 0 if not known known
270    * or not present.  If populated, the return value is the number of seconds
271    * since midnight on 1/1/1970.
272    *
273    * @return The last modified time
274    */
275   public long getLastModified()
276   {
277     return getHeaderFieldDate("last-modified", 0L);
278   }
279
280   /**
281    * Return a String representing the header value at the specified index.
282    * This allows the caller to walk the list of header fields.  The analogous
283    * getHeaderFieldKey(int) method allows access to the corresponding key
284    * for this header field
285    *
286    * @param index The index into the header field list to retrieve the value for
287    *
288    * @return The header value or null if index is past the end of the headers
289    */
290   public String getHeaderField(int index)
291   {
292     // Subclasses for specific protocols override this.
293     return null;
294   }
295
296   /**
297    * Returns a String representing the value of the header field having
298    * the named key.  Returns null if the header field does not exist.
299    *
300    * @param The key of the header field
301    *
302    * @return The value of the header field as a String
303    */
304   public String getHeaderField(String name)
305   {
306     // Subclasses for specific protocols override this.
307     return null;
308   }
309
310   /**
311    * Returns a map of all sent header fields
312    * 
313    * @since 1.4
314    */
315   public Map getHeaderFields()
316   {
317     // Subclasses for specific protocols override this.
318     return null;
319   }
320
321   /**
322    * Returns the value of the named header field as an int.  If the field
323    * is not present or cannot be parsed as an integer, the default value
324    * will be returned.
325    *
326    * @param name The header field key to lookup
327    * @param defaultValue The defaule value if the header field is not found
328    * or can't be parsed.
329    *
330    * @return The value of the header field or the default value if the field
331    * is missing or malformed
332    */
333   public int getHeaderFieldInt(String name, int defaultValue)
334   {
335     String value = getHeaderField (name);
336     
337     if (value == null)
338       return defaultValue;
339
340     try
341       {
342         return Integer.parseInt (value);
343       }
344     catch (NumberFormatException e) 
345       { 
346         return defaultValue;
347       }
348   }
349
350   /**
351    * Returns the value of the named header field as a date.  This date will
352    * be the number of seconds since midnight 1/1/1970 GMT or the default
353    * value if the field is not present or cannot be converted to a date.
354    *
355    * @param name The name of the header field
356    * @param defaultValue The default date if the header field is not found
357    * or can't be converted.
358    *
359    * @return Returns the date value of the header filed or the default value
360    * if the field is missing or malformed
361    */
362   public long getHeaderFieldDate (String name, long defaultValue)
363   {
364     if (! dateformats_initialized)
365       initializeDateFormats ();
366     
367     long result = defaultValue;
368     String str = getHeaderField (name);
369     
370     if (str != null)
371       {
372         Date date;
373         if ((date = dateFormat1.parse (str, new ParsePosition (0))) != null)
374           result = date.getTime ();
375         else if ((date = dateFormat2.parse (str, new ParsePosition (0))) != null)
376           result = date.getTime ();
377         else if ((date = dateFormat3.parse (str, new ParsePosition (0))) != null)
378           result = date.getTime ();
379       }
380     
381     return result;
382   }
383
384   /**
385    * Returns a String representing the header key at the specified index.
386    * This allows the caller to walk the list of header fields.  The analogous
387    * getHeaderField(int) method allows access to the corresponding value for
388    * this tag.
389    *
390    * @param index The index into the header field list to retrieve the key for. 
391    *
392    * @return The header field key or null if index is past the end
393    * of the headers.
394    */
395   public String getHeaderFieldKey (int index)
396   {
397     // Subclasses for specific protocols override this.
398     return null;
399   }
400
401   /**
402    * This method returns the content of the document pointed to by the URL
403    * as an Object.  The type of object depends on the MIME type of the
404    * object and particular content hander loaded.  Most text type content
405    * handlers will return a subclass of InputStream.  Images usually return
406    * a class that implements ImageProducer.  There is not guarantee what
407    * type of object will be returned, however.
408    * <p>
409    * This class first determines the MIME type of the content, then creates
410    * a ContentHandler object to process the input.  If the ContentHandlerFactory
411    * is set, then that object is called to load a content handler, otherwise
412    * a class called gnu.java.net.content.<content_type> is tried.
413    * The default class will also be used if the content handler factory returns
414    * a null content handler.
415    *
416    * @exception IOException If an error occurs
417    * @exception UnknownServiceException If the protocol does not support the
418    * content type
419    */
420   public Object getContent() throws IOException
421   {
422     // FIXME: Doc indicates that other criteria should be applied as
423     // heuristics to determine the true content type, e.g. see 
424     // guessContentTypeFromName() and guessContentTypeFromStream methods
425     // as well as FileNameMap class & fileNameMap field & get/set methods.
426     String cType = getContentType();
427     contentHandler = setContentHandler(cType);
428     if (contentHandler == null)
429       return getInputStream();
430
431     return contentHandler.getContent(this);
432   }
433
434   /**
435    * Retrieves the content of this URLConnection
436    *
437    * @exception IOException If an error occurs
438    * @exception UnknownServiceException If the protocol does not support the
439    * content type
440    */
441   public Object getContent(Class[] classes) throws IOException
442   {
443     // FIXME: implement this
444     return getContent ();
445   }
446
447   /**
448    * This method returns a <code>Permission</code> object representing the
449    * permissions required to access this URL.  This method returns
450    * <code>java.security.AllPermission</code> by default.  Subclasses should
451    * override it to return a more specific permission.  For example, an
452    * HTTP URL should return an instance of <code>SocketPermission</code>
453    * for the appropriate host and port.
454    * <p>
455    * Note that because of items such as HTTP redirects, the permission
456    * object returned might be different before and after connecting.
457    *
458    * @return A Permission object
459    *
460    * @exception IOException If the computation of the permission requires
461    * network or file I/O and an exception occurs while computing it
462    */
463   public Permission getPermission() throws IOException
464   {
465     // Subclasses may override this.
466     return new java.security.AllPermission();
467   }
468
469   /**
470    * Returns an InputStream for this connection.  As this default
471    * implementation returns null, subclasses should override this method
472    *
473    * @return An InputStream for this connection
474    *
475    * @exception IOException If an error occurs
476    * @exception UnknownServiceException If the protocol does not support input
477    */
478   public InputStream getInputStream() throws IOException
479   {
480     // Subclasses for specific protocols override this.
481     throw new UnknownServiceException("Protocol " + url.getProtocol() +
482                         " does not support input.");
483   }
484
485   /**
486    * Returns an OutputStream for this connection.  As this default
487    * implementation returns null, subclasses should override this method
488    *
489    * @return An OutputStream for this connection
490    *
491    * @exception IOException If an error occurs
492    * @exception UnknownServiceException If the protocol does not support output
493    */
494   public OutputStream getOutputStream() throws IOException
495   {
496     // Subclasses for specific protocols override this.
497     throw new UnknownServiceException("Protocol " + url.getProtocol() +
498                         " does not support output.");
499   }
500
501   /**
502    * The methods prints the value of this object as a String by calling the
503    * toString() method of its associated URL.  Overrides Object.toString()
504    * 
505    * @return A String representation of this object
506    */
507   public String toString()
508   {
509     return this.getClass().getName() + ":" + url.toString();
510   }
511
512   /**
513    * Returns the value of a flag indicating whether or not input is going
514    * to be done for this connection.  This default to true unless the
515    * doOutput flag is set to false, in which case this defaults to false.
516    * 
517    * @param input <code>true</code> if input is to be done,
518    * <code>false</code> otherwise
519    *
520    * @exception IllegalStateException If already connected
521    */
522   public void setDoInput(boolean input)
523   {
524     if (connected)
525       throw new IllegalStateException ("Already connected");
526
527     doInput = input;
528   }
529
530   /**
531    * Returns the value of a flag indicating whether or not input is going
532    * to be done for this connection.  This default to true unless the
533    * doOutput flag is set to false, in which case this defaults to false.
534    *
535    * @return true if input is to be done, false otherwise
536    */
537   public boolean getDoInput()
538   {
539     return doInput;
540   }
541
542   /**
543    * Returns a boolean flag indicating whether or not output will be done
544    * on this connection.  The default value is false, so this method can
545    * be used to override the default
546    *
547    * @param output ture if output is to be done, false otherwise
548    *
549    * @exception IllegalStateException If already connected
550    */
551   public void setDoOutput(boolean output)
552   {
553     if (connected)
554       throw new IllegalStateException ("Already connected");
555
556     doOutput = output;
557   }
558
559   /**
560    * Returns a boolean flag indicating whether or not output will be done
561    * on this connection.  This defaults to false.
562    *
563    * @return true if output is to be done, false otherwise
564    */
565   public boolean getDoOutput()
566   {
567     return doOutput;
568   }
569
570   /**
571    * Sets a boolean flag indicating whether or not user interaction is
572    * allowed for this connection.  (For example, in order to prompt for
573    * username and password info.
574    *
575    * @param allow true if user interaction should be allowed, false otherwise.
576    *
577    * @exception IllegalStateException If already connected
578    */
579   public void setAllowUserInteraction(boolean allow)
580   {
581     allowUserInteraction = allow;
582   }
583
584   /**
585    * Returns a boolean flag indicating whether or not user interaction is
586    * allowed for this connection.  (For example, in order to prompt for
587    * username and password info.
588    *
589    * @return true if user interaction is allowed, false otherwise
590    */
591   public boolean getAllowUserInteraction()
592   {
593     return allowUserInteraction;
594   }
595
596   /**
597    * Sets the default flag for whether or not interaction with a user
598    * is allowed.  This will be used for all connections unless overridden
599    *
600    * @param allow true to allow user interaction, false otherwise
601    */
602   public static void setDefaultAllowUserInteraction(boolean allow)
603   {
604     defaultAllowUserInteraction = allow;
605   }
606
607   /**
608    * Returns the default flag for whether or not interaction with a user
609    * is allowed.  This will be used for all connections unless overridden
610    *
611    * @return true if user interaction is allowed, false otherwise
612    */
613   public static boolean getDefaultAllowUserInteraction()
614   {
615     return defaultAllowUserInteraction;
616   }
617
618   /**
619    * Sets a boolean flag indicating whether or not caching will be used
620    * (if possible) to store data downloaded via the connection.
621    *
622    * @param usecaches The new value
623    *
624    * @exception IllegalStateException If already connected
625    */
626   public void setUseCaches(boolean usecaches)
627   {
628     if (connected)
629       throw new IllegalStateException ("Already connected");
630
631     useCaches = usecaches;
632   }
633
634   /**
635    * Returns a boolean flag indicating whether or not caching will be used
636    * (if possible) to store data downloaded via the connection.
637    *
638    * @return true if caching should be used if possible, false otherwise
639    */
640   public boolean getUseCaches()
641   {
642     return useCaches;
643   }
644
645   /**
646    * Sets the ifModified since instance variable.  If this value is non
647    * zero and the underlying protocol supports it, the actual document will
648    * not be fetched unless it has been modified since this time.  The value
649    * passed should  be 0 if this feature is to be disabled or the time expressed
650    * as the number of seconds since midnight 1/1/1970 GMT otherwise.
651    *
652    * @param ifmodifiedsince The new value in milliseconds
653    * since January 1, 1970 GMT
654    *
655    * @exception IllegalStateException If already connected
656    */
657   public void setIfModifiedSince(long ifmodifiedsince)
658   {
659     if (connected)
660       throw new IllegalStateException ("Already connected");
661
662     ifModifiedSince = ifmodifiedsince;
663   }
664
665   /**
666    * Returns the ifModified since instance variable.  If this value is non
667    * zero and the underlying protocol supports it, the actual document will
668    * not be fetched unless it has been modified since this time.  The value
669    * returned will be 0 if this feature is disabled or the time expressed
670    * as the number of seconds since midnight 1/1/1970 GMT otherwise
671    *
672    * @return The ifModifiedSince value
673    */
674   public long getIfModifiedSince()
675   {
676     return ifModifiedSince;
677   }
678
679   /**
680    * Returns the default value used to determine whether or not caching
681    * of documents will be done when possible.
682    *
683    * @return true if caches will be used, false otherwise
684    */
685   public boolean getDefaultUseCaches()
686   {
687     return defaultUseCaches;
688   }
689
690   /**
691    * Sets the default value used to determine whether or not caching
692    * of documents will be done when possible.
693    *
694    * @param use true to use caches if possible by default, false otherwise
695    */
696   public void setDefaultUseCaches(boolean defaultusecaches)
697   {
698     defaultUseCaches = defaultusecaches;
699   }
700
701   /**
702    * Sets the value of the named request property
703    *
704    * @param key The name of the property
705    * @param value The value of the property
706    * 
707    * @exception IllegalStateException If already connected
708    * @exception NullPointerException If key is null
709    *
710    * @see URLConnection#getRequestProperty(String key)
711    * @see URLConnection#addRequestProperty(String key, String value)
712    * 
713    * @since 1.4
714    */
715   public void setRequestProperty(String key, String value)
716   {
717     if (connected)
718       throw new IllegalStateException ("Already connected");
719
720     if (key == null)
721       throw new NullPointerException ("key is null");
722     
723     // Do nothing unless overridden by subclasses that support setting
724     // header fields in the request.
725   }
726
727   /**
728    * Adds a new request property by a key/value pair.
729    * This method does not overwrite* existing properties with the same key.
730    *
731    * @param key Key of the property to add
732    * @param value Value of the Property to add
733    *
734    * @exception IllegalStateException If already connected
735    * @exception NullPointerException If key is null
736    * 
737    * @see URLConnection#getRequestProperty(String key)
738    * @see URLConnection#setRequestProperty(String key, String value)
739    * 
740    * @since 1.4
741    */
742   public void addRequestProperty(String key, String value)
743   {
744     if (connected)
745       throw new IllegalStateException ("Already connected");
746
747     if (key == null)
748       throw new NullPointerException ("key is null");
749     
750     // Do nothing unless overridden by subclasses that support adding
751     // header fields in the request.
752   }
753
754   /**
755    * Returns the value of the named request property.
756    *
757    * @param key The name of the property
758    *
759    * @return Value of the property
760    *
761    * @exception IllegalStateException If already connected
762    *
763    * @see URLConnection#setRequestProperty(String key, String value)
764    * @see URLConnection#addRequestProperty(String key, String value)
765    */
766   public String getRequestProperty(String key)
767   {
768     if (connected)
769       throw new IllegalStateException ("Already connected");
770
771     // Overridden by subclasses that support reading header fields from the
772     // request.
773     return null;
774   }
775
776   /**
777    * Returns an unmodifiable Map containing the request properties.
778    *
779    * @return The map of properties
780    *
781    * @exception IllegalStateException If already connected
782    *
783    * @since 1.4
784    */
785   public Map getRequestProperties()
786   {
787     if (connected)
788       throw new IllegalStateException ("Already connected");
789
790     // Overridden by subclasses that support reading header fields from the
791     // request.
792     return Collections.EMPTY_MAP;
793   }
794
795   /**
796    * Sets the default value of a request property.  This will be used
797    * for all connections unless the value of the property is manually
798    * overridden.
799    *
800    * @param key The request property name the default is being set for
801    * @param value The value to set the default to
802    *
803    * @deprecated 1.3 The method setRequestProperty should be used instead
804    *
805    * @see URLConnectionr#setRequestProperty(String key, String value)
806    */
807   public static void setDefaultRequestProperty(String key, String value)
808   {
809     // Do nothing unless overridden by subclasses that support setting
810     // default request properties.
811   }
812
813   /**
814    * Returns the default value of a request property.  This will be used
815    * for all connections unless the value of the property is manually
816    * overridden.
817    *
818    * @param key The request property to return the default value of
819    *
820    * @return The value of the default property or null if not available
821    * 
822    * @deprecated 1.3 The method getRequestProperty should be used instead
823    *
824    * @see URLConnection#getRequestProperty(String key)
825    */
826   public static String getDefaultRequestProperty(String key)
827   {
828     // Overridden by subclasses that support default request properties.
829     return null;
830   }
831
832   /**
833    * Set's the ContentHandlerFactory for an application.  This can be called
834    * once and only once.  If it is called again, then an Error is thrown.
835    * Unlike for other set factory methods, this one does not do a security
836    * check prior to setting the factory.
837    *
838    * @param factory The ContentHandlerFactory for this application
839    *
840    * @exception Error If the factory has already been defined
841    * @exception SecurityException If a security manager exists and its
842    * checkSetFactory method doesn't allow the operation
843    */
844   public static synchronized void setContentHandlerFactory
845                                     (ContentHandlerFactory fac)
846   {
847     if (factory != null)
848       throw new Error("ContentHandlerFactory already set");
849
850     // Throw an exception if an extant security mgr precludes
851     // setting the factory.
852     SecurityManager s = System.getSecurityManager();
853     if (s != null)
854       s.checkSetFactory();
855
856     factory = fac;
857   }
858
859   /**
860    * Returns the MIME type of a file based on the name of the file.  This
861    * works by searching for the file's extension in a list of file extensions
862    * and returning the MIME type associated with it.  If no type is found,
863    * then a MIME type of "application/octet-stream" will be returned.
864    *
865    * @param filename The filename to determine the MIME type for
866    *
867    * @return The MIME type String
868    *
869    * @specnote public since JDK 1.4
870    */
871   public static String guessContentTypeFromName(String filename)
872   {
873     int dot = filename.lastIndexOf (".");
874     
875     if (dot != -1)
876       {
877         if (dot == filename.length())
878           return ("application/octet-stream");
879         else
880           filename = filename.substring (dot + 1);
881       }
882     
883     String type = MimeTypes.getMimeTypeFromExtension (filename);
884     
885     if (type == null)
886       return("application/octet-stream");
887
888     return type;
889   }
890
891   /**
892    * Returns the MIME type of a stream based on the first few characters
893    * at the beginning of the stream.  This routine can be used to determine
894    * the MIME type if a server is believed to be returning an incorrect
895    * MIME type.  This method returns "application/octet-stream" if it 
896    * cannot determine the MIME type.
897    * <p>
898    * NOTE: Overriding MIME types sent from the server can be obnoxious
899    * to user's.  See Internet Exploder 4 if you don't believe me.
900    *
901    * @param is The InputStream to determine the MIME type from
902    *
903    * @return The MIME type
904    *
905    * @exception IOException If an error occurs
906    */
907   public static String guessContentTypeFromStream(InputStream is)
908     throws IOException
909   {
910     is.mark(1024);
911     // FIXME: Implement this. Use system mimetype informations (like "file").
912     is.reset();
913     return null;
914   }
915
916   /**
917    * This method returns the <code>FileNameMap</code> object being used
918    * to decode MIME types by file extension.
919    *
920    * @return The <code>FileNameMap</code>.
921    *
922    * @since 1.2
923    */
924   public static FileNameMap getFileNameMap()
925   {
926     return fileNameMap;
927   }
928
929   /**
930    * This method set the <code>FileNameMap</code> object being used
931    * to decode MIME types by file extension.
932    *
933    * @param map The <code>FileNameMap</code>.
934    *
935    * @exception SecurityException If a security manager exists and its
936    * checkSetFactory method doesn't allow the operation
937    * 
938    * @since 1.2
939    */
940   public static void setFileNameMap(FileNameMap map)
941   {
942     // Throw an exception if an extant security mgr precludes
943     // setting the factory.
944     SecurityManager s = System.getSecurityManager();
945     if (s != null)
946       s.checkSetFactory();
947
948     fileNameMap = map;
949   }
950
951   private ContentHandler setContentHandler(String contentType)
952   {
953     ContentHandler handler;
954
955     // No content type so just handle it as the default.
956     if (contentType == null || contentType == "")
957       return null;
958
959     // See if a handler has been cached for this content type.
960     // For efficiency, if a content type has been searched for but not
961     // found, it will be in the hash table but as the contentType String
962     // instead of a ContentHandler.
963     if ((handler = (ContentHandler) handlers.get(contentType)) != null)
964       if (handler instanceof ContentHandler)
965         return handler;
966       else
967         return null;
968
969     // If a non-default factory has been set, use it to find the content type.
970     if (factory != null)
971       handler = factory.createContentHandler(contentType);
972
973     // Non-default factory may have returned null or a factory wasn't set.
974     // Use the default search algorithm to find a handler for this content type.
975     if (handler == null)
976       {
977         // Get the list of packages to check and append our default handler
978         // to it, along with the JDK specified default as a last resort.
979         // Except in very unusual environments the JDK specified one shouldn't
980         // ever be needed (or available).
981         String propVal = System.getProperty("java.content.handler.pkgs");
982         propVal = (propVal == null) ? "" : (propVal + "|");
983         propVal = propVal + "gnu.gcj.content|sun.net.www.content";
984
985         // Replace the '/' character in the content type with '.' and
986         // all other non-alphabetic, non-numeric characters with '_'.
987         StringTokenizer pkgPrefix = new StringTokenizer(propVal, "|");
988         char[] cArray = contentType.toCharArray();
989         for (int i = 0; i < cArray.length; i++)
990           {
991             if (cArray[i] == '/')
992               cArray[i] = '.';
993             else if (! ((cArray[i] >= 'A' && cArray[i] <= 'Z') || 
994                         (cArray[i] >= 'a' && cArray[i] <= 'z') ||
995                         (cArray[i] >= '0' && cArray[i] <= '9')))
996               cArray[i] = '_';
997           }
998         String contentClass = new String(cArray);
999
1000         // See if a class of this content type exists in any of the packages.
1001         do
1002           {
1003             String facName = pkgPrefix.nextToken() + "." + contentClass;
1004             try
1005               {
1006                 handler =
1007                   (ContentHandler) Class.forName(facName).newInstance();
1008               }
1009             catch (Exception e)
1010               {
1011                 // Can't instantiate; handler still null, go on to next element.
1012               }
1013           } while ((handler == null ||
1014                     ! (handler instanceof ContentHandler)) &&
1015                    pkgPrefix.hasMoreTokens());
1016       }
1017
1018     // Update the hashtable with the new content handler.
1019     if (handler != null && handler instanceof ContentHandler)
1020       {
1021         handlers.put(contentType, handler);
1022         return handler;
1023       }
1024
1025     // For efficiency on subsequent searches, put a dummy entry in the hash
1026     // table for content types that don't have a non-default ContentHandler.
1027     handlers.put(contentType, contentType);
1028     return null;
1029   }
1030   
1031   // We don't put these in a static initializer, because it creates problems
1032   // with initializer co-dependency: SimpleDateFormat's constructors eventually 
1033   // depend on URLConnection (via the java.text.*Symbols classes).
1034   private synchronized void initializeDateFormats()
1035   {
1036     if (dateformats_initialized)
1037       return;
1038     locale = new Locale("En", "Us", "Unix");
1039     dateFormat1 = new SimpleDateFormat("EEE, dd MMM yyyy hh:mm:ss 'GMT'", 
1040                                        locale);
1041     dateFormat2 = new SimpleDateFormat("EEEE, dd-MMM-yy hh:mm:ss 'GMT'", 
1042                                        locale);
1043     dateFormat3 = new SimpleDateFormat("EEE MMM d hh:mm:ss yyyy", locale);
1044     dateformats_initialized = true;
1045   }
1046 } // class URLConnection
1047