/*
 * Copyright (C) 2012 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.testing;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;

import com.google.common.base.Functions;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.testing.ClassSanityTester.FactoryMethodReturnsNullException;
import com.google.common.testing.ClassSanityTester.ParameterHasNoDistinctValueException;
import com.google.common.testing.ClassSanityTester.ParameterNotInstantiableException;
import com.google.common.testing.NullPointerTester.Visibility;
import com.google.errorprone.annotations.Keep;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import junit.framework.TestCase;
import org.jspecify.annotations.NullUnmarked;
import org.jspecify.annotations.Nullable;

/**
 * Unit tests for {@link ClassSanityTester}.
 *
 * @author Ben Yu
 */
@NullUnmarked
public class ClassSanityTesterTest extends TestCase {

  private final ClassSanityTester tester = new ClassSanityTester();

  public void testEqualsOnReturnValues_good() throws Exception {
    tester.forAllPublicStaticMethods(GoodEqualsFactory.class).testEquals();
  }

  public static class GoodEqualsFactory {
    public static Object good(
        String a,
        int b,
        // oneConstantOnly doesn't matter since it's not nullable and can be only 1 value.
        @SuppressWarnings("unused") OneConstantEnum oneConstantOnly,
        // noConstant doesn't matter since it can only be null
        @SuppressWarnings("unused") @Nullable NoConstantEnum noConstant) {
      return new GoodEquals(a, b);
    }

    // instance method ignored
    public Object badIgnored() {
      return new BadEquals();
    }

    // primitive ignored
    public int returnsInt() {
      throw new UnsupportedOperationException();
    }

    // void ignored
    public void voidMethod() {
      throw new UnsupportedOperationException();
    }

    // non-public method ignored
    static Object badButNotPublic() {
      return new BadEquals();
    }
  }

  public void testForAllPublicStaticMethods_noPublicStaticMethods() throws Exception {
    try {
      tester.forAllPublicStaticMethods(NoPublicStaticMethods.class).testEquals();
    } catch (AssertionError expected) {
      assertThat(expected)
          .hasMessageThat()
          .isEqualTo(
              "No public static methods that return java.lang.Object or subtype are found in "
                  + NoPublicStaticMethods.class
                  + ".");
      return;
    }
    fail();
  }

  public void testEqualsOnReturnValues_bad() throws Exception {
    try {
      tester.forAllPublicStaticMethods(BadEqualsFactory.class).testEquals();
    } catch (AssertionError expected) {
      return;
    }
    fail();
  }

  private static class BadEqualsFactory {
    /** oneConstantOnly matters now since it can be either null or the constant. */
    @Keep
    public static Object bad(String a, int b, @Nullable OneConstantEnum oneConstantOnly) {
      return new GoodEquals(a, b);
    }
  }

  public void testNullsOnReturnValues_good() throws Exception {
    tester.forAllPublicStaticMethods(GoodNullsFactory.class).testNulls();
  }

  private static class GoodNullsFactory {
    @Keep
    public static Object good(String s) {
      return new GoodNulls(s);
    }
  }

  public void testNullsOnReturnValues_bad() throws Exception {
    try {
      tester.forAllPublicStaticMethods(BadNullsFactory.class).thatReturn(Object.class).testNulls();
    } catch (AssertionError expected) {
      return;
    }
    fail();
  }

  public void testNullsOnReturnValues_returnTypeFiltered() throws Exception {
    try {
      tester
          .forAllPublicStaticMethods(BadNullsFactory.class)
          .thatReturn(Iterable.class)
          .testNulls();
    } catch (AssertionError expected) {
      assertThat(expected)
          .hasMessageThat()
          .isEqualTo(
              "No public static methods that return java.lang.Iterable or subtype are found in "
                  + BadNullsFactory.class
                  + ".");
      return;
    }
    fail();
  }

  public static final class BadNullsFactory {
    public static Object bad(@SuppressWarnings("unused") String a) {
      return new BadNulls();
    }

    private BadNullsFactory() {}
  }

  @AndroidIncompatible // TODO(cpovirk): ClassNotFoundException... ClassSanityTesterTest$AnInterface
  public void testSerializableOnReturnValues_good() throws Exception {
    tester.forAllPublicStaticMethods(GoodSerializableFactory.class).testSerializable();
  }

  public static final class GoodSerializableFactory {
    public static Object good(Runnable r) {
      return r;
    }

    public static Object good(AnInterface i) {
      return i;
    }

    private GoodSerializableFactory() {}
  }

