/*
 * Copyright (C) 2007 The Guava 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 com.google.common.base;

import static com.google.common.base.Strings.lenientFormat;

import com.google.common.annotations.GwtCompatible;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
 * Static convenience methods that help a method or constructor check whether it was invoked
 * correctly (that is, whether its preconditions were met).
 *
 *
If the precondition is not met, the {@code Preconditions} method throws an unchecked exception
 * of a specified type, which helps the method in which the exception was thrown communicate that
 * its caller has made a mistake. This allows constructs such as
 *
 *
{@code
 * public static double sqrt(double value) {
 *   if (value < 0) {
 *     throw new IllegalArgumentException("input is negative: " + value);
 *   }
 *   // calculate square root
 * }
 * }

 *
 *
to be replaced with the more compact
 *
 *
{@code
 * public static double sqrt(double value) {
 *   checkArgument(value >= 0, "input is negative: %s", value);
 *   // calculate square root
 * }
 * }

 *
 *
so that a hypothetical bad caller of this method, such as:
 *
 *
{@code
 *   void exampleBadCaller() {
 *     double d = sqrt(-1.0);
 * }
 * }

 *
 *
would be flagged as having called {@code sqrt()} with an illegal argument.
 *
 *
Performance

 *
 *
Avoid passing message arguments that are expensive to compute; your code will always compute
 * them, even though they usually won't be needed. If you have such arguments, use the conventional
 * if/throw idiom instead.
 *
 *
Depending on your message arguments, memory may be allocated for boxing and varargs array
 * creation. However, the methods of this class have a large number of overloads that prevent such
 * allocations in many common cases.
 *
 *
The message string is not formatted unless the exception will be thrown, so the cost of the
 * string formatting itself should not be a concern.
 *
 *
As with any performance concerns, you should consider profiling your code (in a production
 * environment if possible) before spending a lot of effort on tweaking a particular element.
 *
 *
Other types of preconditions

 *
 *
Not every type of precondition failure is supported by these methods. Continue to throw
 * standard JDK exceptions such as {@link java.util.NoSuchElementException} or {@link
 * UnsupportedOperationException} in the situations they are intended for.
 *
 *
Non-preconditions

 *
 *
It is of course possible to use the methods of this class to check for invalid conditions
 * which are not the caller's fault. Doing so is not recommended because it is
 * misleading to future readers of the code and of stack traces. See Conditional failures
 * explained in the Guava User Guide for more advice. Notably, {@link Verify} offers assertions
 * similar to those in this class for non-precondition checks.
 *
 *
{@code java.util.Objects.requireNonNull()}

 *
 *
Projects which use {@code com.google.common} should generally avoid the use of {@link
 * java.util.Objects#requireNonNull(Object)}. Instead, use whichever of {@link
 * #checkNotNull(Object)} or {@link Verify#verifyNotNull(Object)} is appropriate to the situation.
 * (The same goes for the message-accepting overloads.)
 *
 *
Only {@code %s} is supported

 *
 *
{@code Preconditions} uses {@link Strings#lenientFormat} to format error message template
 * strings. This only supports the {@code "%s"} specifier, not the full range of {@link
 * java.util.Formatter} specifiers. However, note that if the number of arguments does not match the
 * number of occurrences of {@code "%s"} in the format string, {@code Preconditions} will still
 * behave as expected, and will still include all argument values in the error message; the message
 * will simply not be formatted exactly as intended.
 *
 *
More information

 *
 *
See the Guava User Guide on using {@code
 * Preconditions}.
 *
 * @author Kevin Bourrillion
 * @since 2.0
 */
