package polyglot.ext.jl.ast;

import java.util.Iterator;
import java.util.List;

import polyglot.ast.Block;
import polyglot.ast.CodeDecl;
import polyglot.ast.Initializer;
import polyglot.ast.Node;
import polyglot.ast.Term;
import polyglot.types.ClassType;
import polyglot.types.CodeInstance;
import polyglot.types.Context;
import polyglot.types.Flags;
import polyglot.types.InitializerInstance;
import polyglot.types.SemanticException;
import polyglot.types.Type;
import polyglot.types.TypeSystem;
import polyglot.util.CodeWriter;
import polyglot.util.Position;
import polyglot.util.SubtypeSet;
import polyglot.visit.AmbiguityRemover;
import polyglot.visit.CFGBuilder;
import polyglot.visit.ExceptionChecker;
import polyglot.visit.NodeVisitor;
import polyglot.visit.PrettyPrinter;
import polyglot.visit.TypeBuilder;
import polyglot.visit.TypeChecker;

/**
 * An <code>Initializer</code> is an immutable representation of an
 * initializer block in a Java class (which appears outside of any
 * method).  Such a block is executed before the code for any of the
 * constructors.  Such a block can optionally be static, in which case
 * it is executed when the class is loaded.
 */
public class Initializer_c extends Term_c implements Initializer
{
    protected Flags flags;
    protected Block body;
    protected InitializerInstance ii;

    public Initializer_c(Position pos, Flags flags, Block body) {
	super(pos);
	this.flags = flags;
	this.body = body;
    }

    /** Get the flags of the initializer. */
    public Flags flags() {
	return this.flags;
    }

    /** Set the flags of the initializer. */
    public Initializer flags(Flags flags) {
	Initializer_c n = (Initializer_c) copy();
	n.flags = flags;
	return n;
    }

    /** Get the initializer instance of the initializer. */
    public InitializerInstance initializerInstance() {
        return ii;
    }

    public CodeInstance codeInstance() {
	return initializerInstance();
    }

    /** Set the initializer instance of the initializer. */
    public Initializer initializerInstance(InitializerInstance ii) {
	Initializer_c n = (Initializer_c) copy();
	n.ii = ii;
	return n;
    }

    /** Get the body of the initializer. */
    public Block body() {
	return this.body;
    }

    /** Set the body of the initializer. */
    public CodeDecl body(Block body) {
	Initializer_c n = (Initializer_c) copy();
	n.body = body;
	return n;
    }

    /** Reconstruct the initializer. */
    protected Initializer_c reconstruct(Block body) {
	if (body != this.body) {
	    Initializer_c n = (Initializer_c) copy();
	    n.body = body;
	    return n;
	}

	return this;
    }

    /** Visit the children of the initializer. */
    public Node visitChildren(NodeVisitor v) {
	Block body = (Block) visitChild(this.body, v);
	return reconstruct(body);
    }

    public Context enterScope(Context c) {
	return c.pushCode(ii);
    }

    public NodeVisitor buildTypesEnter(TypeBuilder tb) throws SemanticException {
        return tb.pushCode();
    }

    /**
     * Return the first (sub)term performed when evaluating this
     * term.
     */
    public Term entry() {
        return this.body().entry();
    }

    public List acceptCFG(CFGBuilder v, List succs) {
        v.visitCFG(this.body(), this);
        return succs;
    }

    /** Build type objects for the method. */
    public Node buildTypes(TypeBuilder tb) throws SemanticException {
        TypeSystem ts = tb.typeSystem();
        ClassType ct = tb.currentClass();
        InitializerInstance ii = ts.initializerInstance(position(), ct, flags);
        return initializerInstance(ii);
    }

    public NodeVisitor disambiguateEnter(AmbiguityRemover ar) throws SemanticException {
        // Do not visit body on the clean-super and clean-signatures passes.
        if (ar.kind() == AmbiguityRemover.SUPER ||
            ar.kind() == AmbiguityRemover.SIGNATURES) {
            return ar.bypassChildren(this);
        }
        return ar;
    }

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

