/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

package codetoanalyze.java.nullsafe_default;

import android.text.TextUtils;
import com.facebook.infer.annotation.FalseOnNull;
import com.facebook.infer.annotation.TrueOnNull;
import com.google.common.base.Strings;
import javax.annotation.Nullable;

/** Testing functionality related to @TrueOnNull and @FalseOnNull methods */
public class TrueFalseOnNull {

  static class StaticOneParam {
    @TrueOnNull
    static boolean trueOnNull(@Nullable Object o) {
      return o == null ? true : o.toString() == "something";
    }

    @FalseOnNull
    static boolean falseOnNull(@Nullable Object o) {
      return o == null ? false : o.toString() == "something";
    }

    static boolean notAnnotated(@Nullable Object o) {
      return o == null ? false : o.toString() == "something";
    }
  }

  static class NonStaticOneParam {
    private String compareTo = "something";

    @TrueOnNull
    boolean trueOnNull(@Nullable Object o) {
      return o == null ? true : o.toString() == compareTo;
    }

    @FalseOnNull
    boolean falseOnNull(@Nullable Object o) {
      return o == null ? false : o.toString() == compareTo;
    }

    boolean notAnnotated(@Nullable Object o) {
      return o == null ? false : o.toString() == compareTo;
    }
  }

  // @TrueOnNull and @FalseOnNull should expect true/false will be returned if ANY of input objects
  // is null.
  // In other words, they should infer that all input nullable objects are non-null in the
  // corresponding branch.
  static class NonStaticSeveralParams {
    private String compareTo = "something";

    @TrueOnNull
    boolean trueOnNull(
        @Nullable Object nullable1, int primitive, Object nonnull, @Nullable Object nullable2) {
      if (nullable1 == null || nullable2 == null) {
        return true;
      }
      return nonnull == compareTo;
    }

    @FalseOnNull
    boolean falseOnNull(
        @Nullable Object nullable1, int primitive, Object nonnull, @Nullable Object nullable2) {
      if (nullable1 == null || nullable2 == null) {
        return false;
      }
      return nonnull == compareTo;
    }

    boolean notAnnotated(
        @Nullable Object nullable1, int primitive, Object nonnull, @Nullable Object nullable2) {
      if (nullable1 == null || nullable2 == null) {
        return false;
      }
      return nonnull == compareTo;
    }
  }

  class TestStaticOneParam {

    void trueOnNullPositiveBranchIsBAD(@Nullable String s) {
      if (StaticOneParam.trueOnNull(s)) {
        s.toString();
      }
    }

    void trueOnNullNegativeBranchIsOK(@Nullable String s) {
      if (!StaticOneParam.trueOnNull(s)) {
        s.toString();
      }
    }

    void falseOnNullPositiveBranchIsOK(@Nullable String s) {
      if (StaticOneParam.falseOnNull(s)) {
        s.toString();
      }
    }

    void falseOnNullNegativeBranchIsBAD(@Nullable String s) {
      if (!StaticOneParam.falseOnNull(s)) {
        s.toString();
      }
    }

    void notAnnotatedPositiveBranchIsBAD(@Nullable String s) {
      if (StaticOneParam.notAnnotated(s)) {
        s.toString();
      }
    }

    void notAnnotatedNegativeBranchIsBAD(@Nullable String s) {
      if (!StaticOneParam.notAnnotated(s)) {
        s.toString();
      }
    }
  }

  class TestNonStaticOneParam {
    private NonStaticOneParam object = new NonStaticOneParam();

    void trueOnNullPositiveBranchIsBAD(@Nullable String s) {
      if (object.trueOnNull(s)) {
        s.toString();
      }
    }

    void trueOnNullNegativeBranchIsOK(@Nullable String s) {
      if (!object.trueOnNull(s)) {
        s.toString();
      }
    }

    void falseOnNullPositiveBranchIsOK(@Nullable String s) {
      if (object.falseOnNull(s)) {
        s.toString();
      }
    }