  public void testSerializableOnReturnValues_bad() throws Exception {
    try {
      tester.forAllPublicStaticMethods(BadSerializableFactory.class).testSerializable();
    } catch (AssertionError expected) {
      return;
    }
    fail();
  }

  public static final class BadSerializableFactory {
    public static Object bad() {
      return new Serializable() {
        @Keep private final Object notSerializable = new Object();
      };
    }

    private BadSerializableFactory() {}
  }

  public void testEqualsAndSerializableOnReturnValues_equalsIsGoodButNotSerializable()
      throws Exception {
    try {
      tester.forAllPublicStaticMethods(GoodEqualsFactory.class).testEqualsAndSerializable();
    } catch (AssertionError expected) {
      return;
    }
    fail("should have failed");
  }

  public void testEqualsAndSerializableOnReturnValues_serializableButNotEquals() throws Exception {
    try {
      tester.forAllPublicStaticMethods(GoodSerializableFactory.class).testEqualsAndSerializable();
    } catch (AssertionError expected) {
      return;
    }
    fail("should have failed");
  }

  @AndroidIncompatible // TODO(cpovirk): ClassNotFoundException... ClassSanityTesterTest$AnInterface
  public void testEqualsAndSerializableOnReturnValues_good() throws Exception {
    tester
        .forAllPublicStaticMethods(GoodEqualsAndSerializableFactory.class)
        .testEqualsAndSerializable();
  }

  public static final class GoodEqualsAndSerializableFactory {
    public static Object good(AnInterface s) {
      return Functions.constant(s);
    }

    private GoodEqualsAndSerializableFactory() {}
  }

  public void testEqualsForReturnValues_factoryReturnsNullButNotAnnotated() throws Exception {
    try {
      tester.forAllPublicStaticMethods(FactoryThatReturnsNullButNotAnnotated.class).testEquals();
    } catch (AssertionError expected) {
      return;
    }
    fail();
  }

  public void testNullsForReturnValues_factoryReturnsNullButNotAnnotated() throws Exception {
    try {
      tester.forAllPublicStaticMethods(FactoryThatReturnsNullButNotAnnotated.class).testNulls();
    } catch (AssertionError expected) {
      return;
    }
    fail();
  }

  public void testSerializableForReturnValues_factoryReturnsNullButNotAnnotated() throws Exception {
    try {
      tester
          .forAllPublicStaticMethods(FactoryThatReturnsNullButNotAnnotated.class)
          .testSerializable();
    } catch (AssertionError expected) {
      return;
    }
    fail();
  }

  public void testEqualsAndSerializableForReturnValues_factoryReturnsNullButNotAnnotated()
      throws Exception {
    try {
      tester
          .forAllPublicStaticMethods(FactoryThatReturnsNullButNotAnnotated.class)
          .testEqualsAndSerializable();
    } catch (AssertionError expected) {
      return;
    }
    fail();
  }

  public static final class FactoryThatReturnsNullButNotAnnotated {
    public static Object bad() {
      return null;
    }

    private FactoryThatReturnsNullButNotAnnotated() {}
  }

  public void testEqualsForReturnValues_factoryReturnsNullAndAnnotated() throws Exception {
    tester.forAllPublicStaticMethods(FactoryThatReturnsNullAndAnnotated.class).testEquals();
  }

  public void testNullsForReturnValues_factoryReturnsNullAndAnnotated() throws Exception {
    tester.forAllPublicStaticMethods(FactoryThatReturnsNullAndAnnotated.class).testNulls();
  }

  public void testSerializableForReturnValues_factoryReturnsNullAndAnnotated() throws Exception {
    tester.forAllPublicStaticMethods(FactoryThatReturnsNullAndAnnotated.class).testSerializable();
  }

  public void testEqualsAndSerializableForReturnValues_factoryReturnsNullAndAnnotated()
      throws Exception {
    tester
        .forAllPublicStaticMethods(FactoryThatReturnsNullAndAnnotated.class)
        .testEqualsAndSerializable();
  }

  public static final class FactoryThatReturnsNullAndAnnotated {
    public static @Nullable Object bad() {
      return null;
    }

    private FactoryThatReturnsNullAndAnnotated() {}
  }

  public void testGoodEquals() throws Exception {
    tester.testEquals(GoodEquals.class);
  }

  public void testEquals_interface() {
    tester.testEquals(AnInterface.class);
  }

  public void testEquals_abstractClass() {
    tester.testEquals(AnAbstractClass.class);
  }

  public void testEquals_enum() {
    tester.testEquals(OneConstantEnum.class);
  }

