com.squareup.okhttp.CacheControl.java Source code

Java tutorial

Introduction

Here is the source code for com.squareup.okhttp.CacheControl.java

Source

package com.squareup.okhttp;

import com.squareup.okhttp.internal.http.HeaderParser;
import java.util.concurrent.TimeUnit;

/**
 * A Cache-Control header with cache directives from a server or client. These
 * directives set policy on what responses can be stored, and which requests can
 * be satisfied by those stored responses.
 *
 * <p>See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9">RFC
 * 2616, 14.9</a>.
 */
public final class CacheControl {
    /**
     * Cache control request directives that require network validation of
     * responses. Note that such requests may be assisted by the cache via
     * conditional GET requests.
     */
    public static final CacheControl FORCE_NETWORK = new Builder().noCache().build();

    /**
     * Cache control request directives that uses the cache only, even if the
     * cached response is stale. If the response isn't available in the cache or
     * requires server validation, the call will fail with a {@code 504
     * Unsatisfiable Request}.
     */
    public static final CacheControl FORCE_CACHE = new Builder().onlyIfCached()
            .maxStale(Integer.MAX_VALUE, TimeUnit.SECONDS).build();

    private final boolean noCache;
    private final boolean noStore;
    private final int maxAgeSeconds;
    private final int sMaxAgeSeconds;
    private final boolean isPrivate;
    private final boolean isPublic;
    private final boolean mustRevalidate;
    private final int maxStaleSeconds;
    private final int minFreshSeconds;
    private final boolean onlyIfCached;
    private final boolean noTransform;

    String headerValue; // Lazily computed, if absent.

    private CacheControl(boolean noCache, boolean noStore, int maxAgeSeconds, int sMaxAgeSeconds, boolean isPrivate,
            boolean isPublic, boolean mustRevalidate, int maxStaleSeconds, int minFreshSeconds,
            boolean onlyIfCached, boolean noTransform, String headerValue) {
        this.noCache = noCache;
        this.noStore = noStore;
        this.maxAgeSeconds = maxAgeSeconds;
        this.sMaxAgeSeconds = sMaxAgeSeconds;
        this.isPrivate = isPrivate;
        this.isPublic = isPublic;
        this.mustRevalidate = mustRevalidate;
        this.maxStaleSeconds = maxStaleSeconds;
        this.minFreshSeconds = minFreshSeconds;
        this.onlyIfCached = onlyIfCached;
        this.noTransform = noTransform;
        this.headerValue = headerValue;
    }

    private CacheControl(Builder builder) {
        this.noCache = builder.noCache;
        this.noStore = builder.noStore;
        this.maxAgeSeconds = builder.maxAgeSeconds;
        this.sMaxAgeSeconds = -1;
        this.isPrivate = false;
        this.isPublic = false;
        this.mustRevalidate = false;
        this.maxStaleSeconds = builder.maxStaleSeconds;
        this.minFreshSeconds = builder.minFreshSeconds;
        this.onlyIfCached = builder.onlyIfCached;
        this.noTransform = builder.noTransform;
    }

    /**
     * In a response, this field's name "no-cache" is misleading. It doesn't
     * prevent us from caching the response; it only means we have to validate the
     * response with the origin server before returning it. We can do this with a
     * conditional GET.
     *
     * <p>In a request, it means do not use a cache to satisfy the request.
     */
    public boolean noCache() {
        return noCache;
    }

    /** If true, this response should not be cached. */
    public boolean noStore() {
        return noStore;
    }

    /**
     * The duration past the response's served date that it can be served without
     * validation.
     */
    public int maxAgeSeconds() {
        return maxAgeSeconds;
    }

    /**
     * The "s-maxage" directive is the max age for shared caches. Not to be
     * confused with "max-age" for non-shared caches, As in Firefox and Chrome,
     * this directive is not honored by this cache.
     */
    public int sMaxAgeSeconds() {
        return sMaxAgeSeconds;
    }

    public boolean isPrivate() {
        return isPrivate;
    }

    public boolean isPublic() {
        return isPublic;
    }

    public boolean mustRevalidate() {
        return mustRevalidate;
    }

    public int maxStaleSeconds() {
        return maxStaleSeconds;
    }

    public int minFreshSeconds() {
        return minFreshSeconds;
    }

