/*
 * Copyright (C) 2005 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.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.truth.Truth.assertThat;

import com.google.common.base.Converter;
import com.google.common.base.Function;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
import com.google.common.collect.ImmutableMultiset;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Multiset;
import com.google.common.collect.Table;
import com.google.common.reflect.TypeToken;
import com.google.common.testing.NullPointerTester.Visibility;
import com.google.common.testing.anotherpackage.SomeClassThatDoesNotUseNullable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import javax.annotation.Nullable;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;

/**
 * Unit test for {@link NullPointerTester}.
 *
 * @author Kevin Bourrillion
 * @author Mick Killianey
 */
public class NullPointerTesterTest extends TestCase {

  /** Non-NPE RuntimeException. */
  public static class FooException extends RuntimeException {
    private static final long serialVersionUID = 1L;
  }

  /**
   * Class for testing all permutations of static/non-static one-argument
   * methods using methodParameter().
   */
  @SuppressWarnings("unused") // used by reflection
  public static class OneArg {

    public static void staticOneArgCorrectlyThrowsNpe(String s) {
      checkNotNull(s); // expect NPE here on null
    }
    public static void staticOneArgThrowsOtherThanNpe(String s) {
      throw new FooException();  // should catch as failure
    }
    public static void staticOneArgShouldThrowNpeButDoesnt(String s) {
      // should catch as failure
    }
    public static void
    staticOneArgNullableCorrectlyDoesNotThrowNPE(@Nullable String s) {
      // null?  no problem
    }
    public static void
    staticOneArgNullableCorrectlyThrowsOtherThanNPE(@Nullable String s) {
      throw new FooException(); // ok, as long as it's not NullPointerException
    }
    public static void
    staticOneArgNullableThrowsNPE(@Nullable String s) {
      checkNotNull(s); // doesn't check if you said you'd accept null, but you don't
    }

    public void oneArgCorrectlyThrowsNpe(String s) {
      checkNotNull(s); // expect NPE here on null
    }
    public void oneArgThrowsOtherThanNpe(String s) {
      throw new FooException();  // should catch as failure
    }
    public void oneArgShouldThrowNpeButDoesnt(String s) {
      // should catch as failure
    }
    public void oneArgNullableCorrectlyDoesNotThrowNPE(@Nullable String s) {
      // null?  no problem
    }
    public void oneArgNullableCorrectlyThrowsOtherThanNPE(@Nullable String s) {
      throw new FooException(); // ok, as long as it's not NullPointerException
    }
    public void oneArgNullableThrowsNPE(@Nullable String s) {
      checkNotNull(s); // doesn't check if you said you'd accept null, but you don't
    }
  }

  private static final String[] STATIC_ONE_ARG_METHODS_SHOULD_PASS = {
    "staticOneArgCorrectlyThrowsNpe",
    "staticOneArgNullableCorrectlyDoesNotThrowNPE",
    "staticOneArgNullableCorrectlyThrowsOtherThanNPE",
    "staticOneArgNullableThrowsNPE",
  };
  private static final String[] STATIC_ONE_ARG_METHODS_SHOULD_FAIL = {
    "staticOneArgThrowsOtherThanNpe",
    "staticOneArgShouldThrowNpeButDoesnt",
  };
  private static final String[] NONSTATIC_ONE_ARG_METHODS_SHOULD_PASS = {
    "oneArgCorrectlyThrowsNpe",
    "oneArgNullableCorrectlyDoesNotThrowNPE",
    "oneArgNullableCorrectlyThrowsOtherThanNPE",
    "oneArgNullableThrowsNPE",
  };
  private static final String[] NONSTATIC_ONE_ARG_METHODS_SHOULD_FAIL = {
    "oneArgThrowsOtherThanNpe",
    "oneArgShouldThrowNpeButDoesnt",
  };

  private static class ThrowsIae {
    public static void christenPoodle(String name) {
      checkArgument(name != null);
    }
  }

  private static class ThrowsNpe {
    public static void christenPoodle(String name) {
      checkNotNull(name);
    }
  }

  private static class ThrowsUoe {
    public static void christenPoodle(String name) {
      throw new UnsupportedOperationException();
    }
  }

  private static class ThrowsSomethingElse {
    public static void christenPoodle(String name) {
      throw new RuntimeException();
    }
  }

  public void testDontAcceptIae() {
    NullPointerTester tester = new NullPointerTester();
    tester.testAllPublicStaticMethods(ThrowsNpe.class);
    tester.testAllPublicStaticMethods(ThrowsUoe.class);
    try {
      tester.testAllPublicStaticMethods(ThrowsIae.class);
    } catch (AssertionFailedError expected) {
      return;
    }
    fail();
  }

  public void testStaticOneArgMethodsThatShouldPass() throws Exception {
    for (String methodName : STATIC_ONE_ARG_METHODS_SHOULD_PASS) {
      Method method = OneArg.class.getMethod(methodName, String.class);
      try {
        new NullPointerTester().testMethodParameter(new OneArg(), method, 0);
      } catch (AssertionFailedError unexpected) {
        fail("Should not have flagged method " + methodName);
      }
    }
  }

  public void testStaticOneArgMethodsThatShouldFail() throws Exception {
    for (String methodName : STATIC_ONE_ARG_METHODS_SHOULD_FAIL) {
      Method method = OneArg.class.getMethod(methodName, String.class);
      boolean foundProblem = false;
      try {
        new NullPointerTester().testMethodParameter(new OneArg(), method, 0);
      } catch (AssertionFailedError expected) {
        foundProblem = true;
      }
      assertTrue("Should report error in method " + methodName, foundProblem);
    }
  }

