/* * Copyright (c) 2012 the original author or authors. * * Licensed 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 org.eclipse.jetty.spdy.api; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; /** * <p>A container for name/value pairs, known as headers.</p> * <p>A {@link Header} is composed of a case-insensitive name string and * of a case-sensitive set of value strings.</p> * <p>The implementation of this class is not thread safe.</p> */ public class Headers implements Iterable<Headers.Header> { private final Map<String, Header> headers; /** * <p>Creates an empty modifiable {@link Headers} instance.</p> * @see #Headers(Headers, boolean) */ public Headers() { headers = new LinkedHashMap<>(); } /** * <p>Creates a {@link Headers} instance by copying the headers from the given * {@link Headers} and making it (im)mutable depending on the given {@code immutable} parameter</p> * * @param original the {@link Headers} to copy headers from * @param immutable whether this instance is immutable */ public Headers(Headers original, boolean immutable) { Map<String, Header> copy = new LinkedHashMap<>(); copy.putAll(original.headers); headers = immutable ? Collections.unmodifiableMap(copy) : copy; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Headers that = (Headers)obj; return headers.equals(that.headers); } @Override public int hashCode() { return headers.hashCode(); } /** * @return a set of header names */ public Set<String> names() { Set<String> result = new LinkedHashSet<>(); for (Header header : headers.values()) result.add(header.name); return result; } /** * @param name the header name * @return the {@link Header} with the given name, or null if no such header exists */ public Header get(String name) { return headers.get(name.trim().toLowerCase()); } /** * <p>Inserts or replaces the given name/value pair as a single-valued {@link Header}.</p> * * @param name the header name * @param value the header value */ public void put(String name, String value) { name = name.trim(); Header header = new Header(name, value.trim()); headers.put(name.toLowerCase(), header); } /** * <p>Inserts or replaces the given {@link Header}, mapped to the {@link Header#name() header's name}</p> * * @param header the header to add */ public void put(Header header) { headers.put(header.name().toLowerCase(), header); } /** * <p>Adds the given value to a header with the given name, creating a {@link Header} is none exists * for the given name.</p> * * @param name the header name * @param value the header value to add */ public void add(String name, String value) { name = name.trim(); Header header = headers.get(name.toLowerCase()); if (header == null) { header = new Header(name, value.trim()); headers.put(name.toLowerCase(), header); } else { header = new Header(header.name(), header.value() + "," + value.trim()); headers.put(name.toLowerCase(), header); } } /** * <p>Removes the {@link Header} with the given name</p> * * @param name the name of the header to remove * @return the removed header, or null if no such header existed */ public Header remove(String name) { name = name.trim(); return headers.remove(name.toLowerCase()); } /** * <p>Empties this {@link Headers} instance from all headers</p> * @see #isEmpty() */ public void clear() { headers.clear(); } /** * @return whether this {@link Headers} instance is empty */ public boolean isEmpty() { return headers.isEmpty(); } /** * @return the number of headers */ public int size() { return headers.size(); } /** * @return an iterator over the {@link Header} present in this instance */ @Override public Iterator<Header> iterator() { return headers.values().iterator(); } @Override public String toString() { return headers.toString(); } /** * <p>A named list of string values.</p> * <p>The name is case-sensitive and there must be at least one value.</p> */ public static class Header { private final String name; private final String[] values; private Header(String name, String value, String... values) { this.name = name; this.values = new String[values.length + 1]; this.values[0] = value; if (values.length > 0) System.arraycopy(values, 0, this.values, 1, values.length); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Header that = (Header)obj; return name.equals(that.name) && Arrays.equals(values, that.values); } @Override public int hashCode() { int result = name.hashCode(); result = 31 * result + Arrays.hashCode(values); return result; } /** * @return the header's name */ public String name() { return name; } /** * @return the first header's value */ public String value() { return values[0]; } /** * <p>Attempts to convert the result of {@link #value()} to an integer, * returning it if the conversion is successful; returns null if the * result of {@link #value()} is null.</p> * * @return the result of {@link #value()} converted to an integer, or null * @throws NumberFormatException if the conversion fails */ public Integer valueAsInt() { final String value = value(); return value == null ? null : Integer.valueOf(value); } /** * @return the header's values */ public String[] values() { return values; } /** * @return whether the header has multiple values */ public boolean hasMultipleValues() { return values.length > 1; } @Override public String toString() { return Arrays.toString(values); } } }