FileDocCategorySizeDatePackage
URLConnection.javaAPI DocAndroid 1.5 API36955Wed May 06 22:41:04 BST 2009java.net

URLConnection.java

/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package java.net;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import org.apache.harmony.luni.internal.net.www.MimeTable;
import org.apache.harmony.luni.util.Msg;
import org.apache.harmony.luni.util.PriviAction;
import org.apache.harmony.luni.util.Util;

/**
 * Concrete implementations of the abstract {@code URLConnection} class provide
 * a communication link to a URL for exchanging data with a specific protocol
 * type. A {@code URLConnection} can only be set up after the instantiation but
 * before connecting to the remote resource.
 * 
 * @since Android 1.0
 */
public abstract class URLConnection {

    /**
     * The URL which represents the remote target of this {@code URLConnection}.
     * 
     * @since Android 1.0
     */
    protected URL url;

    private String contentType;

    private static boolean defaultAllowUserInteraction;

    private static boolean defaultUseCaches = true;

    ContentHandler defaultHandler = new DefaultContentHandler();

    private long lastModified = -1;

    /**
     * The data must be modified more recently than this time in milliseconds
     * since January 1, 1970, GMT to be transmitted.
     * 
     * @since Android 1.0
     */
    protected long ifModifiedSince;

    /**
     * Specifies whether the using of caches is enabled or the data has to be
     * recent for every request.
     * 
     * @since Android 1.0
     */
    protected boolean useCaches = defaultUseCaches;

    /**
     * Specifies whether this {@code URLConnection} is already connected to the
     * remote resource. If this field is set to {@code true} the flags for
     * setting up the connection are not changeable anymore.
     * 
     * @since Android 1.0
     */
    protected boolean connected;

    /**
     * Specifies whether this {@code URLConnection} allows sending data.
     * 
     * @since Android 1.0
     */
    protected boolean doOutput;

    /**
     * Specifies whether this {@code URLConnection} allows receiving data.
     * 
     * @since Android 1.0
     */
    protected boolean doInput = true;

    /**
     * Specifies whether this {@code URLConnection} allows user interaction as
     * it is needed for authentication purposes.
     * 
     * @since Android 1.0
     */
    protected boolean allowUserInteraction = defaultAllowUserInteraction;

    private static ContentHandlerFactory contentHandlerFactory;

    private int readTimeout = 0;

    private int connectTimeout = 0;

    /**
     * Cache for storing content handler
     */
    static Hashtable<String, Object> contentHandlers = new Hashtable<String, Object>();

    /**
     * A hashtable that maps the filename extension (key) to a MIME-type
     * (element)
     */
    private static FileNameMap fileNameMap;

    /**
     * Creates a new {@code URLConnection} instance pointing to the resource
     * specified by the given URL.
     * 
     * @param url
     *            the URL which represents the resource this {@code
     *            URLConnection} will point to.
     * @since Android 1.0
     */
    protected URLConnection(URL url) {
        this.url = url;
    }

    /**
     * Establishes the connection to the earlier configured resource. The
     * connection can only be set up before this method has been called.
     * 
     * @throws IOException
     *             if an error occurs while connecting to the resource.
     * @since Android 1.0
     */
    public abstract void connect() throws IOException;

    /**
     * Gets the option value which indicates whether user interaction is allowed
     * on this {@code URLConnection}.
     * 
     * @return the value of the option {@code allowUserInteraction}.
     * @see #allowUserInteraction
     * @since Android 1.0
     */
    public boolean getAllowUserInteraction() {
        return allowUserInteraction;
    }

    /**
     * Gets an object representing the content of the resource this {@code
     * URLConnection} is connected to. First, it attempts to get the content
     * type from the method {@code getContentType()} which looks at the response
     * header field "Content-Type". If none is found it will guess the content
     * type from the filename extension. If that fails the stream itself will be
     * used to guess the content type.
     * 
     * @return the content representing object.
     * @throws IOException
     *             if an error occurs obtaining the content.
     * @since Android 1.0
     */
    public Object getContent() throws java.io.IOException {
        if (!connected) {
            connect();
        }

        if ((contentType = getContentType()) == null) {
            if ((contentType = guessContentTypeFromName(url.getFile())) == null) {
                contentType = guessContentTypeFromStream(getInputStream());
            }
        }
        if (contentType != null) {
            return getContentHandler(contentType).getContent(this);
        }
        return null;
    }

