/******************************************************************************
 * Copyright (c) 2002 - 2006 IBM Corporation.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *****************************************************************************/
/*
 * Created on Aug 22, 2005
 */
package com.ibm.wala.cast.java.translator.polyglot;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
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.ArrayTypeNode;
import polyglot.ast.Assert;
import polyglot.ast.Assign;
import polyglot.ast.Binary;
import polyglot.ast.Binary.Operator;
import polyglot.ast.Block;
import polyglot.ast.BooleanLit;
import polyglot.ast.Branch;
import polyglot.ast.Call;
import polyglot.ast.CanonicalTypeNode;
import polyglot.ast.Case;
import polyglot.ast.Cast;
import polyglot.ast.Catch;
import polyglot.ast.CharLit;
import polyglot.ast.ClassBody;
import polyglot.ast.ClassDecl;
import polyglot.ast.ClassLit;
import polyglot.ast.ClassMember;
import polyglot.ast.Conditional;
import polyglot.ast.ConstructorCall;
import polyglot.ast.ConstructorCall.Kind;
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.FloatLit;
import polyglot.ast.For;
import polyglot.ast.Formal;
import polyglot.ast.Id;
import polyglot.ast.If;
import polyglot.ast.Import;
import polyglot.ast.Initializer;
import polyglot.ast.Instanceof;
import polyglot.ast.IntLit;
import polyglot.ast.Labeled;
import polyglot.ast.Local;
import polyglot.ast.LocalAssign;
import polyglot.ast.LocalClassDecl;
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.NullLit;
import polyglot.ast.PackageNode;
import polyglot.ast.ProcedureDecl;
import polyglot.ast.Receiver;
import polyglot.ast.Return;
import polyglot.ast.SourceFile;
import polyglot.ast.Special;
import polyglot.ast.Stmt;
import polyglot.ast.StringLit;
import polyglot.ast.Switch;
import polyglot.ast.SwitchBlock;
import polyglot.ast.SwitchElement;
import polyglot.ast.Synchronized;
import polyglot.ast.Throw;
import polyglot.ast.TopLevelDecl;
import polyglot.ast.Try;
import polyglot.ast.Unary;
import polyglot.ast.While;
import polyglot.types.ArrayType;
import polyglot.types.ClassType;
import polyglot.types.CodeInstance;
import polyglot.types.ConstructorInstance;
import polyglot.types.Context;
import polyglot.types.FieldInstance;
import polyglot.types.Flags;
import polyglot.types.InitializerDef;
import polyglot.types.InitializerInstance;
import polyglot.types.MemberDef;
import polyglot.types.MethodInstance;
import polyglot.types.ObjectType;
import polyglot.types.ProcedureInstance;
import polyglot.types.SemanticException;
import polyglot.types.StructType;
import polyglot.types.Type;
import polyglot.types.TypeSystem;
import polyglot.types.Types;
import polyglot.util.Position;

import com.ibm.wala.cast.ir.translator.AstTranslator.InternalCAstSymbol;
import com.ibm.wala.cast.ir.translator.TranslatorToCAst;
import com.ibm.wala.cast.ir.translator.TranslatorToCAst.DoLoopTranslator;
import com.ibm.wala.cast.java.loader.Util;
import com.ibm.wala.cast.java.translator.JavaProcedureEntity;
import com.ibm.wala.cast.java.types.JavaType;
import com.ibm.wala.cast.tree.CAst;
import com.ibm.wala.cast.tree.CAstAnnotation;
import com.ibm.wala.cast.tree.CAstControlFlowMap;
import com.ibm.wala.cast.tree.CAstEntity;
import com.ibm.wala.cast.tree.CAstNode;
import com.ibm.wala.cast.tree.CAstNodeTypeMap;
import com.ibm.wala.cast.tree.CAstQualifier;
import com.ibm.wala.cast.tree.CAstSourcePositionMap;
import com.ibm.wala.cast.tree.CAstSymbol;
import com.ibm.wala.cast.tree.CAstType;
import com.ibm.wala.cast.tree.CAstTypeDictionary;
import com.ibm.wala.cast.tree.impl.AbstractSourcePosition;
import com.ibm.wala.cast.tree.impl.CAstControlFlowRecorder;
import com.ibm.wala.cast.tree.impl.CAstImpl;
import com.ibm.wala.cast.tree.impl.CAstNodeTypeMapRecorder;
import com.ibm.wala.cast.tree.impl.CAstOperator;
import com.ibm.wala.cast.tree.impl.CAstSourcePositionRecorder;
import com.ibm.wala.cast.tree.impl.CAstSymbolImpl;
import com.ibm.wala.classLoader.CallSiteReference;
import com.ibm.wala.shrikeBT.IInvokeInstruction;
import com.ibm.wala.types.ClassLoaderReference;
import com.ibm.wala.types.FieldReference;
import com.ibm.wala.types.MemberReference;
import com.ibm.wala.types.MethodReference;
import com.ibm.wala.types.Selector;
import com.ibm.wala.types.TypeName;
import com.ibm.wala.types.TypeReference;
import com.ibm.wala.util.collections.EmptyIterator;
import com.ibm.wala.util.collections.HashMapFactory;
import com.ibm.wala.util.collections.IteratorPlusOne;
import com.ibm.wala.util.collections.Pair;
import com.ibm.wala.util.debug.Assertions;

public class PolyglotJava2CAstTranslator {
  protected final CAst fFactory = new CAstImpl();

  protected final NodeFactory fNodeFactory;

  protected final TypeSystem fTypeSystem;

  protected Type fNPEType;

  protected Type fCCEType;

  protected Type fREType; // RuntimeException

  protected Type fDivByZeroType;

  protected final ClassLoaderReference fClassLoaderRef;

  private CAstTypeDictionary fTypeDict;

  private TranslatingVisitor fTranslator;

  protected PolyglotIdentityMapper fIdentityMapper;

  protected final DoLoopTranslator doLoopTranslator;
  
  protected final boolean DEBUG = true;
 
  private final Node ast;
 
  final protected TranslatingVisitor getTranslator() {
    if (fTranslator == null)
      fTranslator = createTranslator();
    return fTranslator;
  }

  protected TranslatingVisitor createTranslator() {
    return new JavaTranslatingVisitorImpl();
  }

  protected CAstTypeDictionary getTypeDict() {
    if (fTypeDict == null) {
      fTypeDict = createTypeDict();
    }
    return fTypeDict;
  }

  protected PolyglotTypeDictionary createTypeDict() {
    return new PolyglotTypeDictionary(fTypeSystem, this);
  }

  protected static CAstOperator mapUnaryOpcode(Unary.Operator operator) {
    if (operator.equals(Unary.BIT_NOT))
      return CAstOperator.OP_BITNOT;
    if (operator.equals(Unary.NEG))
      return CAstOperator.OP_SUB; // CAst will handle OP_SUB with only 1
    // arg properly !!! Hah!
    if (operator.equals(Unary.NOT))
      return CAstOperator.OP_NOT;
    if (operator.equals(Unary.POS))
      return CAstOperator.OP_ADD; // CAst will throw away OP_ADD with only
    // 1 arg!!!

    if (operator.equals(Unary.POST_DEC))
      return CAstOperator.OP_SUB; // translator will produce different
    // CAstNode types for post dec
    if (operator.equals(Unary.POST_INC))
      return CAstOperator.OP_ADD; // translator will produce different
    // CAstNode types for post inc
    if (operator.equals(Unary.PRE_DEC))
      return CAstOperator.OP_SUB; // translator will produce different
    // CAstNode types for pre dec
    if (operator.equals(Unary.PRE_INC))
      return CAstOperator.OP_ADD; // translator will produce different
    // CAstNode types for pre inc

    Assertions.UNREACHABLE("Java2CAstTranslator.JavaTranslatingVisitorImpl.mapUnaryOpcode(): unrecognized unary operator.");
    return null;
  }

  protected static CAstOperator mapBinaryOpcode(Binary.Operator operator) {
    if (operator.equals(Binary.ADD))
      return CAstOperator.OP_ADD;
    if (operator.equals(Binary.BIT_AND))
      return CAstOperator.OP_BIT_AND;
    if (operator.equals(Binary.BIT_OR))
      return CAstOperator.OP_BIT_OR;
    if (operator.equals(Binary.BIT_XOR))
      return CAstOperator.OP_BIT_XOR;
    if (operator.equals(Binary.COND_AND))
      return CAstOperator.OP_REL_AND;
    if (operator.equals(Binary.COND_OR))
      return CAstOperator.OP_REL_OR;
    if (operator.equals(Binary.DIV))
      return CAstOperator.OP_DIV;
    if (operator.equals(Binary.EQ))
      return CAstOperator.OP_EQ;
    if (operator.equals(Binary.GE))
      return CAstOperator.OP_GE;
    if (operator.equals(Binary.GT))
      return CAstOperator.OP_GT;
    if (operator.equals(Binary.LE))
      return CAstOperator.OP_LE;
    if (operator.equals(Binary.LT))
      return CAstOperator.OP_LT;
    if (operator.equals(Binary.MOD))
      return CAstOperator.OP_MOD;
    if (operator.equals(Binary.MUL))
      return CAstOperator.OP_MUL;
    if (operator.equals(Binary.NE))
      return CAstOperator.OP_NE;
    if (operator.equals(Binary.SHL))
      return CAstOperator.OP_LSH;
    if (operator.equals(Binary.SHR))
      return CAstOperator.OP_RSH;
    if (operator.equals(Binary.SUB))
      return CAstOperator.OP_SUB;
    if (operator.equals(Binary.USHR))
      return CAstOperator.OP_URSH;
    Assertions.UNREACHABLE("Java2CAstTranslator.JavaTranslatingVisitorImpl.mapBinaryOpcode(): unrecognized binary operator.");
    return null;
  }

  protected CAstNode translateConstant(Object constant) {
    return fFactory.makeConstant(constant);
  }

  protected class JavaTranslatingVisitorImpl implements TranslatingVisitor {
    @Override
    public CAstNode visit(MethodDecl m, MethodContext mc) {
      if (m.body() == null || m.body().statements().size() == 0)
        return makeNode(mc, fFactory, m, CAstNode.RETURN);
      else
        return walkNodes(m.body(), mc);
    }

    @Override
    public CAstNode visit(ConstructorDecl cd, MethodContext mc) {
      // Needs to examine the initializers in the ClassContext
      // and glue that code into the right place relative to the
      // constructor method body ("wherever that may turn out to be").
      List/* <FieldDecl|Initializer> */inits = mc.getInitializers();
      Block body = cd.body();

      if (hasSuperCall(body)) {
        // Split at call to super:
        // super();
        // field initializer code
        // remainder of ctor body
        CAstNode[] bodyNodes = new CAstNode[inits.size() + body.statements().size()];

        int idx = 0;
        for (Iterator iter = body.statements().iterator(); iter.hasNext();) {
          Stmt s = (Stmt) iter.next();

          bodyNodes[idx++] = walkNodes(s, mc);
          if (idx == 1) {
            Assertions.productionAssertion(isSpecialCallStmt(s, ConstructorCall.SUPER));
            idx = insertInitializers(mc, bodyNodes, false, idx);
          }
        }
        return makeNode(mc, fFactory, body, CAstNode.BLOCK_STMT, bodyNodes);
      } else if (hasThisCall(body)) {
        return walkNodes(body, mc);
      } else {
        // add explicit call to default super()
        // RMF 4/17/2009- The following search for a superClass default ctor
        // won't work if we process Object in source. In particular, the
        // superClass might be null. In that case, simply omit the explicit
        // super() call.
        ClassType superClass = (ClassType) ((ObjectType) cd.constructorDef().asInstance().container()).superClass();
        CAstNode[] bodyNodes;
        int idx= 0;
        int bodyInitsSize= inits.size() + body.statements().size();
        if (superClass != null) {
          ProcedureInstance defaultSuperCtor = findDefaultCtor(superClass);
          CallSiteReference callSiteRef = CallSiteReference.make(0, fIdentityMapper.getMethodRef(defaultSuperCtor),
              IInvokeInstruction.Dispatch.SPECIAL);
          CAstNode superCall = makeNode(mc, fFactory, cd, CAstNode.CALL,
              makeNode(mc, fFactory, cd, CAstNode.SUPER),
              fFactory.makeConstant(callSiteRef));
          bodyNodes = new CAstNode[bodyInitsSize + 1];
          bodyNodes[idx++] = superCall;
        } else { // no super class, so no super call
          bodyNodes = new CAstNode[bodyInitsSize];
        }
        idx= insertInitializers(mc, bodyNodes, false, idx);

        for (Iterator iter = body.statements().iterator(); iter.hasNext(); idx++) {
          Stmt s = (Stmt) iter.next();
          bodyNodes[idx] = walkNodes(s, mc);
        }
        return makeNode(mc, fFactory, body, CAstNode.BLOCK_STMT, bodyNodes);
      }
    }