  public void testNonStaticOneArgMethodsThatShouldPass() throws Exception {
    OneArg foo = new OneArg();
    for (String methodName : NONSTATIC_ONE_ARG_METHODS_SHOULD_PASS) {
      Method method = OneArg.class.getMethod(methodName, String.class);
      try {
        new NullPointerTester().testMethodParameter(foo, method, 0);
      } catch (AssertionFailedError unexpected) {
        fail("Should not have flagged method " + methodName);
      }
    }
  }

  public void testNonStaticOneArgMethodsThatShouldFail() throws Exception {
    OneArg foo = new OneArg();
    for (String methodName : NONSTATIC_ONE_ARG_METHODS_SHOULD_FAIL) {
      Method method = OneArg.class.getMethod(methodName, String.class);
      boolean foundProblem = false;
      try {
        new NullPointerTester().testMethodParameter(foo, method, 0);
      } catch (AssertionFailedError expected) {
        foundProblem = true;
      }
      assertTrue("Should report error in method " + methodName, foundProblem);
    }
  }

  public void testMessageOtherException() throws Exception {
    Method method = OneArg.class.getMethod("staticOneArgThrowsOtherThanNpe", String.class);
    boolean foundProblem = false;
    try {
      new NullPointerTester().testMethodParameter(new OneArg(), method, 0);
    } catch (AssertionFailedError expected) {
      assertThat(expected.getMessage()).contains("index 0");
      assertThat(expected.getMessage()).contains("[null]");
      foundProblem = true;
    }
    assertTrue("Should report error when different exception is thrown", foundProblem);
  }

  public void testMessageNoException() throws Exception {
    Method method = OneArg.class.getMethod("staticOneArgShouldThrowNpeButDoesnt", String.class);
    boolean foundProblem = false;
    try {
      new NullPointerTester().testMethodParameter(new OneArg(), method, 0);
    } catch (AssertionFailedError expected) {
      assertThat(expected.getMessage()).contains("index 0");
      assertThat(expected.getMessage()).contains("[null]");
      foundProblem = true;
    }
    assertTrue("Should report error when no exception is thrown", foundProblem);
  }

  /**
   * Class for testing all permutations of nullable/non-nullable two-argument
   * methods using testMethod().
   *
   *   normalNormal:  two params, neither is Nullable
   *   nullableNormal:  only first param is Nullable
   *   normalNullable:  only second param is Nullable
   *   nullableNullable:  both params are Nullable
   */
  public static class TwoArg {
    /** Action to take on a null param. */
    public enum Action {
      THROW_A_NPE {
        @Override public void act() {
          throw new NullPointerException();
        }
      },
      THROW_OTHER {
        @Override public void act() {
          throw new FooException();
        }
      },
      JUST_RETURN {
        @Override public void act() {}
      };

      public abstract void act();
    }
    Action actionWhenFirstParamIsNull;
    Action actionWhenSecondParamIsNull;

    public TwoArg(
        Action actionWhenFirstParamIsNull,
        Action actionWhenSecondParamIsNull) {
      this.actionWhenFirstParamIsNull = actionWhenFirstParamIsNull;
      this.actionWhenSecondParamIsNull = actionWhenSecondParamIsNull;
    }

    /** Method that decides how to react to parameters. */
    public void reactToNullParameters(Object first, Object second) {
      if (first == null) {
        actionWhenFirstParamIsNull.act();
      }
      if (second == null) {
        actionWhenSecondParamIsNull.act();
      }
    }

    /** Two-arg method with no Nullable params. */
    public void normalNormal(String first, Integer second) {
      reactToNullParameters(first, second);
    }

    /** Two-arg method with the second param Nullable. */
    public void normalNullable(String first, @Nullable Integer second) {
      reactToNullParameters(first, second);
    }

    /** Two-arg method with the first param Nullable. */
    public void nullableNormal(@Nullable String first, Integer second) {
      reactToNullParameters(first, second);
    }

    /** Two-arg method with the both params Nullable. */
    public void nullableNullable(
        @Nullable String first, @Nullable Integer second) {
      reactToNullParameters(first, second);
    }

    /** To provide sanity during debugging. */
    @Override public String toString() {
      return rootLocaleFormat("Bar(%s, %s)",
          actionWhenFirstParamIsNull, actionWhenSecondParamIsNull);
    }
  }

  public void verifyBarPass(Method method, TwoArg bar) {
    try {
      new NullPointerTester().testMethod(bar, method);
    } catch (AssertionFailedError incorrectError) {
      String errorMessage = rootLocaleFormat(
          "Should not have flagged method %s for %s", method.getName(), bar);
      assertNull(errorMessage, incorrectError);
    }
  }

  public void verifyBarFail(Method method, TwoArg bar) {
    try {
      new NullPointerTester().testMethod(bar, method);
    } catch (AssertionFailedError expected) {
      return; // good...we wanted a failure
    }
    String errorMessage = rootLocaleFormat(
        "Should have flagged method %s for %s", method.getName(), bar);
    fail(errorMessage);
  }