    /**
     * Gets an object representing the content of the resource this {@code
     * URLConnection} is connected to. First, it attempts to get the content
     * type from the method {@code getContentType()} which looks at the response
     * header field "Content-Type". If none is found it will guess the content
     * type from the filename extension. If that fails the stream itself will be
     * used to guess the content type. The content type must match with one of
     * the list {@code types}.
     * 
     * @param types
     *            the list of acceptable content types.
     * @return the content representing object or {@code null} if the content
     *         type does not match with one of the specified types.
     * @throws IOException
     *             if an error occurs obtaining the content.
     * @since Android 1.0
     */
    // Param is not generic in spec
    @SuppressWarnings("unchecked")
    public Object getContent(Class[] types) throws IOException {
        if (!connected) {
            connect();
        }

        if ((contentType = getContentType()) == null) {
            if ((contentType = guessContentTypeFromName(url.getFile())) == null) {
                contentType = guessContentTypeFromStream(getInputStream());
            }
        }
        if (contentType != null) {
            return getContentHandler(contentType).getContent(this, types);
        }
        return null;
    }

    /**
     * Gets the content encoding type specified by the response header field
     * {@code content-encoding} or {@code null} if this field is not set.
     * 
     * @return the value of the response header field {@code content-encoding}.
     * @since Android 1.0
     */
    public String getContentEncoding() {
        return getHeaderField("Content-Encoding"); //$NON-NLS-1$
    }

    /**
     * Returns the specific ContentHandler that will handle the type {@code
     * contentType}.
     * 
     * @param type
     *            The type that needs to be handled
     * @return An instance of the Content Handler
     */
    private ContentHandler getContentHandler(String type) throws IOException {
        // Replace all non-alphanumeric character by '_'
        final String typeString = parseTypeString(type.replace('/', '.'));

        // if there's a cached content handler, use it
        Object cHandler = contentHandlers.get(type);
        if (cHandler != null) {
            return (ContentHandler) cHandler;
        }

        if (contentHandlerFactory != null) {
            cHandler = contentHandlerFactory.createContentHandler(type);
            if (!(cHandler instanceof ContentHandler)) {
                throw new UnknownServiceException();
            }
            contentHandlers.put(type, cHandler);
            return (ContentHandler) cHandler;
        }

        // search through the package list for the right class for the Content
        // Type
        String packageList = AccessController
                .doPrivileged(new PriviAction<String>(
                        "java.content.handler.pkgs")); //$NON-NLS-1$
        if (packageList != null) {
            final StringTokenizer st = new StringTokenizer(packageList, "|"); //$NON-NLS-1$
            while (st.countTokens() > 0) {
                try {
                    Class<?> cl = Class.forName(st.nextToken() + "." //$NON-NLS-1$
                            + typeString, true, ClassLoader
                            .getSystemClassLoader());
                    cHandler = cl.newInstance();
                } catch (ClassNotFoundException e) {
                } catch (IllegalAccessException e) {
                } catch (InstantiationException e) {
                }
            }
        }

        if (cHandler == null) {
            cHandler = AccessController
                    .doPrivileged(new PrivilegedAction<Object>() {
                        public Object run() {
                            try {
                                String className = "org.apache.harmony.luni.internal.net.www.content." //$NON-NLS-1$
                                        + typeString;
                                return Class.forName(className).newInstance();
                            } catch (ClassNotFoundException e) {
                            } catch (IllegalAccessException e) {
                            } catch (InstantiationException e) {
                            }
                            return null;
                        }
                    });
        }
        if (cHandler != null) {
            if (!(cHandler instanceof ContentHandler)) {
                throw new UnknownServiceException();
            }
            contentHandlers.put(type, cHandler); // if we got the handler,
            // cache it for next time
            return (ContentHandler) cHandler;
        }

        return defaultHandler;
    }

    /**
     * Gets the content length in bytes specified by the response header field
     * {@code content-length} or {@code -1} if this field is not set.
     * 
     * @return the value of the response header field {@code content-length}.
     * @since Android 1.0
     */
    public int getContentLength() {
        return getHeaderFieldInt("Content-Length", -1); //$NON-NLS-1$
    }

