Merge "Return HTTP headers even if there is no HTTP response body."
diff --git a/luni/src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpURLConnectionImpl.java b/luni/src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpURLConnectionImpl.java
index 06eb28f..b31f11d 100644
--- a/luni/src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpURLConnectionImpl.java
+++ b/luni/src/main/java/org/apache/harmony/luni/internal/net/www/protocol/http/HttpURLConnectionImpl.java
@@ -210,7 +210,7 @@
         this.proxy = proxy;
     }
 
-    @Override public void connect() throws IOException {
+    @Override public final void connect() throws IOException {
         if (connected) {
             return;
         }
@@ -225,7 +225,7 @@
      * <p>Request parameters may not be changed after this method has been
      * called.
      */
-    public void makeConnection() throws IOException {
+    protected void makeConnection() throws IOException {
         connected = true;
 
         if (connection != null || responseBodyIn != null) {
@@ -358,14 +358,14 @@
     /**
      * Close the socket connection to the remote origin server or proxy.
      */
-    @Override public void disconnect() {
+    @Override public final void disconnect() {
         releaseSocket(false);
     }
 
     /**
      * Releases this connection so that it may be either reused or closed.
      */
-    protected synchronized void releaseSocket(boolean reuseSocket) {
+    protected final void releaseSocket(boolean reuseSocket) {
         // we cannot recycle sockets that have incomplete output.
         if (requestBodyOut != null && !requestBodyOut.closed) {
             reuseSocket = false;
@@ -406,7 +406,7 @@
      * Discard all state initialized from the HTTP response including response
      * code, message, headers and body.
      */
-    protected void discardIntermediateResponse() throws IOException {
+    protected final void discardIntermediateResponse() throws IOException {
         boolean oldIntermediateResponse = intermediateResponse;
         intermediateResponse = true;
         try {
@@ -441,7 +441,7 @@
      * @return InputStream the error input stream returned by the server.
      */
     @Override
-    public InputStream getErrorStream() {
+    public final InputStream getErrorStream() {
         if (connected && method != HEAD && responseCode >= HTTP_BAD_REQUEST) {
             return responseBodyIn;
         }
@@ -453,9 +453,9 @@
      * are fewer than {@code position} headers.
      */
     @Override
-    public String getHeaderField(int position) {
+    public final String getHeaderField(int position) {
         try {
-            getInputStream();
+            retrieveResponse();
         } catch (IOException ignored) {
         }
         return responseHeader != null ? responseHeader.getValue(position) : null;
@@ -476,9 +476,9 @@
      * @see #getHeaderFieldKey
      */
     @Override
-    public String getHeaderField(String key) {
+    public final String getHeaderField(String key) {
         try {
-            getInputStream();
+            retrieveResponse();
         } catch (IOException ignored) {
         }
         if (responseHeader == null) {
@@ -488,16 +488,16 @@
     }
 
     @Override
-    public String getHeaderFieldKey(int position) {
+    public final String getHeaderFieldKey(int position) {
         try {
-            getInputStream();
+            retrieveResponse();
         } catch (IOException ignored) {
         }
         return responseHeader != null ? responseHeader.getKey(position) : null;
     }
 
     @Override
-    public Map<String, List<String>> getHeaderFields() {
+    public final Map<String, List<String>> getHeaderFields() {
         try {
             retrieveResponse();
         } catch (IOException ignored) {
@@ -506,7 +506,7 @@
     }
 
     @Override
-    public Map<String, List<String>> getRequestProperties() {
+    public final Map<String, List<String>> getRequestProperties() {
         if (connected) {
             throw new IllegalStateException(
                     "Cannot access request header fields after connection is set");
@@ -515,7 +515,7 @@
     }
 
     @Override
-    public InputStream getInputStream() throws IOException {
+    public final InputStream getInputStream() throws IOException {
         if (!doInput) {
             throw new ProtocolException("This protocol does not support input");
         }
@@ -580,7 +580,7 @@
     }
 
     @Override
-    public OutputStream getOutputStream() throws IOException {
+    public final OutputStream getOutputStream() throws IOException {
         if (!doOutput) {
             throw new ProtocolException("Does not support output");
         }
@@ -648,13 +648,13 @@
     }
 
     @Override
-    public Permission getPermission() throws IOException {
+    public final Permission getPermission() throws IOException {
         String connectToAddress = getConnectToHost() + ":" + getConnectToPort();
         return new SocketPermission(connectToAddress, "connect, resolve");
     }
 
     @Override
-    public String getRequestProperty(String field) {
+    public final String getRequestProperty(String field) {
         if (field == null) {
             return null;
         }
@@ -732,7 +732,7 @@
     }
 
     @Override
-    public int getResponseCode() throws IOException {
+    public final int getResponseCode() throws IOException {
         retrieveResponse();
         return responseCode;
     }
@@ -940,7 +940,7 @@
      *             if already connected.
      */
     @Override
-    public void setIfModifiedSince(long newValue) {
+    public final void setIfModifiedSince(long newValue) {
         super.setIfModifiedSince(newValue);
         // convert from millisecond since epoch to date string
         SimpleDateFormat sdf = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
@@ -950,7 +950,7 @@
     }
 
     @Override
-    public void setRequestProperty(String field, String newValue) {
+    public final void setRequestProperty(String field, String newValue) {
         if (connected) {
             throw new IllegalStateException("Cannot set request property after connection is made");
         }
@@ -961,7 +961,7 @@
     }
 
     @Override
-    public void addRequestProperty(String field, String value) {
+    public final void addRequestProperty(String field, String value) {
         if (connected) {
             throw new IllegalStateException("Cannot set request property after connection is made");
         }
diff --git a/luni/src/test/java/libcore/java/net/URLConnectionTest.java b/luni/src/test/java/libcore/java/net/URLConnectionTest.java
index 016f9ba..6fabc74 100644
--- a/luni/src/test/java/libcore/java/net/URLConnectionTest.java
+++ b/luni/src/test/java/libcore/java/net/URLConnectionTest.java
@@ -29,6 +29,7 @@
 import java.net.HttpURLConnection;
 import java.net.InetAddress;
 import java.net.PasswordAuthentication;
+import java.net.ProtocolException;
 import java.net.Proxy;
 import java.net.ResponseCache;
 import java.net.SecureCacheResponse;
@@ -49,6 +50,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.zip.GZIPInputStream;
 import java.util.zip.GZIPOutputStream;
@@ -424,6 +426,67 @@
         server.shutdown(); // tearDown() isn't sufficient; this test starts multiple servers
     }
 
+    /**
+     * Test that we can interrogate the response when the cache is being
+     * populated. http://code.google.com/p/android/issues/detail?id=7787
+     */
+    public void testResponseCacheCallbackApis() throws Exception {
+        final String body = "ABCDE";
+        final AtomicInteger cacheCount = new AtomicInteger();
+
+        server.enqueue(new MockResponse()
+                .setStatus("HTTP/1.1 200 Fantastic")
+                .addHeader("fgh: ijk")
+                .setBody(body));
+        server.play();
+
+        ResponseCache.setDefault(new ResponseCache() {
+            @Override public CacheResponse get(URI uri, String requestMethod,
+                    Map<String, List<String>> requestHeaders) throws IOException {
+                return null;
+            }
+            @Override public CacheRequest put(URI uri, URLConnection conn) throws IOException {
+                HttpURLConnection httpConnection = (HttpURLConnection) conn;
+                assertEquals("HTTP/1.1 200 Fantastic", httpConnection.getHeaderField(null));
+                assertEquals(Arrays.asList("HTP/1.1 200 Fantastic"),
+                        httpConnection.getHeaderFields().get(null));
+                assertEquals(200, httpConnection.getResponseCode());
+                assertEquals("Fantastic", httpConnection.getResponseMessage());
+                assertEquals(body.length(), httpConnection.getContentLength());
+                assertEquals("ijk", httpConnection.getHeaderField("fgh"));
+                try {
+                    httpConnection.getInputStream(); // the RI doesn't forbid this, but it should
+                    fail();
+                } catch (IOException expected) {
+                }
+                cacheCount.incrementAndGet();
+                return null;
+            }
+        });
+
+        URL url = server.getUrl("/");
+        HttpURLConnection connection = (HttpURLConnection) url.openConnection();
+        assertEquals(body, readAscii(connection.getInputStream(), Integer.MAX_VALUE));
+        assertEquals(1, cacheCount.get());
+    }
+
+    public void testGetResponseCodeNoResponseBody() throws Exception {
+        server.enqueue(new MockResponse()
+                .addHeader("abc: def"));
+        server.play();
+
+        URL url = server.getUrl("/");
+        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
+        conn.setDoInput(false);
+        assertEquals("def", conn.getHeaderField("abc"));
+        assertEquals(200, conn.getResponseCode());
+        try {
+            conn.getInputStream();
+            fail();
+        } catch (ProtocolException expected) {
+        }
+    }
+
     public void testConnectViaHttps() throws IOException, InterruptedException {
         TestSSLContext testSSLContext = TestSSLContext.create();