    private ProcedureInstance findDefaultCtor(ClassType superClass) {
      List/* <ProcedureInstance> */ctors = superClass.constructors();
      for (Iterator iter = ctors.iterator(); iter.hasNext();) {
        ConstructorInstance ctor = (ConstructorInstance) iter.next();
        if (ctor.formalTypes().isEmpty())
          return ctor;
      }
      Assertions.UNREACHABLE("Couldn't find default ctor");
      return null;
    }

    @Override
    public CAstNode visit(FieldDecl f, MethodContext ctorContext) {
      // Generate CAST node for the initializer (init())
      // Type targetType = f.memberInstance().container();
      // Type fieldType = f.type().type();
      FieldReference fieldRef = fIdentityMapper.getFieldRef(f.fieldDef().asInstance());
      // We use null to indicate an OBJECT_REF to a static field, as the
      // FieldReference doesn't
      // hold enough info to determine this. In this case, (unlike field ref)
      // we don't have a
      // target expr to evaluate.
      CAstNode thisNode = f.flags().flags().isStatic() ? makeNode(ctorContext, fFactory, null, CAstNode.VOID) : makeNode(ctorContext,
          fFactory, f, CAstNode.THIS);
      CAstNode lhsNode = makeNode(ctorContext, fFactory, f, CAstNode.OBJECT_REF, thisNode, fFactory.makeConstant(fieldRef));

      Expr init = f.init();
      CAstNode rhsNode;
      if (init instanceof ArrayInit) {
        rhsNode = visit((ArrayInit) init, ctorContext, f.declType());
      } else {
        rhsNode = walkNodes(init, ctorContext);
      }
      CAstNode assNode = makeNode(ctorContext, fFactory, f, CAstNode.ASSIGN, lhsNode, rhsNode);

      return assNode;
    }

    @Override
    public CAstNode visit(Import i, WalkContext wc) {
      Assertions.UNREACHABLE("walkNodes.ASTTraverser.visit(Import)");
      return null;
    }

    @Override
    public CAstNode visit(PackageNode p, WalkContext wc) {
      Assertions.UNREACHABLE("walkNodes.ASTTraverser.visit(PackageNode)");
      return null;
    }

    @Override
    public CAstNode visit(CanonicalTypeNode ctn, WalkContext wc) {
      // We'll take care of this in its surrounding context...
      return makeNode(wc, fFactory, null, CAstNode.EMPTY);
    }

    @Override
    public CAstNode visit(ArrayTypeNode ctn, WalkContext wc) {
      // We'll take care of this in its surrounding context...
      Assertions.UNREACHABLE("walkNodes.ASTTraverser.visit(CanonicalTypeNode)");
      return null;
    }

    /*
     * If we've handled all the parent cases, this should never be called --
     * visit(ArrayInit,WalkContext,Type) should be instead.
     */
    @Override
    public CAstNode visit(ArrayInit ai, WalkContext wc) {
      if (((ArrayType) ai.type()).base().isNull()) {
        Assertions.productionAssertion(false, "bad type " + ai.type() + " for " + ai + " at " + ai.position());
      }

      TypeReference newTypeRef = fIdentityMapper.getTypeRef(ai.type());
      CAstNode[] eltNodes = new CAstNode[ai.elements().size() + 1];
      int idx = 0;

      eltNodes[idx++] = makeNode(wc, fFactory, ai, CAstNode.NEW, fFactory.makeConstant(newTypeRef), fFactory.makeConstant(ai
          .elements().size()));
      for (Iterator iter = ai.elements().iterator(); iter.hasNext(); idx++) {
        Expr element = (Expr) iter.next();
        if ( element instanceof ArrayInit ) {
          eltNodes[idx] = visit((ArrayInit)element, wc, ((ArrayType)ai.type()).base());
        } else {
          eltNodes[idx] = walkNodes(element, wc);
        }
        if (eltNodes[idx] == null) {
          Assertions.productionAssertion(eltNodes[idx] != null, element.toString());
        }
      }

      return makeNode(wc, fFactory, ai, CAstNode.ARRAY_LITERAL, eltNodes);
    }

    /*
     * Workaround for the null array init bug: just in case ai.type().base() is null (e.g.
     * "new Object[] {null}") we get the type from the parent (in this example, a
     * NewArray of type Object[])
     */
    public CAstNode visit(ArrayInit ai, WalkContext wc, Type t) {
      TypeReference newTypeRef = fIdentityMapper.getTypeRef(t);
      CAstNode[] eltNodes = new CAstNode[ai.elements().size() + 1];
      int idx = 0;

      eltNodes[idx++] = makeNode(wc, fFactory, ai, CAstNode.NEW, fFactory.makeConstant(newTypeRef), fFactory.makeConstant(ai
          .elements().size()));
      for (Iterator iter = ai.elements().iterator(); iter.hasNext(); idx++) {
        Expr element = (Expr) iter.next();
        if (element instanceof ArrayInit) {
          eltNodes[idx] = visit((ArrayInit) element, wc, ((ArrayType) t).base());
        } else {
          eltNodes[idx] = walkNodes(element, wc);
        }
        if (eltNodes[idx] == null) {
          Assertions.productionAssertion(eltNodes[idx] != null, element.toString());
        }
      }

      return makeNode(wc, fFactory, ai, CAstNode.ARRAY_LITERAL, eltNodes);
    }

    @Override
    public CAstNode visit(ArrayAccessAssign aaa, WalkContext wc) {
      return processAssign(aaa, wc);
    }

    @Override
    public CAstNode visit(FieldAssign fa, WalkContext wc) {
      return processAssign(fa, wc);
    }

    @Override
    public CAstNode visit(LocalAssign la, WalkContext wc) {
      return processAssign(la, wc);
    }

    protected CAstNode processAssign(Node assign, CAstNode lhsCAstNode, Assign.Operator operator, Expr rhs, WalkContext wc) {
      if (operator == Assign.ASSIGN) {
        return makeNode(wc, fFactory, assign, CAstNode.ASSIGN, lhsCAstNode, walkNodes(rhs, wc));
      } else {
        CAstNode op =
          makeNode(wc, 
              fFactory, 
              assign, 
              CAstNode.ASSIGN_PRE_OP,
              lhsCAstNode,
              walkNodes(rhs, wc),
              mapAssignOperator(operator));

        if (rhs.type().isLongOrLess() &&
            (mapAssignOperator(operator)==CAstOperator.OP_DIV ||
             mapAssignOperator(operator)==CAstOperator.OP_MOD))
        {
          Collection excTargets = wc.getCatchTargets(fDivByZeroType);
          if (!excTargets.isEmpty()) {
            for (Iterator iterator = excTargets.iterator(); iterator.hasNext();) {
              Pair catchPair = (Pair) iterator.next();
              wc.cfg().add(op, catchPair.snd, fDivByZeroType);
            }
          } else {
            wc.cfg().add(op, 
                CAstControlFlowMap.EXCEPTION_TO_EXIT, 
                fDivByZeroType);
          }
        }

        return op;
      }
    }

    private CAstNode processAssign(Assign a, WalkContext wc) {
      Expr lhs= a.left(fNodeFactory).type(a.type()); // PORT1.7 An Assign no longer has a lhs Expr per se; but you can ask it to materialize one...
      WalkContext lvc = new AssignmentContext(wc);

      return processAssign(a, walkNodes(lhs, lvc), a.operator(), a.right(), wc);
    }

    protected CAstOperator mapAssignOperator(Assign.Operator op) {
      if (op == Assign.ADD_ASSIGN)
        return CAstOperator.OP_ADD;
      else if (op == Assign.BIT_AND_ASSIGN)
        return CAstOperator.OP_BIT_AND;
      else if (op == Assign.BIT_OR_ASSIGN)
        return CAstOperator.OP_BIT_OR;
      else if (op == Assign.BIT_XOR_ASSIGN)
        return CAstOperator.OP_BIT_XOR;
      else if (op == Assign.DIV_ASSIGN)
        return CAstOperator.OP_DIV;
      else if (op == Assign.MOD_ASSIGN)
        return CAstOperator.OP_MOD;
      else if (op == Assign.MUL_ASSIGN)
        return CAstOperator.OP_MUL;
      else if (op == Assign.SHL_ASSIGN)
        return CAstOperator.OP_LSH;
      else if (op == Assign.SHR_ASSIGN)
        return CAstOperator.OP_RSH;
      else if (op == Assign.SUB_ASSIGN)
        return CAstOperator.OP_SUB;
      else if (op == Assign.USHR_ASSIGN)
        return CAstOperator.OP_URSH;
      Assertions.UNREACHABLE("Unknown assignment operator");
      return null;
    }

    @Override
    public CAstNode visit(Binary b, WalkContext wc) {
      Expr left = b.left();
      Expr right = b.right();
      Operator operator = b.operator();

      if (operator.equals(Binary.COND_AND))
        return makeNode(wc, fFactory, b, CAstNode.IF_EXPR, walkNodes(left, wc), walkNodes(right, wc), fFactory.makeConstant(false));
      else if (operator.equals(Binary.COND_OR))
        return makeNode(wc, fFactory, b, CAstNode.IF_EXPR, walkNodes(left, wc), fFactory.makeConstant(true), walkNodes(right, wc));
      else {
	Type leftType = left.type();
	Type rightType = right.type();
	if (leftType.isPrimitive() && rightType.isPrimitive()) {
	  CAstNode leftNode = walkNodes(left, wc);
	  CAstNode rightNode = walkNodes(right, wc);
	  
	  try {
	    Type result = fTypeSystem.promote(leftType, rightType);

	    if (! result.typeEquals(leftType, fTypeSystem.emptyContext())) {
	      leftNode = makeNode(wc, fFactory, b, CAstNode.CAST, fFactory.makeConstant(getTypeDict().getCAstTypeFor(result)), leftNode, fFactory.makeConstant(getTypeDict().getCAstTypeFor(leftType)));
	    }

	    if (! result.typeEquals(rightType, fTypeSystem.emptyContext())) {
	      rightNode = makeNode(wc, fFactory, b, CAstNode.CAST, fFactory.makeConstant(getTypeDict().getCAstTypeFor(result)), rightNode, fFactory.makeConstant(getTypeDict().getCAstTypeFor(rightType)));
	    }
	  } catch (SemanticException e) {

	  }

	  CAstNode op = makeNode(wc, fFactory, b, CAstNode.BINARY_EXPR, mapBinaryOpcode(operator), leftNode, rightNode);

	  if (leftType.isLongOrLess() &&
	      rightType.isLongOrLess() &&
	      (mapBinaryOpcode(operator)==CAstOperator.OP_DIV ||
	       mapBinaryOpcode(operator)==CAstOperator.OP_MOD))
	  {
	    Collection excTargets = wc.getCatchTargets(fDivByZeroType);
	    if (!excTargets.isEmpty()) {
	      for (Iterator iterator = excTargets.iterator(); 
		   iterator.hasNext();) 
	      {
		Pair catchPair = (Pair) iterator.next();
		wc.cfg().add(op, catchPair.snd, fDivByZeroType);
	      }
	    } else {
	      wc.cfg().add(op, 
			   CAstControlFlowMap.EXCEPTION_TO_EXIT, 
			   fDivByZeroType);
	    }
	  }

	  return op;
	  
	} else {
	  return makeNode(wc, fFactory, b, CAstNode.BINARY_EXPR, mapBinaryOpcode(operator), walkNodes(left, wc), walkNodes(right, wc));
	}
      }
    }

    @SuppressWarnings("unchecked")
    private void handleThrowsFromCall(ProcedureInstance procedureInstance, Node callAstNode, WalkContext wc) {
      List<Type> throwTypes = procedureInstance.throwTypes();
      for (Iterator<Type> iter = IteratorPlusOne.make(throwTypes.iterator(), fREType); iter.hasNext();) {
        Type thrownType = iter.next();
        Collection/* <Pair<Type,Node>> */catchTargets = wc.getCatchTargets(thrownType);

        for (Iterator targetIter = catchTargets.iterator(); targetIter.hasNext();) {
          Pair/* <Type,Node> */catchTarget = (Pair/* <Type,Node> */) targetIter.next();

          wc.cfg().add(callAstNode, catchTarget.snd, catchTarget.fst);
        }
      }
    }

