Package org.openquark.cal.internal.javamodel

Source Code of org.openquark.cal.internal.javamodel.AsmJavaBytecodeGenerator

/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*     * Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*     * Neither the name of Business Objects nor the names of its contributors
*       may be used to endorse or promote products derived from this software
*       without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/


/*
* AsmJavaBytecodeGenerator.java
* Created: Dec 21, 2004
* By: Bo Ilic
*/

package org.openquark.cal.internal.javamodel;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.openquark.cal.internal.javamodel.JavaExpression.ArrayAccess;
import org.openquark.cal.internal.javamodel.JavaExpression.ArrayCreationExpression;
import org.openquark.cal.internal.javamodel.JavaExpression.ArrayLength;
import org.openquark.cal.internal.javamodel.JavaExpression.Assignment;
import org.openquark.cal.internal.javamodel.JavaExpression.CastExpression;
import org.openquark.cal.internal.javamodel.JavaExpression.ClassInstanceCreationExpression;
import org.openquark.cal.internal.javamodel.JavaExpression.ClassLiteral;
import org.openquark.cal.internal.javamodel.JavaExpression.InstanceOf;
import org.openquark.cal.internal.javamodel.JavaExpression.JavaField;
import org.openquark.cal.internal.javamodel.JavaExpression.LiteralWrapper;
import org.openquark.cal.internal.javamodel.JavaExpression.LocalName;
import org.openquark.cal.internal.javamodel.JavaExpression.LocalVariable;
import org.openquark.cal.internal.javamodel.JavaExpression.MethodInvocation;
import org.openquark.cal.internal.javamodel.JavaExpression.MethodVariable;
import org.openquark.cal.internal.javamodel.JavaExpression.OperatorExpression;
import org.openquark.cal.internal.javamodel.JavaExpression.PlaceHolder;
import org.openquark.cal.internal.javamodel.JavaStatement.AssertStatement;
import org.openquark.cal.internal.javamodel.JavaStatement.Block;
import org.openquark.cal.internal.javamodel.JavaStatement.Comment;
import org.openquark.cal.internal.javamodel.JavaStatement.ExpressionStatement;
import org.openquark.cal.internal.javamodel.JavaStatement.IfThenElseStatement;
import org.openquark.cal.internal.javamodel.JavaStatement.LabelledContinue;
import org.openquark.cal.internal.javamodel.JavaStatement.LocalVariableDeclaration;
import org.openquark.cal.internal.javamodel.JavaStatement.ReturnStatement;
import org.openquark.cal.internal.javamodel.JavaStatement.SwitchStatement;
import org.openquark.cal.internal.javamodel.JavaStatement.SynchronizedMethodInvocation;
import org.openquark.cal.internal.javamodel.JavaStatement.ThrowStatement;
import org.openquark.cal.internal.javamodel.JavaStatement.UnconditionalLoop;
import org.openquark.cal.internal.javamodel.JavaStatement.SwitchStatement.IntCaseGroup;
import org.openquark.cal.internal.runtime.lecc.LECCMachineConfiguration;



/**
* A class to generate Java bytecode given an object model representation of Java source.
* This class makes use of the ASM bytecode generator library (http://asm.objectweb.org/). This is
* a BSD-licensed library and is thread safe (see the FAQ on the ASM website for the precise limitations
* of this).
*
* <P>The original Java source model to bytecode generator was written by Edward Lam used the BCEL library.
* Some differences between ASM and BCEL:
* -ASM is lower level, requiring us to specify the precise byte-code instruction to encode.
* -ASM is thread-safe, BCEL is not.
* -ASM requires that byte codes be generated in order. BCEL creates intermediate instruction list objects that can be
*   reordered and further manipulated before being converted to byte codes.
* -ASM is significantly faster than BCEL
* -After its implementation, the ASM bytecode generator was further refined to make the byte code it
* produces closer to what javac produces. There were quite a few optimizations added. While in principle
* these could also be done with the BCEL generator, they were not.
*
* <P>The BCEL generator was then deleted to remove the necessity of including the BCEL jars in the
* distribution of CAL. It can be restored from the file JavaByteCodeGenerator.java if needed.
*
* @author Bo Ilic
*/
public final class AsmJavaBytecodeGenerator {
    
    /**
     * Holds information for describing the context for code generation for a method.
     * For example, it contains information about method variables, local variables, and labels (to continue to) in scope.
     * @author Bo Ilic
     */
    private static final class GenerationContext {
       
        /** The parent context. */
        private final GenerationContext parentContext;
       
        /** use this MethodVisitor for encoding the generated byte codes. */
        private final MethodVisitor mv;             
       
        /** the next local variable will be at this index in the JVM frame. It may take 1 or 2 slots, depending on it type. */
        private int nextAvailableVarIndex;
       
        /** map from local var name to its index in the JVM frame. */
        private final Map<String, Integer> localVarNameToIndexMap;
       
        /** map from method var name to its index in the JVM frame. */
        private final Map<String, Integer> methodVarToIndexMap;
       
        /** map from method var name to its type. Includes "this" for non-static methods. */
        private final Map<String, JavaTypeName> methodVarToTypeMap;
       
        /** map from a label name to the corresponding Label object. Used to implement labelled continue statements. */
        private final Map<String, Label> labelNameToLabelMap;
       
        /** Labels in this context, or in descendant contexts. */
        private final Set<Label> containedStatementEscapeLabelSet;
       
        /** (List of JumpReturnLabelInfo) Info about relevant return and jump instruction labels enclosed by this context,
         *  in the order in which they appear.  */
        private final List<JumpReturnLabelInfo> jumpReturnLabelList = new ArrayList<JumpReturnLabelInfo>();
       
        /** JavaTypeName of the class being generated. */
        private final JavaTypeName classTypeName;

        /**
         * Constructor for the outermost GenerationContext for a method.
         * @param methodVarToTypeMap
         * @param methodVarToIndexMap
         * @param nextAvailableVarIndex
         * @param mv
         * @param classTypeName
         */
        GenerationContext(Map<String, JavaTypeName> methodVarToTypeMap, Map<String, Integer> methodVarToIndexMap, int nextAvailableVarIndex, MethodVisitor mv, JavaTypeName classTypeName) {
            this.parentContext = null;
            this.methodVarToTypeMap = methodVarToTypeMap;
            this.methodVarToIndexMap = methodVarToIndexMap;
            this.nextAvailableVarIndex = nextAvailableVarIndex;
            this.mv = mv;
            this.classTypeName = classTypeName;
           
            this.localVarNameToIndexMap = new HashMap<String, Integer>();
            this.labelNameToLabelMap = new HashMap<String, Label>();
            this.containedStatementEscapeLabelSet = new HashSet<Label>();
        }
       
        /**
         * Constructor for a GenerationContext from a parent context.
         * @param parentContext
         */
        GenerationContext(GenerationContext parentContext) {           
            this.parentContext = parentContext;
            this.methodVarToTypeMap = parentContext.methodVarToTypeMap;
            this.methodVarToIndexMap = parentContext.methodVarToIndexMap;                     
            this.nextAvailableVarIndex = parentContext.nextAvailableVarIndex;                      
            this.mv = parentContext.mv;
            this.classTypeName = parentContext.classTypeName;
           
            this.localVarNameToIndexMap = new HashMap<String, Integer>(parentContext.localVarNameToIndexMap);
            this.labelNameToLabelMap = new HashMap<String, Label>(parentContext.labelNameToLabelMap);
            this.containedStatementEscapeLabelSet = new HashSet<Label>();
        }                     
       
        int addLocalVar(String localVarName, JavaTypeName localVarType) {
           
            if (localVarNameToIndexMap.put(localVarName, Integer.valueOf(nextAvailableVarIndex)) != null) {
                throw new IllegalArgumentException("Duplicate local variable declaration: " + localVarName);
            }
           
            int indexOfVar = nextAvailableVarIndex;             
            nextAvailableVarIndex += getTypeSize(localVarType);
           
            return indexOfVar;
        }
       
        int getLocalVarIndex(String varName) {
            Integer index = localVarNameToIndexMap.get(varName);
            if (index != null) {
                return index.intValue();
            }
           
            return -1;
        }           

        /**
         * Add a label at the scope of this context for a jump instruction generated for the purposes of statement control flow.
         * (not for exception labels, nor for jumps resulting from boolean valued operator evaluation, etc.).
         * @param label the label to add
         */
        void addStatementJumpLabel(Label label) {
            // add the label to this context and parent contexts.
            for (GenerationContext context = GenerationContext.this; context != null; context = context.parentContext) {
                context.containedStatementEscapeLabelSet.add(label);
            }
        }
       
        /**
         * Add a named label at the scope of this context for a jump instruction generated for the purposes of statement control flow.
         * (not for exception labels, nor for jumps resulting from boolean valued operator evaluation, etc.).
         * @param labelName the name of the label
         * @param label the label to add
         */
        void addStatementJumpLabel(String labelName, Label label) {
            if (labelNameToLabelMap.put(labelName, label) != null) {
                throw new IllegalArgumentException("Repeated addition of a label in a single context: " + labelName);
            }
            addStatementJumpLabel(label);
        }
       
        Label getNamedStatementJumpLabel(String labelName) {
            return labelNameToLabelMap.get(labelName);
        }
       
        /**
         * @param label the label to find
         * @return true if both of these conditions apply:
         * 1) it exists within the scope of this context (ie. in this context or child contexts).
         * 2) it is a label for a jump instruction whose destination lies outside the scope of the associated statement.
         */
        boolean containsStatementJumpLabel(Label label) {
            return containedStatementEscapeLabelSet.contains(label);
        }
       
        /**
         * Add a JumpReturnLabelInfo generated (in order) within the scope of this context.
         * @param jumpReturnLabelInfo the info to add
         */
        void addJumpReturnLabelInfo(JumpReturnLabelInfo jumpReturnLabelInfo) {
            jumpReturnLabelList.add(jumpReturnLabelInfo);
        }
       
        /**
         * Add any JumpReturnLabelInfo generated (in order) by a child context.
         * @param childContext the child context whose labels to add.
         */
        void addJumpReturnLabelInfo(GenerationContext childContext) {
            jumpReturnLabelList.addAll(childContext.jumpReturnLabelList);
        }
       
        /**
         * @return (List of JumpReturnLabelInfo)
         * The list of relevant return and jump instruction labels generated within this context, in the order in which they appear. 
         */
        List<JumpReturnLabelInfo> getJumpReturnLabelInfoList() {
            return Collections.unmodifiableList(jumpReturnLabelList);
        }
       
               
        MethodVisitor getMethodVisitor() {
            return mv;
        }
             
        int getMethodVarIndex(String varName) {
            Integer index = methodVarToIndexMap.get(varName);
            if (index != null) {
                return index.intValue();
            }
           
            return -1;
        }
       
        JavaTypeName getMethodVarType(String varName) {
            return methodVarToTypeMap.get(varName);          
        }
    }   
   
    /**
     * Info about a jump or return instruction which has been labeled in order to handle exception table entries
     *   for try-catch statements with try blocks which span the code generating this instruction.
     *
     * Such instructions are excluded from any exception handling blocks if they cause execution to escape the bounds of the try statement.
     * eg. if there is a return statement in a try block in a try-catch statement, the corresponding return instruction will
     *     be excluded from the exception table entry.
     *     if there is a jump (eg. goto) statement in a try block in a try-catch statement, the jump instruction will be
     *     excluded from the exception table entry if the jump destination lies outside the try block.
     *
     * @author Edward Lam
     */
    private static class JumpReturnLabelInfo {
        private final Label instructionLabel;
        private final Label afterInstructionLabel;
        private final Label destinationLabel;

        /**
         * Constructor for a JumpReturnLabelInfo.
         * @param instructionLabel the label for the jump or return instruction.
         * @param afterInstructionLabel the label after the jump or return instruction.
         * @param destinationLabel the destination label for a jump instruction, or null if it's a return instruction.
         */
        private JumpReturnLabelInfo(Label instructionLabel, Label afterInstructionLabel, Label destinationLabel) {
            this.instructionLabel = instructionLabel;
            this.afterInstructionLabel = afterInstructionLabel;
            this.destinationLabel = destinationLabel;
        }
       
        /**
         * @return the label for the instruction.
         */
        public Label getInstructionLabel() {
            return instructionLabel;
        }
       
        /**
         * @return the label after the instruction.
         */
        public Label getAfterInstructionLabel() {
            return afterInstructionLabel;
        }
       
        /**
         * @return the destination label of the jump or null if it's a return instruction.
         */
        public Label getDestinationLabel() {
            return destinationLabel;
        }
    }
           
   
    /**
     * Constructor for a JavaBytecodeGenerator
     */
    AsmJavaBytecodeGenerator() {
    }   

   
    /**
     * Encodes bytecode for a given class.
     * @param classRep the representation for the top-level class.
     * @return byte[] the byte code for the class. 
     * @throws JavaGenerationException
     */
    public static byte[] encodeClass(JavaClassRep classRep) throws JavaGenerationException {
       
        //get ASM to compute max stack and max locals.  Don't compute stack map frames yet.
        final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
       
        encodeClassAcceptingVisitor(classRep, cw);
       
        byte[] result = cw.toByteArray();
       
        // TODO: It would be faster to not have to read the bytecode back out of the array.
        // We do this since we require ClassWriter to computer the maximum locals and stack
        // before the analysis, which is required since the analysis's Frame class needs to
        // know these maximums since it stores the values of locals and the stack in an array.
        if (LECCMachineConfiguration.bytecodeSpaceOptimization()) {
            final ClassReader cr = new ClassReader(result);
            final ClassWriter cw2 = new ClassWriter(ClassWriter.COMPUTE_MAXS);
            final ClassVisitor cv = new NullingClassAdapter(cw2);
            cr.accept(cv, 0);
           
            result = cw2.toByteArray();
        }
               
        return result;
    }      
   
    /**
     * Accepts a visitor and fills it with information for a given class.
     * @param classRep the representation for the top-level class.
     * @param cv the class visitor to accept.
     * @throws JavaGenerationException
     */
    private static void encodeClassAcceptingVisitor(JavaClassRep classRep, ClassVisitor cv) throws JavaGenerationException {
       
        // Get the fully-qualified internal class and superclass names.   
        final JavaTypeName classRepTypeName = classRep.getClassName();
        final String className = classRepTypeName.getJVMInternalName();
        final String superclassName = classRep.getSuperclassName().getJVMInternalName();

        // Determine if the class or any inner class contains assert statements.
        int assertPresence = classRep.getAssertionContainment();
        if ((assertPresence & JavaClassRep.ASSERTS_UNKNOWN) > 0) {
            assertPresence = AsmJavaBytecodeGenerator.containsAsserts(classRep);
        }
       
        // Create the interfaces[] array.
        final int nInterfaces = classRep.getNInterfaces();
        final String[] interfaces = new String[nInterfaces];
        for (int i = 0; i < nInterfaces; i++) {
            interfaces[i] = classRep.getInterface(i).getJVMInternalName();
        }       
                      
        //ACC_SUPER flag should always be set for the flags defining a class file.
        //(see the Java language specification under ACC_SUPER in the index. The flag is not set only
        //by older Java compilers and exists for backwards compatibility reasons).
        int classModifiers = classRep.getModifiers() | Opcodes.ACC_SUPER;     
        //static inner classes are marked with the static modifier, but this is not a valid access flag for a class.
        classModifiers &= ~Modifier.STATIC;
       
        // We aren't generating or using generics, so the signature can be null
        String classSignature = null;
        cv.visit(Opcodes.V1_5, classModifiers, className, classSignature, superclassName, interfaces);
       
        //sourcefileName = null, since this class was not compiled from a Java source file.
        //However, if we are debugging byte codes, use a "fake" source file name as if this class were generated from a Java source file.
        //This eliminates a trivial difference between the byte code generated by ASM and that of javac and makes inspecting the
        //differences in a differencing tool easier.
        String sourceFileName = null;
//        if (AsmJavaBytecodeGenerator.DEBUG_GENERATED_BYTECODE) {              
            String unqualifiedName = classRepTypeName.getUnqualifiedJavaSourceName();
            int dotPosition = unqualifiedName.indexOf('.');
            if (dotPosition != -1) {
                //get the top level class name.
                unqualifiedName = unqualifiedName.substring(0, dotPosition);
            }
            sourceFileName = unqualifiedName + ".java";
//        }
       
        cv.visitSource(sourceFileName, null);
       
        //add the fields       
        for (int i = 0, nFields = classRep.getNFieldDeclarations(); i < nFields; ++i) {
           
            JavaFieldDeclaration fieldDeclaration = classRep.getFieldDeclaration(i);           
           
            //todoBI it may be more efficient to handle initializers for static-fields here in the cases where it is possible
            //(int, long, float, double, String).
            cv.visitField(fieldDeclaration.getModifiers(),
                fieldDeclaration.getFieldName(),
                fieldDeclaration.getFieldType().getJVMDescriptor(),
                null,
                null);
       
       
        /*
         * When dealing with assert statements there is possibly an additional field that needs to be
         * added.
         * If a class contains an assert statement a static final synthetic boolean field called '$assertionsDisabled' is
         * added.  This field is initialized in the class static initializer and is used to determine whether to
         * check or skip assertions.
         */
        if (assertPresence != JavaClassRep.ASSERTS_NONE) {
           
            if ((assertPresence & JavaClassRep.ASSERTS_IN_CLASS) > 0) {
                // We need to add a static final synthetic boolean field to indicate the enabled/disabled state of assertions.
                cv.visitField(Opcodes.ACC_FINAL + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC, "$assertionsDisabled", "Z", null, null);
            }
        }
       
        //add the constructors
        final int nConstructors = classRep.getNConstructors();
        if (nConstructors == 0) {
            //if empty, add the default constructor.            
           
            JavaConstructor defaultConstructor = new JavaConstructor(Modifier.PUBLIC, ((JavaTypeName.Reference.Object)classRepTypeName).getBaseName());
            defaultConstructor.addStatement(new JavaStatement.ReturnStatement());                                  
            encodeConstructor(classRep, defaultConstructor, cv);
           
        } else {
           
            for (int i = 0; i < nConstructors; ++i) {               
                encodeConstructor(classRep, classRep.getConstructor(i), cv);                             
            }           
        }
       
        //add the methods
        for (int i = 0, nMethods = classRep.getNMethods(); i < nMethods; ++i) {
                       
            encodeMethod(classRep, classRep.getMethod(i), cv);                      
        }
       
        //add the initializers for the static fields
        encodeClassInitializer(classRep, cv, (assertPresence & JavaClassRep.ASSERTS_IN_CLASS) > 0)
       
        //add the inner classes (these are basically just references to the inner classes)
       
        //if classRep itself is an inner class, call visitInnerClass on itself. This is what the eclipse java compiler does.
        //javac annotates for every inner class reference occurring within the class file e.g. a field declared of inner class type,
        //an instance of expression of inner class type, a throws declaration on a method where an inner class is thrown.
        if (classRepTypeName.isInnerClass()) {
           
            JavaTypeName.Reference.Object classTypeName = (JavaTypeName.Reference.Object)classRepTypeName;           
            String internalImportName = classTypeName.getImportName().replace('.', '/');
           
            cv.visitInnerClass(classTypeName.getJVMInternalName(), internalImportName, classTypeName.getBaseName(), classRep.getModifiers());
        }
       
        /*
         * Previously we would call visitInnerClass for any inner classes associated with this class.  However,
         * we are no longer doing this. 
         * Bytecode is generated in different scenarios (i.e. static generation, dynamic generation, etc).  In some
         * scenarios inner classes are generated separately from the containing class.  In order to keep the generated
         * bytecode consistent between the dynamic and static scenarios wer are not adding the attributes for contained
         * inner classes to the bytecode.
         *
         for (int i = 0, nInnerClasses = classRep.getNInnerClasses(); i < nInnerClasses; ++i) {
           
            JavaClassRep innerClass = classRep.getInnerClass(i);
            JavaTypeName.Reference.Object innerClassTypeName = (JavaTypeName.Reference.Object)innerClass.getClassName();           
           
            cw.visitInnerClass(innerClassTypeName.getJVMInternalName(), className, innerClassTypeName.getBaseName(), innerClass.getModifiers());
        }              
       
        */
       
       
        cv.visitEnd();
    }      
   
    /**
     * Add the <init> method for the given constructor. This also includes initializing the non-static fields that
     *   have initializers.
     * @param classRep
     * @param javaConstructor
     * @param cv
     * @throws JavaGenerationException
     */
    private static void encodeConstructor(JavaClassRep classRep, JavaConstructor javaConstructor, ClassVisitor cv) throws JavaGenerationException {              
       
        // gather info on the thrown exceptions
        final int nThrownExceptions = javaConstructor.getNThrownExceptions();
        String[] thrownExceptions = new String[nThrownExceptions];
        for (int i = 0; i < nThrownExceptions; ++i) {           
            thrownExceptions[i] = javaConstructor.getThrownException(i).getJVMInternalName();
        }             
               
        MethodVisitor mv = cv.visitMethod(javaConstructor.getModifiers(), "<init>", javaConstructor.getJVMMethodDescriptor(), null, thrownExceptions);
       
        final Map<String, JavaTypeName> methodVarToTypeMap = new HashMap<String, JavaTypeName>();
        final Map<String, Integer> methodVarToIndexMap = new HashMap<String, Integer>();       
        methodVarToTypeMap.put("this", classRep.getClassName());
        methodVarToIndexMap.put("this", Integer.valueOf(0));
        int indexOfNextSlot = 1;
       
        for (int i = 0, nParams = javaConstructor.getNParams(); i < nParams; ++i) {
            JavaTypeName varType = javaConstructor.getParamType(i);
            final int typeSize = getTypeSize(varType);
            String varName = javaConstructor.getParamName(i);
            methodVarToTypeMap.put(varName, varType);
            methodVarToIndexMap.put(varName, Integer.valueOf(indexOfNextSlot));
            indexOfNextSlot += typeSize;
        }               
    
        // Start the method's code.
        mv.visitCode();
       
        // Invoke the superclass initializer.    
                     
        if (javaConstructor.getSuperConstructorParamTypes().length == 0) {
            //push the "this" reference on the stack
            mv.visitVarInsn(Opcodes.ALOAD, 0);
            mv.visitMethodInsn(Opcodes.INVOKESPECIAL, classRep.getSuperclassName().getJVMInternalName(), "<init>", "()V");
        } else {
            GenerationContext context = new GenerationContext(methodVarToTypeMap, methodVarToIndexMap, indexOfNextSlot, mv, classRep.getClassName());           
            MethodInvocation mi =
                new MethodInvocation.Instance(null, "<init>", classRep.getSuperclassName(), javaConstructor.getSuperConstructorParamValues(), javaConstructor.getSuperConstructorParamTypes(), JavaTypeName.VOID, MethodInvocation.InvocationType.SPECIAL);
            encodeMethodInvocationExpr(mi, context);
        }

        // Add initializers for the non-statically-initialized fields.       
        final int nFields = classRep.getNFieldDeclarations();
        for (int i = 0; i < nFields; ++i) {
           
            JavaFieldDeclaration fieldDecl = classRep.getFieldDeclaration(i);
            JavaExpression initializer = fieldDecl.getInitializer();
           
            if (initializer != null && !Modifier.isStatic(fieldDecl.getModifiers())) {
               
                GenerationContext context = new GenerationContext(methodVarToTypeMap, methodVarToIndexMap, indexOfNextSlot, mv, classRep.getClassName());
               
                //push the "this" reference
                mv.visitVarInsn(Opcodes.ALOAD, 0);
               
                //evaluate the initializer
                encodeExpr(initializer, context);
               
                 //do the assignment
                mv.visitFieldInsn(Opcodes.PUTFIELD, classRep.getClassName().getJVMInternalName(), fieldDecl.getFieldName(), fieldDecl.getFieldType().getJVMDescriptor());               
            }                    
        }
             
        // Add the body code for the constructor
        GenerationContext context = new GenerationContext(methodVarToTypeMap, methodVarToIndexMap, indexOfNextSlot, mv, classRep.getClassName());
        boolean isTerminating = encodeStatement(javaConstructor.getBodyCode(), context);
             
        // Add any implicit "return" statement.
        if (!isTerminating) {
            mv.visitInsn(Opcodes.RETURN);
        }
      
        //mark the end of encoding the constructor
        mv.visitMaxs(0, 0);
       
        // End the method.
        mv.visitEnd();
    }   
   
    /**
     * Add the <clinit> method. This initializes the static fields that have initializers.
     * @param classRep
     * @param cv
     * @param initializeForAsserts
     * @throws JavaGenerationException
     */
    private static void encodeClassInitializer(JavaClassRep classRep,
                                               ClassVisitor cv,
                                               boolean initializeForAsserts) throws JavaGenerationException
       
        // Add initializers for the statically-initialized fields.
        final int nFields = classRep.getNFieldDeclarations();
       
        if (!classRep.hasInitializedStaticField() && !initializeForAsserts) {
            //we don't need to bother adding a static initializer if there are no static fields to initialize.
            //note that javac also has this optimization.
            return;
        }
       
       
       
        MethodVisitor mv = cv.visitMethod(Opcodes.ACC_STATIC, "<clinit>", "()V", null, null);       
       
        // Start the method's code.
        mv.visitCode();

        final JavaTypeName classRepTypeName = classRep.getClassName();

        /*
         * If this class contains assert statements we need to initialize the static final synthetic boolean field
         * '$assertionsDisabled'.
         * This is done by loading the Class constant for the top level class. The method 'desiredAssertionStatus()'
         * is then invoked on this Class object and the returned value is used to initialize $assertionsDisabled.
         */
        if (initializeForAsserts) {
            // Get the fully-qualified internal class  name.   
            final String className = classRepTypeName.getJVMInternalName();
           
            // Get the top level class name.
            String topLevelClassName = getTopLevelClassJVMInternalName(className);
           
            // Load the Class constant for the top level class
            mv.visitLdcInsn(Type.getType("L" + topLevelClassName + ";"));
           
            // Now that we have the Class constant for the top level class we invoke the method
            // desiredAssertionStatus on it and use the resulting value to initialize the static field $assertionsDisabled in this class.
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "desiredAssertionStatus", "()Z");
            Label l0 = new Label();
            mv.visitJumpInsn(Opcodes.IFNE, l0);
            mv.visitInsn(Opcodes.ICONST_1);
            Label l1 = new Label();
            mv.visitJumpInsn(Opcodes.GOTO, l1);
            mv.visitLabel(l0);
            mv.visitInsn(Opcodes.ICONST_0);
            mv.visitLabel(l1);
            mv.visitFieldInsn(Opcodes.PUTSTATIC, className, "$assertionsDisabled", "Z");           
        }
       
