View Javadoc

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