    @Override
    public CAstNode visit(Call c, WalkContext wc) {
      MethodInstance methodInstance = c.methodInstance();
      boolean isStatic = methodInstance.flags().isStatic();
      StructType methodOwner = methodInstance.container();

      if (methodOwner.isArray()) {
        List realOne = methodInstance.overrides(fTypeSystem.emptyContext());
        Assertions.productionAssertion(realOne.size() == 2, "bad array method");
        methodInstance = (MethodInstance) realOne.get(1);
        methodOwner = methodInstance.container();
      }

      if (!methodOwner.isClass()) {
        Assertions.productionAssertion(false, "owner " + methodOwner + " of " + methodInstance + " is not a class");
      }

      boolean isIntf = ((ClassType) methodOwner).flags().isInterface();
      Receiver target = c.target();
      boolean isSpecial = methodInstance.flags().isPrivate()
          || (target instanceof Special && ((Special) target).kind() == Special.SUPER);

      CAstNode[] children = new CAstNode[2 + methodInstance.formalTypes().size()]; // including
      // the
      // MethodReference
      int i = 0;

      if (!isStatic)
        children[i++] = walkNodes(target, wc);
      else
        children[i++] = makeNode(wc, fFactory, null, CAstNode.VOID);

      if (children[0] == null) {
        Assertions.productionAssertion(children[0] != null, "no receiver for " + methodInstance + " in " + wc.getEnclosingMethod());
      }

      MethodReference methodRef = fIdentityMapper.getMethodRef(methodInstance);
      int dummyPC = 0; // Just want to wrap the kind of call; the "rear end"
      // won't care about anything else...
      CallSiteReference callSiteRef;

      if (isStatic)
        callSiteRef = CallSiteReference.make(dummyPC, methodRef, IInvokeInstruction.Dispatch.STATIC);
      else if (isIntf)
        callSiteRef = CallSiteReference.make(dummyPC, methodRef, IInvokeInstruction.Dispatch.INTERFACE);
      else if (isSpecial)
        callSiteRef = CallSiteReference.make(dummyPC, methodRef, IInvokeInstruction.Dispatch.SPECIAL);
      else
        callSiteRef = CallSiteReference.make(dummyPC, methodRef, IInvokeInstruction.Dispatch.VIRTUAL);

      children[i++] = fFactory.makeConstant(callSiteRef);
      for (Iterator iter = c.arguments().iterator(); iter.hasNext();) {
        Expr arg = (Expr) iter.next();
        children[i++] = walkNodes(arg, wc);
      }

      handleThrowsFromCall(methodInstance, c, wc);

      CAstNode result = makeNode(wc, fFactory, c, CAstNode.CALL, children);
      wc.cfg().map(c, result);
      return result;
    }

    @Override
    public CAstNode visit(ConstructorCall cc, WalkContext wc) {
      ConstructorInstance ctorInstance = cc.constructorInstance();
      StructType methodOwner = ctorInstance.container();
      Assertions.productionAssertion(methodOwner.isClass());
      MethodReference methodRef = fIdentityMapper.getMethodRef(ctorInstance);

      int dummyPC = 0; // Just want to wrap the kind of call; the "rear end"
      // won't care about anything else...
      CallSiteReference callSiteRef = CallSiteReference.make(dummyPC, methodRef, IInvokeInstruction.Dispatch.SPECIAL);

      CAstNode[] children = new CAstNode[1 + 1 + ctorInstance.formalTypes().size()]; // including
      // the
      // MethodReference
      int i = 0;

      CAstNode targetNode;

      targetNode = (cc.kind() == ConstructorCall.THIS) ? makeNode(wc, fFactory, cc, CAstNode.THIS) : makeNode(wc, fFactory, cc,
          CAstNode.SUPER);

      children[i++] = targetNode;
      children[i++] = fFactory.makeConstant(callSiteRef);
      for (Iterator iter = cc.arguments().iterator(); iter.hasNext();) {
        Expr arg = (Expr) iter.next();
        children[i++] = walkNodes(arg, wc);
      }

      handleThrowsFromCall(ctorInstance, cc, wc);

      CAstNode result = makeNode(wc, fFactory, cc, CAstNode.CALL, children);
      wc.cfg().map(cc, result);
      return result;
    }

    @Override
    public CAstNode visit(Cast c, WalkContext wc) {
      Expr arg = c.expr();
      Type castedFrom = arg.type();
      Type castedTo = c.castType().type();

      // null can go into anything (e.g. in "((Foobar) null)" null can be assumed to be of type Foobar already) 
      if (castedFrom.isNull())
        castedFrom = castedTo;
      
      CAstNode ast = makeNode(wc, fFactory, c, CAstNode.CAST, fFactory.makeConstant(getTypeDict().getCAstTypeFor(castedTo)),
          walkNodes(arg, wc),
	  fFactory.makeConstant(getTypeDict().getCAstTypeFor(castedFrom)));

      Collection excTargets = wc.getCatchTargets(fCCEType);
      if (!excTargets.isEmpty()) {
        // connect ClassCastException exception edge to relevant catch targets
        // (presumably only one)
        for (Iterator iterator = excTargets.iterator(); iterator.hasNext();) {
          Pair catchPair = (Pair) iterator.next();
          wc.cfg().add(c, catchPair.snd, fCCEType);
        }
      } else {
        // connect exception edge to exit
        wc.cfg().add(c, CAstControlFlowMap.EXCEPTION_TO_EXIT, fCCEType);
      }

      wc.cfg().map(c, ast);
      return ast;
    }

    @Override
    public CAstNode visit(Conditional c, WalkContext wc) {
      return makeNode(wc, fFactory, c, CAstNode.IF_EXPR, walkNodes(c.cond(), wc), walkNodes(c.consequent(), wc), walkNodes(c
          .alternative(), wc));
    }

    @Override
    public CAstNode visit(Instanceof io, WalkContext wc) {
      return makeNode(wc, fFactory, io, CAstNode.INSTANCEOF, fFactory.makeConstant(getTypeDict().getCAstTypeFor(
          io.compareType().type())), walkNodes(io.expr(), wc));
    }

    @Override
    public CAstNode visit(BooleanLit bl, WalkContext wc) {
      return fFactory.makeConstant(bl.value());
    }

    @Override
    public CAstNode visit(ClassLit cl, WalkContext wc) {
      Type litType = cl.typeNode().type();
      String typeName = fIdentityMapper.typeToTypeID(litType);
      return makeNode(wc, fFactory, cl, CAstNode.TYPE_LITERAL_EXPR, fFactory.makeConstant(typeName));
    }

    @Override
    public CAstNode visit(FloatLit fl, WalkContext wc) {
      return (fl.kind() == FloatLit.FLOAT) ? fFactory.makeConstant((float) fl.value()) : fFactory.makeConstant(fl.value());
    }

    @Override
    public CAstNode visit(NullLit nl, WalkContext wc) {
      return fFactory.makeConstant(null);
    }

    @Override
    public CAstNode visit(CharLit cl, WalkContext wc) {
      return fFactory.makeConstant(cl.value());
    }

    @Override
    public CAstNode visit(IntLit il, WalkContext wc) {
      return fFactory.makeConstant((int) il.value());
    }

    @Override
    public CAstNode visit(StringLit sl, WalkContext wc) {
      return fFactory.makeConstant(sl.value());
    }

    @Override
    public CAstNode visit(New n, WalkContext wc) {
      CAstEntity anonClass = null;
      String newTypeNameStr;
      TypeReference newTypeRef;
      ConstructorInstance ctorInst = n.constructorInstance();
      MethodReference ctorRef = fIdentityMapper.getMethodRef(ctorInst);

      if (n.body() != null) {
        fIdentityMapper.mapLocalAnonTypeToMethod((ClassType) n.type(), wc.getEnclosingMethod());

        anonClass = walkEntity(n, wc);

        newTypeNameStr = anonClass.getType().getName();
        TypeName newTypeName = TypeName.string2TypeName(newTypeNameStr);
        Selector ctorSel = ctorRef.getSelector();
        newTypeRef = TypeReference.findOrCreate(fClassLoaderRef, newTypeName);
        ctorRef = MethodReference.findOrCreate(newTypeRef, ctorSel);
      } else {
        newTypeRef = fIdentityMapper.getTypeRef(n.type());
      }

      List/* <Expr> */args = n.arguments();
      String tmpName = "ctor temp"; // this name is an illegal Java
      // identifier

      // new nodes with an explicit enclosing argument, e.g. "outer.new Inner()". They are mostly treated the same, except in JavaCAst2IRTranslator.doNewObject
      CAstNode newNode;
      Expr enclosing = n.qualifier();
      if (enclosing != null) {
        CAstNode encNode = walkNodes(enclosing, wc);
        newNode = makeNode(wc, fFactory, n, CAstNode.NEW_ENCLOSING, fFactory.makeConstant(newTypeRef), encNode);
      }
      else 
        newNode = makeNode(wc, fFactory, n, CAstNode.NEW, fFactory.makeConstant(newTypeRef));
      // end enclosing new stuff

      if (n.body() != null)
        wc.addScopedEntity(newNode, anonClass);

      int dummyPC = 0; // Just want to wrap the kind of call; the "rear end"
      // won't care about anything else...
      CallSiteReference callSiteRef = CallSiteReference.make(dummyPC, ctorRef, IInvokeInstruction.Dispatch.SPECIAL);

      CAstNode[] argNodes = new CAstNode[args.size() + 2]; // args + recvr
      // + ctor ref

      int idx = 0;
      argNodes[idx++] = makeNode(wc, fFactory, n, CAstNode.VAR, fFactory.makeConstant(tmpName));
      argNodes[idx++] = fFactory.makeConstant(callSiteRef);
      for (Iterator iter = args.iterator(); iter.hasNext();) {
        Expr arg = (Expr) iter.next();
        argNodes[idx++] = walkNodes(arg, wc);
      }
      CAstNode callNode = makeNode(wc, fFactory, n, CAstNode.CALL, argNodes);
      wc.cfg().map(n, callNode);

      handleThrowsFromCall(ctorInst, n, wc);

      return makeNode(wc, fFactory, n, CAstNode.LOCAL_SCOPE, makeNode(wc, fFactory, n, CAstNode.BLOCK_EXPR, makeNode(wc, fFactory,
          n, CAstNode.DECL_STMT, fFactory.makeConstant(new InternalCAstSymbol(tmpName, getTypeDict().getCAstTypeFor(n.type()), true)), newNode), callNode, makeNode(wc,
          fFactory, n, CAstNode.VAR, fFactory.makeConstant(tmpName))));
    }

    @Override
    public CAstNode visit(NewArray na, WalkContext wc) {
      Type newType = na.type();
      ArrayInit ai = na.init();
      Assertions.productionAssertion(newType.isArray());

      if (ai != null) {
        return visit(ai, wc, newType);
      } else {
        ArrayType arrayType = (ArrayType) newType;
        TypeReference arrayTypeRef = fIdentityMapper.getTypeRef(arrayType);

        List/* <Expr> */dims = na.dims();
        CAstNode[] args = new CAstNode[dims.size() + 1];

        int idx = 0;
        args[idx++] = fFactory.makeConstant(arrayTypeRef);
        for (Iterator iter = dims.iterator(); iter.hasNext();) {
          Expr dimExpr = (Expr) iter.next();
          args[idx++] = walkNodes(dimExpr, wc);
        }
        return makeNode(wc, fFactory, na, CAstNode.NEW, args);
      }
    }

    @Override
    public CAstNode visit(Special s, WalkContext wc) {
      if (s.qualifier() != null) {
        Type owningType = s.qualifier().type();
        TypeReference owningTypeRef = fIdentityMapper.getTypeRef(owningType);

        return makeNode(wc, fFactory, s, s.kind() == Special.THIS ? CAstNode.THIS : CAstNode.SUPER, fFactory
            .makeConstant(owningTypeRef));
      } else {
        return makeNode(wc, fFactory, s, s.kind() == Special.THIS ? CAstNode.THIS : CAstNode.SUPER);
      }
    }

    @Override
    public CAstNode visit(Unary u, WalkContext wc) {
      if (isAssignOp(u.operator())) {
        WalkContext lvc = new AssignmentContext(wc);
        if (u.operator().isPrefix())
          return makeNode(wc, fFactory, u, CAstNode.ASSIGN_PRE_OP, walkNodes(u.expr(), lvc), fFactory.makeConstant(1),
              mapUnaryOpcode(u.operator()));
        else
          return makeNode(wc, fFactory, u, CAstNode.ASSIGN_POST_OP, walkNodes(u.expr(), lvc), fFactory.makeConstant(1),
              mapUnaryOpcode(u.operator()));
      } else if (u.operator() == Unary.POS) // drop useless unary plus
        // operator
        return walkNodes(u.expr(), wc);
      else if (u.operator() == Unary.NEG) {
        CAstNode zero;
        if (u.expr().type().isLongOrLess())
          zero = fFactory.makeConstant(0L);
        else
          zero = fFactory.makeConstant(0.0);
        return makeNode(wc, fFactory, u, CAstNode.BINARY_EXPR, CAstOperator.OP_SUB, zero, walkNodes(u.expr(), wc));
      } else
        return makeNode(wc, fFactory, u, CAstNode.UNARY_EXPR, mapUnaryOpcode(u.operator()), walkNodes(u.expr(), wc));
    }

