@Override
 public void visit(
     final int version,
     final int access,
     final String name,
     final String signature,
     final String superName,
     final String[] interfaces) {
   Set<String> interfacesSet = new LinkedHashSet<String>();
   if (interfaces != null) Collections.addAll(interfacesSet, interfaces);
   for (Class extraInterface : classList) {
     if (extraInterface.isInterface())
       interfacesSet.add(BytecodeHelper.getClassInternalName(extraInterface));
   }
   final boolean addGroovyObjectSupport = !GroovyObject.class.isAssignableFrom(superClass);
   if (addGroovyObjectSupport) interfacesSet.add("groovy/lang/GroovyObject");
   super.visit(
       V1_5,
       ACC_PUBLIC,
       proxyName,
       signature,
       BytecodeHelper.getClassInternalName(superClass),
       interfacesSet.toArray(new String[interfacesSet.size()]));
   addDelegateFields();
   if (addGroovyObjectSupport) {
     createGroovyObjectSupport();
   }
   for (Class clazz : classList) {
     visitClass(clazz);
   }
 }
  /**
   * Visit every class/interface this proxy should implement, and generate the appropriate bytecode
   * for delegation if available.
   *
   * @param clazz an class for which to generate bytecode
   */
  private void visitClass(final Class clazz) {
    Method[] methods = clazz.getDeclaredMethods();
    for (Method method : methods) {
      Class<?>[] exceptionTypes = method.getExceptionTypes();
      String[] exceptions = new String[exceptionTypes.length];
      for (int i = 0; i < exceptions.length; i++) {
        exceptions[i] = BytecodeHelper.getClassInternalName(exceptionTypes[i]);
      }
      // for each method defined in the class, generate the appropriate delegation bytecode
      visitMethod(
          method.getModifiers(),
          method.getName(),
          BytecodeHelper.getMethodDescriptor(method.getReturnType(), method.getParameterTypes()),
          null,
          exceptions);
    }
    Constructor[] constructors = clazz.getDeclaredConstructors();
    for (Constructor method : constructors) {
      Class<?>[] exceptionTypes = method.getExceptionTypes();
      String[] exceptions = new String[exceptionTypes.length];
      for (int i = 0; i < exceptions.length; i++) {
        exceptions[i] = BytecodeHelper.getClassInternalName(exceptionTypes[i]);
      }
      // for each method defined in the class, generate the appropriate delegation bytecode
      visitMethod(
          method.getModifiers(),
          "<init>",
          BytecodeHelper.getMethodDescriptor(Void.TYPE, method.getParameterTypes()),
          null,
          exceptions);
    }

    for (Class intf : clazz.getInterfaces()) {
      visitClass(intf);
    }
    Class superclass = clazz.getSuperclass();
    if (superclass != null) visitClass(superclass);

    // Ultimately, methods can be available in the closure map which are not defined by the
    // superclass
    // nor the interfaces
    for (Map.Entry<String, Boolean> entry : delegatedClosures.entrySet()) {
      Boolean visited = entry.getValue();
      if (!visited) {
        String name = entry.getKey();
        if (!"*".equals(name)) {
          // generate a new method
          visitMethod(ACC_PUBLIC, name, "([Ljava/lang/Object;)Ljava/lang/Object;", null, null);
        }
      }
    }
  }
  public boolean addGeneratedClosureConstructorCall(ConstructorCallExpression call) {
    ClassNode classNode = controller.getClassNode();
    if (!classNode.declaresInterface(ClassHelper.GENERATED_CLOSURE_Type)) return false;

    AsmClassGenerator acg = controller.getAcg();
    OperandStack operandStack = controller.getOperandStack();

    MethodVisitor mv = controller.getMethodVisitor();
    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(acg);
    operandStack.box();
    arguments.getExpression(1).visit(acg);
    operandStack.box();
    // TODO: replace with normal String, p not needed
    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, false);
    operandStack.remove(2);
    return true;
  }
 /**
  * Given a wrapped number type (Byte, Integer, Short, ...), generates bytecode to convert it to a
  * primitive number (int, long, double) using calls to wrapped.[targetType]Value()
  *
  * @param mv method visitor
  * @param sourceType the wrapped number type
  * @param targetType the primitive target type
  */
 public static void doCastToPrimitive(
     MethodVisitor mv, ClassNode sourceType, ClassNode targetType) {
   mv.visitMethodInsn(
       INVOKEVIRTUAL,
       BytecodeHelper.getClassInternalName(sourceType),
       targetType.getName() + "Value",
       "()" + BytecodeHelper.getTypeDescription(targetType));
 }
 boolean makeGetField(
     final Expression receiver,
     final ClassNode receiverType,
     final String fieldName,
     final boolean implicitThis,
     final boolean samePackage) {
   FieldNode field = receiverType.getField(fieldName);
   // direct access is allowed if we are in the same class as the declaring class
   // or we are in an inner class
   if (field != null && isDirectAccessAllowed(field, controller.getClassNode(), samePackage)) {
     CompileStack compileStack = controller.getCompileStack();
     MethodVisitor mv = controller.getMethodVisitor();
     if (field.isStatic()) {
       mv.visitFieldInsn(
           GETSTATIC,
           BytecodeHelper.getClassInternalName(field.getOwner()),
           fieldName,
           BytecodeHelper.getTypeDescription(field.getOriginType()));
       controller.getOperandStack().push(field.getOriginType());
     } else {
       if (implicitThis) {
         compileStack.pushImplicitThis(implicitThis);
       }
       receiver.visit(controller.getAcg());
       if (implicitThis) compileStack.popImplicitThis();
       if (!controller.getOperandStack().getTopOperand().isDerivedFrom(field.getOwner())) {
         mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(field.getOwner()));
       }
       mv.visitFieldInsn(
           GETFIELD,
           BytecodeHelper.getClassInternalName(field.getOwner()),
           fieldName,
           BytecodeHelper.getTypeDescription(field.getOriginType()));
     }
     controller.getOperandStack().replace(field.getOriginType());
     return true;
   }
   ClassNode superClass = receiverType.getSuperClass();
   if (superClass != null) {
     return makeGetField(receiver, superClass, fieldName, implicitThis, false);
   }
   return false;
 }
  /** load the constant on the operand stack. */
  public void pushConstant(ConstantExpression expression) {
    MethodVisitor mv = controller.getMethodVisitor();
    Object value = expression.getValue();
    ClassNode origType = expression.getType().redirect();
    ClassNode type = ClassHelper.getUnwrapper(origType);
    boolean boxing = origType != type;
    boolean asPrimitive = boxing || ClassHelper.isPrimitiveType(type);

    if (value == null) {
      mv.visitInsn(ACONST_NULL);
    } else if (boxing && value instanceof Boolean) {
      // special path for boxed boolean
      Boolean bool = (Boolean) value;
      String text = bool ? "TRUE" : "FALSE";
      mv.visitFieldInsn(GETSTATIC, "java/lang/Boolean", text, "Ljava/lang/Boolean;");
      boxing = false;
      type = origType;
    } else if (asPrimitive) {
      pushPrimitiveConstant(mv, value, type);
    } else if (value instanceof BigDecimal) {
      String className = BytecodeHelper.getClassInternalName(value.getClass().getName());
      mv.visitTypeInsn(NEW, className);
      mv.visitInsn(DUP);
      mv.visitLdcInsn(value.toString());
      mv.visitMethodInsn(INVOKESPECIAL, className, "<init>", "(Ljava/lang/String;)V", false);
    } else if (value instanceof BigInteger) {
      String className = BytecodeHelper.getClassInternalName(value.getClass().getName());
      mv.visitTypeInsn(NEW, className);
      mv.visitInsn(DUP);
      mv.visitLdcInsn(value.toString());
      mv.visitMethodInsn(INVOKESPECIAL, className, "<init>", "(Ljava/lang/String;)V", false);
    } else if (value instanceof String) {
      mv.visitLdcInsn(value);
    } else {
      throw new ClassGeneratorException(
          "Cannot generate bytecode for constant: " + value + " of type: " + type.getName());
    }

    push(type);
    if (boxing) box();
  }
  // GroovyClassVisitor interface
  // -------------------------------------------------------------------------
  public void visitClass(ClassNode classNode) {
    try {
      this.classNode = classNode;
      this.internalClassName = BytecodeHelper.getClassInternalName(classNode);

      // System.out.println("Generating class: " + classNode.getName());

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

      cv.visit(
          Opcodes.V1_3,
          classNode.getModifiers(),
          internalClassName,
          (String) null,
          internalBaseClassName,
          BytecodeHelper.getClassInternalNames(classNode.getInterfaces()));

      classNode.visitContents(this);

      for (Iterator iter = innerClasses.iterator(); iter.hasNext(); ) {
        ClassNode innerClass = (ClassNode) iter.next();
        ClassNode innerClassType = innerClass;
        String innerClassInternalName = BytecodeHelper.getClassInternalName(innerClassType);
        String outerClassName = internalClassName; // default for inner classes
        MethodNode enclosingMethod = innerClass.getEnclosingMethod();
        if (enclosingMethod != null) {
          // local inner classes do not specify the outer class name
          outerClassName = null;
        }
        cv.visitInnerClass(
            innerClassInternalName,
            outerClassName,
            innerClassType.getName(),
            innerClass.getModifiers());
      }
      cv.visitEnd();
    } catch (GroovyRuntimeException e) {
      e.setModule(classNode.getModule());
      throw e;
    }
  }
 public static void doCast(MethodVisitor mv, Class type) {
   if (type == Object.class) return;
   if (type.isPrimitive() && type != Void.TYPE) {
     unbox(mv, type);
   } else {
     mv.visitTypeInsn(
         CHECKCAST,
         type.isArray()
             ? BytecodeHelper.getTypeDescription(type)
             : BytecodeHelper.getClassInternalName(type.getName()));
   }
 }
 public static void doCast(MethodVisitor mv, ClassNode type) {
   if (type == ClassHelper.OBJECT_TYPE) return;
   if (ClassHelper.isPrimitiveType(type) && type != ClassHelper.VOID_TYPE) {
     unbox(mv, type);
   } else {
     mv.visitTypeInsn(
         CHECKCAST,
         type.isArray()
             ? BytecodeHelper.getTypeDescription(type)
             : BytecodeHelper.getClassInternalName(type.getName()));
   }
 }
  public void writeClosure(ClosureExpression expression) {
    CompileStack compileStack = controller.getCompileStack();
    MethodVisitor mv = controller.getMethodVisitor();
    ClassNode classNode = controller.getClassNode();
    AsmClassGenerator acg = controller.getAcg();

    // generate closure as public class to make sure it can be properly invoked by classes of the
    // Groovy runtime without circumventing JVM access checks (see CachedMethod for example).
    ClassNode closureClass = getOrAddClosureClass(expression, ACC_PUBLIC);
    String closureClassinternalName = BytecodeHelper.getClassInternalName(closureClass);
    List constructors = closureClass.getDeclaredConstructors();
    ConstructorNode node = (ConstructorNode) constructors.get(0);

    Parameter[] localVariableParams = node.getParameters();

    mv.visitTypeInsn(NEW, closureClassinternalName);
    mv.visitInsn(DUP);
    if (controller.isStaticMethod() || compileStack.isInSpecialConstructorCall()) {
      (new ClassExpression(classNode)).visit(acg);
      (new ClassExpression(controller.getOutermostClass())).visit(acg);
    } else {
      mv.visitVarInsn(ALOAD, 0);
      controller.getOperandStack().push(ClassHelper.OBJECT_TYPE);
      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();
      loadReference(name, controller);
      if (param.getNodeMetaData(ClosureWriter.UseExistingReference.class) == null) {
        param.setNodeMetaData(ClosureWriter.UseExistingReference.class, Boolean.TRUE);
      }
    }

    // we may need to pass in some other constructors
    // cv.visitMethodInsn(INVOKESPECIAL, innerClassinternalName, "<init>", prototype + ")V");
    mv.visitMethodInsn(
        INVOKESPECIAL,
        closureClassinternalName,
        "<init>",
        BytecodeHelper.getMethodDescriptor(ClassHelper.VOID_TYPE, localVariableParams),
        false);
    controller.getOperandStack().replace(ClassHelper.CLOSURE_TYPE, localVariableParams.length);
  }
  private void evaluateInstanceof(BinaryExpression expression) {
    OperandStack operandStack = controller.getOperandStack();

    expression.getLeftExpression().visit(controller.getAcg());
    operandStack.box();
    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);
    controller.getMethodVisitor().visitTypeInsn(INSTANCEOF, classInternalName);
    operandStack.replace(ClassHelper.boolean_TYPE);
  }
 private MethodVisitor createConstructor(
     final int access,
     final String name,
     final String desc,
     final String signature,
     final String[] exceptions) {
   Type[] args = Type.getArgumentTypes(desc);
   StringBuilder newDesc = new StringBuilder("(");
   for (Type arg : args) {
     newDesc.append(arg.getDescriptor());
   }
   newDesc.append("Ljava/util/Map;"); // the closure map
   if (generateDelegateField) {
     newDesc.append(BytecodeHelper.getTypeDescription(delegateClass));
   }
   newDesc.append(")V");
   MethodVisitor mv = super.visitMethod(access, name, newDesc.toString(), signature, exceptions);
   mv.visitCode();
   initializeDelegateClosure(mv, args.length);
   if (generateDelegateField) {
     initializeDelegateObject(mv, args.length + 1);
   }
   mv.visitVarInsn(ALOAD, 0);
   int idx = 1;
   for (Type arg : args) {
     if (isPrimitive(arg)) {
       mv.visitIntInsn(getLoadInsn(arg), idx);
     } else {
       mv.visitVarInsn(ALOAD, idx); // load argument i
     }
     idx += registerLen(arg);
   }
   mv.visitMethodInsn(
       INVOKESPECIAL, BytecodeHelper.getClassInternalName(superClass), "<init>", desc);
   mv.visitInsn(RETURN);
   int max = idx + 1 + (generateDelegateField ? 1 : 0);
   mv.visitMaxs(max, max);
   mv.visitEnd();
   return EMPTY_VISITOR;
 }
