import org.checkerframework.checker.nullness.qual.*;

public class FlowNullness {

    public void testIf() {

        String str = "foo";
        @NonNull String a;
        // :: warning: (known.nonnull)
        if (str != null) {
            a = str;
        }

        str = null;
        // :: error: (assignment.type.incompatible)
        @NonNull String b = str;
    }

    public void testIfNoBlock() {

        String str = "foo";
        @NonNull String a;
        // :: warning: (known.nonnull)
        if (str != null) {
            a = str;
        }

        str = null;
        // :: error: (assignment.type.incompatible)
        @NonNull String b = str;
    }

    public void testElse() {

        String str = "foo";
        @NonNull String a;
        // :: warning: (known.nonnull)
        if (str == null) {
            testAssert();
        } else {
            a = str;
        }

        str = null;
        // :: error: (assignment.type.incompatible)
        @NonNull String b = str;
    }

    public void testElseNoBlock() {

        String str = "foo";
        @NonNull String a;
        // :: warning: (known.nonnull)
        if (str == null) {
            testAssert();
        } else {
            a = str;
        }

        str = null;
        // :: error: (assignment.type.incompatible)
        @NonNull String b = str;
    }

    public void testReturnIf() {

        String str = "foo";
        // :: warning: (known.nonnull)
        if (str == null) {
            testAssert();
            return;
        }

        @NonNull String a = str;

        str = null;
        // :: error: (assignment.type.incompatible)
        @NonNull String b = str;
    }

    public void testReturnElse() {

        String str = "foo";
        // :: warning: (known.nonnull)
        if (str != null) {
            testAssert();
        } else {
            return;
        }

        @NonNull String a = str;

        str = null;
        // :: error: (assignment.type.incompatible)
        @NonNull String b = str;
    }

    public void testThrowIf() {

        String str = "foo";
        // :: warning: (known.nonnull)
        if (str == null) {
            testAssert();
            throw new RuntimeException("foo");
        }

        @NonNull String a = str;

        str = null;
        // :: error: (assignment.type.incompatible)
        @NonNull String b = str;
    }

    public void testThrowElse() {

        String str = "foo";
        // :: warning: (known.nonnull)
        if (str != null) {
            testAssert();
        } else {
            throw new RuntimeException("foo");
        }

        @NonNull String a = str;

        str = null;
        // :: error: (assignment.type.incompatible)
        @NonNull String b = str;
    }

    public void testAssert() {

        String str = "foo";
        // :: warning: (known.nonnull)
        assert str != null;

        @NonNull String a = str;

        str = null;
        // :: error: (assignment.type.incompatible)
        @NonNull String b = str;
    }

    public void testWhile() {

        String str = "foo";
        // :: warning: (known.nonnull)
        while (str != null) {
            @NonNull String a = str;
            break;
        }

        str = null;
        // :: error: (assignment.type.incompatible)
        @NonNull String b = str;
    }

    public void testIfInstanceOf() {

        String str = "foo";
        @NonNull String a;
        if (str instanceof String) {
            a = str;
        }

        str = null;
        // :: error: (assignment.type.incompatible)
        @NonNull String b = str;
    }

    public void testNew() {

        String str = "foo";
        @NonNull String a = str;

        str = null;
        // :: error: (assignment.type.incompatible)
        @NonNull String b = str;

        String s2 = new String();
        s2.toString();
    }

    public void testExit() {

        String str = "foo";
        // :: warning: (known.nonnull)
        if (str == null) {
            System.exit(0);
        }

        @NonNull String a = str;
    }

    void testMore() {
        String str = null + " foo";
        @NonNull String a = str;
    }

    void orderOfEvaluation() {
        class MyClass {
            @org.checkerframework.dataflow.qual.Pure
            public boolean equals(@Nullable Object o) {
                return true;
            }

            void test(@Nullable Object a, @Nullable Object b) {}
        }
        MyClass m = new MyClass();
        m.equals(m = null);

        MyClass n = new MyClass();
        // :: error: (dereference.of.nullable)
        n.test(n = null, n.toString()); // error

        MyClass o = null;
        // :: error: (dereference.of.nullable)
        o.equals(o == new MyClass()); // error
    }

    void instanceOf(@Nullable Object o) {
        if (o instanceof String) {
            // cannot be null here
            o.toString();
            return;
        }
        // :: error: (dereference.of.nullable)
        o.toString(); // error
    }

    public static void checkConditional1(@Nullable Object a) {
        if (a == null) {
        } else {
            a.getClass(); // not an error
        }
    }

    public static void checkConditional2(@Nullable Object a) {
        if (a == null) {
        } else if (a instanceof String) {
        } else {
            a.getClass(); // not an error
        }
    }

    public static String spf(String format, @NonNull Object[] args) {
        int current_arg = 0;
        Object arg = args[current_arg];
        if (false) {
            return arg.toString(); // not an error
        }
        if (arg instanceof long[]) {
            return "foo";
        } else {
            return arg.toString(); // still not an error
        }
    }

    void empty_makes_no_change() {
        String o1 = "not null!";
        if (false) {
            // empty branch
        } else {
            o1 = "still not null!";
        }
        System.out.println(o1.toString());
    }

    @org.checkerframework.dataflow.qual.Pure
    public boolean equals(@Nullable Object o) {
        if (!(o instanceof Integer)) {
            return false;
        }
        @NonNull Object nno = o;
        @NonNull Integer nni = (Integer) o;
        return true;
    }

    void while_set_and_test(@Nullable String s) {
        String line;
        // imagine "s" is "reader.readLine()" (but avoid use of libraries
        // in unit tests)
        while ((line = s) != null) {
            line.trim();
        }
    }

    void equality_test(@Nullable String s) {
        @NonNull String n = "m";
        if (s == n) {
            s.toString();
        }
    }

    @Nullable Object returnNullable() {
        return null;
    }

    void testNullableCall() {
        if (returnNullable() != null) {
            // :: error: (dereference.of.nullable)
            returnNullable().toString(); // error
        }
    }

    void nonNullArg(@NonNull Object arg) {
        // empty body
    }

    void testNonNullArg(@Nullable Object arg) {
        // :: error: (argument.type.incompatible)
        nonNullArg(arg); // error
        nonNullArg(arg); // no error
    }

    void test() {
        String[] s = null;
        // :: error: (dereference.of.nullable)
        for (int i = 0; i < s.length; ++i) { // error
            String m = s[i]; // fine.. s cannot be null
        }
    }

    private double @MonotonicNonNull []
            intersect; // = null; TODO: do we want to allow assignments of null to MonotonicNonNull?

    public void add_modified(double[] a, int count) {
        // System.out.println ("common: " + ArraysMDE.toString (a));
        // :: warning: (known.nonnull)
        if (a == null) {
            return;
        } else if (intersect == null) {
            intersect = a;
            return;
        }

        double[] tmp = new double[intersect.length];
    }
}
