package xtc.lang.blink.agent;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import xtc.lang.blink.agent.JNIFunction.ExtraArgumentType;
import xtc.lang.blink.agent.JNIFunction.JNIAnnotatedType;
import xtc.lang.blink.agent.JNIType.CStringType;
import xtc.lang.blink.agent.JNIType.JFieldIDType;
import xtc.lang.blink.agent.JNIType.JMethodIDType;
import xtc.lang.blink.agent.JNIType.PointerType;
import xtc.lang.blink.agent.JNIType.PrimitiveType;
import xtc.lang.blink.agent.JNIType.ReferenceType;

/**
 * A JNI Function proxy generator.
 *
 * @author Byeong Lee
 */
public final class GenerateJNIFunctionProxy implements JNIConstants {

  private static void usage(String reason) {
    if (reason != null) {
      System.out.println(reason);
    }
    System.out.println(
        "usage: " +  GenerateJNIFunctionProxy.class.getName() + " [options]\n"
        + "options:"
        + " -help               show help message.\n"
        + " -o <output file>    specify the C source file\n"
    );
    System.exit(-1);
  }

  /**
   * @param args The command line arguments.
   */
  public static void main(String[] args) {
    String outputSourceFileName = null;
    for(int i = 0;i < args.length;i++) {
      if (args[i].equals("-help")) {
    	  usage(null);
      } else if (args[i].equals("-o") ) {
        if ((i + 1) < args.length) {
          outputSourceFileName = args[++i];
        } else {
          usage("Please, specify output file after -o");
        }
      }    
    }

    try {
      //generate source file.
      PrintWriter os = new PrintWriter(
          (outputSourceFileName == null) ? System.out : new FileOutputStream(
              outputSourceFileName));
      GenerateJNIFunctionProxy p = new GenerateJNIFunctionProxy(os);
      p.gen();
      os.flush();
      os.close();
    } catch (IOException e) {
      System.err.println("Can not recover from the input or output fault");
    }
  }

  /** Output stream for the generated JNI function proxy source code. */
  private final PrintWriter w;
  private final ArrayList<ProxyGenerator> pgs;
  
  private GenerateJNIFunctionProxy(PrintWriter w) {
    this.w = w;
    this.pgs = new ArrayList<ProxyGenerator>();
    for(JNIFunction proxyFunction: JNIFunction.jniFunctionList) {
      String fname = proxyFunction.name;
      ProxyGenerator pg;
      
      if (proxyFunction.extraArgType == ExtraArgumentType.DOT_DOT_DOT) {
        String targetName = fname + "V";
        final JNIFunction targetFunction = JNIFunction.jniFunctionMap.get(targetName);
        assert targetFunction != null;
        pg = new VariableArgProxyGenerator(proxyFunction, targetFunction);
      } else {
        pg = new FixedArgProxyGenerator(proxyFunction);
      }
      pgs.add(pg);
    }
  }

  private void gen() throws IOException{
    genHeaders();
    genStatDefinitions();
    for (final ProxyGenerator pg: pgs) {
      pg.genFuncDef(w);
    }
    genProxyInstall();
    genStatReport();
  }

  private void genHeaders() throws IOException{
    String[] headers = {
      "<string.h>", "<assert.h>", "<jni.h>", "<jvmti.h>", 
      "\"agent_main.h\"",
      "\"state.h\"", "\"common.h\"", "\"agent.h\"",
      "\"options.h\"", "\"java_method.h\"", "\"jnicheck.h\"",
      "\"classfile_constants.h\"",
    };
    w.printf("/* This source file is generated by %s.java\n", GenerateJNIFunctionProxy.class.getSimpleName());
    w.printf("Please, do not edit manually.*/\n");
    for(final String h: headers) {
      w.printf("#include %s\n", h);
    }
    w.printf("\n");
  }

  private void genStatDefinitions() throws IOException {
    w.printf("static jniNativeInterface* proxy_jni_funcs = NULL;\n");

    w.printf("struct bda_c2j_stat_count_jint {\n");
    for(final ProxyGenerator p : pgs) {
      w.printf("  %s %s;\n", "jint", p.getJNIFuncName());
    }
    w.printf("};\n");

    w.printf("struct bda_c2j_stat_count_jint bda_c2j_count;\n");
    w.printf("struct bda_c2j_stat_count_jint bda_c2j_count_user;\n");
  }

  private void genProxyInstall() throws IOException {
    w.printf("void bda_c2j_proxy_install(jvmtiEnv *jvmti)\n");
    w.printf("{\n");
    w.printf("  jvmtiError err;\n");
    w.printf("  err = (*jvmti)->GetJNIFunctionTable(jvmti, &proxy_jni_funcs);\n");
    w.printf("  assert(err == JVMTI_ERROR_NONE);\n");
    for(final ProxyGenerator p : pgs) {
      w.printf("  proxy_jni_funcs->%s = %s;\n", p.getJNIFuncName(), p.getProxyFuncName());
    }
    w.printf("  err = (*jvmti)->SetJNIFunctionTable(jvmti, proxy_jni_funcs);\n");
    w.printf("  assert(err == JVMTI_ERROR_NONE);\n");
    w.printf("  memset(&bda_c2j_count, 0, sizeof(bda_c2j_count));\n");
    w.printf("  memset(&bda_c2j_count_user, 0, sizeof(bda_c2j_count_user));\n");
    w.printf("}\n");
  }

