1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.http;
17
18 import io.netty.util.AsciiString;
19 import io.netty.util.internal.UnstableApi;
20
21 import java.util.AbstractMap.SimpleImmutableEntry;
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.Iterator;
25 import java.util.LinkedHashSet;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.NoSuchElementException;
29 import java.util.Set;
30
31 import static io.netty.handler.codec.CharSequenceValueConverter.INSTANCE;
32 import static io.netty.handler.codec.http.DefaultHttpHeaders.HttpNameValidator;
33 import static io.netty.util.AsciiString.contentEquals;
34 import static io.netty.util.AsciiString.contentEqualsIgnoreCase;
35
36
37
38
39
40
41
42
43
44
45 @UnstableApi
46 public final class ReadOnlyHttpHeaders extends HttpHeaders {
47 private final CharSequence[] nameValuePairs;
48
49
50
51
52
53
54
55
56 public ReadOnlyHttpHeaders(boolean validateHeaders, CharSequence... nameValuePairs) {
57 if ((nameValuePairs.length & 1) != 0) {
58 throw newInvalidArraySizeException();
59 }
60 if (validateHeaders) {
61 validateHeaders(nameValuePairs);
62 }
63 this.nameValuePairs = nameValuePairs;
64 }
65
66 private static IllegalArgumentException newInvalidArraySizeException() {
67 return new IllegalArgumentException("nameValuePairs must be arrays of [name, value] pairs");
68 }
69
70 private static void validateHeaders(CharSequence... keyValuePairs) {
71 for (int i = 0; i < keyValuePairs.length; i += 2) {
72 HttpNameValidator.validateName(keyValuePairs[i]);
73 }
74 }
75
76 private CharSequence get0(CharSequence name) {
77 final int nameHash = AsciiString.hashCode(name);
78 for (int i = 0; i < nameValuePairs.length; i += 2) {
79 CharSequence roName = nameValuePairs[i];
80 if (AsciiString.hashCode(roName) == nameHash && contentEqualsIgnoreCase(roName, name)) {
81
82 return nameValuePairs[i + 1];
83 }
84 }
85 return null;
86 }
87
88 @Override
89 public String get(String name) {
90 CharSequence value = get0(name);
91 return value == null ? null : value.toString();
92 }
93
94 @Override
95 public Integer getInt(CharSequence name) {
96 CharSequence value = get0(name);
97 return value == null ? null : INSTANCE.convertToInt(value);
98 }
99
100 @Override
101 public int getInt(CharSequence name, int defaultValue) {
102 CharSequence value = get0(name);
103 return value == null ? defaultValue : INSTANCE.convertToInt(value);
104 }
105
106 @Override
107 public Short getShort(CharSequence name) {
108 CharSequence value = get0(name);
109 return value == null ? null : INSTANCE.convertToShort(value);
110 }
111
112 @Override
113 public short getShort(CharSequence name, short defaultValue) {
114 CharSequence value = get0(name);
115 return value == null ? defaultValue : INSTANCE.convertToShort(value);
116 }
117
118 @Override
119 public Long getTimeMillis(CharSequence name) {
120 CharSequence value = get0(name);
121 return value == null ? null : INSTANCE.convertToTimeMillis(value);
122 }
123
124 @Override
125 public long getTimeMillis(CharSequence name, long defaultValue) {
126 CharSequence value = get0(name);
127 return value == null ? defaultValue : INSTANCE.convertToTimeMillis(value);
128 }
129
130 @Override
131 public List<String> getAll(String name) {
132 if (isEmpty()) {
133 return Collections.emptyList();
134 }
135 final int nameHash = AsciiString.hashCode(name);
136 List<String> values = new ArrayList<String>(4);
137 for (int i = 0; i < nameValuePairs.length; i += 2) {
138 CharSequence roName = nameValuePairs[i];
139 if (AsciiString.hashCode(roName) == nameHash && contentEqualsIgnoreCase(roName, name)) {
140 values.add(nameValuePairs[i + 1].toString());
141 }
142 }
143 return values;
144 }
145
146 @Override
147 public List<Map.Entry<String, String>> entries() {
148 if (isEmpty()) {
149 return Collections.emptyList();
150 }
151 List<Map.Entry<String, String>> entries = new ArrayList<Map.Entry<String, String>>(size());
152 for (int i = 0; i < nameValuePairs.length; i += 2) {
153 entries.add(new SimpleImmutableEntry<String, String>(nameValuePairs[i].toString(),
154 nameValuePairs[i + 1].toString()));
155 }
156 return entries;
157 }
158
159 @Override
160 public boolean contains(String name) {
161 return get0(name) != null;
162 }
163
164 @Override
165 public boolean contains(String name, String value, boolean ignoreCase) {
166 return containsValue(name, value, ignoreCase);
167 }
168
169 @Override
170 public boolean containsValue(CharSequence name, CharSequence value, boolean ignoreCase) {
171 if (ignoreCase) {
172 for (int i = 0; i < nameValuePairs.length; i += 2) {
173 if (contentEqualsIgnoreCase(nameValuePairs[i], name) &&
174 contentEqualsIgnoreCase(nameValuePairs[i + 1], value)) {
175 return true;
176 }
177 }
178 } else {
179 for (int i = 0; i < nameValuePairs.length; i += 2) {
180 if (contentEqualsIgnoreCase(nameValuePairs[i], name) &&
181 contentEquals(nameValuePairs[i + 1], value)) {
182 return true;
183 }
184 }
185 }
186 return false;
187 }
188
189 @Override
190 public Iterator<String> valueStringIterator(CharSequence name) {
191 return new ReadOnlyStringValueIterator(name);
192 }
193
194 @Override
195 public Iterator<CharSequence> valueCharSequenceIterator(CharSequence name) {
196 return new ReadOnlyValueIterator(name);
197 }
198
199 @Override
200 public Iterator<Map.Entry<String, String>> iterator() {
201 return new ReadOnlyStringIterator();
202 }
203
204 @Override
205 public Iterator<Map.Entry<CharSequence, CharSequence>> iteratorCharSequence() {
206 return new ReadOnlyIterator();
207 }
208
209 @Override
210 public boolean isEmpty() {
211 return nameValuePairs.length == 0;
212 }
213
214 @Override
215 public int size() {
216 return nameValuePairs.length >>> 1;
217 }
218
219 @Override
220 public Set<String> names() {
221 if (isEmpty()) {
222 return Collections.emptySet();
223 }
224 Set<String> names = new LinkedHashSet<String>(size());
225 for (int i = 0; i < nameValuePairs.length; i += 2) {
226 names.add(nameValuePairs[i].toString());
227 }
228 return names;
229 }
230
231 @Override
232 public HttpHeaders add(String name, Object value) {
233 throw new UnsupportedOperationException("read only");
234 }
235
236 @Override
237 public HttpHeaders add(String name, Iterable<?> values) {
238 throw new UnsupportedOperationException("read only");
239 }
240
241 @Override
242 public HttpHeaders addInt(CharSequence name, int value) {
243 throw new UnsupportedOperationException("read only");
244 }
245
246 @Override
247 public HttpHeaders addShort(CharSequence name, short value) {
248 throw new UnsupportedOperationException("read only");
249 }
250
251 @Override
252 public HttpHeaders set(String name, Object value) {
253 throw new UnsupportedOperationException("read only");
254 }
255
256 @Override
257 public HttpHeaders set(String name, Iterable<?> values) {
258 throw new UnsupportedOperationException("read only");
259 }
260
261 @Override
262 public HttpHeaders setInt(CharSequence name, int value) {
263 throw new UnsupportedOperationException("read only");
264 }
265
266 @Override
267 public HttpHeaders setShort(CharSequence name, short value) {
268 throw new UnsupportedOperationException("read only");
269 }
270
271 @Override
272 public HttpHeaders remove(String name) {
273 throw new UnsupportedOperationException("read only");
274 }
275
276 @Override
277 public HttpHeaders clear() {
278 throw new UnsupportedOperationException("read only");
279 }
280
281 private final class ReadOnlyIterator implements Map.Entry<CharSequence, CharSequence>,
282 Iterator<Map.Entry<CharSequence, CharSequence>> {
283 private CharSequence key;
284 private CharSequence value;
285 private int nextNameIndex;
286
287 @Override
288 public boolean hasNext() {
289 return nextNameIndex != nameValuePairs.length;
290 }
291
292 @Override
293 public Map.Entry<CharSequence, CharSequence> next() {
294 if (!hasNext()) {
295 throw new NoSuchElementException();
296 }
297 key = nameValuePairs[nextNameIndex];
298 value = nameValuePairs[nextNameIndex + 1];
299 nextNameIndex += 2;
300 return this;
301 }
302
303 @Override
304 public void remove() {
305 throw new UnsupportedOperationException("read only");
306 }
307
308 @Override
309 public CharSequence getKey() {
310 return key;
311 }
312
313 @Override
314 public CharSequence getValue() {
315 return value;
316 }
317
318 @Override
319 public CharSequence setValue(CharSequence value) {
320 throw new UnsupportedOperationException("read only");
321 }
322
323 @Override
324 public String toString() {
325 return key.toString() + '=' + value.toString();
326 }
327 }
328
329 private final class ReadOnlyStringIterator implements Map.Entry<String, String>,
330 Iterator<Map.Entry<String, String>> {
331 private String key;
332 private String value;
333 private int nextNameIndex;
334
335 @Override
336 public boolean hasNext() {
337 return nextNameIndex != nameValuePairs.length;
338 }
339
340 @Override
341 public Map.Entry<String, String> next() {
342 if (!hasNext()) {
343 throw new NoSuchElementException();
344 }
345 key = nameValuePairs[nextNameIndex].toString();
346 value = nameValuePairs[nextNameIndex + 1].toString();
347 nextNameIndex += 2;
348 return this;
349 }
350
351 @Override
352 public void remove() {
353 throw new UnsupportedOperationException("read only");
354 }
355
356 @Override
357 public String getKey() {
358 return key;
359 }
360
361 @Override
362 public String getValue() {
363 return value;
364 }
365
366 @Override
367 public String setValue(String value) {
368 throw new UnsupportedOperationException("read only");
369 }
370
371 @Override
372 public String toString() {
373 return key + '=' + value;
374 }
375 }
376
377 private final class ReadOnlyStringValueIterator implements Iterator<String> {
378 private final CharSequence name;
379 private final int nameHash;
380 private int nextNameIndex;
381
382 ReadOnlyStringValueIterator(CharSequence name) {
383 this.name = name;
384 nameHash = AsciiString.hashCode(name);
385 nextNameIndex = findNextValue();
386 }
387
388 @Override
389 public boolean hasNext() {
390 return nextNameIndex != -1;
391 }
392
393 @Override
394 public String next() {
395 if (!hasNext()) {
396 throw new NoSuchElementException();
397 }
398 String value = nameValuePairs[nextNameIndex + 1].toString();
399 nextNameIndex = findNextValue();
400 return value;
401 }
402
403 @Override
404 public void remove() {
405 throw new UnsupportedOperationException("read only");
406 }
407
408 private int findNextValue() {
409 for (int i = nextNameIndex; i < nameValuePairs.length; i += 2) {
410 final CharSequence roName = nameValuePairs[i];
411 if (nameHash == AsciiString.hashCode(roName) && contentEqualsIgnoreCase(name, roName)) {
412 return i;
413 }
414 }
415 return -1;
416 }
417 }
418
419 private final class ReadOnlyValueIterator implements Iterator<CharSequence> {
420 private final CharSequence name;
421 private final int nameHash;
422 private int nextNameIndex;
423
424 ReadOnlyValueIterator(CharSequence name) {
425 this.name = name;
426 nameHash = AsciiString.hashCode(name);
427 nextNameIndex = findNextValue();
428 }
429
430 @Override
431 public boolean hasNext() {
432 return nextNameIndex != -1;
433 }
434
435 @Override
436 public CharSequence next() {
437 if (!hasNext()) {
438 throw new NoSuchElementException();
439 }
440 CharSequence value = nameValuePairs[nextNameIndex + 1];
441 nextNameIndex = findNextValue();
442 return value;
443 }
444
445 @Override
446 public void remove() {
447 throw new UnsupportedOperationException("read only");
448 }
449
450 private int findNextValue() {
451 for (int i = nextNameIndex; i < nameValuePairs.length; i += 2) {
452 final CharSequence roName = nameValuePairs[i];
453 if (nameHash == AsciiString.hashCode(roName) && contentEqualsIgnoreCase(name, roName)) {
454 return i;
455 }
456 }
457 return -1;
458 }
459 }
460 }