        for (int i = 0; i < nFields; ++i) {
           
            JavaFieldDeclaration fieldDecl = classRep.getFieldDeclaration(i);
            JavaExpression initializer = fieldDecl.getInitializer();
           
            if (initializer != null && Modifier.isStatic(fieldDecl.getModifiers())) {
               
                GenerationContext context = new GenerationContext(new HashMap<String, JavaTypeName>(), new HashMap<String, Integer>(), 0, mv, classRepTypeName);
               
                //evaluate the initializer
                encodeExpr(initializer, context);
               
                 //do the assignment
                mv.visitFieldInsn(Opcodes.PUTSTATIC, classRep.getClassName().getJVMInternalName(), fieldDecl.getFieldName(), fieldDecl.getFieldType().getJVMDescriptor());               
            }                    
        }
       
        // return.
        mv.visitInsn(Opcodes.RETURN);
     
        //mark the end of the method with a call to visitMaxs
        mv.visitMaxs(0, 0);       
       
        // End the method.
        mv.visitEnd();
    }


    /**
     * @param jvmInternalClassName the internal name of the class (potentially containing '$' if an inner class)
     * @return the internal name of the top-level class containing the named class.
     */
    private static String getTopLevelClassJVMInternalName(final String jvmInternalClassName) {
        String topLevelClassName = jvmInternalClassName;
        if (jvmInternalClassName.indexOf('$') >= 0) {
            topLevelClassName = jvmInternalClassName.substring(0, jvmInternalClassName.indexOf('$'));
        }
        return topLevelClassName;
    }      
   
    /**
     * Encode java byte code for the given method.
     * @param classRep model for the class in which the method is defined.
     * @param javaMethod
     * @param cv
     * @throws JavaGenerationException
     */
    private static void encodeMethod(JavaClassRep classRep, JavaMethod javaMethod, ClassVisitor cv) throws JavaGenerationException {
       
        // gather info on the thrown exceptions
        final int nThrownExceptions = javaMethod.getNThrownExceptions();
        String[] thrownExceptions = new String[nThrownExceptions];
        for (int i = 0; i < nThrownExceptions; ++i) {           
            thrownExceptions[i] = javaMethod.getThrownException(i).getJVMInternalName();
        }       
       
        //visit the method itself
        MethodVisitor mv = cv.visitMethod(javaMethod.getModifiers(), javaMethod.getMethodName(), javaMethod.getJVMMethodDescriptor(), null, thrownExceptions);
               
        // Add the code.
        mv.visitCode();
        final Map<String, JavaTypeName> methodVarToTypeMap = new HashMap<String, JavaTypeName>();
        final Map<String, Integer> methodVarToIndexMap = new HashMap<String, Integer>();
        int indexOfNextSlot = 0;
        if (!Modifier.isStatic(javaMethod.getModifiers())) {
            methodVarToTypeMap.put("this", classRep.getClassName());
            methodVarToIndexMap.put("this", Integer.valueOf(0));
            indexOfNextSlot = 1;
        }
        for (int i = 0, nParams = javaMethod.getNParams(); i < nParams; ++i) {
            JavaTypeName varType = javaMethod.getParamType(i);
            final int typeSize = getTypeSize(varType);
            String varName = javaMethod.getParamName(i);
            methodVarToTypeMap.put(varName, varType);
            methodVarToIndexMap.put(varName, Integer.valueOf(indexOfNextSlot));
            indexOfNextSlot += typeSize;
        }       
        GenerationContext context = new GenerationContext(methodVarToTypeMap, methodVarToIndexMap, indexOfNextSlot, mv, classRep.getClassName());
       
        boolean isTerminating = encodeStatement(javaMethod.getBodyCode(), context);
      
        // Add any implicit "return" statement.
        if (!isTerminating && javaMethod.getReturnType().equals(JavaTypeName.VOID)) {
            context.getMethodVisitor().visitInsn(Opcodes.RETURN);           
        }
       
        // Apply peep hole optimizations.
        //todoBI no peephole optimizations with ASM...
        //PeepHoleOptimizer.peepHoleOptimize(il);
       
        //The ClassWriter was constructed with a parameter to automatically compute max stack and max locals.
        //However, we must still call visitMaxs to mark off the end of the method. The arguments are ignored.
        mv.visitMaxs(0, 0);
       
        // End the method.
        mv.visitEnd();
    }   
   
    private static boolean encodeLocalVariableDeclaration(JavaStatement.LocalVariableDeclaration declaration, GenerationContext context) throws JavaGenerationException{
       
        MethodVisitor mv = context.getMethodVisitor();
       
        LocalVariable localVariable = declaration.getLocalVariable();
       
        // encode the instructions for the initializer if any.      
        JavaExpression initializer = declaration.getInitializer();
        if (initializer != null) {
            encodeExpr(initializer, context);           
        }
       
        int localVarIndex = context.addLocalVar(localVariable.getName(), localVariable.getTypeName());
              
        // Store the value from the initializer (if any) into the local var.
        if (initializer != null) {
          
            //encode the store instruction
            mv.visitVarInsn(getStoreOpCode(localVariable.getTypeName()), localVarIndex);           
        }
             
        return false;
    }
   
    private static boolean encodeExprStatement(JavaStatement.ExpressionStatement expressionStatement, GenerationContext context) throws JavaGenerationException {             
       
        MethodVisitor mv = context.getMethodVisitor();
       
        // get the result of evaluating the expression.  The value should not be left on the stack.
        JavaExpression expr = expressionStatement.getJavaExpression();      
       
        if (expr instanceof Assignment) {
            //assign without leaving a value on the stack (since it will be immediately popped off by the statement terminating semicolon.
            encodeAssignmentExpr((Assignment)expr, context, false);
                      
        } else {
           
            JavaTypeName exprType = encodeExpr(expr, context);
           
            //pop the resulting value off the stack
            if (!exprType.equals(JavaTypeName.VOID)) {          
                mv.visitInsn(getPopOpCode(exprType));
            }                     
        }
       
        return false;
    }
   
    /**   
     * @param type
     * @return the POP or POP2 instruction, depending on type.
     */
    private static int getPopOpCode (JavaTypeName type) {
       
       switch (type.getTag()) {           
            case JavaTypeName.BOOLEAN_TAG:                    
            case JavaTypeName.BYTE_TAG:              
            case JavaTypeName.SHORT_TAG:             
            case JavaTypeName.CHAR_TAG:                            
            case JavaTypeName.INT_TAG:
            case JavaTypeName.FLOAT_TAG:             
            case JavaTypeName.ARRAY_TAG:          
            case JavaTypeName.OBJECT_TAG: 
                return Opcodes.POP;
                       
            case JavaTypeName.LONG_TAG:           
            case JavaTypeName.DOUBLE_TAG:
                return Opcodes.POP2;
           
            case JavaTypeName.VOID_TAG:                                                                 
            default:
            {
                throw new IllegalArgumentException("invalid type: " + type);
            }
        }                              
    }     
   
    private static boolean encodeReturnStatement(JavaStatement.ReturnStatement returnStatement, GenerationContext context) throws JavaGenerationException {
        
        MethodVisitor mv = context.getMethodVisitor();
       
        JavaExpression returnExpression = returnStatement.getReturnExpression();
       
        // Label the return instruction and the instruction after the return.
        // This instruction will be excluded from relevant ranges covered in the exception table.
       
        int returnOpCode;
        if (returnExpression == null) {
           
            returnOpCode = Opcodes.RETURN;
           
        } else {
            // encode the result of evaluating the return expression.
            JavaTypeName returnType = encodeExpr(returnExpression, context);
           
            returnOpCode = getReturnOpCode(returnType);
        }
       
        // Add a label for the return instruction.
        Label returnLabel = new Label();
        mv.visitLabel(returnLabel);
       
        // Visit the return instruction.
        mv.visitInsn(returnOpCode);
       
        // Add a label following the return instruction.
        Label afterReturnLabel = new Label();
        mv.visitLabel(afterReturnLabel);

        // Add info about the generated label.
        context.addJumpReturnLabelInfo(new JumpReturnLabelInfo(returnLabel, afterReturnLabel, null));
       
        // return statements are terminating.
        return true;
    }      
   
    /**   
     * @param type
     * @return the RETURN, IRETURN,... op code, depending on type.
     */
    private static int getReturnOpCode (JavaTypeName type) {
       
        switch (type.getTag()) {
            case JavaTypeName.VOID_TAG: 
                return Opcodes.RETURN;
               
            case JavaTypeName.BOOLEAN_TAG:                    
            case JavaTypeName.BYTE_TAG:              
            case JavaTypeName.SHORT_TAG:             
            case JavaTypeName.CHAR_TAG:                            
            case JavaTypeName.INT_TAG:
                return Opcodes.IRETURN;

            case JavaTypeName.LONG_TAG: 
                return Opcodes.LRETURN;
           
            case JavaTypeName.DOUBLE_TAG:
                return Opcodes.DRETURN;
           
            case JavaTypeName.FLOAT_TAG:
                return Opcodes.FRETURN;
           
            case JavaTypeName.ARRAY_TAG:          
            case JavaTypeName.OBJECT_TAG: 
                return Opcodes.ARETURN;                      
           
            default:
            {
                throw new IllegalArgumentException("invalid type: " + type);
            }
        }                              
    }         
   
    private static boolean encodeIfThenElseStatement(JavaStatement.IfThenElseStatement iteStatement, GenerationContext context) throws JavaGenerationException {
        
        MethodVisitor mv = context.getMethodVisitor();
       
        JavaExpression conditionExpr = iteStatement.getCondition();
        JavaStatement thenStatement = iteStatement.getThenStatement();
        JavaStatement elseStatement = iteStatement.getElseStatement();
       
        if (conditionExpr instanceof JavaExpression.OperatorExpression) {
           
            //generate more efficient code in the case of if (boolean-valued-operator)...
           
            //This case exists to handle the special case where an operator occurs as the child of an if-then-else (or ternary operator)
            //conditional. For example, in the situation:
            //
            //if (x != null) {...} else {...}
            //
            // we do not want to evaluate x != null to a boolean value, push that value on the stack,
            // and then test it prior to selecting the correct branch. Rather, we can combine the evaluation
            // and jump operations into a single step.
           
            JavaExpression.OperatorExpression operatorExpr = (JavaExpression.OperatorExpression)conditionExpr;                                    
            JavaOperator operator = operatorExpr.getJavaOperator();          
                                 
            if (operator.isLogicalOp() || operator.isRelationalOp()) {
               
                Label trueContinuation = new Label();
                context.addStatementJumpLabel(trueContinuation);
               
                Label falseContinuation = new Label();
                context.addStatementJumpLabel(falseContinuation);
               
                encodeBooleanValuedOperatorHelper(operatorExpr, context, trueContinuation, falseContinuation);                   
                return encodeThenStatementElseStatement(trueContinuation, falseContinuation, thenStatement, elseStatement, context);               
            }
           
            throw new JavaGenerationException("Unrecognized boolean-valued conditional operator " + operator.getSymbol() + ".");                      
        }
                          
        //encode the condition. It will be boolean valued.
        encodeExpr(iteStatement.getCondition(), context);
       
        //if false, jump to falseContinuation
        Label falseContinuation = new Label();
        mv.visitJumpInsn(Opcodes.IFEQ, falseContinuation)
       
        return encodeThenStatementElseStatement(null, falseContinuation, thenStatement, elseStatement, context);         
    }
   
    private static boolean encodeThenStatementElseStatement(Label trueContinuation, Label falseContinuation, JavaStatement thenStatement, JavaStatement elseStatement, GenerationContext context) throws JavaGenerationException {
       
        MethodVisitor mv = context.getMethodVisitor();
       
        if (trueContinuation != null) {
            mv.visitLabel(trueContinuation);
        }
       
        //in general the "then" part will have its own inner scope, but we optimize this out in the case of ExpressionStatements which
        //can't introduce new variables.
        GenerationContext thenContext = (thenStatement instanceof JavaStatement.ExpressionStatement) ? context : new GenerationContext(context);
       
        boolean thenIsTerminating = encodeStatement(thenStatement, thenContext);
        context.addJumpReturnLabelInfo(thenContext);
               
        if (elseStatement.emptyStatement()) {
            //don't bother to encode the goto skipping over the else part if there is not else part.
           
            mv.visitLabel(falseContinuation);
           
            //the if-then-else is not terminating, because the else part, which is just {}, is not terminating
            return false;
        }
       
        Label label2 = null;
        if (!thenIsTerminating) {              
            label2 = new Label();
            context.addStatementJumpLabel(label2);

            mv.visitJumpInsn(Opcodes.GOTO, label2);
        }
       
        mv.visitLabel(falseContinuation);
       
        //in general the "else" part will have its own inner scope, but we optimize this out in the case of ExpressionStatements which
        //can't introduce new variables.
        GenerationContext elseContext = (elseStatement instanceof JavaStatement.ExpressionStatement) ? context : new GenerationContext(context);       
        boolean elseIsTerminating = encodeStatement(elseStatement, elseContext);
        context.addJumpReturnLabelInfo(elseContext);
       
        if (!thenIsTerminating) {
            mv.visitLabel(label2);
        }
       
        return thenIsTerminating && elseIsTerminating;
    }  
   
    private static boolean encodeSwitchStatement(JavaStatement.SwitchStatement switchStatement, GenerationContext context) throws JavaGenerationException {      
       
        MethodVisitor mv = context.getMethodVisitor();
       
               
        // Append the instructions to evaluate the condition, leaving the result on the operand stack.
        JavaExpression condition = switchStatement.getCondition();
        encodeExpr(condition, context);               
       
        // Follow (what seems to be) javac's rule for deciding between a lookup switch and a table switch:
        //      Use a table switch if allowed by maxTableSize, where
        //          maxTableSize = (# cases - 2) * 5  , (# cases) doesn't include the default.
        //
        // eg. if there are 2 case alternatives (+ default):
        //          maxTableSize = ((2 - 2) * 5) = 0, so always use a lookup switch.
        //     if there are cases {1, 2, 3, 4, 15}:
        //          maxTableSize = ((5 - 2) * 5) = 15.  A table would contain 15 entries {1, 2, .., 15}, so use a table switch.
        //     if there are cases {1, 2, 3, 4, 16}:
        //          A table would contain 16 entries {1, 2, .., 16}, so use a lookup switch.
        List<IntCaseGroup> caseGroups = switchStatement.getCaseGroups();
        int nTotalCases = 0;
       
        // Calculate the first and last values.
        int firstValue = Integer.MAX_VALUE;
        int lastValue = Integer.MIN_VALUE;
        for (int caseN = 0; caseN < caseGroups.size(); ++caseN) {
            SwitchStatement.IntCaseGroup switchCaseGroup = caseGroups.get(caseN);
            for (int i = 0, nCaseLabels = switchCaseGroup.getNCaseLabels(); i < nCaseLabels; i++) {
                nTotalCases++;
                int caseLabel = switchCaseGroup.getNthCaseLabel(i);
                if (firstValue > caseLabel) {
                    firstValue = caseLabel;
                }
               
                if (lastValue < caseLabel) {
                    lastValue = caseLabel;
                }
            }
        }
       
        if (nTotalCases == 0) {
            // If the switch statement contains no cases, javac's behaviour is to generate a pop instruction
            // to pop the condition value off the operand stack.
            mv.visitInsn(Opcodes.POP);
           
        } else {
           
            int nSpannedCases = lastValue - firstValue + 1;

            int maxTableSize = (nTotalCases - 2) * 5;
            boolean useTableSwitch = !(nSpannedCases > maxTableSize);

            // Create the default label and declare the other case labels.
            Label defaultLabel = new Label();

            // (SwitchStatement.IntCaseGroup->Label) A map from case group to label.
            Map<IntCaseGroup, Label> caseGroupToLabelMap = new HashMap<IntCaseGroup, Label>();

            if (!useTableSwitch) {
                // A lookup switch.  Each case match gets its own label.

                // Map from case label to label, sorted by case label.
                SortedMap<Integer, Label> caseLabelToLabelMap = new TreeMap<Integer, Label>();

                // A count of the number of case labels.
                int nCaseLabels = 0;

                // Create the labels, and populate the map.
                for (int caseN = 0; caseN < caseGroups.size(); ++caseN) {
                    SwitchStatement.IntCaseGroup switchCaseGroup = caseGroups.get(caseN);

                    // The label for this case group.
                    Label label = new Label();
                    caseGroupToLabelMap.put(switchCaseGroup, label);

                    for (int i = 0, nCaseGroupLabels = switchCaseGroup.getNCaseLabels(); i < nCaseGroupLabels; i++) {
                        int caseLabel = switchCaseGroup.getNthCaseLabel(i);
                        caseLabelToLabelMap.put(Integer.valueOf(caseLabel), label);
                        nCaseLabels++;
                    }
                }

                // Create the array for case matches and the corresponding labels.
                int[] caseMatches = new int[nCaseLabels];
                Label[] labels = new Label[nCaseLabels];

                int index = 0;
                for (final Map.Entry<Integer, Label> entry : caseLabelToLabelMap.entrySet()) {                 
                    Integer caseLabelInteger = entry.getKey();
                    caseMatches[index] = caseLabelInteger.intValue();
                    labels[index] = entry.getValue();
                    index++;
                }

                // Visit the lookup switch instruction.
                mv.visitLookupSwitchInsn(defaultLabel, caseMatches, labels);

            } else {
                // A table switch
                // The cases which aren't given should be set to the default case.

                Label[] labels = new Label[nSpannedCases];

                // Initially set all elements to the default label, if they won't all be set to something else later.
                if (nSpannedCases != nTotalCases) {
                    Arrays.fill(labels, defaultLabel);
                }

                // For each switch case provided, create a new label.
                for (int caseN = 0; caseN < caseGroups.size(); ++caseN) {
                    SwitchStatement.IntCaseGroup switchCaseGroup = caseGroups.get(caseN);

                    // The label for this case group.
                    Label label = new Label();
                    caseGroupToLabelMap.put(switchCaseGroup, label);

                    for (int i = 0, nCaseGroupLabels = switchCaseGroup.getNCaseLabels(); i < nCaseGroupLabels; i++) {
                        int caseLabel = switchCaseGroup.getNthCaseLabel(i);
                        int labelArrayIndex = caseLabel - firstValue;

                        labels[labelArrayIndex] = label;
                    }
                }

                // Visit the table switch instruction.
                mv.visitTableSwitchInsn(firstValue, lastValue, defaultLabel, labels);           
            }

            // Iterate over the cases.      
            for (int caseN = 0; caseN < caseGroups.size(); ++caseN) {
                SwitchStatement.IntCaseGroup switchCase = caseGroups.get(caseN);
                JavaStatement caseStatementGroup = switchCase.getStatement();

                // Add a local scope..
                GenerationContext innerContext = new GenerationContext(context);

                // Get the label to visit..
                Label labelToVisit = caseGroupToLabelMap.get(switchCase);

                //mark the location of the code for the i'th case.
                mv.visitLabel(labelToVisit);

                // get the instructions for the case statement.
                boolean caseStatementIsTerminating = encodeStatement(caseStatementGroup, innerContext);
                context.addJumpReturnLabelInfo(innerContext);

                // We don't (yet) handle non-terminating code in a case block.
                if (!caseStatementIsTerminating) {
                    throw new JavaGenerationException("Can't (yet) handle non-terminating code in a case block.");
                }                      
            }

            JavaStatement defaultStatementGroup = switchStatement.getDefaultStatement();
            //mark the location of the default statements (even if there are none, this means just progress to the next instruction..)
            mv.visitLabel(defaultLabel);
            if (defaultStatementGroup != null) {
                // Add a local scope..
                GenerationContext innerContext = new GenerationContext(context);

                boolean defaultStatementIsTerminating = encodeStatement(defaultStatementGroup, innerContext);
                context.addJumpReturnLabelInfo(innerContext);

                // We don't (yet) handle non-terminating code in a case block.
                if (!defaultStatementIsTerminating) {
                    throw new JavaGenerationException("Can't (yet) handle non-terminating code in a case block.");
                }                    
            }
        }
              
        // Note: we should add GOTO instructions to each case block to jump to after the switch statement if necessary.
        //   But for now it's not necessary since all cases are terminated.
        boolean isTerminating = true;
       
        return isTerminating;
    }
   
    private static boolean encodeBlockStatement(JavaStatement.Block block, GenerationContext context) throws JavaGenerationException {

        List<JavaExceptionHandler> exceptionHandlers = block.getExceptionHandlers();
       
        if (!exceptionHandlers.isEmpty()) {
            return encodeTryCatchStatement(block, context);
        }
       
        //do not spawn a new context for the block.
        //todoBI it would probably be better to spawn a new context for a block to limit the scoping of introduced variables.
        //however, then one would not use a block in place of a list of statements...                    
        boolean isTerminating = false// starts out false (for the case with no statements..)
       
        // Append the instructions comprising the block's component statements.
        int nStatements = block.getNBlockStatements();
        for (int i = 0; i < nStatements; i++) {
            JavaStatement blockStatement = block.getNthBlockStatement(i);
           
            // skip comments.
            if (blockStatement instanceof Comment) {
                continue;
            }
               
            // is terminating if the last statement is terminating.
            isTerminating = encodeStatement(blockStatement, context);
        }       
       
        return isTerminating;
    }
   
    private static boolean encodeTryCatchStatement(JavaStatement.Block block, GenerationContext context) throws JavaGenerationException {
     
        MethodVisitor mv = context.getMethodVisitor();
       
        List<JavaExceptionHandler> exceptionHandlers = block.getExceptionHandlers();
       
        if (exceptionHandlers.isEmpty()) {
            throw new IllegalStateException();
        }
       
        // Spawn a child context for the try block.
        GenerationContext tryBlockContext = new GenerationContext(context);
       
        boolean isTerminating = false// starts out false (for the case with no statements..)
       
        Label tryStartLabel = new Label();
        mv.visitLabel(tryStartLabel);
       
        // Append the instructions comprising the block's component statements.
        int nStatements = block.getNBlockStatements();
        for (int i = 0; i < nStatements; i++) {
            JavaStatement blockStatement = block.getNthBlockStatement(i);
           
            // skip comments.
            if (blockStatement instanceof Comment) {
                continue;
            }
               
            // is terminating if the last statement is terminating.
            isTerminating = encodeStatement(blockStatement, tryBlockContext);
            context.addJumpReturnLabelInfo(tryBlockContext);
        }
       
        // Add an instruction to jump to after the exception handlers, if the statement block isn't terminating.
        Label tryCatchEndLabel = new Label();
        context.addStatementJumpLabel(tryCatchEndLabel);
        if (!isTerminating) {
            mv.visitJumpInsn(Opcodes.GOTO, tryCatchEndLabel);           
        }              
       
        Label tryEndLabel = new Label();
        mv.visitLabel(tryEndLabel);              
       
        // Add exception handlers as necessary.     
       
        // Now set handlers to handle any exceptions.
        for (int i = 0, nExceptionHandlers = exceptionHandlers.size(); i < nExceptionHandlers; ++i) {
           
            JavaExceptionHandler eh = exceptionHandlers.get(i);
           
            JavaTypeName exceptionType = JavaTypeName.make(eh.getExceptionClass())// exception class is a non-array reference type. 
           
            //encode the start of the catch block.
            Label catchLabel = new Label();
            mv.visitLabel(catchLabel);
           
            // Create a child context for the catch block.
            GenerationContext catchContext = new GenerationContext(context);
           
            // The thrown exception object will be the only item on the stack. 
            // Store it into a new local variable.
            String exceptionVarName = eh.getExceptionVarName();
            int exceptionVarIndex = catchContext.addLocalVar(exceptionVarName, exceptionType);
            mv.visitVarInsn(Opcodes.ASTORE, exceptionVarIndex);
          
            boolean catchIsTerminating = encodeStatement(eh.getHandlerCode(), catchContext);
            context.addJumpReturnLabelInfo(catchContext);
           
            if (!catchIsTerminating) {
                mv.visitJumpInsn(Opcodes.GOTO, tryCatchEndLabel);
            }          
           
            // In the end, we're only terminating if all the catch blocks are terminating.
            isTerminating &= catchIsTerminating;
           
            //encode the try/catch block. This can be done in any order, any time after all labels passed as arguments have been visited,
            //  between visitCode() and visitMaxs().
            encodeTryCatchBlock(tryStartLabel, tryEndLabel, catchLabel, exceptionType, tryBlockContext);
        }
       
        //mark the end of the whole try/catch code
        mv.visitLabel(tryCatchEndLabel);             
               
        return isTerminating;
    }
   
    /**
     * Generate the exception table entry(/entries) for a try/catch block.
     * This can be done in any order, any time after all labels passed as arguments have been visited.
     *
     * Jump and return instructions are excluded from any exception handling blocks if they cause the next executed instruction
     *   to escape the bounds of the try statement.
     *
     * eg. if there is a return statement in a try block in a try-catch statement, the corresponding return instruction will
     *     be excluded from the exception table entry.
     *     if there is a jump (eg. goto) statement in a try block of a try-catch statement, the jump instruction will be
     *     excluded from the exception table entry if the jump destination lies outside the try statement.
     *
     * Exclusion of the instruction will cause the exception table entry to be split or reduced:
     *   one entry will include the part before the excluded instruction,
     *   the other will include the part after it, if it is not the instruction at the end of the block.
     *
     *
     * An interesting case to consider is a try/catch nested with respect to a while(true) statement, with a continue statement in the body:
     *
     *     try {
     *         while (true) {
     *             (statements)
     *             continue;
     *             (more statements)
     *         }
     *     } catch (Exception e) {
     *     }
     *
     *   versus
     *
     *     while (true) {
     *         try {
     *             (statements)
     *             continue;
     *             (more statements)
     *         } catch (Exception e) {
     *         }
     *     }
     *
     * In both cases, the continue is encoded as a goto instruction whose destination is the first instruction in (statements).
     * In the first case, the target "while" statement is nested within the try block, therefore the goto is not excluded.
     * In the second case, the target "while" statement is outside of the try block, therefore the goto is excluded.
     * ie. even though the instructions for the try body are the same, and the instructions for (statements) lies both within
     *   the while block and the try block, a jump to the first statement may or may not be viewed as escaping the try block,
     *   depending on which case was compiled.
     *
     *
     * @param tryStartLabel
     * @param tryEndLabel
     * @param catchLabel
     * @param exceptionType
     * @param tryBlockContext
     */
    private static void encodeTryCatchBlock(Label tryStartLabel, Label tryEndLabel, Label catchLabel, JavaTypeName exceptionType,
                                            GenerationContext tryBlockContext) {
       
        MethodVisitor mv = tryBlockContext.getMethodVisitor();
       
        // The start label for the next table entry to be visited.
        Label exceptionEntryStartLabel = tryStartLabel;
       
        for (final JumpReturnLabelInfo jumpReturnLabelInfo : tryBlockContext.getJumpReturnLabelInfoList()) {           
           
            // Only exclude the jump/return instruction (ie. split the exception block)
            //   if the destination label is outside the scope of the try block
            Label destinationLabel = jumpReturnLabelInfo.getDestinationLabel();
            if (destinationLabel != null && tryBlockContext.containsStatementJumpLabel(destinationLabel)) {
                continue;
            }
           
            // The next entry goes from the start label (inclusive) to the return or continue label (exclusive).
            Label returnContinueLabel = jumpReturnLabelInfo.getInstructionLabel();
            if (exceptionEntryStartLabel.getOffset() != returnContinueLabel.getOffset()) {
                // When there are consecutive instructions which jump/return outside the scope of the try block,
                // after the first iteration of the loop we will have the exception entry start label and the
                // return continue label both pointing to the same offset (ie. the offset of the nth consecutive
                // jump/return instruction on iteration n).
                mv.visitTryCatchBlock(exceptionEntryStartLabel, returnContinueLabel, catchLabel, exceptionType.getJVMInternalName());
            }
           
            // Set the start label for the next table entry to be visited.
            exceptionEntryStartLabel = jumpReturnLabelInfo.getAfterInstructionLabel();    // afterReturnLabel
        }
       
        // Check for the case where the last return instruction comes at the end of the try block.
        if (exceptionEntryStartLabel.getOffset() != tryEndLabel.getOffset()) {
            // Add the final entry, which ends at the tryEndLabel.
            mv.visitTryCatchBlock(exceptionEntryStartLabel, tryEndLabel, catchLabel, exceptionType.getJVMInternalName());
        }
    }
   
    private static boolean encodeThrowStatement(JavaStatement.ThrowStatement throwStatement, GenerationContext context) throws JavaGenerationException {
       
        MethodVisitor mv = context.getMethodVisitor();
       
        // get the result of evaluating the thrown expression.
        encodeExpr(throwStatement.getThrownExpression(), context);
              
        // throw the result.
        mv.visitInsn(Opcodes.ATHROW);
       
        // throw statements are terminating.      
        return true;
    }
   
    private static boolean encodeUnconditionalLoop(JavaStatement.UnconditionalLoop ul, GenerationContext context) throws JavaGenerationException {
       
        //implemented as:
        //label label1:
        //{ body statements}
        //goto label1
       
        MethodVisitor mv = context.getMethodVisitor();
        Label label1 = new Label();
        mv.visitLabel(label1);
       
        context.addStatementJumpLabel(ul.getLabel(), label1);
       
        //the body defines a new scope.
        GenerationContext bodyContext = new GenerationContext(context);
       
        boolean bodyIsTerminating = encodeStatement(ul.getBody(), bodyContext);
        context.addJumpReturnLabelInfo(bodyContext);
       
        if (!bodyIsTerminating) {
            mv.visitJumpInsn(Opcodes.GOTO, label1);
        }
             
        return true;
    }
   
    /**
     * Generate the bytecode for an assert statement.
     * Basically an assert statement like: assert conditionExpr : onFailureExpr;
     * is encoded as:
     * if (!$assertionsDisabled) {
     *     if (!conditionExpr) {
     *         throw new AssertionError (onFailureExpr);
     *     }
     * }
     * @param assertStatement
     * @param context
     * @return true if the statement is terminating.
     * @throws JavaGenerationException
     */
    private static boolean encodeAssertStatement (JavaStatement.AssertStatement assertStatement, GenerationContext context) throws JavaGenerationException {
       
        // Build up a ClassInstanceCreationExpression for the AssertionError instance.
        JavaExpression createAssertionError = null;
        if (assertStatement.getOnFailureExpr() != null) {
            JavaTypeName onFailureExprType = assertStatement.getOnFailureExprType();
            if (onFailureExprType instanceof JavaTypeName.Reference) {
                onFailureExprType = JavaTypeName.OBJECT;
            }
            createAssertionError =
                new ClassInstanceCreationExpression (JavaTypeName.ASSERTION_ERROR,
                                                     assertStatement.getOnFailureExpr(),
                                                     onFailureExprType);
        } else {
            createAssertionError =
                new ClassInstanceCreationExpression(JavaTypeName.ASSERTION_ERROR);
        }
        JavaStatement.ThrowStatement throwAssertError =
            new ThrowStatement(createAssertionError);
       
        // if (!conditionExpr) {throw new AssertionError();}
        IfThenElseStatement ifThen =
            new IfThenElseStatement(new OperatorExpression.Unary(JavaOperator.LOGICAL_NEGATE, assertStatement.getConditionExpr()), throwAssertError);
       
        // if (!$assertionsDisabled) {if (!conditionExpr) {throw new AssertionError;}}
        JavaExpression assertionsDisabledField = new JavaExpression.JavaField.Static(context.classTypeName, "$assertionsDisabled", JavaTypeName.BOOLEAN);
        JavaExpression checkAssertionEnabled = new JavaExpression.OperatorExpression.Unary(JavaOperator.LOGICAL_NEGATE, assertionsDisabledField);
        ifThen = new IfThenElseStatement (checkAssertionEnabled, ifThen);
       
        return encodeIfThenElseStatement(ifThen, context);
    }
   
    /**
     * Encode a method invocation that is wrapped in a synchronized block.
     *   See Sun bug id #4414101 for a discussion of this generated code.
     *
     * @param smi - the SynchronizedMethodInvocation object.
     * @param context - the context of the code generation.
     * @return - true if the SynchronizedMethodInvocation is terminating.
     * @throws JavaGenerationException
     */
    private static boolean encodeSynchronizedMethodInvocation (JavaStatement.SynchronizedMethodInvocation smi, GenerationContext context) throws JavaGenerationException {

        MethodVisitor mv = context.getMethodVisitor();

        // Get the JavaExpression for the object to synchronize on and generate the bytecode.
        JavaExpression objectToSynchOn = smi.getSynchronizingObject();
        encodeExpr(objectToSynchOn, context);

        // The object to synchronize on is now on the stack.  Duplicate it,
        // move one to storage as a local variable, and to MONITORENTER on the other.
        mv.visitInsn(Opcodes.DUP);

        int mutexIndex = context.addLocalVar("$mutex", JavaTypeName.OBJECT);
        mv.visitVarInsn(Opcodes.ASTORE, mutexIndex);
       
        mv.visitInsn(Opcodes.MONITORENTER);
       
        // We need to wrap the method invocation in a try/catch block so
        // the monitor can be exited properly if the method invocation throws
        // an exception.
        Label tryCatchStart = new Label();
        mv.visitLabel(tryCatchStart);

        // Note: if this is generalized to handle any synchronized statement (for example, a synchronized block),
        //   then the scope of entries in the exception table here will have to be modified to exclude return instructions.
        //   See encodeTryCatchStatement() for how to do this.
        //   Here, the only statement in the try block is a single expressionStatement, which has no return instructions,
        //     so we don't have to worry about handling this case.
       
        // Get the method invocation and generate the corresponding bytecode.
        MethodInvocation methodInvocation = smi.getMethodInvocation();
        encodeExpr (methodInvocation, context);
    
        // Load the mutex object back onto the stack and do MonitorExit.
        mv.visitVarInsn(Opcodes.ALOAD, mutexIndex);
        mv.visitInsn(Opcodes.MONITOREXIT);
       
        // Label the end of the try block around the method invocation.
        Label tryEnd = new Label();
        mv.visitLabel(tryEnd);

        // At this point we want to code an instruction to jump past the exception handling
        // code.
        Label tryCatchEnd = new Label();
        mv.visitJumpInsn(Opcodes.GOTO, tryCatchEnd);
       
        // Label the start of the exception handling code.
        Label catchStart = new Label();
        mv.visitLabel(catchStart);
       
        // The exception is on the stack.  Store it as a local.
        int exceptionIndex = context.addLocalVar("exception", JavaTypeName.OBJECT);       
        mv.visitVarInsn(Opcodes.ASTORE, exceptionIndex);
       
        // Retrieve the mutex object and do MONITOREXIT.
        mv.visitVarInsn(Opcodes.ALOAD, mutexIndex);
        mv.visitInsn(Opcodes.MONITOREXIT);
       
        // Label the end of the exception handling code that deals with the monitor.
        Label exceptionMonitorExitEnd = new Label();
        mv.visitLabel(exceptionMonitorExitEnd);
       
        // Retrieve the exception and throw it.
        mv.visitVarInsn(Opcodes.ALOAD, exceptionIndex);
        mv.visitInsn(Opcodes.ATHROW);
       
        // Vist the label to mark the end of the try/catch.
        mv.visitLabel(tryCatchEnd);
       
        // Set up the try/catch block to catch exceptions thrown by the method invocation
        // and handle exiting the monitor.
        mv.visitTryCatchBlock(tryCatchStart, tryEnd, catchStart, null);
       
        // Set up a try catch block so that if an exception is thrown by trying to exit
        // the monitor in the case of an exception from the method invocation we will keep trying to
        // exit the monitor.
        mv.visitTryCatchBlock(catchStart, exceptionMonitorExitEnd, catchStart, null);
       
        return false;
    }
   
    private static boolean encodeLabelledContinue(JavaStatement.LabelledContinue lc, GenerationContext context) {
       
        MethodVisitor mv = context.getMethodVisitor();
        Label label = context.getNamedStatementJumpLabel(lc.getLabel());
        if (label == null) {
            throw new IllegalStateException("label to continue to cannot be null.");
        }
       
        // Label the goto instruction and the instruction after the goto.
        // This instruction will be excluded from relevant ranges covered in the exception table.
       
        // Add a label for the goto instruction.
        Label gotoLabel = new Label();
        mv.visitLabel(gotoLabel);
       
        // Visit the goto instruction.
        mv.visitJumpInsn(Opcodes.GOTO, label);
       
        // Add a label following the goto instruction.
        Label afterGotoLabel = new Label();
        mv.visitLabel(afterGotoLabel);
       
        context.addJumpReturnLabelInfo(new JumpReturnLabelInfo(gotoLabel, afterGotoLabel, label));
       
        return true;
    }
   
    /**
     * Get the Java code for a given java statement.
     * @param statement the java statement for which to generate source.
     * @param context the generation context.
     * @return boolean whether the statement is terminating.
     * @throws JavaGenerationException
     */
    private static boolean encodeStatement(JavaStatement statement, GenerationContext context) throws JavaGenerationException {
       
        if (statement instanceof LocalVariableDeclaration) {
            return encodeLocalVariableDeclaration((LocalVariableDeclaration)statement, context);           

        } else if (statement instanceof ExpressionStatement) {
            return encodeExprStatement((ExpressionStatement)statement, context);          

        } else if (statement instanceof ReturnStatement) {
            return encodeReturnStatement((ReturnStatement)statement, context);         
               
        } else if (statement instanceof IfThenElseStatement) {           
            return encodeIfThenElseStatement((IfThenElseStatement)statement, context);           

        } else if (statement instanceof SwitchStatement) {           
            return encodeSwitchStatement((SwitchStatement)statement, context);
                          
        } else if (statement instanceof Block) {
            return encodeBlockStatement((Block)statement, context);          
               
        } else if (statement instanceof ThrowStatement) {
            return encodeThrowStatement((ThrowStatement)statement, context);
           
        } else if (statement instanceof UnconditionalLoop) {
            return encodeUnconditionalLoop((UnconditionalLoop)statement, context);
           
        } else if (statement instanceof LabelledContinue) {
            return encodeLabelledContinue((LabelledContinue)statement, context);
          
        } else if (statement instanceof SynchronizedMethodInvocation) {
            return encodeSynchronizedMethodInvocation((SynchronizedMethodInvocation)statement, context);
           
        } else if (statement instanceof Comment) {
            throw new JavaGenerationException("Attempt to generate bytecode for a comment.");
           
        } else if (statement instanceof AssertStatement) {
            return encodeAssertStatement ((AssertStatement) statement, context);
        } else {       
            throw new JavaGenerationException("Unrecognized statement type: " + statement.getClass());
        }
    }
  
   
    /**
     * Creates instructions to evaluate the expression, and push the result onto the operand stack.
     * @param expression the java expression
     * @param context  
     * @return JavaTypeName the type of the result on the operand stack.
     * @throws JavaGenerationException
     */
    private static JavaTypeName encodeExpr(JavaExpression expression, GenerationContext context) throws JavaGenerationException {
                    
        if (expression instanceof PlaceHolder) {
            return encodeExpr (((PlaceHolder)expression).getActualExpression(), context);
           
        } else if (expression instanceof MethodInvocation) {
            return encodeMethodInvocationExpr((MethodInvocation)expression, context);           

        } else if (expression instanceof OperatorExpression) {
            return encodeOperatorExpr((OperatorExpression)expression, context);

        } else if (expression instanceof Assignment) {
            return encodeAssignmentExpr((Assignment)expression, context, true);          

        } else if (expression instanceof LiteralWrapper) {
            return encodeLiteralExpr((LiteralWrapper)expression, context);
           
        } else if (expression instanceof ArrayCreationExpression) {
            return encodeArrayCreationExpr((ArrayCreationExpression)expression, context);

        } else if (expression instanceof ClassInstanceCreationExpression) {               
            return encodeClassInstanceCreationExpr((ClassInstanceCreationExpression)expression, context);

        } else if (expression instanceof JavaField) {
            return encodeJavaFieldExpr((JavaField)expression, context);
           
        } else if (expression instanceof MethodVariable) {
            return encodeMethodVariableExpr((MethodVariable)expression, context);
           
        } else if (expression instanceof LocalVariable) {
            return encodeLocalVariableExpr((LocalVariable)expression, context);
           
        } else if (expression instanceof ArrayAccess) {
            return encodeArrayAccessExpr((ArrayAccess)expression, context);
           
        } else if (expression instanceof ArrayLength) {
            return encodeArrayLengthExpr((ArrayLength)expression, context);
           
        } else if (expression instanceof LocalName) {
            return encodeLocalNameExpr((LocalName)expression, context);
               
        } else if (expression instanceof InstanceOf) {               
            return encodeInstanceOfExpr((InstanceOf)expression, context);

        } else if (expression instanceof CastExpression) {
            return encodeCastExpr((CastExpression)expression, context);

        } else if (expression instanceof ClassLiteral) {               
            return encodeClassLiteralExpr((ClassLiteral)expression, context);

        } else {
            throw new JavaGenerationException("Unrecognized expression type: " + expression.getClass());
        }              
    }
   
    /**
     * Creates the code that references a Java field within an expression.
     * @param javaField the java field
     * @param context  
     * @return JavaTypeName the type of the javaField argument.
     * @throws JavaGenerationException
     */
    private static JavaTypeName encodeJavaFieldExpr(JavaField javaField, GenerationContext context) throws JavaGenerationException {
       
        if (javaField instanceof JavaField.This) {           
            //the special 'this' field.
            return encodeThis(context);                   
        }
       
        MethodVisitor mv = context.getMethodVisitor();
       
        if (javaField instanceof JavaField.Instance) {
           
            JavaField.Instance javaInstanceField = (JavaField.Instance)javaField;
            JavaExpression instance = javaInstanceField.getInstance();
            JavaTypeName instanceType;
            if (instance == null) {
                //use 'this' as the instance expression, so if the field is 'foo', then it is 'this.foo'.
                instanceType = encodeThis(context);              
               
            } else {
                instanceType = encodeExpr(instance, context);              
            }
           
            JavaTypeName fieldType = javaField.getFieldType();
                                  
            mv.visitFieldInsn(Opcodes.GETFIELD, instanceType.getJVMInternalName(), javaField.getFieldName(), fieldType.getJVMDescriptor());
           
            return fieldType;                       
        }
       
        if (javaField instanceof JavaField.Static) {
           
           JavaField.Static javaStaticField = (JavaField.Static)javaField;
           JavaTypeName fieldType = javaField.getFieldType();
          
           mv.visitFieldInsn(Opcodes.GETSTATIC, javaStaticField.getInvocationClass().getJVMInternalName(), javaField.getFieldName(), fieldType.getJVMDescriptor());
          
           return fieldType;
        }
       
        throw new IllegalStateException();       
    }       
     
    /**
     * Pushes the value of the local name (which is either a local variable, method variable,
     * or reference to a field using the this object.
     * @param localName
     * @param context
     * @return JavaTypeName the type of the result on the operand stack.
     * @throws JavaGenerationException
     */
    private static JavaTypeName encodeLocalNameExpr(JavaExpression.LocalName localName, GenerationContext context) throws JavaGenerationException {
       
        // Determine what type of creature this name represents.
        //   Note that because of the order of lookups, this takes scoping into account.
        String varName = localName.getName();
        if (context.getLocalVarIndex(varName) != -1) {
            return encodeLocalVariableExpr(new LocalVariable(varName, localName.getTypeName()), context);

        } else if (context.getMethodVarIndex(varName) != -1) {
            return encodeMethodVariableExpr(new MethodVariable(varName), context);

        } else {
            return encodeJavaFieldExpr(new JavaField.Instance(null, varName, localName.getTypeName()), context);
        }             
    }

    /**
     * Encodes instructions to push the value of the local variable onto the operand stack.
     * @param variable
     * @param context
     * @return JavaTypeName the type of the result on the operand stack.
     */
    private static JavaTypeName encodeLocalVariableExpr(JavaExpression.LocalVariable variable, GenerationContext context) {
       
        MethodVisitor mv = context.getMethodVisitor();
       
        //get the local variable index from the context.      
        int localVarIndex = context.getLocalVarIndex(variable.getName());
       
        JavaTypeName varType = variable.getTypeName();
       
        mv.visitVarInsn(getLoadOpCode(varType), localVarIndex);
       
        return varType;
    }
   
    private static int getLoadOpCode(JavaTypeName varType) {
       
        //ASM automatically handles replacing ILOAD 0, ILOAD 1, ILOAD 2 and ILOAD 3 by the special
        //0 argument op codes ILOAD_0, ILOAD_1, ILOAD_2, and ILOAD_3 and similarly for the other
        //types.
       
        switch (varType.getTag()) {
       
            case JavaTypeName.VOID_TAG:           
                throw new IllegalArgumentException("Cannot load a local variable of void type.");
           
            case JavaTypeName.BOOLEAN_TAG:     
            case JavaTypeName.BYTE_TAG:              
            case JavaTypeName.SHORT_TAG:             
            case JavaTypeName.CHAR_TAG:                            
            case JavaTypeName.INT_TAG:
                return Opcodes.ILOAD;         
           
            case JavaTypeName.LONG_TAG:
                return Opcodes.LLOAD;
           
            case JavaTypeName.DOUBLE_TAG:
                return Opcodes.DLOAD;
           
            case JavaTypeName.FLOAT_TAG:
                return Opcodes.FLOAD;
           
            case JavaTypeName.ARRAY_TAG:          
            case JavaTypeName.OBJECT_TAG: 
                return Opcodes.ALOAD;
           
            default:
            {
                throw new IllegalArgumentException("Cannot load a local variable of type " + varType);
            }
        }                              
    }
   
    private static int getStoreOpCode(JavaTypeName varType) {
       
        //ASM automatically handles replacing ISTORE 0, ISTORE 1, ISTORE 2 and ISTORE 3 by the special
        //0 argument op codes ISTORE_0, ISTORE_1, ISTORE_2, and ISTORE_3 and similarly for the other
        //types.
       
        switch (varType.getTag()) {
       
            case JavaTypeName.VOID_TAG:           
                throw new IllegalArgumentException("Cannot load a local variable of void type.");
           
            case JavaTypeName.BOOLEAN_TAG:     
            case JavaTypeName.BYTE_TAG:              
            case JavaTypeName.SHORT_TAG:             
            case JavaTypeName.CHAR_TAG:                            
            case JavaTypeName.INT_TAG:
                return Opcodes.ISTORE;         
           
            case JavaTypeName.LONG_TAG:
                return Opcodes.LSTORE;
           
            case JavaTypeName.DOUBLE_TAG:
                return Opcodes.DSTORE;
           
            case JavaTypeName.FLOAT_TAG:
                return Opcodes.FSTORE;
           
            case JavaTypeName.ARRAY_TAG:          
            case JavaTypeName.OBJECT_TAG: 
                return Opcodes.ASTORE;
           
            default:
            {
                throw new IllegalArgumentException("Cannot load a local variable of type " + varType);
            }
        }                              
    }   

    /**
     * @param variable
     * @param context
     * @return the type of the result on the operand stack.
     */
    private static JavaTypeName encodeMethodVariableExpr(JavaExpression.MethodVariable variable, GenerationContext context) {
       
        MethodVisitor mv = context.getMethodVisitor();
        String varName = variable.getName();
       
        //get the local variable index from the context.      
        int methodVarIndex = context.getMethodVarIndex(varName);
       
        JavaTypeName varType = context.getMethodVarType(varName);
       
        mv.visitVarInsn(getLoadOpCode(varType), methodVarIndex);
       
        return varType; 
    }
      

    /**
     * @param assignment
     * @param context
     * @param retainValue  whether to leave on the stack the result of evaluating the expression. This is an optimization.
     *    The normal case for an assignment expression is with retainValue = true. However, often assignments are used in
     *    statement form in which case the resulting value is immediately popped off the stack. Hence there is no point to
     *    push it onto the stack in the first place.
     * @return the type of the result on the operand stack (assuming retainValue is true).
     * @throws JavaGenerationException
     */
    private static JavaTypeName encodeAssignmentExpr(JavaExpression.Assignment assignment, GenerationContext context, boolean retainValue) throws JavaGenerationException {      

        MethodVisitor mv = context.getMethodVisitor();
       
        JavaExpression.Nameable lhs = assignment.getLeftHandSide();
                                   
        if (lhs instanceof LocalName) {
            // Determine what type of creature this name represents.
            //   Note that because of the order of lookups, this takes scoping into account.
            LocalName localName = (LocalName)lhs;
            String varName = localName.getName();
            if (context.getLocalVarIndex(varName) != -1) {
                lhs = new LocalVariable(varName, localName.getTypeName());
            } else if (context.getMethodVarIndex(varName) != -1) {
                lhs = new MethodVariable(varName);
            } else {
                lhs = new JavaField.Instance(null, varName, localName.getTypeName());
            }
        }
       
        // assign the value
        if (lhs instanceof JavaField.Instance) {                      
                       
            JavaField.Instance javaInstanceField = (JavaField.Instance)lhs;           
                 
            //push the reference to the Java field itself. We can't just call encodeJavaField because we need the type of the instance object.
            JavaExpression instance = javaInstanceField.getInstance();
            JavaTypeName instanceType;
            if (instance == null) {
                //use 'this' as the instance expression, so if the field is 'foo', then it is 'this.foo'.
                instanceType = encodeThis(context);              
               
            } else {
                instanceType = encodeExpr(instance, context);              
            }
                      
            //push the rhs expression
            // the type of the assignment takes on the type of the value (not the assigned variable).      
            JavaTypeName returnType = encodeExpr(assignment.getValue(), context);
           
            if (retainValue) {
                //duplicate the value so that a copy will be left over after assignment
                //field, value --->
                //value, field, value
                mv.visitInsn(getDupX1OpCode(returnType));
            }
           
            //do the assignment
            mv.visitFieldInsn(Opcodes.PUTFIELD, instanceType.getJVMInternalName(), javaInstanceField.getFieldName(), javaInstanceField.getFieldType().getJVMDescriptor());
           
            return returnType;
           
        } else if (lhs instanceof JavaField.Static) {
           
            JavaField.Static javaStaticField = (JavaField.Static)lhs;
                                  
            //push the rhs expression
            // the type of the assignment takes on the type of the value (not the assigned variable).      
            JavaTypeName returnType = encodeExpr(assignment.getValue(), context);
           
            if (retainValue) {
                //duplicate the value so that a copy will be left over after assignment
                //value --->
                //value, value
                mv.visitInsn(getDupOpCode(returnType));
            }
           
            //do the assignment
            mv.visitFieldInsn(Opcodes.PUTSTATIC, javaStaticField.getInvocationClass().getJVMInternalName(), javaStaticField.getFieldName(), javaStaticField.getFieldType().getJVMDescriptor());           
           
            return returnType;
                  
        } else if (lhs instanceof LocalVariable) {
            LocalVariable localVariable = (LocalVariable)lhs;
                       
            int localVarIndex = context.getLocalVarIndex(localVariable.getName());
   
            //push the rhs expression
            JavaTypeName returnType = encodeExpr(assignment.getValue(), context);
           
            if (retainValue) {
                //duplicate the value so that a copy will be left over after assignment
                //value --->
                //value, value
                mv.visitInsn(getDupOpCode(returnType));
            }
                           
            //encode the store instruction
            mv.visitVarInsn(getStoreOpCode(localVariable.getTypeName()), localVarIndex);
           
            return returnType;
   
        } else if (lhs instanceof MethodVariable) {
            MethodVariable methodVariable = (MethodVariable)lhs;
            String varName = methodVariable.getName();
                       
            int methodVarIndex = context.getMethodVarIndex(varName);
            JavaTypeName methodVarType = context.getMethodVarType(varName);
   
            //push the rhs expression
            JavaTypeName returnType = encodeExpr(assignment.getValue(), context);
           
            if (retainValue) {
                //duplicate the value so that a copy will be left over after assignment
                //value --->
                //value, value
                mv.visitInsn(getDupOpCode(returnType));
            }
                           
            //encode the store instruction
            mv.visitVarInsn(getStoreOpCode(methodVarType), methodVarIndex);
           
            return returnType;                         
   
        } else if (lhs instanceof ArrayAccess) {
            ArrayAccess arrayAccess = (ArrayAccess)lhs;

            // Add the instructions to get the array ref.
            encodeExpr(arrayAccess.getArrayReference(), context);           
           
            // Add the instructions to evaluate the array index.
            encodeExpr(arrayAccess.getArrayIndex(), context);
                                     
            // add the instructions to evaluate the expression to assign.
            JavaTypeName returnType = encodeExpr(assignment.getValue(), context);          
           
            if (retainValue) { 
                //duplicate the value so that a copy will be left over after assignment
                //arrayRef index value --->
                //value arrayRef index value
                mv.visitInsn(getDupX2OpCode(returnType));              
            }  
           
            //encode the store array element instruction
            mv.visitInsn(getArrayStoreOpCode(returnType));
           
            return returnType;                      
        }
       
        throw new JavaGenerationException("Cannot assign to this type of expression: " + lhs);      
    }
   
    /**   
     * @param type
     * @return the DUP or DUP2 instruction, depending on type.
     */
    private static int getDupOpCode (JavaTypeName type) {
       
       switch (type.getTag()) {                               
            case JavaTypeName.BOOLEAN_TAG:                    
            case JavaTypeName.BYTE_TAG:              
            case JavaTypeName.SHORT_TAG:             
            case JavaTypeName.CHAR_TAG:                            
            case JavaTypeName.INT_TAG:
            case JavaTypeName.FLOAT_TAG:             
            case JavaTypeName.ARRAY_TAG:          
            case JavaTypeName.OBJECT_TAG: 
                return Opcodes.DUP;
           
            case JavaTypeName.LONG_TAG:           
            case JavaTypeName.DOUBLE_TAG:
                return Opcodes.DUP2;
           
            case JavaTypeName.VOID_TAG:                                                                  
            default:
            {
                throw new IllegalArgumentException("invalid type: " + type);
            }
        }                              
    }   
   
    /**    
     * @param type
     * @return the DUP_X1 or DUP2_X1 instruction, depending on type.
     */
    private static int getDupX1OpCode (JavaTypeName type) {
       
       switch (type.getTag()) {                               
            case JavaTypeName.BOOLEAN_TAG:                    
            case JavaTypeName.BYTE_TAG:              
            case JavaTypeName.SHORT_TAG:             
            case JavaTypeName.CHAR_TAG:                            
            case JavaTypeName.INT_TAG:
            case JavaTypeName.FLOAT_TAG:             
            case JavaTypeName.ARRAY_TAG:          
            case JavaTypeName.OBJECT_TAG: 
                return Opcodes.DUP_X1;
           
            case JavaTypeName.LONG_TAG:           
            case JavaTypeName.DOUBLE_TAG:
                return Opcodes.DUP2_X1;
           
            case JavaTypeName.VOID_TAG:                                                                  
            default:
            {
                throw new IllegalArgumentException("invalid type: " + type);
            }
        }                              
    }
   
    /**    
     * @param type
     * @return the DUP_X2 or DUP2_X2 instruction, depending on type.
     */
    private static int getDupX2OpCode (JavaTypeName type) {
       
       switch (type.getTag()) {                               
            case JavaTypeName.BOOLEAN_TAG:                    
            case JavaTypeName.BYTE_TAG:              
            case JavaTypeName.SHORT_TAG:             
            case JavaTypeName.CHAR_TAG:                            
            case JavaTypeName.INT_TAG:
            case JavaTypeName.FLOAT_TAG:             
            case JavaTypeName.ARRAY_TAG:          
            case JavaTypeName.OBJECT_TAG: 
                return Opcodes.DUP_X2;
           
            case JavaTypeName.LONG_TAG:           
            case JavaTypeName.DOUBLE_TAG:
                return Opcodes.DUP2_X2;
           
            case JavaTypeName.VOID_TAG:                                                                  
            default:
            {
                throw new IllegalArgumentException("invalid type: " + type);
            }
        }                              
    }   

    /**
     * Encodes the Java code for a given Java operator expression.
     *  
     * @param operatorExpr the java operator expression 
     * @param context
     * @return JavaTypeName
     * @throws JavaGenerationException
     */   
    private static JavaTypeName encodeOperatorExpr(JavaExpression.OperatorExpression operatorExpr, GenerationContext context) throws JavaGenerationException{
      
        MethodVisitor mv = context.getMethodVisitor();
       
        JavaOperator operator = operatorExpr.getJavaOperator();
        String symbol = operator.getSymbol();
        JavaTypeName valueType = operator.getValueType();             
                     
        // Now carry out the operation according to its type.
        if (operator.isArithmeticOp()) {
           
            // Add the instructions to evaluate the first argument.
            JavaTypeName arg1Type = encodeExpr(operatorExpr.getArgument(0), context)
           
            if (operatorExpr instanceof OperatorExpression.Unary) {
               
                if (symbol.equals("-")) {  // number negation
           
                    mv.visitInsn(getNegateOpCode(arg1Type));             
                    return arg1Type;
                }
               
                throw new JavaGenerationException("Unknown unary arithmetic operator " + symbol + ".");               
            }
           
            // Add an instruction to widen the argument if necessary.
            int wideningOpCode = getWideningOpCode(arg1Type, valueType);
            if (wideningOpCode != Opcodes.NOP) {
                mv.visitInsn(wideningOpCode);
            }
           
            // Add the instructions to evaluate the second argument.
            JavaTypeName arg2Type = encodeExpr(operatorExpr.getArgument(1), context);
           
            // Add an instruction to widen the argument if necessary.
            wideningOpCode = getWideningOpCode(arg2Type, valueType);
            if (wideningOpCode != Opcodes.NOP) {
                mv.visitInsn(wideningOpCode);
            }
           
            // Evaluate.
            mv.visitInsn(getArithmeticBinaryOpCode(symbol, valueType));
           
            return valueType;           
        }
       
        if (operator.isBitOp()) {
            // Add the instructions to evaluate the first argument.
            JavaTypeName arg1Type = encodeExpr(operatorExpr.getArgument(0), context)
           
            // Add an instruction to widen the argument if necessary.
            int wideningOpCode = getWideningOpCode(arg1Type, valueType);
            if (wideningOpCode != Opcodes.NOP) {
                mv.visitInsn(wideningOpCode);
            }
           
            if (operatorExpr instanceof OperatorExpression.Unary) {
                if (symbol.equals("~")) {  // number negation
                    if (valueType == JavaTypeName.INT) {
                        mv.visitInsn(Opcodes.ICONST_M1);
                        mv.visitInsn(Opcodes.IXOR);
                        return valueType;
                    } else
                    if (valueType == JavaTypeName.LONG) {
                        encodePushLongValue (new Long(-1), context);
                        mv.visitInsn(Opcodes.LXOR);
                        return valueType;
                    }
                }
               
                throw new JavaGenerationException("Unknown unary arithmetic operator " + symbol + ".");               
            }
           
            // Add the instructions to evaluate the second argument.
            JavaTypeName arg2Type = encodeExpr(operatorExpr.getArgument(1), context);
           
            // If this is >>, >>>, or << we may need to narrow the second argument to an int
            if (symbol.equals(">>") || symbol.equals(">>>") || symbol.equals("<<")) {
                if (arg2Type == JavaTypeName.LONG) {
                    mv.visitInsn(Opcodes.L2I);
                }
            } else {
                // Add an instruction to widen the argument if necessary.
                wideningOpCode = getWideningOpCode(arg2Type, valueType);
                if (wideningOpCode != Opcodes.NOP) {
                    mv.visitInsn(wideningOpCode);
                }
            }
           
            // Evaluate.
            mv.visitInsn(getArithmeticBinaryOpCode(symbol, valueType));
           
            return valueType;           
           
        }
       
        if (operator.isLogicalOp() || operator.isRelationalOp()) {
           
            // Logical op:    {"!", "&&", "||"}
            // Relational op: {">", ">=", "<", "<=", "==", "!="}
           
            Label trueContinuation = new Label();
            Label falseContinuation = new Label();
                                                
            encodeBooleanValuedOperatorHelper(operatorExpr, context, trueContinuation, falseContinuation);        
            return encodeThenTrueElseFalse(trueContinuation, falseContinuation, context);          
        }
                             
        if (operator == JavaOperator.STRING_CONCATENATION) {
                                
            // Create an uninitialized StringBuilder, duplicate the reference (so we can invoke the initializer).
            mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder");
            mv.visitInsn(Opcodes.DUP);
                       
            mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V");

            // append the first arg.
            JavaTypeName firstArgType = encodeExpr(operatorExpr.getArgument(0), context);                      
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", getAppendJVMDescriptor(firstArgType));
           
            // Append the results of evaluation of the second arg.
            // Note that, conveniently, StringBuilder has an append() method for all the different types.
            JavaTypeName secondArgType = encodeExpr(operatorExpr.getArgument(1), context);
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", getAppendJVMDescriptor(secondArgType));
           
            // Call toString() on the result.          
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");                        
           
            return JavaTypeName.STRING;
        }
       
        return encodeTernaryOperatorExpr((OperatorExpression.Ternary)operatorExpr, context);                          
    }
   
    private static JavaTypeName encodeThenTrueElseFalse(Label trueContinuation, Label falseContinuation, GenerationContext context) {
       
        MethodVisitor mv = context.getMethodVisitor();
       
        mv.visitLabel(trueContinuation);
        mv.visitInsn(Opcodes.ICONST_1);
        Label nextLabel = new Label();
        mv.visitJumpInsn(Opcodes.GOTO, nextLabel);
        mv.visitLabel(falseContinuation);
        mv.visitInsn(Opcodes.ICONST_0);
        mv.visitLabel(nextLabel);
       
        return JavaTypeName.BOOLEAN;
    }
   
    /**    
     * @param expr
     * @return true if JavaExpression represents the int literal 0.
     */
    private static boolean isLiteralIntZeroExpr(JavaExpression expr) {
       
        if (expr instanceof JavaExpression.LiteralWrapper) {
           
            Object literalValue = ((JavaExpression.LiteralWrapper)expr).getLiteralObject();
            return literalValue instanceof Integer && ((Integer)literalValue).intValue() == 0;           
        }
       
        return false;
   
   
    /**    
     * @param expr
     * @return true if expr is an operator expression for one of !, &&, ||, ==, !=, <, <=, > or >=.
     */
    private static boolean isBooleanValuedOperatorExpr(JavaExpression expr) {
        if (expr instanceof JavaExpression.OperatorExpression) {
            JavaOperator operator = ((JavaExpression.OperatorExpression)expr).getJavaOperator();
            return operator.isLogicalOp() || operator.isRelationalOp();
        }
       
        return false;       
    }
   
    private static void encodeBooleanValuedOperatorHelper(JavaExpression.OperatorExpression operatorExpr, GenerationContext context,
            Label trueContinuation, Label falseContinuation) throws JavaGenerationException{
        encodeBooleanValuedOperatorHelper(operatorExpr, context, trueContinuation, falseContinuation, true);
    }
   
    /** 
     * Boolean valued operators (!, &&, ||, ==, !=, <, <=, > and >=) are highly optimized during compilation to bytecode.
     * Here is a quick outline of the optimizations used:
     * -not (e1 && e2) is compiled as a single notAnd operator
     * -not (e1 || e2) is compiled as a single notOr operator
     * -not (not e) is optimized out.
     * -not (x < y) is compiled as x >= y for integral comparisons. A similar thing is done for not (double <), but it is not quite double >= because
     *  of NaN. However, there is special java bytecode support for treatment of this.
     * -Comparisons where the right-hand-side is an int 0 are treated more efficiently i.e. x > 0.
     * -Comparisons to null are treated specially i.e. x != null, x == null. 
     * -if the result of a boolean valued operator is used by the condition part of an if-then-else statement (or ternary operator) then
     *  the resulting true or false value is not pushed onto the stack and then tested. Rather we directly branch to the appropriate
     *  continuation.
     * -the most complicated optimization is that "trees" of boolean valued operators are effectively compiled as a single operator.
     *  What this means is that the resulting "true" and "false" values are not popped onto the stack and consumed by subsequent operators
     *  but rather a "continuation style" is employed where we just jump to the correct next comparison.
     *  This saves an extra comparison per operator, as well as unecessary pushes of trues and falses compared to the naive compilation scheme.
     *  The precise bytecode instructions used in the compilation schemes varies depending on context (see the endsWithTrueForm argument).
     *
     * @param operatorExpr
     * @param context
     * @param trueContinuation label to jump to if the expression has a true value
     * @param falseContinuation label to jump to if the expression has a false value
     * @param endsWithTrueForm operators are encoded as a series of tests with jumps where if none of the jumps are taken the operator slips
     *    through to the default case. This is usually "true" but if the "endsWithTrueForm" flag is set to false, then the default case will
     *    be false. For example, this is useful when encoding a boolean-valued operator that is the left argument of the || operator.
     *    In that case we want the default case to proceed to evaluation of the second argument of ||.
     * @throws JavaGenerationException
     */
    private static void encodeBooleanValuedOperatorHelper(JavaExpression.OperatorExpression operatorExpr, GenerationContext context,
            Label trueContinuation, Label falseContinuation, boolean endsWithTrueForm) throws JavaGenerationException{
       
        MethodVisitor mv = context.getMethodVisitor();              
   
        JavaOperator operator = operatorExpr.getJavaOperator();
        String symbol = operator.getSymbol();
        JavaTypeName valueType = operator.getValueType();           
                   
        if (operator.isLogicalOp()) {
                        
            // Logical op:    {"!", "&&", "||"}
            // Note: conditional statements should not be handled here..
            //   eg. "if" conditional evaluation happens during "if" source generation.
            //   We can get here if, eg. printing the result of a conditional.           
           
            // boolean negation
            if (symbol.equals("!")) {
                                             
                JavaExpression arg0Expr = operatorExpr.getArgument(0);
               
                //attempt to optimize a variety of cases where not is composed with another boolean valued operator.
               
                if (arg0Expr instanceof JavaExpression.OperatorExpression) {
                   
                    if (arg0Expr instanceof JavaExpression.OperatorExpression.Binary) {
                                            
                        JavaExpression.OperatorExpression.Binary arg0BinaryOperatorExpr = (JavaExpression.OperatorExpression.Binary)arg0Expr;
                        JavaOperator arg0BinaryOperator = arg0BinaryOperatorExpr.getJavaOperator();
                                              
                        //not (expr1 && expr2) is encoded in a special way. Effectively there is a notAnd operator.
                                                                    
                        if (arg0BinaryOperator == JavaOperator.CONDITIONAL_AND) {
                                                                                 
                            //x notAnd y                   
                            //is encoded as                   
                            //if x == false then goto trueContinuation
                            //if y == true then goto falseContinuation
                           
                            ////what follows is a sample continuation in the case when a literal value is pushed onto the stack
                            //label trueContinuation:
                            //push true
                            //goto next
                            //label falseContinuation:
                            //push false
                            //next:
                                                      
                            JavaExpression andOpArg0Expr = arg0BinaryOperatorExpr.getArgument(0);
                            if (isBooleanValuedOperatorExpr(andOpArg0Expr)) { 
                                Label innerTrueContinuation = new Label();
                                encodeBooleanValuedOperatorHelper((JavaExpression.OperatorExpression)andOpArg0Expr, context, innerTrueContinuation, trueContinuation);                             
                                mv.visitLabel(innerTrueContinuation);
                            } else {
                                encodeExpr(andOpArg0Expr, context);              
                                mv.visitJumpInsn(Opcodes.IFEQ, trueContinuation);
                            }
                           
                            JavaExpression andOpArg1Expr = arg0BinaryOperatorExpr.getArgument(1);
                            if (isBooleanValuedOperatorExpr(andOpArg1Expr)) {
                                encodeBooleanValuedOperatorHelper((JavaExpression.OperatorExpression)andOpArg1Expr, context, falseContinuation, trueContinuation, !endsWithTrueForm);
                            } else {
                                encodeExpr(andOpArg1Expr, context);
                                if (endsWithTrueForm) {                                      
                                    mv.visitJumpInsn(Opcodes.IFNE, falseContinuation);                                   
                                } else {
                                    mv.visitJumpInsn(Opcodes.IFEQ, trueContinuation);                                  
                                }
                            }                             
                           
                            return;                                                                                                    
                        }
                       
                        //not (expr1 || expr2) is encoded in a special way. Effectively there is a notOr operator.
                       
                        if (arg0BinaryOperator == JavaOperator.CONDITIONAL_OR) {
                           
                            //x notOr y                   
                            //is encoded as                   
                            //if x == true then goto falseContinuation
                            //if y == true then goto falseContinuation
                           
                            ////what follows is a sample continuation in the case when a literal value is pushed onto the stack                          
                            //label trueContinuation:
                            //push true
                            //goto next
                            //label falseContinuation:
                            //push false
                            //next:
                                                      
                            JavaExpression orOpArg0Expr = arg0BinaryOperatorExpr.getArgument(0);
                            if (isBooleanValuedOperatorExpr(orOpArg0Expr)) { 
                                Label innerFalseContinuation = new Label();
                                //if x evaluates to false, we want to continue with evaluating y, this is why the "endsWithTrueForm" argument is false here.
                                //if x evaluates to false, then x notOr y returns true without needing to evaluate y. That is why the trueContinuation for x, is
                                //the falseContinuation for the call that encodes x.
                                encodeBooleanValuedOperatorHelper((JavaExpression.OperatorExpression)orOpArg0Expr, context, falseContinuation, innerFalseContinuation, false);                             
                                mv.visitLabel(innerFalseContinuation);
                            } else {
                                encodeExpr(orOpArg0Expr, context);              
                                mv.visitJumpInsn(Opcodes.IFNE, falseContinuation);
                            }
                           
                            JavaExpression orOpArg1Expr = arg0BinaryOperatorExpr.getArgument(1);
                            if (isBooleanValuedOperatorExpr(orOpArg1Expr)) {
                                encodeBooleanValuedOperatorHelper((JavaExpression.OperatorExpression)orOpArg1Expr, context, falseContinuation, trueContinuation, !endsWithTrueForm);
                            } else {
                                encodeExpr(orOpArg1Expr, context);  
                                if (endsWithTrueForm) {
                                    mv.visitJumpInsn(Opcodes.IFNE, falseContinuation);                                  
                                } else {
                                    mv.visitJumpInsn(Opcodes.IFEQ, trueContinuation);                                  
                                }
                            }
                                                                                  
                            return;                                                                         
                        }
                       
                        //try to optimize not composed with a boolean valued operator as a single operation
                        //for example, for int operators, not (x < y) is actually encoded as x >= y.                       
                       
                        JavaExpression.OperatorExpression.Binary notComposedOperatorExpr = arg0BinaryOperatorExpr.getNotComposedOperatorExpr();
                        if (notComposedOperatorExpr != null) {
                                                      
                            encodeBooleanValuedOperatorHelper(notComposedOperatorExpr, context, trueContinuation, falseContinuation, endsWithTrueForm);
                            return;
                        }
                                               
                        //not (x Double.< y) is encoded like x Double.>= y except that the opposite DCMP instruction is used.                          
                        //this is to handle NAN. Similar for the others.
                       
                        if (arg0BinaryOperator == JavaOperator.LESS_THAN_DOUBLE ||
                            arg0BinaryOperator == JavaOperator.LESS_THAN_EQUALS_DOUBLE ||
                            arg0BinaryOperator == JavaOperator.GREATER_THAN_DOUBLE ||
                            arg0BinaryOperator == JavaOperator.GREATER_THAN_EQUALS_DOUBLE) {
                                                                                  
                            //encode the first argument
                            JavaTypeName firstArgType = encodeExpr(arg0BinaryOperatorExpr.getArgument(0), context);
                           
                            // Add instructions to widen the first argument if necessary.
                            int wideningOpCode = getWideningOpCode(firstArgType, JavaTypeName.DOUBLE);
                            if (wideningOpCode != Opcodes.NOP) {
                                mv.visitInsn(wideningOpCode);                       
                            }
                                                 
                            //endcode the second argument
                            JavaExpression secondArgExpr = arg0BinaryOperatorExpr.getArgument(1);
                            JavaTypeName secondArgType = encodeExpr(secondArgExpr, context);
                            wideningOpCode = getWideningOpCode(secondArgType, JavaTypeName.DOUBLE);
                            if (wideningOpCode != Opcodes.NOP) {
                                mv.visitInsn(wideningOpCode);
                            }
                           
                            if (arg0BinaryOperator == JavaOperator.LESS_THAN_DOUBLE) {
                               
                                mv.visitInsn(Opcodes.DCMPG);
                                if (endsWithTrueForm) {
                                    mv.visitJumpInsn(Opcodes.IFLT, falseContinuation);
                                } else {
                                    mv.visitJumpInsn(Opcodes.IFGE, trueContinuation);
                                }
                               
                            } else if (arg0BinaryOperator == JavaOperator.LESS_THAN_EQUALS_DOUBLE) {
                               
                                mv.visitInsn(Opcodes.DCMPG);
                                if (endsWithTrueForm) {
                                    mv.visitJumpInsn(Opcodes.IFLE, falseContinuation);
                                } else {
                                    mv.visitJumpInsn(Opcodes.IFGT, trueContinuation);
                                }
                                                         
                            } else if (arg0BinaryOperator == JavaOperator.GREATER_THAN_DOUBLE) {
                               
                                mv.visitInsn(Opcodes.DCMPL);
                                if (endsWithTrueForm) {
                                    mv.visitJumpInsn(Opcodes.IFGT, falseContinuation);
                                } else {
                                    mv.visitJumpInsn(Opcodes.IFLE, trueContinuation);
                                }
                              
                            } else if (arg0BinaryOperator == JavaOperator.GREATER_THAN_EQUALS_DOUBLE) { 
                               
                                mv.visitInsn(Opcodes.DCMPL)
                                if (endsWithTrueForm) {
                                    mv.visitJumpInsn(Opcodes.IFGE, falseContinuation);
                                } else {
                                    mv.visitJumpInsn(Opcodes.IFLT, trueContinuation);
                                }
                               
                            } else {
                               
                                throw new JavaGenerationException("Expecting one of the double operators <, >, <= or >=.");
                            }
                           
                            return;
                        }                                             
                                                                                            
                        //fall through to the unoptimized case...
                                                      
                       
                    } else if (arg0Expr instanceof JavaExpression.OperatorExpression.Unary) {
                       
                        //"not (not expr)" is encoded as "id expr"
                       
                        JavaExpression.OperatorExpression.Unary arg0UnaryOperatorExpr = (JavaExpression.OperatorExpression.Unary)arg0Expr;
                        if (arg0UnaryOperatorExpr.getJavaOperator() != JavaOperator.LOGICAL_NEGATE) {
                            throw new JavaGenerationException("Unary logical negation expected.");
                        }
                       
                        JavaExpression expr = arg0UnaryOperatorExpr.getArgument(0);
                        if (isBooleanValuedOperatorExpr(expr)) {
                            encodeBooleanValuedOperatorHelper((JavaExpression.OperatorExpression)expr, context, trueContinuation, falseContinuation, endsWithTrueForm);
                        } else {
                            encodeExpr(expr, context);
                            if (endsWithTrueForm) {
                                mv.visitJumpInsn(Opcodes.IFEQ, falseContinuation);
                            } else {
                                mv.visitJumpInsn(Opcodes.IFNE, trueContinuation);
                            }
                        }
                       
                        return;
                    }                   
                }
               
                //!x
                //is encoded as
                //if x == true then goto falseContinuation;
               
                ////what follows is a sample continuation in the case when a literal value is pushed onto the stack
                //push true;
                //goto next;
                //falseContinuation:
                //push false;
                //label next:                                  
                
                encodeExpr(arg0Expr, context);
                if (endsWithTrueForm) {
                    //Note that IFNE consumes a value on the stack.
                    mv.visitJumpInsn(Opcodes.IFNE, falseContinuation);
                } else {
                    mv.visitJumpInsn(Opcodes.IFEQ, trueContinuation);
                }
               
                return;              
            }          
           
            if (symbol.equals("&&")) {
               
                //x && y                   
                //is encoded as                   
                //if x == false then goto falseContinuation
                //if y == false then goto falseContinuation
               
                ////what follows is a sample continuation in the case when a literal value is pushed onto the stack
                //push true
                //goto next
                //label falseContinuation:
                //push false
                //label next:
                               
                JavaExpression arg0Expr = operatorExpr.getArgument(0);               
                if (isBooleanValuedOperatorExpr(arg0Expr)) { 
                    Label innerTrueContinuation = new Label();
                    encodeBooleanValuedOperatorHelper((JavaExpression.OperatorExpression)arg0Expr, context, innerTrueContinuation, falseContinuation);                             
                    mv.visitLabel(innerTrueContinuation);
                } else {
                    encodeExpr(arg0Expr, context);              
                    mv.visitJumpInsn(Opcodes.IFEQ, falseContinuation);
                }
               
                JavaExpression arg1Expr = operatorExpr.getArgument(1);
                if (isBooleanValuedOperatorExpr(arg1Expr)) {
                    encodeBooleanValuedOperatorHelper((JavaExpression.OperatorExpression)arg1Expr, context, trueContinuation, falseContinuation, endsWithTrueForm);
                } else {
                    encodeExpr(arg1Expr, context);
                    if (endsWithTrueForm) {
                        mv.visitJumpInsn(Opcodes.IFEQ, falseContinuation);
                    } else {
                        mv.visitJumpInsn(Opcodes.IFNE, trueContinuation);
                    }
                } 
               
                return;
            }
           
            if (symbol.equals("||")) {
               
                //x || y
                //is encoded as
                //if x == true then goto trueContinuation
                //if y == false then goto falseContinuation
               
                ////what follows is a sample continuation in the case when a literal value is pushed onto the stack
                //push true
                //goto next
                //label falseContinuation:
                //push false
                //label next:
               
                JavaExpression arg0Expr = operatorExpr.getArgument(0);               
                if (isBooleanValuedOperatorExpr(arg0Expr)) { 
                    Label innerFalseContinuation = new Label();
                    //if x evaluates to false, we want to continue with evaluating y, this is why the "endsWithTrueForm" argument is false here.
                    encodeBooleanValuedOperatorHelper((JavaExpression.OperatorExpression)arg0Expr, context, trueContinuation, innerFalseContinuation, false);                                 
                    mv.visitLabel(innerFalseContinuation);
                } else {               
                    encodeExpr(arg0Expr, context);              
                    mv.visitJumpInsn(Opcodes.IFNE, trueContinuation);
                }
               
                JavaExpression arg1Expr = operatorExpr.getArgument(1);
                if (isBooleanValuedOperatorExpr(arg1Expr)){
                    encodeBooleanValuedOperatorHelper((JavaExpression.OperatorExpression)arg1Expr, context, trueContinuation, falseContinuation, endsWithTrueForm);
                } else {              
                    encodeExpr(arg1Expr, context);
                    if (endsWithTrueForm) {
                        mv.visitJumpInsn(Opcodes.IFEQ, falseContinuation);
                    } else {
                        mv.visitJumpInsn(Opcodes.IFNE, trueContinuation);
                    }
                }                                 
               
                return;
            }
           
            throw new JavaGenerationException("Unknown logical operator " + symbol + ".");
           
        } // if(operator.isLogicalOp())
       
        // A relational operator
       
        //one comment on the bytecode sequences: there is some subtle points here because of the treatment of special values e.g. such
        //as not a number, plus infinity, minus 0 etc in the double and float types. The code below is based on copying what the Java
        //compiler generates for simple functions such as:
        //double foo(double x, double y) {double z = x < y; return z;}
       
        //encode the first argument
        JavaTypeName firstArgType = encodeExpr(operatorExpr.getArgument(0), context);
       
        // Add instructions to widen the first argument if necessary.
        int wideningOpCode = getWideningOpCode(firstArgType, valueType);
        if (wideningOpCode != Opcodes.NOP) {
            mv.visitInsn(wideningOpCode);                       
        }
       
        //Deal with comparisons to null as a special case. Don't push the second argument, since the null is
        //implicit in the bytecode instruction.
        JavaExpression secondArgExpr = operatorExpr.getArgument(1);
        final boolean compareToNull = secondArgExpr == LiteralWrapper.NULL;
       
        //Deal with comparisons to int zero as a special case. There are special 1 argument operators for this case.
        //javac makes use of this optimization. Interestingly, javac does not optimize the case when the first argument
        //is a literal int zero i.e. 0 < x, is not converted to x > 0 which then can make use of the 1 argument comparison.       
        final boolean compareToIntZero = isInternalIntType(valueType) && isLiteralIntZeroExpr(secondArgExpr);       
       
        if (!compareToNull && !compareToIntZero) {                       
            //endcode the second argument
            JavaTypeName secondArgType = encodeExpr(secondArgExpr, context);
            wideningOpCode = getWideningOpCode(secondArgType, valueType);
            if (wideningOpCode != Opcodes.NOP) {
                mv.visitInsn(wideningOpCode);
            }
        }
            
        // relational symbols: {">", ">=", "<", "<=", "==", "!="}
        if (symbol.equals(">")) {                               
           
            switch (valueType.getTag()) {
                case JavaTypeName.BYTE_TAG:
                case JavaTypeName.SHORT_TAG:
                case JavaTypeName.CHAR_TAG:                       
                case JavaTypeName.INT_TAG:
                {
                    if (endsWithTrueForm) {
                        if (compareToIntZero) {
                            mv.visitJumpInsn(Opcodes.IFLE, falseContinuation);
                        } else {
                            mv.visitJumpInsn(Opcodes.IF_ICMPLE, falseContinuation);
                        }
                    } else {
                        if (compareToIntZero) {
                            mv.visitJumpInsn(Opcodes.IFGT, trueContinuation);
                        } else {
                            mv.visitJumpInsn(Opcodes.IF_ICMPGT, trueContinuation);
                        }                       
                    }
                    break;
                }
               
                case JavaTypeName.LONG_TAG:
                {
                    mv.visitInsn(Opcodes.LCMP);
                    if (endsWithTrueForm) {                                             
                        mv.visitJumpInsn(Opcodes.IFLE, falseContinuation);
                    } else {                                        
                        mv.visitJumpInsn(Opcodes.IFGT, trueContinuation);                       
                    }
                    break;
                }
               
                case JavaTypeName.DOUBLE_TAG:
                {
                    mv.visitInsn(Opcodes.DCMPL);
                    if (endsWithTrueForm) {
                        mv.visitJumpInsn(Opcodes.IFLE, falseContinuation);
                    } else {
                        mv.visitJumpInsn(Opcodes.IFGT, trueContinuation);
                    }
                    break;
                }
               
                case JavaTypeName.FLOAT_TAG:
                {
                    mv.visitInsn(Opcodes.FCMPL)
                    if (endsWithTrueForm) {
                        mv.visitJumpInsn(Opcodes.IFLE, falseContinuation);
                    } else {
                        mv.visitJumpInsn(Opcodes.IFGT, trueContinuation);
                    }
                    break;
                }
               
                default:
                    throw new IllegalArgumentException("Unsupported operand type for JVM > operator.");
            }                                                                                   
           
        } else if (symbol.equals(">=")) {             
           
            switch (valueType.getTag()) {
                case JavaTypeName.BYTE_TAG:
                case JavaTypeName.SHORT_TAG:
                case JavaTypeName.CHAR_TAG:               
                case JavaTypeName.INT_TAG:
                {
                    if (endsWithTrueForm) {
                        if (compareToIntZero) {
                            mv.visitJumpInsn(Opcodes.IFLT, falseContinuation);
                        } else {
                            mv.visitJumpInsn(Opcodes.IF_ICMPLT, falseContinuation);
                        }
                    } else {
                        if (compareToIntZero) {
                            mv.visitJumpInsn(Opcodes.IFGE, trueContinuation);
                        } else {
                            mv.visitJumpInsn(Opcodes.IF_ICMPGE, trueContinuation);
                        }                       
                    }
                    break;
                }
               
                case JavaTypeName.LONG_TAG:
                {
                    mv.visitInsn(Opcodes.LCMP);
                    if (endsWithTrueForm) {                                              
                        mv.visitJumpInsn(Opcodes.IFLT, falseContinuation);
                    } else {                                      
                        mv.visitJumpInsn(Opcodes.IFGE, trueContinuation);                       
                    }
                    break;
                }
               
                case JavaTypeName.DOUBLE_TAG:
                {
                    mv.visitInsn(Opcodes.DCMPL);
                    if (endsWithTrueForm) {
                        mv.visitJumpInsn(Opcodes.IFLT, falseContinuation);                       
                    } else {
                        mv.visitJumpInsn(Opcodes.IFGE, trueContinuation);
                    }
                    break;
                }
               
                case JavaTypeName.FLOAT_TAG:
                {
                    mv.visitInsn(Opcodes.FCMPL);
                    if (endsWithTrueForm) {
                        mv.visitJumpInsn(Opcodes.IFLT, falseContinuation);
                    } else {
                        mv.visitJumpInsn(Opcodes.IFGE, trueContinuation);
                    }
                    break;
                }
               
                default:
                    throw new IllegalArgumentException("Unsupported operand type for JVM >= operator.");
            }                                                                                       
                   
        } else if (symbol.equals("<")) {
           
            switch (valueType.getTag()) {
                 case JavaTypeName.BYTE_TAG:
                 case JavaTypeName.SHORT_TAG:
                 case JavaTypeName.CHAR_TAG:                
                 case JavaTypeName.INT_TAG:
                 {
                     if (endsWithTrueForm) {
                         if (compareToIntZero) {
                             mv.visitJumpInsn(Opcodes.IFGE, falseContinuation);
                         } else {
                             mv.visitJumpInsn(Opcodes.IF_ICMPGE, falseContinuation);
                         }
                     } else {
                         if (compareToIntZero) {
                             mv.visitJumpInsn(Opcodes.IFLT, trueContinuation);
                         } else {
                             mv.visitJumpInsn(Opcodes.IF_ICMPLT, trueContinuation);
                         }                        
                     }
                     break;
                 }
                    
                 case JavaTypeName.LONG_TAG:
                 {
                     mv.visitInsn(Opcodes.LCMP);
                     if (endsWithTrueForm) {
                         mv.visitJumpInsn(Opcodes.IFGE, falseContinuation);
                     } else {
                         mv.visitJumpInsn(Opcodes.IFLT, trueContinuation);                        
                     }
                     break;
                 }
                    
                 case JavaTypeName.DOUBLE_TAG:
                 {
                     mv.visitInsn(Opcodes.DCMPG);
                     if (endsWithTrueForm) {
                         mv.visitJumpInsn(Opcodes.IFGE, falseContinuation);
                     } else {
                         mv.visitJumpInsn(Opcodes.IFLT, trueContinuation);
                     }                    
                     break;
                 }
                    
                 case JavaTypeName.FLOAT_TAG:
                 {
                     mv.visitInsn(Opcodes.FCMPG);
                     if (endsWithTrueForm) {
                         mv.visitJumpInsn(Opcodes.IFGE, falseContinuation);
                     } else {
                         mv.visitJumpInsn(Opcodes.IFLT, trueContinuation);                        
                     }
                     break;
                 }
                    
                 default:
                     throw new IllegalArgumentException("Unsupported operand type for JVM < operator.");
            }                                                                           
               
        } else if (symbol.equals("<=")) {
                                        
            switch (valueType.getTag()) {
                case JavaTypeName.BYTE_TAG:
                case JavaTypeName.SHORT_TAG:
                case JavaTypeName.CHAR_TAG:                
                case JavaTypeName.INT_TAG:
                {
                    if (endsWithTrueForm) {
                        if (compareToIntZero) {
                            mv.visitJumpInsn(Opcodes.IFGT, falseContinuation);
                        } else {
                            mv.visitJumpInsn(Opcodes.IF_ICMPGT, falseContinuation);
                        }
                    } else {
                        if (compareToIntZero) {
                            mv.visitJumpInsn(Opcodes.IFLE, trueContinuation);
                        } else {
                            mv.visitJumpInsn(Opcodes.IF_ICMPLE, trueContinuation);
                        }                       
                    }
                    break;
                }
               
                case JavaTypeName.LONG_TAG:
                {
                    mv.visitInsn(Opcodes.LCMP);
                    if (endsWithTrueForm) {
                        mv.visitJumpInsn(Opcodes.IFGT, falseContinuation);
                    } else {
                        mv.visitJumpInsn(Opcodes.IFLE, trueContinuation);
                    }
                    break;
                }
               
                case JavaTypeName.DOUBLE_TAG:
                {
                    mv.visitInsn(Opcodes.DCMPG);
                    if (endsWithTrueForm) {
                        mv.visitJumpInsn(Opcodes.IFGT, falseContinuation);
                    } else {
                        mv.visitJumpInsn(Opcodes.IFLE, trueContinuation);
                    }
                    break;
                }
               
                case JavaTypeName.FLOAT_TAG:
                {
                    mv.visitInsn(Opcodes.FCMPG);
                    if (endsWithTrueForm) {
                        mv.visitJumpInsn(Opcodes.IFGT, falseContinuation);
                    } else {
                        mv.visitJumpInsn(Opcodes.IFLE, trueContinuation);
                    }
                    break;
                }
               
                default:
                    throw new IllegalArgumentException("Unsupported operand type for JVM <= operator.");
            }                                                                                         

        } else if (symbol.equals("==")) {
           
            switch (valueType.getTag()) {
                case JavaTypeName.BOOLEAN_TAG:
                case JavaTypeName.BYTE_TAG:
                case JavaTypeName.SHORT_TAG:
                case JavaTypeName.CHAR_TAG:               
                case JavaTypeName.INT_TAG:
                {
                    if (endsWithTrueForm) {
                        if (compareToIntZero) {
                            mv.visitJumpInsn(Opcodes.IFNE, falseContinuation);
                        } else {
                            mv.visitJumpInsn(Opcodes.IF_ICMPNE, falseContinuation);
                        }
                    } else {
                        if (compareToIntZero) {
                            mv.visitJumpInsn(Opcodes.IFEQ, trueContinuation);
                        } else {
                            mv.visitJumpInsn(Opcodes.IF_ICMPEQ, trueContinuation);
                        }                       
                    }
                    break;
                }
               
                case JavaTypeName.LONG_TAG:
                {
                    mv.visitInsn(Opcodes.LCMP);
                    if (endsWithTrueForm) {
                        mv.visitJumpInsn(Opcodes.IFNE, falseContinuation);
                    } else {
                        mv.visitJumpInsn(Opcodes.IFEQ, trueContinuation);
                    }
                    break;
                }
               
                case JavaTypeName.DOUBLE_TAG:
                {
                    mv.visitInsn(Opcodes.DCMPL)
                    if (endsWithTrueForm) {
                        mv.visitJumpInsn(Opcodes.IFNE, falseContinuation);
                    } else {
                        mv.visitJumpInsn(Opcodes.IFEQ, trueContinuation);
                    }
                    break;
                }
               
                case JavaTypeName.FLOAT_TAG:
                {
                    mv.visitInsn(Opcodes.FCMPL);
                    if (endsWithTrueForm) {
                        mv.visitJumpInsn(Opcodes.IFNE, falseContinuation);
                    } else {
                        mv.visitJumpInsn(Opcodes.IFEQ, trueContinuation);
                    }
                    break;
                }
                   
                case JavaTypeName.ARRAY_TAG:
                case JavaTypeName.OBJECT_TAG:
                {
                    if (endsWithTrueForm) {
                        if (compareToNull) {
                            mv.visitJumpInsn(Opcodes.IFNONNULL, falseContinuation);
                        } else {
                            mv.visitJumpInsn(Opcodes.IF_ACMPNE, falseContinuation);
                        }
                    } else {
                        if (compareToNull) {
                            mv.visitJumpInsn(Opcodes.IFNULL, trueContinuation);
                        } else {
                            mv.visitJumpInsn(Opcodes.IF_ACMPEQ, trueContinuation);
                        }                       
                    }
                    break;
                }
               
                default:
                    throw new IllegalArgumentException("Unsupported operand type for JVM == operator.");
            }
                            
        } else if (symbol.equals("!=")) {
           
            switch (valueType.getTag()) {
                case JavaTypeName.BOOLEAN_TAG:
                case JavaTypeName.BYTE_TAG:
                case JavaTypeName.SHORT_TAG:
                case JavaTypeName.CHAR_TAG:                
                case JavaTypeName.INT_TAG:
                {
                    if (endsWithTrueForm) {
                        if (compareToIntZero) {
                            mv.visitJumpInsn(Opcodes.IFEQ, falseContinuation);
                        } else {
                            mv.visitJumpInsn(Opcodes.IF_ICMPEQ, falseContinuation);
                        }
                    } else {
                        if (compareToIntZero) {
                            mv.visitJumpInsn(Opcodes.IFNE, trueContinuation);
                        } else {
                            mv.visitJumpInsn(Opcodes.IF_ICMPNE, trueContinuation);
                        }                       
                    }
                    break;
                }
               
                case JavaTypeName.LONG_TAG:
                {
                    mv.visitInsn(Opcodes.LCMP)
                    if (endsWithTrueForm) {                                             
                        mv.visitJumpInsn(Opcodes.IFEQ, falseContinuation);
                    } else {
                        mv.visitJumpInsn(Opcodes.IFNE, trueContinuation);
                    }
                    break;
                }
               
                case JavaTypeName.DOUBLE_TAG:
                {
                    mv.visitInsn(Opcodes.DCMPL);                       
                    if (endsWithTrueForm) {                                             
                        mv.visitJumpInsn(Opcodes.IFEQ, falseContinuation);
                    } else {
                        mv.visitJumpInsn(Opcodes.IFNE, trueContinuation);
                    }
                    break;
                }
               
                case JavaTypeName.FLOAT_TAG:
                {
                    mv.visitInsn(Opcodes.FCMPL);                       
                    if (endsWithTrueForm) {                                             
                        mv.visitJumpInsn(Opcodes.IFEQ, falseContinuation);
                    } else {
                        mv.visitJumpInsn(Opcodes.IFNE, trueContinuation);
                    }
                    break;
                }
                   
                case JavaTypeName.ARRAY_TAG:
                case JavaTypeName.OBJECT_TAG:
                {
                    if (endsWithTrueForm) {
                        if (compareToNull) {
                            mv.visitJumpInsn(Opcodes.IFNULL, falseContinuation);
                        } else {
                            mv.visitJumpInsn(Opcodes.IF_ACMPEQ, falseContinuation);
                        }
                    } else {
                        if (compareToNull) {
                            mv.visitJumpInsn(Opcodes.IFNONNULL, trueContinuation);
                        } else {
                            mv.visitJumpInsn(Opcodes.IF_ACMPNE, trueContinuation);
                        }                       
                    }
                    break;
                }
               
                default:
                    throw new IllegalArgumentException("Unsupported operand type for JVM != operator.");
            }               
          
        } else {
            throw new JavaGenerationException("Unknown relational operator " + symbol + ".");
        }        
    }
   
    /**
     * A helper function to get the JVM descriptor of the right overload of StringBuilder.append().
     * @param typeToAppend the type of the argument supplied to the StringBuilder.append method.
     * @return the JVM descriptor of the appropriate append overload.
     */
    private static String getAppendJVMDescriptor(JavaTypeName typeToAppend) {
             
        switch (typeToAppend.getTag()) { 
       
            case JavaTypeName.VOID_TAG:
                throw new IllegalArgumentException();
           
            case JavaTypeName.BOOLEAN_TAG: return "(Z)Ljava/lang/StringBuilder;";                    
            case JavaTypeName.BYTE_TAG:    return "(B)Ljava/lang/StringBuilder;";         
            case JavaTypeName.SHORT_TAG:   return "(S)Ljava/lang/StringBuilder;";          
            case JavaTypeName.CHAR_TAG:    return "(C)Ljava/lang/StringBuilder;";                        
            case JavaTypeName.INT_TAG:     return "(I)Ljava/lang/StringBuilder;";             
            case JavaTypeName.LONG_TAG:    return "(J)Ljava/lang/StringBuilder;";             
            case JavaTypeName.DOUBLE_TAG:  return "(D)Ljava/lang/StringBuilder;";              
            case JavaTypeName.FLOAT_TAG:   return "(F)Ljava/lang/StringBuilder;"
           
            case JavaTypeName.ARRAY_TAG:
            {
                //there is a more efficient overload for char[] arguments (it does not copy the argument).
                if (typeToAppend.equals(JavaTypeName.CHAR_ARRAY)) {
                    return "([C)Ljava/lang/StringBuilder;";
                }
               
                return "(Ljava/lang/Object;)Ljava/lang/StringBuilder;";               
            }
           
            case JavaTypeName.OBJECT_TAG:
            {
                //there are more efficient overloads for String and StringBuilder arguments
                if (typeToAppend.equals(JavaTypeName.STRING)) {
                    return "(Ljava/lang/String;)Ljava/lang/StringBuilder;";
                   
                } else if (typeToAppend.equals(JavaTypeName.STRING_BUILDER)) {
                    return "(Ljava/lang/StringBuilder;)Ljava/lang/StringBuilder;";
                }
               
                return "(Ljava/lang/Object;)Ljava/lang/StringBuilder;";               
            }
                             
            default:               
                throw new IllegalArgumentException();               
        }                                        
    }
   
    private static JavaTypeName encodeTernaryOperatorExpr(JavaExpression.OperatorExpression.Ternary ternaryOperatorExpr, GenerationContext context) throws JavaGenerationException{
        
        MethodVisitor mv = context.getMethodVisitor();
       
        JavaExpression conditionExpr = ternaryOperatorExpr.getArgument(0);
        JavaExpression thenExpr = ternaryOperatorExpr.getArgument(1);
        JavaExpression elseExpr = ternaryOperatorExpr.getArgument(2);
       
        if (conditionExpr instanceof JavaExpression.OperatorExpression) {
           
            //generate more efficient code in the case of (boolean-valued-operator) ? thenExpr : elseExpr
           
            //This case exists to handle the special case where an operator occurs as the child of an if-then-else (or ternary operator)
            //conditional. For example, in the situation:
            //
            // (x != null) ? thenExpr : elseExpr
            //
            // we do not want to evaluate x != null to a boolean value, push that value on the stack,
            // and then test it prior to selecting the correct branch. Rather, we can combine the evaluation
            // and jump operations into a single step.
           
            JavaExpression.OperatorExpression operatorExpr = (JavaExpression.OperatorExpression)conditionExpr;
                                    
            JavaOperator operator = operatorExpr.getJavaOperator();
            String symbol = operator.getSymbol();
           
            Label trueContinuation = new Label();
            Label falseContinuation =  new Label();
           
            if (operator.isLogicalOp() || operator.isRelationalOp()) {
               
                encodeBooleanValuedOperatorHelper(operatorExpr, context, trueContinuation, falseContinuation);                                   
                return encodeThenExprElseExpr(trueContinuation, falseContinuation, thenExpr, elseExpr, context);                           
            }
           
            throw new JavaGenerationException("Unrecognized boolean-valued conditional operator " + symbol + ".");                      
        }
                          
        //encode the boolean conditional
        encodeExpr(conditionExpr, context);
              
        Label falseContinuation = new Label();
        mv.visitJumpInsn(Opcodes.IFEQ, falseContinuation);
       
        return encodeThenExprElseExpr(null, falseContinuation, thenExpr, elseExpr, context);
    }
   
    private static JavaTypeName encodeThenExprElseExpr(Label trueContinuation, Label falseContinuation, JavaExpression thenExpr, JavaExpression elseExpr, GenerationContext context) throws JavaGenerationException {
       
        MethodVisitor mv = context.getMethodVisitor();
       
        if (trueContinuation != null) {
            mv.visitLabel(trueContinuation);
        }
       
        //encode the then-part expression
        JavaTypeName thenType = encodeExpr(thenExpr, context);       
        Label nextLabel = new Label();
        mv.visitJumpInsn(Opcodes.GOTO, nextLabel);
        mv.visitLabel(falseContinuation);
       
        //encode the else-part expression
        JavaTypeName elseType = encodeExpr(elseExpr, context);     
        mv.visitLabel(nextLabel);
                           
        //The Java language specification section 15.25 defines the rules for the return types of ternary operators.
        //These are complicated, and we don't support this. The basic case supported is when both static types are the same.
        //There is a hack to support the case when the 2 types are different:
        //This case happens a bit in our code generation. The proper fix would be to not create this situation during code generation.
             
        if (thenType.equals(elseType)) {
            return thenType;
         
        } else if (thenType.equals(JavaTypeName.RTVALUE)) {
            //this is a hack, see the above comment
            return thenType;
           
        } else if (elseType.equals(JavaTypeName.RTVALUE)) {
            //this is a hack, see the above comment
            return elseType;
           
        } else {
            throw new JavaGenerationException("The '?' operator must have then parts and else parts of exactly the same static types.");
        }                            
    }   

    /**
     * Creates the byte code for a given array access expression.
     *   The returned instruction list will cause the expression result to be pushed onto the stack.
     * @param arrayAccess the array access   
     * @param context
     * @return ExpressionCode the type of the result on the operand stack.
     * @throws JavaGenerationException
     */
    private static JavaTypeName encodeArrayAccessExpr(JavaExpression.ArrayAccess arrayAccess, GenerationContext context) throws JavaGenerationException {
       
        //encode the instructions to evaluate the array reference
        JavaTypeName.Reference.Array arrayType = (JavaTypeName.Reference.Array)encodeExpr(arrayAccess.getArrayReference(), context);
       
        //encode the instructions to evaluate the array index
        encodeExpr(arrayAccess.getArrayIndex(), context);
                      
        JavaTypeName elementType = arrayType.getIncrementalElementType();
       
        //load the array element
        context.getMethodVisitor().visitInsn(getArrayLoadOpCode(elementType));

        return elementType;
    }
   
    /**
     * Creates the byte code for a given array length expression.
     *   The returned instruction list will cause the expression result to be pushed onto the stack.
     * @param arrayLength the array length expression   
     * @param context
     * @return ExpressionCode the type of the result on the operand stack.
     * @throws JavaGenerationException
     */
    private static JavaTypeName encodeArrayLengthExpr(JavaExpression.ArrayLength arrayLength, GenerationContext context) throws JavaGenerationException {

        //encode the instructions to evaluate the array reference
        encodeExpr(arrayLength.getArrayReference(), context)
       
        //encode the arrayLength instruction
        context.getMethodVisitor().visitInsn(Opcodes.ARRAYLENGTH);

        return JavaTypeName.INT;
    }    
       
    /**
     * Pushes a reference to "this" onto the stack.
     * @param context
     * @return JavaTypeName the type of the result on the operand stack.    
     */
    private static JavaTypeName encodeThis(GenerationContext context) {       
        context.getMethodVisitor().visitVarInsn(Opcodes.ALOAD, 0);
        return context.getMethodVarType("this");
    }
   
    /**
     * Create the Java code for a given method invocation.
     *   The expression result to be pushed onto the stack.
     * @param mi the method invocation    
     * @param context
     * @return JavaTypeName the type of the result on the operand stack.
     * @throws JavaGenerationException
     */
    private static JavaTypeName encodeMethodInvocationExpr(JavaExpression.MethodInvocation mi, GenerationContext context) throws JavaGenerationException {
               
        MethodInvocation.InvocationType invocationType = mi.getInvocationType();
       
        int invocationCode;
        JavaTypeName invocationClassType;
                       
        if (invocationType != MethodInvocation.InvocationType.STATIC) {
           
            JavaExpression invocationTarget = ((MethodInvocation.Instance)mi).getInvocationTarget();
            if (invocationTarget == null) {
                //push a reference to 'this' onto the stack.
                invocationClassType = encodeThis(context);              
            } else {
                //push a reference to the invoking expression onto the stack
                invocationClassType = encodeExpr(invocationTarget, context);               
           
           
            // The MethodInvocation may contain an explicit invoking class type.  If it does
            // use it in preference to the type of the invocation target.
            if (mi instanceof MethodInvocation.Instance && ((MethodInvocation.Instance)mi).getDeclaringClass() != null) {
                invocationClassType = ((MethodInvocation.Instance)mi).getDeclaringClass();
            }           
           
            if (invocationType == MethodInvocation.InvocationType.VIRTUAL) {
                invocationCode = Opcodes.INVOKEVIRTUAL;
               
            } else if (invocationType == MethodInvocation.InvocationType.INTERFACE) {
                if (invocationClassType.isInterface()) {
                    invocationCode = Opcodes.INVOKEINTERFACE;
                } else {
                    //if we invoke an interface method on a reference that is known to be a Class, then we must use invoke virtual
                    //to avoid getting a IncompatibleClassChangeError.
                    invocationCode = Opcodes.INVOKEVIRTUAL;
                }
               
            } else if (invocationType == MethodInvocation.InvocationType.SPECIAL) {
                invocationCode = Opcodes.INVOKESPECIAL;
               
            } else {
                throw new JavaGenerationException("Unknown invocation type: " + invocationType);
            }           
           
        } else {           
            //static invocation. No object reference needs to be pushed.           
            invocationCode = Opcodes.INVOKESTATIC;          
            invocationClassType = ((MethodInvocation.Static)mi).getInvocationClass();                         
        }
       
        //push the arguments onto the stack
       
        for (int i = 0, nArgs = mi.getNArgs(); i < nArgs; ++i) {
            encodeExpr(mi.getArg(i), context);          
        }
       
        //invoke the method                           
        context.getMethodVisitor().visitMethodInsn(invocationCode, invocationClassType.getJVMInternalName(), mi.getMethodName(), mi.getJVMMethodDescriptor());
       
        return mi.getReturnType();
   
         
    /**
     * Create the Java code for a given cast expression. This will cause the resulting casted object reference to
     * be pushed onto the operand stack.
     * 
     * @param castExpression the cast expression
     * @param context 
     * @return JavaTypeName the type of the result on the operand stack.       
     * @throws JavaGenerationException
     */
    private static JavaTypeName encodeCastExpr(JavaExpression.CastExpression castExpression, GenerationContext context) throws JavaGenerationException {
       
        MethodVisitor mv = context.getMethodVisitor();
       
        final JavaTypeName expressionToCastType = encodeExpr(castExpression.getExpressionToCast(), context);       
        final JavaTypeName castType = castExpression.getCastType();
       
        if (expressionToCastType.equals(castType)) {
            //no operation needed if the types are the same.
            return castType;
        }
       
        if (castType instanceof JavaTypeName.Reference) {
            //when the cast type is a object or array type, use the CHECKCAST instruction. This will fail bytecode verification if
            //the expressionToCast type is a primitive type
         
            mv.visitTypeInsn(Opcodes.CHECKCAST, castType.getJVMInternalName());
            return castType;
        }                  
       
        //casting between primitive types.
        //There are 15 supported primitive conversions: I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, I2C, I2S
        //Depending upon the expressionToCastType and castType, choose the appropriate instruction.
       
        final int conversionOpCode;
        switch (expressionToCastType.getTag()) {
   
            case JavaTypeName.VOID_TAG:             
            case JavaTypeName.BOOLEAN_TAG:
                throw new JavaGenerationException("Unsupported primitive cast.");
               
            case JavaTypeName.BYTE_TAG:  
            {
                switch (castType.getTag()) {
       
                    case JavaTypeName.VOID_TAG:             
                    case JavaTypeName.BOOLEAN_TAG:                                                                                                         
                        throw new JavaGenerationException("Unsupported primitive cast.");                       
                   
                    case JavaTypeName.BYTE_TAG:                       
                        //should be handled above as a no-op.
                        throw new IllegalArgumentException();  
                   
                    case JavaTypeName.SHORT_TAG:
                        conversionOpCode = Opcodes.I2S;
                        break;
                   
                    case JavaTypeName.CHAR_TAG:                   
                        conversionOpCode = Opcodes.I2C;
                        break;                   
                   
                    case JavaTypeName.INT_TAG: 
                        //no-op
                        return castType;                
                   
                    case JavaTypeName.LONG_TAG:
                        conversionOpCode = Opcodes.I2L;
                        break;
                       
                    case JavaTypeName.DOUBLE_TAG:
                        conversionOpCode = Opcodes.I2D;
                        break;                       
                       
                    case JavaTypeName.FLOAT_TAG:
                        conversionOpCode = Opcodes.I2F;
                        break;
                       
                    case JavaTypeName.ARRAY_TAG:           
                    case JavaTypeName.OBJECT_TAG:
                        throw new JavaGenerationException("Cannot cast a primitive type to a reference type.");                                                      
                   
                    default:
                    {
                        throw new IllegalArgumentException();
                    }
                }
               
                break;
            }
               
            case JavaTypeName.SHORT_TAG:   
            {
                switch (castType.getTag()) {
       
                    case JavaTypeName.VOID_TAG:             
                    case JavaTypeName.BOOLEAN_TAG:                                                                                                         
                        throw new JavaGenerationException("Unsupported primitive cast.");                       
                   
                    case JavaTypeName.BYTE_TAG:
                        conversionOpCode = Opcodes.I2B;
                        break;   
                   
                    case JavaTypeName.SHORT_TAG:
                        //should be handled above as a no-op.
                        throw new IllegalArgumentException();
                   
                    case JavaTypeName.CHAR_TAG:                   
                        conversionOpCode = Opcodes.I2C;
                        break;                   
                   
                    case JavaTypeName.INT_TAG:   
                        //no-op
                        return castType;                
                   
                    case JavaTypeName.LONG_TAG:
                        conversionOpCode = Opcodes.I2L;
                        break;
                       
                    case JavaTypeName.DOUBLE_TAG:
                        conversionOpCode = Opcodes.I2D;
                        break;                       
                       
                    case JavaTypeName.FLOAT_TAG:
                        conversionOpCode = Opcodes.I2F;
                        break;
                       
                    case JavaTypeName.ARRAY_TAG:           
                    case JavaTypeName.OBJECT_TAG:
                        throw new JavaGenerationException("Cannot cast a primitive type to a reference type.");                                                      
                   
                    default:
                    {
                        throw new IllegalArgumentException();
                    }
                }
               
                break;
            }               
               
            case JavaTypeName.CHAR_TAG:
            {
                switch (castType.getTag()) {
       
                    case JavaTypeName.VOID_TAG:             
                    case JavaTypeName.BOOLEAN_TAG:                                                                                                         
                        throw new JavaGenerationException("Unsupported primitive cast.");                       
                   
                    case JavaTypeName.BYTE_TAG:
                        conversionOpCode = Opcodes.I2B;
                        break;   
                   
                    case JavaTypeName.SHORT_TAG:
                        conversionOpCode = Opcodes.I2S;
                        break;   
                   
                    case JavaTypeName.CHAR_TAG:                   
                        //should be handled above as a no-op.
                        throw new IllegalArgumentException();                   
                   
                    case JavaTypeName.INT_TAG:   
                        //no-op
                        return castType;                
                   
                    case JavaTypeName.LONG_TAG:
                        conversionOpCode = Opcodes.I2L;
                        break;
                       
                    case JavaTypeName.DOUBLE_TAG:
                        conversionOpCode = Opcodes.I2D;
                        break;                       
                       
                    case JavaTypeName.FLOAT_TAG:
                        conversionOpCode = Opcodes.I2F;
                        break;
                       
                    case JavaTypeName.ARRAY_TAG:           
                    case JavaTypeName.OBJECT_TAG:
                        throw new JavaGenerationException("Cannot cast a primitive type to a reference type.");                                                      
                   
                    default:
                    {
                        throw new IllegalArgumentException();
                    }
                }
               
                break;
            }               
               
               
            case JavaTypeName.INT_TAG:
            {
                switch (castType.getTag()) {
       
                    case JavaTypeName.VOID_TAG:             
                    case JavaTypeName.BOOLEAN_TAG:                                                                                                         
                        throw new JavaGenerationException("Unsupported primitive cast.");                       
                   
                    case JavaTypeName.BYTE_TAG:
                        conversionOpCode = Opcodes.I2B;
                        break;
                   
                    case JavaTypeName.SHORT_TAG:
                        conversionOpCode = Opcodes.I2S;
                        break;
                   
                    case JavaTypeName.CHAR_TAG:                   
                        conversionOpCode = Opcodes.I2C;
                        break;                   
                   
                    case JavaTypeName.INT_TAG:                   
                        //should be handled above as a no-op.
                        throw new IllegalArgumentException();                   
                   
                    case JavaTypeName.LONG_TAG:
                        conversionOpCode = Opcodes.I2L;
                        break;
                       
                    case JavaTypeName.DOUBLE_TAG:
                        conversionOpCode = Opcodes.I2D;
                        break;                       
                       
                    case JavaTypeName.FLOAT_TAG:
                        conversionOpCode = Opcodes.I2F;
                        break;
                       
                    case JavaTypeName.ARRAY_TAG:           
                    case JavaTypeName.OBJECT_TAG:
                        throw new JavaGenerationException("Cannot cast a primitive type to a reference type.");                                                      
                   
                    default:
                    {
                        throw new IllegalArgumentException();
                    }
                }
               
                break;
            }
               
            case JavaTypeName.LONG_TAG:
            {
                switch (castType.getTag()) {
       
                    case JavaTypeName.VOID_TAG:             
                    case JavaTypeName.BOOLEAN_TAG:
                        throw new JavaGenerationException("Unsupported primitive cast.");
                       
                    case JavaTypeName.BYTE_TAG: 
                        mv.visitInsn(Opcodes.L2I);
                        conversionOpCode = Opcodes.I2B;
                        break;
                       
                    case JavaTypeName.SHORT_TAG:
                        mv.visitInsn(Opcodes.L2I);
                        conversionOpCode = Opcodes.I2S;
                        break;
                       
                    case JavaTypeName.CHAR_TAG:   
                        mv.visitInsn(Opcodes.L2I);
                        conversionOpCode = Opcodes.I2C;
                        break;                  
                   
                    case JavaTypeName.INT_TAG:                   
                        conversionOpCode = Opcodes.L2I;
                        break;
                   
                    case JavaTypeName.LONG_TAG:
                        //should be handled above as a no-op.
                        throw new IllegalArgumentException();
                       
                    case JavaTypeName.DOUBLE_TAG:
                        conversionOpCode = Opcodes.L2D;
                        break;                       
                       
                    case JavaTypeName.FLOAT_TAG:
                        conversionOpCode = Opcodes.L2F;
                        break;
                       
                    case JavaTypeName.ARRAY_TAG:           
                    case JavaTypeName.OBJECT_TAG:
                        throw new JavaGenerationException("Cannot cast a primitive type to a reference type.");
                                     
                    default:
                    {
                        throw new IllegalArgumentException();
                    }
                }
               
                break;
            }
               
            case JavaTypeName.DOUBLE_TAG:
            {
                switch (castType.getTag()) {
       
                    case JavaTypeName.VOID_TAG:             
                    case JavaTypeName.BOOLEAN_TAG: 
                        throw new JavaGenerationException("Unsupported primitive cast.");
                       
                    case JavaTypeName.BYTE_TAG:
                        mv.visitInsn(Opcodes.D2I);
                        conversionOpCode = Opcodes.I2B;
                        break;
                       
                    case JavaTypeName.SHORT_TAG:
                        mv.visitInsn(Opcodes.D2I);
                        conversionOpCode = Opcodes.I2S;
                        break;
                       
                    case JavaTypeName.CHAR_TAG:                                          
                        mv.visitInsn(Opcodes.D2I);
                        conversionOpCode = Opcodes.I2C;
                        break;             
                   
                    case JavaTypeName.INT_TAG:                   
                        conversionOpCode = Opcodes.D2I;
                        break;
                   
                    case JavaTypeName.LONG_TAG:
                        conversionOpCode = Opcodes.D2L;
                        break;
                       
                    case JavaTypeName.DOUBLE_TAG:
                         //should be handled above as a no-op.
                        throw new IllegalArgumentException();                 
                       
                    case JavaTypeName.FLOAT_TAG:
                        conversionOpCode = Opcodes.D2F;
                        break;
                       
                    case JavaTypeName.ARRAY_TAG:           
                    case JavaTypeName.OBJECT_TAG:
                        throw new JavaGenerationException("Cannot cast a primitive type to a reference type.");                                                          
                   
                    default:
                    {
                        throw new IllegalArgumentException();
                    }
                }
               
                break;
            }
               
            case JavaTypeName.FLOAT_TAG:
            {
                switch (castType.getTag()) {
       
                    case JavaTypeName.VOID_TAG:             
                    case JavaTypeName.BOOLEAN_TAG:
                        throw new JavaGenerationException("Unsupported primitive cast.");
                       
                    case JavaTypeName.BYTE_TAG: 
                        mv.visitInsn(Opcodes.F2I);
                        conversionOpCode = Opcodes.I2B;
                        break;
                       
                    case JavaTypeName.SHORT_TAG: 
                        mv.visitInsn(Opcodes.F2I);
                        conversionOpCode = Opcodes.I2S;
                        break;
                       
                    case JavaTypeName.CHAR_TAG:                                          
                        mv.visitInsn(Opcodes.F2I);
                        conversionOpCode = Opcodes.I2C;
                        break;                     
                   
                    case JavaTypeName.INT_TAG:                   
                        conversionOpCode = Opcodes.F2I;
                        break;
                   
                    case JavaTypeName.LONG_TAG:
                        conversionOpCode = Opcodes.F2L;
                        break;
                       
                    case JavaTypeName.DOUBLE_TAG:
                        conversionOpCode = Opcodes.F2D;
                        break;                       
                       
                    case JavaTypeName.FLOAT_TAG:
                        //should be handled above as a no-op.
                        throw new IllegalArgumentException();
                       
                    case JavaTypeName.ARRAY_TAG:           
                    case JavaTypeName.OBJECT_TAG:
                        throw new JavaGenerationException("Cannot cast a primitive type to a reference type.");
                                  
                    default:
                    {
                        throw new IllegalArgumentException();
                    }
                }
               
                break;
            }
               
            case JavaTypeName.ARRAY_TAG:           
            case JavaTypeName.OBJECT_TAG:
                throw new JavaGenerationException("Cannot cast a reference type to a primitive type.");                                        
           
            default:
            {
                throw new IllegalArgumentException();
            }               
        }      
       
        mv.visitInsn(conversionOpCode);
        return castType;     
    }      
   
   
    /**
     * Creates and initializes a 1-dimensional array (as specified by arrayCreationExpr) and then pushes a reference
     * to the array onto the stack.
     *
     * @param arrayCreationExpr
     * @param context
     * @return JavaTypeName the type of the result on the operand stack.
     * @throws JavaGenerationException
     */
    private static JavaTypeName encodeArrayCreationExpr(JavaExpression.ArrayCreationExpression arrayCreationExpr, GenerationContext context) throws JavaGenerationException {
       
        MethodVisitor mv = context.getMethodVisitor();
        final int nArrayElements = arrayCreationExpr.getNElementValues();
               
        //push the n of elements of the array onto the stack.      
        encodePushIntValue(nArrayElements, context);       
                                    
        JavaTypeName arrayElementType = arrayCreationExpr.getArrayElementTypeName();
        switch (arrayElementType.getTag()) {    
          
            case JavaTypeName.VOID_TAG:
                throw new JavaGenerationException("Cannot have an array of with void element types.");
               
            case JavaTypeName.BOOLEAN_TAG:          
            case JavaTypeName.BYTE_TAG:                     
            case JavaTypeName.SHORT_TAG:          
            case JavaTypeName.CHAR_TAG:          
            case JavaTypeName.INT_TAG:          
            case JavaTypeName.LONG_TAG:          
            case JavaTypeName.DOUBLE_TAG:            
            case JavaTypeName.FLOAT_TAG:
            {                              
                //push the instruction to create a 1-dimensonal array of primitive values
                mv.visitIntInsn(Opcodes.NEWARRAY, getNewArrayArgCode(arrayElementType));   
                break;
            }
               
            case JavaTypeName.ARRAY_TAG:
            {
                throw new JavaGenerationException("JavaExpression.ArrayCreationExpression supports only 1 dimensional arrays.");               
            }
           
            case JavaTypeName.OBJECT_TAG:
            {                                                                                  
                //push the instruction to create a 1-dimensonal array of reference values
                mv.visitTypeInsn(Opcodes.ANEWARRAY, arrayElementType.getJVMInternalName());                  
                break;
            }                             
           
            default:
            {
                throw new IllegalArgumentException();
            }
        }
                   
        final int arrayElemStoreCode = getArrayStoreOpCode(arrayElementType);
       
        //now initialize the elements of the array
        for (int i = 0; i < nArrayElements; ++i) {                   
            //duplicate the array reference on the stack
            mv.visitInsn(Opcodes.DUP);
           
            //push i
            encodePushIntValue(i, context);
           
            //push the code to evalaute the ith element expression
            encodeExpr(arrayCreationExpr.getElementValue(i), context);
           
            //array[i] = elementExpr          
            mv.visitInsn(arrayElemStoreCode);                   
        }       
                               
        return arrayElementType.makeArrayType();
    }
 
    /**
     * Create the Java code for a given class instance creation expression. The new object reference will be pushed onto
     * the operand stack.
     *
     * @param cice the class instance creation expression
     * @param context
     * @return JavaTypeName   
     * @throws JavaGenerationException
     */
    private static JavaTypeName encodeClassInstanceCreationExpr(final JavaExpression.ClassInstanceCreationExpression cice, final GenerationContext context) throws JavaGenerationException {
       
        final MethodVisitor mv = context.getMethodVisitor();
        final JavaTypeName classType = cice.getClassName();       
        if (classType instanceof JavaTypeName.Reference.Array) {
           
            final JavaTypeName.Reference.Array arrayType = (JavaTypeName.Reference.Array)classType;
            final int nSizedDims = cice.getNArgs();
            if (nSizedDims == 1) {
               
                //for example, new String[10][][] will hit this case since it has 1 sized dimension (even though a multi-dimensional array is
                //being created
               
                //push the size of the dimension
                encodeExpr(cice.getArg(0), context);
                               
                final JavaTypeName arrayElementType = arrayType.getIncrementalElementType();
                switch (arrayElementType.getTag()) {    
                  
                    case JavaTypeName.VOID_TAG:
                        throw new JavaGenerationException("Cannot have an array of with void element types.");
                       
                    case JavaTypeName.BOOLEAN_TAG:                   
                    case JavaTypeName.BYTE_TAG:                             
                    case JavaTypeName.SHORT_TAG:                  
                    case JavaTypeName.CHAR_TAG:                  
                    case JavaTypeName.INT_TAG:                  
                    case JavaTypeName.LONG_TAG:                
                    case JavaTypeName.DOUBLE_TAG:                   
                    case JavaTypeName.FLOAT_TAG:
                    {                      
                        //push the instruction to create a 1-dimensonal array of primitive values
                        mv.visitIntInsn(Opcodes.NEWARRAY, getNewArrayArgCode(arrayElementType));                          
                        break;
                    }
                                               
                    case JavaTypeName.ARRAY_TAG:                  
                    case JavaTypeName.OBJECT_TAG:
                    {                                                             
                        //push the instruction to create a 1-dimensonal array of reference values
                        mv.visitTypeInsn(Opcodes.ANEWARRAY, arrayElementType.getJVMInternalName());                       
                        break;
                    }                                              
                   
                    default:
                    {
                        throw new IllegalArgumentException();
                    }
                }               
               
                return arrayType;
               
            } else {
                //the case of multi-dimensional arrays where more than 1 sizing dimension is supplied.
                               
                // push args onto the stack
                for (int i = 0; i < nSizedDims; i++) {
                    encodeExpr(cice.getArg(i), context);              
                }
               
                mv.visitMultiANewArrayInsn(arrayType.getJVMInternalName(), nSizedDims);
               
                return arrayType;
            }
                                                                    
        } else if (classType instanceof JavaTypeName.Reference.Object) {
           
            String internalClassName = classType.getJVMInternalName();
           
            // create uninitialized object, duplicate the ref.
            mv.visitTypeInsn(Opcodes.NEW, internalClassName);
            mv.visitInsn(Opcodes.DUP);
           
             // push args onto the stack
            for (int i = 0, nArgs = cice.getNArgs(); i < nArgs; i++) {
                encodeExpr(cice.getArg(i), context);              
            }
           
            //descriptor for the constructor
            StringBuilder descriptor = new StringBuilder("(");
            for (int i = 0, nArgs = cice.getNArgs(); i < nArgs; ++i) {
                descriptor.append(cice.getParamType(i).getJVMDescriptor());
            }
            descriptor.append(")V");
           
            // initialize - consumes the args and the duplicate reference.
            mv.visitMethodInsn(Opcodes.INVOKESPECIAL, internalClassName, "<init>", descriptor.toString());
           
            return classType;
           
        } else {
            throw new JavaGenerationException("cannot create a new instance of a primitive type.");
        }             
    }      
   
    /**
     * Create the Java code for a given instanceof expression, pushing the result onto the operand stack.
     *  
     * @param instanceOf the instanceof expression
     * @param context
     * @return JavaTypeName the type of the result on the operand stack.
     * @throws JavaGenerationException              
     */
    private static JavaTypeName encodeInstanceOfExpr(JavaExpression.InstanceOf instanceOf, GenerationContext context) throws JavaGenerationException {
       
        //push the expression to test onto the operand stack
        encodeExpr(instanceOf.getJavaExpression(), context);
       
        //endcode the INSTANCEOF instruction
        context.getMethodVisitor().visitTypeInsn(Opcodes.INSTANCEOF, instanceOf.getReferenceType().getJVMInternalName());
       
        return JavaTypeName.BOOLEAN;
    }   

    /**
     * Creates the Java code for a given class literal expression, pushing the result onto the operand stack.
     * @param classLiteral the class literal expression.
     * @param context the generation context.
     * @return the type of the result on the operand stack.
     */
    private static JavaTypeName encodeClassLiteralExpr(ClassLiteral classLiteral, GenerationContext context) {

        final JavaTypeName referentType = classLiteral.getReferentType();

        final MethodVisitor methodVisitor = context.getMethodVisitor();
       
        switch (referentType.getTag()) {

        // The primitive types (and void) are handled specially by javac - it generates code to access the static TYPE field
        // in the corresponding boxed type.
        case JavaTypeName.VOID_TAG:
            methodVisitor.visitFieldInsn(Opcodes.GETSTATIC,
                JavaTypeName.VOID_OBJECT.getJVMInternalName(), "TYPE", JavaTypeName.CLASS.getJVMDescriptor());
            break;

        case JavaTypeName.BOOLEAN_TAG:
            methodVisitor.visitFieldInsn(Opcodes.GETSTATIC,
                JavaTypeName.BOOLEAN_OBJECT.getJVMInternalName(), "TYPE", JavaTypeName.CLASS.getJVMDescriptor());
            break;

        case JavaTypeName.BYTE_TAG:
            methodVisitor.visitFieldInsn(Opcodes.GETSTATIC,
                JavaTypeName.BYTE_OBJECT.getJVMInternalName(), "TYPE", JavaTypeName.CLASS.getJVMDescriptor());
            break;

        case JavaTypeName.SHORT_TAG:
            methodVisitor.visitFieldInsn(Opcodes.GETSTATIC,
                JavaTypeName.SHORT_OBJECT.getJVMInternalName(), "TYPE", JavaTypeName.CLASS.getJVMDescriptor());
            break;

        case JavaTypeName.CHAR_TAG:
            methodVisitor.visitFieldInsn(Opcodes.GETSTATIC,
                JavaTypeName.CHARACTER_OBJECT.getJVMInternalName(), "TYPE", JavaTypeName.CLASS.getJVMDescriptor());
            break;

        case JavaTypeName.INT_TAG:
            methodVisitor.visitFieldInsn(Opcodes.GETSTATIC,
                JavaTypeName.INTEGER_OBJECT.getJVMInternalName(), "TYPE", JavaTypeName.CLASS.getJVMDescriptor());
            break;

        case JavaTypeName.LONG_TAG:
            methodVisitor.visitFieldInsn(Opcodes.GETSTATIC,
                JavaTypeName.LONG_OBJECT.getJVMInternalName(), "TYPE", JavaTypeName.CLASS.getJVMDescriptor());
            break;

        case JavaTypeName.DOUBLE_TAG:
            methodVisitor.visitFieldInsn(Opcodes.GETSTATIC,
                JavaTypeName.DOUBLE_OBJECT.getJVMInternalName(), "TYPE", JavaTypeName.CLASS.getJVMDescriptor());
            break;

        case JavaTypeName.FLOAT_TAG:
            methodVisitor.visitFieldInsn(Opcodes.GETSTATIC,
                JavaTypeName.FLOAT_OBJECT.getJVMInternalName(), "TYPE", JavaTypeName.CLASS.getJVMDescriptor());
            break;

        case JavaTypeName.ARRAY_TAG:          
        case JavaTypeName.OBJECT_TAG:
            // For array and reference types, we generate the Class constant via Java 5's upgraded ldc opcode
            methodVisitor.visitLdcInsn(Type.getType(referentType.getJVMDescriptor()));
            break;

        default:
            throw new IllegalArgumentException("Unrecognized java type: " + referentType);
        }
       
        return JavaTypeName.CLASS;
    }

    /**
     * Get the Java code for a given literal wrapper.
     *   The returned instruction list will cause the expression result to be pushed onto the stack.
     * @param literalWrapper the literal wrapper
     * @param context the generation context
     * @return JavaTypeName the type of the result on the operand stack.
     * @throws JavaGenerationException
     */
    private static JavaTypeName encodeLiteralExpr(JavaExpression.LiteralWrapper literalWrapper, GenerationContext context) throws JavaGenerationException {
      
        // Integer, Double, String, Character, Boolean, Byte, Short, Float, Long, null;       
        //note that byte, short, char, and boolean are implicitly converted and pushed as int values on the stack.
       
        Object literalObject = literalWrapper.getLiteralObject();
        if (literalObject instanceof Integer) {
            return encodePushIntegerValue((Integer)literalObject, context);          

        } else if (literalObject instanceof Boolean) {
            return encodePushBooleanValue((Boolean)literalObject, context);          

        } else if (literalObject instanceof Double) {
            return encodePushDoubleValue((Double)literalObject, context);           

        } else if (literalObject instanceof String) {
            return encodePushStringValue((String)literalObject, context);           

        } else if (literalObject instanceof Character) {
            encodePushIntValue(((Character)literalObject).charValue(), context);
            return JavaTypeName.CHAR;

        } else if (literalObject instanceof Long) {           
            return encodePushLongValue((Long)literalObject, context);          

        } else if (literalObject instanceof Byte) {
            encodePushIntValue(((Byte)literalObject).byteValue(), context);
            return JavaTypeName.BYTE;

        } else if (literalObject instanceof Short) {
            encodePushIntValue(((Short)literalObject).shortValue(), context);
            return JavaTypeName.SHORT;

        } else if (literalObject instanceof Float) {
            return encodePushFloatValue((Float)literalObject, context);          

        } else if (literalObject == null) {
            return encodePushNullValue(context);         

        } else {
            throw new JavaGenerationException("Unrecognized literal type: " + literalObject.getClass());
        }
    }
   
    /**
     * Encodes instructions to push an int value onto the operand stack.
     * Does not reallocate the wrapper argument 'value'.
     * @param value to be pushed
     * @param context
     * @return JavaTypeName the type of the result on the operand stack.     
     */
    private static JavaTypeName encodePushIntegerValue(Integer value, GenerationContext context) {
       
        MethodVisitor mv = context.getMethodVisitor();
       
        int v = value.intValue();
       
        if (v >= -1 && v <= 5) {         
            // Use ICONST_n
            mv.visitInsn(Opcodes.ICONST_0 + v);
           
        } else if (v >= Byte.MIN_VALUE && v <= Byte.MAX_VALUE) {         
            // Use BIPUSH
            mv.visitIntInsn(Opcodes.BIPUSH, (byte)v);
           
        } else if (v >= Short.MIN_VALUE && v <= Short.MAX_VALUE) {         
            // Use SIPUSH
            mv.visitIntInsn(Opcodes.SIPUSH, (short)v);
           
        } else {         
            // If everything fails create a Constant pool entry
            mv.visitLdcInsn(value);        
        }
       
        return JavaTypeName.INT;
    }
   
    /**
     * Encodes instructions to push an int value onto the operand stack.
     * May need to box the argument 'value' if it is sufficiently large that
     * it needs to go into the constant pool.
     * @param value to be pushed
     * @param context
     * @return JavaTypeName the type of the result on the operand stack.
     */
    private static JavaTypeName encodePushIntValue(int value, GenerationContext context) {
       
        MethodVisitor mv = context.getMethodVisitor();
       
        if (value >= -1 && value <= 5) {         
            // Use ICONST_n
            mv.visitInsn(Opcodes.ICONST_0 + value);
           
        } else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {         
            // Use BIPUSH
            mv.visitIntInsn(Opcodes.BIPUSH, (byte)value);
           
        } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {         
            // Use SIPUSH
            mv.visitIntInsn(Opcodes.SIPUSH, (short)value);
           
        } else {         
            // If everything fails create a Constant pool entry
            mv.visitLdcInsn(Integer.valueOf(value));        
        }
       
        return JavaTypeName.INT;
    }   
   
    /**
     * Encodes instructions to push a boolean value onto the operand stack.
     * @param value to be pushed   
     * @param context
     * @return JavaTypeName the type of the result on the operand stack.     
     */
    private static JavaTypeName encodePushBooleanValue(Boolean value, GenerationContext context) {
        MethodVisitor mv = context.getMethodVisitor();
       
        boolean v = value.booleanValue();
        if (v) {
            mv.visitInsn(Opcodes.ICONST_1);
        } else {
            mv.visitInsn(Opcodes.ICONST_0);
        }
               
        return JavaTypeName.BOOLEAN;
    }
   
    /**
     * Encodes instructions to push a float value onto the operand stack.
     * @param value to be pushed
     * @param context
     * @return JavaTypeName the type of the result on the operand stack.       
     */   
    private static JavaTypeName encodePushFloatValue(Float value, GenerationContext context) {
       
        MethodVisitor mv = context.getMethodVisitor();
       
        float v = value.floatValue();
       
        if (v == 0.0) {
            mv.visitInsn(Opcodes.FCONST_0);
           
        } else if (v == 1.0) {
            mv.visitInsn(Opcodes.FCONST_1);
           
        } else if (v == 2.0) {
            mv.visitInsn(Opcodes.FCONST_2);
           
        } else {
            //Create a Constant pool entry
            mv.visitLdcInsn(value);
        }
       
        return JavaTypeName.FLOAT;
    }
   
    /**
     * Encodes instructions to push a long value onto the operand stack.
     * @param value to be pushed
     * @param context
     * @return JavaTypeName the type of the result on the operand stack.     
     */   
    private static JavaTypeName encodePushLongValue(Long value, GenerationContext context) {
       
        MethodVisitor mv = context.getMethodVisitor();
       
        long v = value.longValue();
       
        if (v == 0) {
            mv.visitInsn(Opcodes.LCONST_0);
           
        } else if (v == 1) {
            mv.visitInsn(Opcodes.LCONST_1);
           
        } else {
            // Create a Constant pool entry
            mv.visitLdcInsn(value);           
        }
       
        return JavaTypeName.LONG;
    }
   
    /**
     * Encodes instructions to push a double value onto the operand stack.
     * @param value to be pushed
     * @param context
     * @return JavaTypeName the type of the result on the operand stack.    
     */   
    private static JavaTypeName encodePushDoubleValue(Double value, GenerationContext context) {
       
        MethodVisitor mv = context.getMethodVisitor();

        double v = value.doubleValue();

        if (v == 0.0) {
            mv.visitInsn(Opcodes.DCONST_0);          
           
        } else if (v == 1.0) {
            mv.visitInsn(Opcodes.DCONST_1);        
           
        } else {
            // Create a Constant pool entry
            mv.visitLdcInsn(value);          
        }
       
        return JavaTypeName.DOUBLE;
    }
   
    /**
     * Encodes instructions to push a String value onto the operand stack.
     * @param value to be pushed. Must not be null.
     * @param context
     * @return JavaTypeName the type of the result on the operand stack.         
     */   
    private static JavaTypeName encodePushStringValue(String value, GenerationContext context) {
       
        MethodVisitor mv = context.getMethodVisitor();
       
        // Create a Constant pool entry
        mv.visitLdcInsn(value);
       
        return JavaTypeName.STRING;
    }
   
    /**
     * Encodes instructions to push a null object reference onto the operand stack.
     * @param context
     * @return JavaTypeName the type of the result on the operand stack.
     */    
    private static JavaTypeName encodePushNullValue(GenerationContext context) {
        MethodVisitor mv = context.getMethodVisitor();
        mv.visitInsn(Opcodes.ACONST_NULL);
       
        return JavaTypeName.OBJECT;
    }
   
    /**
     * @param elemType element type of the array.
     * @return java op-code to use for loading arrays with elements of the specified type.
     */
    private static int getArrayLoadOpCode(JavaTypeName elemType) {
       
        switch (elemType.getTag()) {
          
            case JavaTypeName.VOID_TAG:
                throw new IllegalArgumentException();               
               
            case JavaTypeName.BOOLEAN_TAG:              
            case JavaTypeName.BYTE_TAG:
                return Opcodes.BALOAD;
               
            case JavaTypeName.SHORT_TAG:
                return Opcodes.SALOAD;
               
            case JavaTypeName.CHAR_TAG:
                return Opcodes.CALOAD;
               
            case JavaTypeName.INT_TAG:
                return Opcodes.IALOAD;
               
            case JavaTypeName.LONG_TAG:
                return Opcodes.LALOAD;
               
            case JavaTypeName.DOUBLE_TAG:
                return Opcodes.DALOAD;
               
            case JavaTypeName.FLOAT_TAG:
                return Opcodes.FALOAD;
               
            case JavaTypeName.ARRAY_TAG:          
            case JavaTypeName.OBJECT_TAG:                                             
                return Opcodes.AALOAD;
                             
            default:
            {
                throw new IllegalArgumentException();
            }
        }           
    }
   
    /**
     * @param elemType element type of the array.
     * @return java op-code to use for storing into arrays with elements of the specified type.
     */   
    private static int getArrayStoreOpCode(JavaTypeName elemType) {
       
        switch (elemType.getTag()) {
          
            case JavaTypeName.VOID_TAG:
                throw new IllegalArgumentException();               
               
            case JavaTypeName.BOOLEAN_TAG:              
            case JavaTypeName.BYTE_TAG:
                return Opcodes.BASTORE;
               
            case JavaTypeName.SHORT_TAG:
                return Opcodes.SASTORE;
               
            case JavaTypeName.CHAR_TAG:
                return Opcodes.CASTORE;
               
            case JavaTypeName.INT_TAG:
                return Opcodes.IASTORE;
               
            case JavaTypeName.LONG_TAG:
                return Opcodes.LASTORE;
               
            case JavaTypeName.DOUBLE_TAG:
                return Opcodes.DASTORE;
               
            case JavaTypeName.FLOAT_TAG:
                return Opcodes.FASTORE;
               
            case JavaTypeName.ARRAY_TAG:          
            case JavaTypeName.OBJECT_TAG:                                             
                return Opcodes.AASTORE;
                          
            default:
            {
                throw new IllegalArgumentException();
            }
        }           
    }
   
    /**
     * @param elemType element type of the array. Must be a primitive type.
     * @return java op-code to use as the argument for the NEWARRAY op.
     */   
    private static int getNewArrayArgCode(JavaTypeName elemType) {
       
        switch (elemType.getTag()) {
          
            case JavaTypeName.VOID_TAG:
                throw new IllegalArgumentException();               
               
            case JavaTypeName.BOOLEAN_TAG:
                return Opcodes.T_BOOLEAN;
           
            case JavaTypeName.BYTE_TAG:
                return Opcodes.T_BYTE;
               
            case JavaTypeName.SHORT_TAG:
                return Opcodes.T_SHORT;
               
            case JavaTypeName.CHAR_TAG:
                return Opcodes.T_CHAR;
               
            case JavaTypeName.INT_TAG:
                return Opcodes.T_INT;
               
            case JavaTypeName.LONG_TAG:
                return Opcodes.T_LONG;
               
            case JavaTypeName.DOUBLE_TAG:
                return Opcodes.T_DOUBLE;
               
            case JavaTypeName.FLOAT_TAG:
                return Opcodes.T_FLOAT;
               
            case JavaTypeName.ARRAY_TAG:          
            case JavaTypeName.OBJECT_TAG:                                                                     
            default:
            {
                throw new IllegalArgumentException();
            }
        }           
    }
   
    /**
     * @param elemType element type to negate
     * @return java op-code to use for numerical negation.
     */   
    private static int getNegateOpCode(JavaTypeName elemType) {
       
        switch (elemType.getTag()) {
          
            case JavaTypeName.VOID_TAG:             
            case JavaTypeName.BOOLEAN_TAG:              
            case JavaTypeName.BYTE_TAG:              
            case JavaTypeName.SHORT_TAG:             
            case JavaTypeName.CHAR_TAG:
                throw new IllegalArgumentException();
               
            case JavaTypeName.INT_TAG:
                return Opcodes.INEG;
               
            case JavaTypeName.LONG_TAG:
                return Opcodes.LNEG;
               
            case JavaTypeName.DOUBLE_TAG:
                return Opcodes.DNEG;
               
            case JavaTypeName.FLOAT_TAG:
                return Opcodes.FNEG;
               
            case JavaTypeName.ARRAY_TAG:          
            case JavaTypeName.OBJECT_TAG:                                                                     
            default:
            {
                throw new IllegalArgumentException();
            }
        }           
    }
   
    private static final int getBinaryIntOpCode(String op) {
        switch(op.charAt(0)) {
            case '-' : return Opcodes.ISUB;
            case '+' : return Opcodes.IADD;
            case '%' : return Opcodes.IREM;
            case '*' : return Opcodes.IMUL;
            case '/' : return Opcodes.IDIV;
            case '&' : return Opcodes.IAND;
            case '|' : return Opcodes.IOR;
            case '^' : return Opcodes.IXOR;
            case '<' : return Opcodes.ISHL;
            case '>' : return op.equals(">>>")? Opcodes.IUSHR : Opcodes.ISHR;
            default: throw new IllegalArgumentException("Invalid operand " + op);
        }
    }

    private static final int getBinaryLongOpCode(String op) {
        switch(op.charAt(0)) {
            case '-' : return Opcodes.LSUB;
            case '+' : return Opcodes.LADD;
            case '%' : return Opcodes.LREM;
            case '*' : return Opcodes.LMUL;
            case '/' : return Opcodes.LDIV;
            case '&' : return Opcodes.LAND;
            case '|' : return Opcodes.LOR;
            case '^' : return Opcodes.LXOR;
            case '<' : return Opcodes.LSHL;
            case '>' : return op.equals(">>>")? Opcodes.LUSHR : Opcodes.LSHR;
            default: throw new IllegalArgumentException("Invalid operand " + op);
        }
    }

    private static final int getBinaryFloatOpCode(String op) {         
        switch(op.charAt(0)) {
            case '-' : return Opcodes.FSUB;
            case '+' : return Opcodes.FADD;
            case '*' : return Opcodes.FMUL;
            case '/' : return Opcodes.FDIV;
            case '%' : return Opcodes.FREM;
            default: throw new IllegalArgumentException("Invalid operand " + op);
        }
    }

    private static final int getBinaryDoubleOpCode(String op) {
        switch(op.charAt(0)) {
            case '-' : return Opcodes.DSUB;
            case '+' : return Opcodes.DADD;
            case '*' : return Opcodes.DMUL;
            case '/' : return Opcodes.DDIV;
            case '%' : return Opcodes.DREM;
            default: throw new IllegalArgumentException("Invalid operand " + op);
        }
    }

    /**
     * Gets the constant representing the Java op code for the binary numeric operators
     *
     * @param op operation, such as "+", "*", "<<", etc.
     * @param type result type of the operation
     * @return java op code, as defined in org.objectweb.asm.Opcodes.
     */
    private static int getArithmeticBinaryOpCode(String op, JavaTypeName type) {
       
        switch (type.getTag()) {
       
            case JavaTypeName.VOID_TAG:
            case JavaTypeName.BOOLEAN_TAG:     
                throw new IllegalArgumentException("Invalid type for getNumericBinaryOpCode" + type);
           
            case JavaTypeName.BYTE_TAG:              
            case JavaTypeName.SHORT_TAG:             
            case JavaTypeName.CHAR_TAG:                            
            case JavaTypeName.INT_TAG:
                return getBinaryIntOpCode(op);
           
            case JavaTypeName.LONG_TAG:
                return getBinaryLongOpCode(op);
           
            case JavaTypeName.DOUBLE_TAG:
                return getBinaryDoubleOpCode(op);
           
            case JavaTypeName.FLOAT_TAG:
                return getBinaryFloatOpCode(op);
           
            case JavaTypeName.ARRAY_TAG:          
            case JavaTypeName.OBJECT_TAG:                                                                     
            default:
            {
                throw new IllegalArgumentException("Invalid type for getNumericBinaryOpCode" + type);
            }
        }                      
    }
   
    /**
     * Determine whether the given type is handled internally (by the JVM) as an int.
     * @param type the type in question.
     * @return whether the type is handled internally as an int.
     */
    private static boolean isInternalIntType(JavaTypeName type) {
        //note that boolean is not an internal int type.       
        switch (type.getTag()) {                 
            case JavaTypeName.BYTE_TAG:              
            case JavaTypeName.SHORT_TAG:             
            case JavaTypeName.CHAR_TAG:                            
            case JavaTypeName.INT_TAG:
                return true;
                                                                                      
            default:
                return false;
        }                                 
    }   
   
    /**
     * Gets the op-code to widen a value of a given type to a value of another type.
     * @param typeToWiden the type to widen.
     * @param valueType the type to which the typeToWiden should be widened.
     * @return int the widening op code, as defined in org.objectweb.asm.Opcodes. Opcodes.NOP us used for the no-op.    
     */
    private static int getWideningOpCode(JavaTypeName typeToWiden, JavaTypeName valueType) {

        if (typeToWiden.equals(valueType)) {
            return Opcodes.NOP;
        }
           
        // Widen from int-type values -> float, long, double
        if (isInternalIntType(typeToWiden)) {
           
            switch (valueType.getTag()) {
                case JavaTypeName.BYTE_TAG:              
                case JavaTypeName.SHORT_TAG:             
                case JavaTypeName.CHAR_TAG:                            
                case JavaTypeName.INT_TAG:
                    return Opcodes.NOP;
           
                case JavaTypeName.LONG_TAG:
                    return Opcodes.I2L;
               
                case JavaTypeName.DOUBLE_TAG:
                    return Opcodes.I2D;
               
                case JavaTypeName.FLOAT_TAG:
                    return Opcodes.I2F;    
              
                default:
                    throw new IllegalArgumentException("Invalid widening conversion.");               
            }
                          
        // Widen from long -> float, double
        } else if (typeToWiden.equals(JavaTypeName.LONG)) {
           
            switch (valueType.getTag()) {
              
                case JavaTypeName.DOUBLE_TAG:
                    return Opcodes.L2D;
               
                case JavaTypeName.FLOAT_TAG:
                    return Opcodes.L2F;    
              
                default:
                    throw new IllegalArgumentException("Invalid widening conversion.");               
            }
           
        // Widen from float -> double
        } else if (typeToWiden.equals(JavaTypeName.FLOAT)) {

            if (valueType.equals(JavaTypeName.DOUBLE)) {
                return Opcodes.F2D;
            }
           
            throw new IllegalArgumentException("Invalid widening conversion.");
        }
           
        //throw new IllegalArgumentException("Invalid widening conversion.");
        return Opcodes.NOP;
   
   
    /**    
     * @param type
     * @return number of slots required for a variable of the given type (1 or 2)
     */
    private static int getTypeSize(JavaTypeName type) {
        switch (type.getTag()) {
       
            case JavaTypeName.BOOLEAN_TAG:                      
            case JavaTypeName.BYTE_TAG:              
            case JavaTypeName.SHORT_TAG:             
            case JavaTypeName.CHAR_TAG:                            
            case JavaTypeName.INT_TAG:
            case JavaTypeName.FLOAT_TAG:             
            case JavaTypeName.ARRAY_TAG:          
            case JavaTypeName.OBJECT_TAG:    
                return 1;
           
            case JavaTypeName.LONG_TAG:                
            case JavaTypeName.DOUBLE_TAG:
                return 2;
           
            case JavaTypeName.VOID_TAG:
            default:
            {
                throw new IllegalArgumentException();
            }
        }
    } 
   
    /**
     * Determine if a class rep or any inner classes contains any assert statements.
     * @param classRep
     * @return one or a combination of the constants: ASSERTS_UNKNOWN, NO_ASSERTS, ASSERTS_IN_CLASS, ASSERTS_IN_INNER_CLASS
     *         from JavaClassRep
     */
    private static int containsAsserts (JavaClassRep classRep) {
        final class AssertFinder extends JavaModelTraverser<Void, Void> {
            boolean classContainsAsserts = false;
            boolean innerClassContainsAsserts = false;
            boolean inInnerClass = false;
           
            @Override
            public Void visitAssertStatement(AssertStatement assertStatement,
                    Void arg) {
               
                setContinueTraversal(false);
               
                if (inInnerClass) {
                    innerClassContainsAsserts = true;
                } else {
                    classContainsAsserts = true;
                }
                return null;
            }
            @Override
            public Void visitJavaClassRep(JavaClassRep classRep, Void arg) {

                for (int i = 0, n = classRep.getNFieldDeclarations(); i < n && getContinueTraversal(); ++i) {
                    JavaFieldDeclaration fieldDeclaration = classRep.getFieldDeclaration(i);
                    fieldDeclaration.accept(this, arg);
                }
               
                for (int i = 0, n = classRep.getNConstructors(); i < n && getContinueTraversal(); ++i) {
                    JavaConstructor javaConstructor = classRep.getConstructor(i);
                    javaConstructor.accept(this, arg);
                }
               
                for (int i = 0, n = classRep.getNMethods(); i < n && getContinueTraversal(); ++i) {
                    JavaMethod method = classRep.getMethod(i);
                    method.accept(this, arg);
                }
               
                inInnerClass = true;
                setContinueTraversal(true);
                for (int i = 0, n = classRep.getNInnerClasses(); i < n && getContinueTraversal(); ++i) {
                    JavaClassRep innerClass = classRep.getInnerClass(i);
                    innerClass.accept(this, arg);
                    setContinueTraversal(true);
                }
                return null;
            }
           
        }
       
        AssertFinder assertFinder = new AssertFinder();
        assertFinder.visitJavaClassRep(classRep, null);
        if (assertFinder.classContainsAsserts && assertFinder.innerClassContainsAsserts) {
            return JavaClassRep.ASSERTS_IN_CLASS | JavaClassRep.ASSERTS_IN_INNER_CLASS;
        } else if (assertFinder.classContainsAsserts) {
            return JavaClassRep.ASSERTS_IN_CLASS;
        } else if (assertFinder.innerClassContainsAsserts) {
            return JavaClassRep.ASSERTS_IN_INNER_CLASS;
        } else {
            return JavaClassRep.ASSERTS_NONE;
        }
    }
}
TOP

Related Classes of org.openquark.cal.internal.javamodel.AsmJavaBytecodeGenerator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.