DefaultHeaders#valueIterator to support removal (#9063)

Motivation:
While iterating values it is often desirable to be able to remove individual
entries. The existing mechanism to do this involves removal of all entries and
conditional re-insertion which is heavy weight in order to remove a single
value.

Modifications:
- DefaultHeaders$ValueIterator supports removal

Result:
It is possible to remove entries while iterating the values in DefaultHeaders.
This commit is contained in:
Scott Mitchell 2019-04-16 10:37:34 -07:00 committed by Norman Maurer
parent 9ed41db1d7
commit 1d9090aab2
2 changed files with 56 additions and 3 deletions

View File

@ -1012,6 +1012,16 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
return value; return value;
} }
private void remove0(HeaderEntry<K, V> entry) {
int i = index(entry.hash);
HeaderEntry<K, V> e = entries[i];
if (e == entry) {
entries[i] = entry.next;
}
entry.remove();
--size;
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private T thisT() { private T thisT() {
return (T) this; return (T) this;
@ -1055,6 +1065,7 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
private final class ValueIterator implements Iterator<V> { private final class ValueIterator implements Iterator<V> {
private final K name; private final K name;
private final int hash; private final int hash;
private HeaderEntry<K, V> previous;
private HeaderEntry<K, V> next; private HeaderEntry<K, V> next;
ValueIterator(K name) { ValueIterator(K name) {
@ -1073,14 +1084,18 @@ public class DefaultHeaders<K, V, T extends Headers<K, V, T>> implements Headers
if (!hasNext()) { if (!hasNext()) {
throw new NoSuchElementException(); throw new NoSuchElementException();
} }
HeaderEntry<K, V> current = next; previous = next;
calculateNext(next.next); calculateNext(next.next);
return current.value; return previous.value;
} }
@Override @Override
public void remove() { public void remove() {
throw new UnsupportedOperationException("read only"); if (previous == null) {
throw new IllegalStateException();
}
remove0(previous);
previous = null;
} }
private void calculateNext(HeaderEntry<K, V> entry) { private void calculateNext(HeaderEntry<K, V> entry) {

View File

@ -121,9 +121,47 @@ public class DefaultHeadersTest {
Iterator<CharSequence> itr = headers.valueIterator(of("name")); Iterator<CharSequence> itr = headers.valueIterator(of("name"));
while (itr.hasNext()) { while (itr.hasNext()) {
values.add(itr.next()); values.add(itr.next());
itr.remove();
} }
assertEquals(3, values.size()); assertEquals(3, values.size());
assertEquals(0, headers.size());
assertTrue(headers.isEmpty());
assertTrue(values.containsAll(asList(of("value1"), of("value2"), of("value3")))); assertTrue(values.containsAll(asList(of("value1"), of("value2"), of("value3"))));
itr = headers.valueIterator(of("name"));
assertFalse(itr.hasNext());
}
@Test(expected = IllegalStateException.class)
public void valuesItrRemoveThrowsWhenEmpty() {
TestDefaultHeaders headers = newInstance();
assertEquals(0, headers.size());
assertTrue(headers.isEmpty());
Iterator<CharSequence> itr = headers.valueIterator(of("name"));
itr.remove();
}
@Test
public void valuesItrRemoveThrowsAfterLastElement() {
TestDefaultHeaders headers = newInstance();
headers.add(of("name"), of("value1"));
assertEquals(1, headers.size());
List<CharSequence> values = new ArrayList<CharSequence>();
Iterator<CharSequence> itr = headers.valueIterator(of("name"));
while (itr.hasNext()) {
values.add(itr.next());
itr.remove();
}
assertEquals(1, values.size());
assertEquals(0, headers.size());
assertTrue(headers.isEmpty());
assertTrue(values.contains(of("value1")));
try {
itr.remove();
fail();
} catch (IllegalStateException ignored) {
// ignored
}
} }
@Test @Test