/*
 * 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.
 * 
 * This file is a derivative of code released by the University of
 * California under the terms listed below.  
 *
 * WALA JDT Frontend is Copyright (c) 2008 The Regents of the
 * University of California (Regents). Provided that this notice and
 * the following two paragraphs are included in any distribution of
 * Refinement Analysis Tools or its derivative work, Regents agrees
 * not to assert any of Regents' copyright rights in Refinement
 * Analysis Tools against recipient for recipient's reproduction,
 * preparation of derivative works, public display, public
 * performance, distribution or sublicensing of Refinement Analysis
 * Tools and derivative works, in source code and object code form.
 * This agreement not to assert does not confer, by implication,
 * estoppel, or otherwise any license or rights in any intellectual
 * property of Regents, including, but not limited to, any patents
 * of Regents or Regents' employees.
 * 
 * IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT,
 * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
 * INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE
 * AND ITS DOCUMENTATION, EVEN IF REGENTS HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *   
 * REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE AND FURTHER DISCLAIMS ANY STATUTORY
 * WARRANTY OF NON-INFRINGEMENT. THE SOFTWARE AND ACCOMPANYING
 * DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS PROVIDED "AS
 * IS". REGENTS HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT,
 * UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 */
package com.ibm.wala.cast.java.translator.jdt;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
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 org.eclipse.core.resources.IFile;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.AnnotationTypeDeclaration;
import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration;
import org.eclipse.jdt.core.dom.AnonymousClassDeclaration;
import org.eclipse.jdt.core.dom.ArrayAccess;
import org.eclipse.jdt.core.dom.ArrayCreation;
import org.eclipse.jdt.core.dom.ArrayInitializer;
import org.eclipse.jdt.core.dom.AssertStatement;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.BooleanLiteral;
import org.eclipse.jdt.core.dom.BreakStatement;
import org.eclipse.jdt.core.dom.CastExpression;
import org.eclipse.jdt.core.dom.CatchClause;
import org.eclipse.jdt.core.dom.CharacterLiteral;
import org.eclipse.jdt.core.dom.ClassInstanceCreation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ConditionalExpression;
import org.eclipse.jdt.core.dom.ConstructorInvocation;
import org.eclipse.jdt.core.dom.ContinueStatement;
import org.eclipse.jdt.core.dom.DoStatement;
import org.eclipse.jdt.core.dom.EmptyStatement;
import org.eclipse.jdt.core.dom.EnhancedForStatement;
import org.eclipse.jdt.core.dom.EnumConstantDeclaration;
import org.eclipse.jdt.core.dom.EnumDeclaration;
import org.eclipse.jdt.core.dom.Expression;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.ForStatement;
import org.eclipse.jdt.core.dom.IAnnotationBinding;
import org.eclipse.jdt.core.dom.IBinding;
import org.eclipse.jdt.core.dom.IMemberValuePairBinding;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.IfStatement;
import org.eclipse.jdt.core.dom.InfixExpression;
import org.eclipse.jdt.core.dom.Initializer;
import org.eclipse.jdt.core.dom.InstanceofExpression;
import org.eclipse.jdt.core.dom.LabeledStatement;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.MethodInvocation;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.NullLiteral;
import org.eclipse.jdt.core.dom.NumberLiteral;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.ParenthesizedExpression;
import org.eclipse.jdt.core.dom.PostfixExpression;
import org.eclipse.jdt.core.dom.PrefixExpression;
import org.eclipse.jdt.core.dom.QualifiedName;
import org.eclipse.jdt.core.dom.ReturnStatement;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Statement;
import org.eclipse.jdt.core.dom.StringLiteral;
import org.eclipse.jdt.core.dom.SuperConstructorInvocation;
import org.eclipse.jdt.core.dom.SuperFieldAccess;
import org.eclipse.jdt.core.dom.SuperMethodInvocation;
import org.eclipse.jdt.core.dom.SwitchCase;
import org.eclipse.jdt.core.dom.SwitchStatement;
import org.eclipse.jdt.core.dom.SynchronizedStatement;
import org.eclipse.jdt.core.dom.ThisExpression;
import org.eclipse.jdt.core.dom.ThrowStatement;
import org.eclipse.jdt.core.dom.TryStatement;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclarationStatement;
import org.eclipse.jdt.core.dom.TypeLiteral;
import org.eclipse.jdt.core.dom.VariableDeclarationExpression;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.dom.WhileStatement;

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.JavaSourceLoaderImpl;
import com.ibm.wala.cast.java.loader.Util;
import com.ibm.wala.cast.java.translator.JavaProcedureEntity;
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.CAstType;
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.ide.util.JdtPosition;
import com.ibm.wala.shrikeBT.IInvokeInstruction;
import com.ibm.wala.types.FieldReference;
import com.ibm.wala.types.MethodReference;
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.HashSetFactory;
import com.ibm.wala.util.collections.Pair;
import com.ibm.wala.util.debug.Assertions;

// TOTEST:
// "1/0" surrounded by catch ArithmeticException & RunTimeException (TryCatchContext.getCatchTypes"
// another subtype of ArithmeticException surrounded by catch ArithmeticException
// binary ops with all kinds of type conversions
// simplenames: fields of this, fields of an enclosing class, fields of an enclosing method, static fields, fully qualified fields w/ package stuff
// exceptional CFG edges, somehow. call nodes, new nodes, division by zero in binary ops, null pointer in field accesses, etc.   
// implicit constructors

// FIXME 1.4: thing about / ask agout TAGALONG (JDT stuff tagging along in memory cos we keep it).
// FIXME 1.4: find LEFTOUT and find out why polyglot has extra code / infrastructure, if it's used and what for, etc.

// Java 1.6:
// * type parameters/generics: see getArgumentTypes in ProcedureEntity$anon. also anywhere we use ITypeBinding.equals() may be affected
//   see anywhere in code labeled GENERICS for present "treat as raw types"/"pretend it's 1.4 and casts" solution.
// * boxing (YUCK). see resolveBoxing()
// * enums (probably in simplename or something. but using resolveConstantExpressionValue() possible)

@SuppressWarnings("unchecked")
public class JDTJava2CAstTranslator {
  protected final CAst fFactory = new CAstImpl();

  // ///////////////////////////////////////////
  // / HANDLINGS OF VARIOUS THINGS //
  // ///////////////////////////////////////////
  protected final AST ast; // TAGALONG

  protected final JDTIdentityMapper fIdentityMapper; // TAGALONG

  protected final JDTTypeDictionary fTypeDict;

  protected final JavaSourceLoaderImpl fSourceLoader;

  protected final ITypeBinding fDivByZeroExcType;

  protected final ITypeBinding fNullPointerExcType;

  protected final ITypeBinding fClassCastExcType;

  protected final ITypeBinding fRuntimeExcType;

  protected final ITypeBinding NoClassDefFoundError;

  protected final ITypeBinding ExceptionInInitializerError;

  protected final ITypeBinding OutOfMemoryError;

  protected final DoLoopTranslator doLoopTranslator;
  
  private final String fullPath;
  
  private final IFile sourceFile;

  private final CompilationUnit cu;

  //
  // COMPILATION UNITS & TYPES
  //

  public JDTJava2CAstTranslator(JavaSourceLoaderImpl sourceLoader, CompilationUnit astRoot, IFile sourceFile, String fullPath, boolean replicateForDoLoops) {
    fDivByZeroExcType = FakeExceptionTypeBinding.arithmetic;
    fNullPointerExcType = FakeExceptionTypeBinding.nullPointer;
    fClassCastExcType = FakeExceptionTypeBinding.classCast;
    NoClassDefFoundError = FakeExceptionTypeBinding.noClassDef;
    ExceptionInInitializerError = FakeExceptionTypeBinding.initException;
    OutOfMemoryError = FakeExceptionTypeBinding.outOfMemory;

    this.sourceFile = sourceFile;
    this.fSourceLoader = sourceLoader;
    this.cu = astRoot;

    this.fullPath = fullPath;
    ast = cu.getAST();

    this.doLoopTranslator = new DoLoopTranslator(replicateForDoLoops, fFactory);
    
    // FIXME: we might need one AST (-> "Object" class) for all files.
    fIdentityMapper = new JDTIdentityMapper(fSourceLoader.getReference(), ast);
    fTypeDict = new JDTTypeDictionary(ast, fIdentityMapper);

    fRuntimeExcType = ast.resolveWellKnownType("java.lang.RuntimeException");
    assert fRuntimeExcType != null;
  }

  public CAstEntity translateToCAst() {

    List<CAstEntity> declEntities = new ArrayList<CAstEntity>();

    for (Iterator<CAstEntity> iter = cu.types().iterator(); iter.hasNext();) {
      AbstractTypeDeclaration decl = (AbstractTypeDeclaration) iter.next();
      // can be of type AnnotationTypeDeclaration, EnumDeclaration, TypeDeclaration
      declEntities.add(visit(decl, new RootContext()));
    }

    return new CompilationUnitEntity(cu.getPackage(), declEntities);
  }

  //
  // TYPES
  //

  protected final class ClassEntity implements CAstEntity {
    // TAGALONG (not static, will keep reference to ast, fIdentityMapper, etc)

    private final String fName;

    private final Collection<CAstQualifier> fQuals;

    private final Collection<CAstEntity> fEntities;

    private final ITypeBinding fJdtType; // TAGALONG

    private final JdtPosition fSourcePosition;

    private final Set<CAstAnnotation> annotations;
    
    public ClassEntity(ITypeBinding jdtType, String name, Collection<CAstQualifier> quals, Collection<CAstEntity> entities,
        JdtPosition pos, Set<CAstAnnotation> annotations) {
      fName = name;
      fQuals = quals;
      fEntities = entities;
      fJdtType = jdtType;
      fSourcePosition = pos;
      this.annotations = annotations;
    }

    @Override
	public Collection<CAstAnnotation> getAnnotations() {
		// TODO Auto-generated method stub
		return null;
	}


	public int getKind() {
      return TYPE_ENTITY;
    }

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

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

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

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

    public int getArgumentCount() {
      return 0;
    }

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

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

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

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

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

    public CAstSourcePositionMap.Position getPosition() {
      return fSourcePosition;
    }

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

