/*
 * 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.sun.gluegen;

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

import com.sun.gluegen.cgram.types.*;
import com.sun.gluegen.cgram.*;

/**
 * An emitter that emits only the interface for a Java<->C JNI binding.
 */
public class JavaMethodBindingEmitter extends FunctionEmitter
{
  public static final EmissionModifier PUBLIC = new EmissionModifier("public");
  public static final EmissionModifier PROTECTED = new EmissionModifier("protected");
  public static final EmissionModifier PRIVATE = new EmissionModifier("private");
  public static final EmissionModifier ABSTRACT = new EmissionModifier("abstract");
  public static final EmissionModifier FINAL = new EmissionModifier("final");
  public static final EmissionModifier NATIVE = new EmissionModifier("native");
  public static final EmissionModifier SYNCHRONIZED = new EmissionModifier("synchronized");

  protected final CommentEmitter defaultJavaCommentEmitter = new DefaultCommentEmitter();
  protected final CommentEmitter defaultInterfaceCommentEmitter =
    new InterfaceCommentEmitter();
  
  // Exception type raised in the generated code if runtime checks fail
  private String runtimeExceptionType;

  protected boolean emitBody;
  protected boolean eraseBufferAndArrayTypes;
  protected boolean directNIOOnly;
  protected boolean forImplementingMethodCall;
  protected boolean forDirectBufferImplementation;
  protected boolean forIndirectBufferAndArrayImplementation;
  protected boolean isUnimplemented;
  protected boolean tagNativeBinding;

  protected MethodBinding binding;

  // Manually-specified prologue and epilogue code
  protected List/*<String>*/ prologue;
  protected List/*<String>*/ epilogue;

  // A non-null value indicates that rather than returning a compound
  // type accessor we are returning an array of such accessors; this
  // expression is a MessageFormat string taking the names of the
  // incoming Java arguments as parameters and computing as an int the
  // number of elements of the returned array.
  private String returnedArrayLengthExpression;

  public JavaMethodBindingEmitter(MethodBinding binding,
                                  PrintWriter output,
                                  String runtimeExceptionType,
                                  boolean emitBody,
                                  boolean tagNativeBinding,
                                  boolean eraseBufferAndArrayTypes,
                                  boolean directNIOOnly,
                                  boolean forImplementingMethodCall,
                                  boolean forDirectBufferImplementation,
                                  boolean forIndirectBufferAndArrayImplementation,
                                  boolean isUnimplemented)
  {
    super(output);
    this.binding = binding;
    this.runtimeExceptionType = runtimeExceptionType;
    this.emitBody = emitBody;
    this.tagNativeBinding = tagNativeBinding;
    this.eraseBufferAndArrayTypes = eraseBufferAndArrayTypes;
    this.directNIOOnly = directNIOOnly;
    this.forImplementingMethodCall = forImplementingMethodCall;
    this.forDirectBufferImplementation = forDirectBufferImplementation;
    this.forIndirectBufferAndArrayImplementation = forIndirectBufferAndArrayImplementation;
    this.isUnimplemented = isUnimplemented;
    if (forImplementingMethodCall) {
      setCommentEmitter(defaultJavaCommentEmitter);
    } else {
      setCommentEmitter(defaultInterfaceCommentEmitter);
    }
  }
  
  public JavaMethodBindingEmitter(JavaMethodBindingEmitter arg) {
    super(arg);
    binding                       = arg.binding;
    runtimeExceptionType          = arg.runtimeExceptionType;
    emitBody                      = arg.emitBody;
    tagNativeBinding              = arg.tagNativeBinding;
    eraseBufferAndArrayTypes      = arg.eraseBufferAndArrayTypes;
    directNIOOnly                 = arg.directNIOOnly;
    forImplementingMethodCall     = arg.forImplementingMethodCall;
    forDirectBufferImplementation = arg.forDirectBufferImplementation;
    forIndirectBufferAndArrayImplementation = arg.forIndirectBufferAndArrayImplementation;
    isUnimplemented               = arg.isUnimplemented;
    prologue                      = arg.prologue;
    epilogue                      = arg.epilogue;
    returnedArrayLengthExpression = arg.returnedArrayLengthExpression;
  }

  public final MethodBinding getBinding() { return binding; }

  public boolean isForImplementingMethodCall() { return forImplementingMethodCall; }
  public boolean isForDirectBufferImplementation() { return forDirectBufferImplementation; }
  public boolean isForIndirectBufferAndArrayImplementation() { return forIndirectBufferAndArrayImplementation; }