  public void testTwoArgNormalNormal() throws Exception {
    Method method = TwoArg.class.getMethod(
        "normalNormal", String.class, Integer.class);
    for (TwoArg.Action first : TwoArg.Action.values()) {
      for (TwoArg.Action second : TwoArg.Action.values()) {
        TwoArg bar = new TwoArg(first, second);
        if (first.equals(TwoArg.Action.THROW_A_NPE)
            && second.equals(TwoArg.Action.THROW_A_NPE)) {
          verifyBarPass(method, bar); // require both params to throw NPE
        } else {
          verifyBarFail(method, bar);
        }
      }
    }
  }

  public void testTwoArgNormalNullable() throws Exception {
    Method method = TwoArg.class.getMethod(
        "normalNullable", String.class, Integer.class);
    for (TwoArg.Action first : TwoArg.Action.values()) {
      for (TwoArg.Action second : TwoArg.Action.values()) {
        TwoArg bar = new TwoArg(first, second);
        if (first.equals(TwoArg.Action.THROW_A_NPE)) {
          verifyBarPass(method, bar); // only pass if 1st param throws NPE
        } else {
          verifyBarFail(method, bar);
        }
      }
    }
  }

  public void testTwoArgNullableNormal() throws Exception {
    Method method = TwoArg.class.getMethod(
        "nullableNormal", String.class, Integer.class);
    for (TwoArg.Action first : TwoArg.Action.values()) {
      for (TwoArg.Action second : TwoArg.Action.values()) {
        TwoArg bar = new TwoArg(first, second);
        if (second.equals(TwoArg.Action.THROW_A_NPE)) {
          verifyBarPass(method, bar); // only pass if 2nd param throws NPE
        } else {
          verifyBarFail(method, bar);
        }
      }
    }
  }

  public void testTwoArgNullableNullable() throws Exception {
    Method method = TwoArg.class.getMethod(
        "nullableNullable", String.class, Integer.class);
    for (TwoArg.Action first : TwoArg.Action.values()) {
      for (TwoArg.Action second : TwoArg.Action.values()) {
        TwoArg bar = new TwoArg(first, second);
        verifyBarPass(method, bar); // All args nullable:  anything goes!
      }
    }
  }

  /*
   * This next part consists of several sample classes that provide
   * demonstrations of conditions that cause NullPointerTester
   * to succeed/fail.
   */

  /** Lots of well-behaved methods. */
  @SuppressWarnings("unused") // used by reflection
  private static class PassObject extends SomeClassThatDoesNotUseNullable {
    public static void doThrow(Object arg) {
      if (arg == null) {
        throw new FooException();
      }
    }
    public void noArg() {}
    public void oneArg(String s) { checkNotNull(s); }
    void packagePrivateOneArg(String s) { checkNotNull(s); }
    protected void protectedOneArg(String s) { checkNotNull(s); }
    public void oneNullableArg(@Nullable String s) {}
    public void oneNullableArgThrows(@Nullable String s) { doThrow(s); }

    public void twoArg(String s, Integer i) { checkNotNull(s); i.intValue(); }
    public void twoMixedArgs(String s, @Nullable Integer i) { checkNotNull(s); }
    public void twoMixedArgsThrows(String s, @Nullable Integer i) {
      checkNotNull(s); doThrow(i);
    }
    public void twoMixedArgs(@Nullable Integer i, String s) { checkNotNull(s); }
    public void twoMixedArgsThrows(@Nullable Integer i, String s) {
      checkNotNull(s); doThrow(i);
    }
    public void twoNullableArgs(@Nullable String s,
        @javax.annotation.Nullable Integer i) {}
    public void twoNullableArgsThrowsFirstArg(
        @Nullable String s, @Nullable Integer i) {
      doThrow(s);
    }
    public void twoNullableArgsThrowsSecondArg(
        @Nullable String s, @Nullable Integer i) {
      doThrow(i);
    }
    public static void staticOneArg(String s) { checkNotNull(s); }
    public static void staticOneNullableArg(@Nullable String s) {}
    public static void staticOneNullableArgThrows(@Nullable String s) {
      doThrow(s);
    }
  }

  public void testGoodClass() {
    shouldPass(new PassObject());
  }

  private static class FailOneArgDoesntThrowNPE extends PassObject {
    @Override public void oneArg(String s) {
      // Fail:  missing NPE for s
    }
  }

  public void testFailOneArgDoesntThrowNpe() {
    shouldFail(new FailOneArgDoesntThrowNPE());
  }

  private static class FailOneArgThrowsWrongType extends PassObject {
    @Override public void oneArg(String s) {
      doThrow(s); // Fail:  throwing non-NPE exception for null s
    }
  }

  public void testFailOneArgThrowsWrongType() {
    shouldFail(new FailOneArgThrowsWrongType());
  }

  private static class PassOneNullableArgThrowsNPE extends PassObject {
    @Override public void oneNullableArg(@Nullable String s) {
      checkNotNull(s); // ok to throw NPE
    }
  }

  public void testPassOneNullableArgThrowsNPE() {
    shouldPass(new PassOneNullableArgThrowsNPE());
  }

  private static class FailTwoArgsFirstArgDoesntThrowNPE extends PassObject {
    @Override public void twoArg(String s, Integer i) {
      // Fail: missing NPE for s
      i.intValue();
    }
  }

  public void testFailTwoArgsFirstArgDoesntThrowNPE() {
    shouldFail(new FailTwoArgsFirstArgDoesntThrowNPE());
  }