  private void genStatReport() throws IOException {
    w.print("void bda_c2j_proxy_dump_stat()\n");
    w.print("{\n");
    w.print("  int sum = 0;\n");
    w.print("  int sum_user = 0;\n"); 
    w.print("  int count, count_user;\n");
    for(final ProxyGenerator p : pgs) {
      w.printf("\n");
      w.printf("  count = bda_c2j_count.%s;\n", p.getJNIFuncName());
      w.printf("  count_user = bda_c2j_count_user.%s;\n", p.getJNIFuncName());
      w.printf("  printf(\"%%7u %%7u %%s\\n\", count, count_user, \"%s\");\n", p.getJNIFuncName());
      w.printf("  sum += count;\n");
      w.printf("  sum_user += count_user;\n");
    }
    w.printf("  printf(\"%%7u %%7u All JNI functions\\n\", sum, sum_user);\n");
    w.print("}\n");
  }

  private static class FormalArgument {
    final JNIAnnotatedType type;
    final String varName;
    public FormalArgument(JNIAnnotatedType type, String varName) {
      this.type = type;
      this.varName = varName;
    }
  }

  private static abstract class ProxyGenerator {

    protected final JNIFunction wrapperJNIFunction;

    protected final FormalArgument[] fargs;

    protected final JNIFunction targetJNIFunction;

    ProxyGenerator(JNIFunction proxyFunction, JNIFunction targetFunction) {
      this.wrapperJNIFunction = proxyFunction;
      this.targetJNIFunction = targetFunction;

      this.fargs = new FormalArgument[proxyFunction.argumenTypes.length];
      JNIAnnotatedType[] arguments = proxyFunction.argumenTypes;
      assert arguments.length > 0 && arguments[0].jniType == JNI_ENV;
      fargs[0] = new FormalArgument(arguments[0], "env");
      for(int i = 1;i < arguments.length;i++) {
        fargs[i] = new FormalArgument(arguments[i], "p" + i);
      }
    }

    String getJNIEnvName() {
      assert fargs[0].type.jniType == JNI_ENV;
      return fargs[0].varName;
    }

    String getJNIFuncName() {
      return this.wrapperJNIFunction.name;
    }

    String getProxyFuncName() {
      return "bda_c2j_proxy_" + getJNIFuncName();
    }

    String getStatCountName() {
      return "bda_c2j_count." + getJNIFuncName();
    }
    
    String getStatCountUserName() {
      return "bda_c2j_count_user." + getJNIFuncName();
    }

    FormalArgument findMethodIDVariable() {
      for(FormalArgument a: fargs) {
        if (a.type.jniType == JNIConstants.JMETHODID) {
          return a;
        }
      }
      return null;
    }

    private void ensureFormalTypes(JNIType ... types) {
      for(int i=0; i <types.length;i++) {
        assert fargs[i].type.jniType == types[i];
      }
    }

    static final Pattern pGetField =  Pattern.compile("(Get|Set)(Static|)(Object|Boolean|Byte|Char|Short|Int|Long|Float|Double)Field");

    private void genJNICheckFieldID(PrintWriter w, FormalArgument f, int i) {
      final String jniFuncName = getJNIFuncName();
      final Matcher m = pGetField.matcher(wrapperJNIFunction.name);

      if (wrapperJNIFunction == JNIFunction.ToReflectedField) {
        w.printf("    && bda_check_jfieldid_to_reflected_field(%s, %s, %s, %s, \"%s\")\n", 
                 "s", fargs[1].varName, fargs[2].varName, fargs[3].varName, 
                 jniFuncName);
      } else if (m.matches()) {
        boolean getter = m.group(1).equals("Get");
        boolean isStatic = m.group(2).equals("Static");
        String fieldType = m.group(3);
        if (!isStatic) {
          if (getter) {
            w.printf("    && bda_check_jfieldid_get_instance(s, %s, %s, '%s', \"%s\")\n", 
                     fargs[1].varName, fargs[2].varName, getFieldDescFromType(fieldType), jniFuncName);
          } else {
            w.printf("    && bda_check_jfieldid_set_instance(s, %s, %s, v, '%s', \"%s\")\n", 
                     fargs[1].varName, fargs[2].varName, getFieldDescFromType(fieldType), jniFuncName);
          }
        } else {
          if (getter) {
            w.printf("    && bda_check_jfieldid_get_static(s, %s, %s, '%s', \"%s\")\n", 
                     fargs[1].varName, fargs[2].varName, getFieldDescFromType(fieldType), jniFuncName);
          } else {
            w.printf("    && bda_check_jfieldid_set_static(s, %s, %s, v, '%s', \"%s\")\n", 
                     fargs[1].varName, fargs[2].varName, getFieldDescFromType(fieldType), jniFuncName);
          }
        }

      } else {
        assert wrapperJNIFunction.name.equals("ToReflectedField");
      }
    }
    
    private static final Pattern jniNewObjectXPattern = Pattern.compile("NewObject(|V|A)");
    private static final Pattern jniCallXXXMethodXPattern = Pattern.compile("Call(|Nonvirtual|Static)(Object|Boolean|Byte|Char|Short|Int|Long|Float|Double|Void)Method(|V|A)");
      
