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

// This is the example from manual section:
// "Generics (parametric polymorphism or type polymorphism)"
// whose source code is ../../../docs/manual/advanced-features.tex
public class GenericsExample {

  class MyList1<@Nullable T> {
    T t;
    @Nullable T nble;
    @NonNull T nn;

    MyList1(T t, @Nullable T nble, @NonNull T nn) {
      this.t = t;
      this.nble = nble;
      this.nn = nn;
    }

    void add(T arg) {}

    T get(int i) {
      return t;
    }

    void m() {
      t = null;
      t = nble;
      nble = null;
      // :: error: (assignment)
      nn = null;
      t = this.get(0);
      nble = this.get(0);
      // :: error: (assignment)
      nn = this.get(0);
      this.add(t);
      this.add(nble);
      this.add(nn);
    }
  }

  class MyList1a<@Nullable T extends @Nullable Object> {
    T t;
    @Nullable T nble;
    @NonNull T nn;

    MyList1a(T t, @Nullable T nble, @NonNull T nn) {
      this.t = t;
      this.nble = nble;
      this.nn = nn;
    }

    void add(T arg) {}

    T get(int i) {
      return t;
    }

    void m() {
      t = null;
      t = nble;
      nble = null;
      // :: error: (assignment)
      nn = null;
      t = this.get(0);
      nble = this.get(0);
      // :: error: (assignment)
      nn = this.get(0);
      this.add(t);
      this.add(nble);
      this.add(nn);
    }
  }

  class MyList2<@NonNull T extends @NonNull Object> {
    T t;
    @Nullable T nble;
    @NonNull T nn;

    MyList2(T t, @Nullable T nble, @NonNull T nn) {
      this.t = t;
      this.nble = nble;
      this.nn = nn;
    }

    void add(T arg) {}

    T get(int i) {
      return t;
    }

    void m() {
      // :: error: (assignment)
      t = null;
      // :: error: (assignment)
      t = nble;
      nble = null;
      // :: error: (assignment)
      nn = null;
      t = this.get(0);
      nble = this.get(0);
      nn = this.get(0);
      this.add(t);
      // :: error: (argument)
      this.add(nble);
      this.add(nn);
    }
  }

  class MyList2a<T extends @NonNull Object> { // same as MyList2
    T t;
    @Nullable T nble;
    @NonNull T nn;

    MyList2a(T t, @Nullable T nble, @NonNull T nn) {
      this.t = t;
      this.nble = nble;
      this.nn = nn;
    }

    void add(T arg) {}

    T get(int i) {
      return t;
    }

    void m() {
      // :: error: (assignment)
      t = null;
      // :: error: (assignment)
      t = nble;
      nble = null;
      // :: error: (assignment)
      nn = null;
      t = this.get(0);
      nble = this.get(0);
      nn = this.get(0);
      this.add(t);
      // :: error: (argument)
      this.add(nble);
      this.add(nn);
    }
  }

  class MyList3<T extends @Nullable Object> {
    T t;
    @Nullable T nble;
    @NonNull T nn;

    MyList3(T t, @Nullable T nble, @NonNull T nn) {
      this.t = t;
      this.nble = nble;
      this.nn = nn;
    }

    void add(T arg) {}

    T get(int i) {
      return t;
    }

    void m() {
      // :: error: (assignment)
      t = null;
      // :: error: (assignment)
      t = nble;
      nble = null;
      // :: error: (assignment)
      nn = null;
      t = this.get(0);
      nble = this.get(0);
      // :: error: (assignment)
      nn = this.get(0);
      this.add(t);
      // :: error: (argument)
      this.add(nble);
      this.add(nn);
    }
  }
}
