/*
 * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * - Redistribution of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistribution in binary form must reproduce the above copyright
 *   notice, this list of conditions and the following disclaimer in the
 *   documentation and/or other materials provided with the distribution.
 *
 * Neither the name of Sun Microsystems, Inc. or the names of
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
 * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
 * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
 * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
 * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
 * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
 * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * You acknowledge that this software is not designed or intended for use
 * in the design, construction, operation or maintenance of any nuclear
 * facility.
 *
 * Sun gratefully acknowledges that this software was originally authored
 * and developed by Kenneth Bradley Russell and Christopher John Kline.
 */

package com.jogamp.gluegen;

import java.util.*;
import java.io.*;
import java.text.MessageFormat;

import com.jogamp.common.os.MachineDescription;
import com.jogamp.gluegen.cgram.types.*;

import java.util.logging.Logger;

/** Emits the C-side component of the Java<->C JNI binding. */
public class CMethodBindingEmitter extends FunctionEmitter {

  protected static final Logger LOG = Logger.getLogger(CMethodBindingEmitter.class.getPackage().getName());
  protected static final CommentEmitter defaultCommentEmitter = new DefaultCommentEmitter();

  protected static final String arrayResLength = "_array_res_length";
  protected static final String arrayRes       = "_array_res";
  protected static final String arrayIdx       = "_array_idx";

  protected MethodBinding binding;

  /** Name of the package in which the corresponding Java method resides.*/
  private final String packageName;

  /** Name of the class in which the corresponding Java method resides.*/
  private final String className;

  /**
   * Whether or not the Java<->C JNI binding for this emitter's MethodBinding
   * is overloaded.
   */
  private final boolean isOverloadedBinding;

  /**
   * Whether or not the Java-side of the Java<->C JNI binding for this
   * emitter's MethodBinding is static.
   */
  private final boolean isJavaMethodStatic;

  // Flags which change various aspects of glue code generation
  protected boolean forImplementingMethodCall;
  protected boolean forIndirectBufferAndArrayImplementation;

  /**
   * Optional List of Strings containing temporary C variables to declare.
   */
  private List<String> temporaryCVariableDeclarations;

  /**
   * Optional List of Strings containing assignments to temporary C variables
   * to make after the call is completed.
   */
  private List<String> temporaryCVariableAssignments;

  /**
   * Capacity of the return value in the event that it is encapsulated in a
   * java.nio.Buffer. Is ignored if binding.getJavaReturnType().isNIOBuffer()
   * == false;
   */
  private MessageFormat returnValueCapacityExpression = null;

  /**
   * Length of the returned array. Is ignored if
   * binding.getJavaReturnType().isArray() is false.
   */
  private MessageFormat returnValueLengthExpression = null;

  protected static final String STRING_CHARS_PREFIX = "_strchars_";

  // We need this in order to compute sizes of certain types
  protected MachineDescription machDesc;

  /**
   * Constructs an emitter for the specified binding, and sets a default
   * comment emitter that will emit the signature of the C function that is
   * being bound.
   */
  public CMethodBindingEmitter(final MethodBinding binding,
                               final PrintWriter output,
                               final String javaPackageName,
                               final String javaClassName,
                               final boolean isOverloadedBinding,
                               final boolean isJavaMethodStatic,
                               final boolean forImplementingMethodCall,
                               final boolean forIndirectBufferAndArrayImplementation,
                               final MachineDescription machDesc)
  {
    super(output, false);

    assert(binding != null);
    assert(javaClassName != null);
    assert(javaPackageName != null);

    this.binding = binding;
    this.packageName = javaPackageName;
    this.className = javaClassName;
    this.isOverloadedBinding = isOverloadedBinding;
    this.isJavaMethodStatic = isJavaMethodStatic;

    this.forImplementingMethodCall = forImplementingMethodCall;
    this.forIndirectBufferAndArrayImplementation = forIndirectBufferAndArrayImplementation;
    this.machDesc = machDesc;

    setCommentEmitter(defaultCommentEmitter);
  }

  public final MethodBinding getBinding() { return binding; }

  @Override
  public String getName() {
    return binding.getName();
  }

  /**
   * Get the expression for the capacity of the returned java.nio.Buffer.
   */
  public final MessageFormat getReturnValueCapacityExpression()  {
    return returnValueCapacityExpression;
  }

  /**
   * If this function returns a void* encapsulated in a
   * java.nio.Buffer (or compound type wrapper), sets the expression
   * for the capacity of the returned Buffer.
   *
   * @param expression a MessageFormat which, when applied to an array
   * of type String[] that contains each of the arguments names of the
   * Java-side binding, returns an expression that will (when compiled
   * by a C compiler) evaluate to an integer-valued expression. The
   * value of this expression is the capacity of the java.nio.Buffer
   * returned from this method.
   *
   * @throws IllegalArgumentException if the <code>
   * binding.getJavaReturnType().isNIOBuffer() == false and
   * binding.getJavaReturnType().isCompoundTypeWrapper() == false
   * </code>
   */
  public final void setReturnValueCapacityExpression(final MessageFormat expression)  {
    returnValueCapacityExpression = expression;

    if (!binding.getJavaReturnType().isNIOBuffer() &&
        !binding.getJavaReturnType().isCompoundTypeWrapper())    {
      throw new IllegalArgumentException(
        "Cannot specify return value capacity for a method that does not " +
        "return java.nio.Buffer or a compound type wrapper: \"" + binding + "\"");
    }
  }

  /**
   * Get the expression for the length of the returned array
   */
  public final MessageFormat getReturnValueLengthExpression()  {
    return returnValueLengthExpression;
  }

  /**
   * If this function returns an array, sets the expression for the
   * length of the returned array.
   *
   * @param expression a MessageFormat which, when applied to an array
   * of type String[] that contains each of the arguments names of the
   * Java-side binding, returns an expression that will (when compiled
   * by a C compiler) evaluate to an integer-valued expression. The
   * value of this expression is the length of the array returned from
   * this method.
   *
   * @throws IllegalArgumentException if the <code>
   * binding.getJavaReturnType().isNIOBuffer() == false
   * </code>
   */
  public final void setReturnValueLengthExpression(final MessageFormat expression)  {
    returnValueLengthExpression = expression;

    if (!binding.getJavaReturnType().isArray() &&
        !binding.getJavaReturnType().isArrayOfCompoundTypeWrappers())    {
      throw new IllegalArgumentException(
        "Cannot specify return value length for a method that does not " +
        "return an array: \"" + binding + "\"");
    }
  }

  /**
   * Returns the List of Strings containing declarations for temporary
   * C variables to be assigned to after the underlying function call.
   */
  public final List<String> getTemporaryCVariableDeclarations() {
    return temporaryCVariableDeclarations;
  }

  /**
   * Sets up a List of Strings containing declarations for temporary C
   * variables to be assigned to after the underlying function call. A
   * null argument indicates that no manual declarations are to be made.
   */
  public final void setTemporaryCVariableDeclarations(final List<String> arg) {
    temporaryCVariableDeclarations = arg;
  }

  /**
   * Returns the List of Strings containing assignments for temporary
   * C variables which are made after the underlying function call. A
   * null argument indicates that no manual assignments are to be
   * made.
   */
  public final List<String> getTemporaryCVariableAssignments() {
    return temporaryCVariableAssignments;
  }

