package accrue.analysis.interprocvarcontext;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import polyglot.ast.ArrayAccess;
import polyglot.ast.ArrayAccessAssign;
import polyglot.ast.ArrayInit;
import polyglot.ast.Assert;
import polyglot.ast.Assign;
import polyglot.ast.Binary;
import polyglot.ast.Block;
import polyglot.ast.Branch;
import polyglot.ast.Call;
import polyglot.ast.Cast;
import polyglot.ast.Catch;
import polyglot.ast.CodeDecl;
import polyglot.ast.CodeNode;
import polyglot.ast.CompoundStmt;
import polyglot.ast.Conditional;
import polyglot.ast.ConstructorCall;
import polyglot.ast.ConstructorDecl;
import polyglot.ast.Do;
import polyglot.ast.Empty;
import polyglot.ast.Eval;
import polyglot.ast.Expr;
import polyglot.ast.Field;
import polyglot.ast.FieldAssign;
import polyglot.ast.FieldDecl;
import polyglot.ast.Formal;
import polyglot.ast.If;
import polyglot.ast.Initializer;
import polyglot.ast.Instanceof;
import polyglot.ast.Labeled;
import polyglot.ast.Lit;
import polyglot.ast.Local;
import polyglot.ast.LocalAssign;
import polyglot.ast.LocalDecl;
import polyglot.ast.MethodDecl;
import polyglot.ast.New;
import polyglot.ast.NewArray;
import polyglot.ast.Node;
import polyglot.ast.NodeFactory;
import polyglot.ast.Return;
import polyglot.ast.Special;
import polyglot.ast.Stmt;
import polyglot.ast.Synchronized;
import polyglot.ast.Term;
import polyglot.ast.Throw;
import polyglot.ast.Try;
import polyglot.ast.TypeNode;
import polyglot.ast.Unary;
import polyglot.ast.While;
import polyglot.frontend.Job;
import polyglot.types.ClassType;
import polyglot.types.SemanticException;
import polyglot.types.TypeSystem;
import polyglot.util.InternalCompilerError;
import polyglot.util.Position;
import polyglot.visit.FlowGraph;
import polyglot.visit.FlowGraph.Edge;
import polyglot.visit.FlowGraph.EdgeKey;
import polyglot.visit.FlowGraph.ExceptionEdgeKey;
import polyglot.visit.FlowGraph.Peer;
import accrue.analysis.ast.Suppress;
import accrue.analysis.ext.AccrueExt;
import accrue.analysis.ext.AccrueExt_c;
import accrue.analysis.ext.ExtBlock;
import accrue.analysis.ext.ExtTerm;
import accrue.analysis.interprocanalysis.AnalysisUtil;
import accrue.analysis.interprocanalysis.DataFlowDispatch;
import accrue.analysis.interprocanalysis.ExitMap;
import accrue.analysis.interprocanalysis.ExitMap.ExceptionKey;
import accrue.analysis.interprocanalysis.ExitMap.Key;
import accrue.analysis.interprocanalysis.PreciseDataFlow;
import accrue.analysis.interprocanalysis.WorkQueue;

/**
 * Add support for analysis contexts.  When you get to a function determine whether the function has been analyzed in that context; if not adds to work queue.
 *
 * @param <A> Defines the lattice for procedure summaries
 * @param <T> Type that is used for the variable context
 */