  private static class FailTwoArgsFirstArgThrowsWrongType extends PassObject {
    @Override public void twoArg(String s, Integer i) {
      doThrow(s); // Fail:  throwing non-NPE exception for null s
      i.intValue();
    }
  }

  public void testFailTwoArgsFirstArgThrowsWrongType() {
    shouldFail(new FailTwoArgsFirstArgThrowsWrongType());
  }

  private static class FailTwoArgsSecondArgDoesntThrowNPE extends PassObject {
    @Override public void twoArg(String s, Integer i) {
      checkNotNull(s);
      // Fail: missing NPE for i
    }
  }

  public void testFailTwoArgsSecondArgDoesntThrowNPE() {
    shouldFail(new FailTwoArgsSecondArgDoesntThrowNPE());
  }

  private static class FailTwoArgsSecondArgThrowsWrongType extends PassObject {
    @Override public void twoArg(String s, Integer i) {
      checkNotNull(s);
      doThrow(i); // Fail:  throwing non-NPE exception for null i
    }
  }

  public void testFailTwoArgsSecondArgThrowsWrongType() {
    shouldFail(new FailTwoArgsSecondArgThrowsWrongType());
  }

  private static class FailTwoMixedArgsFirstArgDoesntThrowNPE
      extends PassObject {
    @Override public void twoMixedArgs(String s, @Nullable Integer i) {
      // Fail: missing NPE for s
    }
  }

  public void testFailTwoMixedArgsFirstArgDoesntThrowNPE() {
    shouldFail(new FailTwoMixedArgsFirstArgDoesntThrowNPE());
  }

  private static class FailTwoMixedArgsFirstArgThrowsWrongType
      extends PassObject {
    @Override public void twoMixedArgs(String s, @Nullable Integer i) {
      doThrow(s); // Fail:  throwing non-NPE exception for null s
    }
  }

  public void testFailTwoMixedArgsFirstArgThrowsWrongType() {
    shouldFail(new FailTwoMixedArgsFirstArgThrowsWrongType());
  }

  private static class PassTwoMixedArgsNullableArgThrowsNPE extends PassObject {
    @Override public void twoMixedArgs(String s, @Nullable Integer i) {
      checkNotNull(s);
      i.intValue(); // ok to throw NPE?
    }
  }

  public void testPassTwoMixedArgsNullableArgThrowsNPE() {
    shouldPass(new PassTwoMixedArgsNullableArgThrowsNPE());
  }

  private static class PassTwoMixedArgSecondNullableArgThrowsOther
      extends PassObject {
    @Override public void twoMixedArgs(String s, @Nullable Integer i) {
      checkNotNull(s);
      doThrow(i); // ok to throw non-NPE exception for null i
    }
  }

  public void testPassTwoMixedArgSecondNullableArgThrowsOther() {
    shouldPass(new PassTwoMixedArgSecondNullableArgThrowsOther());
  }

  private static class FailTwoMixedArgsSecondArgDoesntThrowNPE
      extends PassObject {
    @Override public void twoMixedArgs(@Nullable Integer i, String s) {
      // Fail: missing NPE for null s
    }
  }

  public void testFailTwoMixedArgsSecondArgDoesntThrowNPE() {
    shouldFail(new FailTwoMixedArgsSecondArgDoesntThrowNPE());
  }

  private static class FailTwoMixedArgsSecondArgThrowsWrongType
      extends PassObject {
    @Override public void twoMixedArgs(@Nullable Integer i, String s) {
      doThrow(s); // Fail:  throwing non-NPE exception for null s
    }
  }

  public void testFailTwoMixedArgsSecondArgThrowsWrongType() {
    shouldFail(new FailTwoMixedArgsSecondArgThrowsWrongType());
  }

  private static class PassTwoNullableArgsFirstThrowsNPE extends PassObject {
    @Override public void twoNullableArgs(
        @Nullable String s, @Nullable Integer i) {
      checkNotNull(s); // ok to throw NPE?
    }
  }

  public void testPassTwoNullableArgsFirstThrowsNPE() {
    shouldPass(new PassTwoNullableArgsFirstThrowsNPE());
  }

  private static class PassTwoNullableArgsFirstThrowsOther extends PassObject {
    @Override public void twoNullableArgs(
        @Nullable String s, @Nullable Integer i) {
      doThrow(s); // ok to throw non-NPE exception for null s
    }
  }

  public void testPassTwoNullableArgsFirstThrowsOther() {
    shouldPass(new PassTwoNullableArgsFirstThrowsOther());
  }

  private static class PassTwoNullableArgsSecondThrowsNPE extends PassObject {
    @Override public void twoNullableArgs(
        @Nullable String s, @Nullable Integer i) {
      i.intValue(); // ok to throw NPE?
    }
  }

  public void testPassTwoNullableArgsSecondThrowsNPE() {
    shouldPass(new PassTwoNullableArgsSecondThrowsNPE());
  }

  private static class PassTwoNullableArgsSecondThrowsOther extends PassObject {
    @Override public void twoNullableArgs(
        @Nullable String s, @Nullable Integer i) {
      doThrow(i); // ok to throw non-NPE exception for null i
    }
  }

  public void testPassTwoNullableArgsSecondThrowsOther() {
    shouldPass(new PassTwoNullableArgsSecondThrowsOther());
  }

  private static class PassTwoNullableArgsNeitherThrowsAnything
      extends PassObject {
    @Override public void twoNullableArgs(
        @Nullable String s, @Nullable Integer i) {
      // ok to do nothing
    }
  }