		public Collection<CAstNode> getMappedNodes() {
			throw new UnsupportedOperationException();
		}
      };
    }

    public Collection getQualifiers() {
      return fQuals;
    }

    public CAstType getType() {
      // return new JdtJavaType(fCT, getTypeDict(), fTypeSystem);
      return fTypeDict.new JdtJavaType(fJdtType);
    }

  }

  private boolean isInterface(AbstractTypeDeclaration decl) {
    return decl instanceof AnnotationTypeDeclaration ||
      (decl instanceof TypeDeclaration && ((TypeDeclaration)decl).isInterface());
  }
  
  private CAstEntity visitTypeDecl(AbstractTypeDeclaration n, WalkContext context) {
    return createClassDeclaration(n, n.bodyDeclarations(), null, n.resolveBinding(), n.getName().getIdentifier(), n.getModifiers(),
        isInterface(n), n instanceof AnnotationTypeDeclaration, context);
  }

  /**
   * 
   * @param n
   * @param bodyDecls
   * @param enumConstants
   * @param typeBinding
   * @param name Used in creating default constructor, and passed into new ClassEntity()
   * @param context
   * @return
   */
  private CAstEntity createClassDeclaration(ASTNode n, List/* <BodyDeclaration> */bodyDecls,
      List/* EnumConstantDeclaration */enumConstants, ITypeBinding typeBinding, String name, int modifiers, 
      boolean isInterface, boolean isAnnotation, WalkContext context) {
    final List<CAstEntity> memberEntities = new ArrayList<CAstEntity>();

    // find and collect all initializers (type Initializer) and field initializers (type VariableDeclarationFragment).
    // instance initializer code will be inserted into each constructors.
    // all static initializer code will be grouped together in its own entity.
    ArrayList<ASTNode> inits = new ArrayList<ASTNode>();
    ArrayList<ASTNode> staticInits = new ArrayList<ASTNode>();

    if (enumConstants != null) {
      for (Object decl : enumConstants) {
        EnumConstantDeclaration ecd = (EnumConstantDeclaration) decl;
        staticInits.add(ecd); // always (implicitly) static,final (actually, no modifiers allowed)
      }
    }

    for (Object decl : bodyDecls) {
      if (decl instanceof Initializer) {
        Initializer initializer = (Initializer) decl;
        boolean isStatic = ((initializer.getModifiers() & Modifier.STATIC) != 0);
        (isStatic ? staticInits : inits).add(initializer);
      } else if (decl instanceof FieldDeclaration) {
        FieldDeclaration fd = (FieldDeclaration) decl;

        for (Object f : fd.fragments()) {
          VariableDeclarationFragment frag = (VariableDeclarationFragment) f;
          if (frag.getInitializer() != null) {
            boolean isStatic = ((fd.getModifiers() & Modifier.STATIC) != 0);
            (isStatic ? staticInits : inits).add(frag);
          }
        }
      }
    }

    // process entities. initializers will be folded in here.
    if (enumConstants != null) {
      for (Object decl : enumConstants) {
        memberEntities.add(visit((EnumConstantDeclaration) decl, context));
      }
    }

    for (Object d : bodyDecls) {
      BodyDeclaration decl = (BodyDeclaration) d;
      if (decl instanceof FieldDeclaration) {
        FieldDeclaration fieldDecl = (FieldDeclaration) decl;
        Collection<CAstQualifier> quals = JDT2CAstUtils.mapModifiersToQualifiers(fieldDecl.getModifiers(), false, false);
        for (Object f : fieldDecl.fragments()) {
          VariableDeclarationFragment fieldFrag = (VariableDeclarationFragment) f;
          IVariableBinding fieldBinding = fieldFrag.resolveBinding();
		memberEntities.add(new FieldEntity(fieldFrag.getName().getIdentifier(), fieldBinding.getType(), quals,
              makePosition(fieldFrag.getStartPosition(), fieldFrag.getStartPosition() + fieldFrag.getLength()),
              handleAnnotations(fieldBinding)));
        }
      } else if (decl instanceof Initializer) {
        // Initializers are inserted into constructors when making constructors.
      } else if (decl instanceof MethodDeclaration) {
        MethodDeclaration metDecl = (MethodDeclaration) decl;

        if (typeBinding.isEnum() && metDecl.isConstructor())
          memberEntities.add(createEnumConstructorWithParameters(metDecl.resolveBinding(), metDecl, context, inits, metDecl));
        else {
          memberEntities.add(visit(metDecl, typeBinding, context, inits));

          // /////////////// Java 1.5 "overridden with subtype" thing (covariant return type) ///////////
          Collection<IMethodBinding> overriddenMets = JDT2CAstUtils.getOverriddenMethod(metDecl.resolveBinding());
          if (overriddenMets != null) {
            for (IMethodBinding overridden : overriddenMets)
              if (!JDT2CAstUtils.sameErasedSignatureAndReturnType(metDecl.resolveBinding(), overridden))
                memberEntities.add(makeSyntheticCovariantRedirect(metDecl, metDecl.resolveBinding(), overridden, context));
          }
        }
      } else if (decl instanceof AbstractTypeDeclaration) {
        memberEntities.add(visit((AbstractTypeDeclaration) decl, context));
      } else if (decl instanceof AnnotationTypeMemberDeclaration) {
        // TODO: need to decide what to do with these
      } else {
        Assertions.UNREACHABLE("BodyDeclaration not Field, Initializer, or Method");
      }
    }

    // add default constructor(s) if necessary
    // most default constructors have no parameters; however, those created by anonymous classes will have parameters
    // (they just call super with those parameters)
    for (Object m : typeBinding.getDeclaredMethods()) {
      IMethodBinding met = (IMethodBinding) m;
      if (met.isDefaultConstructor()) {
        if (typeBinding.isEnum())
          memberEntities.add(createEnumConstructorWithParameters(met, n, context, inits, null));
        else if (met.getParameterTypes().length > 0)
          memberEntities.add(createDefaultConstructorWithParameters(met, n, context, inits));
        else
          memberEntities.add(createDefaultConstructor(name, typeBinding, context, inits, n));
      }
    }

    if (typeBinding.isEnum() && !typeBinding.isAnonymous())
      doEnumHiddenEntities(typeBinding, staticInits, memberEntities, context);

    // collect static inits
    if (!staticInits.isEmpty()) {
      Map<CAstNode, CAstEntity> childEntities = HashMapFactory.make();
      final MethodContext newContext = new MethodContext(context, childEntities);
      // childEntities is the same one as in the ProcedureEntity. later visit(New), etc. may add to this.

      CAstNode[] bodyNodes = new CAstNode[staticInits.size()];
      for (int i = 0; i < staticInits.size(); i++)
        bodyNodes[i] = visitFieldInitNode(staticInits.get(i), newContext);
      CAstNode staticInitAst = makeNode(newContext, fFactory, n, CAstNode.BLOCK_STMT, bodyNodes);
      memberEntities.add(new ProcedureEntity(staticInitAst, typeBinding, childEntities, newContext, null));
    }

    Collection<CAstQualifier> quals = JDT2CAstUtils.mapModifiersToQualifiers(modifiers, isInterface, isAnnotation);

    Set<CAstAnnotation> annotations = handleAnnotations(typeBinding);

    return new ClassEntity(typeBinding, name, quals, memberEntities, makePosition(n), annotations);
  }

  private CAstEntity visit(AnonymousClassDeclaration n, WalkContext context) {
    return createClassDeclaration(n, n.bodyDeclarations(), null, n.resolveBinding(),
        JDT2CAstUtils.anonTypeName(n.resolveBinding()), 0 /* no modifiers */, false, false, context);
  }

  private CAstNode visit(TypeDeclarationStatement n, WalkContext context) {
    // TODO 1.6: enums of course...
    AbstractTypeDeclaration decl = n.getDeclaration();
    assert decl instanceof TypeDeclaration : "Local enum declaration not yet supported";
    CAstEntity classEntity = visitTypeDecl((TypeDeclaration) decl, context);

    // these statements doin't actually do anything, just define a type
    final CAstNode lcdNode = makeNode(context, fFactory, n, CAstNode.EMPTY);

    // so define it!
    context.addScopedEntity(lcdNode, classEntity);
    return lcdNode;
  }

  // ////////////////////////////////
  // METHODS
  // ////////////////////////////////

  /**
   * @param n for positioning.
   *
   * Make a constructor with parameters that calls super(...) with parameters. Used for anonymous classes with arguments to a
   * constructor, like new Foo(arg1,arg2) { }
   */
  private CAstEntity createDefaultConstructorWithParameters(IMethodBinding ctor, ASTNode n, WalkContext oldContext,
      ArrayList<ASTNode> inits) {
    // PART I: find super ctor to call
    ITypeBinding newType = ctor.getDeclaringClass();
    ITypeBinding superType = newType.getSuperclass();
    IMethodBinding superCtor = null;

    for (IMethodBinding m : superType.getDeclaredMethods())
      if (m.isConstructor() && Arrays.equals(m.getParameterTypes(), ctor.getParameterTypes()))
        superCtor = m;

    assert superCtor != null : "couldn't find constructor for anonymous class";

    // PART II: make ctor with simply "super(a,b,c...)"
    final Map<CAstNode, CAstEntity> memberEntities = new LinkedHashMap<CAstNode, CAstEntity>();
    final MethodContext context = new MethodContext(oldContext, memberEntities);
    MethodDeclaration fakeCtor = ast.newMethodDeclaration();
    fakeCtor.setConstructor(true);
    fakeCtor.setSourceRange(n.getStartPosition(), n.getLength());
    fakeCtor.setBody(ast.newBlock());

    // PART IIa: make a fake JDT constructor method with the proper number of args
    // Make fake args that will be passed
    String[] fakeArguments = new String[superCtor.getParameterTypes().length + 1];
    ArrayList<CAstType> paramTypes = new ArrayList<CAstType>(superCtor.getParameterTypes().length);
    for (int i = 0; i < fakeArguments.length; i++)
      fakeArguments[i] = (i == 0) ? "this" : ("argument" + i); // TODO: change to invalid name and don't use
                                                               // singlevariabledeclaration below
    for (int i = 1; i < fakeArguments.length; i++) {
      // the name
      SingleVariableDeclaration svd = ast.newSingleVariableDeclaration();
      svd.setName(ast.newSimpleName(fakeArguments[i]));
      fakeCtor.parameters().add(svd);

      // the type
      paramTypes.add(fTypeDict.getCAstTypeFor(ctor.getParameterTypes()[i - 1]));
    }

    // PART IIb: create the statements in the constructor
    // one super() call plus the inits
    CAstNode[] bodyNodes = new CAstNode[inits.size() + 1];

    // make super(...) call
    // this, call ref, args
    CAstNode[] children = new CAstNode[fakeArguments.length + 1];
    children[0] = makeNode(context, fFactory, n, CAstNode.SUPER);
    CallSiteReference callSiteRef = CallSiteReference.make(0, fIdentityMapper.getMethodRef(superCtor),
        IInvokeInstruction.Dispatch.SPECIAL);
    children[1] = fFactory.makeConstant(callSiteRef);
    for (int i = 1; i < fakeArguments.length; i++) {
      children[i + 1] = makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(fakeArguments[i]));
    }
    bodyNodes[0] = makeNode(context, fFactory, n, CAstNode.CALL, children);
    // QUESTION: no handleExceptions?

    for (int i = 0; i < inits.size(); i++)
      bodyNodes[i + 1] = visitFieldInitNode(inits.get(i), context);

    // finally, make the procedure entity
    CAstNode ast = makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, bodyNodes);
    return new ProcedureEntity(ast, fakeCtor, newType, memberEntities, context, paramTypes, null, null);

  }

  private CAstEntity createDefaultConstructor(String className, ITypeBinding classBinding, WalkContext oldContext,
      ArrayList<ASTNode> inits, ASTNode positioningNode) {
    MethodDeclaration fakeCtor = ast.newMethodDeclaration();
    fakeCtor.setConstructor(true);
    // fakeCtor.setName(ast.newSimpleName(className)); will crash on anonymous types...
    fakeCtor.setSourceRange(positioningNode.getStartPosition(), positioningNode.getLength());
    fakeCtor.setBody(ast.newBlock());

    return visit(fakeCtor, classBinding, oldContext, inits);
  }

  private IMethodBinding findDefaultCtor(ITypeBinding superClass) {
    for (IMethodBinding met : superClass.getDeclaredMethods()) {
      if (met.isConstructor() && met.getParameterTypes().length == 0)
        return met;
    }
    Assertions.UNREACHABLE("Couldn't find default ctor");
    return null;
  }

  /**
   * Setup constructor body. Here we add the initializer code (both initalizer blocks and initializers in field declarations). We
   * may also need to add an implicit super() call.
   * 
   * @param n
   * @param classBinding Used so we can use this with fake MethodDeclaration nodes, as in the case of creating a default
   *          constructor.
   * @param context
   * @param inits
   * @return
   */
  private CAstNode createConstructorBody(MethodDeclaration n, ITypeBinding classBinding, WalkContext context,
      ArrayList<ASTNode> inits) {
    // three possibilites: has super(), has this(), has neither.

    Statement firstStatement = null;
    if (!n.getBody().statements().isEmpty())
      firstStatement = (Statement) n.getBody().statements().get(0);
    if (firstStatement instanceof SuperConstructorInvocation) {
      // Split at call to super:
      // super();
      // field initializer code
      // remainder of ctor body
      ArrayList<CAstNode> origStatements = createBlock(n.getBody(), context);
      CAstNode[] bodyNodes = new CAstNode[inits.size() + origStatements.size()];
      int idx = 0;
      bodyNodes[idx++] = origStatements.get(0);
      for (ASTNode init : inits)
        bodyNodes[idx++] = visitFieldInitNode(init, context); // visit each in this constructor's context, ensuring
      // proper handling of exceptions (we can't just reuse the
      // CAstNodes)
      for (int i = 1; i < origStatements.size(); i++)
        bodyNodes[idx++] = origStatements.get(i);

      return makeNode(context, fFactory, n.getBody(), CAstNode.BLOCK_STMT, bodyNodes); // QUESTION: why no LOCAL_SCOPE?
      // that's the way it is in
      // polyglot.
    } else if (firstStatement instanceof ConstructorInvocation) {
      return visitNode(n.getBody(), context); // has this(...) call; initializers will be set somewhere else.
    } else {
      // add explicit call to default super()
      // QUESTION their todo: following superClass lookup of default ctor won't work if we
      // process Object in source...

      ITypeBinding superType = classBinding.getSuperclass();
      
      // find default constructor. IT is an error to have a constructor
      // without super() when the default constructor of the superclass does not exist.
      IMethodBinding defaultSuperCtor = findDefaultCtor(superType);
      CallSiteReference callSiteRef = CallSiteReference.make(0, fIdentityMapper.getMethodRef(defaultSuperCtor),
          IInvokeInstruction.Dispatch.SPECIAL);

      // QUESTION: why isn't first arg this like in visit(ConstructorInvocation) ?
      // why don't we handle exceptions like in visit(ConstructorInvocation) ? (these two things are same in polyglot
      // implementation)

      CAstNode superCall = makeNode(context, fFactory, n.getBody(), CAstNode.CALL, makeNode(context, fFactory, n.getBody(),
          CAstNode.SUPER), fFactory.makeConstant(callSiteRef));
      Object mapper = new Object(); // dummy used for mapping this node in CFG
      handleThrowsFromCall(defaultSuperCtor, mapper, context);
      context.cfg().map(mapper, superCall);

      ArrayList<CAstNode> origStatements = createBlock(n.getBody(), context);
      CAstNode[] bodyNodes = new CAstNode[inits.size() + origStatements.size() + 1];
      // superCall, inits, ctor body
      int idx = 0;
      bodyNodes[idx++] = superCall;
      for (ASTNode init : inits)
        bodyNodes[idx++] = visitFieldInitNode(init, context);
      for (int i = 0; i < origStatements.size(); i++)
        bodyNodes[idx++] = origStatements.get(i);
      return makeNode(context, fFactory, n.getBody(), CAstNode.BLOCK_STMT, bodyNodes);
    }
  }

  /**
   * Make a "fake" function (it doesn't exist in source code but it does in bytecode) for covariant return types.
   * 
   * @param overriding Declaration of the overriding method.
   * @param overridden Binding of the overridden method, in a a superclass or implemented interface.
   * @param oldContext
   * @return
   */
  private CAstEntity makeSyntheticCovariantRedirect(MethodDeclaration overriding, IMethodBinding overridingBinding,
      IMethodBinding overridden, WalkContext oldContext) {
    // SuperClass foo(A, B, C...)
    // SubClass foo(A,B,C...)
    //
    // add a method exactly like overridden that calls overriding

    final Map<CAstNode, CAstEntity> memberEntities = new LinkedHashMap<CAstNode, CAstEntity>();
    final MethodContext context = new MethodContext(oldContext, memberEntities);

    CAstNode calltarget;
    if ((overridingBinding.getModifiers() & Modifier.STATIC) == 0)
      calltarget = makeNode(context, fFactory, null, CAstNode.SUPER);
    else
      calltarget = makeNode(context, fFactory, null, CAstNode.VOID);

    ITypeBinding paramTypes[] = overridden.getParameterTypes();

    ArrayList<CAstNode> arguments = new ArrayList<CAstNode>();
    int i = 0;
    for (Object o : overriding.parameters()) {
      SingleVariableDeclaration svd = (SingleVariableDeclaration) o;
      CAstNode varNode = makeNode(context, fFactory, null, CAstNode.VAR, fFactory.makeConstant(svd.getName().getIdentifier()));
      ITypeBinding fromType = JDT2CAstUtils.getErasedType(paramTypes[i], ast);
      ITypeBinding toType = JDT2CAstUtils.getErasedType(overridingBinding.getParameterTypes()[i], ast);
      if (fromType.equals(toType)) {
        arguments.add(varNode);
      } else {
        arguments.add(createCast(null, varNode, fromType, toType, context));
      }
      i++;
    }
    CAstNode callnode = createMethodInvocation(null, overridingBinding, calltarget, arguments, context);
    CAstNode mdast = makeNode(context, fFactory, null, CAstNode.LOCAL_SCOPE, makeNode(context, fFactory, null, CAstNode.BLOCK_STMT,
        makeNode(context, fFactory, null, CAstNode.RETURN, callnode)));

    // make parameters to new synthetic method
    // use RETURN TYPE of overridden, everything else from overriding (including parameter names)
    ArrayList<CAstType> paramCAstTypes = new ArrayList<CAstType>(overridden.getParameterTypes().length);
    for (ITypeBinding paramType : overridden.getParameterTypes())
      paramCAstTypes.add(fTypeDict.getCAstTypeFor(paramType));
    return new ProcedureEntity(mdast, overriding, overridingBinding.getDeclaringClass(), memberEntities, context, paramCAstTypes,
        overridden.getReturnType(), null);
  }

  /**
   * @param inits Instance intializers & field initializers. Only used if method is a constructor, in which case the initializers
   *          will be inserted in.
   */
  private CAstEntity visit(MethodDeclaration n, ITypeBinding classBinding, WalkContext oldContext, ArrayList<ASTNode> inits) {

    // pass in memberEntities to the context, later visit(New) etc. may add classes
    final Map<CAstNode, CAstEntity> memberEntities = new LinkedHashMap<CAstNode, CAstEntity>();
    final MethodContext context = new MethodContext(oldContext, memberEntities); // LEFTOUT: in polyglot there is a
    // class context in between method and
    // root

    CAstNode mdast;

    if (n.isConstructor())
      mdast = createConstructorBody(n, classBinding, context, inits);
    else if ((n.getModifiers() & Modifier.ABSTRACT) != 0) // abstract
      mdast = null;
    else if (n.getBody() == null || n.getBody().statements().size() == 0) // empty
      mdast = makeNode(context, fFactory, n, CAstNode.RETURN);
    else
      mdast = visitNode(n.getBody(), context);
    // Polyglot comment: 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.
    
    Set<CAstAnnotation> annotations = null;
    if (n.resolveBinding() != null) {
    	annotations = handleAnnotations(n.resolveBinding());
    }
    
    return new ProcedureEntity(mdast, n, classBinding, memberEntities, context, annotations);
  }

  private Set<CAstAnnotation> handleAnnotations(IBinding binding) {
    IAnnotationBinding[] annotations = binding.getAnnotations();
    
    if(annotations == null || annotations.length == 0) {
    	return null;
    }
    
    Set<CAstAnnotation> castAnnotations = HashSetFactory.make();
    for(IAnnotationBinding annotation : annotations) {
    	ITypeBinding annotationTypeBinding = annotation.getAnnotationType();
    	final CAstType annotationType = fTypeDict.getCAstTypeFor(annotationTypeBinding);
    	final Map<String,Object> args = HashMapFactory.make();
    	for(IMemberValuePairBinding mvpb : annotation.getAllMemberValuePairs()) {
    		String name = mvpb.getName();
    		Object value = mvpb.getValue();
    		args.put(name, value);
    	}
    	castAnnotations.add(new CAstAnnotation() {
			@Override
			public CAstType getType() {
				return annotationType;
			}
			@Override
			public Map<String, Object> getArguments() {
				return args;
			}
			@Override
			public String toString() {
				return annotationType.getName() + args;
			}
    	});
    }
    
    return castAnnotations;
}

  protected final class ProcedureEntity implements JavaProcedureEntity { // TAGALONG (make static, access ast)

    // From Code Body Entity
    private final Map<CAstNode, Collection<CAstEntity>> fEntities;

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

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

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

    private final CAstNode fAst;

    MethodDeclaration fDecl; // TAGALONG serious tagalong...

    private String[] fParameterNames; // INCLUDING this

    private ArrayList<CAstType> fParameterTypes;

    private MethodContext fContext; // possibly TAGALONG, maybe not

    private ITypeBinding fType; // TAGALONG

    protected ITypeBinding fReturnType;

    private int fModifiers;

    private final Set<CAstAnnotation> annotations;

    // can be method, constructor, "fake" default constructor, or null decl = static initializer
    /**
     * For a static initializer, pass a null decl.
     */
    // FIXME: get rid of decl and pass in everything instead of having to do two different things with parameters
    // regular case
    private ProcedureEntity(CAstNode mdast, MethodDeclaration decl, ITypeBinding type, Map<CAstNode, CAstEntity> entities,
        MethodContext context, Set<CAstAnnotation> annotations) {
      this(mdast, decl, type, entities, context, null, null, decl.getModifiers(), annotations);
    }

    // static init
    private ProcedureEntity(CAstNode mdast, ITypeBinding type, Map<CAstNode, CAstEntity> entities, MethodContext context, Set<CAstAnnotation> annotations) {
      this(mdast, null, type, entities, context, null, null, 0, annotations);
    }

    private ProcedureEntity(CAstNode mdast, MethodDeclaration decl, ITypeBinding type, Map<CAstNode, CAstEntity> entities,
        MethodContext context, ArrayList<CAstType> parameterTypes, ITypeBinding returnType, Set<CAstAnnotation> annotations) {
      this(mdast, decl, type, entities, context, parameterTypes, returnType, decl.getModifiers(), annotations);
    }

    private ProcedureEntity(CAstNode mdast, MethodDeclaration decl, ITypeBinding type, Map<CAstNode, CAstEntity> entities,
        MethodContext context, ArrayList<CAstType> parameterTypes, ITypeBinding returnType, int modifiers, Set<CAstAnnotation> annotations) {
      // TypeSystem system, CodeInstance pd, String[] argumentNames,
      // }
      // Map<CAstNode, CAstEntity> entities, MethodContext mc) {
      fDecl = decl;
      fAst = mdast; // "procedure decl ast"
      fContext = context;
      fType = type;
      fReturnType = returnType;
      fModifiers = modifiers;
      this.annotations = annotations;

      // from CodeBodyEntity
      fEntities = new LinkedHashMap<CAstNode, Collection<CAstEntity>>();
      for (Iterator keys = entities.keySet().iterator(); keys.hasNext();) {
        CAstNode key = (CAstNode) keys.next();
        fEntities.put(key, Collections.singleton(entities.get(key)));
      }

      if (fDecl != null) {
        int i = 0; // index to start filling up with real params
        if ((fModifiers & Modifier.STATIC) != 0) {
          fParameterNames = new String[fDecl.parameters().size()];
          i = 0;
        } else {
          fParameterNames = new String[fDecl.parameters().size() + 1];
          fParameterNames[0] = "this";
          i = 1;
        }

        if (parameterTypes == null) {
          fParameterTypes = new ArrayList<CAstType>(fDecl.parameters().size());
          for (Object p : fDecl.parameters()) {
            fParameterNames[i++] = ((SingleVariableDeclaration) p).getName().getIdentifier();
            fParameterTypes.add(fTypeDict.getCAstTypeFor(((SingleVariableDeclaration) p).resolveBinding().getType()));
          }
        } else {
          // currently this is only used in making a default constructor with arguments (anonymous classes).
          // this is because we cannot synthesize bindings.
          fParameterTypes = parameterTypes;
          for (Object p : fDecl.parameters()) {
            fParameterNames[i++] = ((SingleVariableDeclaration) p).getName().getIdentifier();
          }
        }
      } else {
        fParameterNames = new String[0];
        fParameterTypes = new ArrayList<CAstType>(0); // static initializer
      }
    }

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

	public String toString() {
      return fDecl == null ? "<clinit>" : fDecl.toString();
    }

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

    public String getName() {
      if (fDecl == null)
        return MethodReference.clinitName.toString();
      else if (fDecl.isConstructor())
        return MethodReference.initAtom.toString();
      else
        return fDecl.getName().getIdentifier();
    }

    /**
     * INCLUDING first parameter 'this' (for non-static methods)
     */
    public String[] getArgumentNames() {
      return fParameterNames;
    }

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

    /**
     * INCLUDING first parameter 'this' (for non-static methods)
     */
    public int getArgumentCount() {
      return fParameterNames.length;
    }

    public CAstNode getAST() {
      return fAst;
    }

    public CAstControlFlowMap getControlFlow() {
      return fContext.cfg();
    }

    public CAstSourcePositionMap getSourceMap() {
      return fContext.pos();
    }

    public CAstSourcePositionMap.Position getPosition() {
      return fDecl==null? getSourceMap().getPosition(fAst): makePosition(fDecl);
    }

    public CAstNodeTypeMap getNodeTypeMap() {
      return fContext.getNodeTypeMap();
    }

    public Collection getQualifiers() {
      if (fDecl == null)
        return JDT2CAstUtils.mapModifiersToQualifiers(Modifier.STATIC, false, false); // static init
      else
        return JDT2CAstUtils.mapModifiersToQualifiers(fModifiers, false, false);
    }

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

        @SuppressWarnings("deprecation")
        public CAstType getReturnType() {
          if (fReturnType != null)
            return fTypeDict.getCAstTypeFor(fReturnType);
          Type type = fDecl == null ? null : (ast.apiLevel() == 2 ? fDecl.getReturnType() : fDecl.getReturnType2());
          if (type == null)
            return fTypeDict.getCAstTypeFor(ast.resolveWellKnownType("void"));
          else
            return fTypeDict.getCAstTypeFor(type.resolveBinding());
        }

        /**
         * NOT INCLUDING first parameter 'this' (for non-static methods)
         */
        public List getArgumentTypes() {
          return fParameterTypes;
        }

        /**
         * NOT INCLUDING first parameter 'this' (for non-static methods)
         */
        public int getArgumentCount() {
          return fDecl == null ? 0 : fParameterTypes.size();
        }

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

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

        public Collection/* <CAstType> */getExceptionTypes() {
          if (fExceptionTypes == null) {
            fExceptionTypes = new LinkedHashSet<CAstType>();
            if (fDecl != null)
              for (Object exception : fDecl.thrownExceptions())
                fExceptionTypes.add(fTypeDict.getCAstTypeFor(((Name) exception).resolveTypeBinding()));
          }
          return fExceptionTypes;
        }

        public CAstType getDeclaringType() {
          return fTypeDict.getCAstTypeFor(fType);
        }
      };
    }
  }

  // ////////////////////////////////////
  // FIELDS ////////////////////////////
  // ////////////////////////////////////

  // FIELDS ADDED DIRECTLY IN visit(TypeDeclaration,WalkContext)

  private CAstNode visitFieldInitNode(ASTNode node, WalkContext context) {
    // there are gathered by createClassDeclaration and can only be of two types:
    if (node instanceof Initializer) {
      return visitNode(((Initializer) node).getBody(), context);
    } else if (node instanceof VariableDeclarationFragment) {
      VariableDeclarationFragment f = (VariableDeclarationFragment) node; // this is guaranteed to have an initializer

      // Generate CAST node for the initializer (init())
      // Type targetType = f.memberInstance().container();
      // Type fieldType = f.type().type();
      FieldReference fieldRef = fIdentityMapper.getFieldRef(f.resolveBinding());
      // 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.
      boolean isStatic = ((f.resolveBinding().getModifiers() & Modifier.STATIC) != 0);
      CAstNode thisNode = isStatic ? makeNode(context, fFactory, null, CAstNode.VOID) : makeNode(context, fFactory, f,
          CAstNode.THIS);
      CAstNode lhsNode = makeNode(context, fFactory, f, CAstNode.OBJECT_REF, thisNode, fFactory.makeConstant(fieldRef));

      Expression init = f.getInitializer();
      CAstNode rhsNode = visitNode(init, context);
      CAstNode assNode = makeNode(context, fFactory, f, CAstNode.ASSIGN, lhsNode, rhsNode);

      return assNode; // their naming, not mine

    } else if (node instanceof EnumConstantDeclaration) {
      return createEnumConstantDeclarationInit((EnumConstantDeclaration) node, context);
    } else {
      Assertions.UNREACHABLE("invalid init node gathered by createClassDeclaration");
      return null;
    }
  }

  protected final class FieldEntity implements CAstEntity {
    private final ITypeBinding type;

    private final String name;

    private final Collection<CAstQualifier> quals;

    private final JdtPosition position;

    private final Set<CAstAnnotation> annotations;

    private FieldEntity(String name, ITypeBinding type, Collection<CAstQualifier> quals, JdtPosition position, Set<CAstAnnotation> annotations) {
      super();
      this.type = type;
      this.quals = quals;
      this.name = name;
      this.position = position;
      this.annotations = annotations;
    }

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

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

    public String getName() {
      return name;
    }

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

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

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

    public int getArgumentCount() {
      return 0;
    }

    public Iterator getScopedEntities(CAstNode construct) {
      return EmptyIterator.instance();
    }

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

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

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

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

    public CAstSourcePositionMap.Position getPosition() {
      return position;
    }

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

    public Collection getQualifiers() {
      return quals;
    }

    public CAstType getType() {
      return fTypeDict.getCAstTypeFor(type);
    }
  }

  // /////////////////////////////////////
  // / NODES /////////////////////////////
  // /////////////////////////////////////

  /**
   * Visit all the statements in the block and return an arraylist of the statements. Some statements
   * (VariableDeclarationStatements) may expand to more than one CAstNode.
   */
  private ArrayList<CAstNode> createBlock(Block n, WalkContext context) {
    ArrayList<CAstNode> stmtNodes = new ArrayList<CAstNode>();
    for (Object s : n.statements())
      visitNodeOrNodes((ASTNode) s, context, stmtNodes);
    return stmtNodes;
  }

  private CAstNode visit(Block n, WalkContext context) {
    ArrayList<CAstNode> stmtNodes = createBlock(n, context);
    CAstNode stmtNodesArray[] = stmtNodes.toArray(new CAstNode[stmtNodes.size()]);
    return makeNode(context, fFactory, n, CAstNode.LOCAL_SCOPE, makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, stmtNodesArray));
  }

  private CAstNode visit(VariableDeclarationFragment n, WalkContext context) {
    int modifiers;
    if (n.getParent() instanceof VariableDeclarationStatement)
      modifiers = ((VariableDeclarationStatement) n.getParent()).getModifiers();
    else if (n.getParent() instanceof VariableDeclarationExpression)
      modifiers = ((VariableDeclarationExpression) n.getParent()).getModifiers();
    else
      modifiers = ((FieldDeclaration) n.getParent()).getModifiers();
    boolean isFinal = (modifiers & Modifier.FINAL) != 0;
    ITypeBinding type = n.resolveBinding().getType();
    Expression init = n.getInitializer();
    CAstNode initNode;

    String t = type.getBinaryName();
    if (init == null) {
      if (JDT2CAstUtils.isLongOrLess(type)) // doesn't include boolean
        initNode = fFactory.makeConstant(0);
      else if (t.equals("D") || t.equals("F"))
        initNode = fFactory.makeConstant(0.0);
      else
        initNode = fFactory.makeConstant(null);
    } else
      initNode = visitNode(init, context);

    Object defaultValue = JDT2CAstUtils.defaultValueForType(type);
    return makeNode(context, fFactory, n, CAstNode.DECL_STMT, fFactory.makeConstant(new CAstSymbolImpl(n.getName().getIdentifier(),
        isFinal, defaultValue)), initNode);
  }

  /*
   * One VariableDeclarationStatement represents more than one CAstNode statement.
   */
  private ArrayList<CAstNode> visit(VariableDeclarationStatement n, WalkContext context) {
    ArrayList<CAstNode> result = new ArrayList<CAstNode>();

    for (Object o : n.fragments())
      result.add(visit((VariableDeclarationFragment) o, context));
    return result;
  }

  private CAstNode visit(ArrayInitializer n, WalkContext context) {
    ITypeBinding type = n.resolveTypeBinding();
    assert type != null : "Could not determine type of ArrayInitializer";
    TypeReference newTypeRef = fIdentityMapper.getTypeRef(type);
    CAstNode[] eltNodes = new CAstNode[n.expressions().size() + 1];
    int idx = 0;

    eltNodes[idx++] = makeNode(context, fFactory, n, CAstNode.NEW, fFactory.makeConstant(newTypeRef), fFactory.makeConstant(n
        .expressions().size()));
    for (Iterator iter = n.expressions().iterator(); iter.hasNext(); idx++) {
      Expression element = (Expression) iter.next();
      eltNodes[idx] = visitNode(element, context);
      if (eltNodes[idx] == null)
        assert eltNodes[idx] != null : element.toString();
    }

    return makeNode(context, fFactory, n, CAstNode.ARRAY_LITERAL, eltNodes);
  }

  private CAstNode visit(ClassInstanceCreation n, WalkContext context) {
    return createClassInstanceCreation(n, n.arguments(), n.resolveConstructorBinding(), n.getExpression(), n
        .getAnonymousClassDeclaration(), context);
  }

  private CAstNode createClassInstanceCreation(ASTNode nn, List/* Expression or CAstNode */arguments, IMethodBinding ctorBinding,
      Expression qual, AnonymousClassDeclaration anonDecl, WalkContext context) {
    // a new instruction is actually two things: a NEW object and a CALL to a constructor
    CAstNode newNode;
    CAstNode callNode;
    final String tmpName = "ctor temp";
    // this name is an illegal Java identifier. this var will hold the new object so we can call the constructor on it

    // GENERICS getMethodDeclaration()
    ctorBinding = ctorBinding.getMethodDeclaration(); // unlike polyglot, this will
    // point to a default
    // constructor in the anon class
    MethodReference ctorRef = fIdentityMapper.getMethodRef(ctorBinding);

    // ////////////// PART I: make the NEW expression ///////////////////////////////////////////////////////

    ITypeBinding newType = ctorBinding.getDeclaringClass();
    TypeReference newTypeRef = fIdentityMapper.getTypeRef(newType);

    // new nodes with an explicit enclosing argument, e.g. "outer.new Inner()". They are mostly treated the same, except
    // in JavaCAst2IRTranslator.doNewObject
    CAstNode qualNode = null;

    if (qual == null && newType.getDeclaringClass() != null && ((newType.getModifiers() & Modifier.STATIC) == 0)
        && !newType.isLocal()) {
      // "new X()" expanded into "new this.X()" or "new MyClass.this.X"
      // check isLocal because anonymous classes and local classes are not included.
      ITypeBinding plainThisType = JDT2CAstUtils.getDeclaringClassOfNode(nn); // type of "this"
      ITypeBinding implicitThisType = findClosestEnclosingClassSubclassOf(plainThisType, newType.getDeclaringClass(), ((newType
          .getModifiers() & Modifier.PRIVATE) != 0));
      if (implicitThisType.isEqualTo(plainThisType))
        qualNode = makeNode(context, fFactory, nn, CAstNode.THIS); // "new this.X()"
      else
        qualNode = makeNode(context, fFactory, nn, CAstNode.THIS, fFactory.makeConstant(fIdentityMapper
            .getTypeRef(implicitThisType))); // "new Bla.this.X()"
    } else if (qual != null)
      qualNode = visitNode(qual, context);

    if (qualNode != null)
      newNode = makeNode(context, fFactory, nn, CAstNode.NEW_ENCLOSING, fFactory.makeConstant(newTypeRef), qualNode);
    else
      newNode = makeNode(context, fFactory, nn, CAstNode.NEW, fFactory.makeConstant(newTypeRef));

    ITypeBinding[] newExceptions = new ITypeBinding[] { NoClassDefFoundError, ExceptionInInitializerError, OutOfMemoryError };
    context.cfg().map(newNode, newNode);
    for (ITypeBinding exp : newExceptions) {
      for (Pair<ITypeBinding, Object> catchTarget : context.getCatchTargets(exp)) {
        context.cfg().add(newNode, catchTarget.snd, catchTarget.fst);
      }
    }

    // ANONYMOUS CLASSES
    // ctor already points to right place, so should type ref, so all we have to do is make the entity
    if (anonDecl != null)
      context.addScopedEntity(newNode, visit(anonDecl, context));

    // ////////////// PART II: make the CALL expression ///////////////////////////////////////////////////////
    // setup args & handle exceptions

    CAstNode[] argNodes = new CAstNode[arguments.size() + 2];
    int idx = 0; // args: [ this, callsiteref, <actual args> ]

    // arg 0: this
    argNodes[idx++] = makeNode(context, fFactory, nn, CAstNode.VAR, fFactory.makeConstant(tmpName));
    // contains output from newNode (see part III)

    // arg 1: call site ref (WHY?)
    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);
    argNodes[idx++] = fFactory.makeConstant(callSiteRef);

    // rest of args
    for (Iterator iter = arguments.iterator(); iter.hasNext();) {
      Object arg = iter.next();
      argNodes[idx++] = (arg instanceof CAstNode) ? ((CAstNode) arg) : visitNode((Expression) arg, context);
    }
    callNode = makeNode(context, fFactory, nn, CAstNode.CALL, argNodes);
    context.cfg().map(nn, callNode);

    handleThrowsFromCall(ctorBinding, nn, context);

    // PART III: make a node with both NEW and CALL
    // Make a LOCAL_SCOPE with a BLOCK_EXPR node child which does three things:
    // 1) declare a temporary variable and assign the new object to it (LOCAL_SCOPE is needed to chain this new variable
    // to this block)
    // 2) CALL the constructor on the new variable
    // 3) access this temporary variable. Since the value of the block is the last thing in the block, the resultant
    // value will be the variable
    return makeNode(context, fFactory, nn, CAstNode.LOCAL_SCOPE, makeNode(context, fFactory, nn, CAstNode.BLOCK_EXPR, makeNode(
        context, fFactory, nn, CAstNode.DECL_STMT, fFactory.makeConstant(new InternalCAstSymbol(tmpName, true)), newNode),
        callNode, makeNode(context, fFactory, nn, CAstNode.VAR, fFactory.makeConstant(tmpName))));
  }

  /**
   * 
   * @param met
   * @param mappedAstNode An AST node or object mapped in the CFG: we will call context.cfg().add() on it. Caller must worry about
   *          mapping it with context.cfg().map().
   * @param context
   */
  private void handleThrowsFromCall(IMethodBinding met, Object mappedAstNode, WalkContext context) {
    ITypeBinding[] throwTypes = met.getExceptionTypes();
    for (ITypeBinding thrownType : throwTypes) {
      Collection<Pair<ITypeBinding, Object>> catchTargets = context.getCatchTargets(thrownType);
      for (Pair<ITypeBinding, Object> catchTarget : catchTargets)
        context.cfg().add(mappedAstNode, catchTarget.snd, catchTarget.fst);
    }
    // can also throw runtime exception
    for (Pair<ITypeBinding, Object> catchTarget : context.getCatchTargets(fRuntimeExcType))
      context.cfg().add(mappedAstNode, catchTarget.snd, catchTarget.fst);
  }

  private CAstNode visit(ExpressionStatement n, WalkContext context) {
    return visitNode(n.getExpression(), context);
  }

  private CAstNode visit(SuperMethodInvocation n, WalkContext context) {
    CAstNode target;
    if (n.getQualifier() == null)
      target = makeNode(context, fFactory, n, CAstNode.SUPER);
    else {
      TypeReference owningTypeRef = fIdentityMapper.getTypeRef(n.getQualifier().resolveTypeBinding());
      target = makeNode(context, fFactory, n, CAstNode.SUPER, fFactory.makeConstant(owningTypeRef));
    }
    // GENERICS getMethodDeclaration()
    return createMethodInvocation(n, n.resolveMethodBinding().getMethodDeclaration(), target, n.arguments(), context);
  }

  // FIXME: implicit this
  private CAstNode visit(MethodInvocation n, WalkContext context) {
    // GENERICS getMethodDeclaration()
    IMethodBinding binding = n.resolveMethodBinding().getMethodDeclaration();
    if ((binding.getModifiers() & Modifier.STATIC) != 0) {
      CAstNode target;

      // JLS says: evaluate qualifier & throw away unless of course it's just a class name (or null),
      // in which case we replace the EMPTY with a VOID
      // of course, "this" has no side effects either.
      target = visitNode(n.getExpression(), context);
      if (target.getKind() == CAstNode.EMPTY || target.getKind() == CAstNode.THIS)
        return createMethodInvocation(n, binding, makeNode(context, fFactory, null, CAstNode.VOID), n.arguments(), context);
      else
        return makeNode(context, fFactory, n, CAstNode.BLOCK_EXPR, target, createMethodInvocation(n, binding, makeNode(context,
            fFactory, null, CAstNode.VOID), n.arguments(), context));
      // target is evaluated but thrown away, and only result of method invocation is kept

    } else {
      CAstNode target;
      if (n.getExpression() != null) {
        target = visitNode(n.getExpression(), context);
      } else {
        ITypeBinding typeOfThis = JDT2CAstUtils.getDeclaringClassOfNode(n);

        boolean methodIsPrivate = (binding.getModifiers() & Modifier.PRIVATE) != 0;

        // how could it be in the subtype and private? this only happens the supertype is also an
        // enclosing type. in that case the variable refers to the field in the enclosing instance.
        // NOTE: method may be defined in MyClass's superclass, but we still want to expand
        // this into MyClass, so we have to find the enclosing class which defines this function.

        ITypeBinding implicitThisClass = findClosestEnclosingClassSubclassOf(typeOfThis, binding.getDeclaringClass(),
            methodIsPrivate);
        if (typeOfThis.isEqualTo(implicitThisClass))
          // "foo = 5" -> "this.foo = 5": expand into THIS + class
          target = makeNode(context, fFactory, n, CAstNode.THIS);
        else
          // "foo = 5" -> "MyClass.this.foo = 5" -- inner class: expand into THIS + class
          target = makeNode(context, fFactory, n, CAstNode.THIS, fFactory.makeConstant(fIdentityMapper
              .getTypeRef(implicitThisClass)));
        // NOTE: method may be defined in MyClass's superclass, but we still want to expand
        // this into MyClass, so we have to find the enclosing class which defines this function.
      }
      CAstNode node = createMethodInvocation(n, binding, target, n.arguments(), context);
      // TODO: maybe not exactly right... what if it's a capture? we may have to cast it down a little bit.
      if (binding.getReturnType().isTypeVariable()) {
        // GENERICS: add a cast
        ITypeBinding realtype = JDT2CAstUtils.getErasedType(n.resolveMethodBinding().getReturnType(), ast);
        ITypeBinding fromtype = JDT2CAstUtils.getTypesVariablesBase(binding.getReturnType(), ast);
        if (!realtype.isEqualTo(fromtype))
          return createCast(n, node, fromtype, realtype, context);
      }
      return node;
    }
  }

  private CAstNode createMethodInvocation(ASTNode pos, IMethodBinding methodBinding, CAstNode target,
      List/* Expression or CAstNode */arguments, WalkContext context) {
    // MethodMethodInstance methodInstance = n.methodInstance();
    boolean isStatic = (methodBinding.getModifiers() & Modifier.STATIC) != 0;
    ITypeBinding methodOwner = methodBinding.getDeclaringClass();

    if (!(methodOwner.isInterface() || methodOwner.isClass() || methodOwner.isEnum())) {
      assert false : "owner " + methodOwner + " of " + methodBinding + " is not a class";
    }

    // POPULATE PARAMETERS
    // this (or void for static), method reference, rest of args
    int nFormals = methodBinding.getParameterTypes().length;
    CAstNode[] children = new CAstNode[2 + nFormals];

    // this (or void for static)
    assert target != null : "no receiver for " + methodBinding;
    children[0] = target;

    // method reference
    // unlike polyglot, expression will never be super here. this is handled in SuperMethodInvocation.
    IInvokeInstruction.IDispatch dispatchType;
    if (isStatic)
      dispatchType = IInvokeInstruction.Dispatch.STATIC;
    else if (methodOwner.isInterface())
      dispatchType = IInvokeInstruction.Dispatch.INTERFACE;
    else if ((methodBinding.getModifiers() & Modifier.PRIVATE) != 0 || target.getKind() == CAstNode.SUPER)
      // only one possibility, not a virtual call (I guess?)
      dispatchType = IInvokeInstruction.Dispatch.SPECIAL;
    else
      dispatchType = IInvokeInstruction.Dispatch.VIRTUAL;
    // pass 0 for dummyPC: Just want to wrap the kind of call; the "rear end" won't care about anything else...
    CallSiteReference callSiteRef = CallSiteReference.make(0, fIdentityMapper.getMethodRef(methodBinding), dispatchType);

    children[1] = fFactory.makeConstant(callSiteRef);

    populateArguments(children, methodBinding, arguments, context);

    Object fakeCfgMap = new Object();

    handleThrowsFromCall(methodBinding, fakeCfgMap, context);

    CAstNode result = makeNode(context, fFactory, pos, CAstNode.CALL, children);
    context.cfg().map(fakeCfgMap, result);
    return result;
  }

  /**
   * Populate children, starting at index 2, for the invocation of methodBinding. If varargs are used this function will collapse
   * the proper arguments into an array.
   * 
   * If the number of actuals equals the number of formals and the function is varargs, we have to check the type of the last
   * argument to see if we should "box" it in an array. If the arguments[arguments.length-1] is not an Expression, we cannot get the
   * type, so we do not box it. (Making covariant varargs functions require this behavior)
   * 
   * @param children
   * @param methodBinding
   * @param arguments
   * @param context
   */
  private void populateArguments(CAstNode[] children, IMethodBinding methodBinding, List/* CAstNode or Expression */arguments,
      WalkContext context) {
    int nFormals = methodBinding.getParameterTypes().length;
    assert children.length == nFormals + 2;
    int nActuals = arguments.size();

    ITypeBinding lastArgType = null;
    if (nActuals > 0 && arguments.get(nActuals - 1) instanceof Expression)
      lastArgType = ((Expression) arguments.get(nActuals - 1)).resolveTypeBinding();
    // if the # of actuals equals the # of formals, AND the function is varargs, we have to check
    // to see if the lastArgType is subtype compatible with the type of last parameter (which will be an array).
    // If it is, we pass this array in directly. Otherwise this it is wrapped in an array init.
    // Example: 'void foo(int... x)' can be run via 'foo(5)' or 'foo(new int[] { 5, 6 })' -- both have one argument so we must check
    // the type

    if (nActuals == nFormals
        && (!methodBinding.isVarargs() || lastArgType == null || lastArgType
            .isSubTypeCompatible(methodBinding.getParameterTypes()[nFormals - 1]))) {
      int i = 2;
      for (Object arg : arguments)
        children[i++] = (arg instanceof CAstNode) ? ((CAstNode) arg) : visitNode((Expression) arg, context);
    } else {
      assert nActuals >= (nFormals - 1) && methodBinding.isVarargs() : "Invalid number of parameters for constructor call";
      for (int i = 0; i < nFormals - 1; i++) {
        Object arg = arguments.get(i);
        children[i + 2] = (arg instanceof CAstNode) ? ((CAstNode) arg) : visitNode((Expression) arg, context);
      }

      CAstNode subargs[] = new CAstNode[nActuals - nFormals + 2];
      // nodes for args and one extra for NEW expression
      TypeReference newTypeRef = fIdentityMapper.getTypeRef(methodBinding.getParameterTypes()[nFormals - 1]);
      subargs[0] = makeNode(context, fFactory, null, CAstNode.NEW, fFactory.makeConstant(newTypeRef), fFactory
          .makeConstant(subargs.length - 1));
      for (int j = 1; j < subargs.length; j++) {
        Object arg = arguments.get(j + nFormals - 2);
        subargs[j] = (arg instanceof CAstNode) ? ((CAstNode) arg) : visitNode((Expression) arg, context);
      }
      children[nFormals + 1] = makeNode(context, fFactory, (ASTNode) null, CAstNode.ARRAY_LITERAL, subargs);
    }
  }

  private CAstNode visit(ReturnStatement r, WalkContext context) {
    Expression retExpr = r.getExpression();
    if (retExpr == null)
      return makeNode(context, fFactory, r, CAstNode.RETURN);
    else
      return makeNode(context, fFactory, r, CAstNode.RETURN, visitNode(retExpr, context));
  }

  private CAstNode visit(Assignment n, WalkContext context) {
    if (n.getOperator() == Assignment.Operator.ASSIGN)
      return makeNode(context, fFactory, n, CAstNode.ASSIGN, visitNode(n.getLeftHandSide(), new AssignmentContext(context)),
          visitNode(n.getRightHandSide(), context));
    else {
      CAstNode left = visitNode(n.getLeftHandSide(), context);
      // GENERICs lvalue for pre op hack
      if (left.getKind() == CAstNode.CAST) {
        return doFunkyGenericAssignPreOpHack(n, context);
      }

      // +=, %=, &=, etc.
      CAstNode result = makeNode(context, fFactory, n, CAstNode.ASSIGN_PRE_OP, left, visitNode(n.getRightHandSide(), context),
          JDT2CAstUtils.mapAssignOperator(n.getOperator()));

      // integer division by zero
      if (JDT2CAstUtils.isLongOrLess(n.resolveTypeBinding())
          && (n.getOperator() == Assignment.Operator.DIVIDE_ASSIGN || n.getOperator() == Assignment.Operator.REMAINDER_ASSIGN)) {
        Collection<Pair<ITypeBinding, Object>> excTargets = context.getCatchTargets(fDivByZeroExcType);
        if (!excTargets.isEmpty()) {
          for (Pair<ITypeBinding, Object> catchPair : excTargets)
            context.cfg().add(result, catchPair.snd, fDivByZeroExcType);
        } else {
          context.cfg().add(result, CAstControlFlowMap.EXCEPTION_TO_EXIT, fDivByZeroExcType);
        }
      }

      return result;
    }
  }

  /**
   * Consider the case:
   * 
   * <pre>
   * String real_oneheyya = (((returnObjectWithSideEffects().y))+=&quot;hey&quot;)+&quot;ya&quot;
   * </pre>
   * 
   * where field 'y' is parameterized to type string. then += is not defined for type 'object'. This function is a hack that expands
   * the code into an assignment and binary operation.
   * 
   * @param leftCast this is the left cast in the original expression. We throw most of it away, although we use the "Cast from" and
   *          "cast to"
   * @param left
   * @param context
   * @return
   */
  private CAstNode doFunkyGenericAssignPreOpHack(Assignment assign, WalkContext context) {
    Expression left = assign.getLeftHandSide();
    Expression right = assign.getRightHandSide();

    // consider the case:
    // String real_oneheyya = (((returnObjectWithSideEffects().y))+="hey")+"ya"; // this is going to be a MAJOR pain...
    // where field 'y' is parameterized to type string. then += is not defined for type 'object'. we want to transform
    // it kind of like this, except we have to define temp.
    // String real_oneheyya = (String)((temp=cg2WithSideEffects()).y = (String)temp.y + "hey")+"ya";
    // ----------------------------------------------------------------
    //
    // we are responsible for underlined portion
    // CAST(LOCAL SCOPE(BLOCK EXPR(DECL STMT(temp,
    // left.target),ASSIGN(OBJECT_REF(temp,y),BINARY_EXPR(CAST(OBJECT_REF(Temp,y)),RIGHT)))))
    // yeah, I know, it's cheating, LOCAL SCOPE / DECL STMT inside an expression ... will it work?

    while (left instanceof ParenthesizedExpression)
      left = ((ParenthesizedExpression) left).getExpression();
    assert left instanceof FieldAccess : "Cast in assign pre-op but no field access?!";

    FieldAccess field = (FieldAccess) left;
    InfixExpression.Operator infixop = JDT2CAstUtils.mapAssignOperatorToInfixOperator(assign.getOperator());

    // DECL_STMT: temp = ...;
    final String tmpName = "temp generic preop hack"; // illegal Java identifier
    CAstNode exprNode = visitNode(field.getExpression(), context);
    CAstNode tmpDeclNode = makeNode(context, fFactory, left, CAstNode.DECL_STMT, fFactory.makeConstant(new InternalCAstSymbol(
        tmpName, true)), exprNode);

    // need two object refndoes "temp.y"
    CAstNode obref1 = createFieldAccess(makeNode(context, fFactory, left, CAstNode.VAR, fFactory.makeConstant(tmpName)), field
        .getName().getIdentifier(), field.resolveFieldBinding(), left, new AssignmentContext(context));

    CAstNode obref2 = createFieldAccess(makeNode(context, fFactory, left, CAstNode.VAR, fFactory.makeConstant(tmpName)), field
        .getName().getIdentifier(), field.resolveFieldBinding(), left, context);
    ITypeBinding realtype = JDT2CAstUtils.getErasedType(field.resolveFieldBinding().getType(), ast);
    ITypeBinding fromtype = JDT2CAstUtils
        .getTypesVariablesBase(field.resolveFieldBinding().getVariableDeclaration().getType(), ast);
    CAstNode castedObref = obref2;// createCast(left, obref2, fromtype, realtype, context);

    // put it all together
    // CAST(LOCAL SCOPE(BLOCK EXPR(DECL STMT(temp,
    // left.target),ASSIGN(OBJECT_REF(temp,y),BINARY_EXPR(CAST(OBJECT_REF(Temp,y)),RIGHT)))))
    CAstNode result = makeNode(context, fFactory, assign, CAstNode.LOCAL_SCOPE, makeNode(context, fFactory, assign,
        CAstNode.BLOCK_EXPR, tmpDeclNode, makeNode(context, fFactory, assign, CAstNode.ASSIGN, obref1, createInfixExpression(
            infixop, realtype, left.getStartPosition(), left.getLength(), castedObref, right, context))));

    return createCast(assign, result, fromtype, realtype, context);
  }

  private CAstNode visit(ParenthesizedExpression n, WalkContext context) {
    return visitNode(n.getExpression(), context);
  }

  private CAstNode visit(BooleanLiteral n, WalkContext context) {
    return fFactory.makeConstant(n.booleanValue());
  }

  private CAstNode visit(CharacterLiteral n, WalkContext context) {
    return fFactory.makeConstant(n.charValue());
  }

  private CAstNode visit(NullLiteral n, WalkContext context) {
    return fFactory.makeConstant(null);
  }

  private CAstNode visit(StringLiteral n, WalkContext context) {
    return fFactory.makeConstant(n.getLiteralValue());
  }

  private CAstNode visit(TypeLiteral n, WalkContext context) {
    String typeName = fIdentityMapper.typeToTypeID(n.resolveTypeBinding());
    return makeNode(context, fFactory, n, CAstNode.TYPE_LITERAL_EXPR, fFactory.makeConstant(typeName));
  }

  private CAstNode visit(NumberLiteral n, WalkContext context) {
    return fFactory.makeConstant(n.resolveConstantExpressionValue());
  }

  /**
   * SimpleName can be a field access, local, or class name (do nothing case)
   * 
   * @param n
   * @param context
   * @return
   */
  private CAstNode visit(SimpleName n, WalkContext context) {
    // class name, handled above in either method invocation, qualified name, or qualified this
    if (n.resolveBinding() instanceof ITypeBinding)
      return makeNode(context, fFactory, null, CAstNode.EMPTY);

    assert n.resolveBinding() instanceof IVariableBinding : "SimpleName's binding is not a variable or a type binding!";

    IVariableBinding binding = (IVariableBinding) n.resolveBinding();
    binding = binding.getVariableDeclaration(); // ignore weird generic stuff

    // TODO: enum constants
    if (binding.isField()) {
      // enum constants ...

      // implicit field access -- implicit this or class
      CAstNode targetNode;

      if ((binding.getModifiers() & Modifier.STATIC) != 0) {
        // "foo = 5" -> "MyClass.foo = 5" or "SomeEnclosingClass.foo" = 5
        targetNode = makeNode(context, fFactory, null, CAstNode.EMPTY); // we will get type from binding. no side
        // effects in evaluating a class name, so NOP
        // here.
      } else {
        ITypeBinding typeOfThis = JDT2CAstUtils.getDeclaringClassOfNode(n);

        boolean fieldIsPrivate = (binding.getModifiers() & Modifier.PRIVATE) != 0;

        // how could it be in the subtype and private? this only happens the supertype is also an
        // enclosing type. in that case the variable refers to the field in the enclosing instance.
        // NOTE: method may be defined in MyClass's superclass, but we still want to expand
        // this into MyClass, so we have to find the enclosing class which defines this function.

        ITypeBinding implicitThisClass = findClosestEnclosingClassSubclassOf(typeOfThis, binding.getDeclaringClass(),
            fieldIsPrivate);
        if (typeOfThis.isEqualTo(implicitThisClass))
          // "foo = 5" -> "this.foo = 5": expand into THIS + class
          targetNode = makeNode(context, fFactory, n, CAstNode.THIS);
        else
          // "foo = 5" -> "MyClass.this.foo = 5" -- inner class: expand into THIS + class
          targetNode = makeNode(context, fFactory, n, CAstNode.THIS, fFactory.makeConstant(fIdentityMapper
              .getTypeRef(implicitThisClass)));
        // NOTE: method may be defined in MyClass's superclass, but we still want to expand
        // this into MyClass, so we have to find the enclosing class which defines this function.
        // fFactory.makeConstant(owningTypeRef));
      }
      return createFieldAccess(targetNode, n.getIdentifier(), binding, n, context);

    } else {
      // local
      return makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(n.getIdentifier()));
    }

  }

  /**
   * Sees if a field defined in owningTypeRef is contained & accessible to a type of typeOfThis. That is, if owningTypeRef ==
   * typeOfThis or typeOfThis is a subtype and isPrivate is false. If this is not that case, looks in the enclosing class of
   * typeOfThis and tries again, and its enclosing class, ...
   * 
   * Essentially if we have a field/method referenced only by name and we know its type (owningTypeRef), this function will return
   * owningTypeRef or the subtype that the field is accessed thru, for expanding "f = 5" into "TheClass.this.f = 5".
   * 
   * @param typeOfThis
   * @param owningTypeRef
   * @param isPrivate
   * @return
   */
  private ITypeBinding findClosestEnclosingClassSubclassOf(ITypeBinding typeOfThis, ITypeBinding owningType, boolean isPrivate) {
    // GENERICS
//    if (owningType.isParameterizedType())
//      owningType = owningType.getTypeDeclaration();
//    if (typeOfThis.isParameterizedType())
//      typeOfThis = typeOfThis.getTypeDeclaration();
//    // typeOfThis.getTypeDeclaration()
    owningType = owningType.getErasure();

    ITypeBinding current = typeOfThis;
    while (current != null) {
      current = current.getErasure();
      // Walk the hierarchy rather than using isSubTypeCompatible to handle
      // generics -- we need to perform erasure of super types
      boolean isInSubtype = false;//current.isSubTypeCompatible(owningType);
      ITypeBinding supertp = current;
      while (supertp != null){
        supertp = supertp.getErasure();
        // Use isSubTypeCompatible even though we are manually walking type hierarchy --
        // that way interfaces are handled without us having to do it manually.
        if (supertp.isSubTypeCompatible(owningType)){
          isInSubtype = true;
          break;
        }
        supertp = supertp.getSuperclass();
      }

      // how could it be in the subtype and private? this only happens the supertype is also an
      // enclosing type. in that case the variable refers to the field in the enclosing instance.

      if (current.isEqualTo(owningType) || (isInSubtype && !isPrivate))
        return current;

      current = current.getDeclaringClass();
    }

    Assertions.UNREACHABLE("Couldn't find field in class or enclosing class or superclasses of these");
    return null;
  }

  /**
   * Process a field access. Semantics differ for static and instance fields. Fields can throw null pointer exceptions so we must
   * connect proper exceptional edges in the CFG.
   * 
   * @param n
   * @param context
   * @return
   */
  private CAstNode visit(FieldAccess n, WalkContext context) {
    CAstNode targetNode = visitNode(n.getExpression(), context);
    return createFieldAccess(targetNode, n.getName().getIdentifier(), n.resolveFieldBinding(), n, context);
  }

  /**
   * Used by visit(FieldAccess) and visit(SimpleName) -- implicit "this" / static field access. things from 'this' cannot throw an
   * exception. maybe handle this in here as a special case? i don't know... or check if targetNode is THIS, that should even work
   * for this.x = 5 and (this).x = 5
   * 
   * @param target Used to evaluate the field access. In the case of static field accesses, this is included in the first part of a
   *          block -- thus it is evaluated for any side effects but thrown away.
   * @param fieldName Name of the field.
   * @param fieldBinding This is used to determine the field's class.
   * @param positioningNode Used only for making a JdtPosition.
   * @param context
   * @return
   */
  private CAstNode createFieldAccess(CAstNode targetNode, String fieldName, IVariableBinding possiblyParameterizedBinding,
      ASTNode positioningNode, WalkContext context) {

    IVariableBinding fieldBinding = possiblyParameterizedBinding.getVariableDeclaration();

    ITypeBinding targetType = fieldBinding.getDeclaringClass();

    if (targetType == null) { // array
      assert fieldName.equals("length") : "null targetType but not aray length access";
      return makeNode(context, fFactory, positioningNode, CAstNode.ARRAY_LENGTH, targetNode);
    }

    assert fieldBinding.isField() : "Field binding is not a field?!"; // we can probably safely delete this
    // check

    // translate JDT field ref to WALA field ref
    FieldReference fieldRef = fIdentityMapper.getFieldRef(fieldBinding);

    if ((fieldBinding.getModifiers() & Modifier.STATIC) != 0) {
      // 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).
      // TODO: enum constants

      // don't worry about generics (can't declare static fields with type variables)
      if (fieldBinding.getConstantValue() != null) {
        return makeNode(context, fFactory, positioningNode, CAstNode.BLOCK_EXPR, targetNode, fFactory.makeConstant(fieldBinding
            .getConstantValue()));
      } else {
        return makeNode(context, fFactory, positioningNode, CAstNode.BLOCK_EXPR, targetNode,
            makeNode(context, fFactory, positioningNode, CAstNode.OBJECT_REF, makeNode(context, fFactory, null, CAstNode.VOID),
                fFactory.makeConstant(fieldRef)));
      }
    } else {
      CAstNode refNode = makeNode(context, fFactory, positioningNode, CAstNode.OBJECT_REF, targetNode, fFactory
          .makeConstant(fieldRef));

      if (targetNode.getKind() != CAstNode.THIS) { // this.x will never throw a null pointer exception, because this
        // can never be null
        Collection excTargets = context.getCatchTargets(fNullPointerExcType);
        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();
            context.cfg().add(refNode, catchPair.snd, fNullPointerExcType);
          }
        } else {
          // connect exception edge to exit
          context.cfg().add(refNode, CAstControlFlowMap.EXCEPTION_TO_EXIT, fNullPointerExcType);
        }

        context.cfg().map(refNode, refNode);
      }

      if (fieldBinding.getConstantValue() != null) {
        // don't have to worry about generics, a constant of generic type can only be null
        return makeNode(context, fFactory, positioningNode, CAstNode.BLOCK_EXPR, refNode,
        // evaluating 'refNode' can have side effects, so we must still evaluate it!
            fFactory.makeConstant(fieldBinding.getConstantValue()));
      } else {
        if (fieldBinding.getType().isTypeVariable() && !context.needLValue()) {
          // GENERICS: add a cast
          ITypeBinding realtype = JDT2CAstUtils.getErasedType(possiblyParameterizedBinding.getType(), ast);
          ITypeBinding fromtype = JDT2CAstUtils.getTypesVariablesBase(fieldBinding.getType(), ast);
          if (!realtype.isEqualTo(fromtype))
            return createCast(positioningNode, refNode, fromtype, realtype, context);
        }
        return refNode;
      }
    }

  }

  private CAstNode visit(ThisExpression n, WalkContext context) {
    if (n.getQualifier() != null) {
      ITypeBinding owningType = n.getQualifier().resolveTypeBinding();
      TypeReference owningTypeRef = fIdentityMapper.getTypeRef(owningType);
      return makeNode(context, fFactory, n, CAstNode.THIS, fFactory.makeConstant(owningTypeRef));
    } else
      return makeNode(context, fFactory, n, CAstNode.THIS);
  }

  /**
   * QualifiedNames may be: 1) static of non-static field accesses -- we handle this case here 2) type names used in the context of:
   * a) field access (QualifiedName) b) method invocation c) qualifier of "this" in these cases we get the binding info in each of
   * these three functions and use them there, thus we return an EMPTY (no-op) here. 3) package names used in the context of a
   * QualifiedName class we return a EMPTY (no-op) here.
   * 
   * @param n
   * @param context
   * @return
   */
  private CAstNode visit(QualifiedName n, WalkContext context) {
    // "package.Class" is a QualifiedName, but also is "Class.staticField"
    // only handle if it's a "Class.staticField" ("Field" in polyglot AST)

    if (n.resolveBinding() instanceof IVariableBinding) {
      IVariableBinding binding = (IVariableBinding) n.resolveBinding();
      assert binding.isField() : "Non-field variable QualifiedName!";

      // if field access is static, visitNode(n.getQualifier()) will come back here
      // and we will return an EMPTY node
      return createFieldAccess(visitNode(n.getQualifier(), context), n.getName().getIdentifier(), binding, n, context);
    } else
      return makeNode(context, fFactory, null, CAstNode.EMPTY);
    // type name, handled in surrounding context
  }

  private CAstNode visit(InfixExpression n, WalkContext context) {
    Expression left = n.getLeftOperand();
    ITypeBinding leftType = left.resolveTypeBinding();
    int leftStartPosition = left.getStartPosition();

    CAstNode leftNode = visitNode(left, context);

    int leftLength = n.getLeftOperand().getLength();
    CAstNode result = createInfixExpression(n.getOperator(), leftType, leftStartPosition, leftLength, leftNode,
        n.getRightOperand(), context);

    if (n.hasExtendedOperands()) {
      // keep on adding operands on the right side

      leftLength = n.getRightOperand().getStartPosition() + n.getRightOperand().getLength() - leftStartPosition;
      for (Object o : n.extendedOperands()) {
        Expression operand = (Expression) o;
        result = createInfixExpression(n.getOperator(), leftType, leftStartPosition, leftLength, result, operand, context);

        if (leftType.isPrimitive() && operand.resolveTypeBinding().isPrimitive())
          leftType = JDT2CAstUtils.promoteTypes(leftType, operand.resolveTypeBinding(), ast); // TODO: boxing
        else
          leftType = operand.resolveTypeBinding();

        // leftStartPosition doesn't change, beginning is always the first operand
        leftLength = operand.getStartPosition() + operand.getLength() - leftStartPosition;
      }
    }

    return result;
  }

  private CAstNode createInfixExpression(InfixExpression.Operator op, ITypeBinding leftType, int leftStartPosition, int leftLength,
      CAstNode leftNode, Expression right, WalkContext context) {
    CAstNode rightNode = visitNode(right, context);

    int start = leftStartPosition;
    int end = right.getStartPosition() + right.getLength();
    JdtPosition pos = makePosition(start, end);
    JdtPosition rightPos = makePosition(leftStartPosition, leftStartPosition + leftLength);
    JdtPosition leftPos = makePosition(right.getStartPosition(), right.getStartPosition() + right.getLength());

    if (op == InfixExpression.Operator.CONDITIONAL_AND) {
      return makeNode(context, fFactory, pos, CAstNode.IF_EXPR, leftNode, rightNode, fFactory.makeConstant(false));
    } else if (op == InfixExpression.Operator.CONDITIONAL_OR) {
      return makeNode(context, fFactory, pos, CAstNode.IF_EXPR, leftNode, fFactory.makeConstant(true), rightNode);
    } else {
      ITypeBinding rightType = right.resolveTypeBinding();
      if (leftType.isPrimitive() && rightType.isPrimitive()) {
        // TODO: boxing
        ITypeBinding result = JDT2CAstUtils.promoteTypes(leftType, rightType, ast);

        // cast to proper type
        if (!result.isEqualTo(leftType))
          leftNode = makeNode(context, fFactory, leftPos, CAstNode.CAST, fFactory.makeConstant(fTypeDict.getCAstTypeFor(result)),
              leftNode, fFactory.makeConstant(fTypeDict.getCAstTypeFor(leftType)));
        if (!result.isEqualTo(rightType))
          rightNode = makeNode(context, fFactory, rightPos, CAstNode.CAST, fFactory.makeConstant(fTypeDict.getCAstTypeFor(result)),
              rightNode, fFactory.makeConstant(fTypeDict.getCAstTypeFor(rightType)));

        CAstNode opNode = makeNode(context, fFactory, pos, CAstNode.BINARY_EXPR, JDT2CAstUtils.mapBinaryOpcode(op), leftNode,
            rightNode);

        // divide by zero exception implicitly thrown
        if (JDT2CAstUtils.isLongOrLess(leftType)
            && JDT2CAstUtils.isLongOrLess(rightType)
            && (JDT2CAstUtils.mapBinaryOpcode(op) == CAstOperator.OP_DIV || JDT2CAstUtils.mapBinaryOpcode(op) == CAstOperator.OP_MOD)) {
          Collection excTargets = context.getCatchTargets(fDivByZeroExcType);
          if (!excTargets.isEmpty()) {
            for (Iterator iterator = excTargets.iterator(); iterator.hasNext();) {
              Pair catchPair = (Pair) iterator.next();
              context.cfg().add(op, catchPair.snd, fDivByZeroExcType);
            }
          } else {
            context.cfg().add(op, CAstControlFlowMap.EXCEPTION_TO_EXIT, fDivByZeroExcType);
          }
        }

        return opNode;

      } else {
        return makeNode(context, fFactory, pos, CAstNode.BINARY_EXPR, JDT2CAstUtils.mapBinaryOpcode(op), leftNode, rightNode);
      }
    }
  }

  private CAstNode visit(ConstructorInvocation n, WalkContext context) {
    // GENERICS getMethodDeclaration()
    return createConstructorInvocation(n.resolveConstructorBinding().getMethodDeclaration(), n.arguments(), n, context, false);
  }

  private CAstNode visit(SuperConstructorInvocation n, WalkContext context) {
    // FIXME: use expression?! polyglot doesn't handle it and it seems to be a very rare case.
    // class E { class X {} }
    // class Y extends E.X { Y(E e) { e.super(); } }
    // GENERICS getMethodDeclaration()
    return createConstructorInvocation(n.resolveConstructorBinding().getMethodDeclaration(), n.arguments(), n, context, true);
  }

  private CAstNode visit(SuperFieldAccess n, WalkContext context) {
    CAstNode targetNode;
    if (n.getQualifier() == null)
      targetNode = makeNode(context, fFactory, n, CAstNode.SUPER);
    else {
      TypeReference owningTypeRef = fIdentityMapper.getTypeRef(n.getQualifier().resolveTypeBinding());
      targetNode = makeNode(context, fFactory, n, CAstNode.SUPER, fFactory.makeConstant(owningTypeRef));
    }
    return createFieldAccess(targetNode, n.getName().getIdentifier(), n.resolveFieldBinding(), n, context);
  }

  /**
   * callerNode: used for positioning and also in CFG (handleThrowsFrom Call)
   */
  private CAstNode createConstructorInvocation(IMethodBinding ctorBinding, List/* <Expression> */arguments, ASTNode callerNode,
      WalkContext context, boolean isSuper) {
    ITypeBinding ctorType = ctorBinding.getDeclaringClass();
    assert ctorType.isClass();

    // dummy PC = 0 -- Just want to wrap the kind of call; the "rear end"
    // won't care about anything else...
    CallSiteReference callSiteRef = CallSiteReference.make(0, fIdentityMapper.getMethodRef(ctorBinding),
        IInvokeInstruction.Dispatch.SPECIAL);

    int nFormals = ctorBinding.getParameterTypes().length;
    CAstNode[] children = new CAstNode[2 + nFormals];
    // this, call site ref, args

    CAstNode targetNode = makeNode(context, fFactory, callerNode, isSuper ? CAstNode.SUPER : CAstNode.THIS);

    children[0] = targetNode;
    children[1] = fFactory.makeConstant(callSiteRef);

    populateArguments(children, ctorBinding, arguments, context);

    handleThrowsFromCall(ctorBinding, callerNode, context);

    CAstNode result = makeNode(context, fFactory, callerNode, CAstNode.CALL, children);
    context.cfg().map(context, result);
    return result;

  }

  private CAstNode visit(IfStatement n, WalkContext context) {
    return makeNode(context, fFactory, n, CAstNode.IF_STMT, visitNode(n.getExpression(), context), visitNode(n.getThenStatement(),
        context), visitNode(n.getElseStatement(), context));
  }

  private CAstNode visit(InstanceofExpression n, WalkContext context) {
    return makeNode(context, fFactory, n, CAstNode.INSTANCEOF, fFactory.makeConstant(fTypeDict.getCAstTypeFor(n.getRightOperand()
        .resolveBinding())), visitNode(n.getLeftOperand(), context));
  }

  private CAstNode visit(CastExpression n, WalkContext context) {
    Expression arg = n.getExpression();
    ITypeBinding castedFrom = arg.resolveTypeBinding();
    ITypeBinding castedTo = n.getType().resolveBinding();
    return createCast(n, visitNode(arg, context), castedFrom, castedTo, context);
  }

  private CAstNode createCast(ASTNode pos, CAstNode argNode, ITypeBinding castedFrom, ITypeBinding castedTo, WalkContext context) {
    Object cfgMapDummy = new Object(); // safer as 'pos' may be used for another purpose (i.e., this could be an implicit cast)

    // null can go into anything (e.g. in "((Foobar) null)" null can be assumed to be of type Foobar already)
    if (castedFrom.isNullType())
      castedFrom = castedTo;

    CAstNode ast = makeNode(context, fFactory, pos, CAstNode.CAST, fFactory.makeConstant(fTypeDict.getCAstTypeFor(castedTo)),
        argNode, fFactory.makeConstant(fTypeDict.getCAstTypeFor(castedFrom)));

    Collection excTargets = context.getCatchTargets(fClassCastExcType);
    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();
        context.cfg().add(cfgMapDummy, catchPair.snd, fClassCastExcType);
      }
    } else {
      // connect exception edge to exit
      context.cfg().add(cfgMapDummy, CAstControlFlowMap.EXCEPTION_TO_EXIT, fClassCastExcType);
    }

    context.cfg().map(cfgMapDummy, ast);
    return ast;
  }

  private CAstNode visit(ConditionalExpression n, WalkContext context) {
    return makeNode(context, fFactory, n, CAstNode.IF_EXPR, visitNode(n.getExpression(), context), visitNode(n.getThenExpression(),
        context), visitNode(n.getElseExpression(), context));
  }

  private CAstNode visit(PostfixExpression n, WalkContext context) {
    CAstOperator op = (n.getOperator() == PostfixExpression.Operator.DECREMENT) ? CAstOperator.OP_SUB : CAstOperator.OP_ADD;
    return makeNode(context, fFactory, n, CAstNode.ASSIGN_POST_OP, visitNode(n.getOperand(), context), fFactory.makeConstant(1), op);
  }

  private CAstNode visit(PrefixExpression n, WalkContext context) {
    PrefixExpression.Operator op = n.getOperator();

    if (op == PrefixExpression.Operator.DECREMENT || op == PrefixExpression.Operator.INCREMENT) {
      CAstOperator castOp = (n.getOperator() == PrefixExpression.Operator.DECREMENT) ? CAstOperator.OP_SUB : CAstOperator.OP_ADD;
      return makeNode(context, fFactory, n, CAstNode.ASSIGN_PRE_OP, visitNode(n.getOperand(), context), fFactory.makeConstant(1),
          castOp);
    } else if (op == PrefixExpression.Operator.PLUS) {
      return visitNode(n.getOperand(), context); // drop useless unary plus operator
    } else if (op == PrefixExpression.Operator.MINUS) {
      CAstNode zero;
      if (JDT2CAstUtils.isLongOrLess(n.getOperand().resolveTypeBinding()))
        zero = fFactory.makeConstant(0L);
      else
        zero = fFactory.makeConstant(0.0);
      return makeNode(context, fFactory, n, CAstNode.BINARY_EXPR, CAstOperator.OP_SUB, zero, visitNode(n.getOperand(), context));
    } else { // ! and ~
      CAstOperator castOp = (n.getOperator() == PrefixExpression.Operator.NOT) ? CAstOperator.OP_NOT : CAstOperator.OP_BITNOT;
      return makeNode(context, fFactory, n, CAstNode.UNARY_EXPR, castOp, visitNode(n.getOperand(), context));
    }
  }

  private CAstNode visit(EmptyStatement n, WalkContext context) {
    CAstNode result = makeNode(context, fFactory, n, CAstNode.EMPTY);
    context.cfg().map(n, result); // why is this necessary? for break / continue targets? (they use an empty statement)
    return result;
  }

  private CAstNode visit(AssertStatement n, WalkContext context) {
    return makeNode(context, fFactory, n, CAstNode.ASSERT, visitNode(n.getExpression(), context));
  }

  // ////////////////
  // LOOPS -- special handling of for and continue
  // ////////////////

  private CAstNode visit(LabeledStatement n, WalkContext context) {

    // find the first non-block statement ant set-up the label map (useful for breaking many fors)
    ASTNode stmt = n.getBody();
    while (stmt instanceof Block)
      stmt = (ASTNode) ((Block) stmt).statements().iterator().next();

    if (n.getParent() != null)
      // if not a synthetic node from a break/continue -- don't pollute namespace with label, we get it thru the context
      context.getLabelMap().put(stmt, n.getLabel().getIdentifier());

    CAstNode result;
    if (!(n.getBody() instanceof EmptyStatement)) {
      ASTNode breakTarget = makeBreakOrContinueTarget(n, n.getLabel().getIdentifier());
      CAstNode breakNode = visitNode(breakTarget, context);
      WalkContext child = new BreakContext(context, n.getLabel().getIdentifier(), breakTarget);

      result = makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, makeNode(context, fFactory, n, CAstNode.LABEL_STMT, fFactory
          .makeConstant(n.getLabel().getIdentifier()), visitNode(n.getBody(), child)), breakNode);
    } else {
      result = makeNode(context, fFactory, n, CAstNode.LABEL_STMT, fFactory.makeConstant(n.getLabel().getIdentifier()), visitNode(n
          .getBody(), context));
    }

    context.cfg().map(n, result);

    if (n.getParent() != null)
      // if not a synthetic node from a break/continue -- don't pollute namespace with label, we get it thru the context
      context.getLabelMap().remove(stmt);

    return result;
  }

  /**
   * Make a fake labeled node with no body, as an anchor to go to
   */
  private ASTNode makeBreakOrContinueTarget(ASTNode loop, String name) {
    LabeledStatement labeled = ast.newLabeledStatement();
    labeled.setBody(ast.newEmptyStatement());
    labeled.setSourceRange(loop.getStartPosition(), loop.getLength());
    labeled.setLabel(ast.newSimpleName(name)); // we don't have to worry about namespace conflicts as it is only
    // definedwithin
    return labeled;
  }

  private CAstNode visit(BreakStatement n, WalkContext context) {
    String label = n.getLabel() == null ? null : n.getLabel().getIdentifier();
    ASTNode target = context.getBreakFor(label);
    assert target != null;
    CAstNode result = makeNode(context, fFactory, n, CAstNode.GOTO);
    context.cfg().map(n, result);
    context.cfg().add(n, target, null);
    return result;
  }

  private CAstNode visit(ContinueStatement n, WalkContext context) {
    String label = n.getLabel() == null ? null : n.getLabel().getIdentifier();
    ASTNode target = context.getContinueFor(label);
    assert target != null;
    CAstNode result = makeNode(context, fFactory, n, CAstNode.GOTO);
    context.cfg().map(n, result);
    context.cfg().add(n, target, null);
    return result;
  }

  private CAstNode visit(WhileStatement n, WalkContext context) {
    Expression cond = n.getExpression();
    Statement body = n.getBody();

    ASTNode breakTarget = makeBreakOrContinueTarget(n, "breakLabel" + n.getStartPosition());
    CAstNode breakNode = visitNode(breakTarget, context);

    ASTNode continueTarget = makeBreakOrContinueTarget(n, "continueLabel" + n.getStartPosition());
    CAstNode continueNode = visitNode(continueTarget, context);

    String loopLabel = (String) context.getLabelMap().get(n);
    LoopContext lc = new LoopContext(context, loopLabel, breakTarget, continueTarget);

    /*
     * The following loop is created sligtly differently than in jscore. It doesn't have a specific target for continue.
     */
    return makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, makeNode(context, fFactory, n, CAstNode.LOOP, visitNode(cond,
        context), makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, visitNode(body, lc), continueNode)),
        breakNode);
  }

  private CAstNode getSwitchCaseConstant(SwitchCase n, WalkContext context) {
    // TODO: enums
    Expression expr = n.getExpression();
    Object constant = (expr == null) ? new Integer(0) : expr.resolveConstantExpressionValue(); // default case label of
    // "0" (what polyglot
    // does). we also set
    // SWITCH_DEFAULT
    // somewhere else
    // polyglot converts all labels to longs. why? who knows...
    if (constant instanceof Character)
      constant = new Long(((Character) constant).charValue());
    else if (constant instanceof Byte)
      constant = new Long(((Byte) constant).longValue());
    else if (constant instanceof Integer)
      constant = new Long(((Integer) constant).longValue());
    else if (constant instanceof Short)
      constant = new Long(((Short) constant).longValue());

    if (constant != null) {
      return fFactory.makeConstant(constant);
    } else if (expr instanceof SimpleName) {
      // enum constant
      return visit((SimpleName) expr, context);
    } else {
      Assertions.UNREACHABLE("null constant for non-enum switch case!");
      return null;
    }
  }

  private CAstNode visit(SwitchCase n, WalkContext context) {
    CAstNode label = makeNode(context, fFactory, n, CAstNode.LABEL_STMT, getSwitchCaseConstant(n, context));

    context.cfg().map(n, label);
    return label;
  }

  private CAstNode visit(SwitchStatement n, WalkContext context) {
    ASTNode breakTarget = makeBreakOrContinueTarget(n, "breakLabel" + n.getStartPosition());
    CAstNode breakAst = visitNode(breakTarget, context);
    String loopLabel = (String) context.getLabelMap().get(n); // set by labeled statement (if there is one before this
    // switch statement)
    WalkContext childContext = new BreakContext(context, loopLabel, breakTarget);
    Expression cond = n.getExpression();
    List/* <Statement> */cases = n.statements();

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

        if (c.isDefault())
          context.cfg().add(n, c, CAstControlFlowMap.SWITCH_DEFAULT);
        else
          context.cfg().add(n, c, getSwitchCaseConstant(c, context));
        // if we don't do this, we may not get a constant but a
        // block expression or something else
      }
    }

    ArrayList<CAstNode> caseNodes = new ArrayList<CAstNode>();

    // polyglot bundles all statements in between two statements into a block.
    // this is temporary place to hold current bundle of nodes.
    ArrayList<CAstNode> currentBlock = new ArrayList<CAstNode>();

    // Now produce the CAst representation for each case
    for (Object o : cases) {
      Statement s = (Statement) o;
      if (s instanceof SwitchCase) {
        if (!currentBlock.isEmpty()) {
          // bundle up statements before this case
          CAstNode stmtNodes[] = currentBlock.toArray(new CAstNode[currentBlock.size()]);
          // make position from start of first statement to end of last statement
          JdtPosition positionOfAll = makePosition(childContext.pos().getPosition(stmtNodes[0]).getFirstOffset(), childContext
              .pos().getPosition(stmtNodes[stmtNodes.length - 1]).getLastOffset());
          caseNodes.add(makeNode(childContext, fFactory, positionOfAll, CAstNode.BLOCK_STMT, stmtNodes));
          currentBlock.clear();
        }
        caseNodes.add(visitNode(s, childContext));
      } else {
        visitNodeOrNodes(s, childContext, currentBlock);
      }
    }
    if (!currentBlock.isEmpty()) {
      // bundle up statements before this case
      CAstNode stmtNodes[] = currentBlock.toArray(new CAstNode[currentBlock.size()]);
      // make position from start of first statement to end of last statement
      JdtPosition positionOfAll = makePosition(childContext.pos().getPosition(stmtNodes[0]).getFirstOffset(), childContext.pos()
          .getPosition(stmtNodes[stmtNodes.length - 1]).getLastOffset());
      caseNodes.add(makeNode(childContext, fFactory, positionOfAll, CAstNode.BLOCK_STMT, stmtNodes));
    }

    // Now produce the switch stmt itself
    CAstNode switchAst = makeNode(context, fFactory, n, CAstNode.SWITCH, visitNode(cond, context), makeNode(context, fFactory, n,
        CAstNode.BLOCK_STMT, caseNodes.toArray(new CAstNode[caseNodes.size()])));

    context.cfg().map(n, switchAst);

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

  }

  private CAstNode visit(DoStatement n, WalkContext context) {
    String loopLabel = (String) context.getLabelMap().get(n); // set by visit(LabeledStatement)
    String token = loopLabel==null? "at_" + n.getStartPosition(): loopLabel;
    
    ASTNode breakTarget = makeBreakOrContinueTarget(n, "breakLabel_" + token);
    CAstNode breakNode = visitNode(breakTarget, context);

    ASTNode continueTarget = makeBreakOrContinueTarget(n, "continueLabel_" + token);
    CAstNode continueNode = visitNode(continueTarget, context);
 
    CAstNode loopTest = visitNode(n.getExpression(), context);

    WalkContext loopContext = new LoopContext(context, loopLabel, breakTarget, continueTarget);
    CAstNode loopBody = visitNode(n.getBody(), loopContext);

    return doLoopTranslator.translateDoLoop(loopTest, loopBody, continueNode, breakNode, context);  
  }

  /**
   * Expands the form: for ( [final] Type var: iterable ) { ... } Into something equivalent to: for ( Iterator iter =
   * iterable.iter(); iter.hasNext(); ) { [final] Type var = (Type) iter.next(); ... } Or, in the case of an array: for ( int idx =
   * 0; i < iterable.length; i++ ) { [final] Type var = iterable[idx]; ... } Except that the expression "iterable" is only evaluate
   * once (or is it?)
   * 
   * @param n
   * @param context
   * @return
   */
  private CAstNode visit(EnhancedForStatement n, WalkContext context) {
    if (n.getExpression().resolveTypeBinding().isArray())
      return makeArrayEnhancedForLoop(n, context);
    else
      return makeIteratorEnhancedForLoop(n, context);
  }

  private CAstNode makeIteratorEnhancedForLoop(EnhancedForStatement n, WalkContext context) {
    // case 1: iterator
    CAstNode exprNode = visitNode(n.getExpression(), context);
    SingleVariableDeclaration svd = n.getParameter();
    Statement body = n.getBody();

    // expand into:

    // typical for loop:
    // { [inits]; while (cond) { [body]; [label continueTarget]; iters } [label breakTarget]
    // BLOCK(BLOCK(init1,init2,...),LOOP(cond,BLOCK(bodyblock,continuetarget,BLOCK(iter1,iter2,...))),breaktarget

    // in our case:
    // the only init is "Iterator iter = iterable.iter()"
    // cond is "iter.hasNext()"
    // bodyblock should be prepended with "[final] Type var = iter.next()" (put in the block that body belongs to)
    // iter is null
    // continuetarget and breaktarget are the same as in a regular for loop
    // BLOCK(iterassign,LOOP(cond,BLOCK(paramassign,bodyblock,continuetarget)),breaktarget)

    final String tmpName = "iter tmp"; // this is an illegal Java identifier, we will use this variable to hold the "invisible"
                                       // iterator

    /*-------- make "iter = iterator.iter()" ---------*/
    // make a fake method ref
    MethodReference iterMethodRef = fIdentityMapper.fakeMethodRefNoArgs("Ljava/lang/Iterable;.iterator()Ljava/util/Iterator<TT;>;",
        "Ljava/lang/Iterable", "iterator", "Ljava/util/Iterator");
    CAstNode iterCallSiteRef = fFactory.makeConstant(CallSiteReference
        .make(0, iterMethodRef, IInvokeInstruction.Dispatch.INTERFACE));
    // Iterable.iter() throws no exceptions.

    CAstNode iterCallNode = makeNode(context, fFactory, n, CAstNode.CALL, exprNode, iterCallSiteRef);
    // handle runtimeexception
    Object o1 = new Object(); // dummy object used for mapping / exceptions
    for (Pair<ITypeBinding, Object> catchTarget : context.getCatchTargets(fRuntimeExcType))
      context.cfg().add(o1, catchTarget.snd, catchTarget.fst);
    context.cfg().map(o1, iterCallNode); // TODO: this might not work, lots of calls in this one statement.

    CAstNode iterAssignNode = makeNode(context, fFactory, n, CAstNode.DECL_STMT, fFactory.makeConstant(new InternalCAstSymbol(
        tmpName, true)), iterCallNode);

    // MATCHUP: wrap in a block
    iterAssignNode = makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, iterAssignNode);

    // TODO: TOTEST: using this and Iterable.hasNext() explicitly in same file.

    /*---------- cond: iter.hasNext(); -----------*/
    MethodReference hasNextMethodRef = fIdentityMapper.fakeMethodRefNoArgs("Ljava/util/Iterator;.hasNext()Z",
        "Ljava/util/Iterator", "hasNext", "Z");
    CAstNode iterVar = makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(tmpName));
    CAstNode hasNextCallSiteRef = fFactory.makeConstant(CallSiteReference.make(0, hasNextMethodRef,
        IInvokeInstruction.Dispatch.INTERFACE));

    // throws no exceptions.
    CAstNode hasNextCallNode = makeNode(context, fFactory, n, CAstNode.CALL, iterVar, hasNextCallSiteRef);
    // handle runtimeexception
    Object o2 = new Object(); // dummy object used for mapping / exceptions
    for (Pair<ITypeBinding, Object> catchTarget : context.getCatchTargets(fRuntimeExcType))
      context.cfg().add(o2, catchTarget.snd, catchTarget.fst);
    context.cfg().map(o2, hasNextCallNode); // TODO: this might not work, lots of calls in this one statement.

    /*---------- paramassign: var = (Type) iter.next() ---------*/
    MethodReference nextMethodRef = fIdentityMapper.fakeMethodRefNoArgs("Ljava/util/Iterator;.next()TE;", "Ljava/util/Iterator",
        "next", "Ljava/lang/Object");
    CAstNode nextCallSiteRef = fFactory.makeConstant(CallSiteReference
        .make(0, nextMethodRef, IInvokeInstruction.Dispatch.INTERFACE));
    // throws no exceptions.
    CAstNode iterVar2 = makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(tmpName));
    CAstNode nextCallNode = makeNode(context, fFactory, n, CAstNode.CALL, iterVar2, nextCallSiteRef);
    for (Pair<ITypeBinding, Object> catchTarget : context.getCatchTargets(fRuntimeExcType))
      context.cfg().add(svd, catchTarget.snd, catchTarget.fst);
    context.cfg().map(svd, nextCallNode);

    // TODO: another cfg edge associated with svd! is this okay? prolly not... associate it with the cast, somehow...
    CAstNode castedNode = createCast(svd, nextCallNode, ast.resolveWellKnownType("java.lang.Object"), svd.resolveBinding()
        .getType(), context);

    Object defaultValue = JDT2CAstUtils.defaultValueForType(svd.resolveBinding().getType());
    CAstNode nextAssignNode = makeNode(context, fFactory, n, CAstNode.DECL_STMT, fFactory.makeConstant(new CAstSymbolImpl(svd
        .getName().getIdentifier(), (svd.getModifiers() & Modifier.FINAL) != 0, defaultValue)), castedNode);

    /*----------- put it all together ----------*/
    ASTNode breakTarget = makeBreakOrContinueTarget(n, "breakLabel" + n.getStartPosition());
    ASTNode continueTarget = makeBreakOrContinueTarget(n, "continueLabel" + n.getStartPosition());
    String loopLabel = (String) context.getLabelMap().get(n);
    WalkContext loopContext = new LoopContext(context, loopLabel, breakTarget, continueTarget);

    // LOCAL_SCOPE(BLOCK(iterassign,LOOP(cond,BLOCK(BLOCK(paramassign,bodyblock),continuetarget,BLOCK())),breaktarget))
    return makeNode(context, fFactory, n, CAstNode.LOCAL_SCOPE, makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, iterAssignNode,
        makeNode(context, fFactory, n, CAstNode.LOOP, hasNextCallNode, makeNode(context, fFactory, n, CAstNode.BLOCK_STMT,
            makeNode(context, fFactory, n, CAstNode.LOCAL_SCOPE, makeNode(context, fFactory, n, CAstNode.BLOCK_STMT,
                nextAssignNode, visitNode(body, loopContext))), visitNode(continueTarget, context), makeNode(context, fFactory, n,
                CAstNode.BLOCK_STMT))), visitNode(breakTarget, context)));
  }

  private CAstNode makeArrayEnhancedForLoop(EnhancedForStatement n, WalkContext context) {
    // ********* BEFORE:
    // for ( String x: doSomething() ) { ... }
    // ********* AFTER:
    // {
    // String tmparray[] = doSomething();
    // for ( int tmpindex = 0; i < tmparray.length; tmpindex++ ) {
    // String x = tmparray[tmpindex];
    // ...
    // }
    // }
    // simplest:
    // LOCAL_SCOPE(BLOCK(arrayDecl,indexDecl,LOOP(cond,BLOCK(nextAssign,bodyblock,continuetarget,iter)),breaktarget))
    // match up exactly:
    // LOCAL_SCOPE(BLOCK(arrayDecl,LOCAL_SCOPE(BLOCK(BLOCK(indexDecl),LOOP(cond,BLOCK(LOCAL_SCOPE(BLOCK(nextAssign,bodyblock)),continuetarget,BLOCK(iter))),breaktarget))))

    /*------ arrayDecl --------- String tmparray[] = doSomething() ------*/
    final String tmpArrayName = "for temp array"; // illegal java identifier
    CAstNode exprNode = visitNode(n.getExpression(), context);
    CAstNode arrayDeclNode = makeNode(context, fFactory, n, CAstNode.DECL_STMT, fFactory.makeConstant(new InternalCAstSymbol(
        tmpArrayName, true)), exprNode);

    /*------ indexDecl --------- int tmpindex = 0 ------*/
    final String tmpIndexName = "for temp index";
    CAstNode indexDeclNode = makeNode(context, fFactory, n, CAstNode.DECL_STMT, fFactory.makeConstant(new InternalCAstSymbol(
        tmpIndexName, true)), fFactory.makeConstant(new Integer(0)));

    /*------ cond ------------- tmpindex < tmparray.length ------*/
    CAstNode tmpArrayLengthNode = makeNode(context, fFactory, n, CAstNode.ARRAY_LENGTH, makeNode(context, fFactory, n,
        CAstNode.VAR, fFactory.makeConstant(tmpArrayName)));
    CAstNode condNode = makeNode(context, fFactory, n, CAstNode.BINARY_EXPR, CAstOperator.OP_LT, makeNode(context, fFactory, n,
        CAstNode.VAR, fFactory.makeConstant(tmpIndexName)), tmpArrayLengthNode);

    /*------ tmpIndexInc -------- tmpindex++ ------*/
    CAstNode tmpArrayIncNode = makeNode(context, fFactory, n, CAstNode.ASSIGN_POST_OP, makeNode(context, fFactory, n, CAstNode.VAR,
        fFactory.makeConstant(tmpIndexName)), fFactory.makeConstant(1), CAstOperator.OP_ADD);

    /*------ tmpArrayAccess ----- String x = tmparray[tmpindex] ------*/
    CAstNode tmpArrayAccessNode = makeNode(context, fFactory, n, CAstNode.ARRAY_REF, makeNode(context, fFactory, n, CAstNode.VAR,
        fFactory.makeConstant(tmpArrayName)), fFactory.makeConstant(fIdentityMapper.getTypeRef(n.getExpression()
        .resolveTypeBinding().getComponentType())), makeNode(context, fFactory, n, CAstNode.VAR, fFactory
        .makeConstant(tmpIndexName)));

    SingleVariableDeclaration svd = n.getParameter();
    Object defaultValue = JDT2CAstUtils.defaultValueForType(svd.resolveBinding().getType());
    CAstNode nextAssignNode = makeNode(context, fFactory, n, CAstNode.DECL_STMT, fFactory.makeConstant(new CAstSymbolImpl(svd
        .getName().getIdentifier(), (svd.getModifiers() & Modifier.FINAL) != 0, defaultValue)), tmpArrayAccessNode);

    // LOCAL_SCOPE(BLOCK(arrayDecl,LOCAL_SCOPE(BLOCK(BLOCK(indexDecl),LOOP(cond,BLOCK(LOCAL_SCOPE(BLOCK(nextAssign,bodyblock)),continuetarget,BLOCK(iter))),breaktarget))))
    // more complicated than it has to be, but it matches up exactly with the Java expansion above.

    ASTNode breakTarget = makeBreakOrContinueTarget(n, "breakLabel" + n.getStartPosition());
    ASTNode continueTarget = makeBreakOrContinueTarget(n, "continueLabel" + n.getStartPosition());
    String loopLabel = (String) context.getLabelMap().get(n);
    WalkContext loopContext = new LoopContext(context, loopLabel, breakTarget, continueTarget);

    return makeNode(context, fFactory, n, CAstNode.LOCAL_SCOPE,
        makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, arrayDeclNode, makeNode(context, fFactory, n, CAstNode.LOCAL_SCOPE,
            makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, indexDeclNode),
                makeNode(context, fFactory, n, CAstNode.LOOP, condNode, makeNode(context, fFactory, n, CAstNode.BLOCK_STMT,
                    makeNode(context, fFactory, n, CAstNode.LOCAL_SCOPE, makeNode(context, fFactory, n, CAstNode.BLOCK_STMT,
                        nextAssignNode, visitNode(n.getBody(), loopContext))), visitNode(continueTarget, context), makeNode(
                        context, fFactory, n, CAstNode.BLOCK_STMT, tmpArrayIncNode))), visitNode(breakTarget, context)))));

  }

  private CAstNode visit(ForStatement n, WalkContext context) {
    ASTNode breakTarget = makeBreakOrContinueTarget(n, "breakLabel" + n.getStartPosition());
    ASTNode continueTarget = makeBreakOrContinueTarget(n, "continueLabel" + n.getStartPosition());
    String loopLabel = (String) context.getLabelMap().get(n);
    WalkContext loopContext = new LoopContext(context, loopLabel, breakTarget, continueTarget);

    ArrayList<CAstNode> inits = new ArrayList<CAstNode>();
    for (int i = 0; i < n.initializers().size(); i++) {
      ASTNode init = (ASTNode) n.initializers().get(i);
      if (init instanceof VariableDeclarationExpression) {
        for (Object o : ((VariableDeclarationExpression) init).fragments())
          inits.add(visitNode((ASTNode) o, context));
      } else
        inits.add(visitNode(init, context));
    }

    CAstNode[] iters = new CAstNode[n.updaters().size()];
    for (int i = 0; i < iters.length; i++)
      iters[i] = visitNode((ASTNode) n.updaters().get(i), context);

    CAstNode initsBlock = makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, inits.toArray(new CAstNode[inits.size()]));
    CAstNode itersBlock = makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, iters);

    // { [inits]; while (cond) { [body]; [label continueTarget]; iters } [label breakTarget]
    return makeNode(context, fFactory, n, CAstNode.LOCAL_SCOPE, makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, initsBlock,
        makeNode(context, fFactory, n, CAstNode.LOOP, visitNode(n.getExpression(), context), makeNode(context, fFactory, n,
            CAstNode.BLOCK_STMT, visitNode(n.getBody(), loopContext), visitNode(continueTarget, context), itersBlock)), visitNode(
            breakTarget, context)));

  }

  private CAstNode visit(TryStatement n, WalkContext context) {
    List/* <CatchClause> */catchBlocks = n.catchClauses();
    Block finallyBlock = n.getFinally();
    Block tryBlock = n.getBody();

    // try/finally
    if (catchBlocks.isEmpty()) {
      return makeNode(context, fFactory, n, CAstNode.UNWIND, visitNode(tryBlock, context), visitNode(finallyBlock, context));

      // try/catch/[finally]
    } else {
      TryCatchContext tc = new TryCatchContext(context, n);

      CAstNode tryNode = visitNode(tryBlock, tc);
      for (Iterator iter = catchBlocks.iterator(); iter.hasNext();) {
        tryNode = makeNode(context, fFactory, n, CAstNode.TRY, tryNode, visitNode((CatchClause) iter.next(), context));
      }

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

        // try/catch/finally
      } else {
        return makeNode(context, fFactory, n, CAstNode.UNWIND, tryNode, visitNode(finallyBlock, context));
      }
    }

  }

  private CAstNode visit(CatchClause n, WalkContext context) {
    Block body = n.getBody();
    SingleVariableDeclaration formal = n.getException();

    CAstNode excDecl = makeNode(context, fFactory, n, CAstNode.CATCH, fFactory.makeConstant(formal.getName().getIdentifier()),
        visitNode(body, context));
    CAstNode localScope = makeNode(context, fFactory, n, CAstNode.LOCAL_SCOPE, excDecl);

    context.cfg().map(n, excDecl);
    context.getNodeTypeMap().add(excDecl, fTypeDict.getCAstTypeFor(n.getException().resolveBinding().getType()));
    return localScope;
  }

  private CAstNode visit(ThrowStatement n, WalkContext context) {
    CAstNode result = makeNode(context, fFactory, n, CAstNode.THROW, visitNode(n.getExpression(), context));
    ITypeBinding label = n.getExpression().resolveTypeBinding();

    context.cfg().map(n, result);

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

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

      context.cfg().add(n, catchNode.snd, catchNode.fst);
    }

    return result;
  }

  private void hookUpNPETargets(ASTNode n, WalkContext wc) {
    Collection excTargets = wc.getCatchTargets(fNullPointerExcType);
    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, fNullPointerExcType);
      }
    } else {
      // connect exception edge to exit
      wc.cfg().add(n, CAstControlFlowMap.EXCEPTION_TO_EXIT, fNullPointerExcType);
    }
  }

  //
  // ARRAYS
  //

  private CAstNode visit(ArrayAccess n, WalkContext context) {
    TypeReference eltTypeRef = fIdentityMapper.getTypeRef(n.resolveTypeBinding());

    CAstNode cast = makeNode(context, fFactory, n, CAstNode.ARRAY_REF, visitNode(n.getArray(), context), fFactory.makeConstant(eltTypeRef),
        visitNode(n.getIndex(), context));

    hookUpNPETargets(n, context);
    
    context.cfg().map(n, cast);
    
    return cast;
  }

  // FIXME: inner classes here, probably too...
  private CAstNode visit(ArrayCreation n, WalkContext context) {
    ITypeBinding newType = n.resolveTypeBinding();
    ArrayInitializer ai = n.getInitializer();
    assert newType.isArray();

    if (ai != null) {
      return visitNode(ai, context);
    } else {
      TypeReference arrayTypeRef = fIdentityMapper.getTypeRef(newType);

      List/* <Expression> */dims = n.dimensions();
      CAstNode[] args = new CAstNode[dims.size() + 1];

      int idx = 0;
      args[idx++] = fFactory.makeConstant(arrayTypeRef);
      for (Iterator iter = dims.iterator(); iter.hasNext();) {
        Expression dimExpr = (Expression) iter.next();
        args[idx++] = visitNode(dimExpr, context);
      }
      return makeNode(context, fFactory, n, CAstNode.NEW, args);
    }
  }

  private CAstNode visit(SynchronizedStatement n, WalkContext context) {
    CAstNode exprNode = visitNode(n.getExpression(), context);
    String exprName = fFactory.makeUnique();
    CAstNode declStmt = makeNode(context, fFactory, n, CAstNode.DECL_STMT, fFactory
        .makeConstant(new CAstSymbolImpl(exprName, true)), exprNode);

    CAstNode monitorEnterNode = makeNode(context, fFactory, n, CAstNode.MONITOR_ENTER, makeNode(context, fFactory, n, CAstNode.VAR,
        fFactory.makeConstant(exprName)));
    context.cfg().map(monitorEnterNode, monitorEnterNode);
    for (Pair<ITypeBinding, Object> catchTarget : context.getCatchTargets(fNullPointerExcType))
      context.cfg().add(monitorEnterNode, catchTarget.snd, catchTarget.fst);

    CAstNode bodyNodes = visitNode(n.getBody(), context);

    CAstNode monitorExitNode = makeNode(context, fFactory, n, CAstNode.MONITOR_EXIT, makeNode(context, fFactory, n, CAstNode.VAR,
        fFactory.makeConstant(exprName)));
    context.cfg().map(monitorExitNode, monitorExitNode);
    for (Pair<ITypeBinding, Object> catchTarget : context.getCatchTargets(fNullPointerExcType))
      context.cfg().add(monitorExitNode, catchTarget.snd, catchTarget.fst);

    CAstNode tryBody = makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, monitorEnterNode, bodyNodes);
    CAstNode bigBody = makeNode(context, fFactory, n, CAstNode.UNWIND, tryBody, monitorExitNode);

    return makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, declStmt, bigBody);
  }

  // ///////////////////////////////////////////
  // / THE GIANT SWITCH STATEMENT ( BORING ) ///
  // ///////////////////////////////////////////

  /**
   * Giant switch statement.
   * 
   * @param n
   * @return
   */
  private CAstEntity visit(AbstractTypeDeclaration n, WalkContext context) {
    // handling of compilationunit in translate()
    if (n instanceof TypeDeclaration) {
      return visitTypeDecl(n, context);
    } else if (n instanceof EnumDeclaration) {
      return visit((EnumDeclaration) n, context);
    } else if (n instanceof AnnotationTypeDeclaration) {
      return visitTypeDecl(n, context);
    } else {
      Assertions.UNREACHABLE("Unhandled type declaration type");
      return null;
    }
  }

  /**
   * Giant switch statement, part deux
   * 
   * @param body
   * @param context
   * @return
   */
  private CAstNode visitNode(ASTNode n, WalkContext context) {
    if (n == null)
      return makeNode(context, fFactory, null, CAstNode.EMPTY);

    if (n instanceof ArrayAccess) {
      return visit((ArrayAccess) n, context);
    } else if (n instanceof ArrayCreation) {
      return visit((ArrayCreation) n, context);
    } else if (n instanceof ArrayInitializer) {
      return visit((ArrayInitializer) n, context);
    } else if (n instanceof AssertStatement) {
      return visit((AssertStatement) n, context);
    } else if (n instanceof Assignment) {
      return visit((Assignment) n, context);
    } else if (n instanceof Block) {
      return visit((Block) n, context);
    } else if (n instanceof BooleanLiteral) {
      return visit((BooleanLiteral) n, context);
    } else if (n instanceof BreakStatement) {
      return visit((BreakStatement) n, context);
    } else if (n instanceof CastExpression) {
      return visit((CastExpression) n, context);
    } else if (n instanceof CatchClause) {
      return visit((CatchClause) n, context);
    } else if (n instanceof CharacterLiteral) {
      return visit((CharacterLiteral) n, context);
    } else if (n instanceof ClassInstanceCreation) {
      return visit((ClassInstanceCreation) n, context);
    } else if (n instanceof ConditionalExpression) {
      return visit((ConditionalExpression) n, context);
    } else if (n instanceof ConstructorInvocation) {
      return visit((ConstructorInvocation) n, context);
    } else if (n instanceof ContinueStatement) {
      return visit((ContinueStatement) n, context);
    } else if (n instanceof DoStatement) {
      return visit((DoStatement) n, context);
    } else if (n instanceof EmptyStatement) {
      return visit((EmptyStatement) n, context);
    } else if (n instanceof EnhancedForStatement) {
      return visit((EnhancedForStatement) n, context);
    } else if (n instanceof ExpressionStatement) {
      return visit((ExpressionStatement) n, context);
    } else if (n instanceof FieldAccess) {
      return visit((FieldAccess) n, context);
    } else if (n instanceof ForStatement) {
      return visit((ForStatement) n, context);
    } else if (n instanceof IfStatement) {
      return visit((IfStatement) n, context);
    } else if (n instanceof InfixExpression) {
      return visit((InfixExpression) n, context);
    } else if (n instanceof InstanceofExpression) {
      return visit((InstanceofExpression) n, context);
    } else if (n instanceof LabeledStatement) {
      return visit((LabeledStatement) n, context);
    } else if (n instanceof MethodInvocation) {
      return visit((MethodInvocation) n, context);
    } else if (n instanceof NumberLiteral) {
      return visit((NumberLiteral) n, context);
    } else if (n instanceof NullLiteral) {
      return visit((NullLiteral) n, context);
    } else if (n instanceof ParenthesizedExpression) {
      return visit((ParenthesizedExpression) n, context);
    } else if (n instanceof PostfixExpression) {
      return visit((PostfixExpression) n, context);
    } else if (n instanceof PrefixExpression) {
      return visit((PrefixExpression) n, context);
    } else if (n instanceof QualifiedName) {
      return visit((QualifiedName) n, context);
    } else if (n instanceof ReturnStatement) {
      return visit((ReturnStatement) n, context);
    } else if (n instanceof SimpleName) {
      return visit((SimpleName) n, context);
    } else if (n instanceof StringLiteral) {
      return visit((StringLiteral) n, context);
    } else if (n instanceof SuperConstructorInvocation) {
      return visit((SuperConstructorInvocation) n, context);
    } else if (n instanceof SuperFieldAccess) {
      return visit((SuperFieldAccess) n, context);
    } else if (n instanceof SuperMethodInvocation) {
      return visit((SuperMethodInvocation) n, context);
    } else if (n instanceof SynchronizedStatement) {
      return visit((SynchronizedStatement) n, context);
    } else if (n instanceof SwitchStatement) {
      return visit((SwitchStatement) n, context);
    } else if (n instanceof SwitchCase) {
      return visit((SwitchCase) n, context);
    } else if (n instanceof ThisExpression) {
      return visit((ThisExpression) n, context);
    } else if (n instanceof TypeLiteral) {
      return visit((TypeLiteral) n, context);
    } else if (n instanceof ThrowStatement) {
      return visit((ThrowStatement) n, context);
    } else if (n instanceof TryStatement) {
      return visit((TryStatement) n, context);
    } else if (n instanceof TypeDeclarationStatement) {
      return visit((TypeDeclarationStatement) n, context);
    } else if (n instanceof VariableDeclarationFragment) {
      return visit((VariableDeclarationFragment) n, context);
    } else if (n instanceof WhileStatement) {
      return visit((WhileStatement) n, context);
    }

    // VariableDeclarationStatement handled as special case (returns multiple statements)

    Assertions.UNREACHABLE("Unhandled JDT node type " + n.getClass().getCanonicalName());

    return null;
  }

  private void visitNodeOrNodes(ASTNode n, WalkContext context, Collection<CAstNode> coll) {
    if (n instanceof VariableDeclarationStatement)
      coll.addAll(visit((VariableDeclarationStatement) n, context));
    else
      coll.add(visitNode(n, context));
  }

  // /////////////////////////////////////////
  // SPECIALIZED CASTENTITYs AND CASTNODEs //
  // /////////////////////////////////////////

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

    private final Collection<CAstEntity> fTopLevelDecls;

    public CompilationUnitEntity(PackageDeclaration packageDeclaration, List<CAstEntity> topLevelDecls) {
      fName = (packageDeclaration == null) ? "" : packageDeclaration.getName().getFullyQualifiedName().replace('.', '/');
      fTopLevelDecls = topLevelDecls;
    }

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

	public int getKind() {
      return FILE_ENTITY;
    }

    public String getName() {
      return fName;
    }

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

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

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

    public int getArgumentCount() {
      return 0;
    }

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

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

    public CAstNode getAST() {
      return null;
    }

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

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

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

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

    public Collection getQualifiers() {
      return Collections.EMPTY_LIST;
    }

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

  // /////////////////////////////
  // WALK CONTEXTS
  // WHY????
  // ////////////////////////////////

  /**
   * Contains things needed by in the visit() of some nodes to process the nodes. For example, pos() contains the source position
   * mapping which each node registers
   */
  public static interface WalkContext extends TranslatorToCAst.WalkContext<WalkContext, ASTNode> {

    public Collection<Pair<ITypeBinding, Object>> getCatchTargets(ITypeBinding type);

    public Map<ASTNode, String> getLabelMap();

    public boolean needLValue();
  }

  /**
   * Default context functions. When one context doesn't handle something, it the next one up does. For example, there is only one
   * source pos. mapping per MethodContext, so loop contexts delegate it up.
   */
  public static class DelegatingContext extends TranslatorToCAst.DelegatingContext<WalkContext, ASTNode> implements WalkContext {

    public DelegatingContext(WalkContext parent) {
      super(parent);
    }

    public Collection<Pair<ITypeBinding, Object>> getCatchTargets(ITypeBinding type) {
      return parent.getCatchTargets(type);
    }

    public Map<ASTNode, String> getLabelMap() {
      return parent.getLabelMap();
    }

     public boolean needLValue() {
      return parent.needLValue();
    }
  }

  /*
   * Root context. Doesn't do anything.
   */
  public static class RootContext extends TranslatorToCAst.RootContext<WalkContext, ASTNode> implements WalkContext {
     public Collection<Pair<ITypeBinding, Object>> getCatchTargets(ITypeBinding type) {
      Assertions.UNREACHABLE("RootContext.getCatchTargets()");
      return null;
    }

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

    public boolean needLValue() {
      Assertions.UNREACHABLE("Rootcontext.needLValue()");
      return false;
    }
  }

  private class AssignmentContext extends DelegatingContext {

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

    public boolean needLValue() {
      return true;
    }
  }

  private static class TryCatchContext extends DelegatingContext {
    Collection<Pair<ITypeBinding, Object>> fCatchNodes = new ArrayList<Pair<ITypeBinding, Object>>();

    TryCatchContext(WalkContext parent, TryStatement tryNode) {
      super(parent);

      for (Iterator catchIter = tryNode.catchClauses().iterator(); catchIter.hasNext();) {
        CatchClause c = (CatchClause) catchIter.next();
        Pair<ITypeBinding, Object> p = Pair.make(c.getException().resolveBinding().getType(), (Object) c);

        fCatchNodes.add(p);
      }
    }

    public Collection<Pair<ITypeBinding, Object>> getCatchTargets(ITypeBinding 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<ITypeBinding, Object>> catchNodes = new ArrayList<Pair<ITypeBinding, Object>>();

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

        // catchType here should NEVER be FakeExceptionTypeBinary, because these can only be thrown (not caught) by
        // "1/0", implicit null pointer exceptions, etc.
        assert !(catchNodes instanceof FakeExceptionTypeBinding) : "catchNodes instanceof FakeExceptionTypeBinary!";

        if (label.isSubTypeCompatible(catchType) || label.isEqualTo(catchType)) {
          catchNodes.add(p);
          return catchNodes;
          // _might_ get caught
        } else if (catchType.isSubTypeCompatible(label)) {
          catchNodes.add(p);
          continue;
        }
      }
      catchNodes.addAll(parent.getCatchTargets(label));
      return catchNodes;
    }
  }

  private class BreakContext extends DelegatingContext {
    protected final String label;

    private final ASTNode breakTo;

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

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

  private class LoopContext extends BreakContext {
    private final ASTNode continueTo;

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

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

  public class MethodContext extends DelegatingContext {
    private final Map<CAstNode, CAstEntity> fEntities;

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

    public MethodContext(WalkContext parent, Map<CAstNode, CAstEntity> entities) {
      // constructor did take: pd.procedureInstance(), memberEntities, context
      super(parent);
      fEntities = entities;
    }

    public Map<ASTNode, String> getLabelMap() {
      return labelMap; // labels are kept within a method.
    }

    final CAstSourcePositionRecorder fSourceMap = new CAstSourcePositionRecorder();

    final CAstControlFlowRecorder fCFG = new CAstControlFlowRecorder(fSourceMap);

    final CAstNodeTypeMapRecorder fNodeTypeMap = new CAstNodeTypeMapRecorder();

    public CAstControlFlowRecorder cfg() {
      return fCFG;
    }

    public void addScopedEntity(CAstNode node, CAstEntity entity) {
      fEntities.put(node, entity);
    }

    public CAstSourcePositionRecorder pos() {
      return fSourceMap;
    }

    public CAstNodeTypeMapRecorder getNodeTypeMap() {
      return fNodeTypeMap;
    }

    public Collection<Pair<ITypeBinding, Object>> getCatchTargets(ITypeBinding label) {
      // TAGALONG (need fRuntimeExcType)
      // Why do we seemingly catch a RuntimeException in every method? this won't catch the RuntimeException above where
      // it is supposed to be caught?
      Collection<Pair<ITypeBinding, Object>> result = Collections.singleton(Pair.<ITypeBinding, Object> make(fRuntimeExcType,
          CAstControlFlowMap.EXCEPTION_TO_EXIT));
      return result;
    }

    public boolean needLValue() {
      return false;
    }

  }

  // ////////////////////////////////////
  // MAKE NODE VARIATIONS & POSITIONS (BORING)
  // maybe moved to different file.
  // makeNode() simply calls Ast.makeNode() and sets the position for the node
  // ////////////////////////////////////
  protected CAstNode makeNode(WalkContext wc, CAst Ast, ASTNode n, int kind) {
    CAstNode cn = Ast.makeNode(kind);
    setPos(wc, cn, n);
    return cn;
  }

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

  protected CAstNode makeNode(WalkContext wc, CAst Ast, JdtPosition pos, int kind, CAstNode c[]) {
    CAstNode cn = Ast.makeNode(kind, c);
    wc.pos().setPosition(cn, pos);
    return cn;
  }

  protected CAstNode makeNode(WalkContext wc, CAst Ast, ASTNode 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, ASTNode n, int kind, CAstNode c) {
    CAstNode cn = Ast.makeNode(kind, c);
    setPos(wc, cn, n);
    return cn;
  }

  protected CAstNode makeNode(WalkContext wc, CAst Ast, ASTNode 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, ASTNode 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, CAst Ast, JdtPosition pos, int kind, CAstNode c1, CAstNode c2, CAstNode c3) {
    CAstNode cn = Ast.makeNode(kind, c1, c2, c3);
    wc.pos().setPosition(cn, pos);
    return cn;
  }

  protected void setPos(WalkContext wc, CAstNode cn, ASTNode jdtNode) {
    if (jdtNode != null)
      wc.pos().setPosition(cn, makePosition(jdtNode));
  }

  public JdtPosition makePosition(ASTNode n) {
    return makePosition(n.getStartPosition(), n.getStartPosition() + n.getLength());
  }

  public JdtPosition makePosition(int start, int end) {
    return new JdtPosition(start, end, cu.getLineNumber(start), cu.getLineNumber(end), sourceFile, fullPath);
  }

  

  // /////////////////////////////////////////////////////////////////
  // // ENUM TRANSFORMATION //////////////////////////////////////////
  // /////////////////////////////////////////////////////////////////

  private static final ArrayList<CAstQualifier> enumQuals = new ArrayList<CAstQualifier>(3);
  static {
    enumQuals.add(CAstQualifier.PUBLIC);
    enumQuals.add(CAstQualifier.STATIC);
    enumQuals.add(CAstQualifier.FINAL);
  }

  /**
   * Only called from createClassDeclaration.
   * 
   * @param decl
   * @param context
   * @return
   */
  private CAstEntity visit(EnumConstantDeclaration decl, WalkContext context) {
    return new FieldEntity(decl.getName().getIdentifier(), decl.resolveVariable().getType(), enumQuals, makePosition(decl
        .getStartPosition(), decl.getStartPosition() + decl.getLength()), null);
  }

  /**
   * Called only from visitFieldInitNode(node,context)
   */
  private CAstNode createEnumConstantDeclarationInit(EnumConstantDeclaration node, WalkContext context) {
    String hiddenVariableName = (String) node.getProperty("com.ibm.wala.cast.java.translator.jdt.fakeValuesDeclName");
    if (hiddenVariableName == null) {
      FieldReference fieldRef = fIdentityMapper.getFieldRef(node.resolveVariable());
      // We use null to indicate an OBJECT_REF to a static field
      CAstNode lhsNode = makeNode(context, fFactory, node, CAstNode.OBJECT_REF, makeNode(context, fFactory, null, CAstNode.VOID),
          fFactory.makeConstant(fieldRef));

      // CONSTRUCT ARGUMENTS & "new MyEnum(...)" statement
      ArrayList<Object> arguments = new ArrayList<Object>();
      arguments.add(fFactory.makeConstant(node.getName().getIdentifier())); // name of constant
      arguments.add(fFactory.makeConstant(node.resolveVariable().getVariableId())); // id
      arguments.addAll(node.arguments());
      CAstNode rhsNode = createClassInstanceCreation(node, arguments, node.resolveConstructorBinding(), null, node
          .getAnonymousClassDeclaration(), context);

      CAstNode assNode = makeNode(context, fFactory, node, CAstNode.ASSIGN, lhsNode, rhsNode);

      return assNode; // their naming, not mine
    } else {

      // String[] x = (new Direction[] {
      // NORTH, EAST, SOUTH, WEST, $VALUES, $VALUES$
      // });

      return null;
    }
  }

  private CAstEntity createEnumValueOfMethod(ITypeBinding enumType, WalkContext oldContext) {
    IMethodBinding met = null, superMet = null;
    // find our valueOf(String)
    for (IMethodBinding m : enumType.getDeclaredMethods())
      if (m.getName().equals("valueOf") && m.getParameterTypes().length == 1
          && m.getParameterTypes()[0].isEqualTo(ast.resolveWellKnownType("java.lang.String")))
        met = m;
    // find Enum.valueOf(Class, String)
    for (IMethodBinding m : enumType.getSuperclass().getTypeDeclaration().getDeclaredMethods())
      if (m.getName().equals("valueOf") && m.getParameterTypes().length == 2)
        superMet = m;
    assert met != null && superMet != null : "Couldn't find enum values() function in JDT bindings!";

    Map<CAstNode, CAstEntity> memberEntities = new LinkedHashMap<CAstNode, CAstEntity>();
    final MethodContext context = new MethodContext(oldContext, memberEntities);

    MethodDeclaration fakeMet = ast.newMethodDeclaration();
    fakeMet.setName(ast.newSimpleName("valueOf"));
    fakeMet.setSourceRange(-1, 0);
    fakeMet.setBody(ast.newBlock());
    SingleVariableDeclaration stringS = ast.newSingleVariableDeclaration();
    stringS.setName(ast.newSimpleName("s"));
    fakeMet.parameters().add(stringS);

    // TODO: probably uses reflection so isn't very useful for analyses. Is there something more useful we could put in here?
    // return (MyEnum)Enum.valueOf(MyEnum.class, s);
    // cast(call(type_literal, var)))

    CAstNode typeLit = makeNode(context, fFactory, fakeMet, CAstNode.TYPE_LITERAL_EXPR, fFactory.makeConstant(fIdentityMapper
        .typeToTypeID(enumType)));
    CAstNode stringSvar = makeNode(context, fFactory, fakeMet, CAstNode.VAR, fFactory.makeConstant("s"));
    ArrayList<Object> args = new ArrayList<Object>();
    args.add(typeLit);
    args.add(stringSvar);
    CAstNode call = createMethodInvocation(fakeMet, superMet, makeNode(context, fFactory, fakeMet, CAstNode.VOID), args, context);
    CAstNode cast = createCast(fakeMet, call, enumType, superMet.getReturnType(), context);
    CAstNode bodyNode = makeNode(context, fFactory, fakeMet, CAstNode.LOCAL_SCOPE, makeNode(context, fFactory, fakeMet,
        CAstNode.BLOCK_STMT, makeNode(context, fFactory, fakeMet, CAstNode.RETURN, cast)));

    ArrayList<CAstType> paramTypes = new ArrayList<CAstType>(1);
    paramTypes.add(fTypeDict.getCAstTypeFor(ast.resolveWellKnownType("java.lang.String")));

    return new ProcedureEntity(bodyNode, fakeMet, enumType, memberEntities, context, paramTypes, enumType, met.getModifiers(), handleAnnotations(met));
  }

  private CAstEntity createEnumValuesMethod(ITypeBinding enumType, ArrayList<IVariableBinding> constants, WalkContext oldContext) {
    IMethodBinding met = null;
    for (IMethodBinding m : enumType.getDeclaredMethods())
      if (m.getName().equals("values") && m.getParameterTypes().length == 0)
        met = m;
    assert met != null : "Couldn't find enum values() function in JDT bindings!";

    Map<CAstNode, CAstEntity> memberEntities = new LinkedHashMap<CAstNode, CAstEntity>();
    final MethodContext context = new MethodContext(oldContext, memberEntities);

    MethodDeclaration fakeMet = ast.newMethodDeclaration();
    fakeMet.setName(ast.newSimpleName("values"));
    fakeMet.setSourceRange(-1, 0);
    fakeMet.setBody(ast.newBlock());

    // make enum constant values array: new MyEnum() { MYENUMCST1, MYENUMCST2, ... }
    CAstNode[] eltNodes = new CAstNode[constants.size() + 1];
    int idx = 0;
    TypeReference arrayTypeRef = fIdentityMapper.getTypeRef(enumType.createArrayType(1));
    eltNodes[idx++] = makeNode(context, fFactory, fakeMet, CAstNode.NEW, fFactory.makeConstant(arrayTypeRef), fFactory
        .makeConstant(constants.size()));
    for (IVariableBinding cst : constants)
      eltNodes[idx++] = createFieldAccess(makeNode(context, fFactory, fakeMet, CAstNode.VOID), cst.getName(), cst, fakeMet, context);

    CAstNode bodyNode = makeNode(context, fFactory, fakeMet, CAstNode.LOCAL_SCOPE, makeNode(context, fFactory, fakeMet,
        CAstNode.BLOCK_STMT, makeNode(context, fFactory, fakeMet, CAstNode.RETURN, makeNode(context, fFactory, fakeMet,
            CAstNode.ARRAY_LITERAL, eltNodes))));

    ArrayList<CAstType> paramTypes = new ArrayList<CAstType>(0);
    return new ProcedureEntity(bodyNode, fakeMet, enumType, memberEntities, context, paramTypes, enumType.createArrayType(1), met
        .getModifiers(), handleAnnotations(enumType));
  }

  private void doEnumHiddenEntities(ITypeBinding typeBinding, ArrayList<ASTNode> staticInits, List<CAstEntity> memberEntities,
      WalkContext context) {
    // PART I: create a $VALUES field
    // collect constants
    // ArrayList<String> constants = new ArrayList<String>();
    // for ( ASTNode n: staticInits )
    // if ( n instanceof EnumConstantDeclaration )
    // constants.add(((EnumConstantDeclaration)n).getName().getIdentifier());
    // figure out a suitable untaken name
    // String hiddenFieldName = "hidden values field"; // illegal name
    // // public static final MyEnum[] $VALUES;
    // memberEntities.add(new FieldEntity(hiddenFieldName,
    // typeBinding.createArrayType(1), enumQuals,
    // makePosition(-1,-1)));
    //
    // EnumConstantDeclaration fakeValuesDecl = ast.newEnumConstantDeclaration();
    // // pass along values that we will use in createEnumConstantDeclarationInit() in creating static initializer
    // fakeValuesDecl.setProperty("com.ibm.wala.cast.java.translator.jdt.fakeValuesDeclName", hiddenFieldName);
    // fakeValuesDecl.setProperty("com.ibm.wala.cast.java.translator.jdt.fakeValuesDeclConstants", constants);
    // staticInits.add(fakeValuesDecl);

    ArrayList<IVariableBinding> constants = new ArrayList<IVariableBinding>();
    for (IVariableBinding var : typeBinding.getDeclaredFields())
      if (var.isEnumConstant())
        constants.add(var);

    // constants are unsorted by default
    Collections.sort(constants, new Comparator<IVariableBinding>() {
      public int compare(IVariableBinding arg0, IVariableBinding arg1) {
        return arg0.getVariableId() - arg1.getVariableId();
      }
    });

    // PART II: create values()
    memberEntities.add(createEnumValuesMethod(typeBinding, constants, context));

    // PART III: create valueOf()
    memberEntities.add(createEnumValueOfMethod(typeBinding, context));
  }

  private CAstEntity visit(EnumDeclaration n, WalkContext context) {

    // JDT contains correct type info / class / subclass info for the enum
    return createClassDeclaration(n, n.bodyDeclarations(), n.enumConstants(), n.resolveBinding(), n.getName().getIdentifier(), n
        .resolveBinding().getModifiers(), false, false, context);
  }

  /**
   * @param n for positioning.
   */
  private CAstEntity createEnumConstructorWithParameters(IMethodBinding ctor, ASTNode n, WalkContext oldContext,
      ArrayList<ASTNode> inits, MethodDeclaration nonDefaultCtor) {
    // PART I: find super ctor to call
    ITypeBinding newType = ctor.getDeclaringClass();
    ITypeBinding javalangenumType = newType.getSuperclass();
    IMethodBinding superCtor = null;

    if (newType.isEnum()) {
      for (IMethodBinding met : javalangenumType.getDeclaredMethods())
        if (met.isConstructor()) {
          superCtor = met;
          break;
        }
    }

    assert superCtor != null : "enum";

    // PART II: make ctor with simply "super(a,b,c...)"
    // TODO: extra CAstNodes
    final Map<CAstNode, CAstEntity> memberEntities = new LinkedHashMap<CAstNode, CAstEntity>();
    final MethodContext context = new MethodContext(oldContext, memberEntities);
    MethodDeclaration fakeCtor = ast.newMethodDeclaration();
    fakeCtor.setConstructor(true);
    fakeCtor.setSourceRange(n.getStartPosition(), n.getLength());
    fakeCtor.setBody(ast.newBlock());

    // PART IIa: make a fake JDT constructor method with the proper number of args
    // Make fake args that will be passed
    String[] fakeArguments = new String[3 + ctor.getParameterTypes().length];
    if (nonDefaultCtor == null) {
      for (int i = 3; i < fakeArguments.length; i++)
        fakeArguments[i] = "__wala_jdtcast_argument" + i; // this is in the case of an anonymous class with parameters, eg NORTH in
                                                          // the following example: public enum A { NORTH("south") { ...} A(String
                                                          // s){} }
    } else {
      for (int i = 3; i < fakeArguments.length; i++)
        fakeArguments[i] = ((SingleVariableDeclaration) nonDefaultCtor.parameters().get(i - 3)).getName().getIdentifier();
    }

    ArrayList<CAstType> paramTypes = new ArrayList<CAstType>(superCtor.getParameterTypes().length);
    fakeArguments[0] = "this";
    fakeArguments[1] = "__wala_jdtcast_argument1"; // TODO FIXME: change to invalid name in the case that nonDefaultCtor != null
    fakeArguments[2] = "__wala_jdtcast_argument2"; // otherwise there will be conflicts if we name our variable
                                                   // __wala_jdtcast_argument1!!!
    for (int i = 1; i < fakeArguments.length; i++) {
      // the name
      SingleVariableDeclaration svd = ast.newSingleVariableDeclaration();
      svd.setName(ast.newSimpleName(fakeArguments[i]));
      fakeCtor.parameters().add(svd);

      // the type
      if (i == 1)
        paramTypes.add(fTypeDict.getCAstTypeFor(ast.resolveWellKnownType("java.lang.String")));
      else if (i == 2)
        paramTypes.add(fTypeDict.getCAstTypeFor(ast.resolveWellKnownType("int")));
      else
        paramTypes.add(fTypeDict.getCAstTypeFor(ctor.getParameterTypes()[i - 3]));
    }

    // PART IIb: create the statements in the constructor
    // one super() call plus the inits
    CAstNode[] bodyNodes;
    if (nonDefaultCtor == null)
      bodyNodes = new CAstNode[inits.size() + 1];
    else
      bodyNodes = new CAstNode[inits.size() + 2];

    // make super(...) call
    // this, call ref, args
    CAstNode[] children;
    if (ctor.isDefaultConstructor())
      children = new CAstNode[4 + ctor.getParameterTypes().length]; // anonymous class' implicit constructors call constructors with
                                                                    // more than standard two enum args
    else
      children = new CAstNode[4]; // explicit constructor
    children[0] = makeNode(context, fFactory, n, CAstNode.SUPER);
    CallSiteReference callSiteRef = CallSiteReference.make(0, fIdentityMapper.getMethodRef(superCtor),
        IInvokeInstruction.Dispatch.SPECIAL);
    children[1] = fFactory.makeConstant(callSiteRef);
    children[2] = makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(fakeArguments[1]));
    children[3] = makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(fakeArguments[2]));

    if (ctor.isDefaultConstructor())
      for (int i = 0; i < ctor.getParameterTypes().length; i++)
        children[i + 4] = makeNode(context, fFactory, n, CAstNode.VAR, fFactory.makeConstant(fakeArguments[i + 3]));

    bodyNodes[0] = makeNode(context, fFactory, n, CAstNode.CALL, children);
    // QUESTION: no handleExceptions?

    for (int i = 0; i < inits.size(); i++)
      bodyNodes[i + 1] = visitFieldInitNode(inits.get(i), context);

    if (nonDefaultCtor != null)
      bodyNodes[bodyNodes.length - 1] = visitNode(nonDefaultCtor.getBody(), context);

    // finally, make the procedure entity
    CAstNode ast = makeNode(context, fFactory, n, CAstNode.BLOCK_STMT, bodyNodes);
    return new ProcedureEntity(ast, fakeCtor, newType, memberEntities, context, paramTypes, null, handleAnnotations(ctor));

  }

}