  public String getName() {
    return binding.getRenamedMethodName();
  }

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

  /** The type of exception (must subclass
      <code>java.lang.RuntimeException</code>) raised if runtime
      checks fail in the generated code. */
  public String getRuntimeExceptionType() {
    return runtimeExceptionType;
  }

  /** If the underlying function returns an array (currently only
      arrays of compound types are supported) as opposed to a pointer
      to an object, this method should be called to provide a
      MessageFormat string containing an expression that computes the
      number of elements of the returned array. The parameters to the
      MessageFormat expression are the names of the incoming Java
      arguments. */
  public void setReturnedArrayLengthExpression(String expr) {
    returnedArrayLengthExpression = expr;
  }

  /** Sets the manually-generated prologue code for this emitter. */
  public void setPrologue(List/*<String>*/ prologue) {
    this.prologue = prologue;
  }

  /** Sets the manually-generated epilogue code for this emitter. */
  public void setEpilogue(List/*<String>*/ epilogue) {
    this.epilogue = epilogue;
  }

  /** Indicates whether this emitter will print only a signature, or
      whether it will emit Java code for the body of the method as
      well. */
  public boolean signatureOnly() {
    return !emitBody;
  }

  /** Accessor for subclasses. */
  public void setEmitBody(boolean emitBody) {
    this.emitBody = emitBody;
  }

  /** Accessor for subclasses. */
  public void setEraseBufferAndArrayTypes(boolean erase) {
    this.eraseBufferAndArrayTypes = erase;
  }

  /** Accessor for subclasses. */
  public void setForImplementingMethodCall(boolean impl) {
    this.forImplementingMethodCall = impl;
  }

  /** Accessor for subclasses. */
  public void setForDirectBufferImplementation(boolean direct) {
    this.forDirectBufferImplementation = direct;
  }

  /** Accessor for subclasses. */
  public void setForIndirectBufferAndArrayImplementation(boolean indirect) {
    this.forIndirectBufferAndArrayImplementation = indirect;
  }

  protected void emitReturnType(PrintWriter writer)
  {    
    writer.print(getReturnTypeString(false));
  }

  protected String erasedTypeString(JavaType type, boolean skipBuffers) {
    if (eraseBufferAndArrayTypes) {
      if (type.isNIOBuffer() ||
          type.isPrimitiveArray()) {
        if (!skipBuffers) {
          // Direct buffers and arrays sent down as Object (but
          // returned as e.g. ByteBuffer)
          return "Object";
        }
      } else if (type.isNIOBufferArray()) {
        // Arrays of direct Buffers sent down as Object[]
        // (Note we don't yet support returning void**)
        return "Object[]";
      } else if (type.isCompoundTypeWrapper()) {
        // Compound type wrappers are unwrapped to ByteBuffer
        return "java.nio.ByteBuffer";
      } else if (type.isArrayOfCompoundTypeWrappers()) {
        return "java.nio.ByteBuffer";
      }
    }
    return type.getName();
  }

  protected String getReturnTypeString(boolean skipArray) {
    // The first arm of the "if" clause is used by the glue code
    // generation for arrays of compound type wrappers
    if (skipArray ||
    // The following arm is used by most other kinds of return types
        (getReturnedArrayLengthExpression() == null && 
         !binding.getJavaReturnType().isArrayOfCompoundTypeWrappers()) ||
    // The following arm is used specifically to get the splitting up
    // of one returned ByteBuffer into an array of compound type
    // wrappers to work (e.g., XGetVisualInfo)
        (eraseBufferAndArrayTypes &&
         binding.getJavaReturnType().isCompoundTypeWrapper() &&
         (getReturnedArrayLengthExpression() != null))) {
      return erasedTypeString(binding.getJavaReturnType(), true);
    }
    return erasedTypeString(binding.getJavaReturnType(), true) + "[]";
  }

  protected void emitName(PrintWriter writer)
  {
    if (forImplementingMethodCall) {
      if (forIndirectBufferAndArrayImplementation) {
        writer.print(getImplMethodName(false));
      } else {
        writer.print(getImplMethodName(true));
      }
    } else {
      writer.print(getName());
    }
  }