    private void genJNICheckMethodID(PrintWriter w, FormalArgument f, int i) {
      String jniFuncName = getJNIFuncName();
      Matcher m;
      if (wrapperJNIFunction == JNIFunction.ToReflectedMethod) {
        w.printf("    && bda_check_jmethodid_to_reflected(%s, %s, %s, %s, \"%s\")\n", 
            "s", fargs[1].varName, fargs[2].varName, fargs[3].varName, 
            jniFuncName);
      } else if ((m = jniNewObjectXPattern.matcher(jniFuncName)).matches()) {
        String argPass = m.group(1);
        if (argPass.equals("")) {
          w.printf("    && bda_check_jmethodid_new_object(%s, %s, %s, awrap, \"%s\")\n", 
              "s", fargs[1].varName, fargs[2].varName, jniFuncName);
        } else if (argPass.equals("V")) {
          w.printf("    && bda_check_jmethodid_new_object(%s, %s, %s, %s, \"%s\")\n", 
              "s", fargs[1].varName, fargs[2].varName, "awrap", 
              jniFuncName);
        } else if (argPass.equals("A")) {
          w.printf("    && bda_check_jmethodid_new_object(%s, %s, %s, %s, \"%s\")\n", 
              "s", fargs[1].varName, fargs[2].varName, "awrap", 
              jniFuncName);
        } else {
          assert false ;
        }
      } else if ((m = jniCallXXXMethodXPattern.matcher(jniFuncName)).matches()){
        String methodType = m.group(1);
        String returnType = m.group(2); 
        String argPass = m.group(3);

        if (argPass.equals("")) {
          if (methodType.equals("")) {
            w.printf("    && bda_check_jmethodid_instance(%s, %s, %s, %s, \"%s\", '%s')\n",
                     "s", fargs[1].varName, fargs[2].varName, "awrap",
                     jniFuncName, getFieldDescFromType(returnType));
          } else if (methodType.equals("Nonvirtual")) {
            w.printf("    && bda_check_jmethodid_nonvirtual(%s, %s, %s, %s, %s, \"%s\", '%s')\n",
                     "s", fargs[1].varName, fargs[2].varName, fargs[3].varName, "awrap",
                     jniFuncName, getFieldDescFromType(returnType));
          } else if (methodType.equals("Static")) {
            w.printf("    && bda_check_jmethodid_static(%s, %s, %s, %s, \"%s\", '%s')\n",
                     "s", fargs[1].varName, fargs[2].varName, "awrap",
                     jniFuncName, getFieldDescFromType(returnType));
          } else {
            assert false;
          }
        } else if (argPass.equals("V")) {
          if (methodType.equals("")) {
            w.printf("    && bda_check_jmethodid_instance(%s, %s, %s, %s, \"%s\", '%s')\n",
                     "s", fargs[1].varName, fargs[2].varName, "awrap",
                     jniFuncName, getFieldDescFromType(returnType));
          } else if (methodType.equals("Nonvirtual")) {
            w.printf("    &&  bda_check_jmethodid_nonvirtual(%s, %s, %s, %s, %s, \"%s\", '%s')\n",
                     "s", fargs[1].varName, fargs[2].varName, fargs[3].varName, "awrap",
                     jniFuncName, getFieldDescFromType(returnType));
          } else if (methodType.equals("Static")) {
            w.printf("    &&  bda_check_jmethodid_static(%s, %s, %s, %s, \"%s\", '%s')\n",
                     "s", fargs[1].varName, fargs[2].varName, "awrap",
                     jniFuncName, getFieldDescFromType(returnType));
          } else {
            assert false;
          }
        } else if (argPass.equals("A")) {
          if (methodType.equals("")) {
            w.printf("    && bda_check_jmethodid_instance(%s, %s, %s, %s, \"%s\", '%s')\n",
                     "s", fargs[1].varName, fargs[2].varName, "awrap",
                     jniFuncName, getFieldDescFromType(returnType));
          } else if (methodType.equals("Nonvirtual")) {
            w.printf("    && bda_check_jmethodid_nonvirtual(%s, %s, %s, %s, %s, \"%s\", '%s')\n",
                     "s", fargs[1].varName, fargs[2].varName, fargs[3].varName, "awrap",
                     jniFuncName, getFieldDescFromType(returnType));
          } else if (methodType.equals("Static")) {
            w.printf("    && bda_check_jmethodid_static(%s, %s, %s, %s, \"%s\", '%s')\n",
                     "s", fargs[1].varName, fargs[2].varName, "awrap",
                     jniFuncName, getFieldDescFromType(returnType));
          } else {
            assert false;
          }
        } else {
          assert false;
        }
      } else {
        assert false : "Not reachable with " + jniFuncName;
      }
    }
    
    static final String getMethodIDCheckerPrefix(String type) {
      if (type.equals("")) {
        return "bda_check_jmethodid_instance_check";
      } else if (type.equals("Nonvirtual")) {
        return "bda_check_jmethodid_nonvirtual_check";
      } else if (type.equals("Static")) {
        return "bda_check_jmethodid_static_check";
      } else {
        assert false;
        return "";
      }
    }

    static final HashMap<String,String> type2fieldDesc = new HashMap<String,String>();
    {
      type2fieldDesc.put("Boolean", "Z");
      type2fieldDesc.put("Byte", "B");
      type2fieldDesc.put("Char", "C");
      type2fieldDesc.put("Short", "S");
      type2fieldDesc.put("Int", "I");
      type2fieldDesc.put("Long", "J");
      type2fieldDesc.put("Float", "F");
      type2fieldDesc.put("Double", "D");
      type2fieldDesc.put("Object", "O");
      type2fieldDesc.put("Void", "V");
    }

  static String getFieldDescFromType(String s) {
    String fdesc = type2fieldDesc.get(s);
    assert fdesc != null;
    return fdesc;
  }

  static final HashMap<String,String> type2jvalueFieldName = new HashMap<String,String>();
    {
      type2jvalueFieldName.put("Boolean", "z");
      type2jvalueFieldName.put("Byte", "b");
      type2jvalueFieldName.put("Char", "c");
      type2jvalueFieldName.put("Short", "s");
      type2jvalueFieldName.put("Int", "i");
      type2jvalueFieldName.put("Long", "j");
      type2jvalueFieldName.put("Float", "f");
      type2jvalueFieldName.put("Double", "d");
      type2jvalueFieldName.put("Object", "l");
    }

