/* * Copyright 2013 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.handler.codec.spdy; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.NoSuchElementException; import java.util.Set; import java.util.TreeSet; public class DefaultSpdyHeaders extends SpdyHeaders { private static final int BUCKET_SIZE = 17; private static int hash(String name) { int h = 0; for (int i = name.length() - 1; i >= 0; i --) { char c = name.charAt(i); if (c >= 'A' && c <= 'Z') { c += 32; } h = 31 * h + c; } if (h > 0) { return h; } else if (h == Integer.MIN_VALUE) { return Integer.MAX_VALUE; } else { return -h; } } private static boolean eq(String name1, String name2) { int nameLen = name1.length(); if (nameLen != name2.length()) { return false; } for (int i = nameLen - 1; i >= 0; i --) { char c1 = name1.charAt(i); char c2 = name2.charAt(i); if (c1 != c2) { if (c1 >= 'A' && c1 <= 'Z') { c1 += 32; } if (c2 >= 'A' && c2 <= 'Z') { c2 += 32; } if (c1 != c2) { return false; } } } return true; } private static int index(int hash) { return hash % BUCKET_SIZE; } private final HeaderEntry[] entries = new HeaderEntry[BUCKET_SIZE]; private final HeaderEntry head = new HeaderEntry(-1, null, null); DefaultSpdyHeaders() { head.before = head.after = head; } @Override public SpdyHeaders add(final String name, final Object value) { String lowerCaseName = name.toLowerCase(); SpdyCodecUtil.validateHeaderName(lowerCaseName); String strVal = toString(value); SpdyCodecUtil.validateHeaderValue(strVal); int h = hash(lowerCaseName); int i = index(h); add0(h, i, lowerCaseName, strVal); return this; } private void add0(int h, int i, final String name, final String value) { // Update the hash table. HeaderEntry e = entries[i]; HeaderEntry newEntry; entries[i] = newEntry = new HeaderEntry(h, name, value); newEntry.next = e; // Update the linked list. newEntry.addBefore(head); } @Override public SpdyHeaders remove(final String name) { if (name == null) { throw new NullPointerException("name"); } String lowerCaseName = name.toLowerCase(); int h = hash(lowerCaseName); int i = index(h); remove0(h, i, lowerCaseName); return this; } private void remove0(int h, int i, String name) { HeaderEntry e = entries[i]; if (e == null) { return; } for (;;) { if (e.hash == h && eq(name, e.key)) { e.remove(); HeaderEntry next = e.next; if (next != null) { entries[i] = next; e = next; } else { entries[i] = null; return; } } else { break; } } for (;;) { HeaderEntry next = e.next; if (next == null) { break; } if (next.hash == h && eq(name, next.key)) { e.next = next.next; next.remove(); } else { e = next; } } } @Override public SpdyHeaders set(final String name, final Object value) { String lowerCaseName = name.toLowerCase(); SpdyCodecUtil.validateHeaderName(lowerCaseName); String strVal = toString(value); SpdyCodecUtil.validateHeaderValue(strVal); int h = hash(lowerCaseName); int i = index(h); remove0(h, i, lowerCaseName); add0(h, i, lowerCaseName, strVal); return this; } @Override public SpdyHeaders set(final String name, final Iterable<?> values) { if (values == null) { throw new NullPointerException("values"); } String lowerCaseName = name.toLowerCase(); SpdyCodecUtil.validateHeaderName(lowerCaseName); int h = hash(lowerCaseName); int i = index(h); remove0(h, i, lowerCaseName); for (Object v: values) { if (v == null) { break; } String strVal = toString(v); SpdyCodecUtil.validateHeaderValue(strVal); add0(h, i, lowerCaseName, strVal); } return this; } @Override public SpdyHeaders clear() { for (int i = 0; i < entries.length; i ++) { entries[i] = null; } head.before = head.after = head; return this; } @Override public String get(final String name) { if (name == null) { throw new NullPointerException("name"); } int h = hash(name); int i = index(h); HeaderEntry e = entries[i]; while (e != null) { if (e.hash == h && eq(name, e.key)) { return e.value; } e = e.next; } return null; } @Override public List<String> getAll(final String name) { if (name == null) { throw new NullPointerException("name"); } LinkedList<String> values = new LinkedList<String>(); int h = hash(name); int i = index(h); HeaderEntry e = entries[i]; while (e != null) { if (e.hash == h && eq(name, e.key)) { values.addFirst(e.value); } e = e.next; } return values; } @Override public List<Map.Entry<String, String>> entries() { List<Map.Entry<String, String>> all = new LinkedList<Map.Entry<String, String>>(); HeaderEntry e = head.after; while (e != head) { all.add(e); e = e.after; } return all; } @Override public Iterator<Map.Entry<String, String>> iterator() { return new HeaderIterator(); } @Override public boolean contains(String name) { return get(name) != null; } @Override public Set<String> names() { Set<String> names = new TreeSet<String>(); HeaderEntry e = head.after; while (e != head) { names.add(e.key); e = e.after; } return names; } @Override public SpdyHeaders add(String name, Iterable<?> values) { SpdyCodecUtil.validateHeaderValue(name); int h = hash(name); int i = index(h); for (Object v: values) { String vstr = toString(v); SpdyCodecUtil.validateHeaderValue(vstr); add0(h, i, name, vstr); } return this; } @Override public boolean isEmpty() { return head == head.after; } private static String toString(Object value) { if (value == null) { return null; } return value.toString(); } private final class HeaderIterator implements Iterator<Map.Entry<String, String>> { private HeaderEntry current = head; @Override public boolean hasNext() { return current.after != head; } @Override public Entry<String, String> next() { current = current.after; if (current == head) { throw new NoSuchElementException(); } return current; } @Override public void remove() { throw new UnsupportedOperationException(); } } private static final class HeaderEntry implements Map.Entry<String, String> { final int hash; final String key; String value; HeaderEntry next; HeaderEntry before, after; HeaderEntry(int hash, String key, String value) { this.hash = hash; this.key = key; this.value = value; } void remove() { before.after = after; after.before = before; } void addBefore(HeaderEntry e) { after = e; before = e.before; before.after = this; after.before = this; } @Override public String getKey() { return key; } @Override public String getValue() { return value; } @Override public String setValue(String value) { if (value == null) { throw new NullPointerException("value"); } SpdyCodecUtil.validateHeaderValue(value); String oldValue = this.value; this.value = value; return oldValue; } @Override public String toString() { return key + '=' + value; } } }