  public void testPassTwoNullableArgsNeitherThrowsAnything() {
    shouldPass(new PassTwoNullableArgsNeitherThrowsAnything());
  }

  @SuppressWarnings("unused") // for NullPointerTester
  private abstract static class BaseClassThatFailsToThrow {
    public void oneArg(String s) {}
  }

  private static class SubclassWithBadSuperclass
      extends BaseClassThatFailsToThrow {}

  public void testSubclassWithBadSuperclass() {
    shouldFail(new SubclassWithBadSuperclass());
  }

  @SuppressWarnings("unused") // for NullPointerTester
  private abstract static class BaseClassThatFailsToThrowForPackagePrivate {
    void packagePrivateOneArg(String s) {}
  }

  private static class SubclassWithBadSuperclassForPackagePrivate
      extends BaseClassThatFailsToThrowForPackagePrivate {}

  public void testSubclassWithBadSuperclassForPackagePrivateMethod() {
    shouldFail(
        new SubclassWithBadSuperclassForPackagePrivate(), Visibility.PACKAGE);
  }

  @SuppressWarnings("unused") // for NullPointerTester
  private abstract static class BaseClassThatFailsToThrowForProtected {
    protected void protectedOneArg(String s) {}
  }

  private static class SubclassWithBadSuperclassForProtected
      extends BaseClassThatFailsToThrowForProtected {}

  public void testSubclassWithBadSuperclassForPackageProtectedMethod() {
    shouldFail(
        new SubclassWithBadSuperclassForProtected(), Visibility.PROTECTED);
  }

  private static class SubclassThatOverridesBadSuperclassMethod
      extends BaseClassThatFailsToThrow {
    @Override public void oneArg(@Nullable String s) {}
  }

  public void testSubclassThatOverridesBadSuperclassMethod() {
    shouldPass(new SubclassThatOverridesBadSuperclassMethod());
  }

  @SuppressWarnings("unused") // for NullPointerTester
  private static class SubclassOverridesTheWrongMethod
      extends BaseClassThatFailsToThrow {
    public void oneArg(@Nullable CharSequence s) {}
  }

  public void testSubclassOverridesTheWrongMethod() {
    shouldFail(new SubclassOverridesTheWrongMethod());
  }

  @SuppressWarnings("unused") // for NullPointerTester
  private static class ClassThatFailsToThrowForStatic {
    static void staticOneArg(String s) {}
  }

  public void testClassThatFailsToThrowForStatic() {
    shouldFail(ClassThatFailsToThrowForStatic.class);
  }

  private static class SubclassThatFailsToThrowForStatic
      extends ClassThatFailsToThrowForStatic {}

  public void testSubclassThatFailsToThrowForStatic() {
    shouldFail(SubclassThatFailsToThrowForStatic.class);
  }

  private static class SubclassThatTriesToOverrideBadStaticMethod
      extends ClassThatFailsToThrowForStatic {
    static void staticOneArg(@Nullable String s) {}
  }

  public void testSubclassThatTriesToOverrideBadStaticMethod() {
    shouldFail(SubclassThatTriesToOverrideBadStaticMethod.class);
  }

  private static final class HardToCreate {
    private HardToCreate(HardToCreate x) {}
  }

  @SuppressWarnings("unused") // used by reflection
  private static class CanCreateDefault {
    public void foo(@Nullable HardToCreate ignored, String required) {
      checkNotNull(required);
    }
  }

  public void testCanCreateDefault() {
    shouldPass(new CanCreateDefault());
  }

  @SuppressWarnings("unused") // used by reflection
  private static class CannotCreateDefault {
    public void foo(HardToCreate ignored, String required) {
      checkNotNull(ignored);
      checkNotNull(required);
    }
  }

  public void testCannotCreateDefault() {
    shouldFail(new CannotCreateDefault());
  }

  private static void shouldPass(Object instance, Visibility visibility) {
    new NullPointerTester().testInstanceMethods(instance, visibility);
  }

  private static void shouldPass(Object instance) {
    shouldPass(instance, Visibility.PACKAGE);
    shouldPass(instance, Visibility.PROTECTED);
    shouldPass(instance, Visibility.PUBLIC);
  }

  // TODO(cpovirk): eliminate surprising Object/Class overloading of shouldFail

  private static void shouldFail(Object instance, Visibility visibility) {
    try {
      new NullPointerTester().testInstanceMethods(instance, visibility);
    } catch (AssertionFailedError expected) {
      return;
    }
    fail("Should detect problem in " + instance.getClass().getSimpleName());
  }

  private static void shouldFail(Object instance) {
    shouldFail(instance, Visibility.PACKAGE);
    shouldFail(instance, Visibility.PROTECTED);
    shouldFail(instance, Visibility.PUBLIC);
  }

  private static void shouldFail(Class<?> cls, Visibility visibility) {
    try {
      new NullPointerTester().testStaticMethods(cls, visibility);
    } catch (AssertionFailedError expected) {
      return;
    }
    fail("Should detect problem in " + cls.getSimpleName());
  }

  private static void shouldFail(Class<?> cls) {
    shouldFail(cls, Visibility.PACKAGE);
  }

  @SuppressWarnings("unused") // used by reflection
  private static class PrivateClassWithPrivateConstructor {
    private PrivateClassWithPrivateConstructor(@Nullable Integer argument) {}
  }