  public void testBadEquals() throws Exception {
    try {
      tester.testEquals(BadEquals.class);
    } catch (AssertionError expected) {
      assertThat(expected).hasMessageThat().contains("create(null)");
      return;
    }
    fail("should have failed");
  }

  public void testBadEquals_withParameterizedType() throws Exception {
    try {
      tester.testEquals(BadEqualsWithParameterizedType.class);
    } catch (AssertionError expected) {
      assertThat(expected).hasMessageThat().contains("create([[1]])");
      return;
    }
    fail("should have failed");
  }

  public void testBadEquals_withSingleParameterValue() throws Exception {
    assertThrows(
        ParameterHasNoDistinctValueException.class,
        () -> tester.doTestEquals(ConstructorParameterWithOptionalNotInstantiable.class));
  }

  public void testGoodReferentialEqualityComparison() throws Exception {
    tester.testEquals(UsesEnum.class);
    tester.testEquals(UsesReferentialEquality.class);
    tester.testEquals(SameListInstance.class);
  }

  public void testStreamParameterSkippedForNullTesting() throws Exception {
    tester.testNulls(WithStreamParameter.class);
  }

  @AndroidIncompatible // problem with equality of Type objects?
  public void testEqualsUsingReferentialEquality() throws Exception {
    assertBadUseOfReferentialEquality(SameIntegerInstance.class);
    assertBadUseOfReferentialEquality(SameLongInstance.class);
    assertBadUseOfReferentialEquality(SameFloatInstance.class);
    assertBadUseOfReferentialEquality(SameDoubleInstance.class);
    assertBadUseOfReferentialEquality(SameShortInstance.class);
    assertBadUseOfReferentialEquality(SameByteInstance.class);
    assertBadUseOfReferentialEquality(SameCharacterInstance.class);
    assertBadUseOfReferentialEquality(SameBooleanInstance.class);
    assertBadUseOfReferentialEquality(SameObjectInstance.class);
    assertBadUseOfReferentialEquality(SameStringInstance.class);
    assertBadUseOfReferentialEquality(SameInterfaceInstance.class);
  }

  private void assertBadUseOfReferentialEquality(Class<?> cls) throws Exception {
    try {
      tester.testEquals(cls);
    } catch (AssertionError expected) {
      assertThat(expected).hasMessageThat().contains(cls.getSimpleName() + "(");
      return;
    }
    fail("should have failed for " + cls);
  }

  public void testParameterNotInstantiableForEqualsTest() throws Exception {
    assertThrows(
        ParameterNotInstantiableException.class,
        () -> tester.doTestEquals(ConstructorParameterNotInstantiable.class));
  }

  public void testNoDistinctValueForEqualsTest() throws Exception {
    assertThrows(
        ParameterHasNoDistinctValueException.class,
        () -> tester.doTestEquals(ConstructorParameterSingleValue.class));
  }

  public void testConstructorThrowsForEqualsTest() throws Exception {
    assertThrows(
        InvocationTargetException.class, () -> tester.doTestEquals(ConstructorThrows.class));
  }

  public void testFactoryMethodReturnsNullForEqualsTest() throws Exception {
    assertThrows(
        FactoryMethodReturnsNullException.class,
        () -> tester.doTestEquals(FactoryMethodReturnsNullAndAnnotated.class));
  }

  public void testFactoryMethodReturnsNullButNotAnnotatedInEqualsTest() throws Exception {
    try {
      tester.testEquals(FactoryMethodReturnsNullButNotAnnotated.class);
    } catch (AssertionError expected) {
      return;
    }
    fail("should have failed");
  }

  public void testNoEqualsChecksOnEnum() throws Exception {
    tester.testEquals(OneConstantEnum.class);
    tester.testEquals(NoConstantEnum.class);
    tester.testEquals(TimeUnit.class);
  }

  public void testNoEqualsChecksOnInterface() throws Exception {
    tester.testEquals(Runnable.class);
  }

  public void testNoEqualsChecksOnAnnotation() throws Exception {
    tester.testEquals(MyAnnotation.class);
  }

  public void testGoodNulls() throws Exception {
    tester.testNulls(GoodNulls.class);
  }

  public void testNoNullCheckNeededDespiteNotInstantiable() throws Exception {
    tester.doTestNulls(NoNullCheckNeededDespiteNotInstantiable.class, Visibility.PACKAGE);
  }

  public void testNulls_interface() {
    tester.testNulls(AnInterface.class);
  }

  public void testNulls_abstractClass() {
    tester.testNulls(AnAbstractClass.class);
  }