  protected int emitArguments(PrintWriter writer)
  {
    boolean needComma = false;
    int numEmitted = 0;

    if (forImplementingMethodCall  && binding.hasContainingType()) {
      // Always emit outgoing "this" argument
      writer.print("java.nio.ByteBuffer ");
      writer.print(javaThisArgumentName());      
      ++numEmitted;
      needComma = true;
    }

    for (int i = 0; i < binding.getNumArguments(); i++) {
      JavaType type = binding.getJavaArgumentType(i);
      if (type.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.
        if (binding.getNumArguments() != 1) {
          throw new InternalError(
            "\"void\" argument type found in " +
            "multi-argument function \"" + binding + "\"");
        }
        continue;
      } 

      if (type.isJNIEnv() || binding.isArgumentThisPointer(i)) {
        // Don't need to expose these at the Java level
        continue;
      }

      if (needComma) {
        writer.print(", ");
      }

      writer.print(erasedTypeString(type, false));
      writer.print(" ");
      writer.print(getArgumentName(i));

      ++numEmitted;
      needComma = true;

      // Add Buffer and array index offset arguments after each associated argument
      if (forDirectBufferImplementation || forIndirectBufferAndArrayImplementation) {
        if (type.isNIOBuffer()) {
          writer.print(", int " + byteOffsetArgName(i));
        } else if (type.isNIOBufferArray()) {
          writer.print(", int[] " + 
                       byteOffsetArrayArgName(i));
        }
      }

      // Add offset argument after each primitive array
      if (type.isPrimitiveArray()) {
        writer.print(", int " + offsetArgName(i));
      }
    }
    return numEmitted;
  }


  protected String getImplMethodName(boolean direct) {
    if (direct) {
      return binding.getRenamedMethodName() + "0";
    } else {
      return binding.getRenamedMethodName() + "1";
    }
  }

  protected String byteOffsetArgName(int i) {
    return byteOffsetArgName(getArgumentName(i));
  }

  protected String byteOffsetArgName(String s) {
    return s + "_byte_offset";
  }
                                                                                                            
  protected String byteOffsetArrayArgName(int i) {
    return getArgumentName(i) + "_byte_offset_array";
  }
                                                                                                            
  protected String offsetArgName(int i) {
    return getArgumentName(i) + "_offset";
  }

  protected void emitBody(PrintWriter writer)
  {
    if (!emitBody) {
      writer.println(';');
    } else {
      MethodBinding binding = getBinding();
      writer.println();
      writer.println("  {");
      if (isUnimplemented) {
        writer.println("    throw new " + getRuntimeExceptionType() + "(\"Unimplemented\");");
      } else {
        emitPrologueOrEpilogue(prologue, writer);
        emitPreCallSetup(binding, writer);
        //emitReturnVariableSetup(binding, writer);
        emitReturnVariableSetupAndCall(binding, writer);
      }
      writer.println("  }");
    }
  }

  protected void emitPrologueOrEpilogue(List/*<String>*/ code, PrintWriter writer) {
    if (code != null) {
      String[] argumentNames = argumentNameArray();
      for (Iterator iter = code.iterator(); iter.hasNext(); ) {
        MessageFormat fmt = new MessageFormat((String) iter.next());
        writer.println("    " + fmt.format(argumentNames));
      }
    }
  }

  protected void emitPreCallSetup(MethodBinding binding, PrintWriter writer) {
    emitArrayLengthAndNIOBufferChecks(binding, writer);
  }