  /**
   * Sets up a List of Strings containing assignments for temporary C
   * variables which are made after the underlying function call. A
   * null argument indicates that no manual assignments are to be made.
   */
  public final void setTemporaryCVariableAssignments(final List<String> arg) {
    temporaryCVariableAssignments = arg;
  }

  /**
   * Get the name of the class in which the corresponding Java method
   * resides.
   */
  public String getJavaPackageName() { return packageName; }

  /**
   * Get the name of the package in which the corresponding Java method
   * resides.
   */
  public String getJavaClassName() { return className; }

  /**
   * Is the Java<->C JNI binding for this emitter's MethodBinding one of
   * several overloaded methods with the same name?
   */
  public final boolean getIsOverloadedBinding() { return isOverloadedBinding; }

  /**
   * Is the Java side of the Java<->C JNI binding for this emitter's
   * MethodBinding a static method?.
   */
  public final boolean getIsJavaMethodStatic() { return isJavaMethodStatic; }

  /**
   * Is this CMethodBindingEmitter implementing the case of an
   * indirect buffer or array being passed down to C code?
   */
  public final boolean forIndirectBufferAndArrayImplementation() { return forIndirectBufferAndArrayImplementation; }

  /**
   * Used for certain internal type size computations
   */
  public final MachineDescription getMachineDescription() { return machDesc; }


  @Override
  protected void emitReturnType(final PrintWriter writer)  {
    writer.print("JNIEXPORT ");
    writer.print(binding.getJavaReturnType().jniTypeName());
    writer.print(" JNICALL");
  }

  @Override
  protected void emitName(final PrintWriter writer)  {
    writer.println(); // start name on new line
    writer.print(JavaEmitter.getJNIMethodNamePrefix(getJavaPackageName(), getJavaClassName()));
    writer.print("_");
    if (isOverloadedBinding)    {
      writer.print(jniMangle(binding));
      //System.err.println("OVERLOADED MANGLING FOR " + getName() +
      //                   " = " + jniMangle(binding));
    } else {
      writer.print(JavaEmitter.jniMangle(getName()));
      //System.err.println("    NORMAL MANGLING FOR " + binding.getName() +
      //                   " = " + jniMangle(getName()));
    }
  }

  protected String getImplSuffix() {
    if (forImplementingMethodCall) {
      if (forIndirectBufferAndArrayImplementation) {
        return "1";
      } else {
        return "0";
      }
    }
    return "";
  }

  @Override
  protected int emitArguments(final PrintWriter writer)  {
    writer.print("JNIEnv *env, ");
    int numEmitted = 1; // initially just the JNIEnv
    if (isJavaMethodStatic && !binding.hasContainingType())    {
      writer.print("jclass");
    } else {
      writer.print("jobject");
    }
    writer.print(" _unused");
    ++numEmitted;

    if (binding.hasContainingType())   {
      // "this" argument always comes down in argument 0 as direct buffer
      writer.print(", jobject " + JavaMethodBindingEmitter.javaThisArgumentName());
    }
    for (int i = 0; i < binding.getNumArguments(); i++) {
      final JavaType javaArgType = binding.getJavaArgumentType(i);
      // Handle case where only param is void
      if (javaArgType.isVoid()) {
        // Make sure this is the only param to the method; if it isn't,
        // there's something wrong with our parsing of the headers.
        assert(binding.getNumArguments() == 1);
        continue;
      }
      if (javaArgType.isJNIEnv() || binding.isArgumentThisPointer(i)) {
        continue;
      }
      writer.print(", ");
      writer.print(javaArgType.jniTypeName());
      writer.print(" ");
      writer.print(binding.getArgumentName(i));
      ++numEmitted;

      if (javaArgType.isPrimitiveArray() ||
          javaArgType.isNIOBuffer()) {
        writer.print(", jint " + byteOffsetArgName(i));
        if(forIndirectBufferAndArrayImplementation) {
            writer.print(", jboolean " + isNIOArgName(i));
        }
      } else if (javaArgType.isNIOBufferArray()) {
        writer.print(", jintArray " +
                     byteOffsetArrayArgName(i));
      }
    }
    return numEmitted;
  }


  @Override
  protected void emitBody(final PrintWriter writer)  {
    writer.println(" {");
//    writer.println("printf(\" - - - - "+ getName() + getImplSuffix() +" - - - -\\n\");");
    emitBodyVariableDeclarations(writer);
    emitBodyUserVariableDeclarations(writer);
    emitBodyVariablePreCallSetup(writer);
    emitBodyCallCFunction(writer);
    emitBodyUserVariableAssignments(writer);
    emitBodyVariablePostCallCleanup(writer);
    emitBodyReturnResult(writer);
    writer.println("}");
    writer.println();
  }

  protected void emitBodyVariableDeclarations(final PrintWriter writer)  {
    // Emit declarations for all pointer and String conversion variables
    if (binding.hasContainingType()) {
      emitPointerDeclaration(writer,
                             binding.getContainingType(),
                             binding.getContainingCType(),
                             CMethodBindingEmitter.cThisArgumentName(),
                             null);
    }

    boolean emittedDataCopyTemps = false;
    for (int i = 0; i < binding.getNumArguments(); i++) {
      final JavaType type = binding.getJavaArgumentType(i);
      if (type.isJNIEnv() || binding.isArgumentThisPointer(i)) {
        continue;
      }

      if (type.isArray() || type.isNIOBuffer() || type.isCompoundTypeWrapper() || type.isArrayOfCompoundTypeWrappers()) {
        final String javaArgName = binding.getArgumentName(i);
        final String convName = pointerConversionArgumentName(javaArgName);
        // handle array/buffer argument types
        final boolean needsDataCopy =
          emitPointerDeclaration(writer,
                                 type,
                                 binding.getCArgumentType(i),
                                 convName, javaArgName);
        if (needsDataCopy && !emittedDataCopyTemps) {
          // emit loop counter and array length variables used during data
          // copy
          writer.println("  jobject _tmpObj;");
          writer.println("  int _copyIndex;");
          writer.println("  jsize _tmpArrayLen;");

          // Pointer to the data in the Buffer, taking the offset into account
          if(type.isNIOBufferArray()) {
            writer.println("  int * _offsetHandle = NULL;");
          }

          emittedDataCopyTemps = true;
        }
      } else if (type.isString()) {
        final Type cType = binding.getCArgumentType(i);
        if (isUTF8Type(cType)) {
          writer.print("  const char* ");
        } else {
          writer.print("  jchar* ");
        }
        writer.print(STRING_CHARS_PREFIX);
        writer.print(binding.getArgumentName(i));
        writer.println(" = NULL;");
      }

    }

    // Emit declaration for return value if necessary
    final Type cReturnType = binding.getCReturnType();

    final JavaType javaReturnType = binding.getJavaReturnType();
    if (!cReturnType.isVoid()) {
      writer.print("  ");
      // Note we must respect const/volatile for return argument
      writer.print(binding.getCSymbol().getReturnType().getName(true));
      writer.println(" _res;");
      if (javaReturnType.isNIOByteBufferArray() ||
          javaReturnType.isArrayOfCompoundTypeWrappers()) {
        writer.print("  int ");
        writer.print(arrayResLength);
        writer.println(";");
        writer.print("  int ");
        writer.print(arrayIdx);
        writer.println(";");
        writer.print("  jobjectArray ");
        writer.print(arrayRes);
        writer.println(";");
      } else if (javaReturnType.isArray()) {
        writer.print("  int ");
        writer.print(arrayResLength);
        writer.println(";");

        final Class<?> componentType = javaReturnType.getJavaClass().getComponentType();
        if (componentType.isArray()) {
          throw new RuntimeException("Multi-dimensional arrays not supported yet");
        }

        final String javaTypeName = componentType.getName();
        final String javaArrayTypeName = "j" + javaTypeName + "Array";
        writer.print("  ");
        writer.print(javaArrayTypeName);
        writer.print(" ");
        writer.print(arrayRes);
        writer.println(";");
      }
    }
  }