	try {
	    ts.checkInitializerFlags(flags());
	}
	catch (SemanticException e) {
	    throw new SemanticException(e.getMessage(), position());
	}

        // check that inner classes do not declare static initializers
        if (flags().isStatic() &&
              initializerInstance().container().toClass().isInnerClass()) {
            // it's a static initializer in an inner class.
            throw new SemanticException("Inner classes cannot declare " + 
                    "static initializers.", this.position());             
        }

	return this;
    }

    /** Check exceptions thrown by the initializer. */
    public Node exceptionCheck(ExceptionChecker ec) throws SemanticException {
      	TypeSystem ts = ec.typeSystem();

	SubtypeSet s = (SubtypeSet) ec.throwsSet();

	for (Iterator i = s.iterator(); i.hasNext(); ) {
	    Type t = (Type) i.next();

	    if (! t.isUncheckedException()) {
                // TODO: This should agree with Java Language Spec 2nd Ed. 8.6
                // An instance initializer of a named class may not throw
                // a checked exception unless that exception or one of its 
                // superclasses is explicitly declared in the throws clause
                // of each contructor or its class, and the class has at least
                // one explicitly declared constructor.
                if (initializerInstance().flags().isStatic()) {
                    throw new SemanticException(
                        "A static initializer block may not throw a " + t + ".",
                        ec.exceptionPosition(t));
                }
                
                if (!initializerInstance().container().toClass().isAnonymous()) {
                        // XXX should only throw this if it is not common to all
                        // declared constructors, and there is at least one
                        // declared constructor.
        		throw new SemanticException(
        		    "An instance initializer block may not throw a " + t + ".",
                            ec.exceptionPosition(t));
                }
	    }
	}

	return this;
    }

    /** Write the initializer to an output file. */
    public void prettyPrint(CodeWriter w, PrettyPrinter tr) {
	w.write(flags.translate());
	printBlock(body, w, tr);
    }

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

	if (ii != null) {
	    w.allowBreak(4, " ");
	    w.begin(0);
	    w.write("(instance " + ii + ")");
	    w.end();
	}
    }

    public String toString() {
	return flags.translate() + "{ ... }";
    }
}

@@ -98,369 +98,9 @@
 ast.
-Block;%0Aimport polyglot.ast.CodeDecl;%0Aimport polyglot.ast.Initializer;%0Aimport polyglot.ast.Node;%0Aimport polyglot.ast.Term;%0Aimport polyglot.types.ClassType;%0Aimport polyglot.types.CodeInstance;%0Aimport polyglot.types.Context;%0Aimport polyglot.types.Flags;%0Aimport polyglot.types.InitializerInstance;%0Aimport polyglot.types.SemanticException;%0Aimport polyglot.types.Type
+*
 ;%0Aim
@@ -119,26 +119,17 @@
 t.types.
-TypeSystem
+*
 ;%0Aimport
@@ -245,240 +245,9 @@
 sit.
-AmbiguityRemover;%0Aimport polyglot.visit.CFGBuilder;%0Aimport polyglot.visit.ExceptionChecker;%0Aimport polyglot.visit.NodeVisitor;%0Aimport polyglot.visit.PrettyPrinter;%0Aimport polyglot.visit.TypeBuilder;%0Aimport polyglot.visit.TypeChecker
+*
 ;%0A%0A/
@@ -3004,32 +3004,202 @@
 nce(ii);%0A    %7D%0A%0A
+    public NodeVisitor addMembersEnter(AddMemberVisitor am) %7B%0A        // do not add members for the children of this node.%0A        return am.bypassChildren(this);%0A    %7D%0A%0A
     public NodeV

Fix for Todd Millstein's bug, whereby the member methods for anonymous classes defined within initializers were being added to the class type, causing problems later.