  public void testPrivateClass() {
    NullPointerTester tester = new NullPointerTester();
    for (Constructor<?> constructor
        : PrivateClassWithPrivateConstructor.class.getDeclaredConstructors()) {
      tester.testConstructor(constructor);
    }
  }

  private interface Foo<T> {
    void doSomething(T bar, Integer baz);
  }

  private static class StringFoo implements Foo<String> {

    @Override public void doSomething(String bar, Integer baz) {
      checkNotNull(bar);
      checkNotNull(baz);
    }
  }

  public void testBridgeMethodIgnored() {
    new NullPointerTester().testAllPublicInstanceMethods(new StringFoo());
  }

  private abstract static class DefaultValueChecker {

    private final Map<Integer, Object> arguments = Maps.newHashMap();

    final DefaultValueChecker runTester() {
      new NullPointerTester()
          .testInstanceMethods(this, Visibility.PACKAGE);
      return this;
    }

    final void assertNonNullValues(Object... expectedValues) {
      assertEquals(expectedValues.length, arguments.size());
      for (int i = 0; i < expectedValues.length; i++) {
        assertEquals("Default value for parameter #" + i,
            expectedValues[i], arguments.get(i));
      }
    }

    final Object getDefaultParameterValue(int position) {
      return arguments.get(position);
    }

    final void calledWith(Object... args) {
      for (int i = 0; i < args.length; i++) {
        if (args[i] != null) {
          arguments.put(i, args[i]);
        }
      }
      for (Object arg : args) {
        checkNotNull(arg); // to fulfill null check
      }
    }
  }

  private enum Gender {
    MALE, FEMALE
  }

  private static class AllDefaultValuesChecker extends DefaultValueChecker {

    @SuppressWarnings("unused") // called by NullPointerTester
    public void checkDefaultValuesForTheseTypes(
        Gender gender,
        Integer integer, int i,
        String string, CharSequence charSequence,
        List<String> list,
        ImmutableList<Integer> immutableList,
        Map<String, Integer> map,
        ImmutableMap<String, String> immutableMap,
        Set<String> set,
        ImmutableSet<Integer> immutableSet,
        SortedSet<Number> sortedSet,
        ImmutableSortedSet<Number> immutableSortedSet,
        Multiset<String> multiset,
        ImmutableMultiset<Integer> immutableMultiset,
        Multimap<String, Integer> multimap,
        ImmutableMultimap<String, Integer> immutableMultimap,
        Table<String, Integer, Exception> table,
        ImmutableTable<Integer, String, Exception> immutableTable) {
      calledWith(
          gender,
          integer, i,
          string, charSequence,
          list, immutableList,
          map, immutableMap,
          set, immutableSet,
          sortedSet, immutableSortedSet,
          multiset, immutableMultiset,
          multimap, immutableMultimap,
          table, immutableTable);
    }

    final void check() {
      runTester().assertNonNullValues(
          Gender.MALE,
          Integer.valueOf(0), 0,
          "", "",
          ImmutableList.of(), ImmutableList.of(),
          ImmutableMap.of(), ImmutableMap.of(),
          ImmutableSet.of(), ImmutableSet.of(),
          ImmutableSortedSet.of(), ImmutableSortedSet.of(),
          ImmutableMultiset.of(), ImmutableMultiset.of(),
          ImmutableMultimap.of(), ImmutableMultimap.of(),
          ImmutableTable.of(), ImmutableTable.of());
    }
  }

  public void testDefaultValues() {
    new AllDefaultValuesChecker().check();
  }

  private static class ObjectArrayDefaultValueChecker
      extends DefaultValueChecker {

    @SuppressWarnings("unused") // called by NullPointerTester
    public void checkArray(Object[] array, String s) {
      calledWith(array, s);
    }

    void check() {
      runTester();
      Object[] defaultArray = (Object[]) getDefaultParameterValue(0);
      assertThat(defaultArray).isEmpty();
    }
  }

  public void testObjectArrayDefaultValue() {
    new ObjectArrayDefaultValueChecker().check();
  }

  private static class StringArrayDefaultValueChecker
      extends DefaultValueChecker {

    @SuppressWarnings("unused") // called by NullPointerTester
    public void checkArray(String[] array, String s) {
      calledWith(array, s);
    }

    void check() {
      runTester();
      String[] defaultArray = (String[]) getDefaultParameterValue(0);
      assertThat(defaultArray).isEmpty();
    }
  }

  public void testStringArrayDefaultValue() {
    new StringArrayDefaultValueChecker().check();
  }

  private static class IntArrayDefaultValueChecker
      extends DefaultValueChecker {

    @SuppressWarnings("unused") // called by NullPointerTester
    public void checkArray(int[] array, String s) {
      calledWith(array, s);
    }

    void check() {
      runTester();
      int[] defaultArray = (int[]) getDefaultParameterValue(0);
      assertEquals(0, defaultArray.length);
    }
  }

  public void testIntArrayDefaultValue() {
    new IntArrayDefaultValueChecker().check();
  }

  private enum EmptyEnum {}

  private static class EmptyEnumDefaultValueChecker
      extends DefaultValueChecker {

    @SuppressWarnings("unused") // called by NullPointerTester
    public void checkArray(EmptyEnum object, String s) {
      calledWith(object, s);
    }

    void check() {
      try {
        runTester();
      } catch (AssertionFailedError expected) {
        return;
      }
      fail("Should have failed because enum has no constant");
    }
  }

