/**
   * Generates code to wrap Java argument into Object. Non-primitive Java types are left unconverted
   * pending conversion in the helper method. Leaves the wrapper object on the top of the stack.
   */
  private static int generateWrapArg(ClassFileWriter cfw, int paramOffset, Class<?> argType) {
    int size = 1;
    if (!argType.isPrimitive()) {
      cfw.add(ByteCode.ALOAD, paramOffset);

    } else if (argType == Boolean.TYPE) {
      // wrap boolean values with java.lang.Boolean.
      cfw.add(ByteCode.NEW, "java/lang/Boolean");
      cfw.add(ByteCode.DUP);
      cfw.add(ByteCode.ILOAD, paramOffset);
      cfw.addInvoke(ByteCode.INVOKESPECIAL, "java/lang/Boolean", "<init>", "(Z)V");

    } else if (argType == Character.TYPE) {
      // Create a string of length 1 using the character parameter.
      cfw.add(ByteCode.ILOAD, paramOffset);
      cfw.addInvoke(ByteCode.INVOKESTATIC, "java/lang/String", "valueOf", "(C)Ljava/lang/String;");

    } else {
      // convert all numeric values to java.lang.Double.
      cfw.add(ByteCode.NEW, "java/lang/Double");
      cfw.add(ByteCode.DUP);
      String typeName = argType.getName();
      switch (typeName.charAt(0)) {
        case 'b':
        case 's':
        case 'i':
          // load an int value, convert to double.
          cfw.add(ByteCode.ILOAD, paramOffset);
          cfw.add(ByteCode.I2D);
          break;
        case 'l':
          // load a long, convert to double.
          cfw.add(ByteCode.LLOAD, paramOffset);
          cfw.add(ByteCode.L2D);
          size = 2;
          break;
        case 'f':
          // load a float, convert to double.
          cfw.add(ByteCode.FLOAD, paramOffset);
          cfw.add(ByteCode.F2D);
          break;
        case 'd':
          cfw.add(ByteCode.DLOAD, paramOffset);
          size = 2;
          break;
      }
      cfw.addInvoke(ByteCode.INVOKESPECIAL, "java/lang/Double", "<init>", "(D)V");
    }
    return size;
  }
  private static void generateSerialCtor(
      ClassFileWriter cfw, String adapterName, String superName) {
    cfw.startMethod(
        "<init>",
        "(Laurora/javascript/ContextFactory;"
            + "Laurora/javascript/Scriptable;"
            + "Laurora/javascript/Scriptable;"
            + ")V",
        ClassFileWriter.ACC_PUBLIC);

    // Invoke base class constructor
    cfw.add(ByteCode.ALOAD_0); // this
    cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, "<init>", "()V");

    // Save parameter in instance variable "factory"
    cfw.add(ByteCode.ALOAD_0); // this
    cfw.add(ByteCode.ALOAD_1); // first arg: ContextFactory instance
    cfw.add(ByteCode.PUTFIELD, adapterName, "factory", "Laurora/javascript/ContextFactory;");

    // Save parameter in instance variable "delegee"
    cfw.add(ByteCode.ALOAD_0); // this
    cfw.add(ByteCode.ALOAD_2); // second arg: Scriptable delegee
    cfw.add(ByteCode.PUTFIELD, adapterName, "delegee", "Laurora/javascript/Scriptable;");
    // save self
    cfw.add(ByteCode.ALOAD_0); // this
    cfw.add(ByteCode.ALOAD_3); // third arg: Scriptable self
    cfw.add(ByteCode.PUTFIELD, adapterName, "self", "Laurora/javascript/Scriptable;");

    cfw.add(ByteCode.RETURN);
    cfw.stopMethod((short) 4); // 4: this + factory + delegee + self
  }
  /**
   * Generates a method called "super$methodName()" which can be called from JavaScript that is
   * equivalent to calling "super.methodName()" from Java. Eventually, this may be supported
   * directly in JavaScript.
   */
  private static void generateSuper(
      ClassFileWriter cfw,
      String genName,
      String superName,
      String methodName,
      String methodSignature,
      Class<?>[] parms,
      Class<?> returnType) {
    cfw.startMethod("super$" + methodName, methodSignature, ClassFileWriter.ACC_PUBLIC);

    // push "this"
    cfw.add(ByteCode.ALOAD, 0);

    // push the rest of the parameters.
    int paramOffset = 1;
    for (Class<?> parm : parms) {
      paramOffset += generatePushParam(cfw, paramOffset, parm);
    }

    // call the superclass implementation of the method.
    cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, methodName, methodSignature);

    // now, handle the return type appropriately.
    Class<?> retType = returnType;
    if (!retType.equals(Void.TYPE)) {
      generatePopResult(cfw, retType);
    } else {
      cfw.add(ByteCode.RETURN);
    }
    cfw.stopMethod((short) (paramOffset + 1));
  }
  private static void generateEmptyCtor(
      ClassFileWriter cfw, String adapterName, String superName, String scriptClassName) {
    cfw.startMethod("<init>", "()V", ClassFileWriter.ACC_PUBLIC);

    // Invoke base class constructor
    cfw.add(ByteCode.ALOAD_0); // this
    cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, "<init>", "()V");

    // Set factory to null to use current global when necessary
    cfw.add(ByteCode.ALOAD_0);
    cfw.add(ByteCode.ACONST_NULL);
    cfw.add(ByteCode.PUTFIELD, adapterName, "factory", "Laurora/javascript/ContextFactory;");

    // Load script class
    cfw.add(ByteCode.NEW, scriptClassName);
    cfw.add(ByteCode.DUP);
    cfw.addInvoke(ByteCode.INVOKESPECIAL, scriptClassName, "<init>", "()V");

    // Run script and save resulting scope
    cfw.addInvoke(
        ByteCode.INVOKESTATIC,
        "aurora/javascript/JavaAdapter",
        "runScript",
        "(Laurora/javascript/Script;" + ")Laurora/javascript/Scriptable;");
    cfw.add(ByteCode.ASTORE_1);

    // Save the Scriptable in instance variable "delegee"
    cfw.add(ByteCode.ALOAD_0); // this
    cfw.add(ByteCode.ALOAD_1); // the Scriptable
    cfw.add(ByteCode.PUTFIELD, adapterName, "delegee", "Laurora/javascript/Scriptable;");

    cfw.add(ByteCode.ALOAD_0); // this for the following PUTFIELD for self
    // create a wrapper object to be used as "this" in method calls
    cfw.add(ByteCode.ALOAD_1); // the Scriptable
    cfw.add(ByteCode.ALOAD_0); // this
    cfw.addInvoke(
        ByteCode.INVOKESTATIC,
        "aurora/javascript/JavaAdapter",
        "createAdapterWrapper",
        "(Laurora/javascript/Scriptable;"
            + "Ljava/lang/Object;"
            + ")Laurora/javascript/Scriptable;");
    cfw.add(ByteCode.PUTFIELD, adapterName, "self", "Laurora/javascript/Scriptable;");

    cfw.add(ByteCode.RETURN);
    cfw.stopMethod((short) 2); // this + delegee
  }
  private static void generateCtor(ClassFileWriter cfw, String adapterName, String superName) {
    cfw.startMethod(
        "<init>",
        "(Lorg/mozilla/javascript/ContextFactory;" + "Lorg/mozilla/javascript/Scriptable;)V",
        ClassFileWriter.ACC_PUBLIC);

    // Invoke base class constructor
    cfw.add(ByteCode.ALOAD_0); // this
    cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, "<init>", "()V");

    // Save parameter in instance variable "factory"
    cfw.add(ByteCode.ALOAD_0); // this
    cfw.add(ByteCode.ALOAD_1); // first arg: ContextFactory instance
    cfw.add(ByteCode.PUTFIELD, adapterName, "factory", "Lorg/mozilla/javascript/ContextFactory;");

    // Save parameter in instance variable "delegee"
    cfw.add(ByteCode.ALOAD_0); // this
    cfw.add(ByteCode.ALOAD_2); // second arg: Scriptable delegee
    cfw.add(ByteCode.PUTFIELD, adapterName, "delegee", "Lorg/mozilla/javascript/Scriptable;");

    cfw.add(ByteCode.ALOAD_0); // this for the following PUTFIELD for self
    // create a wrapper object to be used as "this" in method calls
    cfw.add(ByteCode.ALOAD_2); // the Scriptable delegee
    cfw.add(ByteCode.ALOAD_0); // this
    cfw.addInvoke(
        ByteCode.INVOKESTATIC,
        "org/mozilla/javascript/JavaAdapter",
        "createAdapterWrapper",
        "(Lorg/mozilla/javascript/Scriptable;"
            + "Ljava/lang/Object;"
            + ")Lorg/mozilla/javascript/Scriptable;");
    cfw.add(ByteCode.PUTFIELD, adapterName, "self", "Lorg/mozilla/javascript/Scriptable;");

    cfw.add(ByteCode.RETURN);
    cfw.stopMethod((short) 3); // 3: this + factory + delegee
  }
  private static void generateMethod(
      ClassFileWriter cfw,
      String genName,
      String methodName,
      Class<?>[] parms,
      Class<?> returnType,
      boolean convertResult) {
    StringBuilder sb = new StringBuilder();
    int paramsEnd = appendMethodSignature(parms, returnType, sb);
    String methodSignature = sb.toString();
    cfw.startMethod(methodName, methodSignature, ClassFileWriter.ACC_PUBLIC);

    // Prepare stack to call method

    // push factory
    cfw.add(ByteCode.ALOAD_0);
    cfw.add(ByteCode.GETFIELD, genName, "factory", "Laurora/javascript/ContextFactory;");

    // push self
    cfw.add(ByteCode.ALOAD_0);
    cfw.add(ByteCode.GETFIELD, genName, "self", "Laurora/javascript/Scriptable;");

    // push function
    cfw.add(ByteCode.ALOAD_0);
    cfw.add(ByteCode.GETFIELD, genName, "delegee", "Laurora/javascript/Scriptable;");
    cfw.addPush(methodName);
    cfw.addInvoke(
        ByteCode.INVOKESTATIC,
        "aurora/javascript/JavaAdapter",
        "getFunction",
        "(Laurora/javascript/Scriptable;" + "Ljava/lang/String;" + ")Laurora/javascript/Function;");

    // push arguments
    generatePushWrappedArgs(cfw, parms, parms.length);

    // push bits to indicate which parameters should be wrapped
    if (parms.length > 64) {
      // If it will be an issue, then passing a static boolean array
      // can be an option, but for now using simple bitmask
      throw Context.reportRuntimeError0(
          "JavaAdapter can not subclass methods with more then" + " 64 arguments.");
    }
    long convertionMask = 0;
    for (int i = 0; i != parms.length; ++i) {
      if (!parms[i].isPrimitive()) {
        convertionMask |= (1 << i);
      }
    }
    cfw.addPush(convertionMask);

    // go through utility method, which creates a Context to run the
    // method in.
    cfw.addInvoke(
        ByteCode.INVOKESTATIC,
        "aurora/javascript/JavaAdapter",
        "callMethod",
        "(Laurora/javascript/ContextFactory;"
            + "Laurora/javascript/Scriptable;"
            + "Laurora/javascript/Function;"
            + "[Ljava/lang/Object;"
            + "J"
            + ")Ljava/lang/Object;");

    generateReturnResult(cfw, returnType, convertResult);

    cfw.stopMethod((short) paramsEnd);
  }
  /**
   * Generates code to convert a wrapped value type to a primitive type. Handles unwrapping
   * java.lang.Boolean, and java.lang.Number types. Generates the appropriate RETURN bytecode.
   */
  static void generateReturnResult(
      ClassFileWriter cfw, Class<?> retType, boolean callConvertResult) {
    // wrap boolean values with java.lang.Boolean, convert all other
    // primitive values to java.lang.Double.
    if (retType == Void.TYPE) {
      cfw.add(ByteCode.POP);
      cfw.add(ByteCode.RETURN);

    } else if (retType == Boolean.TYPE) {
      cfw.addInvoke(
          ByteCode.INVOKESTATIC, "aurora/javascript/Context", "toBoolean", "(Ljava/lang/Object;)Z");
      cfw.add(ByteCode.IRETURN);

    } else if (retType == Character.TYPE) {
      // characters are represented as strings in JavaScript.
      // return the first character.
      // first convert the value to a string if possible.
      cfw.addInvoke(
          ByteCode.INVOKESTATIC,
          "aurora/javascript/Context",
          "toString",
          "(Ljava/lang/Object;)Ljava/lang/String;");
      cfw.add(ByteCode.ICONST_0);
      cfw.addInvoke(ByteCode.INVOKEVIRTUAL, "java/lang/String", "charAt", "(I)C");
      cfw.add(ByteCode.IRETURN);

    } else if (retType.isPrimitive()) {
      cfw.addInvoke(
          ByteCode.INVOKESTATIC, "aurora/javascript/Context", "toNumber", "(Ljava/lang/Object;)D");
      String typeName = retType.getName();
      switch (typeName.charAt(0)) {
        case 'b':
        case 's':
        case 'i':
          cfw.add(ByteCode.D2I);
          cfw.add(ByteCode.IRETURN);
          break;
        case 'l':
          cfw.add(ByteCode.D2L);
          cfw.add(ByteCode.LRETURN);
          break;
        case 'f':
          cfw.add(ByteCode.D2F);
          cfw.add(ByteCode.FRETURN);
          break;
        case 'd':
          cfw.add(ByteCode.DRETURN);
          break;
        default:
          throw new RuntimeException("Unexpected return type " + retType.toString());
      }

    } else {
      String retTypeStr = retType.getName();
      if (callConvertResult) {
        cfw.addLoadConstant(retTypeStr);
        cfw.addInvoke(
            ByteCode.INVOKESTATIC,
            "java/lang/Class",
            "forName",
            "(Ljava/lang/String;)Ljava/lang/Class;");

        cfw.addInvoke(
            ByteCode.INVOKESTATIC,
            "aurora/javascript/JavaAdapter",
            "convertResult",
            "(Ljava/lang/Object;" + "Ljava/lang/Class;" + ")Ljava/lang/Object;");
      }
      // Now cast to return type
      cfw.add(ByteCode.CHECKCAST, retTypeStr);
      cfw.add(ByteCode.ARETURN);
    }
  }
  private static void generateCtor(
      ClassFileWriter cfw, String adapterName, String superName, Constructor<?> superCtor) {
    short locals = 3; // this + factory + delegee
    Class<?>[] parameters = superCtor.getParameterTypes();

    // Note that we swapped arguments in app-facing constructors to avoid
    // conflicting signatures with serial constructor defined below.
    if (parameters.length == 0) {
      cfw.startMethod(
          "<init>",
          "(Laurora/javascript/Scriptable;" + "Laurora/javascript/ContextFactory;)V",
          ClassFileWriter.ACC_PUBLIC);

      // Invoke base class constructor
      cfw.add(ByteCode.ALOAD_0); // this
      cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, "<init>", "()V");
    } else {
      StringBuilder sig =
          new StringBuilder(
              "(Laurora/javascript/Scriptable;" + "Laurora/javascript/ContextFactory;");
      int marker = sig.length(); // lets us reuse buffer for super signature
      for (Class<?> c : parameters) {
        appendTypeString(sig, c);
      }
      sig.append(")V");
      cfw.startMethod("<init>", sig.toString(), ClassFileWriter.ACC_PUBLIC);

      // Invoke base class constructor
      cfw.add(ByteCode.ALOAD_0); // this
      short paramOffset = 3;
      for (Class<?> parameter : parameters) {
        paramOffset += generatePushParam(cfw, paramOffset, parameter);
      }
      locals = paramOffset;
      sig.delete(1, marker);
      cfw.addInvoke(ByteCode.INVOKESPECIAL, superName, "<init>", sig.toString());
    }

    // Save parameter in instance variable "delegee"
    cfw.add(ByteCode.ALOAD_0); // this
    cfw.add(ByteCode.ALOAD_1); // first arg: Scriptable delegee
    cfw.add(ByteCode.PUTFIELD, adapterName, "delegee", "Laurora/javascript/Scriptable;");

    // Save parameter in instance variable "factory"
    cfw.add(ByteCode.ALOAD_0); // this
    cfw.add(ByteCode.ALOAD_2); // second arg: ContextFactory instance
    cfw.add(ByteCode.PUTFIELD, adapterName, "factory", "Laurora/javascript/ContextFactory;");

    cfw.add(ByteCode.ALOAD_0); // this for the following PUTFIELD for self
    // create a wrapper object to be used as "this" in method calls
    cfw.add(ByteCode.ALOAD_1); // the Scriptable delegee
    cfw.add(ByteCode.ALOAD_0); // this
    cfw.addInvoke(
        ByteCode.INVOKESTATIC,
        "aurora/javascript/JavaAdapter",
        "createAdapterWrapper",
        "(Laurora/javascript/Scriptable;"
            + "Ljava/lang/Object;"
            + ")Laurora/javascript/Scriptable;");
    cfw.add(ByteCode.PUTFIELD, adapterName, "self", "Laurora/javascript/Scriptable;");

    cfw.add(ByteCode.RETURN);
    cfw.stopMethod(locals);
  }