Hi David,

David Daney wrote:
> David Daney wrote:
> 
>> PR libgcj/26487 shows problems with our existing header handling.
>>
>> I hacked up the attached patch, which needs more testing (please test
>> it!)
>>
>> The basic problem is that the headers were being held in a map which
>> scrambled them up if there were more than one header of the same name.
>>
>> My change was to just hold the headers in a list.  This makes some
>> searching operations less efficient, but seems the best way to keep
>> the headers from being combined.
>>
> New version of the patch.  Just cleaned up and javadocs added.
> 
> I guess I will commit it tomorrow if no one objects.
> 
> 2006-03-02  David Daney  <[EMAIL PROTECTED]>
> 
>         * gnu/java/net/protocol/http/HTTPURLConnection.java
>         (getRequestProperties): Rewrote.
>         (addRequestProperty): Rewrote.
>         (getHeaderFields): Rewrote.
>         (getHeaderField): Rewrote.
>         (getHeaderFieldKey): Rewrote.
>         (getHeaderField): Removed useless cast.
>         * gnu/java/net/protocol/http/Headers.java: Entire class rewritten.
>         * gnu/java/net/protocol/http/Request.java (dispatch): Use new
> Headers
>         interface.
>         (notifyHeaderHandlers): Use new Headers interface.
> 

Nice to see you have removed that now useless inner Header class. This was one
of the comments I wanted to make. I worked this day on mauve testcases for this
rewrite.

These exposed two small bugs in Headers.java:

> Index: gnu/java/net/protocol/http/Headers.java
> ===================================================================
> RCS file: 
> /sources/classpath/classpath/gnu/java/net/protocol/http/Headers.java,v
> retrieving revision 1.6
> diff -c -p -r1.6 Headers.java
> *** gnu/java/net/protocol/http/Headers.java   2 Mar 2006 00:26:57 -0000       
> 1.6
> --- gnu/java/net/protocol/http/Headers.java   2 Mar 2006 20:40:32 -0000

[...]

> ! 
> !   /**
> !    * Get a new Map containing all the headers.  The keys of the Map
> !    * are Strings (the header names).  The values of the Map are
> !    * unmodifiable Lists containing Strings (the header values).
> !    *
> !    * <p>
> !    * 
> !    * The returned map is modifiable.  Changing it will not effect this
> !    * collection of Headers in any way.
> !    *
> !    * @return a Map containing all the headers.
> !    */
> !   public Map getAsMap()
>     {
> !     LinkedHashMap m = new LinkedHashMap();
> !     for (Iterator it = headers.iterator(); it.hasNext(); )
>         {
> !         HeaderElement e = (HeaderElement)it.next();
> !         String k = e.name.toLowerCase();

No lowercase here. Otherwise keys with uppercase names won't be found.

String k = e.name;

> !         ArrayList l = (ArrayList)m.get(k);
> !         if (l == null)
> !           {
> !             l = new ArrayList(1);
> !             l.add(e.value);
> !             m.put(k, l);
> !           }
> !         else
> !           l.add(e.value);

The second value must be added before. The test show
that SUN always adds the last received value for a
key first.

l.add(0, e.value);

>         }
> !     for (Iterator it = m.entrySet().iterator(); it.hasNext(); )
>         {
> !         Map.Entry me = (Map.Entry)it.next();
> !         ArrayList l = (ArrayList)me.getValue();
> !         me.setValue(Collections.unmodifiableList(l));
>         }
> +     return m;
> +   }
> + 

With these two tests all testcases here pass.
Updated diff -u patch attached.

Wolfgang


Index: gnu/java/net/protocol/http/Headers.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/net/protocol/http/Headers.java,v
retrieving revision 1.6
diff -u -r1.6 Headers.java
--- gnu/java/net/protocol/http/Headers.java	2 Mar 2006 00:26:57 -0000	1.6
+++ gnu/java/net/protocol/http/Headers.java	2 Mar 2006 21:18:28 -0000
@@ -1,5 +1,5 @@
 /* Headers.java --
-   Copyright (C) 2004 Free Software Foundation, Inc.
+   Copyright (C) 2004, 2006 Free Software Foundation, Inc.
 
 This file is part of GNU Classpath.
 
@@ -44,124 +44,74 @@
 import java.io.InputStream;
 import java.text.DateFormat;
 import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Date;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
 import java.util.Map;
-import java.util.Set;
 
 /**
- * A collection of HTTP header names and associated values.
- * Retrieval of values is case insensitive. An iteration over the keys
+ * A collection of HTTP header names and associated values.  The
+ * values are [EMAIL PROTECTED] ArrayList ArrayLists} of Strings.  Retrieval of
+ * values is case insensitive. An iteration over the collection
  * returns the header names in the order they were received.
  *
  * @author Chris Burdess ([EMAIL PROTECTED])
+ * @author David Daney ([EMAIL PROTECTED])
  */