    /**
     * Gets the MIME-type of the content specified by the response header field
     * {@code content-type} or {@code null} if type is unknown.
     * 
     * @return the value of the response header field {@code content-type}.
     * @since Android 1.0
     */
    public String getContentType() {
        return getHeaderField("Content-Type"); //$NON-NLS-1$
    }

    /**
     * Gets the timestamp when this response has been sent as a date in
     * milliseconds since January 1, 1970 GMT or {@code 0} if this timestamp is
     * unknown.
     * 
     * @return the sending timestamp of the current response.
     * @since Android 1.0
     */
    public long getDate() {
        return getHeaderFieldDate("Date", 0); //$NON-NLS-1$
    }

    /**
     * Gets the default setting whether this connection allows user interaction.
     * 
     * @return the value of the default setting {@code
     *         defaultAllowUserInteraction}.
     * @see #allowUserInteraction
     * @since Android 1.0
     */
    public static boolean getDefaultAllowUserInteraction() {
        return defaultAllowUserInteraction;
    }

    /**
     * Gets the default value for the specified request {@code field} or {@code
     * null} if the field could not be found. The current implementation of this
     * method returns always {@code null}.
     * 
     * @param field
     *            the request field whose default value shall be returned.
     * @return the default value for the given field.
     * @deprecated Use {@link #getRequestProperty}
     * @since Android 1.0
     */
    @Deprecated
    public static String getDefaultRequestProperty(String field) {
        return null;
    }

    /**
     * Gets the default setting whether this connection allows using caches.
     * 
     * @return the value of the default setting {@code defaultUseCaches}.
     * @see #useCaches
     * @since Android 1.0
     */
    public boolean getDefaultUseCaches() {
        return defaultUseCaches;
    }

    /**
     * Gets the value of the option {@code doInput} which specifies whether this
     * connection allows to receive data.
     * 
     * @return {@code true} if this connection allows input, {@code false}
     *         otherwise.
     * @see #doInput
     * @since Android 1.0
     */
    public boolean getDoInput() {
        return doInput;
    }

    /**
     * Gets the value of the option {@code doOutput} which specifies whether
     * this connection allows to send data.
     * 
     * @return {@code true} if this connection allows output, {@code false}
     *         otherwise.
     * @see #doOutput
     * @since Android 1.0
     */
    public boolean getDoOutput() {
        return doOutput;
    }

    /**
     * Gets the timestamp when this response will be expired in milliseconds
     * since January 1, 1970 GMT or {@code 0} if this timestamp is unknown.
     * 
     * @return the value of the response header field {@code expires}.
     * @since Android 1.0
     */
    public long getExpiration() {
        return getHeaderFieldDate("Expires", 0); //$NON-NLS-1$
    }

    /**
     * Gets the table which is used by all {@code URLConnection} instances to
     * determine the MIME-type according to a file extension.
     * 
     * @return the file name map to determine the MIME-type.
     * @since Android 1.0
     */
    public static FileNameMap getFileNameMap() {
        // Must use lazy initialization or there is a bootstrap problem
        // trying to load the MimeTable resource from a .jar before
        // JarURLConnection has finished initialization.
        if (fileNameMap == null) {
            fileNameMap = new MimeTable();
        }
        return fileNameMap;
    }

    /**
     * Gets the header value at the field position {@code pos} or {@code null}
     * if the header has fewer than {@code pos} fields. The current
     * implementation of this method returns always {@code null}.
     * 
     * @param pos
     *            the field position of the response header.
     * @return the value of the field at position {@code pos}.
     * @since Android 1.0
     */
    public String getHeaderField(int pos) {
        return null;
    }

    /**
     * Gets an unchangeable map of the response-header fields and values. The
     * response-header field names are the key values of the map. The map values
     * are lists of header field values associated with a particular key name.
     * 
     * @return the response-header representing generic map.
     * @since Android 1.0
     */
    public Map<String, List<String>> getHeaderFields() {
        return Collections.emptyMap();
    }

    /**
     * Gets an unchangeable map of general request properties used by this
     * connection. The request property names are the key values of the map. The
     * map values are lists of property values of the corresponding key name.
     * 
     * @return the request-property representing generic map.
     * @since Android 1.0
     */
    public Map<String, List<String>> getRequestProperties() {
        if (connected) {
            throw new IllegalStateException(Msg.getString("K0037")); //$NON-NLS-1$
        }
        return Collections.emptyMap();
    }

