View Javadoc

1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.handler.codec.http;
17  
18  import io.netty.buffer.ByteBuf;
19  
20  import java.util.Arrays;
21  import java.util.Calendar;
22  import java.util.Date;
23  import java.util.Iterator;
24  import java.util.LinkedHashSet;
25  import java.util.LinkedList;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Map.Entry;
29  import java.util.NoSuchElementException;
30  import java.util.Set;
31  
32  import static io.netty.util.internal.ObjectUtil.checkNotNull;
33  
34  public class DefaultHttpHeaders extends HttpHeaders {
35  
36      private static final int BUCKET_SIZE = 17;
37  
38      private static int index(int hash) {
39          return hash % BUCKET_SIZE;
40      }
41  
42      private final HeaderEntry[] entries = new HeaderEntry[BUCKET_SIZE];
43      private final HeaderEntry head = new HeaderEntry();
44      protected final boolean validate;
45  
46      public DefaultHttpHeaders() {
47          this(true);
48      }
49  
50      public DefaultHttpHeaders(boolean validate) {
51          this.validate = validate;
52          head.before = head.after = head;
53      }
54  
55      void validateHeaderName0(CharSequence headerName) {
56          validateHeaderName(headerName);
57      }
58  
59      @Override
60      public HttpHeaders add(HttpHeaders headers) {
61          if (headers instanceof DefaultHttpHeaders) {
62              if (headers == this) {
63                  throw new IllegalArgumentException("can't add to itself.");
64              }
65              DefaultHttpHeaders defaultHttpHeaders = (DefaultHttpHeaders) headers;
66              HeaderEntry e = defaultHttpHeaders.head.after;
67              while (e != defaultHttpHeaders.head) {
68                  add(e.key, e.value);
69                  e = e.after;
70              }
71              return this;
72          } else {
73              return super.add(headers);
74          }
75      }
76  
77      @Override
78      public HttpHeaders set(HttpHeaders headers) {
79          if (headers instanceof DefaultHttpHeaders) {
80              if (headers != this) {
81                  clear();
82                  DefaultHttpHeaders defaultHttpHeaders = (DefaultHttpHeaders) headers;
83                  HeaderEntry e = defaultHttpHeaders.head.after;
84                  while (e != defaultHttpHeaders.head) {
85                      add(e.key, e.value);
86                      e = e.after;
87                  }
88              }
89              return this;
90          } else {
91              return super.set(headers);
92          }
93      }
94  
95      @Override
96      public HttpHeaders add(final String name, final Object value) {
97          return add((CharSequence) name, value);
98      }
99  
100     @Override
101     public HttpHeaders add(final CharSequence name, final Object value) {
102         CharSequence strVal;
103         if (validate) {
104             validateHeaderName0(name);
105             strVal = toCharSequence(value);
106             validateHeaderValue(strVal);
107         } else {
108             strVal = toCharSequence(value);
109         }
110         int h = hash(name);
111         int i = index(h);
112         add0(h, i, name, strVal);
113         return this;
114     }
115 
116     @Override
117     public HttpHeaders add(String name, Iterable<?> values) {
118         return add((CharSequence) name, values);
119     }
120 
121     @Override
122     public HttpHeaders add(CharSequence name, Iterable<?> values) {
123         if (validate) {
124             validateHeaderName0(name);
125         }
126         int h = hash(name);
127         int i = index(h);
128         for (Object v: values) {
129             CharSequence vstr = toCharSequence(v);
130             if (validate) {
131                 validateHeaderValue(vstr);
132             }
133             add0(h, i, name, vstr);
134         }
135         return this;
136     }
137 
138     private void add0(int h, int i, final CharSequence name, final CharSequence value) {
139         // Update the hash table.
140         HeaderEntry e = entries[i];
141         HeaderEntry newEntry;
142         entries[i] = newEntry = new HeaderEntry(h, name, value);
143         newEntry.next = e;
144 
145         // Update the linked list.
146         newEntry.addBefore(head);
147     }
148 
149     @Override
150     public HttpHeaders remove(final String name) {
151         return remove((CharSequence) name);
152     }
153 
154     @Override
155     public HttpHeaders remove(final CharSequence name) {
156         if (name == null) {
157             throw new NullPointerException("name");
158         }
159         int h = hash(name);
160         int i = index(h);
161         remove0(h, i, name);
162         return this;
163     }
164 
165     private void remove0(int h, int i, CharSequence name) {
166         HeaderEntry e = entries[i];
167         if (e == null) {
168             return;
169         }
170 
171         for (;;) {
172             if (e.hash == h && equalsIgnoreCase(name, e.key)) {
173                 e.remove();
174                 HeaderEntry next = e.next;
175                 if (next != null) {
176                     entries[i] = next;
177                     e = next;
178                 } else {
179                     entries[i] = null;
180                     return;
181                 }
182             } else {
183                 break;
184             }
185         }
186 
187         for (;;) {
188             HeaderEntry next = e.next;
189             if (next == null) {
190                 break;
191             }
192             if (next.hash == h && equalsIgnoreCase(name, next.key)) {
193                 e.next = next.next;
194                 next.remove();
195             } else {
196                 e = next;
197             }
198         }
199     }
200 
201     @Override
202     public HttpHeaders set(final String name, final Object value) {
203         return set((CharSequence) name, value);
204     }
205 
206     @Override
207     public HttpHeaders set(final CharSequence name, final Object value) {
208         CharSequence strVal;
209         if (validate) {
210             validateHeaderName0(name);
211             strVal = toCharSequence(value);
212             validateHeaderValue(strVal);
213         } else {
214             strVal = toCharSequence(value);
215         }
216         int h = hash(name);
217         int i = index(h);
218         remove0(h, i, name);
219         add0(h, i, name, strVal);
220         return this;
221     }
222 
223     @Override
224     public HttpHeaders set(final String name, final Iterable<?> values) {
225         return set((CharSequence) name, values);
226     }
227 
228     @Override
229     public HttpHeaders set(final CharSequence name, final Iterable<?> values) {
230         if (values == null) {
231             throw new NullPointerException("values");
232         }
233         if (validate) {
234             validateHeaderName0(name);
235         }
236 
237         int h = hash(name);
238         int i = index(h);
239 
240         remove0(h, i, name);
241         for (Object v: values) {
242             if (v == null) {
243                 break;
244             }
245             CharSequence strVal = toCharSequence(v);
246             if (validate) {
247                 validateHeaderValue(strVal);
248             }
249             add0(h, i, name, strVal);
250         }
251 
252         return this;
253     }
254 
255     @Override
256     public HttpHeaders clear() {
257         Arrays.fill(entries, null);
258         head.before = head.after = head;
259         return this;
260     }
261 
262     @Override
263     public String get(final String name) {
264         return get((CharSequence) name);
265     }
266 
267     @Override
268     public String get(final CharSequence name) {
269         if (name == null) {
270             throw new NullPointerException("name");
271         }
272 
273         int h = hash(name);
274         int i = index(h);
275         HeaderEntry e = entries[i];
276         CharSequence value = null;
277         // loop until the first header was found
278         while (e != null) {
279             if (e.hash == h && equalsIgnoreCase(name, e.key)) {
280                 value = e.value;
281             }
282 
283             e = e.next;
284         }
285         if (value == null) {
286             return null;
287         }
288         return value.toString();
289     }
290 
291     @Override
292     public List<String> getAll(final String name) {
293         return getAll((CharSequence) name);
294     }
295 
296     @Override
297     public List<String> getAll(final CharSequence name) {
298         if (name == null) {
299             throw new NullPointerException("name");
300         }
301 
302         LinkedList<String> values = new LinkedList<String>();
303 
304         int h = hash(name);
305         int i = index(h);
306         HeaderEntry e = entries[i];
307         while (e != null) {
308             if (e.hash == h && equalsIgnoreCase(name, e.key)) {
309                 values.addFirst(e.getValue());
310             }
311             e = e.next;
312         }
313         return values;
314     }
315 
316     @Override
317     public List<Map.Entry<String, String>> entries() {
318         List<Map.Entry<String, String>> all =
319             new LinkedList<Map.Entry<String, String>>();
320 
321         HeaderEntry e = head.after;
322         while (e != head) {
323             all.add(e);
324             e = e.after;
325         }
326         return all;
327     }
328 
329     @Override
330     public Iterator<Map.Entry<String, String>> iterator() {
331         return new HeaderIterator();
332     }
333 
334     @Override
335     public boolean contains(String name) {
336         return get(name) != null;
337     }
338 
339     @Override
340     public boolean contains(CharSequence name) {
341         return get(name) != null;
342     }
343 
344     @Override
345     public boolean isEmpty() {
346         return head == head.after;
347     }
348 
349     @Override
350     public boolean contains(String name, String value, boolean ignoreCaseValue) {
351         return contains((CharSequence) name, (CharSequence) value, ignoreCaseValue);
352     }
353 
354     @Override
355     public boolean contains(CharSequence name, CharSequence value, boolean ignoreCaseValue) {
356         if (name == null) {
357             throw new NullPointerException("name");
358         }
359 
360         int h = hash(name);
361         int i = index(h);
362         HeaderEntry e = entries[i];
363         while (e != null) {
364             if (e.hash == h && equalsIgnoreCase(name, e.key)) {
365                 if (ignoreCaseValue) {
366                     if (equalsIgnoreCase(e.value, value)) {
367                         return true;
368                     }
369                 } else {
370                     if (e.value.equals(value)) {
371                         return true;
372                     }
373                 }
374             }
375             e = e.next;
376         }
377         return false;
378     }
379 
380     @Override
381     public Set<String> names() {
382         Set<String> names = new LinkedHashSet<String>();
383         HeaderEntry e = head.after;
384         while (e != head) {
385             names.add(e.getKey());
386             e = e.after;
387         }
388         return names;
389     }
390 
391     private static CharSequence toCharSequence(Object value) {
392         checkNotNull(value, "value");
393         if (value instanceof CharSequence) {
394             return (CharSequence) value;
395         }
396         if (value instanceof Number) {
397             return value.toString();
398         }
399         if (value instanceof Date) {
400             return HttpHeaderDateFormat.get().format((Date) value);
401         }
402         if (value instanceof Calendar) {
403             return HttpHeaderDateFormat.get().format(((Calendar) value).getTime());
404         }
405         return value.toString();
406     }
407 
408     void encode(ByteBuf buf) {
409         HeaderEntry e = head.after;
410         while (e != head) {
411             e.encode(buf);
412             e = e.after;
413         }
414     }
415 
416     private final class HeaderIterator implements Iterator<Map.Entry<String, String>> {
417 
418         private HeaderEntry current = head;
419 
420         @Override
421         public boolean hasNext() {
422             return current.after != head;
423         }
424 
425         @Override
426         public Entry<String, String> next() {
427             current = current.after;
428 
429             if (current == head) {
430                 throw new NoSuchElementException();
431             }
432 
433             return current;
434         }
435 
436         @Override
437         public void remove() {
438             throw new UnsupportedOperationException();
439         }
440     }
441 
442     private final class HeaderEntry implements Map.Entry<String, String> {
443         final int hash;
444         final CharSequence key;
445         CharSequence value;
446         HeaderEntry next;
447         HeaderEntry before, after;
448 
449         HeaderEntry(int hash, CharSequence key, CharSequence value) {
450             this.hash = hash;
451             this.key = key;
452             this.value = value;
453         }
454 
455         HeaderEntry() {
456             hash = -1;
457             key = null;
458             value = null;
459         }
460 
461         void remove() {
462             before.after = after;
463             after.before = before;
464         }
465 
466         void addBefore(HeaderEntry e) {
467             after  = e;
468             before = e.before;
469             before.after = this;
470             after.before = this;
471         }
472 
473         @Override
474         public String getKey() {
475             return key.toString();
476         }
477 
478         @Override
479         public String getValue() {
480             return value.toString();
481         }
482 
483         @Override
484         public String setValue(String value) {
485             if (value == null) {
486                 throw new NullPointerException("value");
487             }
488             validateHeaderValue(value);
489             CharSequence oldValue = this.value;
490             this.value = value;
491             return oldValue.toString();
492         }
493 
494         @Override
495         public String toString() {
496             return key.toString() + '=' + value.toString();
497         }
498 
499         void encode(ByteBuf buf) {
500             HttpHeaders.encode(key, value, buf);
501         }
502     }
503 }