  protected void emitArrayLengthAndNIOBufferChecks(MethodBinding binding, PrintWriter writer) {
    int numBufferOffsetArrayArgs = 0;
    boolean firstBuffer = true;
    // Check lengths of any incoming arrays if necessary
    for (int i = 0; i < binding.getNumArguments(); i++) {
      Type type = binding.getCArgumentType(i);
      if (type.isArray()) {
        ArrayType arrayType = type.asArray();
        writer.println("    if (" + getArgumentName(i) + ".length < " +
                       arrayType.getLength() + ")");
        writer.println("      throw new " + getRuntimeExceptionType() +
                       "(\"Length of array \\\"" + getArgumentName(i) +
                       "\\\" was less than the required " + arrayType.getLength() + "\");");
      } else {
        JavaType javaType = binding.getJavaArgumentType(i);
        if (javaType.isNIOBuffer()) {
          if (directNIOOnly) {
            writer.println("    if (!BufferFactory.isDirect(" + getArgumentName(i) + "))");
            writer.println("      throw new " + getRuntimeExceptionType() + "(\"Argument \\\"" +
                           getArgumentName(i) + "\\\" was not a direct buffer\");");
          } else {
            if(firstBuffer) {
              firstBuffer = false;
              writer.println("    boolean _direct = BufferFactory.isDirect(" + getArgumentName(i) + ");");
            } else {
              writer.println("    if (_direct != BufferFactory.isDirect(" + getArgumentName(i) + "))");
              writer.println("      throw new " + getRuntimeExceptionType() +
                             "(\"Argument \\\"" + getArgumentName(i) +
                             "\\\" : Buffers passed to this method must all be either direct or indirect\");");
            }
          }
        } else if (javaType.isNIOBufferArray()) {
          // All buffers passed down in an array of NIO buffers must be direct
          String argName = getArgumentName(i);
          String arrayName = byteOffsetArrayArgName(i);
          writer.println("    int[] " + arrayName + " = new int[" + argName + ".length];");
          // Check direct buffer properties of all buffers within
          writer.println("    if (" + argName + " != null) {");
          writer.println("      for (int _ctr = 0; _ctr < " + argName + ".length; _ctr++) {");
          writer.println("        if (!BufferFactory.isDirect(" + argName + "[_ctr])) {");
          writer.println("          throw new " + getRuntimeExceptionType() + 
                         "(\"Element \" + _ctr + \" of argument \\\"" +
                         getArgumentName(i) + "\\\" was not a direct buffer\");");
          writer.println("        }");
          // get the Buffer Array offset values and save them into another array to send down to JNI
          writer.print  ("        " + arrayName + "[_ctr] = BufferFactory.getDirectBufferByteOffset(");
          writer.println(argName + "[_ctr]);");
          writer.println("      }");
          writer.println("    }");
        } else if (javaType.isPrimitiveArray()) {
          String argName = getArgumentName(i);
          String offsetArg = offsetArgName(i);
          writer.println("    if(" + argName + " != null && " + argName + ".length <= " + offsetArg + ")");
          writer.print  ("      throw new " + getRuntimeExceptionType()); 
          writer.println("(\"array offset argument \\\"" + offsetArg + "\\\" (\" + " + offsetArg +
                         " + \") equals or exceeds array length (\" + " + argName + ".length + \")\");");
        }
      }
    }
  }

  protected void emitCall(MethodBinding binding, PrintWriter writer, boolean direct) {
    writer.print(getImplMethodName(direct));
    writer.print("(");
    emitCallArguments(binding, writer, direct);
    writer.print(")");
  }


  protected void emitReturnVariableSetupAndCall(MethodBinding binding, PrintWriter writer) {
    writer.print("    ");
    JavaType returnType = binding.getJavaReturnType();
    boolean needsResultAssignment = false;

    if (!returnType.isVoid()) {
      if (returnType.isCompoundTypeWrapper() ||
          returnType.isNIOByteBuffer()) {
        writer.println("ByteBuffer _res;");
        needsResultAssignment = true;
      } else if (returnType.isArrayOfCompoundTypeWrappers()) {
        writer.println("ByteBuffer[] _res;");
        needsResultAssignment = true;
      } else if ((epilogue != null) && (epilogue.size() > 0)) {
        emitReturnType(writer);
        writer.println(" _res;");
        needsResultAssignment = true;
      }
    }

    if (binding.signatureCanUseIndirectNIO() && !directNIOOnly) {
      // Must generate two calls for this gated on whether the NIO
      // buffers coming in are all direct or indirect
      writer.println("if (_direct) {");
      writer.print  ("    ");
    }

    if (needsResultAssignment) {
      writer.print("  _res = ");
    } else {
      writer.print("  ");
      if (!returnType.isVoid()) {
        writer.print("return ");
      }
    }

    if (binding.signatureUsesJavaPrimitiveArrays() &&
        !binding.signatureCanUseIndirectNIO()) {
      // FIXME: what happens with a C function of the form
      //  void foo(int* arg0, void* arg1);
      // ?

      // Only one call being made in this body, going to indirect
      // buffer / array entry point
      emitCall(binding, writer, false);
      writer.print(";");
      writer.println();
    } else {
      emitCall(binding, writer, true);
      writer.print(";");
    }

    if (binding.signatureCanUseIndirectNIO() && !directNIOOnly) {
      // Must generate two calls for this gated on whether the NIO
      // buffers coming in are all direct or indirect
      writer.println();
      writer.println("    } else {");
      writer.print  ("    ");
      if (needsResultAssignment) {
        writer.print("    _res = ");
      } else {
        writer.print("  ");
        if (!returnType.isVoid()) {
          writer.print("return ");
        }
      }
      emitCall(binding, writer, false);
      writer.print(";");
      writer.println();
      writer.println("    }");
    } else {
      writer.println();
    }
    emitPrologueOrEpilogue(epilogue, writer);
    if (needsResultAssignment) {
      emitCallResultReturn(binding, writer);
    }
  }
    