  public void testNulls_enum() throws Exception {
    tester.testNulls(OneConstantEnum.class);
    tester.testNulls(NoConstantEnum.class);
    tester.testNulls(TimeUnit.class);
  }

  public void testNulls_parameterOptionalNotInstantiable() throws Exception {
    tester.testNulls(ConstructorParameterWithOptionalNotInstantiable.class);
  }

  public void testEnumFailsToCheckNull() throws Exception {
    try {
      tester.testNulls(EnumFailsToCheckNull.class);
    } catch (AssertionError expected) {
      return;
    }
    fail("should have failed");
  }

  public void testNoNullChecksOnInterface() throws Exception {
    tester.testNulls(Runnable.class);
  }

  public void testNoNullChecksOnAnnotation() throws Exception {
    tester.testNulls(MyAnnotation.class);
  }

  public void testBadNulls() throws Exception {
    try {
      tester.testNulls(BadNulls.class);
    } catch (AssertionError expected) {
      return;
    }
    fail("should have failed");
  }

  public void testInstantiate_factoryMethodReturnsNullButNotAnnotated() throws Exception {
    try {
      FactoryMethodReturnsNullButNotAnnotated unused =
          tester.instantiate(FactoryMethodReturnsNullButNotAnnotated.class);
    } catch (AssertionError expected) {
      assertThat(expected).hasMessageThat().contains("@Nullable");
      return;
    }
    fail("should have failed");
  }

  public void testInstantiate_factoryMethodReturnsNullAndAnnotated() throws Exception {
    assertThrows(
        FactoryMethodReturnsNullException.class,
        () -> tester.instantiate(FactoryMethodReturnsNullAndAnnotated.class));
  }

  public void testInstantiate_factoryMethodAcceptsNull() throws Exception {
    assertNull(tester.instantiate(FactoryMethodAcceptsNull.class).name);
  }

  public void testInstantiate_factoryMethodDoesNotAcceptNull() throws Exception {
    assertNotNull(tester.instantiate(FactoryMethodDoesNotAcceptNull.class).name);
  }

  public void testInstantiate_constructorAcceptsNull() throws Exception {
    assertNull(tester.instantiate(ConstructorAcceptsNull.class).name);
  }

  public void testInstantiate_constructorDoesNotAcceptNull() throws Exception {
    assertNotNull(tester.instantiate(ConstructorDoesNotAcceptNull.class).name);
  }

  public void testInstantiate_notInstantiable() throws Exception {
    assertNull(tester.instantiate(NotInstantiable.class));
  }

  public void testInstantiate_noConstantEnum() throws Exception {
    assertNull(tester.instantiate(NoConstantEnum.class));
  }

  public void testInstantiate_oneConstantEnum() throws Exception {
    assertEquals(OneConstantEnum.A, tester.instantiate(OneConstantEnum.class));
  }

  public void testInstantiate_interface() throws Exception {
    assertNull(tester.instantiate(Runnable.class));
  }

  public void testInstantiate_abstractClass() throws Exception {
    assertNull(tester.instantiate(AbstractList.class));
  }

  public void testInstantiate_annotation() throws Exception {
    assertNull(tester.instantiate(MyAnnotation.class));
  }

  public void testInstantiate_setDefault() throws Exception {
    NotInstantiable x = new NotInstantiable();
    tester.setDefault(NotInstantiable.class, x);
    assertNotNull(tester.instantiate(ConstructorParameterNotInstantiable.class));
  }

  public void testSetDistinctValues_equalInstances() {
    assertThrows(
        IllegalArgumentException.class, () -> tester.setDistinctValues(String.class, "", ""));
  }

  public void testInstantiate_setDistinctValues() throws Exception {
    NotInstantiable x = new NotInstantiable();
    NotInstantiable y = new NotInstantiable();
    tester.setDistinctValues(NotInstantiable.class, x, y);
    assertNotNull(tester.instantiate(ConstructorParameterNotInstantiable.class));
    tester.testEquals(ConstructorParameterMapOfNotInstantiable.class);
  }

  public void testInstantiate_constructorThrows() throws Exception {
    assertThrows(
        InvocationTargetException.class, () -> tester.instantiate(ConstructorThrows.class));
  }

  public void testInstantiate_factoryMethodThrows() throws Exception {
    assertThrows(
        InvocationTargetException.class, () -> tester.instantiate(FactoryMethodThrows.class));
  }

  public void testInstantiate_constructorParameterNotInstantiable() throws Exception {
    assertThrows(
        ParameterNotInstantiableException.class,
        () -> tester.instantiate(ConstructorParameterNotInstantiable.class));
  }