    void falseOnNullNegativeBranchIsBAD(@Nullable String s) {
      if (!object.falseOnNull(s)) {
        s.toString();
      }
    }

    void notAnnotatedPositiveBranchIsBAD(@Nullable String s) {
      if (object.notAnnotated(s)) {
        s.toString();
      }
    }

    void notAnnotatedNegativeBranchIsBAD(@Nullable String s) {
      if (!object.notAnnotated(s)) {
        s.toString();
      }
    }
  }

  class TestNonStaticSeveralParams {
    private NonStaticSeveralParams object = new NonStaticSeveralParams();

    void trueOnNullPositiveBranchIsBAD(@Nullable String s1, @Nullable String s2) {
      if (object.trueOnNull(s1, 1, "123", s2)) {
        s1.toString();
        s2.toString();
      }
    }

    void trueOnNullNegativeBranchIsOK(@Nullable String s1, @Nullable String s2) {
      if (!object.trueOnNull(s1, 1, "123", s2)) {
        s1.toString();
        s2.toString();
      }
    }

    void falseOnNullPositiveBranchIsOK(@Nullable String s1, @Nullable String s2) {
      if (object.falseOnNull(s1, 1, "123", s2)) {
        s1.toString();
        s2.toString();
      }
    }

    void falseOnNullNegativeBranchIsBAD(@Nullable String s1, @Nullable String s2) {
      if (!object.falseOnNull(s1, 1, "123", s2)) {
        s1.toString();
        s2.toString();
      }
    }

    void notAnnotatedPositiveBranchIsBAD(@Nullable String s1, @Nullable String s2) {
      if (object.notAnnotated(s1, 1, "123", s2)) {
        s1.toString();
        s2.toString();
      }
    }

    void notAnnotatedNegativeBranchIsBAD(@Nullable String s1, @Nullable String s2) {
      if (!object.notAnnotated(s1, 1, "123", s2)) {
        s1.toString();
        s2.toString();
      }
    }
  }

  class TestModels {

    void testModelledTrueOnNull(@Nullable String s) {
      // TextUtils.isEmpty is modelled as TrueOnNull
      if (!TextUtils.isEmpty(s)) {
        s.toString(); // OK: if we are here, we know that `s` is not null
      }

      // Strings.isNullOrEmpty is modelled as TrueOnNull
      if (!Strings.isNullOrEmpty(s)) {
        s.toString(); // OK: if we are here, we know that `s` is not null
      }
    }
  }

  // nullsafe should assume Object.equals() and all overrides return false on `null` argument.
  static class TestEqualsIsFalseOnNull {
    // An example of class that overrides equals().
    static class SomeObject {
      private int mContent;

      SomeObject(int content) {
        mContent = content;
      }

      @Override
      // No nullsafe warnings are expected
      public boolean equals(@Nullable Object src) {
        if (!super.equals(src)) {
          return false;
        }
        // at this point src should be a non-nullable: super.equals() would return false otherwise.
        src.toString(); // should be OK to dereference now
        if (!(src instanceof SomeObject)) {
          return false;
        }
        SomeObject asSomeObject = (SomeObject) src;
        return mContent == asSomeObject.mContent;
      }
    }

    private void testObjectEqualsIsFalseOnNull(Object x, @Nullable Object y) {
      if (!x.equals(y)) {
        return;
      }

      // OK to dereference
      y.toString();
    }

    private void testOverrideEqualsIsFalseOnNull(SomeObject x, @Nullable Object y) {
      if (!x.equals(y)) {
        return;
      }

      // OK to dereference even that SomeObject.equals() is not annotated as @FalseOnNull
      y.toString();
    }
  }

  // this is a common enough pattern to be tested separately
  class EarlyReturn {

    void testEarlyReturn(@Nullable CharSequence s1, @Nullable CharSequence s2) {
      if (StaticOneParam.trueOnNull(s1) || StaticOneParam.notAnnotated(s2)) {
        return;
      }

      s1.toString(); // OK: if `s1` was null, we would no rech this point
      s2.toString(); // BAD: no reason for `s2` to become non-nullable
    }
  }
}