/*
 * 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:
 *
 *   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.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 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 DefaultHttpHeaders if your have a fixed set of headers which will not change.

/** * A variant of {@link HttpHeaders} 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 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.
Params:
  • validateHeaders – true to validate the contents of each header name.
  • nameValuePairs – An array of the structure [<name,value>,<name,value>,...]. 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.
/** * Create a new instance. * @param validateHeaders {@code true} to validate the contents of each header name. * @param nameValuePairs An array of the structure {@code [<name,value>,<name,value>,...]}. * 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. */
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)) { return nameValuePairs[i + 1]; } } 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<String> getAll(String name) { if (isEmpty()) { return Collections.emptyList(); } final int nameHash = AsciiString.hashCode(name); List<String> values = new ArrayList<String>(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()); } } return values; } @Override public List<Map.Entry<String, String>> entries() { if (isEmpty()) { return Collections.emptyList(); } List<Map.Entry<String, String>> entries = new ArrayList<Map.Entry<String, String>>(size()); for (int i = 0; i < nameValuePairs.length; i += 2) { entries.add(new SimpleImmutableEntry<String, String>(nameValuePairs[i].toString(), nameValuePairs[i + 1].toString())); } 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)) { return true; } } } else { for (int i = 0; i < nameValuePairs.length; i += 2) { if (contentEqualsIgnoreCase(nameValuePairs[i], name) && contentEquals(nameValuePairs[i + 1], value)) { return true; } } } return false; } @Override public Iterator<String> valueStringIterator(CharSequence name) { return new ReadOnlyStringValueIterator(name); } @Override public Iterator<CharSequence> valueCharSequenceIterator(CharSequence name) { return new ReadOnlyValueIterator(name); } @Override public Iterator<Map.Entry<String, String>> iterator() { return new ReadOnlyStringIterator(); } @Override public Iterator<Map.Entry<CharSequence, CharSequence>> iteratorCharSequence() { return new ReadOnlyIterator(); } @Override public boolean isEmpty() { return nameValuePairs.length == 0; } @Override public int size() { return nameValuePairs.length >>> 1; } @Override public Set<String> names() { if (isEmpty()) { return Collections.emptySet(); } Set<String> names = new LinkedHashSet<String>(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<CharSequence, CharSequence>, Iterator<Map.Entry<CharSequence, CharSequence>> { private CharSequence key; private CharSequence value; private int nextNameIndex; @Override public boolean hasNext() { return nextNameIndex != nameValuePairs.length; } @Override public Map.Entry<CharSequence, CharSequence> 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<String, String>, Iterator<Map.Entry<String, String>> { private String key; private String value; private int nextNameIndex; @Override public boolean hasNext() { return nextNameIndex != nameValuePairs.length; } @Override public Map.Entry<String, String> 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<String> { 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<CharSequence> { 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; } } }