aspect NonNullInference {
  // refine pretty printer to include infered annotations
  refine PrettyPrint public void ParameterDeclaration.toString(StringBuffer s) {
    if(inferedNonNull())
      s.append("[NN]");
    if(inferedRaw())
      s.append("[Raw]");
    PrettyPrint.ParameterDeclaration.toString(s);
  }
  refine PrettyPrint public void VariableDeclaration.toString(StringBuffer s) {
    if(inferedNonNull())
      s.append("[NN]");
    if(inferedRaw())
      s.append("[Raw]");
    PrettyPrint.VariableDeclaration.toString(s);
  }
  refine PrettyPrint public void FieldDeclaration.toString(StringBuffer s) {
    if(inferedNonNull())
      s.append("[NN]");
    if(inferedRaw())
      s.append("[Raw]");
    PrettyPrint.FieldDeclaration.toString(s);
  }
  refine PrettyPrint public void MethodDecl.toString(StringBuffer s) {
    if(inferedNonNull())
      s.append("[NN]");
    if(inferedRaw())
      s.append("[Raw]");
    if(inferedRawThis())
      s.append("[RawThis]");
    PrettyPrint.MethodDecl.toString(s);
  }

  // collect assignment sites through the generic collecting phase in the front-end
  public void VarAccess.updateRemoteAttributeCollectionsFrontend() {
    if(isDest()) {
      ASTNode node = this;
      while(node != null && !(node instanceof AssignExpr))
        node = node.getParent();
      if(node != null && node instanceof AssignExpr) {
        AssignExpr assignExpr = (AssignExpr)node;
        Expr rhs = assignExpr.getSource();
        Variable v = decl();
        if(v instanceof FieldDeclaration)
          ((FieldDeclaration)v).addAssignment(rhs);
        else if(v instanceof ParameterDeclaration)
          ((ParameterDeclaration)v).addAssignment(rhs);
        else if(v instanceof VariableDeclaration)
          ((VariableDeclaration)v).addAssignment(rhs);
      }
    }
    super.updateRemoteAttributeCollectionsFrontend();
  }
  public void VariableDeclaration.addAssignment(Expr rhs) {
    if(assignments == null) assignments = new ArrayList();
    assignments.add(rhs);
  }
  private Collection VariableDeclaration.assignments = null;
  public Collection VariableDeclaration.assignments() {
    return assignments != null ? assignments : Collections.EMPTY_LIST;
  }
  public void FieldDeclaration.addAssignment(Expr rhs) {
    if(assignments == null) assignments = new ArrayList();
    assignments.add(rhs);
  }
  private Collection FieldDeclaration.assignments = null;
  public Collection FieldDeclaration.assignments() {
    return assignments != null ? assignments : Collections.EMPTY_LIST;
  }
  public void ParameterDeclaration.addAssignment(Expr rhs) {
    if(assignments == null) assignments = new ArrayList();
    assignments.add(rhs);
  }
  private Collection ParameterDeclaration.assignments = null;
  public Collection ParameterDeclaration.assignments() {
    return assignments != null ? assignments : Collections.EMPTY_LIST;
  }

  // collect returnValues for methods from return statements through the generic collecting phase in the front-end
  public void ReturnStmt.updateRemoteAttributeCollectionsFrontend() {
    if(hasResult()) {
      BodyDecl bodyDecl = hostBodyDecl();
      if(bodyDecl instanceof MethodDecl) {
        MethodDecl m = (MethodDecl)bodyDecl;
        if(m.type().isReferenceType())
          m.addReturnValue(getResult());
      }
    }
    super.updateRemoteAttributeCollectionsFrontend();
  }
  public void MethodDecl.addReturnValue(Expr rhs) {
    if(returnValues == null) returnValues = new ArrayList();
    returnValues.add(rhs);
  }
  private Collection MethodDecl.returnValues = null;
  public Collection MethodDecl.returnValues() {
    return returnValues != null ? returnValues : Collections.EMPTY_LIST;
  }
  
  // collect argumentValues for parameters from method accesses through the generic collecting phase in the front-end
  public void MethodAccess.updateRemoteAttributeCollectionsFrontend() {
    MethodDecl m = decl();
    m.addInvocation(this);
    for(int i = 0; i < m.getNumParameter(); i++)
      m.getParameter(i).addArgumentValue(getArg(i));
    super.updateRemoteAttributeCollectionsFrontend();
  }
  public void MethodDecl.addInvocation(MethodAccess m) {
    if(invocations == null) invocations = new ArrayList();
    invocations.add(m);
  }
  private Collection MethodDecl.invocations = null;
  public Collection MethodDecl.invocations() {
    return invocations != null ? invocations : Collections.EMPTY_LIST;
  }
  public void ParameterDeclaration.addArgumentValue(Expr rhs) {
    if(argumentValues == null) argumentValues = new ArrayList();
    argumentValues.add(rhs);
  }
  private Collection ParameterDeclaration.argumentValues = null;
  public Collection ParameterDeclaration.argumentValues() {
    return argumentValues != null ? argumentValues : Collections.EMPTY_LIST;
  }
  
  // collect argumentValues for paramters from class instance expressions, this(), and super() invocations
  public void ClassInstanceExpr.updateRemoteAttributeCollectionsFrontend() {
    ConstructorDecl c = decl();
    for(int i = 0; i < c.getNumParameter(); i++)
      c.getParameter(i).addArgumentValue(getArg(i));
    super.updateRemoteAttributeCollectionsFrontend();
  }
  // both constructor and superconstructor
  public void ConstructorAccess.updateRemoteAttributeCollectionsFrontend() {
    ConstructorDecl c = decl();
    for(int i = 0; i < c.getNumParameter(); i++)
      c.getParameter(i).addArgumentValue(getArg(i));
    super.updateRemoteAttributeCollectionsFrontend();
  }

  // collect back links for overrides, that is overriddenBy
  public void MethodDecl.updateRemoteAttributeCollectionsFrontend() {
    for(Iterator iter = overrides().iterator(); iter.hasNext(); ) {
      MethodDecl m = (MethodDecl)iter.next();
      m.addOverriddenBy(this);
    }
    super.updateRemoteAttributeCollectionsFrontend();
  }
  public void MethodDecl.addOverriddenBy(MethodDecl m) {
    if(overriddenBy == null) overriddenBy = new ArrayList();
    overriddenBy.add(m);
  }
  private Collection MethodDecl.overriddenBy = null;
  public Collection MethodDecl.overriddenBy() {
    return overriddenBy != null ? overriddenBy : Collections.EMPTY_LIST;
  }  


  syn lazy boolean Modifiers.isNotNull() = numModifier("@NonNull") != 0;
  syn lazy boolean Modifiers.isRawObjectType() = numModifier("@Raw") != 0;
  syn lazy boolean Modifiers.isRawThisObjectType() = numModifier("@RawThis") != 0;

  // infere types
  syn lazy boolean FieldDeclaration.inferedNonNull() circular [true] {
    if(!type().isReferenceType()) return false;
    if(isStatic()) return false;
    if(getModifiers().isNotNull()) return true;
    if(hasInit() && !getInit().inferedNonNull())
      return false;
    for(Iterator iter = assignments().iterator(); iter.hasNext(); ) {
      Expr rhs = (Expr)iter.next();
      if(!rhs.inferedNonNull())
        return false;
    }

    for(int j = 0; j < hostType().getNumBodyDecl(); j++) {
      if(hostType().getBodyDecl(j) instanceof ConstructorDecl) {
        ConstructorDecl c = (ConstructorDecl)hostType().getBodyDecl(j);
          if(!c.isDAafter(this))
            return false;
      }
    }
    return true;
  }
  syn lazy boolean VariableDeclaration.inferedNonNull() circular [true] {
    if(!type().isReferenceType()) return false;
    if(getModifiers().isNotNull()) return true;
    if(hasInit() && !getInit().inferedNonNull())
      return false;
    for(Iterator iter = assignments().iterator(); iter.hasNext(); ) {
      Expr rhs = (Expr)iter.next();
      if(!rhs.inferedNonNull())
        return false;
    }
    return true;
  }

  syn lazy boolean ParameterDeclaration.inferedNonNullLocally() circular [true] {
    for(Iterator iter = assignments().iterator(); iter.hasNext(); ) {
      Expr rhs = (Expr)iter.next();
      if(!rhs.inferedNonNull())
        return false;
    }
    for(Iterator iter = argumentValues().iterator(); iter.hasNext(); ) {
      Expr rhs = (Expr)iter.next();
      if(!rhs.inferedNonNull())
        return false;
    }
    return true;
  }
    
  syn lazy boolean ParameterDeclaration.inferedNonNull() circular [true] {
    if(!type().isReferenceType()) return false;
    if(getModifiers().isNotNull()) return true;
    if(getParent().getParent() instanceof MethodDecl) {
      int i = getParent().getIndexOfChild(this);
      MethodDecl hostMethod = (MethodDecl)getParent().getParent();
      MethodDecl firstMethod = hostMethod.firstMethod();
      if(hostMethod != firstMethod)
        return firstMethod.getParameter(i).inferedNonNull();

      for(Iterator iter = firstMethod.methodSet().iterator(); iter.hasNext(); ) {
        MethodDecl m = (MethodDecl)iter.next();
        if(!m.getParameter(i).inferedNonNullLocally())
          return false;
      }
      return true;
    }
    return inferedNonNullLocally();
  }
  
  syn lazy boolean MethodDecl.inferedNonNullLocally() circular [true] {
    for(Iterator iter = returnValues().iterator(); iter.hasNext(); ) {
      Expr rhs = (Expr)iter.next();
      if(!rhs.inferedNonNull())
        return false;
    }
    return true;
  }
  
  syn lazy boolean MethodDecl.inferedNonNull() circular [true] {
    if(!type().isReferenceType()) return false;
    if(getModifiers().isNotNull()) return true;
    MethodDecl firstMethod = firstMethod();
    if(this != firstMethod)
      return firstMethod.inferedNonNull();
    for(Iterator iter = firstMethod.methodSet().iterator(); iter.hasNext(); ) {
      MethodDecl m = (MethodDecl)iter.next();
      if(!m.inferedNonNullLocally())
        return false;
    }
    return true;
  }
  
  eq VarAccess.inferedNonNull() {
    Variable v = decl();
    if(!v.type().isReferenceType())
      return false;
    if(v instanceof FieldDeclaration) {
      FieldDeclaration f = (FieldDeclaration)v;
      if(isQualified())
        return (f.inferedNonNull() && !qualifier().inferedRaw()) || inferedGuardedByNullCheck(f);
      else
        return (f.inferedNonNull() && !inferedRawThisType()) || inferedGuardedByNullCheck(f);
    }
    else if(v instanceof ParameterDeclaration)
      return ((ParameterDeclaration)v).inferedNonNull() || inferedGuardedByNullCheck(v);
    else if(v instanceof VariableDeclaration)
      return ((VariableDeclaration)v).inferedNonNull() || inferedGuardedByNullCheck(v);
    else
      return false;
  }
  eq MethodAccess.inferedNonNull() = decl().inferedNonNull();
  
  eq ClassInstanceExpr.inferedNonNull() = true;
  eq StringLiteral.inferedNonNull() = true;

  syn lazy boolean Expr.inferedNonNull() circular [false] = false;
  
  eq AbstractDot.inferedNonNull() = lastAccess().inferedNonNull();
  
  eq ArrayInit.inferedNonNull() = false;

  eq AssignExpr.inferedNonNull() = getDest().inferedNonNull();
  
  eq ParExpr.inferedNonNull() = getExpr().inferedNonNull();

  eq ArrayCreationExpr.inferedNonNull() = false;

  eq Unary.inferedNonNull() = getOperand().inferedNonNull();
  eq CastExpr.inferedNonNull() = getTypeAccess().inferedNonNull();

  eq Binary.inferedNonNull() = false;
  
  eq RelationalExpr.inferedNonNull() = getLeftOperand().inferedNonNull() && getRightOperand().inferedNonNull();



  // infer rawness
  syn lazy boolean FieldDeclaration.inferedRaw() circular [false] {
    if(!type().isReferenceType()) return false;
    if(isStatic()) return true;
    if(getModifiers().isRawObjectType()) return true;
    if(hasInit() && getInit().inferedRaw())
      return true;
    for(Iterator iter = assignments().iterator(); iter.hasNext(); ) {
      Expr rhs = (Expr)iter.next();
      if(rhs.inferedRaw())
        return true;
    }
    return false;
  }
  syn lazy boolean VariableDeclaration.inferedRaw() circular [false] {
    if(!type().isReferenceType()) return false;
    if(getModifiers().isRawObjectType()) return true;
    if(hasInit() && getInit().inferedRaw())
      return true;
    for(Iterator iter = assignments().iterator(); iter.hasNext(); ) {
      Expr rhs = (Expr)iter.next();
      if(rhs.inferedRaw())
        return true;
    }
    return false;
  }
  
  syn lazy boolean ParameterDeclaration.inferedRawLocally() circular [false] {
    for(Iterator iter = assignments().iterator(); iter.hasNext(); ) {
      Expr rhs = (Expr)iter.next();
      if(rhs.inferedRaw())
        return true;
    }
    for(Iterator iter = argumentValues().iterator(); iter.hasNext(); ) {
      Expr rhs = (Expr)iter.next();
      if(rhs.inferedRaw())
        return true;
    }
    return false;
  }
  
  syn lazy boolean ParameterDeclaration.inferedRaw() circular [false] {
    if(!type().isReferenceType()) return false;
    if(getModifiers().isRawObjectType()) return true;
    if(getParent().getParent() instanceof MethodDecl) {
      int i = getParent().getIndexOfChild(this);
      MethodDecl hostMethod = (MethodDecl)getParent().getParent();
      MethodDecl firstMethod = hostMethod.firstMethod();
      if(hostMethod != firstMethod)
        return firstMethod.getParameter(i).inferedRaw();

      for(Iterator iter = firstMethod.methodSet().iterator(); iter.hasNext(); ) {
        MethodDecl m = (MethodDecl)iter.next();
        if(m.getParameter(i).inferedRawLocally())
          return true;
      }
      return false;
    }
    return inferedRawLocally();
  }
  
  syn lazy boolean MethodDecl.inferedRawLocally() circular [false] {
    for(Iterator iter = returnValues().iterator(); iter.hasNext(); ) {
      Expr rhs = (Expr)iter.next();
      if(rhs.inferedRaw())
        return true;
    }
    return false;
  }
  
  syn lazy boolean MethodDecl.inferedRaw() circular [false] {
    if(!type().isReferenceType()) return false;
    if(getModifiers().isRawObjectType()) return true;
    MethodDecl firstMethod = firstMethod();
    if(this != firstMethod)
      return firstMethod.inferedRaw();
    for(Iterator iter = firstMethod.methodSet().iterator(); iter.hasNext(); ) {
      MethodDecl m = (MethodDecl)iter.next();
      if(m.inferedRawLocally())
        return true;
    }
    return false;
  }

  eq Program.getCompilationUnit().inferedRawThisType() {
    throw new Error("Unsupported inferRawThisType");
  }
  eq TypeDecl.getBodyDecl().inferedRawThisType() = false;

  eq ConstructorDecl.getConstructorInvocation().inferedRawThisType() = true;
  eq ConstructorDecl.getBlock().inferedRawThisType() = true;

  eq InstanceInitializer.getBlock().inferedRawThisType() = true;
    
  eq MethodDecl.getBlock().inferedRawThisType() = inferedRawThis();
  
  inh lazy boolean ThisAccess.inferedRawThisType() circular [false];
  inh lazy boolean VarAccess.inferedRawThisType() circular [false];
  inh lazy boolean MethodAccess.inferedRawThisType() circular [false];

  syn lazy boolean MethodDecl.inferedRawThisLocally() circular [false] {
    for(Iterator iter = invocations().iterator(); iter.hasNext(); ) {
      MethodAccess m = (MethodAccess)iter.next();
      if(m.isQualified() && m.qualifier().inferedRaw())
        return true;
      if(!m.isQualified() && m.inferedRawThisType())
        return true;
    }
    return false;
  }
  
  syn lazy boolean MethodDecl.inferedRawThis() circular [false] {
    if(isStatic()) return false;
    if(getModifiers().isRawThisObjectType()) return true;
    MethodDecl firstMethod = firstMethod();
    if(this != firstMethod)
      return firstMethod.inferedRawThis();
    for(Iterator iter = firstMethod.methodSet().iterator(); iter.hasNext(); ) {
      MethodDecl m = (MethodDecl)iter.next();
      if(m.inferedRawThisLocally())
        return true;
    }
    return false;
  }
  
  eq VarAccess.inferedRaw() {
    Variable v = decl();
    if(!v.type().isReferenceType())
      return false;
    if(v instanceof FieldDeclaration)
      return ((FieldDeclaration)v).inferedRaw();
    else if(v instanceof ParameterDeclaration)
      return ((ParameterDeclaration)v).inferedRaw();
    else if(v instanceof VariableDeclaration)
      return ((VariableDeclaration)v).inferedRaw();
    else
      return false;
  }

  eq MethodAccess.inferedRaw() = decl().inferedRaw();
  
  eq ClassInstanceExpr.inferedRaw() = false;
  eq StringLiteral.inferedRaw() = false;

  eq ThisAccess.inferedRaw() = inferedRawThisType();

  syn lazy boolean Expr.inferedRaw() circular [false] = false;
  
  eq AbstractDot.inferedRaw() = lastAccess().inferedRaw();
  
  eq ArrayInit.inferedRaw() = false; //type().isNonNull();

  eq AssignExpr.inferedRaw() = getDest().inferedRaw();
  
  eq ParExpr.inferedRaw() = getExpr().inferedRaw();

  eq ArrayCreationExpr.inferedRaw() = false;

  eq Unary.inferedRaw() = getOperand().inferedRaw();
  eq CastExpr.inferedRaw() = getTypeAccess().inferedRaw();

  eq Binary.inferedRaw() = false;
  
  eq RelationalExpr.inferedRaw() = getLeftOperand().inferedRaw() || getRightOperand().inferedRaw();


  inh lazy boolean VarAccess.inferedGuardedByNullCheck(Variable v) circular [false];
  inh lazy boolean IfStmt.inferedGuardedByNullCheck(Variable v) circular [false];
  eq Program.getCompilationUnit(int index).inferedGuardedByNullCheck(Variable v) = false;
  eq TypeDecl.getBodyDecl().inferedGuardedByNullCheck(Variable v) = false;

  // IfStmt with explicit check against null is a guard
  eq IfStmt.getThen().inferedGuardedByNullCheck(Variable v) {
    if(getCondition() instanceof NEExpr) {
      NEExpr expr = (NEExpr)getCondition();
      if(expr.getLeftOperand().readOf(v) && expr.getRightOperand().type().isNull() ||
         expr.getRightOperand().readOf(v) && expr.getLeftOperand().type().isNull())
        return !updatedWithPossiblyNull(v);
    }
    return inferedGuardedByNullCheck(v);
  }
  eq IfStmt.getElse().inferedGuardedByNullCheck(Variable v) {
    if(getCondition() instanceof EQExpr) {
      EQExpr expr = (EQExpr)getCondition();
      if(expr.getLeftOperand().readOf(v) && expr.getRightOperand().type().isNull() ||
         expr.getRightOperand().readOf(v) && expr.getLeftOperand().type().isNull())
        return !updatedWithPossiblyNull(v);
      
    }
    return inferedGuardedByNullCheck(v);
  }

  syn boolean Expr.readOf(Variable v) = false;
  eq VarAccess.readOf(Variable v) = decl() == v && !(isQualified() && !qualifier().isThisAccess());
  eq AbstractDot.readOf(Variable v) = getRight().readOf(v); 

  public boolean ASTNode.updatedWithPossiblyNull(Variable v) {
    for(int i = 0; i < getNumChild(); i++)
      if(getChild(i).updatedWithPossiblyNull(v))
        return true;
    return false;
  }
  public boolean VarAccess.updatedWithPossiblyNull(Variable v) {
    if(isDest()) {
      ASTNode node = this;
      while(node != null && !(node instanceof AssignExpr))
        node = node.getParent();
      if(node != null && node instanceof AssignExpr) {
        AssignExpr assignExpr = (AssignExpr)node;
        Expr rhs = assignExpr.getSource();
        if(decl() == v && !rhs.inferedNonNull())
          return true;
      }
    }
    return false;
  }


  // group all methods that overrides one another taking both classes and interface into account

  // the first method in the set
  syn MethodDecl MethodDecl.firstMethod() = overrides().isEmpty() && overriddenBy().isEmpty() ? this : (MethodDecl)methodSet().iterator().next();

  // the set of methods that overrides each other
  public HashSet MethodDecl.methodSet = null;
  public HashSet MethodDecl.methodSet() {
    if(methodSet == null) {
      methodSet = new HashSet();
      buildSet(methodSet);
    }
    return methodSet;
  }

  // transitatively visit overriding methods and build a set that each method references
  // only visit methods that have not been processed yet
  // use the set reference in each method to indicate that the method is visited
  public void MethodDecl.buildSet(HashSet set) {
    set.add(this);
    methodSet = set;
    for(Iterator iter = overrides().iterator(); iter.hasNext(); ) {
      MethodDecl m = (MethodDecl)iter.next();
      if(m.methodSet == null)
        m.buildSet(set);
    }
    for(Iterator iter = overriddenBy().iterator(); iter.hasNext(); ) {
      MethodDecl m = (MethodDecl)iter.next();
      if(m.methodSet == null)
        m.buildSet(set);
    }
  }

  public void ASTNode.updateRemoteAttributeCollectionsFrontend() {
    for(int i = 0; i < getNumChild(); i++)
      getChild(i).updateRemoteAttributeCollectionsFrontend();
  }
  public void CompilationUnit.updateRemoteAttributeCollectionsFrontend() {
    if(fromSource())
      super.updateRemoteAttributeCollectionsFrontend();
  }

  syn lazy HashSet MethodDecl.overrides() {
    HashSet set = new HashSet();
    for(Iterator iter = hostType().ancestorMethods(signature()).iterator(); iter.hasNext(); ) {
      MethodDecl m = (MethodDecl)iter.next();
      if(overrides(m)) {
        set.add(m);
        set.addAll(m.overrides());
      }
    }
    return set;
  }

}