    protected boolean isAssignOp(Unary.Operator operator) {
      return operator == Unary.POST_DEC || operator == Unary.POST_INC || operator == Unary.PRE_DEC || operator == Unary.PRE_INC;
    }

    @Override
    public CAstNode visit(ArrayAccess aa, WalkContext wc) {
      TypeReference eltTypeRef = fIdentityMapper.getTypeRef(aa.type());

      hookUpNPETargets(aa, wc);

      CAstNode n = makeNode(wc, fFactory, aa, CAstNode.ARRAY_REF, walkNodes(aa.array(), wc), fFactory.makeConstant(eltTypeRef),
          walkNodes(aa.index(), wc));
      
      wc.cfg().map(aa, n);
      
      return n;
    }

    protected void hookUpNPETargets(Node n, WalkContext wc) {
      Collection excTargets = wc.getCatchTargets(fNPEType);
      if (!excTargets.isEmpty()) {
        // connect NPE exception edge to relevant catch targets
        // (presumably only one)
        for (Iterator iterator = excTargets.iterator(); iterator.hasNext();) {
          Pair catchPair = (Pair) iterator.next();
          wc.cfg().add(n, catchPair.snd, fNPEType);
        }
      } else {
        // connect exception edge to exit
        wc.cfg().add(n, CAstControlFlowMap.EXCEPTION_TO_EXIT, fNPEType);
      }
    }

    @Override
    public CAstNode visit(Field f, WalkContext wc) {
      Receiver target = f.target();
      Type targetType = target.type();

      if (targetType.isArray()) {
        Assertions.productionAssertion(f.name().toString().equals("length"));

        return makeNode(wc, fFactory, f, CAstNode.ARRAY_LENGTH, walkNodes(target, wc));
      }
      FieldInstance fi = f.fieldInstance();
      FieldReference fieldRef = fIdentityMapper.getFieldRef(fi);
      CAstNode targetNode = walkNodes(target, wc);

      if (fi.flags().isStatic()) {
        // JLS says: evaluate the target of the field ref and throw it away.
        // Hence the following block expr, whose 2 children are the target
        // evaluation
        // followed by the OBJECT_REF with a null target child (which the
        // "back-end"
        // CAst -> IR translator interprets as a static ref).
        if (fi.isConstant()) {
          return makeNode(wc, fFactory, f, CAstNode.BLOCK_EXPR, targetNode, // can have side effects!
              translateConstant(fi.constantValue()));
        } else {
          return makeNode(wc, fFactory, f, CAstNode.BLOCK_EXPR, targetNode, makeNode(wc, fFactory, f, CAstNode.OBJECT_REF,
              makeNode(wc, fFactory, null, CAstNode.VOID), fFactory.makeConstant(fieldRef)));
        }
      } else {
        Collection excTargets = wc.getCatchTargets(fNPEType);
        if (!excTargets.isEmpty()) {
          // connect NPE exception edge to relevant catch targets
          // (presumably only one)
          for (Iterator iterator = excTargets.iterator(); iterator.hasNext();) {
            Pair catchPair = (Pair) iterator.next();
            wc.cfg().add(f, catchPair.snd, fNPEType);
          }
        } else {
          // connect exception edge to exit
          wc.cfg().add(f, CAstControlFlowMap.EXCEPTION_TO_EXIT, fNPEType);
        }
        CAstNode refNode = makeNode(wc, fFactory, f, CAstNode.OBJECT_REF, targetNode, fFactory.makeConstant(fieldRef));

        wc.cfg().map(f, refNode);

        if (fi.isConstant()) {
          return makeNode(wc, fFactory, f, CAstNode.BLOCK_EXPR, refNode, // can have side effects!
              translateConstant(fi.constantValue()));
        } else {
          return refNode;
        }
      }
    }

    @Override
    public CAstNode visit(Local l, WalkContext wc) {
      return makeNode(wc, fFactory, l, CAstNode.VAR, fFactory.makeConstant(l.name().id().toString()));
    }

    @Override
    public CAstNode visit(ClassBody cb, WalkContext wc) {
      Assertions.UNREACHABLE("walkNodes.ASTTraverser.visit(ClassBody)");
      return null;
    }

    @Override
    public CAstNode visit(ClassDecl cd, WalkContext wc) {
      Assertions.UNREACHABLE("walkNodes.ASTTraverser.visit(ClassDecl)");
      return null;
    }

    @Override
    public CAstNode visit(Initializer i, WalkContext wc) {
      // Perhaps this is invoked from within the ConstructorDecl visit()
      // method...
      return walkNodes(i.body(), wc);
    }

    @Override
    public CAstNode visit(Assert a, WalkContext wc) {
      return PolyglotJava2CAstTranslator.this.makeNode(wc, fFactory, a, CAstNode.ASSERT, walkNodes(a.cond(), wc));
    }

    @Override
    public CAstNode visit(Branch b, WalkContext wc) {
      Node target = null;
      Id labelNode = b.labelNode();
      String labelStr = labelNode != null ? labelNode.id().toString() : null;
      if (b.kind() == Branch.BREAK) {
        target = wc.getBreakFor(labelStr);
      } else {
        target = wc.getContinueFor(labelStr);
      }

      Assertions.productionAssertion(target != null);

      CAstNode result = makeNode(wc, fFactory, b, CAstNode.GOTO);

      wc.cfg().map(b, result);
      wc.cfg().add(b, target, null);

      return result;
    }

    @Override
    public CAstNode visit(Block b, WalkContext wc) {
      CAstNode[] stmtNodes = new CAstNode[b.statements().size()];

      int idx = 0;
      for (Iterator iter = b.statements().iterator(); iter.hasNext(); idx++) {
        Stmt s = (Stmt) iter.next();
        stmtNodes[idx] = walkNodes(s, wc);
      }
      return makeNode(wc, fFactory, b, CAstNode.LOCAL_SCOPE, makeNode(wc, fFactory, b, CAstNode.BLOCK_STMT, stmtNodes));
    }

    @Override
    public CAstNode visit(SwitchBlock sb, WalkContext wc) {
      CAstNode[] stmtNodes = new CAstNode[sb.statements().size()];

      int idx = 0;
      for (Iterator iter = sb.statements().iterator(); iter.hasNext(); idx++) {
        Stmt s = (Stmt) iter.next();
        stmtNodes[idx] = walkNodes(s, wc);
      }
      return makeNode(wc, fFactory, sb, CAstNode.BLOCK_STMT, stmtNodes);
    }

    @Override
    public CAstNode visit(Catch c, WalkContext wc) {
      Block body = c.body();
      Formal f = c.formal();

      CAstNode excDecl = makeNode(wc, fFactory, c, CAstNode.CATCH, fFactory.makeConstant(f.name().id().toString()), walkNodes(body, wc));
      CAstNode localScope = makeNode(wc, fFactory, c, CAstNode.LOCAL_SCOPE, excDecl);

      wc.cfg().map(c, excDecl);
      wc.getNodeTypeMap().add(excDecl, wc.getTypeDictionary().getCAstTypeFor(c.catchType()));
      return localScope;
    }

    @Override
    public CAstNode visit(If i, WalkContext wc) {
      return makeNode(wc, fFactory, i, CAstNode.IF_STMT, walkNodes(i.cond(), wc), walkNodes(i.consequent(), wc), walkNodes(i
          .alternative(), wc));
    }

    @Override
    public CAstNode visit(Labeled l, WalkContext wc) {
      Node breakTarget = makeBreakTarget(l);

      Node stmt = l.statement();
      while (stmt instanceof Block) {
        stmt = ((Block) stmt).statements().iterator().next();
      }

      wc.getLabelMap().put(stmt, l.labelNode().id().toString());

      CAstNode result;
      if (! (l.statement() instanceof Empty)) {
	WalkContext child = new BreakContext(wc, l.labelNode().id().toString(), breakTarget);

	result = 
	  makeNode(wc, fFactory, l, CAstNode.BLOCK_STMT, 
	    makeNode(wc, fFactory, l, CAstNode.LABEL_STMT, fFactory.makeConstant(l.labelNode().id().toString()), walkNodes(l.statement(), child)),
	    walkNodes(breakTarget, wc));
      } else {
	result = 
	  makeNode(wc, fFactory, l, CAstNode.LABEL_STMT, fFactory.makeConstant(l.labelNode().id().toString()), walkNodes(l.statement(), wc));
      }

      wc.cfg().map(l, result);

      wc.getLabelMap().remove(stmt);

      return result;
    }

    @Override
    public CAstNode visit(LocalClassDecl lcd, WalkContext wc) {
      fIdentityMapper.mapLocalAnonTypeToMethod(lcd.decl().classDef().asType(), wc.getEnclosingMethod());

      CAstEntity classEntity = walkEntity(lcd.decl(), wc);

      final CAstNode lcdNode = makeNode(wc, fFactory, lcd, CAstNode.EMPTY);

      wc.addScopedEntity(lcdNode, classEntity);
      return lcdNode;
    }

    protected Node makeBreakTarget(Node loop) {
      return fNodeFactory.Labeled(Position.COMPILER_GENERATED,
              fNodeFactory.Id(Position.COMPILER_GENERATED, "breakLabel" + loop.position().toString().replace('.', '_')),
              fNodeFactory.Empty(Position.COMPILER_GENERATED));
    }

    protected Node makeContinueTarget(Node loop) {
      return fNodeFactory.Labeled(Position.COMPILER_GENERATED,
              fNodeFactory.Id(Position.COMPILER_GENERATED, "continueLabel" + loop.position().toString().replace('.', '_')),
          fNodeFactory.Empty(Position.COMPILER_GENERATED));
    }

    @Override
    public CAstNode visit(Do d, WalkContext wc) {
      Node breakTarget = makeBreakTarget(d);
      Node continueTarget = makeContinueTarget(d);

      String loopLabel = wc.getLabelMap().get(d);

      CAstNode continueNode = walkNodes(continueTarget, wc);
      CAstNode breakNode = walkNodes(breakTarget, wc);

      
      WalkContext lc = new LoopContext(wc, loopLabel, breakTarget, continueTarget);
      CAstNode loopExpr = walkNodes(d.cond(), wc);
      CAstNode loopBody = walkNodes(d.body(), lc);
 
      return doLoopTranslator.translateDoLoop(loopExpr, loopBody, continueNode, breakNode, wc);
    }

    
    @Override
    public CAstNode visit(For f, WalkContext wc) {
      Node breakTarget = makeBreakTarget(f);
      Node continueTarget = makeContinueTarget(f);
      String loopLabel = wc.getLabelMap().get(f);
      WalkContext lc = new LoopContext(wc, loopLabel, breakTarget, continueTarget);

      CAstNode[] inits = new CAstNode[f.inits().size()];
      for (int i = 0; i < inits.length; i++) {
        inits[i] = walkNodes(f.inits().get(i), wc);
      }

      CAstNode[] iters = new CAstNode[f.iters().size()];
      for (int i = 0; i < iters.length; i++) {
        iters[i] = walkNodes(f.iters().get(i), wc);
      }

      CAstNode initsBlock = makeNode(wc, fFactory, f, CAstNode.BLOCK_STMT, inits);
      CAstNode itersBlock = makeNode(wc, fFactory, f, CAstNode.BLOCK_STMT, iters);

      return makeNode(wc, fFactory, f, CAstNode.BLOCK_STMT, initsBlock, makeNode(wc, fFactory, f, CAstNode.LOOP, walkNodes(
          f.cond(), wc), makeNode(wc, fFactory, f, CAstNode.BLOCK_STMT, walkNodes(f.body(), lc), walkNodes(continueTarget, wc),
          itersBlock)), walkNodes(breakTarget, wc));

    }

    @Override
    public CAstNode visit(While w, WalkContext wc) {
      Expr c = w.cond();
      Stmt b = w.body();

      Node breakTarget = makeBreakTarget(w);
      Node continueTarget = makeContinueTarget(w);
      String loopLabel = wc.getLabelMap().get(w);
      LoopContext lc = new LoopContext(wc, loopLabel, breakTarget, continueTarget);

      /*
       * The following loop is created sligtly differently than in jscore. It doesn't have a specific target for continue.
       */
      return makeNode(wc, fFactory, w, CAstNode.BLOCK_STMT, makeNode(wc, fFactory, w, CAstNode.LOOP, walkNodes(c, wc), makeNode(wc,
          fFactory, w, CAstNode.BLOCK_STMT, walkNodes(b, lc), walkNodes(continueTarget, wc))), walkNodes(breakTarget, wc));
    }