Exemple #13
0
/**
 * A helper class for bytecode generation with AsmClassGenerator.
 *
 * @author <a href="mailto:[email protected]">James Strachan</a>
 * @author <a href="mailto:[email protected]">Bing Ran</a>
 * @author <a href="mailto:[email protected]">Jochen Theodorou</a>
 * @version $Revision$
 */
public class BytecodeHelper implements Opcodes {

  private static String DTT_CLASSNAME =
      BytecodeHelper.getClassInternalName(DefaultTypeTransformation.class.getName());

  public static String getClassInternalName(ClassNode t) {
    if (t.isPrimaryClassNode()) {
      return getClassInternalName(t.getName());
    }
    return getClassInternalName(t.getTypeClass());
  }

  public static String getClassInternalName(Class t) {
    return org.objectweb.asm.Type.getInternalName(t);
  }

  /** @return the ASM internal name of the type */
  public static String getClassInternalName(String name) {
    return name.replace('.', '/');
  }

  public static String getMethodDescriptor(ClassNode returnType, Parameter[] parameters) {
    StringBuffer buffer = new StringBuffer("(");
    for (int i = 0; i < parameters.length; i++) {
      buffer.append(getTypeDescription(parameters[i].getType()));
    }
    buffer.append(")");
    buffer.append(getTypeDescription(returnType));
    return buffer.toString();
  }

  /** @return the ASM method type descriptor */
  public static String getMethodDescriptor(Class returnType, Class[] paramTypes) {
    // lets avoid class loading
    StringBuffer buffer = new StringBuffer("(");
    for (int i = 0; i < paramTypes.length; i++) {
      buffer.append(getTypeDescription(paramTypes[i]));
    }
    buffer.append(")");
    buffer.append(getTypeDescription(returnType));
    return buffer.toString();
  }

  public static String getTypeDescription(Class c) {
    return org.objectweb.asm.Type.getDescriptor(c);
  }

  /**
   * array types are special: eg.: String[]: classname: [Ljava.lang.String; Object: classname:
   * java.lang.Object int[] : classname: [I unlike getTypeDescription '.' is not replaced by '/'. it
   * seems that makes problems for the class loading if '.' is replaced by '/'
   *
   * @return the ASM type description for class loading
   */
  public static String getClassLoadingTypeDescription(ClassNode c) {
    StringBuffer buf = new StringBuffer();
    boolean array = false;
    while (true) {
      if (c.isArray()) {
        buf.append('[');
        c = c.getComponentType();
        array = true;
      } else {
        if (ClassHelper.isPrimitiveType(c)) {
          buf.append(getTypeDescription(c));
        } else {
          if (array) buf.append('L');
          buf.append(c.getName());
          if (array) buf.append(';');
        }
        return buf.toString();
      }
    }
  }