  public void testInstantiate_factoryMethodParameterNotInstantiable() throws Exception {
    assertThrows(
        ParameterNotInstantiableException.class,
        () -> tester.instantiate(FactoryMethodParameterNotInstantiable.class));
  }

  public void testInstantiate_instantiableFactoryMethodChosen() throws Exception {
    assertEquals("good", tester.instantiate(InstantiableFactoryMethodChosen.class).name);
  }

  @AndroidIncompatible // TODO(cpovirk): ClassNotFoundException... ClassSanityTesterTest$AnInterface
  public void testInterfaceProxySerializable() throws Exception {
    SerializableTester.reserializeAndAssert(tester.instantiate(HasAnInterface.class));
  }

  public void testReturnValuesFromAnotherPackageIgnoredForNullTests() throws Exception {
    new ClassSanityTester().forAllPublicStaticMethods(JdkObjectFactory.class).testNulls();
  }

  /** String doesn't check nulls as we expect. But the framework should ignore. */
  private static class JdkObjectFactory {
    @Keep
    public static Object create() {
      return new ArrayList<>();
    }
  }

  static class HasAnInterface implements Serializable {
    private final AnInterface i;

    public HasAnInterface(AnInterface i) {
      this.i = i;
    }

    @Override
    public boolean equals(@Nullable Object obj) {
      if (obj instanceof HasAnInterface) {
        HasAnInterface that = (HasAnInterface) obj;
        return i.equals(that.i);
      } else {
        return false;
      }
    }

    @Override
    public int hashCode() {
      return i.hashCode();
    }
  }

  static class InstantiableFactoryMethodChosen {
    final String name;

    private InstantiableFactoryMethodChosen(String name) {
      this.name = name;
    }

    public InstantiableFactoryMethodChosen(NotInstantiable x) {
      checkNotNull(x);
      this.name = "x1";
    }

    public static InstantiableFactoryMethodChosen create(NotInstantiable x) {
      return new InstantiableFactoryMethodChosen(x);
    }

    public static InstantiableFactoryMethodChosen create(String s) {
      checkNotNull(s);
      return new InstantiableFactoryMethodChosen("good");
    }
  }

  public void testInstantiate_instantiableConstructorChosen() throws Exception {
    assertEquals("good", tester.instantiate(InstantiableConstructorChosen.class).name);
  }

  public void testEquals_setOfNonInstantiable() throws Exception {
    assertThrows(
        ParameterNotInstantiableException.class,
        () -> new ClassSanityTester().doTestEquals(SetWrapper.class));
  }

  private abstract static class Wrapper {
    private final Object wrapped;

    Wrapper(Object wrapped) {
      this.wrapped = checkNotNull(wrapped);
    }

    @Override
    public boolean equals(@Nullable Object obj) {
      // In general getClass().isInstance() is bad for equals.
      // But here we fully control the subclasses to ensure symmetry.
      if (getClass().isInstance(obj)) {
        Wrapper that = (Wrapper) obj;
        return wrapped.equals(that.wrapped);
      }
      return false;
    }

    @Override
    public int hashCode() {
      return wrapped.hashCode();
    }

    @Override
    public String toString() {
      return wrapped.toString();
    }
  }

  private static class SetWrapper extends Wrapper {
    @Keep
    public SetWrapper(Set<NotInstantiable> wrapped) {
      super(wrapped);
    }
  }

  static class InstantiableConstructorChosen {
    final String name;

    public InstantiableConstructorChosen(String name) {
      checkNotNull(name);
      this.name = "good";
    }

    public InstantiableConstructorChosen(NotInstantiable x) {
      checkNotNull(x);
      this.name = "x1";
    }

    public static InstantiableFactoryMethodChosen create(NotInstantiable x) {
      return new InstantiableFactoryMethodChosen(x);
    }
  }

  static class GoodEquals {

    private final String a;
    private final int b;

    private GoodEquals(String a, int b) {
      this.a = checkNotNull(a);
      this.b = b;
    }

    // ignored by testEquals()
    GoodEquals(@SuppressWarnings("unused") NotInstantiable x) {
      this.a = "x";
      this.b = -1;
    }

    // will keep trying
    public GoodEquals(@SuppressWarnings("unused") NotInstantiable x, int b) {
      this.a = "x";
      this.b = b;
    }

    // keep trying
    @Keep
    static GoodEquals create(int a, int b) {
      throw new RuntimeException();
    }

    // Good!
    static GoodEquals create(String a, int b) {
      return new GoodEquals(a, b);
    }

    // keep trying
    @Keep
    public static @Nullable GoodEquals createMayReturnNull(int a, int b) {
      return null;
    }

