View Javadoc

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