public abstract class VarContextDataFlow<T extends AbstractValue<T>> extends
        PreciseDataFlow<VarContext<T>> implements
        DataFlowDispatch<VarContext<T>> {

    /**
     * Change this if we want bottom policies to stick around
     */
    public static final boolean DONT_TRACK_BOTTOM = true;

    /**
     * Determines whether to warn on missing dataflow edges
     */
    public static boolean WARN_ON_MISSING_EDGES = true;

    /**
     * Whether to warn on missing dataflow edges
     */
    private boolean warnOnEdgesMissingData = WARN_ON_MISSING_EDGES;

    /**
     * Create a new dataflow visitor
     * 
     * @param autil
     *            Analysis helper class
     * @param workQueue
     *            Analysis work queue
     * @param job
     *            Polyglot compiler {@link Job}
     * @param ts
     *            Polyglot type system
     * @param nf
     *            factory for creating AST nodes
     * @param forward
     *            forward or backward dataflow
     */
    public VarContextDataFlow(AnalysisUtilVarContextDataFlow<?, T> autil,
            WorkQueue<?> workQueue, Job job, TypeSystem ts, NodeFactory nf,
            boolean forward) {
        this(autil, workQueue, job, ts, nf, forward, false, false);
    }

    /**
     * Create a new dataflow visitor
     * 
     * @param autil
     *            Analysis helper class
     * @param workQueue
     *            Analysis work queue
     * @param job
     *            Polyglot compiler {@link Job}
     * @param ts
     *            Polyglot type system
     * @param nf
     *            factory for creating AST nodes
     * @param forward
     *            forward or backward dataflow
     * @param dataflowOnEntry
     * 			  true if we want to process the entry node
     * @param trackBackEdges 
     * 			  true if the dataflow tracks backward FlowGraph<VarContext<T>> edges           
     */
    public VarContextDataFlow(AnalysisUtilVarContextDataFlow<?, T> autil,
            WorkQueue<?> workQueue, Job job, TypeSystem ts, NodeFactory nf,
            boolean forward, boolean dataflowOnEntry, boolean trackBackEdges) {
        super(autil, job, ts, nf, forward, dataflowOnEntry, trackBackEdges);
        this.workQueue = workQueue;
        if (!forward)
            throw new InternalCompilerError("Only forward interprocedural analyses currently supported");

    }

    /**
     * Analysis work queue
     */
    protected final WorkQueue<?> workQueue;

    /**
     * initial variable context
     */
    private VarContext<T> initial = null;
    /**
     * output variable context
     */
    protected FinalVarContext<T> output = null;

    @SuppressWarnings("unchecked")
    @Override
    protected AnalysisUtilVarContextDataFlow<?, T> autil() {
        return (AnalysisUtilVarContextDataFlow<?, T>) super.autil();
    }

    /**
     * Create an initial Item for the dataflow analysis. By default, the 
     * set of not null variables is empty.
     */
    @Override
    protected VarContext<T> createInitialItem(FlowGraph<VarContext<T>> graph,
            Term node, boolean entry) {
        if (node == graph.root()) {
            return initial;
        }
        return autil().createInitContext();
    }

    /**
     * Construct a graph for the given code and call super
     * {@link #dataflow(FlowGraph)} on it
     * 
     * @param cd
     *            Code to flow
     * @param in
     *            initial variable context
     * @return variable context after analyzing the FlowGraph<VarContext<T>> for
     *         <code>cd</code>
     * @throws SemanticException
     *             thrown on an analysis error
     */
    public FinalVarContext<T> dataflow(CodeNode cd, VarContext<T> in)
            throws SemanticException {
        this.initial = in;
        super.dataflow(cd);
        return this.output;
    }

    @Override
    protected final Map<EdgeKey, VarContext<T>> flow(
            List<VarContext<T>> inItems, List<EdgeKey> inItemKeys,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {

        if (peer.node() instanceof FieldDecl && !peer.isEntry()) {
            // don't bother performing flow for field decl exits. This will
            // be handled specially by the postFieldDecl method.
            // This is because FieldDecls are treated similarly to initializer blocks, 
            // but unlike initializer blocks we actually need to do some processing
            // on them (i.e., FieldDecls are kind of like field assignments, if they
            // have an initializer expression).
            // The postFieldDecl method takes care of the flow for handling a field assignment.
            // So we don't perform flow here, in part because it doesn't make sense to join
            // the inItem for normal termination of the initializer expression with inItems
            // for exceptions thrown by the initializer expression.
            if (peer.succEdgeKeys().isEmpty()) {
                return Collections.emptyMap();
            }
            throw new InternalCompilerError("FieldDecl seems to have successors. Shouldn't be the case.",
                                            peer.node().position());
        }

        List<VarContext<T>> trueItems = new ArrayList<VarContext<T>>();
        List<EdgeKey> trueItemKeys = new ArrayList<FlowGraph.EdgeKey>();
        List<VarContext<T>> falseItems = new ArrayList<VarContext<T>>();
        List<EdgeKey> falseItemKeys = new ArrayList<FlowGraph.EdgeKey>();
        List<VarContext<T>> otherItems = new ArrayList<VarContext<T>>();
        List<EdgeKey> otherItemKeys = new ArrayList<FlowGraph.EdgeKey>();

//        System.err.println("==== flow for " + peer + " " + peer.isEntry()
//                + " :: " + peer.node().getClass());

        ExtTerm et = (ExtTerm) AccrueExt_c.ext(peer.node());
        int finallyDepth = et.enclosingFinallyDepth();
        if (!peer.isEntry() && et instanceof ExtBlock
                && ((ExtBlock) et).isFinallyBlock()) {
            // in the special case that we are exiting a finally block, we need to keep one extra suspended execution.
            finallyDepth++;
        }
//        System.err.println("    is exit to finally block? "
//                + (!peer.isEntry() && et instanceof ExtBlock && ((ExtBlock) et).isFinallyBlock()));
//        System.err.println("    lexical depth is " + et.enclosingFinallyDepth());
//        System.err.println("    intended depth is " + finallyDepth);
        Iterator<VarContext<T>> i = inItems.iterator();
        Iterator<EdgeKey> j = inItemKeys.iterator();
        while (i.hasNext() || j.hasNext()) {
            VarContext<T> item = i.next();
            EdgeKey key = j.next();
            if (item == null) continue;

            // let's make a base case for the suspended execution stacks.
            if (et.enclosingFinallyDepth() == 0
                    && item.suspendedExecutionStack() == null
                    && finallyDepth == 0) {
                item =
                        item.suspendedExecutionStack(SuspendedExecutionStack.<T> emptyStack());
            }
            // make sure that the size of the suspended execution stack is the same as the number of 
            // finally blocks it is nested inside.

            item = item.popSuspendedExecutionStackToDepth(finallyDepth);
            if (FlowGraph.EDGE_KEY_TRUE.equals(key)) {
                trueItems.add(item);
                trueItemKeys.add(key);
            }
            else if (FlowGraph.EDGE_KEY_FALSE.equals(key)) {
                falseItems.add(item);
                falseItemKeys.add(key);
            }
            else {
//                System.err.println("         got edge "
//                        + key
//                        + " with context with stack depth "
//                        + (item.suspendedExecutionStack() == null ? "null"
//                                : item.suspendedExecutionStack().size()));
                otherItems.add(item);
                otherItemKeys.add(key);
            }
        }

        VarContext<T> trueItem =
                trueItems.isEmpty() ? null : this.safeConfluence(trueItems,
                                                                 trueItemKeys,
                                                                 peer,
                                                                 graph);
        VarContext<T> falseItem =
                falseItems.isEmpty() ? null
                        : this.safeConfluence(falseItems,
                                              falseItemKeys,
                                              peer,
                                              graph);
        VarContext<T> otherItem =
                otherItems.isEmpty() ? null
                        : this.safeConfluence(otherItems,
                                              otherItemKeys,
                                              peer,
                                              graph);

//        System.err.println("    doing join ("
//                + otherItems.size()
//                + ") "
//                + this.getClass()
//                + "and depth is now "
//                + (otherItem == null
//                        || otherItem.suspendedExecutionStack() == null ? "null"
//                        : otherItem.suspendedExecutionStack().size()));
        // If it's an entry node 
        if (peer.isEntry()) {
            if (et instanceof ExtBlock && ((ExtBlock) et).isFinallyBlock()) {
                // this is a finally block that we are entering.
                // We need to push suspended execution onto the stack. That is,
                // we are entering this finally block due to a number of possible reasons (e.g.,
                // an exception was thrown, a return was encountered, normal termination),
                // and if this finally block completes normally, we need to restore this
                // suspended execution (e.g., exit the finally block with the appropriately
                // thrown exception).
                // It is a stack due to potentially nested finally blocks.

                // create the map from the other items
                Map<EdgeKey, T> m = new LinkedHashMap<EdgeKey, T>();

                for (int n = 0; n < otherItems.size(); n++) {
                    m.put(otherItemKeys.get(n), otherItems.get(n)
                                                          .returnResult());
                }

                // push the map onto the suspended exec stack  
                trueItem =
                        (trueItem == null) ? null
                                : trueItem.pushSuspendedExecutionStack(m,
                                                                       (Block) peer.node());

                falseItem =
                        (falseItem == null) ? null
                                : falseItem.pushSuspendedExecutionStack(m,
                                                                        (Block) peer.node());

                otherItem =
                        (otherItem == null) ? null
                                : otherItem.pushSuspendedExecutionStack(m,
                                                                        (Block) peer.node());

//                System.err.println("        so pushing onto stack for new size of "
//                        + otherItem.suspendedExecutionStack().size());

                if (otherItem != null
                        && otherItem.suspendedExecutionStack() != null
                        && otherItem.suspendedExecutionStack().size() != finallyDepth + 1) {
                    throw new InternalCompilerError("Size of incoming stack is wrong.");
                }

            }
        }

        // Actually perform the dataflow.
        Map<EdgeKey, VarContext<T>> ret =
                this.flow(trueItem, falseItem, otherItem, graph, peer);

        // If we are finished analyzing a finally block, restore the suspended
        // execution stack
        if (!peer.isEntry()) {
            if (et instanceof ExtBlock && ((ExtBlock) et).isFinallyBlock()) {
                // Assert edge key validity
                checkFinallyKeys(ret, peer.node().position());

                // Save off the error contexts to put it back if necessary
                EdgeKey errorKey = new ExceptionEdgeKey(ts.Error());
                VarContext<T> errorC = ret.get(errorKey);

                VarContext<T> c = ret.get(FlowGraph.EDGE_KEY_OTHER);
                if (c != null) {
                    // On the normal termination for the finally block,
                    // we can restore the suspended execution.
                    // First, make sure that the suspended execution stack has the right depth
                    c =
                            c.popSuspendedExecutionStackToDepth(et.enclosingFinallyDepth() + 1);
                    ret = restoreSuspendedExecution(c);
                }

                // Put the error context back, joining it with any existing error edge
                if (errorC != null) {
                    errorC =
                            errorC.popSuspendedExecutionStackToDepth(et.enclosingFinallyDepth());
                    if (ret.containsKey(errorKey)) {
                        errorC = errorC.upperBound(ret.get(errorKey));
                    }

                    ret.put(errorKey, errorC);
                }
            }
            // If we have an abrupt termination (return or exception) and are in a 
            // finally block, then the finally block will never terminate normally and 
            // thus will never need to use the suspended execution that we used
            // to enter this finally block. So pop off the suspended execution
            // used for this finally block (i.e., make sure the suspended execution stack
            // is no bigger than et.enclosingFinallyDepth()).
            for (EdgeKey ek : ret.keySet()) {
                if (ek instanceof ExceptionEdgeKey
                        || peer.node() instanceof Return) {
                    if (ret.get(ek) != null) {
                        try {
                            ret.put(ek,
                                    ret.get(ek)
                                       .popSuspendedExecutionStackToDepth(et.enclosingFinallyDepth()));
                        }
                        catch (InternalCompilerError e) {
                            throw new InternalCompilerError("Can't pop to correct depth",
                                                            peer.node()
                                                                .position(), e);
                        }
                    }
                }
            }
        }

        if (peer.succEdgeKeys().contains(FlowGraph.EDGE_KEY_OTHER)
                && !ret.keySet().contains(FlowGraph.EDGE_KEY_OTHER)) {
            System.out.println("WARNING: missing OTHER edge "
                    + autil().analysisName() + " when analysing\n"
                    + peer.node() + " at " + peer.node().position());
        }

        if (peer.succEdgeKeys().isEmpty() && !ret.isEmpty()) {
            // Show a warning, unless the peer is a finish peer for a field decl
            if (!(peer.node() instanceof FieldDecl && graph.finishPeers()
                                                           .contains(peer))) {
                System.out.println("WARNING: should return empty map during "
                        + autil().analysisName() + " when analysing\n"
                        + peer.node() + " at " + peer.node().position());
            }
            return new HashMap<EdgeKey, VarContext<T>>();
        }
        return ret;
    }

    /**
     * Produce new variable contexts from the input contexts and {@link Peer}
     * 
     * @param inItems
     *            all the Items flowing into the node.
     * @param inItemKeys
     *            the FlowGraph.EdgeKeys for the items in the list inItems
     * @param graph
     *            the FlowGraph<VarContext<T>> which the dataflow is operating on
     * @param peer
     *            the Peer<VarContext<T>> which this method must calculate the flow for.
     * @return Map of exit keys to variable contexts
     */
    @Override
    public final Map<EdgeKey, VarContext<T>> flow(VarContext<T> trueItem,
            VarContext<T> falseItem, VarContext<T> otherItem,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        if (peer.isEntry() && peer.node() == graph.root() && otherItem == null) {
            otherItem = this.initial;
        }

        if (otherItem == null && trueItem == null && falseItem == null) {
            // nothing to do

            Map<EdgeKey, VarContext<T>> ret =
                    itemToMap(null, peer.succEdgeKeys());
            return ret;
        }
        // record entry of terms for the expression stack
        if (peer.node() instanceof Expr) {
            if (peer.isEntry()) {
                if (trueItem != null)
                    trueItem = trueItem.recordNodeEntry(peer.node());
                if (falseItem != null)
                    falseItem = falseItem.recordNodeEntry(peer.node());
                if (otherItem != null)
                    otherItem = otherItem.recordNodeEntry(peer.node());
            }
        }

        Map<EdgeKey, VarContext<T>> m =
                flowImpl(trueItem, falseItem, otherItem, graph, peer);

        // record exit of terms for the expression stack
        if (peer.node() instanceof Expr) {
            if (!peer.isEntry()) {
                Map<EdgeKey, VarContext<T>> newM =
                        new LinkedHashMap<EdgeKey, VarContext<T>>();
                for (EdgeKey ek : m.keySet()) {
                    if (m.get(ek) == null) {
                        System.err.println("WARNING: null context for edge key "
                                + ek
                                + " for peer "
                                + peer
                                + " at "
                                + peer.node().position());
                    }
                    else {
                        newM.put(ek, m.get(ek).recordNodeExit(peer.node()));
                    }
                }
                m = newM;
            }
        }

        /* Analyze instance initializers after call to super */
        if (!peer.isEntry()
                && peer.node() instanceof ConstructorCall
                && ((ConstructorCall) peer.node()).kind() == ConstructorCall.SUPER) {
            try {
                /* Only the normal termination edge flows to the initializers */
                VarContext<T> before;
                if (!m.containsKey(FlowGraph.EDGE_KEY_OTHER)) {
                    // whoops! Looks like the super call doesn't terminate normally.
                    before = null;
                }
                else {
                    before = m.get(FlowGraph.EDGE_KEY_OTHER);
                }
                FinalVarContext<T> after =
                        autil().analyzeInitializersForDataFlow(before);

                /* add the edges from after to the map */
                for (Key k : after.allKeys()) {
                    EdgeKey ek = keyConvert(k);
                    if (m.containsKey(ek)) {
                        m.put(ek,
                              VarContext.upperBound(after.get(k), m.get(ek)));
                    }
                    else {
                        m.put(ek, after.get(k));
                    }
                }
            }
            catch (SemanticException e) {
                throw new InternalCompilerError("Semantic error while analyzing instance initializers:"
                        + e);
            }
        }

        checkInvariants(m, graph, peer);
        return m;
    }

    /**
     * Check that our dataflow invariants hold
     * 
     * @param m
     *            map holding the variable context for each exit path
     * @param graph
     *            the FlowGraph<VarContext<T>> which the dataflow is operating on
     * @param peer
     *            the Peer<VarContext<T>> which this method must check invariants for.
     */
    protected void checkInvariants(Map<EdgeKey, VarContext<T>> m,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        // Check invariants
        if (peer.isEntry()) {
            for (EdgeKey ek : m.keySet()) {
                VarContext<T> c = m.get(ek);
                if (c == null) continue;

                if (peer.node() instanceof CodeDecl) {
                    if (c.suspendedExecutionStack() != null
                            && !c.suspendedExecutionStack().isEmpty()) {
                        throw new InternalCompilerError("Failed dataflow invariant: non-empty suspended exception stack before "
                                + peer.node() + " :: " + peer.node().getClass());
                    }
                }
            }
        }

        if (!peer.isEntry()) {
            // check that every exception has no expression result stack, and a return result
            // check that the suspended exception stack is empty when exiting a CodeDecl
            for (EdgeKey ek : m.keySet()) {
                VarContext<T> c = m.get(ek);
                if (c == null) continue;

                if (peer.node() instanceof CodeDecl) {
                    if (c != null && !c.suspendedExecutionStack().isEmpty()) {
                        throw new InternalCompilerError("Failed dataflow invariant: non-empty suspended exception stack after "
                                + peer.node());
                    }
                }

                if (ek instanceof ExceptionEdgeKey) {
                    if (!c.exprResultStack().isEmpty()) {
                        throw new InternalCompilerError("Failed dataflow invariant: exceptional flow for term "
                                + peer.node()
                                + " should have empty stack: "
                                + c.exprResultStack());
                    }
                    if (c.returnResult() == null) {
                        throw new InternalCompilerError("Failed dataflow invariant in "
                                + autil().analysisName()
                                + " of "
                                + autil().currentAnalysisUnit()
                                + ": exceptional flow "
                                + ek
                                + " for term "
                                + peer.node()
                                + " should have return result (representing the exception)\nInstead it is "
                                + c);
                    }
                }
                else {
                    if (!(peer.node() instanceof Expr)
                            && !(peer.node() instanceof TypeNode)) {
                        if (!c.exprResultStack().isEmpty()) {
                            throw new InternalCompilerError("Failed dataflow invariant: flow for non-expression term "
                                    + peer.node()
                                    + " should have empty stack: "
                                    + c.exprResultStack());
                        }
                    }
                    if (peer.node() instanceof Expr) {
                        if (c.returnResult() != null) {
                            throw new InternalCompilerError("Failed dataflow invariant: non-null return result from an expression on normal termination "
                                    + peer.node());
                        }
                    }
                }
            }
        }

    }

    @Override
    protected VarContext<T> safeConfluence(List<VarContext<T>> items,
            List<EdgeKey> itemKeys, Peer<VarContext<T>> peer,
            FlowGraph<VarContext<T>> graph) {
        if (items.isEmpty()) {
            // return null instead of an initial context.
            return null;
        }

        VarContext<T> ret;
        if (items.size() == 1) {
            ret = items.get(0);
        }
        else {
            ret = confluence(items, itemKeys, peer, graph);
        }
        if ((peer.node() instanceof Stmt && peer.isEntry())
                || peer.node() instanceof CompoundStmt) {
            ret = ret.clearExprResultStack();
        }
        return ret;
    }

    @Override
    protected VarContext<T> confluence(List<VarContext<T>> items,
            Peer<VarContext<T>> peer, FlowGraph<VarContext<T>> graph) {
        if (!peer.isEntry() && (peer.node() instanceof Suppress)) {
            // discard all exception items that come to the suppress node.
            // When the CFG was being built, any exceptions thrown by
            // the subexpression that was on the list of suppressed exceptions
            // will be "caught" by the suppress node. Thus, any incoming exception
            // edges (i.e,. edges with a non-null returnResult) will be from an
            // exception that we are explicitly suppressing. That is why it is
            // fine to discard all exceptions coming to this Suppress node.
            List<VarContext<T>> l = new ArrayList<VarContext<T>>(items);
            Iterator<VarContext<T>> iter = l.iterator();
            while (iter.hasNext()) {
                VarContext<T> d = iter.next();
                if (d != null && d.returnResult() != null) iter.remove();
            }
            return VarContext.join(l, false, false, false);
        }
        Term node = peer.node();

        boolean mayBeOffByOne = false;
        if (node instanceof Binary) {
            Binary b = (Binary) node;
            if (b.type().isBoolean()
                    && (b.operator() == Binary.COND_AND || b.operator() == Binary.COND_OR)) {
                // there is short circuit evaluation for this node.
                // As such, the result stacks may be off by one.
                mayBeOffByOne = true;
            }
        }

        boolean clearExprStacks = false;
        if ((node instanceof Stmt && peer.isEntry())
                || node instanceof CompoundStmt) {
            clearExprStacks = true;
        }
        else if (node instanceof FieldDecl && !peer.isEntry()) {
            // For confluence for FieldDecl exits, clear the expression
            // stacks, since it may be joining VarContexts from the 
            // normal termination of the intializer expression and
            // exceptional termination of the initializer expression.
            // These may have different expression stack sizes.
            // Note that the interesting dataflow for field decls
            // is done by postFieldDecl, which does not call this method.
            clearExprStacks = true;
        }

        // If we are dealing with the exit node, clear all of the suspended executions
        boolean clearSuspendedExecStacks = false;
        if (graph.exitPeers().contains(peer)) {
            // it is an exit peer
            clearSuspendedExecStacks = true;
        }

        try {
            return VarContext.join(items,
                                   mayBeOffByOne,
                                   clearExprStacks,
                                   clearSuspendedExecStacks);
        }
        catch (RuntimeException e) {
            for (Object c : items) {
                System.err.println(c);
                System.err.println();
            }
            throw new RuntimeException("Problem here in "
                    + this.getClass().getCanonicalName() + " with peer " + peer
                    + " : " + node.getClass() + " at " + node.position(), e);
        }
    }

    protected abstract Map<EdgeKey, VarContext<T>> flowImpl(
            VarContext<T> trueItem, VarContext<T> falseItem,
            VarContext<T> otherItem, FlowGraph<VarContext<T>> graph,
            Peer<VarContext<T>> peer);

    @Override
    protected void post(FlowGraph<VarContext<T>> graph, Term root)
            throws SemanticException {
        this.output = autil().emptyFinalVarContext();
        for (Peer<VarContext<T>> p : graph.finishPeers()) {
            if (p.node() instanceof FieldDecl) {
                // we'll handle FieldDecls specially.
                postFieldDecl(graph, p);
            }
            else {
                // get all the predecessors for p
                for (Edge<VarContext<T>> e : p.preds()) {
                    Key k = edgeKeyConvert(e.getKey());
                    VarContext<T> c = e.getTarget().outItem(e.getKey());
                    if (c != null) {
                        // clear the suspended execution stack, since we are exiting the procedure.
                        c = c.popSuspendedExecutionStackToDepth(0);

                        // clear the expression stacks, since a CodeNode is not an expression
                        c = c.clearExprResultStack();
                        output =
                                (FinalVarContext<T>) output.addKey(k,
                                                                   AnalysisUtil.upperBound(c,
                                                                                           output.get(k)));
                    }
                }
            }
        }

        super.post(graph, root);
    }

    /**
     * Handle the post-processing for a finish peer for a FieldDecl specially. This method
     * updates the field output appropriately for the finish peer.
     * @param graph
     * @param p
     */
    protected void postFieldDecl(FlowGraph<VarContext<T>> graph,
            Peer<VarContext<T>> p) {
        // We will manually perform dataflow
        // on the field decl, since the dataflow framework discards the
        // results of the FieldDecl dataflow (since, as a
        // finish peer, it has no successors).

        // Take the incoming normal termination edges (i.e., non-exception edges)
        // merge them together, into inItem.
        VarContext<T> inItem = null;
        for (Edge<VarContext<T>> e : p.preds()) {
            if (!(e.getKey() instanceof ExceptionEdgeKey)) {
                VarContext<T> c = e.getTarget().outItem(e.getKey());
                if (c != null) {
                    if (inItem == null) {
                        inItem = c;
                    }
                    else {
                        inItem = inItem.upperBound(c);
                    }
                }
            }
        }
        // inItem is now the upperBound of all non-exception edges.
        if (inItem != null) {
            // Perform dataflow for the FieldDecl, and merge the results into this.output.
            Map<EdgeKey, VarContext<T>> flowResult =
                    this.flowFieldDecl((FieldDecl) p.node(), inItem, graph, p);

            if (flowResult.isEmpty()) {
                throw new InternalCompilerError("Method flowFieldDecl in class "
                                                        + this.getClass()
                                                        + " returned an empty map when "
                                                        + "called from postFieldDecl. Possibly this is because "
                                                        + "the implementation is using peer.succEdgeKeys() to determine "
                                                        + "the contents of the map. This is not appropriate for FieldDecls "
                                                        + "as they will have no successors. See, for example, "
                                                        + "accrue.analysis.notnulldataflow.NotNullDataFlow.flowFieldDecl() "
                                                        + " for an appropriate implementation.",
                                                p.node().position());
            }
            for (EdgeKey ek : flowResult.keySet()) {
                Key k = edgeKeyConvert(ek);
                VarContext<T> c = flowResult.get(ek);
                // clear the suspended execution stack, since we are exiting the procedure.
                c = c.popSuspendedExecutionStackToDepth(0);

                output =
                        (FinalVarContext<T>) output.addKey(k,
                                                           AnalysisUtil.upperBound(c,
                                                                                   output.get(k)));
            }
        }

        // now add in all the exceptions edges of the predecessors
        for (Edge<VarContext<T>> e : p.preds()) {
            Key k = edgeKeyConvert(e.getKey());
            VarContext<T> c = e.getTarget().outItem(e.getKey());
            if (c != null && e.getKey() instanceof ExceptionEdgeKey) {
                // clear the suspended execution stack, since we are exiting the procedure.
                c = c.popSuspendedExecutionStackToDepth(0);

                // clear the expression stacks, since a CodeNode is not an expression
                c = c.clearExprResultStack();
                output =
                        (FinalVarContext<T>) output.addKey(k,
                                                           AnalysisUtil.upperBound(c,
                                                                                   output.get(k)));
            }
        }

    }

    /**
     * Convert {@link FlowGraph} edge keys to {@link FinalVarContext} edge keys
     * 
     * @param ek
     *            {@link FlowGraph} key to convert
     * @return {@link FinalVarContext} edge key
     */
    public static Key edgeKeyConvert(EdgeKey ek) {
        if (ek == FlowGraph.EDGE_KEY_OTHER) {
            return ExitMap.KEY_NORM_TERM;
        }
        if (ek == FlowGraph.EDGE_KEY_TRUE) {
            return ExitMap.KEY_TRUE_RESULT;
        }
        if (ek == FlowGraph.EDGE_KEY_FALSE) {
            return ExitMap.KEY_FALSE_RESULT;
        }

        if (ek instanceof ExceptionEdgeKey) {
            return ExitMap.exceptionKey((ClassType) ((ExceptionEdgeKey) ek).type());
        }
        throw new InternalCompilerError("Unhandled Edgekey " + ek + " : "
                + ek.getClass());

    }

    /**
     * Convert {@link FinalVarContext} edge keys to {@link FlowGraph} edge keys
     * 
     * @param ek
     *            {@link FinalVarContext} key to convert
     * @return {@link FlowGraph} edge key
     */
    protected static EdgeKey keyConvert(Key k) {
        if (k == ExitMap.KEY_NORM_TERM) {
            return FlowGraph.EDGE_KEY_OTHER;
        }
        if (k == ExitMap.KEY_TRUE_RESULT) {
            return FlowGraph.EDGE_KEY_TRUE;
        }
        if (k == ExitMap.KEY_FALSE_RESULT) {
            return FlowGraph.EDGE_KEY_FALSE;
        }
        if (k instanceof ExceptionKey) {
            return new FlowGraph.ExceptionEdgeKey(((ExceptionKey) k).exType);
        }
        throw new InternalCompilerError("Unhandled Key " + k + " : "
                + k.getClass());

    }

    /**
     * Converts a FinalVarContext to a map from EdgeKeys to Items. This makes an
     * effort to produce a map that contains all and only keys in succEdgeKeys
     * 
     * @param f
     *            variable context to convert
     * @param succEdgeKeys
     *            edge keys to retain
     * @param node
     *            AST node where this is called (used for debugging)
     */
    public final Map<EdgeKey, VarContext<T>> finalVarContextToDataFlowMap(
            FinalVarContext<T> f, Set<EdgeKey> succEdgeKeys,
            Peer<VarContext<T>> peer) {
        Node node = peer.node();
        Map<EdgeKey, VarContext<T>> m = new HashMap<EdgeKey, VarContext<T>>();
        Set<EdgeKey> remaining = new HashSet<EdgeKey>(succEdgeKeys);

        // let's do some preprocessing.
        if (!f.hasTrueResult()
                && succEdgeKeys.contains(FlowGraph.EDGE_KEY_TRUE)) {
            f = (FinalVarContext<T>) f.trueResult(f.normalTermination());
        }
        if (!f.hasFalseResult()
                && succEdgeKeys.contains(FlowGraph.EDGE_KEY_FALSE)) {
            f = (FinalVarContext<T>) f.falseResult(f.normalTermination());
        }
        for (Key k : f.allKeys()) {
            EdgeKey ek = keyConvert(k);
            if (ek == FlowGraph.EDGE_KEY_OTHER
                    && !succEdgeKeys.contains(ek)
                    && ((f.hasTrueResult() && succEdgeKeys.contains(FlowGraph.EDGE_KEY_TRUE)) || (f.hasFalseResult() && succEdgeKeys.contains(FlowGraph.EDGE_KEY_FALSE)))) {
                // it's ok to ignore this edge. Even though the successor edges don't contain an OTHER edge, it does contain
                // true and false edges.
                continue;
            }
            while (!succEdgeKeys.contains(ek)) {
                if (ek instanceof ExceptionEdgeKey) {
                    // try the parent of the exception
                    ClassType t = ((ExceptionEdgeKey) ek).type().toClass();
                    if (t.isThrowable()) {
                        ek = new ExceptionEdgeKey(t.superType());
                    }
                    else {
                        throw new InternalCompilerError("Got a "
                                                                + k
                                                                + " but only should be producing "
                                                                + succEdgeKeys
                                                                + " for "
                                                                + node,
                                                        node.position());
                    }
                }
                else {
                    // we're hosed, we have an edge key that does belong in the output map.
                    throw new InternalCompilerError("Got a " + k
                            + " but only should be producing " + succEdgeKeys
                            + " for " + node + " in analysis "
                            + this.getClass(), node.position());
                }
            }

            if (m.containsKey(ek)) {
                // join it in with whatever is already in the map.
                m.put(ek, VarContext.upperBound(f.get(k), m.get(ek)));
            }
            else {
                m.put(ek, f.get(k));
                // remove it from the remaining set.
                if (!remaining.remove(ek)) {
                    System.out.println("WARNING: imprecise edge key map, did not contain "
                            + ek);
                    Thread.dumpStack();
                }
            }
        }
        // see if we can take care of any of the remaining edges using the analysis results
        for (Iterator<EdgeKey> iter = remaining.iterator(); iter.hasNext();) {
            EdgeKey ek = iter.next();
            if (ek instanceof ExceptionEdgeKey) {
                ClassType exType = ((ExceptionEdgeKey) ek).type().toClass();
                // there was no context for the exception exType in the final var context map. Let's see if 
                // there was a context for a super class...
                exType = (ClassType) exType.superType();
                while (exType.isThrowable()) {
                    // see if the final var context has something for t.
                    if (f.hasExceptionType(exType)) {
                        // great! we found a match. Put it in the results.
                        m.put(ek, f.exception(exType));
                        iter.remove(); // remove ek from the set remaining
                        break;
                    }
                    exType = (ClassType) exType.superType();
                }
            }
        }

        if (!remaining.isEmpty()) {
            // As a temporary fix, add in missing path maps. The analysis is sound, so adding anything
            // on these edges is fine.
            if (warnOnEdgesMissingData) {
                System.err.println("WARNING: imprecise edge key map in "
                        + this.getClass().getCanonicalName() + " for node "
                        + node + " at " + node.position()
                        + ". Was meant to produce successors " + succEdgeKeys
                        + " but did not produce results for " + remaining);
            }

            for (EdgeKey ek : remaining) {
                m.put(ek, constructMissingContextForEdgeKey(ek, f, peer));
            }

        }
        if (!peer.succEdgeKeys().containsAll(m.keySet())) {
            System.err.println("WARNING: Keyset mismatch for " + peer.node()
                    + " during " + autil().analysisName());
        }
        return m;
    }

    /**
     * Restore execution that was suspended for a finally block. For example if a try block throws a 
     * NullPointerException and the corresponding finally block completes normally then the entire 
     * try/finally will throw that NullPointerException. (Similarly for other exceptions or a return 
     * statement in a try block.
     * 
     * @param afterFinally 
     *          VarContext giving analysis results right after analyzing the finally block 
     * @return Map from edge keys to {@link VarContext} for the restored execution upon leaving the 
     *         finally block
     */
    private Map<EdgeKey, VarContext<T>> restoreSuspendedExecution(
            VarContext<T> afterFinally) {

        Map<EdgeKey, T> edgesToRestore =
                afterFinally.suspendedExecutionStack().peek();

        SuspendedExecutionStack<T> newStack =
                afterFinally.suspendedExecutionStack().pop();
        afterFinally = afterFinally.suspendedExecutionStack(newStack);

        Map<EdgeKey, VarContext<T>> newVarContexts =
                new LinkedHashMap<EdgeKey, VarContext<T>>();
        for (EdgeKey ek : edgesToRestore.keySet()) {
            T newRet = edgesToRestore.get(ek);
            VarContext<T> newC = afterFinally.returnResult(newRet);
            newVarContexts.put(ek, newC);
        }

        return newVarContexts;
    }

    /**
     * Assertion to make sure that the edge keys are valid when leaving a finally block.
     * They should be just an OTHER (normal termination) and/or an ERROR edge.
     * 
     * @param map 
     *          map of edge keys leaving a finally block to var context
     * @param pos 
     *          position we are checking the edge keys for
     */
    private void checkFinallyKeys(Map<EdgeKey, VarContext<T>> map, Position pos) {
        boolean validKeys = false;

        Set<EdgeKey> keys = new HashSet<EdgeKey>(map.keySet());

        // if there is one key it must be OTHER or ERROR
        if (keys.size() == 1) {
            EdgeKey ek = keys.iterator().next();
            if (ek.equals(FlowGraph.EDGE_KEY_OTHER)) {
                validKeys = true;
            }

            boolean isError =
                    (ek instanceof ExceptionEdgeKey)
                            && ((ExceptionEdgeKey) ek).type()
                                                      .equals(ts.Error());
            if (isError) {
                validKeys = true;
            }
        }

        // If there are two keys one must be ERROR and one must be OTHER
        if (keys.size() == 2) {
            boolean containsOther = keys.remove(FlowGraph.EDGE_KEY_OTHER);
            if (containsOther) {
                EdgeKey ek = keys.iterator().next();
                boolean isError =
                        (ek instanceof ExceptionEdgeKey)
                                && ((ExceptionEdgeKey) ek).type()
                                                          .equals(ts.Error());
                if (isError) {
                    validKeys = true;
                }
            }
        }

        if (!validKeys) {
            // If all contexts are null this means we won't restore anyway
            // This usually happens when the input contexts are null and there is
            // no analysis to do
            for (EdgeKey ek : keys) {
                if (map.get(ek) != null) {
                    throw new InternalCompilerError("Invalid edge key set when leaving finally: "
                                                            + keys,
                                                    pos);
                }
            }
            // All var contexts were null
            validKeys = true;
        }
    }

    /**
     * Set whether to warn on missing dataflow edges
     * @param warn
     */
    protected void setWarnOnEdgesMissingData(boolean warn) {
        this.warnOnEdgesMissingData = warn;
    }

    /**
     * If an edge is missing this method is called to approximate the variable
     * context
     * 
     * @param ek
     *            edge key of missing edge
     * @param f the finalVarContext that we were using.
     * @return variable context
     */
    protected VarContext<T> constructMissingContextForEdgeKey(EdgeKey ek,
            FinalVarContext<T> f, Peer<VarContext<T>> peer) {
        return null;
    }

    /**
     * Get the number of argument results on the stack
     * 
     * @param t
     *            AST node to compute the number of arguments for
     * @return number of argument results on the stack for <code>t</code>
     */
    protected static <T extends AbstractValue<T>> int argumentsOnResultStack(
            Term t, VarContext<T> vc) {
        Integer n = vc.depthToNodeEntry(t);
        if (n != null) return n.intValue();
        // we don't have an answer. Try using the extension info.
        AccrueExt ext = AccrueExt_c.ext(t);
        if (ext != null) {
            return ext.numArgumentsOnResultStack();
        }

        throw new InternalCompilerError("Don't know how many arguments on stack for "
                + t + " (" + t.getClass().getName() + ")");
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowField(Field n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        throw new InternalCompilerError("Not yet implemented: " + n + " "
                + n.getClass() + " for analysis " + this.getClass().getName());
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowReturn(Return n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        throw new InternalCompilerError("Not yet implemented: " + n + " "
                + n.getClass() + " for analysis " + this.getClass().getName());
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowAssert(Assert n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        return flowStmt(n, dfIn, graph, peer);
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowThrow(Throw n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        throw new InternalCompilerError("Not yet implemented: " + n + " "
                + n.getClass() + " for analysis " + this.getClass().getName());
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowConditional(Conditional n,
            VarContext<T> dfIn, FlowGraph<VarContext<T>> graph,
            Peer<VarContext<T>> peer) {
        throw new InternalCompilerError("Not yet implemented: " + n + " "
                + n.getClass() + " for analysis " + this.getClass().getName());
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowCall(Call n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        throw new InternalCompilerError("Not yet implemented: " + n + " "
                + n.getClass() + " for analysis " + this.getClass().getName());
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowNew(New n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        throw new InternalCompilerError("Not yet implemented: " + n + " "
                + n.getClass() + " for analysis " + this.getClass().getName());
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowNewArray(NewArray n,
            VarContext<T> dfIn, FlowGraph<VarContext<T>> graph,
            Peer<VarContext<T>> peer) {
        throw new InternalCompilerError("Not yet implemented: " + n + " "
                + n.getClass() + " for analysis " + this.getClass().getName());
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowConstructorCall(ConstructorCall n,
            VarContext<T> dfIn, FlowGraph<VarContext<T>> graph,
            Peer<VarContext<T>> peer) {
        throw new InternalCompilerError("Not yet implemented: " + n + " "
                + n.getClass() + " for analysis " + this.getClass().getName());
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowUnary(Unary n, VarContext<T> dfIn,
            VarContext<T> trueItem, VarContext<T> falseItem,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        throw new InternalCompilerError("Not yet implemented: " + n + " "
                + n.getClass() + " for analysis " + this.getClass().getName());
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowBinary(Binary n, VarContext<T> dfIn,
            VarContext<T> trueItem, VarContext<T> falseItem,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        throw new InternalCompilerError("Not yet implemented: " + n + " "
                + n.getClass() + " for analysis " + this.getClass().getName());
    }

    /**
     * Compute the dataflow for <code>n</code>
     * 
     * @param n
     *            AST node to compute the dataflow for
     * @param dfIn
     *            input dataflow item
     * @param graph
     *            dataflow graph
     * @param peer
     *            occurance of the dataflow node in the flowgraph
     * @return A map of exit edge keys to dataflow items
     */
    public Map<EdgeKey, VarContext<T>> flowAssign(Assign n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        throw new InternalCompilerError("Not yet implemented: " + n + " "
                + n.getClass() + " for analysis " + this.getClass().getName());
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowTypeNode(TypeNode n,
            VarContext<T> dfIn, FlowGraph<VarContext<T>> graph,
            Peer<VarContext<T>> peer) {
        throw new InternalCompilerError("Not yet implemented: " + n + " "
                + n.getClass() + " for analysis " + this.getClass().getName());
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowInstanceof(Instanceof n,
            VarContext<T> dfIn, FlowGraph<VarContext<T>> graph,
            Peer<VarContext<T>> peer) {
        throw new InternalCompilerError("Not yet implemented: " + n + " "
                + n.getClass() + " for analysis " + this.getClass().getName());
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowCast(Cast n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        throw new InternalCompilerError("Not yet implemented: " + n + " "
                + n.getClass() + " for analysis " + this.getClass().getName());
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowLocalDecl(LocalDecl n,
            VarContext<T> dfIn, FlowGraph<VarContext<T>> graph,
            Peer<VarContext<T>> peer) {
        throw new InternalCompilerError("Not yet implemented: " + n + " "
                + n.getClass() + " for analysis " + this.getClass().getName());
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowFormal(Formal n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        throw new InternalCompilerError("Not yet implemented: " + n + " "
                + n.getClass() + " for analysis " + this.getClass().getName());
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowLit(Lit n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        return flowExpr(n, dfIn, graph, peer);
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowEmpty(Empty n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        return flowStmt(n, dfIn, graph, peer);
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowLocalAssign(LocalAssign n,
            VarContext<T> dfIn, FlowGraph<VarContext<T>> graph,
            Peer<VarContext<T>> peer) {
        return flowAssign(n, dfIn, graph, peer);
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowArrayAccessAssign(
            ArrayAccessAssign n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        return flowAssign(n, dfIn, graph, peer);
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowFieldAssign(FieldAssign n,
            VarContext<T> dfIn, FlowGraph<VarContext<T>> graph,
            Peer<VarContext<T>> peer) {
        return flowAssign(n, dfIn, graph, peer);
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowFieldDecl(FieldDecl n,
            VarContext<T> dfIn, FlowGraph<VarContext<T>> graph,
            Peer<VarContext<T>> peer) {
        throw new InternalCompilerError("Not yet implemented: " + n + " "
                + n.getClass() + " for analysis " + this.getClass().getName());
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowArrayAccess(ArrayAccess n,
            VarContext<T> dfIn, FlowGraph<VarContext<T>> graph,
            Peer<VarContext<T>> peer) {
        return flowExpr(n, dfIn, graph, peer);
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowArrayInit(ArrayInit n,
            VarContext<T> dfIn, FlowGraph<VarContext<T>> graph,
            Peer<VarContext<T>> peer) {
        return flowExpr(n, dfIn, graph, peer);
    }

    /**
     * Compute the dataflow for <code>n</code>
     * 
     * @param n
     *            AST node to compute the dataflow for
     * @param dfIn
     *            input dataflow item
     * @param graph
     *            dataflow graph
     * @param peer
     *            occurance of the dataflow node in the flowgraph
     * @return A map of exit edge keys to dataflow items
     */
    public Map<EdgeKey, VarContext<T>> flowStmt(Stmt n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        throw new InternalCompilerError("Not yet implemented: " + n + " "
                + n.getClass() + " for analysis " + this.getClass().getName());
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowTry(Try n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        throw new InternalCompilerError("Not yet implemented: " + n + " "
                + n.getClass() + " for analysis " + this.getClass().getName());
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowCatch(Catch n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        throw new InternalCompilerError("Not yet implemented: " + n + " "
                + n.getClass() + " for analysis " + this.getClass().getName());
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowFinally(Block n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        throw new InternalCompilerError("Not yet implemented: " + n + " "
                + n.getClass() + " for analysis " + this.getClass().getName());
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowEval(Eval n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        return flowStmt(n, dfIn, graph, peer);
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowBranch(Branch n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        return flowStmt(n, dfIn, graph, peer);
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowLabeled(Labeled n,
            VarContext<T> dfIn, FlowGraph<VarContext<T>> graph,
            Peer<VarContext<T>> peer) {
        return flowStmt(n, dfIn, graph, peer);
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowExpr(Expr n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        throw new InternalCompilerError("Not yet implemented: " + n + " "
                + n.getClass() + " for analysis " + this.getClass().getName());
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowIf(If n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        return flowStmt(n, dfIn, graph, peer);
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowBlock(Block n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        return flowStmt(n, dfIn, graph, peer);
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowWhile(While n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        return flowStmt(n, dfIn, graph, peer);
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowDo(Do n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        return flowStmt(n, dfIn, graph, peer);
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowLocal(Local n, VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        return flowExpr(n, dfIn, graph, peer);
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowSuppress(Suppress n,
            VarContext<T> dfIn, FlowGraph<VarContext<T>> graph,
            Peer<VarContext<T>> peer) {
        return flowExpr(n, dfIn, graph, peer);
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowSynchronized(Synchronized n,
            VarContext<T> dfIn, FlowGraph<VarContext<T>> graph,
            Peer<VarContext<T>> peer) {
        return flowStmt(n, dfIn, graph, peer);
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowSpecial(Special n,
            VarContext<T> dfIn, FlowGraph<VarContext<T>> graph,
            Peer<VarContext<T>> peer) {
        return flowExpr(n, dfIn, graph, peer);
    }

    /**
     * Compute the dataflow for <code>n</code>
     * 
     * @param n
     *            AST node to compute the dataflow for
     * @param dfIn
     *            input dataflow item
     * @param graph
     *            dataflow graph
     * @param peer
     *            occurance of the dataflow node in the flowgraph
     * @return A map of exit edge keys to dataflow items
     */
    public Map<EdgeKey, VarContext<T>> flowCodeDecl(CodeDecl n,
            VarContext<T> dfIn, FlowGraph<VarContext<T>> graph,
            Peer<VarContext<T>> peer) {
        throw new InternalCompilerError("Not yet implemented: " + n + " "
                + n.getClass() + " for analysis " + this.getClass().getName());
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowMethodDecl(MethodDecl n,
            VarContext<T> dfIn, FlowGraph<VarContext<T>> graph,
            Peer<VarContext<T>> peer) {
        return flowCodeDecl(n, dfIn, graph, peer);
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowConstructorDecl(ConstructorDecl n,
            VarContext<T> dfIn, FlowGraph<VarContext<T>> graph,
            Peer<VarContext<T>> peer) {
        return flowCodeDecl(n, dfIn, graph, peer);
    }

    @Override
    public Map<EdgeKey, VarContext<T>> flowInitializer(Initializer n,
            VarContext<T> dfIn, FlowGraph<VarContext<T>> graph,
            Peer<VarContext<T>> peer) {
        return flowCodeDecl(n, dfIn, graph, peer);
    }

    @Override
    public Map<EdgeKey, VarContext<T>> ignoreNode(VarContext<T> dfIn,
            FlowGraph<VarContext<T>> graph, Peer<VarContext<T>> peer) {
        // default is that it is a NOP
        return itemToMap(dfIn, peer.succEdgeKeys());
    }
}
