package polyglot.ext.jl.ast;

import java.util.Collections;
import java.util.List;

import polyglot.ast.Expr;
import polyglot.ast.Field;
import polyglot.ast.Node;
import polyglot.ast.Precedence;
import polyglot.ast.Receiver;
import polyglot.ast.Special;
import polyglot.ast.Term;
import polyglot.ast.TypeNode;
import polyglot.types.Context;
import polyglot.types.FieldInstance;
import polyglot.types.Flags;
import polyglot.types.SemanticException;
import polyglot.types.Type;
import polyglot.types.TypeSystem;
import polyglot.types.VarInstance;
import polyglot.util.CodeWriter;
import polyglot.util.InternalCompilerError;
import polyglot.util.Position;
import polyglot.visit.AscriptionVisitor;
import polyglot.visit.CFGBuilder;
import polyglot.visit.NodeVisitor;
import polyglot.visit.PrettyPrinter;
import polyglot.visit.TypeBuilder;
import polyglot.visit.TypeChecker;

/**
 * A <code>Field</code> is an immutable representation of a Java field
 * access.  It consists of field name and may also have either a 
 * <code>Type</code> or an <code>Expr</code> containing the field being 
 * accessed.
 */
public class Field_c extends Expr_c implements Field
{
  protected Receiver target;
  protected String name;
  protected FieldInstance fi;
  protected boolean targetImplicit;

  public Field_c(Position pos, Receiver target, String name) {
    super(pos);
    this.target = target;
    this.name = name;
    this.targetImplicit = false;

    if (target == null) {
      throw new InternalCompilerError("Cannot create a field with a null "
                                      + "null target.  Use AmbExpr or prefix "
                                      + "with the appropriate type node or"
                                      + "this.");
    }
  }

  /** Get the precedence of the field. */
  public Precedence precedence() { 
    return Precedence.LITERAL;
  }

  /** Get the target of the field. */
  public Receiver target() {
    return this.target;
  }

  /** Set the target of the field. */
  public Field target(Receiver target) {
    Field_c n = (Field_c) copy();
    n.target = target;
    return n;
  }

  /** Get the name of the field. */
  public String name() {
    return this.name;
  }

  /** Set the name of the field. */
  public Field name(String name) {
    Field_c n = (Field_c) copy();
    n.name = name;
    return n;
  }

  /** Return the access flags of the variable. */
  public Flags flags() {
    return fi.flags();
  }

  /** Get the field instance of the field. */
  public FieldInstance fieldInstance() {
    return fi;
  }

  /** Set the field instance of the field. */
  public Field fieldInstance(FieldInstance fi) {
    /*
    if (! fi.type().isCanonical()) {
      throw new InternalCompilerError("Type of " + fi + " in " +
                                      fi.container() + " is not canonical.");
    }
    */

    Field_c n = (Field_c) copy();
    n.fi = fi;
    return n;
  }

  public boolean isTargetImplicit() {
      return this.targetImplicit;
  }

  public Field targetImplicit(boolean implicit) {
      Field_c n = (Field_c) copy();
      n.targetImplicit = implicit;
      return n;
  }

  /** Reconstruct the field. */
  protected Field_c reconstruct(Receiver target) {
    if (target != this.target) {
      Field_c n = (Field_c) copy();
      n.target = target;
      return n;
    }

    return this;
  }

  /** Visit the children of the field. */
  public Node visitChildren(NodeVisitor v) {
    Receiver target = (Receiver) visitChild(this.target, v);
    return reconstruct(target);
  }

  public Node buildTypes(TypeBuilder tb) throws SemanticException {
      Field_c n = (Field_c) super.buildTypes(tb);

      TypeSystem ts = tb.typeSystem();

      FieldInstance fi = ts.fieldInstance(position(), ts.Object(), Flags.NONE,
                                          ts.unknownType(position()), name);
      return n.fieldInstance(fi);
  }

  /** Type check the field. */
  public Node typeCheck(TypeChecker tc) throws SemanticException {
    Context c = tc.context();
    TypeSystem ts = tc.typeSystem();

    FieldInstance fi = ts.findField(target.type().toReference(), name, c);
    
    if (fi == null) {
        throw new InternalCompilerError("Cannot access field on node of type " +
            target.getClass().getName() + ".");
    }
    
    Field_c f = (Field_c)fieldInstance(fi).type(fi.type());
    f.checkConsistency(c);
    
    return f;
  }

  public Type childExpectedType(Expr child, AscriptionVisitor av)
  {
      if (child == target) {
          return fi.container();
      }

      return child.type();
  }

  /** Write the field to an output file. */
  public void prettyPrint(CodeWriter w, PrettyPrinter tr) {
    if (!targetImplicit) {
        // explicit target.
        if (target instanceof Expr) {
          printSubExpr((Expr) target, w, tr);
        }
        else if (target instanceof TypeNode) {
          print(target, w, tr);
        }
    
        w.write(".");
    }
    w.write(name);
  }

  public void dump(CodeWriter w) {
    super.dump(w);

    w.allowBreak(4, " ");
    w.begin(0);
    w.write("(name \"" + name + "\")");
    w.end();
  }

  public Term entry() {
      if (target instanceof Expr) {
          return ((Expr) target).entry();
      }
      return this;
  }

  public List acceptCFG(CFGBuilder v, List succs) {
      if (target instanceof Expr) {
          v.visitCFG((Expr) target, this);
      }
      return succs;
  }


  public String toString() {
    return ((target != null && !targetImplicit)? target + "." : "") + name;
  }


  public List throwTypes(TypeSystem ts) {
      if (target instanceof Expr && ! (target instanceof Special)) {
          return Collections.singletonList(ts.NullPointerException());
      }

      return Collections.EMPTY_LIST;
  }

  public boolean isConstant() {
    if (fi != null &&
        (target instanceof TypeNode ||
         (target instanceof Special && targetImplicit))) {
      return fi.isConstant();
    }

    return false;
  }

  public Object constantValue() {
    if (isConstant()) {
      return fi.constantValue();
    }

    return null;
  }
  
  // check that the implicit target setting is correct.
  protected void checkConsistency(Context c) {
      if (targetImplicit) {
          VarInstance vi = c.findVariableSilent(name);
          if (vi instanceof FieldInstance) {
              FieldInstance rfi = (FieldInstance) vi;
              if (c.typeSystem().equals(rfi, fi)) {
                  // all is OK.
                  return;
              }
          }
          throw new InternalCompilerError("Field " + this + " has an " +
               "implicit target, but the name " + name + " resolves to " +
               vi + " instead of " + target, position());
      }      
  }

}

@@ -4061,24 +4061,273 @@
 eSystem();%0A%0A
+    if (!target.type().isReference()) %7B%0A            throw new SemanticException(%22Cannot access field %5C%22%22 + name +%0A                       %22%5C%22 on an expression of non-reference type %22 + %0A                       target.type() + %22.%22, position());%0A    %7D%0A%0A
     FieldIns

Make sure we check that the type is a reference type, to prevent a compiler NullPointerException, as pointed out by Oege de Moor.