    @Override
    public CAstNode visit(Switch s, WalkContext wc) {
	Node breakLabel = fNodeFactory.Labeled(s.position(),
	        fNodeFactory.Id(s.position(), "switchBreakLabel" + s.position().toString().replace('.', '_')),
	        fNodeFactory.Empty(s.position()));
      CAstNode breakAst = walkNodes(breakLabel, wc);
      String loopLabel = wc.getLabelMap().get(s);
      WalkContext child = new BreakContext(wc, loopLabel, breakLabel);
      Expr cond = s.expr();
      List cases = s.elements();

      // First compute the control flow edges for the various case labels
      for (int i = 0; i < cases.size(); i++) {
        SwitchElement se = (SwitchElement) cases.get(i);
        if (se instanceof Case) {
          Case c = (Case) se;

          if (c.isDefault())
            wc.cfg().add(s, c, CAstControlFlowMap.SWITCH_DEFAULT);
          else
            wc.cfg().add(s, c, fFactory.makeConstant(c.value()));
        }
      }
      CAstNode[] caseNodes = new CAstNode[cases.size()];

      // Now produce the CAst representation for each case
      int idx = 0;
      for (Iterator iter = cases.iterator(); iter.hasNext(); idx++) {
        SwitchElement se = (SwitchElement) iter.next();

        caseNodes[idx] = walkNodes(se, child);
      }

      // Now produce the switch stmt itself
      CAstNode switchAst = makeNode(wc, fFactory, s, CAstNode.SWITCH, walkNodes(cond, wc), makeNode(wc, fFactory, s,
          CAstNode.BLOCK_STMT, caseNodes));

      wc.cfg().map(s, switchAst);

      // Finally, wrap the entire switch in a block so that we have a
      // well-defined place to 'break' to.
      return makeNode(wc, fFactory, s, CAstNode.BLOCK_STMT, switchAst, breakAst);
    }

    @Override
    public CAstNode visit(Synchronized s, WalkContext wc) {
      CAstNode exprNode = walkNodes(s.expr(), wc);
      String exprName = fFactory.makeUnique();
      CAstNode declStmt = makeNode(wc, fFactory, s, CAstNode.DECL_STMT, fFactory.makeConstant(new CAstSymbolImpl(exprName, getTypeDict().getCAstTypeFor(s.expr().type()), true)),
          exprNode);
      CAstNode monitorEnterNode = makeNode(wc, fFactory, s, CAstNode.MONITOR_ENTER, makeNode(wc, fFactory, s, CAstNode.VAR,
          fFactory.makeConstant(exprName)));
      CAstNode bodyNodes = walkNodes(s.body(), wc);
      CAstNode monitorExitNode = makeNode(wc, fFactory, s, CAstNode.MONITOR_EXIT, makeNode(wc, fFactory, s, CAstNode.VAR, fFactory
          .makeConstant(exprName)));
      CAstNode tryBody = makeNode(wc, fFactory, s, CAstNode.BLOCK_STMT, monitorEnterNode, bodyNodes);
      CAstNode bigBody = makeNode(wc, fFactory, s, CAstNode.UNWIND, tryBody, monitorExitNode);

      return makeNode(wc, fFactory, s, CAstNode.BLOCK_STMT, declStmt, bigBody);
    }

    @Override
    public CAstNode visit(Try t, WalkContext wc) {
      List catchBlocks = t.catchBlocks();
      Block finallyBlock = t.finallyBlock();
      Block tryBlock = t.tryBlock();

      // try/finally
      if (catchBlocks.isEmpty()) {
        return makeNode(wc, fFactory, t, CAstNode.UNWIND, walkNodes(tryBlock, wc), walkNodes(finallyBlock, wc));

        // try/catch/[finally]
      } else {
        TryCatchContext tc = new TryCatchContext(wc, t, fTypeSystem.emptyContext());

        CAstNode tryNode = walkNodes(tryBlock, tc);
        for (Iterator iter = catchBlocks.iterator(); iter.hasNext();) {
          tryNode = makeNode(wc, fFactory, t, CAstNode.TRY, tryNode, walkNodes((Catch) iter.next(), wc));
        }

        // try/catch
        if (finallyBlock == null) {
          return tryNode;

          // try/catch/finally
        } else {
          return makeNode(wc, fFactory, t, CAstNode.UNWIND, tryNode, walkNodes(finallyBlock, wc));
        }
      }
    }

    @Override
    public CAstNode visit(Empty e, WalkContext wc) {
      CAstNode result = makeNode(wc, fFactory, e, CAstNode.EMPTY);
      wc.cfg().map(e, result);
      return result;
    }

    @Override
    public CAstNode visit(Eval e, WalkContext wc) {
      return walkNodes(e.expr(), wc);
    }

    @Override
    public CAstNode visit(LocalDecl ld, WalkContext wc) {
      Expr init = ld.init();
      Type type = ld.declType();
      CAstNode initNode;
      if (init == null) {
        if (type.isLongOrLess())
          initNode = fFactory.makeConstant(0);
        else if (type.isDouble() || type.isFloat())
          initNode = fFactory.makeConstant(0.0);
        else
          initNode = fFactory.makeConstant(null);
      } else if (init instanceof ArrayInit)
        initNode = visit((ArrayInit) init, wc, type); 
      else
          initNode = walkNodes(init, wc);

      Object defaultValue;
      if (type.isLongOrLess())
        defaultValue = new Integer(0);
      else if (type.isDouble() || type.isFloat())
        defaultValue = new Double(0.0);
      else
        defaultValue = CAstSymbol.NULL_DEFAULT_VALUE;

      boolean isFinal = ld.flags().flags().isFinal();

      return makeNode(wc, fFactory, ld, CAstNode.DECL_STMT, fFactory.makeConstant(new CAstSymbolImpl(ld.name().id().toString(), getTypeDict().getCAstTypeFor(type), isFinal,
          defaultValue)), initNode);
    }

    @Override
    public CAstNode visit(Return r, WalkContext wc) {
      Expr retExpr = r.expr();
      if (retExpr == null)
        return makeNode(wc, fFactory, r, CAstNode.RETURN);
      else
        return makeNode(wc, fFactory, r, CAstNode.RETURN, walkNodes(retExpr, wc));
    }

    @Override
    public CAstNode visit(Case c, WalkContext wc) {
      CAstNode label = makeNode(wc, fFactory, c, CAstNode.LABEL_STMT, fFactory.makeConstant(c.value()));

      wc.cfg().map(c, label);
      return label;
    }

    @Override
    public CAstNode visit(Throw t, WalkContext wc) {
      CAstNode result = makeNode(wc, fFactory, t, CAstNode.THROW, walkNodes(t.expr(), wc));
      Type label = t.expr().type();

      wc.cfg().map(t, result);

      Collection/* <Pair<Type,Node>> */catchNodes = wc.getCatchTargets(label);

      for (Iterator iter = catchNodes.iterator(); iter.hasNext();) {
        Pair/* <Type,Node> */catchNode = (Pair/* <Type,Node> */) iter.next();

        wc.cfg().add(t, catchNode.snd, catchNode.fst);
      }

      return result;
    }

    @Override
    public CAstNode visit(Formal f, WalkContext wc) {
      return makeNode(wc, fFactory, f, CAstNode.VAR, fFactory.makeConstant(f.name().id().toString()));
    }
  }

  protected static final class CompilationUnitEntity implements CAstEntity {
    private final String fName;

    private final Collection<CAstEntity> fTopLevelDecls;

    public CompilationUnitEntity(SourceFile file, List<CAstEntity> topLevelDecls) {
      fName = (file.package_() == null) ? "" : file.package_().package_().get().fullName().toString().replace('.', '/');
      fTopLevelDecls = topLevelDecls;
    }

    @Override
    public int getKind() {
      return FILE_ENTITY;
    }

    @Override
    public String getName() {
      return fName;
    }

    @Override
    public String getSignature() {
      Assertions.UNREACHABLE();
      return null;
    }

    @Override
    public String[] getArgumentNames() {
      return new String[0];
    }

    @Override
    public CAstNode[] getArgumentDefaults() {
      return new CAstNode[0];
    }

    @Override
    public int getArgumentCount() {
      return 0;
    }

    @Override
    public Map<CAstNode, Collection<CAstEntity>> getAllScopedEntities() {
      return Collections.singletonMap(null, fTopLevelDecls);
    }

    @Override
    public Iterator<CAstEntity> getScopedEntities(CAstNode construct) {
      Assertions.UNREACHABLE("CompilationUnitEntity asked for AST-related entities, but it has no AST.");
      return null;
    }

    @Override
    public CAstNode getAST() {
      return null;
    }

    @Override
    public Collection<CAstAnnotation> getAnnotations() {
        return null;
    }

    @Override
    public CAstControlFlowMap getControlFlow() {
      Assertions.UNREACHABLE("CompilationUnitEntity.getControlFlow()");
      return null;
    }

    @Override
    public CAstSourcePositionMap getSourceMap() {
      Assertions.UNREACHABLE("CompilationUnitEntity.getSourceMap()");
      return null;
    }

    @Override
    public CAstSourcePositionMap.Position getPosition() {
      return null;
    }

    @Override
    public CAstNodeTypeMap getNodeTypeMap() {
      Assertions.UNREACHABLE("CompilationUnitEntity.getNodeTypeMap()");
      return null;
    }

    @Override
    public Collection<CAstQualifier> getQualifiers() {
      return Collections.emptyList();
    }

    @Override
    public CAstType getType() {
      Assertions.UNREACHABLE("CompilationUnitEntity.getType()");
      return null;
    }
  }

  public class PolyglotJavaType implements JavaType {
    protected final CAstTypeDictionary fDict;

    protected final TypeSystem fSystem;

    protected final ClassType fType;

    private Collection<CAstType> fSuperTypes = null;

    public PolyglotJavaType(ClassType type, CAstTypeDictionary dict, TypeSystem system) {
      super();
      fDict = dict;
      fSystem = system;
      fType = type;
    }

    @Override
    public String getName() {
      // TODO Will the IdentityMapper do the right thing for anonymous classes?
      // If so, we can delete most of the following logic...
      if (fType.isLocal() || fType.isAnonymous()) {
        return fIdentityMapper.anonLocalTypeToTypeID(fType);
      } else
        return fIdentityMapper.getTypeRef(fType).getName().toString();
    }

    @Override
    public Collection<CAstType> getSupertypes() {
      if (fSuperTypes == null) {
        buildSuperTypes();
      }
      return fSuperTypes;
    }

    private void buildSuperTypes() {
      // TODO this is a source entity, but it might actually be the root type
      // (Object), so assume # intfs + 1
      Type superType;
      if (fType.superClass() == null && fType != fSystem.Object()) {
        superType = fSystem.Object(); // fSystem.Object() MUST be the root of the class hierarchy
      } else {
        superType = fType.superClass();
      }
      int N = fType.interfaces().size() + 1;

      fSuperTypes = new ArrayList<CAstType>(N);
      // Following assumes that no one can call getSupertypes() before we have
      // created CAstType's for every type in the program being analyzed.
      if (superType != null) {
        fSuperTypes.add(fDict.getCAstTypeFor(superType));
      }
      for (Iterator iter = fType.interfaces().iterator(); iter.hasNext(); ) {
        Type t = (Type) iter.next();
        if (t instanceof ClassType) {
          ClassType classType = (ClassType) t;
          if (classType == fSystem.Object()) {
            continue; // Skip fSystem.Object() as a super-interface; it really MUST be a class as far as WALA is concerned
          }
        }
        fSuperTypes.add(fDict.getCAstTypeFor(t));
      }
    }

    @Override
    public Collection<CAstQualifier> getQualifiers() {
      return mapFlagsToQualifiers(fType.flags());
    }

    @Override
    public boolean isInterface() {
      if (fType == fSystem.Object()) {
        return false; // fSystem.Object() MUST be a class, as far as WALA is concerned
      }
      return fType.flags().isInterface();
    }
  }

  protected abstract static class CodeBodyEntity implements CAstEntity {
    private final Map<CAstNode, Collection<CAstEntity>> fEntities;

    public CodeBodyEntity(Map<CAstNode, Collection<CAstEntity>> entities) {
      fEntities = new LinkedHashMap<CAstNode, Collection<CAstEntity>>(entities);
    }

    @Override
    public Map<CAstNode, Collection<CAstEntity>> getAllScopedEntities() {
      return Collections.unmodifiableMap(fEntities);
    }

    @Override
    public Iterator<CAstEntity> getScopedEntities(CAstNode construct) {
      if (fEntities.containsKey(construct)) {
        return (fEntities.get(construct)).iterator();
      } else {
        return EmptyIterator.instance();
      }
    }

    @Override
    public String getSignature() {
      return Util.methodEntityToSelector(this).toString();
    }
  }

  protected final class ClassEntity implements CAstEntity {
    @SuppressWarnings("unused")
    private final ClassContext fContext;

    private final ClassType fCT;

    private final String fName;

    private final Collection<CAstEntity> fEntities;