  /** Emits the user-defined C variable declarations from the
      TemporaryCVariableDeclarations directive in the .cfg file. */
  protected void emitBodyUserVariableDeclarations(final PrintWriter writer) {
    if (temporaryCVariableDeclarations != null) {
      for (final String val : temporaryCVariableDeclarations) {
        writer.print("  ");
        writer.println(val);
      }
    }
  }

  /** Checks a type to see whether it is for a UTF-8 pointer type
      (i.e., "const char *", "const char **"). False implies that this
      type is for a Unicode pointer type ("jchar *", "jchar **"). */
  protected boolean isUTF8Type(Type type) {
    final int i = 0;
    // Try to dereference the type at most two levels
    while (!type.isInt() && !type.isVoid() && (i < 2)) {
      final PointerType pt = type.asPointer();
      if (pt != null) {
        type = pt.getTargetType();
      } else {
        final ArrayType arrt = type.asArray();
        if (arrt == null) {
          throw new IllegalArgumentException("Type " + type + " should have been a pointer or array type");
        }
        type = arrt.getElementType();
      }
    }
    if (type.isVoid()) {
      // Assume UTF-8 since UTF-16 is rare
      return true;
    }
    if (!type.isInt()) {
      throw new IllegalArgumentException("Type " + type + " should have been a one- or two-dimensional integer pointer or array type");
    }
    if (type.getSize(machDesc) != 1 && type.getSize(machDesc) != 2) {
      throw new IllegalArgumentException("Type " + type + " should have been a one- or two-dimensional pointer to char or short");
    }
    return (type.getSize(machDesc) == 1);
  }

  /**
   * Code to init the variables that were declared in
   * emitBodyVariableDeclarations(), PRIOR TO calling the actual C
   * function.
   */
  protected void emitBodyVariablePreCallSetup(final PrintWriter writer)  {

    // Convert all Buffers to pointers first so we don't have to
    // call ReleasePrimitiveArrayCritical for any arrays if any
    // incoming buffers aren't direct
    if (binding.hasContainingType()) {
        emitPointerConversion(writer, binding,
                              binding.getContainingType(),
                              binding.getContainingCType(),
                              JavaMethodBindingEmitter.javaThisArgumentName(),
                              CMethodBindingEmitter.cThisArgumentName(),
                              null);
    }

    // Convert all arrays to pointers, and get UTF-8 versions of jstring args
    for (int i = 0; i < binding.getNumArguments(); i++) {
      final JavaType javaArgType = binding.getJavaArgumentType(i);

      if (javaArgType.isJNIEnv() || binding.isArgumentThisPointer(i)) {
        continue;
      }
      final String javaArgName = binding.getArgumentName(i);

      if (javaArgType.isCompoundTypeWrapper() ||
          (javaArgType.isNIOBuffer() && !forIndirectBufferAndArrayImplementation ) ) {
        emitPointerConversion(writer, binding, javaArgType,
                              binding.getCArgumentType(i), javaArgName,
                              pointerConversionArgumentName(javaArgName),
                              byteOffsetArgName(i));
      } else if (javaArgType.isArray() ||
                 javaArgType.isArrayOfCompoundTypeWrappers() ||
                 ( javaArgType.isNIOBuffer() && forIndirectBufferAndArrayImplementation ) ) {
        final boolean needsDataCopy = javaArgTypeNeedsDataCopy(javaArgType);

        writer.println("  if ( NULL != " + javaArgName + " ) {");

        final Type cArgType = binding.getCArgumentType(i);
        String cArgTypeName = cArgType.getName();

        final String convName = pointerConversionArgumentName(javaArgName);

        if (!needsDataCopy) {
          writer.print("    ");
          writer.print(convName);
          writer.print(" = (");
          if (javaArgType.isStringArray()) {
            // java-side type is String[]
            cArgTypeName = "jstring *";
          }
          writer.print(cArgTypeName);
          writer.print(") ( JNI_TRUE == " + isNIOArgName(i) + " ? ");
          writer.print(" (*env)->GetDirectBufferAddress(env, " + javaArgName + ") : ");
          writer.print(" (*env)->GetPrimitiveArrayCritical(env, " + javaArgName + ", NULL) );");
        } else {
          // Handle the case where the array elements are of a type that needs a
          // data copy operation to convert from the java memory model to the C
          // memory model (e.g., int[][], String[], etc)
          //
          // FIXME: should factor out this whole block of code into a separate
          // method for clarity and maintenance purposes
          //
          // Note that we properly handle only the case of an array of
          // compound type wrappers in emitBodyVariablePostCallCleanup below
          if (!isBaseTypeConst(cArgType) &&
              !javaArgType.isArrayOfCompoundTypeWrappers()) {
            // FIXME: if the arg type is non-const, the sematics might be that
            // the function modifies the argument -- we don't yet support
            // this.
            throw new RuntimeException(
              "Cannot copy data for ptr-to-ptr arg type \"" + cArgType +
              "\": support for non-const ptr-to-ptr types not implemented.");
          }

          writer.println();
          writer.println("    /* Copy contents of " + javaArgName + " into " + convName + "_copy */");

          // get length of array being copied
          final String arrayLenName = "_tmpArrayLen";
          writer.print("    ");
          writer.print(arrayLenName);
          writer.print(" = (*env)->GetArrayLength(env, ");
          writer.print(javaArgName);
          writer.println(");");

          // allocate an array to hold each element
          final Type cArgElementType, cArgElementType2;
          {
              int error = 0;
              if( cArgType.isPointer() ) {
                  cArgElementType = cArgType.asPointer().getTargetType();
                  if( cArgElementType.isPointer() ) {
                      // pointer-to-pointer
                      cArgElementType2 = cArgElementType.asPointer().getTargetType();
                      if( cArgElementType2.isPointer() ) {
                          error = 1;
                      }
                      if(cArgType.pointerDepth() != 2) {
                          error = 2;
                      }
                  } else {
                      cArgElementType2 = null;
                      if(cArgType.pointerDepth() != 1) {
                          error = 10;
                      }
                  }
              } else if( cArgType.isArray() ) {
                  cArgElementType = cArgType.asArray().getBaseElementType();
                  cArgElementType2 = null;
              } else {
                  cArgElementType = null;
                  cArgElementType2 = null;
                  error = 100;
              }
              if( 0 < error ) {
                throw new RuntimeException(
                  "Could not copy data for type \"" + cArgType +
                  "\"; currently only pointer- and array-types are supported. (error "+error+")");
              }
          }
          emitMalloc(
            writer,
            convName+"_copy",
            cArgElementType.getName(),
            isBaseTypeConst(cArgType),
            arrayLenName,
            "Could not allocate buffer for copying data in argument \\\""+javaArgName+"\\\"");

          // Get the handle for the byte offset array sent down for Buffers
          // FIXME: not 100% sure this is correct with respect to the
          // JNI spec because it may be illegal to call
          // GetObjectArrayElement while in a critical section. May
          // need to do another loop and add in the offsets.
          if (javaArgType.isNIOBufferArray()) {
            writer.println
              ("    _offsetHandle = (int *) (*env)->GetPrimitiveArrayCritical(env, " +
               byteOffsetArrayArgName(i) +
               ", NULL);");
          }

          // process each element in the array
          writer.println("    for (_copyIndex = 0; _copyIndex < "+arrayLenName+"; ++_copyIndex) {");

          // get each array element
          writer.println("      /* get each element of the array argument \"" + javaArgName + "\" */");
          writer.print("      _tmpObj = (*env)->GetObjectArrayElement(env, ");
          writer.print(javaArgName);
          writer.println(", _copyIndex);");

          if (javaArgType.isStringArray()) {
            writer.print("  ");
            emitGetStringChars(writer,
                               "(jstring) _tmpObj",
                               convName+"_copy[_copyIndex]",
                               isUTF8Type(cArgType),
                               true);
          } else if (javaArgType.isNIOBufferArray()) {
            /* We always assume an integer "byte offset" argument follows any Buffer
               in the method binding. */
            emitGetDirectBufferAddress(writer,
                                       "_tmpObj",
                                       cArgElementType.getName(),
                                       convName + "_copy[_copyIndex]",
                                       true,
                                       "_offsetHandle[_copyIndex]", true);
          } else if (javaArgType.isArrayOfCompoundTypeWrappers()) {
            // These come down in similar fashion to an array of NIO
            // Buffers only we do not pass down any integer byte
            // offset argument
            emitGetDirectBufferAddress(writer,
                                       "_tmpObj",
                                       cArgElementType.getName(),
                                       "("+convName + "_copy + _copyIndex)",
                                       false /* !receivingIsPtrPtr -> linear layout -> use memcpy */,
                                       null, true);
          } else {
            if( null == cArgElementType2 ) {
                throw new RuntimeException("XXX: Type "+cArgType+" not properly handled as ptr-to-ptr");
            }
            // Question: do we always need to copy the sub-arrays, or just
            // GetPrimitiveArrayCritical on each jobjectarray element and
            // assign it to the appropriate elements at pointer depth 1?
            // Probably depends on const-ness of the argument.
            // Malloc enough space to hold a copy of each sub-array
            writer.print("      ");
            emitMalloc(
                       writer,
                       convName+"_copy[_copyIndex]",
                       cArgElementType2.getName(), // assumes cArgPtrType is ptr-to-ptr-to-primitive !!
                       isBaseTypeConst(cArgType),
                       "(*env)->GetArrayLength(env, _tmpObj)",
                       "Could not allocate buffer during copying of data in argument \\\""+javaArgName+"\\\"");
            // FIXME: copy the data (use matched Get/ReleasePrimitiveArrayCritical() calls)
            if (true) {
                throw new RuntimeException("Cannot yet handle type \"" + cArgType.getName() +
                              "\"; need to add support for copying ptr-to-ptr-to-primitiveType subarrays");
            }

          }
          writer.println("    }");

          if (javaArgType.isNIOBufferArray()) {
            writer.println
              ("    (*env)->ReleasePrimitiveArrayCritical(env, " +
               byteOffsetArrayArgName(i) +
               ", _offsetHandle, JNI_ABORT);");
          }

          writer.println();
        } // end of data copy

        writer.println("  }");

      } else if (javaArgType.isString()) {
        emitGetStringChars(writer, javaArgName,
                           STRING_CHARS_PREFIX + javaArgName,
                           isUTF8Type(binding.getCArgumentType(i)),
                           false);
      }
    }
  }


