io.netty.handler.codec.http.ReadOnlyHttpHeaders Maven / Gradle / Ivy
/*
* Copyright 2017 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:
*
* https://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.http;
import io.netty.util.AsciiString;
import io.netty.util.internal.UnstableApi;
import java.util.AbstractMap.SimpleImmutableEntry;
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.INSTANCE;
import static io.netty.handler.codec.http.DefaultHttpHeaders.HttpNameValidator;
import static io.netty.util.AsciiString.contentEquals;
import static io.netty.util.AsciiString.contentEqualsIgnoreCase;
/**
* A variant of {@link HttpHeaders} 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 {@link DefaultHttpHeaders} if your have a fixed set of headers which will not
* change.
*/
@UnstableApi
public final class ReadOnlyHttpHeaders extends HttpHeaders {
private final CharSequence[] nameValuePairs;
/**
* Create a new instance.
* @param validateHeaders {@code true} to validate the contents of each header name.
* @param nameValuePairs An array of the structure {@code [,,...]}.
* 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.
*/
public ReadOnlyHttpHeaders(boolean validateHeaders, CharSequence... nameValuePairs) {
if ((nameValuePairs.length & 1) != 0) {
throw newInvalidArraySizeException();
}
if (validateHeaders) {
validateHeaders(nameValuePairs);
}
this.nameValuePairs = nameValuePairs;
}
private static IllegalArgumentException newInvalidArraySizeException() {
return new IllegalArgumentException("nameValuePairs must be arrays of [name, value] pairs");
}
private static void validateHeaders(CharSequence... keyValuePairs) {
for (int i = 0; i < keyValuePairs.length; i += 2) {
HttpNameValidator.validateName(keyValuePairs[i]);
}
}
private CharSequence get0(CharSequence name) {
final int nameHash = AsciiString.hashCode(name);
for (int i = 0; i < nameValuePairs.length; i += 2) {
CharSequence roName = nameValuePairs[i];
if (AsciiString.hashCode(roName) == nameHash && contentEqualsIgnoreCase(roName, name)) {
// Suppress a warning out of bounds access since the constructor allows only pairs
return nameValuePairs[i + 1]; // lgtm[java/index-out-of-bounds]
}
}
return null;
}
@Override
public String get(String name) {
CharSequence value = get0(name);
return value == null ? null : value.toString();
}
@Override
public Integer getInt(CharSequence name) {
CharSequence value = get0(name);
return value == null ? null : INSTANCE.convertToInt(value);
}
@Override
public int getInt(CharSequence name, int defaultValue) {
CharSequence value = get0(name);
return value == null ? defaultValue : INSTANCE.convertToInt(value);
}
@Override
public Short getShort(CharSequence name) {
CharSequence value = get0(name);
return value == null ? null : INSTANCE.convertToShort(value);
}
@Override
public short getShort(CharSequence name, short defaultValue) {
CharSequence value = get0(name);
return value == null ? defaultValue : INSTANCE.convertToShort(value);
}
@Override
public Long getTimeMillis(CharSequence name) {
CharSequence value = get0(name);
return value == null ? null : INSTANCE.convertToTimeMillis(value);
}
@Override
public long getTimeMillis(CharSequence name, long defaultValue) {
CharSequence value = get0(name);
return value == null ? defaultValue : INSTANCE.convertToTimeMillis(value);
}
@Override
public List getAll(String name) {
if (isEmpty()) {
return Collections.emptyList();
}
final int nameHash = AsciiString.hashCode(name);
List values = new ArrayList(4);
for (int i = 0; i < nameValuePairs.length; i += 2) {
CharSequence roName = nameValuePairs[i];
if (AsciiString.hashCode(roName) == nameHash && contentEqualsIgnoreCase(roName, name)) {
values.add(nameValuePairs[i + 1].toString()); // lgtm[java/index-out-of-bounds]
}
}
return values;
}
@Override
public List> entries() {
if (isEmpty()) {
return Collections.emptyList();
}
List> entries = new ArrayList>(size());
for (int i = 0; i < nameValuePairs.length; i += 2) {
entries.add(new SimpleImmutableEntry(nameValuePairs[i].toString(),
nameValuePairs[i + 1].toString())); // lgtm[java/index-out-of-bounds]
}
return entries;
}
@Override
public boolean contains(String name) {
return get0(name) != null;
}
@Override
public boolean contains(String name, String value, boolean ignoreCase) {
return containsValue(name, value, ignoreCase);
}
@Override
public boolean containsValue(CharSequence name, CharSequence value, boolean ignoreCase) {
if (ignoreCase) {
for (int i = 0; i < nameValuePairs.length; i += 2) {
if (contentEqualsIgnoreCase(nameValuePairs[i], name) &&
contentEqualsIgnoreCase(nameValuePairs[i + 1], value)) { // lgtm[java/index-out-of-bounds]
return true;
}
}
} else {
for (int i = 0; i < nameValuePairs.length; i += 2) {
if (contentEqualsIgnoreCase(nameValuePairs[i], name) &&
contentEquals(nameValuePairs[i + 1], value)) { // lgtm[java/index-out-of-bounds]
return true;
}
}
}
return false;
}
@Override
public Iterator valueStringIterator(CharSequence name) {
return new ReadOnlyStringValueIterator(name);
}
@Override
public Iterator valueCharSequenceIterator(CharSequence name) {
return new ReadOnlyValueIterator(name);
}
@Override
public Iterator> iterator() {
return new ReadOnlyStringIterator();
}
@Override
public Iterator> iteratorCharSequence() {
return new ReadOnlyIterator();
}
@Override
public boolean isEmpty() {
return nameValuePairs.length == 0;
}
@Override
public int size() {
return nameValuePairs.length >>> 1;
}
@Override
public Set names() {
if (isEmpty()) {
return Collections.emptySet();
}
Set names = new LinkedHashSet(size());
for (int i = 0; i < nameValuePairs.length; i += 2) {
names.add(nameValuePairs[i].toString());
}
return names;
}
@Override
public HttpHeaders add(String name, Object value) {
throw new UnsupportedOperationException("read only");
}
@Override
public HttpHeaders add(String name, Iterable> values) {
throw new UnsupportedOperationException("read only");
}
@Override
public HttpHeaders addInt(CharSequence name, int value) {
throw new UnsupportedOperationException("read only");
}
@Override
public HttpHeaders addShort(CharSequence name, short value) {
throw new UnsupportedOperationException("read only");
}
@Override
public HttpHeaders set(String name, Object value) {
throw new UnsupportedOperationException("read only");
}
@Override
public HttpHeaders set(String name, Iterable> values) {
throw new UnsupportedOperationException("read only");
}
@Override
public HttpHeaders setInt(CharSequence name, int value) {
throw new UnsupportedOperationException("read only");
}
@Override
public HttpHeaders setShort(CharSequence name, short value) {
throw new UnsupportedOperationException("read only");
}
@Override
public HttpHeaders remove(String name) {
throw new UnsupportedOperationException("read only");
}
@Override
public HttpHeaders clear() {
throw new UnsupportedOperationException("read only");
}
private final class ReadOnlyIterator implements Map.Entry,
Iterator> {
private CharSequence key;
private CharSequence value;
private int nextNameIndex;
@Override
public boolean hasNext() {
return nextNameIndex != nameValuePairs.length;
}
@Override
public Map.Entry next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
key = nameValuePairs[nextNameIndex];
value = nameValuePairs[nextNameIndex + 1];
nextNameIndex += 2;
return this;
}
@Override
public void remove() {
throw new UnsupportedOperationException("read only");
}
@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 String toString() {
return key.toString() + '=' + value.toString();
}
}
private final class ReadOnlyStringIterator implements Map.Entry,
Iterator> {
private String key;
private String value;
private int nextNameIndex;
@Override
public boolean hasNext() {
return nextNameIndex != nameValuePairs.length;
}
@Override
public Map.Entry next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
key = nameValuePairs[nextNameIndex].toString();
value = nameValuePairs[nextNameIndex + 1].toString();
nextNameIndex += 2;
return this;
}
@Override
public void remove() {
throw new UnsupportedOperationException("read only");
}
@Override
public String getKey() {
return key;
}
@Override
public String getValue() {
return value;
}
@Override
public String setValue(String value) {
throw new UnsupportedOperationException("read only");
}
@Override
public String toString() {
return key + '=' + value;
}
}
private final class ReadOnlyStringValueIterator implements Iterator {
private final CharSequence name;
private final int nameHash;
private int nextNameIndex;
ReadOnlyStringValueIterator(CharSequence name) {
this.name = name;
nameHash = AsciiString.hashCode(name);
nextNameIndex = findNextValue();
}
@Override
public boolean hasNext() {
return nextNameIndex != -1;
}
@Override
public String next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
String value = nameValuePairs[nextNameIndex + 1].toString();
nextNameIndex = findNextValue();
return value;
}
@Override
public void remove() {
throw new UnsupportedOperationException("read only");
}
private int findNextValue() {
for (int i = nextNameIndex; i < nameValuePairs.length; i += 2) {
final CharSequence roName = nameValuePairs[i];
if (nameHash == AsciiString.hashCode(roName) && contentEqualsIgnoreCase(name, roName)) {
return i;
}
}
return -1;
}
}
private final class ReadOnlyValueIterator implements Iterator {
private final CharSequence name;
private final int nameHash;
private int nextNameIndex;
ReadOnlyValueIterator(CharSequence name) {
this.name = name;
nameHash = AsciiString.hashCode(name);
nextNameIndex = findNextValue();
}
@Override
public boolean hasNext() {
return nextNameIndex != -1;
}
@Override
public CharSequence next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
CharSequence value = nameValuePairs[nextNameIndex + 1];
nextNameIndex = findNextValue();
return value;
}
@Override
public void remove() {
throw new UnsupportedOperationException("read only");
}
private int findNextValue() {
for (int i = nextNameIndex; i < nameValuePairs.length; i += 2) {
final CharSequence roName = nameValuePairs[i];
if (nameHash == AsciiString.hashCode(roName) && contentEqualsIgnoreCase(name, roName)) {
return i;
}
}
return -1;
}
}
}