/*
 * 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 javax.annotation.Nullable;

public class NestedFieldAccess {

  class C {
    @Nullable String s;
  }

  class CC {
    @Nullable C c;
  }

  class CCC {
    @Nullable CC cc;
  }

  /**
   * Tests nullability check patterns for f1.f2.f3, when all components in the chain are nullable.
   * (it should require checking of all components in the chain)
   */
  public class TestNullableChains {
    @Nullable String s;
    C myc;

    TestNullableChains() {
      myc = new C();
    }

    void field_AccessAfterNullCheckIsOK() {
      if (s != null) {
        int n = s.length();
      }
    }

    void field_AccessWithoutNullCheckIsBad() {
      int n = s.length();
    }

    void nestedField_AccessAfterNullCheckIsOK() {
      if (myc.s != null) {
        int n = myc.s.length();
      }
    }

    void nestedField_AccessWithoutNullCheckIsBad() {
      int n = myc.s.length();
    }

    void param_AccessAfterNullCheckIsOK(C c) {
      if (c.s != null) {
        int n = c.s.length();
      }
    }

    void param_AccessWithoutNullCheckIsBad(C c) {
      int n = c.s.length();
    }

    void local_AccessAfterNullCheckIsOK() {
      C c = new C();
      if (c.s != null) {
        int n = c.s.length();
      }
    }

    void local_AccessWithoutNullCheckIsBad() {
      C c = new C();
      int n = c.s.length();
    }

    void deep_AccessWithNullCheckIsOK(CC cc) {
      if (cc.c != null && cc.c.s != null) {
        int n = cc.c.s.length();
      }
    }

    void deep_AccessWithoutNullCheckIsBad(CC cc) {
      if (cc.c != null /* && cc.c.s != null */) {
        int n = cc.c.s.length();
      }
    }

    void veryDeep_AccessWithNullCheckIsOK(CCC ccc) {
      if (ccc.cc != null && ccc.cc.c != null && ccc.cc.c.s != null) {
        int n = ccc.cc.c.s.length();
      }
    }

    void veryDeep_AccessWithoutNullCheckIsBad(CCC ccc) {
      if (ccc.cc != null && ccc.cc.c != null /* && ccc.cc.c.s != null */) {
        int n = ccc.cc.c.s.length();
      }
    }

    void veryDeep_AccessViaOrEarlyReturnIsOK(@Nullable CCC ccc) {
      if (ccc == null || ccc.cc == null || ccc.cc.c == null || ccc.cc.c.s == null) {
      } else {
        int n = ccc.cc.c.s.length();
      }
    }

    void veryDeep_IncompleteAccessViaOrEarlyReturnIsBad(@Nullable CCC ccc) {
      if (ccc == null || ccc.cc == null || ccc.cc.c == null /*|| ccc.cc.c.s == null*/) {
      } else {
        int n = ccc.cc.c.s.length();
      }
    }
  }

  /**
   * Tests nullability patterns for chains a().a().a().a().nullable(). Basically nullsafe needs to
   * realize that objects returned by a().a() and a().a().a() are different so it should not learn
   * anything about the nullability of one based on evidence about the other one.
   */
  class TestFunctionsIdempotent {
    @Nullable String s;
    String dontAssignNull = "";

    @Nullable
    String nullable(int n) {
      return s;
    }

    TestFunctionsIdempotent getSelf() {
      return this;
    }

    void chainOf0VsChainOf0IsOK() {
      if (nullable(3) != null) {
        dontAssignNull = nullable(3);
      }
    }

    void chainOf0VsChainOf0ParamsMismatchIsBad() {
      if (nullable(3) != null) {
        dontAssignNull = nullable(4);
      }
    }

    void otherObjVsItselfIsOK(TestFunctionsIdempotent otherObj) {
      if (otherObj.nullable(3) != null) {
        dontAssignNull = otherObj.nullable(3);
      }
    }

    void otherObjVsItselfIsOKParamsMismatchIsBAD(TestFunctionsIdempotent otherObj) {
      if (otherObj.nullable(3) != null) {
        dontAssignNull = otherObj.nullable(4);
      }
    }

    void selfVsOtherObjectIsBAD(TestFunctionsIdempotent otherObj) {
      if (otherObj.nullable(3) != null) {
        dontAssignNull = nullable(3);
      }
    }

    void chainOf0VsChainOf1IsBad() {
      if (nullable(3) != null) {
        dontAssignNull = getSelf().nullable(3);
      }
    }

    void chainOf1VsChainOf0IsBad() {
      if (getSelf().nullable(3) != null) {
        dontAssignNull = nullable(3);
      }
    }

    void chainOf1VsChainOf1IsOK() {
      if (getSelf().nullable(3) != null) {
        dontAssignNull = getSelf().nullable(3);
      }
    }

    void chainOf1VsChainOf1ParamMismatchIsBad() {
      if (getSelf().nullable(3) != null) {
        dontAssignNull = getSelf().nullable(4);
      }
    }

    void chainOf2VsChainOf2IsOK() {
      if (getSelf().getSelf().nullable(3) != null) {
        dontAssignNull = getSelf().getSelf().nullable(3);
      }
    }

    void chainOf1VsChainOf2IsBad() {
      if (getSelf().nullable(3) != null) {
        dontAssignNull = getSelf().getSelf().nullable(3);
      }
    }

    void chainOf2VsChainOf1IsBad() {
      if (getSelf().getSelf().nullable(3) != null) {
        dontAssignNull = getSelf().nullable(3);
      }
    }
  }
}