  /**
   * Code to clean up any variables that were declared in
   * emitBodyVariableDeclarations(), AFTER calling the actual C function.
   */
  protected void emitBodyVariablePostCallCleanup(final PrintWriter writer) {

    // Release primitive arrays and temporary UTF8 strings if necessary
    for (int i = 0; i < binding.getNumArguments(); i++) {
      final JavaType javaArgType = binding.getJavaArgumentType(i);
      if (javaArgType.isJNIEnv() || binding.isArgumentThisPointer(i)) {
        continue;
      }

      final Type cArgType = binding.getCArgumentType(i);
      final String javaArgName = binding.getArgumentName(i);

      if (javaArgType.isArray() ||
          (javaArgType.isNIOBuffer() && forIndirectBufferAndArrayImplementation) ||
          javaArgType.isArrayOfCompoundTypeWrappers()) {
        final boolean needsDataCopy = javaArgTypeNeedsDataCopy(javaArgType);

        final String convName = pointerConversionArgumentName(javaArgName);

        if (!needsDataCopy) {
          writer.println("  if ( JNI_FALSE == " + isNIOArgName(i) + " && NULL != " + javaArgName + " ) {");

          // Release array
          final String modeFlag = isBaseTypeConst(cArgType) ? "JNI_ABORT" : "0" ;
          writer.print("    (*env)->ReleasePrimitiveArrayCritical(env, " + javaArgName + ", " + convName + ", "+modeFlag+");");
        } else {
          writer.println("  if ( NULL != " + javaArgName + " ) {");

          // clean up the case where the array elements are of a type that needed
          // a data copy operation to convert from the java memory model to the
          // C memory model (e.g., int[][], String[], etc)
          //
          // FIXME: should factor out this whole block of code into a separate
          // method for clarity and maintenance purposes
          if (!isBaseTypeConst(cArgType)) {
            // FIXME: handle any cleanup from treatment of non-const args,
            // assuming they were treated differently in
            // emitBodyVariablePreCallSetup() (see the similar section in that
            // method for details).
            if (javaArgType.isArrayOfCompoundTypeWrappers()) {
              // This is the only form of cleanup we handle right now
              writer.println("    _tmpArrayLen = (*env)->GetArrayLength(env, " + javaArgName + ");");
              writer.println("    for (_copyIndex = 0; _copyIndex < _tmpArrayLen; ++_copyIndex) {");
              writer.println("      _tmpObj = (*env)->GetObjectArrayElement(env, " + javaArgName + ", _copyIndex);");
              emitReturnDirectBufferAddress(writer,
                                          "_tmpObj",
                                          cArgType.asArray().getBaseElementType().getName(),
                                          "("+convName + "_copy + _copyIndex)",
                                          false /* receivingIsPtrPtr */,
                                          null);
              writer.println("    }");
            } else {
              throw new RuntimeException(
                "Cannot clean up copied data for ptr-to-ptr arg type \"" + cArgType +
                "\": support for cleaning up most non-const ptr-to-ptr types not implemented.");
            }
          }

          writer.println("    /* Clean up " + convName + "_copy */");

          // Only need to perform cleanup for individual array
          // elements if they are not direct buffers
          if (!javaArgType.isNIOBufferArray() &&
              !javaArgType.isArrayOfCompoundTypeWrappers()) {
            // Re-fetch length of array that was copied
            final String arrayLenName = "_tmpArrayLen";
            writer.print("    ");
            writer.print(arrayLenName);
            writer.print(" = (*env)->GetArrayLength(env, ");
            writer.print(javaArgName);
            writer.println(");");

            // free each element
            final PointerType cArgPtrType = cArgType.asPointer();
            if (cArgPtrType == null) {
              throw new RuntimeException(
                "Could not copy data for type \"" + cArgType +
                "\"; currently only pointer types supported.");
            }

            // process each element in the array
            writer.println("    for (_copyIndex = 0; _copyIndex < " + arrayLenName +"; ++_copyIndex) {");

            // get each array element
            writer.println("      /* free each element of " +convName +"_copy */");
            writer.print("      _tmpObj = (*env)->GetObjectArrayElement(env, ");
            writer.print(javaArgName);
            writer.println(", _copyIndex);");

            if (javaArgType.isStringArray()) {
              writer.print("     (*env)->ReleaseStringUTFChars(env, ");
              writer.print("(jstring) _tmpObj");
              writer.print(", ");
              writer.print(convName+"_copy[_copyIndex]");
              writer.println(");");
            } else {
              if (true) throw new RuntimeException(
                "Cannot yet handle type \"" + cArgType.getName() +
                "\"; need to add support for cleaning up copied ptr-to-ptr-to-primitiveType subarrays");
            }
            writer.println("    }");
          }

          // free the main array
          writer.print("    free((void*) ");
          writer.print(convName+"_copy");
          writer.println(");");
        } // end of cleaning up copied data

        writer.println("  }");

      } else if (javaArgType.isString()) {
        writer.println("  if ( NULL != " + javaArgName + " ) {");

        if (isUTF8Type(cArgType)) {
          writer.print("    (*env)->ReleaseStringUTFChars(env, ");
          writer.print(javaArgName);
          writer.print(", " + STRING_CHARS_PREFIX);
          writer.print(javaArgName);
          writer.println(");");
        } else {
          writer.println("    free((void*) " + STRING_CHARS_PREFIX + javaArgName + ");");
        }

        writer.println("  }");
      }
    }
  }