  protected int emitCallArguments(MethodBinding binding, PrintWriter writer, boolean direct) {
    boolean needComma = false;
    int numArgsEmitted = 0;

    if (binding.hasContainingType()) {
      // Emit this pointer
      assert(binding.getContainingType().isCompoundTypeWrapper());
      writer.print("getBuffer()");
      needComma = true;
      ++numArgsEmitted;
    }
    for (int i = 0; i < binding.getNumArguments(); i++) {
      JavaType type = binding.getJavaArgumentType(i);
      if (type.isJNIEnv() || binding.isArgumentThisPointer(i)) {
        // Don't need to expose these at the Java level
        continue;
      }

      if (type.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 (needComma) {
        writer.print(", ");
      }

      if (type.isCompoundTypeWrapper()) {
        writer.print("((");
      }

      if (type.isNIOBuffer() && !direct) {
         writer.print("BufferFactory.getArray(" + getArgumentName(i) + ")");
      } else {
         writer.print(getArgumentName(i));
      }

      if (type.isCompoundTypeWrapper()) {
        writer.print(" == null) ? null : ");
        writer.print(getArgumentName(i));
        writer.print(".getBuffer())");
      }
      if (type.isNIOBuffer()) {
        if (direct) {
          writer.print(", BufferFactory.getDirectBufferByteOffset(" + getArgumentName(i) + ")");
        } else {
          writer.print(", BufferFactory.getIndirectBufferByteOffset(" + getArgumentName(i) + ")");
        }
      } else if (type.isNIOBufferArray()) {
        writer.print(", " + byteOffsetArrayArgName(i));
      }

      // Add Array offset parameter for primitive arrays
      if (type.isPrimitiveArray()) {
        if(type.isFloatArray()) {
          writer.print(", BufferFactory.SIZEOF_FLOAT * ");
        } else if(type.isDoubleArray()) {
          writer.print(", BufferFactory.SIZEOF_DOUBLE * ");
        } else if(type.isByteArray()) {
          writer.print(", ");
        } else if(type.isLongArray()) {
          writer.print(", BufferFactory.SIZEOF_LONG * ");
        } else if(type.isShortArray()) {
          writer.print(", BufferFactory.SIZEOF_SHORT * ");
        } else if(type.isIntArray()) {
          writer.print(", BufferFactory.SIZEOF_INT * ");
        } else {
          throw new RuntimeException("Unsupported type for calculating array offset argument for " +
                                     getArgumentName(i) +
                                     " -- error occurred while processing Java glue code for " + getName());
        }
        writer.print(offsetArgName(i));
      }

      needComma = true;
      ++numArgsEmitted;
    }
    return numArgsEmitted;
  }

  protected void emitCallResultReturn(MethodBinding binding, PrintWriter writer) {
    JavaType returnType = binding.getJavaReturnType();

    if (returnType.isCompoundTypeWrapper()) {
      String fmt = getReturnedArrayLengthExpression();
      writer.println("    if (_res == null) return null;");
      if (fmt == null) {
        writer.print("    return " + returnType.getName() + ".create(_res.order(ByteOrder.nativeOrder()))");
      } else {
        writer.println("    _res.order(ByteOrder.nativeOrder());");
        String expr = new MessageFormat(fmt).format(argumentNameArray());
        PointerType cReturnTypePointer = binding.getCReturnType().asPointer();
        CompoundType cReturnType = null;
        if (cReturnTypePointer != null) {
          cReturnType = cReturnTypePointer.getTargetType().asCompound();
        }
        if (cReturnType == null) {
          throw new RuntimeException("ReturnedArrayLength directive currently only supported for pointers to compound types " +
                                     "(error occurred while generating Java glue code for " + getName() + ")");
        }
        writer.println("    " + getReturnTypeString(false) + " _retarray = new " + getReturnTypeString(true) + "[" + expr + "];");
        writer.println("    for (int _count = 0; _count < " + expr + "; _count++) {");
        // Create temporary ByteBuffer slice
        // FIXME: probably need Type.getAlignedSize() for arrays of
        // compound types (rounding up to machine-dependent alignment)
        writer.println("      _res.position(_count * " + getReturnTypeString(true) + ".size());");
        writer.println("      _res.limit   ((1 + _count) * " + getReturnTypeString(true) + ".size());");
        writer.println("      ByteBuffer _tmp = _res.slice();");
        writer.println("      _tmp.order(ByteOrder.nativeOrder());");
        writer.println("      _res.position(0);");
        writer.println("      _res.limit(_res.capacity());");
        writer.println("      _retarray[_count] = " + getReturnTypeString(true) + ".create(_tmp);");
        writer.println("    }");
        writer.print  ("    return _retarray");
      }
      writer.println(";");
    } else if (returnType.isNIOBuffer()) {
      writer.println("    if (_res == null) return null;");
      writer.println("    return _res.order(ByteOrder.nativeOrder());");
    } else if (returnType.isArrayOfCompoundTypeWrappers()) {
      writer.println("    if (_res == null) return null;");
      writer.println("    " + getReturnTypeString(false) + " _retarray = new " + getReturnTypeString(true) + "[_res.length];");
      writer.println("    for (int _count = 0; _count < _res.length; _count++) {");
      writer.println("      _retarray[_count] = " + getReturnTypeString(true) + ".create(_res[_count]);");
      writer.println("    }");
      writer.println("    return _retarray;");
    } else {
      // Assume it's a primitive type or other type we don't have to
      // do any conversion on
      writer.println("    return _res;");
    }
  }

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

  public static String javaThisArgumentName() {
    return "jthis0";
  }

  protected String getCommentStartString() { return "/** "; }

  protected String getBaseIndentString() { return "  "; }

  protected String getReturnedArrayLengthExpression() {
    return returnedArrayLengthExpression;
  }

  /**
   * Class that emits a generic comment for JavaMethodBindingEmitters; the comment
   * includes the C signature of the native method that is being bound by the
   * emitter java method.
   */
  protected class DefaultCommentEmitter implements CommentEmitter {
    public void emit(FunctionEmitter emitter, PrintWriter writer) {
      emitBeginning(emitter, writer);
      emitBindingCSignature(((JavaMethodBindingEmitter)emitter).getBinding(), writer);
      emitEnding(emitter, writer);
    }
    protected void emitBeginning(FunctionEmitter emitter, PrintWriter writer) {
      writer.print("Entry point to C language function: <br> ");
    }
    protected void emitBindingCSignature(MethodBinding binding, PrintWriter writer) {      
      writer.print("<code> ");
      writer.print(binding.getCSymbol().toString(tagNativeBinding));
      writer.print(" </code> ");
    }
    protected void emitEnding(FunctionEmitter emitter, PrintWriter writer) {
      // If argument type is a named enum, then emit a comment detailing the
      // acceptable values of that enum.
      // If we're emitting a direct buffer variant only, then declare
      // that the NIO buffer arguments must be direct.
      MethodBinding binding = ((JavaMethodBindingEmitter)emitter).getBinding();
      for (int i = 0; i < binding.getNumArguments(); i++) {
        Type type = binding.getCArgumentType(i);
        JavaType javaType = binding.getJavaArgumentType(i);
        // don't emit param comments for anonymous enums, since we can't
        // distinguish between the values found within multiple anonymous
        // enums in the same C translation unit.
        if (type.isEnum() && type.getName() != HeaderParser.ANONYMOUS_ENUM_NAME) {
          EnumType enumType = (EnumType)type;
          writer.println();
          writer.print(emitter.getBaseIndentString()); 
          writer.print("    ");
          writer.print("@param ");
          writer.print(getArgumentName(i));
          writer.print(" valid values are: <code>");
          for (int j = 0; j < enumType.getNumEnumerates(); ++j) {
            if (j>0) writer.print(", ");
            writer.print(enumType.getEnumName(j));
          }
          writer.println("</code>");
        } else if (directNIOOnly && javaType.isNIOBuffer()) {
          writer.println();
          writer.print(emitter.getBaseIndentString()); 
          writer.print("    ");
          writer.print("@param ");
          writer.print(getArgumentName(i));
          writer.print(" a direct {@link " + javaType.getName() + "}");
        }
      }
    }
  }

  protected class InterfaceCommentEmitter
    extends JavaMethodBindingEmitter.DefaultCommentEmitter
  {
    protected void emitBeginning(FunctionEmitter emitter,
                                 PrintWriter writer) {
      writer.print("Interface to C language function: <br> ");
    }
  }
}