-public class Headers
-  extends LinkedHashMap
+class Headers
 {
-
+  /**
+   * A list of HeaderElements
+   *
+   */
+  private final ArrayList headers = new ArrayList();
+  
   static final DateFormat dateFormat = new HTTPDateFormat();
 
-  static class Header
+  static class HeaderElement
   {
+    String name;
+    String value;
 
-    final String name;
-
-    Header(String name)
+    HeaderElement(String name, String value)
     {
-      if (name == null || name.length() == 0)
-        {
-          throw new IllegalArgumentException(name);
-        }
       this.name = name;
+      this.value = value;
     }
-
-    public int hashCode()
-    {
-      return name.toLowerCase().hashCode();
-    }
-
-    public boolean equals(Object other)
-    {
-      if (other instanceof Header)
-        {
-          return ((Header) other).name.equalsIgnoreCase(name);
-        }
-      return false;
-    }
-
-    public String toString()
-    {
-      return name;
-    }
-    
-  }
-
-  static class HeaderEntry
-    implements Map.Entry
-  {
-
-    final Map.Entry entry;
-
-    HeaderEntry(Map.Entry entry)
-    {
-      this.entry = entry;
-    }
-
-    public Object getKey()
-    {
-      return ((Header) entry.getKey()).name;
-    }
-
-    public Object getValue()
-    {
-      return entry.getValue();
-    }
-
-    public Object setValue(Object value)
-    {
-      return entry.setValue(value);
-    }
-
-    public int hashCode()
-    {
-      return entry.hashCode();
-    }
-
-    public boolean equals(Object other)
-    {
-      return entry.equals(other);
-    }
-
-    public String toString()
-    {
-      return getKey().toString() + "=" + getValue();
-    }
-    
   }
 
   public Headers()
   {
   }
 
-  public boolean containsKey(Object key)
-  {
-    return super.containsKey(new Header((String) key));
-  }
-
-  public Object get(Object key)
+  /**
+   * Return an Iterator over this collection of headers.
+   * Iterator.getNext() returns objects of type [EMAIL PROTECTED] HeaderElement}.
+   *
+   * @return the Iterator.
+   */
+  Iterator iterator()
   {
-    return super.get(new Header((String) key));
+    return headers.iterator();
   }
-
+  
   /**
-   * Returns the value of the specified header as a string.
+   * Returns the value of the specified header as a string.  If
+   * multiple values are present, the last one is returned.
    */
   public String getValue(String header)
   {
-    return (String) super.get(new Header(header));
+    for (int i = headers.size() - 1; i >= 0; i--)
+      {
+        HeaderElement e = (HeaderElement)headers.get(i);
+        if (e.name.equalsIgnoreCase(header))
+          {
+            return e.value;
+          }
+      }
+    return null;
   }
 
   /**
@@ -227,51 +177,62 @@
       }
   }
 
-  public Object put(Object key, Object value)
-  {
-    return super.put(new Header((String) key), value);
-  }
-
-  public Object remove(Object key)
+  /**
+   * Add a header to this set of headers.  If there is an existing
+   * header with the same name, it is discarded.
+   *
+   * @param name the header name
+   * @param value the header value
+   *
+   * @see #addValue
+   */
+  public void put(String name, String value)
   {
-    return super.remove(new Header((String) key));
+    remove(name);
+    headers.add(headers.size(), new HeaderElement(name, value));
   }
 
-  public void putAll(Map t)
+  /**
+   * Add all headers from a set of headers to this set.  If any of the
+   * headers to be added have the same name as existing headers, the
+   * existing headers will be discarded.
+   *
+   * @param o the headers to be added
+   */
+  public void putAll(Headers o)
   {
-    for (Iterator i = t.keySet().iterator(); i.hasNext(); )
+    for (Iterator it = o.iterator(); it.hasNext(); )
       {
-        String key = (String) i.next();
-        String value = (String) t.get(key);
-        put(key, value);
+        HeaderElement e = (HeaderElement)it.next();
+        remove(e.name);
       }
-  }
-  
-  public Set keySet()
-  {
-    Set keys = super.keySet();
-    Set ret = new LinkedHashSet();
-    for (Iterator i = keys.iterator(); i.hasNext(); )
+    for (Iterator it = o.iterator(); it.hasNext(); )
       {
-        ret.add(((Header) i.next()).name);
+        HeaderElement e = (HeaderElement)it.next();
+        addValue(e.name, e.value);
       }
-    return ret;
   }
 
-  public Set entrySet()
+  /**
+   * Remove a header from this set of headers.  If there is more than
+   * one instance of a header of the given name, they are all removed.
+   *
+   * @param name the header name
+   */
+  public void remove(String name)
   {
-    Set entries = super.entrySet();
-    Set ret = new LinkedHashSet();
-    for (Iterator i = entries.iterator(); i.hasNext(); )
+    for (Iterator it = headers.iterator(); it.hasNext(); )
       {
-        Map.Entry entry = (Map.Entry) i.next();
-        ret.add(new HeaderEntry(entry));
+        HeaderElement e = (HeaderElement)it.next();
+        if (e.name.equalsIgnoreCase(name))
+          it.remove();
       }
-    return ret;
   }
 
   /**
-   * Parse the specified input stream, adding headers to this collection.
+   * Parse the specified InputStream, adding headers to this collection.
+   *
+   * @param in the InputStream.
    */
   public void parse(InputStream in)
     throws IOException
@@ -333,18 +294,91 @@
       }
   }
   