  /** Returns the number of arguments passed so calling code knows
      whether to print a comma */
  protected int emitBodyPassCArguments(final PrintWriter writer) {
    for (int i = 0; i < binding.getNumArguments(); i++) {
      if (i != 0) {
        writer.print(", ");
      }
      final JavaType javaArgType = binding.getJavaArgumentType(i);
      // Handle case where only param is void.
      if (javaArgType.isVoid()) {
        // Make sure this is the only param to the method; if it isn't,
        // there's something wrong with our parsing of the headers.
        assert(binding.getNumArguments() == 1);
        continue;
      }

      if (javaArgType.isJNIEnv()) {
        writer.print("env");
      } else if (binding.isArgumentThisPointer(i)) {
        writer.print(CMethodBindingEmitter.cThisArgumentName());
      } else {
        writer.print("(");
        final Type cArgType = binding.getCArgumentType(i);
        final boolean needsDataCopy = javaArgTypeNeedsDataCopy(javaArgType);
        final boolean needsArrayOffset = !needsDataCopy && (
                                     javaArgType.isArray() ||
                                     javaArgType.isArrayOfCompoundTypeWrappers() ||
                                     ( javaArgType.isNIOBuffer() && forIndirectBufferAndArrayImplementation ) );
        if (isBaseTypeConst(cArgType)) {
            writer.print("const ");
        }

        // if this is a pointer to an unsigned type, add unsigned to the name to avoid compiler warnings
        if(cArgType.isPointer()) {
          final Type baseType = cArgType.getBaseElementType();
          if(baseType.isInt() && (((IntType)baseType).isPrimitiveUnsigned())) {
            writer.print("unsigned ");
          }
        }

        writer.print(cArgType.getName());
        writer.print(") ");
        if (cArgType.isPointer() && javaArgType.isPrimitive()) {
          writer.print("(intptr_t) ");
        }
        if (javaArgType.isArray() || javaArgType.isNIOBuffer() ||
            javaArgType.isCompoundTypeWrapper() || javaArgType.isArrayOfCompoundTypeWrappers()) {
          if( needsArrayOffset ) {
              writer.print("(((char *) ");
          } else if( !cArgType.isPointer() && javaArgType.isCompoundTypeWrapper() ) { // FIXME: Compound call-by-value
              writer.print("*");
          }
          writer.print(pointerConversionArgumentName(binding.getArgumentName(i)));
          if ( needsDataCopy ) {
            writer.print("_copy");
          }
          if( needsArrayOffset ) {
              writer.print(") + " + byteOffsetArgName(i) + ")");
          }
        } else {
          if (javaArgType.isString()) { writer.print(STRING_CHARS_PREFIX); }
          writer.print(binding.getArgumentName(i));
        }
      }
    }
    return binding.getNumArguments();
  }

  private boolean isCStructFunctionPointer = false;

  /**
   * If method originates from a struct, see {@link MethodBinding#hasContainingType()},
   * it can either purposed to call a native static function (default)
   * or a struct's function pointer.
   */
  protected void setIsCStructFunctionPointer(final boolean v) {
      isCStructFunctionPointer = v;
  }

  protected void emitBodyCallCFunction(final PrintWriter writer) {
    // Make the call to the actual C function
    writer.print("  ");

    // WARNING: this code assumes that the return type has already been
    // typedef-resolved.
    final Type cReturnType = binding.getCReturnType();

    if (!cReturnType.isVoid()) {
      writer.print("_res = ");
    }
    if ( isCStructFunctionPointer && binding.hasContainingType() ) {
      // Call through function pointer
      writer.print(CMethodBindingEmitter.cThisArgumentName() + "->");
    }
    writer.print(binding.getCSymbol().getName());
    writer.print("(");
    emitBodyPassCArguments(writer);
    writer.println(");");
  }

  /** Emits the user-defined C variable assignments from the
      TemporaryCVariableAssignments directive in the .cfg file. */
  protected void emitBodyUserVariableAssignments(final PrintWriter writer) {
    if (temporaryCVariableAssignments != null) {
      for (final String val : temporaryCVariableAssignments) {
        writer.print("  ");
        writer.println(val);
      }
    }
  }

