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 }