-  private void addValue(String name, String value)
+
+  /**
+   * Add a header to this set of headers.  If there is an existing
+   * header with the same name, it is not effected.
+   *
+   * @param name the header name
+   * @param value the header value
+   *
+   * @see #put
+   */
+  public void addValue(String name, String value)
+  {
+    headers.add(headers.size(), new HeaderElement(name, value));
+  }
+
+  /**
+   * Get a new Map containing all the headers.  The keys of the Map
+   * are Strings (the header names).  The values of the Map are
+   * unmodifiable Lists containing Strings (the header values).
+   *
+   * <p>
+   * 
+   * The returned map is modifiable.  Changing it will not effect this
+   * collection of Headers in any way.
+   *
+   * @return a Map containing all the headers.
+   */
+  public Map getAsMap()
   {
-    Header key = new Header(name);
-    String old = (String) super.get(key);
-    if (old == null)
+    LinkedHashMap m = new LinkedHashMap();
+    for (Iterator it = headers.iterator(); it.hasNext(); )
       {
-        super.put(key, value);
+        HeaderElement e = (HeaderElement)it.next();
+        String k = e.name;
+        ArrayList l = (ArrayList)m.get(k);
+        if (l == null)
+          {
+            l = new ArrayList(1);
+            l.add(e.value);
+            m.put(k, l);
+          }
+        else
+          l.add(0, e.value);
       }
-    else
+    for (Iterator it = m.entrySet().iterator(); it.hasNext(); )
       {
-        super.put(key, old + ", " + value);
+        Map.Entry me = (Map.Entry)it.next();
+        ArrayList l = (ArrayList)me.getValue();
+        me.setValue(Collections.unmodifiableList(l));
       }
+    return m;
+  }
+  
+  /**
+   * Get the name of the Nth header.
+   *
+   * @param i the header index.
+   *
+   * @return the header name.
+   *
+   * @see #getHeaderValue
+   */
+  public String getHeaderName(int i)
+  {
+    if (i >= headers.size() || i < 0)
+      return null;
+    
+    return ((HeaderElement)headers.get(i)).name;
+  }
+
+  /**
+   * Get the value of the Nth header.
+   *
+   * @param i the header index.
+   *
+   * @return the header value.
+   *
+   * @see #getHeaderName
+   */
+  public String getHeaderValue(int i)
+  {
+    if (i >= headers.size() || i < 0)
+      return null;
+    
+    return ((HeaderElement)headers.get(i)).value;
   }
   
 }
Index: gnu/java/net/protocol/http/Request.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/net/protocol/http/Request.java,v
retrieving revision 1.8
diff -u -r1.8 Request.java
--- gnu/java/net/protocol/http/Request.java	9 Feb 2006 09:23:38 -0000	1.8
+++ gnu/java/net/protocol/http/Request.java	2 Mar 2006 21:18:29 -0000
@@ -302,12 +302,10 @@
             String line = method + ' ' + requestUri + ' ' + version + CRLF;
             out.write(line.getBytes(US_ASCII));
             // Request headers
-            for (Iterator i = requestHeaders.keySet().iterator();
-                 i.hasNext(); )
+            for (Iterator i = requestHeaders.iterator(); i.hasNext(); )
               {
-                String name =(String) i.next();
-                String value =(String) requestHeaders.get(name);
-                line = name + HEADER_SEP + value + CRLF;
+                Headers.HeaderElement elt = (Headers.HeaderElement)i.next();
+                line = elt.name + HEADER_SEP + elt.value + CRLF;
                 out.write(line.getBytes(US_ASCII));
               }
             out.write(CRLF.getBytes(US_ASCII));