@GwtCompatible
public final class Preconditions {
  private Preconditions() {}

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   * @param expression a boolean expression
   * @throws IllegalArgumentException if {@code expression} is false
   */
  public static void checkArgument(boolean expression) {
    if (!expression) {
      throw new IllegalArgumentException();
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   * @param expression a boolean expression
   * @param errorMessage the exception message to use if the check fails; will be converted to a
   *     string using {@link String#valueOf(Object)}
   * @throws IllegalArgumentException if {@code expression} is false
   */
  public static void checkArgument(boolean expression, @Nullable Object errorMessage) {
    if (!expression) {
      throw new IllegalArgumentException(String.valueOf(errorMessage));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   * @param expression a boolean expression
   * @param errorMessageTemplate a template for the exception message should the check fail. The
   *     message is formed by replacing each {@code %s} placeholder in the template with an
   *     argument. These are matched by position - the first {@code %s} gets {@code
   *     errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message in
   *     square braces. Unmatched placeholders will be left as-is.
   * @param errorMessageArgs the arguments to be substituted into the message template. Arguments
   *     are converted to strings using {@link String#valueOf(Object)}.
   * @throws IllegalArgumentException if {@code expression} is false
   */
  public static void checkArgument(
      boolean expression,
      @Nullable String errorMessageTemplate,
      Object @Nullable ... errorMessageArgs) {
    if (!expression) {
      throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, errorMessageArgs));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   *
See {@link #checkArgument(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkArgument(boolean b, @Nullable String errorMessageTemplate, char p1) {
    if (!b) {
      throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   *
See {@link #checkArgument(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkArgument(boolean b, @Nullable String errorMessageTemplate, int p1) {
    if (!b) {
      throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   *
See {@link #checkArgument(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkArgument(boolean b, @Nullable String errorMessageTemplate, long p1) {
    if (!b) {
      throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   *
See {@link #checkArgument(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkArgument(
      boolean b, @Nullable String errorMessageTemplate, @Nullable Object p1) {
    if (!b) {
      throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   *
See {@link #checkArgument(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkArgument(
      boolean b, @Nullable String errorMessageTemplate, char p1, char p2) {
    if (!b) {
      throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   *
See {@link #checkArgument(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkArgument(
      boolean b, @Nullable String errorMessageTemplate, char p1, int p2) {
    if (!b) {
      throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   *
See {@link #checkArgument(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkArgument(
      boolean b, @Nullable String errorMessageTemplate, char p1, long p2) {
    if (!b) {
      throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   *
See {@link #checkArgument(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkArgument(
      boolean b, @Nullable String errorMessageTemplate, char p1, @Nullable Object p2) {
    if (!b) {
      throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   *
See {@link #checkArgument(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkArgument(
      boolean b, @Nullable String errorMessageTemplate, int p1, char p2) {
    if (!b) {
      throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   *
See {@link #checkArgument(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkArgument(
      boolean b, @Nullable String errorMessageTemplate, int p1, int p2) {
    if (!b) {
      throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   *
See {@link #checkArgument(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkArgument(
      boolean b, @Nullable String errorMessageTemplate, int p1, long p2) {
    if (!b) {
      throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   *
See {@link #checkArgument(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkArgument(
      boolean b, @Nullable String errorMessageTemplate, int p1, @Nullable Object p2) {
    if (!b) {
      throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   *
See {@link #checkArgument(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkArgument(
      boolean b, @Nullable String errorMessageTemplate, long p1, char p2) {
    if (!b) {
      throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   *
See {@link #checkArgument(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkArgument(
      boolean b, @Nullable String errorMessageTemplate, long p1, int p2) {
    if (!b) {
      throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   *
See {@link #checkArgument(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkArgument(
      boolean b, @Nullable String errorMessageTemplate, long p1, long p2) {
    if (!b) {
      throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   *
See {@link #checkArgument(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkArgument(
      boolean b, @Nullable String errorMessageTemplate, long p1, @Nullable Object p2) {
    if (!b) {
      throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   *
See {@link #checkArgument(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkArgument(
      boolean b, @Nullable String errorMessageTemplate, @Nullable Object p1, char p2) {
    if (!b) {
      throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   *
See {@link #checkArgument(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkArgument(
      boolean b, @Nullable String errorMessageTemplate, @Nullable Object p1, int p2) {
    if (!b) {
      throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   *
See {@link #checkArgument(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkArgument(
      boolean b, @Nullable String errorMessageTemplate, @Nullable Object p1, long p2) {
    if (!b) {
      throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   *
See {@link #checkArgument(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkArgument(
      boolean b, @Nullable String errorMessageTemplate, @Nullable Object p1, @Nullable Object p2) {
    if (!b) {
      throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   *
See {@link #checkArgument(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkArgument(
      boolean b,
      @Nullable String errorMessageTemplate,
      @Nullable Object p1,
      @Nullable Object p2,
      @Nullable Object p3) {
    if (!b) {
      throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2, p3));
    }
  }

  /**
   * Ensures the truth of an expression involving one or more parameters to the calling method.
   *
   *
See {@link #checkArgument(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkArgument(
      boolean b,
      @Nullable String errorMessageTemplate,
      @Nullable Object p1,
      @Nullable Object p2,
      @Nullable Object p3,
      @Nullable Object p4) {
    if (!b) {
      throw new IllegalArgumentException(lenientFormat(errorMessageTemplate, p1, p2, p3, p4));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   * @param expression a boolean expression
   * @throws IllegalStateException if {@code expression} is false
   * @see Verify#verify Verify.verify()
   */
  public static void checkState(boolean expression) {
    if (!expression) {
      throw new IllegalStateException();
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   * @param expression a boolean expression
   * @param errorMessage the exception message to use if the check fails; will be converted to a
   *     string using {@link String#valueOf(Object)}
   * @throws IllegalStateException if {@code expression} is false
   * @see Verify#verify Verify.verify()
   */
  public static void checkState(boolean expression, @Nullable Object errorMessage) {
    if (!expression) {
      throw new IllegalStateException(String.valueOf(errorMessage));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   * @param expression a boolean expression
   * @param errorMessageTemplate a template for the exception message should the check fail. The
   *     message is formed by replacing each {@code %s} placeholder in the template with an
   *     argument. These are matched by position - the first {@code %s} gets {@code
   *     errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message in
   *     square braces. Unmatched placeholders will be left as-is.
   * @param errorMessageArgs the arguments to be substituted into the message template. Arguments
   *     are converted to strings using {@link String#valueOf(Object)}.
   * @throws IllegalStateException if {@code expression} is false
   * @see Verify#verify Verify.verify()
   */
  public static void checkState(
      boolean expression,
      @Nullable String errorMessageTemplate,
      @Nullable Object @Nullable ... errorMessageArgs) {
    if (!expression) {
      throw new IllegalStateException(lenientFormat(errorMessageTemplate, errorMessageArgs));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   *
See {@link #checkState(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkState(boolean b, @Nullable String errorMessageTemplate, char p1) {
    if (!b) {
      throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   *
See {@link #checkState(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkState(boolean b, @Nullable String errorMessageTemplate, int p1) {
    if (!b) {
      throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   *
See {@link #checkState(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkState(boolean b, @Nullable String errorMessageTemplate, long p1) {
    if (!b) {
      throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   *
See {@link #checkState(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkState(
      boolean b, @Nullable String errorMessageTemplate, @Nullable Object p1) {
    if (!b) {
      throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   *
See {@link #checkState(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkState(
      boolean b, @Nullable String errorMessageTemplate, char p1, char p2) {
    if (!b) {
      throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   *
See {@link #checkState(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkState(boolean b, @Nullable String errorMessageTemplate, char p1, int p2) {
    if (!b) {
      throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   *
See {@link #checkState(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkState(
      boolean b, @Nullable String errorMessageTemplate, char p1, long p2) {
    if (!b) {
      throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   *
See {@link #checkState(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkState(
      boolean b, @Nullable String errorMessageTemplate, char p1, @Nullable Object p2) {
    if (!b) {
      throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   *
See {@link #checkState(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkState(boolean b, @Nullable String errorMessageTemplate, int p1, char p2) {
    if (!b) {
      throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   *
See {@link #checkState(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkState(boolean b, @Nullable String errorMessageTemplate, int p1, int p2) {
    if (!b) {
      throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   *
See {@link #checkState(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkState(boolean b, @Nullable String errorMessageTemplate, int p1, long p2) {
    if (!b) {
      throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   *
See {@link #checkState(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkState(
      boolean b, @Nullable String errorMessageTemplate, int p1, @Nullable Object p2) {
    if (!b) {
      throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   *
See {@link #checkState(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkState(
      boolean b, @Nullable String errorMessageTemplate, long p1, char p2) {
    if (!b) {
      throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   *
See {@link #checkState(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkState(boolean b, @Nullable String errorMessageTemplate, long p1, int p2) {
    if (!b) {
      throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   *
See {@link #checkState(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkState(
      boolean b, @Nullable String errorMessageTemplate, long p1, long p2) {
    if (!b) {
      throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   *
See {@link #checkState(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkState(
      boolean b, @Nullable String errorMessageTemplate, long p1, @Nullable Object p2) {
    if (!b) {
      throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   *
See {@link #checkState(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkState(
      boolean b, @Nullable String errorMessageTemplate, @Nullable Object p1, char p2) {
    if (!b) {
      throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   *
See {@link #checkState(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkState(
      boolean b, @Nullable String errorMessageTemplate, @Nullable Object p1, int p2) {
    if (!b) {
      throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   *
See {@link #checkState(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkState(
      boolean b, @Nullable String errorMessageTemplate, @Nullable Object p1, long p2) {
    if (!b) {
      throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   *
See {@link #checkState(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkState(
      boolean b, @Nullable String errorMessageTemplate, @Nullable Object p1, @Nullable Object p2) {
    if (!b) {
      throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   *
See {@link #checkState(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkState(
      boolean b,
      @Nullable String errorMessageTemplate,
      @Nullable Object p1,
      @Nullable Object p2,
      @Nullable Object p3) {
    if (!b) {
      throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2, p3));
    }
  }

  /**
   * Ensures the truth of an expression involving the state of the calling instance, but not
   * involving any parameters to the calling method.
   *
   *
See {@link #checkState(boolean, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  public static void checkState(
      boolean b,
      @Nullable String errorMessageTemplate,
      @Nullable Object p1,
      @Nullable Object p2,
      @Nullable Object p3,
      @Nullable Object p4) {
    if (!b) {
      throw new IllegalStateException(lenientFormat(errorMessageTemplate, p1, p2, p3, p4));
    }
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   * @param reference an object reference
   * @return the non-null reference that was validated
   * @throws NullPointerException if {@code reference} is null
   * @see Verify#verifyNotNull Verify.verifyNotNull()
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(T reference) {
    if (reference == null) {
      throw new NullPointerException();
    }
    return reference;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   * @param reference an object reference
   * @param errorMessage the exception message to use if the check fails; will be converted to a
   *     string using {@link String#valueOf(Object)}
   * @return the non-null reference that was validated
   * @throws NullPointerException if {@code reference} is null
   * @see Verify#verifyNotNull Verify.verifyNotNull()
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T reference, @Nullable Object errorMessage) {
    if (reference == null) {
      throw new NullPointerException(String.valueOf(errorMessage));
    }
    return reference;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   * @param reference an object reference
   * @param errorMessageTemplate a template for the exception message should the check fail. The
   *     message is formed by replacing each {@code %s} placeholder in the template with an
   *     argument. These are matched by position - the first {@code %s} gets {@code
   *     errorMessageArgs[0]}, etc. Unmatched arguments will be appended to the formatted message in
   *     square braces. Unmatched placeholders will be left as-is.
   * @param errorMessageArgs the arguments to be substituted into the message template. Arguments
   *     are converted to strings using {@link String#valueOf(Object)}.
   * @return the non-null reference that was validated
   * @throws NullPointerException if {@code reference} is null
   * @see Verify#verifyNotNull Verify.verifyNotNull()
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T reference, @Nullable String errorMessageTemplate, Object @Nullable ... errorMessageArgs) {
    if (reference == null) {
      throw new NullPointerException(lenientFormat(errorMessageTemplate, errorMessageArgs));
    }
    return reference;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   *
See {@link #checkNotNull(Object, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T obj, @Nullable String errorMessageTemplate, char p1) {
    if (obj == null) {
      throw new NullPointerException(lenientFormat(errorMessageTemplate, p1));
    }
    return obj;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   *
See {@link #checkNotNull(Object, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T obj, @Nullable String errorMessageTemplate, int p1) {
    if (obj == null) {
      throw new NullPointerException(lenientFormat(errorMessageTemplate, p1));
    }
    return obj;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   *
See {@link #checkNotNull(Object, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T obj, @Nullable String errorMessageTemplate, long p1) {
    if (obj == null) {
      throw new NullPointerException(lenientFormat(errorMessageTemplate, p1));
    }
    return obj;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   *
See {@link #checkNotNull(Object, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T obj, @Nullable String errorMessageTemplate, @Nullable Object p1) {
    if (obj == null) {
      throw new NullPointerException(lenientFormat(errorMessageTemplate, p1));
    }
    return obj;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   *
See {@link #checkNotNull(Object, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T obj, @Nullable String errorMessageTemplate, char p1, char p2) {
    if (obj == null) {
      throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2));
    }
    return obj;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   *
See {@link #checkNotNull(Object, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T obj, @Nullable String errorMessageTemplate, char p1, int p2) {
    if (obj == null) {
      throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2));
    }
    return obj;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   *
See {@link #checkNotNull(Object, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T obj, @Nullable String errorMessageTemplate, char p1, long p2) {
    if (obj == null) {
      throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2));
    }
    return obj;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   *
See {@link #checkNotNull(Object, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T obj, @Nullable String errorMessageTemplate, char p1, @Nullable Object p2) {
    if (obj == null) {
      throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2));
    }
    return obj;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   *
See {@link #checkNotNull(Object, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T obj, @Nullable String errorMessageTemplate, int p1, char p2) {
    if (obj == null) {
      throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2));
    }
    return obj;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   *
See {@link #checkNotNull(Object, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T obj, @Nullable String errorMessageTemplate, int p1, int p2) {
    if (obj == null) {
      throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2));
    }
    return obj;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   *
See {@link #checkNotNull(Object, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T obj, @Nullable String errorMessageTemplate, int p1, long p2) {
    if (obj == null) {
      throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2));
    }
    return obj;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   *
See {@link #checkNotNull(Object, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T obj, @Nullable String errorMessageTemplate, int p1, @Nullable Object p2) {
    if (obj == null) {
      throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2));
    }
    return obj;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   *
See {@link #checkNotNull(Object, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T obj, @Nullable String errorMessageTemplate, long p1, char p2) {
    if (obj == null) {
      throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2));
    }
    return obj;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   *
See {@link #checkNotNull(Object, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T obj, @Nullable String errorMessageTemplate, long p1, int p2) {
    if (obj == null) {
      throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2));
    }
    return obj;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   *
See {@link #checkNotNull(Object, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T obj, @Nullable String errorMessageTemplate, long p1, long p2) {
    if (obj == null) {
      throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2));
    }
    return obj;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   *
See {@link #checkNotNull(Object, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T obj, @Nullable String errorMessageTemplate, long p1, @Nullable Object p2) {
    if (obj == null) {
      throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2));
    }
    return obj;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   *
See {@link #checkNotNull(Object, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T obj, @Nullable String errorMessageTemplate, @Nullable Object p1, char p2) {
    if (obj == null) {
      throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2));
    }
    return obj;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   *
See {@link #checkNotNull(Object, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T obj, @Nullable String errorMessageTemplate, @Nullable Object p1, int p2) {
    if (obj == null) {
      throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2));
    }
    return obj;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   *
See {@link #checkNotNull(Object, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T obj, @Nullable String errorMessageTemplate, @Nullable Object p1, long p2) {
    if (obj == null) {
      throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2));
    }
    return obj;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   *
See {@link #checkNotNull(Object, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T obj, @Nullable String errorMessageTemplate, @Nullable Object p1, @Nullable Object p2) {
    if (obj == null) {
      throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2));
    }
    return obj;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   *
See {@link #checkNotNull(Object, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T obj,
      @Nullable String errorMessageTemplate,
      @Nullable Object p1,
      @Nullable Object p2,
      @Nullable Object p3) {
    if (obj == null) {
      throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2, p3));
    }
    return obj;
  }

  /**
   * Ensures that an object reference passed as a parameter to the calling method is not null.
   *
   *
See {@link #checkNotNull(Object, String, Object...)} for details.
   *
   * @since 20.0 (varargs overload since 2.0)
   */
  @CanIgnoreReturnValue
  public static <T extends @NonNull Object> T checkNotNull(
      T obj,
      @Nullable String errorMessageTemplate,
      @Nullable Object p1,
      @Nullable Object p2,
      @Nullable Object p3,
      @Nullable Object p4) {
    if (obj == null) {
      throw new NullPointerException(lenientFormat(errorMessageTemplate, p1, p2, p3, p4));
    }
    return obj;
  }

  /*
   * All recent hotspots (as of 2009) *really* like to have the natural code
   *
   * if (guardExpression) {
   *    throw new BadException(messageExpression);
   * }
   *
   * refactored so that messageExpression is moved to a separate String-returning method.
   *
   * if (guardExpression) {
   *    throw new BadException(badMsg(...));
   * }
   *
   * The alternative natural refactorings into void or Exception-returning methods are much slower.
   * This is a big deal - we're talking factors of 2-8 in microbenchmarks, not just 10-20%. (This is
   * a hotspot optimizer bug, which should be fixed, but that's a separate, big project).
   *
   * The coding pattern above is heavily used in java.util, e.g. in ArrayList. There is a
   * RangeCheckMicroBenchmark in the JDK that was used to test this.
   *
   * But the methods in this class want to throw different exceptions, depending on the args, so it
   * appears that this pattern is not directly applicable. But we can use the ridiculous, devious
   * trick of throwing an exception in the middle of the construction of another exception. Hotspot
   * is fine with that.
   */

  /**
   * Ensures that {@code index} specifies a valid element in an array, list or string of size
   * {@code size}. An element index may range from zero, inclusive, to {@code size}, exclusive.
   *
   * @param index a user-supplied index identifying an element of an array, list or string
   * @param size the size of that array, list or string
   * @return the value of {@code index}
   * @throws IndexOutOfBoundsException if {@code index} is negative or is not less than {@code size}
   * @throws IllegalArgumentException if {@code size} is negative
   */
  @CanIgnoreReturnValue
  public static int checkElementIndex(int index, int size) {
    return checkElementIndex(index, size, "index");
  }

  /**
   * Ensures that {@code index} specifies a valid element in an array, list or string of size
   * {@code size}. An element index may range from zero, inclusive, to {@code size}, exclusive.
   *
   * @param index a user-supplied index identifying an element of an array, list or string
   * @param size the size of that array, list or string
   * @param desc the text to use to describe this index in an error message
   * @return the value of {@code index}
   * @throws IndexOutOfBoundsException if {@code index} is negative or is not less than {@code size}
   * @throws IllegalArgumentException if {@code size} is negative
   */
  @CanIgnoreReturnValue
  public static int checkElementIndex(int index, int size, @Nullable String desc) {
    // Carefully optimized for execution by hotspot (explanatory comment above)
    if (index < 0 || index >= size) {
      throw new IndexOutOfBoundsException(badElementIndex(index, size, desc));
    }
    return index;
  }

  private static String badElementIndex(int index, int size, @Nullable String desc) {
    if (index < 0) {
      return lenientFormat("%s (%s) must not be negative", desc, index);
    } else if (size < 0) {
      throw new IllegalArgumentException("negative size: " + size);
    } else { // index >= size
      return lenientFormat("%s (%s) must be less than size (%s)", desc, index, size);
    }
  }

  /**
   * Ensures that {@code index} specifies a valid position in an array, list or string of
   * size {@code size}. A position index may range from zero to {@code size}, inclusive.
   *
   * @param index a user-supplied index identifying a position in an array, list or string
   * @param size the size of that array, list or string
   * @return the value of {@code index}
   * @throws IndexOutOfBoundsException if {@code index} is negative or is greater than {@code size}
   * @throws IllegalArgumentException if {@code size} is negative
   */
  @CanIgnoreReturnValue
  public static int checkPositionIndex(int index, int size) {
    return checkPositionIndex(index, size, "index");
  }

  /**
   * Ensures that {@code index} specifies a valid position in an array, list or string of
   * size {@code size}. A position index may range from zero to {@code size}, inclusive.
   *
   * @param index a user-supplied index identifying a position in an array, list or string
   * @param size the size of that array, list or string
   * @param desc the text to use to describe this index in an error message
   * @return the value of {@code index}
   * @throws IndexOutOfBoundsException if {@code index} is negative or is greater than {@code size}
   * @throws IllegalArgumentException if {@code size} is negative
   */
  @CanIgnoreReturnValue
  public static int checkPositionIndex(int index, int size, @Nullable String desc) {
    // Carefully optimized for execution by hotspot (explanatory comment above)
    if (index < 0 || index > size) {
      throw new IndexOutOfBoundsException(badPositionIndex(index, size, desc));
    }
    return index;
  }

  private static String badPositionIndex(int index, int size, @Nullable String desc) {
    if (index < 0) {
      return lenientFormat("%s (%s) must not be negative", desc, index);
    } else if (size < 0) {
      throw new IllegalArgumentException("negative size: " + size);
    } else { // index > size
      return lenientFormat("%s (%s) must not be greater than size (%s)", desc, index, size);
    }
  }

  /**
   * Ensures that {@code start} and {@code end} specify a valid positions in an array, list
   * or string of size {@code size}, and are in order. A position index may range from zero to
   * {@code size}, inclusive.
   *
   * @param start a user-supplied index identifying a starting position in an array, list or string
   * @param end a user-supplied index identifying a ending position in an array, list or string
   * @param size the size of that array, list or string
   * @throws IndexOutOfBoundsException if either index is negative or is greater than {@code size},
   *     or if {@code end} is less than {@code start}
   * @throws IllegalArgumentException if {@code size} is negative
   */
  public static void checkPositionIndexes(int start, int end, int size) {
    // Carefully optimized for execution by hotspot (explanatory comment above)
    if (start < 0 || end < start || end > size) {
      throw new IndexOutOfBoundsException(badPositionIndexes(start, end, size));
    }
  }

  private static String badPositionIndexes(int start, int end, int size) {
    if (start < 0 || start > size) {
      return badPositionIndex(start, size, "start index");
    }
    if (end < 0 || end > size) {
      return badPositionIndex(end, size, "end index");
    }
    // end < start
    return lenientFormat("end index (%s) must not be less than start index (%s)", end, start);
  }
}


/*
 * Copyright (C) 2011 The Guava 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 com.google.common.net;

import static com.google.common.base.CharMatcher.ascii;
import static com.google.common.base.CharMatcher.javaIsoControl;
import static com.google.common.base.Charsets.UTF_8;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import com.google.common.annotations.Beta;
import com.google.common.annotations.GwtCompatible;
import com.google.common.base.Ascii;
import com.google.common.base.CharMatcher;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Joiner.MapJoiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multimaps;
import com.google.errorprone.annotations.Immutable;
import com.google.errorprone.annotations.concurrent.LazyInit;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Collection;
import java.util.Map;
import java.util.Map.Entry;
import org.checkerframework.checker.nullness.qual.Nullable;

/**
 * Represents an Internet Media Type
 * (also known as a MIME Type or Content Type). This class also supports the concept of media ranges
 * defined by HTTP/1.1.
 * As such, the {@code *} character is treated as a wildcard and is used to represent any acceptable
 * type or subtype value. A media type may not have wildcard type with a declared subtype. The
 * {@code *} character has no special meaning as part of a parameter. All values for type, subtype,
 * parameter attributes or parameter values must be valid according to RFCs 2045 and 2046.
 *
 *
All portions of the media type that are case-insensitive (type, subtype, parameter attributes)
 * are normalized to lowercase. The value of the {@code charset} parameter is normalized to
 * lowercase, but all others are left as-is.
 *
 *
Note that this specifically does not represent the value of the MIME {@code
 * Content-Type} header and as such has no support for header-specific considerations such as line
 * folding and comments.
 *
 *
For media types that take a charset the predefined constants default to UTF-8 and have a
 * "_UTF_8" suffix. To get a version without a character set, use {@link #withoutParameters}.
 *
 * @since 12.0
 * @author Gregory Kick
 */
@Beta
@GwtCompatible
@Immutable
public final class MediaType {
  private static final String CHARSET_ATTRIBUTE = "charset";
  private static final ImmutableListMultimap<String, String> UTF_8_CONSTANT_PARAMETERS =
      ImmutableListMultimap.of(CHARSET_ATTRIBUTE, Ascii.toLowerCase(UTF_8.name()));

  /** Matcher for type, subtype and attributes. */
  private static final CharMatcher TOKEN_MATCHER =
      ascii()
          .and(javaIsoControl().negate())
          .and(CharMatcher.isNot(' '))
          .and(CharMatcher.noneOf("()<>@,;:\\\"/[]?="));

  private static final CharMatcher QUOTED_TEXT_MATCHER = ascii().and(CharMatcher.noneOf("\"\\\r"));

  /*
   * This matches the same characters as linear-white-space from RFC 822, but we make no effort to
   * enforce any particular rules with regards to line folding as stated in the class docs.
   */
  private static final CharMatcher LINEAR_WHITE_SPACE = CharMatcher.anyOf(" \t\r\n");

  // TODO(gak): make these public?
  private static final String APPLICATION_TYPE = "application";
  private static final String AUDIO_TYPE = "audio";
  private static final String IMAGE_TYPE = "image";
  private static final String TEXT_TYPE = "text";
  private static final String VIDEO_TYPE = "video";

  private static final String WILDCARD = "*";

  private static final Map<MediaType, MediaType> KNOWN_TYPES = Maps.newHashMap();

  private static MediaType createConstant(String type, String subtype) {
    MediaType mediaType =
        addKnownType(new MediaType(type, subtype, ImmutableListMultimap.<String, String>of()));
    mediaType.parsedCharset = Optional.absent();
    return mediaType;
  }

  private static MediaType createConstantUtf8(String type, String subtype) {
    MediaType mediaType = addKnownType(new MediaType(type, subtype, UTF_8_CONSTANT_PARAMETERS));
    mediaType.parsedCharset = Optional.of(UTF_8);
    return mediaType;
  }

  private static MediaType addKnownType(MediaType mediaType) {
    KNOWN_TYPES.put(mediaType, mediaType);
    return mediaType;
  }

  /*
   * The following constants are grouped by their type and ordered alphabetically by the constant
   * name within that type. The constant name should be a sensible identifier that is closest to the
   * "common name" of the media. This is often, but not necessarily the same as the subtype.
   *
   * Be sure to declare all constants with the type and subtype in all lowercase. For types that
   * take a charset (e.g. all text/* types), default to UTF-8 and suffix the constant name with
   * "_UTF_8".
   */

  public static final MediaType ANY_TYPE = createConstant(WILDCARD, WILDCARD);
  public static final MediaType ANY_TEXT_TYPE = createConstant(TEXT_TYPE, WILDCARD);
  public static final MediaType ANY_IMAGE_TYPE = createConstant(IMAGE_TYPE, WILDCARD);
  public static final MediaType ANY_AUDIO_TYPE = createConstant(AUDIO_TYPE, WILDCARD);
  public static final MediaType ANY_VIDEO_TYPE = createConstant(VIDEO_TYPE, WILDCARD);
  public static final MediaType ANY_APPLICATION_TYPE = createConstant(APPLICATION_TYPE, WILDCARD);

  /* text types */
  public static final MediaType CACHE_MANIFEST_UTF_8 =
      createConstantUtf8(TEXT_TYPE, "cache-manifest");
  public static final MediaType CSS_UTF_8 = createConstantUtf8(TEXT_TYPE, "css");
  public static final MediaType CSV_UTF_8 = createConstantUtf8(TEXT_TYPE, "csv");
  public static final MediaType HTML_UTF_8 = createConstantUtf8(TEXT_TYPE, "html");
  public static final MediaType I_CALENDAR_UTF_8 = createConstantUtf8(TEXT_TYPE, "calendar");
  public static final MediaType PLAIN_TEXT_UTF_8 = createConstantUtf8(TEXT_TYPE, "plain");

  /**
   * RFC 4329 declares {@link
   * #JAVASCRIPT_UTF_8 application/javascript} to be the correct media type for JavaScript, but this
   * may be necessary in certain situations for compatibility.
   */
  public static final MediaType TEXT_JAVASCRIPT_UTF_8 = createConstantUtf8(TEXT_TYPE, "javascript");
  /**
   * Tab separated
   * values.
   *
   * @since 15.0
   */
  public static final MediaType TSV_UTF_8 = createConstantUtf8(TEXT_TYPE, "tab-separated-values");

  public static final MediaType VCARD_UTF_8 = createConstantUtf8(TEXT_TYPE, "vcard");

  /**
   * UTF-8 encoded Wireless Markup
   * Language.
   *
   * @since 13.0
   */
  public static final MediaType WML_UTF_8 = createConstantUtf8(TEXT_TYPE, "vnd.wap.wml");

  /**
   * As described in RFC 3023, this constant
   * ({@code text/xml}) is used for XML documents that are "readable by casual users." {@link
   * #APPLICATION_XML_UTF_8} is provided for documents that are intended for applications.
   */
  public static final MediaType XML_UTF_8 = createConstantUtf8(TEXT_TYPE, "xml");

  /**
   * As described in the VTT spec, this is
   * used for Web Video Text Tracks (WebVTT) files, used with the HTML5 track element.
   *
   * @since 20.0
   */
  public static final MediaType VTT_UTF_8 = createConstantUtf8(TEXT_TYPE, "vtt");

  /**
   * Bitmap file format ({@code bmp}
   * files).
   *
   * @since 13.0
   */
  public static final MediaType BMP = createConstant(IMAGE_TYPE, "bmp");

  /**
   * The Canon Image File
   * Format ({@code crw} files), a widely-used "raw image" format for cameras. It is found in
   * {@code /etc/mime.types}, e.g. in Debian 3.48-1.
   *
   * @since 15.0
   */
  public static final MediaType CRW = createConstant(IMAGE_TYPE, "x-canon-crw");

  public static final MediaType GIF = createConstant(IMAGE_TYPE, "gif");
  public static final MediaType ICO = createConstant(IMAGE_TYPE, "vnd.microsoft.icon");
  public static final MediaType JPEG = createConstant(IMAGE_TYPE, "jpeg");
  public static final MediaType PNG = createConstant(IMAGE_TYPE, "png");

  /**
   * The Photoshop File Format ({@code psd} files) as defined by IANA, and
   * found in {@code /etc/mime.types}, e.g.  of the
   * Apache HTTPD project; for the specification, see
   * Adobe Photoshop Document Format and Wikipedia; this is the
   * regular output/input of Photoshop (which can also export to various image formats; note that
   * files with extension "PSB" are in a distinct but related format).
   *
   *
This is a more recent replacement for the older, experimental type {@code x-photoshop}: RFC-2046.6.
   *
   * @since 15.0
   */
  public static final MediaType PSD = createConstant(IMAGE_TYPE, "vnd.adobe.photoshop");

  public static final MediaType SVG_UTF_8 = createConstantUtf8(IMAGE_TYPE, "svg+xml");
  public static final MediaType TIFF = createConstant(IMAGE_TYPE, "tiff");

  /**
   * WebP image format.
   *
   * @since 13.0
   */
  public static final MediaType WEBP = createConstant(IMAGE_TYPE, "webp");

  /* audio types */
  public static final MediaType MP4_AUDIO = createConstant(AUDIO_TYPE, "mp4");
  public static final MediaType MPEG_AUDIO = createConstant(AUDIO_TYPE, "mpeg");
  public static final MediaType OGG_AUDIO = createConstant(AUDIO_TYPE, "ogg");
  public static final MediaType WEBM_AUDIO = createConstant(AUDIO_TYPE, "webm");

  /**
   * L16 audio, as defined by RFC 2586.
   *
   * @since 24.1
   */
  public static final MediaType L16_AUDIO = createConstant(AUDIO_TYPE, "l16");

  /**
   * L24 audio, as defined by RFC 3190.
   *
   * @since 20.0
   */
  public static final MediaType L24_AUDIO = createConstant(AUDIO_TYPE, "l24");

  /**
   * Basic Audio, as defined by RFC
   * 2046.
   *
   * @since 20.0
   */
  public static final MediaType BASIC_AUDIO = createConstant(AUDIO_TYPE, "basic");

  /**
   * Advanced Audio Coding. For more information, see Advanced Audio Coding.
   *
   * @since 20.0
   */
  public static final MediaType AAC_AUDIO = createConstant(AUDIO_TYPE, "aac");

  /**
   * Vorbis Audio, as defined by RFC 5215.
   *
   * @since 20.0
   */
  public static final MediaType VORBIS_AUDIO = createConstant(AUDIO_TYPE, "vorbis");

  /**
   * Windows Media Audio. For more information, see file
   * name extensions for Windows Media metafiles.
   *
   * @since 20.0
   */
  public static final MediaType WMA_AUDIO = createConstant(AUDIO_TYPE, "x-ms-wma");

  /**
   * Windows Media metafiles. For more information, see file
   * name extensions for Windows Media metafiles.
   *
   * @since 20.0
   */
  public static final MediaType WAX_AUDIO = createConstant(AUDIO_TYPE, "x-ms-wax");

  /**
   * Real Audio. For more information, see this link.
   *
   * @since 20.0
   */
  public static final MediaType VND_REAL_AUDIO = createConstant(AUDIO_TYPE, "vnd.rn-realaudio");

  /**
   * WAVE format, as defined by RFC 2361.
   *
   * @since 20.0
   */
  public static final MediaType VND_WAVE_AUDIO = createConstant(AUDIO_TYPE, "vnd.wave");

  /* video types */
  public static final MediaType MP4_VIDEO = createConstant(VIDEO_TYPE, "mp4");
  public static final MediaType MPEG_VIDEO = createConstant(VIDEO_TYPE, "mpeg");
  public static final MediaType OGG_VIDEO = createConstant(VIDEO_TYPE, "ogg");
  public static final MediaType QUICKTIME = createConstant(VIDEO_TYPE, "quicktime");
  public static final MediaType WEBM_VIDEO = createConstant(VIDEO_TYPE, "webm");
  public static final MediaType WMV = createConstant(VIDEO_TYPE, "x-ms-wmv");

  /**
   * Flash video. For more information, see this link.
   *
   * @since 20.0
   */
  public static final MediaType FLV_VIDEO = createConstant(VIDEO_TYPE, "x-flv");

  /**
   * The 3GP multimedia container format. For more information, see 3GPP TS
   * 26.244.
   *
   * @since 20.0
   */
  public static final MediaType THREE_GPP_VIDEO = createConstant(VIDEO_TYPE, "3gpp");

  /**
   * The 3G2 multimedia container format. For more information, see 3GPP2
   * C.S0050-B.
   *
   * @since 20.0
   */
  public static final MediaType THREE_GPP2_VIDEO = createConstant(VIDEO_TYPE, "3gpp2");

  /* application types */
  /**
   * As described in RFC 3023, this constant
   * ({@code application/xml}) is used for XML documents that are "unreadable by casual users."
   * {@link #XML_UTF_8} is provided for documents that may be read by users.
   *
   * @since 14.0
   */
  public static final MediaType APPLICATION_XML_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "xml");

  public static final MediaType ATOM_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "atom+xml");
  public static final MediaType BZIP2 = createConstant(APPLICATION_TYPE, "x-bzip2");

  /**
   * Files in the dart
   * programming language.
   *
   * @since 19.0
   */
  public static final MediaType DART_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "dart");

  /**
   * Apple Passbook.
   *
   * @since 19.0
   */
  public static final MediaType APPLE_PASSBOOK =
      createConstant(APPLICATION_TYPE, "vnd.apple.pkpass");

  /**
   * Embedded OpenType fonts. This is
   * registered
   *  with the IANA.
   *
   * @since 17.0
   */
  public static final MediaType EOT = createConstant(APPLICATION_TYPE, "vnd.ms-fontobject");

  /**
   * As described in the International Digital Publishing Forum
   * EPUB is the distribution and interchange format standard for digital publications and
   * documents. This media type is defined in the EPUB Open Container Format
   * specification.
   *
   * @since 15.0
   */
  public static final MediaType EPUB = createConstant(APPLICATION_TYPE, "epub+zip");

  public static final MediaType FORM_DATA =
      createConstant(APPLICATION_TYPE, "x-www-form-urlencoded");

  /**
   * As described in PKCS #12: Personal
   * Information Exchange Syntax Standard, PKCS #12 defines an archive file format for storing
   * many cryptography objects as a single file.
   *
   * @since 15.0
   */
  public static final MediaType KEY_ARCHIVE = createConstant(APPLICATION_TYPE, "pkcs12");

  /**
   * This is a non-standard media type, but is commonly used in serving hosted binary files as it is
   *
   * known not to trigger content sniffing in current browsers. It should not be used in
   * other situations as it is not specified by any RFC and does not appear in the /IANA MIME Media Types list. Consider
   * {@link #OCTET_STREAM} for binary data that is not being served to a browser.
   *
   * @since 14.0
   */
  public static final MediaType APPLICATION_BINARY = createConstant(APPLICATION_TYPE, "binary");

  public static final MediaType GZIP = createConstant(APPLICATION_TYPE, "x-gzip");

  /**
   * JSON Hypertext
   * Application Language (HAL) documents.
   *
   * @since 26.0
   */
  public static final MediaType HAL_JSON = createConstant(APPLICATION_TYPE, "hal+json");

  /**
   * RFC 4329 declares this to be the
   * correct media type for JavaScript, but {@link #TEXT_JAVASCRIPT_UTF_8 text/javascript} may be
   * necessary in certain situations for compatibility.
   */
  public static final MediaType JAVASCRIPT_UTF_8 =
      createConstantUtf8(APPLICATION_TYPE, "javascript");

  /**
   * For JWS or JWE objects using the Compact
   * Serialization.
   *
   * @since 27.1
   */
  public static final MediaType JOSE = createConstant(APPLICATION_TYPE, "jose");

  /**
   * For JWS or JWE objects using the JSON
   * Serialization.
   *
   * @since 27.1
   */
  public static final MediaType JOSE_JSON = createConstant(APPLICATION_TYPE, "jose+json");

  public static final MediaType JSON_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "json");

  /**
   * The Manifest for a web application.
   *
   * @since 19.0
   */
  public static final MediaType MANIFEST_JSON_UTF_8 =
      createConstantUtf8(APPLICATION_TYPE, "manifest+json");

  /**
   * OGC KML (Keyhole Markup Language).
   */
  public static final MediaType KML = createConstant(APPLICATION_TYPE, "vnd.google-earth.kml+xml");

  /**
   * OGC KML (Keyhole Markup Language),
   * compressed using the ZIP format into KMZ archives.
   */
  public static final MediaType KMZ = createConstant(APPLICATION_TYPE, "vnd.google-earth.kmz");

  /**
   * The mbox database format.
   *
   * @since 13.0
   */
  public static final MediaType MBOX = createConstant(APPLICATION_TYPE, "mbox");

  /**
   * Apple over-the-air mobile configuration profiles.
   *
   * @since 18.0
   */
  public static final MediaType APPLE_MOBILE_CONFIG =
      createConstant(APPLICATION_TYPE, "x-apple-aspen-config");

  /** Microsoft Excel spreadsheets. */
  public static final MediaType MICROSOFT_EXCEL = createConstant(APPLICATION_TYPE, "vnd.ms-excel");

  /**
   * Microsoft Outlook items.
   *
   * @since 27.1
   */
  public static final MediaType MICROSOFT_OUTLOOK =
      createConstant(APPLICATION_TYPE, "vnd.ms-outlook");

  /** Microsoft Powerpoint presentations. */
  public static final MediaType MICROSOFT_POWERPOINT =
      createConstant(APPLICATION_TYPE, "vnd.ms-powerpoint");

  /** Microsoft Word documents. */
  public static final MediaType MICROSOFT_WORD = createConstant(APPLICATION_TYPE, "msword");

  /**
   * WASM applications. For more information see the Web Assembly
   * overview.
   *
   * @since 27.0
   */
  public static final MediaType WASM_APPLICATION = createConstant(APPLICATION_TYPE, "wasm");

  /**
   * NaCl applications. For more information see the
   * Developer Guide for Native Client Application Structure.
   *
   * @since 20.0
   */
  public static final MediaType NACL_APPLICATION = createConstant(APPLICATION_TYPE, "x-nacl");

  /**
   * NaCl portable applications. For more information see the
   * Developer Guide for Native Client Application Structure.
   *
   * @since 20.0
   */
  public static final MediaType NACL_PORTABLE_APPLICATION =
      createConstant(APPLICATION_TYPE, "x-pnacl");

  public static final MediaType OCTET_STREAM = createConstant(APPLICATION_TYPE, "octet-stream");

  public static final MediaType OGG_CONTAINER = createConstant(APPLICATION_TYPE, "ogg");
  public static final MediaType OOXML_DOCUMENT =
      createConstant(
          APPLICATION_TYPE, "vnd.openxmlformats-officedocument.wordprocessingml.document");
  public static final MediaType OOXML_PRESENTATION =
      createConstant(
          APPLICATION_TYPE, "vnd.openxmlformats-officedocument.presentationml.presentation");
  public static final MediaType OOXML_SHEET =
      createConstant(APPLICATION_TYPE, "vnd.openxmlformats-officedocument.spreadsheetml.sheet");
  public static final MediaType OPENDOCUMENT_GRAPHICS =
      createConstant(APPLICATION_TYPE, "vnd.oasis.opendocument.graphics");
  public static final MediaType OPENDOCUMENT_PRESENTATION =
      createConstant(APPLICATION_TYPE, "vnd.oasis.opendocument.presentation");
  public static final MediaType OPENDOCUMENT_SPREADSHEET =
      createConstant(APPLICATION_TYPE, "vnd.oasis.opendocument.spreadsheet");
  public static final MediaType OPENDOCUMENT_TEXT =
      createConstant(APPLICATION_TYPE, "vnd.oasis.opendocument.text");
  public static final MediaType PDF = createConstant(APPLICATION_TYPE, "pdf");
  public static final MediaType POSTSCRIPT = createConstant(APPLICATION_TYPE, "postscript");

  /**
   * Protocol buffers
   *
   * @since 15.0
   */
  public static final MediaType PROTOBUF = createConstant(APPLICATION_TYPE, "protobuf");

  /**
   * RDF/XML documents, which are XML
   * serializations of Resource Description
   * Framework graphs.
   *
   * @since 14.0
   */
  public static final MediaType RDF_XML_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "rdf+xml");

  public static final MediaType RTF_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "rtf");

  /**
   * SFNT fonts (which includes TrueType and OpenType fonts). This is registered with
   * the IANA.
   *
   * @since 17.0
   */
  public static final MediaType SFNT = createConstant(APPLICATION_TYPE, "font-sfnt");

  public static final MediaType SHOCKWAVE_FLASH =
      createConstant(APPLICATION_TYPE, "x-shockwave-flash");

  /**
   * {@code skp} files produced by the 3D Modeling software SketchUp
   *
   * @since 13.0
   */
  public static final MediaType SKETCHUP = createConstant(APPLICATION_TYPE, "vnd.sketchup.skp");

  /**
   * As described in RFC 3902, this constant
   * ({@code application/soap+xml}) is used to identify SOAP 1.2 message envelopes that have been
   * serialized with XML 1.0.
   *
   *
For SOAP 1.1 messages, see {@code XML_UTF_8} per W3C Note on Simple Object Access Protocol
   * (SOAP) 1.1
   *
   * @since 20.0
   */
  public static final MediaType SOAP_XML_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "soap+xml");

  public static final MediaType TAR = createConstant(APPLICATION_TYPE, "x-tar");

  /**
   * Web Open Font Format (WOFF) defined by the W3C. This is registered with
   * the IANA.
   *
   * @since 17.0
   */
  public static final MediaType WOFF = createConstant(APPLICATION_TYPE, "font-woff");

  /**
   * Web Open Font Format (WOFF)
   * version 2 defined by the W3C.
   *
   * @since 20.0
   */
  public static final MediaType WOFF2 = createConstant(APPLICATION_TYPE, "font-woff2");

  public static final MediaType XHTML_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "xhtml+xml");

  /**
   * Extensible Resource Descriptors. This is not yet registered with the IANA, but it is specified
   * by OASIS in the XRD
   * definition and implemented in projects such as WebFinger.
   *
   * @since 14.0
   */
  public static final MediaType XRD_UTF_8 = createConstantUtf8(APPLICATION_TYPE, "xrd+xml");

  public static final MediaType ZIP = createConstant(APPLICATION_TYPE, "zip");

  private final String type;
  private final String subtype;
  private final ImmutableListMultimap<String, String> parameters;

  @LazyInit private String toString;

  @LazyInit private int hashCode;

  @LazyInit private Optional<Charset> parsedCharset;

  private MediaType(String type, String subtype, ImmutableListMultimap<String, String> parameters) {
    this.type = type;
    this.subtype = subtype;
    this.parameters = parameters;
  }

  /** Returns the top-level media type. For example, {@code "text"} in {@code "text/plain"}. */
  public String type() {
    return type;
  }

  /** Returns the media subtype. For example, {@code "plain"} in {@code "text/plain"}. */
  public String subtype() {
    return subtype;
  }

  /** Returns a multimap containing the parameters of this media type. */
  public ImmutableListMultimap<String, String> parameters() {
    return parameters;
  }

  private Map<String, ImmutableMultiset<String>> parametersAsMap() {
    return Maps.transformValues(
        parameters.asMap(),
        new Function<Collection<String>, ImmutableMultiset<String>>() {
          @Override
          public ImmutableMultiset<String> apply(Collection<String> input) {
            return ImmutableMultiset.copyOf(input);
          }
        });
  }

  /**
   * Returns an optional charset for the value of the charset parameter if it is specified.
   *
   * @throws IllegalStateException if multiple charset values have been set for this media type
   * @throws IllegalCharsetNameException if a charset value is present, but illegal
   * @throws UnsupportedCharsetException if a charset value is present, but no support is available
   *     in this instance of the Java virtual machine
   */
  public Optional<Charset> charset() {
    // racy single-check idiom, this is safe because Optional is immutable.
    Optional<Charset> local = parsedCharset;
    if (local == null) {
      String value = null;
      local = Optional.absent();
      for (String currentValue : parameters.get(CHARSET_ATTRIBUTE)) {
        if (value == null) {
          value = currentValue;
          local = Optional.of(Charset.forName(value));
        } else if (!value.equals(currentValue)) {
          throw new IllegalStateException(
              "Multiple charset values defined: " + value + ", " + currentValue);
        }
      }
      parsedCharset = local;
    }
    return local;
  }

  /**
   * Returns a new instance with the same type and subtype as this instance, but without any
   * parameters.
   */
  public MediaType withoutParameters() {
    return parameters.isEmpty() ? this : create(type, subtype);
  }

  /**
   * Replaces all parameters with the given parameters.
   *
   * @throws IllegalArgumentException if any parameter or value is invalid
   */
  public MediaType withParameters(Multimap<String, String> parameters) {
    return create(type, subtype, parameters);
  }

  /**
   * Replaces all parameters with the given attribute with parameters using the given
   * values. If there are no values, any existing parameters with the given attribute are removed.
   *
   * @throws IllegalArgumentException if either {@code attribute} or {@code values} is invalid
   * @since 24.0
   */
  public MediaType withParameters(String attribute, Iterable<String> values) {
    checkNotNull(attribute);
    checkNotNull(values);
    String normalizedAttribute = normalizeToken(attribute);
    ImmutableListMultimap.Builder<String, String> builder = ImmutableListMultimap.builder();
    for (Entry<String, String> entry : parameters.entries()) {
      String key = entry.getKey();
      if (!normalizedAttribute.equals(key)) {
        builder.put(key, entry.getValue());
      }
    }
    for (String value : values) {
      builder.put(normalizedAttribute, normalizeParameterValue(normalizedAttribute, value));
    }
    MediaType mediaType = new MediaType(type, subtype, builder.build());
    // if the attribute isn't charset, we can just inherit the current parsedCharset
    if (!normalizedAttribute.equals(CHARSET_ATTRIBUTE)) {
      mediaType.parsedCharset = this.parsedCharset;
    }
    // Return one of the constants if the media type is a known type.
    return MoreObjects.firstNonNull(KNOWN_TYPES.get(mediaType), mediaType);
  }

  /**
   * Replaces all parameters with the given attribute with a single parameter with the
   * given value. If multiple parameters with the same attributes are necessary use {@link
   * #withParameters(String, Iterable)}. Prefer {@link #withCharset} for setting the {@code charset}
   * parameter when using a {@link Charset} object.
   *
   * @throws IllegalArgumentException if either {@code attribute} or {@code value} is invalid
   */
  public MediaType withParameter(String attribute, String value) {
    return withParameters(attribute, ImmutableSet.of(value));
  }

  /**
   * Returns a new instance with the same type and subtype as this instance, with the {@code
   * charset} parameter set to the {@link Charset#name name} of the given charset. Only one {@code
   * charset} parameter will be present on the new instance regardless of the number set on this
   * one.
   *
   *
If a charset must be specified that is not supported on this JVM (and thus is not
   * representable as a {@link Charset} instance, use {@link #withParameter}.
   */
  public MediaType withCharset(Charset charset) {
    checkNotNull(charset);
    MediaType withCharset = withParameter(CHARSET_ATTRIBUTE, charset.name());
    // precache the charset so we don't need to parse it
    withCharset.parsedCharset = Optional.of(charset);
    return withCharset;
  }

  /** Returns true if either the type or subtype is the wildcard. */
  public boolean hasWildcard() {
    return WILDCARD.equals(type) || WILDCARD.equals(subtype);
  }

  /**
   * Returns {@code true} if this instance falls within the range (as defined by the HTTP Accept header) given
   * by the argument according to three criteria:
   *
   *

   *
The type of the argument is the wildcard or equal to the type of this instance.
   *
The subtype of the argument is the wildcard or equal to the subtype of this instance.
   *
All of the parameters present in the argument are present in this instance.
   *

   *
   *
For example:
   *
   *
{@code
   * PLAIN_TEXT_UTF_8.is(PLAIN_TEXT_UTF_8) // true
   * PLAIN_TEXT_UTF_8.is(HTML_UTF_8) // false
   * PLAIN_TEXT_UTF_8.is(ANY_TYPE) // true
   * PLAIN_TEXT_UTF_8.is(ANY_TEXT_TYPE) // true
   * PLAIN_TEXT_UTF_8.is(ANY_IMAGE_TYPE) // false
   * PLAIN_TEXT_UTF_8.is(ANY_TEXT_TYPE.withCharset(UTF_8)) // true
   * PLAIN_TEXT_UTF_8.withoutParameters().is(ANY_TEXT_TYPE.withCharset(UTF_8)) // false
   * PLAIN_TEXT_UTF_8.is(ANY_TEXT_TYPE.withCharset(UTF_16)) // false
   * }

   *
   *
Note that while it is possible to have the same parameter declared multiple times within a
   * media type this method does not consider the number of occurrences of a parameter. For example,
   * {@code "text/plain; charset=UTF-8"} satisfies {@code "text/plain; charset=UTF-8;
   * charset=UTF-8"}.
   */
  public boolean is(MediaType mediaTypeRange) {
    return (mediaTypeRange.type.equals(WILDCARD) || mediaTypeRange.type.equals(this.type))
        && (mediaTypeRange.subtype.equals(WILDCARD) || mediaTypeRange.subtype.equals(this.subtype))
        && this.parameters.entries().containsAll(mediaTypeRange.parameters.entries());
  }

  /**
   * Creates a new media type with the given type and subtype.
   *
   * @throws IllegalArgumentException if type or subtype is invalid or if a wildcard is used for the
   *     type, but not the subtype.
   */
  public static MediaType create(String type, String subtype) {
    MediaType mediaType = create(type, subtype, ImmutableListMultimap.<String, String>of());
    mediaType.parsedCharset = Optional.absent();
    return mediaType;
  }

  private static MediaType create(
      String type, String subtype, Multimap<String, String> parameters) {
    checkNotNull(type);
    checkNotNull(subtype);
    checkNotNull(parameters);
    String normalizedType = normalizeToken(type);
    String normalizedSubtype = normalizeToken(subtype);
    checkArgument(
        !WILDCARD.equals(normalizedType) || WILDCARD.equals(normalizedSubtype),
        "A wildcard type cannot be used with a non-wildcard subtype");
    ImmutableListMultimap.Builder<String, String> builder = ImmutableListMultimap.builder();
    for (Entry<String, String> entry : parameters.entries()) {
      String attribute = normalizeToken(entry.getKey());
      builder.put(attribute, normalizeParameterValue(attribute, entry.getValue()));
    }
    MediaType mediaType = new MediaType(normalizedType, normalizedSubtype, builder.build());
    // Return one of the constants if the media type is a known type.
    return MoreObjects.firstNonNull(KNOWN_TYPES.get(mediaType), mediaType);
  }

  /**
   * Creates a media type with the "application" type and the given subtype.
   *
   * @throws IllegalArgumentException if subtype is invalid
   */
  static MediaType createApplicationType(String subtype) {
    return create(APPLICATION_TYPE, subtype);
  }

  /**
   * Creates a media type with the "audio" type and the given subtype.
   *
   * @throws IllegalArgumentException if subtype is invalid
   */
  static MediaType createAudioType(String subtype) {
    return create(AUDIO_TYPE, subtype);
  }

  /**
   * Creates a media type with the "image" type and the given subtype.
   *
   * @throws IllegalArgumentException if subtype is invalid
   */
  static MediaType createImageType(String subtype) {
    return create(IMAGE_TYPE, subtype);
  }

  /**
   * Creates a media type with the "text" type and the given subtype.
   *
   * @throws IllegalArgumentException if subtype is invalid
   */
  static MediaType createTextType(String subtype) {
    return create(TEXT_TYPE, subtype);
  }

  /**
   * Creates a media type with the "video" type and the given subtype.
   *
   * @throws IllegalArgumentException if subtype is invalid
   */
  static MediaType createVideoType(String subtype) {
    return create(VIDEO_TYPE, subtype);
  }

  private static String normalizeToken(String token) {
    checkArgument(TOKEN_MATCHER.matchesAllOf(token));
    return Ascii.toLowerCase(token);
  }

  private static String normalizeParameterValue(String attribute, String value) {
    return CHARSET_ATTRIBUTE.equals(attribute) ? Ascii.toLowerCase(value) : value;
  }

  /**
   * Parses a media type from its string representation.
   *
   * @throws IllegalArgumentException if the input is not parsable
   */
  public static MediaType parse(String input) {
    checkNotNull(input);
    Tokenizer tokenizer = new Tokenizer(input);
    try {
      String type = tokenizer.consumeToken(TOKEN_MATCHER);
      tokenizer.consumeCharacter('/');
      String subtype = tokenizer.consumeToken(TOKEN_MATCHER);
      ImmutableListMultimap.Builder<String, String> parameters = ImmutableListMultimap.builder();
      while (tokenizer.hasMore()) {
        tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE);
        tokenizer.consumeCharacter(';');
        tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE);
        String attribute = tokenizer.consumeToken(TOKEN_MATCHER);
        tokenizer.consumeCharacter('=');
        final String value;
        if ('"' == tokenizer.previewChar()) {
          tokenizer.consumeCharacter('"');
          StringBuilder valueBuilder = new StringBuilder();
          while ('"' != tokenizer.previewChar()) {
            if ('\\' == tokenizer.previewChar()) {
              tokenizer.consumeCharacter('\\');
              valueBuilder.append(tokenizer.consumeCharacter(ascii()));
            } else {
              valueBuilder.append(tokenizer.consumeToken(QUOTED_TEXT_MATCHER));
            }
          }
          value = valueBuilder.toString();
          tokenizer.consumeCharacter('"');
        } else {
          value = tokenizer.consumeToken(TOKEN_MATCHER);
        }
        parameters.put(attribute, value);
      }
      return create(type, subtype, parameters.build());
    } catch (IllegalStateException e) {
      throw new IllegalArgumentException("Could not parse '" + input + "'", e);
    }
  }

  private static final class Tokenizer {
    final String input;
    int position = 0;

    Tokenizer(String input) {
      this.input = input;
    }

    String consumeTokenIfPresent(CharMatcher matcher) {
      checkState(hasMore());
      int startPosition = position;
      position = matcher.negate().indexIn(input, startPosition);
      return hasMore() ? input.substring(startPosition, position) : input.substring(startPosition);
    }

    String consumeToken(CharMatcher matcher) {
      int startPosition = position;
      String token = consumeTokenIfPresent(matcher);
      checkState(position != startPosition);
      return token;
    }

    char consumeCharacter(CharMatcher matcher) {
      checkState(hasMore());
      char c = previewChar();
      checkState(matcher.matches(c));
      position++;
      return c;
    }

    char consumeCharacter(char c) {
      checkState(hasMore());
      checkState(previewChar() == c);
      position++;
      return c;
    }

    char previewChar() {
      checkState(hasMore());
      return input.charAt(position);
    }

    boolean hasMore() {
      return (position >= 0) && (position < input.length());
    }
  }

  @Override
  public boolean equals(@Nullable Object obj) {
    if (obj == this) {
      return true;
    } else if (obj instanceof MediaType) {
      MediaType that = (MediaType) obj;
      return this.type.equals(that.type)
          && this.subtype.equals(that.subtype)
          // compare parameters regardless of order
          && this.parametersAsMap().equals(that.parametersAsMap());
    } else {
      return false;
    }
  }

  @Override
  public int hashCode() {
    // racy single-check idiom
    int h = hashCode;
    if (h == 0) {
      h = Objects.hashCode(type, subtype, parametersAsMap());
      hashCode = h;
    }
    return h;
  }

  private static final MapJoiner PARAMETER_JOINER = Joiner.on("; ").withKeyValueSeparator("=");

  /**
   * Returns the string representation of this media type in the format described in RFC 2045.
   */
  @Override
  public String toString() {
    // racy single-check idiom, safe because String is immutable
    String result = toString;
    if (result == null) {
      result = computeToString();
      toString = result;
    }
    return result;
  }

  private String computeToString() {
    StringBuilder builder = new StringBuilder().append(type).append('/').append(subtype);
    if (!parameters.isEmpty()) {
      builder.append("; ");
      Multimap<String, String> quotedParameters =
          Multimaps.transformValues(
              parameters,
              new Function<String, String>() {
                @Override
                public String apply(String value) {
                  return TOKEN_MATCHER.matchesAllOf(value) ? value : escapeAndQuote(value);
                }
              });
      PARAMETER_JOINER.appendTo(builder, quotedParameters.entries());
    }
    return builder.toString();
  }

  private static String escapeAndQuote(String value) {
    StringBuilder escaped = new StringBuilder(value.length() + 16).append('"');
    for (int i = 0; i < value.length(); i++) {
      char ch = value.charAt(i);
      if (ch == '\r' || ch == '\\' || ch == '"') {
        escaped.append('\\');
      }
      escaped.append(ch);
    }
    return escaped.append('"').toString();
  }
}