All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.api.client.http.HttpHeaders Maven / Gradle / Ivy

Go to download

Google HTTP Client Library for Java. Functionality that works on all supported Java platforms, including Java 5 (or higher) desktop (SE) and web (EE), Android, and Google App Engine.

There is a newer version: 1.45.1
Show newest version
/*
 * Copyright (c) 2010 Google Inc.
 *
 * 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 com.google.api.client.http;

import com.google.api.client.util.ArrayValueMap;
import com.google.api.client.util.Base64;
import com.google.api.client.util.ClassInfo;
import com.google.api.client.util.Data;
import com.google.api.client.util.FieldInfo;
import com.google.api.client.util.GenericData;
import com.google.api.client.util.Key;
import com.google.api.client.util.Preconditions;
import com.google.api.client.util.StringUtils;
import com.google.api.client.util.Throwables;
import com.google.api.client.util.Types;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Stores HTTP headers used in an HTTP request or response, as defined in Header Field Definitions.
 *
 * 

{@code null} is not allowed as a name or value of a header. Names are case-insensitive. * *

Implementation is not thread-safe. * * @since 1.0 * @author Yaniv Inbar */ public class HttpHeaders extends GenericData { public HttpHeaders() { super(EnumSet.of(Flags.IGNORE_CASE)); } /** {@code "Accept"} header. */ @Key("Accept") private List accept; /** {@code "Accept-Encoding"} header. */ @Key("Accept-Encoding") private List acceptEncoding = new ArrayList(Collections.singleton("gzip")); /** {@code "Authorization"} header. */ @Key("Authorization") private List authorization; /** {@code "Cache-Control"} header. */ @Key("Cache-Control") private List cacheControl; /** {@code "Content-Encoding"} header. */ @Key("Content-Encoding") private List contentEncoding; /** {@code "Content-Length"} header. */ @Key("Content-Length") private List contentLength; /** {@code "Content-MD5"} header. */ @Key("Content-MD5") private List contentMD5; /** {@code "Content-Range"} header. */ @Key("Content-Range") private List contentRange; /** {@code "Content-Type"} header. */ @Key("Content-Type") private List contentType; /** {@code "Cookie"} header. */ @Key("Cookie") private List cookie; /** {@code "Date"} header. */ @Key("Date") private List date; /** {@code "ETag"} header. */ @Key("ETag") private List etag; /** {@code "Expires"} header. */ @Key("Expires") private List expires; /** {@code "If-Modified-Since"} header. */ @Key("If-Modified-Since") private List ifModifiedSince; /** {@code "If-Match"} header. */ @Key("If-Match") private List ifMatch; /** {@code "If-None-Match"} header. */ @Key("If-None-Match") private List ifNoneMatch; /** {@code "If-Unmodified-Since"} header. */ @Key("If-Unmodified-Since") private List ifUnmodifiedSince; /** {@code "If-Range"} header. */ @Key("If-Range") private List ifRange; /** {@code "Last-Modified"} header. */ @Key("Last-Modified") private List lastModified; /** {@code "Location"} header. */ @Key("Location") private List location; /** {@code "MIME-Version"} header. */ @Key("MIME-Version") private List mimeVersion; /** {@code "Range"} header. */ @Key("Range") private List range; /** {@code "Retry-After"} header. */ @Key("Retry-After") private List retryAfter; /** {@code "User-Agent"} header. */ @Key("User-Agent") private List userAgent; /** {@code "Warning"} header. */ @Key("Warning") private List warning; /** {@code "WWW-Authenticate"} header. */ @Key("WWW-Authenticate") private List authenticate; /** {@code "Age"} header. */ @Key("Age") private List age; @Override public HttpHeaders clone() { return (HttpHeaders) super.clone(); } @Override public HttpHeaders set(String fieldName, Object value) { return (HttpHeaders) super.set(fieldName, value); } /** * Returns the first {@code "Accept"} header or {@code null} for none. * * @since 1.5 */ public final String getAccept() { return getFirstHeaderValue(accept); } /** * Sets the {@code "Accept"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.5 */ public HttpHeaders setAccept(String accept) { this.accept = getAsList(accept); return this; } /** * Returns the first {@code "Accept-Encoding"} header or {@code null} for none. * * @since 1.5 */ public final String getAcceptEncoding() { return getFirstHeaderValue(acceptEncoding); } /** * Sets the {@code "Accept-Encoding"} header or {@code null} for none. * *

By default, this is {@code "gzip"}. * * @since 1.5 */ public HttpHeaders setAcceptEncoding(String acceptEncoding) { this.acceptEncoding = getAsList(acceptEncoding); return this; } /** * Returns the first {@code "Authorization"} header or {@code null} for none. * * @since 1.5 */ public final String getAuthorization() { return getFirstHeaderValue(authorization); } /** * Returns all {@code "Authorization"} headers or {@code null} for none. * * @since 1.13 */ public final List getAuthorizationAsList() { return authorization; } /** * Sets the {@code "Authorization"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.5 */ public HttpHeaders setAuthorization(String authorization) { return setAuthorization(getAsList(authorization)); } /** * Sets the {@code "Authorization"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.13 */ public HttpHeaders setAuthorization(List authorization) { this.authorization = authorization; return this; } /** * Returns the first {@code "Cache-Control"} header or {@code null} for none. * * @since 1.5 */ public final String getCacheControl() { return getFirstHeaderValue(cacheControl); } /** * Sets the {@code "Cache-Control"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.5 */ public HttpHeaders setCacheControl(String cacheControl) { this.cacheControl = getAsList(cacheControl); return this; } /** * Returns the first {@code "Content-Encoding"} header or {@code null} for none. * * @since 1.5 */ public final String getContentEncoding() { return getFirstHeaderValue(contentEncoding); } /** * Sets the {@code "Content-Encoding"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.5 */ public HttpHeaders setContentEncoding(String contentEncoding) { this.contentEncoding = getAsList(contentEncoding); return this; } /** * Returns the first {@code "Content-Length"} header or {@code null} for none. * * @since 1.5 */ public final Long getContentLength() { return getFirstHeaderValue(contentLength); } /** * Sets the {@code "Content-Length"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.5 */ public HttpHeaders setContentLength(Long contentLength) { this.contentLength = getAsList(contentLength); return this; } /** * Returns the first {@code "Content-MD5"} header or {@code null} for none. * * @since 1.5 */ public final String getContentMD5() { return getFirstHeaderValue(contentMD5); } /** * Sets the {@code "Content-MD5"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.5 */ public HttpHeaders setContentMD5(String contentMD5) { this.contentMD5 = getAsList(contentMD5); return this; } /** * Returns the first {@code "Content-Range"} header or {@code null} for none. * * @since 1.5 */ public final String getContentRange() { return getFirstHeaderValue(contentRange); } /** * Sets the {@code "Content-Range"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.5 */ public HttpHeaders setContentRange(String contentRange) { this.contentRange = getAsList(contentRange); return this; } /** * Returns the first {@code "Content-Type"} header or {@code null} for none. * * @since 1.5 */ public final String getContentType() { return getFirstHeaderValue(contentType); } /** * Sets the {@code "Content-Type"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.5 */ public HttpHeaders setContentType(String contentType) { this.contentType = getAsList(contentType); return this; } /** * Returns the first {@code "Cookie"} header or {@code null} for none. * *

See Cookie Specification. * * @since 1.6 */ public final String getCookie() { return getFirstHeaderValue(cookie); } /** * Sets the {@code "Cookie"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.6 */ public HttpHeaders setCookie(String cookie) { this.cookie = getAsList(cookie); return this; } /** * Returns the first {@code "Date"} header or {@code null} for none. * * @since 1.5 */ public final String getDate() { return getFirstHeaderValue(date); } /** * Sets the {@code "Date"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.5 */ public HttpHeaders setDate(String date) { this.date = getAsList(date); return this; } /** * Returns the first {@code "ETag"} header or {@code null} for none. * * @since 1.5 */ public final String getETag() { return getFirstHeaderValue(etag); } /** * Sets the {@code "ETag"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.5 */ public HttpHeaders setETag(String etag) { this.etag = getAsList(etag); return this; } /** * Returns the first {@code "Expires"} header or {@code null} for none. * * @since 1.5 */ public final String getExpires() { return getFirstHeaderValue(expires); } /** * Sets the {@code "Expires"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.5 */ public HttpHeaders setExpires(String expires) { this.expires = getAsList(expires); return this; } /** * Returns the first {@code "If-Modified-Since"} header or {@code null} for none. * * @since 1.5 */ public final String getIfModifiedSince() { return getFirstHeaderValue(ifModifiedSince); } /** * Sets the {@code "If-Modified-Since"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.5 */ public HttpHeaders setIfModifiedSince(String ifModifiedSince) { this.ifModifiedSince = getAsList(ifModifiedSince); return this; } /** * Returns the first {@code "If-Match"} header or {@code null} for none. * * @since 1.5 */ public final String getIfMatch() { return getFirstHeaderValue(ifMatch); } /** * Sets the {@code "If-Match"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.5 */ public HttpHeaders setIfMatch(String ifMatch) { this.ifMatch = getAsList(ifMatch); return this; } /** * Returns the first {@code "If-None-Match"} header or {@code null} for none. * * @since 1.5 */ public final String getIfNoneMatch() { return getFirstHeaderValue(ifNoneMatch); } /** * Sets the {@code "If-None-Match"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.5 */ public HttpHeaders setIfNoneMatch(String ifNoneMatch) { this.ifNoneMatch = getAsList(ifNoneMatch); return this; } /** * Returns the first {@code "If-Unmodified-Since"} header or {@code null} for none. * * @since 1.5 */ public final String getIfUnmodifiedSince() { return getFirstHeaderValue(ifUnmodifiedSince); } /** * Sets the {@code "If-Unmodified-Since"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.5 */ public HttpHeaders setIfUnmodifiedSince(String ifUnmodifiedSince) { this.ifUnmodifiedSince = getAsList(ifUnmodifiedSince); return this; } /** * Returns the first {@code "If-Range"} header or {@code null} for none. * * @since 1.14 */ public final String getIfRange() { return getFirstHeaderValue(ifRange); } /** * Sets the {@code "If-Range"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.14 */ public HttpHeaders setIfRange(String ifRange) { this.ifRange = getAsList(ifRange); return this; } /** * Returns the first {@code "Last-Modified"} header or {@code null} for none. * * @since 1.5 */ public final String getLastModified() { return getFirstHeaderValue(lastModified); } /** * Sets the {@code "Last-Modified"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.5 */ public HttpHeaders setLastModified(String lastModified) { this.lastModified = getAsList(lastModified); return this; } /** * Returns the first {@code "Location"} header or {@code null} for none. * * @since 1.5 */ public final String getLocation() { return getFirstHeaderValue(location); } /** * Sets the {@code "Location"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.5 */ public HttpHeaders setLocation(String location) { this.location = getAsList(location); return this; } /** * Returns the first {@code "MIME-Version"} header or {@code null} for none. * * @since 1.5 */ public final String getMimeVersion() { return getFirstHeaderValue(mimeVersion); } /** * Sets the {@code "MIME-Version"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.5 */ public HttpHeaders setMimeVersion(String mimeVersion) { this.mimeVersion = getAsList(mimeVersion); return this; } /** * Returns the first {@code "Range"} header or {@code null} for none. * * @since 1.5 */ public final String getRange() { return getFirstHeaderValue(range); } /** * Sets the {@code "Range"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.5 */ public HttpHeaders setRange(String range) { this.range = getAsList(range); return this; } /** * Returns the first {@code "Retry-After"} header or {@code null} for none. * * @since 1.5 */ public final String getRetryAfter() { return getFirstHeaderValue(retryAfter); } /** * Sets the {@code "Retry-After"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.5 */ public HttpHeaders setRetryAfter(String retryAfter) { this.retryAfter = getAsList(retryAfter); return this; } /** * Returns the first {@code "User-Agent"} header or {@code null} for none. * * @since 1.5 */ public final String getUserAgent() { return getFirstHeaderValue(userAgent); } /** * Sets the {@code "User-Agent"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.5 */ public HttpHeaders setUserAgent(String userAgent) { this.userAgent = getAsList(userAgent); return this; } /** * Returns the first {@code "WWW-Authenticate"} header or {@code null} for none. * * @since 1.5 */ public final String getAuthenticate() { return getFirstHeaderValue(authenticate); } /** * Returns all {@code "WWW-Authenticate"} headers or {@code null} for none. * * @since 1.16 */ public final List getAuthenticateAsList() { return authenticate; } /** * Sets the {@code "WWW-Authenticate"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.5 */ public HttpHeaders setAuthenticate(String authenticate) { this.authenticate = getAsList(authenticate); return this; } /** * Adds the {@code "Warning"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.28 */ public HttpHeaders addWarning(String warning) { if (warning == null) { return this; } if (this.warning == null) { this.warning = getAsList(warning); } else { this.warning.add(warning); } return this; } /** * Returns all {@code "Warning"} headers or {@code null} for none. * * @since 1.28 */ public final List getWarning() { return warning == null ? null : new ArrayList<>(warning); } /** * Returns the first {@code "Age"} header or {@code null} for none. * * @since 1.14 */ public final Long getAge() { return getFirstHeaderValue(age); } /** * Sets the {@code "Age"} header or {@code null} for none. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.14 */ public HttpHeaders setAge(Long age) { this.age = getAsList(age); return this; } /** * Sets the {@link #authorization} header as specified in Basic Authentication Scheme. * *

Overriding is only supported for the purpose of calling the super implementation and * changing the return type, but nothing else. * * @since 1.2 */ public HttpHeaders setBasicAuthentication(String username, String password) { String userPass = Preconditions.checkNotNull(username) + ":" + Preconditions.checkNotNull(password); String encoded = Base64.encodeBase64String(StringUtils.getBytesUtf8(userPass)); return setAuthorization("Basic " + encoded); } private static void addHeader( Logger logger, StringBuilder logbuf, StringBuilder curlbuf, LowLevelHttpRequest lowLevelHttpRequest, String name, Object value, Writer writer) throws IOException { // ignore nulls if (value == null || Data.isNull(value)) { return; } // compute value String stringValue = toStringValue(value); // log header String loggedStringValue = stringValue; if (("Authorization".equalsIgnoreCase(name) || "Cookie".equalsIgnoreCase(name)) && (logger == null || !logger.isLoggable(Level.ALL))) { loggedStringValue = ""; } if (logbuf != null) { logbuf.append(name).append(": "); logbuf.append(loggedStringValue); logbuf.append(StringUtils.LINE_SEPARATOR); } if (curlbuf != null) { curlbuf.append(" -H '").append(name).append(": ").append(loggedStringValue).append("'"); } // add header to lowLevelHttpRequest if (lowLevelHttpRequest != null) { lowLevelHttpRequest.addHeader(name, stringValue); } // add header to the writer if (writer != null) { writer.write(name); writer.write(": "); writer.write(stringValue); writer.write("\r\n"); } } /** Returns the string header value for the given header value as an object. */ private static String toStringValue(Object headerValue) { return headerValue instanceof Enum ? FieldInfo.of((Enum) headerValue).getName() : headerValue.toString(); } /** * Serializes headers to an {@link LowLevelHttpRequest}. * * @param headers HTTP headers * @param logbuf log buffer or {@code null} for none * @param curlbuf log buffer for logging curl requests or {@code null} for none * @param logger logger or {@code null} for none. Logger must be specified if log buffer is * specified * @param lowLevelHttpRequest low level HTTP request where HTTP headers will be serialized to or * {@code null} for none */ static void serializeHeaders( HttpHeaders headers, StringBuilder logbuf, StringBuilder curlbuf, Logger logger, LowLevelHttpRequest lowLevelHttpRequest) throws IOException { serializeHeaders(headers, logbuf, curlbuf, logger, lowLevelHttpRequest, null); } static void serializeHeaders( HttpHeaders headers, StringBuilder logbuf, StringBuilder curlbuf, Logger logger, LowLevelHttpRequest lowLevelHttpRequest, Writer writer) throws IOException { HashSet headerNames = new HashSet(); for (Map.Entry headerEntry : headers.entrySet()) { String name = headerEntry.getKey(); Preconditions.checkArgument( headerNames.add(name), "multiple headers of the same name (headers are case insensitive): %s", name); Object value = headerEntry.getValue(); if (value != null) { // compute the display name from the declared field name to fix capitalization String displayName = name; FieldInfo fieldInfo = headers.getClassInfo().getFieldInfo(name); if (fieldInfo != null) { displayName = fieldInfo.getName(); } Class valueClass = value.getClass(); if (value instanceof Iterable || valueClass.isArray()) { for (Object repeatedValue : Types.iterableOf(value)) { addHeader( logger, logbuf, curlbuf, lowLevelHttpRequest, displayName, repeatedValue, writer); } } else { addHeader(logger, logbuf, curlbuf, lowLevelHttpRequest, displayName, value, writer); } } } if (writer != null) { writer.flush(); } } /** * Serializes headers to an {@link Writer} for Multi-part requests. * * @param headers HTTP headers * @param logbuf log buffer or {@code null} for none * @param logger logger or {@code null} for none. Logger must be specified if log buffer is * specified * @param writer Writer where HTTP headers will be serialized to or {@code null} for none * @since 1.9 */ public static void serializeHeadersForMultipartRequests( HttpHeaders headers, StringBuilder logbuf, Logger logger, Writer writer) throws IOException { serializeHeaders(headers, logbuf, null, logger, null, writer); } /** * Puts all headers of the {@link LowLevelHttpResponse} into this {@link HttpHeaders} object. * * @param response Response from which the headers are copied * @param logger {@link StringBuilder} to which logging output is added or {@code null} to disable * logging * @since 1.10 */ public final void fromHttpResponse(LowLevelHttpResponse response, StringBuilder logger) throws IOException { clear(); ParseHeaderState state = new ParseHeaderState(this, logger); int headerCount = response.getHeaderCount(); for (int i = 0; i < headerCount; i++) { parseHeader(response.getHeaderName(i), response.getHeaderValue(i), state); } state.finish(); } /** LowLevelHttpRequest which will call the .parseHeader() method for every header added. */ private static class HeaderParsingFakeLevelHttpRequest extends LowLevelHttpRequest { private final HttpHeaders target; private final ParseHeaderState state; HeaderParsingFakeLevelHttpRequest(HttpHeaders target, ParseHeaderState state) { this.target = target; this.state = state; } @Override public void addHeader(String name, String value) { target.parseHeader(name, value, state); } @Override public LowLevelHttpResponse execute() throws IOException { throw new UnsupportedOperationException(); } } /** Returns the first header value based on the given internal list value. */ private T getFirstHeaderValue(List internalValue) { return internalValue == null ? null : internalValue.get(0); } /** Returns the list value to use for the given parameter passed to the setter method. */ private List getAsList(T passedValue) { if (passedValue == null) { return null; } List result = new ArrayList(); result.add(passedValue); return result; } /** * Returns the first header string value for the given header name. * * @param name header name (may be any case) * @return first header string value or {@code null} if not found * @since 1.13 */ public String getFirstHeaderStringValue(String name) { Object value = get(name.toLowerCase(Locale.US)); if (value == null) { return null; } Class valueClass = value.getClass(); if (value instanceof Iterable || valueClass.isArray()) { for (Object repeatedValue : Types.iterableOf(value)) { return toStringValue(repeatedValue); } } return toStringValue(value); } /** * Returns an unmodifiable list of the header string values for the given header name. * * @param name header name (may be any case) * @return header string values or empty if not found * @since 1.13 */ public List getHeaderStringValues(String name) { Object value = get(name.toLowerCase(Locale.US)); if (value == null) { return Collections.emptyList(); } Class valueClass = value.getClass(); if (value instanceof Iterable || valueClass.isArray()) { List values = new ArrayList(); for (Object repeatedValue : Types.iterableOf(value)) { values.add(toStringValue(repeatedValue)); } return Collections.unmodifiableList(values); } return Collections.singletonList(toStringValue(value)); } /** * Puts all headers of the {@link HttpHeaders} object into this {@link HttpHeaders} object. * * @param headers {@link HttpHeaders} from where the headers are taken * @since 1.10 */ public final void fromHttpHeaders(HttpHeaders headers) { try { ParseHeaderState state = new ParseHeaderState(this, null); serializeHeaders( headers, null, null, null, new HeaderParsingFakeLevelHttpRequest(this, state)); state.finish(); } catch (IOException ex) { // Should never occur as we are dealing with a FakeLowLevelHttpRequest throw Throwables.propagate(ex); } } /** State container for {@link #parseHeader(String, String, ParseHeaderState)}. */ private static final class ParseHeaderState { /** Target map where parsed values are stored. */ final ArrayValueMap arrayValueMap; /** Logger if logging is enabled or {@code null} otherwise. */ final StringBuilder logger; /** ClassInfo of the HttpHeaders. */ final ClassInfo classInfo; /** List of types in the header context. */ final List context; /** * Initializes a new ParseHeaderState. * * @param headers HttpHeaders object for which the headers are being parsed * @param logger Logger if logging is enabled or {@code null} */ public ParseHeaderState(HttpHeaders headers, StringBuilder logger) { Class clazz = headers.getClass(); this.context = Arrays.asList(clazz); this.classInfo = ClassInfo.of(clazz, true); this.logger = logger; this.arrayValueMap = new ArrayValueMap(headers); } /** Finishes the parsing-process by setting all array-values. */ void finish() { arrayValueMap.setValues(); } } /** Parses the specified case-insensitive header pair into this HttpHeaders instance. */ void parseHeader(String headerName, String headerValue, ParseHeaderState state) { List context = state.context; ClassInfo classInfo = state.classInfo; ArrayValueMap arrayValueMap = state.arrayValueMap; StringBuilder logger = state.logger; if (logger != null) { logger.append(headerName + ": " + headerValue).append(StringUtils.LINE_SEPARATOR); } // use field information if available FieldInfo fieldInfo = classInfo.getFieldInfo(headerName); if (fieldInfo != null) { Type type = Data.resolveWildcardTypeOrTypeVariable(context, fieldInfo.getGenericType()); // type is now class, parameterized type, or generic array type if (Types.isArray(type)) { // array that can handle repeating values Class rawArrayComponentType = Types.getRawArrayComponentType(context, Types.getArrayComponentType(type)); arrayValueMap.put( fieldInfo.getField(), rawArrayComponentType, parseValue(rawArrayComponentType, context, headerValue)); } else if (Types.isAssignableToOrFrom( Types.getRawArrayComponentType(context, type), Iterable.class)) { // iterable that can handle repeating values @SuppressWarnings("unchecked") Collection collection = (Collection) fieldInfo.getValue(this); if (collection == null) { collection = Data.newCollectionInstance(type); fieldInfo.setValue(this, collection); } Type subFieldType = type == Object.class ? null : Types.getIterableParameter(type); collection.add(parseValue(subFieldType, context, headerValue)); } else { // parse value based on field type fieldInfo.setValue(this, parseValue(type, context, headerValue)); } } else { // store header values in an array list @SuppressWarnings("unchecked") ArrayList listValue = (ArrayList) this.get(headerName); if (listValue == null) { listValue = new ArrayList(); this.set(headerName, listValue); } listValue.add(headerValue); } } private static Object parseValue(Type valueType, List context, String value) { Type resolved = Data.resolveWildcardTypeOrTypeVariable(context, valueType); return Data.parsePrimitiveValue(resolved, value); } // TODO(yanivi): override equals and hashCode }