Skip to content

Commit 63f3ae5

Browse files
committed
Remove head dummy node in HeadersMultimap
1 parent 776c826 commit 63f3ae5

File tree

2 files changed

+171
-94
lines changed

2 files changed

+171
-94
lines changed

vertx-core/src/main/java/io/vertx/core/http/impl/headers/HeadersMultiMap.java

Lines changed: 130 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,14 @@ public HeadersMultiMap setAll(MultiMap headers) {
8787
if (headers instanceof HeadersMultiMap) {
8888
clear();
8989
HeadersMultiMap that = (HeadersMultiMap) headers;
90-
MapEntry thatHead = that.head;
91-
MapEntry curr = thatHead;
92-
while (curr.after != thatHead) {
93-
MapEntry next = curr.after;
94-
add(next.key, next.value);
95-
curr = next;
90+
MapEntry head = that.head;
91+
MapEntry e = head;
92+
if (e != null) {
93+
do {
94+
add(e.key, e.value);
95+
e = e.after;
96+
}
97+
while (e != head);
9698
}
9799
return this;
98100
} else {
@@ -112,8 +114,8 @@ public int size() {
112114

113115
private final BiConsumer<CharSequence, CharSequence> validator;
114116
private final HeadersMultiMap.MapEntry[] entries = new HeadersMultiMap.MapEntry[16];
115-
private final HeadersMultiMap.MapEntry head = new HeadersMultiMap.MapEntry();
116117
private final boolean readOnly;
118+
private HeadersMultiMap.MapEntry head = null;
117119
private byte[] encoded;
118120

119121
public HeadersMultiMap() {
@@ -125,16 +127,12 @@ public HeadersMultiMap(BiConsumer<CharSequence, CharSequence> validator) {
125127
}
126128

127129
private HeadersMultiMap(boolean mutable, BiConsumer<CharSequence, CharSequence> validator) {
128-
129-
head.before = head.after = head;
130-
131130
this.readOnly = !mutable;
132131
this.validator = validator;
133132
}
134133

135134
private HeadersMultiMap(boolean mutable, HeadersMultiMap that) {
136135

137-
head.before = head.after = head;
138136
setAll((MultiMap) that);
139137

140138
this.readOnly = !mutable;
@@ -373,19 +371,25 @@ public List<String> getAll(String name) {
373371

374372
@Override
375373
public void forEach(Consumer<? super Map.Entry<String, String>> action) {
376-
HeadersMultiMap.MapEntry e = head.after;
377-
while (e != head) {
378-
action.accept(e.stringEntry());
379-
e = e.after;
374+
MapEntry e = head;
375+
if (e != null) {
376+
do {
377+
action.accept(e.stringEntry());
378+
e = e.after;
379+
}
380+
while (e != head);
380381
}
381382
}
382383

383384
@Override
384385
public void forEach(BiConsumer<String, String> action) {
385-
HeadersMultiMap.MapEntry e = head.after;
386-
while (e != head) {
387-
action.accept(e.getKey().toString(), e.getValue().toString());
388-
e = e.after;
386+
MapEntry e = head;
387+
if (e != null) {
388+
do {
389+
action.accept(e.getKey().toString(), e.getValue().toString());
390+
e = e.after;
391+
}
392+
while (e != head);
389393
}
390394
}
391395

@@ -394,56 +398,99 @@ public List<Map.Entry<String, String>> entries() {
394398
return io.vertx.core.http.HttpHeaders.super.entries();
395399
}
396400

401+
private static abstract class IteratorBase<T> implements Iterator<T> {
402+
403+
private final MapEntry head;
404+
private MapEntry curr;
405+
406+
public IteratorBase(MapEntry head) {
407+
this.head = head;
408+
this.curr = head;
409+
}
410+
411+
@Override
412+
public boolean hasNext() {
413+
return curr != null;
414+
}
415+
416+
@Override
417+
public T next() {
418+
if (curr == null) {
419+
throw new NoSuchElementException();
420+
}
421+
MapEntry e = curr;
422+
MapEntry next = e.after;
423+
curr = next == head ? null : next;
424+
return map(e);
425+
}
426+
427+
protected abstract T map(MapEntry e);
428+
429+
}
430+
397431
@Override
398432
public Iterator<Map.Entry<String, String>> iterator() {
399-
return new Iterator<>() {
400-
MapEntry curr = head;
401-
@Override
402-
public boolean hasNext() {
403-
return curr.after != head;
404-
}
405-
@Override
406-
public Map.Entry<String, String> next() {
407-
MapEntry next = curr.after;
408-
if (next == head){
409-
throw new NoSuchElementException();
433+
MapEntry h = head;
434+
if (h == null) {
435+
return Collections.emptyIterator();
436+
} else {
437+
return new IteratorBase<>(h) {
438+
@Override
439+
protected Map.Entry<String, String> map(MapEntry e) {
440+
return new Map.Entry<>() {
441+
@Override
442+
public String getKey() {
443+
return e.key.toString();
444+
}
445+
@Override
446+
public String getValue() {
447+
return e.value.toString();
448+
}
449+
@Override
450+
public String setValue(String value) {
451+
return e.setValue(value).toString();
452+
}
453+
@Override
454+
public String toString() {
455+
return getKey() + "=" + getValue();
456+
}
457+
};
410458
}
411-
curr = next;
412-
return new Map.Entry<>() {
413-
@Override
414-
public String getKey() {
415-
return next.key.toString();
416-
}
417-
@Override
418-
public String getValue() {
419-
return next.value.toString();
420-
}
421-
@Override
422-
public String setValue(String value) {
423-
return next.setValue(value).toString();
424-
}
425-
@Override
426-
public String toString() {
427-
return getKey() + "=" + getValue();
428-
}
429-
};
459+
};
460+
}
461+
}
462+
463+
@Override
464+
public Iterator<Map.Entry<CharSequence, CharSequence>> iteratorCharSequence() {
465+
MapEntry h = head;
466+
if (h == null) {
467+
return Collections.emptyIterator();
468+
}
469+
return new IteratorBase<>(h) {
470+
@Override
471+
protected Map.Entry<CharSequence, CharSequence> map(MapEntry e) {
472+
return e;
430473
}
431474
};
432475
}
433476

434477
@Override
435478
public boolean isEmpty() {
436-
return head == head.after;
479+
return head == null;
437480
}
438481

439482
@Override
440483
public Set<String> names() {
484+
HeadersMultiMap.MapEntry e = head;
485+
if (e == null) {
486+
return Collections.emptySet();
487+
}
441488
Set<String> names = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
442-
HeadersMultiMap.MapEntry e = head.after;
443-
while (e != head) {
489+
do {
444490
names.add(e.getKey().toString());
445491
e = e.after;
446492
}
493+
while (e != head);
447494
return names;
448495
}
449496

@@ -453,7 +500,7 @@ public HeadersMultiMap clear() {
453500
throw new IllegalStateException("Read only");
454501
}
455502
Arrays.fill(entries, null);
456-
head.before = head.after = head;
503+
head = null;
457504
return this;
458505
}
459506

@@ -495,23 +542,6 @@ public long getTimeMillis(CharSequence name, long defaultValue) {
495542
throw new UnsupportedOperationException();
496543
}
497544

498-
@Override
499-
public Iterator<Map.Entry<CharSequence, CharSequence>> iteratorCharSequence() {
500-
return new Iterator<Map.Entry<CharSequence, CharSequence>>() {
501-
HeadersMultiMap.MapEntry current = head.after;
502-
@Override
503-
public boolean hasNext() {
504-
return current != head;
505-
}
506-
@Override
507-
public Map.Entry<CharSequence, CharSequence> next() {
508-
Map.Entry<CharSequence, CharSequence> next = current;
509-
current = current.after;
510-
return next;
511-
}
512-
};
513-
}
514-
515545
@Override
516546
public HttpHeaders addInt(CharSequence name, int value) {
517547
throw new UnsupportedOperationException();
@@ -549,11 +579,16 @@ public void encode(ByteBuf buf) {
549579
}
550580

551581
private void encode0(ByteBuf buf) {
552-
HeadersMultiMap.MapEntry current = head.after;
553-
while (current != head) {
554-
encodeHeader(current.key, current.value, buf);
555-
current = current.after;
582+
MapEntry h = head;
583+
if (h == null) {
584+
return;
556585
}
586+
HeadersMultiMap.MapEntry c = h;
587+
do {
588+
encodeHeader(c.key, c.value, buf);
589+
c = c.after;
590+
}
591+
while (c != h);
557592
}
558593

559594
private static final int COLON_AND_SPACE_SHORT = (COLON << 8) | SP;
@@ -603,20 +638,6 @@ private final class MapEntry implements Map.Entry<CharSequence, CharSequence> {
603638
this.value = value;
604639
}
605640

606-
void remove() {
607-
before.after = after;
608-
after.before = before;
609-
after = null;
610-
before = null;
611-
}
612-
613-
void addBefore(HeadersMultiMap.MapEntry e) {
614-
after = e;
615-
before = e.before;
616-
before.after = this;
617-
after.before = this;
618-
}
619-
620641
@Override
621642
public CharSequence getKey() {
622643
return key;
@@ -671,7 +692,17 @@ private void remove0(int h, int i, CharSequence name) {
671692
} else {
672693
prev.next = next;
673694
}
674-
e.remove();
695+
if (e == head) {
696+
if (head.after == head) {
697+
head = null;
698+
} else {
699+
head = head.after;
700+
}
701+
}
702+
e.before.after = e.after;
703+
e.after.before = e.before;
704+
e.after = null;
705+
e.before = null;
675706
} else {
676707
prev = e;
677708
}
@@ -693,7 +724,16 @@ private void add0(int h, int i, final CharSequence name, final CharSequence valu
693724
newEntry.next = e;
694725

695726
// Update the linked list.
696-
newEntry.addBefore(head);
727+
if (head == null) {
728+
head = newEntry;
729+
newEntry.after = newEntry;
730+
newEntry.before = newEntry;
731+
} else {
732+
newEntry.after = head;
733+
newEntry.before = head.before;
734+
head.before.after = newEntry;
735+
head.before = newEntry;
736+
}
697737
}
698738

699739
private HeadersMultiMap set0(final CharSequence name, final CharSequence strVal) {

vertx-core/src/test/java/io/vertx/tests/http/headers/VertxHttpHeadersTest.java

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,17 @@
1111

1212
package io.vertx.tests.http.headers;
1313

14+
import io.netty.buffer.ByteBuf;
15+
import io.netty.buffer.Unpooled;
1416
import io.netty.util.AsciiString;
1517
import io.vertx.core.MultiMap;
1618
import io.vertx.core.http.impl.headers.HeadersMultiMap;
1719
import org.junit.Test;
1820

19-
import java.util.ArrayList;
20-
import java.util.Arrays;
21-
import java.util.HashMap;
21+
import java.nio.charset.StandardCharsets;
22+
import java.util.*;
2223
import java.util.function.Consumer;
2324
import java.util.function.Function;
24-
import java.util.Set;
2525

2626
import static io.vertx.tests.http.impl.HttpUtilsTest.HEADER_NAME_ALLOWED_CHARS;
2727
import static org.junit.Assert.*;
@@ -321,4 +321,41 @@ public void testMutability() {
321321
headers.set("bar", Arrays.asList("bar1", "bar2"));
322322
HeadersMultiMap copy = headers.copy(false);
323323
}
324+
325+
@Test
326+
public void testListIterator() {
327+
HeadersMultiMap headers = newMultiMap();
328+
headers.add("foo", "foo1");
329+
headers.add("bar", "bar1");
330+
headers.add("bar", "bar2");
331+
Map.Entry<CharSequence, CharSequence> entry;
332+
Iterator<Map.Entry<CharSequence, CharSequence>> it = headers.iteratorCharSequence();
333+
assertTrue(it.hasNext());
334+
entry = it.next();
335+
assertEquals("foo", entry.getKey().toString());
336+
assertEquals("foo1", entry.getValue().toString());
337+
assertTrue(it.hasNext());
338+
entry = it.next();
339+
assertEquals("bar", entry.getKey().toString());
340+
assertEquals("bar1", entry.getValue().toString());
341+
assertTrue(it.hasNext());
342+
entry = it.next();
343+
assertEquals("bar", entry.getKey().toString());
344+
assertEquals("bar2", entry.getValue().toString());
345+
assertFalse(it.hasNext());
346+
}
347+
348+
@Test
349+
public void testEncode() {
350+
HeadersMultiMap headers = newMultiMap();
351+
headers.add("foo", "foo1");
352+
headers.add("bar", "bar1");
353+
headers.add("bar", "bar2");
354+
ByteBuf buf = Unpooled.buffer();
355+
headers.encode(buf);
356+
assertEquals(
357+
"foo: foo1\r\n" +
358+
"bar: bar1\r\n" +
359+
"bar: bar2\r\n", buf.toString(StandardCharsets.UTF_8));
360+
}
324361
}

0 commit comments

Comments
 (0)