    static final String getJValueFieldFromType(String type) {
      String fname = type2jvalueFieldName.get(type);
      assert fname != null;
      return fname;
    }

    private static String getCformatStringFor(JNIType type) {
      if (type instanceof PrimitiveType) {
        PrimitiveType ptype = (PrimitiveType)type;
        if (ptype == JNIConstants.JBOOLEAN) {
          return "%d";
        } else if (ptype == JNIConstants.JBYTE) {
          return "%d";
        } else if (ptype == JNIConstants.JCHAR) {
          return "%d";
        } else if (ptype == JNIConstants.JSHORT) {
          return "%d";
        } else if (ptype == JNIConstants.JINT) {
          return "%d";
        } else if (ptype == JNIConstants.JLONG) {
          return "%lld";
        } else if (ptype == JNIConstants.JFLOAT) {
          return "%f";
        } else if (ptype == JNIConstants.JDOUBLE) {
          return "%lf";
        } else if (ptype == JNIConstants.JSIZE) {
          return "%d";
        } else if (ptype == JNIConstants.JOBJECT_REF_TYPE) {
          return "%d";
        } else {
          assert false;
          return "";
        }
      } else if ((type instanceof ReferenceType) 
            ||(type instanceof JMethodIDType)
            ||(type instanceof JFieldIDType)) {
          return "%p";
      } else if (type instanceof PointerType) {
        if (type instanceof CStringType) {
          return "%s";
        } else if (type == JNIConstants.JBYTE_CONST_POINTER) {
          return "%s";
        } else {
          return "%p";
        }
      } else {
          assert false;
          return null;
      }
    }

    static class PrintfFormatAndArgument {
      final String fmt;
      final String list;
      public PrintfFormatAndArgument(String fmt, String list) {
        this.fmt = fmt;
        this.list = list;
      }
    }

    PrintfFormatAndArgument getFixedArgumentPrintFormatAndArgumentExpression() {
      StringBuffer fmt = new StringBuffer();
      StringBuffer list = new StringBuffer();
      boolean first = true;
      for(FormalArgument f : fargs) {
        JNIType jnitype = f.type.jniType;
        if (first) { first=false;} else { fmt.append(" ");}
        fmt.append(getCformatStringFor(jnitype));
        fmt.append('(').append(jnitype.name).append(')');
        list.append(", ").append(f.varName);
      }
      return new PrintfFormatAndArgument(fmt.toString(), list.toString());
    }

    void genFuncDef(PrintWriter w) {
      genFuncBegin(w);
      genLocalVariableDeclarations(w);
      genPrologue(w);
      genGetStateInfo(w);
      genStatPrologueBegin(w);
      genJNICheckBefore(w);
      genC2JInfoBefore(w);
      genCallOriginal(w);
      genC2JInfoAfter(w);
      genJNICheckAfter(w);
      genFuncEnd(w);
    }

    void genFuncBegin(PrintWriter w) {
      JNIAnnotatedType[] arguments = wrapperJNIFunction.argumenTypes;
      final String jniFuncName = getJNIFuncName();
      final String returnType = wrapperJNIFunction.returnType.getTypeName();
      // function declaration
      w.printf("\n/* proxy for %s*/\n", jniFuncName);
      w.printf("static %s JNICALL %s(", returnType, getProxyFuncName());
      for(int i = 0;i < arguments.length;i++) {
        w.printf("%s%s %s", i==0? "":", ", fargs[i].type.getTypeName(), fargs[i].varName);
      }
      if (wrapperJNIFunction.extraArgType == ExtraArgumentType.DOT_DOT_DOT) {
        w.printf(", ..."); 
      }
      w.printf(")\n{\n");
    }

    void genLocalVariableDeclarations(PrintWriter w) {
      // local variable declaration
      w.printf("  /* local variables */\n");
      if (wrapperJNIFunction.hasReturnType()) {
        w.printf("  %s result;\n", wrapperJNIFunction.returnType.getTypeName());
      }
      w.printf("  void *fp, *ret_addr, *ret_addr_from_original;\n");
      w.printf("  struct bda_c2j_info c2j;\n");
      w.printf("  struct bda_state_info *s;\n");
      w.printf("  enum bda_mode saved_mode;\n");
      w.printf("\n");
    }

    void genPrologue(PrintWriter w) {
      w.printf("  /* Prologue */\n");
      w.printf("  GET_FRAME_POINTER(fp)\n");
      w.printf("  GET_RETURN_ADDRESS(ret_addr);\n");
      w.printf("\n");
    }

    void genGetStateInfo(PrintWriter w) {
      w.printf("  /* Obtain a state variable for the current thraed. */\n");
      if (wrapperJNIFunction == JNIFunction.ReleaseStringCritical
          || wrapperJNIFunction == JNIFunction.ReleasePrimitiveArrayCritical) {
        w.printf("  s = bda_state_find(%s);\n", getJNIEnvName());
      } else {
        w.printf("  s = bda_get_state_info(%s);\n", getJNIEnvName());
      }
    }

    void genStatPrologueBegin(PrintWriter w) {
      w.printf("  /* Update call counts. */\n");
      w.printf("  if (s != NULL){\n");  
      w.printf("    %s++;\n", getStatCountName());
      w.printf("    if (s->mode == USR_NATIVE) {%s++;}\n", getStatCountUserName());
      w.printf("    saved_mode = s->mode;\n");
      w.printf("  }\n");
      w.printf("\n");
    }