    /**
     * Adds the given property to the request header. Existing properties with
     * the same name will not be overwritten by this method.
     * 
     * @param field
     *            the request property field name to add.
     * @param newValue
     *            the value of the property which is to add.
     * @throws IllegalStateException
     *             if the connection has been already established.
     * @throws NullPointerException
     *             if the property name is {@code null}.
     * @since Android 1.0
     */
    public void addRequestProperty(String field, String newValue) {
        if (connected) {
            throw new IllegalStateException(Msg.getString("K0037")); //$NON-NLS-1$
        }
        if (field == null) {
            throw new NullPointerException(Msg.getString("KA007")); //$NON-NLS-1$
        }
    }

    /**
     * Gets the value of the header field specified by {@code key} or {@code
     * null} if there is no field with this name. The current implementation of
     * this method returns always {@code null}.
     * 
     * @param key
     *            the name of the header field.
     * @return the value of the header field.
     * @since Android 1.0
     */
    public String getHeaderField(String key) {
        return null;
    }

    /**
     * Gets the specified header value as a date in milliseconds since January
     * 1, 1970 GMT. Returns the {@code defaultValue} if no such header field
     * could be found.
     * 
     * @param field
     *            the header field name whose value is needed.
     * @param defaultValue
     *            the default value if no field has been found.
     * @return the value of the specified header field as a date in
     *         milliseconds.
     * @since Android 1.0
     */
    public long getHeaderFieldDate(String field, long defaultValue) {
        String date = getHeaderField(field);
        if (date == null) {
            return defaultValue;
        }
        return Util.parseDate(date);
    }

    /**
     * Gets the specified header value as a number. Returns the {@code
     * defaultValue} if no such header field could be found or the value could
     * not be parsed as an {@code Integer}.
     * 
     * @param field
     *            the header field name whose value is needed.
     * @param defaultValue
     *            the default value if no field has been found.
     * @return the value of the specified header field as a number.
     * @since Android 1.0
     */
    public int getHeaderFieldInt(String field, int defaultValue) {
        try {
            return Integer.parseInt(getHeaderField(field));
        } catch (NumberFormatException e) {
            return defaultValue;
        }
    }

    /**
     * Gets the name of the header field at the given position {@code posn} or
     * {@code null} if there are fewer than {@code posn} fields. The current
     * implementation of this method returns always {@code null}.
     * 
     * @param posn
     *            the position of the header field which has to be returned.
     * @return the header field name at the given position.
     * @since Android 1.0
     */
    public String getHeaderFieldKey(int posn) {
        return null;
    }

    /**
     * Gets the point of time since when the data must be modified to be
     * transmitted. Some protocols transmit data only if it has been modified
     * more recently than a particular time.
     * 
     * @return the time in milliseconds since January 1, 1970 GMT.
     * @see #ifModifiedSince
     * @since Android 1.0
     */
    public long getIfModifiedSince() {
        return ifModifiedSince;
    }

    /**
     * Gets an {@code InputStream} for reading data from the resource pointed by
     * this {@code URLConnection}. It throws an UnknownServiceException by
     * default. This method must be overridden by its subclasses.
     * 
     * @return the InputStream to read data from.
     * @throws IOException
     *             if no InputStream could be created.
     * @since Android 1.0
     */
    public InputStream getInputStream() throws IOException {
        throw new UnknownServiceException(Msg.getString("K004d")); //$NON-NLS-1$
    }

    /**
     * Gets the value of the response header field {@code last-modified} or
     * {@code 0} if this value is not set.
     * 
     * @return the value of the {@code last-modified} header field.
     * @since Android 1.0
     */
    public long getLastModified() {
        if (lastModified != -1) {
            return lastModified;
        }
        return lastModified = getHeaderFieldDate("Last-Modified", 0); //$NON-NLS-1$
    }

    /**
     * Gets an {@code OutputStream} for writing data to this {@code
     * URLConnection}. It throws an {@code UnknownServiceException} by default.
     * This method must be overridden by its subclasses.
     * 
     * @return the OutputStream to write data.
     * @throws IOException
     *             if no OutputStream could be created.
     * @since Android 1.0
     */
    public OutputStream getOutputStream() throws IOException {
        throw new UnknownServiceException(Msg.getString("K005f")); //$NON-NLS-1$
    }

