Package org.codehaus.groovy.classgen

Source Code of org.codehaus.groovy.classgen.AsmClassGenerator

/*
* Copyright 2003-2010 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.codehaus.groovy.classgen;

import java.lang.reflect.Modifier;
import groovy.lang.GroovyRuntimeException;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.ast.*;
import org.codehaus.groovy.ast.expr.*;
import org.codehaus.groovy.ast.stmt.*;
import org.codehaus.groovy.classgen.CompileStack.BlockRecorder;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.Janitor;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.runtime.MetaClassHelper;
import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;
import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
import org.codehaus.groovy.syntax.RuntimeParserException;
import org.codehaus.groovy.syntax.Token;
import org.codehaus.groovy.syntax.Types;
import org.codehaus.groovy.syntax.SyntaxException;
import org.codehaus.groovy.transform.powerassert.SourceText;
import org.codehaus.groovy.transform.powerassert.SourceTextNotAvailableException;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.*;

import java.util.*;

/**
* Generates Java class versions of Groovy classes using ASM.
*
* @author <a href="mailto:james@coredevelopers.net">James Strachan</a>
* @author <a href="mailto:b55r@sina.com">Bing Ran</a>
* @author <a href="mailto:blackdrag@gmx.org">Jochen Theodorou</a>
* @author <a href='mailto:the[dot]mindstorm[at]gmail[dot]com'>Alex Popescu</a>
* @author Alex Tkachman
* @version $Revision$
*/
public class AsmClassGenerator extends ClassGenerator {

    private static class AssertionTracker {
        int recorderIndex;
        SourceText sourceText;
    }

    private AssertionTracker assertionTracker;
    private final ClassVisitor cv;
    private MethodVisitor mv;
    private GeneratorContext context;

    private String sourceFile;

    // current class details
    private ClassNode classNode;
    private ClassNode outermostClass;
    private String internalClassName;
    private String internalBaseClassName;

    /*
     * maps the variable names to the JVM indices
     */
    private CompileStack compileStack;

    /*
     * have we output a return statement yet
     */
    private boolean outputReturn;

    /*
     * Are we on the left or right of an expression?
     *
     * The default is false, that means the right side is default.
     * The right side means that variables are read and not written.
     * Any change of leftHandExpression to true, should be made carefully.
     * If such a change is needed, then it should be set to false as soon as
     * possible, but most important in the same method. Setting
     * leftHandExpression to false is needed for writing variables.
     */
    private boolean leftHandExpression = false;

