chromium/components/cronet/android/java/src/org/chromium/net/impl/UrlResponseInfoImpl.java

// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.net.impl;

import org.chromium.net.UrlResponseInfo;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Implements the container for basic information about a response. Included in
 * {@link org.chromium.net.UrlRequest.Callback} callbacks. Each
 * {@link org.chromium.net.UrlRequest.Callback#onRedirectReceived onRedirectReceived()}
 * callback gets a different copy of {@code UrlResponseInfo} describing a particular
 * redirect response.
 */
public final class UrlResponseInfoImpl extends UrlResponseInfo {
    private final List<String> mResponseInfoUrlChain;
    private final int mHttpStatusCode;
    private final String mHttpStatusText;
    private final boolean mWasCached;
    private final String mNegotiatedProtocol;
    private final String mProxyServer;
    private final AtomicLong mReceivedByteCount;
    private final HeaderBlockImpl mHeaders;

    /** Unmodifiable container of response headers or trailers. */
    public static final class HeaderBlockImpl extends HeaderBlock {
        private final List<Map.Entry<String, String>> mAllHeadersList;
        private Map<String, List<String>> mHeadersMap;

        HeaderBlockImpl(List<Map.Entry<String, String>> allHeadersList) {
            mAllHeadersList = allHeadersList;
        }

        @Override
        public List<Map.Entry<String, String>> getAsList() {
            return mAllHeadersList;
        }

        @Override
        public Map<String, List<String>> getAsMap() {
            // This is potentially racy...but races will only result in wasted resource.
            if (mHeadersMap != null) {
                return mHeadersMap;
            }
            Map<String, List<String>> map = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
            for (Map.Entry<String, String> entry : mAllHeadersList) {
                List<String> values = new ArrayList<String>();
                if (map.containsKey(entry.getKey())) {
                    values.addAll(map.get(entry.getKey()));
                }
                values.add(entry.getValue());
                map.put(entry.getKey(), Collections.unmodifiableList(values));
            }
            mHeadersMap = Collections.unmodifiableMap(map);
            return mHeadersMap;
        }
    }

    /**
     * Creates an implementation of {@link UrlResponseInfo}.
     *
     * @param urlChain the URL chain. The first entry is the originally requested URL;
     *         the following entries are redirects followed.
     * @param httpStatusCode the HTTP status code.
     * @param httpStatusText the HTTP status text of the status line.
     * @param allHeadersList list of response header field and value pairs.
     * @param wasCached {@code true} if the response came from the cache, {@code false}
     *         otherwise.
     * @param negotiatedProtocol the protocol negotiated with the server.
     * @param proxyServer the proxy server that was used for the request.
     * @param receivedByteCount minimum count of bytes received from the network to process this
     *         request.
     */
    public UrlResponseInfoImpl(
            List<String> urlChain,
            int httpStatusCode,
            String httpStatusText,
            List<Map.Entry<String, String>> allHeadersList,
            boolean wasCached,
            String negotiatedProtocol,
            String proxyServer,
            long receivedByteCount) {
        mResponseInfoUrlChain = Collections.unmodifiableList(urlChain);
        mHttpStatusCode = httpStatusCode;
        mHttpStatusText = httpStatusText;
        mHeaders = new HeaderBlockImpl(Collections.unmodifiableList(allHeadersList));
        mWasCached = wasCached;
        mNegotiatedProtocol = negotiatedProtocol;
        mProxyServer = proxyServer;
        mReceivedByteCount = new AtomicLong(receivedByteCount);
    }

    /** Constructor for backwards compatibility.  See main constructor above for more info. */
    @Deprecated
    public UrlResponseInfoImpl(
            List<String> urlChain,
            int httpStatusCode,
            String httpStatusText,
            List<Map.Entry<String, String>> allHeadersList,
            boolean wasCached,
            String negotiatedProtocol,
            String proxyServer) {
        this(
                urlChain,
                httpStatusCode,
                httpStatusText,
                allHeadersList,
                wasCached,
                negotiatedProtocol,
                proxyServer,
                0);
    }

    @Override
    public String getUrl() {
        return mResponseInfoUrlChain.get(mResponseInfoUrlChain.size() - 1);
    }

    @Override
    public List<String> getUrlChain() {
        return mResponseInfoUrlChain;
    }

    @Override
    public int getHttpStatusCode() {
        return mHttpStatusCode;
    }

    @Override
    public String getHttpStatusText() {
        return mHttpStatusText;
    }

    @Override
    public List<Map.Entry<String, String>> getAllHeadersAsList() {
        return mHeaders.getAsList();
    }

    @Override
    public Map<String, List<String>> getAllHeaders() {
        return mHeaders.getAsMap();
    }

    @Override
    public boolean wasCached() {
        return mWasCached;
    }

    @Override
    public String getNegotiatedProtocol() {
        return mNegotiatedProtocol;
    }

    @Override
    public String getProxyServer() {
        return mProxyServer;
    }

    @Override
    public long getReceivedByteCount() {
        return mReceivedByteCount.get();
    }

    @Override
    public String toString() {
        return String.format(
                Locale.ROOT,
                "UrlResponseInfo@[%s][%s]: urlChain = %s, "
                        + "httpStatus = %d %s, headers = %s, wasCached = %b, "
                        + "negotiatedProtocol = %s, proxyServer= %s, receivedByteCount = %d",
                // Prevent asserting on the contents of this string
                Integer.toHexString(System.identityHashCode(this)),
                getUrl(),
                getUrlChain().toString(),
                getHttpStatusCode(),
                getHttpStatusText(),
                getAllHeadersAsList().toString(),
                wasCached(),
                getNegotiatedProtocol(),
                getProxyServer(),
                getReceivedByteCount());
    }

    /** Sets mReceivedByteCount. Must not be called after request completion or cancellation. */
    public void setReceivedByteCount(long currentReceivedByteCount) {
        mReceivedByteCount.set(currentReceivedByteCount);
    }
}