    @Override
    public boolean equals(@Nullable Object obj) {
      if (obj instanceof GoodEquals) {
        GoodEquals that = (GoodEquals) obj;
        return a.equals(that.a) && b == that.b;
      } else {
        return false;
      }
    }

    @Override
    public int hashCode() {
      return 0;
    }
  }

  static class BadEquals {

    public BadEquals() {} // ignored by testEquals() since it has less parameters.

    public static BadEquals create(@SuppressWarnings("unused") @Nullable String s) {
      return new BadEquals();
    }

    @Override
    public boolean equals(@Nullable Object obj) {
      return obj instanceof BadEquals;
    }

    @Override
    public int hashCode() {
      return 0;
    }
  }

  static class SameIntegerInstance {
    private final Integer i;

    public SameIntegerInstance(Integer i) {
      this.i = checkNotNull(i);
    }

    @Override
    public int hashCode() {
      return i.hashCode();
    }

    @Override
    @SuppressWarnings({"BoxedPrimitiveEquality", "NumericEquality"})
    public boolean equals(@Nullable Object obj) {
      if (obj instanceof SameIntegerInstance) {
        SameIntegerInstance that = (SameIntegerInstance) obj;
        return i == that.i;
      }
      return false;
    }
  }

  static class SameLongInstance {
    private final Long i;

    public SameLongInstance(Long i) {
      this.i = checkNotNull(i);
    }

    @Override
    public int hashCode() {
      return i.hashCode();
    }

    @Override
    @SuppressWarnings({"BoxedPrimitiveEquality", "NumericEquality"})
    public boolean equals(@Nullable Object obj) {
      if (obj instanceof SameLongInstance) {
        SameLongInstance that = (SameLongInstance) obj;
        return i == that.i;
      }
      return false;
    }
  }

  static class SameFloatInstance {
    private final Float i;

    public SameFloatInstance(Float i) {
      this.i = checkNotNull(i);
    }

    @Override
    public int hashCode() {
      return i.hashCode();
    }

    @Override
    @SuppressWarnings({"BoxedPrimitiveEquality", "NumericEquality"})
    public boolean equals(@Nullable Object obj) {
      if (obj instanceof SameFloatInstance) {
        SameFloatInstance that = (SameFloatInstance) obj;
        return i == that.i;
      }
      return false;
    }
  }

  static class SameDoubleInstance {
    private final Double i;

    public SameDoubleInstance(Double i) {
      this.i = checkNotNull(i);
    }

    @Override
    public int hashCode() {
      return i.hashCode();
    }

    @Override
    @SuppressWarnings({"BoxedPrimitiveEquality", "NumericEquality"})
    public boolean equals(@Nullable Object obj) {
      if (obj instanceof SameDoubleInstance) {
        SameDoubleInstance that = (SameDoubleInstance) obj;
        return i == that.i;
      }
      return false;
    }
  }

  static class SameShortInstance {
    private final Short i;

    public SameShortInstance(Short i) {
      this.i = checkNotNull(i);
    }

    @Override
    public int hashCode() {
      return i.hashCode();
    }

    @Override
    @SuppressWarnings({"BoxedPrimitiveEquality", "NumericEquality"})
    public boolean equals(@Nullable Object obj) {
      if (obj instanceof SameShortInstance) {
        SameShortInstance that = (SameShortInstance) obj;
        return i == that.i;
      }
      return false;
    }
  }

  static class SameByteInstance {
    private final Byte i;

    public SameByteInstance(Byte i) {
      this.i = checkNotNull(i);
    }

    @Override
    public int hashCode() {
      return i.hashCode();
    }

    @Override
    @SuppressWarnings({"BoxedPrimitiveEquality", "NumericEquality"})
    public boolean equals(@Nullable Object obj) {
      if (obj instanceof SameByteInstance) {
        SameByteInstance that = (SameByteInstance) obj;
        return i == that.i;
      }
      return false;
    }
  }

  static class SameCharacterInstance {
    private final Character i;

    public SameCharacterInstance(Character i) {
      this.i = checkNotNull(i);
    }

    @Override
    public int hashCode() {
      return i.hashCode();
    }

    @Override
    @SuppressWarnings("BoxedPrimitiveEquality")
    public boolean equals(@Nullable Object obj) {
      if (obj instanceof SameCharacterInstance) {
        SameCharacterInstance that = (SameCharacterInstance) obj;
        return i == that.i;
      }
      return false;
    }
  }

  static class SameBooleanInstance {
    private final Boolean i;

