1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.net;
19
20import java.io.IOException;
21import java.io.InputStream;
22import java.io.OutputStream;
23import java.security.AccessController;
24import java.security.PrivilegedAction;
25import java.util.Collections;
26import java.util.Date;
27import java.util.Hashtable;
28import java.util.List;
29import java.util.Map;
30import java.util.StringTokenizer;
31
32import org.apache.harmony.luni.internal.net.www.MimeTable;
33import org.apache.harmony.luni.util.Msg;
34import org.apache.harmony.luni.util.PriviAction;
35
36/**
37 * Concrete implementations of the abstract {@code URLConnection} class provide
38 * a communication link to a URL for exchanging data with a specific protocol
39 * type. A {@code URLConnection} can only be set up after the instantiation but
40 * before connecting to the remote resource.
41 */
42public abstract class URLConnection {
43
44    /**
45     * The URL which represents the remote target of this {@code URLConnection}.
46     */
47    protected URL url;
48
49    private String contentType;
50
51    private static boolean defaultAllowUserInteraction;
52
53    private static boolean defaultUseCaches = true;
54
55    ContentHandler defaultHandler = new DefaultContentHandler();
56
57    private long lastModified = -1;
58
59    /**
60     * The data must be modified more recently than this time in milliseconds
61     * since January 1, 1970, GMT to be transmitted.
62     */
63    protected long ifModifiedSince;
64
65    /**
66     * Specifies whether the using of caches is enabled or the data has to be
67     * recent for every request.
68     */
69    protected boolean useCaches = defaultUseCaches;
70
71    /**
72     * Specifies whether this {@code URLConnection} is already connected to the
73     * remote resource. If this field is set to {@code true} the flags for
74     * setting up the connection are not changeable anymore.
75     */
76    protected boolean connected;
77
78    /**
79     * Specifies whether this {@code URLConnection} allows sending data.
80     */
81    protected boolean doOutput;
82
83    /**
84     * Specifies whether this {@code URLConnection} allows receiving data.
85     */
86    protected boolean doInput = true;
87
88    /**
89     * Specifies whether this {@code URLConnection} allows user interaction as
90     * it is needed for authentication purposes.
91     */
92    protected boolean allowUserInteraction = defaultAllowUserInteraction;
93
94    private static ContentHandlerFactory contentHandlerFactory;
95
96    private int readTimeout = 0;
97
98    private int connectTimeout = 0;
99
100    /**
101     * Cache for storing content handler
102     */
103    static Hashtable<String, Object> contentHandlers = new Hashtable<String, Object>();
104
105    /**
106     * A hashtable that maps the filename extension (key) to a MIME-type
107     * (element)
108     */
109    private static FileNameMap fileNameMap;
110
111    /**
112     * Creates a new {@code URLConnection} instance pointing to the resource
113     * specified by the given URL.
114     *
115     * @param url
116     *            the URL which represents the resource this {@code
117     *            URLConnection} will point to.
118     */
119    protected URLConnection(URL url) {
120        this.url = url;
121    }
122
123    /**
124     * Establishes the connection to the earlier configured resource. The
125     * connection can only be set up before this method has been called.
126     *
127     * @throws IOException
128     *             if an error occurs while connecting to the resource.
129     */
130    public abstract void connect() throws IOException;
131
132    /**
133     * Gets the option value which indicates whether user interaction is allowed
134     * on this {@code URLConnection}.
135     *
136     * @return the value of the option {@code allowUserInteraction}.
137     * @see #allowUserInteraction
138     */
139    public boolean getAllowUserInteraction() {
140        return allowUserInteraction;
141    }
142
143    /**
144     * Gets an object representing the content of the resource this {@code
145     * URLConnection} is connected to. First, it attempts to get the content
146     * type from the method {@code getContentType()} which looks at the response
147     * header field "Content-Type". If none is found it will guess the content
148     * type from the filename extension. If that fails the stream itself will be
149     * used to guess the content type.
150     *
151     * @return the content representing object.
152     * @throws IOException
153     *             if an error occurs obtaining the content.
154     */
155    public Object getContent() throws java.io.IOException {
156        if (!connected) {
157            connect();
158        }
159
160        if ((contentType = getContentType()) == null) {
161            if ((contentType = guessContentTypeFromName(url.getFile())) == null) {
162                contentType = guessContentTypeFromStream(getInputStream());
163            }
164        }
165        if (contentType != null) {
166            return getContentHandler(contentType).getContent(this);
167        }
168        return null;
169    }
170
171    /**
172     * Gets an object representing the content of the resource this {@code
173     * URLConnection} is connected to. First, it attempts to get the content
174     * type from the method {@code getContentType()} which looks at the response
175     * header field "Content-Type". If none is found it will guess the content
176     * type from the filename extension. If that fails the stream itself will be
177     * used to guess the content type. The content type must match with one of
178     * the list {@code types}.
179     *
180     * @param types
181     *            the list of acceptable content types.
182     * @return the content representing object or {@code null} if the content
183     *         type does not match with one of the specified types.
184     * @throws IOException
185     *             if an error occurs obtaining the content.
186     */
187    // Param is not generic in spec
188    @SuppressWarnings("unchecked")
189    public Object getContent(Class[] types) throws IOException {
190        if (!connected) {
191            connect();
192        }
193
194        if ((contentType = getContentType()) == null) {
195            if ((contentType = guessContentTypeFromName(url.getFile())) == null) {
196                contentType = guessContentTypeFromStream(getInputStream());
197            }
198        }
199        if (contentType != null) {
200            return getContentHandler(contentType).getContent(this, types);
201        }
202        return null;
203    }
204
205    /**
206     * Gets the content encoding type specified by the response header field
207     * {@code content-encoding} or {@code null} if this field is not set.
208     *
209     * @return the value of the response header field {@code content-encoding}.
210     */
211    public String getContentEncoding() {
212        return getHeaderField("Content-Encoding"); //$NON-NLS-1$
213    }
214
215    /**
216     * Returns the specific ContentHandler that will handle the type {@code
217     * contentType}.
218     *
219     * @param type
220     *            The type that needs to be handled
221     * @return An instance of the Content Handler
222     */
223    private ContentHandler getContentHandler(String type) throws IOException {
224        // Replace all non-alphanumeric character by '_'
225        final String typeString = parseTypeString(type.replace('/', '.'));
226
227        // if there's a cached content handler, use it
228        Object cHandler = contentHandlers.get(type);
229        if (cHandler != null) {
230            return (ContentHandler) cHandler;
231        }
232
233        if (contentHandlerFactory != null) {
234            cHandler = contentHandlerFactory.createContentHandler(type);
235            contentHandlers.put(type, cHandler);
236            return (ContentHandler) cHandler;
237        }
238
239        // search through the package list for the right class for the Content
240        // Type
241        String packageList = AccessController
242                .doPrivileged(new PriviAction<String>(
243                        "java.content.handler.pkgs")); //$NON-NLS-1$
244        if (packageList != null) {
245            final StringTokenizer st = new StringTokenizer(packageList, "|"); //$NON-NLS-1$
246            while (st.countTokens() > 0) {
247                try {
248                    Class<?> cl = Class.forName(st.nextToken() + "." //$NON-NLS-1$
249                            + typeString, true, ClassLoader
250                            .getSystemClassLoader());
251                    cHandler = cl.newInstance();
252                } catch (ClassNotFoundException e) {
253                } catch (IllegalAccessException e) {
254                } catch (InstantiationException e) {
255                }
256            }
257        }
258
259        if (cHandler == null) {
260            cHandler = AccessController
261                    .doPrivileged(new PrivilegedAction<Object>() {
262                        public Object run() {
263                            try {
264                                // Try looking up AWT image content handlers
265                                String className = "org.apache.harmony.awt.www.content." //$NON-NLS-1$
266                                        + typeString;
267                                return Class.forName(className).newInstance();
268                            } catch (ClassNotFoundException e) {
269                            } catch (IllegalAccessException e) {
270                            } catch (InstantiationException e) {
271                            }
272                            return null;
273                        }
274                    });
275        }
276        if (cHandler != null) {
277            if (!(cHandler instanceof ContentHandler)) {
278                throw new UnknownServiceException();
279            }
280            contentHandlers.put(type, cHandler); // if we got the handler,
281            // cache it for next time
282            return (ContentHandler) cHandler;
283        }
284
285        return defaultHandler;
286    }
287
288    /**
289     * Gets the content length in bytes specified by the response header field
290     * {@code content-length} or {@code -1} if this field is not set.
291     *
292     * @return the value of the response header field {@code content-length}.
293     */
294    public int getContentLength() {
295        return getHeaderFieldInt("Content-Length", -1); //$NON-NLS-1$
296    }
297
298    /**
299     * Gets the MIME-type of the content specified by the response header field
300     * {@code content-type} or {@code null} if type is unknown.
301     *
302     * @return the value of the response header field {@code content-type}.
303     */
304    public String getContentType() {
305        return getHeaderField("Content-Type"); //$NON-NLS-1$
306    }
307
308    /**
309     * Gets the timestamp when this response has been sent as a date in
310     * milliseconds since January 1, 1970 GMT or {@code 0} if this timestamp is
311     * unknown.
312     *
313     * @return the sending timestamp of the current response.
314     */
315    public long getDate() {
316        return getHeaderFieldDate("Date", 0); //$NON-NLS-1$
317    }
318
319    /**
320     * Gets the default setting whether this connection allows user interaction.
321     *
322     * @return the value of the default setting {@code
323     *         defaultAllowUserInteraction}.
324     * @see #allowUserInteraction
325     */
326    public static boolean getDefaultAllowUserInteraction() {
327        return defaultAllowUserInteraction;
328    }
329
330    /**
331     * Gets the default value for the specified request {@code field} or {@code
332     * null} if the field could not be found. The current implementation of this
333     * method returns always {@code null}.
334     *
335     * @param field
336     *            the request field whose default value shall be returned.
337     * @return the default value for the given field.
338     * @deprecated Use {@link #getRequestProperty}
339     */
340    @Deprecated
341    public static String getDefaultRequestProperty(String field) {
342        return null;
343    }
344
345    /**
346     * Gets the default setting whether this connection allows using caches.
347     *
348     * @return the value of the default setting {@code defaultUseCaches}.
349     * @see #useCaches
350     */
351    public boolean getDefaultUseCaches() {
352        return defaultUseCaches;
353    }
354
355    /**
356     * Gets the value of the option {@code doInput} which specifies whether this
357     * connection allows to receive data.
358     *
359     * @return {@code true} if this connection allows input, {@code false}
360     *         otherwise.
361     * @see #doInput
362     */
363    public boolean getDoInput() {
364        return doInput;
365    }
366
367    /**
368     * Gets the value of the option {@code doOutput} which specifies whether
369     * this connection allows to send data.
370     *
371     * @return {@code true} if this connection allows output, {@code false}
372     *         otherwise.
373     * @see #doOutput
374     */
375    public boolean getDoOutput() {
376        return doOutput;
377    }
378
379    /**
380     * Gets the timestamp when this response will be expired in milliseconds
381     * since January 1, 1970 GMT or {@code 0} if this timestamp is unknown.
382     *
383     * @return the value of the response header field {@code expires}.
384     */
385    public long getExpiration() {
386        return getHeaderFieldDate("Expires", 0); //$NON-NLS-1$
387    }
388
389    /**
390     * Gets the table which is used by all {@code URLConnection} instances to
391     * determine the MIME-type according to a file extension.
392     *
393     * @return the file name map to determine the MIME-type.
394     */
395    public static FileNameMap getFileNameMap() {
396        // Must use lazy initialization or there is a bootstrap problem
397        // trying to load the MimeTable resource from a .jar before
398        // JarURLConnection has finished initialization.
399        synchronized (URLConnection.class) {
400            if (fileNameMap == null) {
401                fileNameMap = new MimeTable();
402            }
403            return fileNameMap;
404        }
405    }
406
407    /**
408     * Gets the header value at the field position {@code pos} or {@code null}
409     * if the header has fewer than {@code pos} fields. The current
410     * implementation of this method returns always {@code null}.
411     *
412     * @param pos
413     *            the field position of the response header.
414     * @return the value of the field at position {@code pos}.
415     */
416    public String getHeaderField(int pos) {
417        return null;
418    }
419
420    /**
421     * Gets an unchangeable map of the response-header fields and values. The
422     * response-header field names are the key values of the map. The map values
423     * are lists of header field values associated with a particular key name.
424     *
425     * @return the response-header representing generic map.
426     * @since 1.4
427     */
428    public Map<String, List<String>> getHeaderFields() {
429        return Collections.emptyMap();
430    }
431
432    /**
433     * Gets an unchangeable map of general request properties used by this
434     * connection. The request property names are the key values of the map. The
435     * map values are lists of property values of the corresponding key name.
436     *
437     * @return the request-property representing generic map.
438     * @since 1.4
439     */
440    public Map<String, List<String>> getRequestProperties() {
441        if (connected) {
442            throw new IllegalStateException(Msg.getString("K0037")); //$NON-NLS-1$
443        }
444        return Collections.emptyMap();
445    }
446
447    /**
448     * Adds the given property to the request header. Existing properties with
449     * the same name will not be overwritten by this method.
450     *
451     * @param field
452     *            the request property field name to add.
453     * @param newValue
454     *            the value of the property which is to add.
455     * @throws IllegalStateException
456     *             if the connection has been already established.
457     * @throws NullPointerException
458     *             if the property name is {@code null}.
459     * @since 1.4
460     */
461    public void addRequestProperty(String field, String newValue) {
462        if (connected) {
463            throw new IllegalStateException(Msg.getString("K0037")); //$NON-NLS-1$
464        }
465        if (field == null) {
466            throw new NullPointerException(Msg.getString("KA007")); //$NON-NLS-1$
467        }
468    }
469
470    /**
471     * Gets the value of the header field specified by {@code key} or {@code
472     * null} if there is no field with this name. The current implementation of
473     * this method returns always {@code null}.
474     *
475     * @param key
476     *            the name of the header field.
477     * @return the value of the header field.
478     */
479    public String getHeaderField(String key) {
480        return null;
481    }
482
483    /**
484     * Gets the specified header value as a date in milliseconds since January
485     * 1, 1970 GMT. Returns the {@code defaultValue} if no such header field
486     * could be found.
487     *
488     * @param field
489     *            the header field name whose value is needed.
490     * @param defaultValue
491     *            the default value if no field has been found.
492     * @return the value of the specified header field as a date in
493     *         milliseconds.
494     */
495    @SuppressWarnings("deprecation")
496    public long getHeaderFieldDate(String field, long defaultValue) {
497        String date = getHeaderField(field);
498        if (date == null) {
499            return defaultValue;
500        }
501        try {
502            return Date.parse(date);
503        } catch (Exception e) {
504            return defaultValue;
505        }
506    }
507
508    /**
509     * Gets the specified header value as a number. Returns the {@code
510     * defaultValue} if no such header field could be found or the value could
511     * not be parsed as an {@code Integer}.
512     *
513     * @param field
514     *            the header field name whose value is needed.
515     * @param defaultValue
516     *            the default value if no field has been found.
517     * @return the value of the specified header field as a number.
518     */
519    public int getHeaderFieldInt(String field, int defaultValue) {
520        try {
521            return Integer.parseInt(getHeaderField(field));
522        } catch (NumberFormatException e) {
523            return defaultValue;
524        }
525    }
526
527    /**
528     * Gets the name of the header field at the given position {@code posn} or
529     * {@code null} if there are fewer than {@code posn} fields. The current
530     * implementation of this method returns always {@code null}.
531     *
532     * @param posn
533     *            the position of the header field which has to be returned.
534     * @return the header field name at the given position.
535     */
536    public String getHeaderFieldKey(int posn) {
537        return null;
538    }
539
540    /**
541     * Gets the point of time since when the data must be modified to be
542     * transmitted. Some protocols transmit data only if it has been modified
543     * more recently than a particular time.
544     *
545     * @return the time in milliseconds since January 1, 1970 GMT.
546     * @see #ifModifiedSince
547     */
548    public long getIfModifiedSince() {
549        return ifModifiedSince;
550    }
551
552    /**
553     * Gets an {@code InputStream} for reading data from the resource pointed by
554     * this {@code URLConnection}. It throws an UnknownServiceException by
555     * default. This method must be overridden by its subclasses.
556     *
557     * @return the InputStream to read data from.
558     * @throws IOException
559     *             if no InputStream could be created.
560     */
561    public InputStream getInputStream() throws IOException {
562        throw new UnknownServiceException(Msg.getString("K004d")); //$NON-NLS-1$
563    }
564
565    /**
566     * Gets the value of the response header field {@code last-modified} or
567     * {@code 0} if this value is not set.
568     *
569     * @return the value of the {@code last-modified} header field.
570     */
571    public long getLastModified() {
572        if (lastModified != -1) {
573            return lastModified;
574        }
575        return lastModified = getHeaderFieldDate("Last-Modified", 0); //$NON-NLS-1$
576    }
577
578    /**
579     * Gets an {@code OutputStream} for writing data to this {@code
580     * URLConnection}. It throws an {@code UnknownServiceException} by default.
581     * This method must be overridden by its subclasses.
582     *
583     * @return the OutputStream to write data.
584     * @throws IOException
585     *             if no OutputStream could be created.
586     */
587    public OutputStream getOutputStream() throws IOException {
588        throw new UnknownServiceException(Msg.getString("K005f")); //$NON-NLS-1$
589    }
590
591    /**
592     * Gets a {@code Permission} object representing all needed permissions to
593     * open this connection. The returned permission object depends on the state
594     * of the connection and will be {@code null} if no permissions are
595     * necessary. By default, this method returns {@code AllPermission}.
596     * Subclasses should overwrite this method to return an appropriate
597     * permission object.
598     *
599     * @return the permission object representing the needed permissions to open
600     *         this connection.
601     * @throws IOException
602     *             if an I/O error occurs while creating the permission object.
603     */
604    public java.security.Permission getPermission() throws IOException {
605        return new java.security.AllPermission();
606    }
607
608    /**
609     * Gets the value of the request header property specified by {code field}
610     * or {@code null} if there is no field with this name. The current
611     * implementation of this method returns always {@code null}.
612     *
613     * @param field
614     *            the name of the request header property.
615     * @return the value of the property.
616     * @throws IllegalStateException
617     *             if the connection has been already established.
618     */
619    public String getRequestProperty(String field) {
620        if (connected) {
621            throw new IllegalStateException(Msg.getString("K0037")); //$NON-NLS-1$
622        }
623        return null;
624    }
625
626    /**
627     * Gets the URL represented by this {@code URLConnection}.
628     *
629     * @return the URL of this connection.
630     */
631    public URL getURL() {
632        return url;
633    }
634
635    /**
636     * Gets the value of the flag which specifies whether this {@code
637     * URLConnection} allows to use caches.
638     *
639     * @return {@code true} if using caches is allowed, {@code false} otherwise.
640     */
641    public boolean getUseCaches() {
642        return useCaches;
643    }
644
645    /**
646     * Determines the MIME-type of the given resource {@code url} by resolving
647     * the filename extension with the internal FileNameMap. Any fragment
648     * identifier is removed before processing.
649     *
650     * @param url
651     *            the URL with the filename to get the MIME type.
652     * @return the guessed content type or {@code null} if the type could not be
653     *         determined.
654     */
655    public static String guessContentTypeFromName(String url) {
656        return getFileNameMap().getContentTypeFor(url);
657    }
658
659    /**
660     * Determines the MIME-type of the resource represented by the input stream
661     * {@code is} by reading its first few characters.
662     *
663     * @param is
664     *            the resource representing input stream to determine the
665     *            content type.
666     * @return the guessed content type or {@code null} if the type could not be
667     *         determined.
668     * @throws IOException
669     *             if an I/O error occurs while reading from the input stream.
670     */
671    @SuppressWarnings("nls")
672    public static String guessContentTypeFromStream(InputStream is)
673            throws IOException {
674
675        if (!is.markSupported()) {
676            return null;
677        }
678        // Look ahead up to 64 bytes for the longest encoded header
679        is.mark(64);
680        byte[] bytes = new byte[64];
681        int length = is.read(bytes);
682        is.reset();
683
684        // Check for Unicode BOM encoding indicators
685        String encoding = "ASCII";
686        int start = 0;
687        if (length > 1) {
688            if ((bytes[0] == (byte) 0xFF) && (bytes[1] == (byte) 0xFE)) {
689                encoding = "UTF-16LE";
690                start = 2;
691                length -= length & 1;
692            }
693            if ((bytes[0] == (byte) 0xFE) && (bytes[1] == (byte) 0xFF)) {
694                encoding = "UTF-16BE";
695                start = 2;
696                length -= length & 1;
697            }
698            if (length > 2) {
699                if ((bytes[0] == (byte) 0xEF) && (bytes[1] == (byte) 0xBB)
700                        && (bytes[2] == (byte) 0xBF)) {
701                    encoding = "UTF-8";
702                    start = 3;
703                }
704                if (length > 3) {
705                    if ((bytes[0] == (byte) 0x00) && (bytes[1] == (byte) 0x00)
706                            && (bytes[2] == (byte) 0xFE)
707                            && (bytes[3] == (byte) 0xFF)) {
708                        encoding = "UTF-32BE";
709                        start = 4;
710                        length -= length & 3;
711                    }
712                    if ((bytes[0] == (byte) 0xFF) && (bytes[1] == (byte) 0xFE)
713                            && (bytes[2] == (byte) 0x00)
714                            && (bytes[3] == (byte) 0x00)) {
715                        encoding = "UTF-32LE";
716                        start = 4;
717                        length -= length & 3;
718                    }
719                }
720            }
721        }
722
723        String header = new String(bytes, start, length - start, encoding);
724
725        // Check binary types
726        if (header.startsWith("PK")) {
727            return "application/zip";
728        }
729        if (header.startsWith("GI")) {
730            return "image/gif";
731        }
732
733        // Check text types
734        String textHeader = header.trim().toUpperCase();
735        if (textHeader.startsWith("<!DOCTYPE HTML") ||
736                textHeader.startsWith("<HTML") ||
737                textHeader.startsWith("<HEAD") ||
738                textHeader.startsWith("<BODY") ||
739                textHeader.startsWith("<HEAD")) {
740            return "text/html";
741        }
742
743        if (textHeader.startsWith("<?XML")) {
744            return "application/xml";
745        }
746
747        // Give up
748        return null;
749    }
750
751    /**
752     * Performs any necessary string parsing on the input string such as
753     * converting non-alphanumeric character into underscore.
754     *
755     * @param typeString
756     *            the parsed string
757     * @return the string to be parsed
758     */
759    private String parseTypeString(String typeString) {
760        StringBuilder typeStringBuffer = new StringBuilder(typeString);
761        for (int i = 0; i < typeStringBuffer.length(); i++) {
762            // if non-alphanumeric, replace it with '_'
763            char c = typeStringBuffer.charAt(i);
764            if (!(Character.isLetter(c) || Character.isDigit(c) || c == '.')) {
765                typeStringBuffer.setCharAt(i, '_');
766            }
767        }
768        return typeStringBuffer.toString();
769    }
770
771    /**
772     * Sets the flag indicating whether this connection allows user interaction
773     * or not. This method can only be called prior to the connection
774     * establishment.
775     *
776     * @param newValue
777     *            the value of the flag to be set.
778     * @throws IllegalStateException
779     *             if this method attempts to change the flag after the
780     *             connection has been established.
781     * @see #allowUserInteraction
782     */
783    public void setAllowUserInteraction(boolean newValue) {
784        if (connected) {
785            throw new IllegalStateException(Msg.getString("K0037")); //$NON-NLS-1$
786        }
787        this.allowUserInteraction = newValue;
788    }
789
790    /**
791     * Sets the internally used content handler factory. The content factory can
792     * only be set if it is allowed by the security manager and only once during
793     * the lifetime of the application.
794     *
795     * @param contentFactory
796     *            the content factory to be set.
797     * @throws Error
798     *             if the security manager does not allow to set the content
799     *             factory or it has been already set earlier ago.
800     */
801    public static synchronized void setContentHandlerFactory(
802            ContentHandlerFactory contentFactory) {
803        if (contentHandlerFactory != null) {
804            throw new Error(Msg.getString("K004e")); //$NON-NLS-1$
805        }
806        SecurityManager sManager = System.getSecurityManager();
807        if (sManager != null) {
808            sManager.checkSetFactory();
809        }
810        contentHandlerFactory = contentFactory;
811    }
812
813    /**
814     * Sets the default value for the flag indicating whether this connection
815     * allows user interaction or not. Existing {@code URLConnection}s are
816     * unaffected.
817     *
818     * @param allows
819     *            the default value of the flag to be used for new connections.
820     * @see #defaultAllowUserInteraction
821     * @see #allowUserInteraction
822     */
823    public static void setDefaultAllowUserInteraction(boolean allows) {
824        defaultAllowUserInteraction = allows;
825    }
826
827    /**
828     * Sets the default value of the specified request header field. This value
829     * will be used for the specific field of every newly created connection.
830     * The current implementation of this method does nothing.
831     *
832     * @param field
833     *            the request header field to be set.
834     * @param value
835     *            the default value to be used.
836     * @deprecated Use {@link #setRequestProperty} of an existing {@code
837     *             URLConnection} instance.
838     */
839    @Deprecated
840    public static void setDefaultRequestProperty(String field, String value) {
841    }
842
843    /**
844     * Sets the default value for the flag indicating whether this connection
845     * allows to use caches. Existing {@code URLConnection}s are unaffected.
846     *
847     * @param newValue
848     *            the default value of the flag to be used for new connections.
849     * @see #defaultUseCaches
850     * @see #useCaches
851     */
852    public void setDefaultUseCaches(boolean newValue) {
853        // BEGIN android-removed
854        // Setting the default doesn't concern the current connection.
855        // if (connected) {
856        //     throw new IllegalAccessError(Msg.getString("K0037")); //$NON-NLS-1$
857        // }
858        // END android-removed
859        defaultUseCaches = newValue;
860    }
861
862    /**
863     * Sets the flag indicating whether this {@code URLConnection} allows input.
864     * It cannot be set after the connection is established.
865     *
866     * @param newValue
867     *            the new value for the flag to be set.
868     * @throws IllegalAccessError
869     *             if this method attempts to change the value after the
870     *             connection has been already established.
871     * @see #doInput
872     */
873    public void setDoInput(boolean newValue) {
874        if (connected) {
875            throw new IllegalStateException(Msg.getString("K0037")); //$NON-NLS-1$
876        }
877        this.doInput = newValue;
878    }
879
880    /**
881     * Sets the flag indicating whether this {@code URLConnection} allows
882     * output. It cannot be set after the connection is established.
883     *
884     * @param newValue
885     *            the new value for the flag to be set.
886     * @throws IllegalAccessError
887     *             if this method attempts to change the value after the
888     *             connection has been already established.
889     * @see #doOutput
890     */
891    public void setDoOutput(boolean newValue) {
892        if (connected) {
893            throw new IllegalStateException(Msg.getString("K0037")); //$NON-NLS-1$
894        }
895        this.doOutput = newValue;
896    }
897
898    /**
899     * Sets the internal map which is used by all {@code URLConnection}
900     * instances to determine the MIME-type according to a filename extension.
901     *
902     * @param map
903     *            the MIME table to be set.
904     */
905    public static void setFileNameMap(FileNameMap map) {
906        SecurityManager manager = System.getSecurityManager();
907        if (manager != null) {
908            manager.checkSetFactory();
909        }
910        synchronized (URLConnection.class) {
911            fileNameMap = map;
912        }
913    }
914
915    /**
916     * Sets the point of time since when the data must be modified to be
917     * transmitted. Some protocols transmit data only if it has been modified
918     * more recently than a particular time. The data will be transmitted
919     * regardless of its timestamp if this option is set to {@code 0}.
920     *
921     * @param newValue
922     *            the time in milliseconds since January 1, 1970 GMT.
923     * @throws IllegalStateException
924     *             if this {@code URLConnection} has already been connected.
925     * @see #ifModifiedSince
926     */
927    public void setIfModifiedSince(long newValue) {
928        if (connected) {
929            throw new IllegalStateException(Msg.getString("K0037")); //$NON-NLS-1$
930        }
931        this.ifModifiedSince = newValue;
932    }
933
934    /**
935     * Sets the value of the specified request header field. The value will only
936     * be used by the current {@code URLConnection} instance. This method can
937     * only be called before the connection is established.
938     *
939     * @param field
940     *            the request header field to be set.
941     * @param newValue
942     *            the new value of the specified property.
943     * @throws IllegalStateException
944     *             if the connection has been already established.
945     * @throws NullPointerException
946     *             if the parameter {@code field} is {@code null}.
947     */
948    public void setRequestProperty(String field, String newValue) {
949        if (connected) {
950            throw new IllegalStateException(Msg.getString("K0037")); //$NON-NLS-1$
951        }
952        if (field == null) {
953            throw new NullPointerException(Msg.getString("KA007")); //$NON-NLS-1$
954        }
955    }
956
957    /**
958     * Sets the flag indicating whether this connection allows to use caches or
959     * not. This method can only be called prior to the connection
960     * establishment.
961     *
962     * @param newValue
963     *            the value of the flag to be set.
964     * @throws IllegalStateException
965     *             if this method attempts to change the flag after the
966     *             connection has been established.
967     * @see #useCaches
968     */
969    public void setUseCaches(boolean newValue) {
970        if (connected) {
971            throw new IllegalStateException(Msg.getString("K0037")); //$NON-NLS-1$
972        }
973        this.useCaches = newValue;
974    }
975
976    /**
977     * Sets the timeout value in milliseconds for establishing the connection to
978     * the resource pointed by this {@code URLConnection} instance. A {@code
979     * SocketTimeoutException} is thrown if the connection could not be
980     * established in this time. Default is {@code 0} which stands for an
981     * infinite timeout.
982     *
983     * @param timeout
984     *            the connecting timeout in milliseconds.
985     * @throws IllegalArgumentException
986     *             if the parameter {@code timeout} is less than zero.
987     */
988    public void setConnectTimeout(int timeout) {
989        if (0 > timeout) {
990            throw new IllegalArgumentException(Msg.getString("K0036")); //$NON-NLS-1$
991        }
992        this.connectTimeout = timeout;
993    }
994
995    /**
996     * Gets the configured connecting timeout.
997     *
998     * @return the connecting timeout value in milliseconds.
999     */
1000    public int getConnectTimeout() {
1001        return connectTimeout;
1002    }
1003
1004    /**
1005     * Sets the timeout value in milliseconds for reading from the input stream
1006     * of an established connection to the resource. A {@code
1007     * SocketTimeoutException} is thrown if the connection could not be
1008     * established in this time. Default is {@code 0} which stands for an
1009     * infinite timeout.
1010     *
1011     * @param timeout
1012     *            the reading timeout in milliseconds.
1013     * @throws IllegalArgumentException
1014     *             if the parameter {@code timeout} is less than zero.
1015     */
1016    public void setReadTimeout(int timeout) {
1017        if (0 > timeout) {
1018            throw new IllegalArgumentException(Msg.getString("K0036")); //$NON-NLS-1$
1019        }
1020        this.readTimeout = timeout;
1021    }
1022
1023    /**
1024     * Gets the configured timeout for reading from the input stream of an
1025     * established connection to the resource.
1026     *
1027     * @return the reading timeout value in milliseconds.
1028     */
1029    public int getReadTimeout() {
1030        return readTimeout;
1031    }
1032
1033    /**
1034     * Returns the string representation containing the name of this class and
1035     * the URL.
1036     *
1037     * @return the string representation of this {@code URLConnection} instance.
1038     */
1039    @Override
1040    public String toString() {
1041        return getClass().getName() + ":" + url.toString(); //$NON-NLS-1$
1042    }
1043
1044    static class DefaultContentHandler extends java.net.ContentHandler {
1045
1046        /**
1047         * @param u
1048         *            the URL connection
1049         *
1050         * @see java.net.ContentHandler#getContent(java.net.URLConnection)
1051         */
1052        @Override
1053        public Object getContent(URLConnection u) throws IOException {
1054            return u.getInputStream();
1055        }
1056    }
1057}
1058