@@ -438,23 +436,17 @@
 
   void notifyHeaderHandlers(Headers headers)
   {
-    for (Iterator i = headers.entrySet().iterator(); i.hasNext(); )
+    for (Iterator i = headers.iterator(); i.hasNext(); )
       {
-        Map.Entry entry = (Map.Entry) i.next();
-        String name =(String) entry.getKey();
+        Headers.HeaderElement entry = (Headers.HeaderElement) i.next();
         // Handle Set-Cookie
-        if ("Set-Cookie".equalsIgnoreCase(name))
-          {
-            String value = (String) entry.getValue();
-            handleSetCookie(value);
-          }
+        if ("Set-Cookie".equalsIgnoreCase(entry.name))
+            handleSetCookie(entry.value);
+
         ResponseHeaderHandler handler =
-          (ResponseHeaderHandler) responseHeaderHandlers.get(name);
+          (ResponseHeaderHandler) responseHeaderHandlers.get(entry.name);
         if (handler != null)
-          {
-            String value = (String) entry.getValue();
-            handler.setValue(value);
-          }
+            handler.setValue(entry.value);
       }
   }
 
Index: gnu/java/net/protocol/http/HTTPURLConnection.java
===================================================================
RCS file: /cvsroot/classpath/classpath/gnu/java/net/protocol/http/HTTPURLConnection.java,v
retrieving revision 1.22
diff -u -r1.22 HTTPURLConnection.java
--- gnu/java/net/protocol/http/HTTPURLConnection.java	27 Feb 2006 23:37:20 -0000	1.22
+++ gnu/java/net/protocol/http/HTTPURLConnection.java	2 Mar 2006 21:18:29 -0000
@@ -422,20 +422,7 @@
     if (connected)
       throw new IllegalStateException("Already connected");
     
-    HashMap m = new HashMap(requestHeaders);
-    Iterator it = m.entrySet().iterator();
-    while (it.hasNext())
-      {
-        Map.Entry e = (Map.Entry)it.next();
-        String s = (String)e.getValue();
-        String sa[] = s.split(",");
-        ArrayList l = new ArrayList(sa.length);
-        for (int i = 0; i < sa.length; i++)
-          {
-            l.add(sa[i].trim());
-          }
-        e.setValue(Collections.unmodifiableList(l));
-      }
+    Map m = requestHeaders.getAsMap();
     return Collections.unmodifiableMap(m);
   }
 
@@ -449,16 +436,7 @@
   public void addRequestProperty(String key, String value)
   {
     super.addRequestProperty(key, value);
-    
-    String old = requestHeaders.getValue(key);
-    if (old == null)
-      {
-        requestHeaders.put(key, value);
-      }
-    else
-      {
-        requestHeaders.put(key, old + "," + value);
-      }
+    requestHeaders.addValue(key, value);
   }
 
   public OutputStream getOutputStream()
@@ -533,17 +511,9 @@
             return null;
           }
       }
-    Headers headers = response.getHeaders();
-    LinkedHashMap ret = new LinkedHashMap();
-    ret.put(null, Collections.singletonList(getStatusLine(response)));
-    for (Iterator i = headers.entrySet().iterator(); i.hasNext(); )
-      {
-        Map.Entry entry = (Map.Entry) i.next();
-        String key = (String) entry.getKey();
-        String value = (String) entry.getValue();
-        ret.put(key, Collections.singletonList(value));
-      }
-    return Collections.unmodifiableMap(ret);
+    Map m = response.getHeaders().getAsMap();
+    m.put(null, Collections.singletonList(getStatusLine(response)));
+    return Collections.unmodifiableMap(m);
   }
 
   String getStatusLine(Response response)
@@ -571,20 +541,7 @@
       {
         return getStatusLine(response);
       }
-    Iterator i = response.getHeaders().entrySet().iterator();
-    Map.Entry entry;
-    int count = 1;
-    do
-      {
-        if (!i.hasNext())
-          {
-            return null;
-          }
-        entry = (Map.Entry) i.next();
-        count++;
-      }
-    while (count <= index);
-    return (String) entry.getValue();
+    return response.getHeaders().getHeaderValue(index - 1);
   }
 
   public String getHeaderFieldKey(int index)
@@ -600,24 +557,8 @@
             return null;
           }
       }
-    if (index == 0)
-      {
-        return null;
-      }
-    Iterator i = response.getHeaders().entrySet().iterator();
-    Map.Entry entry;
-    int count = 1;
-    do
-      {
-        if (!i.hasNext())
-          {
-            return null;
-          }
-        entry = (Map.Entry) i.next();
-        count++;
-      }
-    while (count <= index);
-    return (String) entry.getKey();
+    // index of zero is the status line.
+    return response.getHeaders().getHeaderName(index - 1);
   }
 
   public String getHeaderField(String name)
@@ -633,7 +574,7 @@
             return null;
           }
       }
-    return (String) response.getHeader(name);
+    return response.getHeader(name);
   }
 
   public long getHeaderFieldDate(String name, long def)

Reply via email to