View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2012 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.spdy.api;
20  
21  import java.util.Arrays;
22  import java.util.Collections;
23  import java.util.Iterator;
24  import java.util.LinkedHashMap;
25  import java.util.LinkedHashSet;
26  import java.util.Map;
27  import java.util.Set;
28  
29  /**
30   * <p>A container for name/value pairs, known as headers.</p>
31   * <p>A {@link Header} is composed of a case-insensitive name string and
32   * of a case-sensitive set of value strings.</p>
33   * <p>The implementation of this class is not thread safe.</p>
34   */
35  public class Headers implements Iterable<Headers.Header>
36  {
37      private final Map<String, Header> headers;
38  
39      /**
40       * <p>Creates an empty modifiable {@link Headers} instance.</p>
41       * @see #Headers(Headers, boolean)
42       */
43      public Headers()
44      {
45          headers = new LinkedHashMap<>();
46      }
47  
48      /**
49       * <p>Creates a {@link Headers} instance by copying the headers from the given
50       * {@link Headers} and making it (im)mutable depending on the given {@code immutable} parameter</p>
51       *
52       * @param original the {@link Headers} to copy headers from
53       * @param immutable whether this instance is immutable
54       */
55      public Headers(Headers original, boolean immutable)
56      {
57          Map<String, Header> copy = new LinkedHashMap<>();
58          copy.putAll(original.headers);
59          headers = immutable ? Collections.unmodifiableMap(copy) : copy;
60      }
61  
62      @Override
63      public boolean equals(Object obj)
64      {
65          if (this == obj)
66              return true;
67          if (obj == null || getClass() != obj.getClass())
68              return false;
69          Headers that = (Headers)obj;
70          return headers.equals(that.headers);
71      }
72  
73      @Override
74      public int hashCode()
75      {
76          return headers.hashCode();
77      }
78  
79      /**
80       * @return a set of header names
81       */
82      public Set<String> names()
83      {
84          Set<String> result = new LinkedHashSet<>();
85          for (Header header : headers.values())
86              result.add(header.name);
87          return result;
88      }
89  
90      /**
91       * @param name the header name
92       * @return the {@link Header} with the given name, or null if no such header exists
93       */
94      public Header get(String name)
95      {
96          return headers.get(name.trim().toLowerCase());
97      }
98  
99      /**
100      * <p>Inserts or replaces the given name/value pair as a single-valued {@link Header}.</p>
101      *
102      * @param name the header name
103      * @param value the header value
104      */
105     public void put(String name, String value)
106     {
107         name = name.trim();
108         Header header = new Header(name, value.trim());
109         headers.put(name.toLowerCase(), header);
110     }
111 
112     /**
113      * <p>Inserts or replaces the given {@link Header}, mapped to the {@link Header#name() header's name}</p>
114      *
115      * @param header the header to add
116      */
117     public void put(Header header)
118     {
119         if (header != null)
120             headers.put(header.name().toLowerCase(), header);
121     }
122 
123     /**
124      * <p>Adds the given value to a header with the given name, creating a {@link Header} is none exists
125      * for the given name.</p>
126      *
127      * @param name the header name
128      * @param value the header value to add
129      */
130     public void add(String name, String value)
131     {
132         name = name.trim();
133         Header header = headers.get(name.toLowerCase());
134         if (header == null)
135         {
136             header = new Header(name, value.trim());
137             headers.put(name.toLowerCase(), header);
138         }
139         else
140         {
141             header = new Header(header.name(), header.value() + "," + value.trim());
142             headers.put(name.toLowerCase(), header);
143         }
144     }
145 
146     /**
147      * <p>Removes the {@link Header} with the given name</p>
148      *
149      * @param name the name of the header to remove
150      * @return the removed header, or null if no such header existed
151      */
152     public Header remove(String name)
153     {
154         name = name.trim();
155         return headers.remove(name.toLowerCase());
156     }
157 
158     /**
159      * <p>Empties this {@link Headers} instance from all headers</p>
160      * @see #isEmpty()
161      */
162     public void clear()
163     {
164         headers.clear();
165     }
166 
167     /**
168      * @return whether this {@link Headers} instance is empty
169      */
170     public boolean isEmpty()
171     {
172         return headers.isEmpty();
173     }
174 
175     /**
176      * @return the number of headers
177      */
178     public int size()
179     {
180         return headers.size();
181     }
182 
183     /**
184      * @return an iterator over the {@link Header} present in this instance
185      */
186     @Override
187     public Iterator<Header> iterator()
188     {
189         return headers.values().iterator();
190     }
191 
192     @Override
193     public String toString()
194     {
195         return headers.toString();
196     }
197 
198     /**
199      * <p>A named list of string values.</p>
200      * <p>The name is case-sensitive and there must be at least one value.</p>
201      */
202     public static class Header
203     {
204         private final String name;
205         private final String[] values;
206 
207         private Header(String name, String value, String... values)
208         {
209             this.name = name;
210             this.values = new String[values.length + 1];
211             this.values[0] = value;
212             if (values.length > 0)
213                 System.arraycopy(values, 0, this.values, 1, values.length);
214         }
215 
216         @Override
217         public boolean equals(Object obj)
218         {
219             if (this == obj)
220                 return true;
221             if (obj == null || getClass() != obj.getClass())
222                 return false;
223             Header that = (Header)obj;
224             // Header names must be lowercase, thus we lowercase them before transmission, but keep them as is
225             // internally. That's why we've to compare them case insensitive.
226             return name.equalsIgnoreCase(that.name) && Arrays.equals(values, that.values);
227         }
228 
229         @Override
230         public int hashCode()
231         {
232             int result = name.toLowerCase().hashCode();
233             result = 31 * result + Arrays.hashCode(values);
234             return result;
235         }
236 
237         /**
238          * @return the header's name
239          */
240         public String name()
241         {
242             return name;
243         }
244 
245         /**
246          * @return the first header's value
247          */
248         public String value()
249         {
250             return values[0];
251         }
252 
253         /**
254          * <p>Attempts to convert the result of {@link #value()} to an integer,
255          * returning it if the conversion is successful; returns null if the
256          * result of {@link #value()} is null.</p>
257          *
258          * @return the result of {@link #value()} converted to an integer, or null
259          * @throws NumberFormatException if the conversion fails
260          */
261         public Integer valueAsInt()
262         {
263             final String value = value();
264             return value == null ? null : Integer.valueOf(value);
265         }
266 
267         /**
268          * @return the header's values
269          */
270         public String[] values()
271         {
272             return values;
273         }
274 
275         /**
276          * @return the values as a comma separated list
277          */
278         public String valuesAsString()
279         {
280             StringBuilder result = new StringBuilder();
281             for (int i = 0; i < values.length; ++i)
282             {
283                 if (i > 0)
284                     result.append(", ");
285                 result.append(values[i]);
286             }
287             return result.toString();
288         }
289 
290         /**
291          * @return whether the header has multiple values
292          */
293         public boolean hasMultipleValues()
294         {
295             return values.length > 1;
296         }
297 
298         @Override
299         public String toString()
300         {
301             return Arrays.toString(values);
302         }
303     }
304 }