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 org.jboss.netty.handler.codec.http;
17  
18  import java.lang.reflect.Field;
19  import java.lang.reflect.Modifier;
20  import java.util.Arrays;
21  import java.util.Calendar;
22  import java.util.Date;
23  import java.util.HashSet;
24  import java.util.Iterator;
25  import java.util.LinkedHashSet;
26  import java.util.LinkedList;
27  import java.util.List;
28  import java.util.Map;
29  import java.util.Map.Entry;
30  import java.util.NoSuchElementException;
31  import java.util.Set;
32  
33  public class DefaultHttpHeaders extends HttpHeaders {
34  
35      private static final int BUCKET_SIZE = 17;
36  
37      private static final Set<String> KNOWN_NAMES = createSet(Names.class);
38      private static final Set<String> KNOWN_VALUES = createSet(Values.class);
39  
40      private static Set<String> createSet(Class<?> clazz) {
41          Set<String> set = new HashSet<String>();
42          Field[] fields = clazz.getDeclaredFields();
43  
44          for (Field f: fields) {
45              int m = f.getModifiers();
46              if (Modifier.isPublic(m) && Modifier.isStatic(m) && Modifier.isFinal(m)
47                      && f.getType().isAssignableFrom(String.class)) {
48                  try {
49                      set.add((String) f.get(null));
50                  } catch (Throwable cause) {
51                      // ignore
52                  }
53              }
54          }
55          return set;
56      }
57  
58      private static int hash(String name, boolean validate) {
59          int h = 0;
60          for (int i = name.length() - 1; i >= 0; i --) {
61              char c = name.charAt(i);
62              if (validate) {
63                  valideHeaderNameChar(c);
64              }
65              c = toLowerCase(c);
66              h = 31 * h + c;
67          }
68  
69          if (h > 0) {
70              return h;
71          } else if (h == Integer.MIN_VALUE) {
72              return Integer.MAX_VALUE;
73          } else {
74              return -h;
75          }
76      }
77  
78      private static boolean eq(String name1, String name2) {
79          if (name1 == name2) {
80              // check for object equality as the user may reuse our static fields in HttpHeaders.Names
81              return true;
82          }
83          int nameLen = name1.length();
84          if (nameLen != name2.length()) {
85              return false;
86          }
87  
88          for (int i = nameLen - 1; i >= 0; i --) {
89              char c1 = name1.charAt(i);
90              char c2 = name2.charAt(i);
91              if (c1 != c2) {
92                  if (toLowerCase(c1) != toLowerCase(c2)) {
93                      return false;
94                  }
95              }
96          }
97          return true;
98      }
99  
100     private static char toLowerCase(char c) {
101         if (c >= 'A' && c <= 'Z') {
102             c += 32;
103         }
104         return c;
105     }
106 
107     private static int index(int hash) {
108         return hash % BUCKET_SIZE;
109     }
110 
111     private final HeaderEntry[] entries = new HeaderEntry[BUCKET_SIZE];
112     private final HeaderEntry head = new HeaderEntry(-1, null, null);
113     protected final boolean validate;
114 
115     public DefaultHttpHeaders() {
116         this(true);
117     }
118 
119     public DefaultHttpHeaders(boolean validate) {
120         head.before = head.after = head;
121         this.validate = validate;
122     }
123 
124     void validateHeaderValue0(String headerValue) {
125         if (KNOWN_VALUES.contains(headerValue)) {
126             return;
127         }
128         validateHeaderValue(headerValue);
129     }
130 
131     @Override
132     public HttpHeaders add(final String name, final Object value) {
133         String strVal = toString(value);
134         boolean validateName = false;
135         if (validate) {
136             validateHeaderValue0(strVal);
137             validateName = !KNOWN_NAMES.contains(name);
138         }
139 
140         int h = hash(name, validateName);
141         int i = index(h);
142         add0(h, i, name, strVal);
143         return this;
144     }
145 
146     @Override
147     public HttpHeaders add(String name, Iterable<?> values) {
148         boolean validateName = false;
149         if (validate) {
150             validateName = !KNOWN_NAMES.contains(name);
151         }
152 
153         int h = hash(name, validateName);
154         int i = index(h);
155         for (Object v: values) {
156             String vstr = toString(v);
157             if (validate) {
158                 validateHeaderValue0(vstr);
159             }
160             add0(h, i, name, vstr);
161         }
162         return this;
163     }
164 
165     private void add0(int h, int i, final String name, final String value) {
166         // Update the hash table.
167         HeaderEntry e = entries[i];
168         HeaderEntry newEntry;
169         entries[i] = newEntry = new HeaderEntry(h, name, value);
170         newEntry.next = e;
171 
172         // Update the linked list.
173         newEntry.addBefore(head);
174     }
175 
176     @Override
177     public HttpHeaders remove(final String name) {
178         if (name == null) {
179             throw new NullPointerException("name");
180         }
181         int h = hash(name, false);
182         int i = index(h);
183         remove0(h, i, name);
184         return this;
185     }
186 
187     private void remove0(int h, int i, String name) {
188         HeaderEntry e = entries[i];
189         if (e == null) {
190             return;
191         }
192 
193         for (;;) {
194             if (e.hash == h && eq(name, e.key)) {
195                 e.remove();
196                 HeaderEntry next = e.next;
197                 if (next != null) {
198                     entries[i] = next;
199                     e = next;
200                 } else {
201                     entries[i] = null;
202                     return;
203                 }
204             } else {
205                 break;
206             }
207         }
208 
209         for (;;) {
210             HeaderEntry next = e.next;
211             if (next == null) {
212                 break;
213             }
214             if (next.hash == h && eq(name, next.key)) {
215                 e.next = next.next;
216                 next.remove();
217             } else {
218                 e = next;
219             }
220         }
221     }
222 
223     @Override
224     public HttpHeaders set(final String name, final Object value) {
225         String strVal = toString(value);
226         boolean validateName = false;
227         if (validate) {
228             validateHeaderValue0(strVal);
229             validateName = !KNOWN_NAMES.contains(name);
230         }
231 
232         int h = hash(name, validateName);
233         int i = index(h);
234         remove0(h, i, name);
235         add0(h, i, name, strVal);
236         return this;
237     }
238 
239     @Override
240     public HttpHeaders set(final String name, final Iterable<?> values) {
241         if (values == null) {
242             throw new NullPointerException("values");
243         }
244 
245         boolean validateName = false;
246         if (validate) {
247             validateName = !KNOWN_NAMES.contains(name);
248         }
249 
250         int h = hash(name, validateName);
251         int i = index(h);
252 
253         remove0(h, i, name);
254         for (Object v: values) {
255             if (v == null) {
256                 break;
257             }
258             String strVal = toString(v);
259             if (validate) {
260                 validateHeaderValue0(strVal);
261             }
262             add0(h, i, name, strVal);
263         }
264 
265         return this;
266     }
267 
268     @Override
269     public HttpHeaders clear() {
270         Arrays.fill(entries, null);
271         head.before = head.after = head;
272         return this;
273     }
274 
275     @Override
276     public String get(final String name) {
277         return get(name, false);
278     }
279 
280     private String get(final String name, boolean last) {
281         if (name == null) {
282             throw new NullPointerException("name");
283         }
284 
285         int h = hash(name, false);
286         int i = index(h);
287         HeaderEntry e = entries[i];
288         String value = null;
289         // loop until the first header was found
290         while (e != null) {
291             if (e.hash == h && eq(name, e.key)) {
292                 value = e.value;
293                 if (last) {
294                     break;
295                 }
296             }
297 
298             e = e.next;
299         }
300         return value;
301     }
302 
303     @Override
304     public List<String> getAll(final String name) {
305         if (name == null) {
306             throw new NullPointerException("name");
307         }
308 
309         LinkedList<String> values = new LinkedList<String>();
310 
311         int h = hash(name, false);
312         int i = index(h);
313         HeaderEntry e = entries[i];
314         while (e != null) {
315             if (e.hash == h && eq(name, e.key)) {
316                 values.addFirst(e.value);
317             }
318             e = e.next;
319         }
320         return values;
321     }
322 
323     @Override
324     public List<Map.Entry<String, String>> entries() {
325         List<Map.Entry<String, String>> all =
326             new LinkedList<Map.Entry<String, String>>();
327 
328         HeaderEntry e = head.after;
329         while (e != head) {
330             all.add(e);
331             e = e.after;
332         }
333         return all;
334     }
335 
336     public Iterator<Map.Entry<String, String>> iterator() {
337         return new HeaderIterator();
338     }
339 
340     @Override
341     public boolean contains(String name) {
342         return get(name, true) != null;
343     }
344 
345     @Override
346     public boolean isEmpty() {
347         return head == head.after;
348     }
349 
350     @Override
351     public boolean contains(String name, String value, boolean ignoreCaseValue) {
352         if (name == null) {
353             throw new NullPointerException("name");
354         }
355 
356         int h = hash(name, false);
357         int i = index(h);
358         HeaderEntry e = entries[i];
359         while (e != null) {
360             if (e.hash == h && eq(name, e.key)) {
361                 if (ignoreCaseValue) {
362                     if (e.value.equalsIgnoreCase(value)) {
363                         return true;
364                     }
365                 } else {
366                     if (e.value.equals(value)) {
367                         return true;
368                     }
369                 }
370             }
371             e = e.next;
372         }
373         return false;
374     }
375 
376     @Override
377     public Set<String> names() {
378         Set<String> names = new LinkedHashSet<String>();
379         HeaderEntry e = head.after;
380         while (e != head) {
381             names.add(e.key);
382             e = e.after;
383         }
384         return names;
385     }
386 
387     private static String toString(Object value) {
388         if (value == null) {
389             return null;
390         }
391         if (value instanceof String) {
392             return (String) value;
393         }
394         if (value instanceof Number) {
395             return value.toString();
396         }
397         if (value instanceof Date) {
398             return HttpHeaderDateFormat.get().format((Date) value);
399         }
400         if (value instanceof Calendar) {
401             return HttpHeaderDateFormat.get().format(((Calendar) value).getTime());
402         }
403         return value.toString();
404     }
405 
406     private final class HeaderIterator implements Iterator<Map.Entry<String, String>> {
407 
408         private HeaderEntry current = head;
409 
410         public boolean hasNext() {
411             return current.after != head;
412         }
413 
414         public Entry<String, String> next() {
415             current = current.after;
416 
417             if (current == head) {
418                 throw new NoSuchElementException();
419             }
420 
421             return current;
422         }
423 
424         public void remove() {
425             throw new UnsupportedOperationException();
426         }
427     }
428 
429     private final class HeaderEntry implements Map.Entry<String, String> {
430         final int hash;
431         final String key;
432         String value;
433         HeaderEntry next;
434         HeaderEntry before, after;
435 
436         HeaderEntry(int hash, String key, String value) {
437             this.hash = hash;
438             this.key = key;
439             this.value = value;
440         }
441 
442         void remove() {
443             before.after = after;
444             after.before = before;
445         }
446 
447         void addBefore(HeaderEntry e) {
448             after  = e;
449             before = e.before;
450             before.after = this;
451             after.before = this;
452         }
453 
454         public String getKey() {
455             return key;
456         }
457 
458         public String getValue() {
459             return value;
460         }
461 
462         public String setValue(String value) {
463             if (value == null) {
464                 throw new NullPointerException("value");
465             }
466             if (validate) {
467                 validateHeaderValue0(value);
468             }
469             String oldValue = this.value;
470             this.value = value;
471             return oldValue;
472         }
473 
474         @Override
475         public String toString() {
476             return key + '=' + value;
477         }
478     }
479 }