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