    public SameBooleanInstance(Boolean i) {
      this.i = checkNotNull(i);
    }

    @Override
    public int hashCode() {
      return i.hashCode();
    }

    @Override
    @SuppressWarnings("BoxedPrimitiveEquality")
    public boolean equals(@Nullable Object obj) {
      if (obj instanceof SameBooleanInstance) {
        SameBooleanInstance that = (SameBooleanInstance) obj;
        return i == that.i;
      }
      return false;
    }
  }

  static class SameStringInstance {
    private final String s;

    public SameStringInstance(String s) {
      this.s = checkNotNull(s);
    }

    @Override
    public int hashCode() {
      return s.hashCode();
    }

    @Override
    public boolean equals(@Nullable Object obj) {
      if (obj instanceof SameStringInstance) {
        SameStringInstance that = (SameStringInstance) obj;
        return s == that.s;
      }
      return false;
    }
  }

  static class SameObjectInstance {
    private final Object s;

    public SameObjectInstance(Object s) {
      this.s = checkNotNull(s);
    }

    @Override
    public int hashCode() {
      return s.hashCode();
    }

    @Override
    public boolean equals(@Nullable Object obj) {
      if (obj instanceof SameObjectInstance) {
        SameObjectInstance that = (SameObjectInstance) obj;
        return s == that.s;
      }
      return false;
    }
  }

  static class SameInterfaceInstance {
    private final Runnable s;

    public SameInterfaceInstance(Runnable s) {
      this.s = checkNotNull(s);
    }

    @Override
    public int hashCode() {
      return s.hashCode();
    }

    @Override
    public boolean equals(@Nullable Object obj) {
      if (obj instanceof SameInterfaceInstance) {
        SameInterfaceInstance that = (SameInterfaceInstance) obj;
        return s == that.s;
      }
      return false;
    }
  }

  static class SameListInstance {
    private final List<?> s;

    public SameListInstance(List<?> s) {
      this.s = checkNotNull(s);
    }

    @Override
    public int hashCode() {
      return System.identityHashCode(s);
    }

    @Override
    public boolean equals(@Nullable Object obj) {
      if (obj instanceof SameListInstance) {
        SameListInstance that = (SameListInstance) obj;
        return s == that.s;
      }
      return false;
    }
  }

  static class WithStreamParameter {
    private final List<?> list;

    // This should be ignored.
    public WithStreamParameter(Stream<?> s, String str) {
      this.list = s.collect(Collectors.toList());
      checkNotNull(str);
    }
  }

  static class UsesReferentialEquality {
    private final ReferentialEquality s;

    public UsesReferentialEquality(ReferentialEquality s) {
      this.s = checkNotNull(s);
    }

    @Override
    public int hashCode() {
      return s.hashCode();
    }

    @Override
    public boolean equals(@Nullable Object obj) {
      if (obj instanceof UsesReferentialEquality) {
        UsesReferentialEquality that = (UsesReferentialEquality) obj;
        return s == that.s;
      }
      return false;
    }
  }

  static class UsesEnum {
    private final TimeUnit s;

    public UsesEnum(TimeUnit s) {
      this.s = checkNotNull(s);
    }

    @Override
    public int hashCode() {
      return s.hashCode();
    }

    @Override
    public boolean equals(@Nullable Object obj) {
      if (obj instanceof UsesEnum) {
        UsesEnum that = (UsesEnum) obj;
        return s == that.s;
      }
      return false;
    }
  }

  public static class ReferentialEquality {
    public ReferentialEquality() {}
  }

  static class BadEqualsWithParameterizedType {

    // ignored by testEquals() since it has less parameters.
    public BadEqualsWithParameterizedType() {}

    public static BadEqualsWithParameterizedType create(
        @SuppressWarnings("unused") ImmutableList<Iterable<? extends String>> s) {
      return new BadEqualsWithParameterizedType();
    }

    @Override
    public boolean equals(@Nullable Object obj) {
      return obj instanceof BadEqualsWithParameterizedType;
    }

    @Override
    public int hashCode() {
      return 0;
    }
  }

  static class GoodNulls {
    public GoodNulls(String s) {
      checkNotNull(s);
    }

    public void rejectNull(String s) {
      checkNotNull(s);
    }
  }

  public static class BadNulls {
    public void failsToRejectNull(@SuppressWarnings("unused") String s) {}
  }

  public static class NoNullCheckNeededDespiteNotInstantiable {

    public NoNullCheckNeededDespiteNotInstantiable(NotInstantiable x) {
      checkNotNull(x);
    }

    @Keep
    void primitiveOnly(int i) {}