  protected void emitBodyReturnResult(final PrintWriter writer)
  {
    // WARNING: this code assumes that the return type has already been
    // typedef-resolved.
    final Type cReturnType = binding.getCReturnType();

    // Return result if necessary
    if (!cReturnType.isVoid()) {
      final JavaType javaReturnType = binding.getJavaReturnType();
      if (javaReturnType.isPrimitive()) {
        writer.print("  return ");
        if (cReturnType.isPointer()) {
          // Pointer being converted to int or long: cast this result
          // (through intptr_t to avoid compiler warnings with gcc)
          writer.print("(" + javaReturnType.jniTypeName() + ") (intptr_t) ");
        }
        writer.println("_res;");
      } else if ( !cReturnType.isPointer() && javaReturnType.isCompoundTypeWrapper() ) { // FIXME: Compound call-by-value
        final String returnSizeOf;
        if (returnValueCapacityExpression != null) {
            returnSizeOf = returnValueCapacityExpression.format(argumentNameArray());
        } else {
            returnSizeOf = "sizeof(" + cReturnType.getName() + ")";
        }
        writer.println("  return JVMUtil_NewDirectByteBufferCopy(env, &_res, "+returnSizeOf+");");
      } else if (javaReturnType.isNIOBuffer() || javaReturnType.isCompoundTypeWrapper()) {
        writer.println("  if (NULL == _res) return NULL;");
        writer.print("  return (*env)->NewDirectByteBuffer(env, (void *)_res, ");

        // See whether capacity has been specified
        if (returnValueCapacityExpression != null) {
          writer.print( returnValueCapacityExpression.format( argumentNameArray() ) );
        } else {
          if (cReturnType.isPointer() &&
              cReturnType.asPointer().getTargetType().isCompound()) {
            if (cReturnType.asPointer().getTargetType().getSize() == null) {
              throw new RuntimeException(
                "Error emitting code for compound return type "+
                "for function \"" + binding + "\": " +
                "Structs to be emitted should have been laid out by this point " +
                "(type " + cReturnType.asPointer().getTargetType().getName() + " / " +
                cReturnType.asPointer().getTargetType() + " was not) for "+binding
              );
            }
          }
          writer.print("sizeof(" + cReturnType.getName() + ")");
          LOG.warning(
            "No capacity specified for java.nio.Buffer return " +
            "value for function \"" + binding.getName() + "\"" +
            " assuming size of equivalent C return type (sizeof(" + cReturnType.getName() + ")): " + binding);
        }
        writer.println(");");
      } else if (javaReturnType.isString()) {
        writer.println("  if (NULL == _res) return NULL;");
        writer.println("  return (*env)->NewStringUTF(env, _res);");
      } else if (javaReturnType.isArrayOfCompoundTypeWrappers() ||
                 (javaReturnType.isArray() && javaReturnType.isNIOByteBufferArray())) {
        writer.println("  if (NULL == _res) return NULL;");
        if (returnValueLengthExpression == null) {
          throw new RuntimeException("Error while generating C code: no length specified for array returned from function " +
                                     binding);
        }
        writer.println("  " + arrayResLength + " = " + returnValueLengthExpression.format(argumentNameArray()) + ";");
        writer.println("  " + arrayRes + " = (*env)->NewObjectArray(env, " + arrayResLength + ", (*env)->FindClass(env, \"java/nio/ByteBuffer\"), NULL);");
        writer.println("  for (" + arrayIdx + " = 0; " + arrayIdx + " < " + arrayResLength + "; " + arrayIdx + "++) {");
        final Type retType = binding.getCSymbol().getReturnType();
        Type pointerType;
        if (retType.isPointer()) {
          pointerType = retType.asPointer().getTargetType();
        } else {
          pointerType = retType.asArray().getBaseElementType();
        }
        writer.println("    (*env)->SetObjectArrayElement(env, " + arrayRes + ", " + arrayIdx +
                       ", (*env)->NewDirectByteBuffer(env, (void *)_res[" + arrayIdx + "], sizeof(" + pointerType.getName() + ")));");
        writer.println("  }");
        writer.println("  return " + arrayRes + ";");
      } else if (javaReturnType.isArray()) {
        // FIXME: must have user provide length of array in .cfg file
        // by providing a constant value, input parameter, or
        // expression which computes the array size (already present
        // as ReturnValueCapacity, not yet implemented / tested here)

        throw new RuntimeException(
                                   "Could not emit native code for function \"" + binding +
                                   "\": array return values for non-char types not implemented yet, for "+binding);

        // FIXME: This is approximately what will be required here
        //
        //writer.print("  ");
        //writer.print(arrayRes);
        //writer.print(" = (*env)->New");
        //writer.print(capitalizedComponentType);
        //writer.print("Array(env, ");
        //writer.print(arrayResLength);
        //writer.println(");");
        //writer.print("  (*env)->Set");
        //writer.print(capitalizedComponentType);
        //writer.print("ArrayRegion(env, ");
        //writer.print(arrayRes);
        //writer.print(", 0, ");
        //writer.print(arrayResLength);
        //writer.println(", _res);");
        //writer.print("  return ");
        //writer.print(arrayRes);
        //writer.println(";");
      } else {
        System.err.print("Unhandled return type: "+javaReturnType.getDebugString());
        throw new RuntimeException("Unhandled return type: "+javaReturnType.getDebugString()+" for "+binding);
      }
    }
  }

  protected static String cThisArgumentName() {
    return "this0";
  }

  protected String jniMangle(final MethodBinding binding) {
    final StringBuilder buf = new StringBuilder();
    buf.append(JavaEmitter.jniMangle(getName()));
    buf.append(getImplSuffix());
    buf.append("__");
    if (binding.hasContainingType()) {
      // "this" argument always comes down in argument 0 as direct buffer
      jniMangle(java.nio.ByteBuffer.class, buf, true);
    }
    for (int i = 0; i < binding.getNumArguments(); i++) {
      if (binding.isArgumentThisPointer(i)) {
        continue;
      }
      final JavaType type = binding.getJavaArgumentType(i);
      if (type.isVoid()) {
        // We should only see "void" as the first argument of a 1-argument function
        // FIXME: should normalize this in the parser
        if ((i != 0) || (binding.getNumArguments() > 1)) {
          throw new RuntimeException("Saw illegal \"void\" argument while emitting \"" + getName() + "\"");
        }
      } else {
        Class<?> c = type.getJavaClass();
        if (c != null) {
          jniMangle(c, buf, false);
          // If Buffer offset arguments were added, we need to mangle the JNI for the
          // extra arguments
          if (type.isNIOBuffer()) {
            jniMangle(Integer.TYPE, buf, false);
            if(forIndirectBufferAndArrayImplementation) {
                jniMangle(Boolean.TYPE, buf, false);
            }
          } else if (type.isNIOBufferArray())   {
            final int[] intArrayType = new int[0];
            c = intArrayType.getClass();
            jniMangle(c , buf, true);
          }
          if (type.isPrimitiveArray()) {
            jniMangle(Integer.TYPE, buf, false);
          }
        } else if (type.isCompoundTypeWrapper()) {
          // Mangle wrappers for C structs as ByteBuffer
          jniMangle(java.nio.ByteBuffer.class, buf, true);
        } else if (type.isArrayOfCompoundTypeWrappers()) {
          // Mangle arrays of C structs as ByteBuffer[]
          final java.nio.ByteBuffer[] tmp = new java.nio.ByteBuffer[0];
          jniMangle(tmp.getClass(), buf, true);
        } else if (type.isJNIEnv()) {
          // These are not exposed at the Java level
        } else {
          // FIXME: add support for char* -> String conversion
          throw new RuntimeException("Unknown kind of JavaType: name="+type.getName());
        }
      }
    }

    return buf.toString();
  }