    void genJNICheckBefore(PrintWriter w) {
      final String jniFuncName = getJNIFuncName();
      final String jniEnvName = getJNIEnvName();
      boolean exceptionCheck = !wrapperJNIFunction.isExceptionOblivious();
      Matcher m;
    
      // check the validity of incoming arguments.
      w.printf("  /* Check the JNI Function call. */\n");
      w.printf("  if (agent_options.jniassert && (s != NULL) && (s->mode != JVM) && !bda_is_in_jdwp_region(ret_addr)) {\n");
      w.printf("    int success;");
      if ((m = jniNewObjectXPattern.matcher(jniFuncName)).matches()) {
        String argPass = m.group(1);
        w.printf("    struct bda_var_arg_wrap awrap;\n");
        if (argPass.equals("")) {
          w.printf("    va_start(awrap.value.ap, %s);\n", fargs[fargs.length-1].varName);
          w.printf("    awrap.type = BDA_VA_LIST;\n");
        } else if (argPass.equals("V")) {
          w.printf("    awrap.type = BDA_VA_LIST;\n");
          w.printf("    awrap.value.ap = %s;\n", fargs[3].varName);
        } else if (argPass.equals("A")) {
          w.printf("    awrap.type = BDA_JARRAY;\n");
          w.printf("    awrap.value.array = %s;\n", fargs[3].varName);
        }
      } else if ((m = jniCallXXXMethodXPattern.matcher(jniFuncName)).matches()){
        String methodType = m.group(1);
        String returnType = m.group(2); 
        String argPass = m.group(3);
        w.printf("    struct bda_var_arg_wrap awrap;\n");
        if (argPass.equals("")) {
          w.printf("    va_start(awrap.value.ap, %s);\n", fargs[fargs.length-1].varName);
          w.printf("    awrap.type = BDA_VA_LIST;\n");
        } else if (argPass.equals("V")) {
          w.printf("    awrap.type = BDA_VA_LIST;\n");
          if (methodType.equals("")) {
            w.printf("     awrap.value.ap = %s;\n", fargs[3].varName);
          } else if (methodType.equals("Nonvirtual")) {
            w.printf("     awrap.value.ap = %s;\n", fargs[4].varName);
          } else if (methodType.equals("Static")) {
            w.printf("     awrap.value.ap = %s;\n", fargs[3].varName);
          } else {
            assert false;
          }
        } else if (argPass.equals("A")) {
          w.printf("    awrap.type = BDA_JARRAY;\n");
          if (methodType.equals("")) {
            w.printf("    awrap.value.array = %s;\n", fargs[3].varName);
          } else if (methodType.equals("Nonvirtual")) {
            w.printf("    awrap.value.array = %s;\n", fargs[4].varName);
          } else if (methodType.equals("Static")) {
            w.printf("    awrap.value.array = %s;\n", fargs[3].varName);
          } else {
            assert false;
          }
        } else {
          assert false;
        }
      } else if ( (m = pGetField.matcher(jniFuncName)).matches()) {
                boolean getter = m.group(1).equals("Get");
        boolean isStatic = m.group(2).equals("Static");
        String fieldType = m.group(3);
        if (!isStatic) {
          if (!getter) {
            w.printf("         jvalue v;\n");
            w.printf("         v.%s = %s;\n", getJValueFieldFromType(fieldType), fargs[3].varName);
          }
        } else {
          if (!getter) {
            w.printf("         jvalue v;\n");
            w.printf("         v.%s = %s;\n", getJValueFieldFromType(fieldType), fargs[3].varName);
          }
        }
      }

      w.printf("\n");

      // Check JVM state
      w.printf("    success = 1 \n");
      w.printf("    && bda_check_env_match(s, %s, \"%s\")\n", jniEnvName, jniFuncName);
    
      if (exceptionCheck) {
        w.printf("    && bda_check_no_exeception(s, \"%s\")\n", jniFuncName);
      }

      if (wrapperJNIFunction == JNIFunction.PopLocalFrame) {
        w.printf("    && bda_check_local_frame_double_free(s)\n");
      }

      if (!jniFuncName.matches("(Get|Release)(String|PrimitiveArray)Critical")) {
        w.printf("    && bda_check_no_critical(s, \"%s\")\n", jniFuncName);
      }

      // Check parameters
      for(int i = 1; i < fargs.length;i++) {
        final FormalArgument f = fargs[i];
        final JNIAnnotatedType t = f.type;
        if (t.nonNull) {
          w.printf("    && bda_check_non_null(s, %s,  %d, \"%s\")\n", f.varName, i, jniFuncName);
        }
      }

      // JNI reference type.
      if (exceptionCheck) {
        for(int i = 1; i < fargs.length;i++) {
          final FormalArgument f = fargs[i];
          final JNIAnnotatedType t = f.type;
          
          if (t.jniType instanceof ReferenceType){
            w.printf("    && bda_check_ref_dangling(s, %s, %d, \"%s\")\n", f.varName, i, jniFuncName);
            if  (t.jniType != JOBJECT) {
              w.printf("    && bda_check_%s(s, %s, %d, \"%s\")\n", t.jniType.name, f.varName, i, jniFuncName);
            }
          }
        }
      } else {
        for(int i = 1; i < fargs.length;i++) {
          final FormalArgument f = fargs[i];
          final JNIAnnotatedType t = f.type;
          
          if (t.jniType instanceof ReferenceType){
            w.printf("    && (bda_orig_jni_funcs->ExceptionCheck(env) || bda_check_ref_dangling(s, %s, %d, \"%s\"))\n", 
                     f.varName, i, jniFuncName);
            if  (t.jniType != JOBJECT) {
              w.printf("    && (bda_orig_jni_funcs->ExceptionCheck(env) || bda_check_%s(s, %s, %d, \"%s\"))\n", 
                       t.jniType.name, f.varName, i, jniFuncName);
            }
          }
        }
      }

      if (jniFuncName.equals("DefineClass")) {
        w.printf("    && bda_check_assignable_jobject_jclass(s, %s, bda_clazz_classloader, %d, \"%s\")\n", 
                 fargs[2].varName, 2, jniFuncName);
      } else if (jniFuncName.equals("FromReflectedMethod")) {
        w.printf("    && bda_check_jobject_reflected_method(s, %s, %d, \"%s\")\n",
                 fargs[1].varName, 1, jniFuncName);
      } else if (jniFuncName.equals("FromReflectedField")) {
        w.printf("    && bda_check_instance_jobject_jclass(s, %s, bda_clazz_field, %d, \"%s\")\n",
                 fargs[1].varName, 1, jniFuncName);
      } else if (jniFuncName.equals("DeleteGlobalRef")) {
        w.printf("    && bda_check_jobject_ref_type(s, %s, JNIGlobalRefType, %d, \"%s\")\n",
                 fargs[1].varName, 1, jniFuncName);
      } else if (jniFuncName.equals("DeleteWeakGlobalRef")) {
        w.printf("    && bda_check_jobject_ref_type(s, %s, JNIWeakGlobalRefType , %d, \"%s\")\n",
                 fargs[1].varName, 1, jniFuncName);
      } else if (jniFuncName.equals("DeleteLocalRef")) {
        w.printf("    && bda_check_jobject_ref_type(s, %s, JNILocalRefType, %d, \"%s\")\n",
                 fargs[1].varName, 1, jniFuncName);
      } else if (jniFuncName.equals("NewObjectArray")) {
        w.printf("    && bda_check_assignable_jclass_jobject(s, %s, %s, %d, \"%s\")\n",
                 fargs[2].varName, fargs[3].varName, 2, jniFuncName);
      } else if (jniFuncName.equals("SetObjectArrayElement")) {
        w.printf("    && bda_check_assignable_jobjectArray_jobject(s, %s, %s, %d,  \"%s\")\n",
                 fargs[1].varName, fargs[3].varName, 3, jniFuncName);
      } else if (jniFuncName.equals("GetDirectBufferAddress")) {
        w.printf("    && bda_check_assignable_jobject_jclass(s, %s, bda_clazz_nio_buffer, %d, \"%s\")\n",
                 fargs[1].varName, 1, jniFuncName);
      } else if (jniFuncName.equals("GetDirectBufferCapacity")) {
        w.printf("    && bda_check_assignable_jobject_jclass(s, %s, bda_clazz_nio_buffer, %d, \"%s\")\n",
                 fargs[1].varName, 1, jniFuncName);
      } else if (jniFuncName.equals("ThrowNew")) {
        w.printf("    && bda_check_assignable_jclass_jclass(s, %s, bda_clazz_throwable, %d, \"%s\")\n", 
                 fargs[1].varName, 1, jniFuncName);
      } else if (jniFuncName.equals("AllocObject")) {
        w.printf("    && bda_check_jclass_scalar_allocatable(s, %s, %d, \"%s\")\n", 
                  fargs[1].varName, 1, jniFuncName);
      }

      for(int i = 1; i < fargs.length;i++) {
        final FormalArgument f = fargs[i];
        final JNIAnnotatedType t = f.type;        
        if (t.jniType instanceof JFieldIDType) {
          genJNICheckFieldID(w, f, i);
        } else if (t.jniType instanceof JMethodIDType) {
          genJNICheckMethodID(w, f, i);
        }
      }
      // access
      if (jniFuncName.matches("Set(Object|Boolean|Byte|Char|Short|Int|Long|Float|Double)Field")) {
        w.printf("    && bda_check_access_set_instance_field(s, %s, %s, %d, \"%s\")\n", fargs[1].varName, fargs[2].varName, 2, jniFuncName);
      } else if (jniFuncName.matches("SetStatic(Object|Boolean|Byte|Char|Short|Int|Long|Float|Double)Field")) {
        w.printf("    && bda_check_access_set_static_field(s, %s, %s, %d, \"%s\")\n", fargs[1].varName, fargs[2].varName, 2, jniFuncName);
      }

      // resource release
      if (jniFuncName.matches("ReleaseString(|UTF)Chars")) {
        w.printf ("    && bda_check_resource_free(s, %s, \"%s\")\n", fargs[2].varName, jniFuncName);
      } else if (jniFuncName.matches("Release(Boolean|Byte|Char|Short|Int|Long|Float|Double)ArrayElements")) {
        w.printf ("    && bda_check_resource_free(s, %s, \"%s\")\n", fargs[2].varName, jniFuncName);
      } else if (jniFuncName.matches("ReleaseStringCritical;\n")) {
        w.printf ("    && bda_check_resource_free(s, %s, \"%s\")\n", fargs[2].varName, jniFuncName);
      } else if (jniFuncName.matches("ReleasePrimitiveArrayCritical")) {
        w.printf ("    && bda_check_resource_free(s, %s, \"%s\")\n", fargs[2].varName, jniFuncName);
      }
      w.printf("    ;\n"); 

      // return if any pending exception
      if (!wrapperJNIFunction.isExceptionOblivious()) { 
        w.printf("    // Just return if an exception is pending here.\n");
        w.printf("    if ((agent_options.jniassert )&& bda_orig_jni_funcs->ExceptionCheck(s->env) == JNI_TRUE){\n");
        w.printf("      s->mode = saved_mode;\n");
        if (wrapperJNIFunction.hasReturnType()) {
          w.printf("      return 0;\n");
        } else {
          w.printf("      return;\n");
        }
        w.printf("    }\n");
      } else {
        w.printf("    // Just return if an exception is pending here.\n");
        w.printf("    if ((agent_options.jniassert)&& !success){\n");
        w.printf("      s->mode = saved_mode;\n");
        if (wrapperJNIFunction.hasReturnType()) {
          w.printf("      return 0;\n");
        } else {
          w.printf("      return;\n");
        }
        w.printf("    }\n");
      }

      // prologue action
      w.printf("  }\n");


      w.printf("\n");
    }

