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