    /**
     * Gets a {@code Permission} object representing all needed permissions to
     * open this connection. The returned permission object depends on the state
     * of the connection and will be {@code null} if no permissions are
     * necessary. By default, this method returns {@code AllPermission}.
     * Subclasses should overwrite this method to return an appropriate
     * permission object.
     * 
     * @return the permission object representing the needed permissions to open
     *         this connection.
     * @throws IOException
     *             if an I/O error occurs while creating the permission object.
     * @since Android 1.0
     */
    public java.security.Permission getPermission() throws IOException {
        return new java.security.AllPermission();
    }

    /**
     * Gets the value of the request header property specified by {code field}
     * or {@code null} if there is no field with this name. The current
     * implementation of this method returns always {@code null}.
     * 
     * @param field
     *            the name of the request header property.
     * @return the value of the property.
     * @throws IllegalStateException
     *             if the connection has been already established.
     * @since Android 1.0
     */
    public String getRequestProperty(String field) {
        if (connected) {
            throw new IllegalStateException(Msg.getString("K0037")); //$NON-NLS-1$
        }
        return null;
    }

    /**
     * Gets the URL represented by this {@code URLConnection}.
     * 
     * @return the URL of this connection.
     * @since Android 1.0
     */
    public URL getURL() {
        return url;
    }

    /**
     * Gets the value of the flag which specifies whether this {@code
     * URLConnection} allows to use caches.
     * 
     * @return {@code true} if using caches is allowed, {@code false} otherwise.
     * @since Android 1.0
     */
    public boolean getUseCaches() {
        return useCaches;
    }

    /**
     * Determines the MIME-type of the given resource {@code url} by resolving
     * the filename extension with the internal FileNameMap. Any fragment
     * identifier is removed before processing.
     * 
     * @param url
     *            the URL with the filename to get the MIME type.
     * @return the guessed content type or {@code null} if the type could not be
     *         determined.
     * @since Android 1.0
     */
    public static String guessContentTypeFromName(String url) {
        return getFileNameMap().getContentTypeFor(url);
    }

    /**
     * Determines the MIME-type of the resource represented by the input stream
     * {@code is} by reading its first few characters.
     * 
     * @param is
     *            the resource representing input stream to determine the
     *            content type.
     * @return the guessed content type or {@code null} if the type could not be
     *         determined.
     * @throws IOException
     *             if an I/O error occurs while reading from the input stream.
     * @since Android 1.0
     */
    public static String guessContentTypeFromStream(InputStream is)
            throws IOException {
        if (!is.markSupported()) {
            return null;
        }
        is.mark(4);
        char[] chars = new char[4];
        for (int i = 0; i < chars.length; i++) {
            chars[i] = (char) is.read();
        }
        is.reset();
        if ((chars[0] == 'P') && (chars[1] == 'K')) {
            return "application/zip"; //$NON-NLS-1$
        }
        if ((chars[0] == 'G') && (chars[1] == 'I')) {
            return "image/gif"; //$NON-NLS-1$
        }
        if (new String(chars).trim().startsWith("<")) { //$NON-NLS-1$
            return "text/html"; //$NON-NLS-1$
        }
        return null;
    }

    /**
     * Performs any necessary string parsing on the input string such as
     * converting non-alphanumeric character into underscore.
     * 
     * @param typeString
     *            the parsed string
     * @return the string to be parsed
     */
    private String parseTypeString(String typeString) {
        StringBuffer typeStringBuffer = new StringBuffer(typeString);
        for (int i = 0; i < typeStringBuffer.length(); i++) {
            // if non-alphanumeric, replace it with '_'
            char c = typeStringBuffer.charAt(i);
            if (!(Character.isLetter(c) || Character.isDigit(c) || c == '.')) {
                typeStringBuffer.setCharAt(i, '_');
            }
        }
        return typeStringBuffer.toString();
    }

    /**
     * Sets the flag indicating whether this connection allows user interaction
     * or not. This method can only be called prior to the connection
     * establishment.
     * 
     * @param newValue
     *            the value of the flag to be set.
     * @throws IllegalStateException
     *             if this method attempts to change the flag after the
     *             connection has been established.
     * @see #allowUserInteraction
     * @since Android 1.0
     */
    public void setAllowUserInteraction(boolean newValue) {
        if (connected) {
            throw new IllegalStateException(Msg.getString("K0037")); //$NON-NLS-1$
        }
        this.allowUserInteraction = newValue;
    }