  protected void jniMangle(final Class<?> c, final StringBuilder res, final boolean syntheticArgument) {
    if (c.isPrimitive()) {
           if (c == Boolean.TYPE)   res.append("Z");
      else if (c == Byte.TYPE)      res.append("B");
      else if (c == Character.TYPE) res.append("C");
      else if (c == Short.TYPE)     res.append("S");
      else if (c == Integer.TYPE)   res.append("I");
      else if (c == Long.TYPE)      res.append("J");
      else if (c == Float.TYPE)     res.append("F");
      else if (c == Double.TYPE)    res.append("D");
      else throw new RuntimeException("Illegal primitive type \"" + c.getName() + "\"");
    } else {
      // Arrays and NIO Buffers are always passed down as java.lang.Object.
      // The only arrays that show up as true arrays in the signature
      // are the synthetic byte offset arrays created when passing
      // down arrays of direct Buffers. Compound type wrappers are
      // passed down as ByteBuffers (no good reason, just to avoid
      // accidental conflation) so we mangle them differently.
      if (syntheticArgument) {
        if (c.isArray()) {
          res.append("_3");
          final Class<?> componentType = c.getComponentType();
          // Handle arrays of compound type wrappers differently for
          // convenience of the Java-level glue code generation
          jniMangle(componentType, res,
                    (componentType == java.nio.ByteBuffer.class));
        } else {
          res.append("L");
          res.append(c.getName().replace('.', '_'));
          res.append("_2");
        }
      } else {
        if (c.isArray()) {
          res.append("_3");
          jniMangle(c.getComponentType(), res, false);
        } else if (c == java.lang.String.class) {
          res.append("L");
          res.append(c.getName().replace('.', '_'));
          res.append("_2");
        } else {
          res.append("L");
          res.append("java_lang_Object");
          res.append("_2");
        }
      }
    }
  }

  private void emitOutOfMemoryCheck(final PrintWriter writer, final String varName, final String errorMessage)  {
    writer.println("  if ( NULL == " + varName + " ) {");
    writer.println("      (*env)->ThrowNew(env, (*env)->FindClass(env, \"java/lang/OutOfMemoryError\"),");
    writer.print("                       \"" + errorMessage);
    writer.print(" in native dispatcher for \\\"");
    writer.print(getName());
    writer.println("\\\"\");");
    writer.print("      return");
    if (!binding.getJavaReturnType().isVoid()) {
      writer.print(" 0");
    }
    writer.println(";");
    writer.println("    }");
  }

  private void emitMalloc(final PrintWriter writer,
                          final String targetVarName,
                          final String elementTypeString,
                          final boolean elementTypeIsConst,
                          final String numElementsExpression,
                          final String mallocFailureErrorString)  {
    writer.print("    ");
    writer.print(targetVarName);
    writer.print(" = (");
    if(elementTypeIsConst) {
        writer.print("const ");
    }
    writer.print(elementTypeString);
    writer.print(" *) malloc(");
    writer.print(numElementsExpression);
    writer.print(" * sizeof(");
    writer.print(elementTypeString);
    writer.println("));");
    // Catch memory allocation failure
    emitOutOfMemoryCheck( writer, targetVarName, mallocFailureErrorString);
  }

  private void emitCalloc(final PrintWriter writer,
                          final String targetVarName,
                          final String elementTypeString,
                          final String numElementsExpression,
                          final String mallocFailureErrorString)  {
    writer.print("    ");
    writer.print(targetVarName);
    writer.print(" = (");
    writer.print(elementTypeString);
    writer.print(" *) calloc(");
    writer.print(numElementsExpression);
    writer.print(", sizeof(");
    writer.print(elementTypeString);
    writer.println("));");
    // Catch memory allocation failure
    emitOutOfMemoryCheck( writer, targetVarName, mallocFailureErrorString);
  }

  private void emitGetStringChars(final PrintWriter writer,
                                  final String sourceVarName,
                                  final String receivingVarName,
                                  final boolean isUTF8,
                                  final boolean emitElseClause)  {
    writer.println("  if ( NULL != " + sourceVarName + " ) {");

    if (isUTF8) {
      writer.print("    ");
      writer.print(receivingVarName);
      writer.print(" = (*env)->GetStringUTFChars(env, ");
      writer.print(sourceVarName);
      writer.println(", (jboolean*)NULL);");
      // Catch memory allocation failure in the event that the VM didn't pin
      // the String and failed to allocate a copy
      emitOutOfMemoryCheck( writer, receivingVarName, "Failed to get UTF-8 chars for argument \\\""+sourceVarName+"\\\"");
    } else {
      // The UTF-16 case is basically Windows specific. Unix platforms
      // tend to use only the UTF-8 encoding. On Windows the problem
      // is that wide character strings are expected to be null
      // terminated, but the JNI GetStringChars doesn't return a
      // null-terminated Unicode string. For this reason we explicitly
      // calloc our buffer, including the null terminator, and use
      // GetStringRegion to fetch the string's characters.
      emitCalloc(writer,
                 receivingVarName,
                 "jchar",
                 "(*env)->GetStringLength(env, " + sourceVarName + ") + 1",
                 "Could not allocate temporary buffer for copying string argument \\\""+sourceVarName+"\\\"");
      writer.println("    (*env)->GetStringRegion(env, " + sourceVarName + ", 0, (*env)->GetStringLength(env, " + sourceVarName + "), " + receivingVarName + ");");
    }
    writer.print("  }");
    if (emitElseClause) {
      writer.print(" else {");
      writer.print("      ");
      writer.print(receivingVarName);
      writer.println(" = NULL;");
      writer.println("  }");
    } else {
      writer.println();
    }
  }

  private void emitGetDirectBufferAddress(final PrintWriter writer,
                                          final String sourceVarName,
                                          final String receivingVarTypeString,
                                          final String receivingVarName,
                                          final boolean receivingIsPtrPtr,
                                          final String byteOffsetVarName, final boolean emitElseClause) {
    writer.println("    if ( NULL != " + sourceVarName + " ) {");
    writer.print("    ");
    writer.print("    ");
    if( receivingIsPtrPtr ) {
        writer.print(receivingVarName+" = ("+receivingVarTypeString+") (((char*) (*env)->GetDirectBufferAddress(env, "+sourceVarName+"))");
        writer.println(" + " + ((byteOffsetVarName != null) ? byteOffsetVarName : "0") + ");");
    } else {
        // linear layout -> use memcpy
        writer.println("memcpy((void *)"+receivingVarName+", (*env)->GetDirectBufferAddress(env, "+sourceVarName+"), sizeof("+receivingVarTypeString+"));");
    }

    if (emitElseClause) {
      writer.println("    } else {");
      writer.print("    ");
      writer.print("    ");
      if( receivingIsPtrPtr ) {
          writer.print(receivingVarName);
          writer.println(" = NULL;");
      } else {
          writer.println("memset((void *)"+receivingVarName+", 0, sizeof("+receivingVarTypeString+"));");
      }
    }
    writer.println("    }");
    writer.println();
  }

