f755e58463
Motivation: * DefaultHeaders from netty-codec has some duplicated logic for header date parsing * Several classes keep on using deprecated HttpHeaderDateFormat Modifications: * Move HttpHeaderDateFormatter to netty-codec and rename it into HeaderDateFormatter * Make DefaultHeaders use HeaderDateFormatter * Replace HttpHeaderDateFormat usage with HeaderDateFormatter Result: Faster and more consistent code
1058 lines
29 KiB
Java
1058 lines
29 KiB
Java
/*
|
|
* Copyright 2014 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;
|
|
|
|
import io.netty.util.HashingStrategy;
|
|
|
|
import java.util.Arrays;
|
|
import java.util.Collections;
|
|
import java.util.Iterator;
|
|
import java.util.LinkedHashSet;
|
|
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 static io.netty.util.HashingStrategy.JAVA_HASHER;
|
|
import static io.netty.util.internal.MathUtil.findNextPositivePowerOfTwo;
|
|
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
|
import static java.lang.Math.min;
|
|
import static java.lang.Math.max;
|
|
|
|
/**
|
|
* Default implementation of {@link Headers};
|
|
*
|
|
* @param <K> the type of the header name.
|
|
* @param <V> the type of the header value.
|
|
* @param <T> the type to use for return values when the intention is to return {@code this} object.
|
|
*/
|
|
public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers<K, V, T> {
|
|
/**
|
|
* Constant used to seed the hash code generation. Could be anything but this was borrowed from murmur3.
|
|
*/
|
|
static final int HASH_CODE_SEED = 0xc2b2ae35;
|
|
|
|
private final HeaderEntry<K, V>[] entries;
|
|
protected final HeaderEntry<K, V> head;
|
|
|
|
private final byte hashMask;
|
|
private final ValueConverter<V> valueConverter;
|
|
private final NameValidator<K> nameValidator;
|
|
private final HashingStrategy<K> hashingStrategy;
|
|
int size;
|
|
|
|
public interface NameValidator<K> {
|
|
/**
|
|
* Verify that {@code name} is valid.
|
|
* @param name The name to validate.
|
|
* @throws RuntimeException if {@code name} is not valid.
|
|
*/
|
|
void validateName(K name);
|
|
|
|
@SuppressWarnings("rawtypes")
|
|
NameValidator NOT_NULL = new NameValidator() {
|
|
@Override
|
|
public void validateName(Object name) {
|
|
checkNotNull(name, "name");
|
|
}
|
|
};
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
public DefaultHeaders(ValueConverter<V> valueConverter) {
|
|
this(JAVA_HASHER, valueConverter);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
public DefaultHeaders(ValueConverter<V> valueConverter, NameValidator<K> nameValidator) {
|
|
this(JAVA_HASHER, valueConverter, nameValidator);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
public DefaultHeaders(HashingStrategy<K> nameHashingStrategy, ValueConverter<V> valueConverter) {
|
|
this(nameHashingStrategy, valueConverter, NameValidator.NOT_NULL);
|
|
}
|
|
|
|
public DefaultHeaders(HashingStrategy<K> nameHashingStrategy,
|
|
ValueConverter<V> valueConverter, NameValidator<K> nameValidator) {
|
|
this(nameHashingStrategy, valueConverter, nameValidator, 16);
|
|
}
|
|
|
|
/**
|
|
* Create a new instance.
|
|
* @param nameHashingStrategy Used to hash and equality compare names.
|
|
* @param valueConverter Used to convert values to/from native types.
|
|
* @param nameValidator Used to validate name elements.
|
|
* @param arraySizeHint A hint as to how large the hash data structure should be.
|
|
* The next positive power of two will be used. An upper bound may be enforced.
|
|
*/
|
|
@SuppressWarnings("unchecked")
|
|
public DefaultHeaders(HashingStrategy<K> nameHashingStrategy,
|
|
ValueConverter<V> valueConverter, NameValidator<K> nameValidator, int arraySizeHint) {
|
|
this.valueConverter = checkNotNull(valueConverter, "valueConverter");
|
|
this.nameValidator = checkNotNull(nameValidator, "nameValidator");
|
|
this.hashingStrategy = checkNotNull(nameHashingStrategy, "nameHashingStrategy");
|
|
// Enforce a bound of [2, 128] because hashMask is a byte. The max possible value of hashMask is one less
|
|
// than the length of this array, and we want the mask to be > 0.
|
|
entries = new DefaultHeaders.HeaderEntry[findNextPositivePowerOfTwo(max(2, min(arraySizeHint, 128)))];
|
|
hashMask = (byte) (entries.length - 1);
|
|
head = new HeaderEntry<K, V>();
|
|
}
|
|
|
|
@Override
|
|
public V get(K name) {
|
|
checkNotNull(name, "name");
|
|
|
|
int h = hashingStrategy.hashCode(name);
|
|
int i = index(h);
|
|
HeaderEntry<K, V> e = entries[i];
|
|
V value = null;
|
|
// loop until the first header was found
|
|
while (e != null) {
|
|
if (e.hash == h && hashingStrategy.equals(name, e.key)) {
|
|
value = e.value;
|
|
}
|
|
|
|
e = e.next;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
@Override
|
|
public V get(K name, V defaultValue) {
|
|
V value = get(name);
|
|
if (value == null) {
|
|
return defaultValue;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
@Override
|
|
public V getAndRemove(K name) {
|
|
int h = hashingStrategy.hashCode(name);
|
|
return remove0(h, index(h), checkNotNull(name, "name"));
|
|
}
|
|
|
|
@Override
|
|
public V getAndRemove(K name, V defaultValue) {
|
|
V value = getAndRemove(name);
|
|
if (value == null) {
|
|
return defaultValue;
|
|
}
|
|
return value;
|
|
}
|
|
|
|
@Override
|
|
public List<V> getAll(K name) {
|
|
checkNotNull(name, "name");
|
|
|
|
LinkedList<V> values = new LinkedList<V>();
|
|
|
|
int h = hashingStrategy.hashCode(name);
|
|
int i = index(h);
|
|
HeaderEntry<K, V> e = entries[i];
|
|
while (e != null) {
|
|
if (e.hash == h && hashingStrategy.equals(name, e.key)) {
|
|
values.addFirst(e.getValue());
|
|
}
|
|
e = e.next;
|
|
}
|
|
return values;
|
|
}
|
|
|
|
@Override
|
|
public List<V> getAllAndRemove(K name) {
|
|
List<V> all = getAll(name);
|
|
remove(name);
|
|
return all;
|
|
}
|
|
|
|
@Override
|
|
public boolean contains(K name) {
|
|
return get(name) != null;
|
|
}
|
|
|
|
@Override
|
|
public boolean containsObject(K name, Object value) {
|
|
return contains(name, valueConverter.convertObject(checkNotNull(value, "value")));
|
|
}
|
|
|
|
@Override
|
|
public boolean containsBoolean(K name, boolean value) {
|
|
return contains(name, valueConverter.convertBoolean(value));
|
|
}
|
|
|
|
@Override
|
|
public boolean containsByte(K name, byte value) {
|
|
return contains(name, valueConverter.convertByte(value));
|
|
}
|
|
|
|
@Override
|
|
public boolean containsChar(K name, char value) {
|
|
return contains(name, valueConverter.convertChar(value));
|
|
}
|
|
|
|
@Override
|
|
public boolean containsShort(K name, short value) {
|
|
return contains(name, valueConverter.convertShort(value));
|
|
}
|
|
|
|
@Override
|
|
public boolean containsInt(K name, int value) {
|
|
return contains(name, valueConverter.convertInt(value));
|
|
}
|
|
|
|
@Override
|
|
public boolean containsLong(K name, long value) {
|
|
return contains(name, valueConverter.convertLong(value));
|
|
}
|
|
|
|
@Override
|
|
public boolean containsFloat(K name, float value) {
|
|
return contains(name, valueConverter.convertFloat(value));
|
|
}
|
|
|
|
@Override
|
|
public boolean containsDouble(K name, double value) {
|
|
return contains(name, valueConverter.convertDouble(value));
|
|
}
|
|
|
|
@Override
|
|
public boolean containsTimeMillis(K name, long value) {
|
|
return contains(name, valueConverter.convertTimeMillis(value));
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
@Override
|
|
public boolean contains(K name, V value) {
|
|
return contains(name, value, JAVA_HASHER);
|
|
}
|
|
|
|
public final boolean contains(K name, V value, HashingStrategy<? super V> valueHashingStrategy) {
|
|
checkNotNull(name, "name");
|
|
|
|
int h = hashingStrategy.hashCode(name);
|
|
int i = index(h);
|
|
HeaderEntry<K, V> e = entries[i];
|
|
while (e != null) {
|
|
if (e.hash == h && hashingStrategy.equals(name, e.key) && valueHashingStrategy.equals(value, e.value)) {
|
|
return true;
|
|
}
|
|
e = e.next;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
@Override
|
|
public int size() {
|
|
return size;
|
|
}
|
|
|
|
@Override
|
|
public boolean isEmpty() {
|
|
return head == head.after;
|
|
}
|
|
|
|
@Override
|
|
public Set<K> names() {
|
|
if (isEmpty()) {
|
|
return Collections.emptySet();
|
|
}
|
|
Set<K> names = new LinkedHashSet<K>(size());
|
|
HeaderEntry<K, V> e = head.after;
|
|
while (e != head) {
|
|
names.add(e.getKey());
|
|
e = e.after;
|
|
}
|
|
return names;
|
|
}
|
|
|
|
@Override
|
|
public T add(K name, V value) {
|
|
nameValidator.validateName(name);
|
|
checkNotNull(value, "value");
|
|
int h = hashingStrategy.hashCode(name);
|
|
int i = index(h);
|
|
add0(h, i, name, value);
|
|
return thisT();
|
|
}
|
|
|
|
@Override
|
|
public T add(K name, Iterable<? extends V> values) {
|
|
nameValidator.validateName(name);
|
|
int h = hashingStrategy.hashCode(name);
|
|
int i = index(h);
|
|
for (V v: values) {
|
|
add0(h, i, name, v);
|
|
}
|
|
return thisT();
|
|
}
|
|
|
|
@Override
|
|
public T add(K name, V... values) {
|
|
nameValidator.validateName(name);
|
|
int h = hashingStrategy.hashCode(name);
|
|
int i = index(h);
|
|
for (V v: values) {
|
|
add0(h, i, name, v);
|
|
}
|
|
return thisT();
|
|
}
|
|
|
|
@Override
|
|
public T addObject(K name, Object value) {
|
|
return add(name, valueConverter.convertObject(checkNotNull(value, "value")));
|
|
}
|
|
|
|
@Override
|
|
public T addObject(K name, Iterable<?> values) {
|
|
for (Object value : values) {
|
|
addObject(name, value);
|
|
}
|
|
return thisT();
|
|
}
|
|
|
|
@Override
|
|
public T addObject(K name, Object... values) {
|
|
for (int i = 0; i < values.length; i++) {
|
|
addObject(name, values[i]);
|
|
}
|
|
return thisT();
|
|
}
|
|
|
|
@Override
|
|
public T addInt(K name, int value) {
|
|
return add(name, valueConverter.convertInt(value));
|
|
}
|
|
|
|
@Override
|
|
public T addLong(K name, long value) {
|
|
return add(name, valueConverter.convertLong(value));
|
|
}
|
|
|
|
@Override
|
|
public T addDouble(K name, double value) {
|
|
return add(name, valueConverter.convertDouble(value));
|
|
}
|
|
|
|
@Override
|
|
public T addTimeMillis(K name, long value) {
|
|
return add(name, valueConverter.convertTimeMillis(value));
|
|
}
|
|
|
|
@Override
|
|
public T addChar(K name, char value) {
|
|
return add(name, valueConverter.convertChar(value));
|
|
}
|
|
|
|
@Override
|
|
public T addBoolean(K name, boolean value) {
|
|
return add(name, valueConverter.convertBoolean(value));
|
|
}
|
|
|
|
@Override
|
|
public T addFloat(K name, float value) {
|
|
return add(name, valueConverter.convertFloat(value));
|
|
}
|
|
|
|
@Override
|
|
public T addByte(K name, byte value) {
|
|
return add(name, valueConverter.convertByte(value));
|
|
}
|
|
|
|
@Override
|
|
public T addShort(K name, short value) {
|
|
return add(name, valueConverter.convertShort(value));
|
|
}
|
|
|
|
@Override
|
|
public T add(Headers<? extends K, ? extends V, ?> headers) {
|
|
if (headers == this) {
|
|
throw new IllegalArgumentException("can't add to itself.");
|
|
}
|
|
addImpl(headers);
|
|
return thisT();
|
|
}
|
|
|
|
protected void addImpl(Headers<? extends K, ? extends V, ?> headers) {
|
|
if (headers instanceof DefaultHeaders) {
|
|
@SuppressWarnings("unchecked")
|
|
final DefaultHeaders<? extends K, ? extends V, T> defaultHeaders =
|
|
(DefaultHeaders<? extends K, ? extends V, T>) headers;
|
|
HeaderEntry<? extends K, ? extends V> e = defaultHeaders.head.after;
|
|
if (defaultHeaders.hashingStrategy == hashingStrategy &&
|
|
defaultHeaders.nameValidator == nameValidator) {
|
|
// Fastest copy
|
|
while (e != defaultHeaders.head) {
|
|
add0(e.hash, index(e.hash), e.key, e.value);
|
|
e = e.after;
|
|
}
|
|
} else {
|
|
// Fast copy
|
|
while (e != defaultHeaders.head) {
|
|
add(e.key, e.value);
|
|
e = e.after;
|
|
}
|
|
}
|
|
} else {
|
|
// Slow copy
|
|
for (Entry<? extends K, ? extends V> header : headers) {
|
|
add(header.getKey(), header.getValue());
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public T set(K name, V value) {
|
|
nameValidator.validateName(name);
|
|
checkNotNull(value, "value");
|
|
int h = hashingStrategy.hashCode(name);
|
|
int i = index(h);
|
|
remove0(h, i, name);
|
|
add0(h, i, name, value);
|
|
return thisT();
|
|
}
|
|
|
|
@Override
|
|
public T set(K name, Iterable<? extends V> values) {
|
|
nameValidator.validateName(name);
|
|
checkNotNull(values, "values");
|
|
|
|
int h = hashingStrategy.hashCode(name);
|
|
int i = index(h);
|
|
|
|
remove0(h, i, name);
|
|
for (V v: values) {
|
|
if (v == null) {
|
|
break;
|
|
}
|
|
add0(h, i, name, v);
|
|
}
|
|
|
|
return thisT();
|
|
}
|
|
|
|
@Override
|
|
public T set(K name, V... values) {
|
|
nameValidator.validateName(name);
|
|
checkNotNull(values, "values");
|
|
|
|
int h = hashingStrategy.hashCode(name);
|
|
int i = index(h);
|
|
|
|
remove0(h, i, name);
|
|
for (V v: values) {
|
|
if (v == null) {
|
|
break;
|
|
}
|
|
add0(h, i, name, v);
|
|
}
|
|
|
|
return thisT();
|
|
}
|
|
|
|
@Override
|
|
public T setObject(K name, Object value) {
|
|
checkNotNull(value, "value");
|
|
V convertedValue = checkNotNull(valueConverter.convertObject(value), "convertedValue");
|
|
return set(name, convertedValue);
|
|
}
|
|
|
|
@Override
|
|
public T setObject(K name, Iterable<?> values) {
|
|
nameValidator.validateName(name);
|
|
|
|
int h = hashingStrategy.hashCode(name);
|
|
int i = index(h);
|
|
|
|
remove0(h, i, name);
|
|
for (Object v: values) {
|
|
if (v == null) {
|
|
break;
|
|
}
|
|
add0(h, i, name, valueConverter.convertObject(v));
|
|
}
|
|
|
|
return thisT();
|
|
}
|
|
|
|
@Override
|
|
public T setObject(K name, Object... values) {
|
|
nameValidator.validateName(name);
|
|
|
|
int h = hashingStrategy.hashCode(name);
|
|
int i = index(h);
|
|
|
|
remove0(h, i, name);
|
|
for (Object v: values) {
|
|
if (v == null) {
|
|
break;
|
|
}
|
|
add0(h, i, name, valueConverter.convertObject(v));
|
|
}
|
|
|
|
return thisT();
|
|
}
|
|
|
|
@Override
|
|
public T setInt(K name, int value) {
|
|
return set(name, valueConverter.convertInt(value));
|
|
}
|
|
|
|
@Override
|
|
public T setLong(K name, long value) {
|
|
return set(name, valueConverter.convertLong(value));
|
|
}
|
|
|
|
@Override
|
|
public T setDouble(K name, double value) {
|
|
return set(name, valueConverter.convertDouble(value));
|
|
}
|
|
|
|
@Override
|
|
public T setTimeMillis(K name, long value) {
|
|
return set(name, valueConverter.convertTimeMillis(value));
|
|
}
|
|
|
|
@Override
|
|
public T setFloat(K name, float value) {
|
|
return set(name, valueConverter.convertFloat(value));
|
|
}
|
|
|
|
@Override
|
|
public T setChar(K name, char value) {
|
|
return set(name, valueConverter.convertChar(value));
|
|
}
|
|
|
|
@Override
|
|
public T setBoolean(K name, boolean value) {
|
|
return set(name, valueConverter.convertBoolean(value));
|
|
}
|
|
|
|
@Override
|
|
public T setByte(K name, byte value) {
|
|
return set(name, valueConverter.convertByte(value));
|
|
}
|
|
|
|
@Override
|
|
public T setShort(K name, short value) {
|
|
return set(name, valueConverter.convertShort(value));
|
|
}
|
|
|
|
@Override
|
|
public T set(Headers<? extends K, ? extends V, ?> headers) {
|
|
if (headers != this) {
|
|
clear();
|
|
addImpl(headers);
|
|
}
|
|
return thisT();
|
|
}
|
|
|
|
@Override
|
|
public T setAll(Headers<? extends K, ? extends V, ?> headers) {
|
|
if (headers != this) {
|
|
for (K key : headers.names()) {
|
|
remove(key);
|
|
}
|
|
addImpl(headers);
|
|
}
|
|
return thisT();
|
|
}
|
|
|
|
@Override
|
|
public boolean remove(K name) {
|
|
return getAndRemove(name) != null;
|
|
}
|
|
|
|
@Override
|
|
public T clear() {
|
|
Arrays.fill(entries, null);
|
|
head.before = head.after = head;
|
|
size = 0;
|
|
return thisT();
|
|
}
|
|
|
|
@Override
|
|
public Iterator<Entry<K, V>> iterator() {
|
|
return new HeaderIterator();
|
|
}
|
|
|
|
@Override
|
|
public Boolean getBoolean(K name) {
|
|
V v = get(name);
|
|
return v != null ? valueConverter.convertToBoolean(v) : null;
|
|
}
|
|
|
|
@Override
|
|
public boolean getBoolean(K name, boolean defaultValue) {
|
|
Boolean v = getBoolean(name);
|
|
return v != null ? v : defaultValue;
|
|
}
|
|
|
|
@Override
|
|
public Byte getByte(K name) {
|
|
V v = get(name);
|
|
return v != null ? valueConverter.convertToByte(v) : null;
|
|
}
|
|
|
|
@Override
|
|
public byte getByte(K name, byte defaultValue) {
|
|
Byte v = getByte(name);
|
|
return v != null ? v : defaultValue;
|
|
}
|
|
|
|
@Override
|
|
public Character getChar(K name) {
|
|
V v = get(name);
|
|
return v != null ? valueConverter.convertToChar(v) : null;
|
|
}
|
|
|
|
@Override
|
|
public char getChar(K name, char defaultValue) {
|
|
Character v = getChar(name);
|
|
return v != null ? v : defaultValue;
|
|
}
|
|
|
|
@Override
|
|
public Short getShort(K name) {
|
|
V v = get(name);
|
|
return v != null ? valueConverter.convertToShort(v) : null;
|
|
}
|
|
|
|
@Override
|
|
public short getShort(K name, short defaultValue) {
|
|
Short v = getShort(name);
|
|
return v != null ? v : defaultValue;
|
|
}
|
|
|
|
@Override
|
|
public Integer getInt(K name) {
|
|
V v = get(name);
|
|
return v != null ? valueConverter.convertToInt(v) : null;
|
|
}
|
|
|
|
@Override
|
|
public int getInt(K name, int defaultValue) {
|
|
Integer v = getInt(name);
|
|
return v != null ? v : defaultValue;
|
|
}
|
|
|
|
@Override
|
|
public Long getLong(K name) {
|
|
V v = get(name);
|
|
return v != null ? valueConverter.convertToLong(v) : null;
|
|
}
|
|
|
|
@Override
|
|
public long getLong(K name, long defaultValue) {
|
|
Long v = getLong(name);
|
|
return v != null ? v : defaultValue;
|
|
}
|
|
|
|
@Override
|
|
public Float getFloat(K name) {
|
|
V v = get(name);
|
|
return v != null ? valueConverter.convertToFloat(v) : null;
|
|
}
|
|
|
|
@Override
|
|
public float getFloat(K name, float defaultValue) {
|
|
Float v = getFloat(name);
|
|
return v != null ? v : defaultValue;
|
|
}
|
|
|
|
@Override
|
|
public Double getDouble(K name) {
|
|
V v = get(name);
|
|
return v != null ? valueConverter.convertToDouble(v) : null;
|
|
}
|
|
|
|
@Override
|
|
public double getDouble(K name, double defaultValue) {
|
|
Double v = getDouble(name);
|
|
return v != null ? v : defaultValue;
|
|
}
|
|
|
|
@Override
|
|
public Long getTimeMillis(K name) {
|
|
V v = get(name);
|
|
return v != null ? valueConverter.convertToTimeMillis(v) : null;
|
|
}
|
|
|
|
@Override
|
|
public long getTimeMillis(K name, long defaultValue) {
|
|
Long v = getTimeMillis(name);
|
|
return v != null ? v : defaultValue;
|
|
}
|
|
|
|
@Override
|
|
public Boolean getBooleanAndRemove(K name) {
|
|
V v = getAndRemove(name);
|
|
return v != null ? valueConverter.convertToBoolean(v) : null;
|
|
}
|
|
|
|
@Override
|
|
public boolean getBooleanAndRemove(K name, boolean defaultValue) {
|
|
Boolean v = getBooleanAndRemove(name);
|
|
return v != null ? v : defaultValue;
|
|
}
|
|
|
|
@Override
|
|
public Byte getByteAndRemove(K name) {
|
|
V v = getAndRemove(name);
|
|
return v != null ? valueConverter.convertToByte(v) : null;
|
|
}
|
|
|
|
@Override
|
|
public byte getByteAndRemove(K name, byte defaultValue) {
|
|
Byte v = getByteAndRemove(name);
|
|
return v != null ? v : defaultValue;
|
|
}
|
|
|
|
@Override
|
|
public Character getCharAndRemove(K name) {
|
|
V v = getAndRemove(name);
|
|
if (v == null) {
|
|
return null;
|
|
}
|
|
try {
|
|
return valueConverter.convertToChar(v);
|
|
} catch (Throwable ignored) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public char getCharAndRemove(K name, char defaultValue) {
|
|
Character v = getCharAndRemove(name);
|
|
return v != null ? v : defaultValue;
|
|
}
|
|
|
|
@Override
|
|
public Short getShortAndRemove(K name) {
|
|
V v = getAndRemove(name);
|
|
return v != null ? valueConverter.convertToShort(v) : null;
|
|
}
|
|
|
|
@Override
|
|
public short getShortAndRemove(K name, short defaultValue) {
|
|
Short v = getShortAndRemove(name);
|
|
return v != null ? v : defaultValue;
|
|
}
|
|
|
|
@Override
|
|
public Integer getIntAndRemove(K name) {
|
|
V v = getAndRemove(name);
|
|
return v != null ? valueConverter.convertToInt(v) : null;
|
|
}
|
|
|
|
@Override
|
|
public int getIntAndRemove(K name, int defaultValue) {
|
|
Integer v = getIntAndRemove(name);
|
|
return v != null ? v : defaultValue;
|
|
}
|
|
|
|
@Override
|
|
public Long getLongAndRemove(K name) {
|
|
V v = getAndRemove(name);
|
|
return v != null ? valueConverter.convertToLong(v) : null;
|
|
}
|
|
|
|
@Override
|
|
public long getLongAndRemove(K name, long defaultValue) {
|
|
Long v = getLongAndRemove(name);
|
|
return v != null ? v : defaultValue;
|
|
}
|
|
|
|
@Override
|
|
public Float getFloatAndRemove(K name) {
|
|
V v = getAndRemove(name);
|
|
return v != null ? valueConverter.convertToFloat(v) : null;
|
|
}
|
|
|
|
@Override
|
|
public float getFloatAndRemove(K name, float defaultValue) {
|
|
Float v = getFloatAndRemove(name);
|
|
return v != null ? v : defaultValue;
|
|
}
|
|
|
|
@Override
|
|
public Double getDoubleAndRemove(K name) {
|
|
V v = getAndRemove(name);
|
|
return v != null ? valueConverter.convertToDouble(v) : null;
|
|
}
|
|
|
|
@Override
|
|
public double getDoubleAndRemove(K name, double defaultValue) {
|
|
Double v = getDoubleAndRemove(name);
|
|
return v != null ? v : defaultValue;
|
|
}
|
|
|
|
@Override
|
|
public Long getTimeMillisAndRemove(K name) {
|
|
V v = getAndRemove(name);
|
|
return v != null ? valueConverter.convertToTimeMillis(v) : null;
|
|
}
|
|
|
|
@Override
|
|
public long getTimeMillisAndRemove(K name, long defaultValue) {
|
|
Long v = getTimeMillisAndRemove(name);
|
|
return v != null ? v : defaultValue;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
@Override
|
|
public boolean equals(Object o) {
|
|
if (!(o instanceof Headers)) {
|
|
return false;
|
|
}
|
|
|
|
return equals((Headers<K, V, ?>) o, JAVA_HASHER);
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
@Override
|
|
public int hashCode() {
|
|
return hashCode(JAVA_HASHER);
|
|
}
|
|
|
|
/**
|
|
* Test this object for equality against {@code h2}.
|
|
* @param h2 The object to check equality for.
|
|
* @param valueHashingStrategy Defines how values will be compared for equality.
|
|
* @return {@code true} if this object equals {@code h2} given {@code valueHashingStrategy}.
|
|
* {@code false} otherwise.
|
|
*/
|
|
public final boolean equals(Headers<K, V, ?> h2, HashingStrategy<V> valueHashingStrategy) {
|
|
if (h2.size() != size()) {
|
|
return false;
|
|
}
|
|
|
|
if (this == h2) {
|
|
return true;
|
|
}
|
|
|
|
for (K name : names()) {
|
|
List<V> otherValues = h2.getAll(name);
|
|
List<V> values = getAll(name);
|
|
if (otherValues.size() != values.size()) {
|
|
return false;
|
|
}
|
|
for (int i = 0; i < otherValues.size(); i++) {
|
|
if (!valueHashingStrategy.equals(otherValues.get(i), values.get(i))) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Generate a hash code for this object given a {@link HashingStrategy} to generate hash codes for
|
|
* individual values.
|
|
* @param valueHashingStrategy Defines how values will be hashed.
|
|
*/
|
|
public final int hashCode(HashingStrategy<V> valueHashingStrategy) {
|
|
int result = HASH_CODE_SEED;
|
|
for (K name : names()) {
|
|
result = 31 * result + hashingStrategy.hashCode(name);
|
|
List<V> values = getAll(name);
|
|
for (int i = 0; i < values.size(); ++i) {
|
|
result = 31 * result + valueHashingStrategy.hashCode(values.get(i));
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
StringBuilder builder = new StringBuilder(getClass().getSimpleName()).append('[');
|
|
String separator = "";
|
|
for (K name : names()) {
|
|
List<V> values = getAll(name);
|
|
for (int i = 0; i < values.size(); ++i) {
|
|
builder.append(separator);
|
|
builder.append(name).append(": ").append(values.get(i));
|
|
separator = ", ";
|
|
}
|
|
}
|
|
return builder.append(']').toString();
|
|
}
|
|
|
|
protected HeaderEntry<K, V> newHeaderEntry(int h, K name, V value, HeaderEntry<K, V> next) {
|
|
return new HeaderEntry<K, V>(h, name, value, next, head);
|
|
}
|
|
|
|
protected ValueConverter<V> valueConverter() {
|
|
return valueConverter;
|
|
}
|
|
|
|
private int index(int hash) {
|
|
return hash & hashMask;
|
|
}
|
|
|
|
private void add0(int h, int i, K name, V value) {
|
|
// Update the hash table.
|
|
entries[i] = newHeaderEntry(h, name, value, entries[i]);
|
|
++size;
|
|
}
|
|
|
|
/**
|
|
* @return the first value inserted whose hash code equals {@code h} and whose name is equal to {@code name}.
|
|
*/
|
|
private V remove0(int h, int i, K name) {
|
|
HeaderEntry<K, V> e = entries[i];
|
|
if (e == null) {
|
|
return null;
|
|
}
|
|
|
|
V value = null;
|
|
HeaderEntry<K, V> next = e.next;
|
|
while (next != null) {
|
|
if (next.hash == h && hashingStrategy.equals(name, next.key)) {
|
|
value = next.value;
|
|
e.next = next.next;
|
|
next.remove();
|
|
--size;
|
|
} else {
|
|
e = next;
|
|
}
|
|
|
|
next = e.next;
|
|
}
|
|
|
|
e = entries[i];
|
|
if (e.hash == h && hashingStrategy.equals(name, e.key)) {
|
|
if (value == null) {
|
|
value = e.value;
|
|
}
|
|
entries[i] = e.next;
|
|
e.remove();
|
|
--size;
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
private T thisT() {
|
|
return (T) this;
|
|
}
|
|
|
|
private final class HeaderIterator implements Iterator<Map.Entry<K, V>> {
|
|
private HeaderEntry<K, V> current = head;
|
|
|
|
@Override
|
|
public boolean hasNext() {
|
|
return current.after != head;
|
|
}
|
|
|
|
@Override
|
|
public Entry<K, V> next() {
|
|
current = current.after;
|
|
|
|
if (current == head) {
|
|
throw new NoSuchElementException();
|
|
}
|
|
|
|
return current;
|
|
}
|
|
|
|
@Override
|
|
public void remove() {
|
|
throw new UnsupportedOperationException("read-only iterator");
|
|
}
|
|
}
|
|
|
|
protected static class HeaderEntry<K, V> implements Map.Entry<K, V> {
|
|
protected final int hash;
|
|
protected final K key;
|
|
protected V value;
|
|
/**
|
|
* In bucket linked list
|
|
*/
|
|
protected HeaderEntry<K, V> next;
|
|
/**
|
|
* Overall insertion order linked list
|
|
*/
|
|
protected HeaderEntry<K, V> before, after;
|
|
|
|
protected HeaderEntry(int hash, K key) {
|
|
this.hash = hash;
|
|
this.key = key;
|
|
}
|
|
|
|
HeaderEntry(int hash, K key, V value, HeaderEntry<K, V> next, HeaderEntry<K, V> head) {
|
|
this.hash = hash;
|
|
this.key = key;
|
|
this.value = value;
|
|
this.next = next;
|
|
|
|
after = head;
|
|
before = head.before;
|
|
pointNeighborsToThis();
|
|
}
|
|
|
|
HeaderEntry() {
|
|
hash = -1;
|
|
key = null;
|
|
before = after = this;
|
|
}
|
|
|
|
protected final void pointNeighborsToThis() {
|
|
before.after = this;
|
|
after.before = this;
|
|
}
|
|
|
|
public final HeaderEntry<K, V> before() {
|
|
return before;
|
|
}
|
|
|
|
public final HeaderEntry<K, V> after() {
|
|
return after;
|
|
}
|
|
|
|
protected void remove() {
|
|
before.after = after;
|
|
after.before = before;
|
|
}
|
|
|
|
@Override
|
|
public final K getKey() {
|
|
return key;
|
|
}
|
|
|
|
@Override
|
|
public final V getValue() {
|
|
return value;
|
|
}
|
|
|
|
@Override
|
|
public final V setValue(V value) {
|
|
checkNotNull(value, "value");
|
|
V oldValue = this.value;
|
|
this.value = value;
|
|
return oldValue;
|
|
}
|
|
|
|
@Override
|
|
public final String toString() {
|
|
return key.toString() + '=' + value.toString();
|
|
}
|
|
}
|
|
}
|