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
|
@Override
|
||||||
Iterator<Entry<CharSequence, CharSequence>> iterator();
|
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
|
* 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();
|
return new ReadOnlyIterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<CharSequence> valueIterator(CharSequence name) {
|
||||||
|
return new ReadOnlyValueIterator(name);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Http2Headers method(CharSequence value) {
|
public Http2Headers method(CharSequence value) {
|
||||||
throw new UnsupportedOperationException("read only");
|
throw new UnsupportedOperationException("read only");
|
||||||
@ -776,6 +781,58 @@ public final class ReadOnlyHttp2Headers implements Http2Headers {
|
|||||||
return builder.append(']').toString();
|
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>,
|
private final class ReadOnlyIterator implements Map.Entry<CharSequence, CharSequence>,
|
||||||
Iterator<Map.Entry<CharSequence, CharSequence>> {
|
Iterator<Map.Entry<CharSequence, CharSequence>> {
|
||||||
private int i;
|
private int i;
|
||||||
|
@ -20,9 +20,14 @@ import org.junit.Test;
|
|||||||
|
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http2.DefaultHttp2HeadersTest.verifyPseudoHeadersFirst;
|
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 {
|
public class ReadOnlyHttp2HeadersTest {
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
@ -150,6 +155,56 @@ public class ReadOnlyHttp2HeadersTest {
|
|||||||
assertNull(headers.get("a missing header"));
|
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) {
|
private void testIteratorReadOnly(Http2Headers headers) {
|
||||||
Iterator<Map.Entry<CharSequence, CharSequence>> itr = headers.iterator();
|
Iterator<Map.Entry<CharSequence, CharSequence>> itr = headers.iterator();
|
||||||
assertTrue(itr.hasNext());
|
assertTrue(itr.hasNext());
|
||||||
|
@ -30,8 +30,8 @@ import java.util.Set;
|
|||||||
import static io.netty.util.HashingStrategy.JAVA_HASHER;
|
import static io.netty.util.HashingStrategy.JAVA_HASHER;
|
||||||
import static io.netty.util.internal.MathUtil.findNextPositivePowerOfTwo;
|
import static io.netty.util.internal.MathUtil.findNextPositivePowerOfTwo;
|
||||||
import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
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.max;
|
||||||
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default implementation of {@link Headers};
|
* Default implementation of {@link Headers};
|
||||||
@ -174,6 +174,15 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
|
|||||||
return values;
|
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
|
@Override
|
||||||
public List<V> getAllAndRemove(K name) {
|
public List<V> getAllAndRemove(K name) {
|
||||||
List<V> all = getAll(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 static class HeaderEntry<K, V> implements Map.Entry<K, V> {
|
||||||
protected final int hash;
|
protected final int hash;
|
||||||
protected final K key;
|
protected final K key;
|
||||||
|
@ -483,6 +483,16 @@ public class EmptyHeaders<K, V, T extends Headers<K, V, T>> implements Headers<K
|
|||||||
return thisT();
|
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
|
@Override
|
||||||
public Iterator<Entry<K, V>> iterator() {
|
public Iterator<Entry<K, V>> iterator() {
|
||||||
List<Entry<K, V>> empty = Collections.emptyList();
|
List<Entry<K, V>> empty = Collections.emptyList();
|
||||||
|
@ -16,6 +16,7 @@ package io.netty.handler.codec;
|
|||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
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.assertNotSame;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link DefaultHeaders}.
|
* Tests for {@link DefaultHeaders}.
|
||||||
@ -102,6 +104,41 @@ public class DefaultHeadersTest {
|
|||||||
assertTrue(values.containsAll(asList(of("value1"), of("value2"), of("value3"))));
|
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
|
@Test
|
||||||
public void testContains() {
|
public void testContains() {
|
||||||
TestDefaultHeaders headers = newInstance();
|
TestDefaultHeaders headers = newInstance();
|
||||||
|
Loading…
Reference in New Issue
Block a user