    void genC2JInfoBefore(PrintWriter w) {
      final String proxy_name = getProxyFuncName();
      w.printf("  /* Push the c2j_info structure. */\n");
      w.printf("  if (s != NULL) {\n");
      w.printf("#if defined(__GNUC__)\n");
      w.printf("    ret_addr_from_original = &&L_RETURN;\n");
      w.printf("#else\n");
      w.printf("    ret_addr_from_original = %s;\n", proxy_name);
      w.printf("#endif\n");
      w.printf("    c2j.return_addr = ret_addr_from_original;\n");
      w.printf("    c2j.caller_fp = fp;\n");  
      w.printf("    c2j.jdwp_context =  bda_is_in_jdwp_region(ret_addr);\n");
      w.printf("    c2j.call_type = %s;\n", wrapperJNIFunction.fclass.name());
      switch(wrapperJNIFunction.fclass) {
        case JNI_CALL_INSTANCE:
          ensureFormalTypes(JNI_ENV, JOBJECT, JMETHODID);
          w.printf("    c2j.object = %s;\n",fargs[1].varName);
          w.printf("    c2j.class = %s;\n",  "NULL");
          w.printf("    c2j.mid = %s;\n", fargs[2].varName); 
          break;
        case JNI_CALL_STATIC:
          ensureFormalTypes(JNI_ENV, JCLASS, JMETHODID);
          w.printf("    c2j.object = %s;\n", "NULL");
          w.printf("    c2j.class = %s;\n",  fargs[1].varName);
          w.printf("    c2j.mid = %s;\n", fargs[2].varName);
          break;
        case JNI_CALL_NONVIRTUAL:
          ensureFormalTypes(JNI_ENV, JOBJECT, JCLASS, JMETHODID);
          w.printf("    c2j.object = %s;\n", fargs[1].varName);
          w.printf("    c2j.class = %s;\n",  fargs[2].varName);
          w.printf("    c2j.mid = %s;\n", fargs[3].varName);
          break;
        case JNI_CALL_NOT_CLASSIFIED:
          w.printf("    c2j.object = %s;\n", "NULL");
          w.printf("    c2j.class = %s;\n",  "NULL");
          w.printf("    c2j.mid = %s;\n", "NULL");
          break;
      }

      w.printf("    bda_state_c2j_call(s, &c2j);\n");
      w.printf("  }\n");
      w.printf("\n");
    }
 