  /**
   * array types are special: eg.: String[]: classname: [Ljava/lang/String; int[]: [I
   *
   * @return the ASM type description
   */
  public static String getTypeDescription(ClassNode c) {
    return getTypeDescription(c, true);
  }

  /**
   * array types are special: eg.: String[]: classname: [Ljava/lang/String; int[]: [I
   *
   * @return the ASM type description
   */
  private static String getTypeDescription(ClassNode c, boolean end) {
    StringBuffer buf = new StringBuffer();
    ClassNode d = c;
    while (true) {
      if (ClassHelper.isPrimitiveType(d)) {
        char car;
        if (d == ClassHelper.int_TYPE) {
          car = 'I';
        } else if (d == ClassHelper.VOID_TYPE) {
          car = 'V';
        } else if (d == ClassHelper.boolean_TYPE) {
          car = 'Z';
        } else if (d == ClassHelper.byte_TYPE) {
          car = 'B';
        } else if (d == ClassHelper.char_TYPE) {
          car = 'C';
        } else if (d == ClassHelper.short_TYPE) {
          car = 'S';
        } else if (d == ClassHelper.double_TYPE) {
          car = 'D';
        } else if (d == ClassHelper.float_TYPE) {
          car = 'F';
        } else /* long */ {
          car = 'J';
        }
        buf.append(car);
        return buf.toString();
      } else if (d.isArray()) {
        buf.append('[');
        d = d.getComponentType();
      } else {
        buf.append('L');
        String name = d.getName();
        int len = name.length();
        for (int i = 0; i < len; ++i) {
          char car = name.charAt(i);
          buf.append(car == '.' ? '/' : car);
        }
        if (end) buf.append(';');
        return buf.toString();
      }
    }
  }

  /** @return an array of ASM internal names of the type */
  public static String[] getClassInternalNames(ClassNode[] names) {
    int size = names.length;
    String[] answer = new String[size];
    for (int i = 0; i < size; i++) {
      answer[i] = getClassInternalName(names[i]);
    }
    return answer;
  }