    @Keep
    void nullableOnly(@Nullable String s) {}

    public void noParameter() {}

    @Keep
    void primitiveAndNullable(@Nullable String s, int i) {}
  }

  static class FactoryMethodReturnsNullButNotAnnotated {
    private FactoryMethodReturnsNullButNotAnnotated() {}

    static FactoryMethodReturnsNullButNotAnnotated returnsNull() {
      return null;
    }
  }

  static class FactoryMethodReturnsNullAndAnnotated {
    private FactoryMethodReturnsNullAndAnnotated() {}

    public static @Nullable FactoryMethodReturnsNullAndAnnotated returnsNull() {
      return null;
    }
  }

  static class FactoryMethodAcceptsNull {

    final String name;

    private FactoryMethodAcceptsNull(String name) {
      this.name = name;
    }

    static FactoryMethodAcceptsNull create(@Nullable String name) {
      return new FactoryMethodAcceptsNull(name);
    }
  }

  static class FactoryMethodDoesNotAcceptNull {

    final String name;

    private FactoryMethodDoesNotAcceptNull(String name) {
      this.name = checkNotNull(name);
    }

    public static FactoryMethodDoesNotAcceptNull create(String name) {
      return new FactoryMethodDoesNotAcceptNull(name);
    }
  }

  static class ConstructorAcceptsNull {

    final String name;

    public ConstructorAcceptsNull(@Nullable String name) {
      this.name = name;
    }
  }

  static class ConstructorDoesNotAcceptNull {

    final String name;

    ConstructorDoesNotAcceptNull(String name) {
      this.name = checkNotNull(name);
    }
  }

  static class ConstructorParameterNotInstantiable {
    public ConstructorParameterNotInstantiable(@SuppressWarnings("unused") NotInstantiable x) {}
  }

  static class ConstructorParameterMapOfNotInstantiable {
    private final Map<NotInstantiable, NotInstantiable> m;

    public ConstructorParameterMapOfNotInstantiable(Map<NotInstantiable, NotInstantiable> m) {
      this.m = checkNotNull(m);
    }

    @Override
    public boolean equals(@Nullable Object obj) {
      if (obj instanceof ConstructorParameterMapOfNotInstantiable) {
        return m.equals(((ConstructorParameterMapOfNotInstantiable) obj).m);
      } else {
        return false;
      }
    }

    @Override
    public int hashCode() {
      return m.hashCode();
    }
  }

  // Test that we should get a distinct parameter error when doing equals test.
  static class ConstructorParameterWithOptionalNotInstantiable {
    public ConstructorParameterWithOptionalNotInstantiable(Optional<NotInstantiable> x) {
      checkNotNull(x);
    }

    @Override
    public boolean equals(@Nullable Object obj) {
      throw new UnsupportedOperationException();
    }

    @Override
    public int hashCode() {
      throw new UnsupportedOperationException();
    }
  }

  static class ConstructorParameterSingleValue {
    public ConstructorParameterSingleValue(@SuppressWarnings("unused") Singleton s) {}

    @Override
    public boolean equals(@Nullable Object obj) {
      return obj instanceof ConstructorParameterSingleValue;
    }

    @Override
    public int hashCode() {
      return 1;
    }

    public static class Singleton {
      public static final Singleton INSTANCE = new Singleton();

      private Singleton() {}
    }
  }

  static class FactoryMethodParameterNotInstantiable {

    private FactoryMethodParameterNotInstantiable() {}

    static FactoryMethodParameterNotInstantiable create(
        @SuppressWarnings("unused") NotInstantiable x) {
      return new FactoryMethodParameterNotInstantiable();
    }
  }

  static class ConstructorThrows {
    public ConstructorThrows() {
      throw new RuntimeException();
    }
  }

  static class FactoryMethodThrows {
    private FactoryMethodThrows() {}

    public static FactoryMethodThrows create() {
      throw new RuntimeException();
    }
  }

  static class NotInstantiable {
    private NotInstantiable() {}
  }

  private enum NoConstantEnum {}

  private enum OneConstantEnum {
    A
  }

  private enum EnumFailsToCheckNull {
    A;

    @Keep
    public void failToCheckNull(String s) {}
  }

  private interface AnInterface {}

  private abstract static class AnAbstractClass {
    @Keep
    public AnAbstractClass(String s) {}

    @Keep
    public void failsToCheckNull(String s) {}
  }

  private static class NoPublicStaticMethods {
    @Keep // To test non-public factory isn't used.
    static String notPublic() {
      return "";
    }
  }

  @interface MyAnnotation {}
}