    abstract void genCallOriginal(PrintWriter w);

    void genC2JInfoAfter(PrintWriter w) {
      w.printf("  /* Pop the c2j_info structure. */\n");
      w.printf("  if (s != NULL) {\n");  
      w.printf("    bda_state_c2j_return(s, &c2j);\n");
      w.printf("    s->mode = saved_mode;\n");
      w.printf("  }\n");
      w.printf("\n");
    }
    
    void genJNICheckAfter(PrintWriter w) {
      final String jniFuncName = getJNIFuncName();
      w.printf("  /* Check the JNI function return. */\n");
      w.printf("  if ((s != NULL) && agent_options.jniassert && (s->mode != JVM)) {\n");  

      // critical resources
      if (jniFuncName.matches("Get(String|PrimitiveArray)Critical")) {
        w.printf("    bda_enter_critical(s, (void*)%s);\n", "result");
      } else if (jniFuncName.matches("Release(String|PrimitiveArray)Critical")) {
        w.printf("    bda_leave_critical(s, (void*)%s);\n", fargs[2].varName);
      }

      // Entity-specific typing
      if (wrapperJNIFunction == JNIFunction.GetMethodID) {
        w.printf("   if (result != NULL) {\n");
        w.printf("       bda_jmethodid_append( result, 0, %s, %s, %s);\n", 
                 fargs[1].varName, fargs[2].varName, fargs[3].varName);
        w.printf("   }\n");
      } else if (wrapperJNIFunction == JNIFunction.GetStaticMethodID) {
        w.printf("   if (result != NULL) {\n");
        w.printf("       bda_jmethodid_append(result, 1, %s, %s, %s);\n", 
                 fargs[1].varName, fargs[2].varName, fargs[3].varName);
        w.printf("   }\n");
      } else if (wrapperJNIFunction == JNIFunction.GetFieldID) {
        w.printf("   if (result != NULL) {\n");
        w.printf("       bda_jfieldid_append(s, result, %s, 0, %s, %s);\n", 
                 fargs[1].varName, fargs[2].varName, fargs[3].varName);
        w.printf("   }\n");
      } else if (wrapperJNIFunction == JNIFunction.GetStaticFieldID) {
        w.printf("   if (result != NULL) {\n");
        w.printf("       bda_jfieldid_append(s, result, %s, 1, %s, %s);\n", 
                 fargs[1].varName, fargs[2].varName, fargs[3].varName);
        w.printf("   }\n");
      }

      // local references
      if (wrapperJNIFunction.returnLocalReference()) {
        if (wrapperJNIFunction == JNIFunction.PopLocalFrame) {
          w.printf("    bda_local_ref_leave(s);\n");
        }
        w.printf("   if (result != NULL) {\n");
        w.printf("      if (!bda_check_local_frame_overflow(s, \"%s\")) {\n", jniFuncName);
        w.printf("         bda_orig_jni_funcs->DeleteLocalRef(env, result);\n");
        w.printf("         result = NULL;\n");
        w.printf("      } else {\n");
        w.printf("         bda_local_ref_add(s, result);");
        w.printf("      }\n");
        w.printf("   }\n");
      }

      if (wrapperJNIFunction == JNIFunction.PushLocalFrame) {
        w.printf("   if (result == 0) {\n");
        w.printf("     bda_local_ref_enter(s, %s, 0);\n", fargs[1].varName);
        w.printf("   }\n");
      } else if (wrapperJNIFunction == JNIFunction.DeleteLocalRef) {
        assert !wrapperJNIFunction.hasReturnType();
        assert fargs[1].type.jniType == JNIConstants.JOBJECT;
        w.printf("   if (%s != NULL) {\n", fargs[1].varName);
        w.printf("     bda_local_ref_delete(s, %s);\n", fargs[1].varName);
        w.printf("   }\n");
      }


      // global references
      if (wrapperJNIFunction == JNIFunction.NewGlobalRef) {
        w.printf("    if (result != NULL) {\n");
        w.printf("      bda_global_ref_add(result, 0);\n");
        w.printf("    }\n");
      } else if (wrapperJNIFunction == JNIFunction.DeleteGlobalRef) {
        assert fargs[1].type.jniType == JNIConstants.JOBJECT;
        w.printf("   bda_global_ref_delete(%s, 0);\n", fargs[1].varName);
      } else if (wrapperJNIFunction == JNIFunction.NewWeakGlobalRef) {
        w.printf("    if (result != NULL) {\n");
        w.printf("      bda_global_ref_add(result, 1);\n");
        w.printf("    }\n");
      } else if (wrapperJNIFunction == JNIFunction.DeleteWeakGlobalRef) {
        assert fargs[1].type.jniType == JNIConstants.JWEAK;
        w.printf("   bda_global_ref_delete(%s, 1);\n", fargs[1].varName);
      }

      // VM resources
      if (jniFuncName.matches("GetString(UTF|)Chars")
          || jniFuncName.matches("Get(Boolean|Byte|Char|Short|Int|Long|Float|Double)ArrayElements")
          || jniFuncName.matches("Get(String|PrimitiveArray)Critical")) {
        w.printf("    if (result != NULL) {bda_resource_acquire(s, result, \"%s\");}\n", jniFuncName);
      } else if (jniFuncName.matches("ReleaseString(UTF|)Chars")
                 || jniFuncName.matches("Release(String|PrimitiveArray)Critical")) {
        w.printf("    bda_resource_release(s, %s, \"%s\");\n", fargs[2].varName, jniFuncName);
      } else if (jniFuncName.matches("Release(Boolean|Byte|Char|Short|Int|Long|Float|Double)ArrayElements")) {
        w.printf("    if ( %s != JNI_COMMIT) {bda_resource_release(s, %s, \"%s\");}\n", fargs[3].varName, fargs[2].varName, jniFuncName);
      }

      // Monitor resources
      if (wrapperJNIFunction == JNIFunction.MonitorEnter) {
        w.printf("   if (result == 0) {\n");
        w.printf("     bda_monitor_enter(s, %s);\n", fargs[1].varName);
        w.printf("   }\n");
      } else if (wrapperJNIFunction == JNIFunction.MonitorExit) {
        w.printf("   if (result == 0) {\n");
        w.printf("     bda_monitor_exit(s, %s);\n", fargs[1].varName);
        w.printf("   }\n");
      }

      w.printf("  }\n\n");

    }