  public static void pushConstant(MethodVisitor mv, int value) {
    switch (value) {
      case 0:
        mv.visitInsn(ICONST_0);
        break;
      case 1:
        mv.visitInsn(ICONST_1);
        break;
      case 2:
        mv.visitInsn(ICONST_2);
        break;
      case 3:
        mv.visitInsn(ICONST_3);
        break;
      case 4:
        mv.visitInsn(ICONST_4);
        break;
      case 5:
        mv.visitInsn(ICONST_5);
        break;
      default:
        if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
          mv.visitIntInsn(BIPUSH, value);
        } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
          mv.visitIntInsn(SIPUSH, value);
        } else {
          mv.visitLdcInsn(Integer.valueOf(value));
        }
    }
  }

  /** negate a boolean on stack. true->false, false->true */
  public static void negateBoolean(MethodVisitor mv) {
    // code to negate the primitive boolean
    Label endLabel = new Label();
    Label falseLabel = new Label();
    mv.visitJumpInsn(IFNE, falseLabel);
    mv.visitInsn(ICONST_1);
    mv.visitJumpInsn(GOTO, endLabel);
    mv.visitLabel(falseLabel);
    mv.visitInsn(ICONST_0);
    mv.visitLabel(endLabel);
  }

  /**
   * load a message on the stack and remove it right away. Good for put a mark in the generated
   * bytecode for debugging purpose.
   *
   * @param msg
   */
  /*public void mark(String msg) {
      mv.visitLdcInsn(msg);
      mv.visitInsn(POP);
  }*/

  /**
   * returns a name that Class.forName() can take. Notably for arrays: [I, [Ljava.lang.String; etc
   * Regular object type: java.lang.String
   *
   * @param name
   */
  public static String formatNameForClassLoading(String name) {
    if (name.equals("int")
        || name.equals("long")
        || name.equals("short")
        || name.equals("float")
        || name.equals("double")
        || name.equals("byte")
        || name.equals("char")
        || name.equals("boolean")
        || name.equals("void")) {
      return name;
    }

    if (name == null) {
      return "java.lang.Object;";
    }

    if (name.startsWith("[")) {
      return name.replace('/', '.');
    }

    if (name.startsWith("L")) {
      name = name.substring(1);
      if (name.endsWith(";")) {
        name = name.substring(0, name.length() - 1);
      }
      return name.replace('/', '.');
    }

    String prefix = "";
    if (name.endsWith("[]")) { // todo need process multi
      prefix = "[";
      name = name.substring(0, name.length() - 2);
      if (name.equals("int")) {
        return prefix + "I";
      } else if (name.equals("long")) {
        return prefix + "J";
      } else if (name.equals("short")) {
        return prefix + "S";
      } else if (name.equals("float")) {
        return prefix + "F";
      } else if (name.equals("double")) {
        return prefix + "D";
      } else if (name.equals("byte")) {
        return prefix + "B";
      } else if (name.equals("char")) {
        return prefix + "C";
      } else if (name.equals("boolean")) {
        return prefix + "Z";
      } else {
        return prefix + "L" + name.replace('/', '.') + ";";
      }
    }
    return name.replace('/', '.');
  }

  /*public void dup() {
      mv.visitInsn(DUP);
  }*/

  public static void doReturn(MethodVisitor mv, ClassNode returnType) {
    if (returnType == ClassHelper.double_TYPE) {
      mv.visitInsn(DRETURN);
    } else if (returnType == ClassHelper.float_TYPE) {
      mv.visitInsn(FRETURN);
    } else if (returnType == ClassHelper.long_TYPE) {
      mv.visitInsn(LRETURN);
    } else if (returnType == ClassHelper.boolean_TYPE
        || returnType == ClassHelper.char_TYPE
        || returnType == ClassHelper.byte_TYPE
        || returnType == ClassHelper.int_TYPE
        || returnType == ClassHelper.short_TYPE) {
      // byte,short,boolean,int are all IRETURN
      mv.visitInsn(IRETURN);
    } else if (returnType == ClassHelper.VOID_TYPE) {
      mv.visitInsn(RETURN);
    } else {
      mv.visitInsn(ARETURN);
    }
  }

  private static boolean hasGenerics(Parameter[] param) {
    if (param.length == 0) return false;
    for (int i = 0; i < param.length; i++) {
      ClassNode type = param[i].getType();
      if (hasGenerics(type)) return true;
    }
    return false;
  }

  private static boolean hasGenerics(ClassNode type) {
    return type.isArray() ? hasGenerics(type.getComponentType()) : type.getGenericsTypes() != null;
  }

  public static String getGenericsMethodSignature(MethodNode node) {
    GenericsType[] generics = node.getGenericsTypes();
    Parameter[] param = node.getParameters();
    ClassNode returnType = node.getReturnType();

    if (generics == null && !hasGenerics(param) && !hasGenerics(returnType)) return null;

    StringBuffer ret = new StringBuffer(100);
    getGenericsTypeSpec(ret, generics);

    GenericsType[] paramTypes = new GenericsType[param.length];
    for (int i = 0; i < param.length; i++) {
      ClassNode pType = param[i].getType();
      if (pType.getGenericsTypes() == null || !pType.isGenericsPlaceHolder()) {
        paramTypes[i] = new GenericsType(pType);
      } else {
        paramTypes[i] = pType.getGenericsTypes()[0];
      }
    }
    addSubTypes(ret, paramTypes, "(", ")");
    addSubTypes(ret, new GenericsType[] {new GenericsType(returnType)}, "", "");
    return ret.toString();
  }

  private static boolean usesGenericsInClassSignature(ClassNode node) {
    if (!node.isUsingGenerics()) return false;
    if (hasGenerics(node)) return true;
    ClassNode sclass = node.getUnresolvedSuperClass(false);
    if (sclass.isUsingGenerics()) return true;
    ClassNode[] interfaces = node.getInterfaces();
    if (interfaces != null) {
      for (int i = 0; i < interfaces.length; i++) {
        if (interfaces[i].isUsingGenerics()) return true;
      }
    }

    return false;
  }

  public static String getGenericsSignature(ClassNode node) {
    if (!usesGenericsInClassSignature(node)) return null;
    GenericsType[] genericsTypes = node.getGenericsTypes();
    StringBuffer ret = new StringBuffer(100);
    getGenericsTypeSpec(ret, genericsTypes);
    GenericsType extendsPart = new GenericsType(node.getUnresolvedSuperClass(false));
    writeGenericsBounds(ret, extendsPart, true);
    ClassNode[] interfaces = node.getInterfaces();
    for (int i = 0; i < interfaces.length; i++) {
      GenericsType interfacePart = new GenericsType(interfaces[i]);
      writeGenericsBounds(ret, interfacePart, false);
    }
    return ret.toString();
  }

  private static void getGenericsTypeSpec(StringBuffer ret, GenericsType[] genericsTypes) {
    if (genericsTypes == null) return;
    ret.append('<');
    for (int i = 0; i < genericsTypes.length; i++) {
      String name = genericsTypes[i].getName();
      ret.append(name);
      ret.append(':');
      writeGenericsBounds(ret, genericsTypes[i], true);
    }
    ret.append('>');
  }

  public static String getGenericsBounds(ClassNode type) {
    GenericsType[] genericsTypes = type.getGenericsTypes();
    if (genericsTypes == null) return null;
    StringBuffer ret = new StringBuffer(100);
    if (type.isGenericsPlaceHolder()) {
      addSubTypes(ret, type.getGenericsTypes(), "", "");
    } else {
      GenericsType gt = new GenericsType(type);
      writeGenericsBounds(ret, gt, false);
    }

    return ret.toString();
  }

  private static void writeGenericsBoundType(
      StringBuffer ret, ClassNode printType, boolean writeInterfaceMarker) {
    if (writeInterfaceMarker && printType.isInterface()) ret.append(":");
    if (printType.equals(ClassHelper.OBJECT_TYPE) && printType.getGenericsTypes() != null) {
      ret.append("T");
      ret.append(printType.getGenericsTypes()[0].getName());
      ret.append(";");
    } else {
      ret.append(getTypeDescription(printType, false));
      addSubTypes(ret, printType.getGenericsTypes(), "<", ">");
      if (!ClassHelper.isPrimitiveType(printType)) ret.append(";");
    }
  }

  private static void writeGenericsBounds(
      StringBuffer ret, GenericsType type, boolean writeInterfaceMarker) {
    if (type.getUpperBounds() != null) {
      ClassNode[] bounds = type.getUpperBounds();
      for (int i = 0; i < bounds.length; i++) {
        writeGenericsBoundType(ret, bounds[i], writeInterfaceMarker);
      }
    } else if (type.getLowerBound() != null) {
      writeGenericsBoundType(ret, type.getLowerBound(), writeInterfaceMarker);
    } else {
      writeGenericsBoundType(ret, type.getType(), writeInterfaceMarker);
    }
  }

  private static void addSubTypes(
      StringBuffer ret, GenericsType[] types, String start, String end) {
    if (types == null) return;
    ret.append(start);
    for (int i = 0; i < types.length; i++) {
      if (types[i].getType().isArray()) {
        ret.append("[");
        addSubTypes(
            ret,
            new GenericsType[] {new GenericsType(types[i].getType().getComponentType())},
            "",
            "");
      } else {
        if (types[i].isPlaceholder()) {
          ret.append('T');
          String name = types[i].getName();
          ret.append(name);
          ret.append(';');
        } else if (types[i].isWildcard()) {
          if (types[i].getUpperBounds() != null) {
            ret.append('+');
            writeGenericsBounds(ret, types[i], false);
          } else if (types[i].getLowerBound() != null) {
            ret.append('-');
            writeGenericsBounds(ret, types[i], false);
          } else {
            ret.append('*');
          }
        } else {
          writeGenericsBounds(ret, types[i], false);
        }
      }
    }
    ret.append(end);
  }

  public static void load(MethodVisitor mv, ClassNode type, int idx) {
    if (type == ClassHelper.double_TYPE) {
      mv.visitVarInsn(DLOAD, idx);
    } else if (type == ClassHelper.float_TYPE) {
      mv.visitVarInsn(FLOAD, idx);
    } else if (type == ClassHelper.long_TYPE) {
      mv.visitVarInsn(LLOAD, idx);
    } else if (type == ClassHelper.boolean_TYPE
        || type == ClassHelper.char_TYPE
        || type == ClassHelper.byte_TYPE
        || type == ClassHelper.int_TYPE
        || type == ClassHelper.short_TYPE) {
      mv.visitVarInsn(ILOAD, idx);
    } else {
      mv.visitVarInsn(ALOAD, idx);
    }
  }

  public static void doCast(MethodVisitor mv, ClassNode type) {
    if (type == ClassHelper.OBJECT_TYPE) return;
    if (ClassHelper.isPrimitiveType(type) && type != ClassHelper.VOID_TYPE) {
      unbox(mv, type);
    } else {
      mv.visitTypeInsn(
          CHECKCAST,
          type.isArray()
              ? BytecodeHelper.getTypeDescription(type)
              : BytecodeHelper.getClassInternalName(type.getName()));
    }
  }

  public static void doCast(MethodVisitor mv, Class type) {
    if (type == Object.class) return;
    if (type.isPrimitive() && type != Void.TYPE) {
      unbox(mv, type);
    } else {
      mv.visitTypeInsn(
          CHECKCAST,
          type.isArray()
              ? BytecodeHelper.getTypeDescription(type)
              : BytecodeHelper.getClassInternalName(type.getName()));
    }
  }

  /** Generates the bytecode to unbox the current value on the stack */
  public static void unbox(MethodVisitor mv, Class type) {
    if (type.isPrimitive() && type != Void.TYPE) {
      String returnString = "(Ljava/lang/Object;)" + BytecodeHelper.getTypeDescription(type);
      mv.visitMethodInsn(INVOKESTATIC, DTT_CLASSNAME, type.getName() + "Unbox", returnString);
    }
  }

  public static void unbox(MethodVisitor mv, ClassNode type) {
    if (type.isPrimaryClassNode()) return;
    unbox(mv, type.getTypeClass());
  }

  /** box top level operand */
  public static boolean box(MethodVisitor mv, ClassNode type) {
    if (type.isPrimaryClassNode()) return false;
    return box(mv, type.getTypeClass());
  }

  /** Generates the bytecode to autobox the current value on the stack */
  public static boolean box(MethodVisitor mv, Class type) {
    if (ReflectionCache.getCachedClass(type).isPrimitive && type != void.class) {
      String returnString = "(" + BytecodeHelper.getTypeDescription(type) + ")Ljava/lang/Object;";
      mv.visitMethodInsn(INVOKESTATIC, DTT_CLASSNAME, "box", returnString);
      return true;
    }
    return false;
  }
}
/**
 * A helper class for bytecode generation with AsmClassGenerator.
 *
 * @author <a href="mailto:[email protected]">James Strachan</a>
 * @author <a href="mailto:[email protected]">Bing Ran</a>
 * @author <a href="mailto:[email protected]">Jochen Theodorou</a>
 * @version $Revision$
 */
public class BytecodeHelper implements Opcodes {

  private static String DTT_CLASSNAME =
      BytecodeHelper.getClassInternalName(DefaultTypeTransformation.class.getName());

  public static String getClassInternalName(ClassNode t) {
    if (t.isPrimaryClassNode()) {
      if (t.isArray()) return "[L" + getClassInternalName(t.getComponentType()) + ";";
      return getClassInternalName(t.getName());
    }
    return getClassInternalName(t.getTypeClass());
  }

  public static String getClassInternalName(Class t) {
    return org.objectweb.asm.Type.getInternalName(t);
  }

  /** @return the ASM internal name of the type */
  public static String getClassInternalName(String name) {
    return name.replace('.', '/');
  }

  public static String getMethodDescriptor(ClassNode returnType, Parameter[] parameters) {
    StringBuffer buffer = new StringBuffer("(");
    for (int i = 0; i < parameters.length; i++) {
      buffer.append(getTypeDescription(parameters[i].getType()));
    }
    buffer.append(")");
    buffer.append(getTypeDescription(returnType));
    return buffer.toString();
  }

  /**
   * Returns a method descriptor for the given {@link org.codehaus.groovy.ast.MethodNode}.
   *
   * @param methodNode the method node for which to create the descriptor
   * @return a method descriptor as defined in section JVMS section 4.3.3
   */
  public static String getMethodDescriptor(MethodNode methodNode) {
    return getMethodDescriptor(methodNode.getReturnType(), methodNode.getParameters());
  }

  /** @return the ASM method type descriptor */
  public static String getMethodDescriptor(Class returnType, Class[] paramTypes) {
    // lets avoid class loading
    StringBuffer buffer = new StringBuffer("(");
    for (int i = 0; i < paramTypes.length; i++) {
      buffer.append(getTypeDescription(paramTypes[i]));
    }
    buffer.append(")");
    buffer.append(getTypeDescription(returnType));
    return buffer.toString();
  }

