/*
* Copyright 2016 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.http2;
import io.netty.handler.codec.Headers;
import io.netty.util.AsciiString;
import io.netty.util.HashingStrategy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import static io.netty.handler.codec.CharSequenceValueConverter.*;
import static io.netty.handler.codec.http2.DefaultHttp2Headers.*;
import static io.netty.util.AsciiString.*;
import static io.netty.util.internal.EmptyArrays.*;
A variant of Http2Headers
which only supports read-only methods.
Any array passed to this class may be used directly in the underlying data structures of this class. If these
arrays may be modified it is the caller's responsibility to supply this class with a copy of the array.
This may be a good alternative to DefaultHttp2Headers
if your have a fixed set of headers which will not change.
/**
* A variant of {@link Http2Headers} which only supports read-only methods.
* <p>
* Any array passed to this class may be used directly in the underlying data structures of this class. If these
* arrays may be modified it is the caller's responsibility to supply this class with a copy of the array.
* <p>
* This may be a good alternative to {@link DefaultHttp2Headers} if your have a fixed set of headers which will not
* change.
*/
public final class ReadOnlyHttp2Headers implements Http2Headers {
private static final byte PSEUDO_HEADER_TOKEN = (byte) ':';
private final AsciiString[] pseudoHeaders;
private final AsciiString[] otherHeaders;
Used to create read only object designed to represent trailers.
If this is used for a purpose other than trailers you may violate the header serialization ordering defined by
RFC 7540, 8.1.2.1.
Params: - validateHeaders –
true
will run validation on each header name/value pair to ensure protocol compliance. - otherHeaders – A an array of key:value pairs. Must not contain any
pseudo headers or
null
names/values. A copy will NOT be made of this array. If the contents of this array
may be modified externally you are responsible for passing in a copy.
Returns: A read only representation of the headers.
/**
* Used to create read only object designed to represent trailers.
* <p>
* If this is used for a purpose other than trailers you may violate the header serialization ordering defined by
* <a href="https://tools.ietf.org/html/rfc7540#section-8.1.2.1">RFC 7540, 8.1.2.1</a>.
* @param validateHeaders {@code true} will run validation on each header name/value pair to ensure protocol
* compliance.
* @param otherHeaders A an array of key:value pairs. Must not contain any
* <a href="https://tools.ietf.org/html/rfc7540#section-8.1.2.1">pseudo headers</a>
* or {@code null} names/values.
* A copy will <strong>NOT</strong> be made of this array. If the contents of this array
* may be modified externally you are responsible for passing in a copy.
* @return A read only representation of the headers.
*/
public static ReadOnlyHttp2Headers trailers(boolean validateHeaders, AsciiString... otherHeaders) {
return new ReadOnlyHttp2Headers(validateHeaders, EMPTY_ASCII_STRINGS, otherHeaders);
}
Create a new read only representation of headers used by clients.
Params: - validateHeaders –
true
will run validation on each header name/value pair to ensure protocol compliance. - method – The value for
PseudoHeaderName.METHOD
. - path – The value for
PseudoHeaderName.PATH
. - scheme – The value for
PseudoHeaderName.SCHEME
. - authority – The value for
PseudoHeaderName.AUTHORITY
. - otherHeaders – A an array of key:value pairs. Must not contain any
pseudo headers or
null
names/values. A copy will NOT be made of this array. If the contents of this array
may be modified externally you are responsible for passing in a copy.
Returns: a new read only representation of headers used by clients.
/**
* Create a new read only representation of headers used by clients.
* @param validateHeaders {@code true} will run validation on each header name/value pair to ensure protocol
* compliance.
* @param method The value for {@link PseudoHeaderName#METHOD}.
* @param path The value for {@link PseudoHeaderName#PATH}.
* @param scheme The value for {@link PseudoHeaderName#SCHEME}.
* @param authority The value for {@link PseudoHeaderName#AUTHORITY}.
* @param otherHeaders A an array of key:value pairs. Must not contain any
* <a href="https://tools.ietf.org/html/rfc7540#section-8.1.2.1">pseudo headers</a>
* or {@code null} names/values.
* A copy will <strong>NOT</strong> be made of this array. If the contents of this array
* may be modified externally you are responsible for passing in a copy.
* @return a new read only representation of headers used by clients.
*/
public static ReadOnlyHttp2Headers clientHeaders(boolean validateHeaders,
AsciiString method, AsciiString path,
AsciiString scheme, AsciiString authority,
AsciiString... otherHeaders) {
return new ReadOnlyHttp2Headers(validateHeaders,
new AsciiString[] {
PseudoHeaderName.METHOD.value(), method, PseudoHeaderName.PATH.value(), path,
PseudoHeaderName.SCHEME.value(), scheme, PseudoHeaderName.AUTHORITY.value(), authority
},
otherHeaders);
}
Create a new read only representation of headers used by servers.
Params: - validateHeaders –
true
will run validation on each header name/value pair to ensure protocol compliance. - status – The value for
PseudoHeaderName.STATUS
. - otherHeaders – A an array of key:value pairs. Must not contain any
pseudo headers or
null
names/values. A copy will NOT be made of this array. If the contents of this array
may be modified externally you are responsible for passing in a copy.
Returns: a new read only representation of headers used by servers.
/**
* Create a new read only representation of headers used by servers.
* @param validateHeaders {@code true} will run validation on each header name/value pair to ensure protocol
* compliance.
* @param status The value for {@link PseudoHeaderName#STATUS}.
* @param otherHeaders A an array of key:value pairs. Must not contain any
* <a href="https://tools.ietf.org/html/rfc7540#section-8.1.2.1">pseudo headers</a>
* or {@code null} names/values.
* A copy will <strong>NOT</strong> be made of this array. If the contents of this array
* may be modified externally you are responsible for passing in a copy.
* @return a new read only representation of headers used by servers.
*/
public static ReadOnlyHttp2Headers serverHeaders(boolean validateHeaders,
AsciiString status,
AsciiString... otherHeaders) {
return new ReadOnlyHttp2Headers(validateHeaders,
new AsciiString[] { PseudoHeaderName.STATUS.value(), status },
otherHeaders);
}
private ReadOnlyHttp2Headers(boolean validateHeaders, AsciiString[] pseudoHeaders, AsciiString... otherHeaders) {
assert (pseudoHeaders.length & 1) == 0; // pseudoHeaders are only set internally so assert should be enough.
if ((otherHeaders.length & 1) != 0) {
throw newInvalidArraySizeException();
}
if (validateHeaders) {
validateHeaders(pseudoHeaders, otherHeaders);
}
this.pseudoHeaders = pseudoHeaders;
this.otherHeaders = otherHeaders;
}
private static IllegalArgumentException newInvalidArraySizeException() {
return new IllegalArgumentException("pseudoHeaders and otherHeaders must be arrays of [name, value] pairs");
}
private static void validateHeaders(AsciiString[] pseudoHeaders, AsciiString... otherHeaders) {
// We are only validating values... so start at 1 and go until end.
for (int i = 1; i < pseudoHeaders.length; i += 2) {
// pseudoHeaders names are only set internally so they are assumed to be valid.
if (pseudoHeaders[i] == null) {
throw new IllegalArgumentException("pseudoHeaders value at index " + i + " is null");
}
}
boolean seenNonPseudoHeader = false;
final int otherHeadersEnd = otherHeaders.length - 1;
for (int i = 0; i < otherHeadersEnd; i += 2) {
AsciiString name = otherHeaders[i];
HTTP2_NAME_VALIDATOR.validateName(name);
if (!seenNonPseudoHeader && !name.isEmpty() && name.byteAt(0) != PSEUDO_HEADER_TOKEN) {
seenNonPseudoHeader = true;
} else if (seenNonPseudoHeader && !name.isEmpty() && name.byteAt(0) == PSEUDO_HEADER_TOKEN) {
throw new IllegalArgumentException(
"otherHeaders name at index " + i + " is a pseudo header that appears after non-pseudo headers.");
}
if (otherHeaders[i + 1] == null) {
throw new IllegalArgumentException("otherHeaders value at index " + (i + 1) + " is null");
}
}
}
private AsciiString get0(CharSequence name) {
final int nameHash = AsciiString.hashCode(name);
final int pseudoHeadersEnd = pseudoHeaders.length - 1;
for (int i = 0; i < pseudoHeadersEnd; i += 2) {
AsciiString roName = pseudoHeaders[i];
if (roName.hashCode() == nameHash && roName.contentEqualsIgnoreCase(name)) {
return pseudoHeaders[i + 1];
}
}
final int otherHeadersEnd = otherHeaders.length - 1;
for (int i = 0; i < otherHeadersEnd; i += 2) {
AsciiString roName = otherHeaders[i];
if (roName.hashCode() == nameHash && roName.contentEqualsIgnoreCase(name)) {
return otherHeaders[i + 1];
}
}
return null;
}
@Override
public CharSequence get(CharSequence name) {
return get0(name);
}
@Override
public CharSequence get(CharSequence name, CharSequence defaultValue) {
CharSequence value = get(name);
return value != null ? value : defaultValue;
}
@Override
public CharSequence getAndRemove(CharSequence name) {
throw new UnsupportedOperationException("read only");
}
@Override
public CharSequence getAndRemove(CharSequence name, CharSequence defaultValue) {
throw new UnsupportedOperationException("read only");
}
@Override
public List<CharSequence> getAll(CharSequence name) {
final int nameHash = AsciiString.hashCode(name);
List<CharSequence> values = new ArrayList<CharSequence>();
final int pseudoHeadersEnd = pseudoHeaders.length - 1;
for (int i = 0; i < pseudoHeadersEnd; i += 2) {
AsciiString roName = pseudoHeaders[i];
if (roName.hashCode() == nameHash && roName.contentEqualsIgnoreCase(name)) {
values.add(pseudoHeaders[i + 1]);
}
}
final int otherHeadersEnd = otherHeaders.length - 1;
for (int i = 0; i < otherHeadersEnd; i += 2) {
AsciiString roName = otherHeaders[i];
if (roName.hashCode() == nameHash && roName.contentEqualsIgnoreCase(name)) {
values.add(otherHeaders[i + 1]);
}
}
return values;
}
@Override
public List<CharSequence> getAllAndRemove(CharSequence name) {
throw new UnsupportedOperationException("read only");
}
@Override
public Boolean getBoolean(CharSequence name) {
AsciiString value = get0(name);
return value != null ? INSTANCE.convertToBoolean(value) : null;
}
@Override
public boolean getBoolean(CharSequence name, boolean defaultValue) {
Boolean value = getBoolean(name);
return value != null ? value : defaultValue;
}
@Override
public Byte getByte(CharSequence name) {
AsciiString value = get0(name);
return value != null ? INSTANCE.convertToByte(value) : null;
}
@Override
public byte getByte(CharSequence name, byte defaultValue) {
Byte value = getByte(name);
return value != null ? value : defaultValue;
}
@Override
public Character getChar(CharSequence name) {
AsciiString value = get0(name);
return value != null ? INSTANCE.convertToChar(value) : null;
}
@Override
public char getChar(CharSequence name, char defaultValue) {
Character value = getChar(name);
return value != null ? value : defaultValue;
}
@Override
public Short getShort(CharSequence name) {
AsciiString value = get0(name);
return value != null ? INSTANCE.convertToShort(value) : null;
}
@Override
public short getShort(CharSequence name, short defaultValue) {
Short value = getShort(name);
return value != null ? value : defaultValue;
}
@Override
public Integer getInt(CharSequence name) {
AsciiString value = get0(name);
return value != null ? INSTANCE.convertToInt(value) : null;
}
@Override
public int getInt(CharSequence name, int defaultValue) {
Integer value = getInt(name);
return value != null ? value : defaultValue;
}
@Override
public Long getLong(CharSequence name) {
AsciiString value = get0(name);
return value != null ? INSTANCE.convertToLong(value) : null;
}
@Override
public long getLong(CharSequence name, long defaultValue) {
Long value = getLong(name);
return value != null ? value : defaultValue;
}
@Override
public Float getFloat(CharSequence name) {
AsciiString value = get0(name);
return value != null ? INSTANCE.convertToFloat(value) : null;
}
@Override
public float getFloat(CharSequence name, float defaultValue) {
Float value = getFloat(name);
return value != null ? value : defaultValue;
}
@Override
public Double getDouble(CharSequence name) {
AsciiString value = get0(name);
return value != null ? INSTANCE.convertToDouble(value) : null;
}
@Override
public double getDouble(CharSequence name, double defaultValue) {
Double value = getDouble(name);
return value != null ? value : defaultValue;
}
@Override
public Long getTimeMillis(CharSequence name) {
AsciiString value = get0(name);
return value != null ? INSTANCE.convertToTimeMillis(value) : null;
}
@Override
public long getTimeMillis(CharSequence name, long defaultValue) {
Long value = getTimeMillis(name);
return value != null ? value : defaultValue;
}
@Override
public Boolean getBooleanAndRemove(CharSequence name) {
throw new UnsupportedOperationException("read only");
}
@Override
public boolean getBooleanAndRemove(CharSequence name, boolean defaultValue) {
throw new UnsupportedOperationException("read only");
}
@Override
public Byte getByteAndRemove(CharSequence name) {
throw new UnsupportedOperationException("read only");
}
@Override
public byte getByteAndRemove(CharSequence name, byte defaultValue) {
throw new UnsupportedOperationException("read only");
}
@Override
public Character getCharAndRemove(CharSequence name) {
throw new UnsupportedOperationException("read only");
}
@Override
public char getCharAndRemove(CharSequence name, char defaultValue) {
throw new UnsupportedOperationException("read only");
}
@Override
public Short getShortAndRemove(CharSequence name) {
throw new UnsupportedOperationException("read only");
}
@Override
public short getShortAndRemove(CharSequence name, short defaultValue) {
throw new UnsupportedOperationException("read only");
}
@Override
public Integer getIntAndRemove(CharSequence name) {
throw new UnsupportedOperationException("read only");
}
@Override
public int getIntAndRemove(CharSequence name, int defaultValue) {
throw new UnsupportedOperationException("read only");
}
@Override
public Long getLongAndRemove(CharSequence name) {
throw new UnsupportedOperationException("read only");
}
@Override
public long getLongAndRemove(CharSequence name, long defaultValue) {
throw new UnsupportedOperationException("read only");
}
@Override
public Float getFloatAndRemove(CharSequence name) {
throw new UnsupportedOperationException("read only");
}
@Override
public float getFloatAndRemove(CharSequence name, float defaultValue) {
throw new UnsupportedOperationException("read only");
}
@Override
public Double getDoubleAndRemove(CharSequence name) {
throw new UnsupportedOperationException("read only");
}
@Override
public double getDoubleAndRemove(CharSequence name, double defaultValue) {
throw new UnsupportedOperationException("read only");
}
@Override
public Long getTimeMillisAndRemove(CharSequence name) {
throw new UnsupportedOperationException("read only");
}
@Override
public long getTimeMillisAndRemove(CharSequence name, long defaultValue) {
throw new UnsupportedOperationException("read only");
}
@Override
public boolean contains(CharSequence name) {
return get(name) != null;
}
@Override
public boolean contains(CharSequence name, CharSequence value) {
return contains(name, value, false);
}
@Override
public boolean containsObject(CharSequence name, Object value) {
if (value instanceof CharSequence) {
return contains(name, (CharSequence) value);
}
return contains(name, value.toString());
}
@Override
public boolean containsBoolean(CharSequence name, boolean value) {
return contains(name, String.valueOf(value));
}
@Override
public boolean containsByte(CharSequence name, byte value) {
return contains(name, String.valueOf(value));
}
@Override
public boolean containsChar(CharSequence name, char value) {
return contains(name, String.valueOf(value));
}
@Override
public boolean containsShort(CharSequence name, short value) {
return contains(name, String.valueOf(value));
}
@Override
public boolean containsInt(CharSequence name, int value) {
return contains(name, String.valueOf(value));
}
@Override
public boolean containsLong(CharSequence name, long value) {
return contains(name, String.valueOf(value));
}
@Override
public boolean containsFloat(CharSequence name, float value) {
return false;
}
@Override
public boolean containsDouble(CharSequence name, double value) {
return contains(name, String.valueOf(value));
}
@Override
public boolean containsTimeMillis(CharSequence name, long value) {
return contains(name, String.valueOf(value));
}
@Override
public int size() {
return (pseudoHeaders.length + otherHeaders.length) >>> 1;
}
@Override
public boolean isEmpty() {
return pseudoHeaders.length == 0 && otherHeaders.length == 0;
}
@Override
public Set<CharSequence> names() {
if (isEmpty()) {
return Collections.emptySet();
}
Set<CharSequence> names = new LinkedHashSet<CharSequence>(size());
final int pseudoHeadersEnd = pseudoHeaders.length - 1;
for (int i = 0; i < pseudoHeadersEnd; i += 2) {
names.add(pseudoHeaders[i]);
}
final int otherHeadersEnd = otherHeaders.length - 1;
for (int i = 0; i < otherHeadersEnd; i += 2) {
names.add(otherHeaders[i]);
}
return names;
}
@Override
public Http2Headers add(CharSequence name, CharSequence value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers add(CharSequence name, Iterable<? extends CharSequence> values) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers add(CharSequence name, CharSequence... values) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers addObject(CharSequence name, Object value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers addObject(CharSequence name, Iterable<?> values) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers addObject(CharSequence name, Object... values) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers addBoolean(CharSequence name, boolean value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers addByte(CharSequence name, byte value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers addChar(CharSequence name, char value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers addShort(CharSequence name, short value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers addInt(CharSequence name, int value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers addLong(CharSequence name, long value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers addFloat(CharSequence name, float value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers addDouble(CharSequence name, double value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers addTimeMillis(CharSequence name, long value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers add(Headers<? extends CharSequence, ? extends CharSequence, ?> headers) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers set(CharSequence name, CharSequence value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers set(CharSequence name, Iterable<? extends CharSequence> values) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers set(CharSequence name, CharSequence... values) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers setObject(CharSequence name, Object value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers setObject(CharSequence name, Iterable<?> values) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers setObject(CharSequence name, Object... values) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers setBoolean(CharSequence name, boolean value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers setByte(CharSequence name, byte value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers setChar(CharSequence name, char value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers setShort(CharSequence name, short value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers setInt(CharSequence name, int value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers setLong(CharSequence name, long value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers setFloat(CharSequence name, float value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers setDouble(CharSequence name, double value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers setTimeMillis(CharSequence name, long value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers set(Headers<? extends CharSequence, ? extends CharSequence, ?> headers) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers setAll(Headers<? extends CharSequence, ? extends CharSequence, ?> headers) {
throw new UnsupportedOperationException("read only");
}
@Override
public boolean remove(CharSequence name) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers clear() {
throw new UnsupportedOperationException("read only");
}
@Override
public Iterator<Map.Entry<CharSequence, CharSequence>> iterator() {
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");
}
@Override
public Http2Headers scheme(CharSequence value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers authority(CharSequence value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers path(CharSequence value) {
throw new UnsupportedOperationException("read only");
}
@Override
public Http2Headers status(CharSequence value) {
throw new UnsupportedOperationException("read only");
}
@Override
public CharSequence method() {
return get(PseudoHeaderName.METHOD.value());
}
@Override
public CharSequence scheme() {
return get(PseudoHeaderName.SCHEME.value());
}
@Override
public CharSequence authority() {
return get(PseudoHeaderName.AUTHORITY.value());
}
@Override
public CharSequence path() {
return get(PseudoHeaderName.PATH.value());
}
@Override
public CharSequence status() {
return get(PseudoHeaderName.STATUS.value());
}
@Override
public boolean contains(CharSequence name, CharSequence value, boolean caseInsensitive) {
final int nameHash = AsciiString.hashCode(name);
final HashingStrategy<CharSequence> strategy =
caseInsensitive ? CASE_INSENSITIVE_HASHER : CASE_SENSITIVE_HASHER;
final int valueHash = strategy.hashCode(value);
return contains(name, nameHash, value, valueHash, strategy, otherHeaders)
|| contains(name, nameHash, value, valueHash, strategy, pseudoHeaders);
}
private static boolean contains(CharSequence name, int nameHash, CharSequence value, int valueHash,
HashingStrategy<CharSequence> hashingStrategy, AsciiString[] headers) {
final int headersEnd = headers.length - 1;
for (int i = 0; i < headersEnd; i += 2) {
AsciiString roName = headers[i];
AsciiString roValue = headers[i + 1];
if (roName.hashCode() == nameHash && roValue.hashCode() == valueHash &&
roName.contentEqualsIgnoreCase(name) && hashingStrategy.equals(roValue, value)) {
return true;
}
}
return false;
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder(getClass().getSimpleName()).append('[');
String separator = "";
for (Map.Entry<CharSequence, CharSequence> entry : this) {
builder.append(separator);
builder.append(entry.getKey()).append(": ").append(entry.getValue());
separator = ", ";
}
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;
private AsciiString[] current = pseudoHeaders.length != 0 ? pseudoHeaders : otherHeaders;
private AsciiString key;
private AsciiString value;
@Override
public boolean hasNext() {
return i != current.length;
}
@Override
public Map.Entry<CharSequence, CharSequence> next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
key = current[i];
value = current[i + 1];
i += 2;
if (i == current.length && current == pseudoHeaders) {
current = otherHeaders;
i = 0;
}
return this;
}
@Override
public CharSequence getKey() {
return key;
}
@Override
public CharSequence getValue() {
return value;
}
@Override
public CharSequence setValue(CharSequence value) {
throw new UnsupportedOperationException("read only");
}
@Override
public void remove() {
throw new UnsupportedOperationException("read only");
}
@Override
public String toString() {
return key.toString() + '=' + value.toString();
}
}
}