    private final CAstSourcePositionMap.Position sourcePosition;

    private ClassEntity(ClassContext context, List<CAstEntity> entities, ClassDecl cd, Position p) {
      this(context, entities, cd.classDef().asType(), cd.name().id().toString(), p);
    }

    private ClassEntity(ClassContext context, List<CAstEntity> entities, ClassType ct, String name, Position p) {
      fContext = context;
      this.fEntities = entities;
      fCT = ct;
      fName = name;
      sourcePosition = makePosition(p);
    }

    @Override
    public int getKind() {
      return TYPE_ENTITY;
    }

    @Override
    public String getName() {
      return fName; // unqualified?
    }

    @Override
    public String getSignature() {
      return "L" + fName.replace('.', '/') + ";";
    }

    @Override
    public String[] getArgumentNames() {
      return new String[0];
    }

    @Override
    public CAstNode[] getArgumentDefaults() {
      return new CAstNode[0];
    }

    @Override
    public int getArgumentCount() {
      return 0;
    }

    @Override
    public CAstNode getAST() {
      // This entity has no AST nodes, really.
      return null;
    }

    @Override
    public Collection<CAstAnnotation> getAnnotations() {
        return null;
    }

    @Override
    public Map<CAstNode, Collection<CAstEntity>> getAllScopedEntities() {
      return Collections.singletonMap(null, fEntities);
    }

    @Override
    public Iterator<CAstEntity> getScopedEntities(CAstNode construct) {
      Assertions.UNREACHABLE("Non-AST-bearing entity (ClassEntity) asked for scoped entities related to a given AST node");
      return null;
    }

    @Override
    public CAstControlFlowMap getControlFlow() {
      // This entity has no AST nodes, really.
      return null;
    }

    @Override
    public CAstSourcePositionMap getSourceMap() {
      // This entity has no AST nodes, really.
      return null;
    }

    @Override
    public CAstSourcePositionMap.Position getPosition() {
      return sourcePosition;
    }

    @Override
    public CAstNodeTypeMap getNodeTypeMap() {
      // This entity has no AST nodes, really.
      return new CAstNodeTypeMap() {
        @Override
        public CAstType getNodeType(CAstNode node) {
          throw new UnsupportedOperationException();
        }
        
        @Override
        public Collection<CAstNode> getMappedNodes() {
          throw new UnsupportedOperationException();
        }
      };
    }

    @Override
    public Collection<CAstQualifier> getQualifiers() {
      if (fCT == fTypeSystem.Object()) { // pretend the root of the hierarchy is always a class
        return mapFlagsToQualifiers(fCT.flags().clear(Flags.INTERFACE));
      }
      return mapFlagsToQualifiers(fCT.flags());
    }

    @Override
    public CAstType getType() {
      return new PolyglotJavaType(fCT, getTypeDict(), fTypeSystem);
    }
    
    @Override
    public String toString() {
      return fCT.fullName().toString();
    }
  }

  protected final class ProcedureEntity extends CodeBodyEntity implements JavaProcedureEntity {
    private final CAstNode fPdast;

    private final TypeSystem fSystem;

    private final Type declaringType;

    private final CodeInstance fPd;

    private final MethodContext fMc;

    private final String[] argumentNames;

    public ProcedureEntity(CAstNode pdast, TypeSystem system, CodeInstance pd, Type declaringType, String[] argumentNames,
        Map<CAstNode, Collection<CAstEntity>> entities, MethodContext mc) {
      super(entities);
      fPdast = pdast;
      fSystem = system;
      fPd = pd;
      this.declaringType = declaringType;
      this.argumentNames = argumentNames;
      fMc = mc;
    }

    public ProcedureEntity(CAstNode pdast, TypeSystem system, CodeInstance pd, String[] argumentNames,
        Map<CAstNode, Collection<CAstEntity>> entities, MethodContext mc) {
        //PORT1.7 used to be this(pdast, system, pd, ((MemberInstance) pd).container(), argumentNames, entities, mc);
        this(pdast, system, pd, ((MemberDef) pd.def()).container().get(), argumentNames, entities, mc);
    }

    private List formalTypes() {
      if (fPd instanceof ProcedureInstance) {
        return ((ProcedureInstance) fPd).formalTypes();
      } else {
        return Collections.EMPTY_LIST;
      }
    }

    @Override
    public String toString() {
      return fPd.toString();
    }

    @Override
    public int getKind() {
      return CAstEntity.FUNCTION_ENTITY;
    }

    @Override
    public String getName() {
      if (fPd instanceof ConstructorInstance) {
        return MethodReference.initAtom.toString();
      } else {
        if (fPd instanceof InitializerInstance) {
          return MethodReference.clinitName.toString();
        } else {
          Assertions.productionAssertion(fPd instanceof MethodInstance);
          return ((MethodInstance) fPd).name().toString();
        }
      }
    }

    @Override
    public String[] getArgumentNames() {
      return argumentNames;
    }

    @Override
    public CAstNode[] getArgumentDefaults() {
      return new CAstNode[0];
    }

    private Flags getFlags() {
        // PORT1.7
        if (fPd instanceof ProcedureInstance) {
            return ((MemberDef) fPd.def()).flags();
        }
        return Flags.NONE;
    }

    private boolean isStatic() {
        // PORT1.7
        if (fPd instanceof ProcedureInstance) {
            return ((MemberDef) fPd.def()).flags().isStatic();
        } else if (fPd instanceof InitializerInstance) {
            return ((InitializerInstance) fPd).def().flags().isStatic();
        }
        return false;
    }

    @Override
    public int getArgumentCount() {
      return isStatic() ? formalTypes().size() : formalTypes().size() + 1;
    }

    @Override
    public CAstNode getAST() {
      return fPdast;
    }

    @Override
    public CAstControlFlowMap getControlFlow() {
      return fMc.cfg();
    }

    @Override
    public CAstSourcePositionMap getSourceMap() {
      return fMc.pos();
    }

    @Override
    public CAstSourcePositionMap.Position getPosition() {
      return getSourceMap().getPosition(fPdast);
    }

    @Override
    public CAstNodeTypeMap getNodeTypeMap() {
      return fMc.getNodeTypeMap();
    }

    @Override
    public Collection<CAstQualifier> getQualifiers() {
      return mapFlagsToQualifiers(getFlags());
    }

    @Override
    public CAstType getType() {
      return new CAstType.Method() {
        private Collection<CAstType> fExceptionTypes = null;

        private List<CAstType> fParameterTypes = null;

        @Override
        public CAstType getReturnType() {
          return fMc.getTypeDictionary().getCAstTypeFor(
              (fPd instanceof MethodInstance) ? ((MethodInstance) fPd).returnType() : fSystem.Void());
        }

        @Override
        public List<CAstType> getArgumentTypes() {
          if (fParameterTypes == null) {
            final List formalTypes = formalTypes();
            fParameterTypes = new ArrayList<CAstType>(formalTypes.size());

            for (Iterator iter = formalTypes.iterator(); iter.hasNext();) {
              fParameterTypes.add(fMc.getTypeDictionary().getCAstTypeFor(iter.next()));
            }
          }
          return fParameterTypes;
        }

        @Override
        public String getName() {
          Assertions.UNREACHABLE("CAstType.FunctionImpl#getName() called???");
          return "?";
        }

        @Override
        public Collection<CAstType> getSupertypes() {
          Assertions.UNREACHABLE("CAstType.FunctionImpl#getSupertypes() called???");
          return null;
        }

        @Override
        public Collection/* <CAstType> */<CAstType> getExceptionTypes() {
          if (fExceptionTypes == null) {
            fExceptionTypes = new LinkedHashSet<CAstType>();

            if (fPd instanceof ProcedureInstance) {
              List exceptions = ((ProcedureInstance) fPd).throwTypes();

              if (exceptions != null) {
                for (Iterator iterator = exceptions.iterator(); iterator.hasNext();) {
                  Type type = (Type) iterator.next();
                  fExceptionTypes.add(fMc.getTypeDictionary().getCAstTypeFor(type));
                }
              }
            }
          }

          return fExceptionTypes;
        }

        @Override
        public int getArgumentCount() {
          return formalTypes().size();
        }

        @Override
        public CAstType getDeclaringType() {
          return getTypeDict().getCAstTypeFor(declaringType);
        }
      };
    }
    
    @Override
    public Collection<CAstAnnotation> getAnnotations() {
        return null;
    }
  }

  protected final class FieldEntity implements CAstEntity {
    private final FieldInstance fFI;

    private final WalkContext fContext;

    private FieldEntity(FieldDecl fd, WalkContext context) {
      super();
      fFI = fd.fieldDef().asInstance();
      fContext = context;
    }

    @Override
    public int getKind() {
      return CAstEntity.FIELD_ENTITY;
    }

    @Override
    public String getName() {
      return fFI.name().toString();
    }

    @Override
    public String getSignature() {
      return fFI.name() + fIdentityMapper.typeToTypeID(fFI.type());
    }

    @Override
    public String[] getArgumentNames() {
      return new String[0];
    }

    @Override
    public CAstNode[] getArgumentDefaults() {
      return new CAstNode[0];
    }

    @Override
    public int getArgumentCount() {
      return 0;
    }

    @Override
    public Iterator<CAstEntity> getScopedEntities(CAstNode construct) {
      return EmptyIterator.instance();
    }

    @Override
    public Map<CAstNode, Collection<CAstEntity>> getAllScopedEntities() {
      return Collections.emptyMap();
    }

    @Override
    public CAstNode getAST() {
      // No AST for a field decl; initializers folded into
      // constructor processing...
      return null;
    }

    @Override
    public Collection<CAstAnnotation> getAnnotations() {
        return null;
    }

    @Override
    public CAstControlFlowMap getControlFlow() {
      // No AST for a field decl; initializers folded into
      // constructor processing...
      return null;
    }

    @Override
    public CAstSourcePositionMap getSourceMap() {
      // No AST for a field decl; initializers folded into
      // constructor processing...
      return null;
    }

    @Override
    public CAstSourcePositionMap.Position getPosition() {
      return makePosition(fFI.position());
    }

    @Override
    public CAstNodeTypeMap getNodeTypeMap() {
      // No AST for a field decl; initializers folded into
      // constructor processing...
      return null;
    }

    @Override
    public Collection<CAstQualifier> getQualifiers() {
      return mapFlagsToQualifiers(fFI.flags());
    }

    @Override
    public CAstType getType() {
      return fContext.getTypeDictionary().getCAstTypeFor(fFI.type());
    }
  }

  public interface WalkContext extends TranslatorToCAst.WalkContext<WalkContext, Node> {
    Collection<Pair<Type, Object>> getCatchTargets(Type label);

    Node getFinally();

    CodeInstance getEnclosingMethod();

    Type getEnclosingType();

    CAstTypeDictionary getTypeDictionary();

    List<ClassMember> getStaticInitializers();

    List<ClassMember> getInitializers();

    Map<Node, String> getLabelMap();

    boolean needLVal();
  }

  protected static class DelegatingContext extends TranslatorToCAst.DelegatingContext<WalkContext, Node> implements WalkContext {
    protected DelegatingContext(WalkContext parent) {
      super(parent);
    }

    @Override
    public Collection<Pair<Type, Object>> getCatchTargets(Type label) {
      return parent.getCatchTargets(label);
    }

    @Override
    public Node getFinally() {
      return parent.getFinally();
    }

    @Override
    public CodeInstance getEnclosingMethod() {
      return parent.getEnclosingMethod();
    }

    @Override
    public Type getEnclosingType() {
      return parent.getEnclosingType();
    }

    @Override
    public CAstTypeDictionary getTypeDictionary() {
      return parent.getTypeDictionary();
    }

    @Override
    public List<ClassMember> getStaticInitializers() {
      return parent.getStaticInitializers();
    }

    @Override
    public List<ClassMember> getInitializers() {
      return parent.getInitializers();
    }

    @Override
    public Map<Node, String> getLabelMap() {
      return parent.getLabelMap();
    }

    @Override
    public boolean needLVal() {
      return parent.needLVal();
    }
  }

  public class ClassContext extends DelegatingContext {
    // private final ClassDecl cd;
    private final Type type;

    private List<ClassMember>/* <Initializer+FieldDecl> */fInitializers = new ArrayList<ClassMember>();

    private List<ClassMember>/* <Initializer+FieldDecl> */fStaticInitializers = new ArrayList<ClassMember>();

    private List<CAstEntity> fChildren;

    public ClassContext(Type type, List<CAstEntity> entities, WalkContext parent) {
      super(parent);
      this.type = type;
      fChildren = entities;
    }

    @Override
    public void addScopedEntity(CAstNode node, CAstEntity e) {
      Assertions.productionAssertion(node == null);
      fChildren.add(e);
    }

    // public Map/*<CAstNode,CAstEntity>*/ getScopedEntities() {
    // return null; // fChildren;
    // }