  public static String getTypeDescription(Class c) {
    return org.objectweb.asm.Type.getDescriptor(c);
  }

  /**
   * array types are special: eg.: String[]: classname: [Ljava.lang.String; Object: classname:
   * java.lang.Object int[] : classname: [I unlike getTypeDescription '.' is not replaced by '/'. it
   * seems that makes problems for the class loading if '.' is replaced by '/'
   *
   * @return the ASM type description for class loading
   */
  public static String getClassLoadingTypeDescription(ClassNode c) {
    StringBuffer buf = new StringBuffer();
    boolean array = false;
    while (true) {
      if (c.isArray()) {
        buf.append('[');
        c = c.getComponentType();
        array = true;
      } else {
        if (ClassHelper.isPrimitiveType(c)) {
          buf.append(getTypeDescription(c));
        } else {
          if (array) buf.append('L');
          buf.append(c.getName());
          if (array) buf.append(';');
        }
        return buf.toString();
      }
    }
  }

  /**
   * array types are special: eg.: String[]: classname: [Ljava/lang/String; int[]: [I
   *
   * @return the ASM type description
   */
  public static String getTypeDescription(ClassNode c) {
    return getTypeDescription(c, true);
  }

  /**
   * array types are special: eg.: String[]: classname: [Ljava/lang/String; int[]: [I
   *
   * @return the ASM type description
   */
  private static String getTypeDescription(ClassNode c, boolean end) {
    StringBuffer buf = new StringBuffer();
    ClassNode d = c;
    while (true) {
      if (ClassHelper.isPrimitiveType(d)) {
        char car;
        if (d == ClassHelper.int_TYPE) {
          car = 'I';
        } else if (d == ClassHelper.VOID_TYPE) {
          car = 'V';
        } else if (d == ClassHelper.boolean_TYPE) {
          car = 'Z';
        } else if (d == ClassHelper.byte_TYPE) {
          car = 'B';
        } else if (d == ClassHelper.char_TYPE) {
          car = 'C';
        } else if (d == ClassHelper.short_TYPE) {
          car = 'S';
        } else if (d == ClassHelper.double_TYPE) {
          car = 'D';
        } else if (d == ClassHelper.float_TYPE) {
          car = 'F';
        } else /* long */ {
          car = 'J';
        }
        buf.append(car);
        return buf.toString();
      } else if (d.isArray()) {
        buf.append('[');
        d = d.getComponentType();
      } else {
        buf.append('L');
        String name = d.getName();
        int len = name.length();
        for (int i = 0; i < len; ++i) {
          char car = name.charAt(i);
          buf.append(car == '.' ? '/' : car);
        }
        if (end) buf.append(';');
        return buf.toString();
      }
    }
  }

  /** @return an array of ASM internal names of the type */
  public static String[] getClassInternalNames(ClassNode[] names) {
    int size = names.length;
    String[] answer = new String[size];
    for (int i = 0; i < size; i++) {
      answer[i] = getClassInternalName(names[i]);
    }
    return answer;
  }