    /**
     * This field's name "only-if-cached" is misleading. It actually means "do
     * not use the network". It is set by a client who only wants to make a
     * request if it can be fully satisfied by the cache. Cached responses that
     * would require validation (ie. conditional gets) are not permitted if this
     * header is set.
     */
    public boolean onlyIfCached() {
        return onlyIfCached;
    }

    public boolean noTransform() {
        return noTransform;
    }

    /**
     * Returns the cache directives of {@code headers}. This honors both
     * Cache-Control and Pragma headers if they are present.
     */
    public static CacheControl parse(Headers headers) {
        boolean noCache = false;
        boolean noStore = false;
        int maxAgeSeconds = -1;
        int sMaxAgeSeconds = -1;
        boolean isPrivate = false;
        boolean isPublic = false;
        boolean mustRevalidate = false;
        int maxStaleSeconds = -1;
        int minFreshSeconds = -1;
        boolean onlyIfCached = false;
        boolean noTransform = false;

        boolean canUseHeaderValue = true;
        String headerValue = null;

        for (int i = 0, size = headers.size(); i < size; i++) {
            String name = headers.name(i);
            String value = headers.value(i);

            if (name.equalsIgnoreCase("Cache-Control")) {
                if (headerValue != null) {
                    // Multiple cache-control headers means we can't use the raw value.
                    canUseHeaderValue = false;
                } else {
                    headerValue = value;
                }
            } else if (name.equalsIgnoreCase("Pragma")) {
                // Might specify additional cache-control params. We invalidate just in case.
                canUseHeaderValue = false;
            } else {
                continue;
            }

            int pos = 0;
            while (pos < value.length()) {
                int tokenStart = pos;
                pos = HeaderParser.skipUntil(value, pos, "=,;");
                String directive = value.substring(tokenStart, pos).trim();
                String parameter;

                if (pos == value.length() || value.charAt(pos) == ',' || value.charAt(pos) == ';') {
                    pos++; // consume ',' or ';' (if necessary)
                    parameter = null;
                } else {
                    pos++; // consume '='
                    pos = HeaderParser.skipWhitespace(value, pos);

                    // quoted string
                    if (pos < value.length() && value.charAt(pos) == '\"') {
                        pos++; // consume '"' open quote
                        int parameterStart = pos;
                        pos = HeaderParser.skipUntil(value, pos, "\"");
                        parameter = value.substring(parameterStart, pos);
                        pos++; // consume '"' close quote (if necessary)

                        // unquoted string
                    } else {
                        int parameterStart = pos;
                        pos = HeaderParser.skipUntil(value, pos, ",;");
                        parameter = value.substring(parameterStart, pos).trim();
                    }
                }

                if ("no-cache".equalsIgnoreCase(directive)) {
                    noCache = true;
                } else if ("no-store".equalsIgnoreCase(directive)) {
                    noStore = true;
                } else if ("max-age".equalsIgnoreCase(directive)) {
                    maxAgeSeconds = HeaderParser.parseSeconds(parameter, -1);
                } else if ("s-maxage".equalsIgnoreCase(directive)) {
                    sMaxAgeSeconds = HeaderParser.parseSeconds(parameter, -1);
                } else if ("private".equalsIgnoreCase(directive)) {
                    isPrivate = true;
                } else if ("public".equalsIgnoreCase(directive)) {
                    isPublic = true;
                } else if ("must-revalidate".equalsIgnoreCase(directive)) {
                    mustRevalidate = true;
                } else if ("max-stale".equalsIgnoreCase(directive)) {
                    maxStaleSeconds = HeaderParser.parseSeconds(parameter, Integer.MAX_VALUE);
                } else if ("min-fresh".equalsIgnoreCase(directive)) {
                    minFreshSeconds = HeaderParser.parseSeconds(parameter, -1);
                } else if ("only-if-cached".equalsIgnoreCase(directive)) {
                    onlyIfCached = true;
                } else if ("no-transform".equalsIgnoreCase(directive)) {
                    noTransform = true;
                }
            }
        }

        if (!canUseHeaderValue) {
            headerValue = null;
        }
        return new CacheControl(noCache, noStore, maxAgeSeconds, sMaxAgeSeconds, isPrivate, isPublic,
                mustRevalidate, maxStaleSeconds, minFreshSeconds, onlyIfCached, noTransform, headerValue);
    }

    @Override
    public String toString() {
        String result = headerValue;
        return result != null ? result : (headerValue = headerValue());
    }