    @Override
    public Type getEnclosingType() {
      return type;
    }

    @Override
    public List<ClassMember> getInitializers() {
      return fInitializers;
    }

    @Override
    public List<ClassMember> getStaticInitializers() {
      return fStaticInitializers;
    }

    @Override
    public CAstControlFlowRecorder cfg() {
      Assertions.UNREACHABLE("ClassContext.cfg()");
      return null;
    }

    public Iterator/* <Pair<Type,Node>> */getCatchTarget(Type label) {
      Assertions.UNREACHABLE("ClassContext.getCatchTarget()");
      return null;
    }

    @Override
    public Node getFinally() {
      Assertions.UNREACHABLE("ClassContext.getFinally()");
      return null;
    }

    @Override
    public CodeInstance getEnclosingMethod() {
      // No one outside a method defining a local class can see it,
      // so it clearly can't escape through to the method's enclosing
      // type...
      Assertions.UNREACHABLE("ClassContext.getEnclosingMethod()");
      return null;
    }

    @Override
    public CAstSourcePositionRecorder pos() {
      // No AST, so no AST map
      Assertions.UNREACHABLE("ClassContext.pos()");
      return null;
    }

    @Override
    public Node getContinueFor(String label) {
      Assertions.UNREACHABLE("ClassContext.getContinueFor() with label " + label + " in " + type);
      return null;
    }

    @Override
    public Node getBreakFor(String label) {
      System.err.println("Cannot find break target for " + label + " in " + type);
      Assertions.UNREACHABLE("ClassContext.getBreakFor()");
      return null;
    }

    @Override
    public Map<Node, String> getLabelMap() {
      Assertions.UNREACHABLE("ClassContext.getLabelMap()");
      return null;
    }

    @Override
    public boolean needLVal() {
      Assertions.UNREACHABLE("ClassContext.needLVal()");
      return false;
    }
  }

  public class CodeBodyContext extends DelegatingContext {
    final CAstSourcePositionRecorder fSourceMap = new CAstSourcePositionRecorder();

    final CAstControlFlowRecorder fCFG = new CAstControlFlowRecorder(fSourceMap);

    final CAstNodeTypeMapRecorder fNodeTypeMap = new CAstNodeTypeMapRecorder();

    private final Map<Node, String> labelMap = HashMapFactory.make(2);

    private final Map<CAstNode, Collection<CAstEntity>> fEntities;

    public CodeBodyContext(WalkContext parent, Map<CAstNode, Collection<CAstEntity>> entities) {
      super(parent);
      fEntities = entities;
    }

    @Override
    public CAstNodeTypeMapRecorder getNodeTypeMap() {
      return fNodeTypeMap;
    }

    @Override
    public CAstSourcePositionRecorder pos() {
      return fSourceMap;
    }

    @Override
    public CAstControlFlowRecorder cfg() {
      return fCFG;
    }

    @Override
    public void addScopedEntity(CAstNode node, CAstEntity entity) {
      if (! fEntities.containsKey(node)) { fEntities.put(node, new HashSet<CAstEntity>(1)); }
      fEntities.get(node).add(entity);
    }

    @Override
    public Map<CAstNode, Collection<CAstEntity>> getScopedEntities() {
      return fEntities;
    }

    @Override
    public Map<Node, String> getLabelMap() {
      return labelMap;
    }

    @Override
    public boolean needLVal() {
      return false;
    }
  }

  public class MethodContext extends CodeBodyContext {
    final CodeInstance fPI;

    public MethodContext(CodeInstance pi, Map<CAstNode, Collection<CAstEntity>> entities, WalkContext parent) {
      super(parent, entities);
      fPI = pi;
    }

    @Override
    public Collection<Pair<Type, Object>> getCatchTargets(Type label) {
      Collection<Pair<Type, Object>> result = Collections.singleton(Pair.<Type,Object>make(fREType, CAstControlFlowMap.EXCEPTION_TO_EXIT));
      return result;
    }

    @Override
    public CodeInstance getEnclosingMethod() {
      return fPI;
    }
  }

  private static class TryCatchContext extends DelegatingContext {
    @SuppressWarnings("unused")
    private final Try tryNode;
    
    private final Context context;

    Collection<Pair<Type,Object>> fCatchNodes = new ArrayList<Pair<Type, Object>>();

    TryCatchContext(WalkContext parent, Try tryNode, final Context context) {
      super(parent);
      this.tryNode = tryNode;
      this.context = context;   

      for (Iterator<Catch> catchIter = tryNode.catchBlocks().iterator(); catchIter.hasNext();) {
        Catch c = catchIter.next();
        Pair<Type,Object> p = Pair.make(c.catchType(), (Object)c);

        fCatchNodes.add(p);
      }
    }

    @Override
    public Collection<Pair<Type, Object>> getCatchTargets(Type label) {
      // Look for all matching targets for this thrown type:
      // if supertpe match, then return only matches at this catch
      // if subtype match, then matches here and parent matches
      Collection<Pair<Type, Object>> catchNodes = new ArrayList<Pair<Type, Object>>();

      for (Iterator<Pair<Type, Object>> iter = fCatchNodes.iterator(); iter.hasNext();) {
        Pair<Type, Object> p = iter.next();
        Type catchType = p.fst;

        // _must_ be caught
        if (label.isSubtype(catchType, this.context) || label.typeEquals(catchType, this.context)) {
          catchNodes.add(p);
          return catchNodes;

          // _might_ get caught
        } else if (catchType.isSubtype(label, this.context)) {
          catchNodes.add(p);
          continue;
        }
      }
      catchNodes.addAll(parent.getCatchTargets(label));
      return catchNodes;
    }

    @Override
    public List<ClassMember> getStaticInitializers() {
      return null;
    }

    @Override
    public List<ClassMember> getInitializers() {
      return null;
    }
  }

  protected static class RootContext extends TranslatorToCAst.RootContext<WalkContext, Node> implements WalkContext {
    final CAstTypeDictionary fTypeDict;

    public RootContext(CAstTypeDictionary typeDict) {
      fTypeDict = typeDict;
    }

    @Override
    public Collection<Pair<Type, Object>> getCatchTargets(Type label) {
      Assertions.UNREACHABLE("RootContext.getCatchTargets()");
      return null;
    }

    @Override
    public Node getFinally() {
      Assertions.UNREACHABLE("RootContext.getFinally()");
      return null;
    }

    @Override
    public CodeInstance getEnclosingMethod() {
      Assertions.UNREACHABLE("RootContext.getEnclosingMethod()");
      return null;
    }

    @Override
    public Type getEnclosingType() {
      Assertions.UNREACHABLE("RootContext.getEnclosingType()");
      return null;
    }

    @Override
    public CAstTypeDictionary getTypeDictionary() {
      return fTypeDict;
    }

    @Override
    public List<ClassMember> getStaticInitializers() {
      Assertions.UNREACHABLE("RootContext.getStaticInitializers()");
      return null;
    }

    @Override
    public List<ClassMember> getInitializers() {
      Assertions.UNREACHABLE("RootContext.getInitializers()");
      return null;
    }

    @Override
    public Map<Node, String> getLabelMap() {
      Assertions.UNREACHABLE("RootContext.getLabelMap()");
      return null;
    }

    @Override
    public boolean needLVal() {
      Assertions.UNREACHABLE("ClassContext.needLVal()");
      return false;
    }
  }

  public class BreakContext extends DelegatingContext {
    protected final String label;

    private final Node breakTo;

    BreakContext(WalkContext parent, String label, Node breakTo) {
      super(parent);
      this.label = label;
      this.breakTo = breakTo;
    }

    @Override
    public Node getBreakFor(String label) {
      return (label == null || label.equals(this.label)) ? breakTo : super.getBreakFor(label);
    }

    @Override
    public List<ClassMember> getStaticInitializers() {
      return null;
    }

    @Override
    public List<ClassMember> getInitializers() {
      return null;
    }
  }

  public class LoopContext extends BreakContext {
    private final Node continueTo;

    public LoopContext(WalkContext parent, String label, Node breakTo, Node continueTo) {
      super(parent, label, breakTo);
      this.continueTo = continueTo;
    }

    @Override
    public Node getContinueFor(String label) {
      return (label == null || label.equals(this.label)) ? continueTo : super.getContinueFor(label);
    }
  }

  private class AssignmentContext extends DelegatingContext {

    protected AssignmentContext(WalkContext parent) {
      super(parent);
    }

    @Override
    public boolean needLVal() {
      return true;
    }
  }

  public PolyglotJava2CAstTranslator(Node ast, ClassLoaderReference clr, NodeFactory nf, TypeSystem ts, PolyglotIdentityMapper identityMapper, boolean replicateForDoLoops) {
    this.ast = ast;
    fClassLoaderRef = clr;
    fTypeSystem = ts;
    fNodeFactory = nf;
    fIdentityMapper = identityMapper;
    doLoopTranslator = new DoLoopTranslator(replicateForDoLoops, fFactory);
    fNPEType = fTypeSystem.NullPointerException();
    fCCEType = fTypeSystem.ClassCastException();
    fREType = fTypeSystem.RuntimeException();
    fDivByZeroType = fTypeSystem.ArithmeticException();
  }

  public static class PolyglotSourcePosition extends AbstractSourcePosition {
    private final Position p;

    public PolyglotSourcePosition(Position p) {
      this.p = p;
    }

    @Override
    public int getFirstLine() {
      return p.line();
    }

    @Override
    public int getLastLine() {
      return p.endLine();
    }

    @Override
    public int getFirstCol() {
      return p.column();
    }

    @Override
    public int getLastCol() {
      return p.endColumn();
    }
     
    @Override
    public int getFirstOffset() {
      return p.offset();
    }

    @Override
    public int getLastOffset() {
      return p.endOffset();
    }

    @Override
    public URL getURL() {
      try {
        String path = p.path();
        return new URL("file:" + (path.length() == 0 ? p.file() : path));
      } catch (MalformedURLException e) {
        Assertions.UNREACHABLE(e.toString());
        return null;
      }
    }

    @Override
    public Reader getReader() throws IOException {
      return new InputStreamReader(getURL().openConnection().getInputStream());
    }
  }

  protected CAstSourcePositionMap.Position makePosition(Position p) {
    return new PolyglotSourcePosition(p);
  }

  private void setPos(WalkContext wc, CAstNode cn, Node pn) {
    if (pn != null) {
      wc.pos().setPosition(cn, makePosition(pn.position()));
    }
  }

  private void setPos(WalkContext wc, CAstNode cn, Position p) {
    if (p != null) {
      wc.pos().setPosition(cn, makePosition(p));
    }
  }

  protected CAstNode makeNode(WalkContext wc, CAst Ast, Node n, int kind) {
    CAstNode cn = Ast.makeNode(kind);
    setPos(wc, cn, n);
    return cn;
  }

  protected CAstNode makeNode(WalkContext wc, CAst Ast, Node n, int kind, CAstNode c[]) {
    CAstNode cn = Ast.makeNode(kind, c);
    setPos(wc, cn, n);
    return cn;
  }

  protected CAstNode makeNode(WalkContext wc, CAst Ast, Node n, int kind, CAstNode c) {
    CAstNode cn = Ast.makeNode(kind, c);
    setPos(wc, cn, n);
    return cn;
  }

  protected CAstNode makeNode(WalkContext wc, CAst Ast, Node n, int kind, CAstNode c1, CAstNode c2) {
    CAstNode cn = Ast.makeNode(kind, c1, c2);
    setPos(wc, cn, n);
    return cn;
  }

  protected CAstNode makeNode(WalkContext wc, CAst Ast, Node n, int kind, CAstNode c1, CAstNode c2, CAstNode c3) {
    CAstNode cn = Ast.makeNode(kind, c1, c2, c3);
    setPos(wc, cn, n);
    return cn;
  }

  protected CAstNode makeNode(WalkContext wc, CAst Ast, Node n, int kind, CAstNode c1, CAstNode c2, CAstNode c3, CAstNode c4) {
    CAstNode cn = Ast.makeNode(kind, c1, c2, c3, c4);
    setPos(wc, cn, n);
    return cn;
  }

  protected CAstNode makeNode(WalkContext wc, Node n, int kind) {
    return makeNode(wc, fFactory, n, kind);
  }

  protected CAstNode makeNode(WalkContext wc, Node n, int kind, CAstNode c[]) {
    return makeNode(wc, fFactory, n, kind, c);
  }

  protected CAstNode makeNode(WalkContext wc, Node n, int kind, CAstNode c) {
    return makeNode(wc, fFactory, n, kind, c);
  }

  protected CAstNode makeNode(WalkContext wc, Node n, int kind, CAstNode c1, CAstNode c2) {
    return makeNode(wc, fFactory, n, kind, c1, c2);
  }