  public static void pushConstant(MethodVisitor mv, int value) {
    switch (value) {
      case 0:
        mv.visitInsn(ICONST_0);
        break;
      case 1:
        mv.visitInsn(ICONST_1);
        break;
      case 2:
        mv.visitInsn(ICONST_2);
        break;
      case 3:
        mv.visitInsn(ICONST_3);
        break;
      case 4:
        mv.visitInsn(ICONST_4);
        break;
      case 5:
        mv.visitInsn(ICONST_5);
        break;
      default:
        if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
          mv.visitIntInsn(BIPUSH, value);
        } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
          mv.visitIntInsn(SIPUSH, value);
        } else {
          mv.visitLdcInsn(Integer.valueOf(value));
        }
    }
  }

  /** negate a boolean on stack. true->false, false->true */
  public static void negateBoolean(MethodVisitor mv) {
    // code to negate the primitive boolean
    Label endLabel = new Label();
    Label falseLabel = new Label();
    mv.visitJumpInsn(IFNE, falseLabel);
    mv.visitInsn(ICONST_1);
    mv.visitJumpInsn(GOTO, endLabel);
    mv.visitLabel(falseLabel);
    mv.visitInsn(ICONST_0);
    mv.visitLabel(endLabel);
  }

  /**
   * load a message on the stack and remove it right away. Good for put a mark in the generated
   * bytecode for debugging purpose.
   *
   * @param msg
   */
  /*public void mark(String msg) {
      mv.visitLdcInsn(msg);
      mv.visitInsn(POP);
  }*/

  /**
   * returns a name that Class.forName() can take. Notably for arrays: [I, [Ljava.lang.String; etc
   * Regular object type: java.lang.String
   *
   * @param name
   */
  public static String formatNameForClassLoading(String name) {
    if (name.equals("int")
        || name.equals("long")
        || name.equals("short")
        || name.equals("float")
        || name.equals("double")
        || name.equals("byte")
        || name.equals("char")
        || name.equals("boolean")
        || name.equals("void")) {
      return name;
    }

    if (name == null) {
      return "java.lang.Object;";
    }

    if (name.startsWith("[")) {
      return name.replace('/', '.');
    }

    if (name.startsWith("L")) {
      name = name.substring(1);
      if (name.endsWith(";")) {
        name = name.substring(0, name.length() - 1);
      }
      return name.replace('/', '.');
    }

    String prefix = "";
    if (name.endsWith("[]")) { // todo need process multi
      prefix = "[";
      name = name.substring(0, name.length() - 2);
      if (name.equals("int")) {
        return prefix + "I";
      } else if (name.equals("long")) {
        return prefix + "J";
      } else if (name.equals("short")) {
        return prefix + "S";
      } else if (name.equals("float")) {
        return prefix + "F";
      } else if (name.equals("double")) {
        return prefix + "D";
      } else if (name.equals("byte")) {
        return prefix + "B";
      } else if (name.equals("char")) {
        return prefix + "C";
      } else if (name.equals("boolean")) {
        return prefix + "Z";
      } else {
        return prefix + "L" + name.replace('/', '.') + ";";
      }
    }
    return name.replace('/', '.');
  }

  /*public void dup() {
      mv.visitInsn(DUP);
  }*/

  public static void doReturn(MethodVisitor mv, ClassNode returnType) {
    if (returnType == ClassHelper.double_TYPE) {
      mv.visitInsn(DRETURN);
    } else if (returnType == ClassHelper.float_TYPE) {
      mv.visitInsn(FRETURN);
    } else if (returnType == ClassHelper.long_TYPE) {
      mv.visitInsn(LRETURN);
    } else if (returnType == ClassHelper.boolean_TYPE
        || returnType == ClassHelper.char_TYPE
        || returnType == ClassHelper.byte_TYPE
        || returnType == ClassHelper.int_TYPE
        || returnType == ClassHelper.short_TYPE) {
      // byte,short,boolean,int are all IRETURN
      mv.visitInsn(IRETURN);
    } else if (returnType == ClassHelper.VOID_TYPE) {
      mv.visitInsn(RETURN);
    } else {
      mv.visitInsn(ARETURN);
    }
  }

  private static boolean hasGenerics(Parameter[] param) {
    if (param.length == 0) return false;
    for (int i = 0; i < param.length; i++) {
      ClassNode type = param[i].getType();
      if (hasGenerics(type)) return true;
    }
    return false;
  }

  private static boolean hasGenerics(ClassNode type) {
    return type.isArray() ? hasGenerics(type.getComponentType()) : type.getGenericsTypes() != null;
  }

  public static String getGenericsMethodSignature(MethodNode node) {
    GenericsType[] generics = node.getGenericsTypes();
    Parameter[] param = node.getParameters();
    ClassNode returnType = node.getReturnType();

    if (generics == null && !hasGenerics(param) && !hasGenerics(returnType)) return null;

    StringBuffer ret = new StringBuffer(100);
    getGenericsTypeSpec(ret, generics);

    GenericsType[] paramTypes = new GenericsType[param.length];
    for (int i = 0; i < param.length; i++) {
      ClassNode pType = param[i].getType();
      if (pType.getGenericsTypes() == null || !pType.isGenericsPlaceHolder()) {
        paramTypes[i] = new GenericsType(pType);
      } else {
        paramTypes[i] = pType.getGenericsTypes()[0];
      }
    }
    addSubTypes(ret, paramTypes, "(", ")");
    addSubTypes(ret, new GenericsType[] {new GenericsType(returnType)}, "", "");
    return ret.toString();
  }

  private static boolean usesGenericsInClassSignature(ClassNode node) {
    if (!node.isUsingGenerics()) return false;
    if (hasGenerics(node)) return true;
    ClassNode sclass = node.getUnresolvedSuperClass(false);
    if (sclass.isUsingGenerics()) return true;
    ClassNode[] interfaces = node.getInterfaces();
    if (interfaces != null) {
      for (int i = 0; i < interfaces.length; i++) {
        if (interfaces[i].isUsingGenerics()) return true;
      }
    }

    return false;
  }

  public static String getGenericsSignature(ClassNode node) {
    if (!usesGenericsInClassSignature(node)) return null;
    GenericsType[] genericsTypes = node.getGenericsTypes();
    StringBuffer ret = new StringBuffer(100);
    getGenericsTypeSpec(ret, genericsTypes);
    GenericsType extendsPart = new GenericsType(node.getUnresolvedSuperClass(false));
    writeGenericsBounds(ret, extendsPart, true);
    ClassNode[] interfaces = node.getInterfaces();
    for (int i = 0; i < interfaces.length; i++) {
      GenericsType interfacePart = new GenericsType(interfaces[i]);
      writeGenericsBounds(ret, interfacePart, false);
    }
    return ret.toString();
  }

  private static void getGenericsTypeSpec(StringBuffer ret, GenericsType[] genericsTypes) {
    if (genericsTypes == null) return;
    ret.append('<');
    for (int i = 0; i < genericsTypes.length; i++) {
      String name = genericsTypes[i].getName();
      ret.append(name);
      ret.append(':');
      writeGenericsBounds(ret, genericsTypes[i], true);
    }
    ret.append('>');
  }

  public static String getGenericsBounds(ClassNode type) {
    GenericsType[] genericsTypes = type.getGenericsTypes();
    if (genericsTypes == null) return null;
    StringBuffer ret = new StringBuffer(100);
    if (type.isGenericsPlaceHolder()) {
      addSubTypes(ret, type.getGenericsTypes(), "", "");
    } else {
      GenericsType gt = new GenericsType(type);
      writeGenericsBounds(ret, gt, false);
    }

    return ret.toString();
  }

  private static void writeGenericsBoundType(
      StringBuffer ret, ClassNode printType, boolean writeInterfaceMarker) {
    if (writeInterfaceMarker && printType.isInterface()) ret.append(":");
    if (printType.equals(ClassHelper.OBJECT_TYPE) && printType.getGenericsTypes() != null) {
      ret.append("T");
      ret.append(printType.getGenericsTypes()[0].getName());
      ret.append(";");
    } else {
      ret.append(getTypeDescription(printType, false));
      addSubTypes(ret, printType.getGenericsTypes(), "<", ">");
      if (!ClassHelper.isPrimitiveType(printType)) ret.append(";");
    }
  }

  private static void writeGenericsBounds(
      StringBuffer ret, GenericsType type, boolean writeInterfaceMarker) {
    if (type.getUpperBounds() != null) {
      ClassNode[] bounds = type.getUpperBounds();
      for (int i = 0; i < bounds.length; i++) {
        writeGenericsBoundType(ret, bounds[i], writeInterfaceMarker);
      }
    } else if (type.getLowerBound() != null) {
      writeGenericsBoundType(ret, type.getLowerBound(), writeInterfaceMarker);
    } else {
      writeGenericsBoundType(ret, type.getType(), writeInterfaceMarker);
    }
  }

  private static void addSubTypes(
      StringBuffer ret, GenericsType[] types, String start, String end) {
    if (types == null) return;
    ret.append(start);
    for (int i = 0; i < types.length; i++) {
      if (types[i].getType().isArray()) {
        ret.append("[");
        addSubTypes(
            ret,
            new GenericsType[] {new GenericsType(types[i].getType().getComponentType())},
            "",
            "");
      } else {
        if (types[i].isPlaceholder()) {
          ret.append('T');
          String name = types[i].getName();
          ret.append(name);
          ret.append(';');
        } else if (types[i].isWildcard()) {
          if (types[i].getUpperBounds() != null) {
            ret.append('+');
            writeGenericsBounds(ret, types[i], false);
          } else if (types[i].getLowerBound() != null) {
            ret.append('-');
            writeGenericsBounds(ret, types[i], false);
          } else {
            ret.append('*');
          }
        } else {
          writeGenericsBounds(ret, types[i], false);
        }
      }
    }
    ret.append(end);
  }

  public static void load(MethodVisitor mv, ClassNode type, int idx) {
    if (type == ClassHelper.double_TYPE) {
      mv.visitVarInsn(DLOAD, idx);
    } else if (type == ClassHelper.float_TYPE) {
      mv.visitVarInsn(FLOAD, idx);
    } else if (type == ClassHelper.long_TYPE) {
      mv.visitVarInsn(LLOAD, idx);
    } else if (type == ClassHelper.boolean_TYPE
        || type == ClassHelper.char_TYPE
        || type == ClassHelper.byte_TYPE
        || type == ClassHelper.int_TYPE
        || type == ClassHelper.short_TYPE) {
      mv.visitVarInsn(ILOAD, idx);
    } else {
      mv.visitVarInsn(ALOAD, idx);
    }
  }

  public static void doCast(MethodVisitor mv, ClassNode type) {
    if (type == ClassHelper.OBJECT_TYPE) return;
    if (ClassHelper.isPrimitiveType(type) && type != ClassHelper.VOID_TYPE) {
      unbox(mv, type);
    } else {
      mv.visitTypeInsn(
          CHECKCAST,
          type.isArray()
              ? BytecodeHelper.getTypeDescription(type)
              : BytecodeHelper.getClassInternalName(type.getName()));
    }
  }

  /**
   * Given a wrapped number type (Byte, Integer, Short, ...), generates bytecode to convert it to a
   * primitive number (int, long, double) using calls to wrapped.[targetType]Value()
   *
   * @param mv method visitor
   * @param sourceType the wrapped number type
   * @param targetType the primitive target type
   */
  public static void doCastToPrimitive(
      MethodVisitor mv, ClassNode sourceType, ClassNode targetType) {
    mv.visitMethodInsn(
        INVOKEVIRTUAL,
        BytecodeHelper.getClassInternalName(sourceType),
        targetType.getName() + "Value",
        "()" + BytecodeHelper.getTypeDescription(targetType));
  }

  /**
   * Given a primitive number type (byte, integer, short, ...), generates bytecode to convert it to
   * a wrapped number (Integer, Long, Double) using calls to [WrappedType].valueOf
   *
   * @param mv method visitor
   * @param sourceType the primitive number type
   * @param targetType the wrapped target type
   */
  public static void doCastToWrappedType(
      MethodVisitor mv, ClassNode sourceType, ClassNode targetType) {
    mv.visitMethodInsn(
        INVOKESTATIC,
        getClassInternalName(targetType),
        "valueOf",
        "(" + getTypeDescription(sourceType) + ")" + getTypeDescription(targetType));
  }

  public static void doCast(MethodVisitor mv, Class type) {
    if (type == Object.class) return;
    if (type.isPrimitive() && type != Void.TYPE) {
      unbox(mv, type);
    } else {
      mv.visitTypeInsn(
          CHECKCAST,
          type.isArray()
              ? BytecodeHelper.getTypeDescription(type)
              : BytecodeHelper.getClassInternalName(type.getName()));
    }
  }

  /** Generates the bytecode to unbox the current value on the stack */
  public static void unbox(MethodVisitor mv, Class type) {
    if (type.isPrimitive() && type != Void.TYPE) {
      String returnString = "(Ljava/lang/Object;)" + BytecodeHelper.getTypeDescription(type);
      mv.visitMethodInsn(INVOKESTATIC, DTT_CLASSNAME, type.getName() + "Unbox", returnString);
    }
  }

  public static void unbox(MethodVisitor mv, ClassNode type) {
    if (type.isPrimaryClassNode()) return;
    unbox(mv, type.getTypeClass());
  }

  /** box top level operand */
  public static boolean box(MethodVisitor mv, ClassNode type) {
    if (type.isPrimaryClassNode()) return false;
    return box(mv, type.getTypeClass());
  }

  /** Generates the bytecode to autobox the current value on the stack */
  public static boolean box(MethodVisitor mv, Class type) {
    if (ReflectionCache.getCachedClass(type).isPrimitive && type != void.class) {
      String returnString = "(" + BytecodeHelper.getTypeDescription(type) + ")Ljava/lang/Object;";
      mv.visitMethodInsn(INVOKESTATIC, DTT_CLASSNAME, "box", returnString);
      return true;
    }
    return false;
  }

  /**
   * Visits a class literal. If the type of the classnode is a primitive type, the generated
   * bytecode will be a GETSTATIC Integer.TYPE. If the classnode is not a primitive type, we will
   * generate a LDC instruction.
   */
  public static void visitClassLiteral(MethodVisitor mv, ClassNode classNode) {
    if (ClassHelper.isPrimitiveType(classNode)) {
      mv.visitFieldInsn(
          GETSTATIC,
          getClassInternalName(ClassHelper.getWrapper(classNode)),
          "TYPE",
          "Ljava/lang/Class;");
    } else {
      mv.visitLdcInsn(org.objectweb.asm.Type.getType(getTypeDescription(classNode)));
    }
  }

  /**
   * Tells if a class node is candidate for class literal bytecode optimization. If so, bytecode may
   * use LDC instructions instead of static constant Class fields to retrieve class literals.
   *
   * @param classNode the classnode for which we want to know if bytecode optimization is possible
   * @return true if the bytecode can be optimized
   */
  public static boolean isClassLiteralPossible(ClassNode classNode) {
    // the current implementation only checks for public modifier, because Groovy used to allow
    // handles on classes even if they are package protected and not in the same package.
    // There are situations where we could make more fine grained checks, but be careful of
    // potential breakage of existing code.
    return Modifier.isPublic(classNode.getModifiers());
  }

  /**
   * Returns true if the two classes share the same compilation unit.
   *
   * @param a class a
   * @param b class b
   * @return true if both classes share the same compilation unit
   */
  public static boolean isSameCompilationUnit(ClassNode a, ClassNode b) {
    CompileUnit cu1 = a.getCompileUnit();
    CompileUnit cu2 = b.getCompileUnit();
    return cu1 != null && cu2 != null && cu1 == cu2;
  }
}
  @Override
  public void makeGetPropertySite(
      Expression receiver,
      final String methodName,
      final boolean safe,
      final boolean implicitThis) {
    Object dynamic =
        receiver.getNodeMetaData(StaticCompilationMetadataKeys.RECEIVER_OF_DYNAMIC_PROPERTY);
    if (dynamic != null) {
      MethodNode target =
          safe ? INVOKERHELPER_GETPROPERTYSAFE_METHOD : INVOKERHELPER_GETPROPERTY_METHOD;
      MethodCallExpression mce =
          new MethodCallExpression(
              new ClassExpression(INVOKERHELPER_TYPE),
              target.getName(),
              new ArgumentListExpression(receiver, new ConstantExpression(methodName)));
      mce.setSafe(false);
      mce.setImplicitThis(false);
      mce.setMethodTarget(target);
      mce.visit(controller.getAcg());
      return;
    }
    TypeChooser typeChooser = controller.getTypeChooser();
    ClassNode classNode = controller.getClassNode();
    ClassNode receiverType =
        (ClassNode) receiver.getNodeMetaData(StaticCompilationMetadataKeys.PROPERTY_OWNER);
    if (receiverType == null) {
      receiverType = typeChooser.resolveType(receiver, classNode);
    }
    Object type = receiver.getNodeMetaData(StaticTypesMarker.INFERRED_TYPE);
    if (type == null && receiver instanceof VariableExpression) {
      Variable variable = ((VariableExpression) receiver).getAccessedVariable();
      if (variable instanceof Expression) {
        type = ((Expression) variable).getNodeMetaData(StaticTypesMarker.INFERRED_TYPE);
      }
    }
    if (type != null) {
      // in case a "flow type" is found, it is preferred to use it instead of
      // the declaration type
      receiverType = (ClassNode) type;
    }
    boolean isClassReceiver = false;
    if (isClassClassNodeWrappingConcreteType(receiverType)) {
      isClassReceiver = true;
      receiverType = receiverType.getGenericsTypes()[0].getType();
    }
    MethodVisitor mv = controller.getMethodVisitor();

    if (receiverType.isArray() && methodName.equals("length")) {
      receiver.visit(controller.getAcg());
      ClassNode arrayGetReturnType = typeChooser.resolveType(receiver, classNode);
      controller.getOperandStack().doGroovyCast(arrayGetReturnType);
      mv.visitInsn(ARRAYLENGTH);
      controller.getOperandStack().replace(int_TYPE);
      return;
    } else if ((receiverType.implementsInterface(COLLECTION_TYPE)
            || COLLECTION_TYPE.equals(receiverType))
        && ("size".equals(methodName) || "length".equals(methodName))) {
      MethodCallExpression expr =
          new MethodCallExpression(receiver, "size", ArgumentListExpression.EMPTY_ARGUMENTS);
      expr.setMethodTarget(COLLECTION_SIZE_METHOD);
      expr.setImplicitThis(implicitThis);
      expr.setSafe(safe);
      expr.visit(controller.getAcg());
      return;
    }
    if (makeGetPropertyWithGetter(receiver, receiverType, methodName, safe, implicitThis)) return;
    if (makeGetField(
        receiver,
        receiverType,
        methodName,
        implicitThis,
        samePackages(receiverType.getPackageName(), classNode.getPackageName()))) return;
    if (receiverType.isEnum()) {
      mv.visitFieldInsn(
          GETSTATIC,
          BytecodeHelper.getClassInternalName(receiverType),
          methodName,
          BytecodeHelper.getTypeDescription(receiverType));
      controller.getOperandStack().push(receiverType);
      return;
    }
    if (receiver instanceof ClassExpression) {
      if (makeGetField(
          receiver,
          receiver.getType(),
          methodName,
          implicitThis,
          samePackages(receiver.getType().getPackageName(), classNode.getPackageName()))) return;
      if (makeGetPropertyWithGetter(receiver, receiver.getType(), methodName, safe, implicitThis))
        return;
      if (makeGetPrivateFieldWithBridgeMethod(
          receiver, receiver.getType(), methodName, safe, implicitThis)) return;
    }
    if (isClassReceiver) {
      // we are probably looking for a property of the class
      if (makeGetPropertyWithGetter(receiver, CLASS_Type, methodName, safe, implicitThis)) return;
      if (makeGetField(receiver, CLASS_Type, methodName, false, true)) return;
    }
    if (makeGetPrivateFieldWithBridgeMethod(receiver, receiverType, methodName, safe, implicitThis))
      return;

    // GROOVY-5580, it is still possible that we're calling a superinterface property
    String getterName = "get" + MetaClassHelper.capitalize(methodName);
    if (receiverType.isInterface()) {
      Set<ClassNode> allInterfaces = receiverType.getAllInterfaces();
      MethodNode getterMethod = null;
      for (ClassNode anInterface : allInterfaces) {
        getterMethod = anInterface.getGetterMethod(getterName);
        if (getterMethod != null) break;
      }
      // GROOVY-5585
      if (getterMethod == null) {
        getterMethod = OBJECT_TYPE.getGetterMethod(getterName);
      }

      if (getterMethod != null) {
        MethodCallExpression call =
            new MethodCallExpression(receiver, getterName, ArgumentListExpression.EMPTY_ARGUMENTS);
        call.setMethodTarget(getterMethod);
        call.setImplicitThis(false);
        call.setSourcePosition(receiver);
        call.visit(controller.getAcg());
        return;
      }
    }

    // GROOVY-5568, we would be facing a DGM call, but instead of foo.getText(), have foo.text
    List<MethodNode> methods =
        findDGMMethodsByNameAndArguments(
            controller.getSourceUnit().getClassLoader(),
            receiverType,
            getterName,
            ClassNode.EMPTY_ARRAY);
    if (!methods.isEmpty()) {
      List<MethodNode> methodNodes = chooseBestMethod(receiverType, methods, ClassNode.EMPTY_ARRAY);
      if (methodNodes.size() == 1) {
        MethodNode getter = methodNodes.get(0);
        MethodCallExpression call =
            new MethodCallExpression(receiver, getterName, ArgumentListExpression.EMPTY_ARGUMENTS);
        call.setMethodTarget(getter);
        call.setImplicitThis(false);
        call.setSourcePosition(receiver);
        call.visit(controller.getAcg());
        return;
      }
    }

    boolean isStaticProperty =
        receiver instanceof ClassExpression
            && (receiverType.isDerivedFrom(receiver.getType())
                || receiverType.implementsInterface(receiver.getType()));

    if (!isStaticProperty) {
      if (receiverType.implementsInterface(MAP_TYPE) || MAP_TYPE.equals(receiverType)) {
        // for maps, replace map.foo with map.get('foo')
        writeMapDotProperty(receiver, methodName, mv, safe);
        return;
      }
      if (receiverType.implementsInterface(LIST_TYPE) || LIST_TYPE.equals(receiverType)) {
        writeListDotProperty(receiver, methodName, mv, safe);
        return;
      }
    }

    controller
        .getSourceUnit()
        .addError(
            new SyntaxException(
                "Access to "
                    + (receiver instanceof ClassExpression ? receiver.getType() : receiverType)
                        .toString(false)
                    + "#"
                    + methodName
                    + " is forbidden",
                receiver.getLineNumber(),
                receiver.getColumnNumber(),
                receiver.getLastLineNumber(),
                receiver.getLastColumnNumber()));
    controller.getMethodVisitor().visitInsn(ACONST_NULL);
    controller.getOperandStack().push(OBJECT_TYPE);
  }
  private void writeListDotProperty(
      final Expression receiver,
      final String methodName,
      final MethodVisitor mv,
      final boolean safe) {
    ClassNode componentType =
        (ClassNode) receiver.getNodeMetaData(StaticCompilationMetadataKeys.COMPONENT_TYPE);
    if (componentType == null) {
      componentType = OBJECT_TYPE;
    }
    // for lists, replace list.foo with:
    // def result = new ArrayList(list.size())
    // for (e in list) { result.add (e.foo) }
    // result
    CompileStack compileStack = controller.getCompileStack();

    Label exit = new Label();
    if (safe) {
      receiver.visit(controller.getAcg());
      Label doGet = new Label();
      mv.visitJumpInsn(IFNONNULL, doGet);
      controller.getOperandStack().remove(1);
      mv.visitInsn(ACONST_NULL);
      mv.visitJumpInsn(GOTO, exit);
      mv.visitLabel(doGet);
    }

    Variable tmpList = new VariableExpression("tmpList", make(ArrayList.class));
    int var = compileStack.defineTemporaryVariable(tmpList, false);
    Variable iterator = new VariableExpression("iterator", Iterator_TYPE);
    int it = compileStack.defineTemporaryVariable(iterator, false);
    Variable nextVar = new VariableExpression("next", componentType);
    final int next = compileStack.defineTemporaryVariable(nextVar, false);

    mv.visitTypeInsn(NEW, "java/util/ArrayList");
    mv.visitInsn(DUP);
    receiver.visit(controller.getAcg());
    mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "size", "()I", true);
    controller.getOperandStack().remove(1);
    mv.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "<init>", "(I)V", false);
    mv.visitVarInsn(ASTORE, var);
    Label l1 = new Label();
    mv.visitLabel(l1);
    receiver.visit(controller.getAcg());
    mv.visitMethodInsn(
        INVOKEINTERFACE, "java/util/List", "iterator", "()Ljava/util/Iterator;", true);
    controller.getOperandStack().remove(1);
    mv.visitVarInsn(ASTORE, it);
    Label l2 = new Label();
    mv.visitLabel(l2);
    mv.visitVarInsn(ALOAD, it);
    mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "hasNext", "()Z", true);
    Label l3 = new Label();
    mv.visitJumpInsn(IFEQ, l3);
    mv.visitVarInsn(ALOAD, it);
    mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Iterator", "next", "()Ljava/lang/Object;", true);
    mv.visitTypeInsn(CHECKCAST, BytecodeHelper.getClassInternalName(componentType));
    mv.visitVarInsn(ASTORE, next);
    Label l4 = new Label();
    mv.visitLabel(l4);
    mv.visitVarInsn(ALOAD, var);
    final ClassNode finalComponentType = componentType;
    PropertyExpression pexp =
        new PropertyExpression(
            new BytecodeExpression() {
              @Override
              public void visit(final MethodVisitor mv) {
                mv.visitVarInsn(ALOAD, next);
              }

              @Override
              public ClassNode getType() {
                return finalComponentType;
              }
            },
            methodName);
    pexp.visit(controller.getAcg());
    controller.getOperandStack().box();
    controller.getOperandStack().remove(1);
    mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "add", "(Ljava/lang/Object;)Z", true);
    mv.visitInsn(POP);
    Label l5 = new Label();
    mv.visitLabel(l5);
    mv.visitJumpInsn(GOTO, l2);
    mv.visitLabel(l3);
    mv.visitVarInsn(ALOAD, var);
    if (safe) {
      mv.visitLabel(exit);
    }
    controller.getOperandStack().push(make(ArrayList.class));
    controller.getCompileStack().removeVar(next);
    controller.getCompileStack().removeVar(it);
    controller.getCompileStack().removeVar(var);
  }
 protected MethodVisitor makeDelegateToClosureCall(
     final String name,
     final String desc,
     final String signature,
     final String[] exceptions,
     final int accessFlags) {
   MethodVisitor mv = super.visitMethod(accessFlags, name, desc, signature, exceptions);
   //        TraceMethodVisitor tmv = new TraceMethodVisitor(mv);
   //        mv = tmv;
   mv.visitCode();
   int stackSize = 0;
   // method body should be:
   //  this.$delegate$closure$methodName.call(new Object[] { method arguments })
   Type[] args = Type.getArgumentTypes(desc);
   int arrayStore = args.length + 1;
   BytecodeHelper.pushConstant(mv, args.length);
   mv.visitTypeInsn(ANEWARRAY, "java/lang/Object"); // stack size = 1
   stackSize = 1;
   int idx = 1;
   for (int i = 0; i < args.length; i++) {
     Type arg = args[i];
     mv.visitInsn(DUP); // stack size = 2
     BytecodeHelper.pushConstant(mv, i); // array index, stack size = 3
     stackSize = 3;
     // primitive types must be boxed
     if (isPrimitive(arg)) {
       mv.visitIntInsn(getLoadInsn(arg), idx);
       String wrappedType = getWrappedClassDescriptor(arg);
       mv.visitMethodInsn(
           INVOKESTATIC,
           wrappedType,
           "valueOf",
           "(" + arg.getDescriptor() + ")L" + wrappedType + ";");
     } else {
       mv.visitVarInsn(ALOAD, idx); // load argument i
     }
     idx += registerLen(arg);
     stackSize = Math.max(4, 3 + registerLen(arg));
     mv.visitInsn(AASTORE); // store value into array
   }
   mv.visitVarInsn(ASTORE, arrayStore); // store array
   int arrayIndex = arrayStore;
   mv.visitVarInsn(ALOAD, 0); // load this
   mv.visitFieldInsn(
       GETFIELD, proxyName, CLOSURES_MAP_FIELD, "Ljava/util/Map;"); // load closure map
   mv.visitLdcInsn(name); // load method name
   mv.visitMethodInsn(
       INVOKEINTERFACE, "java/util/Map", "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
   arrayStore++;
   mv.visitVarInsn(ASTORE, arrayStore);
   // if null, test if wildcard exists
   Label notNull = new Label();
   mv.visitIntInsn(ALOAD, arrayStore);
   mv.visitJumpInsn(IFNONNULL, notNull);
   mv.visitVarInsn(ALOAD, 0); // load this
   mv.visitFieldInsn(
       GETFIELD, proxyName, CLOSURES_MAP_FIELD, "Ljava/util/Map;"); // load closure map
   mv.visitLdcInsn("*"); // load wildcard
   mv.visitMethodInsn(
       INVOKEINTERFACE, "java/util/Map", "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
   mv.visitVarInsn(ASTORE, arrayStore);
   mv.visitLabel(notNull);
   mv.visitVarInsn(ALOAD, arrayStore);
   mv.visitMethodInsn(
       INVOKESTATIC,
       BytecodeHelper.getClassInternalName(this.getClass()),
       "ensureClosure",
       "(Ljava/lang/Object;)Lgroovy/lang/Closure;");
   mv.visitVarInsn(ALOAD, arrayIndex); // load argument array
   stackSize++;
   mv.visitMethodInsn(
       INVOKEVIRTUAL,
       "groovy/lang/Closure",
       "call",
       "([Ljava/lang/Object;)Ljava/lang/Object;"); // call closure
   unwrapResult(mv, desc);
   mv.visitMaxs(stackSize, arrayStore + 1);
   mv.visitEnd();
   //        System.out.println("tmv.getText() = " + tmv.getText());
   return EMPTY_VISITOR;
 }