  private void emitReturnDirectBufferAddress(final PrintWriter writer,
                                          final String sourceVarName,
                                          final String receivingVarTypeString,
                                          final String receivingVarName,
                                          final boolean receivingIsPtrPtr,
                                          final String byteOffsetVarName) {
    writer.print("    ");
    writer.print("    ");
    if( receivingIsPtrPtr ) {
        writer.print("(((char*) (*env)->GetDirectBufferAddress(env, "+sourceVarName+"))");
        writer.println(" + " + ((byteOffsetVarName != null) ? byteOffsetVarName : "0") + ") = "+receivingVarName+";");
        throw new RuntimeException("incomplete implementation"); // FIXME doesn't work, currently unused
    } else {
        // linear layout -> use memcpy
        writer.println("memcpy((*env)->GetDirectBufferAddress(env, "+sourceVarName+"), "+receivingVarName+", sizeof("+receivingVarTypeString+"));");
    }
    writer.println();
  }

  // Note: if the data in the Type needs to be converted from the Java memory
  // model to the C memory model prior to calling any C-side functions, then
  // an extra variable named XXX_copy (where XXX is the value of the
  // cVariableName argument) will be emitted and TRUE will be returned.
  private boolean emitPointerDeclaration(final PrintWriter writer,
                                         final JavaType javaType,
                                         final Type cType,
                                         final String cVariableName,
                                         final String javaArgumentName) {
    String ptrTypeString = null;
    boolean needsDataCopy = false;

    // Emit declaration for the pointer variable.
    //
    // Note that we don't need to obey const/volatile for outgoing arguments
    //
    if (javaType.isNIOBuffer()) {
      ptrTypeString = cType.getName();
    } else if (javaType.isArray() || javaType.isArrayOfCompoundTypeWrappers()) {
      needsDataCopy = javaArgTypeNeedsDataCopy(javaType);
      if (javaType.isPrimitiveArray() ||
          javaType.isNIOBufferArray() ||
          javaType.isArrayOfCompoundTypeWrappers()) {
        ptrTypeString = cType.getName();
      } else if (!javaType.isStringArray()) {
        final Class<?> elementType = javaType.getJavaClass().getComponentType();
        if (elementType.isArray()) {
          final Class<?> subElementType = elementType.getComponentType();
          if (subElementType.isPrimitive()) {
            // type is pointer to pointer to primitive
            ptrTypeString = cType.getName();
          } else {
            // type is pointer to pointer of some type we don't support (maybe
            // it's an array of pointers to structs?)
            throw new RuntimeException("Unsupported pointer type: \"" + cType.getName() + "\"");
          }
        } else {
          // type is pointer to pointer of some type we don't support (maybe
          // it's an array of pointers to structs?)
          throw new RuntimeException("Unsupported pointer type: \"" + cType.getName() + "\"");
        }
      }
    } else {
      ptrTypeString = cType.getName();
    }

    writer.print("  ");
    if (!needsDataCopy) {
      // declare the pointer variable
      writer.print(ptrTypeString);
      if( !cType.isPointer() && javaType.isCompoundTypeWrapper() ) { // FIXME: Compound call-by-value
          writer.print(" * ");
      } else {
          writer.print(" ");
      }
      writer.print(cVariableName);
      writer.println(" = NULL;");
    } else {
      // Declare a variable to hold a copy of the argument data in which the
      // incoming data has been properly laid out in memory to match the C
      // memory model
      if (javaType.isStringArray()) {
        String cElementTypeName = "char *";
        final PointerType cPtrType = cType.asPointer();
        if (cPtrType != null) {
            cElementTypeName = cPtrType.getTargetType().asPointer().getName();
        }
        if (isBaseTypeConst(cType)) {
            writer.print("const ");
        }
        writer.print(cElementTypeName+" *");
      } else {
        if (isBaseTypeConst(cType)) {
            writer.print("const ");
        }
        writer.print(ptrTypeString);
      }
      writer.print(" ");
      writer.print(cVariableName);
      writer.print("_copy = NULL; /* copy of data in ");
      writer.print(javaArgumentName);
      writer.println(", laid out according to C memory model */");
    }

    return needsDataCopy;
  }

  private void emitPointerConversion(final PrintWriter writer,
                                     final MethodBinding binding,
                                     final JavaType type,
                                     final Type cType,
                                     final String incomingArgumentName,
                                     final String cVariableName,
                                     String byteOffsetVarName) {
    // Compound type wrappers do not get byte offsets added on
    if (type.isCompoundTypeWrapper()) {
      byteOffsetVarName = null;
    }

    final String cVariableType;
    if( !cType.isPointer() && type.isCompoundTypeWrapper() ) { // FIXME: Compound call-by-value
        cVariableType = cType.getName()+" *";
    } else {
        cVariableType = cType.getName();
    }
    emitGetDirectBufferAddress(writer,
                               incomingArgumentName,
                               cVariableType,
                               cVariableName,
                               true,
                               byteOffsetVarName, false);
  }

  protected String byteOffsetArgName(final int i) {
    return JavaMethodBindingEmitter.byteOffsetArgName(binding.getArgumentName(i));
  }

  protected String isNIOArgName(final int i) {
    return isNIOArgName(binding.getArgumentName(i));
  }

  protected String isNIOArgName(final String s) {
    return s + "_is_nio";
  }

  protected String byteOffsetArrayArgName(final int i) {
    return binding.getArgumentName(i) + "_byte_offset_array";
  }

  protected String[] argumentNameArray() {
    final String[] argumentNames = new String[binding.getNumArguments()];
    for (int i = 0; i < binding.getNumArguments(); i++) {
      argumentNames[i] = binding.getArgumentName(i);
      if (binding.getJavaArgumentType(i).isPrimitiveArray()) {
        // Add on _offset argument in comma-separated expression
        argumentNames[i] = argumentNames[i] + ", " + byteOffsetArgName(i);
      }
    }
    return argumentNames;
  }

  protected String pointerConversionArgumentName(final String argName) {
    return "_" + argName + "_ptr";
  }

  /**
   * Class that emits a generic comment for CMethodBindingEmitters; the comment
   * includes the C signature of the native method that is being bound by the
   * emitter java method.
   */
  protected static class DefaultCommentEmitter implements CommentEmitter {
    @Override
    public void emit(final FunctionEmitter emitter, final PrintWriter writer) {
      emitBeginning((CMethodBindingEmitter)emitter, writer);
      emitEnding((CMethodBindingEmitter)emitter, writer);
    }
    protected void emitBeginning(final CMethodBindingEmitter emitter, final PrintWriter writer) {
      writer.println("  Java->C glue code:");
      writer.print(" *   Java package: ");
      writer.print(emitter.getJavaPackageName());
      writer.print(".");
      writer.println(emitter.getJavaClassName());
      writer.print(" *    Java method: ");
      final MethodBinding binding = emitter.getBinding();
      writer.println(binding);
      writer.println(" *     C function: " + binding.getCSymbol());
    }
    protected void emitEnding(final CMethodBindingEmitter emitter, final PrintWriter writer) {
    }
  }

  protected boolean javaArgTypeNeedsDataCopy(final JavaType javaArgType) {
    if (javaArgType.isArray()) {
      return (javaArgType.isNIOBufferArray() ||
              javaArgType.isStringArray() ||
              javaArgType.getJavaClass().getComponentType().isArray());
    }
    if (javaArgType.isArrayOfCompoundTypeWrappers()) {
      return true;
    }
    return false;
  }
}