    /**
     * Sets the internally used content handler factory. The content factory can
     * only be set if it is allowed by the security manager and only once during
     * the lifetime of the application.
     * 
     * @param contentFactory
     *            the content factory to be set.
     * @throws Error
     *             if the security manager does not allow to set the content
     *             factory or it has been already set earlier ago.
     * @since Android 1.0
     */
    public static synchronized void setContentHandlerFactory(
            ContentHandlerFactory contentFactory) {
        if (contentHandlerFactory != null) {
            throw new Error(Msg.getString("K004e")); //$NON-NLS-1$
        }
        SecurityManager sManager = System.getSecurityManager();
        if (sManager != null) {
            sManager.checkSetFactory();
        }
        contentHandlerFactory = contentFactory;
    }

    /**
     * Sets the default value for the flag indicating whether this connection
     * allows user interaction or not. Existing {@code URLConnection}s are
     * unaffected.
     * 
     * @param allows
     *            the default value of the flag to be used for new connections.
     * @see #defaultAllowUserInteraction
     * @see #allowUserInteraction
     * @since Android 1.0
     */
    public static void setDefaultAllowUserInteraction(boolean allows) {
        defaultAllowUserInteraction = allows;
    }

    /**
     * Sets the default value of the specified request header field. This value
     * will be used for the specific field of every newly created connection.
     * The current implementation of this method does nothing.
     * 
     * @param field
     *            the request header field to be set.
     * @param value
     *            the default value to be used.
     * @deprecated Use {@link #setRequestProperty} of an existing {@code
     *             URLConnection} instance.
     * @since Android 1.0
     */
    @Deprecated
    public static void setDefaultRequestProperty(String field, String value) {
    }

    /**
     * Sets the default value for the flag indicating whether this connection
     * allows to use caches. Existing {@code URLConnection}s are unaffected.
     * 
     * @param newValue
     *            the default value of the flag to be used for new connections.
     * @see #defaultUseCaches
     * @see #useCaches
     * @since Android 1.0
     */
    public void setDefaultUseCaches(boolean newValue) {
        // BEGIN android-removed
        // Setting the default doesn't concern the current connection.
        // if (connected) {
        //     throw new IllegalAccessError(Msg.getString("K0037")); //$NON-NLS-1$
        // }
        // END android-removed
        defaultUseCaches = newValue;
    }

    /**
     * Sets the flag indicating whether this {@code URLConnection} allows input.
     * It cannot be set after the connection is established.
     * 
     * @param newValue
     *            the new value for the flag to be set.
     * @throws IllegalAccessError
     *             if this method attempts to change the value after the
     *             connection has been already established.
     * @see #doInput
     * @since Android 1.0
     */
    public void setDoInput(boolean newValue) {
        if (connected) {
            throw new IllegalStateException(Msg.getString("K0037")); //$NON-NLS-1$
        }
        this.doInput = newValue;
    }

    /**
     * Sets the flag indicating whether this {@code URLConnection} allows
     * output. It cannot be set after the connection is established.
     * 
     * @param newValue
     *            the new value for the flag to be set.
     * @throws IllegalAccessError
     *             if this method attempts to change the value after the
     *             connection has been already established.
     * @see #doOutput
     * @since Android 1.0
     */
    public void setDoOutput(boolean newValue) {
        if (connected) {
            throw new IllegalStateException(Msg.getString("K0037")); //$NON-NLS-1$
        }
        this.doOutput = newValue;
    }

    /**
     * Sets the internal map which is used by all {@code URLConnection}
     * instances to determine the MIME-type according to a filename extension.
     * 
     * @param map
     *            the MIME table to be set.
     * @since Android 1.0
     */
    public static void setFileNameMap(FileNameMap map) {
        SecurityManager manager = System.getSecurityManager();
        if (manager != null) {
            manager.checkSetFactory();
        }
        fileNameMap = map;
    }

