/**
  * 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));
 }
 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()));
   }
 }
  private void doConvertAndCast(ClassNode targetType, boolean coerce) {
    int size = stack.size();
    throwExceptionForNoStackElement(size, targetType, coerce);

    ClassNode top = stack.get(size - 1);
    targetType = targetType.redirect();
    if (targetType == top) return;

    if (coerce) {
      controller.getInvocationWriter().coerce(top, targetType);
      return;
    }

    boolean primTarget = ClassHelper.isPrimitiveType(targetType);
    boolean primTop = ClassHelper.isPrimitiveType(top);

    if (primTop && primTarget) {
      // here we box and unbox to get the goal type
      if (convertPrimitive(top, targetType)) {
        replace(targetType);
        return;
      }
      box();
    } else if (primTop) {
      // top is primitive, target is not
      // so box and do groovy cast
      controller.getInvocationWriter().castToNonPrimitiveIfNecessary(top, targetType);
    } else if (primTarget) {
      // top is not primitive so unbox
      // leave that BH#doCast later
    } else {
      controller.getInvocationWriter().castToNonPrimitiveIfNecessary(top, targetType);
    }

    MethodVisitor mv = controller.getMethodVisitor();
    if (primTarget
        && !ClassHelper.boolean_TYPE.equals(targetType)
        && !primTop
        && ClassHelper.getWrapper(targetType).equals(top)) {
      BytecodeHelper.doCastToPrimitive(mv, top, targetType);
    } else {
      top = stack.get(size - 1);
      if (!WideningCategories.implementsInterfaceOrSubclassOf(top, targetType)) {
        BytecodeHelper.doCast(mv, targetType);
      }
    }
    replace(targetType);
  }
  public void loadOrStoreVariable(BytecodeVariable variable, boolean useReferenceDirectly) {
    CompileStack compileStack = controller.getCompileStack();

    if (compileStack.isLHS()) {
      storeVar(variable);
    } else {
      MethodVisitor mv = controller.getMethodVisitor();
      int idx = variable.getIndex();
      ClassNode type = variable.getType();

      if (variable.isHolder()) {
        mv.visitVarInsn(ALOAD, idx);
        if (!useReferenceDirectly) {
          mv.visitMethodInsn(
              INVOKEVIRTUAL, "groovy/lang/Reference", "get", "()Ljava/lang/Object;", false);
          BytecodeHelper.doCast(mv, type);
          push(type);
        } else {
          push(ClassHelper.REFERENCE_TYPE);
        }
      } else {
        load(type, idx);
      }
    }
  }
 /** 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;
 }
 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();
  }
 public ClassNode box() {
   MethodVisitor mv = controller.getMethodVisitor();
   int size = stack.size();
   ClassNode type = stack.get(size - 1);
   if (ClassHelper.isPrimitiveType(type) && ClassHelper.VOID_TYPE != type) {
     ClassNode wrapper = ClassHelper.getWrapper(type);
     BytecodeHelper.doCastToWrappedType(mv, type, wrapper);
     type = wrapper;
   } // else nothing to box
   stack.set(size - 1, type);
   return type;
 }
 /** 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);
   }
 }
/**
 * 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);
  }
Beispiel #14
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;
  }
}
 public void load(ClassNode type, int idx) {
   MethodVisitor mv = controller.getMethodVisitor();
   BytecodeHelper.load(mv, type, idx);
   push(type);
 }