    void genFuncEnd(PrintWriter w) {
      if (wrapperJNIFunction.hasReturnType()) {w.printf("  return result;\n");}
      w.printf("}\n\n");
    }
  }

  private static class FixedArgProxyGenerator extends ProxyGenerator {

    FixedArgProxyGenerator(JNIFunction jniFunction) {
      super(jniFunction, jniFunction);
    }

    void genCallOriginal(PrintWriter w) {
      w.printf("  /* Call the target JNI function. */\n");      
      w.printf("  %s bda_orig_jni_funcs->%s(", 
               wrapperJNIFunction.hasReturnType()? "result =":"",
               getJNIFuncName());
      for(int i = 0; i < fargs.length;i++) {
        FormalArgument f = fargs[i];
        w.printf("%s%s", i==0?"":", ", f.varName);
      }
      w.printf(");\n");
      w.printf("  L_RETURN:\n");
      w.printf("\n");
    }
  }

  private static class VariableArgProxyGenerator extends ProxyGenerator {

    VariableArgProxyGenerator(JNIFunction proxyFunction, JNIFunction targetFunction) {
      super(proxyFunction, targetFunction);
      assert proxyFunction.extraArgType == ExtraArgumentType.DOT_DOT_DOT && targetFunction.extraArgType == ExtraArgumentType.VA_LIST;
    }

    void genLocalVariableDeclarations(PrintWriter w) {
      super.genLocalVariableDeclarations(w);
      w.printf("  va_list args;\n");
    }

    void genCallOriginal(PrintWriter w) {
      JNIAnnotatedType[] arguments = wrapperJNIFunction.argumenTypes;
      String jni_target_fname = targetJNIFunction.name;
      w.printf("  /* Call the target JNI function. */\n");     
      w.printf("  va_start(args,p%d);\n", arguments.length-1);
      if (wrapperJNIFunction.hasReturnType()) {
        w.printf("  result = ");
      }
      w.printf("  bda_orig_jni_funcs->%s(", jni_target_fname);
      for(int i = 0; i < fargs.length;i++) {
        FormalArgument f = fargs[i];
        w.printf("%s%s", i==0?"":", ", f.varName);
      }
      w.printf(", args");
      w.printf(");\n");
      w.printf("  L_RETURN:\n");
      w.printf("\n");
    }
  }
}