    /**
     * Sets the point of time since when the data must be modified to be
     * transmitted. Some protocols transmit data only if it has been modified
     * more recently than a particular time. The data will be transmitted
     * regardless of its timestamp if this option is set to {@code 0}.
     * 
     * @param newValue
     *            the time in milliseconds since January 1, 1970 GMT.
     * @throws IllegalStateException
     *             if this {@code URLConnection} has already been connected.
     * @see #ifModifiedSince
     * @since Android 1.0
     */
    public void setIfModifiedSince(long newValue) {
        if (connected) {
            throw new IllegalStateException(Msg.getString("K0037")); //$NON-NLS-1$
        }
        this.ifModifiedSince = newValue;
    }

    /**
     * Sets the value of the specified request header field. The value will only
     * be used by the current {@code URLConnection} instance. This method can
     * only be called before the connection is established.
     * 
     * @param field
     *            the request header field to be set.
     * @param newValue
     *            the new value of the specified property.
     * @throws IllegalStateException
     *             if the connection has been already established.
     * @throws NullPointerException
     *             if the parameter {@code field} is {@code null}.
     * @since Android 1.0
     */
    public void setRequestProperty(String field, String newValue) {
        if (connected) {
            throw new IllegalStateException(Msg.getString("K0037")); //$NON-NLS-1$
        }
        if (field == null) {
            throw new NullPointerException(Msg.getString("KA007")); //$NON-NLS-1$
        }
    }

    /**
     * Sets the flag indicating whether this connection allows to use caches or
     * not. This method can only be called prior to the connection
     * establishment.
     * 
     * @param newValue
     *            the value of the flag to be set.
     * @throws IllegalStateException
     *             if this method attempts to change the flag after the
     *             connection has been established.
     * @see #useCaches
     * @since Android 1.0
     */
    public void setUseCaches(boolean newValue) {
        if (connected) {
            throw new IllegalStateException(Msg.getString("K0037")); //$NON-NLS-1$
        }
        this.useCaches = newValue;
    }

    /**
     * Sets the timeout value in milliseconds for establishing the connection to
     * the resource pointed by this {@code URLConnection} instance. A {@code
     * SocketTimeoutException} is thrown if the connection could not be
     * established in this time. Default is {@code 0} which stands for an
     * infinite timeout.
     * 
     * @param timeout
     *            the connecting timeout in milliseconds.
     * @throws IllegalArgumentException
     *             if the parameter {@code timeout} is less than zero.
     * @since Android 1.0
     */
    public void setConnectTimeout(int timeout) {
        if (0 > timeout) {
            throw new IllegalArgumentException(Msg.getString("K0036")); //$NON-NLS-1$
        }
        this.connectTimeout = timeout;
    }

    /**
     * Gets the configured connecting timeout.
     * 
     * @return the connecting timeout value in milliseconds.
     * @since Android 1.0
     */
    public int getConnectTimeout() {
        return connectTimeout;
    }

    /**
     * Sets the timeout value in milliseconds for reading from the input stream
     * of an established connection to the resource. A {@code
     * SocketTimeoutException} is thrown if the connection could not be
     * established in this time. Default is {@code 0} which stands for an
     * infinite timeout.
     * 
     * @param timeout
     *            the reading timeout in milliseconds.
     * @throws IllegalArgumentException
     *             if the parameter {@code timeout} is less than zero.
     * @since Android 1.0
     */
    public void setReadTimeout(int timeout) {
        if (0 > timeout) {
            throw new IllegalArgumentException(Msg.getString("K0036")); //$NON-NLS-1$
        }
        this.readTimeout = timeout;
    }

    /**
     * Gets the configured timeout for reading from the input stream of an
     * established connection to the resource.
     * 
     * @return the reading timeout value in milliseconds.
     * @since Android 1.0
     */
    public int getReadTimeout() {
        return readTimeout;
    }

    /**
     * Returns the string representation containing the name of this class and
     * the URL.
     * 
     * @return the string representation of this {@code URLConnection} instance.
     * @since Android 1.0
     */
    @Override
    public String toString() {
        return getClass().getName() + ":" + url.toString(); //$NON-NLS-1$
    }

    static class DefaultContentHandler extends java.net.ContentHandler {

        /**
         * @param u
         *            the URL connection
         * 
         * @see java.net.ContentHandler#getContent(java.net.URLConnection)
         */
        @Override
        public Object getContent(URLConnection u) throws IOException {
            return u.getInputStream();
        }
    }
}