  protected CAstNode makeNode(WalkContext wc, Node n, int kind, CAstNode c1, CAstNode c2, CAstNode c3) {
    return makeNode(wc, fFactory, n, kind, c1, c2, c3);
  }

  protected CAstNode makeNode(WalkContext wc, Node n, int kind, CAstNode c1, CAstNode c2, CAstNode c3, CAstNode c4) {
    return makeNode(wc, fFactory, n, kind, c1, c2, c3, c4);
  }

  protected CAstNode makeNode(WalkContext wc, int kind, Position p) {
    CAstNode cn = fFactory.makeNode(kind);
    setPos(wc, cn, p);
    return cn;
  }

  protected CAstNode makeNode(WalkContext wc, Position p, int kind, CAstNode c1) {
    CAstNode cn = fFactory.makeNode(kind, c1);
    setPos(wc, cn, p);
    return cn;
  }

  protected CAstNode makeNode(WalkContext wc, Position p, int kind, CAstNode c1, CAstNode[] rest) {
    CAstNode cn = fFactory.makeNode(kind, c1, rest);
    setPos(wc, cn, p);
    return cn;
  }

  protected CAstNode makeNode(WalkContext wc, Position p, int kind, CAstNode c1, CAstNode c2) {
    CAstNode cn = fFactory.makeNode(kind, c1, c2);
    setPos(wc, cn, p);
    return cn;
  }

  protected CAstNode makeNode(WalkContext wc, Position p, int kind, CAstNode c1, CAstNode c2, CAstNode c3) {
    CAstNode cn = fFactory.makeNode(kind, c1, c2, c3);
    setPos(wc, cn, p);
    return cn;
  }

  protected CAstNode makeNode(WalkContext wc, Position p, int kind, CAstNode c1, CAstNode c2, CAstNode c3, CAstNode c4) {
    CAstNode cn = fFactory.makeNode(kind, c1, c2, c3, c4);
    setPos(wc, cn, p);
    return cn;
  }

  public CAstEntity translateToCAst() {
    return walkEntity(ast, new RootContext(getTypeDict()));
  }

  /**
   * Maps front-end-specific representations into WALA references of the appropriate kind.
   * @author rfuhrer
   *
   * @param <TypeRep> The front-end-specific representation of a type (e.g., for Polyglot, a Type)
   * @param <MethodRep> The front-end-specific representation of a procedure/method (e.g., for Polyglot, a CodeInstance)
   * @param <FieldRep> The front-end-specific representation of a field (e.g., for Polyglot, a FieldInstance)
   */
  public interface IdentityMapper<TypeRep, MethodRep, FieldRep> {
    MemberReference getMethodRef(MethodRep method);

    TypeReference getTypeRef(TypeRep type);

    FieldReference getFieldRef(FieldRep field);
  }

  protected static Collection<CAstQualifier> mapFlagsToQualifiers(Flags flags) {
    Set<CAstQualifier> quals = new LinkedHashSet<CAstQualifier>();

    if (flags.isAbstract())
      quals.add(CAstQualifier.ABSTRACT);
    if (flags.isFinal())
      quals.add(CAstQualifier.FINAL);
    if (flags.isInterface())
      quals.add(CAstQualifier.INTERFACE);
    if (flags.isNative())
      quals.add(CAstQualifier.NATIVE);
    // if (flags.isPackage()) quals.add(CAstQualifier.PACKAGE);
    if (flags.isPrivate())
      quals.add(CAstQualifier.PRIVATE);
    if (flags.isProtected())
      quals.add(CAstQualifier.PROTECTED);
    if (flags.isPublic())
      quals.add(CAstQualifier.PUBLIC);
    if (flags.isStatic())
      quals.add(CAstQualifier.STATIC);
    if (flags.isStrictFP())
      quals.add(CAstQualifier.STRICTFP);
    if (flags.isSynchronized())
      quals.add(CAstQualifier.SYNCHRONIZED);
    if (flags.isTransient())
      quals.add(CAstQualifier.TRANSIENT);
    if (flags.isVolatile())
      quals.add(CAstQualifier.VOLATILE);

    return quals;
  }

  protected void processClassMembers(Node n, ClassType classType, List<ClassMember> members, DelegatingContext classContext,
      List<CAstEntity> memberEntities) {
    // Collect all initializer-related gorp
    for (Iterator<ClassMember> memberIter = members.iterator(); memberIter.hasNext();) {
      ClassMember member = memberIter.next();

      if (member instanceof Initializer) {
        Initializer initializer = (Initializer) member;

        if (initializer.flags().flags().isStatic())
          classContext.getStaticInitializers().add(initializer);
        else
          classContext.getInitializers().add(initializer);
      } else if (member instanceof FieldDecl) {
        FieldDecl fd = (FieldDecl) member;

        if (fd.init() != null) {
          if (fd.flags().flags().isStatic())
            classContext.getStaticInitializers().add(fd);
          else
            classContext.getInitializers().add(fd);
        }
      }
    }

    // Now process
    for (Iterator<ClassMember> memberIter = members.iterator(); memberIter.hasNext();) {
      ClassMember member = memberIter.next();

      if (!(member instanceof Initializer)) {
        CAstEntity memberEntity = walkEntity(member, classContext);

        memberEntities.add(memberEntity);
      }
    }

    // add class initializer, if needed
    if (!classContext.getStaticInitializers().isEmpty()) {
        InitializerDef initDef = fTypeSystem.initializerDef(n.position(), Types.ref(classType), Flags.STATIC);
      InitializerInstance initInstance = fTypeSystem.createInitializerInstance(n.position(), Types.ref(initDef));

      Map<CAstNode, Collection<CAstEntity>> childEntities = HashMapFactory.make();
      final MethodContext mc = new MethodContext(initInstance, childEntities, classContext);

      List inits = classContext.getStaticInitializers();
      CAstNode[] bodyNodes = new CAstNode[inits.size()];
      insertInitializers(mc, bodyNodes, true, 0);

      CAstNode ast = makeNode(mc, fFactory, n, CAstNode.BLOCK_STMT, bodyNodes);

      memberEntities.add(new ProcedureEntity(ast, fTypeSystem, initInstance, new String[0], childEntities, mc));

    }
  }

  protected void addConstructorsToAnonymousClass(New n, ClassType anonType, ClassContext classContext,
      List<CAstEntity> memberEntities) {
    List superConstructors = ((ClassType) anonType.superClass()).constructors();

    for (Iterator iter = superConstructors.iterator(); iter.hasNext();) {
      ConstructorInstance superCtor = (ConstructorInstance) iter.next();

      Map<CAstNode, Collection<CAstEntity>> childEntities = HashMapFactory.make();
      final MethodContext mc = new MethodContext(superCtor, childEntities, classContext);

      String[] fakeArguments = new String[superCtor.formalTypes().size() + 1];
      for (int i = 0; i < fakeArguments.length; i++) {
        fakeArguments[i] = (i == 0) ? "this" : ("argument" + i);
      }

      List inits = classContext.getInitializers();

      CAstNode[] bodyNodes = new CAstNode[inits.size() + 1];

      CallSiteReference callSiteRef = CallSiteReference.make(0, fIdentityMapper.getMethodRef(superCtor),
          IInvokeInstruction.Dispatch.SPECIAL);
      CAstNode[] children = new CAstNode[fakeArguments.length + 1];
      children[0] = makeNode(mc, fFactory, n, CAstNode.SUPER);
      children[1] = fFactory.makeConstant(callSiteRef);
      for (int i = 1; i < fakeArguments.length; i++) {
        children[i + 1] = makeNode(mc, fFactory, n, CAstNode.VAR, fFactory.makeConstant(fakeArguments[i]));
      }
      bodyNodes[0] = makeNode(mc, fFactory, n, CAstNode.CALL, children);

      insertInitializers(mc, bodyNodes, false, 1);

      CAstNode ast = makeNode(mc, fFactory, n, CAstNode.BLOCK_STMT, bodyNodes);

      memberEntities.add(new ProcedureEntity(ast, fTypeSystem, superCtor, anonType, fakeArguments, childEntities, mc));
    }
  }

  static String anonTypeName(ClassType ct) {
    Position pos = ct.position();

    return ct.fullName() + "$" + pos.line() + "$" + pos.column();
  }

  protected CAstEntity walkEntity(Node rootNode, final WalkContext context) {
    if (rootNode instanceof SourceFile) {
      SourceFile file = (SourceFile) rootNode;
      List<CAstEntity> declEntities = new ArrayList<CAstEntity>();

      for (Iterator iter = file.decls().iterator(); iter.hasNext();) {
        TopLevelDecl decl = (TopLevelDecl) iter.next();

        declEntities.add(walkEntity(decl, context));
      }
      return new CompilationUnitEntity(file, declEntities);
    } else if (rootNode instanceof ClassDecl) {
      final ClassDecl cd = (ClassDecl) rootNode;
      final List<CAstEntity> memberEntities = new ArrayList<CAstEntity>();
      final ClassContext classContext = new ClassContext(cd.classDef().asType(), memberEntities, context);

      processClassMembers(rootNode, cd.classDef().asType(), cd.body().members(), classContext, memberEntities);

      return new ClassEntity(classContext, memberEntities, cd, cd.position());
    } else if (rootNode instanceof New) {
      final New n = (New) rootNode;
      final List<CAstEntity> memberEntities = new ArrayList<CAstEntity>();
      ClassType anonType = n.anonType().asType();
      String anonTypeName = anonTypeName(anonType);
      final ClassContext classContext = new ClassContext(anonType, memberEntities, context);

      processClassMembers(rootNode, anonType, n.body().members(), classContext, memberEntities);
      addConstructorsToAnonymousClass(n, anonType, classContext, memberEntities);

      return new ClassEntity(classContext, memberEntities, anonType, anonTypeName, n.position());
    } else if (rootNode instanceof ProcedureDecl) {
      final ProcedureDecl pd = (ProcedureDecl) rootNode;
      final Map<CAstNode, Collection<CAstEntity>> memberEntities = HashMapFactory.make();
      final MethodContext mc = new MethodContext(pd.procedureInstance().asInstance(), memberEntities, context);

      CAstNode pdAST = null;

      if (!pd.flags().flags().isAbstract()) {
        // Presumably the MethodContext's parent is a ClassContext,
        // and he has the list of initializers. Hopefully the following
        // will glue that stuff in the right place in any constructor body.
        pdAST = walkNodes(pd, mc);
      }

      List/* <Formal> */formals = pd.formals();
      String[] argNames;
      int i = 0;
      if (!pd.flags().flags().isStatic()) {
        argNames = new String[formals.size() + 1];
        argNames[i++] = "this";
      } else {
        argNames = new String[formals.size()];
      }
      for (Iterator iter = formals.iterator(); iter.hasNext(); i++) {
        Formal formal = (Formal) iter.next();
        argNames[i] = formal.name().toString();
      }

      return new ProcedureEntity(pdAST, fTypeSystem, pd.procedureInstance().asInstance(), argNames, memberEntities, mc);
    } else if (rootNode instanceof FieldDecl) {
      final FieldDecl fd = (FieldDecl) rootNode;

      return new FieldEntity(fd, context);
    } else {
      Assertions.UNREACHABLE("Unknown node type for walkEntity():" + rootNode.getClass().getName());
      return null;
    }
  }

  private boolean isSpecialCallStmt(Stmt maybeSuper, Kind kind) {
    if (maybeSuper instanceof ConstructorCall) {
      ConstructorCall cc = (ConstructorCall) maybeSuper;
      return cc.kind() == kind;
    }
    return false;
  }

  private boolean hasSpecialCall(Block body, Kind kind) {
    if (body.statements().size() <= 0)
      return false;

    Stmt maybeSuper = body.statements().get(0);

    return isSpecialCallStmt(maybeSuper, kind);
  }

  private boolean hasSuperCall(Block body) {
    return hasSpecialCall(body, ConstructorCall.SUPER);
  }

  private boolean hasThisCall(Block body) {
    return hasSpecialCall(body, ConstructorCall.THIS);
  }

  private int insertInitializers(WalkContext wc, CAstNode[] initCode, boolean wantStatic, int offset) {
    List<ClassMember> inits = wantStatic ? wc.getStaticInitializers() : wc.getInitializers();

    for (Iterator<ClassMember> iter = inits.iterator(); iter.hasNext(); offset++) {
      ClassMember init = iter.next();
      CAstNode initNode = walkNodes(init, wc);

      if (initNode != null) {
        initCode[offset] = initNode;
      } else {
        initCode[offset] = makeNode(wc, fFactory, null, CAstNode.EMPTY);
      }
    }
    return offset;
  }

  protected CAstNode walkNodes(Node n, final WalkContext context) {
    if (n == null)
      return makeNode(context, fFactory, null, CAstNode.EMPTY);
    return ASTTraverser.visit(n, getTranslator(), context);
  }
}