    // method invocation
    static final MethodCallerMultiAdapter invokeMethodOnCurrent = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "invokeMethodOnCurrent", true, false);
    static final MethodCallerMultiAdapter invokeMethodOnSuper = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "invokeMethodOnSuper", true, false);
    static final MethodCallerMultiAdapter invokeMethod = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "invokeMethod", true, false);
    static final MethodCallerMultiAdapter invokeStaticMethod = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "invokeStaticMethod", true, true);
    static final MethodCallerMultiAdapter invokeNew = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "invokeNew", true, true);

    // fields and properties
    static final MethodCallerMultiAdapter setField = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "setField", false, false);
    static final MethodCallerMultiAdapter getField = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "getField", false, false);
    static final MethodCallerMultiAdapter setGroovyObjectField = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "setGroovyObjectField", false, false);
    static final MethodCallerMultiAdapter getGroovyObjectField = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "getGroovyObjectField", false, false);
    static final MethodCallerMultiAdapter setFieldOnSuper = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "setFieldOnSuper", false, false);
    static final MethodCallerMultiAdapter getFieldOnSuper = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "getFieldOnSuper", false, false);

    static final MethodCallerMultiAdapter setProperty = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "setProperty", false, false);
    static final MethodCallerMultiAdapter getProperty = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "getProperty", false, false);
    static final MethodCallerMultiAdapter setGroovyObjectProperty = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "setGroovyObjectProperty", false, false);
    static final MethodCallerMultiAdapter getGroovyObjectProperty = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "getGroovyObjectProperty", false, false);
    static final MethodCallerMultiAdapter setPropertyOnSuper = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "setPropertyOnSuper", false, false);
    static final MethodCallerMultiAdapter getPropertyOnSuper = MethodCallerMultiAdapter.newStatic(ScriptBytecodeAdapter.class, "getPropertyOnSuper", false, false);

    // iterator
    static final MethodCaller iteratorNextMethod = MethodCaller.newInterface(Iterator.class, "next");
    static final MethodCaller iteratorHasNextMethod = MethodCaller.newInterface(Iterator.class, "hasNext");
    // assert
    static final MethodCaller assertFailedMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "assertFailed");
    // isCase
    static final MethodCaller isCaseMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "isCase");
    //compare
    static final MethodCaller compareIdenticalMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareIdentical");
    static final MethodCaller compareNotIdenticalMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareNotIdentical");
    static final MethodCaller compareEqualMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareEqual");
    static final MethodCaller compareNotEqualMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareNotEqual");
    static final MethodCaller compareToMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareTo");
    static final MethodCaller compareLessThanMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareLessThan");
    static final MethodCaller compareLessThanEqualMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareLessThanEqual");
    static final MethodCaller compareGreaterThanMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareGreaterThan");
    static final MethodCaller compareGreaterThanEqualMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "compareGreaterThanEqual");
    //regexpr
    static final MethodCaller findRegexMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "findRegex");
    static final MethodCaller matchRegexMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "matchRegex");
    static final MethodCaller regexPattern = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "regexPattern");
    // spread expressions
    static final MethodCaller spreadMap = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "spreadMap");
    static final MethodCaller despreadList = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "despreadList");
    // Closure
    static final MethodCaller getMethodPointer = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "getMethodPointer");
    static final MethodCaller invokeClosureMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "invokeClosure");
    // unary plus, unary minus, bitwise negation
    static final MethodCaller unaryPlus = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "unaryPlus");
    static final MethodCaller unaryMinus = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "unaryMinus");
    static final MethodCaller bitwiseNegate = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "bitwiseNegate");

    // type conversions
    static final MethodCaller asTypeMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "asType");
    static final MethodCaller castToTypeMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "castToType");
    static final MethodCaller createListMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "createList");
    static final MethodCaller createTupleMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "createTuple");
    static final MethodCaller createMapMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "createMap");
    static final MethodCaller createRangeMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "createRange");

    // wrapper creation methods
    static final MethodCaller createPojoWrapperMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "createPojoWrapper");
    static final MethodCaller createGroovyObjectWrapperMethod = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "createGroovyObjectWrapper");

    // constructor calls with this() and super()
    static final MethodCaller selectConstructorAndTransformArguments = MethodCaller.newStatic(ScriptBytecodeAdapter.class, "selectConstructorAndTransformArguments");

    // exception blocks list
    private Map<String, ClassNode> referencedClasses = new HashMap<String, ClassNode>();
    private boolean passingParams;

    private ConstructorNode constructorNode;
    private MethodNode methodNode;
    private BytecodeHelper helper = new BytecodeHelper(null);

    public static final boolean CREATE_DEBUG_INFO = true;
    public static final boolean CREATE_LINE_NUMBER_INFO = true;
    public static final boolean ASM_DEBUG = false; // add marker in the bytecode to show source-byecode relationship

    private int lineNumber = -1;
    private ASTNode currentASTNode = null;

    private ClassWriter dummyClassWriter = null;

    private ClassNode interfaceClassLoadingClass;

    private boolean implicitThis = false;

    private Map<String, GenericsType> genericParameterNames = null;
    private ClassNode rightHandType;
    private static final String CONSTRUCTOR = "<$constructor$>";
    private List callSites = new ArrayList();
    private int callSiteArrayVarIndex;
    private HashMap<ClosureExpression, ClassNode> closureClassMap;
    private SourceUnit source;
    private static final String DTT = BytecodeHelper.getClassInternalName(DefaultTypeTransformation.class.getName());
    private boolean specialCallWithinConstructor = false;

    public AsmClassGenerator(
            SourceUnit source,
            GeneratorContext context, ClassVisitor classVisitor,
            ClassLoader classLoader, String sourceFile
    ) {
        super(classLoader);
        this.source = source;
        this.context = context;
        this.cv = classVisitor;
        this.sourceFile = sourceFile;

        this.dummyClassWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        new DummyClassGenerator(context, dummyClassWriter, classLoader, sourceFile);
        compileStack = new CompileStack();
        genericParameterNames = new HashMap<String, GenericsType>();
        closureClassMap = new HashMap<ClosureExpression, ClassNode>();
    }

    protected SourceUnit getSourceUnit() {
        return source;
    }


    // GroovyClassVisitor interface
    //-------------------------------------------------------------------------

    public void visitClass(ClassNode classNode) {
        try {
            callSites.clear();
            if (classNode instanceof InterfaceHelperClassNode) {
                InterfaceHelperClassNode ihcn = (InterfaceHelperClassNode) classNode;
                callSites.addAll(ihcn.getCallSites());
            }
            referencedClasses.clear();
            this.classNode = classNode;
            this.outermostClass = null;
            this.internalClassName = BytecodeHelper.getClassInternalName(classNode);

            this.internalBaseClassName = BytecodeHelper.getClassInternalName(classNode.getSuperClass());

            cv.visit(
                    getBytecodeVersion(),
                    adjustedClassModifiers(classNode.getModifiers()),
                    internalClassName,
                    BytecodeHelper.getGenericsSignature(classNode),
                    internalBaseClassName,
                    BytecodeHelper.getClassInternalNames(classNode.getInterfaces())
            );
            cv.visitSource(sourceFile, null);
            if (classNode.getName().endsWith("package-info")) {
                PackageNode packageNode = classNode.getPackage();
                if (packageNode != null) {
                    // pull them out of package node but treat them like they were on class node
                    for (AnnotationNode an : packageNode.getAnnotations()) {
                        // skip built-in properties
                        if (an.isBuiltIn()) continue;
                        if (an.hasSourceRetention()) continue;

                        AnnotationVisitor av = getAnnotationVisitor(classNode, an, cv);
                        visitAnnotationAttributes(an, av);
                        av.visitEnd();
                    }
                }
                cv.visitEnd();
                return;
            } else {
                visitAnnotations(classNode, cv);
            }

            if (classNode.isInterface()) {
                ClassNode owner = classNode;
                if (owner instanceof InnerClassNode) {
                    owner = owner.getOuterClass();
                }
                String outerClassName = owner.getName();
                String name = outerClassName + "$" + context.getNextInnerClassIdx();
                interfaceClassLoadingClass = new InterfaceHelperClassNode(owner, name, 4128, ClassHelper.OBJECT_TYPE, callSites);

                super.visitClass(classNode);
                createInterfaceSyntheticStaticFields();
            } else {
                super.visitClass(classNode);
                if (!classNode.declaresInterface(ClassHelper.GENERATED_CLOSURE_Type)) {
                    createMopMethods();
                }
                generateCallSiteArray();
                createSyntheticStaticFields();
            }

            for (Iterator<InnerClassNode> iter = classNode.getInnerClasses(); iter.hasNext();) {
                InnerClassNode innerClass = iter.next();
                makeInnerClassEntry(innerClass);
            }
            makeInnerClassEntry(classNode);

            cv.visitEnd();
        }
        catch (GroovyRuntimeException e) {
            e.setModule(classNode.getModule());
            throw e;
        }
    }

    private void makeInnerClassEntry(ClassNode cn) {
        if (!(cn instanceof InnerClassNode)) return;
        InnerClassNode innerClass = (InnerClassNode) cn;
        String innerClassName = innerClass.getName();
        String innerClassInternalName = BytecodeHelper.getClassInternalName(innerClassName);
        {
            int index = innerClassName.lastIndexOf('$');
            if (index >= 0) innerClassName = innerClassName.substring(index + 1);
        }
        String outerClassName = BytecodeHelper.getClassInternalName(innerClass.getOuterClass().getName());
        MethodNode enclosingMethod = innerClass.getEnclosingMethod();
        if (enclosingMethod != null) {
            // local inner classes do not specify the outer class name
            outerClassName = null;
            innerClassName = null;
        }
        int mods = innerClass.getModifiers();
        cv.visitInnerClass(
                innerClassInternalName,
                outerClassName,
                innerClassName,
                mods);
    }

    /*
     * Classes but not interfaces should have ACC_SUPER set
     */
    private int adjustedClassModifiers(int modifiers) {
        boolean needsSuper = (modifiers & ACC_INTERFACE) == 0;
        modifiers = needsSuper ? modifiers | ACC_SUPER : modifiers;
        // eliminate static
        modifiers = modifiers & ~ACC_STATIC;
        return modifiers;
    }

    private void generateCallSiteArray() {
        if (!classNode.isInterface()) {
            cv.visitField(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$callSiteArray", "Ljava/lang/ref/SoftReference;", null, null);

            generateCreateCallSiteArray();
            generateGetCallSiteArray();

//            generateCallSiteMethods();
//            generateAdapterMethods ();
        }
    }


    private void generateGetCallSiteArray() {
        int visibility = (classNode instanceof InterfaceHelperClassNode) ? ACC_PUBLIC : ACC_PRIVATE;
        MethodVisitor mv = cv.visitMethod(visibility + ACC_SYNTHETIC + ACC_STATIC, "$getCallSiteArray", "()[Lorg/codehaus/groovy/runtime/callsite/CallSite;", null, null);
        mv.visitCode();
        mv.visitFieldInsn(GETSTATIC, internalClassName, "$callSiteArray", "Ljava/lang/ref/SoftReference;");
        Label l0 = new Label();
        mv.visitJumpInsn(IFNULL, l0);
        mv.visitFieldInsn(GETSTATIC, internalClassName, "$callSiteArray", "Ljava/lang/ref/SoftReference;");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/ref/SoftReference", "get", "()Ljava/lang/Object;");
        mv.visitTypeInsn(CHECKCAST, "org/codehaus/groovy/runtime/callsite/CallSiteArray");
        mv.visitInsn(DUP);
        mv.visitVarInsn(ASTORE, 0);
        Label l1 = new Label();
        mv.visitJumpInsn(IFNONNULL, l1);
        mv.visitLabel(l0);
        mv.visitMethodInsn(INVOKESTATIC, internalClassName, "$createCallSiteArray", "()Lorg/codehaus/groovy/runtime/callsite/CallSiteArray;");
        mv.visitVarInsn(ASTORE, 0);
        mv.visitTypeInsn(NEW, "java/lang/ref/SoftReference");
        mv.visitInsn(DUP);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/ref/SoftReference", "<init>", "(Ljava/lang/Object;)V");
        mv.visitFieldInsn(PUTSTATIC, internalClassName, "$callSiteArray", "Ljava/lang/ref/SoftReference;");
        mv.visitLabel(l1);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, "org/codehaus/groovy/runtime/callsite/CallSiteArray", "array", "[Lorg/codehaus/groovy/runtime/callsite/CallSite;");
        mv.visitInsn(ARETURN);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

    private void generateCreateCallSiteArray() {
        List<String> callSiteInitMethods = new LinkedList<String>();
        int index = 0;
        int methodIndex = 0;
        final int size = callSites.size();
        final int maxArrayInit = 5000;
        // create array initialization methods
        while (index < size) {
            methodIndex++;
            String methodName = "$createCallSiteArray_" + methodIndex;
            callSiteInitMethods.add(methodName);
            MethodVisitor mv = cv.visitMethod(ACC_PRIVATE+ACC_SYNTHETIC+ACC_STATIC, methodName, "([Ljava/lang/String;)V", null, null);
            mv.visitCode();
            int methodLimit = size;
            // check if the next block is over the max allowed
            if ((methodLimit - index) > maxArrayInit) {
                methodLimit = index + maxArrayInit;
            }
            for (; index < methodLimit; index++) {
                mv.visitVarInsn(ALOAD, 0);
                mv.visitLdcInsn(index);
                mv.visitLdcInsn(callSites.get(index));
                mv.visitInsn(AASTORE);
            }
            mv.visitInsn(RETURN);
            mv.visitMaxs(2,1);
            mv.visitEnd();
        }
        // create base createCallSiteArray method
        mv = cv.visitMethod(ACC_PRIVATE+ACC_SYNTHETIC+ACC_STATIC,"$createCallSiteArray", "()Lorg/codehaus/groovy/runtime/callsite/CallSiteArray;", null, null);
        mv.visitCode();
        mv.visitLdcInsn(size);
        mv.visitTypeInsn(ANEWARRAY, "java/lang/String");
        mv.visitVarInsn(ASTORE, 0);
        for (String methodName : callSiteInitMethods) {
          mv.visitVarInsn(ALOAD, 0);
          mv.visitMethodInsn(INVOKESTATIC,internalClassName,methodName,"([Ljava/lang/String;)V");
        }

        mv.visitTypeInsn(NEW, "org/codehaus/groovy/runtime/callsite/CallSiteArray");
        mv.visitInsn(DUP);
        visitClassExpression(new ClassExpression(classNode));

        mv.visitVarInsn(ALOAD, 0);

        mv.visitMethodInsn(INVOKESPECIAL, "org/codehaus/groovy/runtime/callsite/CallSiteArray", "<init>", "(Ljava/lang/Class;[Ljava/lang/String;)V");
        mv.visitInsn(ARETURN);
        mv.visitMaxs(0,0);
        mv.visitEnd();
    }

    public void visitGenericType(GenericsType genericsType) {
        ClassNode type = genericsType.getType();
        genericParameterNames.put(type.getName(), genericsType);
    }

    private void createMopMethods() {
        visitMopMethodList(classNode.getMethods(), true);
        visitMopMethodList(classNode.getSuperClass().getAllDeclaredMethods(), false);
    }

    private String[] buildExceptions(ClassNode[] exceptions) {
        if (exceptions == null) return null;
        String[] ret = new String[exceptions.length];
        for (int i = 0; i < exceptions.length; i++) {
            ret[i] = BytecodeHelper.getClassInternalName(exceptions[i]);
        }
        return ret;
    }

    /**
     * filters a list of method for MOP methods. For all methods that are no
     * MOP methods a MOP method is created if the method is not public and the
     * call would be a call on "this" (isThis == true). If the call is not on
     * "this", then the call is a call on "super" and all methods are used,
     * unless they are already a MOP method
     *
     * @param methods unfiltered list of methods for MOP
     * @param isThis  if true, then we are creating a MOP method on "this", "super" else
     * @see #generateMopCalls(LinkedList, boolean)
     */
    private void visitMopMethodList(List methods, boolean isThis) {
        class Key {
            int hash = 0;
            String name;
            Parameter[] params;

            Key(String name, Parameter[] params) {
                this.name = name;
                this.params = params;
                hash = name.hashCode() << 2 + params.length;
            }

            public int hashCode() {
                return hash;
            }

            public boolean equals(Object obj) {
                Key other = (Key) obj;
                return other.name.equals(name) && equalParameterTypes(other.params, params);
            }
        }
        HashMap<Key, MethodNode> mops = new HashMap<Key, MethodNode>();
        LinkedList<MethodNode> mopCalls = new LinkedList<MethodNode>();
        for (Object method : methods) {
            MethodNode mn = (MethodNode) method;
            if ((mn.getModifiers() & ACC_ABSTRACT) != 0) continue;
            if (mn.isStatic()) continue;
            // no this$ methods for non-private isThis=true
            // super$ method for non-private isThis=false
            // --> results in XOR
            boolean isPrivate = Modifier.isPrivate(mn.getModifiers());
            if (isThis ^ isPrivate) continue;

            String methodName = mn.getName();
            if (isMopMethod(methodName)) {
                mops.put(new Key(methodName, mn.getParameters()), mn);
                continue;
            }
            if (methodName.startsWith("<")) continue;
            String name = getMopMethodName(mn, isThis);
            Key key = new Key(name, mn.getParameters());
            if (mops.containsKey(key)) continue;
            mops.put(key, mn);
            mopCalls.add(mn);
        }
        generateMopCalls(mopCalls, isThis);
        mopCalls.clear();
        mops.clear();
    }

    private boolean equalParameterTypes(Parameter[] p1, Parameter[] p2) {
        if (p1.length != p2.length) return false;
        for (int i = 0; i < p1.length; i++) {
            if (!p1[i].getType().equals(p2[i].getType())) return false;
        }
        return true;
    }

    /**
     * generates a Meta Object Protocol method, that is used to call a non public
     * method, or to make a call to super.
     *
     * @param mopCalls list of methods a mop call method should be generated for
     * @param useThis  true if "this" should be used for the naming
     */
    private void generateMopCalls(LinkedList<MethodNode> mopCalls, boolean useThis) {
        for (MethodNode method : mopCalls) {
            String name = getMopMethodName(method, useThis);
            Parameter[] parameters = method.getParameters();
            String methodDescriptor = BytecodeHelper.getMethodDescriptor(method.getReturnType(), method.getParameters());
            mv = cv.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, name, methodDescriptor, null, null);
            mv.visitVarInsn(ALOAD, 0);
            int newRegister = 1;
            BytecodeHelper helper = new BytecodeHelper(mv);
            for (Parameter parameter : parameters) {
                ClassNode type = parameter.getType();
                helper.load(parameter.getType(), newRegister);
                // increment to next register, double/long are using two places
                newRegister++;
                if (type == ClassHelper.double_TYPE || type == ClassHelper.long_TYPE) newRegister++;
            }
            mv.visitMethodInsn(INVOKESPECIAL, BytecodeHelper.getClassInternalName(method.getDeclaringClass()), method.getName(), methodDescriptor);
            helper.doReturn(method.getReturnType());
            mv.visitMaxs(0, 0);
            mv.visitEnd();
            classNode.addMethod(name, Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, method.getReturnType(), parameters, null, null);
        }
    }

    /**
     * creates a MOP method name from a method
     *
     * @param method  the method to be called by the mop method
     * @param useThis if true, then it is a call on "this", "super" else
     * @return the mop method name
     */
    public static String getMopMethodName(MethodNode method, boolean useThis) {
        ClassNode declaringNode = method.getDeclaringClass();
        int distance = 0;
        for (; declaringNode != null; declaringNode = declaringNode.getSuperClass()) {
            distance++;
        }
        return (useThis ? "this" : "super") + "$" + distance + "$" + method.getName();
    }

    /**
     * method to determine if a method is a MOP method. This is done by the
     * method name. If the name starts with "this$" or "super$" but does not
     * contain "$dist$", then it is an MOP method
     *
     * @param methodName name of the method to test
     * @return true if the method is a MOP method
     */
    public static boolean isMopMethod(String methodName) {
        return (methodName.startsWith("this$") ||
                methodName.startsWith("super$")) && !methodName.contains("$dist$");
    }

    protected void visitConstructorOrMethod(MethodNode node, boolean isConstructor) {
        lineNumber = -1;
        Parameter[] parameters = node.getParameters();
        String methodType = BytecodeHelper.getMethodDescriptor(node.getReturnType(), parameters);

        String signature = BytecodeHelper.getGenericsMethodSignature(node);
        int modifiers = node.getModifiers();
        if (isVargs(node.getParameters())) modifiers |= Opcodes.ACC_VARARGS;
        mv = cv.visitMethod(modifiers, node.getName(), methodType, signature, buildExceptions(node.getExceptions()));

        mv = new MyMethodAdapter();
//        mv = DebugMethodAdapter.create(new MyMethodAdapter());

        visitAnnotations(node, mv);
        for (int i = 0; i < parameters.length; i++) {
            visitParameterAnnotations(parameters[i], i, mv);
        }
        helper = new BytecodeHelper(mv);

        if (classNode.isAnnotationDefinition() && !node.isStaticConstructor()) {
            visitAnnotationDefault(node, mv);
        } else if (!node.isAbstract()) {
            Statement code = node.getCode();

            // fast path for getter/setters etc.
            if (code instanceof BytecodeSequence && ((BytecodeSequence) code).getInstructions().size() == 1 && ((BytecodeSequence) code).getInstructions().get(0) instanceof BytecodeInstruction) {
                ((BytecodeInstruction) ((BytecodeSequence) code).getInstructions().get(0)).visit(mv);
            } else {
                visitStdMethod(node, isConstructor, parameters, code);
            }
            // we use this NOP to have a valid jump target for the various labels
            mv.visitInsn(NOP);
            mv.visitMaxs(0, 0);
        }
        mv.visitEnd();
    }

    private void visitStdMethod(MethodNode node, boolean isConstructor, Parameter[] parameters, Statement code) {
        final ClassNode superClass = classNode.getSuperClass();
        if (isConstructor && (code == null || !((ConstructorNode) node).firstStatementIsSpecialConstructorCall())) {
            boolean hasCallToSuper = false;
            if (code != null && classNode instanceof InnerClassNode) {
                // if the class not is an inner class node, there are chances that the call to super is already added
                // so we must ensure not to add it twice (see GROOVY-4471)
                if (code instanceof BlockStatement) {
                    for (Statement statement : ((BlockStatement) code).getStatements()) {
                        if (statement instanceof ExpressionStatement) {
                            final Expression expression = ((ExpressionStatement) statement).getExpression();
                            if (expression instanceof ConstructorCallExpression) {
                                ConstructorCallExpression call = (ConstructorCallExpression) expression;
                                if (call.isSuperCall()) {
                                    hasCallToSuper = true;
                                    break;
                                }
                            }
                        }
                    }
                }
            }

            if (!hasCallToSuper) {
                // invokes the super class constructor
                mv.visitVarInsn(ALOAD, 0);
                mv.visitMethodInsn(INVOKESPECIAL, BytecodeHelper.getClassInternalName(superClass), "<init>", "()V");
            }
        }
       
        compileStack.init(node.getVariableScope(), parameters, mv, classNode);


        if (isNotClinit()) {
            mv.visitMethodInsn(INVOKESTATIC, internalClassName, "$getCallSiteArray", "()[Lorg/codehaus/groovy/runtime/callsite/CallSite;");
            callSiteArrayVarIndex = compileStack.defineTemporaryVariable("$local$callSiteArray", ClassHelper.make(CallSite[].class), true);
        }

        // handle body
        super.visitConstructorOrMethod(node, isConstructor);
        if (!outputReturn || node.isVoidMethod()) {
            mv.visitInsn(RETURN);
        }

        compileStack.clear();
    }

    void visitAnnotationDefaultExpression(AnnotationVisitor av, ClassNode type, Expression exp) {
        if (type.isArray()) {
            ListExpression list = (ListExpression) exp;
            AnnotationVisitor avl = av.visitArray(null);
            ClassNode componentType = type.getComponentType();
            for (Expression lExp : list.getExpressions()) {
                visitAnnotationDefaultExpression(avl, componentType, lExp);
            }
        } else if (ClassHelper.isPrimitiveType(type) || type.equals(ClassHelper.STRING_TYPE)) {
            ConstantExpression constExp = (ConstantExpression) exp;
            av.visit(null, constExp.getValue());
        } else if (ClassHelper.CLASS_Type.equals(type)) {
            ClassNode clazz = exp.getType();
            Type t = Type.getType(BytecodeHelper.getTypeDescription(clazz));
            av.visit(null, t);
        } else if (type.isDerivedFrom(ClassHelper.Enum_Type)) {
            PropertyExpression pExp = (PropertyExpression) exp;
            ClassExpression cExp = (ClassExpression) pExp.getObjectExpression();
            String desc = BytecodeHelper.getTypeDescription(cExp.getType());
            String name = pExp.getPropertyAsString();
            av.visitEnum(null, desc, name);
        } else if (type.implementsInterface(ClassHelper.Annotation_TYPE)) {
            AnnotationConstantExpression avExp = (AnnotationConstantExpression) exp;
            AnnotationNode value = (AnnotationNode) avExp.getValue();
            AnnotationVisitor avc = av.visitAnnotation(null, BytecodeHelper.getTypeDescription(avExp.getType()));
            visitAnnotationAttributes(value, avc);
        } else {
            throw new GroovyBugError("unexpected annotation type " + type.getName());
        }
        av.visitEnd();
    }

    private void visitAnnotationDefault(MethodNode node, MethodVisitor mv) {
        if (!node.hasAnnotationDefault()) return;
        Expression exp = ((ReturnStatement) node.getCode()).getExpression();
        AnnotationVisitor av = mv.visitAnnotationDefault();
        visitAnnotationDefaultExpression(av, node.getReturnType(), exp);
    }


    private boolean isNotClinit() {
        return methodNode == null || !methodNode.getName().equals("<clinit>");
    }

    private boolean isVargs(Parameter[] p) {
        if (p.length == 0) return false;
        ClassNode clazz = p[p.length - 1].getType();
        return (clazz.isArray());
    }

    public void visitConstructor(ConstructorNode node) {
        this.constructorNode = node;
        this.methodNode = null;
        outputReturn = false;
        super.visitConstructor(node);
    }

    public void visitMethod(MethodNode node) {
        this.constructorNode = null;
        this.methodNode = node;
        outputReturn = false;

        super.visitMethod(node);
    }

    public void visitField(FieldNode fieldNode) {
        onLineNumber(fieldNode, "visitField: " + fieldNode.getName());
        ClassNode t = fieldNode.getType();
        String signature = BytecodeHelper.getGenericsBounds(t);
        FieldVisitor fv = cv.visitField(
                fieldNode.getModifiers(),
                fieldNode.getName(),
                BytecodeHelper.getTypeDescription(t),
                signature,
                null);
        visitAnnotations(fieldNode, fv);
        fv.visitEnd();
    }

    public void visitProperty(PropertyNode statement) {
        // the verifier created the field and the setter/getter methods, so here is
        // not really something to do
        onLineNumber(statement, "visitProperty:" + statement.getField().getName());
        this.methodNode = null;
    }

    // GroovyCodeVisitor interface
    //-------------------------------------------------------------------------

    // Statements
    //-------------------------------------------------------------------------

    protected void visitStatement(Statement statement) {
        String name = statement.getStatementLabel();
        if (name != null) {
            Label label = compileStack.createLocalLabel(name);
            mv.visitLabel(label);
        }
    }

    public void visitBlockStatement(BlockStatement block) {
        visitStatement(block);

        compileStack.pushVariableScope(block.getVariableScope());
        super.visitBlockStatement(block);
        compileStack.pop();
    }

    private void visitExpressionOrStatement(Object o) {
        if (o == EmptyExpression.INSTANCE) return;
        if (o instanceof Expression) {
            Expression expr = (Expression) o;
            visitAndAutoboxBoolean(expr);
            if (isPopRequired(expr)) mv.visitInsn(POP);
        } else {
            ((Statement) o).visit(this);
        }
    }

    private void visitForLoopWithClosureList(ForStatement loop) {
        compileStack.pushLoop(loop.getVariableScope(), loop.getStatementLabel());

        ClosureListExpression clExpr = (ClosureListExpression) loop.getCollectionExpression();
        compileStack.pushVariableScope(clExpr.getVariableScope());

        List expressions = clExpr.getExpressions();
        int size = expressions.size();

        // middle element is condition, lower half is init, higher half is increment
        int condIndex = (size - 1) / 2;

        // visit init
        for (int i = 0; i < condIndex; i++) {
            visitExpressionOrStatement(expressions.get(i));
        }

        Label continueLabel = compileStack.getContinueLabel();
        Label breakLabel = compileStack.getBreakLabel();

        Label cond = new Label();
        mv.visitLabel(cond);
        // visit condition leave boolean on stack
        {
            Expression condExpr = (Expression) expressions.get(condIndex);
            if (condExpr == EmptyExpression.INSTANCE) {
                mv.visitIntInsn(BIPUSH, 1);
            } else if (isComparisonExpression(condExpr)) {
                condExpr.visit(this);
            } else {
                visitAndAutoboxBoolean(condExpr);
                helper.unbox(ClassHelper.boolean_TYPE);
            }
        }
        // jump if we don't want to continue
        // note: ifeq tests for ==0, a boolean is 0 if it is false
        mv.visitJumpInsn(IFEQ, breakLabel);

        // Generate the loop body
        loop.getLoopBlock().visit(this);

        // visit increment
        mv.visitLabel(continueLabel);
        for (int i = condIndex + 1; i < size; i++) {
            visitExpressionOrStatement(expressions.get(i));
        }

        // jump to test the condition again
        mv.visitJumpInsn(GOTO, cond);

        // loop end
        mv.visitLabel(breakLabel);

        compileStack.pop();
        compileStack.pop();

    }

    public void visitForLoop(ForStatement loop) {

        onLineNumber(loop, "visitForLoop");
        visitStatement(loop);


        Parameter loopVar = loop.getVariable();
        if (loopVar == ForStatement.FOR_LOOP_DUMMY) {
            visitForLoopWithClosureList(loop);
            return;
        }

        compileStack.pushLoop(loop.getVariableScope(), loop.getStatementLabel());

        // Declare the loop counter.
        Variable variable = compileStack.defineVariable(loop.getVariable(), false);

        //
        // Then get the iterator and generate the loop control
        MethodCallExpression iterator = new MethodCallExpression(loop.getCollectionExpression(), "iterator", new ArgumentListExpression());
        iterator.visit(this);

        final int iteratorIdx = compileStack.defineTemporaryVariable("iterator", ClassHelper.make(java.util.Iterator.class), true);

        Label continueLabel = compileStack.getContinueLabel();
        Label breakLabel = compileStack.getBreakLabel();

        mv.visitLabel(continueLabel);
        mv.visitVarInsn(ALOAD, iteratorIdx);
        iteratorHasNextMethod.call(mv);
        // note: ifeq tests for ==0, a boolean is 0 if it is false
        mv.visitJumpInsn(IFEQ, breakLabel);

        mv.visitVarInsn(ALOAD, iteratorIdx);
        iteratorNextMethod.call(mv);
        helper.storeVar(variable);

        // Generate the loop body
        loop.getLoopBlock().visit(this);

        mv.visitJumpInsn(GOTO, continueLabel);
        mv.visitLabel(breakLabel);

        compileStack.pop();
    }

    public void visitWhileLoop(WhileStatement loop) {
        onLineNumber(loop, "visitWhileLoop");
        visitStatement(loop);

        compileStack.pushLoop(loop.getStatementLabel());
        Label continueLabel = compileStack.getContinueLabel();
        Label breakLabel = compileStack.getBreakLabel();

        mv.visitLabel(continueLabel);
        Expression bool = loop.getBooleanExpression();
        boolean boolHandled = false;
        if (bool instanceof ConstantExpression) {
            ConstantExpression constant = (ConstantExpression) bool;
            if (constant.getValue() == Boolean.TRUE) {
                boolHandled = true;
                // do nothing
            } else if (constant.getValue() == Boolean.FALSE) {
                boolHandled = true;
                mv.visitJumpInsn(GOTO, breakLabel);
            }
        }

        if (!boolHandled) {
            bool.visit(this);
            mv.visitJumpInsn(IFEQ, breakLabel);
        }

        loop.getLoopBlock().visit(this);

        mv.visitJumpInsn(GOTO, continueLabel);
        mv.visitLabel(breakLabel);

        compileStack.pop();
    }

    public void visitDoWhileLoop(DoWhileStatement loop) {
        onLineNumber(loop, "visitDoWhileLoop");
        visitStatement(loop);

        compileStack.pushLoop(loop.getStatementLabel());
        Label breakLabel = compileStack.getBreakLabel();
        Label continueLabel = compileStack.getContinueLabel();
        mv.visitLabel(continueLabel);

        loop.getLoopBlock().visit(this);

        loop.getBooleanExpression().visit(this);
        mv.visitJumpInsn(IFEQ, continueLabel);
        mv.visitLabel(breakLabel);

        compileStack.pop();
    }

    public void visitIfElse(IfStatement ifElse) {
        onLineNumber(ifElse, "visitIfElse");
        visitStatement(ifElse);
        ifElse.getBooleanExpression().visit(this);

        Label l0 = new Label();
        mv.visitJumpInsn(IFEQ, l0);

        // if-else is here handled as a special version
        // of a boolean expression
        compileStack.pushBooleanExpression();
        ifElse.getIfBlock().visit(this);
        compileStack.pop();

        Label l1 = new Label();
        mv.visitJumpInsn(GOTO, l1);
        mv.visitLabel(l0);

        compileStack.pushBooleanExpression();
        ifElse.getElseBlock().visit(this);
        compileStack.pop();

        mv.visitLabel(l1);
    }

    public void visitTernaryExpression(TernaryExpression expression) {
        onLineNumber(expression, "visitTernaryExpression");

        BooleanExpression boolPart = expression.getBooleanExpression();
        Expression truePart = expression.getTrueExpression();
        Expression falsePart = expression.getFalseExpression();

        if (expression instanceof ElvisOperatorExpression) {
            visitAndAutoboxBoolean(expression.getTrueExpression());
            boolPart = new BooleanExpression(
                    new BytecodeExpression() {
                        public void visit(MethodVisitor mv) {
                            mv.visitInsn(DUP);
                        }
                    }
            );
            truePart = BytecodeExpression.NOP;
            final Expression oldFalse = falsePart;
            falsePart = new BytecodeExpression() {
                public void visit(MethodVisitor mv) {
                    mv.visitInsn(POP);
                    visitAndAutoboxBoolean(oldFalse);
                }
            };
        }


        boolPart.visit(this);

        Label l0 = new Label();
        mv.visitJumpInsn(IFEQ, l0);
        compileStack.pushBooleanExpression();
        visitAndAutoboxBoolean(truePart);
        compileStack.pop();

        Label l1 = new Label();
        mv.visitJumpInsn(GOTO, l1);
        mv.visitLabel(l0);
        compileStack.pushBooleanExpression();
        visitAndAutoboxBoolean(falsePart);
        compileStack.pop();

        mv.visitLabel(l1);
    }

    public void visitAssertStatement(AssertStatement statement) {
        onLineNumber(statement, "visitAssertStatement");
        visitStatement(statement);

        boolean rewriteAssert = true;
        // don't rewrite assertions with message
        rewriteAssert = statement.getMessageExpression() == ConstantExpression.NULL;
        AssertionTracker oldTracker = assertionTracker;
        Janitor janitor = new Janitor();
        final Label tryStart = new Label();
        if (rewriteAssert) {
            assertionTracker = new AssertionTracker();
            try {
                // because source position seems to be more reliable for statements
                // than for expressions, we get the source text for the whole statement
                assertionTracker.sourceText = new SourceText(statement, source, janitor);
                mv.visitTypeInsn(NEW, "org/codehaus/groovy/transform/powerassert/ValueRecorder");
                mv.visitInsn(DUP);
                mv.visitMethodInsn(INVOKESPECIAL, "org/codehaus/groovy/transform/powerassert/ValueRecorder", "<init>", "()V");
                assertionTracker.recorderIndex = compileStack.defineTemporaryVariable("recorder", true);
                mv.visitLabel(tryStart);
            } catch (SourceTextNotAvailableException e) {
                // don't rewrite assertions w/o source text
                rewriteAssert = false;
                assertionTracker = oldTracker;
            }
        }

        BooleanExpression booleanExpression = statement.getBooleanExpression();
        booleanExpression.visit(this);

        Label exceptionThrower = new Label();
        mv.visitJumpInsn(IFEQ, exceptionThrower);

        // do nothing, but clear the value recorder
        if (rewriteAssert) {
            //clean up assertion recorder
            mv.visitVarInsn(ALOAD, assertionTracker.recorderIndex);
            mv.visitMethodInsn(
                    INVOKEVIRTUAL,
                    "org/codehaus/groovy/transform/powerassert/ValueRecorder",
                    "clear",
                    "()V");
        }
        Label afterAssert = new Label();
        mv.visitJumpInsn(GOTO, afterAssert);
        mv.visitLabel(exceptionThrower);

        if (rewriteAssert) {
            mv.visitLdcInsn(assertionTracker.sourceText.getNormalizedText());
            mv.visitVarInsn(ALOAD, assertionTracker.recorderIndex);
            mv.visitMethodInsn(
                    INVOKESTATIC,
                    "org/codehaus/groovy/transform/powerassert/AssertionRenderer",
                    "render",
                    "(Ljava/lang/String;Lorg/codehaus/groovy/transform/powerassert/ValueRecorder;)Ljava/lang/String;"
            );
        } else {
            writeSourclessAssertText(statement);
        }
        AssertionTracker savedTracker = assertionTracker;
        assertionTracker = null;

        // now the optional exception expression
        visitAndAutoboxBoolean(statement.getMessageExpression());
        assertFailedMethod.call(mv);

        if (rewriteAssert) {
            final Label tryEnd = new Label();
            mv.visitLabel(tryEnd);
            mv.visitJumpInsn(GOTO, afterAssert);
            // finally block to clean assertion recorder
            final Label catchAny = new Label();
            mv.visitLabel(catchAny);
            mv.visitVarInsn(ALOAD, savedTracker.recorderIndex);
            mv.visitMethodInsn(
                    INVOKEVIRTUAL,
                    "org/codehaus/groovy/transform/powerassert/ValueRecorder",
                    "clear",
                    "()V");
            mv.visitInsn(ATHROW);
            // add catch any block to exception table
            compileStack.addExceptionBlock(tryStart, tryEnd, catchAny, null);
        }

        mv.visitLabel(afterAssert);
        assertionTracker = oldTracker;
        // close possibly open file handles from getting a sample for
        // power asserts
        janitor.cleanup();
    }

    private void writeSourclessAssertText(AssertStatement statement) {
        BooleanExpression booleanExpression = statement.getBooleanExpression();
        // push expression string onto stack
        String expressionText = booleanExpression.getText();
        List<String> list = new ArrayList<String>();
        addVariableNames(booleanExpression, list);
        if (list.isEmpty()) {
            mv.visitLdcInsn(expressionText);
        } else {
            boolean first = true;

            // let's create a new expression
            mv.visitTypeInsn(NEW, "java/lang/StringBuffer");
            mv.visitInsn(DUP);
            mv.visitLdcInsn(expressionText + ". Values: ");

            mv.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuffer", "<init>", "(Ljava/lang/String;)V");

            int tempIndex = compileStack.defineTemporaryVariable("assert", true);

            for (String name : list) {
                String text = name + " = ";
                if (first) {
                    first = false;
                } else {
                    text = ", " + text;
                }

                mv.visitVarInsn(ALOAD, tempIndex);
                mv.visitLdcInsn(text);
                mv.visitMethodInsn(
                        INVOKEVIRTUAL,
                        "java/lang/StringBuffer",
                        "append",
                        "(Ljava/lang/Object;)Ljava/lang/StringBuffer;");
                mv.visitInsn(POP);

                mv.visitVarInsn(ALOAD, tempIndex);
                new VariableExpression(name).visit(this);
                mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/InvokerHelper", "toString", "(Ljava/lang/Object;)Ljava/lang/String;");
                mv.visitMethodInsn(
                        INVOKEVIRTUAL,
                        "java/lang/StringBuffer",
                        "append",
                        "(Ljava/lang/String;)Ljava/lang/StringBuffer;");
                mv.visitInsn(POP);

            }
            mv.visitVarInsn(ALOAD, tempIndex);
            compileStack.removeVar(tempIndex);
        }
    }

    private void addVariableNames(Expression expression, List<String> list) {
        if (expression instanceof BooleanExpression) {
            BooleanExpression boolExp = (BooleanExpression) expression;
            addVariableNames(boolExp.getExpression(), list);
        } else if (expression instanceof BinaryExpression) {
            BinaryExpression binExp = (BinaryExpression) expression;
            addVariableNames(binExp.getLeftExpression(), list);
            addVariableNames(binExp.getRightExpression(), list);
        } else if (expression instanceof VariableExpression) {
            VariableExpression varExp = (VariableExpression) expression;
            list.add(varExp.getName());
        }
    }

    public void visitTryCatchFinally(TryCatchStatement statement) {
        visitStatement(statement);

        Statement tryStatement = statement.getTryStatement();
        final Statement finallyStatement = statement.getFinallyStatement();

        // start try block, label needed for exception table
        Label tryStart = new Label();
        mv.visitLabel(tryStart);
        BlockRecorder tryBlock = makeBlockRecorder(finallyStatement);
        tryBlock.startRange(tryStart);

        tryStatement.visit(this);

        // goto finally part
        Label finallyStart = new Label();
        mv.visitJumpInsn(GOTO, finallyStart);

        Label tryEnd = new Label();
        mv.visitLabel(tryEnd);
        tryBlock.closeRange(tryEnd);
        // pop for "makeBlockRecorder(finallyStatement)"
        compileStack.pop();

        BlockRecorder catches = makeBlockRecorder(finallyStatement);
        for (CatchStatement catchStatement : statement.getCatchStatements()) {
            ClassNode exceptionType = catchStatement.getExceptionType();
            String exceptionTypeInternalName = BytecodeHelper.getClassInternalName(exceptionType);

            // start catch block, label needed for exception table
            Label catchStart = new Label();
            mv.visitLabel(catchStart);
            catches.startRange(catchStart);

            // create exception variable and store the exception
            compileStack.pushState();
            compileStack.defineVariable(catchStatement.getVariable(), true);
            // handle catch body
            catchStatement.visit(this);
            // place holder to avoid problems with empty catch blocks
            mv.visitInsn(NOP);
            // pop for the variable
            compileStack.pop();

            // end of catch
            Label catchEnd = new Label();
            mv.visitLabel(catchEnd);
            catches.closeRange(catchEnd);

            // goto finally start
            mv.visitJumpInsn(GOTO, finallyStart);
            compileStack.writeExceptionTable(tryBlock, catchStart, exceptionTypeInternalName);
        }

        // Label used to handle exceptions in catches and regularly
        // visited finals.
        Label catchAny = new Label();

        // add "catch any" block to exception table for try part we do this
        // after the exception blocks, because else this one would superseed
        // any of those otherwise
        compileStack.writeExceptionTable(tryBlock, catchAny, null);
        // same for the catch parts
        compileStack.writeExceptionTable(catches, catchAny, null);

        // pop for "makeBlockRecorder(catches)"
        compileStack.pop();

        // start finally
        mv.visitLabel(finallyStart);
        finallyStatement.visit(this);
        mv.visitInsn(NOP)//**

        // goto after all-catching block
        Label skipCatchAll = new Label();
        mv.visitJumpInsn(GOTO, skipCatchAll);

        // start a block catching any Exception
        mv.visitLabel(catchAny);
        //store exception
        int anyExceptionIndex = compileStack.defineTemporaryVariable("exception", true);

        finallyStatement.visit(this);

        // load the exception and rethrow it
        mv.visitVarInsn(ALOAD, anyExceptionIndex);
        mv.visitInsn(ATHROW);

        mv.visitLabel(skipCatchAll);
    }

    private BlockRecorder makeBlockRecorder(final Statement finallyStatement) {
        final BlockRecorder block = new BlockRecorder();
        Runnable tryRunner = new Runnable() {
            public void run() {
                compileStack.pushBlockRecorderVisit(block);
                finallyStatement.visit(AsmClassGenerator.this);
                compileStack.popBlockRecorderVisit(block);
            }
        };
        block.excludedStatement = tryRunner;
        compileStack.pushBlockRecorder(block);
        return block;
    }

    public void visitSwitch(SwitchStatement statement) {
        onLineNumber(statement, "visitSwitch");
        visitStatement(statement);

        statement.getExpression().visit(this);

        // switch does not have a continue label. use its parent's for continue
        Label breakLabel = compileStack.pushSwitch();

        int switchVariableIndex = compileStack.defineTemporaryVariable("switch", true);

        List caseStatements = statement.getCaseStatements();
        int caseCount = caseStatements.size();
        Label[] labels = new Label[caseCount + 1];
        for (int i = 0; i < caseCount; i++) {
            labels[i] = new Label();
        }

        int i = 0;
        for (Iterator iter = caseStatements.iterator(); iter.hasNext(); i++) {
            CaseStatement caseStatement = (CaseStatement) iter.next();
            visitCaseStatement(caseStatement, switchVariableIndex, labels[i], labels[i + 1]);
        }

        statement.getDefaultStatement().visit(this);

        mv.visitLabel(breakLabel);

        compileStack.pop();
    }

    public void visitCaseStatement(CaseStatement statement) {
    }

    public void visitCaseStatement(
            CaseStatement statement,
            int switchVariableIndex,
            Label thisLabel,
            Label nextLabel) {

        onLineNumber(statement, "visitCaseStatement");

        mv.visitVarInsn(ALOAD, switchVariableIndex);
        statement.getExpression().visit(this);

        isCaseMethod.call(mv);

        Label l0 = new Label();
        mv.visitJumpInsn(IFEQ, l0);

        mv.visitLabel(thisLabel);

        statement.getCode().visit(this);

        // now if we don't finish with a break we need to jump past
        // the next comparison
        if (nextLabel != null) {
            mv.visitJumpInsn(GOTO, nextLabel);
        }

        mv.visitLabel(l0);
    }

    public void visitBreakStatement(BreakStatement statement) {
        onLineNumber(statement, "visitBreakStatement");
        visitStatement(statement);

        String name = statement.getLabel();
        Label breakLabel = compileStack.getNamedBreakLabel(name);
        compileStack.applyFinallyBlocks(breakLabel, true);

        mv.visitJumpInsn(GOTO, breakLabel);
    }

    public void visitContinueStatement(ContinueStatement statement) {
        onLineNumber(statement, "visitContinueStatement");
        visitStatement(statement);

        String name = statement.getLabel();
        Label continueLabel = compileStack.getContinueLabel();
        if (name != null) continueLabel = compileStack.getNamedContinueLabel(name);
        compileStack.applyFinallyBlocks(continueLabel, false);
        mv.visitJumpInsn(GOTO, continueLabel);
    }

    public void visitSynchronizedStatement(SynchronizedStatement statement) {
        onLineNumber(statement, "visitSynchronizedStatement");
        visitStatement(statement);

        statement.getExpression().visit(this);
        final int index = compileStack.defineTemporaryVariable("synchronized", ClassHelper.Integer_TYPE, true);

        final Label synchronizedStart = new Label();
        final Label synchronizedEnd = new Label();
        final Label catchAll = new Label();

        mv.visitVarInsn(ALOAD, index);
        mv.visitInsn(MONITORENTER);
        mv.visitLabel(synchronizedStart);
        // place holder for "empty" synchronized blocks, for example
        // if there is only a break/continue.
        mv.visitInsn(NOP);

        Runnable finallyPart = new Runnable() {
            public void run() {
                mv.visitVarInsn(ALOAD, index);
                mv.visitInsn(MONITOREXIT);
            }
        };
        BlockRecorder fb = new BlockRecorder(finallyPart);
        fb.startRange(synchronizedStart);
        compileStack.pushBlockRecorder(fb);
       
        statement.getCode().visit(this);

        fb.closeRange(catchAll);
        compileStack.writeExceptionTable(fb, catchAll, null);
        compileStack.pop(); //pop fb
       
        finallyPart.run();
        mv.visitJumpInsn(GOTO, synchronizedEnd);
        mv.visitLabel(catchAll);
        finallyPart.run();
        mv.visitInsn(ATHROW);

        mv.visitLabel(synchronizedEnd);
    }

    public void visitThrowStatement(ThrowStatement statement) {
        onLineNumber(statement, "visitThrowStatement");
        visitStatement(statement);

        statement.getExpression().visit(this);

        // we should infer the type of the exception from the expression
        mv.visitTypeInsn(CHECKCAST, "java/lang/Throwable");

        mv.visitInsn(ATHROW);
    }

    public void visitReturnStatement(ReturnStatement statement) {
        onLineNumber(statement, "visitReturnStatement");
        visitStatement(statement);

        ClassNode returnType;
        if (methodNode != null) {
            returnType = methodNode.getReturnType();
        } else if (constructorNode != null) {
            returnType = constructorNode.getReturnType();
        } else {
            throw new GroovyBugError("I spotted a return that is neither in a method nor in a constructor... I can not handle that");
        }

        if (returnType == ClassHelper.VOID_TYPE) {
            if (!(statement.isReturningNullOrVoid())) {
                throwException("Cannot use return statement with an expression on a method that returns void");
            }
            compileStack.applyBlockRecorder();
            mv.visitInsn(RETURN);
            outputReturn = true;
            return;
        }

        Expression expression = statement.getExpression();
        evaluateExpression(expression);
        if (returnType == ClassHelper.OBJECT_TYPE && expression.getType() != null && expression.getType() == ClassHelper.VOID_TYPE) {
            mv.visitInsn(ACONST_NULL); // cheat the caller
        } else {
            // return is based on class type
            // we may need to cast
            doConvertAndCast(returnType, expression, false, true, false);
        }
        if (compileStack.hasBlockRecorder()) {
            // value is always saved in boxed form, so no need to have a special load routine here
            int returnValueIdx = compileStack.defineTemporaryVariable("returnValue", ClassHelper.OBJECT_TYPE, true);
            compileStack.applyBlockRecorder();
            helper.load(ClassHelper.OBJECT_TYPE, returnValueIdx);
        }
        // value is always saved in boxed form, so we need to unbox it here
        helper.unbox(returnType);
        helper.doReturn(returnType);
        outputReturn = true;
    }

    /**
     * Casts to the given type unless it can be determined that the cast is unnecessary
     */
    protected void doConvertAndCast(ClassNode type, Expression expression, boolean ignoreAutoboxing, boolean forceCast, boolean coerce) {
        ClassNode expType = getExpressionType(expression);
        // temp resolution: convert all primitive casting to corresponding Object type
        if (!ignoreAutoboxing && ClassHelper.isPrimitiveType(type)) {
            type = ClassHelper.getWrapper(type);
        }

        if (forceCast || (type != null && !expType.isDerivedFrom(type) && !expType.implementsInterface(type))) {
            doConvertAndCast(type, coerce);
        }
    }

    /**
     * @param expression
     */
    protected void evaluateExpression(Expression expression) {
        visitAndAutoboxBoolean(expression);

        if (isPopRequired(expression)) {
            return; // we already have the return value
        }
        // otherwise create return value if appropriate
        Expression assignExpr = createReturnLHSExpression(expression);
        if (assignExpr != null) {
            leftHandExpression = false;
            assignExpr.visit(this);
        }
    }

    public void visitExpressionStatement(ExpressionStatement statement) {
        onLineNumber(statement, "visitExpressionStatement: " + statement.getExpression().getClass().getName());
        visitStatement(statement);

        Expression expression = statement.getExpression();

        visitAndAutoboxBoolean(expression);

        if (isPopRequired(expression)) {
            mv.visitInsn(POP);
        }
    }

    // Expressions
    //-------------------------------------------------------------------------

    public void visitDeclarationExpression(DeclarationExpression expression) {
        onLineNumber(expression, "visitDeclarationExpression: \"" + expression.getText() + "\"");
        evaluateEqual(expression, true);
    }

    public void visitBinaryExpression(BinaryExpression expression) {
        onLineNumber(expression, "visitBinaryExpression: \"" + expression.getOperation().getText() + "\" ");
        switch (expression.getOperation().getType()) {
            case Types.EQUAL: // = assignment
                evaluateEqual(expression, false);
                break;

//            case Types.COMPARE_IDENTICAL: // ===
//                evaluateBinaryExpression(compareIdenticalMethod, expression);
//                break;
//
//            case Types.COMPARE_NOT_IDENTICAL: // !==
//                evaluateBinaryExpression(compareNotIdenticalMethod, expression);
//                break;

            case Types.COMPARE_IDENTICAL: // ===
            case Types.COMPARE_NOT_IDENTICAL: // !==
                source.addError(new SyntaxException("Operators === and !== are not supported. Use this.is(that) instead", expression.getLineNumber(), expression.getColumnNumber()));
                break;

            case Types.COMPARE_EQUAL: // ==
                evaluateBinaryExpression(compareEqualMethod, expression);
                break;

            case Types.COMPARE_NOT_EQUAL:
                evaluateBinaryExpression(compareNotEqualMethod, expression);
                break;

            case Types.COMPARE_TO:
                evaluateCompareTo(expression);
                break;

            case Types.COMPARE_GREATER_THAN:
                evaluateBinaryExpression(compareGreaterThanMethod, expression);
                break;

            case Types.COMPARE_GREATER_THAN_EQUAL:
                evaluateBinaryExpression(compareGreaterThanEqualMethod, expression);
                break;

            case Types.COMPARE_LESS_THAN:
                evaluateBinaryExpression(compareLessThanMethod, expression);
                break;

            case Types.COMPARE_LESS_THAN_EQUAL:
                evaluateBinaryExpression(compareLessThanEqualMethod, expression);
                break;

            case Types.LOGICAL_AND:
                evaluateLogicalAndExpression(expression);
                break;

            case Types.LOGICAL_OR:
                evaluateLogicalOrExpression(expression);
                break;

            case Types.BITWISE_AND:
                evaluateBinaryExpression("and", expression);
                break;

            case Types.BITWISE_AND_EQUAL:
                evaluateBinaryExpressionWithAssignment("and", expression);
                break;

            case Types.BITWISE_OR:
                evaluateBinaryExpression("or", expression);
                break;

            case Types.BITWISE_OR_EQUAL:
                evaluateBinaryExpressionWithAssignment("or", expression);
                break;

            case Types.BITWISE_XOR:
                evaluateBinaryExpression("xor", expression);
                break;

            case Types.BITWISE_XOR_EQUAL:
                evaluateBinaryExpressionWithAssignment("xor", expression);
                break;

            case Types.PLUS:
                evaluateBinaryExpression("plus", expression);
                break;

            case Types.PLUS_EQUAL:
                evaluateBinaryExpressionWithAssignment("plus", expression);
                break;

            case Types.MINUS:
                evaluateBinaryExpression("minus", expression);
                break;

            case Types.MINUS_EQUAL:
                evaluateBinaryExpressionWithAssignment("minus", expression);
                break;

            case Types.MULTIPLY:
                evaluateBinaryExpression("multiply", expression);
                break;

            case Types.MULTIPLY_EQUAL:
                evaluateBinaryExpressionWithAssignment("multiply", expression);
                break;

            case Types.DIVIDE:
                evaluateBinaryExpression("div", expression);
                break;

            case Types.DIVIDE_EQUAL:
                //SPG don't use divide since BigInteger implements directly
                //and we want to dispatch through DefaultGroovyMethods to get a BigDecimal result
                evaluateBinaryExpressionWithAssignment("div", expression);
                break;

            case Types.INTDIV:
                evaluateBinaryExpression("intdiv", expression);
                break;

            case Types.INTDIV_EQUAL:
                evaluateBinaryExpressionWithAssignment("intdiv", expression);
                break;

            case Types.MOD:
                evaluateBinaryExpression("mod", expression);
                break;

            case Types.MOD_EQUAL:
                evaluateBinaryExpressionWithAssignment("mod", expression);
                break;

            case Types.POWER:
                evaluateBinaryExpression("power", expression);
                break;

            case Types.POWER_EQUAL:
                evaluateBinaryExpressionWithAssignment("power", expression);
                break;

            case Types.LEFT_SHIFT:
                evaluateBinaryExpression("leftShift", expression);
                break;

            case Types.LEFT_SHIFT_EQUAL:
                evaluateBinaryExpressionWithAssignment("leftShift", expression);
                break;

            case Types.RIGHT_SHIFT:
                evaluateBinaryExpression("rightShift", expression);
                break;

            case Types.RIGHT_SHIFT_EQUAL:
                evaluateBinaryExpressionWithAssignment("rightShift", expression);
                break;

            case Types.RIGHT_SHIFT_UNSIGNED:
                evaluateBinaryExpression("rightShiftUnsigned", expression);
                break;

            case Types.RIGHT_SHIFT_UNSIGNED_EQUAL:
                evaluateBinaryExpressionWithAssignment("rightShiftUnsigned", expression);
                break;

            case Types.KEYWORD_INSTANCEOF:
                evaluateInstanceof(expression);
                break;

            case Types.FIND_REGEX:
                evaluateBinaryExpression(findRegexMethod, expression);
                break;

            case Types.MATCH_REGEX:
                evaluateBinaryExpression(matchRegexMethod, expression);
                break;

            case Types.LEFT_SQUARE_BRACKET:
                if (leftHandExpression) {
                    throwException("Should not be called here. Possible reason: postfix operation on array.");
                    // This is handled right now in the evaluateEqual()
                    // should support this here later
                    //evaluateBinaryExpression("putAt", expression);
                } else {
                    evaluateBinaryExpression("getAt", expression);
                }
                break;

            case Types.KEYWORD_IN:
                evaluateBinaryExpression(isCaseMethod, expression);
                break;

            default:
                throwException("Operation: " + expression.getOperation() + " not supported");
        }
        record(expression.getOperation(), isComparisonExpression(expression));
    }

    private void load(Expression exp) {

        boolean wasLeft = leftHandExpression;
        leftHandExpression = false;
//        if (CREATE_DEBUG_INFO)
//            helper.mark("-- loading expression: " + exp.getClass().getName() +
//                    " at [" + exp.getLineNumber() + ":" + exp.getColumnNumber() + "]");
        //exp.visit(this);
        visitAndAutoboxBoolean(exp);
//        if (CREATE_DEBUG_INFO)
//            helper.mark(" -- end of loading --");

        leftHandExpression = wasLeft;
    }

    public void visitPostfixExpression(PostfixExpression expression) {
        switch (expression.getOperation().getType()) {
            case Types.PLUS_PLUS:
                evaluatePostfixMethod("next", expression.getExpression());
                break;
            case Types.MINUS_MINUS:
                evaluatePostfixMethod("previous", expression.getExpression());
                break;
        }
        record(expression);
    }

    private void record(Token token, boolean unboxedValue) {
        if (assertionTracker == null) return;
        if (unboxedValue) {
            mv.visitInsn(DUP);
            helper.boxBoolean();
        }
        record(assertionTracker.sourceText.getNormalizedColumn(token.getStartLine(), token.getStartColumn()));
        if (unboxedValue) mv.visitInsn(POP);
    }

    private void record(Expression expression) {
        if (assertionTracker == null) return;
        record(assertionTracker.sourceText.getNormalizedColumn(expression.getLineNumber(), expression.getColumnNumber()));
    }

    private void recordBool(Expression expression) {
        if (assertionTracker == null) return;
        mv.visitInsn(DUP);
        helper.boxBoolean();
        record(expression);
        mv.visitInsn(POP);
    }

    private void record(int normalizedColumn) {
        if (assertionTracker == null) return;
        mv.visitVarInsn(ALOAD, assertionTracker.recorderIndex);
        helper.swapWithObject(ClassHelper.OBJECT_TYPE);
        mv.visitLdcInsn(normalizedColumn);
        mv.visitMethodInsn(
                INVOKEVIRTUAL,
                "org/codehaus/groovy/transform/powerassert/ValueRecorder",
                "record",
                "(Ljava/lang/Object;I)Ljava/lang/Object;");
    }

    private void throwException(String s) {
        throw new RuntimeParserException(s, currentASTNode);
    }

    public void visitPrefixExpression(PrefixExpression expression) {
        switch (expression.getOperation().getType()) {
            case Types.PLUS_PLUS:
                evaluatePrefixMethod("next", expression.getExpression());
                break;
            case Types.MINUS_MINUS:
                evaluatePrefixMethod("previous", expression.getExpression());
                break;
        }
        record(expression);
    }

    public void visitClosureExpression(ClosureExpression expression) {
        ClassNode innerClass = closureClassMap.get(expression);
        if (innerClass == null) {
            innerClass = createClosureClass(expression);
            closureClassMap.put(expression, innerClass);
            addInnerClass(innerClass);
            innerClass.addInterface(ClassHelper.GENERATED_CLOSURE_Type);
        }

        String innerClassinternalName = BytecodeHelper.getClassInternalName(innerClass);
        passingParams = true;
        List constructors = innerClass.getDeclaredConstructors();
        ConstructorNode node = (ConstructorNode) constructors.get(0);

        Parameter[] localVariableParams = node.getParameters();

        mv.visitTypeInsn(NEW, innerClassinternalName);
        mv.visitInsn(DUP);
        if ((isStaticMethod() || specialCallWithinConstructor) && !classNode.declaresInterface(ClassHelper.GENERATED_CLOSURE_Type)) {
            visitClassExpression(new ClassExpression(classNode));
            visitClassExpression(new ClassExpression(getOutermostClass()));
        } else {
            mv.visitVarInsn(ALOAD, 0);
            loadThis();
        }

        // now let's load the various parameters we're passing
        // we start at index 2 because the first variable we pass
        // is the owner instance and at this point it is already
        // on the stack
        for (int i = 2; i < localVariableParams.length; i++) {
            Parameter param = localVariableParams[i];
            String name = param.getName();

            // compileStack.containsVariable(name) means to ask if the variable is already declared
            // compileStack.getScope().isReferencedClassVariable(name) means to ask if the variable is a field
            // If it is no field and is not yet declared, then it is either a closure shared variable or
            // an already declared variable.
            if (!compileStack.containsVariable(name) && compileStack.getScope().isReferencedClassVariable(name)) {
                visitFieldExpression(new FieldExpression(classNode.getDeclaredField(name)));
            } else {
                Variable v = compileStack.getVariable(name, !classNodeUsesReferences());
                if (v == null) {
                    // variable is not on stack because we are
                    // inside a nested Closure and this variable
                    // was not used before
                    // then load it from the Closure field
                    FieldNode field = classNode.getDeclaredField(name);
                    mv.visitVarInsn(ALOAD, 0);
                    mv.visitFieldInsn(GETFIELD, internalClassName, name, BytecodeHelper.getTypeDescription(field.getType()));
                    // and define it
                    // Note:
                    // we can simply define it here and don't have to
                    // be afraid about name problems because a second
                    // variable with that name is not allowed inside the closure
                    param.setClosureSharedVariable(false);
                    v = compileStack.defineVariable(param, true);
                    param.setClosureSharedVariable(true);
                    v.setHolder(true);
                }
                mv.visitVarInsn(ALOAD, v.getIndex());
            }
        }
        passingParams = false;

        // we may need to pass in some other constructors
        //cv.visitMethodInsn(INVOKESPECIAL, innerClassinternalName, "<init>", prototype + ")V");
        mv.visitMethodInsn(
                INVOKESPECIAL,
                innerClassinternalName,
                "<init>",
                BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, localVariableParams));
    }

    private boolean classNodeUsesReferences() {
        boolean ret = classNode.getSuperClass() == ClassHelper.CLOSURE_TYPE;
        if (ret) return ret;
        if (classNode instanceof InnerClassNode) {
            InnerClassNode inner = (InnerClassNode) classNode;
            return inner.isAnonymous();
        }
        return false;
    }

    /**
     * Loads either this object or if we're inside a closure then load the top level owner
     */
    protected void loadThisOrOwner() {
        if (isInnerClass()) {
            visitFieldExpression(new FieldExpression(classNode.getDeclaredField("owner")));
        } else {
            loadThis();
        }
    }

    @Deprecated
    public void visitRegexExpression(RegexExpression expression) {
        expression.getRegex().visit(this);
        regexPattern.call(mv);
    }

    /**
     * Generate byte code for constants
     *
     * @see <a href="http://java.sun.com/docs/books/vmspec/2nd-edition/html/ClassFile.doc.html#14152">Class field types</a>
     */
    public void visitConstantExpression(ConstantExpression expression) {
        final String constantName = expression.getConstantName();
        if ((methodNode != null && methodNode.getName().equals("<clinit>")) || constantName == null) {
            Object value = expression.getValue();
            helper.loadConstant(value);
        } else {
            mv.visitFieldInsn(GETSTATIC, internalClassName, constantName, BytecodeHelper.getTypeDescription(expression.getType()));
        }
    }

    public void visitSpreadExpression(SpreadExpression expression) {
        throw new GroovyBugError("SpreadExpression should not be visited here");
    }

    public void visitSpreadMapExpression(SpreadMapExpression expression) {
        Expression subExpression = expression.getExpression();
        // to not record the underlying MapExpression twice, we set
        // assertionTracker = null
        // see http://jira.codehaus.org/browse/GROOVY-3421
        AssertionTracker old = assertionTracker;
        assertionTracker = null;

        subExpression.visit(this);
        spreadMap.call(mv);

        assertionTracker = old;
    }

    public void visitMethodPointerExpression(MethodPointerExpression expression) {
        Expression subExpression = expression.getExpression();
        subExpression.visit(this);
        loadDynamicName(expression.getMethodName());
        getMethodPointer.call(mv);
    }

    private void loadDynamicName(Expression name) {
        if (name instanceof ConstantExpression) {
            ConstantExpression ce = (ConstantExpression) name;
            Object value = ce.getValue();
            if (value instanceof String) {
                helper.loadConstant(value);
                return;
            }
        }
        new CastExpression(ClassHelper.STRING_TYPE, name).visit(this);
    }

    public void visitUnaryMinusExpression(UnaryMinusExpression expression) {
        Expression subExpression = expression.getExpression();
        subExpression.visit(this);
        unaryMinus.call(mv);
        record(expression);
    }

    public void visitUnaryPlusExpression(UnaryPlusExpression expression) {
        Expression subExpression = expression.getExpression();
        subExpression.visit(this);
        unaryPlus.call(mv);
        record(expression);
    }

    public void visitBitwiseNegationExpression(BitwiseNegationExpression expression) {
        Expression subExpression = expression.getExpression();
        subExpression.visit(this);
        bitwiseNegate.call(mv);
        record(expression);
    }

    public void visitCastExpression(CastExpression castExpression) {
        ClassNode type = castExpression.getType();
        visitAndAutoboxBoolean(castExpression.getExpression());
        final ClassNode rht = rightHandType;
        rightHandType = castExpression.getExpression().getType();
        doConvertAndCast(type, castExpression.getExpression(), castExpression.isIgnoringAutoboxing(), false, castExpression.isCoerce());
        rightHandType = rht;
    }

    public void visitNotExpression(NotExpression expression) {
        Expression subExpression = expression.getExpression();
        subExpression.visit(this);
        // if we do !object, then the cast to boolean will
        // do the conversion of Object to boolean. so a simple
        // call to unbox is enough here.
        if (
                !isComparisonExpression(subExpression) &&
                        !(subExpression instanceof BooleanExpression)) {
            helper.unbox(boolean.class);
        }
        helper.negateBoolean();
        recordBool(expression);
    }

    /**
     * return a primitive boolean value of the BooleanExpresion.
     *
     * @param expression
     */
    public void visitBooleanExpression(BooleanExpression expression) {
        compileStack.pushBooleanExpression();
        expression.getExpression().visit(this);

        if (!isComparisonExpression(expression.getExpression())) {
// comment out for optimization when boolean values are not autoboxed for eg. function calls.
//           Class typeClass = expression.getExpression().getTypeClass();
//           if (typeClass != null && typeClass != boolean.class) {
            helper.unbox(boolean.class); // to return a primitive boolean
//            }
        }
        compileStack.pop();
    }

    private void makeInvokeMethodCall(MethodCallExpression call, boolean useSuper, MethodCallerMultiAdapter adapter) {
        // receiver
        // we operate on GroovyObject if possible
        Expression objectExpression = call.getObjectExpression();
        if (!isStaticMethod() && !isStaticContext() && isThisExpression(call.getObjectExpression())) {
            objectExpression = new CastExpression(classNode, objectExpression);
        }
        // message name
        Expression messageName = new CastExpression(ClassHelper.STRING_TYPE, call.getMethod());
        if (useSuper) {
            ClassNode superClass = isInClosure() ?
                    getOutermostClass().getSuperClass() : classNode.getSuperClass(); // GROOVY-4035
            makeCall(new ClassExpression(superClass),
                    objectExpression, messageName,
                    call.getArguments(), adapter,
                    call.isSafe(), call.isSpreadSafe(),
                    false
            );
        } else {
            makeCall(objectExpression, messageName,
                    call.getArguments(), adapter,
                    call.isSafe(), call.isSpreadSafe(),
                    call.isImplicitThis()
            );
        }
    }

    private void makeCall(
            Expression receiver, Expression message, Expression arguments,
            MethodCallerMultiAdapter adapter,
            boolean safe, boolean spreadSafe, boolean implicitThis
    ) {
        ClassNode cn = classNode;
        if (isInClosure() && !implicitThis /*&& AsmClassGenerator.isThisExpression(receiver)*/) cn = cn.getOuterClass();
        makeCall(new ClassExpression(cn), receiver, message, arguments,
                adapter, safe, spreadSafe, implicitThis);
    }

    private void makeCall(
            ClassExpression sender,
            Expression receiver, Expression message, Expression arguments,
            MethodCallerMultiAdapter adapter,
            boolean safe, boolean spreadSafe, boolean implicitThis
    ) {
        if ((adapter == invokeMethod || adapter == invokeMethodOnCurrent || adapter == invokeStaticMethod) && !spreadSafe) {
            String methodName = getMethodName(message);

            if (methodName != null) {
                makeCallSite(receiver, methodName, arguments, safe, implicitThis, adapter == invokeMethodOnCurrent, adapter == invokeStaticMethod);
                return;
            }
        }

        // ensure VariableArguments are read, not stored
        boolean lhs = leftHandExpression;
        leftHandExpression = false;

        // sender
        sender.visit(this);
        // receiver
        boolean oldVal = this.implicitThis;
        this.implicitThis = implicitThis;
        visitAndAutoboxBoolean(receiver);
        this.implicitThis = oldVal;
        // message
        if (message != null) message.visit(this);

        // arguments
        boolean containsSpreadExpression = containsSpreadExpression(arguments);
        int numberOfArguments = containsSpreadExpression ? -1 : argumentSize(arguments);
        if (numberOfArguments > MethodCallerMultiAdapter.MAX_ARGS || containsSpreadExpression) {
            ArgumentListExpression ae;
            if (arguments instanceof ArgumentListExpression) {
                ae = (ArgumentListExpression) arguments;
            } else if (arguments instanceof TupleExpression) {
                TupleExpression te = (TupleExpression) arguments;
                ae = new ArgumentListExpression(te.getExpressions());
            } else {
                ae = new ArgumentListExpression();
                ae.addExpression(arguments);
            }
            if (containsSpreadExpression) {
                despreadList(ae.getExpressions(), true);
            } else {
                ae.visit(this);
            }
        } else if (numberOfArguments > 0) {
            TupleExpression te = (TupleExpression) arguments;
            for (int i = 0; i < numberOfArguments; i++) {
                Expression argument = te.getExpression(i);
                visitAndAutoboxBoolean(argument);
                if (argument instanceof CastExpression) loadWrapper(argument);
            }
        }

        adapter.call(mv, numberOfArguments, safe, spreadSafe);

        leftHandExpression = lhs;
    }

    private void makeGetPropertySite(Expression receiver, String methodName, boolean safe, boolean implicitThis) {
        if (isNotClinit()) {
            mv.visitVarInsn(ALOAD, callSiteArrayVarIndex);
        } else {
            mv.visitMethodInsn(INVOKESTATIC, getClassName(), "$getCallSiteArray", "()[Lorg/codehaus/groovy/runtime/callsite/CallSite;");
        }
        final int index = allocateIndex(methodName);
        mv.visitLdcInsn(index);
        mv.visitInsn(AALOAD);

        // site
        boolean lhs = leftHandExpression;
        leftHandExpression = false;
        boolean oldVal = this.implicitThis;
        this.implicitThis = implicitThis;
        visitAndAutoboxBoolean(receiver);
        this.implicitThis = oldVal;
        if (!safe)
            mv.visitMethodInsn(INVOKEINTERFACE, "org/codehaus/groovy/runtime/callsite/CallSite", "callGetProperty", "(Ljava/lang/Object;)Ljava/lang/Object;");
        else {
            mv.visitMethodInsn(INVOKEINTERFACE, "org/codehaus/groovy/runtime/callsite/CallSite", "callGetPropertySafe", "(Ljava/lang/Object;)Ljava/lang/Object;");
        }
        leftHandExpression = lhs;
    }

    private void makeGroovyObjectGetPropertySite(Expression receiver, String methodName, boolean safe, boolean implicitThis) {
        if (isNotClinit()) {
            mv.visitVarInsn(ALOAD, callSiteArrayVarIndex);
        } else {
            mv.visitMethodInsn(INVOKESTATIC, getClassName(), "$getCallSiteArray", "()[Lorg/codehaus/groovy/runtime/callsite/CallSite;");
        }
        final int index = allocateIndex(methodName);
        mv.visitLdcInsn(index);
        mv.visitInsn(AALOAD);

        // site
        boolean lhs = leftHandExpression;
        leftHandExpression = false;
        boolean oldVal = this.implicitThis;
        this.implicitThis = implicitThis;
        visitAndAutoboxBoolean(receiver);
        this.implicitThis = oldVal;
        if (!safe)
            mv.visitMethodInsn(INVOKEINTERFACE, "org/codehaus/groovy/runtime/callsite/CallSite", "callGroovyObjectGetProperty", "(Ljava/lang/Object;)Ljava/lang/Object;");
        else {
            mv.visitMethodInsn(INVOKEINTERFACE, "org/codehaus/groovy/runtime/callsite/CallSite", "callGroovyObjectGetPropertySafe", "(Ljava/lang/Object;)Ljava/lang/Object;");
        }
        leftHandExpression = lhs;
    }

    private String getMethodName(Expression message) {
        String methodName = null;
        if (message instanceof CastExpression) {
            CastExpression msg = (CastExpression) message;
            if (msg.getType() == ClassHelper.STRING_TYPE) {
                final Expression methodExpr = msg.getExpression();
                if (methodExpr instanceof ConstantExpression)
                    methodName = methodExpr.getText();
            }
        }

        if (methodName == null && message instanceof ConstantExpression) {
            ConstantExpression constantExpression = (ConstantExpression) message;
            methodName = constantExpression.getText();
        }
        return methodName;
    }

    private void makeCallSite(Expression receiver, String message, Expression arguments, boolean safe, boolean implicitThis, boolean callCurrent, boolean callStatic) {
        if (isNotClinit()) {
            mv.visitVarInsn(ALOAD, callSiteArrayVarIndex);
        } else {
            mv.visitMethodInsn(INVOKESTATIC, getClassName(), "$getCallSiteArray", "()[Lorg/codehaus/groovy/runtime/callsite/CallSite;");
        }
        final int index = allocateIndex(message);
        mv.visitLdcInsn(index);
        mv.visitInsn(AALOAD);

        boolean constructor = message.equals(CONSTRUCTOR);

        // ensure VariableArguments are read, not stored
        boolean lhs = leftHandExpression;
        leftHandExpression = false;

        // receiver
        boolean oldVal = this.implicitThis;
        this.implicitThis = implicitThis;
        visitAndAutoboxBoolean(receiver);
        this.implicitThis = oldVal;

        // arguments
        boolean containsSpreadExpression = containsSpreadExpression(arguments);
        int numberOfArguments = containsSpreadExpression ? -1 : argumentSize(arguments);
        if (numberOfArguments > MethodCallerMultiAdapter.MAX_ARGS || containsSpreadExpression) {
            ArgumentListExpression ae;
            if (arguments instanceof ArgumentListExpression) {
                ae = (ArgumentListExpression) arguments;
            } else if (arguments instanceof TupleExpression) {
                TupleExpression te = (TupleExpression) arguments;
                ae = new ArgumentListExpression(te.getExpressions());
            } else {
                ae = new ArgumentListExpression();
                ae.addExpression(arguments);
            }
            if (containsSpreadExpression) {
                numberOfArguments = -1;
                despreadList(ae.getExpressions(), true);
            } else {
                numberOfArguments = ae.getExpressions().size();
                for (int i = 0; i < numberOfArguments; i++) {
                    Expression argument = ae.getExpression(i);
                    visitAndAutoboxBoolean(argument);
                    if (argument instanceof CastExpression) loadWrapper(argument);
                }
            }
        }

        if (numberOfArguments == -1) {
            // despreaded array already on stack
        } else {
            if (numberOfArguments > 4) {
                final String createArraySignature = getCreateArraySignature(numberOfArguments);
                mv.visitMethodInsn(INVOKESTATIC, "org/codehaus/groovy/runtime/ArrayUtil", "createArray", createArraySignature);
            }
        }

        final String desc = getDescForParamNum(numberOfArguments);
        if (callStatic) {
            mv.visitMethodInsn(INVOKEINTERFACE, "org/codehaus/groovy/runtime/callsite/CallSite", "callStatic", "(Ljava/lang/Class;" + desc);
        } else if (constructor) {
            mv.visitMethodInsn(INVOKEINTERFACE, "org/codehaus/groovy/runtime/callsite/CallSite", "callConstructor", "(Ljava/lang/Object;" + desc);
        } else {
            if (callCurrent) {
                mv.visitMethodInsn(INVOKEINTERFACE, "org/codehaus/groovy/runtime/callsite/CallSite", "callCurrent", "(Lgroovy/lang/GroovyObject;" + desc);
            } else {
                if (safe) {
                    mv.visitMethodInsn(INVOKEINTERFACE, "org/codehaus/groovy/runtime/callsite/CallSite", "callSafe", "(Ljava/lang/Object;" + desc);
                } else {
                    mv.visitMethodInsn(INVOKEINTERFACE, "org/codehaus/groovy/runtime/callsite/CallSite", "call", "(Ljava/lang/Object;" + desc);
                }
            }
        }
        leftHandExpression = lhs;
    }

    private static String getDescForParamNum(int numberOfArguments) {
        switch (numberOfArguments) {
            case 0:
                return ")Ljava/lang/Object;";
            case 1:
                return "Ljava/lang/Object;)Ljava/lang/Object;";
            case 2:
                return "Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;";
            case 3:
                return "Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;";
            case 4:
                return "Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;";
            default:
                return "[Ljava/lang/Object;)Ljava/lang/Object;";
        }
    }

    private static String[] sig = new String[255];

    private static String getCreateArraySignature(int numberOfArguments) {
        if (sig[numberOfArguments] == null) {
            StringBuilder sb = new StringBuilder("(");
            for (int i = 0; i != numberOfArguments; ++i) {
                sb.append("Ljava/lang/Object;");
            }
            sb.append(")[Ljava/lang/Object;");
            sig[numberOfArguments] = sb.toString();
        }
        return sig[numberOfArguments];
    }

    private static final HashSet<String> names = new HashSet<String>();
    private static final HashSet<String> basic = new HashSet<String>();

    static {
        Collections.addAll(names, "plus", "minus", "multiply", "div", "compareTo", "or", "and", "xor", "intdiv", "mod", "leftShift", "rightShift", "rightShiftUnsigned");
        Collections.addAll(basic, "plus", "minus", "multiply", "div");
    }

    private void makeBinopCallSite(Expression receiver, String message, Expression arguments) {

        prepareCallSite(message);

        // site

        // ensure VariableArguments are read, not stored
        boolean lhs = leftHandExpression;
        leftHandExpression = false;
        boolean oldVal = this.implicitThis;
        this.implicitThis = false;
        visitAndAutoboxBoolean(receiver);
        this.implicitThis = oldVal;
        visitAndAutoboxBoolean(arguments);
        mv.visitMethodInsn(INVOKEINTERFACE, "org/codehaus/groovy/runtime/callsite/CallSite", "call", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
        leftHandExpression = lhs;
    }

    private void prepareCallSite(String message) {
        if (isNotClinit()) {
            mv.visitVarInsn(ALOAD, callSiteArrayVarIndex);
        } else {
            mv.visitMethodInsn(INVOKESTATIC, getClassName(), "$getCallSiteArray", "()[Lorg/codehaus/groovy/runtime/callsite/CallSite;");
        }
        final int index = allocateIndex(message);
        mv.visitLdcInsn(index);
        mv.visitInsn(AALOAD);
    }

    private String getClassName() {
        String className;
        if (!classNode.isInterface() || interfaceClassLoadingClass == null) {
            className = internalClassName;
        } else {
            className = BytecodeHelper.getClassInternalName(interfaceClassLoadingClass);
        }
        return className;
    }

    private int allocateIndex(String name) {
        callSites.add(name);
        return callSites.size() - 1;
    }

    private void despreadList(List<Expression> expressions, boolean wrap) {

        List<Expression> spreadIndexes = new ArrayList<Expression>();
        List<Expression> spreadExpressions = new ArrayList<Expression>();
        List<Expression> normalArguments = new ArrayList<Expression>();
        for (int i = 0; i < expressions.size(); i++) {
            Expression expr = expressions.get(i);
            if (!(expr instanceof SpreadExpression)) {
                normalArguments.add(expr);
            } else {
                spreadIndexes.add(new ConstantExpression(i - spreadExpressions.size()));
                spreadExpressions.add(((SpreadExpression) expr).getExpression());
            }
        }

        //load normal arguments as array
        visitTupleExpression(new ArgumentListExpression(normalArguments), wrap);
        //load spread expressions as array
        (new TupleExpression(spreadExpressions)).visit(this);
        //load insertion index
        (new ArrayExpression(ClassHelper.int_TYPE, spreadIndexes, null)).visit(this);
        despreadList.call(mv);
    }

    public void visitMethodCallExpression(MethodCallExpression call) {
        onLineNumber(call, "visitMethodCallExpression: \"" + call.getMethod() + "\":");

        // Closure calls of the form "localVariable(args)" have already
        // been rewritten to "localVariable.call(args)" by VariableScopeVisitor
        // and can be treated like regular method calls here.
        // Qualified closure calls such as "foo.bar(args)" (where foo.bar returns
        // a closure) or even "this.someField(args)" are also treated like regular
        // method calls here, and will be handled by the MOP.
        if (isUnqualifiedClosureFieldCall(call)) {
            // let's invoke the closure method
            invokeClosure(call.getArguments(), call.getMethodAsString());
            record(call.getObjectExpression());
        } else {
            boolean isSuperMethodCall = usesSuper(call);
            MethodCallerMultiAdapter adapter = invokeMethod;
            if (isThisExpression(call.getObjectExpression())) adapter = invokeMethodOnCurrent;
            if (isSuperMethodCall) adapter = invokeMethodOnSuper;
            if (isStaticInvocation(call)) adapter = invokeStaticMethod;
            makeInvokeMethodCall(call, isSuperMethodCall, adapter);

            record(call.getMethod());
        }
    }

    /**
     * Tells if the specified MethodCallExpression represents a closure call
     * of the form "someField(args)".
     */
    private boolean isUnqualifiedClosureFieldCall(MethodCallExpression call) {
        // are we a local variable?
        // it should not be an explicitly "this" qualified method call
        // and the current class should not have a possible method
        String methodName = call.getMethodAsString();
        if (methodName == null) return false;
        if (!call.isImplicitThis()) return false;
        if (!isThisExpression(call.getObjectExpression())) return false;
        FieldNode field = classNode.getDeclaredField(methodName);
        if (field == null) return false;
        if (isStaticInvocation(call) && !field.isStatic()) return false;
        Expression arguments = call.getArguments();
        return !classNode.hasPossibleMethod(methodName, arguments);
    }

    private void invokeClosure(Expression arguments, String methodName) {
        visitVariableExpression(new VariableExpression(methodName));
        if (arguments instanceof TupleExpression) {
            arguments.visit(this);
        } else {
            new TupleExpression(arguments).visit(this);
        }
        invokeClosureMethod.call(mv);
    }

    private boolean isStaticInvocation(MethodCallExpression call) {
        if (!isThisExpression(call.getObjectExpression())) return false;
        if (isStaticMethod()) return true;
        return isStaticContext() && !call.isImplicitThis();
    }

    protected boolean emptyArguments(Expression arguments) {
        return argumentSize(arguments) == 0;
    }

    protected static boolean containsSpreadExpression(Expression arguments) {
        List<Expression> args = null;
        if (arguments instanceof TupleExpression) {
            TupleExpression tupleExpression = (TupleExpression) arguments;
            args = tupleExpression.getExpressions();
        } else if (arguments instanceof ListExpression) {
            ListExpression le = (ListExpression) arguments;
            args = le.getExpressions();
        } else {
            return arguments instanceof SpreadExpression;
        }
        for (Expression arg : args) {
            if (arg instanceof SpreadExpression) return true;
        }
        return false;
    }

    protected static int argumentSize(Expression arguments) {
        if (arguments instanceof TupleExpression) {
            TupleExpression tupleExpression = (TupleExpression) arguments;
            return tupleExpression.getExpressions().size();
        }
        return 1;
    }

    public void visitStaticMethodCallExpression(StaticMethodCallExpression call) {
        onLineNumber(call, "visitStaticMethodCallExpression: \"" + call.getMethod() + "\":");

        makeCall(
                new ClassExpression(call.getOwnerType()),
                new ConstantExpression(call.getMethod()),
                call.getArguments(),
                invokeStaticMethod,
                false, false, false);

        record(call);
    }

    private void addGeneratedClosureConstructorCall(ConstructorCallExpression call) {
        mv.visitVarInsn(ALOAD, 0);
        ClassNode callNode = classNode.getSuperClass();
        TupleExpression arguments = (TupleExpression) call.getArguments();
        if (arguments.getExpressions().size() != 2)
            throw new GroovyBugError("expected 2 arguments for closure constructor super call, but got" + arguments.getExpressions().size());
        arguments.getExpression(0).visit(this);
        arguments.getExpression(1).visit(this);
        Parameter p = new Parameter(ClassHelper.OBJECT_TYPE, "_p");
        String descriptor = BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, new Parameter[]{p, p});
        mv.visitMethodInsn(INVOKESPECIAL, BytecodeHelper.getClassInternalName(callNode), "<init>", descriptor);
    }

    private void visitSpecialConstructorCall(ConstructorCallExpression call) {
        if (classNode.declaresInterface(ClassHelper.GENERATED_CLOSURE_Type)) {
            addGeneratedClosureConstructorCall(call);
            return;
        }

        ClassNode callNode = classNode;
        if (call.isSuperCall()) callNode = callNode.getSuperClass();
        List constructors = sortConstructors(call, callNode);
        call.getArguments().visit(this);
        // keep Object[] on stack
        mv.visitInsn(DUP);
        // to select the constructor we need also the number of
        // available constructors and the class we want to make
        // the call on
        helper.pushConstant(constructors.size());
        visitClassExpression(new ClassExpression(callNode));
        // removes one Object[] leaves the int containing the
        // call flags and the constructor number
        selectConstructorAndTransformArguments.call(mv);
        // Object[],int -> int,Object[],int
        // we need to examine the flags and maybe change the
        // Object[] later, so this reordering will do the job
        mv.visitInsn(DUP_X1);
        // test if rewrap flag is set
        mv.visitInsn(ICONST_1);
        mv.visitInsn(IAND);
        Label afterIf = new Label();
        mv.visitJumpInsn(IFEQ, afterIf);
        // true part, so rewrap using the first argument
        mv.visitInsn(ICONST_0);
        mv.visitInsn(AALOAD);
        mv.visitTypeInsn(CHECKCAST, "[Ljava/lang/Object;");
        mv.visitLabel(afterIf);
        // here the stack is int,Object[], but we need the
        // the int for our table, so swap it
        mv.visitInsn(SWAP);
        //load "this"
        if (constructorNode != null) {
            mv.visitVarInsn(ALOAD, 0);
        } else {
            mv.visitTypeInsn(NEW, BytecodeHelper.getClassInternalName(callNode));
        }
        mv.visitInsn(SWAP);
        //prepare switch with >>8
        mv.visitIntInsn(BIPUSH, 8);
        mv.visitInsn(ISHR);
        Label[] targets = new Label[constructors.size()];
        int[] indices = new int[constructors.size()];
        for (int i = 0; i < targets.length; i++) {
            targets[i] = new Label();
            indices[i] = i;
        }
        // create switch targets
        Label defaultLabel = new Label();
        Label afterSwitch = new Label();
        mv.visitLookupSwitchInsn(defaultLabel, indices, targets);
        for (int i = 0; i < targets.length; i++) {
            mv.visitLabel(targets[i]);
            // to keep the stack height, we need to leave
            // one Object[] on the stack as last element. At the
            // same time, we need the Object[] on top of the stack
            // to extract the parameters.
            if (constructorNode != null) {
                // in this case we need one "this", so a SWAP will exchange
                // "this" and Object[], a DUP_X1 will then copy the Object[]
                /// to the last place in the stack:
                //     Object[],this -SWAP-> this,Object[]
                //     this,Object[] -DUP_X1-> Object[],this,Object[]
                mv.visitInsn(SWAP);
                mv.visitInsn(DUP_X1);
            } else {
                // in this case we need two "this" in between and the Object[]
                // at the bottom of the stack as well as on top for our invokeSpecial
                // So we do DUP_X1, DUP2_X1, POP
                //     Object[],this -DUP_X1-> this,Object[],this
                //     this,Object[],this -DUP2_X1-> Object[],this,this,Object[],this
                //     Object[],this,this,Object[],this -POP->  Object[],this,this,Object[]
                mv.visitInsn(DUP_X1);
                mv.visitInsn(DUP2_X1);
                mv.visitInsn(POP);
            }

            ConstructorNode cn = (ConstructorNode) constructors.get(i);
            String descriptor = BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, cn.getParameters());
            // unwrap the Object[] and make transformations if needed
            // that means, to duplicate the Object[], make a cast with possible
            // unboxing and then swap it with the Object[] for each parameter
            Parameter[] parameters = cn.getParameters();
            for (int p = 0; p < parameters.length; p++) {
                mv.visitInsn(DUP);
                helper.pushConstant(p);
                mv.visitInsn(AALOAD);
                ClassNode type = parameters[p].getType();
                if (ClassHelper.isPrimitiveType(type)) {
                    helper.unbox(type);
                } else {
                    helper.doCast(type);
                }
                helper.swapWithObject(type);
            }
            // at the end we remove the Object[]
            mv.visitInsn(POP);
            // make the constructor call
            mv.visitMethodInsn(INVOKESPECIAL, BytecodeHelper.getClassInternalName(callNode), "<init>", descriptor);
            mv.visitJumpInsn(GOTO, afterSwitch);
        }
        mv.visitLabel(defaultLabel);
        // this part should never be reached!
        mv.visitTypeInsn(NEW, "java/lang/IllegalArgumentException");
        mv.visitInsn(DUP);
        mv.visitLdcInsn("illegal constructor number");
        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/IllegalArgumentException", "<init>", "(Ljava/lang/String;)V");
        mv.visitInsn(ATHROW);
        mv.visitLabel(afterSwitch);

        // to keep the stack height we kept one object on the stack
        // for the switch, now we remove that object
        if (constructorNode == null) {
            // but in case we are not in a constructor we have an additional
            // object on the stack, the result of our constructor call
            // which we want to keep, so we swap the arguments to remove
            // the right one
            mv.visitInsn(SWAP);
        }
        mv.visitInsn(POP);
    }

    private List sortConstructors(ConstructorCallExpression call, ClassNode callNode) {
        // sort in a new list to prevent side effects
        List constructors = new ArrayList(callNode.getDeclaredConstructors());
        Comparator comp = new Comparator() {
            public int compare(Object arg0, Object arg1) {
                ConstructorNode c0 = (ConstructorNode) arg0;
                ConstructorNode c1 = (ConstructorNode) arg1;
                String descriptor0 = BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, c0.getParameters());
                String descriptor1 = BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, c1.getParameters());
                return descriptor0.compareTo(descriptor1);
            }
        };
        Collections.sort(constructors, comp);
        return constructors;
    }

    public void visitConstructorCallExpression(ConstructorCallExpression call) {
        onLineNumber(call, "visitConstructorCallExpression: \"" + call.getType().getName() + "\":");

        if (call.isSpecialCall()) {
            specialCallWithinConstructor = true;
            visitSpecialConstructorCall(call);
            // reset the variable
            specialCallWithinConstructor = false;
            return;
        }

        Expression arguments = call.getArguments();
        if (arguments instanceof TupleExpression) {
            TupleExpression tupleExpression = (TupleExpression) arguments;
            int size = tupleExpression.getExpressions().size();
            if (size == 0) {
                arguments = MethodCallExpression.NO_ARGUMENTS;
            }
        }

        Expression receiverClass = new ClassExpression(call.getType());
        makeCallSite(
                receiverClass, CONSTRUCTOR,
                arguments, false, false, false,
                false);
        record(call);
    }

    private static String makeFieldClassName(ClassNode type) {
        String internalName = BytecodeHelper.getClassInternalName(type);
        StringBuffer ret = new StringBuffer(internalName.length());
        for (int i = 0; i < internalName.length(); i++) {
            char c = internalName.charAt(i);
            if (c == '/') {
                ret.append('$');
            } else if (c == ';') {
                //append nothing -> delete ';'
            } else {
                ret.append(c);
            }
        }
        return ret.toString();
    }

    private static String getStaticFieldName(ClassNode type) {
        ClassNode componentType = type;
        String prefix = "";
        for (; componentType.isArray(); componentType = componentType.getComponentType()) {
            prefix += "$";
        }
        if (prefix.length() != 0) prefix = "array" + prefix;
        String name = prefix + "$class$" + makeFieldClassName(componentType);
        return name;
    }

    private void visitAttributeOrProperty(PropertyExpression expression, MethodCallerMultiAdapter adapter) {
        Expression objectExpression = expression.getObjectExpression();
        if (isThisOrSuper(objectExpression)) {
            // let's use the field expression if it's available
            String name = expression.getPropertyAsString();
            if (name != null) {
                FieldNode field = null;
                boolean privateSuperField = false;
                if (isSuperExpression(objectExpression)) {
                    field = classNode.getSuperClass().getDeclaredField(name);
                    if (field != null && ((field.getModifiers() & ACC_PRIVATE) != 0)) {
                      privateSuperField = true;
                    }
                } else {
                    if (isNotExplicitThisInClosure(expression.isImplicitThis())) {
                        field = classNode.getDeclaredField(name);
                    }
                }
                if (field != null && !privateSuperField) {//GROOVY-4497: don't visit super field if it is private
                    visitFieldExpression(new FieldExpression(field));
                    return;
                }
            }
            if (isSuperExpression(objectExpression)) {
                String prefix;
                if (leftHandExpression) {
                    prefix = "set";
                } else {
                    prefix = "get";
                }
                String propName = prefix + MetaClassHelper.capitalize(name);
                visitMethodCallExpression(new MethodCallExpression(objectExpression, propName, MethodCallExpression.NO_ARGUMENTS));
                return;
            }
        }

        final String propName = expression.getPropertyAsString();
        //TODO: add support for super here too
        if (expression.getObjectExpression() instanceof ClassExpression &&
                propName != null && propName.equals("this")) {
            // we have something like A.B.this, and need to make it
            // into this.this$0.this$0, where this.this$0 returns
            // A.B and this.this$0.this$0 return A.
            ClassNode type = expression.getObjectExpression().getType();
            ClassNode iterType = classNode;
            mv.visitVarInsn(ALOAD, 0);
            while (!iterType.equals(type)) {
                String ownerName = BytecodeHelper.getClassInternalName(iterType);
                iterType = iterType.getOuterClass();
                String typeName = BytecodeHelper.getTypeDescription(iterType);
                mv.visitFieldInsn(GETFIELD, ownerName, "this$0", typeName);
            }
            return;
        }

        if (adapter == getProperty && !expression.isSpreadSafe() && propName != null) {
            makeGetPropertySite(objectExpression, propName, expression.isSafe(), expression.isImplicitThis());
        } else {
            // arguments already on stack if any
            if (adapter == getGroovyObjectProperty && !expression.isSpreadSafe() && propName != null) {
                makeGroovyObjectGetPropertySite(objectExpression, propName, expression.isSafe(), expression.isImplicitThis());
            } else {
                makeCall(
                        objectExpression, // receiver
                        new CastExpression(ClassHelper.STRING_TYPE, expression.getProperty()), // messageName
                        MethodCallExpression.NO_ARGUMENTS,
                        adapter,
                        expression.isSafe(), expression.isSpreadSafe(), expression.isImplicitThis()
                );
            }
        }
    }

    private boolean isStaticContext() {
        if (compileStack != null && compileStack.getScope() != null) {
            return compileStack.getScope().isInStaticContext();
        }
        if (!isInClosure()) return false;
        if (constructorNode != null) return false;
        return classNode.isStaticClass() || methodNode.isStatic();
    }

    public void visitPropertyExpression(PropertyExpression expression) {
        Expression objectExpression = expression.getObjectExpression();
        MethodCallerMultiAdapter adapter;
        if (leftHandExpression) {
            adapter = setProperty;
            if (isGroovyObject(objectExpression)) adapter = setGroovyObjectProperty;
            if (isStaticContext() && isThisOrSuper(objectExpression)) adapter = setProperty;
        } else {
            adapter = getProperty;
            if (isGroovyObject(objectExpression)) adapter = getGroovyObjectProperty;
            if (isStaticContext() && isThisOrSuper(objectExpression)) adapter = getProperty;
        }
        visitAttributeOrProperty(expression, adapter);
        record(expression.getProperty());
    }

    public void visitAttributeExpression(AttributeExpression expression) {
        Expression objectExpression = expression.getObjectExpression();
        MethodCallerMultiAdapter adapter;
        if (leftHandExpression) {
            adapter = setField;
            if (isGroovyObject(objectExpression)) adapter = setGroovyObjectField;
            if (usesSuper(expression)) adapter = setFieldOnSuper;
        } else {
            adapter = getField;
            if (isGroovyObject(objectExpression)) adapter = getGroovyObjectField;
            if (usesSuper(expression)) adapter = getFieldOnSuper;
        }
        visitAttributeOrProperty(expression, adapter);
        if (!leftHandExpression) record(expression.getProperty());
    }

    protected boolean isGroovyObject(Expression objectExpression) {
        return isThisExpression(objectExpression) || objectExpression.getType().isDerivedFromGroovyObject() && !(objectExpression instanceof ClassExpression);
    }

    public void visitFieldExpression(FieldExpression expression) {
        FieldNode field = expression.getField();

        if (field.isStatic()) {
            if (leftHandExpression) {
                storeStaticField(expression);
            } else {
                loadStaticField(expression);
            }
        } else {
            if (leftHandExpression) {
                storeThisInstanceField(expression);
            } else {
                loadInstanceField(expression);
            }
            record(expression);
        }
    }

    /**
     * @param fldExp
     */
    public void loadStaticField(FieldExpression fldExp) {
        FieldNode field = fldExp.getField();
        boolean holder = field.isHolder() && !isInClosureConstructor();
        ClassNode type = field.getType();

        String ownerName = (field.getOwner().equals(classNode))
                ? internalClassName
                : BytecodeHelper.getClassInternalName(field.getOwner());
        if (holder) {
            mv.visitFieldInsn(GETSTATIC, ownerName, fldExp.getFieldName(), BytecodeHelper.getTypeDescription(type));
            mv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "get", "()Ljava/lang/Object;");
        } else {
            mv.visitFieldInsn(GETSTATIC, ownerName, fldExp.getFieldName(), BytecodeHelper.getTypeDescription(type));
            if (ClassHelper.isPrimitiveType(type)) {
                helper.box(type);
            } else {
            }
        }
    }

    /**
     * RHS instance field. should move most of the code in the BytecodeHelper
     *
     * @param fldExp
     */
    public void loadInstanceField(FieldExpression fldExp) {
        FieldNode field = fldExp.getField();
        boolean holder = field.isHolder() && !isInClosureConstructor();
        ClassNode type = field.getType();
        String ownerName = (field.getOwner().equals(classNode))
                ? internalClassName
                : BytecodeHelper.getClassInternalName(field.getOwner());

        mv.visitVarInsn(ALOAD, 0);
        mv.visitFieldInsn(GETFIELD, ownerName, fldExp.getFieldName(), BytecodeHelper.getTypeDescription(type));

        if (holder) {
            mv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "get", "()Ljava/lang/Object;");
        } else {
            if (ClassHelper.isPrimitiveType(type)) {
                helper.box(type);
            } else {
            }
        }
    }

    public void storeThisInstanceField(FieldExpression expression) {
        FieldNode field = expression.getField();

        boolean holder = field.isHolder() && !isInClosureConstructor() && !expression.isUseReferenceDirectly();
        ClassNode type = field.getType();

        String ownerName = (field.getOwner().equals(classNode)) ?
                internalClassName : BytecodeHelper.getClassInternalName(field.getOwner());
        if (holder) {
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETFIELD, ownerName, expression.getFieldName(), BytecodeHelper.getTypeDescription(type));
            mv.visitInsn(SWAP);
            mv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "set", "(Ljava/lang/Object;)V");
        } else {
            if (isInClosureConstructor()) {
                helper.doCast(type);
            } else if (!ClassHelper.isPrimitiveType(type)) {
                doConvertAndCast(type);
            }
            mv.visitVarInsn(ALOAD, 0);
            //helper.swapObjectWith(type);
            mv.visitInsn(SWAP);
            helper.unbox(type);
            helper.putField(field, ownerName);
        }
    }


    public void storeStaticField(FieldExpression expression) {
        FieldNode field = expression.getField();

        boolean holder = field.isHolder() && !isInClosureConstructor();

        ClassNode type = field.getType();

        String ownerName = (field.getOwner().equals(classNode))
                ? internalClassName
                : BytecodeHelper.getClassInternalName(field.getOwner());
        if (holder) {
            mv.visitFieldInsn(GETSTATIC, ownerName, expression.getFieldName(), BytecodeHelper.getTypeDescription(type));
            mv.visitInsn(SWAP);
            mv.visitMethodInsn(INVOKEVIRTUAL, "groovy/lang/Reference", "set", "(Ljava/lang/Object;)V");
        } else {
            helper.doCast(type);
            mv.visitFieldInsn(PUTSTATIC, ownerName, expression.getFieldName(), BytecodeHelper.getTypeDescription(type));
        }
    }

    protected void visitOuterFieldExpression(FieldExpression expression, ClassNode outerClassNode, int steps, boolean first) {
        FieldNode field = expression.getField();
        boolean isStatic = field.isStatic();

        int tempIdx = compileStack.defineTemporaryVariable(field, leftHandExpression && first);

        if (steps > 1 || !isStatic) {
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(
                    GETFIELD,
                    internalClassName,
                    "owner",
                    BytecodeHelper.getTypeDescription(outerClassNode));
        }

        if (steps == 1) {
            int opcode = (leftHandExpression) ? ((isStatic) ? PUTSTATIC : PUTFIELD) : ((isStatic) ? GETSTATIC : GETFIELD);
            String ownerName = BytecodeHelper.getClassInternalName(outerClassNode);

            if (leftHandExpression) {
                mv.visitVarInsn(ALOAD, tempIdx);
                boolean holder = field.isHolder() && !isInClosureConstructor();
                if (!holder) {
                    doConvertAndCast(field.getType());
                }
            }
            mv.visitFieldInsn(opcode, ownerName, expression.getFieldName(), BytecodeHelper.getTypeDescription(field.getType()));
            if (!leftHandExpression) {
                if (ClassHelper.isPrimitiveType(field.getType())) {
                    helper.box(field.getType());
                }
            }
        } else {
            visitOuterFieldExpression(expression, outerClassNode.getOuterClass(), steps - 1, false);
        }
    }


    /**
     * Visits a bare (unqualified) variable expression.
     */

    public void visitVariableExpression(VariableExpression expression) {
        String variableName = expression.getName();

        //-----------------------------------------------------------------------
        // SPECIAL CASES

        //
        // "this" for static methods is the Class instance

        ClassNode classNode = this.classNode;
        if (isInClosure()) classNode = getOutermostClass();

        if (variableName.equals("this")) {
            if (isStaticMethod() || (!implicitThis && isStaticContext())) {
                visitClassExpression(new ClassExpression(classNode));
            } else {
                loadThis();
            }
            return;
        }

        //
        // "super" also requires special handling

        if (variableName.equals("super")) {
            if (isStaticMethod()) {
                visitClassExpression(new ClassExpression(classNode.getSuperClass()));
            } else {
                loadThis();
            }
            return;                                               // <<< FLOW CONTROL <<<<<<<<<
        }

        Variable variable = compileStack.getVariable(variableName, false);

        if (variable == null) {
            processClassVariable(variableName);
        } else {
            processStackVariable(variable, expression.isUseReferenceDirectly());
        }
        if (!leftHandExpression) record(expression);
    }

    private void loadThis() {
        mv.visitVarInsn(ALOAD, 0);
        if (!implicitThis && isInClosure()) {
            mv.visitMethodInsn(
                    INVOKEVIRTUAL,
                    "groovy/lang/Closure",
                    "getThisObject",
                    "()Ljava/lang/Object;"
            );
        }
    }

    protected void processStackVariable(Variable variable, boolean useReferenceDirectly) {
        if (leftHandExpression) {
            helper.storeVar(variable);
        } else {
            helper.loadVar(variable, useReferenceDirectly);
        }
        if (ASM_DEBUG) {
            helper.mark("var: " + variable.getName());
        }
    }

    protected void processClassVariable(String name) {
        if (passingParams && isInScriptBody()) {
            // let's create a ScriptReference to pass into the closure
            mv.visitTypeInsn(NEW, "org/codehaus/groovy/runtime/ScriptReference");
            mv.visitInsn(DUP);

            loadThisOrOwner();
            mv.visitLdcInsn(name);

            mv.visitMethodInsn(
                    INVOKESPECIAL,
                    "org/codehaus/groovy/runtime/ScriptReference",
                    "<init>",
                    "(Lgroovy/lang/Script;Ljava/lang/String;)V");
        } else {
            PropertyExpression pexp = new PropertyExpression(VariableExpression.THIS_EXPRESSION, name);
            pexp.setImplicitThis(true);
            visitPropertyExpression(pexp);
        }
    }


    protected void processFieldAccess(String name, FieldNode field, int steps) {
        FieldExpression expression = new FieldExpression(field);

        if (steps == 0) {
            visitFieldExpression(expression);
        } else {
            visitOuterFieldExpression(expression, classNode.getOuterClass(), steps, true);
        }
    }


    /**
     * @return true if we are in a script body, where all variables declared are no longer
     *         local variables but are properties
     */
    protected boolean isInScriptBody() {
        if (classNode.isScriptBody()) {
            return true;
        } else {
            return classNode.isScript() && methodNode != null && methodNode.getName().equals("run");
        }
    }

    /**
     * @return true if this expression will have left a value on the stack
     *         that must be popped
     */
    protected boolean isPopRequired(Expression expression) {
        if (expression instanceof MethodCallExpression) {
            return expression.getType() != ClassHelper.VOID_TYPE;
        }
        if (expression instanceof DeclarationExpression) {
            DeclarationExpression de = (DeclarationExpression) expression;
            return de.getLeftExpression() instanceof TupleExpression;
        }
        if (expression instanceof BinaryExpression) {
            BinaryExpression binExp = (BinaryExpression) expression;
            switch (binExp.getOperation().getType()) {   // br todo should leave a copy of the value on the stack for all the assignment.
//                case Types.EQUAL :   // br a copy of the right value is left on the stack (see evaluateEqual()) so a pop is required for a standalone assignment
//                case Types.PLUS_EQUAL : // this and the following are related to evaluateBinaryExpressionWithAssignment()
//                case Types.MINUS_EQUAL :
//                case Types.MULTIPLY_EQUAL :
//                case Types.DIVIDE_EQUAL :
//                case Types.INTDIV_EQUAL :
//                case Types.MOD_EQUAL :
//                    return false;
            }
        }
        if (expression instanceof ConstructorCallExpression) {
            ConstructorCallExpression cce = (ConstructorCallExpression) expression;
            return !cce.isSpecialCall();
        }
        return true;
    }

    protected void createInterfaceSyntheticStaticFields() {
        if (referencedClasses.isEmpty()) return;

        addInnerClass(interfaceClassLoadingClass);

        for (String staticFieldName : referencedClasses.keySet()) {            // generate a field node
            interfaceClassLoadingClass.addField(staticFieldName, ACC_STATIC + ACC_SYNTHETIC, ClassHelper.CLASS_Type, new ClassExpression(referencedClasses.get(staticFieldName)));
        }
    }

    protected void createSyntheticStaticFields() {
        for (String staticFieldName : referencedClasses.keySet()) {
            // generate a field node
            FieldNode fn = classNode.getDeclaredField(staticFieldName);
            if (fn != null) {
                boolean type = fn.getType() == ClassHelper.CLASS_Type;
                boolean modifiers = fn.getModifiers() == ACC_STATIC + ACC_SYNTHETIC;
                if (!type || !modifiers) {
                    String text = "";
                    if (!type) text = " with wrong type: " + fn.getType() + " (java.lang.Class needed)";
                    if (!modifiers)
                        text = " with wrong modifiers: " + fn.getModifiers() + " (" + (ACC_STATIC + ACC_SYNTHETIC) + " needed)";
                    throwException(
                            "tried to set a static syntethic field " + staticFieldName + " in " + classNode.getName() +
                                    " for class resolving, but found alreeady a node of that" +
                                    " name " + text);
                }
            } else {
                cv.visitField(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, staticFieldName, "Ljava/lang/Class;", null, null);
            }

            mv = cv.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$get$" + staticFieldName, "()Ljava/lang/Class;", null, null);
            mv.visitCode();
            mv.visitFieldInsn(GETSTATIC, internalClassName, staticFieldName, "Ljava/lang/Class;");
            mv.visitInsn(DUP);
            Label l0 = new Label();
            mv.visitJumpInsn(IFNONNULL, l0);
            mv.visitInsn(POP);
            mv.visitLdcInsn(BytecodeHelper.getClassLoadingTypeDescription(referencedClasses.get(staticFieldName)));
            mv.visitMethodInsn(INVOKESTATIC, internalClassName, "class$", "(Ljava/lang/String;)Ljava/lang/Class;");
            mv.visitInsn(DUP);
            mv.visitFieldInsn(PUTSTATIC, internalClassName, staticFieldName, "Ljava/lang/Class;");
            mv.visitLabel(l0);
            mv.visitInsn(ARETURN);
            mv.visitMaxs(0, 0);
            mv.visitEnd();
        }

        mv =
                cv.visitMethod(
                        ACC_STATIC + ACC_SYNTHETIC,
                        "class$",
                        "(Ljava/lang/String;)Ljava/lang/Class;",
                        null,
                        null);
        Label l0 = new Label();
        mv.visitLabel(l0);
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESTATIC, "java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;");
        Label l1 = new Label();
        mv.visitLabel(l1);
        mv.visitInsn(ARETURN);
        Label l2 = new Label();
        mv.visitLabel(l2);
        mv.visitVarInsn(ASTORE, 1);
        mv.visitTypeInsn(NEW, "java/lang/NoClassDefFoundError");
        mv.visitInsn(DUP);
        mv.visitVarInsn(ALOAD, 1);
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/ClassNotFoundException", "getMessage", "()Ljava/lang/String;");
        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/NoClassDefFoundError", "<init>", "(Ljava/lang/String;)V");
        mv.visitInsn(ATHROW);
        mv.visitTryCatchBlock(l0, l2, l2, "java/lang/ClassNotFoundException"); // br using l2 as the 2nd param seems create the right table entry
        mv.visitMaxs(3, 2);
    }

    /**
     * load class object on stack
     */
    public void visitClassExpression(ClassExpression expression) {
        ClassNode type = expression.getType();

        if (ClassHelper.isPrimitiveType(type)) {
            ClassNode objectType = ClassHelper.getWrapper(type);
            mv.visitFieldInsn(GETSTATIC, BytecodeHelper.getClassInternalName(objectType), "TYPE", "Ljava/lang/Class;");
        } else {
            String staticFieldName = getStaticFieldName(type);
            referencedClasses.put(staticFieldName, type);

            String internalClassName = this.internalClassName;
            if (classNode.isInterface()) {
                internalClassName = BytecodeHelper.getClassInternalName(interfaceClassLoadingClass);
                mv.visitFieldInsn(GETSTATIC, internalClassName, staticFieldName, "Ljava/lang/Class;");
            } else {
                mv.visitMethodInsn(INVOKESTATIC, internalClassName, "$get$" + staticFieldName, "()Ljava/lang/Class;");
            }
        }
    }

    public void visitRangeExpression(RangeExpression expression) {
        expression.getFrom().visit(this);
        expression.getTo().visit(this);

        helper.pushConstant(expression.isInclusive());

        createRangeMethod.call(mv);
    }

    public void visitMapEntryExpression(MapEntryExpression expression) {
        throw new GroovyBugError("MapEntryExpression should not be visited here");
    }

    public void visitMapExpression(MapExpression expression) {
        List entries = expression.getMapEntryExpressions();
        int size = entries.size();
        helper.pushConstant(size * 2);

        mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");

        int i = 0;
        for (Iterator iter = entries.iterator(); iter.hasNext();) {
            Object object = iter.next();
            MapEntryExpression entry = (MapEntryExpression) object;

            mv.visitInsn(DUP);
            helper.pushConstant(i++);
            visitAndAutoboxBoolean(entry.getKeyExpression());
            mv.visitInsn(AASTORE);

            mv.visitInsn(DUP);
            helper.pushConstant(i++);
            visitAndAutoboxBoolean(entry.getValueExpression());
            mv.visitInsn(AASTORE);
        }
        createMapMethod.call(mv);
    }

    public void visitArgumentlistExpression(ArgumentListExpression ale) {
        if (containsSpreadExpression(ale)) {
            despreadList(ale.getExpressions(), true);
        } else {
            visitTupleExpression(ale, true);
        }
    }

    public void visitTupleExpression(TupleExpression expression) {
        visitTupleExpression(expression, false);
    }

    private void visitTupleExpression(TupleExpression expression, boolean useWrapper) {
        int size = expression.getExpressions().size();

        helper.pushConstant(size);

        mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");

        for (int i = 0; i < size; i++) {
            mv.visitInsn(DUP);
            helper.pushConstant(i);
            Expression argument = expression.getExpression(i);
            visitAndAutoboxBoolean(argument);
            if (useWrapper && argument instanceof CastExpression) loadWrapper(argument);

            mv.visitInsn(AASTORE);
        }
    }

    private void loadWrapper(Expression argument) {
        ClassNode goalClass = argument.getType();
        visitClassExpression(new ClassExpression(goalClass));
        if (goalClass.isDerivedFromGroovyObject()) {
            createGroovyObjectWrapperMethod.call(mv);
        } else {
            createPojoWrapperMethod.call(mv);
        }
    }

    public void visitArrayExpression(ArrayExpression expression) {
        ClassNode elementType = expression.getElementType();
        String arrayTypeName = BytecodeHelper.getClassInternalName(elementType);
        List<Expression> sizeExpression = expression.getSizeExpression();

        int size = 0;
        int dimensions = 0;
        if (sizeExpression != null) {
            for (Expression element : sizeExpression) {
                if (element == ConstantExpression.EMPTY_EXPRESSION) break;
                dimensions++;
                // let's convert to an int
                visitAndAutoboxBoolean(element);
                helper.unbox(int.class);
            }
        } else {
            size = expression.getExpressions().size();
            helper.pushConstant(size);
        }

        int storeIns = AASTORE;
        if (sizeExpression != null) {
            arrayTypeName = BytecodeHelper.getTypeDescription(expression.getType());
            mv.visitMultiANewArrayInsn(arrayTypeName, dimensions);
        } else if (ClassHelper.isPrimitiveType(elementType)) {
            int primType = 0;
            if (elementType == ClassHelper.boolean_TYPE) {
                primType = T_BOOLEAN;
                storeIns = BASTORE;
            } else if (elementType == ClassHelper.char_TYPE) {
                primType = T_CHAR;
                storeIns = CASTORE;
            } else if (elementType == ClassHelper.float_TYPE) {
                primType = T_FLOAT;
                storeIns = FASTORE;
            } else if (elementType == ClassHelper.double_TYPE) {
                primType = T_DOUBLE;
                storeIns = DASTORE;
            } else if (elementType == ClassHelper.byte_TYPE) {
                primType = T_BYTE;
                storeIns = BASTORE;
            } else if (elementType == ClassHelper.short_TYPE) {
                primType = T_SHORT;
                storeIns = SASTORE;
            } else if (elementType == ClassHelper.int_TYPE) {
                primType = T_INT;
                storeIns = IASTORE;
            } else if (elementType == ClassHelper.long_TYPE) {
                primType = T_LONG;
                storeIns = LASTORE;
            }
            mv.visitIntInsn(NEWARRAY, primType);
        } else {
            mv.visitTypeInsn(ANEWARRAY, arrayTypeName);
        }

        for (int i = 0; i < size; i++) {
            mv.visitInsn(DUP);
            helper.pushConstant(i);
            Expression elementExpression = expression.getExpression(i);
            if (elementExpression == null) {
                ConstantExpression.NULL.visit(this);
            } else {
                if (!elementType.equals(elementExpression.getType())) {
                    visitCastExpression(new CastExpression(elementType, elementExpression, true));
                } else {
                    visitAndAutoboxBoolean(elementExpression);
                }
            }
            mv.visitInsn(storeIns);
        }

        if (sizeExpression == null && ClassHelper.isPrimitiveType(elementType)) {
            int par = compileStack.defineTemporaryVariable("par", true);
            mv.visitVarInsn(ALOAD, par);
        }
    }

    public void visitClosureListExpression(ClosureListExpression expression) {
        compileStack.pushVariableScope(expression.getVariableScope());

        List<Expression> expressions = expression.getExpressions();
        final int size = expressions.size();
        // init declarations
        LinkedList<DeclarationExpression> declarations = new LinkedList<DeclarationExpression>();
        for (int i = 0; i < size; i++) {
            Expression expr = expressions.get(i);
            if (expr instanceof DeclarationExpression) {
                declarations.add((DeclarationExpression) expr);
                DeclarationExpression de = (DeclarationExpression) expr;
                BinaryExpression be = new BinaryExpression(
                        de.getLeftExpression(),
                        de.getOperation(),
                        de.getRightExpression());
                expressions.set(i, be);
                de.setRightExpression(ConstantExpression.NULL);
                visitDeclarationExpression(de);
            }
        }

        LinkedList instructions = new LinkedList();
        BytecodeSequence seq = new BytecodeSequence(instructions);
        BlockStatement bs = new BlockStatement();
        bs.addStatement(seq);
        Parameter closureIndex = new Parameter(ClassHelper.int_TYPE, "__closureIndex");
        ClosureExpression ce = new ClosureExpression(new Parameter[]{closureIndex}, bs);
        ce.setVariableScope(expression.getVariableScope());

        // to keep stack height put a null on stack
        instructions.add(ConstantExpression.NULL);

        // init table
        final Label dflt = new Label();
        final Label tableEnd = new Label();
        final Label[] labels = new Label[size];
        instructions.add(new BytecodeInstruction() {
            public void visit(MethodVisitor mv) {
                mv.visitVarInsn(ILOAD, 1);
                mv.visitTableSwitchInsn(0, size - 1, dflt, labels);
            }
        });

        // visit cases
        for (int i = 0; i < size; i++) {
            final Label label = new Label();
            Object expr = expressions.get(i);
            final boolean isStatement = expr instanceof Statement;
            labels[i] = label;
            instructions.add(new BytecodeInstruction() {
                public void visit(MethodVisitor mv) {
                    mv.visitLabel(label);
                    // expressions will leave a value on stack, statements not
                    // so expressions need to pop the alibi null
                    if (!isStatement) mv.visitInsn(POP);
                }
            });
            instructions.add(expr);
            instructions.add(new BytecodeInstruction() {
                public void visit(MethodVisitor mv) {
                    mv.visitJumpInsn(GOTO, tableEnd);
                }
            });
        }

        // default case
        {
            instructions.add(new BytecodeInstruction() {
                public void visit(MethodVisitor mv) {
                    mv.visitLabel(dflt);
                }
            });
            ConstantExpression text = new ConstantExpression("invalid index for closure");
            ConstructorCallExpression cce = new ConstructorCallExpression(ClassHelper.make(IllegalArgumentException.class), text);
            ThrowStatement ts = new ThrowStatement(cce);
            instructions.add(ts);
        }

        // return
        instructions.add(new BytecodeInstruction() {
            public void visit(MethodVisitor mv) {
                mv.visitLabel(tableEnd);
                mv.visitInsn(ARETURN);
            }
        });

        // load main Closure
        visitClosureExpression(ce);

        // we need later an array to store the curried
        // closures, so we create it here and ave it
        // in a temporary variable
        helper.pushConstant(size);
        mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");
        int listArrayVar = compileStack.defineTemporaryVariable("_listOfClosures", true);

        // add curried versions
        for (int i = 0; i < size; i++) {
            // stack: closure

            // we need to create a curried closure version
            // so we store the type on stack
            mv.visitTypeInsn(NEW, "org/codehaus/groovy/runtime/CurriedClosure");
            // stack: closure, type
            // for a constructor call we need the type two times

            // and the closure after them
            mv.visitInsn(DUP2);
            mv.visitInsn(SWAP);
            // stack: closure,type,type,closure

            // so we can create the curried closure
            helper.pushConstant(i);
            mv.visitMethodInsn(INVOKESPECIAL, "org/codehaus/groovy/runtime/CurriedClosure", "<init>", "(Lgroovy/lang/Closure;I)V");
            // stack: closure,curriedClosure

            // we need to save the result
            mv.visitVarInsn(ALOAD, listArrayVar);
            mv.visitInsn(SWAP);
            helper.pushConstant(i);
            mv.visitInsn(SWAP);
            mv.visitInsn(AASTORE);
            // stack: closure
        }

        // we don't need the closure any longer, so remove it
        mv.visitInsn(POP);
        // we load the array and create a list from it
        mv.visitVarInsn(ALOAD, listArrayVar);
        createListMethod.call(mv);

        // remove the temporary variable to keep the
        // stack clean
        compileStack.removeVar(listArrayVar);
        compileStack.pop();
    }

    public void visitBytecodeSequence(BytecodeSequence bytecodeSequence) {
        List instructions = bytecodeSequence.getInstructions();
        for (Iterator iterator = instructions.iterator(); iterator.hasNext();) {
            Object part = iterator.next();
            if (part == EmptyExpression.INSTANCE) {
                mv.visitInsn(ACONST_NULL);
            } else if (part instanceof Expression) {
                visitAndAutoboxBoolean((Expression) part);
            } else if (part instanceof Statement) {
                Statement stm = (Statement) part;
                stm.visit(this);
                mv.visitInsn(ACONST_NULL);
            } else {
                BytecodeInstruction runner = (BytecodeInstruction) part;
                runner.visit(mv);
            }
        }
    }

    public void visitListExpression(ListExpression expression) {
        onLineNumber(expression, "ListExpression");
        int size = expression.getExpressions().size();
        boolean containsSpreadExpression = containsSpreadExpression(expression);
        if (!containsSpreadExpression) {
            helper.pushConstant(size);

            mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");

            for (int i = 0; i < size; i++) {
                mv.visitInsn(DUP);
                helper.pushConstant(i);
                visitAndAutoboxBoolean(expression.getExpression(i));
                mv.visitInsn(AASTORE);
            }
        } else {
            despreadList(expression.getExpressions(), false);
        }
        createListMethod.call(mv);
    }

    public void visitGStringExpression(GStringExpression expression) {

        mv.visitTypeInsn(NEW, "org/codehaus/groovy/runtime/GStringImpl");
        mv.visitInsn(DUP);

        int size = expression.getValues().size();
        helper.pushConstant(size);
        mv.visitTypeInsn(ANEWARRAY, "java/lang/Object");

        for (int i = 0; i < size; i++) {
            mv.visitInsn(DUP);
            helper.pushConstant(i);
            visitAndAutoboxBoolean(expression.getValue(i));
            mv.visitInsn(AASTORE);
        }

        List strings = expression.getStrings();
        size = strings.size();
        helper.pushConstant(size);
        mv.visitTypeInsn(ANEWARRAY, "java/lang/String");

        for (int i = 0; i < size; i++) {
            mv.visitInsn(DUP);
            helper.pushConstant(i);
            mv.visitLdcInsn(((ConstantExpression) strings.get(i)).getValue());
            mv.visitInsn(AASTORE);
        }

        mv.visitMethodInsn(INVOKESPECIAL, "org/codehaus/groovy/runtime/GStringImpl", "<init>", "([Ljava/lang/Object;[Ljava/lang/String;)V");
    }

    /**
     * Note: ignore it. Annotation generation needs the current visitor.
     */
    public void visitAnnotations(AnnotatedNode node) {
    }

    private void visitAnnotations(AnnotatedNode targetNode, Object visitor) {
        for (AnnotationNode an : targetNode.getAnnotations()) {
            // skip built-in properties
            if (an.isBuiltIn()) continue;
            if (an.hasSourceRetention()) continue;

            AnnotationVisitor av = getAnnotationVisitor(targetNode, an, visitor);
            visitAnnotationAttributes(an, av);
            av.visitEnd();
        }
    }

    private void visitParameterAnnotations(Parameter parameter, int paramNumber, MethodVisitor mv) {
        for (AnnotationNode an : parameter.getAnnotations()) {
            // skip built-in properties
            if (an.isBuiltIn()) continue;
            if (an.hasSourceRetention()) continue;

            final String annotationDescriptor = BytecodeHelper.getTypeDescription(an.getClassNode());
            AnnotationVisitor av = mv.visitParameterAnnotation(paramNumber, annotationDescriptor, an.hasRuntimeRetention());
            visitAnnotationAttributes(an, av);
            av.visitEnd();
        }
    }

    private AnnotationVisitor getAnnotationVisitor(AnnotatedNode targetNode, AnnotationNode an, Object visitor) {
        final String annotationDescriptor = BytecodeHelper.getTypeDescription(an.getClassNode());
        if (targetNode instanceof MethodNode) {
            return ((MethodVisitor) visitor).visitAnnotation(annotationDescriptor, an.hasRuntimeRetention());
        } else if (targetNode instanceof FieldNode) {
            return ((FieldVisitor) visitor).visitAnnotation(annotationDescriptor, an.hasRuntimeRetention());
        } else if (targetNode instanceof ClassNode) {
            return ((ClassVisitor) visitor).visitAnnotation(annotationDescriptor, an.hasRuntimeRetention());
        }
        throwException("Cannot create an AnnotationVisitor. Please report Groovy bug");
        return null;
    }

    /**
     * Generate the annotation attributes.
     *
     * @param an the node with an annotation
     * @param av the visitor to use
     */
    private void visitAnnotationAttributes(AnnotationNode an, AnnotationVisitor av) {
        Map<String, Object> constantAttrs = new HashMap<String, Object>();
        Map<String, PropertyExpression> enumAttrs = new HashMap<String, PropertyExpression>();
        Map<String, Object> atAttrs = new HashMap<String, Object>();
        Map<String, ListExpression> arrayAttrs = new HashMap<String, ListExpression>();

        for (String name : an.getMembers().keySet()) {
            Expression expr = an.getMember(name);
            if (expr instanceof AnnotationConstantExpression) {
                atAttrs.put(name, ((AnnotationConstantExpression) expr).getValue());
            } else if (expr instanceof ConstantExpression) {
                constantAttrs.put(name, ((ConstantExpression) expr).getValue());
            } else if (expr instanceof ClassExpression) {
                constantAttrs.put(name,
                        Type.getType(BytecodeHelper.getTypeDescription(expr.getType())));
            } else if (expr instanceof PropertyExpression) {
                enumAttrs.put(name, (PropertyExpression) expr);
            } else if (expr instanceof ListExpression) {
                arrayAttrs.put(name, (ListExpression) expr);
            }
        }

        for (Map.Entry entry : constantAttrs.entrySet()) {
            av.visit((String) entry.getKey(), entry.getValue());
        }
        for (Map.Entry entry : enumAttrs.entrySet()) {
            PropertyExpression propExp = (PropertyExpression) entry.getValue();
            av.visitEnum((String) entry.getKey(),
                    BytecodeHelper.getTypeDescription(propExp.getObjectExpression().getType()),
                    String.valueOf(((ConstantExpression) propExp.getProperty()).getValue()));
        }
        for (Map.Entry entry : atAttrs.entrySet()) {
            AnnotationNode atNode = (AnnotationNode) entry.getValue();
            AnnotationVisitor av2 = av.visitAnnotation((String) entry.getKey(),
                    BytecodeHelper.getTypeDescription(atNode.getClassNode()));
            visitAnnotationAttributes(atNode, av2);
            av2.visitEnd();
        }
        visitArrayAttributes(an, arrayAttrs, av);
    }

    private void visitArrayAttributes(AnnotationNode an, Map<String, ListExpression> arrayAttr, AnnotationVisitor av) {
        if (arrayAttr.isEmpty()) return;
        for (Map.Entry entry : arrayAttr.entrySet()) {
            AnnotationVisitor av2 = av.visitArray((String) entry.getKey());
            List<Expression> values = ((ListExpression) entry.getValue()).getExpressions();
            if (!values.isEmpty()) {
                int arrayElementType = determineCommonArrayType(values);
                for (Expression exprChild : values) {
                    visitAnnotationArrayElement(exprChild, arrayElementType, av2);
                }
            }
            av2.visitEnd();
        }
    }

    private int determineCommonArrayType(List values) {
        Expression expr = (Expression) values.get(0);
        int arrayElementType = -1;
        if (expr instanceof AnnotationConstantExpression) {
            arrayElementType = 1;
        } else if (expr instanceof ConstantExpression) {
            arrayElementType = 2;
        } else if (expr instanceof ClassExpression) {
            arrayElementType = 3;
        } else if (expr instanceof PropertyExpression) {
            arrayElementType = 4;
        }
        return arrayElementType;
    }

    private void visitAnnotationArrayElement(Expression expr, int arrayElementType, AnnotationVisitor av) {
        switch (arrayElementType) {
            case 1:
                AnnotationNode atAttr = (AnnotationNode) ((AnnotationConstantExpression) expr).getValue();
                AnnotationVisitor av2 = av.visitAnnotation(null, BytecodeHelper.getTypeDescription(atAttr.getClassNode()));
                visitAnnotationAttributes(atAttr, av2);
                av2.visitEnd();
                break;
            case 2:
                av.visit(null, ((ConstantExpression) expr).getValue());
                break;
            case 3:
                av.visit(null, Type.getType(BytecodeHelper.getTypeDescription(expr.getType())));
                break;
            case 4:
                PropertyExpression propExpr = (PropertyExpression) expr;
                av.visitEnum(null,
                        BytecodeHelper.getTypeDescription(propExpr.getObjectExpression().getType()),
                        String.valueOf(((ConstantExpression) propExpr.getProperty()).getValue()));
                break;
        }
    }

    // Implementation methods
    //-------------------------------------------------------------------------

    protected boolean addInnerClass(ClassNode innerClass) {
        innerClass.setModule(classNode.getModule());
        return innerClasses.add(innerClass);
    }

    protected ClassNode createClosureClass(ClosureExpression expression) {
        ClassNode outerClass = getOutermostClass();
        String name = outerClass.getName() + "$"
                + context.getNextClosureInnerName(outerClass, classNode, methodNode); // add a more informative name
        boolean staticMethodOrInStaticClass = isStaticMethod() || classNode.isStaticClass();

        Parameter[] parameters = expression.getParameters();
        if (parameters == null) {
            parameters = Parameter.EMPTY_ARRAY;
        } else if (parameters.length == 0) {
            // let's create a default 'it' parameter
            Parameter it = new Parameter(ClassHelper.OBJECT_TYPE, "it", ConstantExpression.NULL);
            parameters = new Parameter[]{it};
            org.codehaus.groovy.ast.Variable ref = expression.getVariableScope().getDeclaredVariable("it");
            if (ref != null) it.setClosureSharedVariable(ref.isClosureSharedVariable());
        }

        Parameter[] localVariableParams = getClosureSharedVariables(expression);
        removeInitialValues(localVariableParams);

        InnerClassNode answer = new InnerClassNode(outerClass, name, 0, ClassHelper.CLOSURE_TYPE); // closures are local inners and not public
        answer.setEnclosingMethod(this.methodNode);
        answer.setSynthetic(true);
        answer.setUsingGenerics(outerClass.isUsingGenerics());

        if (staticMethodOrInStaticClass) {
            answer.setStaticClass(true);
        }
        if (isInScriptBody()) {
            answer.setScriptBody(true);
        }
        MethodNode method =
                answer.addMethod("doCall", ACC_PUBLIC, ClassHelper.OBJECT_TYPE, parameters, ClassNode.EMPTY_ARRAY, expression.getCode());
        method.setSourcePosition(expression);

        VariableScope varScope = expression.getVariableScope();
        if (varScope == null) {
            throw new RuntimeException(
                    "Must have a VariableScope by now! for expression: " + expression + " class: " + name);
        } else {
            method.setVariableScope(varScope.copy());
        }
        if (parameters.length > 1
                || (parameters.length == 1
                && parameters[0].getType() != null
                && parameters[0].getType() != ClassHelper.OBJECT_TYPE)) {

            // let's add a typesafe call method
            MethodNode call = answer.addMethod(
                    "call",
                    ACC_PUBLIC,
                    ClassHelper.OBJECT_TYPE,
                    parameters,
                    ClassNode.EMPTY_ARRAY,
                    new ReturnStatement(
                            new MethodCallExpression(
                                    VariableExpression.THIS_EXPRESSION,
                                    "doCall",
                                    new ArgumentListExpression(parameters))));
            call.setSourcePosition(expression);
        }

        // let's make the constructor
        BlockStatement block = new BlockStatement();
        // this block does not get a source position, because we don't
        // want this synthetic constructor to show up in corbertura reports
        VariableExpression outer = new VariableExpression("_outerInstance");
        outer.setSourcePosition(expression);
        block.getVariableScope().putReferencedLocalVariable(outer);
        VariableExpression thisObject = new VariableExpression("_thisObject");
        thisObject.setSourcePosition(expression);
        block.getVariableScope().putReferencedLocalVariable(thisObject);
        TupleExpression conArgs = new TupleExpression(outer, thisObject);
        block.addStatement(
                new ExpressionStatement(
                        new ConstructorCallExpression(
                                ClassNode.SUPER,
                                conArgs)));

        // let's assign all the parameter fields from the outer context
        for (Parameter param : localVariableParams) {
            String paramName = param.getName();
            Expression initialValue = null;
            ClassNode type = param.getType();
            FieldNode paramField = null;
            if (true) {
                initialValue = new VariableExpression(paramName);
                ClassNode realType = type;
                type = ClassHelper.makeReference();
                param.setType(ClassHelper.makeReference());
                paramField = answer.addField(paramName, ACC_PRIVATE, type, initialValue);
                paramField.setHolder(true);
                String methodName = Verifier.capitalize(paramName);

                // let's add a getter & setter
                Expression fieldExp = new FieldExpression(paramField);
                answer.addMethod(
                        "get" + methodName,
                        ACC_PUBLIC,
                        realType,
                        Parameter.EMPTY_ARRAY,
                        ClassNode.EMPTY_ARRAY,
                        new ReturnStatement(fieldExp));

                /*
                answer.addMethod(
                    "set" + methodName,
                    ACC_PUBLIC,
                    "void",
                    new Parameter[] { new Parameter(realType, "__value") },
                    new ExpressionStatement(
                        new BinaryExpression(expression, Token.newSymbol(Types.EQUAL, 0, 0), new VariableExpression("__value"))));
                        */
            }
        }

        Parameter[] params = new Parameter[2 + localVariableParams.length];
        params[0] = new Parameter(ClassHelper.OBJECT_TYPE, "_outerInstance");
        params[1] = new Parameter(ClassHelper.OBJECT_TYPE, "_thisObject");
        System.arraycopy(localVariableParams, 0, params, 2, localVariableParams.length);

        ASTNode sn = answer.addConstructor(ACC_PUBLIC, params, ClassNode.EMPTY_ARRAY, block);
        sn.setSourcePosition(expression);
        return answer;
    }

    /*
     * this method is called for local variables shared between scopes.
     * These variables must not have init values because these would
     * then in later steps be used to create multiple versions of the
     * same method, in this case the constructor. A closure should not
     * have more than one constructor!
     */

    private void removeInitialValues(Parameter[] params) {
        for (int i = 0; i < params.length; i++) {
            if (params[i].hasInitialExpression()) {
                params[i] = new Parameter(params[i].getType(), params[i].getName());
            }
        }
    }

    protected Parameter[] getClosureSharedVariables(ClosureExpression ce) {
        VariableScope scope = ce.getVariableScope();
        Parameter[] ret = new Parameter[scope.getReferencedLocalVariablesCount()];
        int index = 0;
        for (Iterator iter = scope.getReferencedLocalVariablesIterator(); iter.hasNext();) {
            org.codehaus.groovy.ast.Variable element = (org.codehaus.groovy.ast.Variable) iter.next();
            Parameter p = new Parameter(element.getType(), element.getName());
            ret[index] = p;
            index++;
        }
        return ret;
    }

    protected ClassNode getOutermostClass() {
        if (outermostClass == null) {
            outermostClass = classNode;
            while (outermostClass instanceof InnerClassNode) {
                outermostClass = outermostClass.getOuterClass();
            }
        }
        return outermostClass;
    }

    protected void doConvertAndCast(ClassNode type) {
        doConvertAndCast(type, false);
    }

    protected void doConvertAndCast(ClassNode type, boolean coerce) {
        if (type == ClassHelper.OBJECT_TYPE) return;
        if (rightHandType == null || !rightHandType.isDerivedFrom(type) || !rightHandType.implementsInterface(type)) {
            if (isValidTypeForCast(type)) {
                visitClassExpression(new ClassExpression(type));
                if (coerce) {
                    asTypeMethod.call(mv);
                } else {
                    castToTypeMethod.call(mv);
                }
            }
        }
        helper.doCast(type);
    }

    protected void evaluateLogicalOrExpression(BinaryExpression expression) {
        visitBooleanExpression(new BooleanExpression(expression.getLeftExpression()));
        Label l0 = new Label();
        Label l2 = new Label();
        mv.visitJumpInsn(IFEQ, l0);

        mv.visitLabel(l2);

        visitConstantExpression(ConstantExpression.TRUE);

        Label l1 = new Label();
        mv.visitJumpInsn(GOTO, l1);
        mv.visitLabel(l0);

        visitBooleanExpression(new BooleanExpression(expression.getRightExpression()));

        mv.visitJumpInsn(IFNE, l2);

        visitConstantExpression(ConstantExpression.FALSE);
        mv.visitLabel(l1);
    }

    // todo: optimization: change to return primitive boolean. need to adjust the BinaryExpression and isComparisonExpression for
    // consistency.

    protected void evaluateLogicalAndExpression(BinaryExpression expression) {
        visitBooleanExpression(new BooleanExpression(expression.getLeftExpression()));
        Label l0 = new Label();
        mv.visitJumpInsn(IFEQ, l0);

        visitBooleanExpression(new BooleanExpression(expression.getRightExpression()));

        mv.visitJumpInsn(IFEQ, l0);

        visitConstantExpression(ConstantExpression.TRUE);

        Label l1 = new Label();
        mv.visitJumpInsn(GOTO, l1);
        mv.visitLabel(l0);

        visitConstantExpression(ConstantExpression.FALSE);

        mv.visitLabel(l1);
    }

    protected void evaluateBinaryExpression(String method, BinaryExpression expression) {
//        makeBinopCallSite(expression, method);
        makeBinopCallSite(expression.getLeftExpression(), method, expression.getRightExpression());
    }

    protected void evaluateCompareTo(BinaryExpression expression) {
        Expression leftExpression = expression.getLeftExpression();
        leftExpression.visit(this);
        if (isComparisonExpression(leftExpression)) {
            helper.boxBoolean();
        }

        // if the right hand side is a boolean expression, we need to autobox
        Expression rightExpression = expression.getRightExpression();
        rightExpression.visit(this);
        if (isComparisonExpression(rightExpression)) {
            helper.boxBoolean();
        }
        compareToMethod.call(mv);
    }

    protected void evaluateBinaryExpressionWithAssignment(String method, BinaryExpression expression) {
        Expression leftExpression = expression.getLeftExpression();
        if (leftExpression instanceof BinaryExpression) {
            BinaryExpression leftBinExpr = (BinaryExpression) leftExpression;
            if (leftBinExpr.getOperation().getType() == Types.LEFT_SQUARE_BRACKET) {
                // let's replace this assignment to a subscript operator with a
                // method call
                // e.g. x[5] += 10
                // -> (x, [], 5), =, x[5] + 10
                // -> methodCall(x, "putAt", [5, methodCall(x[5], "plus", 10)])

                prepareCallSite("putAt");
                // cs_put
                prepareCallSite(method);
                prepareCallSite("getAt");
                visitAndAutoboxBoolean(leftBinExpr.getLeftExpression());
                visitAndAutoboxBoolean(leftBinExpr.getRightExpression());
                // cs_put cs_method cs_get obj index
                mv.visitInsn(DUP2_X2);
                // cs_put obj index cs_method cs_get obj index
                mv.visitMethodInsn(INVOKEINTERFACE, "org/codehaus/groovy/runtime/callsite/CallSite", "call", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
                // cs_put obj index cs_method obj[index]
                visitAndAutoboxBoolean(expression.getRightExpression());
                // cs_put obj index cs_method obj[index] right
                mv.visitMethodInsn(INVOKEINTERFACE, "org/codehaus/groovy/runtime/callsite/CallSite", "call", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
                // cs_put obj index (obj[index] + right)
                final int resultVar = compileStack.defineTemporaryVariable("$result", true);
                mv.visitVarInsn(ALOAD, resultVar);
                mv.visitMethodInsn(INVOKEINTERFACE, "org/codehaus/groovy/runtime/callsite/CallSite", "call", "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
                mv.visitInsn(POP); //drop return value

                mv.visitVarInsn(ALOAD, resultVar);
                compileStack.removeVar(resultVar);
                return;
            }
        }

        evaluateBinaryExpression(method, expression);

        // br to leave a copy of rvalue on the stack. see also isPopRequired()
        mv.visitInsn(DUP);
        doConvertAndCast(ClassHelper.getWrapper(leftExpression.getType()));

        leftHandExpression = true;
        evaluateExpression(leftExpression);
        leftHandExpression = false;
    }

    private void evaluateBinaryExpression(MethodCaller compareMethod, BinaryExpression expression) {
        Expression leftExp = expression.getLeftExpression();
        Expression rightExp = expression.getRightExpression();
        load(leftExp);
        load(rightExp);
        compareMethod.call(mv);
    }

    protected void evaluateEqual(BinaryExpression expression, boolean defineVariable) {
        Expression leftExpression = expression.getLeftExpression();
        if (leftExpression instanceof BinaryExpression) {
            BinaryExpression leftBinExpr = (BinaryExpression) leftExpression;
            if (leftBinExpr.getOperation().getType() == Types.LEFT_SQUARE_BRACKET) {
                // let's replace this assignment to a subscript operator with a
                // method call
                // e.g. x[5] = 10
                // -> (x, [], 5), =, 10
                // -> methodCall(x, "putAt", [5, 10])

                prepareCallSite("putAt");
                visitAndAutoboxBoolean(leftBinExpr.getLeftExpression());
                visitAndAutoboxBoolean(leftBinExpr.getRightExpression());
                visitAndAutoboxBoolean(expression.getRightExpression());

                final int resultVar = compileStack.defineTemporaryVariable("$result", true);
                mv.visitVarInsn(ALOAD, resultVar);
                mv.visitMethodInsn(INVOKEINTERFACE, "org/codehaus/groovy/runtime/callsite/CallSite", "call", "(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
                mv.visitInsn(POP); //drop return value

                mv.visitVarInsn(ALOAD, resultVar);
                compileStack.removeVar(resultVar);
                return;
            }
        }

        // let's evaluate the RHS then hopefully the LHS will be a field
        Expression rightExpression = expression.getRightExpression();
        if (!(leftExpression instanceof TupleExpression)) {
            ClassNode type = null;
            if (expression instanceof DeclarationExpression) {
                type = leftExpression.getType();
            } else {
                type = getLHSType(leftExpression);
            }
            assignmentCastAndVisit(type, rightExpression);
        } else {
            // multiple assignment here!
            visitAndAutoboxBoolean(rightExpression);
        }

        rightHandType = rightExpression.getType();
        leftHandExpression = true;
        if (leftExpression instanceof TupleExpression) {
            TupleExpression tuple = (TupleExpression) leftExpression;
            int i = 0;
            Expression lhsExpr = new BytecodeExpression() {
                public void visit(MethodVisitor mv) {
                    // copy for method call
                    mv.visitInsn(SWAP);
                    mv.visitInsn(DUP_X1);
                }
            };
            for (Expression e : tuple.getExpressions()) {
                VariableExpression var = (VariableExpression) e;
                MethodCallExpression call = new MethodCallExpression(
                        lhsExpr, "getAt",
                        new ArgumentListExpression(new ConstantExpression(i)));
                ClassNode type = getLHSType(var);
                assignmentCastAndVisit(type, call);
                i++;
                if (defineVariable) {
                    compileStack.defineVariable(var, true);
                } else {
                    visitVariableExpression(var);
                }
            }
        } else if (defineVariable) {
            VariableExpression var = (VariableExpression) leftExpression;
            compileStack.defineVariable(var, true);
        } else {
            mv.visitInsn(DUP)// to leave a copy of the rightExpression value on the stack after the assignment.
            leftExpression.visit(this);
        }
        rightHandType = null;
        leftHandExpression = false;
    }

    private void assignmentCastAndVisit(ClassNode type, Expression rightExpression) {
        // let's not cast for primitive types as we handle these in field setting etc
        if (ClassHelper.isPrimitiveType(type)) {
            visitAndAutoboxBoolean(rightExpression);
        } else if (!rightExpression.getType().isDerivedFrom(type)) {
            visitCastExpression(new CastExpression(type, rightExpression));
        } else {
            visitAndAutoboxBoolean(rightExpression);
        }
    }

    /**
     * Deduces the type name required for some casting
     *
     * @return the type of the given (LHS) expression or null if it is java.lang.Object or it cannot be deduced
     */
    protected ClassNode getLHSType(Expression leftExpression) {
        if (leftExpression instanceof VariableExpression) {
            VariableExpression varExp = (VariableExpression) leftExpression;
            ClassNode type = varExp.getType();
            if (isValidTypeForCast(type)) {
                return type;
            }
            String variableName = varExp.getName();
            Variable variable = compileStack.getVariable(variableName, false);
            if (variable != null) {
                if (variable.isHolder()) {
                    return type;
                }
                if (variable.isProperty()) return variable.getType();
                type = variable.getType();
                if (isValidTypeForCast(type)) {
                    return type;
                }
            } else {
                FieldNode field = classNode.getDeclaredField(variableName);
                if (field == null) {
                    field = classNode.getOuterField(variableName);
                }
                if (field != null) {
                    type = field.getType();
                    if (!field.isHolder() && isValidTypeForCast(type)) {
                        return type;
                    }
                }
            }
        } else if (leftExpression instanceof FieldExpression) {
            FieldExpression fieldExp = (FieldExpression) leftExpression;
            ClassNode type = fieldExp.getType();
            if (isValidTypeForCast(type)) {
                return type;
            }
        }
        return leftExpression.getType();
    }

    protected boolean isValidTypeForCast(ClassNode type) {
        return type != ClassHelper.DYNAMIC_TYPE &&
                type != ClassHelper.REFERENCE_TYPE;
    }

    public void visitBytecodeExpression(BytecodeExpression cle) {
        cle.visit(mv);
    }

    protected void visitAndAutoboxBoolean(Expression expression) {
        expression.visit(this);

        if (isComparisonExpression(expression)) {
            helper.boxBoolean(); // convert boolean to Boolean
        }
    }

    private void execMethodAndStoreForSubscriptOperator(String method, Expression expression) {
      execMethodAndStoreForSubscriptOperator(method, expression, null);
    }
   
    private void execMethodAndStoreForSubscriptOperator(String method, Expression expression,
        Expression getAtResultExp) {
        // execute method
        makeCallSite(
            (getAtResultExp == null ? expression : getAtResultExp),
                method,
                MethodCallExpression.NO_ARGUMENTS,
                false, false, false, false);

        // we need special code for arrays to store the result
        if (expression instanceof BinaryExpression) {
            BinaryExpression be = (BinaryExpression) expression;
            if (be.getOperation().getType() == Types.LEFT_SQUARE_BRACKET) {
                mv.visitInsn(DUP);
                final int resultIdx = compileStack.defineTemporaryVariable("postfix_" + method, true);
                BytecodeExpression result = new BytecodeExpression() {
                    public void visit(MethodVisitor mv) {
                        mv.visitVarInsn(ALOAD, resultIdx);
                    }
                };
                TupleExpression args = new ArgumentListExpression();
                args.addExpression(be.getRightExpression());
                args.addExpression(result);
                makeCallSite(
                        be.getLeftExpression(), "putAt",
                        args,
                        false, false, false, false);
                mv.visitInsn(POP);
                compileStack.removeVar(resultIdx);
            }
        }

        if (expression instanceof VariableExpression ||
                expression instanceof FieldExpression ||
                expression instanceof PropertyExpression) {
            mv.visitInsn(DUP);
            leftHandExpression = true;
            expression.visit(this);
            leftHandExpression = false;
        }
    }

    protected void evaluatePrefixMethod(String method, Expression expression) {
        // execute Method
        execMethodAndStoreForSubscriptOperator(method, expression);

        // new value is already on stack, so nothing to do here
    }

    protected void evaluatePostfixMethod(String method, Expression expression) {
      // GROOVY-4246: arr[rand()]++ should evaulate rand() only once and reuse its result
      boolean getAtOp = false;
      BinaryExpression be = null;
      Expression getAtResultExp = null;
      String varName = "tmp_postfix_" + method;
      final int idx = compileStack.defineTemporaryVariable(varName, false);
     
      if(expression instanceof BinaryExpression) {
        be = (BinaryExpression) expression;
        if (be.getOperation().getType() == Types.LEFT_SQUARE_BRACKET) {
          getAtOp = true;
          be.getRightExpression().visit(this); // execute subscript exp
          mv.visitVarInsn(ASTORE, idx); // store the exp result in a local var
          BytecodeExpression newRightExp = new BytecodeExpression() {
            public void visit(MethodVisitor mv) {
              mv.visitVarInsn(ALOAD, idx);
            }
          };
          // change the subscript exp to pick up the local var so that the orig exp is not re-executed
          be.setRightExpression(newRightExp);
        }
       
      }
        // load
        expression.visit(this);

        // save value for later
        final int tempIdx = compileStack.defineTemporaryVariable("postfix_" + method, true);

        if(getAtOp) {
          // exp to allow reuse of binary exp already evaluated so that following putAt does not
         //  execute the getAt() again
         getAtResultExp = new BytecodeExpression() {
           public void visit(MethodVisitor mv) {
             mv.visitVarInsn(ALOAD, tempIdx);
           }
         };
       }
      
        // execute Method
        execMethodAndStoreForSubscriptOperator(method, expression, getAtResultExp);
        // remove the result of the method call
        mv.visitInsn(POP);

        //reload saved value
        mv.visitVarInsn(ALOAD, tempIdx);
        compileStack.removeVar(tempIdx);
        compileStack.removeVar(idx);
    }

    protected void evaluateInstanceof(BinaryExpression expression) {
        visitAndAutoboxBoolean(expression.getLeftExpression());
        Expression rightExp = expression.getRightExpression();
        ClassNode classType;
        if (rightExp instanceof ClassExpression) {
            ClassExpression classExp = (ClassExpression) rightExp;
            classType = classExp.getType();
        } else {
            throw new RuntimeException(
                    "Right hand side of the instanceof keyword must be a class name, not: " + rightExp);
        }
        String classInternalName = BytecodeHelper.getClassInternalName(classType);
        mv.visitTypeInsn(INSTANCEOF, classInternalName);
    }

    /**
     * @return true if the given argument expression requires the stack, in
     *         which case the arguments are evaluated first, stored in the
     *         variable stack and then reloaded to make a method call
     */
    protected boolean argumentsUseStack(Expression arguments) {
        return arguments instanceof TupleExpression || arguments instanceof ClosureExpression;
    }

    private static boolean isThisExpression(Expression expression) {
        if (expression instanceof VariableExpression) {
            VariableExpression varExp = (VariableExpression) expression;
            return varExp.getName().equals("this");
        }
        return false;
    }

    private static boolean isSuperExpression(Expression expression) {
        if (expression instanceof VariableExpression) {
            VariableExpression varExp = (VariableExpression) expression;
            return varExp.getName().equals("super");
        }
        return false;
    }

    private static boolean isThisOrSuper(Expression expression) {
        return isThisExpression(expression) || isSuperExpression(expression);
    }


    /**
     * For assignment expressions, return a safe expression for the LHS we can use
     * to return the value
     */
    protected Expression createReturnLHSExpression(Expression expression) {
        if (expression instanceof BinaryExpression) {
            BinaryExpression binExpr = (BinaryExpression) expression;
            if (binExpr.getOperation().isA(Types.ASSIGNMENT_OPERATOR)) {
                return createReusableExpression(binExpr.getLeftExpression());
            }
        }
        return null;
    }

    protected Expression createReusableExpression(Expression expression) {
        ExpressionTransformer transformer = new ExpressionTransformer() {
            public Expression transform(Expression expression) {
                if (expression instanceof PostfixExpression) {
                    PostfixExpression postfixExp = (PostfixExpression) expression;
                    return postfixExp.getExpression();
                } else if (expression instanceof PrefixExpression) {
                    PrefixExpression prefixExp = (PrefixExpression) expression;
                    return prefixExp.getExpression();
                }
                return expression;
            }
        };

        // could just be a postfix / prefix expression or nested inside some other expression
        return transformer.transform(expression.transformExpression(transformer));
    }

    protected boolean isComparisonExpression(Expression expression) {
        if (expression instanceof BinaryExpression) {
            BinaryExpression binExpr = (BinaryExpression) expression;
            switch (binExpr.getOperation().getType()) {
                case Types.COMPARE_EQUAL:
                case Types.MATCH_REGEX:
                case Types.COMPARE_GREATER_THAN:
                case Types.COMPARE_GREATER_THAN_EQUAL:
                case Types.COMPARE_LESS_THAN:
                case Types.COMPARE_LESS_THAN_EQUAL:
                case Types.COMPARE_IDENTICAL:
                case Types.COMPARE_NOT_EQUAL:
                case Types.COMPARE_NOT_IDENTICAL:
                case Types.KEYWORD_INSTANCEOF:
                case Types.KEYWORD_IN:
                    return true;
            }
        } else if (expression instanceof BooleanExpression) {
            return true;
        }
        return false;
    }

    protected void onLineNumber(ASTNode statement, String message) {
        if (statement == null) return;
        int line = statement.getLineNumber();
        this.currentASTNode = statement;

        if (line < 0) return;
        if (!ASM_DEBUG && line == lineNumber) return;

        lineNumber = line;
        if (mv != null) {
            Label l = new Label();
            mv.visitLabel(l);
            mv.visitLineNumber(line, l);
            if (ASM_DEBUG) {
                helper.mark(message + "[" + statement.getLineNumber() + ":" + statement.getColumnNumber() + "]");
            }
        }
    }

    private boolean isInnerClass() {
        return classNode instanceof InnerClassNode;
    }

    /**
     * @return true if the given name is a local variable or a field
     */
    protected boolean isFieldOrVariable(String name) {
        return compileStack.containsVariable(name) || classNode.getDeclaredField(name) != null;
    }

    /**
     * @return if the type of the expression can be determined at compile time
     *         then this method returns the type - otherwise null
     */
    protected ClassNode getExpressionType(Expression expression) {
        if (isComparisonExpression(expression)) {
            return ClassHelper.boolean_TYPE;
        }
        if (expression instanceof VariableExpression) {
            VariableExpression varExpr = (VariableExpression) expression;

            if (varExpr.isThisExpression()) {
                return classNode;
            } else if (varExpr.isSuperExpression()) {
                return classNode.getSuperClass();
            }

            Variable variable = compileStack.getVariable(varExpr.getName(), false);
            if (variable != null && !variable.isHolder()) {
                ClassNode type = variable.getType();
                if (!variable.isDynamicTyped()) return type;
            }
            if (variable == null) {
                org.codehaus.groovy.ast.Variable var = compileStack.getScope().getReferencedClassVariable(varExpr.getName());
                if (var != null && !var.isDynamicTyped()) return var.getType();
            }
        }
        return expression.getType();
    }

    protected boolean isInClosureConstructor() {
        return constructorNode != null
                && classNode.getOuterClass() != null
                && classNode.getSuperClass() == ClassHelper.CLOSURE_TYPE;
    }

    protected boolean isInClosure() {
        return classNode.getOuterClass() != null
                && classNode.getSuperClass() == ClassHelper.CLOSURE_TYPE;
    }

    protected boolean isNotExplicitThisInClosure(boolean implicitThis) {
        return implicitThis || !isInClosure();
    }

    protected boolean isStaticMethod() {
        return methodNode != null && methodNode.isStatic();
    }

    protected CompileUnit getCompileUnit() {
        CompileUnit answer = classNode.getCompileUnit();
        if (answer == null) {
            answer = context.getCompileUnit();
        }
        return answer;
    }

    public static boolean usesSuper(MethodCallExpression call) {
        Expression expression = call.getObjectExpression();
        if (expression instanceof VariableExpression) {
            VariableExpression varExp = (VariableExpression) expression;
            String variable = varExp.getName();
            return variable.equals("super");
        }
        return false;
    }

    public static boolean usesSuper(PropertyExpression pe) {
        Expression expression = pe.getObjectExpression();
        if (expression instanceof VariableExpression) {
            VariableExpression varExp = (VariableExpression) expression;
            String variable = varExp.getName();
            return variable.equals("super");
        }
        return false;
    }

    protected int getBytecodeVersion() {
        if (!classNode.isUsingGenerics() &&
                !classNode.isAnnotated() &&
                !classNode.isAnnotationDefinition()) {
            return Opcodes.V1_3;
        }

        final String target = getCompileUnit().getConfig().getTargetBytecode();
        return CompilerConfiguration.POST_JDK5.equals(target) ? Opcodes.V1_5 : Opcodes.V1_3;
    }

    private class MyMethodAdapter extends MethodAdapter {
        private String boxingDesc = null;

        public MyMethodAdapter() {
            super(AsmClassGenerator.this.mv);
        }

        private void dropBoxing() {
            if (boxingDesc != null) {
                super.visitMethodInsn(INVOKESTATIC, DTT, "box", boxingDesc);
                boxingDesc = null;
            }
        }

        public void visitInsn(int opcode) {
            dropBoxing();
            super.visitInsn(opcode);
        }

        public void visitIntInsn(int opcode, int operand) {
            dropBoxing();
            super.visitIntInsn(opcode, operand);
        }

        public void visitVarInsn(int opcode, int var) {
            dropBoxing();
            super.visitVarInsn(opcode, var);
        }

        public void visitTypeInsn(int opcode, String desc) {
            dropBoxing();
            super.visitTypeInsn(opcode, desc);
        }

        public void visitFieldInsn(int opcode, String owner, String name, String desc) {
            dropBoxing();
            super.visitFieldInsn(opcode, owner, name, desc);
        }

        public void visitMethodInsn(int opcode, String owner, String name, String desc) {
            if (boxing(opcode, owner, name)) {
                boxingDesc = desc;
                dropBoxing();
            } else {
                if (unboxing(opcode, owner, name)) {
                    if (boxingDesc != null)
                        boxingDesc = null;
                    else
                        super.visitMethodInsn(opcode, owner, name, desc);
                } else {
                    dropBoxing();
                    super.visitMethodInsn(opcode, owner, name, desc);
                }
            }
        }

        private boolean boxing(int opcode, String owner, String name) {
            return opcode == INVOKESTATIC && owner.equals(DTT) && name.equals("box");
        }

        private boolean unboxing(int opcode, String owner, String name) {
            return opcode == INVOKESTATIC && owner.equals(DTT) && name.endsWith("Unbox");
        }

        public void visitJumpInsn(int opcode, Label label) {
            dropBoxing();
            super.visitJumpInsn(opcode, label);
        }

        public void visitLabel(Label label) {
            dropBoxing();
            super.visitLabel(label);
        }

        public void visitLdcInsn(Object cst) {
            dropBoxing();
            super.visitLdcInsn(cst);
        }

        public void visitIincInsn(int var, int increment) {
            dropBoxing();
            super.visitIincInsn(var, increment);
        }

        public void visitTableSwitchInsn(int min, int max, Label dflt, Label labels[]) {
            dropBoxing();
            super.visitTableSwitchInsn(min, max, dflt, labels);
        }

        public void visitLookupSwitchInsn(Label dflt, int keys[], Label labels[]) {
            dropBoxing();
            super.visitLookupSwitchInsn(dflt, keys, labels);
        }

        public void visitMultiANewArrayInsn(String desc, int dims) {
            dropBoxing();
            super.visitMultiANewArrayInsn(desc, dims);
        }

        public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
            dropBoxing();
            super.visitTryCatchBlock(start, end, handler, type);
        }
    }
}
TOP

Related Classes of org.codehaus.groovy.classgen.AsmClassGenerator

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.