  public void testEmptyEnumDefaultValue() {
    new EmptyEnumDefaultValueChecker().check();
  }

  private static class GenericClassTypeDefaultValueChecker
      extends DefaultValueChecker {

    @SuppressWarnings("unused") // called by NullPointerTester
    public void checkArray(Class<? extends List<?>> cls, String s) {
      calledWith(cls, s);
    }

    void check() {
      runTester();
      Class<?> defaultClass = (Class<?>) getDefaultParameterValue(0);
      assertEquals(List.class, defaultClass);
    }
  }

  public void testGenericClassDefaultValue() {
    new GenericClassTypeDefaultValueChecker().check();
  }

  private static class NonGenericClassTypeDefaultValueChecker
      extends DefaultValueChecker {

    @SuppressWarnings("unused") // called by NullPointerTester
    public void checkArray(@SuppressWarnings("rawtypes") Class cls, String s) {
      calledWith(cls, s);
    }

    void check() {
      runTester();
      Class<?> defaultClass = (Class<?>) getDefaultParameterValue(0);
      assertEquals(Object.class, defaultClass);
    }
  }

  public void testNonGenericClassDefaultValue() {
    new NonGenericClassTypeDefaultValueChecker().check();
  }

  private static class GenericTypeTokenDefaultValueChecker
      extends DefaultValueChecker {

    @SuppressWarnings("unused") // called by NullPointerTester
    public void checkArray(
        TypeToken<? extends List<? super Number>> type, String s) {
      calledWith(type, s);
    }

    void check() {
      runTester();
      TypeToken<?> defaultType = (TypeToken<?>) getDefaultParameterValue(0);
      assertTrue(new TypeToken<List<? super Number>>() {}
          .isSupertypeOf(defaultType));
    }
  }

  public void testGenericTypeTokenDefaultValue() {
    new GenericTypeTokenDefaultValueChecker().check();
  }

  private static class NonGenericTypeTokenDefaultValueChecker
      extends DefaultValueChecker {

    @SuppressWarnings("unused") // called by NullPointerTester
    public void checkArray(
        @SuppressWarnings("rawtypes") TypeToken type, String s) {
      calledWith(type, s);
    }

    void check() {
      runTester();
      TypeToken<?> defaultType = (TypeToken<?>) getDefaultParameterValue(0);
      assertEquals(new TypeToken<Object>() {}, defaultType);
    }
  }

  public void testNonGenericTypeTokenDefaultValue() {
    new NonGenericTypeTokenDefaultValueChecker().check();
  }

  private interface FromTo<F, T> extends Function<F, T> {}

  private static class GenericInterfaceDefaultValueChecker
      extends DefaultValueChecker {

    @SuppressWarnings("unused") // called by NullPointerTester
    public void checkArray(FromTo<String, Integer> f, String s) {
      calledWith(f, s);
    }

    void check() {
      runTester();
      FromTo<?, ?> defaultFunction = (FromTo<?, ?>) getDefaultParameterValue(0);
      assertEquals(0, defaultFunction.apply(null));
    }
  }

  public void testGenericInterfaceDefaultValue() {
    new GenericInterfaceDefaultValueChecker().check();
  }

  private interface NullRejectingFromTo<F, T> extends Function<F, T> {
    @Override public abstract T apply(F from);
  }

  private static class NullRejectingInterfaceDefaultValueChecker
      extends DefaultValueChecker {

    @SuppressWarnings("unused") // called by NullPointerTester
    public void checkArray(NullRejectingFromTo<String, Integer> f, String s) {
      calledWith(f, s);
    }

    void check() {
      runTester();
      NullRejectingFromTo<?, ?> defaultFunction = (NullRejectingFromTo<?, ?>)
          getDefaultParameterValue(0);
      assertNotNull(defaultFunction);
      try {
        defaultFunction.apply(null);
        fail("Proxy Should have rejected null");
      } catch (NullPointerException expected) {}
    }
  }

  public void testNullRejectingInterfaceDefaultValue() {
    new NullRejectingInterfaceDefaultValueChecker().check();
  }

  private static class MultipleInterfacesDefaultValueChecker
      extends DefaultValueChecker {

    @SuppressWarnings("unused") // called by NullPointerTester
    public <T extends FromTo<String, Integer> & Supplier<Long>> void checkArray(
        T f, String s) {
      calledWith(f, s);
    }

    void check() {
      runTester();
      FromTo<?, ?> defaultFunction = (FromTo<?, ?>) getDefaultParameterValue(0);
      assertEquals(0, defaultFunction.apply(null));
      Supplier<?> defaultSupplier = (Supplier<?>) defaultFunction;
      assertEquals(Long.valueOf(0), defaultSupplier.get());
    }
  }

  public void testMultipleInterfacesDefaultValue() {
    new MultipleInterfacesDefaultValueChecker().check();
  }

  private static class GenericInterface2DefaultValueChecker
      extends DefaultValueChecker {

    @SuppressWarnings("unused") // called by NullPointerTester
    public void checkArray(FromTo<String, FromTo<Integer, String>> f, String s) {
      calledWith(f, s);
    }

    void check() {
      runTester();
      FromTo<?, ?> defaultFunction = (FromTo<?, ?>) getDefaultParameterValue(0);
      FromTo<?, ?> returnValue = (FromTo<?, ?>) defaultFunction.apply(null);
      assertEquals("", returnValue.apply(null));
    }
  }

