DefaultHeaders value iterator
Motivation: The Headers interface supports an interface to get all the headers values corresponding to a particular name. This API returns a List which requires intermediate storage and increases GC pressure. Modifications: - Add a method which returns an iterator over all the values for a specific name Result: Ability to iterator over values for a specific name with no intermediate collection.
This commit is contained in:
parent
b32cd26a96
commit
44bb3b6f3a
@ -90,6 +90,13 @@ public interface Http2Headers extends Headers<CharSequence, CharSequence, Http2H
|
||||
@Override
|
||||
Iterator<Entry<CharSequence, CharSequence>> iterator();
|
||||
|
||||
/**
|
||||
* Equivalent to {@link #getAll(Object)} but no intermediate list is generated.
|
||||
* @param name the name of the header to retrieve
|
||||
* @return an {@link Iterator} of header values corresponding to {@code name}.
|
||||
*/
|
||||
Iterator<CharSequence> valueIterator(CharSequence name);
|
||||
|
||||
/**
|
||||
* Sets the {@link PseudoHeaderName#METHOD} header or {@code null} if there is no such header
|
||||
*/
|
||||
|
@ -714,6 +714,11 @@ public final class ReadOnlyHttp2Headers implements Http2Headers {
|
||||
return new ReadOnlyIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<CharSequence> valueIterator(CharSequence name) {
|
||||
return new ReadOnlyValueIterator(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Http2Headers method(CharSequence value) {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
@ -776,6 +781,58 @@ public final class ReadOnlyHttp2Headers implements Http2Headers {
|
||||
return builder.append(']').toString();
|
||||
}
|
||||
|
||||
private final class ReadOnlyValueIterator implements Iterator<CharSequence> {
|
||||
private int i;
|
||||
private final int nameHash;
|
||||
private final CharSequence name;
|
||||
private AsciiString[] current = pseudoHeaders.length != 0 ? pseudoHeaders : otherHeaders;
|
||||
private AsciiString next;
|
||||
|
||||
ReadOnlyValueIterator(CharSequence name) {
|
||||
nameHash = AsciiString.hashCode(name);
|
||||
this.name = name;
|
||||
calculateNext();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return next != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CharSequence next() {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
CharSequence current = next;
|
||||
calculateNext();
|
||||
return current;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
private void calculateNext() {
|
||||
for (; i < current.length; i += 2) {
|
||||
AsciiString roName = current[i];
|
||||
if (roName.hashCode() == nameHash && roName.contentEqualsIgnoreCase(name)) {
|
||||
next = current[i + 1];
|
||||
i += 2;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (i >= current.length && current == pseudoHeaders) {
|
||||
i = 0;
|
||||
current = otherHeaders;
|
||||
calculateNext();
|
||||
} else {
|
||||
next = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final class ReadOnlyIterator implements Map.Entry<CharSequence, CharSequence>,
|
||||
Iterator<Map.Entry<CharSequence, CharSequence>> {
|
||||
private int i;
|
||||
|
@ -20,9 +20,14 @@ import org.junit.Test;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
import static io.netty.handler.codec.http2.DefaultHttp2HeadersTest.verifyPseudoHeadersFirst;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
public class ReadOnlyHttp2HeadersTest {
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@ -150,6 +155,56 @@ public class ReadOnlyHttp2HeadersTest {
|
||||
assertNull(headers.get("a missing header"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientOtherValueIterator() {
|
||||
testValueIteratorSingleValue(newClientHeaders(), "name2", "value2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testClientPsuedoValueIterator() {
|
||||
testValueIteratorSingleValue(newClientHeaders(), ":path", "/foo");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testServerPsuedoValueIterator() {
|
||||
testValueIteratorSingleValue(newServerHeaders(), ":status", "200");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEmptyValueIterator() {
|
||||
Http2Headers headers = newServerHeaders();
|
||||
Iterator<CharSequence> itr = headers.valueIterator("foo");
|
||||
assertFalse(itr.hasNext());
|
||||
try {
|
||||
itr.next();
|
||||
fail();
|
||||
} catch (NoSuchElementException ignored) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIteratorMultipleValues() {
|
||||
Http2Headers headers = ReadOnlyHttp2Headers.serverHeaders(false, new AsciiString("200"), new AsciiString[] {
|
||||
new AsciiString("name2"), new AsciiString("value1"),
|
||||
new AsciiString("name1"), new AsciiString("value2"),
|
||||
new AsciiString("name2"), new AsciiString("value3")
|
||||
});
|
||||
Iterator<CharSequence> itr = headers.valueIterator("name2");
|
||||
assertTrue(itr.hasNext());
|
||||
assertTrue(AsciiString.contentEqualsIgnoreCase("value1", itr.next()));
|
||||
assertTrue(itr.hasNext());
|
||||
assertTrue(AsciiString.contentEqualsIgnoreCase("value3", itr.next()));
|
||||
assertFalse(itr.hasNext());
|
||||
}
|
||||
|
||||
private void testValueIteratorSingleValue(Http2Headers headers, CharSequence name, CharSequence value) {
|
||||
Iterator<CharSequence> itr = headers.valueIterator(name);
|
||||
assertTrue(itr.hasNext());
|
||||
assertTrue(AsciiString.contentEqualsIgnoreCase(value, itr.next()));
|
||||
assertFalse(itr.hasNext());
|
||||
}
|
||||
|
||||
private void testIteratorReadOnly(Http2Headers headers) {
|
||||
Iterator<Map.Entry<CharSequence, CharSequence>> itr = headers.iterator();
|
||||
assertTrue(itr.hasNext());
|
||||
|
@ -30,8 +30,8 @@ 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;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link Headers};
|
||||
@ -174,6 +174,15 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@link #getAll(Object)} but no intermediate list is generated.
|
||||
* @param name the name of the header to retrieve
|
||||
* @return an {@link Iterator} of header values corresponding to {@code name}.
|
||||
*/
|
||||
public Iterator<V> valueIterator(K name) {
|
||||
return new ValueIterator(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<V> getAllAndRemove(K name) {
|
||||
List<V> all = getAll(name);
|
||||
@ -978,6 +987,49 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
|
||||
}
|
||||
}
|
||||
|
||||
private final class ValueIterator implements Iterator<V> {
|
||||
private final K name;
|
||||
private final int hash;
|
||||
private HeaderEntry<K, V> next;
|
||||
|
||||
ValueIterator(K name) {
|
||||
this.name = checkNotNull(name, "name");
|
||||
hash = hashingStrategy.hashCode(name);
|
||||
calculateNext(entries[index(hash)]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return next != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V next() {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
HeaderEntry<K, V> current = next;
|
||||
calculateNext(next.next);
|
||||
return current.value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException("read only");
|
||||
}
|
||||
|
||||
private void calculateNext(HeaderEntry<K, V> entry) {
|
||||
while (entry != null) {
|
||||
if (entry.hash == hash && hashingStrategy.equals(name, entry.key)) {
|
||||
next = entry;
|
||||
return;
|
||||
}
|
||||
entry = entry.next;
|
||||
}
|
||||
next = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected static class HeaderEntry<K, V> implements Map.Entry<K, V> {
|
||||
protected final int hash;
|
||||
protected final K key;
|
||||
|
@ -483,6 +483,16 @@ public class EmptyHeaders<K, V, T extends Headers<K, V, T>> implements Headers<K
|
||||
return thisT();
|
||||
}
|
||||
|
||||
/**
|
||||
* Equivalent to {@link #getAll(Object)} but no intermediate list is generated.
|
||||
* @param name the name of the header to retrieve
|
||||
* @return an {@link Iterator} of header values corresponding to {@code name}.
|
||||
*/
|
||||
public Iterator<V> valueIterator(@SuppressWarnings("unused") K name) {
|
||||
List<V> empty = Collections.emptyList();
|
||||
return empty.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<K, V>> iterator() {
|
||||
List<Entry<K, V>> empty = Collections.emptyList();
|
||||
|
@ -16,6 +16,7 @@ package io.netty.handler.codec;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -30,6 +31,7 @@ import static org.junit.Assert.assertNotEquals;
|
||||
import static org.junit.Assert.assertNotSame;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* Tests for {@link DefaultHeaders}.
|
||||
@ -102,6 +104,41 @@ public class DefaultHeadersTest {
|
||||
assertTrue(values.containsAll(asList(of("value1"), of("value2"), of("value3"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleValuesPerNameIterator() {
|
||||
TestDefaultHeaders headers = newInstance();
|
||||
headers.add(of("name"), of("value1"));
|
||||
headers.add(of("name"), of("value2"));
|
||||
headers.add(of("name"), of("value3"));
|
||||
assertEquals(3, headers.size());
|
||||
|
||||
List<CharSequence> values = new ArrayList<CharSequence>();
|
||||
Iterator<CharSequence> itr = headers.valueIterator(of("name"));
|
||||
while (itr.hasNext()) {
|
||||
values.add(itr.next());
|
||||
}
|
||||
assertEquals(3, values.size());
|
||||
assertTrue(values.containsAll(asList(of("value1"), of("value2"), of("value3"))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleValuesPerNameIteratorEmpty() {
|
||||
TestDefaultHeaders headers = newInstance();
|
||||
|
||||
List<CharSequence> values = new ArrayList<CharSequence>();
|
||||
Iterator<CharSequence> itr = headers.valueIterator(of("name"));
|
||||
while (itr.hasNext()) {
|
||||
values.add(itr.next());
|
||||
}
|
||||
assertEquals(0, values.size());
|
||||
try {
|
||||
itr.next();
|
||||
fail();
|
||||
} catch (NoSuchElementException ignored) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testContains() {
|
||||
TestDefaultHeaders headers = newInstance();
|
||||
|
Loading…
Reference in New Issue
Block a user