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