  public void testGenericInterfaceReturnedByGenericMethod() {
    new GenericInterface2DefaultValueChecker().check();
  }

  private abstract static class AbstractGenericDefaultValueChecker<T>
      extends DefaultValueChecker {

    @SuppressWarnings("unused") // called by NullPointerTester
    public void checkGeneric(T value, String s) {
      calledWith(value, s);
    }
  }

  private static class GenericDefaultValueResolvedToStringChecker
      extends AbstractGenericDefaultValueChecker<String> {
    void check() {
      runTester();
      assertEquals("", getDefaultParameterValue(0));
    }
  }

  public void testGenericTypeResolvedForDefaultValue() {
    new GenericDefaultValueResolvedToStringChecker().check();
  }

  private abstract static
  class AbstractGenericDefaultValueForPackagePrivateMethodChecker<T>
      extends DefaultValueChecker {

    @SuppressWarnings("unused") // called by NullPointerTester
    void checkGeneric(T value, String s) {
      calledWith(value, s);
    }
  }

  private static
  class DefaultValueForPackagePrivateMethodResolvedToStringChecker
      extends AbstractGenericDefaultValueForPackagePrivateMethodChecker<String>
  {
    void check() {
      runTester();
      assertEquals("", getDefaultParameterValue(0));
    }
  }

  public void testDefaultValueResolvedForPackagePrivateMethod() {
    new DefaultValueForPackagePrivateMethodResolvedToStringChecker().check();
  }

  private static class ConverterDefaultValueChecker
      extends DefaultValueChecker {

    @SuppressWarnings("unused") // called by NullPointerTester
    public void checkArray(Converter<String, Integer> c, String s) {
      calledWith(c, s);
    }

    void check() {
      runTester();
      @SuppressWarnings("unchecked") // We are checking it anyway
      Converter<String, Integer> defaultConverter = (Converter<String, Integer>)
          getDefaultParameterValue(0);
      assertEquals(Integer.valueOf(0), defaultConverter.convert("anything"));
      assertEquals("", defaultConverter.reverse().convert(123));
      assertNull(defaultConverter.convert(null));
      assertNull(defaultConverter.reverse().convert(null));
    }
  }

  public void testConverterDefaultValue() {
    new ConverterDefaultValueChecker().check();
  }

  private static class VisibilityMethods {

    @SuppressWarnings("unused") // Called by reflection
    private void privateMethod() {}

    @SuppressWarnings("unused") // Called by reflection
    void packagePrivateMethod() {}

    @SuppressWarnings("unused") // Called by reflection
    protected void protectedMethod() {}

    @SuppressWarnings("unused") // Called by reflection
    public void publicMethod() {}
  }

  public void testVisibility_public() throws Exception {
    assertFalse(Visibility.PUBLIC.isVisible(
        VisibilityMethods.class.getDeclaredMethod("privateMethod")));
    assertFalse(Visibility.PUBLIC.isVisible(
        VisibilityMethods.class.getDeclaredMethod("packagePrivateMethod")));
    assertFalse(Visibility.PUBLIC.isVisible(
        VisibilityMethods.class.getDeclaredMethod("protectedMethod")));
    assertTrue(Visibility.PUBLIC.isVisible(
        VisibilityMethods.class.getDeclaredMethod("publicMethod")));
  }

  public void testVisibility_protected() throws Exception {
    assertFalse(Visibility.PROTECTED.isVisible(
        VisibilityMethods.class.getDeclaredMethod("privateMethod")));
    assertFalse(Visibility.PROTECTED.isVisible(
        VisibilityMethods.class.getDeclaredMethod("packagePrivateMethod")));
    assertTrue(Visibility.PROTECTED.isVisible(
        VisibilityMethods.class.getDeclaredMethod("protectedMethod")));
    assertTrue(Visibility.PROTECTED.isVisible(
        VisibilityMethods.class.getDeclaredMethod("publicMethod")));
  }

  public void testVisibility_package() throws Exception {
    assertFalse(Visibility.PACKAGE.isVisible(
        VisibilityMethods.class.getDeclaredMethod("privateMethod")));
    assertTrue(Visibility.PACKAGE.isVisible(
        VisibilityMethods.class.getDeclaredMethod("packagePrivateMethod")));
    assertTrue(Visibility.PACKAGE.isVisible(
        VisibilityMethods.class.getDeclaredMethod("protectedMethod")));
    assertTrue(Visibility.PACKAGE.isVisible(
        VisibilityMethods.class.getDeclaredMethod("publicMethod")));
  }

  private class Inner {
    public Inner(String s) {
      checkNotNull(s);
    }
  }

  public void testNonStaticInnerClass() {
    try {
      new NullPointerTester().testAllPublicConstructors(Inner.class);
      fail();
    } catch (IllegalArgumentException expected) {
      assertThat(expected.getMessage()).contains("inner class");
    }
  }

  private static String rootLocaleFormat(String format, Object... args) {
    return String.format(Locale.ROOT, format, args);
  }

  static class OverridesEquals {
    @Override
    public boolean equals(Object o) {
      return true;
    }
  }

  static class DoesNotOverrideEquals {
    public boolean equals(Object a, Object b) {
      return true;
    }
  }

  public void testEqualsMethod() {
    shouldPass(new OverridesEquals());
    shouldFail(new DoesNotOverrideEquals());
  }
}