    private String headerValue() {
        StringBuilder result = new StringBuilder();
        if (noCache)
            result.append("no-cache, ");
        if (noStore)
            result.append("no-store, ");
        if (maxAgeSeconds != -1)
            result.append("max-age=").append(maxAgeSeconds).append(", ");
        if (sMaxAgeSeconds != -1)
            result.append("s-maxage=").append(sMaxAgeSeconds).append(", ");
        if (isPrivate)
            result.append("private, ");
        if (isPublic)
            result.append("public, ");
        if (mustRevalidate)
            result.append("must-revalidate, ");
        if (maxStaleSeconds != -1)
            result.append("max-stale=").append(maxStaleSeconds).append(", ");
        if (minFreshSeconds != -1)
            result.append("min-fresh=").append(minFreshSeconds).append(", ");
        if (onlyIfCached)
            result.append("only-if-cached, ");
        if (noTransform)
            result.append("no-transform, ");
        if (result.length() == 0)
            return "";
        result.delete(result.length() - 2, result.length());
        return result.toString();
    }

    /** Builds a {@code Cache-Control} request header. */
    public static final class Builder {
        boolean noCache;
        boolean noStore;
        int maxAgeSeconds = -1;
        int maxStaleSeconds = -1;
        int minFreshSeconds = -1;
        boolean onlyIfCached;
        boolean noTransform;

        /** Don't accept an unvalidated cached response. */
        public Builder noCache() {
            this.noCache = true;
            return this;
        }

        /** Don't store the server's response in any cache. */
        public Builder noStore() {
            this.noStore = true;
            return this;
        }

        /**
         * Sets the maximum age of a cached response. If the cache response's age
         * exceeds {@code maxAge}, it will not be used and a network request will
         * be made.
         *
         * @param maxAge a non-negative integer. This is stored and transmitted with
         *     {@link TimeUnit#SECONDS} precision; finer precision will be lost.
         */
        public Builder maxAge(int maxAge, TimeUnit timeUnit) {
            if (maxAge < 0)
                throw new IllegalArgumentException("maxAge < 0: " + maxAge);
            long maxAgeSecondsLong = timeUnit.toSeconds(maxAge);
            this.maxAgeSeconds = maxAgeSecondsLong > Integer.MAX_VALUE ? Integer.MAX_VALUE
                    : (int) maxAgeSecondsLong;
            return this;
        }

        /**
         * Accept cached responses that have exceeded their freshness lifetime by
         * up to {@code maxStale}. If unspecified, stale cache responses will not be
         * used.
         *
         * @param maxStale a non-negative integer. This is stored and transmitted
         *     with {@link TimeUnit#SECONDS} precision; finer precision will be
         *     lost.
         */
        public Builder maxStale(int maxStale, TimeUnit timeUnit) {
            if (maxStale < 0)
                throw new IllegalArgumentException("maxStale < 0: " + maxStale);
            long maxStaleSecondsLong = timeUnit.toSeconds(maxStale);
            this.maxStaleSeconds = maxStaleSecondsLong > Integer.MAX_VALUE ? Integer.MAX_VALUE
                    : (int) maxStaleSecondsLong;
            return this;
        }

        /**
         * Sets the minimum number of seconds that a response will continue to be
         * fresh for. If the response will be stale when {@code minFresh} have
         * elapsed, the cached response will not be used and a network request will
         * be made.
         *
         * @param minFresh a non-negative integer. This is stored and transmitted
         *     with {@link TimeUnit#SECONDS} precision; finer precision will be
         *     lost.
         */
        public Builder minFresh(int minFresh, TimeUnit timeUnit) {
            if (minFresh < 0)
                throw new IllegalArgumentException("minFresh < 0: " + minFresh);
            long minFreshSecondsLong = timeUnit.toSeconds(minFresh);
            this.minFreshSeconds = minFreshSecondsLong > Integer.MAX_VALUE ? Integer.MAX_VALUE
                    : (int) minFreshSecondsLong;
            return this;
        }

        /**
         * Only accept the response if it is in the cache. If the response isn't
         * cached, a {@code 504 Unsatisfiable Request} response will be returned.
         */
        public Builder onlyIfCached() {
            this.onlyIfCached = true;
            return this;
        }

        /** Don't accept a transformed response. */
        public Builder noTransform() {
            this.noTransform = true;
            return this;
        }

        public CacheControl build() {
            return new CacheControl(this);
        }
    }
}